blob: 61adf42d9a5190f72d3d2a83ce7b9425bda9a1b1 [file] [log] [blame]
// Copyright 2022 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#ifndef CONTENT_BROWSER_INTEREST_GROUP_INTEREST_GROUP_PERMISSIONS_CACHE_H_
#define CONTENT_BROWSER_INTEREST_GROUP_INTEREST_GROUP_PERMISSIONS_CACHE_H_
#include "content/common/content_export.h"
#include <map>
#include <memory>
#include <tuple>
#include "base/containers/lru_cache.h"
#include "base/time/time.h"
#include "base/timer/timer.h"
#include "net/base/network_isolation_key.h"
#include "url/origin.h"
namespace content {
// Cache of per-origin join/leave interest group permissions, partitioned by
// NetworkIsolationKey and initiating frame origin. To minimize leaks across
// first party contexts, uses a separate LRU cache "shard" for each {frame
// origin, NetworkIsolationKey} pair. Each LRU cache has a maximum number of
// entries, but there's no global limit, to prevent leaks. The short entry
// lifetime and delay in creating entries, along with the LRU size cap, should
// keep memory usage low.
//
// In addition to the LRU-ness of the cache, individual entries are passively
// expired (i.e., no longer returned, but not proactively deleted) after
// `kCacheDuration` has passed since they were added to the cache. Cache shards
// are garbage collected on a timer, at some point after all their entries have
// expired. Expired entries and shards are also deleted on access, which isn't
// strictly necessary.
//
// Permissions are typically learned from .well-known fetches, and should be
// cached by the network process, depending on HTTP response headers, but
// caching it in the browser process results in much better performance for
// sites that want to join (or, more likely, leave) large numbers of
// cross-origin interest groups, due to the limit of outstanding joins/leaves in
// the renderer process, and overhead on accessing the network stack's HTTP
// cache.
class CONTENT_EXPORT InterestGroupPermissionsCache {
public:
// Cache duration for cache entries. Cache shards expire when all their
// individual entries expire, so this is also the expiration duration of
// cache shards, relative to last added entry.
static constexpr base::TimeDelta kCacheDuration = base::Minutes(1);
// How often expired cache shards are evicted. Expired but not-yet-evicted
// entries are not returned.
static constexpr base::TimeDelta kDeleteExpiredTimerDuration =
2 * kCacheDuration;
// The maximum number of cache entries for each cache shard. There is no
// global maximum, to prevent that from becoming a sidechannel.
static constexpr int kMaxCacheEntriesPerShard = 50;
// Permissions associated with an interest group origin.
struct Permissions {
bool can_join = false;
bool can_leave = false;
// Comparison operators are only useful for testing.
friend bool operator==(const Permissions&, const Permissions&) = default;
};
InterestGroupPermissionsCache();
InterestGroupPermissionsCache(InterestGroupPermissionsCache&) = delete;
~InterestGroupPermissionsCache();
InterestGroupPermissionsCache& operator=(
const InterestGroupPermissionsCache&) = delete;
// Retrieves unexpired cached Permissions that `interest_group_owner` has
// granted to `frame_origin`, learned in the context of
// `network_isolation_key`.
Permissions* GetPermissions(
const url::Origin& frame_origin,
const url::Origin& interest_group_owner,
const net::NetworkIsolationKey& network_isolation_key);
// Adds `permissions` to the cache as the set of permissions
// `interest_group_owner` has granted to `frame_origin`, learned in the
// context of `network_isolation_key`.
void CachePermissions(Permissions permissions,
const url::Origin& frame_origin,
const url::Origin& interest_group_owner,
const net::NetworkIsolationKey& network_isolation_key);
// Deletes all cache entries.
void Clear();
// Returns the number of non-deleted cache shards, to test cleanup logic. All
// values in a cache shard may be expired, since expired cache shards are only
// deleted on access and on a timer.
size_t cache_shards_for_testing() const;
private:
// An entry in the cache.
struct CacheEntry {
base::TimeTicks expiry;
Permissions permissions;
};
// An independent shard of the cache. Each shard must have no impact on the
// others, including when their entries are observably evicted.
struct CacheShard {
CacheShard();
CacheShard(CacheShard&&);
~CacheShard();
// Last expiration time of any of the shard's CacheEntry.
base::TimeTicks expiry;
// Map of interest group owner origins to CacheEntries. A std::unique_ptr
// wrapper is needed to make these movable, so they can be put in a
// std::map.
std::unique_ptr<base::LRUCache<url::Origin, CacheEntry>> cache;
};
// Key indicating which cache shard to use.
struct CacheShardKey {
bool operator<(const CacheShardKey& other) const {
return std::tie(frame_origin, network_isolation_key) <
std::tie(other.frame_origin, other.network_isolation_key);
}
url::Origin frame_origin;
net::NetworkIsolationKey network_isolation_key;
};
// Returns the specified cache shard, if it exists and is not expired. Deletes
// the cache shard if it exists but is expired.
CacheShard* FindShard(const url::Origin& frame_origin,
const net::NetworkIsolationKey& network_isolation_key,
base::TimeTicks now);
// Starts a timer to invoke DeleteExpired(), if there are any cache shards and
// the timer hasn't already been started.
void MaybeStartDeleteExpiredTimer();
// Walks through the cache shards, deleting any that have expired, and
// restarts timer if there are any shards left. Expired LRU entries are not
// deleted on an individual basis, except when accessed directly.
void DeleteExpired();
base::OneShotTimer delete_expired_timer_;
std::map<CacheShardKey, CacheShard> cache_shards_;
};
} // namespace content
#endif // CONTENT_BROWSER_INTEREST_GROUP_INTEREST_GROUP_PERMISSIONS_CACHE_H_