910e62b5创建于 1月15日历史提交
// Copyright 2012 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#include "chrome/browser/ui/search_engines/search_engine_tab_helper.h"

#include <memory>

#include "base/metrics/histogram_macros.h"
#include "chrome/browser/favicon/favicon_utils.h"
#include "chrome/browser/profiles/profile.h"
#include "chrome/browser/search_engines/template_url_fetcher_factory.h"
#include "chrome/browser/search_engines/template_url_service_factory.h"
#include "chrome/browser/ui/search_engines/edit_search_engine_controller.h"
#include "chrome/common/url_constants.h"
#include "components/search_engines/template_url.h"
#include "components/search_engines/template_url_fetcher.h"
#include "components/search_engines/template_url_service.h"
#include "content/public/browser/favicon_status.h"
#include "content/public/browser/navigation_controller.h"
#include "content/public/browser/navigation_entry.h"
#include "content/public/browser/navigation_handle.h"
#include "content/public/browser/render_frame_host.h"
#include "content/public/browser/render_process_host.h"
#include "content/public/browser/web_contents.h"
#include "mojo/public/cpp/bindings/remote.h"
#include "services/network/public/mojom/url_loader_factory.mojom.h"
#include "third_party/blink/public/mojom/loader/referrer.mojom.h"

using content::NavigationController;
using content::NavigationEntry;
using content::WebContents;

namespace {

// Returns true if the entry's transition type is FORM_SUBMIT.
bool IsFormSubmit(NavigationEntry* entry) {
  return ui::PageTransitionCoreTypeIs(entry->GetTransitionType(),
                                      ui::PAGE_TRANSITION_FORM_SUBMIT);
}

}  // namespace

// static
void SearchEngineTabHelper::BindOpenSearchDescriptionDocumentHandler(
    content::RenderFrameHost* rfh,
    mojo::PendingReceiver<chrome::mojom::OpenSearchDescriptionDocumentHandler>
        receiver) {
  // Bind only for outermost main frames.
  if (rfh->GetParentOrOuterDocument()) {
    return;
  }

  auto* web_contents = content::WebContents::FromRenderFrameHost(rfh);
  if (!web_contents) {
    return;
  }
  auto* tab_helper = SearchEngineTabHelper::FromWebContents(web_contents);
  if (!tab_helper) {
    return;
  }
  tab_helper->osdd_handler_receivers_.Add(tab_helper, std::move(receiver));
}

SearchEngineTabHelper::~SearchEngineTabHelper() = default;

void SearchEngineTabHelper::DidFinishNavigation(
    content::NavigationHandle* handle) {
  GenerateKeywordIfNecessary(handle);
}

void SearchEngineTabHelper::WebContentsDestroyed() {
  favicon_driver_observation_.Reset();
}

std::u16string SearchEngineTabHelper::GenerateKeywordFromNavigationEntry(
    NavigationEntry* entry) {
  // Don't autogenerate keywords for pages that are the result of form
  // submissions.
  if (IsFormSubmit(entry)) {
    return std::u16string();
  }

  // We want to use the user typed URL if available since that represents what
  // the user typed to get here, and fall back on the regular URL if not.
  GURL url = entry->GetUserTypedURL();
  if (!url.is_valid()) {
    url = entry->GetURL();
    if (!url.is_valid()) {
      return std::u16string();
    }
  }

  // Don't autogenerate keywords for referrers that
  // a) are anything other than HTTP/HTTPS or
  // b) have a path.
  //
  // If we relax the path constraint, we need to be sure to sanitize the path
  // elements and update AutocompletePopup to look for keywords using the path.
  // See http://b/issue?id=863583.
  if (!(url.SchemeIs(url::kHttpScheme) || url.SchemeIs(url::kHttpsScheme)) ||
      (url.GetPath().length() > 1)) {
    return std::u16string();
  }

  return TemplateURL::GenerateKeyword(url);
}

SearchEngineTabHelper::SearchEngineTabHelper(WebContents* web_contents)
    : content::WebContentsObserver(web_contents),
      content::WebContentsUserData<SearchEngineTabHelper>(*web_contents) {
  DCHECK(web_contents);

  favicon::CreateContentFaviconDriverForWebContents(web_contents);
  favicon_driver_observation_.Observe(
      favicon::ContentFaviconDriver::FromWebContents(web_contents));
}

void SearchEngineTabHelper::PageHasOpenSearchDescriptionDocument(
    const GURL& page_url,
    const GURL& osdd_url) {
  // Checks to see if we should generate a keyword based on the OSDD, and if
  // necessary uses TemplateURLFetcher to download the OSDD and create a
  // keyword.

  // Make sure that the page is the current page and other basic checks.
  // When |page_url| has file: scheme, this method doesn't work because of
  // http://b/issue?id=863583. For that reason, this doesn't check and allow
  // urls referring to osdd urls with same schemes.
  if (!osdd_url.is_valid() || !osdd_url.SchemeIsHTTPOrHTTPS()) {
    return;
  }

  Profile* profile =
      Profile::FromBrowserContext(web_contents()->GetBrowserContext());
  if (page_url != web_contents()->GetLastCommittedURL() ||
      !TemplateURLFetcherFactory::GetForProfile(profile) ||
      profile->IsOffTheRecord()) {
    return;
  }

  // If the current page is a form submit, find the last page that was not a
  // form submit and use its url to generate the keyword from.
  NavigationController& controller = web_contents()->GetController();
  NavigationEntry* entry = controller.GetLastCommittedEntry();
  for (int index = controller.GetLastCommittedEntryIndex();
       (index > 0) && IsFormSubmit(entry);
       entry = controller.GetEntryAtIndex(index)) {
    --index;
  }
  if (!entry || IsFormSubmit(entry)) {
    return;
  }

  // Autogenerate a keyword for the autodetected case; in the other cases we'll
  // generate a keyword later after fetching the OSDD.
  std::u16string keyword = GenerateKeywordFromNavigationEntry(entry);
  if (keyword.empty()) {
    return;
  }

  auto* frame = web_contents()->GetPrimaryMainFrame();
  mojo::Remote<network::mojom::URLLoaderFactory> url_loader_factory;
  frame->CreateNetworkServiceDefaultFactory(
      url_loader_factory.BindNewPipeAndPassReceiver());

  // Download the OpenSearch description document. If this is successful, a
  // new keyword will be created when done.
  TemplateURLFetcherFactory::GetForProfile(profile)->ScheduleDownload(
      keyword, osdd_url, entry->GetFavicon().url,
      frame->GetLastCommittedOrigin(), url_loader_factory.get(),
      frame->GetRoutingID(),
      content::GlobalRequestID::MakeBrowserInitiated().request_id);
}

void SearchEngineTabHelper::OnFaviconUpdated(
    favicon::FaviconDriver* driver,
    NotificationIconType notification_icon_type,
    const GURL& icon_url,
    bool icon_url_changed,
    const gfx::Image& image) {
  Profile* profile =
      Profile::FromBrowserContext(web_contents()->GetBrowserContext());
  TemplateURLService* url_service =
      TemplateURLServiceFactory::GetForProfile(profile);
  if (url_service && url_service->loaded()) {
    url_service->UpdateProviderFavicons(driver->GetActiveURL(), icon_url);
  }
}

void SearchEngineTabHelper::GenerateKeywordIfNecessary(
    content::NavigationHandle* handle) {
  if (!handle->IsInPrimaryMainFrame() ||
      !handle->GetSearchableFormURL().is_valid()) {
    return;
  }

  Profile* profile =
      Profile::FromBrowserContext(web_contents()->GetBrowserContext());
  if (profile->IsOffTheRecord()) {
    return;
  }

  NavigationController& controller = web_contents()->GetController();
  int last_index = controller.GetLastCommittedEntryIndex();
  // When there was no previous page, the last index will be 0. This is
  // normally due to a form submit that opened in a new tab.
  // TODO(brettw) bug 916126: we should support keywords when form submits
  //              happen in new tabs.
  if (last_index <= 0) {
    return;
  }

  std::u16string keyword(GenerateKeywordFromNavigationEntry(
      controller.GetEntryAtIndex(last_index - 1)));
  if (keyword.empty()) {
    return;
  }

  TemplateURLService* url_service =
      TemplateURLServiceFactory::GetForProfile(profile);
  if (!url_service) {
    return;
  }

  if (!url_service->loaded()) {
    url_service->Load();
    return;
  }

  GURL url = handle->GetSearchableFormURL();
  if (!url_service->CanAddAutogeneratedKeyword(keyword, url)) {
    return;
  }

  TemplateURLData data;
  data.SetShortName(keyword);
  data.SetKeyword(keyword);
  data.SetURL(url.spec());
  DCHECK(controller.GetLastCommittedEntry());
  const GURL& current_favicon =
      controller.GetLastCommittedEntry()->GetFavicon().url;
  // If the favicon url isn't valid, it means there really isn't a favicon, or
  // the favicon url wasn't obtained before the load started. This assumes the
  // latter.
  // TODO(sky): Need a way to set the favicon that doesn't involve generating
  // its url.
  if (current_favicon.is_valid()) {
    data.favicon_url = current_favicon;
  } else if (handle->GetReferrer().url.is_valid()) {
    data.favicon_url =
        TemplateURL::GenerateFaviconURL(handle->GetReferrer().url);
  }
  data.safe_for_autoreplace = true;
  data.input_encodings.push_back(handle->GetSearchableFormEncoding());

  // This Add() call may displace the previously auto-generated TemplateURL.
  // But it will never displace the Default Search Engine, nor will it displace
  // any OpenSearch document derived engines, which outrank this one.
  url_service->Add(std::make_unique<TemplateURL>(data));
}

WEB_CONTENTS_USER_DATA_KEY_IMPL(SearchEngineTabHelper);