| // Copyright 2022 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. |
| |
| #include "chrome/browser/prerender/prerender_manager.h" |
| |
| #include "base/metrics/histogram_functions.h" |
| #include "chrome/browser/content_settings/host_content_settings_map_factory.h" |
| #include "chrome/browser/prerender/prerender_utils.h" |
| #include "chrome/browser/profiles/profile.h" |
| #include "chrome/browser/search_engines/template_url_service_factory.h" |
| #include "chrome/common/pref_names.h" |
| #include "components/content_settings/core/browser/host_content_settings_map.h" |
| #include "components/content_settings/core/common/content_settings.h" |
| #include "components/omnibox/browser/base_search_provider.h" |
| #include "components/prefs/pref_service.h" |
| #include "components/search_engines/template_url_service.h" |
| #include "content/public/browser/navigation_entry.h" |
| #include "content/public/browser/page.h" |
| |
| namespace internal { |
| const char kHistogramPrerenderPredictionStatusDefaultSearchEngine[] = |
| "Prerender.Experimental.PredictionStatus.DefaultSearchEngine"; |
| } // namespace internal |
| |
| namespace { |
| |
| bool IsJavascriptDisabled(content::WebContents& web_contents, const GURL& url) { |
| Profile* profile = |
| Profile::FromBrowserContext(web_contents.GetBrowserContext()); |
| if (!profile) |
| return true; |
| |
| if (!profile->GetPrefs() || |
| !profile->GetPrefs()->GetBoolean(prefs::kWebKitJavascriptEnabled)) { |
| return true; |
| } |
| |
| HostContentSettingsMap* content_settings = |
| HostContentSettingsMapFactory::GetForProfile(profile); |
| return (!content_settings || content_settings->GetContentSetting( |
| url, url, ContentSettingsType::JAVASCRIPT) == |
| CONTENT_SETTING_BLOCK); |
| } |
| |
| TemplateURLService* GetTemplateURLServiceFromWebContents( |
| content::WebContents& web_contents) { |
| if (Profile* profile = |
| Profile::FromBrowserContext(web_contents.GetBrowserContext())) { |
| return TemplateURLServiceFactory::GetForProfile(profile); |
| } |
| return nullptr; |
| } |
| |
| std::u16string ExtractSearchTermsFromURL(content::WebContents& web_contents, |
| const GURL& url) { |
| TemplateURLService* template_url_service = |
| GetTemplateURLServiceFromWebContents(web_contents); |
| // Can be nullptr in unit tests. |
| if (!template_url_service) |
| return u""; |
| auto* default_search_provider = |
| template_url_service->GetDefaultSearchProvider(); |
| DCHECK(default_search_provider); |
| std::u16string matched_search_terms; |
| default_search_provider->ExtractSearchTermsFromURL( |
| url, template_url_service->search_terms_data(), &matched_search_terms); |
| return matched_search_terms; |
| } |
| |
| // Returns true when the two given URLs are considered as navigating to the same |
| // search term. |
| bool IsSearchDestinationMatch(const std::u16string& prerendered_search_terms, |
| content::WebContents& web_contents, |
| const GURL& navigation_url) { |
| DCHECK(!prerendered_search_terms.empty()); |
| std::u16string matched_search_terms = |
| ExtractSearchTermsFromURL(web_contents, navigation_url); |
| return matched_search_terms == prerendered_search_terms; |
| } |
| |
| // TODO(https://crbug.com/1291147): This is a workaround to stop the location |
| // bar from displaying the prefetch flag. This should be removed after we ensure |
| // the prerendered documents update the page by theirselves. |
| void UpdateVirtualUrlIfNecessary(content::WebContents& web_contents, |
| TemplateURLRef::SearchTermsArgs& search_args, |
| const GURL& prerendered_url) { |
| content::NavigationController& controller = web_contents.GetController(); |
| content::NavigationEntry* entry = controller.GetVisibleEntry(); |
| if (!entry) { |
| return; |
| } |
| TemplateURLService* template_url_service = |
| GetTemplateURLServiceFromWebContents(web_contents); |
| DCHECK(template_url_service); |
| |
| const GURL& displayed_url = entry->GetVirtualURL(); |
| if (displayed_url == prerendered_url) { |
| search_args.is_prefetch = false; |
| entry->SetVirtualURL( |
| GURL(template_url_service->GetDefaultSearchProvider() |
| ->url_ref() |
| .ReplaceSearchTerms(search_args, |
| template_url_service->search_terms_data(), |
| /*post_content=*/nullptr))); |
| } |
| } |
| |
| } // namespace |
| |
| PrerenderManager::~PrerenderManager() = default; |
| |
| void PrerenderManager::PrimaryPageChanged(content::Page& page) { |
| direct_url_input_prerender_handle_.reset(); |
| if (!search_prerender_handle_) { |
| return; |
| } |
| |
| // Record whether or not the prediction is correct when prerendering for |
| // search suggestion was started. The value `kNotStarted` is recorded in |
| // AutocompleteActionPredictor::OnOmniboxOpenedUrl(). |
| if (IsSearchDestinationMatch(prerendered_search_terms_args_.search_terms, |
| *web_contents(), |
| page.GetMainDocument().GetLastCommittedURL())) { |
| base::UmaHistogramEnumeration( |
| internal::kHistogramPrerenderPredictionStatusDefaultSearchEngine, |
| PrerenderPredictionStatus::kHitFinished); |
| } else { |
| base::UmaHistogramEnumeration( |
| internal::kHistogramPrerenderPredictionStatusDefaultSearchEngine, |
| PrerenderPredictionStatus::kUnused); |
| } |
| |
| // If `skip_template_url_service_for_testing_` is set for testing, no |
| // TemplateUrlService will be provided for updating the URL, so it needs not |
| // to update the URL. |
| if (prerender_utils::ShouldUpdateVirtualUrlForSearchManually() && |
| !skip_template_url_service_for_testing_) { |
| GURL search_prerendered_url = |
| search_prerender_handle_->GetInitialPrerenderingUrl(); |
| UpdateVirtualUrlIfNecessary(*web_contents(), prerendered_search_terms_args_, |
| search_prerendered_url); |
| } |
| |
| search_prerender_handle_.reset(); |
| prerendered_search_terms_args_ = TemplateURLRef::SearchTermsArgs(); |
| } |
| |
| base::WeakPtr<content::PrerenderHandle> |
| PrerenderManager::StartPrerenderDirectUrlInput(const GURL& prerendering_url) { |
| if (direct_url_input_prerender_handle_ && |
| direct_url_input_prerender_handle_->GetInitialPrerenderingUrl() == |
| prerendering_url) { |
| return nullptr; |
| } |
| direct_url_input_prerender_handle_.reset(); |
| direct_url_input_prerender_handle_ = web_contents()->StartPrerendering( |
| prerendering_url, content::PrerenderTriggerType::kEmbedder, |
| prerender_utils::kDirectUrlInputMetricSuffix, |
| ui::PageTransitionFromInt(ui::PAGE_TRANSITION_TYPED | |
| ui::PAGE_TRANSITION_FROM_ADDRESS_BAR)); |
| if (direct_url_input_prerender_handle_) { |
| return direct_url_input_prerender_handle_->GetWeakPtr(); |
| } |
| return nullptr; |
| } |
| |
| void PrerenderManager::CancelPrerenderDirectUrlInput() { |
| direct_url_input_prerender_handle_.reset(); |
| } |
| |
| base::WeakPtr<content::PrerenderHandle> |
| PrerenderManager::StartPrerenderAutocompleteMatch( |
| const AutocompleteMatch& match) { |
| DCHECK(AutocompleteMatch::IsSearchType(match.type)); |
| |
| // Since search pages require Javascirpt to perform the basic prerender |
| // loading logic, do not prerender a search result if Javascript is disabled. |
| if (IsJavascriptDisabled(*web_contents(), match.destination_url)) { |
| return nullptr; |
| } |
| |
| TemplateURLRef::SearchTermsArgs& search_terms_args = |
| *(match.search_terms_args); |
| const std::u16string& search_terms = search_terms_args.search_terms; |
| |
| // Do not re-prerender the same search result. |
| if (search_prerender_handle_) { |
| if (prerendered_search_terms_args_.search_terms == search_terms) |
| return search_prerender_handle_->GetWeakPtr(); |
| |
| base::UmaHistogramEnumeration( |
| internal::kHistogramPrerenderPredictionStatusDefaultSearchEngine, |
| PrerenderPredictionStatus::kCancelled); |
| search_prerender_handle_.reset(); |
| } |
| |
| // Make a copy. Use a copy instead of a reference, since we may modify it, and |
| // we do not want to modify the original one which might be used to activate a |
| // page. |
| prerendered_search_terms_args_ = search_terms_args; |
| |
| // When prerendered_search_terms_args_ is reset, search_prerender_handle_ |
| // should be reset as well, which leads to the destruction of the instance |
| // that owns this callback. So the content stored in |
| // prerender_search_terms_arg_ outlives the callback, so it is safe to use |
| // std::ref. |
| // web_contents() owns the instance that stores this callback, so it is safe |
| // to call std::ref. |
| base::RepeatingCallback<bool(const GURL&)> url_match_predicate = |
| base::BindRepeating(&IsSearchDestinationMatch, |
| std::ref(prerendered_search_terms_args_.search_terms), |
| std::ref(*web_contents())); |
| |
| GURL prerender_url = match.destination_url; |
| |
| // Skip changing the prerender URL in tests as they may not have Profile or |
| // TemplateURLServiceFactory. In that case, the callers of |
| // StartPrerenderAutocompleteMatch() should ensure the prerender URL is valid |
| // instead. |
| if (!skip_template_url_service_for_testing_) { |
| TemplateURLService* template_url_service = |
| GetTemplateURLServiceFromWebContents(*web_contents()); |
| if (!template_url_service) |
| return nullptr; |
| |
| prerendered_search_terms_args_.is_prefetch = true; |
| prerender_url = |
| GURL(template_url_service->GetDefaultSearchProvider() |
| ->url_ref() |
| .ReplaceSearchTerms(prerendered_search_terms_args_, |
| template_url_service->search_terms_data(), |
| /*post_content=*/nullptr)); |
| } |
| |
| search_prerender_handle_ = web_contents()->StartPrerendering( |
| prerender_url, content::PrerenderTriggerType::kEmbedder, |
| prerender_utils::kDefaultSearchEngineMetricSuffix, |
| ui::PageTransitionFromInt(ui::PAGE_TRANSITION_GENERATED | |
| ui::PAGE_TRANSITION_FROM_ADDRESS_BAR), |
| std::move(url_match_predicate)); |
| if (search_prerender_handle_) { |
| return search_prerender_handle_->GetWeakPtr(); |
| } |
| return nullptr; |
| } |
| |
| PrerenderManager::PrerenderManager(content::WebContents* web_contents) |
| : content::WebContentsObserver(web_contents), |
| content::WebContentsUserData<PrerenderManager>(*web_contents) {} |
| |
| WEB_CONTENTS_USER_DATA_KEY_IMPL(PrerenderManager); |