blob: 9569c89b252a650fc7392ea76e41f92872915700 [file] [log] [blame]
// Copyright 2019 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/content_settings/browser/ui/cookie_controls_controller.h"
#include <memory>
#include <string>
#include "base/feature_list.h"
#include "base/functional/bind.h"
#include "base/json/values_util.h"
#include "base/metrics/histogram_functions.h"
#include "base/metrics/user_metrics.h"
#include "base/metrics/user_metrics_action.h"
#include "base/observer_list.h"
#include "components/browsing_data/content/browsing_data_helper.h"
#include "components/content_settings/browser/page_specific_content_settings.h"
#include "components/content_settings/browser/ui/cookie_controls_view.h"
#include "components/content_settings/core/browser/content_settings_utils.h"
#include "components/content_settings/core/browser/cookie_settings.h"
#include "components/content_settings/core/common/content_settings.h"
#include "components/content_settings/core/common/content_settings_pattern.h"
#include "components/content_settings/core/common/content_settings_utils.h"
#include "components/content_settings/core/common/cookie_blocking_3pcd_status.h"
#include "components/content_settings/core/common/cookie_controls_enforcement.h"
#include "components/content_settings/core/common/cookie_controls_state.h"
#include "components/content_settings/core/common/features.h"
#include "components/content_settings/core/common/pref_names.h"
#include "components/content_settings/core/common/third_party_site_data_access_type.h"
#include "components/fingerprinting_protection_filter/browser/fingerprinting_protection_web_contents_helper.h"
#include "components/ip_protection/common/ip_protection_status.h"
#include "components/ip_protection/common/ip_protection_status_observer.h"
#include "components/prefs/pref_service.h"
#include "components/privacy_sandbox/privacy_sandbox_features.h"
#include "components/privacy_sandbox/tracking_protection_settings.h"
#include "components/site_engagement/content/site_engagement_service.h"
#include "components/strings/grit/privacy_sandbox_strings.h"
#include "content/public/browser/browser_context.h"
#include "content/public/browser/browser_thread.h"
#include "content/public/browser/reload_type.h"
#include "content/public/browser/web_contents.h"
#include "content/public/common/url_constants.h"
#include "net/cookies/site_for_cookies.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 "ui/base/l10n/l10n_util.h"
namespace {
using ::base::UserMetricsAction;
using ::site_engagement::SiteEngagementService;
constexpr char kEntryPointAnimatedKey[] = "entry_point_animated";
constexpr char kLastExpirationKey[] = "last_expiration";
constexpr char kLastVisitedActiveException[] = "last_visited_active_exception";
constexpr char kActivationsCountKey[] = "activations_count_key";
base::Value::Dict GetMetadata(HostContentSettingsMap* settings_map,
const GURL& url) {
base::Value stored_value = settings_map->GetWebsiteSetting(
url, url, ContentSettingsType::COOKIE_CONTROLS_METADATA);
if (!stored_value.is_dict()) {
return base::Value::Dict();
}
return std::move(stored_value.GetDict());
}
bool WasEntryPointAlreadyAnimated(const base::Value::Dict& metadata) {
std::optional<bool> entry_point_animated =
metadata.FindBool(kEntryPointAnimatedKey);
return entry_point_animated.has_value() && entry_point_animated.value();
}
int GetActivationCount(const base::Value::Dict& metadata) {
return metadata.FindInt(kActivationsCountKey).value_or(0);
}
bool HasExceptionExpiredSinceLastVisit(const base::Value::Dict& metadata) {
auto last_expiration = base::ValueToTime(metadata.Find(kLastExpirationKey))
.value_or(base::Time());
auto last_visited =
base::ValueToTime(metadata.Find(kLastVisitedActiveException))
.value_or(base::Time());
return !last_expiration.is_null() // Exception should have an expiration,
&& last_expiration < base::Time::Now() // that has already expired,
&& !last_visited.is_null() // from a previous visit,
&& last_visited < last_expiration; // with no visit since.
}
void ApplyMetadataChanges(HostContentSettingsMap* settings_map,
const GURL& url,
base::Value::Dict&& dict) {
settings_map->SetWebsiteSettingDefaultScope(
url, url, ContentSettingsType::COOKIE_CONTROLS_METADATA,
base::Value(std::move(dict)));
}
ThirdPartySiteDataAccessType GetSiteDataAccessType(int allowed_sites,
int blocked_sites) {
if (blocked_sites > 0) {
return ThirdPartySiteDataAccessType::kAnyBlockedThirdPartySiteAccesses;
}
if (allowed_sites > 0) {
return ThirdPartySiteDataAccessType::kAnyAllowedThirdPartySiteAccesses;
}
return ThirdPartySiteDataAccessType::kNoThirdPartySiteAccesses;
}
} // namespace
namespace content_settings {
CookieControlsController::CookieControlsController(
scoped_refptr<CookieSettings> cookie_settings,
scoped_refptr<CookieSettings> original_cookie_settings,
HostContentSettingsMap* settings_map,
privacy_sandbox::TrackingProtectionSettings* tracking_protection_settings,
bool is_incognito_profile)
: cookie_settings_(cookie_settings),
original_cookie_settings_(original_cookie_settings),
settings_map_(settings_map),
tracking_protection_settings_(tracking_protection_settings),
is_incognito_profile_(is_incognito_profile) {
CHECK(cookie_settings_);
CHECK(tracking_protection_settings_);
cookie_observation_.Observe(cookie_settings_.get());
}
CookieControlsController::Status::Status(
CookieControlsState controls_state,
CookieControlsEnforcement enforcement,
CookieBlocking3pcdStatus blocking_status,
base::Time expiration)
: controls_state(controls_state),
enforcement(enforcement),
blocking_status(blocking_status),
expiration(expiration) {}
CookieControlsController::Status::~Status() = default;
CookieControlsController::~CookieControlsController() = default;
void CookieControlsController::OnUiClosing() {
auto* web_contents = GetWebContents();
if (should_reload_ && web_contents && !web_contents->IsBeingDestroyed()) {
web_contents->GetController().Reload(content::ReloadType::NORMAL, true);
}
should_reload_ = false;
}
void CookieControlsController::Update(content::WebContents* web_contents) {
DCHECK(web_contents);
if (!tab_observer_ || GetWebContents() != web_contents) {
tab_observer_ = std::make_unique<TabObserver>(this, web_contents);
SetStateChangedViaBypass(false);
}
if (observers_.empty()) {
return;
}
auto status = GetStatus(web_contents);
const bool icon_visible =
ShouldUserBypassIconBeVisible(status.controls_state);
const bool should_highlight =
ShouldHighlightUserBypass(status.controls_state);
for (auto& observer : observers_) {
observer.OnStatusChanged(status.controls_state, status.enforcement,
status.blocking_status, status.expiration);
observer.OnCookieControlsIconStatusChanged(
icon_visible, status.controls_state, status.blocking_status,
should_highlight);
}
}
void CookieControlsController::OnSubresourceBlocked() {
// When a subresource is blocked by fingerprinting protection,
// `UpdateUserBypass` will show the User Bypass.
UpdateUserBypass();
}
void CookieControlsController::OnFirstSubresourceProxiedOnCurrentPrimaryPage() {
UpdateUserBypass();
}
CookieControlsController::Status CookieControlsController::GetStatus(
content::WebContents* web_contents) {
if (!cookie_settings_->ShouldBlockThirdPartyCookies()) {
return {CookieControlsState::kHidden,
CookieControlsEnforcement::kNoEnforcement,
CookieBlocking3pcdStatus::kNotIn3pcd, base::Time()};
}
const GURL& url = web_contents->GetLastCommittedURL();
if (url.SchemeIs(content::kChromeUIScheme) ||
url.SchemeIs(kExtensionScheme)) {
return {CookieControlsState::kHidden,
CookieControlsEnforcement::kNoEnforcement,
CookieBlocking3pcdStatus::kNotIn3pcd, base::Time()};
}
auto blocking_status = CookieBlocking3pcdStatus::kNotIn3pcd;
if (cookie_settings_->AreThirdPartyCookiesLimited()) {
blocking_status = CookieBlocking3pcdStatus::kLimited;
} else if (tracking_protection_settings_->AreAllThirdPartyCookiesBlocked()) {
blocking_status = CookieBlocking3pcdStatus::kAll;
}
SettingInfo info;
bool cookies_allowed =
cookie_settings_->IsThirdPartyAccessAllowed(url, &info);
CookieControlsEnforcement enforcement =
GetEnforcementForThirdPartyCookieBlocking(blocking_status, url, info,
cookies_allowed);
CookieControlsState controls_state;
if (enforcement == CookieControlsEnforcement::kEnforcedByTpcdGrant) {
controls_state = CookieControlsState::kHidden;
} else if (ShowActFeatures()) {
controls_state =
tracking_protection_settings_->HasTrackingProtectionException(url,
&info)
? CookieControlsState::kPausedTp
: CookieControlsState::kActiveTp;
} else {
controls_state = cookies_allowed ? CookieControlsState::kAllowed3pc
: CookieControlsState::kBlocked3pc;
}
return {controls_state, enforcement, blocking_status,
info.metadata.expiration()};
}
void CookieControlsController::RecordActMetrics(bool enable_protections) {
if (GetIsSubresourceBlocked()) {
base::RecordAction(UserMetricsAction(
enable_protections
? "TrackingProtections.Bubble.FppActive.EnableProtections"
: "TrackingProtections.Bubble.FppActive.DisableProtections"));
}
if (GetIsSubresourceProxied()) {
base::RecordAction(UserMetricsAction(
enable_protections
? "TrackingProtections.Bubble.IppActive.EnableProtections"
: "TrackingProtections.Bubble.IppActive.DisableProtections"));
}
}
bool CookieControlsController::ShowActFeatures() {
return is_incognito_profile_ &&
(tracking_protection_settings_->IsIpProtectionEnabled() ||
tracking_protection_settings_->IsFpProtectionEnabled()) &&
base::FeatureList::IsEnabled(privacy_sandbox::kActUserBypassUx);
}
CookieControlsEnforcement
CookieControlsController::GetEnforcementForThirdPartyCookieBlocking(
CookieBlocking3pcdStatus status,
const GURL url,
const SettingInfo& info,
bool cookies_allowed) {
const bool is_default_setting =
info.primary_pattern == ContentSettingsPattern::Wildcard() &&
info.secondary_pattern == ContentSettingsPattern::Wildcard();
// The UI can reset only host-scoped (without wildcards in the domain) or
// site-scoped exceptions.
const bool host_or_site_scoped_exception =
!info.secondary_pattern.HasDomainWildcard() ||
info.secondary_pattern ==
ContentSettingsPattern::FromURLToSchemefulSitePattern(url);
// Rules from regular mode can't be temporarily overridden in off the record
// profiles.
bool exception_exists_in_regular_profile = false;
if (cookies_allowed && original_cookie_settings_) {
SettingInfo original_info;
original_cookie_settings_->IsThirdPartyAccessAllowed(url, &original_info);
exception_exists_in_regular_profile =
original_info.primary_pattern != ContentSettingsPattern::Wildcard() ||
original_info.secondary_pattern != ContentSettingsPattern::Wildcard();
}
if (info.source == SettingSource::kTpcdGrant &&
status == CookieBlocking3pcdStatus::kLimited) {
return CookieControlsEnforcement::kEnforcedByTpcdGrant;
} else if (info.source == SettingSource::kPolicy) {
return CookieControlsEnforcement::kEnforcedByPolicy;
} else if (info.source == SettingSource::kExtension) {
return CookieControlsEnforcement::kEnforcedByExtension;
} else if (exception_exists_in_regular_profile ||
(!is_default_setting && !host_or_site_scoped_exception)) {
// If the exception cannot be reset in-context because of the nature of the
// setting, display as managed by setting.
return CookieControlsEnforcement::kEnforcedByCookieSetting;
} else {
return CookieControlsEnforcement::kNoEnforcement;
}
}
bool CookieControlsController::HasOriginSandboxedTopLevelDocument() const {
content::RenderFrameHost* rfh = GetWebContents()->GetPrimaryMainFrame();
// If the WebContents has not fully initialized the RenderFrameHost yet.
// TODO(crbug.com/346386726): Remove the HasPolicyContainerHost() call once
// RenderFrameHost initialization order is fixed.
if (!rfh || !rfh->HasPolicyContainerHost()) {
// In that case, we fall back on assuming it is not sandboxed.
// Since this is only for determining whether to render the User Bypass
// icon this fallback is acceptable.
return false;
}
return rfh->IsSandboxed(network::mojom::WebSandboxFlags::kOrigin);
}
void CookieControlsController::OnTrackingProtectionsChangedForSite() {
const GURL& url = GetWebContents()->GetLastCommittedURL();
bool reenable_protections =
tracking_protection_settings_->HasTrackingProtectionException(url);
if (reenable_protections) {
tracking_protection_settings_->RemoveTrackingProtectionException(url);
} else {
tracking_protection_settings_->AddTrackingProtectionException(url);
}
OnCookieBlockingEnabledForSite(
/*block_third_party_cookies=*/reenable_protections);
RecordActMetrics(reenable_protections);
}
void CookieControlsController::OnCookieBlockingEnabledForSite(
bool block_third_party_cookies) {
const GURL& url = GetWebContents()->GetLastCommittedURL();
should_reload_ = true;
if (block_third_party_cookies) {
base::RecordAction(UserMetricsAction("CookieControls.Bubble.TurnOn"));
cookie_settings_->ResetThirdPartyCookieSetting(url);
return;
}
CHECK(!block_third_party_cookies);
base::RecordAction(UserMetricsAction("CookieControls.Bubble.TurnOff"));
cookie_settings_->SetCookieSettingForUserBypass(url);
// Record expiration metadata for the newly created exception, and increased
// the activation count.
base::Value::Dict metadata = GetMetadata(settings_map_, url);
metadata.Set(kLastExpirationKey,
base::TimeToValue(GetStatus(GetWebContents()).expiration));
metadata.Set(kActivationsCountKey, GetActivationCount(metadata) + 1);
ApplyMetadataChanges(settings_map_, url, std::move(metadata));
RecordActivationMetrics();
}
void CookieControlsController::OnEntryPointAnimated() {
// sanity check if WebContents was instantiated (update method called before)
// TODO(b/341972754): refactor this to be handled properly via update method
// for all Android corner cases.
if (GetWebContents() == nullptr) {
return;
}
const GURL& url = GetWebContents()->GetLastCommittedURL();
base::Value::Dict metadata = GetMetadata(settings_map_, url);
metadata.Set(kEntryPointAnimatedKey, base::Value(true));
ApplyMetadataChanges(settings_map_, url, std::move(metadata));
}
bool CookieControlsController::StateChangedViaBypass() {
return user_changed_ub_state_;
}
void CookieControlsController::SetStateChangedViaBypass(bool changed) {
// Avoid a toggle back and forth being marked as "changed".
user_changed_ub_state_ = changed && !user_changed_ub_state_;
}
int CookieControlsController::GetAllowedThirdPartyCookiesSitesCount() const {
auto* pscs = content_settings::PageSpecificContentSettings::GetForPage(
GetWebContents()->GetPrimaryPage());
if (!pscs) {
return 0;
}
return browsing_data::GetUniqueThirdPartyCookiesHostCount(
GetWebContents()->GetLastCommittedURL(),
*(pscs->allowed_browsing_data_model()));
}
int CookieControlsController::GetBlockedThirdPartyCookiesSitesCount() const {
auto* pscs = content_settings::PageSpecificContentSettings::GetForPage(
GetWebContents()->GetPrimaryPage());
if (!pscs) {
return 0;
}
return browsing_data::GetUniqueThirdPartyCookiesHostCount(
GetWebContents()->GetLastCommittedURL(),
*(pscs->blocked_browsing_data_model()));
}
int CookieControlsController::GetStatefulBounceCount() const {
auto* pscs = content_settings::PageSpecificContentSettings::GetForPage(
GetWebContents()->GetPrimaryPage());
if (pscs) {
return pscs->stateful_bounce_count();
} else {
return 0;
}
}
bool CookieControlsController::GetIsSubresourceBlocked() const {
// Check WebContents are valid. A possible race condition on Android causes
// this to be called before WebContents are instantiated.
if (GetWebContents() == nullptr) {
return false;
}
auto* fpf_web_contents_helper = fingerprinting_protection_filter::
FingerprintingProtectionWebContentsHelper::FromWebContents(
GetWebContents());
return fpf_web_contents_helper != nullptr &&
fpf_web_contents_helper->subresource_blocked_in_current_primary_page();
}
bool CookieControlsController::GetIsSubresourceProxied() const {
// Check WebContents are valid. A possible race condition on Android causes
// this to be called before WebContents are instantiated.
if (GetWebContents() == nullptr) {
return false;
}
auto* ip_protection_status =
ip_protection::IpProtectionStatus::FromWebContents(GetWebContents());
return ip_protection_status != nullptr &&
ip_protection_status->IsSubresourceProxiedOnCurrentPrimaryPage();
}
void CookieControlsController::UpdateUserBypass() {
if (observers_.empty()) {
return;
}
auto status = GetStatus(GetWebContents());
const bool icon_visible =
ShouldUserBypassIconBeVisible(status.controls_state);
const bool should_highlight =
ShouldHighlightUserBypass(status.controls_state);
for (auto& observer : observers_) {
observer.OnCookieControlsIconStatusChanged(
icon_visible, status.controls_state, status.blocking_status,
should_highlight);
}
}
void CookieControlsController::UpdateLastVisitedSitesMap() {
// Cache whether the expiration has expired since last visit before updating
// the last visited metadata.
const GURL& url = GetWebContents()->GetLastCommittedURL();
has_exception_expired_since_last_visit_ =
HasExceptionExpiredSinceLastVisit(GetMetadata(settings_map_, url));
// We only care about visits with active expirations, if there is an active
// exception, update the last visited time, otherwise clear it.
base::Value::Dict metadata = GetMetadata(settings_map_, url);
auto status = GetStatus(GetWebContents());
if (status.controls_state == CookieControlsState::kAllowed3pc) {
metadata.Set(kLastVisitedActiveException,
base::TimeToValue(base::Time::Now()));
} else {
metadata.Remove(kLastVisitedActiveException);
}
ApplyMetadataChanges(settings_map_, url, std::move(metadata));
}
void CookieControlsController::UpdatePageReloadStatus(
int recent_reloads_count) {
if (StateChangedViaBypass() && recent_reloads_count > 0) {
waiting_for_page_load_finish_ = true;
}
SetStateChangedViaBypass(false);
recent_reloads_count_ = recent_reloads_count;
if (recent_reloads_count_ >= features::kUserBypassUIReloadCount.Get()) {
for (auto& observer : observers_) {
observer.OnReloadThresholdExceeded();
}
}
}
void CookieControlsController::OnPageFinishedLoading() {
if (!waiting_for_page_load_finish_) {
return;
}
waiting_for_page_load_finish_ = false;
for (auto& observer : observers_) {
observer.OnFinishedPageReloadWithChangedSettings();
}
}
void CookieControlsController::OnThirdPartyCookieBlockingChanged(
bool block_third_party_cookies) {
if (GetWebContents()) {
Update(GetWebContents());
}
}
void CookieControlsController::OnCookieSettingChanged() {
if (GetWebContents()) {
Update(GetWebContents());
}
}
content::WebContents* CookieControlsController::GetWebContents() const {
if (!tab_observer_) {
return nullptr;
}
return tab_observer_->content::WebContentsObserver::web_contents();
}
void CookieControlsController::AddObserver(CookieControlsObserver* obs) {
observers_.AddObserver(obs);
}
void CookieControlsController::RemoveObserver(CookieControlsObserver* obs) {
observers_.RemoveObserver(obs);
}
double CookieControlsController::GetSiteEngagementScore() {
auto* web_contents = GetWebContents();
return SiteEngagementService::Get(web_contents->GetBrowserContext())
->GetScore(web_contents->GetVisibleURL());
}
void CookieControlsController::RecordActivationMetrics() {
const GURL& url = GetWebContents()->GetLastCommittedURL();
// Metrics, related to confidence signals:
// TODO(crbug.com/40064612): Add CookieControlsActivated.FedCmInitiated
base::UmaHistogramBoolean(
"Privacy.CookieControlsActivated.SaaRequested",
cookie_settings_->HasAnyFrameRequestedStorageAccess(url));
base::UmaHistogramCounts100(
"Privacy.CookieControlsActivated.PageRefreshCount",
recent_reloads_count_);
base::UmaHistogramExactLinear(
"Privacy.CookieControlsActivated.SiteEngagementScore",
GetSiteEngagementScore(), 100);
auto site_data_access_type =
GetSiteDataAccessType(GetAllowedThirdPartyCookiesSitesCount(),
GetBlockedThirdPartyCookiesSitesCount());
base::UmaHistogramEnumeration(
"Privacy.CookieControlsActivated.SiteDataAccessType",
site_data_access_type);
// Record activation UKM.
// TODO(crbug.com/40064612): Include FedCM information.
DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
auto ukm_source_id =
GetWebContents()->GetPrimaryMainFrame()->GetPageUkmSourceId();
ukm::builders::ThirdPartyCookies_CookieControlsActivated(ukm_source_id)
.SetFedCmInitiated(false)
.SetStorageAccessAPIRequested(
cookie_settings_->HasAnyFrameRequestedStorageAccess(url))
.SetPageRefreshCount(std::clamp(recent_reloads_count_, 0, 10))
.SetRepeatedActivation(
GetActivationCount(GetMetadata(settings_map_, url)) > 1)
.SetSiteEngagementLevel(static_cast<uint64_t>(
SiteEngagementService::Get(GetWebContents()->GetBrowserContext())
->GetEngagementLevel(url)))
.SetThirdPartySiteDataAccessType(
static_cast<uint64_t>(site_data_access_type))
.Record(ukm::UkmRecorder::Get());
// TODO(crbug.com/40064612): Add metrics, related to repeated activations.
}
bool CookieControlsController::ShouldHighlightUserBypass(
CookieControlsState controls_state) {
// Highlighting is meant to draw attention to bypassing, so just return if
// bypass has already happened.
if (controls_state == CookieControlsState::kHidden ||
controls_state == CookieControlsState::kAllowed3pc) {
return false;
}
auto* web_contents = GetWebContents();
// We don't want to show UI animation, and IPH in this case as we can't
// persist their usage cross-session. This puts us at high risk of
// over-triggering noisy UI and annoying users.
if (web_contents->GetBrowserContext()->IsOffTheRecord()) {
return false;
}
// TODO(crbug.com/40064612): Check if FedCM was requested.
const GURL& url = web_contents->GetLastCommittedURL();
if (cookie_settings_->HasAnyFrameRequestedStorageAccess(url)) {
return false;
}
// If the user is returning to the site after their previous exception has
// expired, highlight user bypass. The order of this check is important,
// as the site may now be using SAA / FedCM instead of relying on 3PC. It
// should also come before any check for whether the entrypoint was already
// animated.
if (has_exception_expired_since_last_visit_) {
return true;
}
// Check if the entry point was already animated for the site.
if (WasEntryPointAlreadyAnimated(GetMetadata(settings_map_, url))) {
return false;
}
if (recent_reloads_count_ >= features::kUserBypassUIReloadCount.Get()) {
return true;
}
if (SiteEngagementService::IsEngagementAtLeast(
GetSiteEngagementScore(), blink::mojom::EngagementLevel::HIGH)) {
return true;
}
return false;
}
bool CookieControlsController::ShouldUserBypassIconBeVisible(
CookieControlsState controls_state) {
if (controls_state == CookieControlsState::kHidden) {
return false;
}
// 3PCD prevents SameSite=None cookies from being sent when the top-level
// document is sandboxed without `allow-origin`. For instance when loaded
// with: `Content-Security-Policy: sandbox`. In that case, we render the UI to
// allow the user to opt into sending SameSite=None cookies again in those
// contexts.
return HasOriginSandboxedTopLevelDocument() ||
controls_state == CookieControlsState::kAllowed3pc ||
controls_state == CookieControlsState::kPausedTp ||
// If no 3P sites have attempted to access site data, nor were any
// stateful bounces recorded, the icon should not be displayed. Take
// into account both allow and blocked counts, since the breakage might
// be related to storage partitioning. Partitioned site will be allowed
// to access partitioned storage.
SiteDataAccessAttempted() || GetIsSubresourceBlocked() ||
GetIsSubresourceProxied();
}
bool CookieControlsController::SiteDataAccessAttempted() {
return GetStatefulBounceCount() || GetAllowedThirdPartyCookiesSitesCount() ||
GetBlockedThirdPartyCookiesSitesCount();
}
CookieControlsController::TabObserver::TabObserver(
CookieControlsController* cookie_controls,
content::WebContents* web_contents)
: content_settings::PageSpecificContentSettings::SiteDataObserver(
web_contents),
content::WebContentsObserver(web_contents),
cookie_controls_(cookie_controls) {
last_visited_url_ =
content::WebContentsObserver::web_contents()->GetVisibleURL();
auto* fpf_web_contents_helper = fingerprinting_protection_filter::
FingerprintingProtectionWebContentsHelper::FromWebContents(web_contents);
if (fpf_web_contents_helper) {
fpf_observation_.Observe(fpf_web_contents_helper);
}
auto* ip_protection_status =
ip_protection::IpProtectionStatus::FromWebContents(web_contents);
if (ip_protection_status) {
ip_protection_observation_.Observe(ip_protection_status);
}
}
CookieControlsController::TabObserver::~TabObserver() = default;
void CookieControlsController::TabObserver::WebContentsDestroyed() {
fpf_observation_.Reset();
ip_protection_observation_.Reset();
}
void CookieControlsController::TabObserver::OnSiteDataAccessed(
const AccessDetails& access_details) {
if (access_details.site_data_type != SiteDataType::kCookies) {
cookie_controls_->UpdateUserBypass();
return;
}
// When User Bypass is enabled, a large number of string comparisons are
// performed to determine what sites are 3P / 1P. Cookie accesses are
// reported _very_ frequently as many sites are always reading or writing to
// the same cookie, and there is no caching of these accesses anywhere before
// here (in constrast to JS storage, which does cache accesses earlier).
// A simple cache of cookie accesses is used here to limit the number of
// repeated updates.
// We can't cache all types of accesses here, because the `site_data_type` is
// not always populated with sufficient granularity (often aliasing to
// kUnknown). This is relevant as some daya types may impact the block 3P
// count, while others may not.
// TODO(crbug.com/40205603): Replace the SiteDataType with the Browsing Data
// Model's StorageType, which would let us remove an enum, and let us cache
// all accesses here.
if (cookie_accessed_set_.count(access_details)) {
return;
}
cookie_accessed_set_.insert(access_details);
cookie_controls_->UpdateUserBypass();
}
void CookieControlsController::TabObserver::OnStatefulBounceDetected() {
cookie_controls_->UpdateUserBypass();
}
void CookieControlsController::TabObserver::OnSubresourceBlocked() {
cookie_controls_->OnSubresourceBlocked();
}
void CookieControlsController::TabObserver::
OnFirstSubresourceProxiedOnCurrentPrimaryPage() const {
cookie_controls_->OnFirstSubresourceProxiedOnCurrentPrimaryPage();
}
void CookieControlsController::TabObserver::PrimaryPageChanged(
content::Page& page) {
const GURL& current_url =
content::WebContentsObserver::web_contents()->GetVisibleURL();
cookie_accessed_set_.clear();
if (current_url != last_visited_url_) {
reload_count_ = 0;
timer_.Stop();
} else {
if (!timer_.IsRunning()) {
timer_.Start(FROM_HERE, features::kUserBypassUIReloadTime.Get(), this,
&CookieControlsController::TabObserver::ResetReloadCounter);
}
reload_count_++;
}
last_visited_url_ = current_url;
cookie_controls_->UpdatePageReloadStatus(reload_count_);
cookie_controls_->UpdateLastVisitedSitesMap();
}
void CookieControlsController::TabObserver::DidStopLoading() {
cookie_controls_->OnPageFinishedLoading();
}
void CookieControlsController::TabObserver::ResetReloadCounter() {
reload_count_ = 0;
}
} // namespace content_settings