blob: 5ea629d44f0709874be8e899ad6cea284ba26772 [file] [log] [blame]
// Copyright 2023 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/privacy_sandbox/privacy_sandbox_service_impl.h"
#include <algorithm>
#include <iterator>
#include <numeric>
#include "base/command_line.h"
#include "base/feature_list.h"
#include "base/i18n/time_formatting.h"
#include "base/metrics/histogram.h"
#include "base/metrics/histogram_functions.h"
#include "base/metrics/user_metrics.h"
#include "base/ranges/algorithm.h"
#include "base/strings/string_number_conversions.h"
#include "base/strings/utf_string_conversions.h"
#include "base/time/time.h"
#include "base/types/optional_util.h"
#include "build/branding_buildflags.h"
#include "chrome/browser/browser_process.h"
#include "chrome/browser/privacy_sandbox/privacy_sandbox_notice_confirmation.h"
#include "chrome/common/chrome_switches.h"
#include "chrome/common/webui_url_constants.h"
#include "chromeos/components/mgs/managed_guest_session_utils.h"
#include "components/browsing_topics/browsing_topics_service.h"
#include "components/browsing_topics/common/common_types.h"
#include "components/browsing_topics/common/semantic_tree.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_types.h"
#include "components/content_settings/core/common/pref_names.h"
#include "components/metrics/metrics_pref_names.h"
#include "components/prefs/pref_service.h"
#include "components/privacy_sandbox/privacy_sandbox_features.h"
#include "components/privacy_sandbox/privacy_sandbox_notice_constants.h"
#include "components/privacy_sandbox/privacy_sandbox_prefs.h"
#include "components/strings/grit/components_strings.h"
#include "components/url_formatter/url_formatter.h"
#include "content/public/browser/browsing_data_filter_builder.h"
#include "content/public/browser/browsing_data_remover.h"
#include "content/public/browser/first_party_sets_handler.h"
#include "content/public/browser/interest_group_manager.h"
#include "content/public/common/content_features.h"
#include "google_apis/gaia/google_service_auth_error.h"
#include "net/base/registry_controlled_domains/registry_controlled_domain.h"
#include "net/base/schemeful_site.h"
#include "net/first_party_sets/first_party_set_entry.h"
#include "net/first_party_sets/global_first_party_sets.h"
#include "third_party/blink/public/common/features.h"
#include "ui/base/l10n/l10n_util.h"
#if !BUILDFLAG(IS_ANDROID)
#include "chrome/browser/ui/hats/trust_safety_sentiment_service.h"
#include "ui/views/widget/widget.h"
#endif // !BUILDFLAG(IS_ANDROID)
#if BUILDFLAG(IS_ANDROID)
#include "base/json/values_util.h"
#include "base/time/time.h"
#endif // BUILDFLAG(IS_ANDROID)
#if BUILDFLAG(IS_CHROMEOS)
#include "chrome/browser/profiles/profiles_state.h"
#include "chromeos/components/kiosk/kiosk_utils.h"
#endif
namespace {
using PromptAction = ::PrivacySandboxService::PromptAction;
using SurfaceType = ::PrivacySandboxService::SurfaceType;
constexpr char kBlockedTopicsTopicKey[] = "topic";
bool g_prompt_disabled_for_tests = false;
bool IsFirstRunSuppressed(const base::CommandLine& command_line) {
return command_line.HasSwitch(switches::kNoFirstRun);
}
// Returns whether 3P cookies are blocked by |cookie_settings|. This can be
// either through blocking 3P cookies directly, or blocking all cookies.
// Blocking in this case also covers the "3P cookies limited" state.
bool ShouldBlockThirdPartyOrFirstPartyCookies(
content_settings::CookieSettings* cookie_settings) {
const auto default_content_setting =
cookie_settings->GetDefaultCookieSetting();
return cookie_settings->ShouldBlockThirdPartyCookies() ||
default_content_setting == ContentSetting::CONTENT_SETTING_BLOCK;
}
// Similar to the function above, but checks for ALL 3P cookies to be blocked
// pre and post 3PCD.
bool AreAllThirdPartyCookiesBlocked(
content_settings::CookieSettings* cookie_settings,
PrefService* prefs,
privacy_sandbox::TrackingProtectionSettings* tracking_protection_settings) {
// Check if 1PCs are blocked.
if (cookie_settings->GetDefaultCookieSetting() ==
ContentSetting::CONTENT_SETTING_BLOCK) {
return true;
}
// Check if all 3PCs are blocked.
return tracking_protection_settings->AreAllThirdPartyCookiesBlocked() ||
(!tracking_protection_settings->IsTrackingProtection3pcdEnabled() &&
prefs->GetInteger(prefs::kCookieControlsMode) ==
static_cast<int>(
content_settings::CookieControlsMode::kBlockThirdParty));
}
// Sorts |topics| alphabetically by topic display name for display.
// In addition, removes duplicate topics.
void SortAndDeduplicateTopicsForDisplay(
std::vector<privacy_sandbox::CanonicalTopic>& topics) {
std::sort(topics.begin(), topics.end(),
[](const privacy_sandbox::CanonicalTopic& a,
const privacy_sandbox::CanonicalTopic& b) {
return a.GetLocalizedRepresentation() <
b.GetLocalizedRepresentation();
});
topics.erase(std::unique(topics.begin(), topics.end()), topics.end());
}
// Returns whether |profile_type|, and the current browser session on CrOS,
// represent a regular (e.g. non guest, non system, non kiosk) profile.
bool IsRegularProfile(profile_metrics::BrowserProfileType profile_type) {
if (profile_type != profile_metrics::BrowserProfileType::kRegular) {
return false;
}
#if BUILDFLAG(IS_CHROMEOS)
// Any Device Local account, which is a CrOS concept powering things like
// Kiosks and Managed Guest Sessions, is not considered regular.
return !chromeos::IsManagedGuestSession() && !chromeos::IsKioskSession() &&
!profiles::IsChromeAppKioskSession();
#else
return true;
#endif
}
// Returns the text contents of the Topics Consent dialog.
std::string GetTopicsConfirmationText() {
std::vector<int> string_ids = {
IDS_PRIVACY_SANDBOX_M1_CONSENT_TITLE,
IDS_PRIVACY_SANDBOX_M1_CONSENT_DESCRIPTION_1,
IDS_PRIVACY_SANDBOX_M1_CONSENT_DESCRIPTION_2,
IDS_PRIVACY_SANDBOX_M1_CONSENT_DESCRIPTION_3,
IDS_PRIVACY_SANDBOX_M1_CONSENT_DESCRIPTION_4,
IDS_PRIVACY_SANDBOX_M1_CONSENT_LEARN_MORE_EXPAND_LABEL,
IDS_PRIVACY_SANDBOX_M1_CONSENT_LEARN_MORE_BULLET_1,
IDS_PRIVACY_SANDBOX_M1_CONSENT_LEARN_MORE_BULLET_2,
IDS_PRIVACY_SANDBOX_M1_CONSENT_LEARN_MORE_BULLET_3,
IDS_PRIVACY_SANDBOX_M1_CONSENT_LEARN_MORE_LINK};
return std::accumulate(
string_ids.begin(), string_ids.end(), std::string(),
[](const std::string& previous_result, int next_id) {
auto next_string = l10n_util::GetStringUTF8(next_id);
// Remove bold tags present in some strings.
base::ReplaceSubstringsAfterOffset(&next_string, 0, "<b>", "");
base::ReplaceSubstringsAfterOffset(&next_string, 0, "</b>", "");
return previous_result + (!previous_result.empty() ? " " : "") +
next_string;
}
);
}
// Returns the text contents of the Topics settings page.
std::string GetTopicsSettingsText(bool did_consent,
bool has_current_topics,
bool has_blocked_topics) {
// `did_consent` refers to the _updated_ state, and so the previous state,
// e.g. when the user clicked the toggle, will be the opposite.
auto topics_prev_enabled = !did_consent;
// A user should only have current topics while topics is enabled. Old topics
// will not appear when the user enables, as they will have been cleared when
// topics was previously disabled, or never generated at all.
DCHECK(topics_prev_enabled || !has_current_topics);
int blocked_topics_description =
has_blocked_topics
? IDS_SETTINGS_TOPICS_PAGE_BLOCKED_TOPICS_DESCRIPTION
: IDS_SETTINGS_TOPICS_PAGE_BLOCKED_TOPICS_DESCRIPTION_EMPTY;
std::vector<int> string_ids = {
IDS_SETTINGS_TOPICS_PAGE_TITLE,
IDS_SETTINGS_TOPICS_PAGE_TOGGLE_LABEL,
IDS_SETTINGS_TOPICS_PAGE_TOGGLE_SUB_LABEL,
IDS_SETTINGS_TOPICS_PAGE_CURRENT_TOPICS_HEADING,
IDS_SETTINGS_TOPICS_PAGE_CURRENT_TOPICS_DESCRIPTION_CANONICAL,
IDS_SETTINGS_TOPICS_PAGE_LEARN_MORE_HEADING,
IDS_SETTINGS_TOPICS_PAGE_LEARN_MORE_BULLET_1,
IDS_SETTINGS_TOPICS_PAGE_LEARN_MORE_BULLET_2,
IDS_SETTINGS_TOPICS_PAGE_LEARN_MORE_BULLET_3_CANONICAL,
IDS_SETTINGS_TOPICS_PAGE_BLOCKED_TOPICS_HEADING,
blocked_topics_description,
IDS_SETTINGS_TOPICS_PAGE_FOOTER_CANONICAL};
// Additional strings are displayed if there were no current topics, either
// because they were empty, or because Topics was disabled. These will have
// appeared after the current topics description.
if (!topics_prev_enabled) {
string_ids.insert(
string_ids.begin() + 5,
IDS_SETTINGS_TOPICS_PAGE_CURRENT_TOPICS_DESCRIPTION_DISABLED);
} else if (!has_current_topics) {
string_ids.insert(
string_ids.begin() + 5,
IDS_SETTINGS_TOPICS_PAGE_CURRENT_TOPICS_DESCRIPTION_EMPTY);
}
return std::accumulate(string_ids.begin(), string_ids.end(), std::string(),
[](const std::string& previous_result, int next_id) {
auto next_string = l10n_util::GetStringUTF8(next_id);
return previous_result +
(!previous_result.empty() ? " " : "") +
next_string;
});
}
// Returns whether this is a Google Chrome-branded build.
bool IsChromeBuild() {
#if BUILDFLAG(GOOGLE_CHROME_BRANDING)
return true;
#else
return false;
#endif
}
void RecordProtectedAudienceJoiningTopFrameDisplayedHistogram(bool value) {
base::UmaHistogramBoolean(
"PrivacySandbox.ProtectedAudience.JoiningTopFrameDisplayed", value);
}
constexpr std::string_view GetTopicsConsentNoticeName(
SurfaceType surface_type) {
switch (surface_type) {
case SurfaceType::kDesktop: {
return privacy_sandbox::kTopicsConsentModal;
}
case SurfaceType::kBrApp: {
return privacy_sandbox::kTopicsConsentModalClankBrApp;
}
case SurfaceType::kAGACCT: {
return privacy_sandbox::kTopicsConsentModalClankCCT;
}
}
}
constexpr std::string_view GetProtectedAudienceMeasurementNoticeName(
SurfaceType surface_type) {
switch (surface_type) {
case SurfaceType::kDesktop: {
return privacy_sandbox::kProtectedAudienceMeasurementNoticeModal;
}
case SurfaceType::kBrApp: {
return privacy_sandbox::
kProtectedAudienceMeasurementNoticeModalClankBrApp;
}
case SurfaceType::kAGACCT: {
return privacy_sandbox::kProtectedAudienceMeasurementNoticeModalClankCCT;
}
}
}
constexpr std::string_view GetThreeAdsAPIsNoticeName(SurfaceType surface_type) {
switch (surface_type) {
case SurfaceType::kDesktop: {
return privacy_sandbox::kThreeAdsAPIsNoticeModal;
}
case SurfaceType::kBrApp: {
return privacy_sandbox::kThreeAdsAPIsNoticeModalClankBrApp;
}
case SurfaceType::kAGACCT: {
return privacy_sandbox::kThreeAdsAPIsNoticeModalClankCCT;
}
}
}
constexpr std::string_view GetMeasurementNoticeName(SurfaceType surface_type) {
switch (surface_type) {
case SurfaceType::kDesktop: {
return privacy_sandbox::kMeasurementNoticeModal;
}
case SurfaceType::kBrApp: {
return privacy_sandbox::kMeasurementNoticeModalClankBrApp;
}
case SurfaceType::kAGACCT: {
return privacy_sandbox::kMeasurementNoticeModalClankCCT;
}
}
}
std::string_view GetNoticeName(PromptAction action, SurfaceType surface_type) {
std::string_view empty_view;
switch (action) {
case PromptAction::kConsentShown:
case PromptAction::kConsentAccepted:
case PromptAction::kConsentDeclined:
return GetTopicsConsentNoticeName(surface_type);
case PromptAction::kRestrictedNoticeShown:
case PromptAction::kRestrictedNoticeAcknowledge:
case PromptAction::kRestrictedNoticeOpenSettings:
return GetMeasurementNoticeName(surface_type);
case PromptAction::kNoticeShown:
case PromptAction::kNoticeAcknowledge:
case PromptAction::kNoticeOpenSettings:
return privacy_sandbox::IsConsentRequired()
? GetProtectedAudienceMeasurementNoticeName(surface_type)
: GetThreeAdsAPIsNoticeName(surface_type);
default:
return empty_view;
}
}
} // namespace
// static
bool PrivacySandboxService::IsUrlSuitableForPrompt(const GURL& url) {
// The prompt should be shown on a limited list of pages:
// about:blank is valid.
if (url.IsAboutBlank()) {
return true;
}
// Chrome settings page is valid. The subpages aren't as most of them are not
// related to the prompt.
if (url == GURL(chrome::kChromeUISettingsURL)) {
return true;
}
// Chrome history is valid as the prompt mentions history.
if (url == GURL(chrome::kChromeUIHistoryURL)) {
return true;
}
// Only a Chrome controlled New Tab Page is valid. Third party NTP is still
// Chrome controlled, but is without Google branding.
if (url == GURL(chrome::kChromeUINewTabPageURL) ||
url == GURL(chrome::kChromeUINewTabPageThirdPartyURL)) {
return true;
}
return false;
}
// static
void PrivacySandboxService::SetPromptDisabledForTests(bool disabled) {
g_prompt_disabled_for_tests = disabled;
}
PrivacySandboxServiceImpl::PrivacySandboxServiceImpl(
privacy_sandbox::PrivacySandboxSettings* privacy_sandbox_settings,
privacy_sandbox::TrackingProtectionSettings* tracking_protection_settings,
scoped_refptr<content_settings::CookieSettings> cookie_settings,
PrefService* pref_service,
content::InterestGroupManager* interest_group_manager,
profile_metrics::BrowserProfileType profile_type,
content::BrowsingDataRemover* browsing_data_remover,
HostContentSettingsMap* host_content_settings_map,
#if !BUILDFLAG(IS_ANDROID)
TrustSafetySentimentService* sentiment_service,
#endif
browsing_topics::BrowsingTopicsService* browsing_topics_service,
first_party_sets::FirstPartySetsPolicyService* first_party_sets_service)
: privacy_sandbox_settings_(privacy_sandbox_settings),
tracking_protection_settings_(tracking_protection_settings),
cookie_settings_(cookie_settings),
pref_service_(pref_service),
interest_group_manager_(interest_group_manager),
profile_type_(profile_type),
browsing_data_remover_(browsing_data_remover),
host_content_settings_map_(host_content_settings_map),
#if !BUILDFLAG(IS_ANDROID)
sentiment_service_(sentiment_service),
#endif
browsing_topics_service_(browsing_topics_service),
first_party_sets_policy_service_(first_party_sets_service) {
// Create notice storage
notice_storage_ =
std::make_unique<privacy_sandbox::PrivacySandboxNoticeStorage>();
DCHECK(privacy_sandbox_settings_);
DCHECK(pref_service_);
DCHECK(cookie_settings_);
CHECK(tracking_protection_settings_);
CHECK(notice_storage_);
// Register observers for the Privacy Sandbox preferences.
user_prefs_registrar_.Init(pref_service_);
user_prefs_registrar_.Add(
prefs::kPrivacySandboxM1TopicsEnabled,
base::BindRepeating(&PrivacySandboxServiceImpl::OnTopicsPrefChanged,
base::Unretained(this)));
user_prefs_registrar_.Add(
prefs::kPrivacySandboxM1FledgeEnabled,
base::BindRepeating(&PrivacySandboxServiceImpl::OnFledgePrefChanged,
base::Unretained(this)));
user_prefs_registrar_.Add(
prefs::kPrivacySandboxM1AdMeasurementEnabled,
base::BindRepeating(
&PrivacySandboxServiceImpl::OnAdMeasurementPrefChanged,
base::Unretained(this)));
// If the Sandbox is currently restricted, disable it and reset any consent
// information. The user must manually enable the sandbox if they stop being
// restricted.
if (IsPrivacySandboxRestricted()) {
// Disable M1 prefs. Measurement pref should not be reset when restricted
// notice feature is enabled.
pref_service_->SetBoolean(prefs::kPrivacySandboxM1TopicsEnabled, false);
pref_service_->SetBoolean(prefs::kPrivacySandboxM1FledgeEnabled, false);
if (!privacy_sandbox::IsRestrictedNoticeRequired()) {
pref_service_->SetBoolean(prefs::kPrivacySandboxM1AdMeasurementEnabled,
false);
}
// Clear any recorded consent information.
pref_service_->ClearPref(prefs::kPrivacySandboxTopicsConsentGiven);
pref_service_->ClearPref(prefs::kPrivacySandboxTopicsConsentLastUpdateTime);
pref_service_->ClearPref(
prefs::kPrivacySandboxTopicsConsentLastUpdateReason);
pref_service_->ClearPref(
prefs::kPrivacySandboxTopicsConsentTextAtLastUpdate);
}
// kRestricted prompt suppression reason must be cleared at startup when
// restricted notice feature is enabled.
if (privacy_sandbox::IsRestrictedNoticeRequired() &&
static_cast<PromptSuppressedReason>(
pref_service->GetInteger(prefs::kPrivacySandboxM1PromptSuppressed)) ==
PromptSuppressedReason::kRestricted) {
pref_service_->ClearPref(prefs::kPrivacySandboxM1PromptSuppressed);
}
// Check for FPS pref init at each startup.
// TODO(crbug.com/40234448): Remove this logic when most users have run init.
MaybeInitializeFirstPartySetsPref();
// Record preference state for UMA at each startup.
LogPrivacySandboxState();
}
PrivacySandboxServiceImpl::~PrivacySandboxServiceImpl() = default;
PrivacySandboxService::PromptType
// TODO(crbug.com/352575567): Use the SurfaceType passed in.
PrivacySandboxServiceImpl::GetRequiredPromptType(SurfaceType surface_type) {
bool third_party_cookies_blocked = AreAllThirdPartyCookiesBlocked(
cookie_settings_.get(), pref_service_, tracking_protection_settings_);
return GetRequiredPromptTypeInternal(
pref_service_, profile_type_, privacy_sandbox_settings_,
third_party_cookies_blocked,
force_chrome_build_for_tests_ || IsChromeBuild());
}
void UpdateNoticeStorage(
PromptAction action,
privacy_sandbox::PrivacySandboxNoticeStorage* notice_storage,
PrefService* pref_service,
SurfaceType surface_type) {
if (!base::FeatureList::IsEnabled(
privacy_sandbox::kPsDualWritePrefsToNoticeStorage)) {
return;
}
// Set correct notice names, ready to receive and log PromptActions
std::string_view notice_name = GetNoticeName(action, surface_type);
switch (action) {
// Topics notices (only shown for EEA, consent option)
case PromptAction::kConsentShown: {
notice_storage->SetNoticeShown(pref_service, notice_name,
base::Time::Now());
break;
}
case PromptAction::kConsentAccepted: {
notice_storage->SetNoticeActionTaken(
pref_service, notice_name, privacy_sandbox::NoticeActionTaken::kOptIn,
base::Time::Now());
break;
}
case PromptAction::kConsentDeclined: {
notice_storage->SetNoticeActionTaken(
pref_service, notice_name,
privacy_sandbox::NoticeActionTaken::kOptOut, base::Time::Now());
break;
}
// EEA and ROW notices
case PromptAction::kNoticeShown: {
notice_storage->SetNoticeShown(pref_service, notice_name,
base::Time::Now());
break;
}
case PromptAction::kNoticeAcknowledge: {
notice_storage->SetNoticeActionTaken(
pref_service, notice_name, privacy_sandbox::NoticeActionTaken::kAck,
base::Time::Now());
break;
}
case PromptAction::kNoticeOpenSettings: {
notice_storage->SetNoticeActionTaken(
pref_service, notice_name,
privacy_sandbox::NoticeActionTaken::kSettings, base::Time::Now());
break;
}
// Restricted notices
case PromptAction::kRestrictedNoticeShown: {
DCHECK(privacy_sandbox::IsRestrictedNoticeRequired());
notice_storage->SetNoticeShown(pref_service, notice_name,
base::Time::Now());
break;
}
case PromptAction::kRestrictedNoticeAcknowledge: {
DCHECK(privacy_sandbox::IsRestrictedNoticeRequired());
notice_storage->SetNoticeActionTaken(
pref_service, notice_name, privacy_sandbox::NoticeActionTaken::kAck,
base::Time::Now());
break;
}
case PromptAction::kRestrictedNoticeOpenSettings: {
DCHECK(privacy_sandbox::IsRestrictedNoticeRequired());
notice_storage->SetNoticeActionTaken(
pref_service, notice_name,
privacy_sandbox::NoticeActionTaken::kSettings, base::Time::Now());
break;
}
default:
break;
}
}
void PrivacySandboxServiceImpl::PromptActionOccurred(PromptAction action,
SurfaceType surface_type) {
RecordPromptActionMetrics(action);
UpdateNoticeStorage(action, notice_storage_.get(), pref_service_.get(),
surface_type);
InformSentimentService(action);
if (PromptAction::kNoticeAcknowledge == action ||
PromptAction::kNoticeOpenSettings == action) {
if (privacy_sandbox::IsConsentRequired()) {
pref_service_->SetBoolean(prefs::kPrivacySandboxM1EEANoticeAcknowledged,
true);
// It's possible the user is seeing this notice as part of an upgrade to
// EEA consent. In this instance, we shouldn't alter the control state,
// as the user may have already altered it in settings.
if (!pref_service_->GetBoolean(
prefs::kPrivacySandboxM1RowNoticeAcknowledged)) {
pref_service_->SetBoolean(prefs::kPrivacySandboxM1FledgeEnabled, true);
pref_service_->SetBoolean(prefs::kPrivacySandboxM1AdMeasurementEnabled,
true);
}
} else {
DCHECK(privacy_sandbox::IsNoticeRequired());
pref_service_->SetBoolean(prefs::kPrivacySandboxM1RowNoticeAcknowledged,
true);
pref_service_->SetBoolean(prefs::kPrivacySandboxM1TopicsEnabled, true);
pref_service_->SetBoolean(prefs::kPrivacySandboxM1FledgeEnabled, true);
pref_service_->SetBoolean(prefs::kPrivacySandboxM1AdMeasurementEnabled,
true);
}
#if !BUILDFLAG(IS_ANDROID)
MaybeCloseOpenPrompts();
#endif // !BUILDFLAG(IS_ANDROID)
// Consent-related PromptActions refer to to Topics Notice Consent
} else if (PromptAction::kConsentAccepted == action) {
DCHECK(privacy_sandbox::IsConsentRequired());
pref_service_->SetBoolean(prefs::kPrivacySandboxM1ConsentDecisionMade,
true);
pref_service_->SetBoolean(prefs::kPrivacySandboxM1TopicsEnabled, true);
RecordUpdatedTopicsConsent(
privacy_sandbox::TopicsConsentUpdateSource::kConfirmation, true);
} else if (PromptAction::kConsentDeclined == action) {
DCHECK(privacy_sandbox::IsConsentRequired());
pref_service_->SetBoolean(prefs::kPrivacySandboxM1ConsentDecisionMade,
true);
pref_service_->SetBoolean(prefs::kPrivacySandboxM1TopicsEnabled, false);
RecordUpdatedTopicsConsent(
privacy_sandbox::TopicsConsentUpdateSource::kConfirmation, false);
} else if (PromptAction::kRestrictedNoticeAcknowledge == action ||
PromptAction::kRestrictedNoticeOpenSettings == action) {
CHECK(privacy_sandbox::IsRestrictedNoticeRequired());
pref_service_->SetBoolean(
prefs::kPrivacySandboxM1RestrictedNoticeAcknowledged, true);
pref_service_->SetBoolean(prefs::kPrivacySandboxM1AdMeasurementEnabled,
true);
#if !BUILDFLAG(IS_ANDROID)
MaybeCloseOpenPrompts();
#endif // !BUILDFLAG(IS_ANDROID)
}
}
#if !BUILDFLAG(IS_ANDROID)
void PrivacySandboxServiceImpl::PromptOpenedForBrowser(Browser* browser,
views::Widget* widget) {
DCHECK(!browsers_to_open_prompts_.count(browser));
browsers_to_open_prompts_[browser] = widget;
}
void PrivacySandboxServiceImpl::PromptClosedForBrowser(Browser* browser) {
DCHECK(browsers_to_open_prompts_.count(browser));
browsers_to_open_prompts_.erase(browser);
}
bool PrivacySandboxServiceImpl::IsPromptOpenForBrowser(Browser* browser) {
return browsers_to_open_prompts_.count(browser);
}
#endif // !BUILDFLAG(IS_ANDROID)
void PrivacySandboxServiceImpl::ForceChromeBuildForTests(
bool force_chrome_build) {
force_chrome_build_for_tests_ = force_chrome_build;
}
bool PrivacySandboxServiceImpl::IsPrivacySandboxRestricted() {
return privacy_sandbox_settings_->IsPrivacySandboxRestricted();
}
bool PrivacySandboxServiceImpl::IsRestrictedNoticeEnabled() {
return privacy_sandbox_settings_->IsRestrictedNoticeEnabled();
}
void PrivacySandboxServiceImpl::SetFirstPartySetsDataAccessEnabled(
bool enabled) {
pref_service_->SetBoolean(prefs::kPrivacySandboxRelatedWebsiteSetsEnabled,
enabled);
}
bool PrivacySandboxServiceImpl::IsFirstPartySetsDataAccessEnabled() const {
return privacy_sandbox_settings_->AreRelatedWebsiteSetsEnabled();
}
bool PrivacySandboxServiceImpl::IsFirstPartySetsDataAccessManaged() const {
return pref_service_->IsManagedPreference(
prefs::kPrivacySandboxRelatedWebsiteSetsEnabled);
}
base::flat_map<net::SchemefulSite, net::SchemefulSite>
PrivacySandboxServiceImpl::GetSampleFirstPartySets() const {
if (privacy_sandbox::kPrivacySandboxFirstPartySetsUISampleSets.Get() &&
IsFirstPartySetsDataAccessEnabled()) {
return {{net::SchemefulSite(GURL("https://youtube.com")),
net::SchemefulSite(GURL("https://google.com"))},
{net::SchemefulSite(GURL("https://google.com")),
net::SchemefulSite(GURL("https://google.com"))},
{net::SchemefulSite(GURL("https://google.com.au")),
net::SchemefulSite(GURL("https://google.com"))},
{net::SchemefulSite(GURL("https://google.de")),
net::SchemefulSite(GURL("https://google.com"))},
{net::SchemefulSite(GURL("https://chromium.org")),
net::SchemefulSite(GURL("https://chromium.org"))},
{net::SchemefulSite(GURL("https://googlesource.com")),
net::SchemefulSite(GURL("https://chromium.org"))},
{net::SchemefulSite(GURL("https://muenchen.de")),
net::SchemefulSite(GURL("https://xn--mnchen-3ya.de"))}};
}
return {};
}
std::optional<net::SchemefulSite>
PrivacySandboxServiceImpl::GetFirstPartySetOwner(const GURL& site_url) const {
// If FPS is not affecting cookie access, then there are effectively no
// first party sets.
if (!(cookie_settings_->ShouldBlockThirdPartyCookies() &&
cookie_settings_->GetDefaultCookieSetting() != CONTENT_SETTING_BLOCK &&
base::FeatureList::IsEnabled(
privacy_sandbox::kPrivacySandboxFirstPartySetsUI))) {
return std::nullopt;
}
// Return the owner according to the sample sets if they're provided.
if (privacy_sandbox::kPrivacySandboxFirstPartySetsUISampleSets.Get()) {
const base::flat_map<net::SchemefulSite, net::SchemefulSite> sets =
GetSampleFirstPartySets();
net::SchemefulSite schemeful_site(site_url);
base::flat_map<net::SchemefulSite, net::SchemefulSite>::const_iterator
site_entry = sets.find(schemeful_site);
if (site_entry == sets.end()) {
return std::nullopt;
}
return site_entry->second;
}
std::optional<net::FirstPartySetEntry> site_entry =
first_party_sets_policy_service_->FindEntry(net::SchemefulSite(site_url));
if (!site_entry.has_value()) {
return std::nullopt;
}
return site_entry->primary();
}
std::optional<std::u16string>
PrivacySandboxServiceImpl::GetFirstPartySetOwnerForDisplay(
const GURL& site_url) const {
std::optional<net::SchemefulSite> site_owner =
GetFirstPartySetOwner(site_url);
if (!site_owner.has_value()) {
return std::nullopt;
}
return url_formatter::IDNToUnicode(site_owner->GetURL().host());
}
bool PrivacySandboxServiceImpl::IsPartOfManagedFirstPartySet(
const net::SchemefulSite& site) const {
if (privacy_sandbox::kPrivacySandboxFirstPartySetsUISampleSets.Get()) {
return IsFirstPartySetsDataAccessManaged() ||
GetSampleFirstPartySets()[site] ==
net::SchemefulSite(GURL("https://chromium.org"));
}
return first_party_sets_policy_service_->IsSiteInManagedSet(site);
}
void PrivacySandboxServiceImpl::GetFledgeJoiningEtldPlusOneForDisplay(
base::OnceCallback<void(std::vector<std::string>)> callback) {
if (!interest_group_manager_) {
std::move(callback).Run({});
return;
}
interest_group_manager_->GetAllInterestGroupDataKeys(base::BindOnce(
&PrivacySandboxServiceImpl::ConvertInterestGroupDataKeysForDisplay,
weak_factory_.GetWeakPtr(), std::move(callback)));
}
std::vector<std::string>
PrivacySandboxServiceImpl::GetBlockedFledgeJoiningTopFramesForDisplay() const {
const base::Value::Dict& pref_value =
pref_service_->GetDict(prefs::kPrivacySandboxFledgeJoinBlocked);
std::vector<std::string> blocked_top_frames;
for (auto entry : pref_value) {
blocked_top_frames.emplace_back(entry.first);
}
// Apply a lexographic ordering to match other settings permission surfaces.
std::sort(blocked_top_frames.begin(), blocked_top_frames.end());
return blocked_top_frames;
}
void PrivacySandboxServiceImpl::SetFledgeJoiningAllowed(
const std::string& top_frame_etld_plus1,
bool allowed) const {
privacy_sandbox_settings_->SetFledgeJoiningAllowed(top_frame_etld_plus1,
allowed);
if (!allowed && browsing_data_remover_) {
std::unique_ptr<content::BrowsingDataFilterBuilder> filter =
content::BrowsingDataFilterBuilder::Create(
content::BrowsingDataFilterBuilder::Mode::kDelete);
filter->AddRegisterableDomain(top_frame_etld_plus1);
browsing_data_remover_->RemoveWithFilter(
base::Time::Min(), base::Time::Max(),
content::BrowsingDataRemover::DATA_TYPE_INTEREST_GROUPS,
content::BrowsingDataRemover::ORIGIN_TYPE_UNPROTECTED_WEB,
std::move(filter));
}
}
void PrivacySandboxServiceImpl::RecordFirstPartySetsStateHistogram(
FirstPartySetsState state) {
base::UmaHistogramEnumeration("Settings.FirstPartySets.State", state);
}
void PrivacySandboxServiceImpl::RecordPrivacySandbox4StartupMetrics() {
// Record the status of the APIs.
const bool topics_enabled =
pref_service_->GetBoolean(prefs::kPrivacySandboxM1TopicsEnabled);
base::UmaHistogramBoolean("Settings.PrivacySandbox.Topics.Enabled",
topics_enabled);
base::UmaHistogramBoolean(
"Settings.PrivacySandbox.Fledge.Enabled",
pref_service_->GetBoolean(prefs::kPrivacySandboxM1FledgeEnabled));
base::UmaHistogramBoolean(
"Settings.PrivacySandbox.AdMeasurement.Enabled",
pref_service_->GetBoolean(prefs::kPrivacySandboxM1AdMeasurementEnabled));
const std::string privacy_sandbox_prompt_startup_histogram =
"Settings.PrivacySandbox.PromptStartupState";
const bool user_reported_restricted =
pref_service_->GetBoolean(prefs::kPrivacySandboxM1Restricted);
const bool user_is_currently_unrestricted =
privacy_sandbox_settings_->IsPrivacySandboxCurrentlyUnrestricted();
// Prompt suppressed cases.
PromptSuppressedReason prompt_suppressed_reason =
static_cast<PromptSuppressedReason>(
pref_service_->GetInteger(prefs::kPrivacySandboxM1PromptSuppressed));
switch (prompt_suppressed_reason) {
// Prompt never suppressed.
case PromptSuppressedReason::kNone: {
break;
}
case PromptSuppressedReason::kRestricted: {
base::UmaHistogramEnumeration(
privacy_sandbox_prompt_startup_histogram,
PromptStartupState::kPromptNotShownDueToPrivacySandboxRestricted);
return;
}
case PromptSuppressedReason::kThirdPartyCookiesBlocked: {
base::UmaHistogramEnumeration(
privacy_sandbox_prompt_startup_histogram,
PromptStartupState::kPromptNotShownDueTo3PCBlocked);
return;
}
case PromptSuppressedReason::kTrialsConsentDeclined: {
base::UmaHistogramEnumeration(
privacy_sandbox_prompt_startup_histogram,
PromptStartupState::kPromptNotShownDueToTrialConsentDeclined);
return;
}
case PromptSuppressedReason::kTrialsDisabledAfterNotice: {
base::UmaHistogramEnumeration(
privacy_sandbox_prompt_startup_histogram,
PromptStartupState::
kPromptNotShownDueToTrialsDisabledAfterNoticeShown);
return;
}
case PromptSuppressedReason::kPolicy: {
base::UmaHistogramEnumeration(
privacy_sandbox_prompt_startup_histogram,
PromptStartupState::kPromptNotShownDueToManagedState);
return;
}
case PromptSuppressedReason::kEEAFlowCompletedBeforeRowMigration: {
base::UmaHistogramEnumeration(
privacy_sandbox_prompt_startup_histogram,
topics_enabled
? PromptStartupState::kEEAFlowCompletedWithTopicsAccepted
: PromptStartupState::kEEAFlowCompletedWithTopicsDeclined);
return;
}
case PromptSuppressedReason::
kROWFlowCompletedAndTopicsDisabledBeforeEEAMigration: {
base::UmaHistogramEnumeration(
privacy_sandbox_prompt_startup_histogram,
PromptStartupState::kROWNoticeFlowCompleted);
return;
}
case PromptSuppressedReason::kNoticeShownToGuardian: {
// Check for users waiting for graduation: If a user was ever reported as
// restricted and is currently unrestricted it means they are ready for
// graduation.
base::UmaHistogramEnumeration(
privacy_sandbox_prompt_startup_histogram,
user_reported_restricted && user_is_currently_unrestricted
? PromptStartupState::
kWaitingForGraduationRestrictedNoticeFlowNotCompleted
: PromptStartupState::
kRestrictedNoticeNotShownDueToNoticeShownToGuardian);
return;
}
}
// Prompt was not suppressed explicitly at this point.
CHECK_EQ(prompt_suppressed_reason, PromptSuppressedReason::kNone);
// Check if prompt was suppressed implicitly.
if (IsM1PrivacySandboxEffectivelyManaged(pref_service_)) {
base::UmaHistogramEnumeration(
privacy_sandbox_prompt_startup_histogram,
PromptStartupState::kPromptNotShownDueToManagedState);
return;
}
const bool restricted_notice_acknowledged = pref_service_->GetBoolean(
prefs::kPrivacySandboxM1RestrictedNoticeAcknowledged);
// Check for users waiting for graduation: If a user was ever reported as
// restricted and is currently unrestricted it means they are ready for
// graduation.
if (user_reported_restricted && user_is_currently_unrestricted) {
base::UmaHistogramEnumeration(
privacy_sandbox_prompt_startup_histogram,
restricted_notice_acknowledged
? PromptStartupState::
kWaitingForGraduationRestrictedNoticeFlowCompleted
: PromptStartupState::
kWaitingForGraduationRestrictedNoticeFlowNotCompleted);
return;
}
const bool row_notice_acknowledged =
pref_service_->GetBoolean(prefs::kPrivacySandboxM1RowNoticeAcknowledged);
const bool eaa_notice_acknowledged =
pref_service_->GetBoolean(prefs::kPrivacySandboxM1EEANoticeAcknowledged);
// Restricted Notice
// Note that ordering is important: one of consent or notice will always be
// required when the restricted prompt is shown, and both return
// unconditionally.
if (privacy_sandbox_settings_->IsSubjectToM1NoticeRestricted()) {
// Acknowledgement of any of the prompt types implies acknowledgement of the
// restricted notice as well.
if (row_notice_acknowledged || eaa_notice_acknowledged) {
base::UmaHistogramEnumeration(
privacy_sandbox_prompt_startup_histogram,
PromptStartupState::
kRestrictedNoticeNotShownDueToFullNoticeAcknowledged);
return;
}
base::UmaHistogramEnumeration(
privacy_sandbox_prompt_startup_histogram,
restricted_notice_acknowledged
? PromptStartupState::kRestrictedNoticeFlowCompleted
: PromptStartupState::kRestrictedNoticePromptWaiting);
return;
}
// EEA
if (privacy_sandbox::IsConsentRequired()) {
// Consent decision not made
if (!pref_service_->GetBoolean(
prefs::kPrivacySandboxM1ConsentDecisionMade)) {
base::UmaHistogramEnumeration(
privacy_sandbox_prompt_startup_histogram,
PromptStartupState::kEEAConsentPromptWaiting);
return;
}
// Consent decision made at this point.
// Notice Acknowledged
if (eaa_notice_acknowledged) {
base::UmaHistogramEnumeration(
privacy_sandbox_prompt_startup_histogram,
topics_enabled
? PromptStartupState::kEEAFlowCompletedWithTopicsAccepted
: PromptStartupState::kEEAFlowCompletedWithTopicsDeclined);
} else {
base::UmaHistogramEnumeration(
privacy_sandbox_prompt_startup_histogram,
PromptStartupState::kEEANoticePromptWaiting);
}
return;
}
// ROW
if (privacy_sandbox::IsNoticeRequired()) {
base::UmaHistogramEnumeration(
privacy_sandbox_prompt_startup_histogram,
row_notice_acknowledged ? PromptStartupState::kROWNoticeFlowCompleted
: PromptStartupState::kROWNoticePromptWaiting);
return;
}
}
void PrivacySandboxServiceImpl::LogPrivacySandboxState() {
// Do not record metrics for non-regular profiles.
if (!IsRegularProfile(profile_type_)) {
return;
}
auto fps_status = FirstPartySetsState::kFpsNotRelevant;
if (cookie_settings_->ShouldBlockThirdPartyCookies() &&
cookie_settings_->GetDefaultCookieSetting() != CONTENT_SETTING_BLOCK) {
fps_status = privacy_sandbox_settings_->AreRelatedWebsiteSetsEnabled()
? FirstPartySetsState::kFpsEnabled
: FirstPartySetsState::kFpsDisabled;
}
RecordFirstPartySetsStateHistogram(fps_status);
RecordPrivacySandbox4StartupMetrics();
// TODO(crbug.com/333406690): After migration, move this portion to the
// chrome/browser/privacy_sandbox/privacy_sandbox_notice_service.h constructor
// and emit ALL startup histograms instead of just Topics consent related
// histograms.
for (const auto& notice_name : privacy_sandbox::kPrivacySandboxNoticeNames) {
notice_storage_->RecordHistogramsOnStartup(pref_service_, notice_name);
}
}
void PrivacySandboxServiceImpl::ConvertInterestGroupDataKeysForDisplay(
base::OnceCallback<void(std::vector<std::string>)> callback,
std::vector<content::InterestGroupManager::InterestGroupDataKey>
data_keys) {
std::set<std::string> display_entries;
for (const auto& data_key : data_keys) {
// When displaying interest group information in settings, the joining
// origin is the relevant origin.
const auto& origin = data_key.joining_origin;
// Prefer to display the associated eTLD+1, if there is one.
auto etld_plus_one = net::registry_controlled_domains::GetDomainAndRegistry(
origin, net::registry_controlled_domains::INCLUDE_PRIVATE_REGISTRIES);
if (etld_plus_one.length() > 0) {
display_entries.emplace(std::move(etld_plus_one));
RecordProtectedAudienceJoiningTopFrameDisplayedHistogram(true);
continue;
}
// The next best option is a host, which may be an IP address or an eTLD
// itself (e.g. github.io).
if (origin.host().length() > 0) {
display_entries.emplace(origin.host());
RecordProtectedAudienceJoiningTopFrameDisplayedHistogram(true);
continue;
}
// By design, each interest group should have a joining site or host, and
// so this could ideally be a NOTREACHED(). However, following
// crbug.com/1487191, it is apparent that this is not always true.
// A host or site is expected in other parts of the UI, so we cannot simply
// display the origin directly (it may also be empty). Instead, we elide it
// but record a metric to understand how widespread this is.
// TODO(crbug.com/40283983) - Investigate how much of an issue this is.
RecordProtectedAudienceJoiningTopFrameDisplayedHistogram(false);
}
// Entries should be displayed alphabetically, as |display_entries| is a
// std::set<std::string>, entries are already ordered correctly.
std::move(callback).Run(
std::vector<std::string>{display_entries.begin(), display_entries.end()});
}
std::vector<privacy_sandbox::CanonicalTopic>
PrivacySandboxServiceImpl::GetCurrentTopTopics() const {
if (pref_service_->GetBoolean(prefs::kPrivacySandboxM1TopicsEnabled) &&
privacy_sandbox::kPrivacySandboxSettings4ShowSampleDataForTesting.Get()) {
return {fake_current_topics_.begin(), fake_current_topics_.end()};
}
if (!browsing_topics_service_) {
return {};
}
auto topics = browsing_topics_service_->GetTopTopicsForDisplay();
SortAndDeduplicateTopicsForDisplay(topics);
return topics;
}
std::vector<privacy_sandbox::CanonicalTopic>
PrivacySandboxServiceImpl::GetBlockedTopics() const {
if (privacy_sandbox::kPrivacySandboxSettings4ShowSampleDataForTesting.Get()) {
return {fake_blocked_topics_.begin(), fake_blocked_topics_.end()};
}
const base::Value::List& pref_value =
pref_service_->GetList(prefs::kPrivacySandboxBlockedTopics);
std::vector<privacy_sandbox::CanonicalTopic> blocked_topics;
for (const auto& entry : pref_value) {
auto blocked_topic = privacy_sandbox::CanonicalTopic::FromValue(
*entry.GetDict().Find(kBlockedTopicsTopicKey));
if (blocked_topic) {
blocked_topics.emplace_back(*blocked_topic);
}
}
SortAndDeduplicateTopicsForDisplay(blocked_topics);
return blocked_topics;
}
std::vector<privacy_sandbox::CanonicalTopic>
PrivacySandboxServiceImpl::GetFirstLevelTopics() const {
static const base::NoDestructor<std::vector<privacy_sandbox::CanonicalTopic>>
kFirstLevelTopics([]() -> std::vector<privacy_sandbox::CanonicalTopic> {
browsing_topics::SemanticTree semantic_tree;
auto topics = semantic_tree.GetFirstLevelTopicsInCurrentTaxonomy();
std::vector<privacy_sandbox::CanonicalTopic> first_level_topics;
first_level_topics.reserve(topics.size());
std::transform(
topics.begin(), topics.end(),
std::back_inserter(first_level_topics),
[&](const browsing_topics::Topic& topic) {
return privacy_sandbox::CanonicalTopic(
topic, blink::features::kBrowsingTopicsTaxonomyVersion.Get());
});
SortAndDeduplicateTopicsForDisplay(first_level_topics);
return first_level_topics;
}());
return *kFirstLevelTopics;
}
std::vector<privacy_sandbox::CanonicalTopic>
PrivacySandboxServiceImpl::GetChildTopicsCurrentlyAssigned(
const privacy_sandbox::CanonicalTopic& parent_topic) const {
browsing_topics::SemanticTree semantic_tree;
auto descendant_topics =
semantic_tree.GetDescendantTopics(parent_topic.topic_id());
auto current_assigned_topics = GetCurrentTopTopics();
std::set<privacy_sandbox::CanonicalTopic> descendant_topics_set;
std::transform(
std::begin(descendant_topics), std::end(descendant_topics),
std::inserter(descendant_topics_set, descendant_topics_set.begin()),
[](browsing_topics::Topic topic) {
return privacy_sandbox::CanonicalTopic(
topic, blink::features::kBrowsingTopicsTaxonomyVersion.Get());
});
std::vector<privacy_sandbox::CanonicalTopic> child_topics_assigned;
for (const auto topic : current_assigned_topics) {
if (descendant_topics_set.contains(topic)) {
child_topics_assigned.push_back(topic);
}
}
return child_topics_assigned;
}
void PrivacySandboxServiceImpl::SetTopicAllowed(
privacy_sandbox::CanonicalTopic topic,
bool allowed) {
if (privacy_sandbox::kPrivacySandboxSettings4ShowSampleDataForTesting.Get()) {
if (allowed) {
fake_current_topics_.insert(topic);
fake_blocked_topics_.erase(topic);
} else {
fake_current_topics_.erase(topic);
fake_blocked_topics_.insert(topic);
}
return;
}
if (!allowed && browsing_topics_service_) {
browsing_topics_service_->ClearTopic(topic);
}
privacy_sandbox_settings_->SetTopicAllowed(topic, allowed);
}
void PrivacySandboxServiceImpl::TopicsToggleChanged(bool new_value) const {
RecordUpdatedTopicsConsent(
privacy_sandbox::TopicsConsentUpdateSource::kSettings, new_value);
}
bool PrivacySandboxServiceImpl::TopicsConsentRequired() const {
return privacy_sandbox::IsConsentRequired();
}
bool PrivacySandboxServiceImpl::TopicsHasActiveConsent() const {
return pref_service_->GetBoolean(prefs::kPrivacySandboxTopicsConsentGiven);
}
privacy_sandbox::TopicsConsentUpdateSource
PrivacySandboxServiceImpl::TopicsConsentLastUpdateSource() const {
return static_cast<privacy_sandbox::TopicsConsentUpdateSource>(
pref_service_->GetInteger(
prefs::kPrivacySandboxTopicsConsentLastUpdateReason));
}
base::Time PrivacySandboxServiceImpl::TopicsConsentLastUpdateTime() const {
return pref_service_->GetTime(
prefs::kPrivacySandboxTopicsConsentLastUpdateTime);
}
std::string PrivacySandboxServiceImpl::TopicsConsentLastUpdateText() const {
return pref_service_->GetString(
prefs::kPrivacySandboxTopicsConsentTextAtLastUpdate);
}
// static
PrivacySandboxService::PromptType
PrivacySandboxServiceImpl::GetRequiredPromptTypeInternal(
PrefService* pref_service,
profile_metrics::BrowserProfileType profile_type,
privacy_sandbox::PrivacySandboxSettings* privacy_sandbox_settings,
bool third_party_cookies_blocked,
bool is_chrome_build) {
// If the prompt is disabled for testing, never show it.
if (g_prompt_disabled_for_tests) {
return PromptType::kNone;
}
// If the profile isn't a regular profile, no prompt should ever be shown.
if (!IsRegularProfile(profile_type)) {
return PromptType::kNone;
}
// Forced testing feature parameters override everything.
if (base::FeatureList::IsEnabled(
privacy_sandbox::kDisablePrivacySandboxPrompts)) {
return PromptType::kNone;
}
if (privacy_sandbox::kPrivacySandboxSettings4ForceShowConsentForTesting
.Get()) {
return PromptType::kM1Consent;
}
if (privacy_sandbox::kPrivacySandboxSettings4ForceShowNoticeRowForTesting
.Get()) {
return PromptType::kM1NoticeROW;
}
if (privacy_sandbox::kPrivacySandboxSettings4ForceShowNoticeEeaForTesting
.Get()) {
return PromptType::kM1NoticeEEA;
}
if (privacy_sandbox::
kPrivacySandboxSettings4ForceShowNoticeRestrictedForTesting.Get()) {
return PromptType::kM1NoticeRestricted;
}
// Suppress the prompt if we force --no-first-run for testing
// and benchmarking.
if (IsFirstRunSuppressed(*base::CommandLine::ForCurrentProcess())) {
return PromptType::kNone;
}
// If this a non-Chrome build, do not show a prompt.
if (!is_chrome_build) {
return PromptType::kNone;
}
// If neither a notice nor a consent is required, do not show a prompt.
if (!privacy_sandbox::IsNoticeRequired() &&
!privacy_sandbox::IsConsentRequired()) {
return PromptType::kNone;
}
// Only one of the consent or notice should be required.
DCHECK(!privacy_sandbox::IsNoticeRequired() ||
!privacy_sandbox::IsConsentRequired());
// If a prompt was suppressed once, for any reason, it will forever remain
// suppressed and a prompt will not be shown.
if (static_cast<PromptSuppressedReason>(
pref_service->GetInteger(prefs::kPrivacySandboxM1PromptSuppressed)) !=
PromptSuppressedReason::kNone) {
return PromptType::kNone;
}
// If an Admin controls any of the K-APIs or suppresses the prompt explicitly
// then don't show the prompt.
if (IsM1PrivacySandboxEffectivelyManaged(pref_service)) {
return PromptType::kNone;
}
// If third party cookies are blocked, set the suppression reason as such, and
// do not show a prompt.
if (third_party_cookies_blocked) {
pref_service->SetInteger(
prefs::kPrivacySandboxM1PromptSuppressed,
static_cast<int>(PromptSuppressedReason::kThirdPartyCookiesBlocked));
return PromptType::kNone;
}
// If the Privacy Sandbox is restricted, set the suppression reason as such,
// and do not show a prompt.
if (privacy_sandbox_settings->IsPrivacySandboxRestricted() &&
!privacy_sandbox::IsRestrictedNoticeRequired()) {
pref_service->SetInteger(
prefs::kPrivacySandboxM1PromptSuppressed,
static_cast<int>(PromptSuppressedReason::kRestricted));
return PromptType::kNone;
}
if (privacy_sandbox::IsRestrictedNoticeRequired()) {
CHECK(privacy_sandbox::IsConsentRequired() ||
privacy_sandbox::IsNoticeRequired());
if (!pref_service->GetBoolean(
prefs::kPrivacySandboxM1RestrictedNoticeAcknowledged) &&
!pref_service->GetBoolean(
prefs::kPrivacySandboxM1EEANoticeAcknowledged) &&
!pref_service->GetBoolean(
prefs::kPrivacySandboxM1RowNoticeAcknowledged)) {
if (privacy_sandbox_settings->IsSubjectToM1NoticeRestricted()) {
return PromptType::kM1NoticeRestricted;
}
if (privacy_sandbox_settings->IsPrivacySandboxRestricted()) {
pref_service->SetInteger(
prefs::kPrivacySandboxM1PromptSuppressed,
static_cast<int>(PromptSuppressedReason::kNoticeShownToGuardian));
pref_service->SetBoolean(prefs::kPrivacySandboxM1AdMeasurementEnabled,
true);
return PromptType::kNone;
}
}
}
if (privacy_sandbox::IsConsentRequired()) {
if (pref_service->GetBoolean(prefs::kPrivacySandboxM1ConsentDecisionMade)) {
// Since a consent decision has been made, if the eea notice has already
// been acknowledged, do not show a prompt; else, show the eea notice.
if (pref_service->GetBoolean(
prefs::kPrivacySandboxM1EEANoticeAcknowledged)) {
return PromptType::kNone;
} else {
return PromptType::kM1NoticeEEA;
}
} else {
// A consent decision has not yet been made. If the user has seen a notice
// and disabled Topics, we should not attempt to consent them. As they
// already have sufficient notice for the other APIs, no prompt is
// required.
if (pref_service->GetBoolean(
prefs::kPrivacySandboxM1RowNoticeAcknowledged) &&
!pref_service->GetBoolean(prefs::kPrivacySandboxM1TopicsEnabled)) {
pref_service->SetInteger(
prefs::kPrivacySandboxM1PromptSuppressed,
static_cast<int>(
PromptSuppressedReason::
kROWFlowCompletedAndTopicsDisabledBeforeEEAMigration));
return PromptType::kNone;
}
return PromptType::kM1Consent;
}
}
DCHECK(privacy_sandbox::IsNoticeRequired());
// If a user that migrated from EEA to ROW has already completed the EEA
// consent and notice flow, set the suppression reason as such and do not show
// a prompt.
if (pref_service->GetBoolean(prefs::kPrivacySandboxM1ConsentDecisionMade) &&
(pref_service->GetBoolean(
prefs::kPrivacySandboxM1EEANoticeAcknowledged))) {
pref_service->SetInteger(
prefs::kPrivacySandboxM1PromptSuppressed,
static_cast<int>(
PromptSuppressedReason::kEEAFlowCompletedBeforeRowMigration));
return PromptType::kNone;
}
// If either the ROW notice or the restricted notice has already been
// acknowledged, do not show a prompt. Else, show the row notice prompt.
if (pref_service->GetBoolean(prefs::kPrivacySandboxM1RowNoticeAcknowledged) ||
pref_service->GetBoolean(
prefs::kPrivacySandboxM1RestrictedNoticeAcknowledged)) {
return PromptType::kNone;
} else {
return PromptType::kM1NoticeROW;
}
}
void PrivacySandboxServiceImpl::MaybeInitializeFirstPartySetsPref() {
// If initialization has already run, it is not required.
if (pref_service_->GetBoolean(
prefs::kPrivacySandboxFirstPartySetsDataAccessAllowedInitialized)) {
return;
}
// If the FPS UI is not available, no initialization is required.
if (!base::FeatureList::IsEnabled(
privacy_sandbox::kPrivacySandboxFirstPartySetsUI)) {
return;
}
// If the user blocks 3P cookies, disable the FPS data access preference.
// As this logic relies on checking synced preference state, it is possible
// that synced state is available when this decision is made. To err on the
// side of privacy, this init logic is run per-device (the pref recording that
// init has been run is not synced). If any of the user's devices local state
// would disable the pref, it is disabled across all devices.
if (ShouldBlockThirdPartyOrFirstPartyCookies(cookie_settings_.get())) {
pref_service_->SetBoolean(prefs::kPrivacySandboxRelatedWebsiteSetsEnabled,
false);
}
pref_service_->SetBoolean(
prefs::kPrivacySandboxFirstPartySetsDataAccessAllowedInitialized, true);
}
void PrivacySandboxServiceImpl::RecordUpdatedTopicsConsent(
privacy_sandbox::TopicsConsentUpdateSource source,
bool did_consent) const {
std::string consent_text;
switch (source) {
case privacy_sandbox::TopicsConsentUpdateSource::kDefaultValue: {
NOTREACHED_IN_MIGRATION();
break;
}
case privacy_sandbox::TopicsConsentUpdateSource::kConfirmation: {
consent_text = GetTopicsConfirmationText();
break;
}
case privacy_sandbox::TopicsConsentUpdateSource::kSettings: {
int current_topics_count = GetCurrentTopTopics().size();
int blocked_topics_count = GetBlockedTopics().size();
consent_text = GetTopicsSettingsText(
did_consent, current_topics_count > 0, blocked_topics_count > 0);
break;
}
default:
NOTREACHED_IN_MIGRATION();
}
pref_service_->SetBoolean(prefs::kPrivacySandboxTopicsConsentGiven,
did_consent);
pref_service_->SetTime(prefs::kPrivacySandboxTopicsConsentLastUpdateTime,
base::Time::Now());
pref_service_->SetInteger(prefs::kPrivacySandboxTopicsConsentLastUpdateReason,
static_cast<int>(source));
pref_service_->SetString(prefs::kPrivacySandboxTopicsConsentTextAtLastUpdate,
consent_text);
}
#if !BUILDFLAG(IS_ANDROID)
void PrivacySandboxServiceImpl::MaybeCloseOpenPrompts() {
// Take a copy to avoid concurrent modification issues as widgets close and
// remove themselves from the map synchronously. The map will typically have
// at most a few elements, so this is cheap.
// It is not possible that a new prompt may be added during this process, as
// all prompts are created on the same thread, based on information which does
// not cross task boundaries.
auto browsers_to_open_prompts_copy = browsers_to_open_prompts_;
for (const auto& browser_prompt : browsers_to_open_prompts_copy) {
auto* prompt = browser_prompt.second;
CHECK(prompt);
prompt->CloseWithReason(views::Widget::ClosedReason::kUnspecified);
}
}
#endif
void PrivacySandboxServiceImpl::InformSentimentService(PromptAction action) {
#if !BUILDFLAG(IS_ANDROID)
if (!sentiment_service_) {
return;
}
TrustSafetySentimentService::FeatureArea area;
switch (action) {
case PromptAction::kNoticeOpenSettings:
area = TrustSafetySentimentService::FeatureArea::
kPrivacySandbox4NoticeSettings;
break;
case PromptAction::kNoticeAcknowledge:
area = TrustSafetySentimentService::FeatureArea::kPrivacySandbox4NoticeOk;
break;
case PromptAction::kConsentAccepted:
area = TrustSafetySentimentService::FeatureArea::
kPrivacySandbox4ConsentAccept;
break;
case PromptAction::kConsentDeclined:
area = TrustSafetySentimentService::FeatureArea::
kPrivacySandbox4ConsentDecline;
break;
default:
return;
}
sentiment_service_->InteractedWithPrivacySandbox4(area);
#endif
}
void PrivacySandboxServiceImpl::RecordPromptActionMetrics(PromptAction action) {
switch (action) {
case PromptAction::kNoticeShown: {
base::RecordAction(
base::UserMetricsAction("Settings.PrivacySandbox.Notice.Shown"));
break;
}
case PromptAction::kNoticeOpenSettings: {
base::RecordAction(base::UserMetricsAction(
"Settings.PrivacySandbox.Notice.OpenedSettings"));
break;
}
case PromptAction::kNoticeAcknowledge: {
base::RecordAction(base::UserMetricsAction(
"Settings.PrivacySandbox.Notice.Acknowledged"));
break;
}
case PromptAction::kNoticeDismiss: {
base::RecordAction(
base::UserMetricsAction("Settings.PrivacySandbox.Notice.Dismissed"));
break;
}
case PromptAction::kNoticeClosedNoInteraction: {
base::RecordAction(base::UserMetricsAction(
"Settings.PrivacySandbox.Notice.ClosedNoInteraction"));
break;
}
case PromptAction::kConsentShown: {
base::RecordAction(
base::UserMetricsAction("Settings.PrivacySandbox.Consent.Shown"));
break;
}
case PromptAction::kConsentAccepted: {
base::RecordAction(
base::UserMetricsAction("Settings.PrivacySandbox.Consent.Accepted"));
break;
}
case PromptAction::kConsentDeclined: {
base::RecordAction(
base::UserMetricsAction("Settings.PrivacySandbox.Consent.Declined"));
break;
}
case PromptAction::kConsentMoreInfoOpened: {
base::RecordAction(base::UserMetricsAction(
"Settings.PrivacySandbox.Consent.LearnMoreExpanded"));
break;
}
case PromptAction::kConsentMoreInfoClosed: {
base::RecordAction(base::UserMetricsAction(
"Settings.PrivacySandbox.Consent.LearnMoreClosed"));
break;
}
case PromptAction::kConsentClosedNoDecision: {
base::RecordAction(base::UserMetricsAction(
"Settings.PrivacySandbox.Consent.ClosedNoInteraction"));
break;
}
case PromptAction::kNoticeLearnMore: {
base::RecordAction(
base::UserMetricsAction("Settings.PrivacySandbox.Notice.LearnMore"));
break;
}
case PromptAction::kNoticeMoreInfoOpened: {
base::RecordAction(base::UserMetricsAction(
"Settings.PrivacySandbox.Notice.LearnMoreExpanded"));
break;
}
case PromptAction::kNoticeMoreInfoClosed: {
base::RecordAction(base::UserMetricsAction(
"Settings.PrivacySandbox.Notice.LearnMoreClosed"));
break;
}
case PromptAction::kConsentMoreButtonClicked: {
base::RecordAction(base::UserMetricsAction(
"Settings.PrivacySandbox.Consent.MoreButtonClicked"));
break;
}
case PromptAction::kNoticeMoreButtonClicked: {
base::RecordAction(base::UserMetricsAction(
"Settings.PrivacySandbox.Notice.MoreButtonClicked"));
break;
}
case PromptAction::kRestrictedNoticeAcknowledge: {
base::RecordAction(base::UserMetricsAction(
"Settings.PrivacySandbox.RestrictedNotice.Acknowledged"));
break;
}
case PromptAction::kRestrictedNoticeOpenSettings: {
base::RecordAction(base::UserMetricsAction(
"Settings.PrivacySandbox.RestrictedNotice.OpenedSettings"));
break;
}
case PromptAction::kRestrictedNoticeShown: {
base::RecordAction(base::UserMetricsAction(
"Settings.PrivacySandbox.RestrictedNotice.Shown"));
break;
}
case PromptAction::kRestrictedNoticeClosedNoInteraction: {
base::RecordAction(base::UserMetricsAction(
"Settings.PrivacySandbox.RestrictedNotice.ClosedNoInteraction"));
break;
}
case PromptAction::kRestrictedNoticeMoreButtonClicked: {
base::RecordAction(base::UserMetricsAction(
"Settings.PrivacySandbox.RestrictedNotice.MoreButtonClicked"));
break;
}
}
}
void PrivacySandboxServiceImpl::OnTopicsPrefChanged() {
// If the user has disabled the preference, any related data stored should be
// cleared.
if (pref_service_->GetBoolean(prefs::kPrivacySandboxM1TopicsEnabled)) {
return;
}
if (browsing_topics_service_) {
browsing_topics_service_->ClearAllTopicsData();
}
}
void PrivacySandboxServiceImpl::OnFledgePrefChanged() {
// If the user has disabled the preference, any related data stored should be
// cleared.
if (pref_service_->GetBoolean(prefs::kPrivacySandboxM1FledgeEnabled)) {
return;
}
if (browsing_data_remover_) {
browsing_data_remover_->Remove(
base::Time::Min(), base::Time::Max(),
content::BrowsingDataRemover::DATA_TYPE_INTEREST_GROUPS |
content::BrowsingDataRemover::DATA_TYPE_SHARED_STORAGE |
content::BrowsingDataRemover::DATA_TYPE_INTEREST_GROUPS_INTERNAL,
content::BrowsingDataRemover::ORIGIN_TYPE_UNPROTECTED_WEB);
}
}
void PrivacySandboxServiceImpl::OnAdMeasurementPrefChanged() {
// If the user has disabled the preference, any related data stored should be
// cleared.
if (pref_service_->GetBoolean(prefs::kPrivacySandboxM1AdMeasurementEnabled)) {
return;
}
if (browsing_data_remover_) {
browsing_data_remover_->Remove(
base::Time::Min(), base::Time::Max(),
content::BrowsingDataRemover::DATA_TYPE_ATTRIBUTION_REPORTING |
content::BrowsingDataRemover::DATA_TYPE_AGGREGATION_SERVICE |
content::BrowsingDataRemover::
DATA_TYPE_PRIVATE_AGGREGATION_INTERNAL,
content::BrowsingDataRemover::ORIGIN_TYPE_UNPROTECTED_WEB);
}
}
// static
bool PrivacySandboxServiceImpl::IsM1PrivacySandboxEffectivelyManaged(
PrefService* pref_service) {
bool is_prompt_suppressed_by_policy =
pref_service->IsManagedPreference(
prefs::kPrivacySandboxM1PromptSuppressed) &&
static_cast<int>(PromptSuppressedReason::kPolicy) ==
pref_service->GetInteger(prefs::kPrivacySandboxM1PromptSuppressed);
return is_prompt_suppressed_by_policy ||
pref_service->IsManagedPreference(
prefs::kPrivacySandboxM1TopicsEnabled) ||
pref_service->IsManagedPreference(
prefs::kPrivacySandboxM1FledgeEnabled) ||
pref_service->IsManagedPreference(
prefs::kPrivacySandboxM1AdMeasurementEnabled);
}
// TODO(b/341978070): Move Clank Activity Type Impl into it's own service.
#if BUILDFLAG(IS_ANDROID)
void RecordPercentageMetrics(const base::Value::List& activity_type_record) {
using ActivityType = PrivacySandboxService::PrivacySandboxStorageActivityType;
std::unordered_map<ActivityType, int> activity_type_counts{
{ActivityType::kOther, 0},
{ActivityType::kTabbed, 0},
{ActivityType::kAGSACustomTab, 0},
{ActivityType::kNonAGSACustomTab, 0},
{ActivityType::kTrustedWebActivity, 0},
{ActivityType::kWebapp, 0},
{ActivityType::kWebApk, 0},
{ActivityType::kPreFirstTab, 0}};
for (const base::Value& record : activity_type_record) {
std::optional<int> activity_type_int =
record.GetDict().FindInt("activity_type");
CHECK(activity_type_int.has_value());
ActivityType activity_type =
static_cast<ActivityType>(activity_type_int.value());
activity_type_counts[activity_type]++;
}
std::unordered_map<ActivityType, int> activity_type_percentages;
// Set each activity type percentage based on the count / total_records.
for (const auto& [key, value] : activity_type_counts) {
double raw_percentage = (value * 100.0) / activity_type_record.size();
activity_type_percentages[key] = std::round(raw_percentage);
}
constexpr auto kTypesToHistogramSuffix =
base::MakeFixedFlatMap<ActivityType, std::string_view>(
{{ActivityType::kOther, "Other"},
{ActivityType::kTabbed, "BrApp"},
{ActivityType::kAGSACustomTab, "AGSACCT"},
{ActivityType::kNonAGSACustomTab, "NonAGSACCT"},
{ActivityType::kTrustedWebActivity, "TWA"},
{ActivityType::kWebapp, "WebApp"},
{ActivityType::kWebApk, "WebApk"},
{ActivityType::kPreFirstTab, "PreFirstTab"}});
// Emit all the histograms with each percentage value.
for (const auto& [type, suffix] : kTypesToHistogramSuffix) {
if (!activity_type_percentages.contains(type)) {
return;
}
base::UmaHistogramPercentage(
base::StrCat(
{"PrivacySandbox.ActivityTypeStorage.Percentage.", suffix, "2"}),
activity_type_percentages[type]);
}
}
void RecordUserSegmentMetrics(const base::Value::List& activity_type_record,
int records_in_a_row) {
// If a different value for records_in_a_row is needed for these metrics,
// tools/metrics/histograms/metadata/privacy/histograms.xml needs to be
// updated with new histograms. Currently, only 10MostRecentRecordsUserSegment
// and 20MostRecentRecordsUserSegment histograms are necessary.
DCHECK(records_in_a_row == 10 || records_in_a_row == 20);
// Can't emit user segment metrics when the size of the list is less than
// records_in_a_row
if (activity_type_record.size() < static_cast<size_t>(records_in_a_row)) {
return;
}
using ActivityType = PrivacySandboxService::PrivacySandboxStorageActivityType;
using SegmentType =
PrivacySandboxService::PrivacySandboxStorageUserSegmentByRecentActivity;
// Helper function to get the activity type from a base::Value
auto GetActivityType = [](const base::Value& record) -> ActivityType {
std::optional<int> activity_type_int =
record.GetDict().FindInt("activity_type");
CHECK(activity_type_int.has_value());
return static_cast<ActivityType>(activity_type_int.value());
};
std::unordered_set<ActivityType> encountered_activities;
for (int i = 0; i < records_in_a_row; ++i) {
encountered_activities.insert(GetActivityType(activity_type_record[i]));
}
SegmentType segment_type = SegmentType::kHasOther;
if (encountered_activities.contains(ActivityType::kTabbed)) {
segment_type = SegmentType::kHasBrowserApp;
} else if (encountered_activities.contains(ActivityType::kAGSACustomTab)) {
segment_type = SegmentType::kHasAGSACCT;
} else if (encountered_activities.contains(ActivityType::kNonAGSACustomTab)) {
segment_type = SegmentType::kHasNonAGSACCT;
} else if (encountered_activities.contains(ActivityType::kWebApk)) {
segment_type = SegmentType::kHasPWA;
} else if (encountered_activities.contains(
ActivityType::kTrustedWebActivity)) {
segment_type = SegmentType::kHasTWA;
} else if (encountered_activities.contains(ActivityType::kWebapp)) {
segment_type = SegmentType::kHasWebapp;
} else if (encountered_activities.contains(ActivityType::kPreFirstTab)) {
segment_type = SegmentType::kHasPreFirstTab;
}
base::UmaHistogramEnumeration(
base::StrCat({"PrivacySandbox.ActivityTypeStorage.",
base::NumberToString(records_in_a_row),
"MostRecentRecordsUserSegment2"}),
segment_type);
}
void RecordDaysSinceMetrics(const base::Value::List& activity_type_record) {
auto* timestamp =
activity_type_record[activity_type_record.size() - 1].GetDict().Find(
"timestamp");
CHECK(timestamp);
std::optional<base::Time> oldest_record_timestamp =
base::ValueToTime(*timestamp);
CHECK(oldest_record_timestamp.has_value());
int days_since_oldest_record =
(base::Time::Now() - oldest_record_timestamp.value()).InDays();
base::UmaHistogramCustomCounts(
"PrivacySandbox.ActivityTypeStorage.DaysSinceOldestRecord",
days_since_oldest_record, 1, 61, 60);
}
void RecordActivityTypeMetrics(const base::Value::List& activity_type_record,
base::Time current_time) {
int total_records = static_cast<int>(activity_type_record.size());
auto* oldest_record_timestamp_ptr =
activity_type_record[total_records - 1].GetDict().Find("timestamp");
CHECK(oldest_record_timestamp_ptr);
std::optional<base::Time> oldest_record_timestamp =
base::ValueToTime(*oldest_record_timestamp_ptr);
base::Time uma_enabled_timestamp =
base::Time::FromTimeT(g_browser_process->local_state()->GetInt64(
metrics::prefs::kMetricsReportingEnabledTimestamp));
// If a user has opted in, but the opt-in date is after the oldest record
// timestamp in the activity type list, then no metrics should be emitted.
if (oldest_record_timestamp.value() < uma_enabled_timestamp) {
return;
}
// Min: 1, Max: 201 (exclusive), Buckets: 200 (in case the max total records
// changes from 100).
base::UmaHistogramCustomCounts(
"PrivacySandbox.ActivityTypeStorage.RecordsLength",
static_cast<int>(activity_type_record.size()), 1, 201, 200);
RecordPercentageMetrics(activity_type_record);
RecordUserSegmentMetrics(activity_type_record, 10);
RecordUserSegmentMetrics(activity_type_record, 20);
RecordDaysSinceMetrics(activity_type_record);
}
void PrivacySandboxServiceImpl::RecordActivityType(
PrivacySandboxStorageActivityType type) const {
base::UmaHistogramEnumeration(
"PrivacySandbox.ActivityTypeStorage.TypeReceived", type);
// If skip-pre-first-tab is turned on, the list is not updated when the type
// passed in is kPreFirstTab.
if (type == PrivacySandboxService::PrivacySandboxStorageActivityType::
kPreFirstTab &&
privacy_sandbox::kPrivacySandboxActivityTypeStorageSkipPreFirstTab
.Get()) {
return;
}
// Activity type launches can only be recorded if they fall within a specific
// timeframe. This timeframe is determined by the within-x-days parameter,
// where oldest_timestamp_allowed marks the end of the timeframe and
// current_time marks the beginning.
base::Time current_time = base::Time::Now();
base::Time oldest_timestamp_allowed =
current_time -
base::Days(
privacy_sandbox::kPrivacySandboxActivityTypeStorageWithinXDays.Get());
base::Value::Dict new_dict;
new_dict.Set("timestamp", base::TimeToValue(current_time));
new_dict.Set("activity_type", static_cast<int>(type));
const base::Value::List& old_activity_type_record =
pref_service_->GetList(prefs::kPrivacySandboxActivityTypeRecord2);
base::Value::List new_activity_type_record;
new_activity_type_record.Append(std::move(new_dict));
int last_n_launches =
privacy_sandbox::kPrivacySandboxActivityTypeStorageLastNLaunches.Get();
// The list is ordered from most recent records in the beginning of the list
// and old records at the end of the list.
for (const base::Value& child : old_activity_type_record) {
const base::Value* child_timestamp_ptr = child.GetDict().Find("timestamp");
if (!child_timestamp_ptr) {
continue;
}
std::optional<base::Time> child_timestamp =
base::ValueToTime(*child_timestamp_ptr);
if (!child_timestamp.has_value()) {
continue;
}
if (current_time >= child_timestamp.value() &&
child_timestamp.value() >= oldest_timestamp_allowed &&
new_activity_type_record.size() <
static_cast<size_t>(last_n_launches)) {
new_activity_type_record.Append(child.Clone());
}
}
RecordActivityTypeMetrics(new_activity_type_record, current_time);
pref_service_->SetList(prefs::kPrivacySandboxActivityTypeRecord2,
std::move(new_activity_type_record));
}
#endif // BUILDFLAG(IS_ANDROID)