blob: fb5cf23d4a50a52f5895c1f2e2a9759844a2412e [file] [log] [blame]
// Copyright 2018 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#import "ios/chrome/browser/search_engines/search_engine_tab_helper.h"
#include "base/bind.h"
#include "base/strings/utf_string_conversions.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 "ios/chrome/browser/browser_state/chrome_browser_state.h"
#include "ios/chrome/browser/search_engines/template_url_fetcher_factory.h"
#include "ios/chrome/browser/search_engines/template_url_service_factory.h"
#include "ios/web/public/favicon_status.h"
#import "ios/web/public/navigation_item.h"
#import "ios/web/public/navigation_manager.h"
#import "ios/web/public/web_state/navigation_context.h"
#include "ui/base/page_transition_types.h"
#include "url/gurl.h"
#if !defined(__has_feature) || !__has_feature(objc_arc)
#error "This file requires ARC support."
#endif
namespace {
const char kCommandPrefix[] = "searchEngine";
const char kCommandOpenSearch[] = "searchEngine.openSearch";
const char kOpenSearchPageUrlKey[] = "pageUrl";
const char kOpenSearchOsddUrlKey[] = "osddUrl";
const char kCommandSearchableUrl[] = "searchEngine.searchableUrl";
const char kSearchableUrlUrlKey[] = "url";
// Returns true if the |item|'s transition type is FORM_SUBMIT.
bool IsFormSubmit(const web::NavigationItem* item) {
return ui::PageTransitionCoreTypeIs(item->GetTransitionType(),
ui::PAGE_TRANSITION_FORM_SUBMIT);
}
// Generates a keyword from |item|. This code is based on:
// https://cs.chromium.org/chromium/src/chrome/browser/ui/search_engines/search_engine_tab_helper.cc
base::string16 GenerateKeywordFromNavigationItem(
const web::NavigationItem* item) {
// Don't autogenerate keywords for pages that are the result of form
// submissions.
if (IsFormSubmit(item))
return base::string16();
// The code from Desktop will try NavigationEntry::GetUserTypedURL() first if
// available since that represents what the user typed to get here, and fall
// back on the regular URL if not.
// TODO(crbug.com/433824): Use GetUserTypedURL() once NavigationItem supports
// it.
GURL url = item->GetURL();
if (!url.is_valid()) {
return base::string16();
}
// Don't autogenerate keywords for referrers that
// a) are anything other than HTTP/HTTPS or
// b) have a path.
//
// To relax the path constraint, make sure to sanitize the path
// elements and update AutocompletePopup to look for keywords using the path.
// See http://b/issue?id=863583.
if (!url.SchemeIsHTTPOrHTTPS() || url.path().length() > 1) {
return base::string16();
}
return TemplateURL::GenerateKeyword(url);
}
}
SearchEngineTabHelper::~SearchEngineTabHelper() {}
SearchEngineTabHelper::SearchEngineTabHelper(web::WebState* web_state)
: web_state_(web_state) {
web_state->AddObserver(this);
web_state->AddScriptCommandCallback(
base::BindRepeating(&SearchEngineTabHelper::OnJsMessage,
base::Unretained(this)),
kCommandPrefix);
DCHECK(favicon::WebFaviconDriver::FromWebState(web_state));
favicon_driver_observer_.Add(
favicon::WebFaviconDriver::FromWebState(web_state));
}
void SearchEngineTabHelper::WebStateDestroyed(web::WebState* web_state) {
web_state->RemoveScriptCommandCallback(kCommandPrefix);
web_state->RemoveObserver(this);
web_state_ = nullptr;
favicon_driver_observer_.RemoveAll();
}
// When favicon is updated, notify TemplateURLService about the change.
void SearchEngineTabHelper::OnFaviconUpdated(
favicon::FaviconDriver* driver,
NotificationIconType notification_icon_type,
const GURL& icon_url,
bool icon_url_changed,
const gfx::Image& image) {
ios::ChromeBrowserState* browser_state =
ios::ChromeBrowserState::FromBrowserState(web_state_->GetBrowserState());
TemplateURLService* url_service =
ios::TemplateURLServiceFactory::GetForBrowserState(browser_state);
const GURL potential_search_url = driver->GetActiveURL();
if (url_service && url_service->loaded() && potential_search_url.is_valid())
url_service->UpdateProviderFavicons(potential_search_url, icon_url);
}
// When the page is loaded, checks if |searchable_url_| has a value generated
// from the <form> submission before the navigation. If true, and the navigation
// is successful, adds a TemplateURL by |searchable_url_|. |searchable_url_|
// will be set to empty in the end.
void SearchEngineTabHelper::DidFinishNavigation(
web::WebState* web_state,
web::NavigationContext* navigation_context) {
if (!searchable_url_.is_empty()) {
if (!navigation_context->GetError() &&
!navigation_context->IsSameDocument()) {
AddTemplateURLBySearchableURL(searchable_url_);
}
searchable_url_ = GURL();
}
}
bool SearchEngineTabHelper::OnJsMessage(const base::DictionaryValue& message,
const GURL& page_url,
bool has_user_gesture,
bool form_in_main_frame,
web::WebFrame* sender_frame) {
const base::Value* cmd = message.FindKey("command");
if (!cmd || !cmd->is_string()) {
return false;
}
std::string cmd_str = cmd->GetString();
if (cmd_str == kCommandOpenSearch) {
const base::Value* document_url = message.FindKey(kOpenSearchPageUrlKey);
if (!document_url || !document_url->is_string())
return false;
const base::Value* osdd_url = message.FindKey(kOpenSearchOsddUrlKey);
if (!osdd_url || !osdd_url->is_string())
return false;
AddTemplateURLByOSDD(GURL(document_url->GetString()),
GURL(osdd_url->GetString()));
} else if (cmd_str == kCommandSearchableUrl) {
const base::Value* url = message.FindKey(kSearchableUrlUrlKey);
if (!url || !url->is_string())
return false;
// Save |url| to |searchable_url_| when generated from <form> submission,
// and create the TemplateURL when the submission did lead to a successful
// navigation.
searchable_url_ = GURL(url->GetString());
} else {
return false;
}
return true;
}
// Creates a new TemplateURL by OSDD. The TemplateURL will be added to
// TemplateURLService by TemplateURLFecther. This code is based on:
// https://cs.chromium.org/chromium/src/chrome/browser/ui/search_engines/search_engine_tab_helper.cc
void SearchEngineTabHelper::AddTemplateURLByOSDD(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;
ios::ChromeBrowserState* browser_state =
ios::ChromeBrowserState::FromBrowserState(web_state_->GetBrowserState());
if ((page_url != web_state_->GetLastCommittedURL()) ||
(!ios::TemplateURLFetcherFactory::GetForBrowserState(browser_state)) ||
(browser_state->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.
const web::NavigationManager* manager = web_state_->GetNavigationManager();
const web::NavigationItem* item = nullptr;
for (int index = manager->GetLastCommittedItemIndex(); true; --index) {
if (index < 0)
return;
item = manager->GetItemAtIndex(index);
if (!IsFormSubmit(item))
break;
}
// Autogenerate a keyword for the autodetected case; in the other cases we'll
// generate a keyword later after fetching the OSDD.
base::string16 keyword = GenerateKeywordFromNavigationItem(item);
if (keyword.empty())
return;
// Download the OpenSearch description document. If this is successful, a
// new keyword will be created when done. For the last two args:
// 1. Used by newwork::ResourceRequest::render_frame_id. We don't use Blink
// so leave it to be the default value defined here:
// https://cs.chromium.org/chromium/src/services/network/public/cpp/resource_request.h?rcl=39c6fbea496641a6514e34c0ab689871d14e6d52&l=194;
// 2. Used by network::ResourceRequest::resource_type. It's a design defect:
// https://cs.chromium.org/chromium/src/services/network/public/cpp/resource_request.h?rcl=39c6fbea496641a6514e34c0ab689871d14e6d52&l=100
// Use the same value as the SearchEngineTabHelper for Desktop.
ios::TemplateURLFetcherFactory::GetForBrowserState(browser_state)
->ScheduleDownload(keyword, osdd_url, item->GetFavicon().url,
url::Origin::Create(web_state_->GetLastCommittedURL()),
browser_state->GetURLLoaderFactory(), MSG_ROUTING_NONE,
/* content::ResourceType::kSubResource */ 6);
}
// Creates a TemplateURL by |searchable_url| and adds it to TemplateURLService.
// This code is based on:
// https://cs.chromium.org/chromium/src/chrome/browser/ui/search_engines/search_engine_tab_helper.cc
void SearchEngineTabHelper::AddTemplateURLBySearchableURL(
const GURL& searchable_url) {
if (!searchable_url.is_valid()) {
return;
}
ios::ChromeBrowserState* browser_state =
ios::ChromeBrowserState::FromBrowserState(web_state_->GetBrowserState());
// Don't add TemplateURL under incognito mode.
if (browser_state->IsOffTheRecord())
return;
const web::NavigationManager* manager = web_state_->GetNavigationManager();
int last_index = manager->GetLastCommittedItemIndex();
// 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.
if (last_index <= 0)
return;
const web::NavigationItem* current_item = manager->GetItemAtIndex(last_index);
const web::NavigationItem* previous_item =
manager->GetItemAtIndex(last_index - 1);
base::string16 keyword(GenerateKeywordFromNavigationItem(previous_item));
if (keyword.empty())
return;
TemplateURLService* url_service =
ios::TemplateURLServiceFactory::GetForBrowserState(browser_state);
if (!url_service)
return;
if (!url_service->loaded()) {
url_service->Load();
return;
}
const TemplateURL* existing_url;
if (!url_service->CanAddAutogeneratedKeyword(keyword, searchable_url,
&existing_url)) {
return;
}
if (existing_url) {
if (existing_url->originating_url().is_valid()) {
// The existing keyword was generated from an OpenSearch description
// document, don't regenerate.
return;
}
url_service->Remove(existing_url);
}
TemplateURLData data;
data.SetShortName(keyword);
data.SetKeyword(keyword);
data.SetURL(searchable_url.spec());
// Try to get favicon url by following methods:
// 1. Get from FaviconStatus of previous NavigationItem;
// 2. Create by current NavigationItem's referrer if valid;
// 3. Create by previous NavigationItem's URL if valid;
if (previous_item->GetFavicon().url.is_valid()) {
data.favicon_url = previous_item->GetFavicon().url;
} else if (current_item->GetReferrer().url.is_valid()) {
data.favicon_url =
TemplateURL::GenerateFaviconURL(current_item->GetReferrer().url);
} else if (previous_item->GetURL().is_valid()) {
data.favicon_url = TemplateURL::GenerateFaviconURL(previous_item->GetURL());
}
data.safe_for_autoreplace = true;
url_service->Add(std::make_unique<TemplateURL>(data));
}
WEB_STATE_USER_DATA_KEY_IMPL(SearchEngineTabHelper)