evicted_domain_cookie_counter.cc revision 868fa2fe829687343ffae624259930155e16dbd8
1// Copyright 2013 The Chromium Authors. All rights reserved.
2// Use of this source code is governed by a BSD-style license that can be
3// found in the LICENSE file.
4
5#include "chrome/browser/net/evicted_domain_cookie_counter.h"
6
7#include <algorithm>
8#include <vector>
9
10#include "base/metrics/histogram.h"
11#include "base/stl_util.h"
12#include "base/strings/string_util.h"
13#include "chrome/browser/google/google_util.h"
14#include "net/cookies/canonical_cookie.h"
15
16namespace chrome_browser_net {
17
18using base::Time;
19using base::TimeDelta;
20
21namespace {
22
23const size_t kMaxEvictedDomainCookies = 500;
24const size_t kPurgeEvictedDomainCookies = 100;
25
26class DelegateImpl : public EvictedDomainCookieCounter::Delegate {
27 public:
28  DelegateImpl();
29
30  // EvictedDomainCookieCounter::Delegate implementation.
31  virtual void Report(
32      const EvictedDomainCookieCounter::EvictedCookie& evicted_cookie,
33      const Time& reinstatement_time) OVERRIDE;
34  virtual Time CurrentTime() const OVERRIDE;
35};
36
37DelegateImpl::DelegateImpl() {}
38
39void DelegateImpl::Report(
40    const EvictedDomainCookieCounter::EvictedCookie& evicted_cookie,
41    const Time& reinstatement_time) {
42  TimeDelta reinstatement_delay(
43      reinstatement_time - evicted_cookie.eviction_time);
44  // Need to duplicate HISTOGRAM_CUSTOM_TIMES(), since it is a macro that
45  // defines a static variable.
46  if (evicted_cookie.is_google) {
47    UMA_HISTOGRAM_CUSTOM_TIMES("Cookie.ReinstatedCookiesGoogle",
48                               reinstatement_delay,
49                               TimeDelta::FromSeconds(1),
50                               TimeDelta::FromDays(7),
51                               50);
52  } else {
53    UMA_HISTOGRAM_CUSTOM_TIMES("Cookie.ReinstatedCookiesOther",
54                               reinstatement_delay,
55                               TimeDelta::FromSeconds(1),
56                               TimeDelta::FromDays(7),
57                               50);
58  }
59}
60
61Time DelegateImpl::CurrentTime() const {
62  return Time::Now();
63}
64
65}  // namespace
66
67EvictedDomainCookieCounter::EvictedDomainCookieCounter(
68    scoped_refptr<net::CookieMonster::Delegate> next_cookie_monster_delegate)
69    : next_cookie_monster_delegate_(next_cookie_monster_delegate),
70      cookie_counter_delegate_(new DelegateImpl),
71      max_size_(kMaxEvictedDomainCookies),
72      purge_count_(kPurgeEvictedDomainCookies) {
73}
74
75EvictedDomainCookieCounter::EvictedDomainCookieCounter(
76    scoped_refptr<net::CookieMonster::Delegate> next_cookie_monster_delegate,
77    scoped_ptr<Delegate> cookie_counter_delegate,
78    size_t max_size,
79    size_t purge_count)
80    : next_cookie_monster_delegate_(next_cookie_monster_delegate),
81      cookie_counter_delegate_(cookie_counter_delegate.Pass()),
82      max_size_(max_size),
83      purge_count_(purge_count) {
84  DCHECK(cookie_counter_delegate_);
85  DCHECK_LT(purge_count, max_size_);
86}
87
88EvictedDomainCookieCounter::~EvictedDomainCookieCounter() {
89  STLDeleteContainerPairSecondPointers(evicted_cookies_.begin(),
90                                       evicted_cookies_.end());
91}
92
93size_t EvictedDomainCookieCounter::GetStorageSize() const {
94  return evicted_cookies_.size();
95}
96
97void EvictedDomainCookieCounter::OnCookieChanged(
98    const net::CanonicalCookie& cookie,
99    bool removed,
100    ChangeCause cause) {
101  EvictedDomainCookieCounter::EvictedCookieKey key(GetKey(cookie));
102  Time current_time(cookie_counter_delegate_->CurrentTime());
103  if (removed) {
104    if (cause == net::CookieMonster::Delegate::CHANGE_COOKIE_EVICTED)
105      StoreEvictedCookie(key, cookie, current_time);
106  } else {  // Includes adds or updates.
107    ProcessNewCookie(key, cookie, current_time);
108  }
109
110  if (next_cookie_monster_delegate_.get())
111    next_cookie_monster_delegate_->OnCookieChanged(cookie, removed, cause);
112}
113
114// static
115EvictedDomainCookieCounter::EvictedCookieKey
116    EvictedDomainCookieCounter::GetKey(const net::CanonicalCookie& cookie) {
117  return cookie.Domain() + ";" + cookie.Path() + ";" + cookie.Name();
118}
119
120// static
121bool EvictedDomainCookieCounter::CompareEvictedCookie(
122    const EvictedCookieMap::iterator evicted_cookie1,
123    const EvictedCookieMap::iterator evicted_cookie2) {
124  return evicted_cookie1->second->eviction_time
125      < evicted_cookie2->second->eviction_time;
126}
127
128void EvictedDomainCookieCounter::GarbageCollect(const Time& current_time) {
129  if (evicted_cookies_.size() <= max_size_)
130    return;
131
132  // From |evicted_cookies_|, removed all expired cookies, and remove cookies
133  // with the oldest |eviction_time| so that |size_goal| is attained.
134  size_t size_goal = max_size_ - purge_count_;
135  // Bound on number of non-expired cookies to remove.
136  size_t remove_quota = evicted_cookies_.size() - size_goal;
137  DCHECK_GT(remove_quota, 0u);
138
139  std::vector<EvictedCookieMap::iterator> remove_list;
140  remove_list.reserve(evicted_cookies_.size());
141
142  EvictedCookieMap::iterator it = evicted_cookies_.begin();
143  while (it != evicted_cookies_.end()) {
144    if (it->second->is_expired(current_time)) {
145      delete it->second;
146      evicted_cookies_.erase(it++); // Post-increment idiom for in-loop removal.
147      if (remove_quota)
148        --remove_quota;
149    } else {
150      if (remove_quota)  // Don't bother storing if quota met.
151        remove_list.push_back(it);
152      ++it;
153    }
154  }
155
156  // Free the oldest |remove_quota| non-expired cookies.
157  std::partial_sort(remove_list.begin(), remove_list.begin() + remove_quota,
158                    remove_list.end(), CompareEvictedCookie);
159  for (size_t i = 0; i < remove_quota; ++i) {
160    delete remove_list[i]->second;
161    evicted_cookies_.erase(remove_list[i]);
162  }
163
164  // Apply stricter check if non-expired cookies were deleted.
165  DCHECK(remove_quota ? evicted_cookies_.size() == size_goal :
166         evicted_cookies_.size() <= size_goal);
167}
168
169void EvictedDomainCookieCounter::StoreEvictedCookie(
170    const EvictedCookieKey& key,
171    const net::CanonicalCookie& cookie,
172    const Time& current_time) {
173  bool is_google = google_util::IsGoogleHostname(
174      cookie.Domain(), google_util::ALLOW_SUBDOMAIN);
175  EvictedCookie* evicted_cookie =
176      new EvictedCookie(current_time, cookie.ExpiryDate(), is_google);
177  std::pair<EvictedCookieMap::iterator, bool> prev_entry =
178      evicted_cookies_.insert(
179          EvictedCookieMap::value_type(key, evicted_cookie));
180  if (!prev_entry.second) {
181    NOTREACHED();
182    delete prev_entry.first->second;
183    prev_entry.first->second = evicted_cookie;
184  }
185
186  GarbageCollect(current_time);
187}
188
189void EvictedDomainCookieCounter::ProcessNewCookie(
190    const EvictedCookieKey& key,
191    const net::CanonicalCookie& cc,
192    const Time& current_time) {
193  EvictedCookieMap::iterator it = evicted_cookies_.find(key);
194  if (it != evicted_cookies_.end()) {
195    if (!it->second->is_expired(current_time))  // Reinstatement.
196      cookie_counter_delegate_->Report(*it->second, current_time);
197    delete it->second;
198    evicted_cookies_.erase(it);
199  }
200}
201
202}  // namespace chrome_browser_net
203