blob: 375a7a99411513530c9e68b74570f8768e49398a [file] [log] [blame]
// Copyright 2024 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "content/browser/btm/btm_service_impl.h"
#include <memory>
#include <optional>
#include <set>
#include <string>
#include <string_view>
#include <utility>
#include <vector>
#include "base/check.h"
#include "base/check_deref.h"
#include "base/debug/dump_without_crashing.h"
#include "base/feature_list.h"
#include "base/files/file_path.h"
#include "base/functional/bind.h"
#include "base/functional/callback_forward.h"
#include "base/functional/callback_helpers.h"
#include "base/memory/raw_ref.h"
#include "base/memory/scoped_refptr.h"
#include "base/metrics/histogram_functions.h"
#include "base/strings/strcat.h"
#include "base/strings/string_util.h"
#include "base/task/sequenced_task_runner.h"
#include "base/task/thread_pool.h"
#include "base/time/time.h"
#include "base/types/pass_key.h"
#include "content/browser/browser_context_impl.h"
#include "content/browser/btm/btm_storage.h"
#include "content/browser/btm/btm_utils.h"
#include "content/browser/btm/persistent_repeating_timer.h"
#include "content/public/browser/browser_context.h"
#include "content/public/browser/browser_task_traits.h"
#include "content/public/browser/browser_thread.h"
#include "content/public/browser/browsing_data_filter_builder.h"
#include "content/public/browser/browsing_data_remover.h"
#include "content/public/browser/btm_redirect_info.h"
#include "content/public/browser/content_browser_client.h"
#include "content/public/browser/web_contents.h"
#include "content/public/common/btm_utils.h"
#include "content/public/common/content_client.h"
#include "content/public/common/content_features.h"
#include "net/base/schemeful_site.h"
#include "net/cookies/cookie_partition_key.h"
#include "net/cookies/cookie_partition_key_collection.h"
#include "net/cookies/cookie_setting_override.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 "services/network/public/mojom/clear_data_filter.mojom.h"
#include "url/origin.h"
namespace content {
namespace {
BtmRedirectCategory ClassifyRedirect(BtmDataAccessType access,
bool has_user_activation) {
using enum BtmRedirectCategory;
switch (access) {
case BtmDataAccessType::kUnknown:
return has_user_activation ? kUnknownCookies_HasEngagement
: kUnknownCookies_NoEngagement;
case BtmDataAccessType::kNone:
return has_user_activation ? kNoCookies_HasEngagement
: kNoCookies_NoEngagement;
case BtmDataAccessType::kRead:
return has_user_activation ? kReadCookies_HasEngagement
: kReadCookies_NoEngagement;
case BtmDataAccessType::kWrite:
return has_user_activation ? kWriteCookies_HasEngagement
: kWriteCookies_NoEngagement;
case BtmDataAccessType::kReadWrite:
return has_user_activation ? kReadWriteCookies_HasEngagement
: kReadWriteCookies_NoEngagement;
}
}
inline void UmaHistogramBounceCategory(BtmRedirectCategory category,
BtmCookieMode mode,
BtmRedirectType type) {
const std::string histogram_name =
base::StrCat({"Privacy.DIPS.BounceCategory", GetHistogramPiece(type),
GetHistogramSuffix(mode)});
base::UmaHistogramEnumeration(histogram_name, category);
}
inline void UmaHistogramDeletionLatency(base::Time deletion_start) {
base::UmaHistogramLongTimes100("Privacy.DIPS.DeletionLatency2",
base::Time::Now() - deletion_start);
}
inline void UmaHistogramClearedSitesCount(BtmCookieMode mode, int size) {
base::UmaHistogramCounts1000(base::StrCat({"Privacy.DIPS.ClearedSitesCount",
GetHistogramSuffix(mode)}),
size);
}
inline void UmaHistogramBounceDelay(base::TimeDelta sample) {
base::UmaHistogramTimes("Privacy.DIPS.ServerBounceDelay", sample);
}
inline void UmaHistogramBounceChainDelay(base::TimeDelta sample) {
base::UmaHistogramTimes("Privacy.DIPS.ServerBounceChainDelay", sample);
}
inline void UmaHistogramBounceStatusCode(int response_code, bool cached) {
base::UmaHistogramSparse(cached ? "Privacy.DIPS.BounceStatusCode.Cached"
: "Privacy.DIPS.BounceStatusCode.NoCache",
response_code);
}
inline void UmaHistogramDeletion(BtmCookieMode mode, BtmDeletionAction action) {
base::UmaHistogramEnumeration(
base::StrCat({"Privacy.DIPS.Deletion", GetHistogramSuffix(mode)}),
action);
}
inline void UmaHistogramSiteToClearDomainLength(
std::string const& site_to_clear,
bool is_canonical_host) {
base::UmaHistogramSparse(
is_canonical_host ? "Privacy.DIPS.DeletionDomainLength.Serializable"
: "Privacy.DIPS.DeletionDomainLength.NonCanonical",
site_to_clear.length());
}
void RecordRedirectMetrics(const BtmRedirectInfo& redirect,
const BtmRedirectChainInfo& chain) {
DCHECK(redirect.site_had_user_activation.has_value());
DCHECK(redirect.site_had_webauthn_assertion.has_value());
DCHECK(redirect.chain_id.has_value());
DCHECK(redirect.chain_index.has_value());
DCHECK_LT(redirect.chain_index.value(), chain.length);
bool initial_site_same = (redirect.site == chain.initial_site);
bool final_site_same = (redirect.site == chain.final_site);
if (!chain.are_3pcs_generally_enabled) {
ukm::builders::BTM_Redirect(redirect.redirector_source_id)
.SetSiteHadUserActivation(redirect.site_had_user_activation.value())
.SetSiteHadWebAuthnAssertion(
redirect.site_had_webauthn_assertion.value())
.SetRedirectType(static_cast<int64_t>(redirect.redirect_type))
.SetCookieAccessType(static_cast<int64_t>(redirect.access_type))
.SetRedirectAndInitialSiteSame(initial_site_same)
.SetRedirectAndFinalSiteSame(final_site_same)
.SetInitialAndFinalSitesSame(chain.initial_and_final_sites_same)
.SetRedirectChainIndex(redirect.chain_index.value())
.SetRedirectChainLength(chain.length)
.SetIsPartialRedirectChain(chain.is_partial_chain)
.SetClientBounceDelay(
BucketizeBtmBounceDelay(redirect.client_bounce_delay))
.SetHasStickyActivation(redirect.has_sticky_activation)
.SetWebAuthnAssertionRequestSucceeded(
redirect.web_authn_assertion_request_succeeded)
.SetChainId(redirect.chain_id.value())
.Record(ukm::UkmRecorder::Get());
}
// Don't record UMA metrics for same-site redirects.
if (initial_site_same || final_site_same) {
return;
}
BtmRedirectCategory category = ClassifyRedirect(
redirect.access_type, redirect.site_had_user_activation.value());
UmaHistogramBounceCategory(category, chain.cookie_mode.value(),
redirect.redirect_type);
if (redirect.redirect_type == BtmRedirectType::kServer) {
UmaHistogramBounceDelay(redirect.server_bounce_delay);
UmaHistogramBounceStatusCode(redirect.response_code,
redirect.was_response_cached);
}
}
net::CookiePartitionKeyCollection CookiePartitionKeyCollectionForSites(
const std::vector<std::string>& sites) {
std::vector<net::CookiePartitionKey> keys;
for (const auto& site : sites) {
for (const auto& [scheme, port] :
{std::make_pair("http", 80), std::make_pair("https", 443)}) {
std::optional<url::Origin> origin =
url::Origin::UnsafelyCreateTupleOriginWithoutNormalization(
scheme, site, port);
UmaHistogramSiteToClearDomainLength(site, origin.has_value());
// The host may be non-canonical or invalid. In such a case, we ignore it,
// since it will cause IPC deserialization issues later on.
if (!origin.has_value()) {
break;
}
for (auto ancestorChainBit :
{net::CookiePartitionKey::AncestorChainBit::kSameSite,
net::CookiePartitionKey::AncestorChainBit::kCrossSite}) {
std::optional<net::CookiePartitionKey> key =
net::CookiePartitionKey::FromStorageKeyComponents(
net::SchemefulSite(*origin), ancestorChainBit,
/*nonce=*/std::nullopt);
if (key.has_value()) {
keys.push_back(*key);
}
}
}
}
return net::CookiePartitionKeyCollection(keys);
}
class StateClearer : public BrowsingDataRemover::Observer {
public:
StateClearer(const StateClearer&) = delete;
StateClearer& operator=(const StateClearer&) = delete;
~StateClearer() override { remover_->RemoveObserver(this); }
// Clears state for the sites in `sites_to_clear`. Runs `callback` once
// clearing is complete.
//
// NOTE: This deletion task removing rows for `sites_to_clear` from the
// BtmStorage backend relies on the assumption that rows flagged as BTM
// eligible don't have user activation time values. So even though 'remover'
// will only clear the storage timestamps, that's sufficient to delete the
// entire row.
static void DeleteState(BrowsingDataRemover* remover,
std::vector<std::string> sites_to_clear,
BrowsingDataRemover::DataType remove_mask,
base::OnceClosure callback) {
DCHECK_CURRENTLY_ON(BrowserThread::UI);
// This filter will match unpartitioned cookies and storage, as well as
// storage (but not cookies) that is partitioned under tracking domains.
std::unique_ptr<BrowsingDataFilterBuilder> filter =
BrowsingDataFilterBuilder::Create(
BrowsingDataFilterBuilder::Mode::kDelete);
for (const auto& site : sites_to_clear) {
filter->AddRegisterableDomain(site);
}
// Don't delete CHIPS partitioned under non-tracking sites.
filter->SetCookiePartitionKeyCollection(
net::CookiePartitionKeyCollection());
// This filter will match cookies partitioned under tracking domains.
std::unique_ptr<BrowsingDataFilterBuilder> partitioned_cookie_filter =
BrowsingDataFilterBuilder::Create(
BrowsingDataFilterBuilder::Mode::kPreserve);
partitioned_cookie_filter->SetCookiePartitionKeyCollection(
CookiePartitionKeyCollectionForSites(sites_to_clear));
partitioned_cookie_filter->SetPartitionedCookiesOnly(true);
// We don't add any domains to this filter, so with mode=kPreserve it will
// delete everything partitioned under the sites.
// StateClearer manages its own lifetime and deletes itself when finished.
StateClearer* clearer =
new StateClearer(remover, /*callback_count=*/2, std::move(callback));
// Don't delete Privacy Sandbox data - see crbug.com/41488981.
remove_mask &= ~BrowsingDataRemover::DATA_TYPE_PRIVACY_SANDBOX;
remover->RemoveWithFilterAndReply(
base::Time::Min(), base::Time::Max(),
remove_mask | BrowsingDataRemover::DATA_TYPE_AVOID_CLOSING_CONNECTIONS,
BrowsingDataRemover::ORIGIN_TYPE_UNPROTECTED_WEB |
BrowsingDataRemover::ORIGIN_TYPE_PROTECTED_WEB,
std::move(filter), clearer);
remover->RemoveWithFilterAndReply(
base::Time::Min(), base::Time::Max(),
BrowsingDataRemover::DATA_TYPE_COOKIES,
BrowsingDataRemover::ORIGIN_TYPE_UNPROTECTED_WEB |
BrowsingDataRemover::ORIGIN_TYPE_PROTECTED_WEB,
std::move(partitioned_cookie_filter), clearer);
}
private:
// StateClearer will run `callback` and delete itself after
// OnBrowsingDataRemoverDone() is called `callback_count` times.
StateClearer(BrowsingDataRemover* remover,
int callback_count,
base::OnceClosure callback)
: remover_(remover),
deletion_start_(base::Time::Now()),
expected_callback_count_(callback_count),
callback_(std::move(callback)) {
remover_->AddObserver(this);
}
// BrowsingDataRemover::Observer overrides:
void OnBrowsingDataRemoverDone(uint64_t failed_data_types) override {
CHECK_CURRENTLY_ON(BrowserThread::UI);
if (++callback_count_ == expected_callback_count_) {
UmaHistogramDeletionLatency(deletion_start_);
std::move(callback_).Run();
delete this; // Matches the new in DeleteState()
}
}
raw_ptr<BrowsingDataRemover> remover_;
const base::Time deletion_start_;
const int expected_callback_count_;
int callback_count_ = 0;
base::OnceClosure callback_;
};
class DipsTimerStorage : public PersistentRepeatingTimer::Storage {
public:
explicit DipsTimerStorage(base::SequenceBound<BtmStorage>* dips_storage);
~DipsTimerStorage() override;
// Reads the timestamp from the DIPS DB.
void GetLastFired(TimeCallback callback) const override {
dips_storage_->AsyncCall(&BtmStorage::GetTimerLastFired)
.Then(std::move(callback));
}
// Write the timestamp to the DIPS DB.
void SetLastFired(base::Time time) override {
dips_storage_->AsyncCall(base::IgnoreResult(&BtmStorage::SetTimerLastFired))
.WithArgs(time);
}
private:
raw_ref<base::SequenceBound<BtmStorage>> dips_storage_;
};
DipsTimerStorage::DipsTimerStorage(
base::SequenceBound<BtmStorage>* dips_storage)
: dips_storage_(CHECK_DEREF(dips_storage)) {}
DipsTimerStorage::~DipsTimerStorage() = default;
} // namespace
// static
BtmService* BtmService::Get(BrowserContext* context) {
return BtmServiceImpl::Get(context);
}
BtmServiceImpl::BtmServiceImpl(base::PassKey<BrowserContextImpl>,
BrowserContext* context)
: browser_context_(context) {
DCHECK(base::FeatureList::IsEnabled(features::kBtm));
base::FilePath btm_path = GetBtmFilePath(browser_context_);
// This feature explicitly uses in-memory storage on WebEngine on Fuchsia to
// avoid consuming too much storage space. WebEngine has only 2MB of storage
// for the user data directory.
const bool use_in_memory_db =
#if BUILDFLAG(IS_FUCHSIA) && defined(IS_WEB_ENGINE)
true;
#else
browser_context_->IsOffTheRecord();
#endif
storage_ =
use_in_memory_db
? base::SequenceBound<BtmStorage>(CreateTaskRunner(), std::nullopt)
: base::SequenceBound<BtmStorage>(
CreateTaskRunnerForResource(btm_path), btm_path);
#if BUILDFLAG(IS_FUCHSIA) && defined(IS_WEB_ENGINE)
// WebEngine on Fuchsia has a limited amount of storage, so we don't want to
// keep around any data from previous sessions before the change was made to
// always use an in-memory database.
BtmStorage::DeleteDatabaseFiles(btm_path,
fuchsia_cleanup_loop_.QuitClosure());
#endif
repeating_timer_ = CreateTimer();
repeating_timer_->Start();
}
std::unique_ptr<PersistentRepeatingTimer> BtmServiceImpl::CreateTimer() {
CHECK(!storage_.is_null());
// base::Unretained(this) is safe here since the timer that is created has the
// same lifetime as this service.
return std::make_unique<PersistentRepeatingTimer>(
std::make_unique<DipsTimerStorage>(&storage_),
features::kBtmTimerDelay.Get(),
base::BindRepeating(&BtmServiceImpl::OnTimerFired,
base::Unretained(this)));
}
BtmServiceImpl::~BtmServiceImpl() {
// Some UserData may interact with `this` during their destruction. Delete
// them now, before it's too late. If we don't delete them manually,
// ~SupportsUserData() will, but `this` will be invalid at that time.
//
// Note that we can't put this call in ~BtmService() either, even though
// BtmService is the class that directly inherits from SupportsUserData.
// Because when ~BtmService() is called, it's undefined behavior to call
// pure virtual functions like BtmService::RemoveObserver().
ClearAllUserData();
}
// static
BtmServiceImpl* BtmServiceImpl::Get(BrowserContext* context) {
return BrowserContextImpl::From(context)->GetBtmService();
}
scoped_refptr<base::SequencedTaskRunner> BtmServiceImpl::CreateTaskRunner() {
return base::ThreadPool::CreateSequencedTaskRunner(
{base::MayBlock(), base::TaskPriority::BEST_EFFORT,
base::ThreadPolicy::PREFER_BACKGROUND});
}
scoped_refptr<base::SequencedTaskRunner>
BtmServiceImpl::CreateTaskRunnerForResource(const base::FilePath& path) {
return base::ThreadPool::CreateSequencedTaskRunnerForResource(
{base::MayBlock(), base::TaskPriority::BEST_EFFORT,
base::ThreadPolicy::PREFER_BACKGROUND},
path);
}
BtmCookieMode BtmServiceImpl::GetCookieMode() const {
return GetBtmCookieMode(browser_context_->IsOffTheRecord());
}
void BtmServiceImpl::RemoveEvents(const base::Time& delete_begin,
const base::Time& delete_end,
network::mojom::ClearDataFilterPtr filter,
BtmEventRemovalType type) {
// Storage init should be finished by now, so no need to delay until then.
storage_.AsyncCall(&BtmStorage::RemoveEvents)
.WithArgs(delete_begin, delete_end, std::move(filter), type);
}
void BtmServiceImpl::HandleRedirectChain(
std::vector<BtmRedirectInfoPtr> redirects,
BtmRedirectChainInfoPtr chain,
StatefulBounceCallback stateful_bounce_callback) {
DCHECK_LE(redirects.size(), chain->length);
if (redirects.empty()) {
DCHECK(!chain->is_partial_chain);
for (auto& observer : observers_) {
observer.OnChainHandled(redirects, chain);
}
return;
}
if (!chain->are_3pcs_generally_enabled &&
chain->initial_source_id != ukm::kInvalidSourceId) {
ukm::builders::BTM_ChainBegin(chain->initial_source_id)
.SetChainId(chain->chain_id)
.SetInitialAndFinalSitesSame(chain->initial_and_final_sites_same)
.Record(ukm::UkmRecorder::Get());
}
if (!chain->are_3pcs_generally_enabled &&
chain->final_source_id != ukm::kInvalidSourceId) {
ukm::builders::BTM_ChainEnd(chain->final_source_id)
.SetChainId(chain->chain_id)
.SetInitialAndFinalSitesSame(chain->initial_and_final_sites_same)
.Record(ukm::UkmRecorder::Get());
}
std::set<std::string> redirect_sites;
base::TimeDelta total_server_bounce_delay;
for (const auto& redirect : redirects) {
if (redirect->redirect_type == BtmRedirectType::kServer) {
total_server_bounce_delay += redirect->server_bounce_delay;
}
redirect_sites.insert(GetSiteForBtm(redirect->redirector_url));
}
UmaHistogramBounceChainDelay(total_server_bounce_delay);
chain->cookie_mode = GetCookieMode();
storage_.AsyncCall(&BtmStorage::FilterSitesWithProtectiveEvent)
.WithArgs(redirect_sites)
.Then(base::BindOnce(&BtmServiceImpl::HandleRedirects,
weak_factory_.GetWeakPtr(), std::move(redirects),
std::move(chain), stateful_bounce_callback));
}
void BtmServiceImpl::HandleRedirects(
std::vector<BtmRedirectInfoPtr> redirects,
BtmRedirectChainInfoPtr chain,
StatefulBounceCallback stateful_bounce_callback,
std::pair<std::set<std::string>, std::set<std::string>>
sites_with_protective_events) {
const auto& [sites_with_user_activation, sites_with_webauthn_assertion] =
sites_with_protective_events;
for (size_t index = 0; index < redirects.size(); index++) {
auto& redirect = *redirects[index];
DCHECK(!redirect.site_had_user_activation.has_value());
redirect.site_had_user_activation =
sites_with_user_activation.contains(redirect.site);
DCHECK(!redirect.site_had_webauthn_assertion.has_value());
redirect.site_had_webauthn_assertion =
sites_with_webauthn_assertion.contains(redirect.site);
DCHECK(!redirect.chain_id.has_value());
redirect.chain_id = chain->chain_id;
// If the chain was too long, some redirects may have been trimmed already,
// which would make `index` not the "true" index of the redirect in the
// whole chain. `chain->length` is accurate though. `chain->length -
// redirects.size()` is then the number of trimmed redirects; so add that to
// `index` to get the "true" index to report in our metrics.
DCHECK(!redirect.chain_index.has_value());
redirect.chain_index = chain->length - redirects.size() + index;
RecordRedirectMetrics(redirect, *chain);
bool initial_site_same = (redirect.site == chain->initial_site);
bool final_site_same = (redirect.site == chain->final_site);
if (initial_site_same || final_site_same) {
continue;
}
if (redirect.access_type == BtmDataAccessType::kUnknown) {
continue;
}
RecordBounce(stateful_bounce_callback, redirect, *chain);
}
// All redirects handled.
if (!chain->is_partial_chain) {
for (auto& observer : observers_) {
observer.OnChainHandled(redirects, chain);
}
}
}
void BtmServiceImpl::RecordBounce(
StatefulBounceCallback stateful_bounce_callback,
const BtmRedirectInfo& redirect,
const BtmRedirectChainInfo& chain) {
const GURL& url = redirect.redirector_url;
bool stateful = redirect.access_type > BtmDataAccessType::kRead;
// If the bounced URL has a 3PC exception when embedded under the initial or
// final URL in the redirect, then clear the tracking site from the BTM
// database to avoid deleting its storage. The exception overrides any bounces
// from non-excepted sites.
if (redirect.has_3pc_exception.value()) {
// Check whether the site would have hypothetically been cleared.
bool would_be_cleared;
// TODO(crbug.com/430921459): Refactor killswitch behavior into the
// top-level feature so there's no need to maintain multiple triggering
// actions.
switch (features::kBtmTriggeringAction.Get()) {
case BtmTriggeringAction::kNone: {
would_be_cleared = false;
break;
}
case BtmTriggeringAction::kBounce: {
would_be_cleared = true;
break;
}
}
if (!chain.are_3pcs_generally_enabled && would_be_cleared) {
// TODO(crbug.com/40268849): Investigate and fix the presence of empty
// site(s) in the `site_to_clear` list. Once this is fixed remove this
// escape.
if (url.is_empty()) {
UmaHistogramDeletion(GetCookieMode(), BtmDeletionAction::kIgnored);
return;
}
UmaHistogramDeletion(GetCookieMode(), BtmDeletionAction::kExcepted);
}
const std::set<std::string> site_to_clear{GetSiteForBtm(url)};
// Don't clear the row if the tracker has history indicating that we
// should preserve that context for future bounces.
storage_.AsyncCall(&BtmStorage::RemoveRowsWithoutProtectiveEvent)
.WithArgs(site_to_clear);
return;
}
// If the bounce is stateful and not excepted by cookie settings, run the
// callback.
if (stateful) {
stateful_bounce_callback.Run(chain.final_url);
}
// Record the bounce at the storage layer.
storage_.AsyncCall(&BtmStorage::RecordBounce).WithArgs(url, redirect.time);
}
// static
void BtmServiceImpl::RecordRedirectMetricsForTesting(
const BtmRedirectInfo& redirect,
const BtmRedirectChainInfo& chain) {
RecordRedirectMetrics(redirect, chain);
}
void BtmServiceImpl::OnTimerFired() {
// Storage init should be finished by now, so no need to delay until then.
storage_.AsyncCall(&BtmStorage::GetSitesToClear)
.WithArgs(std::nullopt)
.Then(base::BindOnce(&BtmServiceImpl::DeleteBtmEligibleState,
weak_factory_.GetWeakPtr(), base::DoNothing()));
}
void BtmServiceImpl::DeleteEligibleSitesImmediately(
DeletedSitesCallback callback) {
// Storage init should be finished by now, so no need to delay until then.
storage_.AsyncCall(&BtmStorage::GetSitesToClear)
.WithArgs(base::Seconds(0))
.Then(base::BindOnce(&BtmServiceImpl::DeleteBtmEligibleState,
weak_factory_.GetWeakPtr(), std::move(callback)));
}
void BtmServiceImpl::DeleteBtmEligibleState(
DeletedSitesCallback callback,
std::vector<std::string> sites_to_clear) {
// Do not clear sites from currently open tabs.
for (const std::pair<std::string, int> site_ctr : open_sites_) {
CHECK(site_ctr.second > 0);
std::erase(sites_to_clear, site_ctr.first);
}
std::vector<std::string> filtered_sites_to_clear;
for (const auto& site : sites_to_clear) {
// TODO(crbug.com/40268849): Investigate and fix the presence of empty
// site(s) in the `site_to_clear` list. Once this is fixed remove this loop
// escape.
if (site.empty()) {
UmaHistogramDeletion(GetCookieMode(), BtmDeletionAction::kIgnored);
continue;
}
UmaHistogramDeletion(GetCookieMode(), BtmDeletionAction::kEnforced);
const ukm::SourceId source_id = ukm::UkmRecorder::GetSourceIdForDipsSite(
base::PassKey<BtmServiceImpl>(), site);
ukm::builders::DIPS_Deletion(source_id)
// These settings are checked at bounce time, before logging the bounce.
// At this time, we guarantee that 3PC are blocked and this site is not
// excepted (provided the user hasn't changed their settings in the
// meantime).
.SetShouldBlockThirdPartyCookies(true)
.SetHasCookieException(false)
.SetIsDeletionEnabled(true)
.Record(ukm::UkmRecorder::Get());
filtered_sites_to_clear.push_back(site);
}
UmaHistogramClearedSitesCount(GetCookieMode(), sites_to_clear.size());
base::OnceClosure finish_callback = base::BindOnce(
std::move(callback), std::vector<std::string>(filtered_sites_to_clear));
if (filtered_sites_to_clear.empty()) {
std::move(finish_callback).Run();
return;
}
// Perform state deletion on the filtered list of sites.
RunDeletionTaskOnUIThread(std::move(filtered_sites_to_clear),
std::move(finish_callback));
}
void BtmServiceImpl::RunDeletionTaskOnUIThread(std::vector<std::string> sites,
base::OnceClosure callback) {
DCHECK_CURRENTLY_ON(BrowserThread::UI);
uint64_t remove_mask = GetContentClient()->browser()->GetBtmRemoveMask();
StateClearer::DeleteState(browser_context_->GetBrowsingDataRemover(),
std::move(sites), remove_mask, std::move(callback));
}
void BtmServiceImpl::AddObserver(Observer* observer) {
observers_.AddObserver(observer);
}
void BtmServiceImpl::RemoveObserver(const Observer* observer) {
observers_.RemoveObserver(observer);
}
void BtmServiceImpl::RecordUserActivationForTesting(const GURL& url) {
storage_.AsyncCall(&BtmStorage::RecordUserActivation)
.WithArgs(url, base::Time::Now());
}
void BtmServiceImpl::DidSiteHaveUserActivationSince(
const GURL& url,
base::Time bound,
CheckUserActivationCallback callback) const {
storage_.AsyncCall(&BtmStorage::DidSiteHaveUserActivationSince)
.WithArgs(url, bound)
.Then(std::move(callback));
}
void BtmServiceImpl::RecordBrowserSignIn(std::string_view domain) {
storage()
->AsyncCall(&BtmStorage::RecordUserActivation)
.WithArgs(url::SchemeHostPort("http", domain, 80).GetURL(),
base::Time::Now());
}
void BtmServiceImpl::NotifyStatefulBounce(WebContents* web_contents) {
for (auto& observer : observers_) {
observer.OnStatefulBounce(web_contents);
}
}
} // namespace content