// 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 #include #include "base/metrics/histogram.h" #include "base/stl_util.h" #include "base/strings/string_util.h" #include "chrome/browser/google/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 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 next_cookie_monster_delegate, scoped_ptr 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); } // 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 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 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