1 // Copyright (c) 2012 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.
4
5 #include "chrome/browser/prerender/prerender_manager.h"
6
7 #include <algorithm>
8 #include <functional>
9 #include <string>
10 #include <vector>
11
12 #include "base/bind.h"
13 #include "base/bind_helpers.h"
14 #include "base/logging.h"
15 #include "base/memory/weak_ptr.h"
16 #include "base/metrics/histogram.h"
17 #include "base/prefs/pref_service.h"
18 #include "base/strings/utf_string_conversions.h"
19 #include "base/time/time.h"
20 #include "base/timer/elapsed_timer.h"
21 #include "base/values.h"
22 #include "chrome/browser/browser_process.h"
23 #include "chrome/browser/chrome_notification_types.h"
24 #include "chrome/browser/history/history_service_factory.h"
25 #include "chrome/browser/net/chrome_cookie_notification_details.h"
26 #include "chrome/browser/net/prediction_options.h"
27 #include "chrome/browser/predictors/predictor_database.h"
28 #include "chrome/browser/predictors/predictor_database_factory.h"
29 #include "chrome/browser/prerender/prerender_contents.h"
30 #include "chrome/browser/prerender/prerender_field_trial.h"
31 #include "chrome/browser/prerender/prerender_final_status.h"
32 #include "chrome/browser/prerender/prerender_handle.h"
33 #include "chrome/browser/prerender/prerender_histograms.h"
34 #include "chrome/browser/prerender/prerender_history.h"
35 #include "chrome/browser/prerender/prerender_local_predictor.h"
36 #include "chrome/browser/prerender/prerender_manager_factory.h"
37 #include "chrome/browser/prerender/prerender_tab_helper.h"
38 #include "chrome/browser/prerender/prerender_tracker.h"
39 #include "chrome/browser/prerender/prerender_util.h"
40 #include "chrome/browser/profiles/profile.h"
41 #include "chrome/browser/search/search.h"
42 #include "chrome/browser/tab_contents/tab_util.h"
43 #include "chrome/browser/ui/browser_navigator.h"
44 #include "chrome/browser/ui/tab_contents/core_tab_helper.h"
45 #include "chrome/browser/ui/tab_contents/core_tab_helper_delegate.h"
46 #include "chrome/common/chrome_switches.h"
47 #include "chrome/common/pref_names.h"
48 #include "chrome/common/prerender_messages.h"
49 #include "chrome/common/prerender_types.h"
50 #include "content/public/browser/browser_thread.h"
51 #include "content/public/browser/devtools_agent_host.h"
52 #include "content/public/browser/navigation_controller.h"
53 #include "content/public/browser/notification_service.h"
54 #include "content/public/browser/notification_source.h"
55 #include "content/public/browser/render_frame_host.h"
56 #include "content/public/browser/render_process_host.h"
57 #include "content/public/browser/render_view_host.h"
58 #include "content/public/browser/resource_request_details.h"
59 #include "content/public/browser/session_storage_namespace.h"
60 #include "content/public/browser/site_instance.h"
61 #include "content/public/browser/storage_partition.h"
62 #include "content/public/browser/web_contents.h"
63 #include "content/public/browser/web_contents_delegate.h"
64 #include "content/public/common/url_constants.h"
65 #include "extensions/common/constants.h"
66 #include "net/base/registry_controlled_domains/registry_controlled_domain.h"
67 #include "net/url_request/url_request_context.h"
68 #include "net/url_request/url_request_context_getter.h"
69
70 using content::BrowserThread;
71 using content::RenderViewHost;
72 using content::RenderFrameHost;
73 using content::SessionStorageNamespace;
74 using content::WebContents;
75 using predictors::LoggedInPredictorTable;
76
77 namespace prerender {
78
79 namespace {
80
81 // Time interval at which periodic cleanups are performed.
82 const int kPeriodicCleanupIntervalMs = 1000;
83
84 // Valid HTTP methods for prerendering.
85 const char* const kValidHttpMethods[] = {
86 "GET",
87 "HEAD",
88 "OPTIONS",
89 "POST",
90 "TRACE",
91 };
92
93 // Length of prerender history, for display in chrome://net-internals
94 const int kHistoryLength = 100;
95
96 // Timeout, in ms, for a session storage namespace merge.
97 const int kSessionStorageNamespaceMergeTimeoutMs = 500;
98
99 // If true, all session storage merges hang indefinitely.
100 bool g_hang_session_storage_merges_for_testing = false;
101
102 // Indicates whether a Prerender has been cancelled such that we need
103 // a dummy replacement for the purpose of recording the correct PPLT for
104 // the Match Complete case.
105 // Traditionally, "Match" means that a prerendered page was actually visited &
106 // the prerender was used. Our goal is to have "Match" cases line up in the
107 // control group & the experiment group, so that we can make meaningful
108 // comparisons of improvements. However, in the control group, since we don't
109 // actually perform prerenders, many of the cancellation reasons cannot be
110 // detected. Therefore, in the Prerender group, when we cancel for one of these
111 // reasons, we keep track of a dummy Prerender representing what we would
112 // have in the control group. If that dummy prerender in the prerender group
113 // would then be swapped in (but isn't actually b/c it's a dummy), we record
114 // this as a MatchComplete. This allows us to compare MatchComplete's
115 // across Prerender & Control group which ideally should be lining up.
116 // This ensures that there is no bias in terms of the page load times
117 // of the pages forming the difference between the two sets.
118
NeedMatchCompleteDummyForFinalStatus(FinalStatus final_status)119 bool NeedMatchCompleteDummyForFinalStatus(FinalStatus final_status) {
120 return final_status != FINAL_STATUS_USED &&
121 final_status != FINAL_STATUS_TIMED_OUT &&
122 final_status != FINAL_STATUS_MANAGER_SHUTDOWN &&
123 final_status != FINAL_STATUS_PROFILE_DESTROYED &&
124 final_status != FINAL_STATUS_APP_TERMINATING &&
125 final_status != FINAL_STATUS_WINDOW_OPENER &&
126 final_status != FINAL_STATUS_CACHE_OR_HISTORY_CLEARED &&
127 final_status != FINAL_STATUS_CANCELLED &&
128 final_status != FINAL_STATUS_DEVTOOLS_ATTACHED &&
129 final_status != FINAL_STATUS_CROSS_SITE_NAVIGATION_PENDING &&
130 final_status != FINAL_STATUS_PAGE_BEING_CAPTURED &&
131 final_status != FINAL_STATUS_NAVIGATION_UNCOMMITTED &&
132 final_status != FINAL_STATUS_NON_EMPTY_BROWSING_INSTANCE;
133 }
134
CheckIfCookiesExistForDomainResultOnUIThread(const net::CookieMonster::HasCookiesForETLDP1Callback & callback,bool cookies_exist)135 void CheckIfCookiesExistForDomainResultOnUIThread(
136 const net::CookieMonster::HasCookiesForETLDP1Callback& callback,
137 bool cookies_exist) {
138 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
139 callback.Run(cookies_exist);
140 }
141
CheckIfCookiesExistForDomainResultOnIOThread(const net::CookieMonster::HasCookiesForETLDP1Callback & callback,bool cookies_exist)142 void CheckIfCookiesExistForDomainResultOnIOThread(
143 const net::CookieMonster::HasCookiesForETLDP1Callback& callback,
144 bool cookies_exist) {
145 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
146 BrowserThread::PostTask(
147 BrowserThread::UI,
148 FROM_HERE,
149 base::Bind(&CheckIfCookiesExistForDomainResultOnUIThread,
150 callback,
151 cookies_exist));
152 }
153
CheckIfCookiesExistForDomainOnIOThread(net::URLRequestContextGetter * rq_context,const std::string & domain_key,const net::CookieMonster::HasCookiesForETLDP1Callback & callback)154 void CheckIfCookiesExistForDomainOnIOThread(
155 net::URLRequestContextGetter* rq_context,
156 const std::string& domain_key,
157 const net::CookieMonster::HasCookiesForETLDP1Callback& callback) {
158 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
159 net::CookieStore* cookie_store =
160 rq_context->GetURLRequestContext()->cookie_store();
161 cookie_store->GetCookieMonster()->HasCookiesForETLDP1Async(
162 domain_key,
163 base::Bind(&CheckIfCookiesExistForDomainResultOnIOThread, callback));
164 }
165
166 } // namespace
167
168 class PrerenderManager::OnCloseWebContentsDeleter
169 : public content::WebContentsDelegate,
170 public base::SupportsWeakPtr<
171 PrerenderManager::OnCloseWebContentsDeleter> {
172 public:
OnCloseWebContentsDeleter(PrerenderManager * manager,WebContents * tab)173 OnCloseWebContentsDeleter(PrerenderManager* manager,
174 WebContents* tab)
175 : manager_(manager),
176 tab_(tab),
177 suppressed_dialog_(false) {
178 tab_->SetDelegate(this);
179 base::MessageLoop::current()->PostDelayedTask(FROM_HERE,
180 base::Bind(&OnCloseWebContentsDeleter::ScheduleWebContentsForDeletion,
181 AsWeakPtr(), true),
182 base::TimeDelta::FromSeconds(kDeleteWithExtremePrejudiceSeconds));
183 }
184
CloseContents(WebContents * source)185 virtual void CloseContents(WebContents* source) OVERRIDE {
186 DCHECK_EQ(tab_, source);
187 ScheduleWebContentsForDeletion(false);
188 }
189
SwappedOut(WebContents * source)190 virtual void SwappedOut(WebContents* source) OVERRIDE {
191 DCHECK_EQ(tab_, source);
192 ScheduleWebContentsForDeletion(false);
193 }
194
ShouldSuppressDialogs()195 virtual bool ShouldSuppressDialogs() OVERRIDE {
196 // Use this as a proxy for getting statistics on how often we fail to honor
197 // the beforeunload event.
198 suppressed_dialog_ = true;
199 return true;
200 }
201
202 private:
203 static const int kDeleteWithExtremePrejudiceSeconds = 3;
204
ScheduleWebContentsForDeletion(bool timeout)205 void ScheduleWebContentsForDeletion(bool timeout) {
206 UMA_HISTOGRAM_BOOLEAN("Prerender.TabContentsDeleterTimeout", timeout);
207 UMA_HISTOGRAM_BOOLEAN("Prerender.TabContentsDeleterSuppressedDialog",
208 suppressed_dialog_);
209 tab_->SetDelegate(NULL);
210 manager_->ScheduleDeleteOldWebContents(tab_.release(), this);
211 // |this| is deleted at this point.
212 }
213
214 PrerenderManager* manager_;
215 scoped_ptr<WebContents> tab_;
216 bool suppressed_dialog_;
217
218 DISALLOW_COPY_AND_ASSIGN(OnCloseWebContentsDeleter);
219 };
220
221 // static
222 int PrerenderManager::prerenders_per_session_count_ = 0;
223
224 // static
225 PrerenderManager::PrerenderManagerMode PrerenderManager::mode_ =
226 PRERENDER_MODE_ENABLED;
227
228 struct PrerenderManager::NavigationRecord {
NavigationRecordprerender::PrerenderManager::NavigationRecord229 NavigationRecord(const GURL& url, base::TimeTicks time)
230 : url(url),
231 time(time) {
232 }
233
234 GURL url;
235 base::TimeTicks time;
236 };
237
PrerenderManager(Profile * profile,PrerenderTracker * prerender_tracker)238 PrerenderManager::PrerenderManager(Profile* profile,
239 PrerenderTracker* prerender_tracker)
240 : profile_(profile),
241 prerender_tracker_(prerender_tracker),
242 prerender_contents_factory_(PrerenderContents::CreateFactory()),
243 last_prerender_start_time_(GetCurrentTimeTicks() -
244 base::TimeDelta::FromMilliseconds(kMinTimeBetweenPrerendersMs)),
245 prerender_history_(new PrerenderHistory(kHistoryLength)),
246 histograms_(new PrerenderHistograms()),
247 profile_network_bytes_(0),
248 last_recorded_profile_network_bytes_(0),
249 cookie_store_loaded_(false) {
250 // There are some assumptions that the PrerenderManager is on the UI thread.
251 // Any other checks simply make sure that the PrerenderManager is accessed on
252 // the same thread that it was created on.
253 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
254
255 if (IsLocalPredictorEnabled())
256 local_predictor_.reset(new PrerenderLocalPredictor(this));
257
258 if (IsLoggedInPredictorEnabled() && !profile_->IsOffTheRecord()) {
259 predictors::PredictorDatabase* predictor_db =
260 predictors::PredictorDatabaseFactory::GetForProfile(profile);
261 if (predictor_db) {
262 logged_in_predictor_table_ = predictor_db->logged_in_table();
263 scoped_ptr<LoggedInStateMap> new_state_map(new LoggedInStateMap);
264 LoggedInStateMap* new_state_map_ptr = new_state_map.get();
265 BrowserThread::PostTaskAndReply(
266 BrowserThread::DB, FROM_HERE,
267 base::Bind(&LoggedInPredictorTable::GetAllData,
268 logged_in_predictor_table_,
269 new_state_map_ptr),
270 base::Bind(&PrerenderManager::LoggedInPredictorDataReceived,
271 AsWeakPtr(),
272 base::Passed(&new_state_map)));
273 }
274 }
275
276 // Certain experiments override our default config_ values.
277 switch (PrerenderManager::GetMode()) {
278 case PrerenderManager::PRERENDER_MODE_EXPERIMENT_MULTI_PRERENDER_GROUP:
279 config_.max_link_concurrency = 4;
280 config_.max_link_concurrency_per_launcher = 2;
281 break;
282 case PrerenderManager::PRERENDER_MODE_EXPERIMENT_15MIN_TTL_GROUP:
283 config_.time_to_live = base::TimeDelta::FromMinutes(15);
284 break;
285 default:
286 break;
287 }
288
289 notification_registrar_.Add(
290 this, chrome::NOTIFICATION_COOKIE_CHANGED,
291 content::NotificationService::AllBrowserContextsAndSources());
292
293 notification_registrar_.Add(
294 this, chrome::NOTIFICATION_PROFILE_DESTROYED,
295 content::Source<Profile>(profile_));
296
297 MediaCaptureDevicesDispatcher::GetInstance()->AddObserver(this);
298 }
299
~PrerenderManager()300 PrerenderManager::~PrerenderManager() {
301 MediaCaptureDevicesDispatcher::GetInstance()->RemoveObserver(this);
302
303 // The earlier call to KeyedService::Shutdown() should have
304 // emptied these vectors already.
305 DCHECK(active_prerenders_.empty());
306 DCHECK(to_delete_prerenders_.empty());
307
308 for (PrerenderProcessSet::const_iterator it =
309 prerender_process_hosts_.begin();
310 it != prerender_process_hosts_.end();
311 ++it) {
312 (*it)->RemoveObserver(this);
313 }
314 }
315
Shutdown()316 void PrerenderManager::Shutdown() {
317 DestroyAllContents(FINAL_STATUS_MANAGER_SHUTDOWN);
318 on_close_web_contents_deleters_.clear();
319 // Must happen before |profile_| is set to NULL as
320 // |local_predictor_| accesses it.
321 if (local_predictor_)
322 local_predictor_->Shutdown();
323 profile_ = NULL;
324
325 DCHECK(active_prerenders_.empty());
326 }
327
AddPrerenderFromLinkRelPrerender(int process_id,int route_id,const GURL & url,const uint32 rel_types,const content::Referrer & referrer,const gfx::Size & size)328 PrerenderHandle* PrerenderManager::AddPrerenderFromLinkRelPrerender(
329 int process_id,
330 int route_id,
331 const GURL& url,
332 const uint32 rel_types,
333 const content::Referrer& referrer,
334 const gfx::Size& size) {
335 Origin origin = rel_types & PrerenderRelTypePrerender ?
336 ORIGIN_LINK_REL_PRERENDER_CROSSDOMAIN :
337 ORIGIN_LINK_REL_NEXT;
338 SessionStorageNamespace* session_storage_namespace = NULL;
339 // Unit tests pass in a process_id == -1.
340 if (process_id != -1) {
341 RenderViewHost* source_render_view_host =
342 RenderViewHost::FromID(process_id, route_id);
343 if (!source_render_view_host)
344 return NULL;
345 WebContents* source_web_contents =
346 WebContents::FromRenderViewHost(source_render_view_host);
347 if (!source_web_contents)
348 return NULL;
349 if (origin == ORIGIN_LINK_REL_PRERENDER_CROSSDOMAIN &&
350 source_web_contents->GetURL().host() == url.host()) {
351 origin = ORIGIN_LINK_REL_PRERENDER_SAMEDOMAIN;
352 }
353 // TODO(ajwong): This does not correctly handle storage for isolated apps.
354 session_storage_namespace =
355 source_web_contents->GetController()
356 .GetDefaultSessionStorageNamespace();
357 }
358
359 return AddPrerender(origin, process_id, url, referrer, size,
360 session_storage_namespace);
361 }
362
AddPrerenderFromOmnibox(const GURL & url,SessionStorageNamespace * session_storage_namespace,const gfx::Size & size)363 PrerenderHandle* PrerenderManager::AddPrerenderFromOmnibox(
364 const GURL& url,
365 SessionStorageNamespace* session_storage_namespace,
366 const gfx::Size& size) {
367 if (!IsOmniboxEnabled(profile_))
368 return NULL;
369 return AddPrerender(ORIGIN_OMNIBOX, -1, url, content::Referrer(), size,
370 session_storage_namespace);
371 }
372
AddPrerenderFromLocalPredictor(const GURL & url,SessionStorageNamespace * session_storage_namespace,const gfx::Size & size)373 PrerenderHandle* PrerenderManager::AddPrerenderFromLocalPredictor(
374 const GURL& url,
375 SessionStorageNamespace* session_storage_namespace,
376 const gfx::Size& size) {
377 return AddPrerender(ORIGIN_LOCAL_PREDICTOR, -1, url, content::Referrer(),
378 size, session_storage_namespace);
379 }
380
AddPrerenderFromExternalRequest(const GURL & url,const content::Referrer & referrer,SessionStorageNamespace * session_storage_namespace,const gfx::Size & size)381 PrerenderHandle* PrerenderManager::AddPrerenderFromExternalRequest(
382 const GURL& url,
383 const content::Referrer& referrer,
384 SessionStorageNamespace* session_storage_namespace,
385 const gfx::Size& size) {
386 return AddPrerender(ORIGIN_EXTERNAL_REQUEST, -1, url, referrer, size,
387 session_storage_namespace);
388 }
389
AddPrerenderForInstant(const GURL & url,content::SessionStorageNamespace * session_storage_namespace,const gfx::Size & size)390 PrerenderHandle* PrerenderManager::AddPrerenderForInstant(
391 const GURL& url,
392 content::SessionStorageNamespace* session_storage_namespace,
393 const gfx::Size& size) {
394 DCHECK(chrome::ShouldPrefetchSearchResults());
395 return AddPrerender(ORIGIN_INSTANT, -1, url, content::Referrer(), size,
396 session_storage_namespace);
397 }
398
CancelAllPrerenders()399 void PrerenderManager::CancelAllPrerenders() {
400 DCHECK(CalledOnValidThread());
401 while (!active_prerenders_.empty()) {
402 PrerenderContents* prerender_contents =
403 active_prerenders_.front()->contents();
404 prerender_contents->Destroy(FINAL_STATUS_CANCELLED);
405 }
406 }
407
MaybeUsePrerenderedPage(const GURL & url,chrome::NavigateParams * params)408 bool PrerenderManager::MaybeUsePrerenderedPage(const GURL& url,
409 chrome::NavigateParams* params) {
410 DCHECK(CalledOnValidThread());
411
412 content::WebContents* web_contents = params->target_contents;
413 DCHECK(!IsWebContentsPrerendering(web_contents, NULL));
414
415 // Don't prerender if the navigation involves some special parameters.
416 if (params->uses_post || !params->extra_headers.empty())
417 return false;
418
419 DeleteOldEntries();
420 to_delete_prerenders_.clear();
421
422 // First, try to find prerender data with the correct session storage
423 // namespace.
424 // TODO(ajwong): This doesn't handle isolated apps correctly.
425 PrerenderData* prerender_data = FindPrerenderData(
426 url,
427 web_contents->GetController().GetDefaultSessionStorageNamespace());
428
429 // If this failed, we may still find a prerender for the same URL, but a
430 // different session storage namespace. If we do, we might have to perform
431 // a merge.
432 if (!prerender_data) {
433 prerender_data = FindPrerenderData(url, NULL);
434 } else {
435 RecordEvent(prerender_data->contents(),
436 PRERENDER_EVENT_SWAPIN_CANDIDATE_NAMESPACE_MATCHES);
437 }
438
439 if (!prerender_data)
440 return false;
441 RecordEvent(prerender_data->contents(), PRERENDER_EVENT_SWAPIN_CANDIDATE);
442 DCHECK(prerender_data->contents());
443
444 // If there is currently a merge pending for this prerender data, don't swap.
445 if (prerender_data->pending_swap())
446 return false;
447
448 // Abort any existing pending swap on the target contents.
449 PrerenderData* pending_swap =
450 FindPrerenderDataForTargetContents(web_contents);
451 if (pending_swap) {
452 pending_swap->ClearPendingSwap();
453 DCHECK(FindPrerenderDataForTargetContents(web_contents) == NULL);
454 }
455
456 RecordEvent(prerender_data->contents(),
457 PRERENDER_EVENT_SWAPIN_NO_MERGE_PENDING);
458 SessionStorageNamespace* target_namespace =
459 web_contents->GetController().GetDefaultSessionStorageNamespace();
460 SessionStorageNamespace* prerender_namespace =
461 prerender_data->contents()->GetSessionStorageNamespace();
462 // Only when actually prerendering is session storage namespace merging an
463 // issue. For the control group, it will be assumed that the merge succeeded.
464 if (prerender_namespace && prerender_namespace != target_namespace &&
465 !prerender_namespace->IsAliasOf(target_namespace)) {
466 if (!ShouldMergeSessionStorageNamespaces()) {
467 RecordEvent(prerender_data->contents(),
468 PRERENDER_EVENT_SWAPIN_MERGING_DISABLED);
469 return false;
470 }
471 RecordEvent(prerender_data->contents(),
472 PRERENDER_EVENT_SWAPIN_ISSUING_MERGE);
473 prerender_data->set_pending_swap(new PendingSwap(
474 this, web_contents, prerender_data, url,
475 params->should_replace_current_entry));
476 prerender_data->pending_swap()->BeginSwap();
477 // Although this returns false, creating a PendingSwap registers with
478 // PrerenderTracker to throttle MAIN_FRAME navigations while the swap is
479 // pending.
480 return false;
481 }
482
483 // No need to merge; swap synchronously.
484 WebContents* new_web_contents = SwapInternal(
485 url, web_contents, prerender_data,
486 params->should_replace_current_entry);
487 if (!new_web_contents)
488 return false;
489
490 // Record the new target_contents for the callers.
491 params->target_contents = new_web_contents;
492 return true;
493 }
494
SwapInternal(const GURL & url,WebContents * web_contents,PrerenderData * prerender_data,bool should_replace_current_entry)495 WebContents* PrerenderManager::SwapInternal(
496 const GURL& url,
497 WebContents* web_contents,
498 PrerenderData* prerender_data,
499 bool should_replace_current_entry) {
500 DCHECK(CalledOnValidThread());
501 DCHECK(!IsWebContentsPrerendering(web_contents, NULL));
502
503 // Only swap if the target WebContents has a CoreTabHelper delegate to swap
504 // out of it. For a normal WebContents, this is if it is in a TabStripModel.
505 CoreTabHelper* core_tab_helper = CoreTabHelper::FromWebContents(web_contents);
506 if (!core_tab_helper || !core_tab_helper->delegate()) {
507 RecordEvent(prerender_data->contents(), PRERENDER_EVENT_SWAPIN_NO_DELEGATE);
508 return NULL;
509 }
510
511 PrerenderTabHelper* target_tab_helper =
512 PrerenderTabHelper::FromWebContents(web_contents);
513 if (!target_tab_helper) {
514 NOTREACHED();
515 return NULL;
516 }
517
518 if (IsNoSwapInExperiment(prerender_data->contents()->experiment_id()))
519 return NULL;
520
521 if (WebContents* new_web_contents =
522 prerender_data->contents()->prerender_contents()) {
523 if (web_contents == new_web_contents)
524 return NULL; // Do not swap in to ourself.
525
526 // We cannot swap in if there is no last committed entry, because we would
527 // show a blank page under an existing entry from the current tab. Even if
528 // there is a pending entry, it may not commit.
529 // TODO(creis): If there is a pending navigation and no last committed
530 // entry, we might be able to transfer the network request instead.
531 if (!new_web_contents->GetController().CanPruneAllButLastCommitted()) {
532 // Abort this prerender so it is not used later. http://crbug.com/292121
533 prerender_data->contents()->Destroy(FINAL_STATUS_NAVIGATION_UNCOMMITTED);
534 return NULL;
535 }
536 }
537
538 // Do not swap if the target WebContents is not the only WebContents in its
539 // current BrowsingInstance.
540 if (web_contents->GetSiteInstance()->GetRelatedActiveContentsCount() != 1u) {
541 DCHECK_GT(
542 web_contents->GetSiteInstance()->GetRelatedActiveContentsCount(), 1u);
543 prerender_data->contents()->Destroy(
544 FINAL_STATUS_NON_EMPTY_BROWSING_INSTANCE);
545 return NULL;
546 }
547
548 // Do not use the prerendered version if there is an opener object.
549 if (web_contents->HasOpener()) {
550 prerender_data->contents()->Destroy(FINAL_STATUS_WINDOW_OPENER);
551 return NULL;
552 }
553
554 // Do not swap in the prerender if the current WebContents is being captured.
555 if (web_contents->GetCapturerCount() > 0) {
556 prerender_data->contents()->Destroy(FINAL_STATUS_PAGE_BEING_CAPTURED);
557 return NULL;
558 }
559
560 // If we are just in the control group (which can be detected by noticing
561 // that prerendering hasn't even started yet), record that |web_contents| now
562 // would be showing a prerendered contents, but otherwise, don't do anything.
563 if (!prerender_data->contents()->prerendering_has_started()) {
564 target_tab_helper->WouldHavePrerenderedNextLoad(
565 prerender_data->contents()->origin());
566 prerender_data->contents()->Destroy(FINAL_STATUS_WOULD_HAVE_BEEN_USED);
567 return NULL;
568 }
569
570 // Don't use prerendered pages if debugger is attached to the tab.
571 // See http://crbug.com/98541
572 if (content::DevToolsAgentHost::IsDebuggerAttached(web_contents)) {
573 DestroyAndMarkMatchCompleteAsUsed(prerender_data->contents(),
574 FINAL_STATUS_DEVTOOLS_ATTACHED);
575 return NULL;
576 }
577
578 // If the prerendered page is in the middle of a cross-site navigation,
579 // don't swap it in because there isn't a good way to merge histories.
580 if (prerender_data->contents()->IsCrossSiteNavigationPending()) {
581 DestroyAndMarkMatchCompleteAsUsed(
582 prerender_data->contents(),
583 FINAL_STATUS_CROSS_SITE_NAVIGATION_PENDING);
584 return NULL;
585 }
586
587 // For bookkeeping purposes, we need to mark this WebContents to
588 // reflect that it would have been prerendered.
589 if (GetMode() == PRERENDER_MODE_EXPERIMENT_NO_USE_GROUP) {
590 target_tab_helper->WouldHavePrerenderedNextLoad(
591 prerender_data->contents()->origin());
592 prerender_data->contents()->Destroy(FINAL_STATUS_WOULD_HAVE_BEEN_USED);
593 return NULL;
594 }
595
596 // At this point, we've determined that we will use the prerender.
597 content::RenderProcessHost* process_host =
598 prerender_data->contents()->GetRenderViewHost()->GetProcess();
599 prerender_process_hosts_.erase(process_host);
600 BrowserThread::PostTask(
601 BrowserThread::IO, FROM_HERE,
602 base::Bind(&PrerenderTracker::RemovePrerenderCookieStoreOnIOThread,
603 base::Unretained(prerender_tracker()), process_host->GetID(),
604 true));
605 if (!prerender_data->contents()->load_start_time().is_null()) {
606 histograms_->RecordTimeUntilUsed(
607 prerender_data->contents()->origin(),
608 GetCurrentTimeTicks() - prerender_data->contents()->load_start_time());
609 }
610 histograms_->RecordAbandonTimeUntilUsed(
611 prerender_data->contents()->origin(),
612 prerender_data->abandon_time().is_null() ?
613 base::TimeDelta() :
614 GetCurrentTimeTicks() - prerender_data->abandon_time());
615
616 histograms_->RecordPerSessionCount(prerender_data->contents()->origin(),
617 ++prerenders_per_session_count_);
618 histograms_->RecordUsedPrerender(prerender_data->contents()->origin());
619
620 if (prerender_data->pending_swap())
621 prerender_data->pending_swap()->set_swap_successful(true);
622 ScopedVector<PrerenderData>::iterator to_erase =
623 FindIteratorForPrerenderContents(prerender_data->contents());
624 DCHECK(active_prerenders_.end() != to_erase);
625 DCHECK_EQ(prerender_data, *to_erase);
626 scoped_ptr<PrerenderContents>
627 prerender_contents(prerender_data->ReleaseContents());
628 active_prerenders_.erase(to_erase);
629
630 // Mark prerender as used.
631 prerender_contents->PrepareForUse();
632
633 WebContents* new_web_contents =
634 prerender_contents->ReleasePrerenderContents();
635 WebContents* old_web_contents = web_contents;
636 DCHECK(new_web_contents);
637 DCHECK(old_web_contents);
638
639 // Merge the browsing history.
640 new_web_contents->GetController().CopyStateFromAndPrune(
641 &old_web_contents->GetController(),
642 should_replace_current_entry);
643 CoreTabHelper::FromWebContents(old_web_contents)->delegate()->
644 SwapTabContents(old_web_contents,
645 new_web_contents,
646 true,
647 prerender_contents->has_finished_loading());
648 prerender_contents->CommitHistory(new_web_contents);
649
650 // Update PPLT metrics:
651 // If the tab has finished loading, record a PPLT of 0.
652 // If the tab is still loading, reset its start time to the current time.
653 PrerenderTabHelper* prerender_tab_helper =
654 PrerenderTabHelper::FromWebContents(new_web_contents);
655 DCHECK(prerender_tab_helper != NULL);
656 prerender_tab_helper->PrerenderSwappedIn();
657
658 if (old_web_contents->NeedToFireBeforeUnload()) {
659 // Schedule the delete to occur after the tab has run its unload handlers.
660 // TODO(davidben): Honor the beforeunload event. http://crbug.com/304932
661 on_close_web_contents_deleters_.push_back(
662 new OnCloseWebContentsDeleter(this, old_web_contents));
663 old_web_contents->DispatchBeforeUnload(false);
664 } else {
665 // No unload handler to run, so delete asap.
666 ScheduleDeleteOldWebContents(old_web_contents, NULL);
667 }
668
669 // TODO(cbentzel): Should prerender_contents move to the pending delete
670 // list, instead of deleting directly here?
671 AddToHistory(prerender_contents.get());
672 RecordNavigation(url);
673 return new_web_contents;
674 }
675
MoveEntryToPendingDelete(PrerenderContents * entry,FinalStatus final_status)676 void PrerenderManager::MoveEntryToPendingDelete(PrerenderContents* entry,
677 FinalStatus final_status) {
678 DCHECK(CalledOnValidThread());
679 DCHECK(entry);
680
681 ScopedVector<PrerenderData>::iterator it =
682 FindIteratorForPrerenderContents(entry);
683 DCHECK(it != active_prerenders_.end());
684
685 // If this PrerenderContents is being deleted due to a cancellation any time
686 // after the prerender has started then we need to create a dummy replacement
687 // for PPLT accounting purposes for the Match Complete group. This is the case
688 // if the cancellation is for any reason that would not occur in the control
689 // group case.
690 if (entry->prerendering_has_started() &&
691 entry->match_complete_status() ==
692 PrerenderContents::MATCH_COMPLETE_DEFAULT &&
693 NeedMatchCompleteDummyForFinalStatus(final_status) &&
694 ActuallyPrerendering() &&
695 GetMode() == PRERENDER_MODE_EXPERIMENT_MATCH_COMPLETE_GROUP) {
696 // TODO(tburkard): I'd like to DCHECK that we are actually prerendering.
697 // However, what if new conditions are added and
698 // NeedMatchCompleteDummyForFinalStatus is not being updated. Not sure
699 // what's the best thing to do here. For now, I will just check whether
700 // we are actually prerendering.
701 (*it)->MakeIntoMatchCompleteReplacement();
702 } else {
703 to_delete_prerenders_.push_back(*it);
704 (*it)->ClearPendingSwap();
705 active_prerenders_.weak_erase(it);
706 }
707
708 // Destroy the old WebContents relatively promptly to reduce resource usage.
709 PostCleanupTask();
710 }
711
RecordPageLoadTimeNotSwappedIn(Origin origin,base::TimeDelta page_load_time,const GURL & url)712 void PrerenderManager::RecordPageLoadTimeNotSwappedIn(
713 Origin origin,
714 base::TimeDelta page_load_time,
715 const GURL& url) {
716 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
717 histograms_->RecordPageLoadTimeNotSwappedIn(origin, page_load_time, url);
718 }
719
RecordPerceivedPageLoadTime(Origin origin,NavigationType navigation_type,base::TimeDelta perceived_page_load_time,double fraction_plt_elapsed_at_swap_in,const GURL & url)720 void PrerenderManager::RecordPerceivedPageLoadTime(
721 Origin origin,
722 NavigationType navigation_type,
723 base::TimeDelta perceived_page_load_time,
724 double fraction_plt_elapsed_at_swap_in,
725 const GURL& url) {
726 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
727 if (!IsEnabled())
728 return;
729
730 histograms_->RecordPerceivedPageLoadTime(
731 origin, perceived_page_load_time, navigation_type, url);
732
733 if (navigation_type == NAVIGATION_TYPE_PRERENDERED) {
734 histograms_->RecordPercentLoadDoneAtSwapin(
735 origin, fraction_plt_elapsed_at_swap_in);
736 }
737 if (local_predictor_) {
738 local_predictor_->OnPLTEventForURL(url, perceived_page_load_time);
739 }
740 }
741
742 // static
GetMode()743 PrerenderManager::PrerenderManagerMode PrerenderManager::GetMode() {
744 return mode_;
745 }
746
747 // static
SetMode(PrerenderManagerMode mode)748 void PrerenderManager::SetMode(PrerenderManagerMode mode) {
749 mode_ = mode;
750 }
751
752 // static
GetModeString()753 const char* PrerenderManager::GetModeString() {
754 switch (mode_) {
755 case PRERENDER_MODE_DISABLED:
756 return "_Disabled";
757 case PRERENDER_MODE_ENABLED:
758 case PRERENDER_MODE_EXPERIMENT_PRERENDER_GROUP:
759 return "_Enabled";
760 case PRERENDER_MODE_EXPERIMENT_CONTROL_GROUP:
761 return "_Control";
762 case PRERENDER_MODE_EXPERIMENT_MULTI_PRERENDER_GROUP:
763 return "_Multi";
764 case PRERENDER_MODE_EXPERIMENT_15MIN_TTL_GROUP:
765 return "_15MinTTL";
766 case PRERENDER_MODE_EXPERIMENT_NO_USE_GROUP:
767 return "_NoUse";
768 case PRERENDER_MODE_EXPERIMENT_MATCH_COMPLETE_GROUP:
769 return "_MatchComplete";
770 case PRERENDER_MODE_MAX:
771 default:
772 NOTREACHED() << "Invalid PrerenderManager mode.";
773 break;
774 }
775 return "";
776 }
777
778 // static
IsPrerenderingPossible()779 bool PrerenderManager::IsPrerenderingPossible() {
780 return GetMode() != PRERENDER_MODE_DISABLED;
781 }
782
783 // static
ActuallyPrerendering()784 bool PrerenderManager::ActuallyPrerendering() {
785 return IsPrerenderingPossible() && !IsControlGroup(kNoExperiment);
786 }
787
788 // static
IsControlGroup(uint8 experiment_id)789 bool PrerenderManager::IsControlGroup(uint8 experiment_id) {
790 return GetMode() == PRERENDER_MODE_EXPERIMENT_CONTROL_GROUP ||
791 IsControlGroupExperiment(experiment_id);
792 }
793
794 // static
IsNoUseGroup()795 bool PrerenderManager::IsNoUseGroup() {
796 return GetMode() == PRERENDER_MODE_EXPERIMENT_NO_USE_GROUP;
797 }
798
IsWebContentsPrerendering(const WebContents * web_contents,Origin * origin) const799 bool PrerenderManager::IsWebContentsPrerendering(
800 const WebContents* web_contents,
801 Origin* origin) const {
802 DCHECK(CalledOnValidThread());
803 if (PrerenderContents* prerender_contents =
804 GetPrerenderContents(web_contents)) {
805 if (origin)
806 *origin = prerender_contents->origin();
807 return true;
808 }
809 return false;
810 }
811
HasPrerenderedUrl(GURL url,content::WebContents * web_contents) const812 bool PrerenderManager::HasPrerenderedUrl(
813 GURL url,
814 content::WebContents* web_contents) const {
815 content::SessionStorageNamespace* session_storage_namespace = web_contents->
816 GetController().GetDefaultSessionStorageNamespace();
817
818 for (ScopedVector<PrerenderData>::const_iterator it =
819 active_prerenders_.begin();
820 it != active_prerenders_.end(); ++it) {
821 PrerenderContents* prerender_contents = (*it)->contents();
822 if (prerender_contents->Matches(url, session_storage_namespace)) {
823 return true;
824 }
825 }
826 return false;
827 }
828
GetPrerenderContents(const content::WebContents * web_contents) const829 PrerenderContents* PrerenderManager::GetPrerenderContents(
830 const content::WebContents* web_contents) const {
831 DCHECK(CalledOnValidThread());
832 for (ScopedVector<PrerenderData>::const_iterator it =
833 active_prerenders_.begin();
834 it != active_prerenders_.end(); ++it) {
835 WebContents* prerender_web_contents =
836 (*it)->contents()->prerender_contents();
837 if (prerender_web_contents == web_contents) {
838 return (*it)->contents();
839 }
840 }
841
842 // Also check the pending-deletion list. If the prerender is in pending
843 // delete, anyone with a handle on the WebContents needs to know.
844 for (ScopedVector<PrerenderData>::const_iterator it =
845 to_delete_prerenders_.begin();
846 it != to_delete_prerenders_.end(); ++it) {
847 WebContents* prerender_web_contents =
848 (*it)->contents()->prerender_contents();
849 if (prerender_web_contents == web_contents) {
850 return (*it)->contents();
851 }
852 }
853 return NULL;
854 }
855
GetPrerenderContentsForRoute(int child_id,int route_id) const856 PrerenderContents* PrerenderManager::GetPrerenderContentsForRoute(
857 int child_id,
858 int route_id) const {
859 content::WebContents* web_contents =
860 tab_util::GetWebContentsByID(child_id, route_id);
861 if (web_contents == NULL)
862 return NULL;
863 return GetPrerenderContents(web_contents);
864 }
865
866 const std::vector<WebContents*>
GetAllPrerenderingContents() const867 PrerenderManager::GetAllPrerenderingContents() const {
868 DCHECK(CalledOnValidThread());
869 std::vector<WebContents*> result;
870
871 for (ScopedVector<PrerenderData>::const_iterator it =
872 active_prerenders_.begin();
873 it != active_prerenders_.end(); ++it) {
874 if (WebContents* contents = (*it)->contents()->prerender_contents())
875 result.push_back(contents);
876 }
877
878 return result;
879 }
880
HasRecentlyBeenNavigatedTo(Origin origin,const GURL & url)881 bool PrerenderManager::HasRecentlyBeenNavigatedTo(Origin origin,
882 const GURL& url) {
883 DCHECK(CalledOnValidThread());
884
885 CleanUpOldNavigations();
886 std::list<NavigationRecord>::const_reverse_iterator end = navigations_.rend();
887 for (std::list<NavigationRecord>::const_reverse_iterator it =
888 navigations_.rbegin();
889 it != end;
890 ++it) {
891 if (it->url == url) {
892 base::TimeDelta delta = GetCurrentTimeTicks() - it->time;
893 histograms_->RecordTimeSinceLastRecentVisit(origin, delta);
894 return true;
895 }
896 }
897
898 return false;
899 }
900
901 // static
IsValidHttpMethod(const std::string & method)902 bool PrerenderManager::IsValidHttpMethod(const std::string& method) {
903 // method has been canonicalized to upper case at this point so we can just
904 // compare them.
905 DCHECK_EQ(method, StringToUpperASCII(method));
906 for (size_t i = 0; i < arraysize(kValidHttpMethods); ++i) {
907 if (method.compare(kValidHttpMethods[i]) == 0)
908 return true;
909 }
910
911 return false;
912 }
913
914 // static
DoesURLHaveValidScheme(const GURL & url)915 bool PrerenderManager::DoesURLHaveValidScheme(const GURL& url) {
916 return (url.SchemeIsHTTPOrHTTPS() ||
917 url.SchemeIs(extensions::kExtensionScheme) ||
918 url.SchemeIs("data"));
919 }
920
921 // static
DoesSubresourceURLHaveValidScheme(const GURL & url)922 bool PrerenderManager::DoesSubresourceURLHaveValidScheme(const GURL& url) {
923 return DoesURLHaveValidScheme(url) || url == GURL(url::kAboutBlankURL);
924 }
925
GetAsValue() const926 base::DictionaryValue* PrerenderManager::GetAsValue() const {
927 DCHECK(CalledOnValidThread());
928 base::DictionaryValue* dict_value = new base::DictionaryValue();
929 dict_value->Set("history", prerender_history_->GetEntriesAsValue());
930 dict_value->Set("active", GetActivePrerendersAsValue());
931 dict_value->SetBoolean("enabled", IsEnabled());
932 dict_value->SetBoolean("omnibox_enabled", IsOmniboxEnabled(profile_));
933 // If prerender is disabled via a flag this method is not even called.
934 std::string enabled_note;
935 if (IsControlGroup(kNoExperiment))
936 enabled_note += "(Control group: Not actually prerendering) ";
937 if (IsNoUseGroup())
938 enabled_note += "(No-use group: Not swapping in prerendered pages) ";
939 if (GetMode() == PRERENDER_MODE_EXPERIMENT_15MIN_TTL_GROUP)
940 enabled_note +=
941 "(15 min TTL group: Extended prerender eviction to 15 mins) ";
942 dict_value->SetString("enabled_note", enabled_note);
943 return dict_value;
944 }
945
ClearData(int clear_flags)946 void PrerenderManager::ClearData(int clear_flags) {
947 DCHECK_GE(clear_flags, 0);
948 DCHECK_LT(clear_flags, CLEAR_MAX);
949 if (clear_flags & CLEAR_PRERENDER_CONTENTS)
950 DestroyAllContents(FINAL_STATUS_CACHE_OR_HISTORY_CLEARED);
951 // This has to be second, since destroying prerenders can add to the history.
952 if (clear_flags & CLEAR_PRERENDER_HISTORY)
953 prerender_history_->Clear();
954 }
955
RecordFinalStatusWithMatchCompleteStatus(Origin origin,uint8 experiment_id,PrerenderContents::MatchCompleteStatus mc_status,FinalStatus final_status) const956 void PrerenderManager::RecordFinalStatusWithMatchCompleteStatus(
957 Origin origin,
958 uint8 experiment_id,
959 PrerenderContents::MatchCompleteStatus mc_status,
960 FinalStatus final_status) const {
961 histograms_->RecordFinalStatus(origin,
962 experiment_id,
963 mc_status,
964 final_status);
965 }
966
RecordNavigation(const GURL & url)967 void PrerenderManager::RecordNavigation(const GURL& url) {
968 DCHECK(CalledOnValidThread());
969
970 navigations_.push_back(NavigationRecord(url, GetCurrentTimeTicks()));
971 CleanUpOldNavigations();
972 }
973
974 // protected
975 struct PrerenderManager::PrerenderData::OrderByExpiryTime {
operator ()prerender::PrerenderManager::PrerenderData::OrderByExpiryTime976 bool operator()(const PrerenderData* a, const PrerenderData* b) const {
977 return a->expiry_time() < b->expiry_time();
978 }
979 };
980
PrerenderData(PrerenderManager * manager,PrerenderContents * contents,base::TimeTicks expiry_time)981 PrerenderManager::PrerenderData::PrerenderData(PrerenderManager* manager,
982 PrerenderContents* contents,
983 base::TimeTicks expiry_time)
984 : manager_(manager),
985 contents_(contents),
986 handle_count_(0),
987 expiry_time_(expiry_time) {
988 DCHECK_NE(static_cast<PrerenderContents*>(NULL), contents_);
989 }
990
~PrerenderData()991 PrerenderManager::PrerenderData::~PrerenderData() {
992 }
993
MakeIntoMatchCompleteReplacement()994 void PrerenderManager::PrerenderData::MakeIntoMatchCompleteReplacement() {
995 DCHECK(contents_);
996 contents_->set_match_complete_status(
997 PrerenderContents::MATCH_COMPLETE_REPLACED);
998 PrerenderData* to_delete = new PrerenderData(manager_, contents_.release(),
999 expiry_time_);
1000 contents_.reset(to_delete->contents_->CreateMatchCompleteReplacement());
1001 manager_->to_delete_prerenders_.push_back(to_delete);
1002 }
1003
OnHandleCreated(PrerenderHandle * handle)1004 void PrerenderManager::PrerenderData::OnHandleCreated(PrerenderHandle* handle) {
1005 DCHECK_NE(static_cast<PrerenderContents*>(NULL), contents_);
1006 ++handle_count_;
1007 contents_->AddObserver(handle);
1008 }
1009
OnHandleNavigatedAway(PrerenderHandle * handle)1010 void PrerenderManager::PrerenderData::OnHandleNavigatedAway(
1011 PrerenderHandle* handle) {
1012 DCHECK_LT(0, handle_count_);
1013 DCHECK_NE(static_cast<PrerenderContents*>(NULL), contents_);
1014 if (abandon_time_.is_null())
1015 abandon_time_ = base::TimeTicks::Now();
1016 // We intentionally don't decrement the handle count here, so that the
1017 // prerender won't be canceled until it times out.
1018 manager_->SourceNavigatedAway(this);
1019 }
1020
OnHandleCanceled(PrerenderHandle * handle)1021 void PrerenderManager::PrerenderData::OnHandleCanceled(
1022 PrerenderHandle* handle) {
1023 DCHECK_LT(0, handle_count_);
1024 DCHECK_NE(static_cast<PrerenderContents*>(NULL), contents_);
1025
1026 if (--handle_count_ == 0) {
1027 // This will eventually remove this object from active_prerenders_.
1028 contents_->Destroy(FINAL_STATUS_CANCELLED);
1029 }
1030 }
1031
ClearPendingSwap()1032 void PrerenderManager::PrerenderData::ClearPendingSwap() {
1033 pending_swap_.reset(NULL);
1034 }
1035
ReleaseContents()1036 PrerenderContents* PrerenderManager::PrerenderData::ReleaseContents() {
1037 return contents_.release();
1038 }
1039
PendingSwap(PrerenderManager * manager,content::WebContents * target_contents,PrerenderData * prerender_data,const GURL & url,bool should_replace_current_entry)1040 PrerenderManager::PendingSwap::PendingSwap(
1041 PrerenderManager* manager,
1042 content::WebContents* target_contents,
1043 PrerenderData* prerender_data,
1044 const GURL& url,
1045 bool should_replace_current_entry)
1046 : content::WebContentsObserver(target_contents),
1047 manager_(manager),
1048 prerender_data_(prerender_data),
1049 url_(url),
1050 should_replace_current_entry_(should_replace_current_entry),
1051 start_time_(base::TimeTicks::Now()),
1052 seen_target_route_id_(false),
1053 swap_successful_(false),
1054 weak_factory_(this) {
1055 }
1056
~PendingSwap()1057 PrerenderManager::PendingSwap::~PendingSwap() {
1058 manager_->prerender_tracker()->RemovePrerenderPendingSwap(
1059 target_route_id_, swap_successful_);
1060 }
1061
BeginSwap()1062 void PrerenderManager::PendingSwap::BeginSwap() {
1063 if (g_hang_session_storage_merges_for_testing)
1064 return;
1065
1066 SessionStorageNamespace* target_namespace =
1067 web_contents()->GetController().GetDefaultSessionStorageNamespace();
1068 SessionStorageNamespace* prerender_namespace =
1069 prerender_data_->contents()->GetSessionStorageNamespace();
1070
1071 prerender_namespace->Merge(
1072 true, prerender_data_->contents()->child_id(),
1073 target_namespace,
1074 base::Bind(&PrerenderManager::PendingSwap::OnMergeCompleted,
1075 weak_factory_.GetWeakPtr()));
1076
1077 merge_timeout_.Start(
1078 FROM_HERE,
1079 base::TimeDelta::FromMilliseconds(
1080 kSessionStorageNamespaceMergeTimeoutMs),
1081 this, &PrerenderManager::PendingSwap::OnMergeTimeout);
1082 }
1083
AboutToNavigateRenderView(RenderViewHost * render_view_host)1084 void PrerenderManager::PendingSwap::AboutToNavigateRenderView(
1085 RenderViewHost* render_view_host) {
1086 if (seen_target_route_id_) {
1087 // A second navigation began browser-side.
1088 prerender_data_->ClearPendingSwap();
1089 return;
1090 }
1091
1092 seen_target_route_id_ = true;
1093 target_route_id_ = PrerenderTracker::ChildRouteIdPair(
1094 render_view_host->GetMainFrame()->GetProcess()->GetID(),
1095 render_view_host->GetMainFrame()->GetRoutingID());
1096 manager_->prerender_tracker()->AddPrerenderPendingSwap(
1097 target_route_id_, url_);
1098 }
1099
DidStartProvisionalLoadForFrame(content::RenderFrameHost * render_frame_host,const GURL & validated_url,bool is_error_page,bool is_iframe_srcdoc)1100 void PrerenderManager::PendingSwap::DidStartProvisionalLoadForFrame(
1101 content::RenderFrameHost* render_frame_host,
1102 const GURL& validated_url,
1103 bool is_error_page,
1104 bool is_iframe_srcdoc) {
1105 if (render_frame_host->GetParent())
1106 return;
1107
1108 // We must only cancel the pending swap if the url navigated to is not
1109 // the URL being attempted to be swapped in. That's because in the normal
1110 // flow, a ProvisionalChangeToMainFrameUrl will happen for the URL attempted
1111 // to be swapped in immediately after the pending swap has issued its merge.
1112 if (validated_url != url_)
1113 prerender_data_->ClearPendingSwap();
1114 }
1115
DidCommitProvisionalLoadForFrame(content::RenderFrameHost * render_frame_host,const GURL & validated_url,ui::PageTransition transition_type)1116 void PrerenderManager::PendingSwap::DidCommitProvisionalLoadForFrame(
1117 content::RenderFrameHost* render_frame_host,
1118 const GURL& validated_url,
1119 ui::PageTransition transition_type) {
1120 if (render_frame_host->GetParent())
1121 return;
1122 prerender_data_->ClearPendingSwap();
1123 }
1124
DidFailProvisionalLoad(content::RenderFrameHost * render_frame_host,const GURL & validated_url,int error_code,const base::string16 & error_description)1125 void PrerenderManager::PendingSwap::DidFailProvisionalLoad(
1126 content::RenderFrameHost* render_frame_host,
1127 const GURL& validated_url,
1128 int error_code,
1129 const base::string16& error_description) {
1130 if (render_frame_host->GetParent())
1131 return;
1132 prerender_data_->ClearPendingSwap();
1133 }
1134
WebContentsDestroyed()1135 void PrerenderManager::PendingSwap::WebContentsDestroyed() {
1136 prerender_data_->ClearPendingSwap();
1137 }
1138
RecordEvent(PrerenderEvent event) const1139 void PrerenderManager::PendingSwap::RecordEvent(PrerenderEvent event) const {
1140 manager_->RecordEvent(prerender_data_->contents(), event);
1141 }
1142
OnMergeCompleted(SessionStorageNamespace::MergeResult result)1143 void PrerenderManager::PendingSwap::OnMergeCompleted(
1144 SessionStorageNamespace::MergeResult result) {
1145 UMA_HISTOGRAM_TIMES("Prerender.SessionStorageNamespaceMergeTime",
1146 base::TimeTicks::Now() - start_time_);
1147 RecordEvent(PRERENDER_EVENT_MERGE_RESULT_MERGE_DONE);
1148
1149 // Log the exact merge result in a histogram.
1150 switch (result) {
1151 case SessionStorageNamespace::MERGE_RESULT_NAMESPACE_NOT_FOUND:
1152 RecordEvent(PRERENDER_EVENT_MERGE_RESULT_RESULT_NAMESPACE_NOT_FOUND);
1153 break;
1154 case SessionStorageNamespace::MERGE_RESULT_NAMESPACE_NOT_ALIAS:
1155 RecordEvent(PRERENDER_EVENT_MERGE_RESULT_RESULT_NAMESPACE_NOT_ALIAS);
1156 break;
1157 case SessionStorageNamespace::MERGE_RESULT_NOT_LOGGING:
1158 RecordEvent(PRERENDER_EVENT_MERGE_RESULT_RESULT_NOT_LOGGING);
1159 break;
1160 case SessionStorageNamespace::MERGE_RESULT_NO_TRANSACTIONS:
1161 RecordEvent(PRERENDER_EVENT_MERGE_RESULT_RESULT_NO_TRANSACTIONS);
1162 break;
1163 case SessionStorageNamespace::MERGE_RESULT_TOO_MANY_TRANSACTIONS:
1164 RecordEvent(PRERENDER_EVENT_MERGE_RESULT_RESULT_TOO_MANY_TRANSACTIONS);
1165 break;
1166 case SessionStorageNamespace::MERGE_RESULT_NOT_MERGEABLE:
1167 RecordEvent(PRERENDER_EVENT_MERGE_RESULT_RESULT_NOT_MERGEABLE);
1168 break;
1169 case SessionStorageNamespace::MERGE_RESULT_MERGEABLE:
1170 RecordEvent(PRERENDER_EVENT_MERGE_RESULT_RESULT_MERGEABLE);
1171 break;
1172 default:
1173 NOTREACHED();
1174 }
1175
1176 if (result != SessionStorageNamespace::MERGE_RESULT_MERGEABLE &&
1177 result != SessionStorageNamespace::MERGE_RESULT_NO_TRANSACTIONS) {
1178 RecordEvent(PRERENDER_EVENT_MERGE_RESULT_MERGE_FAILED);
1179 prerender_data_->ClearPendingSwap();
1180 return;
1181 }
1182
1183 RecordEvent(PRERENDER_EVENT_MERGE_RESULT_SWAPPING_IN);
1184
1185 // Note that SwapInternal will, on success, delete |prerender_data_| and
1186 // |this|. It will also delete |this| in some failure cases. Pass in a new
1187 // GURL object rather than a reference to |url_|. Also hold on to |manager_|
1188 // and |prerender_data_|.
1189 //
1190 // TODO(davidben): Can we make this less fragile?
1191 PrerenderManager* manager = manager_;
1192 PrerenderData* prerender_data = prerender_data_;
1193 WebContents* new_web_contents =
1194 manager_->SwapInternal(GURL(url_),
1195 web_contents(),
1196 prerender_data_,
1197 should_replace_current_entry_);
1198 if (!new_web_contents) {
1199 manager->RecordEvent(prerender_data->contents(),
1200 PRERENDER_EVENT_MERGE_RESULT_SWAPIN_FAILED);
1201 // Depending on whether SwapInternal called Destroy() or simply failed to
1202 // swap, |this| may or may not be deleted. Either way, if the swap failed,
1203 // |prerender_data| is deleted asynchronously, so this call is a no-op if
1204 // |this| is already gone.
1205 prerender_data->ClearPendingSwap();
1206 }
1207 }
1208
OnMergeTimeout()1209 void PrerenderManager::PendingSwap::OnMergeTimeout() {
1210 RecordEvent(PRERENDER_EVENT_MERGE_RESULT_TIMED_OUT);
1211 prerender_data_->ClearPendingSwap();
1212 }
1213
SetPrerenderContentsFactory(PrerenderContents::Factory * prerender_contents_factory)1214 void PrerenderManager::SetPrerenderContentsFactory(
1215 PrerenderContents::Factory* prerender_contents_factory) {
1216 DCHECK(CalledOnValidThread());
1217 prerender_contents_factory_.reset(prerender_contents_factory);
1218 }
1219
SourceNavigatedAway(PrerenderData * prerender_data)1220 void PrerenderManager::SourceNavigatedAway(PrerenderData* prerender_data) {
1221 // The expiry time of our prerender data will likely change because of
1222 // this navigation. This requires a resort of active_prerenders_.
1223 ScopedVector<PrerenderData>::iterator it =
1224 std::find(active_prerenders_.begin(), active_prerenders_.end(),
1225 prerender_data);
1226 if (it == active_prerenders_.end())
1227 return;
1228
1229 (*it)->set_expiry_time(
1230 std::min((*it)->expiry_time(),
1231 GetExpiryTimeForNavigatedAwayPrerender()));
1232 SortActivePrerenders();
1233 }
1234
GetURLRequestContext()1235 net::URLRequestContextGetter* PrerenderManager::GetURLRequestContext() {
1236 return content::BrowserContext::GetDefaultStoragePartition(profile_)->
1237 GetURLRequestContext();
1238 }
1239
1240
1241 // private
AddPrerender(Origin origin,int process_id,const GURL & url_arg,const content::Referrer & referrer,const gfx::Size & size,SessionStorageNamespace * session_storage_namespace)1242 PrerenderHandle* PrerenderManager::AddPrerender(
1243 Origin origin,
1244 int process_id,
1245 const GURL& url_arg,
1246 const content::Referrer& referrer,
1247 const gfx::Size& size,
1248 SessionStorageNamespace* session_storage_namespace) {
1249 DCHECK(CalledOnValidThread());
1250
1251 if (!IsEnabled())
1252 return NULL;
1253
1254 if ((origin == ORIGIN_LINK_REL_PRERENDER_CROSSDOMAIN ||
1255 origin == ORIGIN_LINK_REL_PRERENDER_SAMEDOMAIN) &&
1256 IsGoogleSearchResultURL(referrer.url)) {
1257 origin = ORIGIN_GWS_PRERENDER;
1258 }
1259
1260 GURL url = url_arg;
1261 GURL alias_url;
1262 uint8 experiment = GetQueryStringBasedExperiment(url_arg);
1263 if (IsControlGroup(experiment) &&
1264 MaybeGetQueryStringBasedAliasURL(url, &alias_url)) {
1265 url = alias_url;
1266 }
1267
1268 // From here on, we will record a FinalStatus so we need to register with the
1269 // histogram tracking.
1270 histograms_->RecordPrerender(origin, url_arg);
1271
1272 if (PrerenderData* preexisting_prerender_data =
1273 FindPrerenderData(url, session_storage_namespace)) {
1274 RecordFinalStatusWithoutCreatingPrerenderContents(
1275 url, origin, experiment, FINAL_STATUS_DUPLICATE);
1276 return new PrerenderHandle(preexisting_prerender_data);
1277 }
1278
1279 // Do not prerender if there are too many render processes, and we would
1280 // have to use an existing one. We do not want prerendering to happen in
1281 // a shared process, so that we can always reliably lower the CPU
1282 // priority for prerendering.
1283 // In single-process mode, ShouldTryToUseExistingProcessHost() always returns
1284 // true, so that case needs to be explicitly checked for.
1285 // TODO(tburkard): Figure out how to cancel prerendering in the opposite
1286 // case, when a new tab is added to a process used for prerendering.
1287 // TODO(ppi): Check whether there are usually enough render processes
1288 // available on Android. If not, kill an existing renderers so that we can
1289 // create a new one.
1290 if (content::RenderProcessHost::ShouldTryToUseExistingProcessHost(
1291 profile_, url) &&
1292 !content::RenderProcessHost::run_renderer_in_process()) {
1293 RecordFinalStatusWithoutCreatingPrerenderContents(
1294 url, origin, experiment, FINAL_STATUS_TOO_MANY_PROCESSES);
1295 return NULL;
1296 }
1297
1298 // Check if enough time has passed since the last prerender.
1299 if (!DoesRateLimitAllowPrerender(origin)) {
1300 // Cancel the prerender. We could add it to the pending prerender list but
1301 // this doesn't make sense as the next prerender request will be triggered
1302 // by a navigation and is unlikely to be the same site.
1303 RecordFinalStatusWithoutCreatingPrerenderContents(
1304 url, origin, experiment, FINAL_STATUS_RATE_LIMIT_EXCEEDED);
1305 return NULL;
1306 }
1307
1308 if (IsPrerenderCookieStoreEnabled() && !cookie_store_loaded()) {
1309 // Only prerender if the cookie store for this profile has been loaded.
1310 // This is required by PrerenderCookieMonster.
1311 RecordFinalStatusWithoutCreatingPrerenderContents(
1312 url, origin, experiment, FINAL_STATUS_COOKIE_STORE_NOT_LOADED);
1313 return NULL;
1314 }
1315
1316 PrerenderContents* prerender_contents = CreatePrerenderContents(
1317 url, referrer, origin, experiment);
1318 DCHECK(prerender_contents);
1319 active_prerenders_.push_back(
1320 new PrerenderData(this, prerender_contents,
1321 GetExpiryTimeForNewPrerender(origin)));
1322 if (!prerender_contents->Init()) {
1323 DCHECK(active_prerenders_.end() ==
1324 FindIteratorForPrerenderContents(prerender_contents));
1325 return NULL;
1326 }
1327
1328 histograms_->RecordPrerenderStarted(origin);
1329 DCHECK(!prerender_contents->prerendering_has_started());
1330
1331 PrerenderHandle* prerender_handle =
1332 new PrerenderHandle(active_prerenders_.back());
1333 SortActivePrerenders();
1334
1335 last_prerender_start_time_ = GetCurrentTimeTicks();
1336
1337 gfx::Size contents_size =
1338 size.IsEmpty() ? config_.default_tab_bounds.size() : size;
1339
1340 net::URLRequestContextGetter* request_context =
1341 (IsPrerenderCookieStoreEnabled() ? GetURLRequestContext() : NULL);
1342
1343 prerender_contents->StartPrerendering(process_id, contents_size,
1344 session_storage_namespace,
1345 request_context);
1346
1347 DCHECK(IsControlGroup(experiment) ||
1348 prerender_contents->prerendering_has_started() ||
1349 (origin == ORIGIN_LOCAL_PREDICTOR &&
1350 IsLocalPredictorPrerenderAlwaysControlEnabled()));
1351
1352 if (GetMode() == PRERENDER_MODE_EXPERIMENT_MULTI_PRERENDER_GROUP)
1353 histograms_->RecordConcurrency(active_prerenders_.size());
1354
1355 // Query the history to see if the URL being prerendered has ever been
1356 // visited before.
1357 HistoryService* history_service = HistoryServiceFactory::GetForProfile(
1358 profile_, Profile::EXPLICIT_ACCESS);
1359 if (history_service) {
1360 history_service->QueryURL(
1361 url,
1362 false,
1363 base::Bind(&PrerenderManager::OnHistoryServiceDidQueryURL,
1364 base::Unretained(this),
1365 origin,
1366 experiment),
1367 &query_url_tracker_);
1368 }
1369
1370 StartSchedulingPeriodicCleanups();
1371 return prerender_handle;
1372 }
1373
StartSchedulingPeriodicCleanups()1374 void PrerenderManager::StartSchedulingPeriodicCleanups() {
1375 DCHECK(CalledOnValidThread());
1376 if (repeating_timer_.IsRunning())
1377 return;
1378 repeating_timer_.Start(FROM_HERE,
1379 base::TimeDelta::FromMilliseconds(kPeriodicCleanupIntervalMs),
1380 this,
1381 &PrerenderManager::PeriodicCleanup);
1382 }
1383
StopSchedulingPeriodicCleanups()1384 void PrerenderManager::StopSchedulingPeriodicCleanups() {
1385 DCHECK(CalledOnValidThread());
1386 repeating_timer_.Stop();
1387 }
1388
PeriodicCleanup()1389 void PrerenderManager::PeriodicCleanup() {
1390 DCHECK(CalledOnValidThread());
1391
1392 base::ElapsedTimer resource_timer;
1393
1394 // Grab a copy of the current PrerenderContents pointers, so that we
1395 // will not interfere with potential deletions of the list.
1396 std::vector<PrerenderContents*>
1397 prerender_contents(active_prerenders_.size());
1398 std::transform(active_prerenders_.begin(), active_prerenders_.end(),
1399 prerender_contents.begin(),
1400 std::mem_fun(&PrerenderData::contents));
1401
1402 // And now check for prerenders using too much memory.
1403 std::for_each(prerender_contents.begin(), prerender_contents.end(),
1404 std::mem_fun(
1405 &PrerenderContents::DestroyWhenUsingTooManyResources));
1406
1407 // Measure how long the resource checks took. http://crbug.com/305419.
1408 UMA_HISTOGRAM_TIMES("Prerender.PeriodicCleanupResourceCheckTime",
1409 resource_timer.Elapsed());
1410
1411 base::ElapsedTimer cleanup_timer;
1412
1413 // Perform deferred cleanup work.
1414 DeleteOldWebContents();
1415 DeleteOldEntries();
1416 if (active_prerenders_.empty())
1417 StopSchedulingPeriodicCleanups();
1418
1419 to_delete_prerenders_.clear();
1420
1421 // Measure how long a the various cleanup tasks took. http://crbug.com/305419.
1422 UMA_HISTOGRAM_TIMES("Prerender.PeriodicCleanupDeleteContentsTime",
1423 cleanup_timer.Elapsed());
1424 }
1425
PostCleanupTask()1426 void PrerenderManager::PostCleanupTask() {
1427 DCHECK(CalledOnValidThread());
1428 base::MessageLoop::current()->PostTask(
1429 FROM_HERE,
1430 base::Bind(&PrerenderManager::PeriodicCleanup, AsWeakPtr()));
1431 }
1432
GetExpiryTimeForNewPrerender(Origin origin) const1433 base::TimeTicks PrerenderManager::GetExpiryTimeForNewPrerender(
1434 Origin origin) const {
1435 base::TimeDelta ttl = config_.time_to_live;
1436 if (origin == ORIGIN_LOCAL_PREDICTOR)
1437 ttl = base::TimeDelta::FromSeconds(GetLocalPredictorTTLSeconds());
1438 return GetCurrentTimeTicks() + ttl;
1439 }
1440
GetExpiryTimeForNavigatedAwayPrerender() const1441 base::TimeTicks PrerenderManager::GetExpiryTimeForNavigatedAwayPrerender()
1442 const {
1443 return GetCurrentTimeTicks() + config_.abandon_time_to_live;
1444 }
1445
DeleteOldEntries()1446 void PrerenderManager::DeleteOldEntries() {
1447 DCHECK(CalledOnValidThread());
1448 while (!active_prerenders_.empty()) {
1449 PrerenderData* prerender_data = active_prerenders_.front();
1450 DCHECK(prerender_data);
1451 DCHECK(prerender_data->contents());
1452
1453 if (prerender_data->expiry_time() > GetCurrentTimeTicks())
1454 return;
1455 prerender_data->contents()->Destroy(FINAL_STATUS_TIMED_OUT);
1456 }
1457 }
1458
GetCurrentTime() const1459 base::Time PrerenderManager::GetCurrentTime() const {
1460 return base::Time::Now();
1461 }
1462
GetCurrentTimeTicks() const1463 base::TimeTicks PrerenderManager::GetCurrentTimeTicks() const {
1464 return base::TimeTicks::Now();
1465 }
1466
CreatePrerenderContents(const GURL & url,const content::Referrer & referrer,Origin origin,uint8 experiment_id)1467 PrerenderContents* PrerenderManager::CreatePrerenderContents(
1468 const GURL& url,
1469 const content::Referrer& referrer,
1470 Origin origin,
1471 uint8 experiment_id) {
1472 DCHECK(CalledOnValidThread());
1473 return prerender_contents_factory_->CreatePrerenderContents(
1474 this, profile_, url, referrer, origin, experiment_id);
1475 }
1476
SortActivePrerenders()1477 void PrerenderManager::SortActivePrerenders() {
1478 std::sort(active_prerenders_.begin(), active_prerenders_.end(),
1479 PrerenderData::OrderByExpiryTime());
1480 }
1481
FindPrerenderData(const GURL & url,const SessionStorageNamespace * session_storage_namespace)1482 PrerenderManager::PrerenderData* PrerenderManager::FindPrerenderData(
1483 const GURL& url,
1484 const SessionStorageNamespace* session_storage_namespace) {
1485 for (ScopedVector<PrerenderData>::iterator it = active_prerenders_.begin();
1486 it != active_prerenders_.end(); ++it) {
1487 if ((*it)->contents()->Matches(url, session_storage_namespace))
1488 return *it;
1489 }
1490 return NULL;
1491 }
1492
1493 PrerenderManager::PrerenderData*
FindPrerenderDataForTargetContents(WebContents * target_contents)1494 PrerenderManager::FindPrerenderDataForTargetContents(
1495 WebContents* target_contents) {
1496 for (ScopedVector<PrerenderData>::iterator it = active_prerenders_.begin();
1497 it != active_prerenders_.end(); ++it) {
1498 if ((*it)->pending_swap() &&
1499 (*it)->pending_swap()->web_contents() == target_contents)
1500 return *it;
1501 }
1502 return NULL;
1503 }
1504
1505 ScopedVector<PrerenderManager::PrerenderData>::iterator
FindIteratorForPrerenderContents(PrerenderContents * prerender_contents)1506 PrerenderManager::FindIteratorForPrerenderContents(
1507 PrerenderContents* prerender_contents) {
1508 for (ScopedVector<PrerenderData>::iterator it = active_prerenders_.begin();
1509 it != active_prerenders_.end(); ++it) {
1510 if (prerender_contents == (*it)->contents())
1511 return it;
1512 }
1513 return active_prerenders_.end();
1514 }
1515
DoesRateLimitAllowPrerender(Origin origin) const1516 bool PrerenderManager::DoesRateLimitAllowPrerender(Origin origin) const {
1517 DCHECK(CalledOnValidThread());
1518 base::TimeDelta elapsed_time =
1519 GetCurrentTimeTicks() - last_prerender_start_time_;
1520 histograms_->RecordTimeBetweenPrerenderRequests(origin, elapsed_time);
1521 if (!config_.rate_limit_enabled)
1522 return true;
1523 // The LocalPredictor may issue multiple prerenders simultaneously (if so
1524 // configured), so no throttling.
1525 if (origin == ORIGIN_LOCAL_PREDICTOR)
1526 return true;
1527 return elapsed_time >=
1528 base::TimeDelta::FromMilliseconds(kMinTimeBetweenPrerendersMs);
1529 }
1530
DeleteOldWebContents()1531 void PrerenderManager::DeleteOldWebContents() {
1532 while (!old_web_contents_list_.empty()) {
1533 WebContents* web_contents = old_web_contents_list_.front();
1534 old_web_contents_list_.pop_front();
1535 // TODO(dominich): should we use Instant Unload Handler here?
1536 delete web_contents;
1537 }
1538 }
1539
CleanUpOldNavigations()1540 void PrerenderManager::CleanUpOldNavigations() {
1541 DCHECK(CalledOnValidThread());
1542
1543 // Cutoff. Navigations before this cutoff can be discarded.
1544 base::TimeTicks cutoff = GetCurrentTimeTicks() -
1545 base::TimeDelta::FromMilliseconds(kNavigationRecordWindowMs);
1546 while (!navigations_.empty()) {
1547 if (navigations_.front().time > cutoff)
1548 break;
1549 navigations_.pop_front();
1550 }
1551 }
1552
ScheduleDeleteOldWebContents(WebContents * tab,OnCloseWebContentsDeleter * deleter)1553 void PrerenderManager::ScheduleDeleteOldWebContents(
1554 WebContents* tab,
1555 OnCloseWebContentsDeleter* deleter) {
1556 old_web_contents_list_.push_back(tab);
1557 PostCleanupTask();
1558
1559 if (deleter) {
1560 ScopedVector<OnCloseWebContentsDeleter>::iterator i = std::find(
1561 on_close_web_contents_deleters_.begin(),
1562 on_close_web_contents_deleters_.end(),
1563 deleter);
1564 DCHECK(i != on_close_web_contents_deleters_.end());
1565 on_close_web_contents_deleters_.erase(i);
1566 }
1567 }
1568
AddToHistory(PrerenderContents * contents)1569 void PrerenderManager::AddToHistory(PrerenderContents* contents) {
1570 PrerenderHistory::Entry entry(contents->prerender_url(),
1571 contents->final_status(),
1572 contents->origin(),
1573 base::Time::Now());
1574 prerender_history_->AddEntry(entry);
1575 }
1576
GetActivePrerendersAsValue() const1577 base::Value* PrerenderManager::GetActivePrerendersAsValue() const {
1578 base::ListValue* list_value = new base::ListValue();
1579 for (ScopedVector<PrerenderData>::const_iterator it =
1580 active_prerenders_.begin();
1581 it != active_prerenders_.end(); ++it) {
1582 if (base::Value* prerender_value = (*it)->contents()->GetAsValue())
1583 list_value->Append(prerender_value);
1584 }
1585 return list_value;
1586 }
1587
DestroyAllContents(FinalStatus final_status)1588 void PrerenderManager::DestroyAllContents(FinalStatus final_status) {
1589 DeleteOldWebContents();
1590 while (!active_prerenders_.empty()) {
1591 PrerenderContents* contents = active_prerenders_.front()->contents();
1592 contents->Destroy(final_status);
1593 }
1594 to_delete_prerenders_.clear();
1595 }
1596
DestroyAndMarkMatchCompleteAsUsed(PrerenderContents * prerender_contents,FinalStatus final_status)1597 void PrerenderManager::DestroyAndMarkMatchCompleteAsUsed(
1598 PrerenderContents* prerender_contents,
1599 FinalStatus final_status) {
1600 prerender_contents->set_match_complete_status(
1601 PrerenderContents::MATCH_COMPLETE_REPLACED);
1602 histograms_->RecordFinalStatus(prerender_contents->origin(),
1603 prerender_contents->experiment_id(),
1604 PrerenderContents::MATCH_COMPLETE_REPLACEMENT,
1605 FINAL_STATUS_WOULD_HAVE_BEEN_USED);
1606 prerender_contents->Destroy(final_status);
1607 }
1608
RecordFinalStatusWithoutCreatingPrerenderContents(const GURL & url,Origin origin,uint8 experiment_id,FinalStatus final_status) const1609 void PrerenderManager::RecordFinalStatusWithoutCreatingPrerenderContents(
1610 const GURL& url, Origin origin, uint8 experiment_id,
1611 FinalStatus final_status) const {
1612 PrerenderHistory::Entry entry(url, final_status, origin, base::Time::Now());
1613 prerender_history_->AddEntry(entry);
1614 RecordFinalStatusWithMatchCompleteStatus(
1615 origin, experiment_id,
1616 PrerenderContents::MATCH_COMPLETE_DEFAULT,
1617 final_status);
1618 }
1619
Observe(int type,const content::NotificationSource & source,const content::NotificationDetails & details)1620 void PrerenderManager::Observe(int type,
1621 const content::NotificationSource& source,
1622 const content::NotificationDetails& details) {
1623 switch (type) {
1624 case chrome::NOTIFICATION_COOKIE_CHANGED: {
1625 Profile* profile = content::Source<Profile>(source).ptr();
1626 if (!profile || !profile_->IsSameProfile(profile) ||
1627 profile->IsOffTheRecord()) {
1628 return;
1629 }
1630 CookieChanged(content::Details<ChromeCookieDetails>(details).ptr());
1631 break;
1632 }
1633 case chrome::NOTIFICATION_PROFILE_DESTROYED:
1634 DestroyAllContents(FINAL_STATUS_PROFILE_DESTROYED);
1635 on_close_web_contents_deleters_.clear();
1636 break;
1637 default:
1638 NOTREACHED() << "Unexpected notification sent.";
1639 break;
1640 }
1641 }
1642
OnCreatingAudioStream(int render_process_id,int render_frame_id)1643 void PrerenderManager::OnCreatingAudioStream(int render_process_id,
1644 int render_frame_id) {
1645 content::RenderFrameHost* render_frame_host =
1646 content::RenderFrameHost::FromID(render_process_id, render_frame_id);
1647 WebContents* tab = WebContents::FromRenderFrameHost(render_frame_host);
1648 if (!tab)
1649 return;
1650
1651 PrerenderContents* prerender_contents = GetPrerenderContents(tab);
1652 if (!prerender_contents)
1653 return;
1654
1655 prerender_contents->Destroy(prerender::FINAL_STATUS_CREATING_AUDIO_STREAM);
1656 }
1657
RecordLikelyLoginOnURL(const GURL & url)1658 void PrerenderManager::RecordLikelyLoginOnURL(const GURL& url) {
1659 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
1660 if (!url.SchemeIsHTTPOrHTTPS())
1661 return;
1662 if (logged_in_predictor_table_.get()) {
1663 BrowserThread::PostTask(
1664 BrowserThread::DB,
1665 FROM_HERE,
1666 base::Bind(&LoggedInPredictorTable::AddDomainFromURL,
1667 logged_in_predictor_table_,
1668 url));
1669 }
1670 std::string key = LoggedInPredictorTable::GetKey(url);
1671 if (!logged_in_state_.get())
1672 return;
1673 if (logged_in_state_->count(key))
1674 return;
1675 (*logged_in_state_)[key] = base::Time::Now().ToInternalValue();
1676 }
1677
CheckIfLikelyLoggedInOnURL(const GURL & url,bool * lookup_result,bool * database_was_present,const base::Closure & result_cb)1678 void PrerenderManager::CheckIfLikelyLoggedInOnURL(
1679 const GURL& url,
1680 bool* lookup_result,
1681 bool* database_was_present,
1682 const base::Closure& result_cb) {
1683 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
1684 if (!logged_in_predictor_table_.get()) {
1685 *database_was_present = false;
1686 *lookup_result = false;
1687 BrowserThread::PostTask(BrowserThread::UI, FROM_HERE, result_cb);
1688 return;
1689 }
1690 BrowserThread::PostTaskAndReply(
1691 BrowserThread::DB, FROM_HERE,
1692 base::Bind(&LoggedInPredictorTable::HasUserLoggedIn,
1693 logged_in_predictor_table_,
1694 url,
1695 lookup_result,
1696 database_was_present),
1697 result_cb);
1698 }
1699
1700
CookieChanged(ChromeCookieDetails * details)1701 void PrerenderManager::CookieChanged(ChromeCookieDetails* details) {
1702 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
1703
1704 if (!logged_in_predictor_table_.get())
1705 return;
1706
1707 // We only care when a cookie has been removed.
1708 if (!details->removed)
1709 return;
1710
1711 std::string domain_key =
1712 LoggedInPredictorTable::GetKeyFromDomain(details->cookie->Domain());
1713
1714 // If we have no record of this domain as a potentially logged in domain,
1715 // nothing to do here.
1716 if (logged_in_state_.get() && logged_in_state_->count(domain_key) < 1)
1717 return;
1718
1719 net::URLRequestContextGetter* rq_context = profile_->GetRequestContext();
1720 if (!rq_context)
1721 return;
1722
1723 BrowserThread::PostTask(
1724 BrowserThread::IO, FROM_HERE,
1725 base::Bind(&CheckIfCookiesExistForDomainOnIOThread,
1726 base::Unretained(rq_context),
1727 domain_key,
1728 base::Bind(
1729 &PrerenderManager::CookieChangedAnyCookiesLeftLookupResult,
1730 AsWeakPtr(),
1731 domain_key)
1732 ));
1733 }
1734
CookieChangedAnyCookiesLeftLookupResult(const std::string & domain_key,bool cookies_exist)1735 void PrerenderManager::CookieChangedAnyCookiesLeftLookupResult(
1736 const std::string& domain_key,
1737 bool cookies_exist) {
1738 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
1739
1740 if (cookies_exist)
1741 return;
1742
1743 if (logged_in_predictor_table_.get()) {
1744 BrowserThread::PostTask(BrowserThread::DB,
1745 FROM_HERE,
1746 base::Bind(&LoggedInPredictorTable::DeleteDomain,
1747 logged_in_predictor_table_,
1748 domain_key));
1749 }
1750
1751 if (logged_in_state_.get())
1752 logged_in_state_->erase(domain_key);
1753 }
1754
LoggedInPredictorDataReceived(scoped_ptr<LoggedInStateMap> new_map)1755 void PrerenderManager::LoggedInPredictorDataReceived(
1756 scoped_ptr<LoggedInStateMap> new_map) {
1757 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
1758 logged_in_state_.swap(new_map);
1759 }
1760
RecordEvent(PrerenderContents * contents,PrerenderEvent event) const1761 void PrerenderManager::RecordEvent(PrerenderContents* contents,
1762 PrerenderEvent event) const {
1763 if (!contents)
1764 histograms_->RecordEvent(ORIGIN_NONE, kNoExperiment, event);
1765 else
1766 histograms_->RecordEvent(contents->origin(), contents->experiment_id(),
1767 event);
1768 }
1769
1770 // static
RecordCookieEvent(int process_id,int frame_id,const GURL & url,const GURL & frame_url,bool is_for_blocking_resource,PrerenderContents::CookieEvent event,const net::CookieList * cookie_list)1771 void PrerenderManager::RecordCookieEvent(int process_id,
1772 int frame_id,
1773 const GURL& url,
1774 const GURL& frame_url,
1775 bool is_for_blocking_resource,
1776 PrerenderContents::CookieEvent event,
1777 const net::CookieList* cookie_list) {
1778 RenderFrameHost* rfh = RenderFrameHost::FromID(process_id, frame_id);
1779 WebContents* web_contents = WebContents::FromRenderFrameHost(rfh);
1780 if (!web_contents)
1781 return;
1782
1783 bool is_main_frame = (rfh == web_contents->GetMainFrame());
1784
1785 bool is_third_party_cookie =
1786 (!frame_url.is_empty() &&
1787 !net::registry_controlled_domains::SameDomainOrHost(
1788 url, frame_url,
1789 net::registry_controlled_domains::INCLUDE_PRIVATE_REGISTRIES));
1790
1791 PrerenderContents* prerender_contents =
1792 PrerenderContents::FromWebContents(web_contents);
1793
1794 if (!prerender_contents)
1795 return;
1796
1797 base::Time earliest_create_date;
1798 if (event == PrerenderContents::COOKIE_EVENT_SEND) {
1799 if (!cookie_list || cookie_list->empty())
1800 return;
1801 for (size_t i = 0; i < cookie_list->size(); i++) {
1802 if (earliest_create_date.is_null() ||
1803 (*cookie_list)[i].CreationDate() < earliest_create_date) {
1804 earliest_create_date = (*cookie_list)[i].CreationDate();
1805 }
1806 }
1807 }
1808
1809 prerender_contents->RecordCookieEvent(event,
1810 is_main_frame && url == frame_url,
1811 is_third_party_cookie,
1812 is_for_blocking_resource,
1813 earliest_create_date);
1814 }
1815
RecordCookieStatus(Origin origin,uint8 experiment_id,int cookie_status) const1816 void PrerenderManager::RecordCookieStatus(Origin origin,
1817 uint8 experiment_id,
1818 int cookie_status) const {
1819 histograms_->RecordCookieStatus(origin, experiment_id, cookie_status);
1820 }
1821
RecordCookieSendType(Origin origin,uint8 experiment_id,int cookie_send_type) const1822 void PrerenderManager::RecordCookieSendType(Origin origin,
1823 uint8 experiment_id,
1824 int cookie_send_type) const {
1825 histograms_->RecordCookieSendType(origin, experiment_id, cookie_send_type);
1826 }
1827
OnHistoryServiceDidQueryURL(Origin origin,uint8 experiment_id,bool success,const history::URLRow & url_row,const history::VisitVector &)1828 void PrerenderManager::OnHistoryServiceDidQueryURL(
1829 Origin origin,
1830 uint8 experiment_id,
1831 bool success,
1832 const history::URLRow& url_row,
1833 const history::VisitVector& /*visits*/) {
1834 histograms_->RecordPrerenderPageVisitedStatus(origin, experiment_id, success);
1835 }
1836
1837 // static
HangSessionStorageMergesForTesting()1838 void PrerenderManager::HangSessionStorageMergesForTesting() {
1839 g_hang_session_storage_merges_for_testing = true;
1840 }
1841
RecordNetworkBytes(Origin origin,bool used,int64 prerender_bytes)1842 void PrerenderManager::RecordNetworkBytes(Origin origin,
1843 bool used,
1844 int64 prerender_bytes) {
1845 if (!ActuallyPrerendering())
1846 return;
1847 int64 recent_profile_bytes =
1848 profile_network_bytes_ - last_recorded_profile_network_bytes_;
1849 last_recorded_profile_network_bytes_ = profile_network_bytes_;
1850 DCHECK_GE(recent_profile_bytes, 0);
1851 histograms_->RecordNetworkBytes(
1852 origin, used, prerender_bytes, recent_profile_bytes);
1853 }
1854
IsEnabled() const1855 bool PrerenderManager::IsEnabled() const {
1856 DCHECK(CalledOnValidThread());
1857
1858 return chrome_browser_net::CanPrefetchAndPrerenderUI(profile_->GetPrefs());
1859 }
1860
AddProfileNetworkBytesIfEnabled(int64 bytes)1861 void PrerenderManager::AddProfileNetworkBytesIfEnabled(int64 bytes) {
1862 DCHECK_GE(bytes, 0);
1863 if (IsEnabled() && ActuallyPrerendering())
1864 profile_network_bytes_ += bytes;
1865 }
1866
OnCookieStoreLoaded()1867 void PrerenderManager::OnCookieStoreLoaded() {
1868 cookie_store_loaded_ = true;
1869 if (!on_cookie_store_loaded_cb_for_testing_.is_null())
1870 on_cookie_store_loaded_cb_for_testing_.Run();
1871 }
1872
AddPrerenderProcessHost(content::RenderProcessHost * process_host)1873 void PrerenderManager::AddPrerenderProcessHost(
1874 content::RenderProcessHost* process_host) {
1875 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
1876 DCHECK(prerender_process_hosts_.find(process_host) ==
1877 prerender_process_hosts_.end());
1878 prerender_process_hosts_.insert(process_host);
1879 process_host->AddObserver(this);
1880 }
1881
MayReuseProcessHost(content::RenderProcessHost * process_host)1882 bool PrerenderManager::MayReuseProcessHost(
1883 content::RenderProcessHost* process_host) {
1884 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
1885 // If prerender cookie stores are disabled, there is no need to require
1886 // isolated prerender processes.
1887 if (!IsPrerenderCookieStoreEnabled())
1888 return true;
1889 return (prerender_process_hosts_.find(process_host) ==
1890 prerender_process_hosts_.end());
1891 }
1892
RenderProcessHostDestroyed(content::RenderProcessHost * host)1893 void PrerenderManager::RenderProcessHostDestroyed(
1894 content::RenderProcessHost* host) {
1895 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
1896 prerender_process_hosts_.erase(host);
1897 BrowserThread::PostTask(
1898 BrowserThread::IO, FROM_HERE,
1899 base::Bind(&PrerenderTracker::RemovePrerenderCookieStoreOnIOThread,
1900 base::Unretained(prerender_tracker()), host->GetID(), false));
1901 }
1902
1903 } // namespace prerender
1904