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_
->IsBackendInitialized()))
96 return AllowedType::PENDING
;
98 // SyncService delegates to SyncPrefs, which must be called on the UI thread.
99 if (history_service_
&&
100 sync_service_
->GetActiveDataTypes().Has(syncer::SESSIONS
) &&
101 !sync_service_
->GetEncryptedDataTypes().Has(syncer::SESSIONS
))
102 return AllowedType::ALLOWED
;
104 return AllowedType::DISALLOWED
;
107 void PrecacheManager::StartPrecaching(
108 const PrecacheCompletionCallback
& precache_completion_callback
) {
109 DCHECK_CURRENTLY_ON(BrowserThread::UI
);
111 if (is_precaching_
) {
112 DLOG(WARNING
) << "Cannot start precaching because precaching is already "
116 precache_completion_callback_
= precache_completion_callback
;
118 if (IsInExperimentGroup()) {
119 is_precaching_
= true;
121 BrowserThread::PostTask(
122 BrowserThread::DB
, FROM_HERE
,
123 base::Bind(&PrecacheDatabase::DeleteExpiredPrecacheHistory
,
124 precache_database_
, base::Time::Now()));
126 // Request NumTopHosts() top hosts. Note that PrecacheFetcher is further
127 // bound by the value of PrecacheConfigurationSettings.top_sites_count, as
128 // retrieved from the server.
129 history_service_
->TopHosts(
131 base::Bind(&PrecacheManager::OnHostsReceived
, AsWeakPtr()));
132 } else if (IsInControlGroup()) {
133 // Set is_precaching_ so that the longer delay is placed between calls to
135 is_precaching_
= true;
137 // Calculate TopHosts solely for metrics purposes.
138 history_service_
->TopHosts(
140 base::Bind(&PrecacheManager::OnHostsReceivedThenDone
, AsWeakPtr()));
142 if (PrecachingAllowed() != AllowedType::PENDING
) {
143 // We are not waiting on the sync backend to be initialized. The user
144 // either is not in the field trial, or does not have sync enabled.
145 // Pretend that precaching started, so that the PrecacheServiceLauncher
146 // doesn't try to start it again.
147 is_precaching_
= true;
154 void PrecacheManager::CancelPrecaching() {
155 DCHECK_CURRENTLY_ON(BrowserThread::UI
);
157 if (!is_precaching_
) {
158 // Do nothing if precaching is not in progress.
161 is_precaching_
= false;
163 // Destroying the |precache_fetcher_| will cancel any fetch in progress.
164 precache_fetcher_
.reset();
166 // Uninitialize the callback so that any scoped_refptrs in it are released.
167 precache_completion_callback_
.Reset();
170 bool PrecacheManager::IsPrecaching() const {
171 DCHECK_CURRENTLY_ON(BrowserThread::UI
);
172 return is_precaching_
;
175 void PrecacheManager::RecordStatsForFetch(const GURL
& url
,
176 const GURL
& referrer
,
177 const base::TimeDelta
& latency
,
178 const base::Time
& fetch_time
,
181 DCHECK_CURRENTLY_ON(BrowserThread::UI
);
183 if (size
== 0 || url
.is_empty() || !url
.SchemeIsHTTPOrHTTPS()) {
184 // Ignore empty responses, empty URLs, or URLs that aren't HTTP or HTTPS.
188 if (!history_service_
)
191 history_service_
->HostRankIfAvailable(
193 base::Bind(&PrecacheManager::RecordStatsForFetchInternal
, AsWeakPtr(),
194 url
, latency
, fetch_time
, size
, was_cached
));
197 void PrecacheManager::RecordStatsForFetchInternal(
199 const base::TimeDelta
& latency
,
200 const base::Time
& fetch_time
,
204 if (is_precaching_
) {
205 // Assume that precache is responsible for all requests made while
206 // precaching is currently in progress.
207 // TODO(sclittle): Make PrecacheFetcher explicitly mark precache-motivated
208 // fetches, and use that to determine whether or not a fetch was motivated
210 BrowserThread::PostTask(
211 BrowserThread::DB
, FROM_HERE
,
212 base::Bind(&PrecacheDatabase::RecordURLPrefetch
, precache_database_
,
213 url
, latency
, fetch_time
, size
, was_cached
));
215 bool is_connection_cellular
=
216 net::NetworkChangeNotifier::IsConnectionCellular(
217 net::NetworkChangeNotifier::GetConnectionType());
219 BrowserThread::PostTask(
220 BrowserThread::DB
, FROM_HERE
,
221 base::Bind(&PrecacheDatabase::RecordURLNonPrefetch
, precache_database_
,
222 url
, latency
, fetch_time
, size
, was_cached
, host_rank
,
223 is_connection_cellular
));
227 void PrecacheManager::ClearHistory() {
228 // PrecacheDatabase::ClearHistory must run after PrecacheDatabase::Init has
229 // finished. Using PostNonNestableTask guarantees this, by definition. See
230 // base::SequencedTaskRunner for details.
231 BrowserThread::PostNonNestableTask(
232 BrowserThread::DB
, FROM_HERE
,
233 base::Bind(&PrecacheDatabase::ClearHistory
, precache_database_
));
236 void PrecacheManager::Shutdown() {
240 void PrecacheManager::OnDone() {
241 DCHECK_CURRENTLY_ON(BrowserThread::UI
);
243 precache_fetcher_
.reset();
245 precache_completion_callback_
.Run(!is_precaching_
);
246 // Uninitialize the callback so that any scoped_refptrs in it are released.
247 precache_completion_callback_
.Reset();
249 is_precaching_
= false;
252 void PrecacheManager::OnHostsReceived(
253 const history::TopHostsList
& host_counts
) {
254 DCHECK_CURRENTLY_ON(BrowserThread::UI
);
256 if (!is_precaching_
) {
257 // Don't start precaching if it was canceled while waiting for the list of
262 std::vector
<std::string
> hosts
;
263 for (const auto& host_count
: host_counts
)
264 hosts
.push_back(host_count
.first
);
267 precache_fetcher_
.reset(
268 new PrecacheFetcher(hosts
, browser_context_
->GetRequestContext(),
269 GURL(variations::GetVariationParamValue(
270 kPrecacheFieldTrialName
, kConfigURLParam
)),
271 variations::GetVariationParamValue(
272 kPrecacheFieldTrialName
, kManifestURLPrefixParam
),
274 precache_fetcher_
->Start();
277 void PrecacheManager::OnHostsReceivedThenDone(
278 const history::TopHostsList
& host_counts
) {
282 } // namespace precache