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