| // Copyright 2022 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/preloading/prerender/prerender_manager.h" |
| |
| #include <memory> |
| #include <string> |
| |
| #include "base/functional/bind.h" |
| #include "base/location.h" |
| #include "base/metrics/histogram_functions.h" |
| #include "base/time/time.h" |
| #include "base/timer/timer.h" |
| #include "chrome/browser/content_settings/host_content_settings_map_factory.h" |
| #include "chrome/browser/preloading/chrome_preloading.h" |
| #include "chrome/browser/preloading/prefetch/search_prefetch/field_trial_settings.h" |
| #include "chrome/browser/preloading/prefetch/search_prefetch/search_prefetch_service.h" |
| #include "chrome/browser/preloading/prefetch/search_prefetch/search_prefetch_service_factory.h" |
| #include "chrome/browser/preloading/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/autocomplete_match.h" |
| #include "components/omnibox/browser/base_search_provider.h" |
| #include "components/prefs/pref_service.h" |
| #include "components/search_engines/template_url.h" |
| #include "components/search_engines/template_url_service.h" |
| #include "content/public/browser/navigation_entry.h" |
| #include "content/public/browser/navigation_handle.h" |
| #include "content/public/browser/preloading.h" |
| #include "content/public/browser/preloading_data.h" |
| #include "content/public/browser/prerender_handle.h" |
| #include "content/public/browser/replaced_navigation_entry_data.h" |
| #include "content/public/browser/web_contents.h" |
| #include "content/public/browser/web_contents_observer.h" |
| #include "content/public/browser/web_contents_user_data.h" |
| #include "content/public/common/content_features.h" |
| #include "net/base/url_util.h" |
| #include "third_party/blink/public/common/features.h" |
| #include "url/gurl.h" |
| |
| namespace internal { |
| const char kHistogramPrerenderPredictionStatusDefaultSearchEngine[] = |
| "Prerender.Experimental.PredictionStatus.DefaultSearchEngine"; |
| const char kHistogramPrerenderPredictionStatusDirectUrlInput[] = |
| "Prerender.Experimental.PredictionStatus.DirectUrlInput"; |
| } // namespace internal |
| |
| namespace { |
| |
| using content::PreloadingTriggeringOutcome; |
| |
| 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); |
| } |
| |
| // Prerendered pages are considered stale after a fixed duration. |
| // TODO(https://crbug.com/1295170): Use the search prefetch setting for now. The |
| // timedelta should be calculated by SearchPrefetchService after search |
| // prerender reuses the prefetched responses. |
| base::TimeDelta GetSearchPrerenderExpiryDuration() { |
| return SearchPrefetchCachingLimit(); |
| } |
| |
| // TODO(https://crbug.com/1295170): This is a workaround. Remove this method |
| // after the unification work is done. |
| GURL RemoveParameterFromUrl(const GURL& url) { |
| std::string query = url.query(); |
| base::ReplaceFirstSubstringAfterOffset(&query, /*start_offset=*/0, "&pf=cs", |
| ""); |
| GURL::Replacements replacements; |
| replacements.SetQueryStr(query); |
| return url.ReplaceComponents(replacements); |
| } |
| |
| void MarkPreloadingAttemptAsDuplicate( |
| content::PreloadingAttempt* preloading_attempt) { |
| // In addition to the globally-controlled preloading config, check for the |
| // feature-specific holdback. We disable the feature if the user is in either |
| // of those holdbacks. |
| if (base::FeatureList::IsEnabled(features::kPrerender2Holdback)) { |
| preloading_attempt->SetHoldbackStatus( |
| content::PreloadingHoldbackStatus::kHoldback); |
| } |
| if (!preloading_attempt->ShouldHoldback()) { |
| preloading_attempt->SetTriggeringOutcome( |
| PreloadingTriggeringOutcome::kDuplicate); |
| } |
| } |
| |
| content::PreloadingFailureReason ToPreloadingFailureReason( |
| PrerenderPredictionStatus status) { |
| return static_cast<content::PreloadingFailureReason>( |
| static_cast<int>(status) + |
| static_cast<int>(content::PreloadingFailureReason:: |
| kPreloadingFailureReasonContentEnd)); |
| } |
| |
| } // namespace |
| |
| PrerenderManager::~PrerenderManager() = default; |
| |
| class PrerenderManager::SearchPrerenderTask { |
| public: |
| SearchPrerenderTask( |
| const GURL& canonical_search_url, |
| std::unique_ptr<content::PrerenderHandle> search_prerender_handle) |
| : search_prerender_handle_(std::move(search_prerender_handle)), |
| prerendered_canonical_search_url_(canonical_search_url) { |
| expiry_timer_.Start(FROM_HERE, GetSearchPrerenderExpiryDuration(), |
| base::BindOnce(&SearchPrerenderTask::OnTimerTriggered, |
| base::Unretained(this))); |
| } |
| |
| ~SearchPrerenderTask() { |
| // Record whether or not the prediction is correct when prerendering for |
| // search suggestion was started. The value `kNotStarted` is recorded in |
| // AutocompleteControllerAndroid::OnSuggestionSelected() or |
| // ChromeOmniboxClient::OnURLOpenedFromOmnibox() if there is no started |
| // prerender. |
| CHECK_NE(prediction_status_, PrerenderPredictionStatus::kNotStarted); |
| SetFailureReason(prediction_status_); |
| base::UmaHistogramEnumeration( |
| internal::kHistogramPrerenderPredictionStatusDefaultSearchEngine, |
| prediction_status_); |
| } |
| |
| void SetFailureReason(PrerenderPredictionStatus status) { |
| if (!search_prerender_handle_) |
| return; |
| switch (status) { |
| case PrerenderPredictionStatus::kNotStarted: |
| case PrerenderPredictionStatus::kCancelled: |
| search_prerender_handle_->SetPreloadingAttemptFailureReason( |
| ToPreloadingFailureReason(status)); |
| return; |
| case PrerenderPredictionStatus::kUnused: |
| case PrerenderPredictionStatus::kHitFinished: |
| // Only set failure reasons for failing cases. kUnused and kHitFinished |
| // are not considered prerender failures. |
| return; |
| } |
| } |
| |
| // Not copyable or movable. |
| SearchPrerenderTask(const SearchPrerenderTask&) = delete; |
| SearchPrerenderTask& operator=(const SearchPrerenderTask&) = delete; |
| |
| const GURL& prerendered_canonical_search_url() const { |
| return prerendered_canonical_search_url_; |
| } |
| |
| void OnActivated(content::WebContents& web_contents) const { |
| if (!search_prerender_handle_) { |
| return; |
| } |
| content::NavigationController& controller = web_contents.GetController(); |
| content::NavigationEntry* entry = controller.GetVisibleEntry(); |
| if (!entry) { |
| return; |
| } |
| SearchPrefetchService* search_prefetch_service = |
| SearchPrefetchServiceFactory::GetForProfile( |
| Profile::FromBrowserContext(web_contents.GetBrowserContext())); |
| if (!search_prefetch_service) { |
| return; |
| } |
| |
| if (prerender_utils::SearchPrefetchUpgradeToPrerenderIsEnabled()) { |
| search_prefetch_service->OnPrerenderedRequestUsed( |
| prerendered_canonical_search_url_, |
| web_contents.GetLastCommittedURL()); |
| return; |
| } |
| |
| // TODO(https://crbug.com/1295170): This rule is hard coded according to |
| // TemplateUrl, which is not good, and can be removed after the unification |
| // work is done. |
| const std::string prerender_key = "pf"; |
| |
| // Maybe the prerendering page has updated its URL. In this case, obtain the |
| // original URL with the ReplacedNavigationEntryData. The reason why we do |
| // not compare the URL with GetInitialPrerenderingUrl here is that the URL |
| // can be changed by other mechanisms, such as safe search. |
| if (const absl::optional<content::ReplacedNavigationEntryData>& |
| replaced_data = entry->GetReplacedEntryData()) { |
| const GURL& maybe_prerendering_url = replaced_data->first_committed_url; |
| std::string out_value; |
| bool key_exists = net::GetValueForKeyInQuery(maybe_prerendering_url, |
| prerender_key, &out_value); |
| if (key_exists && |
| !net::GetValueForKeyInQuery(web_contents.GetLastCommittedURL(), |
| prerender_key, &out_value)) { |
| search_prefetch_service->AddCacheEntryForPrerender( |
| web_contents.GetLastCommittedURL(), |
| replaced_data->first_committed_url); |
| return; |
| } |
| } |
| |
| const GURL& activated_url = web_contents.GetLastCommittedURL(); |
| std::string out_value; |
| bool key_exists = |
| net::GetValueForKeyInQuery(activated_url, prerender_key, &out_value); |
| if (key_exists) { |
| GURL new_url = RemoveParameterFromUrl(activated_url); |
| search_prefetch_service->AddCacheEntryForPrerender(new_url, |
| activated_url); |
| } |
| } |
| |
| void RecordTimestampOnDidStartNavigation( |
| base::TimeTicks start_navigation_timestamp) { |
| lastest_start_navigation_event_timestamp_ = start_navigation_timestamp; |
| } |
| |
| void RecordLifeTimeMetric() { |
| // Record the lifetime of this prerender. |
| // |<------------GetSearchPrerenderExpiryDuration()------------>| |
| // @ PrerenderHintReceived @ Activation/NavigationStarted @ Expire |
| // |<---------delta---------->| |
| // where: |
| // expiry_timer_.desired_run_time() = Timestamp@Expire. |
| // lastest_start_navigation_event_timestamp_ = |
| // Timestamp@Activation/NavigationStarted |
| base::TimeDelta delta = |
| GetSearchPrerenderExpiryDuration() - |
| std::max(base::TimeDelta(), |
| expiry_timer_.desired_run_time() - |
| lastest_start_navigation_event_timestamp_); |
| // The upper-bound of this histogram is decided by the default duration of |
| // the search prefetch setting. See `prefetch_caching_limit_ms`. |
| // TODO(https://crbug.com/1278634): Reconsider the duration after |
| // PrerenderManager supports to re-prerender the search results. |
| base::UmaHistogramCustomTimes( |
| "Prerender.Experimental.Search." |
| "FirstCorrectPrerenderHintReceivedToRealSearchNavigationStartedDuratio" |
| "n", |
| delta, base::Milliseconds(1), base::Seconds(60), /*buckets=*/50); |
| } |
| |
| void set_prediction_status(PrerenderPredictionStatus prediction_status) { |
| // If the final status was set, do nothing because the status has been |
| // finalized. |
| if (prediction_status_ != PrerenderPredictionStatus::kUnused) |
| return; |
| CHECK_NE(prediction_status, PrerenderPredictionStatus::kUnused); |
| prediction_status_ = prediction_status; |
| } |
| |
| private: |
| // Called by OneShotTimer. Will cancel the ongoing prerender to ensure the |
| // content displayed to users is up-to-date. |
| void OnTimerTriggered() { |
| SetFailureReason(prediction_status_); |
| search_prerender_handle_.reset(); |
| } |
| |
| std::unique_ptr<content::PrerenderHandle> search_prerender_handle_; |
| |
| // Recorded on OnDidStartNavigation and used on PrimaryPageChanged. Only the |
| // latest recorded TimeTicks is meaningful. See the comment in |
| // PrerenderManager::DidStartNavigation for more information. |
| base::TimeTicks lastest_start_navigation_event_timestamp_; |
| |
| // Stops the ongoing prerender when the prerendered result is out-of-date. |
| base::OneShotTimer expiry_timer_; |
| |
| // A task is associated with a prediction, this tracks the correctness of the |
| // prediction. |
| PrerenderPredictionStatus prediction_status_ = |
| PrerenderPredictionStatus::kUnused; |
| |
| // Stores the search term that `search_prerender_handle_` is prerendering. |
| const GURL prerendered_canonical_search_url_; |
| }; |
| |
| void PrerenderManager::DidStartNavigation( |
| content::NavigationHandle* navigation_handle) { |
| // Only watching the changes to primary main frame. |
| if (!navigation_handle->IsInPrimaryMainFrame() || |
| navigation_handle->IsSameDocument()) |
| return; |
| |
| // Ideally it should record the lifetime metric directly here if the search |
| // terms match. However, the DidStartNavigation method can be called in other |
| // cases(for example, the primary page has an ongoing navigation), and we only |
| // care about the latest DidStartNavigation event right before |
| // PrimaryPageChanged, and record metric if the search terms match(Note: we do |
| // not only record the metric on the successful prerender activation, but also |
| // on the failed cases, as long as the predictions are correct, since this |
| // metric is used to understand the search prerender prediction rather than |
| // the prerender operation). Besides this, it would waste the resources if we |
| // parsed the URL for many times. i.e., in this method and in |
| // PrimaryPageChanged. So it only records the timestamp, and |
| // PrimaryPageChanged will record the metric later if needed. |
| // TODO(https://crbug.com/1278634): Record the metrics at the moment |
| // when a suggestion is selected. |
| if (search_prerender_task_) { |
| search_prerender_task_->RecordTimestampOnDidStartNavigation( |
| navigation_handle->NavigationStart()); |
| } |
| } |
| |
| void PrerenderManager::DidFinishNavigation( |
| content::NavigationHandle* navigation_handle) { |
| if (!navigation_handle->HasCommitted() || |
| !navigation_handle->IsInPrimaryMainFrame() || |
| navigation_handle->IsSameDocument()) { |
| return; |
| } |
| |
| // This is a primary page change. Reset the prerender handles. |
| // PrerenderManager does not listen to the PrimaryPageChanged event, because |
| // it needs the navigation_handle to figure out whether the PrimaryPageChanged |
| // event is caused by prerender activation. |
| ResetPrerenderHandlesOnPrimaryPageChanged(navigation_handle); |
| } |
| |
| base::WeakPtr<content::PrerenderHandle> |
| PrerenderManager::StartPrerenderBookmark( |
| const GURL& prerendering_url, |
| content::PreloadingPredictor predictor) { |
| // Helpers to create content::PreloadingAttempt. |
| auto* preloading_data = |
| content::PreloadingData::GetOrCreateForWebContents(web_contents()); |
| content::PreloadingURLMatchCallback same_url_matcher = |
| content::PreloadingData::GetSameURLMatcher(prerendering_url); |
| |
| // Create new PreloadingAttempt and pass all the values corresponding to |
| // this prerendering attempt for Prerender. |
| content::PreloadingAttempt* preloading_attempt = |
| preloading_data->AddPreloadingAttempt(predictor, |
| content::PreloadingType::kPrerender, |
| std::move(same_url_matcher)); |
| |
| if (bookmark_prerender_handle_) { |
| if (bookmark_prerender_handle_->GetInitialPrerenderingUrl() == |
| prerendering_url) { |
| // In case a prerender is already present for the URL, prerendering is |
| // eligible but mark triggering outcome as a duplicate. |
| preloading_attempt->SetEligibility( |
| content::PreloadingEligibility::kEligible); |
| |
| MarkPreloadingAttemptAsDuplicate(preloading_attempt); |
| return bookmark_prerender_handle_->GetWeakPtr(); |
| } |
| bookmark_prerender_handle_.reset(); |
| } |
| |
| bookmark_prerender_handle_ = web_contents()->StartPrerendering( |
| prerendering_url, content::PrerenderTriggerType::kEmbedder, |
| prerender_utils::kBookmarkBarMetricSuffix, |
| ui::PageTransitionFromInt(ui::PAGE_TRANSITION_AUTO_BOOKMARK), |
| content::PreloadingHoldbackStatus::kUnspecified, preloading_attempt); |
| |
| return bookmark_prerender_handle_ ? bookmark_prerender_handle_->GetWeakPtr() |
| : nullptr; |
| } |
| |
| void PrerenderManager::StopPrerenderBookmark( |
| base::WeakPtr<content::PrerenderHandle> prerender_handle) { |
| if (!prerender_handle) { |
| return; |
| } |
| CHECK_EQ(prerender_handle.get(), |
| bookmark_prerender_handle_->GetWeakPtr().get()); |
| bookmark_prerender_handle_.reset(); |
| } |
| |
| base::WeakPtr<content::PrerenderHandle> |
| PrerenderManager::StartPrerenderDirectUrlInput( |
| const GURL& prerendering_url, |
| content::PreloadingAttempt& preloading_attempt) { |
| if (direct_url_input_prerender_handle_) { |
| if (direct_url_input_prerender_handle_->GetInitialPrerenderingUrl() == |
| prerendering_url) { |
| // In case a prerender is already present for the URL, prerendering is |
| // eligible but mark triggering outcome as a duplicate. |
| preloading_attempt.SetEligibility( |
| content::PreloadingEligibility::kEligible); |
| |
| MarkPreloadingAttemptAsDuplicate(&preloading_attempt); |
| return direct_url_input_prerender_handle_->GetWeakPtr(); |
| } |
| |
| base::UmaHistogramEnumeration( |
| internal::kHistogramPrerenderPredictionStatusDirectUrlInput, |
| PrerenderPredictionStatus::kCancelled); |
| // Mark the previous prerender as failure as we can't keep multiple DUI |
| // prerenders active at the same time. |
| direct_url_input_prerender_handle_->SetPreloadingAttemptFailureReason( |
| ToPreloadingFailureReason(PrerenderPredictionStatus::kCancelled)); |
| 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), |
| content::PreloadingHoldbackStatus::kUnspecified, &preloading_attempt); |
| |
| if (direct_url_input_prerender_handle_) { |
| return direct_url_input_prerender_handle_->GetWeakPtr(); |
| } |
| return nullptr; |
| } |
| |
| void PrerenderManager::StartPrerenderSearchSuggestion( |
| const AutocompleteMatch& match, |
| const GURL& canonical_search_url) { |
| CHECK(AutocompleteMatch::IsSearchType(match.type)); |
| TemplateURLRef::SearchTermsArgs& search_terms_args = |
| *(match.search_terms_args); |
| |
| content::PreloadingURLMatchCallback same_url_matcher = |
| base::BindRepeating(&IsSearchDestinationMatch, canonical_search_url, |
| web_contents()->GetBrowserContext()); |
| auto* preloading_data = |
| content::PreloadingData::GetOrCreateForWebContents(web_contents()); |
| |
| // Create new PreloadingAttempt and pass all the values corresponding to |
| // this prerendering attempt. |
| content::PreloadingAttempt* preloading_attempt = |
| preloading_data->AddPreloadingAttempt( |
| chrome_preloading_predictor::kDefaultSearchEngine, |
| content::PreloadingType::kPrerender, same_url_matcher); |
| |
| // If the caller does not want to prerender a new result, this does not need |
| // to do anything. |
| if (!ResetSearchPrerenderTaskIfNecessary(canonical_search_url, |
| preloading_attempt->GetWeakPtr())) { |
| return; |
| } |
| |
| // Since search pages require Javascript to perform the basic prerender |
| // loading logic, do not prerender a search result if Javascript is disabled. |
| if (IsJavascriptDisabled(*web_contents(), match.destination_url)) { |
| preloading_attempt->SetEligibility( |
| content::PreloadingEligibility::kJavascriptDisabled); |
| return; |
| } |
| |
| 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 |
| // StartPrerenderSearchSuggestion() should ensure the prerender URL is valid |
| // instead. |
| if (!skip_template_url_service_for_testing_) { |
| TemplateURLService* template_url_service = |
| GetTemplateURLServiceFromBrowserContext( |
| web_contents()->GetBrowserContext()); |
| if (!template_url_service) { |
| return; |
| } |
| |
| // TODO(https://crbug.com/1329011): Metric for investigation. Remove this |
| // one after we get more than 30k records. |
| base::UmaHistogramBoolean( |
| "Prerender.Experimental.DefaultSearchEngine." |
| "SearchTermExtractorCorrectness", |
| IsSearchDestinationMatch(canonical_search_url, |
| web_contents()->GetBrowserContext(), |
| match.destination_url)); |
| |
| { |
| // Undo the change. This information might be used during activation so |
| // we should not change it. |
| base::AutoReset<std::string> resetter(&search_terms_args.prefetch_param, |
| kSuggestPrefetchParam.Get()); |
| const TemplateURL* default_provider = |
| template_url_service->GetDefaultSearchProvider(); |
| CHECK(default_provider); |
| prerender_url = GURL(default_provider->url_ref().ReplaceSearchTerms( |
| search_terms_args, template_url_service->search_terms_data(), |
| /*post_content=*/nullptr)); |
| } |
| CHECK(search_terms_args.prefetch_param.empty()); |
| } |
| |
| StartPrerenderSearchResultInternal(canonical_search_url, prerender_url, |
| preloading_attempt->GetWeakPtr()); |
| } |
| |
| void PrerenderManager::StartPrerenderSearchResult( |
| const GURL& canonical_search_url, |
| const GURL& prerendering_url, |
| base::WeakPtr<content::PreloadingAttempt> preloading_attempt) { |
| CHECK(prerender_utils::SearchPrefetchUpgradeToPrerenderIsEnabled()); |
| |
| // If the caller does not want to prerender a new result, this does not need |
| // to do anything. |
| if (!ResetSearchPrerenderTaskIfNecessary(canonical_search_url, |
| preloading_attempt)) { |
| return; |
| } |
| StartPrerenderSearchResultInternal(canonical_search_url, prerendering_url, |
| preloading_attempt); |
| } |
| |
| void PrerenderManager::StopPrerenderSearchResult( |
| const GURL& canonical_search_url) { |
| if (search_prerender_task_ && |
| search_prerender_task_->prerendered_canonical_search_url() == |
| canonical_search_url) { |
| // TODO(https://crbug.com/1295170): Now there is no kUnused record: all the |
| // unused tasks are canceled before navigation happens. Consider recording |
| // the result upon opening the URL rather than waiting for the navigation |
| // finishes. |
| search_prerender_task_->set_prediction_status( |
| PrerenderPredictionStatus::kCancelled); |
| search_prerender_task_.reset(); |
| } |
| } |
| |
| bool PrerenderManager::HasSearchResultPagePrerendered() const { |
| return !!search_prerender_task_; |
| } |
| |
| base::WeakPtr<PrerenderManager> PrerenderManager::GetWeakPtr() { |
| return weak_factory_.GetWeakPtr(); |
| } |
| |
| const GURL PrerenderManager::GetPrerenderCanonicalSearchURLForTesting() const { |
| return search_prerender_task_ |
| ? search_prerender_task_->prerendered_canonical_search_url() |
| : GURL(); |
| } |
| |
| void PrerenderManager::ResetPrerenderHandlesOnPrimaryPageChanged( |
| content::NavigationHandle* navigation_handle) { |
| CHECK(navigation_handle->HasCommitted() && |
| navigation_handle->IsInPrimaryMainFrame() && |
| !navigation_handle->IsSameDocument()); |
| const GURL& opened_url = navigation_handle->GetURL(); |
| if (direct_url_input_prerender_handle_) { |
| // Record whether or not the prediction is correct when prerendering for |
| // direct url input was started. The value `kNotStarted` is recorded in |
| // AutocompleteActionPredictor::OnOmniboxOpenedUrl(). |
| base::UmaHistogramEnumeration( |
| internal::kHistogramPrerenderPredictionStatusDirectUrlInput, |
| direct_url_input_prerender_handle_->GetInitialPrerenderingUrl() == |
| opened_url |
| ? PrerenderPredictionStatus::kHitFinished |
| : PrerenderPredictionStatus::kUnused); |
| // We don't set the PreloadingFailureReason for wrong predictions, as this |
| // is not a prerender failure rather it is an in accurate triggering for DUI |
| // predictor as the user didn't end up navigating to the predicted URL. |
| direct_url_input_prerender_handle_.reset(); |
| } |
| |
| if (search_prerender_task_) { |
| // TODO(https://crbug.com/1278634): Move all operations below into a |
| // dedicated method of SearchPrerenderTask. |
| |
| bool is_search_destination_match = IsSearchDestinationMatch( |
| search_prerender_task_->prerendered_canonical_search_url(), |
| web_contents()->GetBrowserContext(), opened_url); |
| |
| if (is_search_destination_match) { |
| // We may want to record this metric on AutocompleteMatch selected relying |
| // on GetMatchSelectionTimestamp. But this is for rough estimation so it |
| // may not need the precise data. |
| search_prerender_task_->set_prediction_status( |
| PrerenderPredictionStatus::kHitFinished); |
| search_prerender_task_->RecordLifeTimeMetric(); |
| } |
| |
| if (is_search_destination_match && |
| navigation_handle->IsPrerenderedPageActivation()) { |
| search_prerender_task_->OnActivated(*web_contents()); |
| } |
| |
| search_prerender_task_.reset(); |
| } |
| |
| bookmark_prerender_handle_.reset(); |
| } |
| |
| bool PrerenderManager::ResetSearchPrerenderTaskIfNecessary( |
| const GURL& canonical_search_url, |
| base::WeakPtr<content::PreloadingAttempt> preloading_attempt) { |
| if (!search_prerender_task_) |
| return true; |
| |
| // Do not re-prerender the same search result. |
| // TODO(https://crbug.com/1278634): re-prerender the search result if the |
| // prerendered content has been removed. |
| if (search_prerender_task_->prerendered_canonical_search_url() == |
| canonical_search_url) { |
| // In case a prerender is already present for the URL, prerendering is |
| // eligible but mark triggering outcome as a duplicate. |
| if (preloading_attempt) { |
| preloading_attempt->SetEligibility( |
| content::PreloadingEligibility::kEligible); |
| |
| MarkPreloadingAttemptAsDuplicate(preloading_attempt.get()); |
| } |
| return false; |
| } |
| search_prerender_task_->set_prediction_status( |
| PrerenderPredictionStatus::kCancelled); |
| search_prerender_task_.reset(); |
| return true; |
| } |
| |
| void PrerenderManager::StartPrerenderSearchResultInternal( |
| const GURL& canonical_search_url, |
| const GURL& prerendering_url, |
| base::WeakPtr<content::PreloadingAttempt> attempt) { |
| // 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, canonical_search_url, |
| web_contents()->GetBrowserContext()); |
| |
| std::unique_ptr<content::PrerenderHandle> prerender_handle = |
| web_contents()->StartPrerendering( |
| prerendering_url, content::PrerenderTriggerType::kEmbedder, |
| prerender_utils::kDefaultSearchEngineMetricSuffix, |
| ui::PageTransitionFromInt(ui::PAGE_TRANSITION_GENERATED | |
| ui::PAGE_TRANSITION_FROM_ADDRESS_BAR), |
| content::PreloadingHoldbackStatus::kUnspecified, |
| /*preloading_attempt=*/attempt.get(), std::move(url_match_predicate)); |
| |
| if (prerender_handle) { |
| CHECK(!search_prerender_task_) |
| << "SearchPrerenderTask should be reset before setting a new one."; |
| search_prerender_task_ = std::make_unique<SearchPrerenderTask>( |
| canonical_search_url, std::move(prerender_handle)); |
| } |
| } |
| |
| PrerenderManager::PrerenderManager(content::WebContents* web_contents) |
| : content::WebContentsObserver(web_contents), |
| content::WebContentsUserData<PrerenderManager>(*web_contents) {} |
| |
| WEB_CONTENTS_USER_DATA_KEY_IMPL(PrerenderManager); |