diff options
author | guohui@google.com <guohui@google.com@0039d316-1c4b-4281-b951-d872f2087c98> | 2011-10-17 17:35:04 +0000 |
---|---|---|
committer | guohui@google.com <guohui@google.com@0039d316-1c4b-4281-b951-d872f2087c98> | 2011-10-17 17:35:04 +0000 |
commit | 8562034e402a7a8e778896f817d1fdcfb3d934c9 (patch) | |
tree | 7e209a3b76da5d81981363b39e2867edbf84b54c | |
parent | f5796891ec99f4977eb1761166a33a6b22f459fd (diff) | |
download | chromium_src-8562034e402a7a8e778896f817d1fdcfb3d934c9.zip chromium_src-8562034e402a7a8e778896f817d1fdcfb3d934c9.tar.gz chromium_src-8562034e402a7a8e778896f817d1fdcfb3d934c9.tar.bz2 |
The change list splits loading of cookies from DB by the domain key(eTLD+1).
BUG=52909
TEST=NONE
TBR=rdsmith@chromium.org
Review URL: http://codereview.chromium.org/8318006
git-svn-id: svn://svn.chromium.org/chrome/trunk/src@105836 0039d316-1c4b-4281-b951-d872f2087c98
-rw-r--r-- | chrome/browser/fast_shutdown_uitest.cc | 4 | ||||
-rw-r--r-- | chrome/browser/net/sqlite_persistent_cookie_store.cc | 276 | ||||
-rw-r--r-- | chrome/browser/net/sqlite_persistent_cookie_store.h | 7 | ||||
-rw-r--r-- | chrome/browser/net/sqlite_persistent_cookie_store_perftest.cc | 114 | ||||
-rw-r--r-- | chrome/browser/net/sqlite_persistent_cookie_store_unittest.cc | 114 | ||||
-rw-r--r-- | chrome/chrome_tests.gypi | 3 | ||||
-rw-r--r-- | net/base/cookie_monster.cc | 116 | ||||
-rw-r--r-- | net/base/cookie_monster.h | 71 | ||||
-rw-r--r-- | net/base/cookie_monster_perftest.cc | 1 | ||||
-rw-r--r-- | net/base/cookie_monster_store_test.cc | 59 | ||||
-rw-r--r-- | net/base/cookie_monster_store_test.h | 50 | ||||
-rw-r--r-- | net/base/cookie_monster_unittest.cc | 88 |
12 files changed, 758 insertions, 145 deletions
diff --git a/chrome/browser/fast_shutdown_uitest.cc b/chrome/browser/fast_shutdown_uitest.cc index 3bb9298..a7f1c00 100644 --- a/chrome/browser/fast_shutdown_uitest.cc +++ b/chrome/browser/fast_shutdown_uitest.cc @@ -42,11 +42,11 @@ class FastShutdown : public UITest { user_data_dir_.AppendASCII(chrome::kInitialProfile) .Append(chrome::kCookieFilename))); std::vector<net::CookieMonster::CanonicalCookie*> cookies; - ASSERT_TRUE(cookie_store->Load( + cookie_store->Load( base::Bind(&FastShutdown::LoadCookiesCallback, base::Unretained(this), MessageLoop::current(), - base::Unretained(&cookies)))); + base::Unretained(&cookies))); // Will receive a QuitTask when LoadCookiesCallback is invoked. MessageLoop::current()->Run(); *has_cookie = false; diff --git a/chrome/browser/net/sqlite_persistent_cookie_store.cc b/chrome/browser/net/sqlite_persistent_cookie_store.cc index b2adff1..632802b 100644 --- a/chrome/browser/net/sqlite_persistent_cookie_store.cc +++ b/chrome/browser/net/sqlite_persistent_cookie_store.cc @@ -5,6 +5,9 @@ #include "chrome/browser/net/sqlite_persistent_cookie_store.h" #include <list> +#include <map> +#include <set> +#include <utility> #include "base/basictypes.h" #include "base/bind.h" @@ -15,11 +18,13 @@ #include "base/memory/scoped_ptr.h" #include "base/metrics/histogram.h" #include "base/string_util.h" +#include "base/synchronization/lock.h" #include "base/threading/thread.h" #include "base/threading/thread_restrictions.h" #include "chrome/browser/diagnostics/sqlite_diagnostics.h" #include "content/browser/browser_thread.h" #include "googleurl/src/gurl.h" +#include "net/base/registry_controlled_domain.h" #include "sql/meta_table.h" #include "sql/statement.h" #include "sql/transaction.h" @@ -27,10 +32,24 @@ using base::Time; // This class is designed to be shared between any calling threads and the -// database thread. It batches operations and commits them on a timer. -// This class expects to be Load()'ed once on any thread. Loading occurs -// asynchronously on the DB thread and the caller will be notified on the IO -// thread. Subsequent to loading, mutations may be queued by any thread using +// database thread. It batches operations and commits them on a timer. +// +// SQLitePersistentCookieStore::Load is called to load all cookies. It +// delegates to Backend::Load, which posts a Backend::LoadAndNotifyOnDBThread +// task to the DB thread. This task calls Backend::ChainLoadCookies(), which +// repeatedly posts itself to the DB thread to load each eTLD+1's cookies in +// separate tasks. When this is complete, Backend::NotifyOnIOThread is posted +// to the IO thread, which notifies the caller of SQLitePersistentCookieStore:: +// Load that the load is complete. +// +// If a priority load request is invoked via SQLitePersistentCookieStore:: +// LoadCookiesForKey, it is delegated to Backend::LoadCookiesForKey, which posts +// Backend::LoadKeyAndNotifyOnDBThread to the DB thread. That routine loads just +// that single domain key (eTLD+1)'s cookies, and posts a Backend:: +// NotifyOnIOThread to the IO thread to notify the caller of +// SQLitePersistentCookieStore::LoadCookiesForKey that that load is complete. +// +// Subsequent to loading, mutations may be queued by any thread using // AddCookie, UpdateCookieAccessTime, and DeleteCookie. These are flushed to // disk on the DB thread every 30 seconds, 512 operations, or call to Flush(), // whichever occurs first. @@ -41,11 +60,16 @@ class SQLitePersistentCookieStore::Backend : path_(path), db_(NULL), num_pending_(0), - clear_local_state_on_exit_(false) { + clear_local_state_on_exit_(false), + initialized_(false) { } - // Creates or load the SQLite database. - bool Load(const LoadedCallback& loaded_callback); + // Creates or loads the SQLite database. + void Load(const LoadedCallback& loaded_callback); + + // Loads cookies for the domain key (eTLD+1). + void LoadCookiesForKey(const std::string& domain, + const LoadedCallback& loaded_callback); // Batch a cookie addition. void AddCookie(const net::CookieMonster::CanonicalCookie& cc); @@ -98,17 +122,30 @@ class SQLitePersistentCookieStore::Backend }; private: - // Creates or load the SQLite database on DB thread. + // Creates or loads the SQLite database on DB thread. void LoadAndNotifyOnDBThread(const LoadedCallback& loaded_callback); - // Notify the CookieMonster when loading complete. + + // Loads cookies for the domain key (eTLD+1) on DB thread. + void LoadKeyAndNotifyOnDBThread(const std::string& domains, + const LoadedCallback& loaded_callback); + + // Notifies the CookieMonster when loading completes for a specific domain key + // or for all domain keys. Triggers the callback and passes it all cookies + // that have been loaded from DB since last IO notification. void NotifyOnIOThread( const LoadedCallback& loaded_callback, - bool load_success, - const std::vector<net::CookieMonster::CanonicalCookie*>& cookies); + bool load_success); + // Initialize the data base. bool InitializeDatabase(); - // Load cookies to the data base, and read cookies. - bool LoadInternal(std::vector<net::CookieMonster::CanonicalCookie*>* cookies); + + // Loads cookies for the next domain key from the DB, then either reschedules + // itself or schedules the provided callback to run on the IO thread (if all + // domains are loaded). + void ChainLoadCookies(const LoadedCallback& loaded_callback); + + // Load all cookies for a set of domains/hosts + bool LoadCookiesForDomains(const std::set<std::string>& key); // Batch a cookie operation (add or delete) void BatchOperation(PendingOperation::OperationType op, @@ -127,9 +164,21 @@ class SQLitePersistentCookieStore::Backend PendingOperationsList::size_type num_pending_; // True if the persistent store should be deleted upon destruction. bool clear_local_state_on_exit_; - // Guard |pending_|, |num_pending_| and |clear_local_state_on_exit_|. + // Guard |cookies_|, |pending_|, |num_pending_| and + // |clear_local_state_on_exit_|. base::Lock lock_; + // Temporary buffer for cookies loaded from DB. Accumulates cookies to reduce + // the number of messages sent to the IO thread. Sent back in response to + // individual load requests for domain keys or when all loading completes. + std::vector<net::CookieMonster::CanonicalCookie*> cookies_; + + // Map of domain keys(eTLD+1) to domains/hosts that are to be loaded from DB. + std::map<std::string, std::set<std::string> > keys_to_load_; + + // Indicates if DB has been initialized. + bool initialized_; + DISALLOW_COPY_AND_ASSIGN(Backend); }; @@ -167,43 +216,91 @@ bool InitTable(sql::Connection* db) { // so we want those people to get it. Ignore errors, since it may exist. db->Execute("CREATE INDEX IF NOT EXISTS cookie_times ON cookies" " (creation_utc)"); + + db->Execute("CREATE INDEX IF NOT EXISTS domain ON cookies(host_key)"); + return true; } } // namespace -bool SQLitePersistentCookieStore::Backend::Load( +void SQLitePersistentCookieStore::Backend::Load( const LoadedCallback& loaded_callback) { // This function should be called only once per instance. DCHECK(!db_.get()); BrowserThread::PostTask( BrowserThread::DB, FROM_HERE, - base::Bind(&Backend::LoadAndNotifyOnDBThread, base::Unretained(this), - loaded_callback)); - return true; + base::Bind(&Backend::LoadAndNotifyOnDBThread, this, loaded_callback)); +} + +void SQLitePersistentCookieStore::Backend::LoadCookiesForKey( + const std::string& key, + const LoadedCallback& loaded_callback) { + BrowserThread::PostTask( + BrowserThread::DB, FROM_HERE, + base::Bind(&Backend::LoadKeyAndNotifyOnDBThread, this, + key, + loaded_callback)); } void SQLitePersistentCookieStore::Backend::LoadAndNotifyOnDBThread( const LoadedCallback& loaded_callback) { DCHECK(BrowserThread::CurrentlyOn(BrowserThread::DB)); - std::vector<net::CookieMonster::CanonicalCookie*> cookies; - bool load_success = LoadInternal(&cookies); + if (!InitializeDatabase()) { + BrowserThread::PostTask( + BrowserThread::IO, FROM_HERE, + base::Bind(&SQLitePersistentCookieStore::Backend::NotifyOnIOThread, + this, loaded_callback, false)); + } else { + ChainLoadCookies(loaded_callback); + } +} + +void SQLitePersistentCookieStore::Backend::LoadKeyAndNotifyOnDBThread( + const std::string& key, + const LoadedCallback& loaded_callback) { + DCHECK(BrowserThread::CurrentlyOn(BrowserThread::DB)); + + bool success = false; + if (InitializeDatabase()) { + std::map<std::string, std::set<std::string> >::iterator + it = keys_to_load_.find(key); + if (it != keys_to_load_.end()) { + success = LoadCookiesForDomains(it->second); + keys_to_load_.erase(it); + } else { + success = true; + } + } - BrowserThread::PostTask(BrowserThread::IO, FROM_HERE, base::Bind( - &SQLitePersistentCookieStore::Backend::NotifyOnIOThread, - base::Unretained(this), loaded_callback, load_success, cookies)); + BrowserThread::PostTask( + BrowserThread::IO, FROM_HERE, + base::Bind(&SQLitePersistentCookieStore::Backend::NotifyOnIOThread, + this, loaded_callback, success)); } void SQLitePersistentCookieStore::Backend::NotifyOnIOThread( const LoadedCallback& loaded_callback, - bool load_success, - const std::vector<net::CookieMonster::CanonicalCookie*>& cookies) { + bool load_success) { DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO)); + + std::vector<net::CookieMonster::CanonicalCookie*> cookies; + { + base::AutoLock locked(lock_); + cookies.swap(cookies_); + } + loaded_callback.Run(cookies); } bool SQLitePersistentCookieStore::Backend::InitializeDatabase() { + DCHECK(BrowserThread::CurrentlyOn(BrowserThread::DB)); + + if (initialized_) { + return true; + } + const FilePath dir = path_.DirName(); if (!file_util::PathExists(dir) && !file_util::CreateDirectory(dir)) { return false; @@ -225,47 +322,108 @@ bool SQLitePersistentCookieStore::Backend::InitializeDatabase() { } db_->Preload(); + + // Retrieve all the domains + sql::Statement smt(db_->GetUniqueStatement( + "SELECT DISTINCT host_key FROM cookies")); + + if (!smt) { + NOTREACHED() << "select statement prep failed"; + db_.reset(); + return false; + } + + // Build a map of domain keys (always eTLD+1) to domains. + while (smt.Step()) { + std::string domain = smt.ColumnString(0); + std::string key = + net::RegistryControlledDomainService::GetDomainAndRegistry(domain); + + std::map<std::string, std::set<std::string> >::iterator it = + keys_to_load_.find(key); + if (it == keys_to_load_.end()) + it = keys_to_load_.insert(std::make_pair + (key, std::set<std::string>())).first; + it->second.insert(domain); + } + + initialized_ = true; return true; } -bool SQLitePersistentCookieStore::Backend::LoadInternal( - std::vector<net::CookieMonster::CanonicalCookie*>* cookies) { - if (!InitializeDatabase()) { - return false; +void SQLitePersistentCookieStore::Backend::ChainLoadCookies( + const LoadedCallback& loaded_callback) { + DCHECK(BrowserThread::CurrentlyOn(BrowserThread::DB)); + + bool load_success = true; + + if (keys_to_load_.size() > 0) { + // Load cookies for the first domain key. + std::map<std::string, std::set<std::string> >::iterator + it = keys_to_load_.begin(); + load_success = LoadCookiesForDomains(it->second); + keys_to_load_.erase(it); } - // Slurp all the cookies into the out-vector. - sql::Statement smt(db_->GetUniqueStatement( - "SELECT creation_utc, host_key, name, value, path, expires_utc, secure, " - "httponly, last_access_utc FROM cookies")); + // If load is successful and there are more domain keys to be loaded, + // then post a DB task to continue chain-load; + // Otherwise notify on IO thread. + if (load_success && keys_to_load_.size() > 0) { + BrowserThread::PostTask( + BrowserThread::DB, FROM_HERE, + base::Bind(&Backend::ChainLoadCookies, this, loaded_callback)); + } else { + BrowserThread::PostTask( + BrowserThread::IO, FROM_HERE, + base::Bind(&SQLitePersistentCookieStore::Backend::NotifyOnIOThread, + this, loaded_callback, load_success)); + } +} + +bool SQLitePersistentCookieStore::Backend::LoadCookiesForDomains( + const std::set<std::string>& domains) { + DCHECK(BrowserThread::CurrentlyOn(BrowserThread::DB)); + + sql::Statement smt(db_->GetCachedStatement(SQL_FROM_HERE, + "SELECT creation_utc, host_key, name, value, path, expires_utc, secure, " + "httponly, last_access_utc FROM cookies WHERE host_key = ?")); if (!smt) { NOTREACHED() << "select statement prep failed"; db_.reset(); return false; } - while (smt.Step()) { - scoped_ptr<net::CookieMonster::CanonicalCookie> cc( - new net::CookieMonster::CanonicalCookie( - // The "source" URL is not used with persisted cookies. - GURL(), // Source - smt.ColumnString(2), // name - smt.ColumnString(3), // value - smt.ColumnString(1), // domain - smt.ColumnString(4), // path - std::string(), // TODO(abarth): Persist mac_key - std::string(), // TODO(abarth): Persist mac_algorithm - Time::FromInternalValue(smt.ColumnInt64(0)), // creation_utc - Time::FromInternalValue(smt.ColumnInt64(5)), // expires_utc - Time::FromInternalValue(smt.ColumnInt64(8)), // last_access_utc - smt.ColumnInt(6) != 0, // secure - smt.ColumnInt(7) != 0, // httponly - true)); // has_expires - DLOG_IF(WARNING, - cc->CreationDate() > Time::Now()) << L"CreationDate too recent"; - cookies->push_back(cc.release()); + std::vector<net::CookieMonster::CanonicalCookie*> cookies; + std::set<std::string>::const_iterator it = domains.begin(); + for (; it != domains.end(); ++it) { + smt.BindString(0, *it); + while (smt.Step()) { + scoped_ptr<net::CookieMonster::CanonicalCookie> cc( + new net::CookieMonster::CanonicalCookie( + // The "source" URL is not used with persisted cookies. + GURL(), // Source + smt.ColumnString(2), // name + smt.ColumnString(3), // value + smt.ColumnString(1), // domain + smt.ColumnString(4), // path + std::string(), // TODO(abarth): Persist mac_key + std::string(), // TODO(abarth): Persist mac_algorithm + Time::FromInternalValue(smt.ColumnInt64(0)), // creation_utc + Time::FromInternalValue(smt.ColumnInt64(5)), // expires_utc + Time::FromInternalValue(smt.ColumnInt64(8)), // last_access_utc + smt.ColumnInt(6) != 0, // secure + smt.ColumnInt(7) != 0, // httponly + true)); // has_expires + DLOG_IF(WARNING, + cc->CreationDate() > Time::Now()) << L"CreationDate too recent"; + cookies.push_back(cc.release()); + } + smt.Reset(); + } + { + base::AutoLock locked(lock_); + cookies_.insert(cookies_.end(), cookies.begin(), cookies.end()); } - return true; } @@ -534,8 +692,14 @@ SQLitePersistentCookieStore::~SQLitePersistentCookieStore() { } } -bool SQLitePersistentCookieStore::Load(const LoadedCallback& loaded_callback) { - return backend_->Load(loaded_callback); +void SQLitePersistentCookieStore::Load(const LoadedCallback& loaded_callback) { + backend_->Load(loaded_callback); +} + +void SQLitePersistentCookieStore::LoadCookiesForKey( + const std::string& key, + const LoadedCallback& loaded_callback) { + backend_->LoadCookiesForKey(key, loaded_callback); } void SQLitePersistentCookieStore::AddCookie( diff --git a/chrome/browser/net/sqlite_persistent_cookie_store.h b/chrome/browser/net/sqlite_persistent_cookie_store.h index 12a7de5..a634f4c 100644 --- a/chrome/browser/net/sqlite_persistent_cookie_store.h +++ b/chrome/browser/net/sqlite_persistent_cookie_store.h @@ -11,10 +11,12 @@ #include <string> #include <vector> +#include "base/compiler_specific.h" #include "base/memory/ref_counted.h" #include "net/base/cookie_monster.h" class FilePath; +class Task; // Implements the PersistentCookieStore interface in terms of a SQLite database. // For documentation about the actual member functions consult the documentation @@ -25,7 +27,10 @@ class SQLitePersistentCookieStore explicit SQLitePersistentCookieStore(const FilePath& path); virtual ~SQLitePersistentCookieStore(); - virtual bool Load(const LoadedCallback& loaded_callback) OVERRIDE; + virtual void Load(const LoadedCallback& loaded_callback) OVERRIDE; + + virtual void LoadCookiesForKey(const std::string& key, + const LoadedCallback& callback) OVERRIDE; virtual void AddCookie( const net::CookieMonster::CanonicalCookie& cc) OVERRIDE; diff --git a/chrome/browser/net/sqlite_persistent_cookie_store_perftest.cc b/chrome/browser/net/sqlite_persistent_cookie_store_perftest.cc new file mode 100644 index 0000000..03de457 --- /dev/null +++ b/chrome/browser/net/sqlite_persistent_cookie_store_perftest.cc @@ -0,0 +1,114 @@ +// Copyright (c) 2011 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 "base/bind.h" +#include "base/message_loop.h" +#include "base/perftimer.h" +#include "base/scoped_temp_dir.h" +#include "base/stringprintf.h" +#include "base/synchronization/waitable_event.h" +#include "base/test/thread_test_helper.h" +#include "chrome/browser/net/sqlite_persistent_cookie_store.h" +#include "chrome/common/chrome_constants.h" +#include "content/browser/browser_thread.h" +#include "googleurl/src/gurl.h" +#include "testing/gtest/include/gtest/gtest.h" + +class SQLitePersistentCookieStorePerfTest : public testing::Test { + public: + SQLitePersistentCookieStorePerfTest() + : db_thread_(BrowserThread::DB), + io_thread_(BrowserThread::IO), + loaded_event_(false, false), + key_loaded_event_(false, false) { + } + + void OnLoaded( + const std::vector<net::CookieMonster::CanonicalCookie*>& cookies) { + cookies_ = cookies; + loaded_event_.Signal(); + } + + void OnKeyLoaded( + const std::vector<net::CookieMonster::CanonicalCookie*>& cookies) { + cookies_ = cookies; + key_loaded_event_.Signal(); + } + + void Load() { + store_->Load(base::Bind(&SQLitePersistentCookieStorePerfTest::OnLoaded, + base::Unretained(this))); + loaded_event_.Wait(); + } + + virtual void SetUp() { + db_thread_.Start(); + io_thread_.Start(); + ASSERT_TRUE(temp_dir_.CreateUniqueTempDir()); + store_ = new SQLitePersistentCookieStore( + temp_dir_.path().Append(chrome::kCookieFilename)); + std::vector<net::CookieMonster::CanonicalCookie*> cookies; + Load(); + ASSERT_EQ(0u, cookies_.size()); + // Creates 15000 cookies from 300 eTLD+1s. + base::Time t = base::Time::Now(); + for (int domain_num = 0; domain_num < 300; domain_num++) { + std::string domain_name(base::StringPrintf(".domain_%d.com", domain_num)); + GURL gurl("www" + domain_name); + for (int cookie_num = 0; cookie_num < 50; ++cookie_num) { + t += base::TimeDelta::FromInternalValue(10); + store_->AddCookie( + net::CookieMonster::CanonicalCookie(gurl, + base::StringPrintf("Cookie_%d", cookie_num), "1", + domain_name, "/", std::string(), std::string(), + t, t, t, false, false, true)); + } + } + // Replace the store effectively destroying the current one and forcing it + // to write it's data to disk. + store_ = NULL; + scoped_refptr<base::ThreadTestHelper> helper( + new base::ThreadTestHelper( + BrowserThread::GetMessageLoopProxyForThread(BrowserThread::DB))); + // Make sure we wait until the destructor has run. + ASSERT_TRUE(helper->Run()); + + store_ = new SQLitePersistentCookieStore( + temp_dir_.path().Append(chrome::kCookieFilename)); + } + + protected: + BrowserThread db_thread_; + BrowserThread io_thread_; + base::WaitableEvent loaded_event_; + base::WaitableEvent key_loaded_event_; + std::vector<net::CookieMonster::CanonicalCookie*> cookies_; + ScopedTempDir temp_dir_; + scoped_refptr<SQLitePersistentCookieStore> store_; +}; + +// Test the performance of priority load of cookies for a specfic domain key +TEST_F(SQLitePersistentCookieStorePerfTest, TestLoadForKeyPerformance) { + for (int domain_num = 0; domain_num < 3; ++domain_num) { + std::string domain_name(base::StringPrintf("domain_%d.com", domain_num)); + PerfTimeLogger timer( + ("Load cookies for the eTLD+1 " + domain_name).c_str()); + store_->LoadCookiesForKey(domain_name, + base::Bind(&SQLitePersistentCookieStorePerfTest::OnKeyLoaded, + base::Unretained(this))); + key_loaded_event_.Wait(); + timer.Done(); + + ASSERT_EQ(50U, cookies_.size()); + } +} + +// Test the performance of load +TEST_F(SQLitePersistentCookieStorePerfTest, TestLoadPerformance) { + PerfTimeLogger timer("Load all cookies"); + Load(); + timer.Done(); + + ASSERT_EQ(15000U, cookies_.size()); +} diff --git a/chrome/browser/net/sqlite_persistent_cookie_store_unittest.cc b/chrome/browser/net/sqlite_persistent_cookie_store_unittest.cc index 942ca83..55b22d7 100644 --- a/chrome/browser/net/sqlite_persistent_cookie_store_unittest.cc +++ b/chrome/browser/net/sqlite_persistent_cookie_store_unittest.cc @@ -23,23 +23,34 @@ class SQLitePersistentCookieStoreTest : public testing::Test { : ui_thread_(BrowserThread::UI), db_thread_(BrowserThread::DB), io_thread_(BrowserThread::IO), - event_(false, false) { + loaded_event_(false, false), + key_loaded_event_(false, false), + db_thread_event_(false, false) { } - protected: void OnLoaded( const std::vector<net::CookieMonster::CanonicalCookie*>& cookies) { cookies_ = cookies; - event_.Signal(); + loaded_event_.Signal(); + } + + void OnKeyLoaded( + const std::vector<net::CookieMonster::CanonicalCookie*>& cookies) { + cookies_ = cookies; + key_loaded_event_.Signal(); } - bool Load(std::vector<net::CookieMonster::CanonicalCookie*>* cookies) { - bool result = - store_->Load(base::Bind(&SQLitePersistentCookieStoreTest::OnLoaded, + void Load(std::vector<net::CookieMonster::CanonicalCookie*>* cookies) { + store_->Load(base::Bind(&SQLitePersistentCookieStoreTest::OnLoaded, base::Unretained(this))); - event_.Wait(); + loaded_event_.Wait(); *cookies = cookies_; - return result; + } + + // We have to create this method to wrap WaitableEvent::Wait, since we cannot + // bind a non-void returning method as a Closure. + void WaitOnDBEvent() { + db_thread_event_.Wait(); } virtual void SetUp() { @@ -50,7 +61,7 @@ class SQLitePersistentCookieStoreTest : public testing::Test { store_ = new SQLitePersistentCookieStore( temp_dir_.path().Append(chrome::kCookieFilename)); std::vector<net::CookieMonster::CanonicalCookie*> cookies; - ASSERT_TRUE(Load(&cookies)); + Load(&cookies); ASSERT_EQ(0u, cookies.size()); // Make sure the store gets written at least once. store_->AddCookie( @@ -62,10 +73,13 @@ class SQLitePersistentCookieStoreTest : public testing::Test { false, false, true)); } + protected: BrowserThread ui_thread_; BrowserThread db_thread_; BrowserThread io_thread_; - base::WaitableEvent event_; + base::WaitableEvent loaded_event_; + base::WaitableEvent key_loaded_event_; + base::WaitableEvent db_thread_event_; std::vector<net::CookieMonster::CanonicalCookie*> cookies_; ScopedTempDir temp_dir_; scoped_refptr<SQLitePersistentCookieStore> store_; @@ -118,7 +132,7 @@ TEST_F(SQLitePersistentCookieStoreTest, TestPersistance) { temp_dir_.path().Append(chrome::kCookieFilename)); // Reload and test for persistence - ASSERT_TRUE(Load(&cookies)); + Load(&cookies); ASSERT_EQ(1U, cookies.size()); ASSERT_STREQ("http://foo.bar", cookies[0]->Domain().c_str()); ASSERT_STREQ("A", cookies[0]->Name().c_str()); @@ -135,10 +149,86 @@ TEST_F(SQLitePersistentCookieStoreTest, TestPersistance) { temp_dir_.path().Append(chrome::kCookieFilename)); // Reload and check if the cookie has been removed. - ASSERT_TRUE(Load(&cookies)); + Load(&cookies); ASSERT_EQ(0U, cookies.size()); } +// Test that priority load of cookies for a specfic domain key could be +// completed before the entire store is loaded +TEST_F(SQLitePersistentCookieStoreTest, TestLoadCookiesForKey) { + base::Time t = base::Time::Now() + base::TimeDelta::FromInternalValue(10); + // A foo.bar cookie was already added in setup. + store_->AddCookie( + net::CookieMonster::CanonicalCookie(GURL(), "A", "B", + "www.aaa.com", "/", + std::string(), std::string(), + t, t, t, false, false, true)); + t += base::TimeDelta::FromInternalValue(10); + store_->AddCookie( + net::CookieMonster::CanonicalCookie(GURL(), "A", "B", + "travel.aaa.com", "/", + std::string(), std::string(), + t, t, t, false, false, true)); + t += base::TimeDelta::FromInternalValue(10); + store_->AddCookie( + net::CookieMonster::CanonicalCookie(GURL(), "A", "B", + "www.bbb.com", "/", + std::string(), std::string(), + t, t, t, false, false, true)); + store_ = NULL; + + scoped_refptr<base::ThreadTestHelper> helper( + new base::ThreadTestHelper( + BrowserThread::GetMessageLoopProxyForThread(BrowserThread::DB))); + // Make sure we wait until the destructor has run. + ASSERT_TRUE(helper->Run()); + + store_ = new SQLitePersistentCookieStore( + temp_dir_.path().Append(chrome::kCookieFilename)); + // Posting a blocking task to db_thread_ makes sure that the DB thread waits + // until both Load and LoadCookiesForKey have been posted to its task queue. + db_thread_.PostTask(BrowserThread::DB, FROM_HERE, + base::Bind(&SQLitePersistentCookieStoreTest::WaitOnDBEvent, + base::Unretained(this))); + store_->Load(base::Bind(&SQLitePersistentCookieStoreTest::OnLoaded, + base::Unretained(this))); + store_->LoadCookiesForKey("aaa.com", + base::Bind(&SQLitePersistentCookieStoreTest::OnKeyLoaded, + base::Unretained(this))); + db_thread_.PostTask(BrowserThread::DB, FROM_HERE, + base::Bind(&SQLitePersistentCookieStoreTest::WaitOnDBEvent, + base::Unretained(this))); + + // Now the DB-thread queue contains: + // (active:) + // 1. Wait (on db_event) + // (pending:) + // 2. "Init And Chain-Load First Domain" + // 3. Priority Load (aaa.com) + // 4. Wait (on db_event) + db_thread_event_.Signal(); + key_loaded_event_.Wait(); + ASSERT_EQ(loaded_event_.IsSignaled(), false); + std::set<std::string> cookies_loaded; + for (std::vector<net::CookieMonster::CanonicalCookie*>::iterator + it = cookies_.begin(); it != cookies_.end(); ++it) + cookies_loaded.insert((*it)->Domain().c_str()); + ASSERT_GT(4U, cookies_loaded.size()); + ASSERT_EQ(cookies_loaded.find("www.aaa.com") != cookies_loaded.end(), true); + ASSERT_EQ(cookies_loaded.find("travel.aaa.com") != cookies_loaded.end(), + true); + + db_thread_event_.Signal(); + loaded_event_.Wait(); + for (std::vector<net::CookieMonster::CanonicalCookie*>::iterator + it = cookies_.begin(); it != cookies_.end(); ++it) + cookies_loaded.insert((*it)->Domain().c_str()); + ASSERT_EQ(4U, cookies_loaded.size()); + ASSERT_EQ(cookies_loaded.find("http://foo.bar") != cookies_loaded.end(), + true); + ASSERT_EQ(cookies_loaded.find("www.bbb.com") != cookies_loaded.end(), true); +} + // Test that we can force the database to be written by calling Flush(). TEST_F(SQLitePersistentCookieStoreTest, TestFlush) { // File timestamps don't work well on all platforms, so we'll determine diff --git a/chrome/chrome_tests.gypi b/chrome/chrome_tests.gypi index 1267d91..5f1abd1 100644 --- a/chrome/chrome_tests.gypi +++ b/chrome/chrome_tests.gypi @@ -3683,7 +3683,8 @@ '../webkit/support/webkit_support.gyp:glue', ], 'sources': [ - 'browser/safe_browsing/filter_false_positive_perftest.cc', + 'browser/net/sqlite_origin_bound_cert_store_unittest.cc', + 'browser/safe_browsing/filter_false_positive_perftest.cc', 'browser/visitedlink/visitedlink_perftest.cc', 'common/json_value_serializer_perftest.cc', 'test/perf/perftests.cc', diff --git a/net/base/cookie_monster.cc b/net/base/cookie_monster.cc index 6afe739..3ee2d40 100644 --- a/net/base/cookie_monster.cc +++ b/net/base/cookie_monster.cc @@ -49,6 +49,7 @@ #include "base/basictypes.h" #include "base/bind.h" +#include "base/callback.h" #include "base/format_macros.h" #include "base/logging.h" #include "base/memory/scoped_ptr.h" @@ -67,6 +68,26 @@ using base::Time; using base::TimeDelta; using base::TimeTicks; +// In steady state, most cookie requests can be satisfied by the in memory +// cookie monster store. However, if a request comes in during the initial +// cookie load, it must be delayed until that load completes. That is done by +// queueing it on CookieMonster::queue_ and running it when notification of +// cookie load completion is received via CookieMonster::OnLoaded. This callback +// is passed to the persistent store from CookieMonster::InitStore(), which is +// called on the first operation invoked on the CookieMonster. +// +// On the browser critical paths (e.g. for loading initial web pages in a +// session restore) it may take too long to wait for the full load. If a cookie +// request is for a specific URL, DoCookieTaskForURL is called, which triggers a +// priority load if the key is not loaded yet by calling PersistentCookieStore +// :: LoadCookiesForKey. The request is queued in CookieMonster::tasks_queued +// and executed upon receiving notification of key load completion via +// CookieMonster::OnKeyLoaded(). If multiple requests for the same eTLD+1 are +// received before key load completion, only the first request calls +// PersistentCookieStore::LoadCookiesForKey, all subsequent requests are queued +// in CookieMonster::tasks_queued and executed upon receiving notification of +// key load completion triggered by the first request for the same eTLD+1. + static const int kMinutesInTenYears = 10 * 365 * 24 * 60; namespace net { @@ -993,7 +1014,7 @@ void CookieMonster::SetCookieWithDetailsAsync( expiration_time, secure, http_only, callback); - DoCookieTask(task); + DoCookieTaskForURL(task, url); } void CookieMonster::GetAllCookiesAsync(const GetCookieListCallback& callback) { @@ -1011,7 +1032,7 @@ void CookieMonster::GetAllCookiesForURLWithOptionsAsync( scoped_refptr<GetAllCookiesForURLWithOptionsTask> task = new GetAllCookiesForURLWithOptionsTask(this, url, options, callback); - DoCookieTask(task); + DoCookieTaskForURL(task, url); } void CookieMonster::GetAllCookiesForURLAsync( @@ -1021,7 +1042,7 @@ void CookieMonster::GetAllCookiesForURLAsync( scoped_refptr<GetAllCookiesForURLWithOptionsTask> task = new GetAllCookiesForURLWithOptionsTask(this, url, options, callback); - DoCookieTask(task); + DoCookieTaskForURL(task, url); } void CookieMonster::DeleteAllAsync(const DeleteCallback& callback) { @@ -1046,7 +1067,7 @@ void CookieMonster::DeleteAllForHostAsync( scoped_refptr<DeleteAllForHostTask> task = new DeleteAllForHostTask(this, url, callback); - DoCookieTask(task); + DoCookieTaskForURL(task, url); } void CookieMonster::DeleteCanonicalCookieAsync( @@ -1066,7 +1087,7 @@ void CookieMonster::SetCookieWithOptionsAsync( scoped_refptr<SetCookieWithOptionsTask> task = new SetCookieWithOptionsTask(this, url, cookie_line, options, callback); - DoCookieTask(task); + DoCookieTaskForURL(task, url); } void CookieMonster::GetCookiesWithOptionsAsync( @@ -1076,7 +1097,7 @@ void CookieMonster::GetCookiesWithOptionsAsync( scoped_refptr<GetCookiesWithOptionsTask> task = new GetCookiesWithOptionsTask(this, url, options, callback); - DoCookieTask(task); + DoCookieTaskForURL(task, url); } void CookieMonster::GetCookiesWithInfoAsync( @@ -1086,7 +1107,7 @@ void CookieMonster::GetCookiesWithInfoAsync( scoped_refptr<GetCookiesWithInfoTask> task = new GetCookiesWithInfoTask(this, url, options, callback); - DoCookieTask(task); + DoCookieTaskForURL(task, url); } void CookieMonster::DeleteCookieAsync(const GURL& url, @@ -1095,15 +1116,14 @@ void CookieMonster::DeleteCookieAsync(const GURL& url, scoped_refptr<DeleteCookieTask> task = new DeleteCookieTask(this, url, cookie_name, callback); - DoCookieTask(task); + DoCookieTaskForURL(task, url); } void CookieMonster::DoCookieTask( const scoped_refptr<CookieMonsterTask>& task_item) { - InitIfNecessary(); - { base::AutoLock autolock(lock_); + InitIfNecessary(); if (!loaded_) { queue_.push(task_item); return; @@ -1113,6 +1133,34 @@ void CookieMonster::DoCookieTask( task_item->Run(); } +void CookieMonster::DoCookieTaskForURL( + const scoped_refptr<CookieMonsterTask>& task_item, + const GURL& url) { + { + base::AutoLock autolock(lock_); + InitIfNecessary(); + // If cookies for the requested domain key (eTLD+1) have been loaded from DB + // then run the task, otherwise load from DB. + if (!loaded_) { + // Checks if the domain key has been loaded. + std::string key(GetEffectiveDomain(url.scheme(), url.host())); + if (keys_loaded_.find(key) == keys_loaded_.end()) { + std::map<std::string, std::deque<scoped_refptr<CookieMonsterTask> > > + ::iterator it = tasks_queued_.find(key); + if (it == tasks_queued_.end()) { + store_->LoadCookiesForKey(key, + base::Bind(&CookieMonster::OnKeyLoaded, this, key)); + it = tasks_queued_.insert(std::make_pair(key, + std::deque<scoped_refptr<CookieMonsterTask> >())).first; + } + it->second.push_back(task_item); + return; + } + } + } + task_item->Run(); +} + bool CookieMonster::SetCookieWithDetails( const GURL& url, const std::string& name, const std::string& value, const std::string& domain, const std::string& path, @@ -1470,6 +1518,30 @@ void CookieMonster::OnLoaded(TimeTicks beginning_time, InvokeQueue(); } +void CookieMonster::OnKeyLoaded(const std::string& key, + const std::vector<CanonicalCookie*>& cookies) { + // This function does its own separate locking. + StoreLoadedCookies(cookies); + + std::deque<scoped_refptr<CookieMonsterTask> > tasks_queued; + { + base::AutoLock autolock(lock_); + keys_loaded_.insert(key); + std::map<std::string, std::deque<scoped_refptr<CookieMonsterTask> > > + ::iterator it = tasks_queued_.find(key); + if (it == tasks_queued_.end()) + return; + it->second.swap(tasks_queued); + tasks_queued_.erase(it); + } + + while (!tasks_queued.empty()) { + scoped_refptr<CookieMonsterTask> task = tasks_queued.front(); + task->Run(); + tasks_queued.pop_front(); + } +} + void CookieMonster::StoreLoadedCookies( const std::vector<CanonicalCookie*>& cookies) { // Initialize the store and sync in any saved persistent cookies. We don't @@ -1477,24 +1549,16 @@ void CookieMonster::StoreLoadedCookies( // and sync'd. base::AutoLock autolock(lock_); - // Avoid ever letting cookies with duplicate creation times into the store; - // that way we don't have to worry about what sections of code are safe - // to call while it's in that state. - std::set<int64> creation_times; - - // Presumably later than any access time in the store. - Time earliest_access_time; - for (std::vector<CanonicalCookie*>::const_iterator it = cookies.begin(); it != cookies.end(); ++it) { int64 cookie_creation_time = (*it)->CreationDate().ToInternalValue(); - if (creation_times.insert(cookie_creation_time).second) { + if (creation_times_.insert(cookie_creation_time).second) { InternalInsertCookie(GetKey((*it)->Domain()), *it, false); const Time cookie_access_time((*it)->LastAccessDate()); - if (earliest_access_time.is_null() || - cookie_access_time < earliest_access_time) - earliest_access_time = cookie_access_time; + if (earliest_access_time_.is_null() || + cookie_access_time < earliest_access_time_) + earliest_access_time_ = cookie_access_time; } else { LOG(ERROR) << base::StringPrintf("Found cookies with duplicate creation " "times in backing store: " @@ -1507,12 +1571,14 @@ void CookieMonster::StoreLoadedCookies( delete (*it); } } - earliest_access_time_= earliest_access_time; // After importing cookies from the PersistentCookieStore, verify that // none of our other constraints are violated. - // // In particular, the backing store might have given us duplicate cookies. + + // This method could be called multiple times due to priority loading, thus + // cookies loaded in previous runs will be validated again, but this is OK + // since they are expected to be much fewer than total DB. EnsureCookiesMapIsValid(); } @@ -1523,6 +1589,8 @@ void CookieMonster::InvokeQueue() { base::AutoLock autolock(lock_); if (queue_.empty()) { loaded_ = true; + creation_times_.clear(); + keys_loaded_.clear(); break; } request_task = queue_.front(); diff --git a/net/base/cookie_monster.h b/net/base/cookie_monster.h index 44fbc6d..9dea152 100644 --- a/net/base/cookie_monster.h +++ b/net/base/cookie_monster.h @@ -8,13 +8,16 @@ #define NET_BASE_COOKIE_MONSTER_H_ #pragma once +#include <deque> #include <map> #include <queue> +#include <set> #include <string> #include <utility> #include <vector> #include "base/basictypes.h" +#include "base/callback.h" #include "base/gtest_prod_util.h" #include "base/memory/ref_counted.h" #include "base/memory/scoped_ptr.h" @@ -29,7 +32,7 @@ class GURL; namespace base { class Histogram; class TimeTicks; -} +} // namespace base namespace net { @@ -43,11 +46,18 @@ class CookieList; // This class IS thread-safe. Normally, it is only used on the I/O thread, but // is also accessed directly through Automation for UI testing. // -// Several methods exist in asynchronous forms. Calls may be deferred if all -// affected cookies are not yet loaded from the backing store. Otherwise, the -// callback may be invoked immediately (prior to return of the asynchronous +// All cookie tasks are handled asynchronously. Tasks may be deferred if +// all affected cookies are not yet loaded from the backing store. Otherwise, +// the callback may be invoked immediately (prior to return of the asynchronous // function). // +// A cookie task is either pending loading of the entire cookie store, or +// loading of cookies for a specfic domain key(eTLD+1). In the former case, the +// cookie task will be queued in queue_ while PersistentCookieStore chain loads +// the cookie store on DB thread. In the latter case, the cookie task will be +// queued in tasks_queued_ while PermanentCookieStore loads cookies for the +// specified domain key(eTLD+1) on DB thread. +// // Callbacks are guaranteed to be invoked on the calling thread. // // TODO(deanm) Implement CookieMonster, the cookie database. @@ -450,9 +460,19 @@ class NET_EXPORT CookieMonster : public CookieStore { // Stores cookies loaded from the backing store and invokes any deferred // calls. |beginning_time| should be the moment PersistentCookieStore::Load // was invoked and is used for reporting histogram_time_load_. + // See PersistentCookieStore::Load for details on the contents of cookies. void OnLoaded(base::TimeTicks beginning_time, const std::vector<CanonicalCookie*>& cookies); + // Stores cookies loaded from the backing store and invokes the deferred + // task(s) pending loading of cookies associated with the domain key + // (eTLD+1). Called when all cookies for the domain key(eTLD+1) have been + // loaded from DB. See PersistentCookieStore::Load for details on the contents + // of cookies. + void OnKeyLoaded( + const std::string& key, + const std::vector<CanonicalCookie*>& cookies); + // Stores the loaded cookies. void StoreLoadedCookies(const std::vector<CanonicalCookie*>& cookies); @@ -568,10 +588,15 @@ class NET_EXPORT CookieMonster : public CookieStore { // ugly and increment when we've seen the same time twice. base::Time CurrentTime(); - // Run the cookie request task if cookie loaded, otherwise added the task - // to task queue. + // Runs the task if, or defers the task until, the full cookie database is + // loaded. void DoCookieTask(const scoped_refptr<CookieMonsterTask>& task_item); + // Runs the task if, or defers the task until, the cookies for the given URL + // are loaded. + void DoCookieTaskForURL(const scoped_refptr<CookieMonsterTask>& task_item, + const GURL& url); + // Histogram variables; see CookieMonster::InitializeHistograms() in // cookie_monster.cc for details. base::Histogram* histogram_expiration_duration_minutes_; @@ -597,8 +622,17 @@ class NET_EXPORT CookieMonster : public CookieStore { // calls may be immediately processed. bool loaded_; - // Queues calls to CookieMonster until loading from the backend store is - // completed. + // List of domain keys that have been loaded from the DB. + std::set<std::string> keys_loaded_; + + // Map of domain keys to their associated task queues. These tasks are blocked + // until all cookies for the associated domain key eTLD+1 are loaded from the + // backend store. + std::map<std::string, std::deque<scoped_refptr<CookieMonsterTask> > > + tasks_queued_; + + // Queues tasks that are blocked until all cookies are loaded from the backend + // store. std::queue<scoped_refptr<CookieMonsterTask> > queue_; // Indicates whether this cookie monster uses the new effective domain @@ -620,10 +654,16 @@ class NET_EXPORT CookieMonster : public CookieStore { // This value is used to determine whether global garbage collection might // find cookies to purge. // Note: The default Time() constructor will create a value that compares - // earlier than any other time value, which is is wanted. Thus this + // earlier than any other time value, which is wanted. Thus this // value is not initialized. base::Time earliest_access_time_; + // During loading, holds the set of all loaded cookie creation times. Used to + // avoid ever letting cookies with duplicate creation times into the store; + // that way we don't have to worry about what sections of code are safe + // to call while it's in that state. + std::set<int64> creation_times_; + std::vector<std::string> cookieable_schemes_; scoped_refptr<Delegate> delegate_; @@ -919,8 +959,17 @@ class CookieMonster::PersistentCookieStore CookieMonster::CanonicalCookie*>&)> LoadedCallback; // Initializes the store and retrieves the existing cookies. This will be - // called only once at startup. - virtual bool Load(const LoadedCallback& loaded_callback) = 0; + // called only once at startup. The callback will return all the cookies + // that are not yet returned to CookieMonster by previous priority loads. + virtual void Load(const LoadedCallback& loaded_callback) = 0; + + // Does a priority load of all cookies for the domain key (eTLD+1). The + // callback will return all the cookies that are not yet returned by previous + // loads, which includes cookies for the requested domain key if they are not + // already returned, plus all cookies that are chain-loaded and not yet + // returned to CookieMonster. + virtual void LoadCookiesForKey(const std::string& key, + const LoadedCallback& loaded_callback) = 0; virtual void AddCookie(const CanonicalCookie& cc) = 0; virtual void UpdateCookieAccessTime(const CanonicalCookie& cc) = 0; diff --git a/net/base/cookie_monster_perftest.cc b/net/base/cookie_monster_perftest.cc index 98a2b5b..a425138 100644 --- a/net/base/cookie_monster_perftest.cc +++ b/net/base/cookie_monster_perftest.cc @@ -63,6 +63,7 @@ class BaseCallback { // Therefore, callbacks will actually always complete synchronously. If the // tests get more advanced we need to add other means of signaling // completion. + MessageLoop::current()->RunAllPending(); EXPECT_TRUE(has_run_); has_run_ = false; } diff --git a/net/base/cookie_monster_store_test.cc b/net/base/cookie_monster_store_test.cc index f8257c3..dc1b495 100644 --- a/net/base/cookie_monster_store_test.cc +++ b/net/base/cookie_monster_store_test.cc @@ -4,6 +4,7 @@ #include "net/base/cookie_monster_store_test.h" +#include "base/bind.h" #include "base/message_loop.h" #include "base/stringprintf.h" #include "base/time.h" @@ -11,9 +12,17 @@ #include "testing/gtest/include/gtest/gtest.h" namespace net { +LoadedCallbackTask::LoadedCallbackTask(LoadedCallback loaded_callback, + std::vector<CookieMonster::CanonicalCookie*> cookies) + : loaded_callback_(loaded_callback), + cookies_(cookies) { +} + +LoadedCallbackTask::~LoadedCallbackTask() {} MockPersistentCookieStore::MockPersistentCookieStore() - : load_return_value_(true) { + : load_return_value_(true), + loaded_(false) { } MockPersistentCookieStore::~MockPersistentCookieStore() {} @@ -25,14 +34,27 @@ void MockPersistentCookieStore::SetLoadExpectation( load_result_ = result; } -bool MockPersistentCookieStore::Load(const LoadedCallback& loaded_callback) { - bool ok = load_return_value_; +void MockPersistentCookieStore::Load(const LoadedCallback& loaded_callback) { std::vector<CookieMonster::CanonicalCookie*> out_cookies; - if (ok) { + if (load_return_value_) { out_cookies = load_result_; + loaded_ = true; + } + MessageLoop::current()->PostTask(FROM_HERE, + base::Bind(&LoadedCallbackTask::Run, + new LoadedCallbackTask(loaded_callback, out_cookies))); +} + +void MockPersistentCookieStore::LoadCookiesForKey(const std::string& key, + const LoadedCallback& loaded_callback) { + if (!loaded_) { + Load(loaded_callback); + } else { + MessageLoop::current()->PostTask(FROM_HERE, + base::Bind(&LoadedCallbackTask::Run, + new LoadedCallbackTask(loaded_callback, + std::vector<CookieMonster::CanonicalCookie*>()))); } - loaded_callback.Run(out_cookies); - return ok; } void MockPersistentCookieStore::AddCookie( @@ -113,19 +135,36 @@ CookieMonster::CanonicalCookie BuildCanonicalCookie( !cookie_expires.is_null()); } -MockSimplePersistentCookieStore::MockSimplePersistentCookieStore() {} +MockSimplePersistentCookieStore::MockSimplePersistentCookieStore() + : loaded_(false) {} MockSimplePersistentCookieStore::~MockSimplePersistentCookieStore() {} -bool MockSimplePersistentCookieStore::Load( +void MockSimplePersistentCookieStore::Load( const LoadedCallback& loaded_callback) { std::vector<CookieMonster::CanonicalCookie*> out_cookies; + for (CanonicalCookieMap::const_iterator it = cookies_.begin(); it != cookies_.end(); it++) out_cookies.push_back( new CookieMonster::CanonicalCookie(it->second)); - loaded_callback.Run(out_cookies); - return true; + + MessageLoop::current()->PostTask(FROM_HERE, + base::Bind(&LoadedCallbackTask::Run, + new LoadedCallbackTask(loaded_callback, out_cookies))); + loaded_ = true; +} + +void MockSimplePersistentCookieStore::LoadCookiesForKey(const std::string& key, + const LoadedCallback& loaded_callback) { + if (!loaded_) { + Load(loaded_callback); + } else { + MessageLoop::current()->PostTask(FROM_HERE, + base::Bind(&LoadedCallbackTask::Run, + new LoadedCallbackTask(loaded_callback, + std::vector<CookieMonster::CanonicalCookie*>()))); + } } void MockSimplePersistentCookieStore::AddCookie( diff --git a/net/base/cookie_monster_store_test.h b/net/base/cookie_monster_store_test.h index 4bae6f9..658e9e8 100644 --- a/net/base/cookie_monster_store_test.h +++ b/net/base/cookie_monster_store_test.h @@ -23,6 +23,29 @@ class Time; namespace net { +// Wrapper class for posting a loaded callback. Since the Callback class is not +// reference counted, we cannot post a callback to the message loop directly, +// instead we post a LoadedCallbackTask. +class LoadedCallbackTask + : public base::RefCountedThreadSafe<LoadedCallbackTask> { + public: + typedef CookieMonster::PersistentCookieStore::LoadedCallback LoadedCallback; + + LoadedCallbackTask(LoadedCallback loaded_callback, + std::vector<CookieMonster::CanonicalCookie*> cookies); + ~LoadedCallbackTask(); + + void Run() { + loaded_callback_.Run(cookies_); + } + + private: + LoadedCallback loaded_callback_; + std::vector<CookieMonster::CanonicalCookie*> cookies_; + + DISALLOW_COPY_AND_ASSIGN(LoadedCallbackTask); +}; // Wrapper class LoadedCallbackTask + // Describes a call to one of the 3 functions of PersistentCookieStore. struct CookieStoreCommand { enum Type { @@ -59,7 +82,10 @@ class MockPersistentCookieStore return commands_; } - virtual bool Load(const LoadedCallback& loaded_callback) OVERRIDE; + virtual void Load(const LoadedCallback& loaded_callback) OVERRIDE; + + virtual void LoadCookiesForKey(const std::string& key, + const LoadedCallback& loaded_callback) OVERRIDE; virtual void AddCookie(const CookieMonster::CanonicalCookie& cookie) OVERRIDE; @@ -80,6 +106,9 @@ class MockPersistentCookieStore // Deferred result to use when Load() is called. bool load_return_value_; std::vector<CookieMonster::CanonicalCookie*> load_result_; + // Indicates if the store has been fully loaded to avoid returning duplicate + // cookies. + bool loaded_; DISALLOW_COPY_AND_ASSIGN(MockPersistentCookieStore); }; @@ -130,26 +159,33 @@ class MockSimplePersistentCookieStore MockSimplePersistentCookieStore(); virtual ~MockSimplePersistentCookieStore(); - virtual bool Load(const LoadedCallback& loaded_callback); + virtual void Load(const LoadedCallback& loaded_callback) OVERRIDE; + + virtual void LoadCookiesForKey(const std::string& key, + const LoadedCallback& loaded_callback) OVERRIDE; virtual void AddCookie( - const CookieMonster::CanonicalCookie& cookie); + const CookieMonster::CanonicalCookie& cookie) OVERRIDE; virtual void UpdateCookieAccessTime( - const CookieMonster::CanonicalCookie& cookie); + const CookieMonster::CanonicalCookie& cookie) OVERRIDE; virtual void DeleteCookie( - const CookieMonster::CanonicalCookie& cookie); + const CookieMonster::CanonicalCookie& cookie) OVERRIDE; - virtual void Flush(Task* completion_task); + virtual void Flush(Task* completion_task) OVERRIDE; - virtual void SetClearLocalStateOnExit(bool clear_local_state); + virtual void SetClearLocalStateOnExit(bool clear_local_state) OVERRIDE; private: typedef std::map<int64, CookieMonster::CanonicalCookie> CanonicalCookieMap; CanonicalCookieMap cookies_; + + // Indicates if the store has been fully loaded to avoid return duplicate + // cookies in subsequent load requests + bool loaded_; }; // Helper function for creating a CookieMonster backed by a diff --git a/net/base/cookie_monster_unittest.cc b/net/base/cookie_monster_unittest.cc index fac72f8..7b53397 100644 --- a/net/base/cookie_monster_unittest.cc +++ b/net/base/cookie_monster_unittest.cc @@ -37,7 +37,9 @@ namespace { class NewMockPersistentCookieStore : public CookieMonster::PersistentCookieStore { public: - MOCK_METHOD1(Load, bool(const LoadedCallback& loaded_callback)); + MOCK_METHOD1(Load, void(const LoadedCallback& loaded_callback)); + MOCK_METHOD2(LoadCookiesForKey, void(const std::string& key, + const LoadedCallback& loaded_callback)); MOCK_METHOD1(AddCookie, void(const CookieMonster::CanonicalCookie& cc)); MOCK_METHOD1(UpdateCookieAccessTime, void(const CookieMonster::CanonicalCookie& cc)); @@ -1008,12 +1010,16 @@ ACTION_P3(GetAllCookiesForUrlAction, cookie_monster, url, callback) { cookie_monster->GetAllCookiesForURLAsync(url, callback->AsCallback()); } +ACTION_P(PushCallbackAction, callback_vector) { + callback_vector->push(arg1); +} } // namespace // This test suite verifies the task deferral behaviour of the CookieMonster. // Specifically, for each asynchronous method, verify that: // 1. invoking it on an uninitialized cookie store causes the store to begin -// loading its backing data. +// chain-loading its backing data or loading data for a specific domain key +// (eTLD+1). // 2. The initial invocation does not complete until the loading completes. // 3. Invocations after the loading has completed complete immediately. class DeferredCookieTaskTest : public CookieMonsterTest { @@ -1039,9 +1045,16 @@ class DeferredCookieTaskTest : public CookieMonsterTest { testing::Mock::VerifyAndClear(persistent_store_.get()); } - // Invokes the PersistentCookieStore::Load completion callback and waits + // Invokes the PersistentCookieStore::LoadCookiesForKey completion callbacks + // and PersistentCookieStore::Load completion callback and waits // until the message loop is quit. void CompleteLoadingAndWait() { + while (!loaded_for_key_callbacks_.empty()) { + loaded_for_key_callbacks_.front().Run(loaded_cookies_); + loaded_cookies_.clear(); + loaded_for_key_callbacks_.pop(); + } + loaded_callback_.Run(loaded_cookies_); RunFor(kTimeout); } @@ -1055,13 +1068,34 @@ class DeferredCookieTaskTest : public CookieMonsterTest { Begin(); } + void BeginWithForDomainKey(std::string key, + testing::Action<void(void)> action) { + EXPECT_CALL(*this, Begin()).WillOnce(action); + ExpectLoadCall(); + ExpectLoadForKeyCall(key, false); + Begin(); + } + // Declares an expectation that PersistentCookieStore::Load will be called, // saving the provided callback and sending a quit to the message loop. void ExpectLoadCall() { EXPECT_CALL(*persistent_store_, Load(testing::_)).WillOnce(testing::DoAll( testing::SaveArg<0>(&loaded_callback_), - QuitCurrentMessageLoop(), - testing::Return(true))); + QuitCurrentMessageLoop())); + } + + // Declares an expectation that PersistentCookieStore::LoadCookiesForKey + // will be called, saving the provided callback and sending a quit to the + // message loop. + void ExpectLoadForKeyCall(std::string key, bool quit_queue) { + if (quit_queue) + EXPECT_CALL(*persistent_store_, LoadCookiesForKey(key, testing::_)). + WillOnce(testing::DoAll( + PushCallbackAction(&loaded_for_key_callbacks_), + QuitCurrentMessageLoop())); + else + EXPECT_CALL(*persistent_store_, LoadCookiesForKey(key, testing::_)). + WillOnce(PushCallbackAction(&loaded_for_key_callbacks_)); } // Invokes the initial action. @@ -1073,11 +1107,17 @@ class DeferredCookieTaskTest : public CookieMonsterTest { private: // Declares that mock expectations in this test suite are strictly ordered. testing::InSequence in_sequence_; - // Holds cookies to be returned from PersistentCookieStore::Load. + // Holds cookies to be returned from PersistentCookieStore::Load or + // PersistentCookieStore::LoadCookiesForKey. std::vector<CookieMonster::CanonicalCookie*> loaded_cookies_; // Stores the callback passed from the CookieMonster to the - // PersistentCookieStore + // PersistentCookieStore::Load CookieMonster::PersistentCookieStore::LoadedCallback loaded_callback_; + // Stores the callback passed from the CookieMonster to the + // PersistentCookieStore::LoadCookiesForKey + std::queue<CookieMonster::PersistentCookieStore::LoadedCallback> + loaded_for_key_callbacks_; + // Stores the CookieMonster under test. scoped_refptr<CookieMonster> cookie_monster_; // Stores the mock PersistentCookieStore. @@ -1091,7 +1131,7 @@ TEST_F(DeferredCookieTaskTest, DeferredGetCookies) { MockGetCookiesCallback get_cookies_callback; - BeginWith(GetCookiesAction( + BeginWithForDomainKey("google.izzle", GetCookiesAction( &cookie_monster(), url_google_, &get_cookies_callback)); WaitForLoadCall(); @@ -1111,7 +1151,7 @@ TEST_F(DeferredCookieTaskTest, DeferredGetCookiesWithInfo) { MockGetCookieInfoCallback get_cookie_info_callback; - BeginWith(GetCookiesWithInfoAction( + BeginWithForDomainKey("google.izzle", GetCookiesWithInfoAction( &cookie_monster(), url_google_, &get_cookie_info_callback)); WaitForLoadCall(); @@ -1128,7 +1168,7 @@ TEST_F(DeferredCookieTaskTest, DeferredGetCookiesWithInfo) { TEST_F(DeferredCookieTaskTest, DeferredSetCookie) { MockSetCookiesCallback set_cookies_callback; - BeginWith(SetCookieAction( + BeginWithForDomainKey("google.izzle", SetCookieAction( &cookie_monster(), url_google_, "A=B", &set_cookies_callback)); WaitForLoadCall(); @@ -1145,7 +1185,7 @@ TEST_F(DeferredCookieTaskTest, DeferredSetCookie) { TEST_F(DeferredCookieTaskTest, DeferredDeleteCookie) { MockClosure delete_cookie_callback; - BeginWith(DeleteCookieAction( + BeginWithForDomainKey("google.izzle", DeleteCookieAction( &cookie_monster(), url_google_, "A", &delete_cookie_callback)); WaitForLoadCall(); @@ -1162,7 +1202,7 @@ TEST_F(DeferredCookieTaskTest, DeferredDeleteCookie) { TEST_F(DeferredCookieTaskTest, DeferredSetCookieWithDetails) { MockSetCookiesCallback set_cookies_callback; - BeginWith(SetCookieWithDetailsAction( + BeginWithForDomainKey("google.izzle", SetCookieWithDetailsAction( &cookie_monster(), url_google_foo_, "A", "B", std::string(), "/foo", base::Time(), false, false, &set_cookies_callback)); @@ -1205,7 +1245,7 @@ TEST_F(DeferredCookieTaskTest, DeferredGetAllForUrlCookies) { MockGetCookieListCallback get_cookie_list_callback; - BeginWith(GetAllCookiesForUrlAction( + BeginWithForDomainKey("google.izzle", GetAllCookiesForUrlAction( &cookie_monster(), url_google_, &get_cookie_list_callback)); WaitForLoadCall(); @@ -1227,7 +1267,7 @@ TEST_F(DeferredCookieTaskTest, DeferredGetAllForUrlWithOptionsCookies) { MockGetCookieListCallback get_cookie_list_callback; - BeginWith(GetAllCookiesForUrlWithOptionsAction( + BeginWithForDomainKey("google.izzle", GetAllCookiesForUrlWithOptionsAction( &cookie_monster(), url_google_, &get_cookie_list_callback)); WaitForLoadCall(); @@ -1278,7 +1318,7 @@ TEST_F(DeferredCookieTaskTest, DeferredDeleteAllCreatedBetweenCookies) { TEST_F(DeferredCookieTaskTest, DeferredDeleteAllForHostCookies) { MockDeleteCallback delete_callback; - BeginWith(DeleteAllForHostAction( + BeginWithForDomainKey("google.izzle", DeleteAllForHostAction( &cookie_monster(), url_google_, &delete_callback)); WaitForLoadCall(); @@ -1334,17 +1374,17 @@ TEST_F(DeferredCookieTaskTest, DeferredTaskOrder) { DeleteCookieAction( &cookie_monster(), url_google_, "A", &delete_cookie_callback))); ExpectLoadCall(); + ExpectLoadForKeyCall("google.izzle", false); Begin(); WaitForLoadCall(); EXPECT_CALL(get_cookies_callback, Invoke("X=1")).WillOnce( GetCookiesWithInfoAction( &cookie_monster(), url_google_, &get_cookie_info_callback)); - - EXPECT_CALL(set_cookies_callback, Invoke(true)); - EXPECT_CALL(delete_cookie_callback, Invoke()); EXPECT_CALL(get_cookie_info_callback, Invoke("X=1", testing::_)).WillOnce( QuitCurrentMessageLoop()); + EXPECT_CALL(set_cookies_callback, Invoke(true)); + EXPECT_CALL(delete_cookie_callback, Invoke()); CompleteLoadingAndWait(); } @@ -2970,10 +3010,16 @@ class FlushablePersistentStore : public CookieMonster::PersistentCookieStore { public: FlushablePersistentStore() : flush_count_(0) {} - bool Load(const LoadedCallback& loaded_callback) { + void Load(const LoadedCallback& loaded_callback) { std::vector<CookieMonster::CanonicalCookie*> out_cookies; - loaded_callback.Run(out_cookies); - return false; + MessageLoop::current()->PostTask(FROM_HERE, + base::Bind(&net::LoadedCallbackTask::Run, + new net::LoadedCallbackTask(loaded_callback, out_cookies))); + } + + void LoadCookiesForKey(const std::string& key, + const LoadedCallback& loaded_callback) { + Load(loaded_callback); } void AddCookie(const CookieMonster::CanonicalCookie&) {} |