| // Copyright 2018 The Chromium Authors |
| // 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" |
| |
| #import "base/functional/bind.h" |
| #import "base/strings/utf_string_conversions.h" |
| #import "components/search_engines/template_url.h" |
| #import "components/search_engines/template_url_fetcher.h" |
| #import "components/search_engines/template_url_service.h" |
| #import "ios/chrome/browser/search_engines/template_url_fetcher_factory.h" |
| #import "ios/chrome/browser/search_engines/template_url_service_factory.h" |
| #import "ios/chrome/browser/shared/model/browser_state/chrome_browser_state.h" |
| #import "ios/web/public/favicon/favicon_status.h" |
| #import "ios/web/public/navigation/navigation_context.h" |
| #import "ios/web/public/navigation/navigation_item.h" |
| #import "ios/web/public/navigation/navigation_manager.h" |
| #import "ui/base/page_transition_types.h" |
| #import "url/gurl.h" |
| |
| #if !defined(__has_feature) || !__has_feature(objc_arc) |
| #error "This file requires ARC support." |
| #endif |
| |
| namespace { |
| |
| // 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 |
| std::u16string GenerateKeywordFromNavigationItem( |
| const web::NavigationItem* item) { |
| // Don't autogenerate keywords for pages that are the result of form |
| // submissions. |
| if (IsFormSubmit(item)) |
| return std::u16string(); |
| |
| // 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 std::u16string(); |
| } |
| |
| // 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 std::u16string(); |
| } |
| |
| return TemplateURL::GenerateKeyword(url); |
| } |
| } |
| |
| SearchEngineTabHelper::~SearchEngineTabHelper() {} |
| |
| SearchEngineTabHelper::SearchEngineTabHelper(web::WebState* web_state) |
| : web_state_(web_state) { |
| web_state->AddObserver(this); |
| DCHECK(favicon::WebFaviconDriver::FromWebState(web_state)); |
| favicon_driver_observation_.Observe( |
| favicon::WebFaviconDriver::FromWebState(web_state)); |
| } |
| |
| void SearchEngineTabHelper::WebStateDestroyed(web::WebState* web_state) { |
| web_state->RemoveObserver(this); |
| web_state_ = nullptr; |
| favicon_driver_observation_.Reset(); |
| } |
| |
| // 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) { |
| ChromeBrowserState* browser_state = |
| 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(); |
| } |
| } |
| |
| void SearchEngineTabHelper::SetSearchableUrl(GURL searchable_url) { |
| searchable_url_ = searchable_url; |
| } |
| |
| // 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; |
| |
| ChromeBrowserState* browser_state = |
| 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. |
| std::u16string 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 `render_frame_id` arg, it's used |
| // by network::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; |
| ios::TemplateURLFetcherFactory::GetForBrowserState(browser_state) |
| ->ScheduleDownload(keyword, osdd_url, item->GetFaviconStatus().url, |
| url::Origin::Create(web_state_->GetLastCommittedURL()), |
| browser_state->GetURLLoaderFactory(), |
| /* render_frame_id */ MSG_ROUTING_NONE, |
| /* request_id */ 0); |
| } |
| |
| // 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; |
| } |
| |
| ChromeBrowserState* browser_state = |
| 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); |
| |
| std::u16string 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; |
| } |
| |
| if (!url_service->CanAddAutogeneratedKeyword(keyword, searchable_url)) { |
| return; |
| } |
| |
| 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; |
| const web::FaviconStatus& previous_item_favicon_status = |
| previous_item->GetFaviconStatus(); |
| if (previous_item_favicon_status.url.is_valid()) { |
| data.favicon_url = previous_item_favicon_status.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; |
| |
| // 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_STATE_USER_DATA_KEY_IMPL(SearchEngineTabHelper) |