blob: 76557a6bc560d7f055cebe6a9895c443eaa8c5e0 [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 "components/blocked_content/safe_browsing_triggered_popup_blocker.h"
#include <utility>
#include "base/memory/ptr_util.h"
#include "base/metrics/histogram_macros.h"
#include "components/back_forward_cache/back_forward_cache_disable.h"
#include "components/blocked_content/pref_names.h"
#include "components/pref_registry/pref_registry_syncable.h"
#include "components/prefs/pref_service.h"
#include "components/safe_browsing/core/browser/db/util.h"
#include "components/safe_browsing/core/browser/db/v4_protocol_manager_util.h"
#include "components/subresource_filter/content/browser/safe_browsing_page_activation_throttle.h"
#include "components/user_prefs/user_prefs.h"
#include "content/public/browser/back_forward_cache.h"
#include "content/public/browser/browser_context.h"
#include "content/public/browser/frame_type.h"
#include "content/public/browser/navigation_handle.h"
#include "content/public/browser/render_frame_host.h"
#include "content/public/browser/web_contents.h"
#include "third_party/blink/public/mojom/devtools/console_message.mojom.h"
#include "third_party/blink/public/mojom/frame/frame.mojom.h"
namespace blocked_content {
namespace {
void LogAction(SafeBrowsingTriggeredPopupBlocker::Action action) {
UMA_HISTOGRAM_ENUMERATION("ContentSettings.Popups.StrongBlockerActions",
action,
SafeBrowsingTriggeredPopupBlocker::Action::kCount);
}
} // namespace
using safe_browsing::SubresourceFilterLevel;
BASE_FEATURE(kAbusiveExperienceEnforce, base::FEATURE_ENABLED_BY_DEFAULT);
SafeBrowsingTriggeredPopupBlocker::PageData::PageData(content::Page& page)
: PageUserData(page) {}
SafeBrowsingTriggeredPopupBlocker::PageData::~PageData() = default;
SafeBrowsingTriggeredPopupBlocker::NavigationHandleData::NavigationHandleData(
content::NavigationHandle&) {}
SafeBrowsingTriggeredPopupBlocker::NavigationHandleData::
~NavigationHandleData() = default;
// static
void SafeBrowsingTriggeredPopupBlocker::RegisterProfilePrefs(
user_prefs::PrefRegistrySyncable* registry) {
registry->RegisterBooleanPref(prefs::kAbusiveExperienceInterventionEnforce,
true /* default_value */);
}
// static
void SafeBrowsingTriggeredPopupBlocker::MaybeCreate(
content::WebContents* web_contents) {
if (!IsEnabled(web_contents))
return;
auto* observer_manager =
subresource_filter::SubresourceFilterObserverManager::FromWebContents(
web_contents);
if (!observer_manager)
return;
if (FromWebContents(web_contents))
return;
web_contents->SetUserData(
UserDataKey(), base::WrapUnique(new SafeBrowsingTriggeredPopupBlocker(
web_contents, observer_manager)));
}
SafeBrowsingTriggeredPopupBlocker::~SafeBrowsingTriggeredPopupBlocker() =
default;
bool SafeBrowsingTriggeredPopupBlocker::ShouldApplyAbusivePopupBlocker(
content::Page& page) {
LogAction(Action::kConsidered);
PageData& page_data = GetPageData(page);
if (!page_data.is_triggered())
return false;
if (!IsEnabled(web_contents()))
return false;
LogAction(Action::kBlocked);
page_data.inc_num_popups_blocked();
page.GetMainDocument().AddMessageToConsole(
blink::mojom::ConsoleMessageLevel::kError, kAbusiveEnforceMessage);
return true;
}
SafeBrowsingTriggeredPopupBlocker::SafeBrowsingTriggeredPopupBlocker(
content::WebContents* web_contents,
subresource_filter::SubresourceFilterObserverManager* observer_manager)
: content::WebContentsObserver(web_contents),
content::WebContentsUserData<SafeBrowsingTriggeredPopupBlocker>(
*web_contents) {
DCHECK(observer_manager);
scoped_observation_.Observe(observer_manager);
}
void SafeBrowsingTriggeredPopupBlocker::DidFinishNavigation(
content::NavigationHandle* navigation_handle) {
if (!navigation_handle->IsInMainFrame() ||
navigation_handle->GetNavigatingFrameType() ==
content::FrameType::kFencedFrameRoot) {
return;
}
std::optional<SubresourceFilterLevel> level;
NavigationHandleData* data =
NavigationHandleData::GetOrCreateForNavigationHandle(*navigation_handle);
data->level_for_next_committed_navigation().swap(level);
// Only care about main frame navigations that commit.
if (!navigation_handle->HasCommitted() ||
navigation_handle->IsSameDocument()) {
return;
}
if (navigation_handle->IsErrorPage())
return;
// Log a warning only if we've matched a warn-only safe browsing list.
if (level == SubresourceFilterLevel::ENFORCE) {
GetPageData(navigation_handle->GetRenderFrameHost()->GetPage())
.set_is_triggered(true);
LogAction(Action::kEnforcedSite);
// When a page is restored from back-forward cache, we don't get
// OnSafeBrowsingChecksComplete callback, so |level| will always
// be empty.
// To work around this, we disable back-forward cache if the original
// page load had abusive enforcement - this means that not doing checks on
// back-forward navigation is fine as it's guaranteed that
// the original page load didn't have enforcement.
// Note that it's possible for the safe browsing list to update while
// the page is in the cache, the risk of this is mininal due to
// having a time limit for how long pages are allowed to be in the
// cache.
content::BackForwardCache::DisableForRenderFrameHost(
navigation_handle->GetRenderFrameHost(),
back_forward_cache::DisabledReason(
back_forward_cache::DisabledReasonId::
kSafeBrowsingTriggeredPopupBlocker));
} else if (level == SubresourceFilterLevel::WARN) {
navigation_handle->GetRenderFrameHost()->AddMessageToConsole(
blink::mojom::ConsoleMessageLevel::kWarning, kAbusiveWarnMessage);
LogAction(Action::kWarningSite);
}
LogAction(Action::kNavigation);
}
// This method will always be called before the DidFinishNavigation associated
// with this handle.
// The exception is a navigation restoring a page from back-forward cache --
// in that case don't issue any requests, therefore we don't get any
// safe browsing callbacks. See the comment above for the mitigation.
void SafeBrowsingTriggeredPopupBlocker::OnSafeBrowsingChecksComplete(
content::NavigationHandle* navigation_handle,
const subresource_filter::SubresourceFilterSafeBrowsingClient::CheckResult&
result) {
DCHECK(navigation_handle->IsInMainFrame());
// TODO(crbug.com/40202987): Replace it with DCHECK.
if (navigation_handle->GetNavigatingFrameType() ==
content::FrameType::kFencedFrameRoot) {
return;
}
std::optional<safe_browsing::SubresourceFilterLevel> match_level;
if (result.threat_type ==
safe_browsing::SBThreatType::SB_THREAT_TYPE_SUBRESOURCE_FILTER) {
auto abusive = result.threat_metadata.subresource_filter_match.find(
safe_browsing::SubresourceFilterType::ABUSIVE);
if (abusive != result.threat_metadata.subresource_filter_match.end())
match_level = abusive->second;
}
if (match_level.has_value()) {
NavigationHandleData* data =
NavigationHandleData::GetOrCreateForNavigationHandle(
*navigation_handle);
data->level_for_next_committed_navigation() = match_level;
}
}
void SafeBrowsingTriggeredPopupBlocker::OnSubresourceFilterGoingAway() {
DCHECK(scoped_observation_.IsObserving());
scoped_observation_.Reset();
}
bool SafeBrowsingTriggeredPopupBlocker::IsEnabled(
content::WebContents* web_contents) {
// If feature is disabled, return false. This is done so that if the feature
// is broken it can be disabled irrespective of the policy.
if (!base::FeatureList::IsEnabled(kAbusiveExperienceEnforce))
return false;
// If enterprise policy is not set, this will return true which is the default
// preference value.
return user_prefs::UserPrefs::Get(web_contents->GetBrowserContext())
->GetBoolean(prefs::kAbusiveExperienceInterventionEnforce);
}
SafeBrowsingTriggeredPopupBlocker::PageData&
SafeBrowsingTriggeredPopupBlocker::GetPageData(content::Page& page) {
return *PageData::GetOrCreateForPage(page);
}
PAGE_USER_DATA_KEY_IMPL(SafeBrowsingTriggeredPopupBlocker::PageData);
NAVIGATION_HANDLE_USER_DATA_KEY_IMPL(
SafeBrowsingTriggeredPopupBlocker::NavigationHandleData);
WEB_CONTENTS_USER_DATA_KEY_IMPL(SafeBrowsingTriggeredPopupBlocker);
} // namespace blocked_content