diff options
author | sclittle@chromium.org <sclittle@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2013-12-17 07:21:29 +0000 |
---|---|---|
committer | sclittle@chromium.org <sclittle@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2013-12-17 07:21:29 +0000 |
commit | 628373a344d2cb58045d4873cac899348d249a60 (patch) | |
tree | 9df6d921a5b0c158da480c068da55805b42c66b1 /components/precache | |
parent | bc46215b29f90d698c49c054f535d4fba11b002d (diff) | |
download | chromium_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/DEPS | 13 | ||||
-rw-r--r-- | components/precache/content/precache_manager.cc | 163 | ||||
-rw-r--r-- | components/precache/content/precache_manager.h | 104 | ||||
-rw-r--r-- | components/precache/content/precache_manager_factory.cc | 36 | ||||
-rw-r--r-- | components/precache/content/precache_manager_factory.h | 41 | ||||
-rw-r--r-- | components/precache/content/precache_manager_unittest.cc | 358 | ||||
-rw-r--r-- | components/precache/core/precache_database.cc | 108 | ||||
-rw-r--r-- | components/precache/core/precache_database.h | 38 | ||||
-rw-r--r-- | components/precache/core/precache_database_unittest.cc | 15 | ||||
-rw-r--r-- | components/precache/core/precache_switches.cc | 4 | ||||
-rw-r--r-- | components/precache/core/precache_switches.h | 1 | ||||
-rw-r--r-- | components/precache/core/url_list_provider.h | 28 |
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_ |