blob: fc5a6eb4d1a55e849c6a3bcaccb4d657c8298fce [file] [log] [blame]
// Copyright 2017 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "services/network/restricted_cookie_manager.h"
#include <algorithm>
#include <cstdint>
#include <memory>
#include <utility>
#include <vector>
#include "base/check.h"
#include "base/compiler_specific.h" // for [[fallthrough]];
#include "base/feature_list.h"
#include "base/functional/bind.h"
#include "base/memory/raw_ptr.h"
#include "base/memory/read_only_shared_memory_region.h"
#include "base/memory/structured_shared_memory.h"
#include "base/memory/weak_ptr.h"
#include "base/metrics/histogram_functions.h"
#include "base/rand_util.h"
#include "base/run_loop.h"
#include "base/strings/string_util.h"
#include "base/task/sequenced_task_runner.h"
#include "base/timer/elapsed_timer.h"
#include "base/trace_event/trace_event.h"
#include "base/types/optional_util.h"
#include "mojo/public/cpp/base/shared_memory_version.h"
#include "mojo/public/cpp/bindings/message.h"
#include "mojo/public/cpp/bindings/remote.h"
#include "net/base/isolation_info.h"
#include "net/base/registry_controlled_domains/registry_controlled_domain.h"
#include "net/cookies/canonical_cookie.h"
#include "net/cookies/cookie_access_params.h"
#include "net/cookies/cookie_access_result.h"
#include "net/cookies/cookie_constants.h"
#include "net/cookies/cookie_inclusion_status.h"
#include "net/cookies/cookie_options.h"
#include "net/cookies/cookie_partition_key.h"
#include "net/cookies/cookie_setting_override.h"
#include "net/cookies/cookie_store.h"
#include "net/cookies/cookie_util.h"
#include "net/cookies/site_for_cookies.h"
#include "net/cookies/unique_cookie_key.h"
#include "net/first_party_sets/first_party_set_metadata.h"
#include "net/first_party_sets/first_party_sets_cache_filter.h"
#include "net/storage_access_api/status.h"
#include "services/network/ad_heuristic_cookie_overrides.h"
#include "services/network/cookie_settings.h"
#include "services/network/public/cpp/features.h"
#include "services/network/public/mojom/cookie_manager.mojom.h"
#include "services/network/public/mojom/network_context.mojom.h"
#include "services/network/public/mojom/restricted_cookie_manager.mojom.h"
#include "url/gurl.h"
namespace network {
namespace {
static constexpr int kHoursInOneWeek = 24 * 7;
static constexpr int kHoursInOneYear = 24 * 365;
// How often to call CookieObserveer.OnCookiesAccessed. This value was picked
// because it reduces calls by up to 90% on slow Android devices while not
// adding a user-perceptible delay.
constexpr base::TimeDelta kCookiesAccessedTimeout = base::Milliseconds(100);
constexpr size_t kMaxCookieCacheCount = 32u;
// TODO(https://crbug.com/375352611): add the check for enabling third-party
// cookies.
constexpr uint64_t kAllowedDevToolsCookieSettingOverrides =
1u << static_cast<int>(
net::CookieSettingOverride::kForceDisableThirdPartyCookies) |
1u << static_cast<int>(
net::CookieSettingOverride::kForceEnableThirdPartyCookieMitigations) |
1u << static_cast<int>(net::CookieSettingOverride::kSkipTPCDMetadataGrant) |
1u << static_cast<int>(
net::CookieSettingOverride::kSkipTPCDHeuristicsGrant);
net::CookieOptions MakeOptionsForSet(
mojom::RestrictedCookieManagerRole role,
const GURL& url,
const net::SiteForCookies& site_for_cookies,
const CookieSettings& cookie_settings) {
net::CookieOptions options;
bool force_ignore_site_for_cookies =
cookie_settings.ShouldIgnoreSameSiteRestrictions(url, site_for_cookies);
if (role == mojom::RestrictedCookieManagerRole::SCRIPT) {
options.set_exclude_httponly(); // Default, but make it explicit here.
options.set_same_site_cookie_context(
net::cookie_util::ComputeSameSiteContextForScriptSet(
url, site_for_cookies, force_ignore_site_for_cookies));
} else {
// mojom::RestrictedCookieManagerRole::NETWORK
options.set_include_httponly();
options.set_same_site_cookie_context(
net::cookie_util::ComputeSameSiteContextForSubresource(
url, site_for_cookies, force_ignore_site_for_cookies));
}
return options;
}
net::CookieOptions MakeOptionsForGet(
mojom::RestrictedCookieManagerRole role,
const GURL& url,
const net::SiteForCookies& site_for_cookies,
const CookieSettings& cookie_settings) {
// TODO(crbug.com/40611099): Wire initiator here.
net::CookieOptions options;
bool force_ignore_site_for_cookies =
cookie_settings.ShouldIgnoreSameSiteRestrictions(url, site_for_cookies);
if (role == mojom::RestrictedCookieManagerRole::SCRIPT) {
options.set_exclude_httponly(); // Default, but make it explicit here.
options.set_same_site_cookie_context(
net::cookie_util::ComputeSameSiteContextForScriptGet(
url, site_for_cookies, std::nullopt /*initiator*/,
force_ignore_site_for_cookies));
} else {
// mojom::RestrictedCookieManagerRole::NETWORK
options.set_include_httponly();
options.set_same_site_cookie_context(
net::cookie_util::ComputeSameSiteContextForSubresource(
url, site_for_cookies, force_ignore_site_for_cookies));
}
return options;
}
// Records the time until expiration for a cookie set via script.
void HistogramScriptCookieExpiration(const net::CanonicalCookie& cookie) {
// Ignore session cookies as they have no expiration date.
if (!cookie.IsPersistent()) {
return;
}
// We are studying the requested expiration dates of cookies set via script.
// Network cookies are handled in
// URLRequestHttpJob::SaveCookiesAndNotifyHeadersComplete.
const int script_cookie_expiration_in_hours =
(cookie.ExpiryDate() - base::Time::Now()).InHours();
if (script_cookie_expiration_in_hours > kHoursInOneWeek) {
base::UmaHistogramCustomCounts(
"Cookie.ScriptExpirationInHoursGTOneWeek.Subsampled",
script_cookie_expiration_in_hours, kHoursInOneWeek + 1, kHoursInOneYear,
100);
} else {
base::UmaHistogramCustomCounts(
"Cookie.ScriptExpirationInHoursLTEOneWeek.Subsampled",
script_cookie_expiration_in_hours, 1, kHoursInOneWeek + 1, 100);
}
}
void RunCallbackWithResponse(
RestrictedCookieManager::SetCookieFromStringCallback callback,
uint64_t version,
base::ReadOnlySharedMemoryRegion shared_memory_region,
const std::string& cookies) {
std::move(callback).Run(mojom::CookiesResponse::New(
version, std::move(shared_memory_region), cookies));
}
} // namespace
RestrictedCookieManager::UmaMetricsUpdater::UmaMetricsUpdater() = default;
RestrictedCookieManager::UmaMetricsUpdater::~UmaMetricsUpdater() = default;
// static
void RestrictedCookieManager::ComputeFirstPartySetMetadata(
const url::Origin& origin,
const net::CookieStore* cookie_store,
const net::IsolationInfo& isolation_info,
base::OnceCallback<void(net::FirstPartySetMetadata)> callback) {
std::pair<base::OnceCallback<void(net::FirstPartySetMetadata)>,
base::OnceCallback<void(net::FirstPartySetMetadata)>>
callbacks = base::SplitOnceCallback(std::move(callback));
std::optional<std::pair<net::FirstPartySetMetadata,
net::FirstPartySetsCacheFilter::MatchInfo>>
metadata_and_match_info =
net::cookie_util::ComputeFirstPartySetMetadataMaybeAsync(
/*request_site=*/net::SchemefulSite(origin), isolation_info,
cookie_store->cookie_access_delegate(),
base::BindOnce([](net::FirstPartySetMetadata metadata,
net::FirstPartySetsCacheFilter::MatchInfo
match_info) {
return metadata;
}).Then(std::move(callbacks.first)));
if (metadata_and_match_info.has_value()) {
std::move(callbacks.second)
.Run(std::move(metadata_and_match_info.value().first));
}
}
bool CookieWithAccessResultComparer::operator()(
const net::CookieWithAccessResult& cookie_with_access_result1,
const net::CookieWithAccessResult& cookie_with_access_result2) const {
// Compare just the cookie portion of the CookieWithAccessResults so a cookie
// only ever has one entry in the map. For a given cookie we want to send a
// new access notification whenever its access results change. If we keyed off
// of both the cookie and its current access result, if a cookie shifted from
// "allowed" to "blocked" the cookie would wind up with two entries in the
// map. If the cookie then shifted back to "allowed" we wouldn't send a new
// notification because cookie/allowed already existed in the map. In the case
// of a cookie shifting from "allowed" to "blocked,"
// SkipAccessNotificationForCookieItem() checks the access result. If the
// cookie exists in the map but its status is "allowed" we evict the old
// entry.
return cookie_with_access_result1.cookie < cookie_with_access_result2.cookie;
}
// Optimized comparisons using a key directly, to avoid key recalculation.
bool CookieWithAccessResultComparer::operator()(
const net::RefUniqueCookieKey& key1,
const net::CookieWithAccessResult& cookie_with_access_result2) const {
return key1 < cookie_with_access_result2.cookie.RefUniqueKey();
}
bool CookieWithAccessResultComparer::operator()(
const net::CookieWithAccessResult& cookie_with_access_result1,
const net::RefUniqueCookieKey& key2) const {
return cookie_with_access_result1.cookie.RefUniqueKey() < key2;
}
CookieAccesses* RestrictedCookieManager::GetCookieAccessesForURLAndSite(
const GURL& url,
const net::SiteForCookies& site_for_cookies) {
std::unique_ptr<CookieAccesses>& entry =
recent_cookie_accesses_[std::make_pair(url, site_for_cookies)];
if (!entry) {
entry = std::make_unique<CookieAccesses>();
}
return entry.get();
}
bool RestrictedCookieManager::SkipAccessNotificationForCookieItem(
CookieAccesses* cookie_accesses,
const net::CookieWithAccessResult& cookie_item) {
DCHECK(cookie_accesses);
// Have we sent information about this cookie to the |cookie_observer_|
// before?
std::set<net::CookieWithAccessResult>::iterator existing_slot =
cookie_accesses->find(cookie_item.cookie.RefUniqueKey());
// If this is the first time seeing this cookie make a note and don't skip
// the notification.
if (existing_slot == cookie_accesses->end()) {
// Don't store more than a max number of cookies, in the interest of
// limiting memory consumption.
if (cookie_accesses->size() == max_cookie_cache_count_) {
cookie_accesses->clear();
}
cookie_accesses->insert(cookie_item);
return false;
}
// If the cookie and its access result are likely unchanged since we last
// updated the `cookie_observer_`, skip notifying the `cookie_observer_`
// again.
if (existing_slot->cookie.IsProbablyEquivalentTo(cookie_item.cookie) &&
existing_slot->access_result == cookie_item.access_result) {
return true;
}
// The cookie's access result or data has changed - update them in the record
// of what we've sent to the |cookie_observer_|. It's safe to update the
// existing entry in the set because the changed fields do not determine the
// CookieWithAccessResult's location in the set.
const_cast<net::CookieWithAccessResult&>(*existing_slot) = cookie_item;
// Don't skip notifying the |cookie_observer_| of the change.
return false;
}
class RestrictedCookieManager::Listener : public base::LinkNode<Listener> {
public:
Listener(net::CookieStore* cookie_store,
const RestrictedCookieManager* restricted_cookie_manager,
const GURL& url,
const net::SiteForCookies& site_for_cookies,
const url::Origin& top_frame_origin,
net::StorageAccessApiStatus storage_access_api_status,
const std::optional<net::CookiePartitionKey>& cookie_partition_key,
net::CookieOptions options,
mojo::PendingRemote<mojom::CookieChangeListener> mojo_listener)
: cookie_store_(cookie_store),
restricted_cookie_manager_(restricted_cookie_manager),
url_(url),
site_for_cookies_(site_for_cookies),
top_frame_origin_(top_frame_origin),
storage_access_api_status_(storage_access_api_status),
options_(options),
mojo_listener_(std::move(mojo_listener)) {
// TODO(pwnall): add a constructor w/options to net::CookieChangeDispatcher.
cookie_store_subscription_ =
cookie_store->GetChangeDispatcher().AddCallbackForUrl(
url, cookie_partition_key,
base::BindRepeating(
&Listener::OnCookieChange,
// Safe because net::CookieChangeDispatcher guarantees that
// the callback will stop being called immediately after we
// remove the subscription, and the cookie store lives on
// the same thread as we do.
base::Unretained(this)));
}
Listener(const Listener&) = delete;
Listener& operator=(const Listener&) = delete;
~Listener() { DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); }
mojo::Remote<mojom::CookieChangeListener>& mojo_listener() {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
return mojo_listener_;
}
private:
// net::CookieChangeDispatcher callback.
void OnCookieChange(const net::CookieChangeInfo& change) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
bool delegate_treats_url_as_trustworthy =
cookie_store_->cookie_access_delegate() &&
cookie_store_->cookie_access_delegate()->ShouldTreatUrlAsTrustworthy(
url_);
// CookieChangeDispatcher doesn't check for inclusion against `options_`, so
// we need to double-check that.
if (!change.cookie
.IncludeForRequestURL(
url_, options_,
net::CookieAccessParams{change.access_result.access_semantics,
change.access_result.scope_semantics,
delegate_treats_url_as_trustworthy})
.status.IsInclude()) {
return;
}
// TODO(crbug.com/390010271): Consider whether/how to apply devtools cookies
// setting overrides for Listeners.
// When a user blocks a site's access to cookies, the existing cookies are
// not deleted. This check prevents the
// site from observing their cookies being deleted at a later time, which
// can happen due to eviction or due to the user explicitly deleting all
// cookies.
if (!restricted_cookie_manager_->cookie_settings().IsCookieAccessible(
change.cookie, url_, site_for_cookies_, top_frame_origin_,
restricted_cookie_manager_->first_party_set_metadata_,
restricted_cookie_manager_->GetCookieSettingOverrides(
storage_access_api_status_, /*is_ad_tagged=*/false,
/*apply_devtools_overrides=*/false,
/*force_disable_third_party_cookies=*/false),
/*cookie_inclusion_status=*/nullptr)) {
return;
}
mojo_listener_->OnCookieChange(change);
}
// Expected to outlive |restricted_cookie_manager_| which outlives this.
raw_ptr<const net::CookieStore> cookie_store_;
// The CookieChangeDispatcher subscription used by this listener.
std::unique_ptr<net::CookieChangeSubscription> cookie_store_subscription_;
// Raw pointer usage is safe because RestrictedCookieManager owns this
// instance and is guaranteed to outlive it.
const raw_ptr<const RestrictedCookieManager> restricted_cookie_manager_;
// The URL whose cookies this listener is interested in.
const GURL url_;
// Site context in which we're used; used to determine if a cookie is accessed
// in a third-party context.
const net::SiteForCookies site_for_cookies_;
// Site context in which we're used; used to check content settings.
const url::Origin top_frame_origin_;
// Whether the Listener has storage access. Note that if a listener is created
// from a document that has not called `document.requestStorageAccess()`, and
// the script later calls `document.requestStorageAccess()` to obtain storage
// access, this listener's state will not be updated.
const net::StorageAccessApiStatus storage_access_api_status_;
// CanonicalCookie::IncludeForRequestURL options for this listener's interest.
const net::CookieOptions options_;
mojo::Remote<mojom::CookieChangeListener> mojo_listener_;
SEQUENCE_CHECKER(sequence_checker_);
};
RestrictedCookieManager::RestrictedCookieManager(
const mojom::RestrictedCookieManagerRole role,
net::CookieStore* cookie_store,
const CookieSettings& cookie_settings,
const url::Origin& origin,
const net::IsolationInfo& isolation_info,
const net::CookieSettingOverrides& cookie_setting_overrides,
const net::CookieSettingOverrides& devtools_cookie_setting_overrides,
mojo::PendingRemote<mojom::CookieAccessObserver> cookie_observer,
net::FirstPartySetMetadata first_party_set_metadata,
UmaMetricsUpdater* metrics_updater)
: role_(role),
cookie_store_(cookie_store),
cookie_settings_(cookie_settings),
cookie_setting_overrides_(cookie_setting_overrides),
devtools_cookie_setting_overrides_(devtools_cookie_setting_overrides),
origin_(origin),
isolation_info_(isolation_info),
cookie_observer_(std::move(cookie_observer)),
first_party_set_metadata_(std::move(first_party_set_metadata)),
cookie_partition_key_(net::CookiePartitionKey::FromNetworkIsolationKey(
isolation_info.network_isolation_key(),
isolation_info.site_for_cookies(),
net::SchemefulSite(origin),
isolation_info_.IsMainFrameRequest())),
cookie_partition_key_collection_(
net::CookiePartitionKeyCollection(cookie_partition_key_)),
receiver_(this),
metrics_updater_(metrics_updater),
max_cookie_cache_count_(
base::FeatureList::IsEnabled(features::kIncreaseCookieAccessCacheSize)
? features::kCookieAccessCacheSize.Get()
: kMaxCookieCacheCount),
cookies_access_timer_(
FROM_HERE,
kCookiesAccessedTimeout,
base::BindRepeating(&RestrictedCookieManager::CallCookiesAccessed,
base::Unretained(this))) {
DCHECK(cookie_store);
DCHECK(!cookie_setting_overrides_.Has(
net::CookieSettingOverride::kStorageAccessGrantEligible));
// Make sure there are not any disallowed devtool cookie setting overrides.
CHECK_EQ(devtools_cookie_setting_overrides_.ToEnumBitmask() &
~kAllowedDevToolsCookieSettingOverrides,
0u);
if (role == mojom::RestrictedCookieManagerRole::SCRIPT) {
CHECK(origin_.IsSameOriginWith(isolation_info_.frame_origin().value()));
}
}
RestrictedCookieManager::~RestrictedCookieManager() {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
if (cookies_access_timer_.IsRunning()) {
// There are cookie accesses which haven't been reported. Tell the observer
// before we're destroyed.
CallCookiesAccessed();
}
base::LinkNode<Listener>* node = listeners_.head();
while (node != listeners_.end()) {
Listener* listener_reference = node->value();
node = node->next();
// The entire list is going away, no need to remove nodes from it.
delete listener_reference;
}
}
void RestrictedCookieManager::OnCookieSettingsChanged() {
// Cookie settings changes can change cookie values as seen by content.
// Increment the shared version to make sure it issues a full cookie string
// request next time around.
IncrementSharedVersion();
}
base::ReadOnlySharedMemoryRegion
RestrictedCookieManager::GetAndPrepareSharedMemoryRegion(const GURL& url) {
auto shared_memory_region =
shared_memory_version_controller_.GetSharedMemoryRegion();
// Clients can change their URL. If that happens the subscription needs to
// mirror that to get the correct updates.
bool new_url = cookie_store_subscription_ && change_subscribed_url_ != url;
if (!cookie_store_subscription_ || new_url) {
change_subscribed_url_ = url;
cookie_store_subscription_ =
cookie_store_->GetChangeDispatcher().AddCallbackForUrl(
url, cookie_partition_key_,
base::IgnoreArgs<const net::CookieChangeInfo&>(base::BindRepeating(
&RestrictedCookieManager::IncrementSharedVersion,
base::Unretained(this))));
}
return shared_memory_region;
}
void RestrictedCookieManager::IncrementSharedVersion() {
shared_memory_version_controller_.Increment();
}
void RestrictedCookieManager::OverrideIsolationInfoForTesting(
const net::IsolationInfo& new_isolation_info) {
base::RunLoop run_loop;
isolation_info_ = new_isolation_info;
cookie_partition_key_ = net::CookiePartitionKey::FromNetworkIsolationKey(
isolation_info_.network_isolation_key(),
isolation_info_.site_for_cookies(), net::SchemefulSite(origin_),
isolation_info_.IsMainFrameRequest());
ComputeFirstPartySetMetadata(
origin_, cookie_store_, isolation_info_,
base::BindOnce(
&RestrictedCookieManager::OnGotFirstPartySetMetadataForTesting,
weak_ptr_factory_.GetWeakPtr(), run_loop.QuitClosure()));
run_loop.Run();
}
void RestrictedCookieManager::OnGotFirstPartySetMetadataForTesting(
base::OnceClosure done_closure,
net::FirstPartySetMetadata first_party_set_metadata) {
first_party_set_metadata_ = std::move(first_party_set_metadata);
cookie_partition_key_ = net::CookiePartitionKey::FromNetworkIsolationKey(
isolation_info_.network_isolation_key(),
isolation_info_.site_for_cookies(), net::SchemefulSite(origin_),
isolation_info_.IsMainFrameRequest());
cookie_partition_key_collection_ =
net::CookiePartitionKeyCollection(cookie_partition_key_);
std::move(done_closure).Run();
}
bool RestrictedCookieManager::IsPartitionedCookiesEnabled() const {
return cookie_partition_key_.has_value();
}
void RestrictedCookieManager::GetAllForUrl(
const GURL& url,
const net::SiteForCookies& site_for_cookies,
const url::Origin& top_frame_origin,
net::StorageAccessApiStatus storage_access_api_status,
mojom::CookieManagerGetOptionsPtr options,
bool is_ad_tagged,
bool apply_devtools_overrides,
bool force_disable_third_party_cookies,
GetAllForUrlCallback callback) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
if (!ValidateAccessToCookiesAt(url, site_for_cookies, top_frame_origin)) {
std::move(callback).Run({});
return;
}
// TODO(morlovich): Try to validate site_for_cookies as well.
net::CookieOptions net_options =
MakeOptionsForGet(role_, url, site_for_cookies, cookie_settings());
// TODO(crbug.com/40632967): remove set_return_excluded_cookies() once
// removing deprecation warnings.
net_options.set_return_excluded_cookies();
cookie_store_->GetCookieListWithOptionsAsync(
url, net_options, cookie_partition_key_collection_,
base::BindOnce(
&RestrictedCookieManager::CookieListToGetAllForUrlCallback,
weak_ptr_factory_.GetWeakPtr(), url, site_for_cookies,
top_frame_origin,
isolation_info_.top_frame_origin().value_or(url::Origin()),
is_ad_tagged,
GetCookieSettingOverrides(
storage_access_api_status, /*is_ad_tagged=*/is_ad_tagged,
/*apply_devtools_overrides=*/apply_devtools_overrides,
force_disable_third_party_cookies),
net_options, std::move(options), std::move(callback)));
}
void RestrictedCookieManager::CookieListToGetAllForUrlCallback(
const GURL& url,
const net::SiteForCookies& site_for_cookies,
const url::Origin& top_frame_origin,
const url::Origin& isolated_top_frame_origin,
bool is_ad_tagged,
const net::CookieSettingOverrides& cookie_setting_overrides,
const net::CookieOptions& net_options,
mojom::CookieManagerGetOptionsPtr options,
GetAllForUrlCallback callback,
const net::CookieAccessResultList& cookie_list,
const net::CookieAccessResultList& excluded_list) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
net::CookieAccessResultList maybe_included_cookies = cookie_list;
net::CookieAccessResultList excluded_cookies = excluded_list;
cookie_settings().AnnotateAndMoveUserBlockedCookies(
url, site_for_cookies, &top_frame_origin, first_party_set_metadata_,
cookie_setting_overrides, maybe_included_cookies, excluded_cookies);
std::vector<net::CookieWithAccessResult> result;
std::vector<mojom::CookieOrLineWithAccessResultPtr>
on_cookies_accessed_result;
CookieAccesses* cookie_accesses =
GetCookieAccessesForURLAndSite(url, site_for_cookies);
if (!maybe_included_cookies.empty())
result.reserve(maybe_included_cookies.size());
mojom::CookieMatchType match_type = options->match_type;
const std::string& match_name = options->name;
for (net::CookieWithAccessResult& cookie_item : maybe_included_cookies) {
const net::CanonicalCookie& cookie = cookie_item.cookie;
net::CookieAccessResult access_result = cookie_item.access_result;
const std::string& cookie_name = cookie.Name();
if (match_type == mojom::CookieMatchType::EQUALS) {
if (cookie_name != match_name)
continue;
} else if (match_type == mojom::CookieMatchType::STARTS_WITH) {
if (!base::StartsWith(cookie_name, match_name,
base::CompareCase::SENSITIVE)) {
continue;
}
} else {
NOTREACHED();
}
if (access_result.status.IsInclude()) {
result.push_back(std::move(cookie_item));
}
}
if (!result.empty() && IsPartitionedCookiesEnabled() &&
metrics_subsampler_.ShouldSample(net::kHistogramSampleProbability)) {
base::UmaHistogramCounts100(
"Net.RestrictedCookieManager.PartitionedCookiesInScript.Subsampled",
std::ranges::count_if(result, [](const net::CookieWithAccessResult& c) {
return c.cookie.IsPartitioned();
}));
}
UpdateSharedMemoryVersionInvalidationTimer(result);
std::move(callback).Run(result);
// If the number of cookies exceed the cache size, we won't be able to dedup
// much, so just skip it, as it's an expensive operation.
bool can_dedup =
excluded_cookies.size() + result.size() <= max_cookie_cache_count_;
if (!can_dedup) {
// We cannot longer trust the cache to be up-to-date after this.
cookie_accesses->clear();
}
// TODO(crbug.com/40632967): Stop reporting accesses of cookies with
// warning reasons once samesite tightening up is rolled out.
for (const auto& cookie_and_access_result : excluded_cookies) {
if (!cookie_and_access_result.access_result.status.ShouldWarn() &&
!cookie_and_access_result.access_result.status
.ExcludedByUserPreferencesOrTPCD()) {
continue;
}
// Skip sending a notification about this cookie access?
if (can_dedup && SkipAccessNotificationForCookieItem(
cookie_accesses, cookie_and_access_result)) {
continue;
}
on_cookies_accessed_result.push_back(
mojom::CookieOrLineWithAccessResult::New(
mojom::CookieOrLine::NewCookie(cookie_and_access_result.cookie),
cookie_and_access_result.access_result));
}
for (auto& cookie : result) {
// Skip sending a notification about this cookie access?
if (can_dedup &&
SkipAccessNotificationForCookieItem(cookie_accesses, cookie)) {
continue;
}
on_cookies_accessed_result.push_back(
mojom::CookieOrLineWithAccessResult::New(
mojom::CookieOrLine::NewCookie(cookie.cookie),
cookie.access_result));
}
if (cookie_observer_ && !on_cookies_accessed_result.empty()) {
OnCookiesAccessed(mojom::CookieAccessDetails::New(
mojom::CookieAccessDetails::Type::kRead, url,
/*frame_origin=*/std::nullopt, isolated_top_frame_origin,
site_for_cookies, std::move(on_cookies_accessed_result), std::nullopt,
is_ad_tagged, cookie_setting_overrides));
}
}
void RestrictedCookieManager::UpdateSharedMemoryVersionInvalidationTimer(
const std::vector<net::CookieWithAccessResult>& cookies) {
base::Time minimal_expiry = base::Time::Max();
for (const net::CookieWithAccessResult& cookie : cookies) {
if (cookie.cookie.IsPersistent()) {
if (cookie.cookie.ExpiryDate() < minimal_expiry) {
minimal_expiry = cookie.cookie.ExpiryDate();
}
}
}
if (minimal_expiry == base::Time::Max()) {
return;
}
const base::TimeDelta desired_expiry_delay =
minimal_expiry - base::Time::Now();
const base::TimeTicks desired_expiry_time =
base::TimeTicks::Now() + desired_expiry_delay;
if (!shared_memory_invalidation_timer_.IsRunning() ||
desired_expiry_time <
shared_memory_invalidation_timer_.desired_run_time()) {
// Schedule a task to invalidate the shared memory version on earliest
// expiry of cookies. This prevents clients from retaining access to expired
// cookies.
shared_memory_invalidation_timer_.Start(
FROM_HERE, desired_expiry_delay,
base::BindRepeating(&RestrictedCookieManager::OnCookieSettingsChanged,
weak_ptr_factory_.GetWeakPtr()));
}
}
void RestrictedCookieManager::SetCanonicalCookie(
const net::CanonicalCookie& cookie,
const GURL& url,
const net::SiteForCookies& site_for_cookies,
const url::Origin& top_frame_origin,
net::StorageAccessApiStatus storage_access_api_status,
net::CookieInclusionStatus status,
bool is_ad_tagged,
bool apply_devtools_overrides,
SetCanonicalCookieCallback callback) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
bool collect_metrics =
metrics_subsampler_.ShouldSample(net::kHistogramSampleProbability);
// Don't allow a status that has an exclusion reason as they should have
// already been taken care of on the renderer side.
if (!status.IsInclude()) {
receiver_.ReportBadMessage(
"RestrictedCookieManager: unexpected cookie inclusion status");
std::move(callback).Run(false);
return;
}
if (!ValidateAccessToCookiesAt(url, site_for_cookies, top_frame_origin,
&cookie)) {
std::move(callback).Run(false);
return;
}
const net::CookieSettingOverrides cookie_setting_overrides =
GetCookieSettingOverrides(storage_access_api_status, is_ad_tagged,
apply_devtools_overrides,
/*force_disable_third_party_cookies=*/false);
// Check cookie accessibility with cookie_settings.
// TODO(morlovich): Try to validate site_for_cookies as well.
bool blocked = !cookie_settings_->IsCookieAccessible(
cookie, url, site_for_cookies, top_frame_origin,
first_party_set_metadata_, cookie_setting_overrides, &status);
if (blocked) {
// Cookie allowed by cookie_settings checks could be blocked explicitly,
// e.g. via Android Webview APIs, we need to manually add exclusion reason
// in this case.
if (status.IsInclude()) {
status.AddExclusionReason(net::CookieInclusionStatus::ExclusionReason::
EXCLUDE_USER_PREFERENCES);
}
}
// Don't allow URLs with leading dots like https://.some-weird-domain.com
// This probably never happens.
if (!net::cookie_util::DomainIsHostOnly(url.GetHost())) {
status.AddExclusionReason(
net::CookieInclusionStatus::ExclusionReason::EXCLUDE_INVALID_DOMAIN);
}
// For better safety, we use isolated_info_.top_frame_origin() instead of
// top_frame_origin to create the CookieAccessDetails , eventually
// isolation_info is always used.
url::Origin isolated_top_frame_origin =
isolation_info_.top_frame_origin().value_or(url::Origin());
if (!status.IsInclude()) {
if (cookie_observer_) {
std::vector<network::mojom::CookieOrLineWithAccessResultPtr>
result_with_access_result;
result_with_access_result.push_back(
mojom::CookieOrLineWithAccessResult::New(
mojom::CookieOrLine::NewCookie(cookie),
net::CookieAccessResult(status)));
OnCookiesAccessed(mojom::CookieAccessDetails::New(
mojom::CookieAccessDetails::Type::kChange, url,
/*frame_origin=*/std::nullopt, isolated_top_frame_origin,
site_for_cookies, std::move(result_with_access_result), std::nullopt,
is_ad_tagged, cookie_setting_overrides));
}
std::move(callback).Run(false);
return;
}
// TODO(pwnall): Validate the CanonicalCookie fields.
// Update the creation and last access times.
// Note: This used to be a call to NowFromSystemTime, but this caused
// inconsistency with the expiration date, which was capped checking
// against Now. If any issues crop up related to this change please
// contact the owners of http://crbug.com/1335859.
base::Time now = base::Time::Now();
// TODO(http://crbug.com/1024053): Log metrics
const GURL& origin_url = origin_.GetURL();
net::CookieSourceScheme source_scheme =
GURL::SchemeIsCryptographic(origin_.scheme())
? net::CookieSourceScheme::kSecure
: net::CookieSourceScheme::kNonSecure;
// If the renderer's cookie has a partition key that was not created using
// CookiePartitionKey::FromScript, then the cookie's partition key should be
// equal to RestrictedCookieManager's partition key.
std::optional<net::CookiePartitionKey> cookie_partition_key =
cookie.PartitionKey();
// If the `cookie_partition_key_` has a nonce then force all cookie writes to
// be in the nonce based partition even if the cookie was not set with the
// Partitioned attribute.
if (net::CookiePartitionKey::HasNonce(cookie_partition_key_)) {
cookie_partition_key = cookie_partition_key_;
}
if (cookie_partition_key) {
// RestrictedCookieManager having a null partition key strictly implies the
// feature is disabled. If that is the case, we treat the cookie as
// unpartitioned.
if (!cookie_partition_key_) {
cookie_partition_key = std::nullopt;
} else {
bool cookie_partition_key_ok =
cookie_partition_key->from_script() ||
cookie_partition_key.value() == cookie_partition_key_.value();
if (collect_metrics) {
base::UmaHistogramBoolean(
"Net.RestrictedCookieManager.CookiePartitionKeyOK.Subsampled",
cookie_partition_key_ok);
}
if (!cookie_partition_key_ok) {
receiver_.ReportBadMessage(
"RestrictedCookieManager: unexpected cookie partition key");
std::move(callback).Run(false);
return;
}
if (cookie_partition_key->from_script()) {
cookie_partition_key = cookie_partition_key_;
}
}
}
if (IsPartitionedCookiesEnabled() && collect_metrics) {
base::UmaHistogramBoolean(
"Net.RestrictedCookieManager.SetPartitionedCookie.Subsampled",
cookie_partition_key.has_value());
}
std::unique_ptr<net::CanonicalCookie> sanitized_cookie =
net::CanonicalCookie::FromStorage(
cookie.Name(), cookie.Value(), cookie.Domain(), cookie.Path(), now,
cookie.ExpiryDate(), now, now, cookie.SecureAttribute(),
cookie.IsHttpOnly(), cookie.SameSite(), cookie.Priority(),
cookie_partition_key, source_scheme, origin_.port(),
cookie.SourceType(),
net::CanonicalCookieFromStorageCallSite::kRestrictedCookieManager);
DCHECK(sanitized_cookie);
// FromStorage() uses a less strict version of IsCanonical(), we need to check
// the stricter version as well here.
if (!sanitized_cookie->IsCanonical()) {
std::move(callback).Run(false);
return;
}
net::CanonicalCookie cookie_copy = *sanitized_cookie;
net::CookieOptions options =
MakeOptionsForSet(role_, url, site_for_cookies, cookie_settings());
net::CookieAccessResult cookie_access_result(status);
cookie_store_->SetCanonicalCookieAsync(
std::move(sanitized_cookie), origin_url, options,
base::BindOnce(&RestrictedCookieManager::SetCanonicalCookieResult,
weak_ptr_factory_.GetWeakPtr(), url,
isolated_top_frame_origin, cookie_setting_overrides,
site_for_cookies, cookie_copy, is_ad_tagged,
std::move(callback)),
cookie_access_result);
}
void RestrictedCookieManager::SetCanonicalCookieResult(
const GURL& url,
const url::Origin& isolated_top_frame_origin,
const net::CookieSettingOverrides& cookie_setting_overrides,
const net::SiteForCookies& site_for_cookies,
const net::CanonicalCookie& cookie,
bool is_ad_tagged,
SetCanonicalCookieCallback user_callback,
net::CookieAccessResult access_result) {
// TODO(crbug.com/40632967): Only report pure INCLUDE once samesite
// tightening up is rolled out.
DCHECK(!access_result.status.HasExclusionReason(
net::CookieInclusionStatus::ExclusionReason::
EXCLUDE_USER_PREFERENCES) &&
!access_result.status.HasExclusionReason(
net::CookieInclusionStatus::ExclusionReason::
EXCLUDE_THIRD_PARTY_PHASEOUT));
if (access_result.status.IsInclude() || access_result.status.ShouldWarn()) {
if (cookie_observer_) {
std::vector<mojom::CookieOrLineWithAccessResultPtr> notify;
notify.push_back(mojom::CookieOrLineWithAccessResult::New(
mojom::CookieOrLine::NewCookie(cookie), access_result));
OnCookiesAccessed(mojom::CookieAccessDetails::New(
mojom::CookieAccessDetails::Type::kChange, url,
/*frame_origin=*/std::nullopt, isolated_top_frame_origin,
site_for_cookies, std::move(notify), std::nullopt, is_ad_tagged,
cookie_setting_overrides));
}
}
std::move(user_callback).Run(access_result.status.IsInclude());
}
void RestrictedCookieManager::AddChangeListener(
const GURL& url,
const net::SiteForCookies& site_for_cookies,
const url::Origin& top_frame_origin,
net::StorageAccessApiStatus storage_access_api_status,
mojo::PendingRemote<mojom::CookieChangeListener> mojo_listener,
AddChangeListenerCallback callback) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
if (!ValidateAccessToCookiesAt(url, site_for_cookies, top_frame_origin)) {
std::move(callback).Run();
return;
}
net::CookieOptions net_options =
MakeOptionsForGet(role_, url, site_for_cookies, cookie_settings());
auto listener = std::make_unique<Listener>(
cookie_store_, this, url, site_for_cookies, top_frame_origin,
storage_access_api_status, cookie_partition_key_, net_options,
std::move(mojo_listener));
listener->mojo_listener().set_disconnect_handler(
base::BindOnce(&RestrictedCookieManager::RemoveChangeListener,
weak_ptr_factory_.GetWeakPtr(),
// Safe because this owns the listener, so the listener is
// guaranteed to be alive for as long as the weak pointer
// above resolves.
base::Unretained(listener.get())));
// The linked list takes over the Listener ownership.
listeners_.Append(listener.release());
std::move(callback).Run();
}
void RestrictedCookieManager::SetCookieFromString(
const GURL& url,
const net::SiteForCookies& site_for_cookies,
const url::Origin& top_frame_origin,
net::StorageAccessApiStatus storage_access_api_status,
bool get_version_shared_memory,
bool is_ad_tagged,
bool apply_devtools_overrides,
const std::string& cookie,
SetCookieFromStringCallback callback) {
TRACE_EVENT("net", "RestrictedCookieManager::SetCookieFromString");
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
base::ElapsedTimer timer;
// The cookie is about to be set. Proactively increment the version so it's
// instantly reflected.
IncrementSharedVersion();
// Report that a write is being processed.
shared_memory_version_controller_.CommitWrite();
const bool get_cookies_on_set =
base::FeatureList::IsEnabled(features::kGetCookiesOnSet);
base::ReadOnlySharedMemoryRegion shared_memory_region;
if (!get_cookies_on_set) {
// Unblock the caller before the cookie is actually set.
std::move(callback).Run(/*response=*/nullptr);
} else if (get_version_shared_memory) {
shared_memory_region = GetAndPrepareSharedMemoryRegion(url);
}
net::CookieInclusionStatus status;
std::unique_ptr<net::CanonicalCookie> parsed_cookie =
net::CanonicalCookie::Create(
url, cookie, base::Time::Now(), /*server_time=*/std::nullopt,
cookie_partition_key_, net::CookieSourceType::kScript, &status);
if (!parsed_cookie) {
if (cookie_observer_) {
std::vector<network::mojom::CookieOrLineWithAccessResultPtr>
result_with_access_result;
result_with_access_result.push_back(
mojom::CookieOrLineWithAccessResult::New(
mojom::CookieOrLine::NewCookieString(cookie),
net::CookieAccessResult(status)));
OnCookiesAccessed(mojom::CookieAccessDetails::New(
mojom::CookieAccessDetails::Type::kChange, url,
/*frame_origin=*/std::nullopt,
isolation_info_.top_frame_origin().value_or(url::Origin()),
site_for_cookies, std::move(result_with_access_result), std::nullopt,
is_ad_tagged,
GetCookieSettingOverrides(
storage_access_api_status, is_ad_tagged, apply_devtools_overrides,
/*force_disable_third_party_cookies=*/false)));
}
if (get_cookies_on_set) {
// Unblock the caller on failure.
std::move(callback).Run(/*response=*/nullptr);
}
return;
}
if (metrics_subsampler_.ShouldSample(net::kHistogramSampleProbability)) {
HistogramScriptCookieExpiration(*parsed_cookie);
}
auto on_set = get_cookies_on_set
? base::BindOnce(
&RestrictedCookieManager::GetCookiesAfterSet,
weak_ptr_factory_.GetWeakPtr(), url, site_for_cookies,
top_frame_origin, storage_access_api_status,
is_ad_tagged, apply_devtools_overrides,
std::move(callback), std::move(shared_memory_region))
: base::DoNothing();
// Further checks (origin_, settings), as well as logging done by
// SetCanonicalCookie()
SetCanonicalCookie(*parsed_cookie, url, site_for_cookies, top_frame_origin,
storage_access_api_status, status, is_ad_tagged,
apply_devtools_overrides, std::move(on_set));
if (metrics_subsampler_.ShouldSample(net::kHistogramSampleProbability)) {
base::UmaHistogramCustomMicrosecondsTimes(
"Net.RestrictedCookieManager.SetCookieFromString.Duration.Subsampled",
timer.Elapsed(), base::Microseconds(1), base::Milliseconds(128), 100);
}
}
void RestrictedCookieManager::GetCookiesString(
const GURL& url,
const net::SiteForCookies& site_for_cookies,
const url::Origin& top_frame_origin,
net::StorageAccessApiStatus storage_access_api_status,
bool get_version_shared_memory,
bool is_ad_tagged,
bool apply_devtools_overrides,
bool force_disable_third_party_cookies,
GetCookiesStringCallback callback) {
TRACE_EVENT("net", "RestrictedCookieManager::GetCookiesString");
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
base::ElapsedTimer timer;
// Checks done by GetAllForUrl
if (metrics_updater_) {
metrics_updater_->OnGetCookiesString();
}
base::ReadOnlySharedMemoryRegion shared_memory_region;
if (get_version_shared_memory) {
shared_memory_region = GetAndPrepareSharedMemoryRegion(url);
}
// Bind the current shared cookie version to |callback| to be returned once
// the cookie string is retrieved. At that point the cookie version might have
// been incremented by actions that happened in the meantime. Returning a
// slightly stale version like this is still correct since it's a best effort
// mechanism to avoid unnecessary IPCs. When the version is stale an
// additional IPC will take place which is the way it would always be if there
// was not shared memory versioning.
auto bound_callback = base::BindOnce(
std::move(callback), shared_memory_version_controller_.GetSharedVersion(),
std::move(shared_memory_region));
// Match everything.
auto match_options = mojom::CookieManagerGetOptions::New();
match_options->name = "";
match_options->match_type = mojom::CookieMatchType::STARTS_WITH;
GetAllForUrl(url, site_for_cookies, top_frame_origin,
storage_access_api_status, std::move(match_options),
is_ad_tagged, apply_devtools_overrides,
force_disable_third_party_cookies,
base::BindOnce([](const std::vector<net::CookieWithAccessResult>&
cookies) {
return net::CanonicalCookie::BuildCookieLine(cookies);
}).Then(std::move(bound_callback)));
if (metrics_subsampler_.ShouldSample(net::kHistogramSampleProbability)) {
base::UmaHistogramCustomMicrosecondsTimes(
"Net.RestrictedCookieManager.GetCookiesString.Duration.Subsampled",
timer.Elapsed(), base::Microseconds(1), base::Milliseconds(128), 100);
}
}
void RestrictedCookieManager::CookiesEnabledFor(
const GURL& url,
const net::SiteForCookies& site_for_cookies,
const url::Origin& top_frame_origin,
net::StorageAccessApiStatus storage_access_api_status,
bool apply_devtools_overrides,
CookiesEnabledForCallback callback) {
if (!ValidateAccessToCookiesAt(url, site_for_cookies, top_frame_origin)) {
std::move(callback).Run(false);
return;
}
std::move(callback).Run(cookie_settings_->IsFullCookieAccessAllowed(
url, site_for_cookies, top_frame_origin,
GetCookieSettingOverrides(
storage_access_api_status,
/*is_ad_tagged=*/false,
/*apply_devtools_overrides=*/apply_devtools_overrides,
/*force_disable_third_party_cookies=*/false),
cookie_partition_key_));
}
void RestrictedCookieManager::InstallReceiver(
mojo::PendingReceiver<mojom::RestrictedCookieManager> pending_receiver,
base::OnceClosure on_disconnect_callback) {
DCHECK(!receiver_.is_bound());
receiver_.Bind(std::move(pending_receiver));
receiver_.set_disconnect_handler(std::move(on_disconnect_callback));
}
void RestrictedCookieManager::RemoveChangeListener(Listener* listener) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
listener->RemoveFromList();
delete listener;
}
bool RestrictedCookieManager::ValidateAccessToCookiesAt(
const GURL& url,
const net::SiteForCookies& site_for_cookies,
const url::Origin& top_frame_origin,
const net::CanonicalCookie* cookie_being_set) {
if (origin_.opaque()) {
receiver_.ReportBadMessage("Access is denied in this context");
return false;
}
bool site_for_cookies_ok =
BoundSiteForCookies().IsEquivalent(site_for_cookies);
// TODO(crbug.com/402207912): Switch back to a DCEHCK once this condition
// always holds again.
if (!site_for_cookies_ok) {
LOG(ERROR) << "site_for_cookies from renderer='"
<< site_for_cookies.ToDebugString() << "' from browser='"
<< BoundSiteForCookies().ToDebugString() << "';";
}
bool top_frame_origin_ok = (top_frame_origin == BoundTopFrameOrigin());
// TODO(crbug.com/402207912): Switch back to a DCEHCK once this condition
// always holds again.
if (!top_frame_origin_ok) {
LOG(ERROR) << "top_frame_origin from renderer='" << top_frame_origin
<< "' from browser='" << BoundTopFrameOrigin() << "';";
}
if (metrics_subsampler_.ShouldSample(net::kHistogramSampleProbability)) {
base::UmaHistogramBoolean(
"Net.RestrictedCookieManager.SiteForCookiesOK.Subsampled",
site_for_cookies_ok);
base::UmaHistogramBoolean(
"Net.RestrictedCookieManager.TopFrameOriginOK.Subsampled",
top_frame_origin_ok);
}
// Don't allow setting cookies on other domains. See crbug.com/996786.
if (cookie_being_set && !cookie_being_set->IsDomainMatch(url.GetHost())) {
receiver_.ReportBadMessage(
"Setting cookies on other domains is disallowed.");
return false;
}
if (origin_.IsSameOriginWith(url))
return true;
receiver_.ReportBadMessage("Incorrect url origin");
return false;
}
net::CookieSettingOverrides RestrictedCookieManager::GetCookieSettingOverrides(
net::StorageAccessApiStatus storage_access_api_status,
bool is_ad_tagged,
bool apply_devtools_overrides,
bool force_disable_third_party_cookies) const {
net::CookieSettingOverrides overrides = cookie_setting_overrides_;
switch (storage_access_api_status) {
case net::StorageAccessApiStatus::kNone:
break;
case net::StorageAccessApiStatus::kAccessViaAPI:
overrides.Put(net::CookieSettingOverride::kStorageAccessGrantEligible);
break;
}
if (force_disable_third_party_cookies) {
overrides.Put(net::CookieSettingOverride::kForceDisableThirdPartyCookies);
}
AddAdsHeuristicCookieSettingOverrides(is_ad_tagged, overrides,
/*emit_metrics=*/true);
if (apply_devtools_overrides) {
overrides = base::Union(overrides, devtools_cookie_setting_overrides_);
}
return overrides;
}
void RestrictedCookieManager::GetCookiesAfterSet(
const GURL& url,
const net::SiteForCookies& site_for_cookies,
const url::Origin& top_frame_origin,
net::StorageAccessApiStatus storage_access_api_status,
bool is_ad_tagged,
bool apply_devtools_overrides,
SetCookieFromStringCallback callback,
base::ReadOnlySharedMemoryRegion shared_memory_region,
bool succeeded) {
if (!succeeded) {
std::move(callback).Run(/*response=*/nullptr);
return;
}
auto bound_callback =
base::BindOnce(&RunCallbackWithResponse, std::move(callback),
shared_memory_version_controller_.GetSharedVersion(),
std::move(shared_memory_region));
// Match everything.
auto match_options = mojom::CookieManagerGetOptions::New();
match_options->name = "";
match_options->match_type = mojom::CookieMatchType::STARTS_WITH;
// The caller will be unblocked by GetAllForUrl.
GetAllForUrl(url, site_for_cookies, top_frame_origin,
storage_access_api_status, std::move(match_options),
is_ad_tagged, apply_devtools_overrides,
/*force_disable_third_party_cookies=*/false,
base::BindOnce([](const std::vector<net::CookieWithAccessResult>&
cookies) {
return net::CanonicalCookie::BuildCookieLine(cookies);
}).Then(std::move(bound_callback)));
}
void RestrictedCookieManager::OnCookiesAccessed(
mojom::CookieAccessDetailsPtr details) {
cookie_access_details_.push_back(std::move(details));
if (!cookies_access_timer_.IsRunning()) {
cookies_access_timer_.Reset();
}
}
void RestrictedCookieManager::CallCookiesAccessed() {
DCHECK(!cookie_access_details_.empty());
cookie_observer_->OnCookiesAccessed(std::move(cookie_access_details_));
}
} // namespace network