summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--net/cookies/cookie_monster.cc279
-rw-r--r--net/cookies/cookie_monster.h67
-rw-r--r--net/cookies/cookie_monster_unittest.cc173
3 files changed, 380 insertions, 139 deletions
diff --git a/net/cookies/cookie_monster.cc b/net/cookies/cookie_monster.cc
index c9b386a..0b0bb68 100644
--- a/net/cookies/cookie_monster.cc
+++ b/net/cookies/cookie_monster.cc
@@ -45,6 +45,7 @@
#include "net/cookies/cookie_monster.h"
#include <algorithm>
+#include <functional>
#include <set>
#include "base/basictypes.h"
@@ -97,6 +98,14 @@ const size_t CookieMonster::kDomainMaxCookies = 180;
const size_t CookieMonster::kDomainPurgeCookies = 30;
const size_t CookieMonster::kMaxCookies = 3300;
const size_t CookieMonster::kPurgeCookies = 300;
+
+const size_t CookieMonster::kDomainCookiesQuotaLow = 30;
+const size_t CookieMonster::kDomainCookiesQuotaMedium = 50;
+const size_t CookieMonster::kDomainCookiesQuotaHigh =
+ CookieMonster::kDomainMaxCookies - CookieMonster::kDomainPurgeCookies
+ - CookieMonster::kDomainCookiesQuotaLow
+ - CookieMonster::kDomainCookiesQuotaMedium;
+
const int CookieMonster::kSafeFromGlobalPurgeDays = 30;
namespace {
@@ -132,7 +141,7 @@ bool CookieSorter(CanonicalCookie* cc1, CanonicalCookie* cc2) {
return cc1->Path().length() > cc2->Path().length();
}
-bool LRUCookieSorter(const CookieMonster::CookieMap::iterator& it1,
+bool LRACookieSorter(const CookieMonster::CookieMap::iterator& it1,
const CookieMonster::CookieMap::iterator& it2) {
// Cookies accessed less recently should be deleted first.
if (it1->second->LastAccessDate() != it2->second->LastAccessDate())
@@ -192,36 +201,57 @@ bool GetCookieDomain(const GURL& url,
return cookie_util::GetCookieDomainWithString(url, domain_string, result);
}
-// Helper for GarbageCollection. If |cookie_its->size() > num_max|, remove the
-// |num_max - num_purge| most recently accessed cookies from cookie_its.
-// (In other words, leave the entries that are candidates for
-// eviction in cookie_its.) The cookies returned will be in order sorted by
-// access time, least recently accessed first. The access time of the least
-// recently accessed entry not returned will be placed in
-// |*lra_removed| if that pointer is set. FindLeastRecentlyAccessed
-// returns false if no manipulation is done (because the list size is less
-// than num_max), true otherwise.
-bool FindLeastRecentlyAccessed(
- size_t num_max,
- size_t num_purge,
- Time* lra_removed,
- std::vector<CookieMonster::CookieMap::iterator>* cookie_its) {
- DCHECK_LE(num_purge, num_max);
- if (cookie_its->size() > num_max) {
- VLOG(kVlogGarbageCollection)
- << "FindLeastRecentlyAccessed() Deep Garbage Collect.";
- num_purge += cookie_its->size() - num_max;
- DCHECK_GT(cookie_its->size(), num_purge);
-
- // Add 1 so that we can get the last time left in the store.
- std::partial_sort(cookie_its->begin(), cookie_its->begin() + num_purge + 1,
- cookie_its->end(), LRUCookieSorter);
- *lra_removed =
- (*(cookie_its->begin() + num_purge))->second->LastAccessDate();
- cookie_its->erase(cookie_its->begin() + num_purge, cookie_its->end());
- return true;
+// For a CookieItVector iterator range [|it_begin|, |it_end|),
+// sorts the first |num_sort| + 1 elements by LastAccessDate().
+// The + 1 element exists so for any interval of length <= |num_sort| starting
+// from |cookies_its_begin|, a LastAccessDate() bound can be found.
+void SortLeastRecentlyAccessed(
+ CookieMonster::CookieItVector::iterator it_begin,
+ CookieMonster::CookieItVector::iterator it_end,
+ size_t num_sort) {
+ DCHECK_LT(static_cast<int>(num_sort), it_end - it_begin);
+ std::partial_sort(it_begin, it_begin + num_sort + 1, it_end, LRACookieSorter);
+}
+
+// Predicate to support PartitionCookieByPriority().
+struct CookiePriorityEqualsTo
+ : std::unary_function<const CookieMonster::CookieMap::iterator, bool> {
+ CookiePriorityEqualsTo(CookiePriority priority)
+ : priority_(priority) {}
+
+ bool operator()(const CookieMonster::CookieMap::iterator it) const {
+ return it->second->Priority() == priority_;
}
- return false;
+
+ const CookiePriority priority_;
+};
+
+// For a CookieItVector iterator range [|it_begin|, |it_end|),
+// moves all cookies with a given |priority| to the beginning of the list.
+// Returns: An iterator in [it_begin, it_end) to the first element with
+// priority != |priority|, or |it_end| if all have priority == |priority|.
+CookieMonster::CookieItVector::iterator PartitionCookieByPriority(
+ CookieMonster::CookieItVector::iterator it_begin,
+ CookieMonster::CookieItVector::iterator it_end,
+ CookiePriority priority) {
+ return std::partition(it_begin, it_end, CookiePriorityEqualsTo(priority));
+}
+
+bool LowerBoundAccessDateComparator(
+ const CookieMonster::CookieMap::iterator it, const Time& access_date) {
+ return it->second->LastAccessDate() < access_date;
+}
+
+// For a CookieItVector iterator range [|it_begin|, |it_end|)
+// from a CookieItVector sorted by LastAccessDate(), returns the
+// first iterator with access date >= |access_date|, or cookie_its_end if this
+// holds for all.
+CookieMonster::CookieItVector::iterator LowerBoundAccessDate(
+ const CookieMonster::CookieItVector::iterator its_begin,
+ const CookieMonster::CookieItVector::iterator its_end,
+ const Time& access_date) {
+ return std::lower_bound(its_begin, its_end, access_date,
+ LowerBoundAccessDateComparator);
}
// Mapping between DeletionCause and Delegate::ChangeCause; the mapping also
@@ -288,7 +318,8 @@ CookieMonster::CookieMonster(PersistentCookieStore* store, Delegate* delegate)
delegate_(delegate),
last_statistic_record_time_(Time::Now()),
keep_expired_cookies_(false),
- persist_session_cookies_(false) {
+ persist_session_cookies_(false),
+ priority_aware_garbage_collection_(false) {
InitializeHistograms();
SetDefaultCookieableSchemes();
}
@@ -304,7 +335,8 @@ CookieMonster::CookieMonster(PersistentCookieStore* store,
delegate_(delegate),
last_statistic_record_time_(base::Time::Now()),
keep_expired_cookies_(false),
- persist_session_cookies_(false) {
+ persist_session_cookies_(false),
+ priority_aware_garbage_collection_(false) {
InitializeHistograms();
SetDefaultCookieableSchemes();
}
@@ -1293,12 +1325,19 @@ CookieMonster* CookieMonster::GetCookieMonster() {
return this;
}
+// This function must be called before the CookieMonster is used.
void CookieMonster::SetPersistSessionCookies(bool persist_session_cookies) {
- // This function must be called before the CookieMonster is used.
DCHECK(!initialized_);
persist_session_cookies_ = persist_session_cookies;
}
+// This function must be called before the CookieMonster is used.
+void CookieMonster::SetPriorityAwareGarbageCollection(
+ bool priority_aware_garbage_collection) {
+ DCHECK(!initialized_);
+ priority_aware_garbage_collection_ = priority_aware_garbage_collection;
+}
+
void CookieMonster::SetForceKeepSessionState() {
if (store_) {
store_->SetForceKeepSessionState();
@@ -1758,78 +1797,124 @@ void CookieMonster::InternalDeleteCookie(CookieMap::iterator it,
}
// Domain expiry behavior is unchanged by key/expiry scheme (the
-// meaning of the key is different, but that's not visible to this
-// routine).
+// meaning of the key is different, but that's not visible to this routine).
int CookieMonster::GarbageCollect(const Time& current,
const std::string& key) {
lock_.AssertAcquired();
int num_deleted = 0;
+ Time safe_date(
+ Time::Now() - TimeDelta::FromDays(kSafeFromGlobalPurgeDays));
- // Collect garbage for this key.
+ // Collect garbage for this key, minding cookie priorities.
if (cookies_.count(key) > kDomainMaxCookies) {
VLOG(kVlogGarbageCollection) << "GarbageCollect() key: " << key;
- std::vector<CookieMap::iterator> cookie_its;
+ CookieItVector cookie_its;
num_deleted += GarbageCollectExpired(
current, cookies_.equal_range(key), &cookie_its);
- base::Time oldest_removed;
- if (FindLeastRecentlyAccessed(kDomainMaxCookies, kDomainPurgeCookies,
- &oldest_removed, &cookie_its)) {
- // Delete in two passes so we can figure out what we're nuking
- // that would be kept at the global level.
- int num_subject_to_global_purge =
- GarbageCollectDeleteList(
- current,
- Time::Now() - TimeDelta::FromDays(kSafeFromGlobalPurgeDays),
- DELETE_COOKIE_EVICTED_DOMAIN_PRE_SAFE,
- cookie_its);
- num_deleted += num_subject_to_global_purge;
- // Correct because FindLeastRecentlyAccessed returns a sorted list.
- cookie_its.erase(cookie_its.begin(),
- cookie_its.begin() + num_subject_to_global_purge);
- num_deleted +=
- GarbageCollectDeleteList(
- current,
- Time(),
- DELETE_COOKIE_EVICTED_DOMAIN_POST_SAFE,
- cookie_its);
+ if (cookie_its.size() > kDomainMaxCookies) {
+ VLOG(kVlogGarbageCollection) << "Deep Garbage Collect domain.";
+ size_t purge_goal =
+ cookie_its.size() - (kDomainMaxCookies - kDomainPurgeCookies);
+ DCHECK(purge_goal > kDomainPurgeCookies);
+
+ // Boundary iterators into |cookie_its| for different priorities.
+ CookieItVector::iterator it_bdd[4];
+ // Intialize |it_bdd| while sorting |cookie_its| by priorities.
+ // Schematic: [MLLHMHHLMM] => [LLL|MMMM|HHH], with 4 boundaries.
+ it_bdd[0] = cookie_its.begin();
+ it_bdd[3] = cookie_its.end();
+ it_bdd[1] = PartitionCookieByPriority(it_bdd[0], it_bdd[3],
+ COOKIE_PRIORITY_LOW);
+ it_bdd[2] = PartitionCookieByPriority(it_bdd[1], it_bdd[3],
+ COOKIE_PRIORITY_MEDIUM);
+ size_t quota[3] = {
+ kDomainCookiesQuotaLow,
+ kDomainCookiesQuotaMedium,
+ kDomainCookiesQuotaHigh
+ };
+
+ // Purge domain cookies in 3 rounds.
+ // Round 1: consider low-priority cookies only: evict least-recently
+ // accessed, while protecting quota[0] of these from deletion.
+ // Round 2: consider {low, medium}-priority cookies, evict least-recently
+ // accessed, while protecting quota[0] + quota[1].
+ // Round 3: consider all cookies, evict least-recently accessed.
+ size_t accumulated_quota = 0;
+ CookieItVector::iterator it_purge_begin = it_bdd[0];
+ for (int i = 0; i < 3 && purge_goal > 0; ++i) {
+ accumulated_quota += quota[i];
+
+ // If we are not using priority, only do Round 3. This reproduces the
+ // old way of indiscriminately purging least-recently accessed cookies.
+ if (!priority_aware_garbage_collection_ && i < 2)
+ continue;
+
+ size_t num_considered = it_bdd[i + 1] - it_purge_begin;
+ if (num_considered <= accumulated_quota)
+ continue;
+
+ // Number of cookies that will be purged in this round.
+ size_t round_goal =
+ std::min(purge_goal, num_considered - accumulated_quota);
+ purge_goal -= round_goal;
+
+ SortLeastRecentlyAccessed(it_purge_begin, it_bdd[i + 1], round_goal);
+ // Cookies accessed on or after |safe_date| would have been safe from
+ // global purge, and we want to keep track of this.
+ CookieItVector::iterator it_purge_end = it_purge_begin + round_goal;
+ CookieItVector::iterator it_purge_middle =
+ LowerBoundAccessDate(it_purge_begin, it_purge_end, safe_date);
+ // Delete cookies accessed before |safe_date|.
+ num_deleted += GarbageCollectDeleteRange(
+ current,
+ DELETE_COOKIE_EVICTED_DOMAIN_PRE_SAFE,
+ it_purge_begin,
+ it_purge_middle);
+ // Delete cookies accessed on or after |safe_date|.
+ num_deleted += GarbageCollectDeleteRange(
+ current,
+ DELETE_COOKIE_EVICTED_DOMAIN_POST_SAFE,
+ it_purge_middle,
+ it_purge_end);
+ it_purge_begin = it_purge_end;
+ }
+ DCHECK_EQ(0U, purge_goal);
}
}
- // Collect garbage for everything. With firefox style we want to
- // preserve cookies touched in kSafeFromGlobalPurgeDays, otherwise
- // not.
+ // Collect garbage for everything. With firefox style we want to preserve
+ // cookies accessed in kSafeFromGlobalPurgeDays, otherwise evict.
if (cookies_.size() > kMaxCookies &&
- (earliest_access_time_ <
- Time::Now() - TimeDelta::FromDays(kSafeFromGlobalPurgeDays))) {
+ earliest_access_time_ < safe_date) {
VLOG(kVlogGarbageCollection) << "GarbageCollect() everything";
- std::vector<CookieMap::iterator> cookie_its;
- base::Time oldest_left;
+ CookieItVector cookie_its;
num_deleted += GarbageCollectExpired(
current, CookieMapItPair(cookies_.begin(), cookies_.end()),
&cookie_its);
- if (FindLeastRecentlyAccessed(kMaxCookies, kPurgeCookies,
- &oldest_left, &cookie_its)) {
- Time oldest_safe_cookie(
- (Time::Now() - TimeDelta::FromDays(kSafeFromGlobalPurgeDays)));
- int num_evicted = GarbageCollectDeleteList(
+ if (cookie_its.size() > kMaxCookies) {
+ VLOG(kVlogGarbageCollection) << "Deep Garbage Collect everything.";
+ size_t purge_goal = cookie_its.size() - (kMaxCookies - kPurgeCookies);
+ DCHECK(purge_goal > kPurgeCookies);
+ // Sorts up to *and including* |cookie_its[purge_goal]|, so
+ // |earliest_access_time| will be properly assigned even if
+ // |global_purge_it| == |cookie_its.begin() + purge_goal|.
+ SortLeastRecentlyAccessed(cookie_its.begin(), cookie_its.end(),
+ purge_goal);
+ // Find boundary to cookies older than safe_date.
+ CookieItVector::iterator global_purge_it =
+ LowerBoundAccessDate(cookie_its.begin(),
+ cookie_its.begin() + purge_goal,
+ safe_date);
+ // Only delete the old cookies.
+ num_deleted += GarbageCollectDeleteRange(
current,
- oldest_safe_cookie,
DELETE_COOKIE_EVICTED_GLOBAL,
- cookie_its);
-
- // If no cookies were preserved by the time limit, the global last
- // access is set to the value returned from FindLeastRecentlyAccessed.
- // If the time limit preserved some cookies, we use the last access of
- // the oldest preserved cookie.
- if (num_evicted == static_cast<int>(cookie_its.size())) {
- earliest_access_time_ = oldest_left;
- } else {
- earliest_access_time_ =
- (*(cookie_its.begin() + num_evicted))->second->LastAccessDate();
- }
- num_deleted += num_evicted;
+ cookie_its.begin(),
+ global_purge_it);
+ // Set access day to the oldest cookie that wasn't deleted.
+ earliest_access_time_ = (*global_purge_it)->second->LastAccessDate();
}
}
@@ -1839,7 +1924,7 @@ int CookieMonster::GarbageCollect(const Time& current,
int CookieMonster::GarbageCollectExpired(
const Time& current,
const CookieMapItPair& itpair,
- std::vector<CookieMap::iterator>* cookie_its) {
+ CookieItVector* cookie_its) {
if (keep_expired_cookies_)
return 0;
@@ -1861,23 +1946,17 @@ int CookieMonster::GarbageCollectExpired(
return num_deleted;
}
-int CookieMonster::GarbageCollectDeleteList(
+int CookieMonster::GarbageCollectDeleteRange(
const Time& current,
- const Time& keep_accessed_after,
DeletionCause cause,
- std::vector<CookieMap::iterator>& cookie_its) {
- int num_deleted = 0;
- for (std::vector<CookieMap::iterator>::iterator it = cookie_its.begin();
- it != cookie_its.end(); it++) {
- if (keep_accessed_after.is_null() ||
- (*it)->second->LastAccessDate() < keep_accessed_after) {
- histogram_evicted_last_access_minutes_->Add(
- (current - (*it)->second->LastAccessDate()).InMinutes());
- InternalDeleteCookie((*it), true, cause);
- num_deleted++;
- }
- }
- return num_deleted;
+ CookieMonster::CookieItVector::iterator it_begin,
+ CookieMonster::CookieItVector::iterator it_end) {
+ for (CookieItVector::iterator it = it_begin; it != it_end; it++) {
+ histogram_evicted_last_access_minutes_->Add(
+ (current - (*it)->second->LastAccessDate()).InMinutes());
+ InternalDeleteCookie((*it), true, cause);
+ }
+ return it_end - it_begin;
}
// A wrapper around RegistryControlledDomainService::GetDomainAndRegistry
diff --git a/net/cookies/cookie_monster.h b/net/cookies/cookie_monster.h
index bb7686e..c957539 100644
--- a/net/cookies/cookie_monster.h
+++ b/net/cookies/cookie_monster.h
@@ -106,6 +106,31 @@ class NET_EXPORT CookieMonster : public CookieStore {
// subtantially more entries in the map.
typedef std::multimap<std::string, CanonicalCookie*> CookieMap;
typedef std::pair<CookieMap::iterator, CookieMap::iterator> CookieMapItPair;
+ typedef std::vector<CookieMap::iterator> CookieItVector;
+
+ // Cookie garbage collection thresholds. Based off of the Mozilla defaults.
+ // When the number of cookies gets to k{Domain,}MaxCookies
+ // purge down to k{Domain,}MaxCookies - k{Domain,}PurgeCookies.
+ // It might seem scary to have a high purge value, but really it's not.
+ // You just make sure that you increase the max to cover the increase
+ // in purge, and we would have been purging the same number of cookies.
+ // We're just going through the garbage collection process less often.
+ // Note that the DOMAIN values are per eTLD+1; see comment for the
+ // CookieMap typedef. So, e.g., the maximum number of cookies allowed for
+ // google.com and all of its subdomains will be 150-180.
+ //
+ // Any cookies accessed more recently than kSafeFromGlobalPurgeDays will not
+ // be evicted by global garbage collection, even if we have more than
+ // kMaxCookies. This does not affect domain garbage collection.
+ static const size_t kDomainMaxCookies;
+ static const size_t kDomainPurgeCookies;
+ static const size_t kMaxCookies;
+ static const size_t kPurgeCookies;
+
+ // Quota for cookies with {low, medium, high} priorities within a domain.
+ static const size_t kDomainCookiesQuotaLow;
+ static const size_t kDomainCookiesQuotaMedium;
+ static const size_t kDomainCookiesQuotaHigh;
// The store passed in should not have had Init() called on it yet. This
// class will take care of initializing it. The backing store is NOT owned by
@@ -255,6 +280,11 @@ class NET_EXPORT CookieMonster : public CookieStore {
// (i.e. as part of the instance initialization process).
void SetPersistSessionCookies(bool persist_session_cookies);
+ // Enables the new garbage collection algorithm where domain cookie eviction
+ // uses cookie priorities to decide which cookies to purge and which to keep.
+ void SetPriorityAwareGarbageCollection(
+ bool priority_aware_garbage_collection);
+
// Debugging method to perform various validation checks on the map.
// Currently just checking that there are no null CanonicalCookie pointers
// in the map.
@@ -340,28 +370,6 @@ class NET_EXPORT CookieMonster : public CookieStore {
DELETE_COOKIE_LAST_ENTRY
};
- // Cookie garbage collection thresholds. Based off of the Mozilla defaults.
- // When the number of cookies gets to k{Domain,}MaxCookies
- // purge down to k{Domain,}MaxCookies - k{Domain,}PurgeCookies.
- // It might seem scary to have a high purge value, but really it's not.
- // You just make sure that you increase the max to cover the increase
- // in purge, and we would have been purging the same amount of cookies.
- // We're just going through the garbage collection process less often.
- // Note that the DOMAIN values are per eTLD+1; see comment for the
- // CookieMap typedef. So, e.g., the maximum number of cookies allowed for
- // google.com and all of its subdomains will be 150-180.
- //
- // Any cookies accessed more recently than kSafeFromGlobalPurgeDays will not
- // be evicted by global garbage collection, even if we have more than
- // kMaxCookies. This does not affect domain garbage collection.
- //
- // Present in .h file to make accessible to tests through FRIEND_TEST.
- // Actual definitions are in cookie_monster.cc.
- static const size_t kDomainMaxCookies;
- static const size_t kDomainPurgeCookies;
- static const size_t kMaxCookies;
- static const size_t kPurgeCookies;
-
// The number of days since last access that cookies will not be subject
// to global garbage collection.
static const int kSafeFromGlobalPurgeDays;
@@ -538,14 +546,12 @@ class NET_EXPORT CookieMonster : public CookieStore {
const CookieMapItPair& itpair,
std::vector<CookieMap::iterator>* cookie_its);
- // Helper for GarbageCollect(). Deletes all cookies in the list
- // that were accessed before |keep_accessed_after|, using DeletionCause
- // |cause|. If |keep_accessed_after| is null, deletes all cookies in the
- // list. Returns the number of cookies deleted.
- int GarbageCollectDeleteList(const base::Time& current,
- const base::Time& keep_accessed_after,
- DeletionCause cause,
- std::vector<CookieMap::iterator>& cookie_its);
+ // Helper for GarbageCollect(). Deletes all cookies in the range specified by
+ // [|it_begin|, |it_end|). Returns the number of cookies deleted.
+ int GarbageCollectDeleteRange(const base::Time& current,
+ DeletionCause cause,
+ CookieItVector::iterator cookie_its_begin,
+ CookieItVector::iterator cookie_its_end);
// Find the key (for lookup in cookies_) based on the given domain.
// See comment on keys before the CookieMap typedef.
@@ -650,6 +656,7 @@ class NET_EXPORT CookieMonster : public CookieStore {
bool keep_expired_cookies_;
bool persist_session_cookies_;
+ bool priority_aware_garbage_collection_;
// Static setting for whether or not file scheme cookies are allows when
// a new CookieMonster is created, or the accepted schemes on a CookieMonster
diff --git a/net/cookies/cookie_monster_unittest.cc b/net/cookies/cookie_monster_unittest.cc
index 9034975..df4fa9d 100644
--- a/net/cookies/cookie_monster_unittest.cc
+++ b/net/cookies/cookie_monster_unittest.cc
@@ -4,7 +4,9 @@
#include "net/cookies/cookie_store_unittest.h"
+#include <algorithm>
#include <string>
+#include <vector>
#include "base/basictypes.h"
#include "base/bind.h"
@@ -14,6 +16,9 @@
#include "base/metrics/histogram.h"
#include "base/metrics/histogram_samples.h"
#include "base/stringprintf.h"
+#include "base/strings/string_number_conversions.h"
+#include "base/strings/string_piece.h"
+#include "base/strings/string_split.h"
#include "base/strings/string_tokenizer.h"
#include "base/threading/thread.h"
#include "base/time.h"
@@ -357,9 +362,9 @@ class CookieMonsterTest : public CookieStoreTest<CookieMonsterTestTraits> {
return std::count(str.begin(), str.end(), c);
}
- void TestHostGarbageCollectHelper(
- int domain_max_cookies,
- int domain_purge_cookies) {
+ void TestHostGarbageCollectHelper() {
+ int domain_max_cookies = CookieMonster::kDomainMaxCookies;
+ int domain_purge_cookies = CookieMonster::kDomainPurgeCookies;
const int more_than_enough_cookies =
(domain_max_cookies + domain_purge_cookies) * 2;
// Add a bunch of cookies on a single host, should purge them.
@@ -402,12 +407,159 @@ class CookieMonsterTest : public CookieStoreTest<CookieMonsterTestTraits> {
std::string cookies_specific = this->GetCookies(cm, url_google_specific);
int total_cookies = (CountInString(cookies_general, '=') +
CountInString(cookies_specific, '='));
- EXPECT_GE(total_cookies,
- domain_max_cookies - domain_purge_cookies);
+ EXPECT_GE(total_cookies, domain_max_cookies - domain_purge_cookies);
EXPECT_LE(total_cookies, domain_max_cookies);
}
}
+ CookiePriority CharToPriority(char ch) {
+ switch (ch) {
+ case 'L':
+ return COOKIE_PRIORITY_LOW;
+ case 'M':
+ return COOKIE_PRIORITY_MEDIUM;
+ case 'H':
+ return COOKIE_PRIORITY_HIGH;
+ }
+ NOTREACHED();
+ return COOKIE_PRIORITY_DEFAULT;
+ }
+
+ // Instantiates a CookieMonster, adds multiple cookies (to url_google_) with
+ // priorities specified by |coded_priority_str|, and tests priority-aware
+ // domain cookie eviction.
+ // |coded_priority_str| specifies a run-length-encoded string of priorities.
+ // Example: "2M 3L M 4H" means "MMLLLMHHHH", and speicifies sequential (i.e.,
+ // from least- to most-recently accessed) insertion of 2 medium-priority
+ // cookies, 3 low-priority cookies, 1 medium-priority cookie, and 4
+ // high-priority cookies.
+ // Within each priority, only the least-accessed cookies should be evicted.
+ // Thus, to describe expected suriving cookies, it suffices to specify the
+ // expected population of surviving cookies per priority, i.e.,
+ // |expected_low_count|, |expected_medium_count|, and |expected_high_count|.
+ void TestPriorityCookieCase(CookieMonster* cm,
+ const std::string& coded_priority_str,
+ size_t expected_low_count,
+ size_t expected_medium_count,
+ size_t expected_high_count) {
+ DeleteAll(cm);
+ int next_cookie_id = 0;
+ std::vector<CookiePriority> priority_list;
+ std::vector<int> id_list[3]; // Indexed by CookiePriority.
+
+ // Parse |coded_priority_str| and add cookies.
+ std::vector<std::string> priority_tok_list;
+ base::SplitString(coded_priority_str, ' ', &priority_tok_list);
+ for (std::vector<std::string>::iterator it = priority_tok_list.begin();
+ it != priority_tok_list.end(); ++it) {
+ size_t len = it->length();
+ DCHECK_NE(len, 0U);
+ // Take last character as priority.
+ CookiePriority priority = CharToPriority((*it)[len - 1]);
+ std::string priority_str = CookiePriorityToString(priority);
+ // The rest of the string (possibly empty) specifies repetition.
+ int rep = 1;
+ if (!it->empty()) {
+ bool result = base::StringToInt(
+ base::StringPiece(it->begin(), it->end() - 1), &rep);
+ DCHECK(result);
+ }
+ for (; rep > 0; --rep, ++next_cookie_id) {
+ std::string cookie = base::StringPrintf(
+ "a%d=b;priority=%s", next_cookie_id, priority_str.c_str());
+ EXPECT_TRUE(SetCookie(cm, url_google_, cookie));
+ priority_list.push_back(priority);
+ id_list[priority].push_back(next_cookie_id);
+ }
+ }
+
+ int num_cookies = static_cast<int>(priority_list.size());
+ std::vector<int> surviving_id_list[3]; // Indexed by CookiePriority.
+
+ // Parse the list of cookies
+ std::string cookie_str = this->GetCookies(cm, url_google_);
+ std::vector<std::string> cookie_tok_list;
+ base::SplitString(cookie_str, ';', &cookie_tok_list);
+ for (std::vector<std::string>::iterator it = cookie_tok_list.begin();
+ it != cookie_tok_list.end(); ++it) {
+ // Assuming *it is "a#=b", so extract and parse "#" portion.
+ int id = -1;
+ bool result = base::StringToInt(
+ base::StringPiece(it->begin() + 1, it->end() - 2), &id);
+ DCHECK(result);
+ DCHECK_GE(id, 0);
+ DCHECK_LT(id, num_cookies);
+ surviving_id_list[priority_list[id]].push_back(id);
+ }
+
+ // Validate each priority.
+ size_t expected_count[3] = {
+ expected_low_count, expected_medium_count, expected_high_count
+ };
+ for (int i = 0; i < 3; ++i) {
+ DCHECK_LE(surviving_id_list[i].size(), id_list[i].size());
+ EXPECT_EQ(expected_count[i], surviving_id_list[i].size());
+ // Verify that the remaining cookies are the most recent among those
+ // with the same priorities.
+ if (expected_count[i] == surviving_id_list[i].size()) {
+ std::sort(surviving_id_list[i].begin(), surviving_id_list[i].end());
+ EXPECT_TRUE(std::equal(surviving_id_list[i].begin(),
+ surviving_id_list[i].end(),
+ id_list[i].end() - expected_count[i]));
+ }
+ }
+ }
+
+ void TestPriorityAwareGarbageCollectHelper() {
+ // Hard-coding limits in the test, but use DCHECK_EQ to enforce constraint.
+ DCHECK_EQ(180U, CookieMonster::kDomainMaxCookies);
+ DCHECK_EQ(150U, CookieMonster::kDomainMaxCookies -
+ CookieMonster::kDomainPurgeCookies);
+ DCHECK_EQ(30U, CookieMonster::kDomainCookiesQuotaLow);
+ DCHECK_EQ(50U, CookieMonster::kDomainCookiesQuotaMedium);
+ DCHECK_EQ(70U, CookieMonster::kDomainCookiesQuotaHigh);
+
+ scoped_refptr<CookieMonster> cm(new CookieMonster(NULL, NULL));
+ cm->SetPriorityAwareGarbageCollection(true);
+
+ // Each test case adds 181 cookies, so 31 cookies are evicted.
+ // Cookie same priority, repeated for each priority.
+ TestPriorityCookieCase(cm, "181L", 150U, 0U, 0U);
+ TestPriorityCookieCase(cm, "181M", 0U, 150U, 0U);
+ TestPriorityCookieCase(cm, "181H", 0U, 0U, 150U);
+
+ // Pairwise scenarios.
+ // Round 1 => none; round2 => 31M; round 3 => none.
+ TestPriorityCookieCase(cm, "10H 171M", 0U, 140U, 10U);
+ // Round 1 => 10L; round2 => 21M; round 3 => none.
+ TestPriorityCookieCase(cm, "141M 40L", 30U, 120U, 0U);
+ // Round 1 => none; round2 => none; round 3 => 31H.
+ TestPriorityCookieCase(cm, "101H 80M", 0U, 80U, 70U);
+
+ // For {low, medium} priorities right on quota, different orders.
+ // Round 1 => 1L; round 2 => none, round3 => 30L.
+ TestPriorityCookieCase(cm, "31L 50M 100H", 0U, 50U, 100U);
+ // Round 1 => none; round 2 => 1M, round3 => 30M.
+ TestPriorityCookieCase(cm, "51M 100H 30L", 30U, 20U, 100U);
+ // Round 1 => none; round 2 => none; round3 => 31H.
+ TestPriorityCookieCase(cm, "101H 50M 30L", 30U, 50U, 70U);
+
+ // Round 1 => 10L; round 2 => 10M; round3 => 11H.
+ TestPriorityCookieCase(cm, "81H 60M 40L", 30U, 50U, 70U);
+
+ // More complex scenarios.
+ // Round 1 => 10L; round 2 => 10M; round 3 => 11H.
+ TestPriorityCookieCase(cm, "21H 60M 40L 60H", 30U, 50U, 70U);
+ // Round 1 => 10L; round 2 => 11M, 10L; round 3 => none.
+ TestPriorityCookieCase(cm, "11H 10M 20L 110M 20L 10H", 20U, 109U, 21U);
+ // Round 1 => none; round 2 => none; round 3 => 11L, 10M, 10H.
+ TestPriorityCookieCase(cm, "11L 10M 140H 10M 10L", 10U, 10U, 130U);
+ // Round 1 => none; round 2 => 1M; round 3 => 10L, 10M, 10H.
+ TestPriorityCookieCase(cm, "11M 10H 10L 60M 90H", 0U, 60U, 90U);
+ // Round 1 => none; round 2 => 10L, 21M; round 3 => none.
+ TestPriorityCookieCase(cm, "11M 10H 10L 90M 60H", 0U, 80U, 70U);
+ }
+
// Function for creating a CM with a number of cookies in it,
// no store (and hence no ability to affect access time).
CookieMonster* CreateMonsterForGC(int num_cookies) {
@@ -561,8 +713,8 @@ class DeferredCookieTaskTest : public CookieMonsterTest {
// Defines a cookie to be returned from PersistentCookieStore::Load
void DeclareLoadedCookie(const std::string& key,
- const std::string& cookie_line,
- const base::Time& creation_time) {
+ const std::string& cookie_line,
+ const base::Time& creation_time) {
AddCookieToList(key, cookie_line, creation_time, &loaded_cookies_);
}
@@ -1011,8 +1163,11 @@ TEST_F(CookieMonsterTest, TestLastAccess) {
}
TEST_F(CookieMonsterTest, TestHostGarbageCollection) {
- TestHostGarbageCollectHelper(
- CookieMonster::kDomainMaxCookies, CookieMonster::kDomainPurgeCookies);
+ TestHostGarbageCollectHelper();
+}
+
+TEST_F(CookieMonsterTest, TestPriorityAwareGarbageCollection) {
+ TestPriorityAwareGarbageCollectHelper();
}
TEST_F(CookieMonsterTest, TestDeleteSingleCookie) {