summaryrefslogtreecommitdiffstats
path: root/components/precache
diff options
context:
space:
mode:
authorsclittle@chromium.org <sclittle@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98>2013-12-17 07:21:29 +0000
committersclittle@chromium.org <sclittle@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98>2013-12-17 07:21:29 +0000
commit628373a344d2cb58045d4873cac899348d249a60 (patch)
tree9df6d921a5b0c158da480c068da55805b42c66b1 /components/precache
parentbc46215b29f90d698c49c054f535d4fba11b002d (diff)
downloadchromium_src-628373a344d2cb58045d4873cac899348d249a60.zip
chromium_src-628373a344d2cb58045d4873cac899348d249a60.tar.gz
chromium_src-628373a344d2cb58045d4873cac899348d249a60.tar.bz2
Entry point for interacting with the precache component.
BUG=306185, 309216 Review URL: https://codereview.chromium.org/106193005 git-svn-id: svn://svn.chromium.org/chrome/trunk/src@241180 0039d316-1c4b-4281-b951-d872f2087c98
Diffstat (limited to 'components/precache')
-rw-r--r--components/precache/content/DEPS13
-rw-r--r--components/precache/content/precache_manager.cc163
-rw-r--r--components/precache/content/precache_manager.h104
-rw-r--r--components/precache/content/precache_manager_factory.cc36
-rw-r--r--components/precache/content/precache_manager_factory.h41
-rw-r--r--components/precache/content/precache_manager_unittest.cc358
-rw-r--r--components/precache/core/precache_database.cc108
-rw-r--r--components/precache/core/precache_database.h38
-rw-r--r--components/precache/core/precache_database_unittest.cc15
-rw-r--r--components/precache/core/precache_switches.cc4
-rw-r--r--components/precache/core/precache_switches.h1
-rw-r--r--components/precache/core/url_list_provider.h28
12 files changed, 876 insertions, 33 deletions
diff --git a/components/precache/content/DEPS b/components/precache/content/DEPS
new file mode 100644
index 0000000..81ed9b8
--- /dev/null
+++ b/components/precache/content/DEPS
@@ -0,0 +1,13 @@
+include_rules = [
+ "+components/browser_context_keyed_service",
+ "+content/public/browser",
+ "+net/base",
+]
+
+specific_include_rules = {
+ '.*_[a-z]*test\.cc': [
+ "+content/public/test",
+ "+net/http",
+ "+net/url_request",
+ ],
+}
diff --git a/components/precache/content/precache_manager.cc b/components/precache/content/precache_manager.cc
new file mode 100644
index 0000000..ac017d4
--- /dev/null
+++ b/components/precache/content/precache_manager.cc
@@ -0,0 +1,163 @@
+// Copyright 2013 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "components/precache/content/precache_manager.h"
+
+#include "base/bind.h"
+#include "base/command_line.h"
+#include "base/files/file_path.h"
+#include "base/logging.h"
+#include "base/metrics/field_trial.h"
+#include "base/time/time.h"
+#include "components/precache/core/precache_database.h"
+#include "components/precache/core/precache_switches.h"
+#include "components/precache/core/url_list_provider.h"
+#include "content/public/browser/browser_context.h"
+#include "content/public/browser/browser_thread.h"
+#include "net/base/network_change_notifier.h"
+
+using content::BrowserThread;
+
+namespace {
+
+const char kPrecacheFieldTrialName[] = "Precache";
+const char kPrecacheFieldTrialEnabledGroup[] = "PrecacheEnabled";
+
+} // namespace
+
+namespace precache {
+
+PrecacheManager::PrecacheManager(content::BrowserContext* browser_context)
+ : browser_context_(browser_context),
+ precache_database_(new PrecacheDatabase()),
+ is_precaching_(false) {
+ base::FilePath db_path(browser_context_->GetPath().Append(
+ base::FilePath(FILE_PATH_LITERAL("PrecacheDatabase"))));
+
+ BrowserThread::PostTask(
+ BrowserThread::DB, FROM_HERE,
+ base::Bind(base::IgnoreResult(&PrecacheDatabase::Init),
+ precache_database_, db_path));
+}
+
+PrecacheManager::~PrecacheManager() {}
+
+// static
+bool PrecacheManager::IsPrecachingEnabled() {
+ return base::FieldTrialList::FindFullName(kPrecacheFieldTrialName) ==
+ kPrecacheFieldTrialEnabledGroup ||
+ CommandLine::ForCurrentProcess()->HasSwitch(switches::kEnablePrecache);
+}
+
+void PrecacheManager::StartPrecaching(
+ const PrecacheCompletionCallback& precache_completion_callback,
+ URLListProvider* url_list_provider) {
+ DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
+
+ if (is_precaching_) {
+ DLOG(WARNING) << "Cannot start precaching because precaching is already "
+ "in progress.";
+ return;
+ }
+ is_precaching_ = true;
+
+ BrowserThread::PostTask(
+ BrowserThread::DB, FROM_HERE,
+ base::Bind(&PrecacheDatabase::DeleteExpiredPrecacheHistory,
+ precache_database_, base::Time::Now()));
+
+ precache_completion_callback_ = precache_completion_callback;
+
+ url_list_provider->GetURLs(
+ base::Bind(&PrecacheManager::OnURLsReceived, AsWeakPtr()));
+}
+
+void PrecacheManager::CancelPrecaching() {
+ DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
+
+ if (!is_precaching_) {
+ // Do nothing if precaching is not in progress.
+ return;
+ }
+ is_precaching_ = false;
+
+ // Destroying the |precache_fetcher_| will cancel any fetch in progress.
+ precache_fetcher_.reset();
+
+ // Uninitialize the callback so that any scoped_refptrs in it are released.
+ precache_completion_callback_.Reset();
+}
+
+bool PrecacheManager::IsPrecaching() const {
+ DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
+ return is_precaching_;
+}
+
+void PrecacheManager::RecordStatsForFetch(const GURL& url,
+ const base::Time& fetch_time,
+ int64 size,
+ bool was_cached) {
+ DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
+
+ if (size == 0 || url.is_empty() || !url.SchemeIsHTTPOrHTTPS()) {
+ // Ignore empty responses, empty URLs, or URLs that aren't HTTP or HTTPS.
+ return;
+ }
+
+ if (is_precaching_) {
+ // Assume that precache is responsible for all requests made while
+ // precaching is currently in progress.
+ // TODO(sclittle): Make PrecacheFetcher explicitly mark precache-motivated
+ // fetches, and use that to determine whether or not a fetch was motivated
+ // by precaching.
+ BrowserThread::PostTask(
+ BrowserThread::DB, FROM_HERE,
+ base::Bind(&PrecacheDatabase::RecordURLPrecached, precache_database_,
+ url, fetch_time, size, was_cached));
+ } else {
+ bool is_connection_cellular =
+ net::NetworkChangeNotifier::IsConnectionCellular(
+ net::NetworkChangeNotifier::GetConnectionType());
+
+ BrowserThread::PostTask(
+ BrowserThread::DB, FROM_HERE,
+ base::Bind(&PrecacheDatabase::RecordURLFetched, precache_database_, url,
+ fetch_time, size, was_cached, is_connection_cellular));
+ }
+}
+
+void PrecacheManager::Shutdown() {
+ CancelPrecaching();
+}
+
+void PrecacheManager::OnDone() {
+ DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
+
+ // If OnDone has been called, then we should just be finishing precaching.
+ DCHECK(is_precaching_);
+ is_precaching_ = false;
+
+ precache_fetcher_.reset();
+
+ precache_completion_callback_.Run();
+ // Uninitialize the callback so that any scoped_refptrs in it are released.
+ precache_completion_callback_.Reset();
+}
+
+void PrecacheManager::OnURLsReceived(const std::list<GURL>& urls) {
+ DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
+
+ if (!is_precaching_) {
+ // Don't start precaching if it was canceled while waiting for the list of
+ // URLs.
+ return;
+ }
+
+ // Start precaching.
+ precache_fetcher_.reset(
+ new PrecacheFetcher(urls, browser_context_->GetRequestContext(), this));
+ precache_fetcher_->Start();
+}
+
+} // namespace precache
diff --git a/components/precache/content/precache_manager.h b/components/precache/content/precache_manager.h
new file mode 100644
index 0000000..8813a01
--- /dev/null
+++ b/components/precache/content/precache_manager.h
@@ -0,0 +1,104 @@
+// Copyright 2013 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef COMPONENTS_PRECACHE_CONTENT_PRECACHE_MANAGER_H_
+#define COMPONENTS_PRECACHE_CONTENT_PRECACHE_MANAGER_H_
+
+#include <list>
+
+#include "base/callback.h"
+#include "base/compiler_specific.h"
+#include "base/memory/ref_counted.h"
+#include "base/memory/scoped_ptr.h"
+#include "base/memory/weak_ptr.h"
+#include "components/browser_context_keyed_service/browser_context_keyed_service.h"
+#include "components/precache/core/precache_fetcher.h"
+#include "url/gurl.h"
+
+namespace base {
+class Time;
+}
+
+namespace content {
+class BrowserContext;
+}
+
+namespace precache {
+
+class PrecacheDatabase;
+class URLListProvider;
+
+// Class that manages all precaching-related activities. Owned by the
+// BrowserContext that it is constructed for. Use
+// PrecacheManagerFactory::GetForBrowserContext to get an instance of this
+// class. All methods must be called on the UI thread unless indicated
+// otherwise.
+// TODO(sclittle): Delete precache history when browsing history is deleted.
+// http://crbug.com/326549
+class PrecacheManager : public BrowserContextKeyedService,
+ public PrecacheFetcher::PrecacheDelegate,
+ public base::SupportsWeakPtr<PrecacheManager> {
+ public:
+ typedef base::Closure PrecacheCompletionCallback;
+
+ explicit PrecacheManager(content::BrowserContext* browser_context);
+ virtual ~PrecacheManager();
+
+ // Returns true if precaching is enabled as part of a field trial or by the
+ // command line flag. This method can be called on any thread.
+ static bool IsPrecachingEnabled();
+
+ // Starts precaching resources that the user is predicted to fetch in the
+ // future. If precaching is already currently in progress, then this method
+ // does nothing. The |precache_completion_callback| will be run when
+ // precaching finishes, but will not be run if precaching is canceled.
+ void StartPrecaching(
+ const PrecacheCompletionCallback& precache_completion_callback,
+ URLListProvider* url_list_provider);
+
+ // Cancels precaching if it is in progress.
+ void CancelPrecaching();
+
+ // Returns true if precaching is currently in progress, or false otherwise.
+ bool IsPrecaching() const;
+
+ // Update precache-related metrics in response to a URL being fetched.
+ void RecordStatsForFetch(const GURL& url,
+ const base::Time& fetch_time,
+ int64 size,
+ bool was_cached);
+
+ private:
+ // From BrowserContextKeyedService.
+ virtual void Shutdown() OVERRIDE;
+
+ // From PrecacheFetcher::PrecacheDelegate.
+ virtual void OnDone() OVERRIDE;
+
+ void OnURLsReceived(const std::list<GURL>& urls);
+
+ // The browser context that owns this PrecacheManager.
+ content::BrowserContext* browser_context_;
+
+ // The PrecacheFetcher used to precache resources. Should only be used on the
+ // UI thread.
+ scoped_ptr<PrecacheFetcher> precache_fetcher_;
+
+ // The callback that will be run if precaching finishes without being
+ // canceled.
+ PrecacheCompletionCallback precache_completion_callback_;
+
+ // The PrecacheDatabase for tracking precache metrics. Should only be used on
+ // the DB thread.
+ scoped_refptr<PrecacheDatabase> precache_database_;
+
+ // Flag indicating whether or not precaching is currently in progress.
+ bool is_precaching_;
+
+ DISALLOW_COPY_AND_ASSIGN(PrecacheManager);
+};
+
+} // namespace precache
+
+#endif // COMPONENTS_PRECACHE_CONTENT_PRECACHE_MANAGER_H_
diff --git a/components/precache/content/precache_manager_factory.cc b/components/precache/content/precache_manager_factory.cc
new file mode 100644
index 0000000..bd1e6f2
--- /dev/null
+++ b/components/precache/content/precache_manager_factory.cc
@@ -0,0 +1,36 @@
+// Copyright 2013 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "components/precache/content/precache_manager_factory.h"
+
+#include "components/browser_context_keyed_service/browser_context_dependency_manager.h"
+#include "components/precache/content/precache_manager.h"
+#include "content/public/browser/browser_context.h"
+
+namespace precache {
+
+// static
+PrecacheManager* PrecacheManagerFactory::GetForBrowserContext(
+ content::BrowserContext* browser_context) {
+ return static_cast<PrecacheManager*>(
+ GetInstance()->GetServiceForBrowserContext(browser_context, true));
+}
+
+// static
+PrecacheManagerFactory* PrecacheManagerFactory::GetInstance() {
+ return Singleton<PrecacheManagerFactory>::get();
+}
+
+PrecacheManagerFactory::PrecacheManagerFactory()
+ : BrowserContextKeyedServiceFactory(
+ "PrecacheManager", BrowserContextDependencyManager::GetInstance()) {}
+
+PrecacheManagerFactory::~PrecacheManagerFactory() {}
+
+BrowserContextKeyedService* PrecacheManagerFactory::BuildServiceInstanceFor(
+ content::BrowserContext* browser_context) const {
+ return new PrecacheManager(browser_context);
+}
+
+} // namespace precache
diff --git a/components/precache/content/precache_manager_factory.h b/components/precache/content/precache_manager_factory.h
new file mode 100644
index 0000000..d6715c7
--- /dev/null
+++ b/components/precache/content/precache_manager_factory.h
@@ -0,0 +1,41 @@
+// Copyright 2013 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef COMPONENTS_PRECACHE_CONTENT_PRECACHE_MANAGER_FACTORY_H_
+#define COMPONENTS_PRECACHE_CONTENT_PRECACHE_MANAGER_FACTORY_H_
+
+#include "base/basictypes.h"
+#include "base/memory/singleton.h"
+#include "components/browser_context_keyed_service/browser_context_keyed_service_factory.h"
+
+namespace content {
+class BrowserContext;
+}
+
+namespace precache {
+
+class PrecacheManager;
+
+class PrecacheManagerFactory : public BrowserContextKeyedServiceFactory {
+ public:
+ static PrecacheManager* GetForBrowserContext(
+ content::BrowserContext* browser_context);
+ static PrecacheManagerFactory* GetInstance();
+
+ private:
+ friend struct DefaultSingletonTraits<PrecacheManagerFactory>;
+
+ PrecacheManagerFactory();
+ virtual ~PrecacheManagerFactory();
+
+ // BrowserContextKeyedServiceFactory:
+ virtual BrowserContextKeyedService* BuildServiceInstanceFor(
+ content::BrowserContext* browser_context) const OVERRIDE;
+
+ DISALLOW_COPY_AND_ASSIGN(PrecacheManagerFactory);
+};
+
+} // namespace precache
+
+#endif // COMPONENTS_PRECACHE_CONTENT_PRECACHE_MANAGER_FACTORY_H_
diff --git a/components/precache/content/precache_manager_unittest.cc b/components/precache/content/precache_manager_unittest.cc
new file mode 100644
index 0000000..bd11130
--- /dev/null
+++ b/components/precache/content/precache_manager_unittest.cc
@@ -0,0 +1,358 @@
+// Copyright 2013 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "components/precache/content/precache_manager.h"
+
+#include <list>
+#include <map>
+#include <set>
+#include <string>
+
+#include "base/basictypes.h"
+#include "base/bind.h"
+#include "base/callback.h"
+#include "base/command_line.h"
+#include "base/compiler_specific.h"
+#include "base/message_loop/message_loop.h"
+#include "base/metrics/histogram.h"
+#include "base/metrics/histogram_samples.h"
+#include "base/metrics/statistics_recorder.h"
+#include "components/precache/core/precache_switches.h"
+#include "components/precache/core/url_list_provider.h"
+#include "content/public/test/test_browser_context.h"
+#include "content/public/test/test_browser_thread_bundle.h"
+#include "net/http/http_status_code.h"
+#include "net/url_request/test_url_fetcher_factory.h"
+#include "net/url_request/url_request_status.h"
+#include "net/url_request/url_request_test_util.h"
+#include "testing/gtest/include/gtest/gtest.h"
+#include "url/gurl.h"
+
+namespace precache {
+
+namespace {
+
+// A map of histogram names to the total sample counts.
+typedef std::map<std::string, base::HistogramBase::Count> HistogramCountMap;
+
+const char kConfigURL[] = "http://config-url.com";
+const char kManifestURLPrefix[] = "http://manifest-url-prefix.com/";
+
+base::HistogramBase::Count GetHistogramTotalCount(const char* histogram_name) {
+ base::HistogramBase* histogram =
+ base::StatisticsRecorder::FindHistogram(histogram_name);
+ return histogram ? histogram->SnapshotSamples()->TotalCount() : 0;
+}
+
+HistogramCountMap GetHistogramCountMap() {
+ // Note that the PrecacheManager tests don't care about the ".Cellular"
+ // histograms.
+ const char* kHistogramNames[] = {"Precache.DownloadedPrecacheMotivated",
+ "Precache.DownloadedNonPrecache",
+ "Precache.Saved"};
+
+ HistogramCountMap histogram_count_map;
+ for (size_t i = 0; i < arraysize(kHistogramNames); ++i) {
+ histogram_count_map[kHistogramNames[i]] =
+ GetHistogramTotalCount(kHistogramNames[i]);
+ }
+ return histogram_count_map;
+}
+
+class TestURLFetcherCallback {
+ public:
+ scoped_ptr<net::FakeURLFetcher> CreateURLFetcher(
+ const GURL& url, net::URLFetcherDelegate* delegate,
+ const std::string& response_data, net::HttpStatusCode response_code,
+ net::URLRequestStatus::Status status) {
+ scoped_ptr<net::FakeURLFetcher> fetcher(new net::FakeURLFetcher(
+ url, delegate, response_data, response_code, status));
+
+ requested_urls_.insert(url);
+ return fetcher.Pass();
+ }
+
+ const std::multiset<GURL>& requested_urls() const {
+ return requested_urls_;
+ }
+
+ private:
+ // Multiset with one entry for each URL requested.
+ std::multiset<GURL> requested_urls_;
+};
+
+class FakeURLListProvider : public URLListProvider {
+ public:
+ FakeURLListProvider(const std::list<GURL>& urls, bool run_immediately)
+ : urls_(urls),
+ run_immediately_(run_immediately),
+ was_get_urls_called_(false) {}
+
+ virtual void GetURLs(const GetURLsCallback& callback) OVERRIDE {
+ was_get_urls_called_ = true;
+
+ if (run_immediately_) {
+ callback.Run(urls_);
+ } else {
+ // Post the callback to be run later in the message loop.
+ base::MessageLoop::current()->PostTask(FROM_HERE,
+ base::Bind(callback, urls_));
+ }
+ }
+
+ bool was_get_urls_called() const {
+ return was_get_urls_called_;
+ }
+
+ private:
+ const std::list<GURL> urls_;
+ const bool run_immediately_;
+ bool was_get_urls_called_;
+};
+
+class TestPrecacheCompletionCallback {
+ public:
+ TestPrecacheCompletionCallback() : was_on_done_called_(false) {}
+
+ void OnDone() {
+ was_on_done_called_ = true;
+ }
+
+ PrecacheManager::PrecacheCompletionCallback GetCallback() {
+ return base::Bind(&TestPrecacheCompletionCallback::OnDone,
+ base::Unretained(this));
+ }
+
+ bool was_on_done_called() const {
+ return was_on_done_called_;
+ }
+
+ private:
+ bool was_on_done_called_;
+};
+
+class PrecacheManagerTest : public testing::Test {
+ public:
+ PrecacheManagerTest()
+ : precache_manager_(&browser_context_),
+ factory_(NULL, base::Bind(&TestURLFetcherCallback::CreateURLFetcher,
+ base::Unretained(&url_callback_))) {}
+
+ protected:
+ virtual void SetUp() OVERRIDE {
+ base::StatisticsRecorder::Initialize();
+
+ CommandLine::ForCurrentProcess()->AppendSwitchASCII(
+ switches::kPrecacheConfigSettingsURL, kConfigURL);
+ CommandLine::ForCurrentProcess()->AppendSwitchASCII(
+ switches::kPrecacheManifestURLPrefix, kManifestURLPrefix);
+
+ // Make the fetch of the precache configuration settings fail. Precaching
+ // should still complete normally in this case.
+ factory_.SetFakeResponse(GURL(kConfigURL), "",
+ net::HTTP_INTERNAL_SERVER_ERROR,
+ net::URLRequestStatus::FAILED);
+ }
+
+ content::TestBrowserThreadBundle test_browser_thread_bundle_;
+ content::TestBrowserContext browser_context_;
+ PrecacheManager precache_manager_;
+ TestURLFetcherCallback url_callback_;
+ net::FakeURLFetcherFactory factory_;
+ TestPrecacheCompletionCallback precache_callback_;
+};
+
+TEST_F(PrecacheManagerTest, StartAndFinishPrecaching) {
+ EXPECT_FALSE(precache_manager_.IsPrecaching());
+
+ FakeURLListProvider url_list_provider(
+ std::list<GURL>(1, GURL("http://starting-url.com")), false);
+ precache_manager_.StartPrecaching(precache_callback_.GetCallback(),
+ &url_list_provider);
+
+ EXPECT_TRUE(precache_manager_.IsPrecaching());
+
+ base::MessageLoop::current()->RunUntilIdle();
+ EXPECT_FALSE(precache_manager_.IsPrecaching());
+ EXPECT_TRUE(url_list_provider.was_get_urls_called());
+ EXPECT_TRUE(precache_callback_.was_on_done_called());
+
+ std::multiset<GURL> expected_requested_urls;
+ expected_requested_urls.insert(GURL(kConfigURL));
+ EXPECT_EQ(expected_requested_urls, url_callback_.requested_urls());
+}
+
+TEST_F(PrecacheManagerTest, StartAndCancelPrecachingBeforeURLsReceived) {
+ EXPECT_FALSE(precache_manager_.IsPrecaching());
+
+ FakeURLListProvider url_list_provider(
+ std::list<GURL>(1, GURL("http://starting-url.com")), false);
+
+ precache_manager_.StartPrecaching(precache_callback_.GetCallback(),
+ &url_list_provider);
+ EXPECT_TRUE(precache_manager_.IsPrecaching());
+
+ precache_manager_.CancelPrecaching();
+ EXPECT_FALSE(precache_manager_.IsPrecaching());
+
+ base::MessageLoop::current()->RunUntilIdle();
+ EXPECT_FALSE(precache_manager_.IsPrecaching());
+ EXPECT_TRUE(url_list_provider.was_get_urls_called());
+ EXPECT_FALSE(precache_callback_.was_on_done_called());
+ EXPECT_TRUE(url_callback_.requested_urls().empty());
+}
+
+TEST_F(PrecacheManagerTest, StartAndCancelPrecachingAfterURLsReceived) {
+ EXPECT_FALSE(precache_manager_.IsPrecaching());
+
+ FakeURLListProvider url_list_provider(
+ std::list<GURL>(1, GURL("http://starting-url.com")), true);
+
+ precache_manager_.StartPrecaching(precache_callback_.GetCallback(),
+ &url_list_provider);
+
+ // Since the |url_list_provider| ran the callback immediately, Start() has
+ // been called on the PrecacheFetcher, and the precache config settings have
+ // been requested. The response has not yet been received though, so
+ // precaching is still in progress.
+ EXPECT_TRUE(precache_manager_.IsPrecaching());
+
+ precache_manager_.CancelPrecaching();
+ EXPECT_FALSE(precache_manager_.IsPrecaching());
+
+ base::MessageLoop::current()->RunUntilIdle();
+ EXPECT_FALSE(precache_manager_.IsPrecaching());
+ EXPECT_TRUE(url_list_provider.was_get_urls_called());
+ EXPECT_FALSE(precache_callback_.was_on_done_called());
+
+ // Even though the response for the precache config settings should not have
+ // been received, the request should still have been made.
+ std::multiset<GURL> expected_requested_urls;
+ expected_requested_urls.insert(GURL(kConfigURL));
+ EXPECT_EQ(expected_requested_urls, url_callback_.requested_urls());
+}
+
+TEST_F(PrecacheManagerTest, RecordStatsForFetchWithIrrelevantFetches) {
+ HistogramCountMap expected_histogram_count_map = GetHistogramCountMap();
+
+ // Fetches with size 0 should be ignored.
+ precache_manager_.RecordStatsForFetch(GURL("http://url.com"), base::Time(), 0,
+ false);
+ base::MessageLoop::current()->RunUntilIdle();
+ EXPECT_EQ(expected_histogram_count_map, GetHistogramCountMap());
+
+ // Fetches for URLs with schemes other than HTTP or HTTPS should be ignored.
+ precache_manager_.RecordStatsForFetch(GURL("ftp://ftp.com"), base::Time(),
+ 1000, false);
+ base::MessageLoop::current()->RunUntilIdle();
+ EXPECT_EQ(expected_histogram_count_map, GetHistogramCountMap());
+
+ // Fetches for empty URLs should be ignored.
+ precache_manager_.RecordStatsForFetch(GURL(), base::Time(), 1000, false);
+ base::MessageLoop::current()->RunUntilIdle();
+ EXPECT_EQ(expected_histogram_count_map, GetHistogramCountMap());
+}
+
+TEST_F(PrecacheManagerTest, RecordStatsForFetchDuringPrecaching) {
+ HistogramCountMap expected_histogram_count_map = GetHistogramCountMap();
+
+ FakeURLListProvider url_list_provider(std::list<GURL>(), false);
+ precache_manager_.StartPrecaching(precache_callback_.GetCallback(),
+ &url_list_provider);
+
+ EXPECT_TRUE(precache_manager_.IsPrecaching());
+ precache_manager_.RecordStatsForFetch(GURL("http://url.com"), base::Time(),
+ 1000, false);
+
+ precache_manager_.CancelPrecaching();
+
+ base::MessageLoop::current()->RunUntilIdle();
+ expected_histogram_count_map["Precache.DownloadedPrecacheMotivated"]++;
+ EXPECT_EQ(expected_histogram_count_map, GetHistogramCountMap());
+}
+
+TEST_F(PrecacheManagerTest, RecordStatsForFetchHTTP) {
+ HistogramCountMap expected_histogram_count_map = GetHistogramCountMap();
+
+ precache_manager_.RecordStatsForFetch(GURL("http://http-url.com"),
+ base::Time(), 1000, false);
+ base::MessageLoop::current()->RunUntilIdle();
+
+ expected_histogram_count_map["Precache.DownloadedNonPrecache"]++;
+ EXPECT_EQ(expected_histogram_count_map, GetHistogramCountMap());
+}
+
+TEST_F(PrecacheManagerTest, RecordStatsForFetchHTTPS) {
+ HistogramCountMap expected_histogram_count_map = GetHistogramCountMap();
+
+ precache_manager_.RecordStatsForFetch(GURL("https://https-url.com"),
+ base::Time(), 1000, false);
+ base::MessageLoop::current()->RunUntilIdle();
+
+ expected_histogram_count_map["Precache.DownloadedNonPrecache"]++;
+ EXPECT_EQ(expected_histogram_count_map, GetHistogramCountMap());
+}
+
+TEST_F(PrecacheManagerTest, DeleteExpiredPrecacheHistory) {
+ // This test has to use Time::Now() because StartPrecaching uses Time::Now().
+ const base::Time kCurrentTime = base::Time::Now();
+ HistogramCountMap expected_histogram_count_map = GetHistogramCountMap();
+
+ FakeURLListProvider url_list_provider(std::list<GURL>(), false);
+ precache_manager_.StartPrecaching(precache_callback_.GetCallback(),
+ &url_list_provider);
+ EXPECT_TRUE(precache_manager_.IsPrecaching());
+
+ // Precache a bunch of URLs, with different fetch times.
+ precache_manager_.RecordStatsForFetch(
+ GURL("http://old-fetch.com"),
+ kCurrentTime - base::TimeDelta::FromDays(61), 1000, false);
+ precache_manager_.RecordStatsForFetch(
+ GURL("http://recent-fetch.com"),
+ kCurrentTime - base::TimeDelta::FromDays(59), 1000, false);
+ precache_manager_.RecordStatsForFetch(
+ GURL("http://yesterday-fetch.com"),
+ kCurrentTime - base::TimeDelta::FromDays(1), 1000, false);
+ expected_histogram_count_map["Precache.DownloadedPrecacheMotivated"] += 3;
+
+ precache_manager_.CancelPrecaching();
+ base::MessageLoop::current()->RunUntilIdle();
+ EXPECT_EQ(expected_histogram_count_map, GetHistogramCountMap());
+
+ // The expired precache will be deleted during precaching this time.
+ precache_manager_.StartPrecaching(precache_callback_.GetCallback(),
+ &url_list_provider);
+ EXPECT_TRUE(precache_manager_.IsPrecaching());
+
+ precache_manager_.CancelPrecaching();
+ base::MessageLoop::current()->RunUntilIdle();
+ EXPECT_FALSE(precache_manager_.IsPrecaching());
+
+ // A fetch for the same URL as the expired precache was served from the cache,
+ // but it isn't reported as saved bytes because it had expired in the precache
+ // history.
+ precache_manager_.RecordStatsForFetch(
+ GURL("http://old-fetch.com"),
+ kCurrentTime, 1000, true);
+
+ base::MessageLoop::current()->RunUntilIdle();
+ EXPECT_EQ(expected_histogram_count_map, GetHistogramCountMap());
+
+ // The other precaches should not have expired, so the following fetches from
+ // the cache should count as saved bytes.
+ precache_manager_.RecordStatsForFetch(
+ GURL("http://recent-fetch.com"),
+ kCurrentTime, 1000, true);
+ precache_manager_.RecordStatsForFetch(
+ GURL("http://yesterday-fetch.com"),
+ kCurrentTime, 1000, true);
+ expected_histogram_count_map["Precache.Saved"] += 2;
+
+ base::MessageLoop::current()->RunUntilIdle();
+ EXPECT_EQ(expected_histogram_count_map, GetHistogramCountMap());
+}
+
+} // namespace
+
+} // namespace precache
diff --git a/components/precache/core/precache_database.cc b/components/precache/core/precache_database.cc
index e47afb6..80be637 100644
--- a/components/precache/core/precache_database.cc
+++ b/components/precache/core/precache_database.cc
@@ -4,10 +4,11 @@
#include "components/precache/core/precache_database.h"
+#include "base/bind.h"
#include "base/files/file_path.h"
+#include "base/message_loop/message_loop.h"
#include "base/metrics/histogram.h"
#include "base/time/time.h"
-#include "components/precache/core/precache_url_table.h"
#include "sql/connection.h"
#include "sql/transaction.h"
#include "url/gurl.h"
@@ -22,8 +23,7 @@ const int kPrecacheHistoryExpiryPeriodDays = 60;
namespace precache {
-PrecacheDatabase::PrecacheDatabase()
- : precache_url_table_(new PrecacheURLTable()) {
+PrecacheDatabase::PrecacheDatabase() : is_flush_posted_(false) {
// A PrecacheDatabase can be constructed on any thread.
thread_checker_.DetachFromThread();
}
@@ -48,7 +48,7 @@ bool PrecacheDatabase::Init(const base::FilePath& db_path) {
return false;
}
- if (!precache_url_table_->Init(db_.get())) {
+ if (!precache_url_table_.Init(db_.get())) {
// Raze and close the database connection to indicate that it's not usable,
// and so that the database will be created anew next time, in case it's
// corrupted.
@@ -66,9 +66,13 @@ void PrecacheDatabase::DeleteExpiredPrecacheHistory(
}
// Delete old precache history that has expired.
- precache_url_table_->DeleteAllPrecachedBefore(
- current_time -
- base::TimeDelta::FromDays(kPrecacheHistoryExpiryPeriodDays));
+ base::Time delete_end = current_time - base::TimeDelta::FromDays(
+ kPrecacheHistoryExpiryPeriodDays);
+ buffered_writes_.push_back(
+ base::Bind(&PrecacheURLTable::DeleteAllPrecachedBefore,
+ base::Unretained(&precache_url_table_), delete_end));
+
+ Flush();
}
void PrecacheDatabase::RecordURLPrecached(const GURL& url,
@@ -79,13 +83,13 @@ void PrecacheDatabase::RecordURLPrecached(const GURL& url,
return;
}
- sql::Transaction transaction(db_.get());
- if (!transaction.Begin()) {
- // Do nothing if unable to begin a transaction.
- return;
+ if (buffered_urls_.find(url.spec()) != buffered_urls_.end()) {
+ // If the URL for this fetch is in the write buffer, then flush the write
+ // buffer.
+ Flush();
}
- if (was_cached && !precache_url_table_->HasURL(url)) {
+ if (was_cached && !precache_url_table_.HasURL(url)) {
// Since the precache came from the cache, and there's no entry in the URL
// table for the URL, this means that the resource was already in the cache
// because of user browsing. Thus, this precache had no effect, so ignore
@@ -102,9 +106,11 @@ void PrecacheDatabase::RecordURLPrecached(const GURL& url,
// Use the URL table to keep track of URLs that are in the cache thanks to
// precaching. If a row for the URL already exists, than update the timestamp
// to |fetch_time|.
- precache_url_table_->AddURL(url, fetch_time);
-
- transaction.Commit();
+ buffered_writes_.push_back(
+ base::Bind(&PrecacheURLTable::AddURL,
+ base::Unretained(&precache_url_table_), url, fetch_time));
+ buffered_urls_.insert(url.spec());
+ MaybePostFlush();
}
void PrecacheDatabase::RecordURLFetched(const GURL& url,
@@ -116,13 +122,13 @@ void PrecacheDatabase::RecordURLFetched(const GURL& url,
return;
}
- sql::Transaction transaction(db_.get());
- if (!transaction.Begin()) {
- // Do nothing if unable to begin a transaction.
- return;
+ if (buffered_urls_.find(url.spec()) != buffered_urls_.end()) {
+ // If the URL for this fetch is in the write buffer, then flush the write
+ // buffer.
+ Flush();
}
- if (was_cached && !precache_url_table_->HasURL(url)) {
+ if (was_cached && !precache_url_table_.HasURL(url)) {
// Ignore cache hits that precache can't take credit for.
return;
}
@@ -150,9 +156,11 @@ void PrecacheDatabase::RecordURLFetched(const GURL& url,
// The current fetch would have put this resource in the cache regardless of
// whether or not it was previously precached, so delete any record of that
// URL having been precached from the URL table.
- precache_url_table_->DeleteURL(url);
-
- transaction.Commit();
+ buffered_writes_.push_back(
+ base::Bind(&PrecacheURLTable::DeleteURL,
+ base::Unretained(&precache_url_table_), url));
+ buffered_urls_.insert(url.spec());
+ MaybePostFlush();
}
bool PrecacheDatabase::IsDatabaseAccessible() const {
@@ -162,4 +170,58 @@ bool PrecacheDatabase::IsDatabaseAccessible() const {
return db_->is_open();
}
+void PrecacheDatabase::Flush() {
+ DCHECK(thread_checker_.CalledOnValidThread());
+ if (buffered_writes_.empty()) {
+ // Do nothing if there's nothing to flush.
+ DCHECK(buffered_urls_.empty());
+ return;
+ }
+
+ if (IsDatabaseAccessible()) {
+ sql::Transaction transaction(db_.get());
+ if (transaction.Begin()) {
+ for (std::vector<base::Closure>::const_iterator it =
+ buffered_writes_.begin();
+ it != buffered_writes_.end(); ++it) {
+ it->Run();
+ }
+
+ transaction.Commit();
+ }
+ }
+
+ // Clear the buffer, even if the database is inaccessible or unable to begin a
+ // transaction.
+ buffered_writes_.clear();
+ buffered_urls_.clear();
+}
+
+void PrecacheDatabase::PostedFlush() {
+ DCHECK(thread_checker_.CalledOnValidThread());
+ DCHECK(is_flush_posted_);
+ is_flush_posted_ = false;
+ Flush();
+}
+
+void PrecacheDatabase::MaybePostFlush() {
+ DCHECK(thread_checker_.CalledOnValidThread());
+
+ if (buffered_writes_.empty() || is_flush_posted_) {
+ // There's no point in posting a flush if there's nothing to be flushed or
+ // if a flush has already been posted.
+ return;
+ }
+
+ DCHECK(base::MessageLoop::current());
+ // Post a delayed task to flush the buffer in 1 second, so that multiple
+ // database writes can be buffered up and flushed together in the same
+ // transaction.
+ base::MessageLoop::current()->PostDelayedTask(
+ FROM_HERE, base::Bind(&PrecacheDatabase::PostedFlush,
+ scoped_refptr<PrecacheDatabase>(this)),
+ base::TimeDelta::FromSeconds(1));
+ is_flush_posted_ = true;
+}
+
} // namespace precache
diff --git a/components/precache/core/precache_database.h b/components/precache/core/precache_database.h
index 1351383..47fd2da 100644
--- a/components/precache/core/precache_database.h
+++ b/components/precache/core/precache_database.h
@@ -5,10 +5,16 @@
#ifndef COMPONENTS_PRECACHE_CORE_PRECACHE_DATABASE_H_
#define COMPONENTS_PRECACHE_CORE_PRECACHE_DATABASE_H_
+#include <string>
+#include <vector>
+
#include "base/basictypes.h"
+#include "base/callback.h"
+#include "base/containers/hash_tables.h"
#include "base/memory/ref_counted.h"
#include "base/memory/scoped_ptr.h"
#include "base/threading/thread_checker.h"
+#include "components/precache/core/precache_url_table.h"
class GURL;
@@ -23,8 +29,6 @@ class Connection;
namespace precache {
-class PrecacheURLTable;
-
// Class that tracks information related to precaching. This class can be
// constructed or destroyed on any threads, but all other methods must be called
// on the same thread (e.g. the DB thread).
@@ -61,11 +65,37 @@ class PrecacheDatabase : public base::RefCountedThreadSafe<PrecacheDatabase> {
bool IsDatabaseAccessible() const;
+ // Flushes any buffered write operations. |buffered_writes_| will be empty
+ // after calling this function. To maximize performance, all the buffered
+ // writes are run in a single database transaction.
+ void Flush();
+
+ // Same as Flush(), but also updates the flag |is_flush_posted_| to indicate
+ // that a flush is no longer posted.
+ void PostedFlush();
+
+ // Post a call to PostedFlush() on the current thread's MessageLoop, if
+ // |buffered_writes_| is non-empty and there isn't already a flush call
+ // posted.
+ void MaybePostFlush();
+
scoped_ptr<sql::Connection> db_;
// Table that keeps track of URLs that are in the cache because of precaching,
- // and wouldn't be in the cache otherwise.
- scoped_ptr<PrecacheURLTable> precache_url_table_;
+ // and wouldn't be in the cache otherwise. If |buffered_writes_| is non-empty,
+ // then this table will not be up to date until the next call to Flush().
+ PrecacheURLTable precache_url_table_;
+
+ // A vector of write operations to be run on the database.
+ std::vector<base::Closure> buffered_writes_;
+
+ // Set of URLs that have been modified in |buffered_writes_|. It's a hash set
+ // of strings, and not GURLs, because there is no hash function on GURL.
+ base::hash_set<std::string> buffered_urls_;
+
+ // Flag indicating whether or not a call to Flush() has been posted to run in
+ // the future.
+ bool is_flush_posted_;
// ThreadChecker used to ensure that all methods other than the constructor
// or destructor are called on the same thread.
diff --git a/components/precache/core/precache_database_unittest.cc b/components/precache/core/precache_database_unittest.cc
index 681c33d..575bd4d 100644
--- a/components/precache/core/precache_database_unittest.cc
+++ b/components/precache/core/precache_database_unittest.cc
@@ -5,16 +5,14 @@
#include "components/precache/core/precache_database.h"
#include <map>
-#include <string>
-#include "base/basictypes.h"
#include "base/files/file_path.h"
#include "base/files/scoped_temp_dir.h"
+#include "base/message_loop/message_loop.h"
#include "base/metrics/histogram.h"
#include "base/metrics/histogram_samples.h"
#include "base/metrics/statistics_recorder.h"
#include "base/time/time.h"
-#include "components/precache/core/precache_url_table.h"
#include "sql/connection.h"
#include "testing/gtest/include/gtest/gtest.h"
#include "url/gurl.h"
@@ -86,13 +84,16 @@ class PrecacheDatabaseTest : public testing::Test {
}
std::map<GURL, base::Time> GetActualURLTableMap() {
+ // Flush any buffered writes so that the URL table will be up to date.
+ precache_database_->Flush();
+
std::map<GURL, base::Time> url_table_map;
precache_url_table()->GetAllDataForTesting(&url_table_map);
return url_table_map;
}
PrecacheURLTable* precache_url_table() {
- return precache_database_->precache_url_table_.get();
+ return &precache_database_->precache_url_table_;
}
scoped_ptr<base::HistogramSamples> GetHistogramSamplesDelta(
@@ -133,10 +134,12 @@ class PrecacheDatabaseTest : public testing::Test {
void RecordFetchFromCacheCellular(const GURL& url,
const base::Time& fetch_time, int64 size);
- scoped_refptr<PrecacheDatabase> precache_database_;
+ // Having this MessageLoop member variable causes base::MessageLoop::current()
+ // to be set properly.
+ base::MessageLoopForUI loop_;
+ scoped_refptr<PrecacheDatabase> precache_database_;
base::ScopedTempDir scoped_temp_dir_;
-
scoped_ptr<base::HistogramSamples> initial_histogram_samples_
[arraysize(kHistogramNames)];
std::map<std::string, base::HistogramSamples*> initial_histogram_samples_map_;
diff --git a/components/precache/core/precache_switches.cc b/components/precache/core/precache_switches.cc
index 156546e..a0ea84a 100644
--- a/components/precache/core/precache_switches.cc
+++ b/components/precache/core/precache_switches.cc
@@ -7,6 +7,10 @@
namespace precache {
namespace switches {
+// Enables the proactive populating of the disk cache with Web resources that
+// are likely to be needed in future page fetches.
+const char kEnablePrecache[] = "enable-precache";
+
// The URL that provides the PrecacheConfigurationSettings proto.
const char kPrecacheConfigSettingsURL[] = "precache-config-settings-url";
diff --git a/components/precache/core/precache_switches.h b/components/precache/core/precache_switches.h
index c0d2aba..5ed0c43 100644
--- a/components/precache/core/precache_switches.h
+++ b/components/precache/core/precache_switches.h
@@ -10,6 +10,7 @@ namespace switches {
// All switches in alphabetical order. The switches should be documented
// alongside the definition of their values in the .cc file.
+extern const char kEnablePrecache[];
extern const char kPrecacheConfigSettingsURL[];
extern const char kPrecacheManifestURLPrefix[];
diff --git a/components/precache/core/url_list_provider.h b/components/precache/core/url_list_provider.h
new file mode 100644
index 0000000..5b879b7
--- /dev/null
+++ b/components/precache/core/url_list_provider.h
@@ -0,0 +1,28 @@
+// Copyright 2013 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef COMPONENTS_PRECACHE_CORE_URL_LIST_PROVIDER_H_
+#define COMPONENTS_PRECACHE_CORE_URL_LIST_PROVIDER_H_
+
+#include <list>
+
+#include "base/callback.h"
+#include "url/gurl.h"
+
+namespace precache {
+
+// Interface for classes that can provide a list of URLs.
+class URLListProvider {
+ public:
+ typedef base::Callback<void(const std::list<GURL>&)> GetURLsCallback;
+
+ // Runs |callback| with a list of URLs. |callback| may be run before the call
+ // to GetURLs returns. |callback| will be run on the same thread that this
+ // method was called on.
+ virtual void GetURLs(const GetURLsCallback& callback) = 0;
+};
+
+} // namespace precache
+
+#endif // COMPONENTS_PRECACHE_CORE_URL_LIST_PROVIDER_H_