blob: c030f2b3cdbeb8a276427a183490506c6fb01c5a [file] [log] [blame]
// Copyright 2019 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/optimization_guide/optimization_guide_web_contents_observer.h"
#include "chrome/browser/optimization_guide/optimization_guide_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/optimization_guide/optimization_guide_top_host_provider.h"
#include "chrome/browser/profiles/profile.h"
#include "components/optimization_guide/core/hints_fetcher.h"
#include "components/optimization_guide/core/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) {
return navigation_handle->IsInMainFrame() &&
navigation_handle->GetURL().SchemeIsHTTPOrHTTPS();
}
} // namespace
OptimizationGuideWebContentsObserver::OptimizationGuideWebContentsObserver(
content::WebContents* web_contents)
: content::WebContentsObserver(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);
int64_t navigation_id = navigation_handle->GetNavigationId();
if (inflight_optimization_guide_navigation_datas_.find(navigation_id) ==
inflight_optimization_guide_navigation_datas_.end()) {
// We do not have one already - create one.
inflight_optimization_guide_navigation_datas_[navigation_id] =
std::make_unique<OptimizationGuideNavigationData>(navigation_id);
}
DCHECK(inflight_optimization_guide_navigation_datas_.find(navigation_id) !=
inflight_optimization_guide_navigation_datas_.end());
return inflight_optimization_guide_navigation_datas_.find(navigation_id)
->second.get();
}
void OptimizationGuideWebContentsObserver::DidStartNavigation(
content::NavigationHandle* navigation_handle) {
DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
// Clear any leftover hint requests from a previous navigation.
if (navigation_handle->IsInMainFrame()) {
ClearHintsToFetchBasedOnPredictions(navigation_handle);
}
if (!IsValidOptimizationGuideNavigation(navigation_handle))
return;
OptimizationGuideTopHostProvider::MaybeUpdateTopHostBlocklist(
navigation_handle);
if (!optimization_guide_keyed_service_)
return;
optimization_guide_keyed_service_->OnNavigationStartOrRedirect(
navigation_handle);
}
void OptimizationGuideWebContentsObserver::DidRedirectNavigation(
content::NavigationHandle* navigation_handle) {
DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
if (!IsValidOptimizationGuideNavigation(navigation_handle))
return;
if (!optimization_guide_keyed_service_)
return;
optimization_guide_keyed_service_->OnNavigationStartOrRedirect(
navigation_handle);
}
void OptimizationGuideWebContentsObserver::ClearHintsToFetchBasedOnPredictions(
content::NavigationHandle* navigation_handle) {
hints_target_urls_.clear();
sent_batched_hints_request_ = false;
}
void OptimizationGuideWebContentsObserver::DidFinishNavigation(
content::NavigationHandle* navigation_handle) {
DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
// Clear any leftover hint requests from a previous navigation.
if (navigation_handle->IsInMainFrame()) {
ClearHintsToFetchBasedOnPredictions(navigation_handle);
}
if (!IsValidOptimizationGuideNavigation(navigation_handle)) {
return;
}
// Delete Optimization Guide information later, so that other
// DidFinishNavigation methods can reliably use
// GetOptimizationGuideNavigationData regardless of order of
// WebContentsObservers.
// Note that a lot of Navigations (sub-frames, 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.
base::ThreadTaskRunnerHandle::Get()->PostTask(
FROM_HERE,
base::BindOnce(
&OptimizationGuideWebContentsObserver::NotifyNavigationFinish,
weak_factory_.GetWeakPtr(), navigation_handle->GetNavigationId(),
navigation_handle->GetRedirectChain()));
}
void OptimizationGuideWebContentsObserver::DocumentOnLoadCompletedInMainFrame(
content::RenderFrameHost* render_frame_host) {
if (!render_frame_host->GetLastCommittedURL().SchemeIsHTTPOrHTTPS()) {
return;
}
if (web_contents() !=
content::WebContents::FromRenderFrameHost(render_frame_host)) {
// The current web contents isn't for the main frame that reached onload.
return;
}
// NavigationPredictor currently sends predictions just after onload. This
// will soon change, but in the meantime, give it 200 ms to report predictions
// before fetching them.
// TODO(spelchat): avoid posting a task and just inline FetchHints here
// once NavigationPredictor starts streaming predictions rather than sending
// them just after onload.
base::ThreadTaskRunnerHandle::Get()->PostDelayedTask(
FROM_HERE,
base::BindOnce(&OptimizationGuideWebContentsObserver::FetchHints,
weak_factory_.GetWeakPtr()),
base::TimeDelta::FromMilliseconds(200));
}
void OptimizationGuideWebContentsObserver::FetchHintsUsingManagerForTesting(
OptimizationGuideHintsManager* hints_manager) {
DCHECK(hints_manager);
sent_batched_hints_request_ = true;
hints_manager->FetchHintsForPredictions(
std::move(hints_target_urls_.vector()));
hints_target_urls_.clear();
}
void OptimizationGuideWebContentsObserver::FetchHints() {
if (!optimization_guide_keyed_service_) {
return;
}
OptimizationGuideHintsManager* hints_manager =
optimization_guide_keyed_service_->GetHintsManager();
DCHECK(hints_manager);
sent_batched_hints_request_ = true;
hints_manager->FetchHintsForPredictions(
std::move(hints_target_urls_.vector()));
hints_target_urls_.clear();
}
void OptimizationGuideWebContentsObserver::NotifyNavigationFinish(
int64_t navigation_id,
const std::vector<GURL>& navigation_redirect_chain) {
auto nav_data_iter =
inflight_optimization_guide_navigation_datas_.find(navigation_id);
if (nav_data_iter == inflight_optimization_guide_navigation_datas_.end())
return;
if (optimization_guide_keyed_service_) {
optimization_guide_keyed_service_->OnNavigationFinish(
navigation_redirect_chain);
}
// We keep the last navigation data 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).
last_navigation_data_ = std::move(nav_data_iter->second);
inflight_optimization_guide_navigation_datas_.erase(navigation_id);
}
void OptimizationGuideWebContentsObserver::FlushLastNavigationData() {
if (last_navigation_data_)
last_navigation_data_.reset();
}
void OptimizationGuideWebContentsObserver::AddURLsToBatchFetchBasedOnPrediction(
std::vector<GURL> urls,
content::WebContents* web_contents) {
if (!this->web_contents()) {
return;
}
DCHECK_EQ(this->web_contents(), web_contents);
if (sent_batched_hints_request_) {
return;
}
for (const GURL& url : urls) {
hints_target_urls_.insert(url);
}
}
WEB_CONTENTS_USER_DATA_KEY_IMPL(OptimizationGuideWebContentsObserver)