blob: 25def7f765d7a79824ac254c53b542dca9b6f463 [file] [log] [blame]
// 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/navigation_ablation_throttle.h"
#include "base/feature_list.h"
#include "base/functional/bind.h"
#include "base/metrics/field_trial_params.h"
#include "base/metrics/histogram_functions.h"
#include "base/strings/pattern.h"
#include "base/time/time.h"
#include "base/timer/timer.h"
#include "chrome/browser/profiles/profile.h"
#include "chrome/browser/search_engines/template_url_service_factory.h"
#include "components/search_engines/search_terms_data.h"
#include "components/search_engines/template_url_service.h"
#include "content/public/browser/navigation_handle.h"
#include "content/public/browser/navigation_throttle.h"
#include "content/public/browser/web_contents.h"
namespace {
BASE_FEATURE(kNavigationLatencyAblation,
"NavigationLatencyAblation",
base::FEATURE_DISABLED_BY_DEFAULT);
// The amount of time to stall before resuming loading.
const base::FeatureParam<base::TimeDelta> kNavigationLatencyAblationDuration{
&kNavigationLatencyAblation, "duration", base::Milliseconds(250)};
// The `base::MatchPattern` pattern that identifies the URLs should be ablated.
const base::FeatureParam<std::string> kAblationTargetPattern{
&kNavigationLatencyAblation, "pattern", ""};
// Whether default search Search queries should be ablated.
const base::FeatureParam<bool> kShouldAblateDefaultSearchQueries{
&kNavigationLatencyAblation, "ablate_default_search_queries", true};
// Whether default search navigations to any of the related hosts should be
// ablated.
const base::FeatureParam<bool> kShouldAblateDefaultSearchHost{
&kNavigationLatencyAblation, "ablate_default_search_host", true};
// Whether the corpus of URLs that are not part of the default search host
// corpus should be ablated.
const base::FeatureParam<bool> kShouldAblateNonDefaultSearchHost{
&kNavigationLatencyAblation, "ablate_non_default_search_host", true};
// Different URLs re grouped into ablation types, which can be configured to be
// ablated or not.
enum class AblationType {
kDefaultSearchQuery = 0,
kDefaultSearchHost = 1,
kNonDefaultSearchHost = 2,
};
// The name of the throttle for logging purposes.
constexpr char kNavigationAblationThrottleName[] = "NavigationAblationThrottle";
// Return the type of navigation based on whether `navigation_url` is a search
// query or related to the user's default search provider.
AblationType GetAblationType(const GURL& navigation_url,
content::BrowserContext* browser_context) {
auto* template_url_service = TemplateURLServiceFactory::GetForProfile(
Profile::FromBrowserContext(browser_context));
if (!template_url_service)
return AblationType::kNonDefaultSearchHost;
auto* default_search = template_url_service->GetDefaultSearchProvider();
if (!default_search)
return AblationType::kNonDefaultSearchHost;
if (default_search->IsSearchURL(navigation_url,
template_url_service->search_terms_data())) {
return AblationType::kDefaultSearchQuery;
}
for (auto url_ref : default_search->url_refs()) {
if (url::Origin::Create(navigation_url) ==
url::Origin::Create(GURL(url_ref.GetURL()))) {
return AblationType::kDefaultSearchHost;
}
}
return AblationType::kNonDefaultSearchHost;
}
bool ShouldCreateThrottle(content::NavigationHandle* navigation) {
if (!base::FeatureList::IsEnabled(kNavigationLatencyAblation)) {
return false;
}
// Exclude navigations for prerender or other MPArch.
if (!navigation->IsInPrimaryMainFrame())
return false;
// Don't slow down activations from prerender or BFCache.
if (navigation->IsPageActivation())
return false;
// Avoid ablating pages that likely are served from HTTP Cache.
if (navigation->GetPageTransition() & ui::PAGE_TRANSITION_FORWARD_BACK) {
return false;
}
// Avoid ablataing client redirects as they may be part of a chain for a
// single navigation.
if (navigation->GetPageTransition() & ui::PAGE_TRANSITION_CLIENT_REDIRECT) {
return false;
}
const GURL& url = navigation->GetURL();
// Ignore navigations that are not HTTP(S).
if (!url.SchemeIsHTTPOrHTTPS()) {
return false;
}
// Ignore navigations to IP addresses as these may be local network.
if (url.HostIsIPAddress()) {
return false;
}
std::string pattern = kAblationTargetPattern.Get();
if (!pattern.empty() && base::MatchPattern(url.spec(), pattern)) {
return true;
}
auto ablation_type =
GetAblationType(url, navigation->GetWebContents()->GetBrowserContext());
switch (ablation_type) {
case AblationType::kDefaultSearchQuery:
return kShouldAblateDefaultSearchQueries.Get();
case AblationType::kDefaultSearchHost:
return kShouldAblateDefaultSearchHost.Get();
case AblationType::kNonDefaultSearchHost:
return kShouldAblateNonDefaultSearchHost.Get();
}
}
// A navigation throttle that deferes during WillStartRequest and resumes after
// a fixed duration.
class NavigationAblationThrottle : public content::NavigationThrottle {
public:
explicit NavigationAblationThrottle(content::NavigationHandle* navigation)
: content::NavigationThrottle(navigation) {}
~NavigationAblationThrottle() override = default;
private:
const char* GetNameForLogging() override {
return kNavigationAblationThrottleName;
}
ThrottleCheckResult WillStartRequest() override {
timer_.Start(
FROM_HERE, kNavigationLatencyAblationDuration.Get(),
base::BindOnce(&NavigationAblationThrottle::ResumeLoading,
base::Unretained(this), base::TimeTicks::Now()));
return content::NavigationThrottle::DEFER;
}
void ResumeLoading(base::TimeTicks start_time) {
// Measure the actual wait time to make sure we are near the expected wait
// time for the experiment arm.
auto wait_time = base::TimeTicks::Now() - start_time;
auto excess_wait_time =
wait_time - kNavigationLatencyAblationDuration.Get();
// Record the time spent waiting beyond what was configured.
base::UmaHistogramTimes("Navigation.LatencyAblation.ExcessWaitTime",
excess_wait_time);
Resume();
}
// A timer that resumes the loading after a fixed duration. The timer ensures
// that if |this| is deleted the callback does not run and cause a UAF.
base::OneShotTimer timer_;
};
} // namespace
// static
std::unique_ptr<content::NavigationThrottle>
MaybeCreateNavigationAblationThrottle(content::NavigationHandle* navigation) {
DCHECK(navigation);
if (!ShouldCreateThrottle(navigation)) {
return nullptr;
}
return std::make_unique<NavigationAblationThrottle>(navigation);
}