summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorguohui@google.com <guohui@google.com@0039d316-1c4b-4281-b951-d872f2087c98>2011-10-17 17:35:04 +0000
committerguohui@google.com <guohui@google.com@0039d316-1c4b-4281-b951-d872f2087c98>2011-10-17 17:35:04 +0000
commit8562034e402a7a8e778896f817d1fdcfb3d934c9 (patch)
tree7e209a3b76da5d81981363b39e2867edbf84b54c
parentf5796891ec99f4977eb1761166a33a6b22f459fd (diff)
downloadchromium_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.cc4
-rw-r--r--chrome/browser/net/sqlite_persistent_cookie_store.cc276
-rw-r--r--chrome/browser/net/sqlite_persistent_cookie_store.h7
-rw-r--r--chrome/browser/net/sqlite_persistent_cookie_store_perftest.cc114
-rw-r--r--chrome/browser/net/sqlite_persistent_cookie_store_unittest.cc114
-rw-r--r--chrome/chrome_tests.gypi3
-rw-r--r--net/base/cookie_monster.cc116
-rw-r--r--net/base/cookie_monster.h71
-rw-r--r--net/base/cookie_monster_perftest.cc1
-rw-r--r--net/base/cookie_monster_store_test.cc59
-rw-r--r--net/base/cookie_monster_store_test.h50
-rw-r--r--net/base/cookie_monster_unittest.cc88
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&) {}