blob: 2010540fe096dfd288160e85f28d142f4a1d0136 [file] [log] [blame]
// Copyright 2015 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 "components/navigation_interception/intercept_navigation_throttle.h"
#include "base/bind.h"
#include "base/metrics/histogram_macros.h"
#include "base/threading/thread_task_runner_handle.h"
#include "base/time/time.h"
#include "base/timer/elapsed_timer.h"
#include "build/build_config.h"
#include "content/public/browser/browser_thread.h"
#include "content/public/browser/navigation_handle.h"
#include "url/gurl.h"
namespace navigation_interception {
// Note: this feature is a no-op on non-Android platforms.
const base::Feature InterceptNavigationThrottle::kAsyncCheck{
"AsyncNavigationIntercept", base::FEATURE_ENABLED_BY_DEFAULT};
InterceptNavigationThrottle::InterceptNavigationThrottle(
content::NavigationHandle* navigation_handle,
CheckCallback should_ignore_callback)
: content::NavigationThrottle(navigation_handle),
should_ignore_callback_(should_ignore_callback),
ui_task_runner_(base::ThreadTaskRunnerHandle::Get()),
weak_factory_(this) {}
InterceptNavigationThrottle::~InterceptNavigationThrottle() {
UMA_HISTOGRAM_BOOLEAN("Navigation.Intercept.Ignored", should_ignore_);
}
content::NavigationThrottle::ThrottleCheckResult
InterceptNavigationThrottle::WillStartRequest() {
DCHECK(!should_ignore_);
base::ElapsedTimer timer;
auto result = CheckIfShouldIgnoreNavigation(false /* is_redirect */);
UMA_HISTOGRAM_COUNTS_10M("Navigation.Intercept.WillStart",
timer.Elapsed().InMicroseconds());
return result;
}
content::NavigationThrottle::ThrottleCheckResult
InterceptNavigationThrottle::WillRedirectRequest() {
if (should_ignore_)
return content::NavigationThrottle::CANCEL_AND_IGNORE;
return CheckIfShouldIgnoreNavigation(true /* is_redirect */);
}
content::NavigationThrottle::ThrottleCheckResult
InterceptNavigationThrottle::WillProcessResponse() {
DCHECK(!deferring_);
if (should_ignore_)
return content::NavigationThrottle::CANCEL_AND_IGNORE;
if (pending_checks_ > 0) {
deferring_ = true;
return content::NavigationThrottle::DEFER;
}
return content::NavigationThrottle::PROCEED;
}
const char* InterceptNavigationThrottle::GetNameForLogging() {
return "InterceptNavigationThrottle";
}
content::NavigationThrottle::ThrottleCheckResult
InterceptNavigationThrottle::CheckIfShouldIgnoreNavigation(bool is_redirect) {
if (ShouldCheckAsynchronously()) {
pending_checks_++;
ui_task_runner_->PostTask(
FROM_HERE, base::BindOnce(&InterceptNavigationThrottle::RunCheckAsync,
weak_factory_.GetWeakPtr(),
GetNavigationParams(is_redirect)));
return content::NavigationThrottle::PROCEED;
}
// No need to set |should_ignore_| since if it is true, we'll cancel the
// navigation immediately.
return should_ignore_callback_.Run(navigation_handle()->GetWebContents(),
GetNavigationParams(is_redirect))
? content::NavigationThrottle::CANCEL_AND_IGNORE
: content::NavigationThrottle::PROCEED;
// Careful, |this| can be deleted at this point.
}
void InterceptNavigationThrottle::RunCheckAsync(
const NavigationParams& params) {
DCHECK(base::FeatureList::IsEnabled(kAsyncCheck));
DCHECK_GT(pending_checks_, 0);
pending_checks_--;
bool final_deferred_check = deferring_ && pending_checks_ == 0;
auto weak_this = weak_factory_.GetWeakPtr();
bool should_ignore = should_ignore_callback_.Run(
navigation_handle()->GetWebContents(), params);
if (!weak_this)
return;
should_ignore_ |= should_ignore;
if (!final_deferred_check)
return;
if (should_ignore) {
CancelDeferredNavigation(content::NavigationThrottle::CANCEL_AND_IGNORE);
} else {
Resume();
}
}
bool InterceptNavigationThrottle::ShouldCheckAsynchronously() const {
// Do not apply the async optimization for:
// - Non-Android platforms (where the check is always fast)
// - POST navigations, to ensure we aren't violating idempotency.
// - Subframe navigations, which aren't observed on Android, and should be
// fast on other platforms.
// - non-http/s URLs, which are more likely to be intercepted.
#if defined(OS_ANDROID)
return navigation_handle()->IsInMainFrame() &&
!navigation_handle()->IsPost() &&
navigation_handle()->GetURL().SchemeIsHTTPOrHTTPS() &&
base::FeatureList::IsEnabled(kAsyncCheck);
#else
return false;
#endif
}
NavigationParams InterceptNavigationThrottle::GetNavigationParams(
bool is_redirect) const {
return NavigationParams(
navigation_handle()->GetURL(), navigation_handle()->GetReferrer(),
navigation_handle()->HasUserGesture(), navigation_handle()->IsPost(),
navigation_handle()->GetPageTransition(), is_redirect,
navigation_handle()->IsExternalProtocol(), true,
navigation_handle()->IsRendererInitiated(),
navigation_handle()->GetBaseURLForDataURL());
}
} // namespace navigation_interception