blob: cd3d3da52eab06966fc12f58e1b084f5f69bd7ad [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/navigation_predictor/navigation_predictor_preconnect_client.h"
#include <memory>
#include "base/bind.h"
#include "base/feature_list.h"
#include "base/metrics/field_trial_params.h"
#include "base/time/time.h"
#include "build/build_config.h"
#include "chrome/browser/navigation_predictor/navigation_predictor_features.h"
#include "chrome/browser/navigation_predictor/search_engine_preconnector.h"
#include "chrome/browser/predictors/loading_predictor.h"
#include "chrome/browser/predictors/loading_predictor_factory.h"
#include "chrome/browser/profiles/profile.h"
#include "chrome/browser/search_engines/template_url_service_factory.h"
#include "components/search_engines/template_url_service.h"
#include "content/public/browser/browser_context.h"
#include "content/public/browser/navigation_handle.h"
#include "content/public/browser/render_frame_host.h"
#include "content/public/browser/web_contents.h"
#include "net/base/features.h"
namespace {
// Experiment with which event triggers the preconnect after commit.
const base::Feature kPreconnectOnDidFinishNavigation{
"PreconnectOnDidFinishNavigation", base::FEATURE_DISABLED_BY_DEFAULT};
} // namespace
NavigationPredictorPreconnectClient::NavigationPredictorPreconnectClient(
content::WebContents* web_contents)
: content::WebContentsObserver(web_contents),
browser_context_(web_contents->GetBrowserContext()),
current_visibility_(web_contents->GetVisibility()) {}
NavigationPredictorPreconnectClient::~NavigationPredictorPreconnectClient() =
default;
void NavigationPredictorPreconnectClient::DidFinishNavigation(
content::NavigationHandle* navigation_handle) {
if (!navigation_handle->IsInMainFrame() ||
!navigation_handle->HasCommitted() ||
(!base::FeatureList::IsEnabled(
features::
kNavigationPredictorEnablePreconnectOnSameDocumentNavigations) &&
navigation_handle->IsSameDocument())) {
return;
}
if (!navigation_handle->GetURL().SchemeIsHTTPOrHTTPS())
return;
// New page, so stop the preconnect timer.
timer_.Stop();
if (base::FeatureList::IsEnabled(kPreconnectOnDidFinishNavigation) ||
navigation_handle->IsSameDocument()) {
int delay_ms = base::GetFieldTrialParamByFeatureAsInt(
kPreconnectOnDidFinishNavigation, "delay_after_commit_in_ms", 3000);
if (delay_ms <= 0) {
MaybePreconnectNow(/*preconnects_attempted=*/0u);
return;
}
timer_.Start(
FROM_HERE, base::TimeDelta::FromMilliseconds(delay_ms),
base::BindOnce(&NavigationPredictorPreconnectClient::MaybePreconnectNow,
base::Unretained(this), /*preconnects_attempted=*/0u));
}
}
void NavigationPredictorPreconnectClient::OnVisibilityChanged(
content::Visibility visibility) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
// Check for same state.
if (current_visibility_ == visibility)
return;
// Check if the visibility is now visible, if not, cancel future preconnects.
// If visible, we can begin preconnecting.
if (visibility != content::Visibility::VISIBLE) {
current_visibility_ = visibility;
// Stop any future preconnects while hidden.
timer_.Stop();
return;
}
current_visibility_ = visibility;
// Previously, the visibility was HIDDEN, and now it is VISIBLE implying that
// the web contents that was fully hidden is now fully visible.
MaybePreconnectNow(/*preconnects_attempted=*/0u);
}
void NavigationPredictorPreconnectClient::DidFinishLoad(
content::RenderFrameHost* render_frame_host,
const GURL& validated_url) {
// Ignore sub-frame loads.
if (render_frame_host->GetParent())
return;
MaybePreconnectNow(/*preconnects_attempted=*/0u);
}
void NavigationPredictorPreconnectClient::MaybePreconnectNow(
size_t preconnects_attempted) {
if (base::FeatureList::IsEnabled(
features::kNavigationPredictorPreconnectHoldback))
return;
if (browser_context_->IsOffTheRecord())
return;
// Only preconnect foreground tab.
if (current_visibility_ != content::Visibility::VISIBLE)
return;
// Only allow 5 preconnects per foreground/load.
if (preconnects_attempted >= 5u)
return;
// On search engine results page, next navigation is likely to be a
// different origin. Currently, the preconnect is only allowed for same
// origins. Hence, preconnect is currently disabled on search engine results
// page. If preconnect to DSE is enabled, skip this check.
if (!base::FeatureList::IsEnabled(features::kPreconnectToSearch) &&
IsSearchEnginePage())
return;
url::Origin preconnect_origin =
url::Origin::Create(web_contents()->GetLastCommittedURL());
if (preconnect_origin.scheme() != url::kHttpScheme &&
preconnect_origin.scheme() != url::kHttpsScheme) {
return;
}
auto* loading_predictor = predictors::LoadingPredictorFactory::GetForProfile(
Profile::FromBrowserContext(browser_context_));
GURL preconnect_url_serialized(preconnect_origin.Serialize());
DCHECK(preconnect_url_serialized.is_valid());
if (!loading_predictor)
return;
loading_predictor->PrepareForPageLoad(
preconnect_url_serialized, predictors::HintOrigin::NAVIGATION_PREDICTOR,
true);
// The delay beyond the idle socket timeout that net uses when
// re-preconnecting. If negative, no retries occur.
constexpr int retry_delay_ms = 50;
// Set/Reset the timer to fire after the preconnect times out. Add an extra
// delay to make sure the preconnect has expired if it wasn't used.
timer_.Start(
FROM_HERE,
base::TimeDelta::FromSeconds(base::GetFieldTrialParamByFeatureAsInt(
net::features::kNetUnusedIdleSocketTimeout,
"unused_idle_socket_timeout_seconds", 60)) +
base::TimeDelta::FromMilliseconds(retry_delay_ms),
base::BindOnce(&NavigationPredictorPreconnectClient::MaybePreconnectNow,
base::Unretained(this), preconnects_attempted + 1));
}
bool NavigationPredictorPreconnectClient::IsSearchEnginePage() const {
auto* template_service = TemplateURLServiceFactory::GetForProfile(
Profile::FromBrowserContext(browser_context_));
if (!template_service)
return false;
return template_service->IsSearchResultsPageFromDefaultSearchProvider(
web_contents()->GetLastCommittedURL());
}
WEB_CONTENTS_USER_DATA_KEY_IMPL(NavigationPredictorPreconnectClient)