| // Copyright 2013 The Chromium Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| #include "chrome/browser/net/evicted_domain_cookie_counter.h" |
| |
| #include <algorithm> |
| #include <vector> |
| |
| #include "base/metrics/histogram.h" |
| #include "base/stl_util.h" |
| #include "base/strings/string_util.h" |
| #include "components/google/core/browser/google_util.h" |
| #include "net/cookies/canonical_cookie.h" |
| |
| namespace chrome_browser_net { |
| |
| using base::Time; |
| using base::TimeDelta; |
| |
| namespace { |
| |
| const size_t kMaxEvictedDomainCookies = 500; |
| const size_t kPurgeEvictedDomainCookies = 100; |
| |
| class DelegateImpl : public EvictedDomainCookieCounter::Delegate { |
| public: |
| DelegateImpl(); |
| |
| // EvictedDomainCookieCounter::Delegate implementation. |
| virtual void Report( |
| const EvictedDomainCookieCounter::EvictedCookie& evicted_cookie, |
| const Time& reinstatement_time) OVERRIDE; |
| virtual Time CurrentTime() const OVERRIDE; |
| }; |
| |
| DelegateImpl::DelegateImpl() {} |
| |
| void DelegateImpl::Report( |
| const EvictedDomainCookieCounter::EvictedCookie& evicted_cookie, |
| const Time& reinstatement_time) { |
| TimeDelta reinstatement_delay( |
| reinstatement_time - evicted_cookie.eviction_time); |
| // Need to duplicate HISTOGRAM_CUSTOM_TIMES(), since it is a macro that |
| // defines a static variable. |
| if (evicted_cookie.is_google) { |
| UMA_HISTOGRAM_CUSTOM_TIMES("Cookie.ReinstatedCookiesGoogle", |
| reinstatement_delay, |
| TimeDelta::FromSeconds(1), |
| TimeDelta::FromDays(7), |
| 50); |
| } else { |
| UMA_HISTOGRAM_CUSTOM_TIMES("Cookie.ReinstatedCookiesOther", |
| reinstatement_delay, |
| TimeDelta::FromSeconds(1), |
| TimeDelta::FromDays(7), |
| 50); |
| } |
| } |
| |
| Time DelegateImpl::CurrentTime() const { |
| return Time::Now(); |
| } |
| |
| } // namespace |
| |
| EvictedDomainCookieCounter::EvictedDomainCookieCounter( |
| scoped_refptr<net::CookieMonster::Delegate> next_cookie_monster_delegate) |
| : next_cookie_monster_delegate_(next_cookie_monster_delegate), |
| cookie_counter_delegate_(new DelegateImpl), |
| max_size_(kMaxEvictedDomainCookies), |
| purge_count_(kPurgeEvictedDomainCookies) { |
| } |
| |
| EvictedDomainCookieCounter::EvictedDomainCookieCounter( |
| scoped_refptr<net::CookieMonster::Delegate> next_cookie_monster_delegate, |
| scoped_ptr<Delegate> cookie_counter_delegate, |
| size_t max_size, |
| size_t purge_count) |
| : next_cookie_monster_delegate_(next_cookie_monster_delegate), |
| cookie_counter_delegate_(cookie_counter_delegate.Pass()), |
| max_size_(max_size), |
| purge_count_(purge_count) { |
| DCHECK(cookie_counter_delegate_); |
| DCHECK_LT(purge_count, max_size_); |
| } |
| |
| EvictedDomainCookieCounter::~EvictedDomainCookieCounter() { |
| STLDeleteContainerPairSecondPointers(evicted_cookies_.begin(), |
| evicted_cookies_.end()); |
| } |
| |
| size_t EvictedDomainCookieCounter::GetStorageSize() const { |
| return evicted_cookies_.size(); |
| } |
| |
| void EvictedDomainCookieCounter::OnCookieChanged( |
| const net::CanonicalCookie& cookie, |
| bool removed, |
| ChangeCause cause) { |
| EvictedDomainCookieCounter::EvictedCookieKey key(GetKey(cookie)); |
| Time current_time(cookie_counter_delegate_->CurrentTime()); |
| if (removed) { |
| if (cause == net::CookieMonster::Delegate::CHANGE_COOKIE_EVICTED) |
| StoreEvictedCookie(key, cookie, current_time); |
| } else { // Includes adds or updates. |
| ProcessNewCookie(key, cookie, current_time); |
| } |
| |
| if (next_cookie_monster_delegate_.get()) |
| next_cookie_monster_delegate_->OnCookieChanged(cookie, removed, cause); |
| } |
| |
| void EvictedDomainCookieCounter::OnLoaded() { |
| if (next_cookie_monster_delegate_.get()) |
| next_cookie_monster_delegate_->OnLoaded(); |
| } |
| |
| // static |
| EvictedDomainCookieCounter::EvictedCookieKey |
| EvictedDomainCookieCounter::GetKey(const net::CanonicalCookie& cookie) { |
| return cookie.Domain() + ";" + cookie.Path() + ";" + cookie.Name(); |
| } |
| |
| // static |
| bool EvictedDomainCookieCounter::CompareEvictedCookie( |
| const EvictedCookieMap::iterator evicted_cookie1, |
| const EvictedCookieMap::iterator evicted_cookie2) { |
| return evicted_cookie1->second->eviction_time |
| < evicted_cookie2->second->eviction_time; |
| } |
| |
| void EvictedDomainCookieCounter::GarbageCollect(const Time& current_time) { |
| if (evicted_cookies_.size() <= max_size_) |
| return; |
| |
| // From |evicted_cookies_|, removed all expired cookies, and remove cookies |
| // with the oldest |eviction_time| so that |size_goal| is attained. |
| size_t size_goal = max_size_ - purge_count_; |
| // Bound on number of non-expired cookies to remove. |
| size_t remove_quota = evicted_cookies_.size() - size_goal; |
| DCHECK_GT(remove_quota, 0u); |
| |
| std::vector<EvictedCookieMap::iterator> remove_list; |
| remove_list.reserve(evicted_cookies_.size()); |
| |
| EvictedCookieMap::iterator it = evicted_cookies_.begin(); |
| while (it != evicted_cookies_.end()) { |
| if (it->second->is_expired(current_time)) { |
| delete it->second; |
| evicted_cookies_.erase(it++); // Post-increment idiom for in-loop removal. |
| if (remove_quota) |
| --remove_quota; |
| } else { |
| if (remove_quota) // Don't bother storing if quota met. |
| remove_list.push_back(it); |
| ++it; |
| } |
| } |
| |
| // Free the oldest |remove_quota| non-expired cookies. |
| std::partial_sort(remove_list.begin(), remove_list.begin() + remove_quota, |
| remove_list.end(), CompareEvictedCookie); |
| for (size_t i = 0; i < remove_quota; ++i) { |
| delete remove_list[i]->second; |
| evicted_cookies_.erase(remove_list[i]); |
| } |
| |
| // Apply stricter check if non-expired cookies were deleted. |
| DCHECK(remove_quota ? evicted_cookies_.size() == size_goal : |
| evicted_cookies_.size() <= size_goal); |
| } |
| |
| void EvictedDomainCookieCounter::StoreEvictedCookie( |
| const EvictedCookieKey& key, |
| const net::CanonicalCookie& cookie, |
| const Time& current_time) { |
| bool is_google = google_util::IsGoogleHostname( |
| cookie.Domain(), google_util::ALLOW_SUBDOMAIN); |
| EvictedCookie* evicted_cookie = |
| new EvictedCookie(current_time, cookie.ExpiryDate(), is_google); |
| std::pair<EvictedCookieMap::iterator, bool> prev_entry = |
| evicted_cookies_.insert( |
| EvictedCookieMap::value_type(key, evicted_cookie)); |
| if (!prev_entry.second) { |
| NOTREACHED(); |
| delete prev_entry.first->second; |
| prev_entry.first->second = evicted_cookie; |
| } |
| |
| GarbageCollect(current_time); |
| } |
| |
| void EvictedDomainCookieCounter::ProcessNewCookie( |
| const EvictedCookieKey& key, |
| const net::CanonicalCookie& cc, |
| const Time& current_time) { |
| EvictedCookieMap::iterator it = evicted_cookies_.find(key); |
| if (it != evicted_cookies_.end()) { |
| if (!it->second->is_expired(current_time)) // Reinstatement. |
| cookie_counter_delegate_->Report(*it->second, current_time); |
| delete it->second; |
| evicted_cookies_.erase(it); |
| } |
| } |
| |
| } // namespace chrome_browser_net |