blob: 4e1f98933c38f66b70d6471465e6a3d591490db0 [file] [log] [blame]
// 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);