1 // Copyright 2013 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
5 #include "components/precache/content/precache_manager.h"
11 #include "base/bind.h"
12 #include "base/command_line.h"
13 #include "base/files/file_path.h"
14 #include "base/logging.h"
15 #include "base/metrics/field_trial.h"
16 #include "base/prefs/pref_service.h"
17 #include "base/strings/string_util.h"
18 #include "base/time/time.h"
19 #include "components/history/core/browser/history_service.h"
20 #include "components/precache/core/precache_database.h"
21 #include "components/precache/core/precache_switches.h"
22 #include "components/sync_driver/sync_service.h"
23 #include "components/variations/variations_associated_data.h"
24 #include "content/public/browser/browser_context.h"
25 #include "content/public/browser/browser_thread.h"
26 #include "net/base/network_change_notifier.h"
28 using content::BrowserThread
;
32 const char kPrecacheFieldTrialName
[] = "Precache";
33 const char kPrecacheFieldTrialEnabledGroup
[] = "Enabled";
34 const char kPrecacheFieldTrialControlGroup
[] = "Control";
35 const char kConfigURLParam
[] = "config_url";
36 const char kManifestURLPrefixParam
[] = "manifest_url_prefix";
37 const int kNumTopHosts
= 100;
47 PrecacheManager::PrecacheManager(
48 content::BrowserContext
* browser_context
,
49 const sync_driver::SyncService
* const sync_service
,
50 const history::HistoryService
* const history_service
)
51 : browser_context_(browser_context
),
52 sync_service_(sync_service
),
53 history_service_(history_service
),
54 precache_database_(new PrecacheDatabase()),
55 is_precaching_(false) {
56 base::FilePath
db_path(browser_context_
->GetPath().Append(
57 base::FilePath(FILE_PATH_LITERAL("PrecacheDatabase"))));
59 BrowserThread::PostTask(
60 BrowserThread::DB
, FROM_HERE
,
61 base::Bind(base::IgnoreResult(&PrecacheDatabase::Init
),
62 precache_database_
, db_path
));
65 PrecacheManager::~PrecacheManager() {}
67 bool PrecacheManager::IsInExperimentGroup() const {
68 // Verify IsPrecachingAllowed() before calling FieldTrialList::FindFullName().
69 // This is because field trials are only assigned when requested. This allows
70 // us to create Control and Experiment groups that are limited to users for
71 // whom PrecachingAllowed() is true, thus accentuating the impact of
73 return IsPrecachingAllowed() &&
75 base::FieldTrialList::FindFullName(kPrecacheFieldTrialName
),
76 kPrecacheFieldTrialEnabledGroup
, base::CompareCase::SENSITIVE
) ||
77 base::CommandLine::ForCurrentProcess()->HasSwitch(
78 switches::kEnablePrecache
));
81 bool PrecacheManager::IsInControlGroup() const {
82 // Verify IsPrecachingAllowed() before calling FindFullName(). See
83 // PrecacheManager::IsInExperimentGroup() for an explanation of why.
84 return IsPrecachingAllowed() &&
86 base::FieldTrialList::FindFullName(kPrecacheFieldTrialName
),
87 kPrecacheFieldTrialControlGroup
, base::CompareCase::SENSITIVE
);
90 bool PrecacheManager::IsPrecachingAllowed() const {
91 return PrecachingAllowed() == AllowedType::ALLOWED
;
94 PrecacheManager::AllowedType
PrecacheManager::PrecachingAllowed() const {
95 if (!(sync_service_
&& sync_service_
->backend_initialized()))
96 return AllowedType::PENDING
;
98 // SyncService delegates to SyncPrefs, which must be called on the UI thread.
99 if (sync_service_
->GetActiveDataTypes().Has(syncer::SESSIONS
) &&
100 !sync_service_
->GetEncryptedDataTypes().Has(syncer::SESSIONS
))
101 return AllowedType::ALLOWED
;
103 return AllowedType::DISALLOWED
;
106 void PrecacheManager::StartPrecaching(
107 const PrecacheCompletionCallback
& precache_completion_callback
) {
108 DCHECK_CURRENTLY_ON(BrowserThread::UI
);
110 if (is_precaching_
) {
111 DLOG(WARNING
) << "Cannot start precaching because precaching is already "
115 precache_completion_callback_
= precache_completion_callback
;
117 if (history_service_
&& IsInExperimentGroup()) {
118 is_precaching_
= true;
120 BrowserThread::PostTask(
121 BrowserThread::DB
, FROM_HERE
,
122 base::Bind(&PrecacheDatabase::DeleteExpiredPrecacheHistory
,
123 precache_database_
, base::Time::Now()));
125 // Request NumTopHosts() top hosts. Note that PrecacheFetcher is further
126 // bound by the value of PrecacheConfigurationSettings.top_sites_count, as
127 // retrieved from the server.
128 history_service_
->TopHosts(
130 base::Bind(&PrecacheManager::OnHostsReceived
, AsWeakPtr()));
131 } else if (history_service_
&& IsInControlGroup()) {
132 // Set is_precaching_ so that the longer delay is placed between calls to
134 is_precaching_
= true;
136 // Calculate TopHosts solely for metrics purposes.
137 history_service_
->TopHosts(
139 base::Bind(&PrecacheManager::OnHostsReceivedThenDone
, AsWeakPtr()));
141 if (PrecachingAllowed() != AllowedType::PENDING
) {
142 // We are not waiting on the sync backend to be initialized. The user
143 // either is not in the field trial, or does not have sync enabled.
144 // Pretend that precaching started, so that the PrecacheServiceLauncher
145 // doesn't try to start it again.
146 is_precaching_
= true;
153 void PrecacheManager::CancelPrecaching() {
154 DCHECK_CURRENTLY_ON(BrowserThread::UI
);
156 if (!is_precaching_
) {
157 // Do nothing if precaching is not in progress.
160 is_precaching_
= false;
162 // Destroying the |precache_fetcher_| will cancel any fetch in progress.
163 precache_fetcher_
.reset();
165 // Uninitialize the callback so that any scoped_refptrs in it are released.
166 precache_completion_callback_
.Reset();
169 bool PrecacheManager::IsPrecaching() const {
170 DCHECK_CURRENTLY_ON(BrowserThread::UI
);
171 return is_precaching_
;
174 void PrecacheManager::RecordStatsForFetch(const GURL
& url
,
175 const GURL
& referrer
,
176 const base::TimeDelta
& latency
,
177 const base::Time
& fetch_time
,
180 DCHECK_CURRENTLY_ON(BrowserThread::UI
);
182 if (size
== 0 || url
.is_empty() || !url
.SchemeIsHTTPOrHTTPS()) {
183 // Ignore empty responses, empty URLs, or URLs that aren't HTTP or HTTPS.
187 if (!history_service_
)
190 history_service_
->HostRankIfAvailable(
192 base::Bind(&PrecacheManager::RecordStatsForFetchInternal
, AsWeakPtr(),
193 url
, latency
, fetch_time
, size
, was_cached
));
196 void PrecacheManager::RecordStatsForFetchInternal(
198 const base::TimeDelta
& latency
,
199 const base::Time
& fetch_time
,
203 if (is_precaching_
) {
204 // Assume that precache is responsible for all requests made while
205 // precaching is currently in progress.
206 // TODO(sclittle): Make PrecacheFetcher explicitly mark precache-motivated
207 // fetches, and use that to determine whether or not a fetch was motivated
209 BrowserThread::PostTask(
210 BrowserThread::DB
, FROM_HERE
,
211 base::Bind(&PrecacheDatabase::RecordURLPrefetch
, precache_database_
,
212 url
, latency
, fetch_time
, size
, was_cached
));
214 bool is_connection_cellular
=
215 net::NetworkChangeNotifier::IsConnectionCellular(
216 net::NetworkChangeNotifier::GetConnectionType());
218 BrowserThread::PostTask(
219 BrowserThread::DB
, FROM_HERE
,
220 base::Bind(&PrecacheDatabase::RecordURLNonPrefetch
, precache_database_
,
221 url
, latency
, fetch_time
, size
, was_cached
, host_rank
,
222 is_connection_cellular
));
226 void PrecacheManager::ClearHistory() {
227 // PrecacheDatabase::ClearHistory must run after PrecacheDatabase::Init has
228 // finished. Using PostNonNestableTask guarantees this, by definition. See
229 // base::SequencedTaskRunner for details.
230 BrowserThread::PostNonNestableTask(
231 BrowserThread::DB
, FROM_HERE
,
232 base::Bind(&PrecacheDatabase::ClearHistory
, precache_database_
));
235 void PrecacheManager::Shutdown() {
239 void PrecacheManager::OnDone() {
240 DCHECK_CURRENTLY_ON(BrowserThread::UI
);
242 precache_fetcher_
.reset();
244 precache_completion_callback_
.Run(!is_precaching_
);
245 // Uninitialize the callback so that any scoped_refptrs in it are released.
246 precache_completion_callback_
.Reset();
248 is_precaching_
= false;
251 void PrecacheManager::OnHostsReceived(
252 const history::TopHostsList
& host_counts
) {
253 DCHECK_CURRENTLY_ON(BrowserThread::UI
);
255 if (!is_precaching_
) {
256 // Don't start precaching if it was canceled while waiting for the list of
261 std::vector
<std::string
> hosts
;
262 for (const auto& host_count
: host_counts
)
263 hosts
.push_back(host_count
.first
);
266 precache_fetcher_
.reset(
267 new PrecacheFetcher(hosts
, browser_context_
->GetRequestContext(),
268 GURL(variations::GetVariationParamValue(
269 kPrecacheFieldTrialName
, kConfigURLParam
)),
270 variations::GetVariationParamValue(
271 kPrecacheFieldTrialName
, kManifestURLPrefixParam
),
273 precache_fetcher_
->Start();
276 void PrecacheManager::OnHostsReceivedThenDone(
277 const history::TopHostsList
& host_counts
) {
281 } // namespace precache