| // Copyright 2019 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/optimization_guide/optimization_guide_web_contents_observer.h" |
| |
| #include "base/task/single_thread_task_runner.h" |
| #include "chrome/browser/optimization_guide/chrome_hints_manager.h" |
| #include "chrome/browser/optimization_guide/optimization_guide_keyed_service.h" |
| #include "chrome/browser/optimization_guide/optimization_guide_keyed_service_factory.h" |
| #include "chrome/browser/preloading/prefetch/no_state_prefetch/no_state_prefetch_manager_factory.h" |
| #include "chrome/browser/profiles/profile.h" |
| #include "components/google/core/common/google_util.h" |
| #include "components/no_state_prefetch/browser/no_state_prefetch_manager.h" |
| #include "components/optimization_guide/core/hints/hints_fetcher.h" |
| #include "components/optimization_guide/core/hints/hints_processing_util.h" |
| #include "components/optimization_guide/core/optimization_guide_enums.h" |
| #include "components/optimization_guide/core/optimization_guide_features.h" |
| #include "components/optimization_guide/proto/hints.pb.h" |
| #include "content/public/browser/browser_thread.h" |
| #include "content/public/browser/navigation_handle.h" |
| #include "content/public/browser/web_contents.h" |
| |
| namespace { |
| |
| bool IsValidOptimizationGuideNavigation( |
| content::NavigationHandle* navigation_handle) { |
| if (!navigation_handle->IsInPrimaryMainFrame()) |
| return false; |
| |
| if (!navigation_handle->GetURL().SchemeIsHTTPOrHTTPS()) |
| return false; |
| |
| // Now check if this is a NSP navigation. NSP is not a valid navigation. |
| prerender::NoStatePrefetchManager* no_state_prefetch_manager = |
| prerender::NoStatePrefetchManagerFactory::GetForBrowserContext( |
| navigation_handle->GetWebContents()->GetBrowserContext()); |
| if (!no_state_prefetch_manager) { |
| // Not a NSP navigation if there is no NSP manager. |
| return true; |
| } |
| return !(no_state_prefetch_manager->IsWebContentsPrefetching( |
| navigation_handle->GetWebContents())); |
| } |
| |
| } // namespace |
| |
| OptimizationGuideWebContentsObserver::OptimizationGuideWebContentsObserver( |
| content::WebContents* web_contents) |
| : content::WebContentsObserver(web_contents), |
| content::WebContentsUserData<OptimizationGuideWebContentsObserver>( |
| *web_contents) { |
| DCHECK_CURRENTLY_ON(content::BrowserThread::UI); |
| |
| optimization_guide_keyed_service_ = |
| OptimizationGuideKeyedServiceFactory::GetForProfile( |
| Profile::FromBrowserContext(web_contents->GetBrowserContext())); |
| } |
| |
| OptimizationGuideWebContentsObserver::~OptimizationGuideWebContentsObserver() = |
| default; |
| |
| OptimizationGuideNavigationData* OptimizationGuideWebContentsObserver:: |
| GetOrCreateOptimizationGuideNavigationData( |
| content::NavigationHandle* navigation_handle) { |
| DCHECK_CURRENTLY_ON(content::BrowserThread::UI); |
| DCHECK_EQ(web_contents(), navigation_handle->GetWebContents()); |
| |
| NavigationHandleData* navigation_handle_data = |
| NavigationHandleData::GetOrCreateForNavigationHandle(*navigation_handle); |
| OptimizationGuideNavigationData* navigation_data = |
| navigation_handle_data->GetOptimizationGuideNavigationData(); |
| if (!navigation_data) { |
| // We do not have one already - create one. |
| navigation_handle_data->SetOptimizationGuideNavigationData( |
| std::make_unique<OptimizationGuideNavigationData>( |
| navigation_handle->GetNavigationId(), |
| navigation_handle->NavigationStart())); |
| navigation_data = |
| navigation_handle_data->GetOptimizationGuideNavigationData(); |
| } |
| |
| DCHECK(navigation_data); |
| return navigation_data; |
| } |
| |
| void OptimizationGuideWebContentsObserver::DidStartNavigation( |
| content::NavigationHandle* navigation_handle) { |
| DCHECK_CURRENTLY_ON(content::BrowserThread::UI); |
| |
| if (!IsValidOptimizationGuideNavigation(navigation_handle)) |
| return; |
| |
| if (!optimization_guide_keyed_service_) |
| return; |
| |
| OptimizationGuideNavigationData* navigation_data = |
| GetOrCreateOptimizationGuideNavigationData(navigation_handle); |
| navigation_data->set_navigation_url(navigation_handle->GetURL()); |
| optimization_guide_keyed_service_->OnNavigationStartOrRedirect( |
| navigation_data); |
| } |
| |
| void OptimizationGuideWebContentsObserver::DidRedirectNavigation( |
| content::NavigationHandle* navigation_handle) { |
| DCHECK_CURRENTLY_ON(content::BrowserThread::UI); |
| |
| if (!IsValidOptimizationGuideNavigation(navigation_handle)) |
| return; |
| |
| if (!optimization_guide_keyed_service_) |
| return; |
| |
| OptimizationGuideNavigationData* navigation_data = |
| GetOrCreateOptimizationGuideNavigationData(navigation_handle); |
| navigation_data->set_navigation_url(navigation_handle->GetURL()); |
| optimization_guide_keyed_service_->OnNavigationStartOrRedirect( |
| navigation_data); |
| } |
| |
| void OptimizationGuideWebContentsObserver::DidFinishNavigation( |
| content::NavigationHandle* navigation_handle) { |
| DCHECK_CURRENTLY_ON(content::BrowserThread::UI); |
| |
| if (!IsValidOptimizationGuideNavigation(navigation_handle)) |
| return; |
| |
| // Note that a lot of Navigations (same document, non-committed, etc.) might |
| // not have navigation data associated with them, but we reduce likelihood of |
| // future leaks by always trying to remove the data. |
| NavigationHandleData* navigation_handle_data = |
| NavigationHandleData::GetForNavigationHandle(*navigation_handle); |
| if (!navigation_handle_data) |
| return; |
| base::SingleThreadTaskRunner::GetCurrentDefault()->PostTask( |
| FROM_HERE, |
| base::BindOnce( |
| &OptimizationGuideWebContentsObserver::NotifyNavigationFinish, |
| weak_factory_.GetWeakPtr(), |
| navigation_handle_data->TakeOptimizationGuideNavigationData(), |
| navigation_handle->GetRedirectChain())); |
| } |
| |
| void OptimizationGuideWebContentsObserver::WebContentsDestroyed() { |
| // The web contents are being destroyed. Stop observing. |
| Observe(/*web_contents=*/nullptr); |
| } |
| |
| void OptimizationGuideWebContentsObserver:: |
| DocumentOnLoadCompletedInPrimaryMainFrame() { |
| DCHECK_CURRENTLY_ON(content::BrowserThread::UI); |
| if (!web_contents() || !web_contents() |
| ->GetPrimaryMainFrame() |
| ->GetLastCommittedURL() |
| .SchemeIsHTTPOrHTTPS()) { |
| return; |
| } |
| |
| if (!optimization_guide_keyed_service_) |
| return; |
| |
| if (optimization_guide::features::IsSRPFetchingEnabled() && |
| google_util::IsGoogleSearchUrl( |
| web_contents()->GetPrimaryMainFrame()->GetLastCommittedURL())) { |
| base::SingleThreadTaskRunner::GetCurrentDefault()->PostTask( |
| FROM_HERE, |
| base::BindOnce( |
| &OptimizationGuideWebContentsObserver::FetchHintsUsingManager, |
| weak_factory_.GetWeakPtr(), |
| optimization_guide_keyed_service_->GetHintsManager(), |
| web_contents()->GetPrimaryPage().GetWeakPtr())); |
| } |
| } |
| |
| void OptimizationGuideWebContentsObserver::FetchHintsUsingManager( |
| optimization_guide::ChromeHintsManager* hints_manager, |
| base::WeakPtr<content::Page> page) { |
| DCHECK_CURRENTLY_ON(content::BrowserThread::UI); |
| DCHECK(hints_manager); |
| if (!page) |
| return; |
| |
| CHECK(optimization_guide::features::IsSRPFetchingEnabled()); |
| PageData& page_data = GetPageData(*page); |
| |
| std::vector<GURL> top_urls = page_data.GetHintsTargetUrls(); |
| |
| if (!top_urls.empty()) { |
| page_data.set_sent_batched_hints_request(); |
| top_urls.resize( |
| std::min(top_urls.size(), |
| optimization_guide::features::MaxResultsForSRPFetch())); |
| hints_manager->FetchHintsForURLs( |
| top_urls, optimization_guide::proto::CONTEXT_BATCH_UPDATE_GOOGLE_SRP); |
| } |
| } |
| |
| void OptimizationGuideWebContentsObserver::NotifyNavigationFinish( |
| std::unique_ptr<OptimizationGuideNavigationData> navigation_data, |
| const std::vector<GURL>& navigation_redirect_chain) { |
| DCHECK_CURRENTLY_ON(content::BrowserThread::UI); |
| if (optimization_guide_keyed_service_) { |
| optimization_guide_keyed_service_->OnNavigationFinish( |
| navigation_redirect_chain); |
| } |
| |
| // We keep the navigation data in the PageData around to keep track of events |
| // happening for the navigation that can happen after commit, such as a fetch |
| // for the navigation successfully completing (which is not guaranteed to come |
| // back before commit, if at all). |
| if (!web_contents()) { |
| return; |
| } |
| PageData& page_data = GetPageData(web_contents()->GetPrimaryPage()); |
| page_data.SetNavigationData(std::move(navigation_data)); |
| } |
| |
| void OptimizationGuideWebContentsObserver::FlushLastNavigationData() { |
| DCHECK_CURRENTLY_ON(content::BrowserThread::UI); |
| if (!web_contents()) { |
| return; |
| } |
| PageData& page_data = GetPageData(web_contents()->GetPrimaryPage()); |
| page_data.SetNavigationData(nullptr); |
| } |
| |
| void OptimizationGuideWebContentsObserver::AddURLsToBatchFetchBasedOnPrediction( |
| std::vector<GURL> urls, |
| content::WebContents* web_contents) { |
| DCHECK_CURRENTLY_ON(content::BrowserThread::UI); |
| |
| if (!this->web_contents()) |
| return; |
| DCHECK_EQ(this->web_contents(), web_contents); |
| |
| PageData& page_data = GetPageData(web_contents->GetPrimaryPage()); |
| if (page_data.is_sent_batched_hints_request()) |
| return; |
| page_data.InsertHintTargetUrls(urls); |
| |
| // In rare cases (such as some in browsertests), the onload event could come |
| // earlier than the first predictions, in which case we should attempt the |
| // fetch as prediction URLs are received. |
| if (web_contents->IsDocumentOnLoadCompletedInPrimaryMainFrame()) { |
| base::SingleThreadTaskRunner::GetCurrentDefault()->PostTask( |
| FROM_HERE, |
| base::BindOnce( |
| &OptimizationGuideWebContentsObserver::FetchHintsUsingManager, |
| weak_factory_.GetWeakPtr(), |
| optimization_guide_keyed_service_->GetHintsManager(), |
| web_contents->GetPrimaryPage().GetWeakPtr())); |
| } |
| } |
| |
| OptimizationGuideWebContentsObserver::PageData& |
| OptimizationGuideWebContentsObserver::GetPageData(content::Page& page) { |
| return *PageData::GetOrCreateForPage(page); |
| } |
| |
| OptimizationGuideWebContentsObserver::PageData::PageData(content::Page& page) |
| : PageUserData(page) {} |
| OptimizationGuideWebContentsObserver::PageData::~PageData() = default; |
| |
| void OptimizationGuideWebContentsObserver::PageData::InsertHintTargetUrls( |
| const std::vector<GURL>& urls) { |
| DCHECK(!sent_batched_hints_request_); |
| for (const GURL& url : urls) |
| hints_target_urls_.insert(url); |
| } |
| |
| std::vector<GURL> |
| OptimizationGuideWebContentsObserver::PageData::GetHintsTargetUrls() { |
| std::vector<GURL> target_urls = std::move(hints_target_urls_.vector()); |
| hints_target_urls_.clear(); |
| return target_urls; |
| } |
| |
| OptimizationGuideWebContentsObserver::NavigationHandleData:: |
| NavigationHandleData(content::NavigationHandle&) {} |
| OptimizationGuideWebContentsObserver::NavigationHandleData:: |
| ~NavigationHandleData() = default; |
| |
| PAGE_USER_DATA_KEY_IMPL(OptimizationGuideWebContentsObserver::PageData); |
| NAVIGATION_HANDLE_USER_DATA_KEY_IMPL( |
| OptimizationGuideWebContentsObserver::NavigationHandleData); |
| WEB_CONTENTS_USER_DATA_KEY_IMPL(OptimizationGuideWebContentsObserver); |