blob: 67b1b666bfa0a8102f997d920d8c5aec1e237568 [file] [log] [blame]
// Copyright 2017 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/ui/blocked_content/tab_under_navigation_throttle.h"
#include <cmath>
#include <string>
#include <utility>
#include "base/check.h"
#include "base/functional/bind.h"
#include "base/functional/callback.h"
#include "base/memory/ptr_util.h"
#include "base/strings/stringprintf.h"
#include "build/build_config.h"
#include "chrome/browser/content_settings/host_content_settings_map_factory.h"
#include "chrome/browser/profiles/profile.h"
#include "chrome/common/pref_names.h"
#include "components/blocked_content/popup_opener_tab_helper.h"
#include "components/content_settings/core/browser/host_content_settings_map.h"
#include "components/content_settings/core/common/content_settings.h"
#include "components/content_settings/core/common/content_settings_types.h"
#include "content/public/browser/browser_context.h"
#include "content/public/browser/navigation_handle.h"
#include "content/public/browser/visibility.h"
#include "content/public/browser/web_contents.h"
#include "content/public/browser/web_contents_delegate.h"
#include "extensions/buildflags/buildflags.h"
#include "net/base/registry_controlled_domains/registry_controlled_domain.h"
#include "services/metrics/public/cpp/ukm_builders.h"
#include "services/metrics/public/cpp/ukm_recorder.h"
#include "services/metrics/public/cpp/ukm_source_id.h"
#include "third_party/blink/public/mojom/devtools/console_message.mojom.h"
#include "url/gurl.h"
#if BUILDFLAG(IS_ANDROID)
#include "chrome/browser/android/framebust_intervention/framebust_blocked_delegate_android.h"
#else
#include "chrome/browser/ui/blocked_content/framebust_block_tab_helper.h"
#endif
#if BUILDFLAG(ENABLE_EXTENSIONS)
#include "extensions/common/constants.h"
#endif
namespace {
void LogTabUnderAttempt(content::NavigationHandle* handle) {
// The source id should generally be set, except for very rare circumstances
// where the popup opener tab helper is not observing at the time the
// previous navigation commit.
ukm::UkmRecorder* ukm_recorder = ukm::UkmRecorder::Get();
ukm::SourceId opener_source_id =
handle->GetWebContents()->GetPrimaryMainFrame()->GetPageUkmSourceId();
if (opener_source_id != ukm::kInvalidSourceId && ukm_recorder) {
ukm::builders::AbusiveExperienceHeuristic_TabUnder(opener_source_id)
.SetDidTabUnder(true)
.Record(ukm_recorder);
}
}
} // namespace
// static
void TabUnderNavigationThrottle::MaybeCreateAndAdd(
content::NavigationThrottleRegistry& registry) {
// TODO(crbug.com/40187173): TabUnderNavigationThrottle doesn't block
// prerendering activations. However, currently prerender is same-origin only
// so a prerendered activation could never be classified as a tab-under.
// Otherwise, it should be safe to avoid creating a throttle in non primary
// pages because prerendered pages should not be able to open popups. A
// tab-under could therefore never occur within the non-primary page.
if (registry.GetNavigationHandle().IsInPrimaryMainFrame()) {
registry.AddThrottle(
base::WrapUnique(new TabUnderNavigationThrottle(registry)));
}
}
TabUnderNavigationThrottle::~TabUnderNavigationThrottle() = default;
TabUnderNavigationThrottle::TabUnderNavigationThrottle(
content::NavigationThrottleRegistry& registry)
: content::NavigationThrottle(registry),
has_opened_popup_since_last_user_gesture_at_start_(
HasOpenedPopupSinceLastUserGesture()),
started_in_foreground_(
registry.GetNavigationHandle().GetWebContents()->GetVisibility() ==
content::Visibility::VISIBLE) {}
bool TabUnderNavigationThrottle::IsSuspiciousClientRedirect() const {
// This throttle is only created for primary main frame navigations. See
// MaybeCreate().
DCHECK(navigation_handle()->IsInPrimaryMainFrame());
DCHECK(!navigation_handle()->HasCommitted());
// Some browser initiated navigations have HasUserGesture set to false. This
// should eventually be fixed in crbug.com/617904. In the meantime, just dont
// block browser initiated ones.
if (started_in_foreground_ || navigation_handle()->HasUserGesture() ||
!navigation_handle()->IsRendererInitiated()) {
return false;
}
// An empty previous URL indicates this was the first load. We filter these
// out because we're primarily interested in sites which navigate themselves
// away while in the background.
content::WebContents* contents = navigation_handle()->GetWebContents();
const GURL& previous_main_frame_url = contents->GetLastCommittedURL();
if (previous_main_frame_url.is_empty()) {
return false;
}
// Same-site navigations are exempt from tab-under protection.
const GURL& target_url = navigation_handle()->GetURL();
if (net::registry_controlled_domains::SameDomainOrHost(
previous_main_frame_url, target_url,
net::registry_controlled_domains::INCLUDE_PRIVATE_REGISTRIES)) {
return false;
}
#if BUILDFLAG(ENABLE_EXTENSIONS)
// Exempt navigating to or from extension URLs, as they will redirect pages in
// the background. By exempting in both directions, extensions can always
// round-trip a page through an extension URL in order to perform arbitrary
// redirections with content scripts.
if (target_url.SchemeIs(extensions::kExtensionScheme) ||
previous_main_frame_url.SchemeIs(extensions::kExtensionScheme)) {
return false;
}
#endif
return true;
}
content::NavigationThrottle::ThrottleCheckResult
TabUnderNavigationThrottle::MaybeBlockNavigation() {
if (seen_tab_under_ || !has_opened_popup_since_last_user_gesture_at_start_ ||
!IsSuspiciousClientRedirect()) {
return content::NavigationThrottle::PROCEED;
}
seen_tab_under_ = true;
LogTabUnderAttempt(navigation_handle());
// We unconditionally proceed. There used to be a tab-under blocking
// experiment, but it never launched.
return content::NavigationThrottle::PROCEED;
}
void TabUnderNavigationThrottle::ShowUI() {
content::WebContents* web_contents = navigation_handle()->GetWebContents();
const GURL& url = navigation_handle()->GetURL();
#if BUILDFLAG(IS_ANDROID)
blocked_content::FramebustBlockedMessageDelegate::CreateForWebContents(
web_contents);
blocked_content::FramebustBlockedMessageDelegate*
framebust_blocked_message_delegate =
blocked_content::FramebustBlockedMessageDelegate::FromWebContents(
web_contents);
framebust_blocked_message_delegate->ShowMessage(
url,
HostContentSettingsMapFactory::GetForProfile(
web_contents->GetBrowserContext()),
base::NullCallback());
#else
if (auto* tab_helper =
FramebustBlockTabHelper::FromWebContents(web_contents)) {
tab_helper->AddBlockedUrl(url, base::NullCallback());
}
#endif
}
bool TabUnderNavigationThrottle::HasOpenedPopupSinceLastUserGesture() const {
content::WebContents* contents = navigation_handle()->GetWebContents();
auto* popup_opener =
blocked_content::PopupOpenerTabHelper::FromWebContents(contents);
return popup_opener &&
popup_opener->has_opened_popup_since_last_user_gesture();
}
content::NavigationThrottle::ThrottleCheckResult
TabUnderNavigationThrottle::WillStartRequest() {
return MaybeBlockNavigation();
}
content::NavigationThrottle::ThrottleCheckResult
TabUnderNavigationThrottle::WillRedirectRequest() {
return MaybeBlockNavigation();
}
const char* TabUnderNavigationThrottle::GetNameForLogging() {
return "TabUnderNavigationThrottle";
}