prerender_manager.cc revision 7d4cd473f85ac64c3747c96c277f9e506a0d2246
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.h"
21#include "base/values.h"
22#include "chrome/browser/browser_process.h"
23#include "chrome/browser/common/cancelable_request.h"
24#include "chrome/browser/favicon/favicon_tab_helper.h"
25#include "chrome/browser/net/chrome_cookie_notification_details.h"
26#include "chrome/browser/predictors/predictor_database.h"
27#include "chrome/browser/predictors/predictor_database_factory.h"
28#include "chrome/browser/prerender/prerender_condition.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/ui/tab_contents/core_tab_helper.h"
42#include "chrome/browser/ui/tab_contents/core_tab_helper_delegate.h"
43#include "chrome/common/chrome_notification_types.h"
44#include "chrome/common/chrome_switches.h"
45#include "chrome/common/pref_names.h"
46#include "chrome/common/prerender_messages.h"
47#include "content/public/browser/browser_thread.h"
48#include "content/public/browser/devtools_agent_host.h"
49#include "content/public/browser/navigation_controller.h"
50#include "content/public/browser/notification_service.h"
51#include "content/public/browser/notification_source.h"
52#include "content/public/browser/render_process_host.h"
53#include "content/public/browser/render_view_host.h"
54#include "content/public/browser/session_storage_namespace.h"
55#include "content/public/browser/web_contents.h"
56#include "content/public/browser/web_contents_delegate.h"
57#include "content/public/browser/web_contents_view.h"
58#include "content/public/common/favicon_url.h"
59#include "extensions/common/constants.h"
60#include "net/url_request/url_request_context.h"
61#include "net/url_request/url_request_context_getter.h"
62
63using content::BrowserThread;
64using content::RenderViewHost;
65using content::SessionStorageNamespace;
66using content::WebContents;
67using predictors::LoggedInPredictorTable;
68
69namespace prerender {
70
71namespace {
72
73// Time interval at which periodic cleanups are performed.
74const int kPeriodicCleanupIntervalMs = 1000;
75
76// Valid HTTP methods for prerendering.
77const char* const kValidHttpMethods[] = {
78  "GET",
79  "HEAD",
80  "OPTIONS",
81  "POST",
82  "TRACE",
83};
84
85// Length of prerender history, for display in chrome://net-internals
86const int kHistoryLength = 100;
87
88// Indicates whether a Prerender has been cancelled such that we need
89// a dummy replacement for the purpose of recording the correct PPLT for
90// the Match Complete case.
91// Traditionally, "Match" means that a prerendered page was actually visited &
92// the prerender was used.  Our goal is to have "Match" cases line up in the
93// control group & the experiment group, so that we can make meaningful
94// comparisons of improvements.  However, in the control group, since we don't
95// actually perform prerenders, many of the cancellation reasons cannot be
96// detected.  Therefore, in the Prerender group, when we cancel for one of these
97// reasons, we keep track of a dummy Prerender representing what we would
98// have in the control group.  If that dummy prerender in the prerender group
99// would then be swapped in (but isn't actually b/c it's a dummy), we record
100// this as a MatchComplete.  This allows us to compare MatchComplete's
101// across Prerender & Control group which ideally should be lining up.
102// This ensures that there is no bias in terms of the page load times
103// of the pages forming the difference between the two sets.
104
105bool NeedMatchCompleteDummyForFinalStatus(FinalStatus final_status) {
106  return final_status != FINAL_STATUS_USED &&
107      final_status != FINAL_STATUS_TIMED_OUT &&
108      final_status != FINAL_STATUS_MANAGER_SHUTDOWN &&
109      final_status != FINAL_STATUS_APP_TERMINATING &&
110      final_status != FINAL_STATUS_WINDOW_OPENER &&
111      final_status != FINAL_STATUS_CACHE_OR_HISTORY_CLEARED &&
112      final_status != FINAL_STATUS_CANCELLED &&
113      final_status != FINAL_STATUS_DEVTOOLS_ATTACHED &&
114      final_status != FINAL_STATUS_CROSS_SITE_NAVIGATION_PENDING;
115}
116
117void CheckIfCookiesExistForDomainResultOnUIThread(
118    const net::CookieMonster::HasCookiesForETLDP1Callback& callback,
119    bool cookies_exist) {
120  DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
121  callback.Run(cookies_exist);
122}
123
124void CheckIfCookiesExistForDomainResultOnIOThread(
125    const net::CookieMonster::HasCookiesForETLDP1Callback& callback,
126    bool cookies_exist) {
127  DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
128  BrowserThread::PostTask(
129      BrowserThread::UI,
130      FROM_HERE,
131      base::Bind(&CheckIfCookiesExistForDomainResultOnUIThread,
132                 callback,
133                 cookies_exist));
134}
135
136void CheckIfCookiesExistForDomainOnIOThread(
137    net::URLRequestContextGetter* rq_context,
138    const std::string& domain_key,
139    const net::CookieMonster::HasCookiesForETLDP1Callback& callback) {
140  DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
141  net::CookieStore* cookie_store =
142      rq_context->GetURLRequestContext()->cookie_store();
143  cookie_store->GetCookieMonster()->HasCookiesForETLDP1Async(
144      domain_key,
145      base::Bind(&CheckIfCookiesExistForDomainResultOnIOThread, callback));
146}
147
148}  // namespace
149
150class PrerenderManager::OnCloseWebContentsDeleter
151    : public content::WebContentsDelegate,
152      public base::SupportsWeakPtr<
153          PrerenderManager::OnCloseWebContentsDeleter> {
154 public:
155  OnCloseWebContentsDeleter(PrerenderManager* manager,
156                            WebContents* tab)
157      : manager_(manager),
158        tab_(tab) {
159    tab_->SetDelegate(this);
160    base::MessageLoop::current()->PostDelayedTask(FROM_HERE,
161        base::Bind(&OnCloseWebContentsDeleter::ScheduleWebContentsForDeletion,
162                   AsWeakPtr(), true),
163        base::TimeDelta::FromSeconds(kDeleteWithExtremePrejudiceSeconds));
164  }
165
166  virtual void CloseContents(WebContents* source) OVERRIDE {
167    DCHECK_EQ(tab_, source);
168    ScheduleWebContentsForDeletion(false);
169  }
170
171  virtual void SwappedOut(WebContents* source) OVERRIDE {
172    DCHECK_EQ(tab_, source);
173    ScheduleWebContentsForDeletion(false);
174  }
175
176  virtual bool ShouldSuppressDialogs() OVERRIDE {
177    return true;
178  }
179
180 private:
181  static const int kDeleteWithExtremePrejudiceSeconds = 3;
182
183  void ScheduleWebContentsForDeletion(bool timeout) {
184    tab_->SetDelegate(NULL);
185    manager_->ScheduleDeleteOldWebContents(tab_.release(), this);
186    UMA_HISTOGRAM_BOOLEAN("Prerender.TabContentsDeleterTimeout", timeout);
187  }
188
189  PrerenderManager* manager_;
190  scoped_ptr<WebContents> tab_;
191
192  DISALLOW_COPY_AND_ASSIGN(OnCloseWebContentsDeleter);
193};
194
195// static
196bool PrerenderManager::is_prefetch_enabled_ = false;
197
198// static
199int PrerenderManager::prerenders_per_session_count_ = 0;
200
201// static
202PrerenderManager::PrerenderManagerMode PrerenderManager::mode_ =
203    PRERENDER_MODE_ENABLED;
204
205struct PrerenderManager::NavigationRecord {
206  NavigationRecord(const GURL& url, base::TimeTicks time)
207      : url(url),
208        time(time) {
209  }
210
211  GURL url;
212  base::TimeTicks time;
213};
214
215PrerenderManager::PrerenderedWebContentsData::
216PrerenderedWebContentsData(Origin origin) : origin(origin) {
217}
218
219PrerenderManager::WouldBePrerenderedWebContentsData::
220WouldBePrerenderedWebContentsData(Origin origin)
221    : origin(origin),
222      state(WAITING_FOR_PROVISIONAL_LOAD) {
223}
224
225PrerenderManager::PrerenderManager(Profile* profile,
226                                   PrerenderTracker* prerender_tracker)
227    : enabled_(profile && profile->GetPrefs() &&
228          profile->GetPrefs()->GetBoolean(prefs::kNetworkPredictionEnabled)),
229      profile_(profile),
230      prerender_tracker_(prerender_tracker),
231      prerender_contents_factory_(PrerenderContents::CreateFactory()),
232      last_prerender_start_time_(GetCurrentTimeTicks() -
233          base::TimeDelta::FromMilliseconds(kMinTimeBetweenPrerendersMs)),
234      prerender_history_(new PrerenderHistory(kHistoryLength)),
235      histograms_(new PrerenderHistograms()) {
236  // There are some assumptions that the PrerenderManager is on the UI thread.
237  // Any other checks simply make sure that the PrerenderManager is accessed on
238  // the same thread that it was created on.
239  DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
240
241  if (IsLocalPredictorEnabled())
242    local_predictor_.reset(new PrerenderLocalPredictor(this));
243
244  if (IsLoggedInPredictorEnabled() && !profile_->IsOffTheRecord()) {
245    predictors::PredictorDatabase* predictor_db =
246        predictors::PredictorDatabaseFactory::GetForProfile(profile);
247    if (predictor_db) {
248      logged_in_predictor_table_ = predictor_db->logged_in_table();
249      scoped_ptr<LoggedInStateMap> new_state_map(new LoggedInStateMap);
250      LoggedInStateMap* new_state_map_ptr = new_state_map.get();
251      BrowserThread::PostTaskAndReply(
252          BrowserThread::DB, FROM_HERE,
253          base::Bind(&LoggedInPredictorTable::GetAllData,
254                     logged_in_predictor_table_,
255                     new_state_map_ptr),
256          base::Bind(&PrerenderManager::LoggedInPredictorDataReceived,
257                     AsWeakPtr(),
258                     base::Passed(&new_state_map)));
259    }
260  }
261
262  // Certain experiments override our default config_ values.
263  switch (PrerenderManager::GetMode()) {
264    case PrerenderManager::PRERENDER_MODE_EXPERIMENT_MULTI_PRERENDER_GROUP:
265      config_.max_link_concurrency = 4;
266      config_.max_link_concurrency_per_launcher = 2;
267      break;
268    case PrerenderManager::PRERENDER_MODE_EXPERIMENT_15MIN_TTL_GROUP:
269      config_.time_to_live = base::TimeDelta::FromMinutes(15);
270      break;
271    default:
272      break;
273  }
274
275  notification_registrar_.Add(
276      this, chrome::NOTIFICATION_COOKIE_CHANGED,
277      content::NotificationService::AllBrowserContextsAndSources());
278}
279
280PrerenderManager::~PrerenderManager() {
281  // The earlier call to BrowserContextKeyedService::Shutdown() should have
282  // emptied these vectors already.
283  DCHECK(active_prerenders_.empty());
284  DCHECK(to_delete_prerenders_.empty());
285}
286
287void PrerenderManager::Shutdown() {
288  DestroyAllContents(FINAL_STATUS_MANAGER_SHUTDOWN);
289  STLDeleteElements(&prerender_conditions_);
290  on_close_web_contents_deleters_.clear();
291  // Must happen before |profile_| is set to NULL as
292  // |local_predictor_| accesses it.
293  if (local_predictor_)
294    local_predictor_->Shutdown();
295  profile_ = NULL;
296
297  DCHECK(active_prerenders_.empty());
298}
299
300PrerenderHandle* PrerenderManager::AddPrerenderFromLinkRelPrerender(
301    int process_id,
302    int route_id,
303    const GURL& url,
304    const content::Referrer& referrer,
305    const gfx::Size& size) {
306#if defined(OS_ANDROID)
307  // TODO(jcivelli): http://crbug.com/113322 We should have an option to disable
308  //                link-prerender and enable omnibox-prerender only.
309  return NULL;
310#else
311  DCHECK(!size.IsEmpty());
312  Origin origin = ORIGIN_LINK_REL_PRERENDER_CROSSDOMAIN;
313  SessionStorageNamespace* session_storage_namespace = NULL;
314  // Unit tests pass in a process_id == -1.
315  if (process_id != -1) {
316    RenderViewHost* source_render_view_host =
317        RenderViewHost::FromID(process_id, route_id);
318    if (!source_render_view_host)
319      return NULL;
320    WebContents* source_web_contents =
321        WebContents::FromRenderViewHost(source_render_view_host);
322    if (!source_web_contents)
323      return NULL;
324    if (source_web_contents->GetURL().host() == url.host())
325      origin = ORIGIN_LINK_REL_PRERENDER_SAMEDOMAIN;
326    // TODO(ajwong): This does not correctly handle storage for isolated apps.
327    session_storage_namespace =
328        source_web_contents->GetController()
329            .GetDefaultSessionStorageNamespace();
330  }
331
332  // If the prerender request comes from a recently cancelled prerender that
333  // |this| still owns, then abort the prerender.
334  for (ScopedVector<PrerenderData>::iterator it = to_delete_prerenders_.begin();
335       it != to_delete_prerenders_.end(); ++it) {
336    PrerenderContents* prerender_contents = (*it)->contents();
337    int contents_child_id;
338    int contents_route_id;
339    if (prerender_contents->GetChildId(&contents_child_id) &&
340        prerender_contents->GetRouteId(&contents_route_id)) {
341      if (contents_child_id == process_id && contents_route_id == route_id)
342        return NULL;
343    }
344  }
345
346  if (PrerenderData* parent_prerender_data =
347          FindPrerenderDataForChildAndRoute(process_id, route_id)) {
348    // Instead of prerendering from inside of a running prerender, we will defer
349    // this request until its launcher is made visible.
350    if (PrerenderContents* contents = parent_prerender_data->contents()) {
351      PrerenderHandle* prerender_handle =
352          new PrerenderHandle(static_cast<PrerenderData*>(NULL));
353      scoped_ptr<PrerenderContents::PendingPrerenderInfo>
354          pending_prerender_info(new PrerenderContents::PendingPrerenderInfo(
355              prerender_handle->weak_ptr_factory_.GetWeakPtr(),
356              origin, url, referrer, size));
357
358      contents->AddPendingPrerender(pending_prerender_info.Pass());
359      return prerender_handle;
360    }
361  }
362
363  return AddPrerender(origin, process_id, url, referrer, size,
364                      session_storage_namespace);
365#endif
366}
367
368PrerenderHandle* PrerenderManager::AddPrerenderFromOmnibox(
369    const GURL& url,
370    SessionStorageNamespace* session_storage_namespace,
371    const gfx::Size& size) {
372  if (!IsOmniboxEnabled(profile_))
373    return NULL;
374  return AddPrerender(ORIGIN_OMNIBOX, -1, url, content::Referrer(), size,
375                      session_storage_namespace);
376}
377
378PrerenderHandle* PrerenderManager::AddPrerenderFromLocalPredictor(
379    const GURL& url,
380    SessionStorageNamespace* session_storage_namespace,
381    const gfx::Size& size) {
382  return AddPrerender(ORIGIN_LOCAL_PREDICTOR, -1, url, content::Referrer(),
383                      size, session_storage_namespace);
384}
385
386void PrerenderManager::DestroyPrerenderForRenderView(
387    int process_id, int view_id, FinalStatus final_status) {
388  DCHECK(CalledOnValidThread());
389  if (PrerenderData* prerender_data =
390          FindPrerenderDataForChildAndRoute(process_id, view_id)) {
391    prerender_data->contents()->Destroy(final_status);
392  }
393}
394
395void PrerenderManager::CancelAllPrerenders() {
396  DCHECK(CalledOnValidThread());
397  while (!active_prerenders_.empty()) {
398    PrerenderContents* prerender_contents =
399        active_prerenders_.front()->contents();
400    prerender_contents->Destroy(FINAL_STATUS_CANCELLED);
401  }
402}
403
404bool PrerenderManager::MaybeUsePrerenderedPage(WebContents* web_contents,
405                                               const GURL& url) {
406  DCHECK(CalledOnValidThread());
407  DCHECK(!IsWebContentsPrerendering(web_contents, NULL));
408
409  DeleteOldEntries();
410  to_delete_prerenders_.clear();
411  // TODO(ajwong): This doesn't handle isolated apps correctly.
412  PrerenderData* prerender_data = FindPrerenderData(
413          url,
414          web_contents->GetController().GetDefaultSessionStorageNamespace());
415  if (!prerender_data)
416    return false;
417  DCHECK(prerender_data->contents());
418  if (IsNoSwapInExperiment(prerender_data->contents()->experiment_id()))
419    return false;
420
421  if (WebContents* new_web_contents =
422      prerender_data->contents()->prerender_contents()) {
423    if (web_contents == new_web_contents)
424      return false;  // Do not swap in to ourself.
425
426    // We cannot swap in if there is no last committed entry, because we would
427    // show a blank page under an existing entry from the current tab.  Even if
428    // there is a pending entry, it may not commit.
429    // TODO(creis): If there is a pending navigation and no last committed
430    // entry, we might be able to transfer the network request instead.
431    if (!new_web_contents->GetController().CanPruneAllButVisible())
432      return false;
433  }
434
435  // Do not use the prerendered version if there is an opener object.
436  if (web_contents->HasOpener()) {
437    prerender_data->contents()->Destroy(FINAL_STATUS_WINDOW_OPENER);
438    return false;
439  }
440
441  // If we are just in the control group (which can be detected by noticing
442  // that prerendering hasn't even started yet), record that |web_contents| now
443  // would be showing a prerendered contents, but otherwise, don't do anything.
444  if (!prerender_data->contents()->prerendering_has_started()) {
445    MarkWebContentsAsWouldBePrerendered(web_contents,
446                                        prerender_data->contents()->origin());
447    prerender_data->contents()->Destroy(FINAL_STATUS_WOULD_HAVE_BEEN_USED);
448    return false;
449  }
450
451  // Don't use prerendered pages if debugger is attached to the tab.
452  // See http://crbug.com/98541
453  if (content::DevToolsAgentHost::IsDebuggerAttached(web_contents)) {
454    DestroyAndMarkMatchCompleteAsUsed(prerender_data->contents(),
455                                      FINAL_STATUS_DEVTOOLS_ATTACHED);
456    return false;
457  }
458
459  // If the prerendered page is in the middle of a cross-site navigation,
460  // don't swap it in because there isn't a good way to merge histories.
461  if (prerender_data->contents()->IsCrossSiteNavigationPending()) {
462    DestroyAndMarkMatchCompleteAsUsed(
463        prerender_data->contents(),
464        FINAL_STATUS_CROSS_SITE_NAVIGATION_PENDING);
465    return false;
466  }
467
468  // For bookkeeping purposes, we need to mark this WebContents to
469  // reflect that it would have been prerendered.
470  if (GetMode() == PRERENDER_MODE_EXPERIMENT_NO_USE_GROUP) {
471    MarkWebContentsAsWouldBePrerendered(web_contents,
472                                        prerender_data->contents()->origin());
473    prerender_data->contents()->Destroy(FINAL_STATUS_WOULD_HAVE_BEEN_USED);
474    return false;
475  }
476
477  int child_id, route_id;
478  CHECK(prerender_data->contents()->GetChildId(&child_id));
479  CHECK(prerender_data->contents()->GetRouteId(&route_id));
480
481  // Try to set the prerendered page as used, so any subsequent attempts to
482  // cancel on other threads will fail.  If this fails because the prerender
483  // was already cancelled, possibly on another thread, fail.
484  if (!prerender_tracker_->TryUse(child_id, route_id))
485    return false;
486
487  // At this point, we've determined that we will use the prerender.
488  ScopedVector<PrerenderData>::iterator to_erase =
489      FindIteratorForPrerenderContents(prerender_data->contents());
490  DCHECK(active_prerenders_.end() != to_erase);
491  DCHECK_EQ(prerender_data, *to_erase);
492  scoped_ptr<PrerenderContents>
493      prerender_contents(prerender_data->ReleaseContents());
494  active_prerenders_.erase(to_erase);
495
496  if (!prerender_contents->load_start_time().is_null()) {
497    histograms_->RecordTimeUntilUsed(
498        prerender_contents->origin(),
499        GetCurrentTimeTicks() - prerender_contents->load_start_time());
500  }
501
502  histograms_->RecordPerSessionCount(prerender_contents->origin(),
503                                     ++prerenders_per_session_count_);
504  histograms_->RecordUsedPrerender(prerender_contents->origin());
505  prerender_contents->SetFinalStatus(FINAL_STATUS_USED);
506
507  RenderViewHost* new_render_view_host =
508      prerender_contents->prerender_contents()->GetRenderViewHost();
509  new_render_view_host->Send(
510      new PrerenderMsg_SetIsPrerendering(new_render_view_host->GetRoutingID(),
511                                         false));
512
513  // Start pending prerender requests from the PrerenderContents, if there are
514  // any.
515  prerender_contents->PrepareForUse();
516
517  WebContents* new_web_contents =
518      prerender_contents->ReleasePrerenderContents();
519  WebContents* old_web_contents = web_contents;
520  DCHECK(new_web_contents);
521  DCHECK(old_web_contents);
522
523  MarkWebContentsAsPrerendered(new_web_contents, prerender_contents->origin());
524
525  // Merge the browsing history.
526  new_web_contents->GetController().CopyStateFromAndPrune(
527      &old_web_contents->GetController());
528  CoreTabHelper::FromWebContents(old_web_contents)->delegate()->
529      SwapTabContents(old_web_contents, new_web_contents);
530  prerender_contents->CommitHistory(new_web_contents);
531
532  GURL icon_url = prerender_contents->icon_url();
533
534  if (!icon_url.is_empty()) {
535#if defined(OS_ANDROID)
536    // Do the delayed icon fetch since we didn't download
537    // the favicon during prerendering on mobile devices.
538    FaviconTabHelper * favicon_tap_helper =
539        FaviconTabHelper::FromWebContents(new_web_contents);
540    favicon_tap_helper->set_should_fetch_icons(true);
541    favicon_tap_helper->FetchFavicon(icon_url);
542#endif  // defined(OS_ANDROID)
543
544    std::vector<content::FaviconURL> urls;
545    urls.push_back(content::FaviconURL(icon_url, content::FaviconURL::FAVICON));
546    FaviconTabHelper::FromWebContents(new_web_contents)->
547        DidUpdateFaviconURL(prerender_contents->page_id(), urls);
548  }
549
550  // Update PPLT metrics:
551  // If the tab has finished loading, record a PPLT of 0.
552  // If the tab is still loading, reset its start time to the current time.
553  PrerenderTabHelper* prerender_tab_helper =
554      PrerenderTabHelper::FromWebContents(new_web_contents);
555  DCHECK(prerender_tab_helper != NULL);
556  prerender_tab_helper->PrerenderSwappedIn();
557
558  if (old_web_contents->NeedToFireBeforeUnload()) {
559    // Schedule the delete to occur after the tab has run its unload handlers.
560    on_close_web_contents_deleters_.push_back(
561        new OnCloseWebContentsDeleter(this, old_web_contents));
562    old_web_contents->GetRenderViewHost()->
563        FirePageBeforeUnload(false);
564  } else {
565    // No unload handler to run, so delete asap.
566    ScheduleDeleteOldWebContents(old_web_contents, NULL);
567  }
568
569  // TODO(cbentzel): Should prerender_contents move to the pending delete
570  //                 list, instead of deleting directly here?
571  AddToHistory(prerender_contents.get());
572  RecordNavigation(url);
573  return true;
574}
575
576void PrerenderManager::MoveEntryToPendingDelete(PrerenderContents* entry,
577                                                FinalStatus final_status) {
578  DCHECK(CalledOnValidThread());
579  DCHECK(entry);
580
581  ScopedVector<PrerenderData>::iterator it =
582      FindIteratorForPrerenderContents(entry);
583  DCHECK(it != active_prerenders_.end());
584
585  // If this PrerenderContents is being deleted due to a cancellation any time
586  // after the prerender has started then we need to create a dummy replacement
587  // for PPLT accounting purposes for the Match Complete group. This is the case
588  // if the cancellation is for any reason that would not occur in the control
589  // group case.
590  if (entry->prerendering_has_started() &&
591      entry->match_complete_status() ==
592          PrerenderContents::MATCH_COMPLETE_DEFAULT &&
593      NeedMatchCompleteDummyForFinalStatus(final_status) &&
594      ActuallyPrerendering()) {
595    // TODO(tburkard): I'd like to DCHECK that we are actually prerendering.
596    // However, what if new conditions are added and
597    // NeedMatchCompleteDummyForFinalStatus is not being updated.  Not sure
598    // what's the best thing to do here.  For now, I will just check whether
599    // we are actually prerendering.
600    (*it)->MakeIntoMatchCompleteReplacement();
601  } else {
602    to_delete_prerenders_.push_back(*it);
603    active_prerenders_.weak_erase(it);
604  }
605
606  // Destroy the old WebContents relatively promptly to reduce resource usage,
607  // and in the case of HTML5 media, reduce the chance of playing any sound.
608  PostCleanupTask();
609}
610
611// static
612void PrerenderManager::RecordPerceivedPageLoadTime(
613    base::TimeDelta perceived_page_load_time,
614    double fraction_plt_elapsed_at_swap_in,
615    WebContents* web_contents,
616    const GURL& url) {
617  DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
618  PrerenderManager* prerender_manager =
619      PrerenderManagerFactory::GetForProfile(
620          Profile::FromBrowserContext(web_contents->GetBrowserContext()));
621  if (!prerender_manager)
622    return;
623  if (!prerender_manager->IsEnabled())
624    return;
625
626  Origin prerender_origin = ORIGIN_NONE;
627  if (prerender_manager->IsWebContentsPrerendering(web_contents,
628                                                   &prerender_origin)) {
629    prerender_manager->histograms_->RecordPageLoadTimeNotSwappedIn(
630        prerender_origin, perceived_page_load_time, url);
631    return;
632  }
633
634  bool was_prerender = prerender_manager->IsWebContentsPrerendered(
635      web_contents, &prerender_origin);
636  bool was_complete_prerender = was_prerender ||
637      prerender_manager->WouldWebContentsBePrerendered(web_contents,
638                                                       &prerender_origin);
639  prerender_manager->histograms_->RecordPerceivedPageLoadTime(
640      prerender_origin, perceived_page_load_time, was_prerender,
641      was_complete_prerender, url);
642
643  if (was_prerender) {
644    prerender_manager->histograms_->RecordPercentLoadDoneAtSwapin(
645        prerender_origin, fraction_plt_elapsed_at_swap_in);
646  }
647  if (prerender_manager->local_predictor_.get()) {
648    prerender_manager->local_predictor_->
649        OnPLTEventForURL(url, perceived_page_load_time);
650  }
651}
652
653void PrerenderManager::RecordFractionPixelsFinalAtSwapin(
654    content::WebContents* web_contents,
655    double fraction) {
656  Origin origin = ORIGIN_NONE;
657  bool is_prerendered = IsWebContentsPrerendered(web_contents, &origin);
658  DCHECK(is_prerendered);
659  histograms_->RecordFractionPixelsFinalAtSwapin(origin, fraction);
660}
661
662void PrerenderManager::set_enabled(bool enabled) {
663  DCHECK(CalledOnValidThread());
664  enabled_ = enabled;
665}
666
667// static
668bool PrerenderManager::IsPrefetchEnabled() {
669  return is_prefetch_enabled_;
670}
671
672// static
673void PrerenderManager::SetIsPrefetchEnabled(bool value) {
674  is_prefetch_enabled_ = value;
675}
676
677// static
678PrerenderManager::PrerenderManagerMode PrerenderManager::GetMode() {
679  return mode_;
680}
681
682// static
683void PrerenderManager::SetMode(PrerenderManagerMode mode) {
684  mode_ = mode;
685}
686
687// static
688const char* PrerenderManager::GetModeString() {
689  switch (mode_) {
690    case PRERENDER_MODE_DISABLED:
691      return "_Disabled";
692    case PRERENDER_MODE_ENABLED:
693    case PRERENDER_MODE_EXPERIMENT_PRERENDER_GROUP:
694      return "_Enabled";
695    case PRERENDER_MODE_EXPERIMENT_CONTROL_GROUP:
696      return "_Control";
697    case PRERENDER_MODE_EXPERIMENT_MULTI_PRERENDER_GROUP:
698      return "_Multi";
699    case PRERENDER_MODE_EXPERIMENT_15MIN_TTL_GROUP:
700      return "_15MinTTL";
701    case PRERENDER_MODE_EXPERIMENT_NO_USE_GROUP:
702      return "_NoUse";
703    case PRERENDER_MODE_MAX:
704    default:
705      NOTREACHED() << "Invalid PrerenderManager mode.";
706      break;
707  };
708  return "";
709}
710
711// static
712bool PrerenderManager::IsPrerenderingPossible() {
713  return GetMode() != PRERENDER_MODE_DISABLED;
714}
715
716// static
717bool PrerenderManager::ActuallyPrerendering() {
718  return IsPrerenderingPossible() && !IsControlGroup(kNoExperiment);
719}
720
721// static
722bool PrerenderManager::IsControlGroup(uint8 experiment_id) {
723  return GetMode() == PRERENDER_MODE_EXPERIMENT_CONTROL_GROUP ||
724      IsControlGroupExperiment(experiment_id);
725}
726
727// static
728bool PrerenderManager::IsNoUseGroup() {
729  return GetMode() == PRERENDER_MODE_EXPERIMENT_NO_USE_GROUP;
730}
731
732bool PrerenderManager::IsWebContentsPrerendering(
733    const WebContents* web_contents,
734    Origin* origin) const {
735  DCHECK(CalledOnValidThread());
736  if (PrerenderContents* prerender_contents =
737          GetPrerenderContents(web_contents)) {
738    if (origin)
739      *origin = prerender_contents->origin();
740    return true;
741  }
742
743  // Also look through the pending-deletion list.
744  for (ScopedVector<PrerenderData>::const_iterator it =
745           to_delete_prerenders_.begin();
746       it != to_delete_prerenders_.end();
747       ++it) {
748    if (PrerenderContents* prerender_contents = (*it)->contents()) {
749      WebContents* prerender_web_contents =
750          prerender_contents->prerender_contents();
751      if (prerender_web_contents == web_contents) {
752        if (origin)
753          *origin = prerender_contents->origin();
754        return true;
755      }
756    }
757  }
758
759  return false;
760}
761
762PrerenderContents* PrerenderManager::GetPrerenderContents(
763    const content::WebContents* web_contents) const {
764  DCHECK(CalledOnValidThread());
765  for (ScopedVector<PrerenderData>::const_iterator it =
766           active_prerenders_.begin();
767       it != active_prerenders_.end(); ++it) {
768    WebContents* prerender_web_contents =
769        (*it)->contents()->prerender_contents();
770    if (prerender_web_contents == web_contents) {
771      return (*it)->contents();
772    }
773  }
774  return NULL;
775}
776
777const std::vector<WebContents*>
778PrerenderManager::GetAllPrerenderingContents() const {
779  DCHECK(CalledOnValidThread());
780  std::vector<WebContents*> result;
781
782  for (ScopedVector<PrerenderData>::const_iterator it =
783           active_prerenders_.begin();
784       it != active_prerenders_.end(); ++it) {
785    if (WebContents* contents = (*it)->contents()->prerender_contents())
786      result.push_back(contents);
787  }
788
789  return result;
790}
791
792void PrerenderManager::MarkWebContentsAsPrerendered(WebContents* web_contents,
793                                                    Origin origin) {
794  DCHECK(CalledOnValidThread());
795  prerendered_web_contents_data_.insert(
796      base::hash_map<content::WebContents*,
797                     PrerenderedWebContentsData>::value_type(
798                         web_contents, PrerenderedWebContentsData(origin)));
799}
800
801void PrerenderManager::MarkWebContentsAsWouldBePrerendered(
802    WebContents* web_contents,
803    Origin origin) {
804  DCHECK(CalledOnValidThread());
805  would_be_prerendered_map_.insert(
806      base::hash_map<content::WebContents*,
807                     WouldBePrerenderedWebContentsData>::value_type(
808                         web_contents,
809                         WouldBePrerenderedWebContentsData(origin)));
810}
811
812void PrerenderManager::MarkWebContentsAsNotPrerendered(
813    WebContents* web_contents) {
814  DCHECK(CalledOnValidThread());
815  prerendered_web_contents_data_.erase(web_contents);
816  base::hash_map<content::WebContents*, WouldBePrerenderedWebContentsData>::
817      iterator it = would_be_prerendered_map_.find(web_contents);
818  if (it != would_be_prerendered_map_.end()) {
819    if (it->second.state ==
820            WouldBePrerenderedWebContentsData::WAITING_FOR_PROVISIONAL_LOAD) {
821      it->second.state =
822          WouldBePrerenderedWebContentsData::SEEN_PROVISIONAL_LOAD;
823    } else {
824      would_be_prerendered_map_.erase(it);
825    }
826  }
827}
828
829bool PrerenderManager::IsWebContentsPrerendered(
830    content::WebContents* web_contents,
831    Origin* origin) const {
832  DCHECK(CalledOnValidThread());
833  base::hash_map<content::WebContents*, PrerenderedWebContentsData>::
834      const_iterator it = prerendered_web_contents_data_.find(web_contents);
835  if (it == prerendered_web_contents_data_.end())
836    return false;
837  if (origin)
838    *origin = it->second.origin;
839  return true;
840}
841
842bool PrerenderManager::WouldWebContentsBePrerendered(
843    WebContents* web_contents,
844    Origin* origin) const {
845  DCHECK(CalledOnValidThread());
846  base::hash_map<content::WebContents*, WouldBePrerenderedWebContentsData>::
847      const_iterator it = would_be_prerendered_map_.find(web_contents);
848  if (it == would_be_prerendered_map_.end())
849    return false;
850  if (origin)
851    *origin = it->second.origin;
852  return true;
853}
854
855bool PrerenderManager::HasRecentlyBeenNavigatedTo(Origin origin,
856                                                  const GURL& url) {
857  DCHECK(CalledOnValidThread());
858
859  CleanUpOldNavigations();
860  std::list<NavigationRecord>::const_reverse_iterator end = navigations_.rend();
861  for (std::list<NavigationRecord>::const_reverse_iterator it =
862           navigations_.rbegin();
863       it != end;
864       ++it) {
865    if (it->url == url) {
866      base::TimeDelta delta = GetCurrentTimeTicks() - it->time;
867      histograms_->RecordTimeSinceLastRecentVisit(origin, delta);
868      return true;
869    }
870  }
871
872  return false;
873}
874
875// static
876bool PrerenderManager::IsValidHttpMethod(const std::string& method) {
877  // method has been canonicalized to upper case at this point so we can just
878  // compare them.
879  DCHECK_EQ(method, StringToUpperASCII(method));
880  for (size_t i = 0; i < arraysize(kValidHttpMethods); ++i) {
881    if (method.compare(kValidHttpMethods[i]) == 0)
882      return true;
883  }
884
885  return false;
886}
887
888// static
889bool PrerenderManager::DoesURLHaveValidScheme(const GURL& url) {
890  return IsWebURL(url) || url.SchemeIs(extensions::kExtensionScheme);
891}
892
893DictionaryValue* PrerenderManager::GetAsValue() const {
894  DCHECK(CalledOnValidThread());
895  DictionaryValue* dict_value = new DictionaryValue();
896  dict_value->Set("history", prerender_history_->GetEntriesAsValue());
897  dict_value->Set("active", GetActivePrerendersAsValue());
898  dict_value->SetBoolean("enabled", enabled_);
899  dict_value->SetBoolean("omnibox_enabled", IsOmniboxEnabled(profile_));
900  // If prerender is disabled via a flag this method is not even called.
901  std::string enabled_note;
902  if (IsControlGroup(kNoExperiment))
903    enabled_note += "(Control group: Not actually prerendering) ";
904  if (IsNoUseGroup())
905    enabled_note += "(No-use group: Not swapping in prerendered pages) ";
906  if (GetMode() == PRERENDER_MODE_EXPERIMENT_15MIN_TTL_GROUP)
907    enabled_note +=
908        "(15 min TTL group: Extended prerender eviction to 15 mins) ";
909  dict_value->SetString("enabled_note", enabled_note);
910  return dict_value;
911}
912
913void PrerenderManager::ClearData(int clear_flags) {
914  DCHECK_GE(clear_flags, 0);
915  DCHECK_LT(clear_flags, CLEAR_MAX);
916  if (clear_flags & CLEAR_PRERENDER_CONTENTS)
917    DestroyAllContents(FINAL_STATUS_CACHE_OR_HISTORY_CLEARED);
918  // This has to be second, since destroying prerenders can add to the history.
919  if (clear_flags & CLEAR_PRERENDER_HISTORY)
920    prerender_history_->Clear();
921}
922
923void PrerenderManager::RecordFinalStatusWithMatchCompleteStatus(
924    Origin origin,
925    uint8 experiment_id,
926    PrerenderContents::MatchCompleteStatus mc_status,
927    FinalStatus final_status) const {
928  histograms_->RecordFinalStatus(origin,
929                                 experiment_id,
930                                 mc_status,
931                                 final_status);
932}
933
934void PrerenderManager::AddCondition(const PrerenderCondition* condition) {
935  prerender_conditions_.push_back(condition);
936}
937
938void PrerenderManager::RecordNavigation(const GURL& url) {
939  DCHECK(CalledOnValidThread());
940
941  navigations_.push_back(NavigationRecord(url, GetCurrentTimeTicks()));
942  CleanUpOldNavigations();
943}
944
945// protected
946struct PrerenderManager::PrerenderData::OrderByExpiryTime {
947  bool operator()(const PrerenderData* a, const PrerenderData* b) const {
948    return a->expiry_time() < b->expiry_time();
949  }
950};
951
952PrerenderManager::PrerenderData::PrerenderData(PrerenderManager* manager,
953                                               PrerenderContents* contents,
954                                               base::TimeTicks expiry_time)
955    : manager_(manager),
956      contents_(contents),
957      handle_count_(0),
958      expiry_time_(expiry_time) {
959  DCHECK_NE(static_cast<PrerenderContents*>(NULL), contents_);
960}
961
962PrerenderManager::PrerenderData::~PrerenderData() {
963}
964
965void PrerenderManager::PrerenderData::MakeIntoMatchCompleteReplacement() {
966  DCHECK(contents_);
967  contents_->set_match_complete_status(
968      PrerenderContents::MATCH_COMPLETE_REPLACED);
969  PrerenderData* to_delete = new PrerenderData(manager_, contents_.release(),
970                                               expiry_time_);
971  contents_.reset(to_delete->contents_->CreateMatchCompleteReplacement());
972  manager_->to_delete_prerenders_.push_back(to_delete);
973}
974
975void PrerenderManager::PrerenderData::OnHandleCreated(PrerenderHandle* handle) {
976  DCHECK_NE(static_cast<PrerenderContents*>(NULL), contents_);
977  ++handle_count_;
978  contents_->AddObserver(handle);
979}
980
981void PrerenderManager::PrerenderData::OnHandleNavigatedAway(
982    PrerenderHandle* handle) {
983  DCHECK_LT(0, handle_count_);
984  DCHECK_NE(static_cast<PrerenderContents*>(NULL), contents_);
985  // We intentionally don't decrement the handle count here, so that the
986  // prerender won't be canceled until it times out.
987  manager_->SourceNavigatedAway(this);
988}
989
990void PrerenderManager::PrerenderData::OnHandleCanceled(
991    PrerenderHandle* handle) {
992  DCHECK_LT(0, handle_count_);
993  DCHECK_NE(static_cast<PrerenderContents*>(NULL), contents_);
994
995  if (--handle_count_ == 0) {
996    // This will eventually remove this object from active_prerenders_.
997    contents_->Destroy(FINAL_STATUS_CANCELLED);
998  }
999}
1000
1001PrerenderContents* PrerenderManager::PrerenderData::ReleaseContents() {
1002  return contents_.release();
1003}
1004
1005void PrerenderManager::SetPrerenderContentsFactory(
1006    PrerenderContents::Factory* prerender_contents_factory) {
1007  DCHECK(CalledOnValidThread());
1008  prerender_contents_factory_.reset(prerender_contents_factory);
1009}
1010
1011void PrerenderManager::StartPendingPrerenders(
1012    const int process_id,
1013    ScopedVector<PrerenderContents::PendingPrerenderInfo>* pending_prerenders,
1014    content::SessionStorageNamespace* session_storage_namespace) {
1015  for (ScopedVector<PrerenderContents::PendingPrerenderInfo>::iterator
1016           it = pending_prerenders->begin();
1017       it != pending_prerenders->end(); ++it) {
1018    PrerenderContents::PendingPrerenderInfo* info = *it;
1019    PrerenderHandle* existing_prerender_handle =
1020        info->weak_prerender_handle.get();
1021    if (!existing_prerender_handle)
1022      continue;
1023
1024    DCHECK(!existing_prerender_handle->IsPrerendering());
1025    DCHECK(process_id == -1 || session_storage_namespace);
1026
1027    scoped_ptr<PrerenderHandle> new_prerender_handle(AddPrerender(
1028        info->origin, process_id,
1029        info->url, info->referrer, info->size,
1030        session_storage_namespace));
1031    if (new_prerender_handle) {
1032      // AddPrerender has returned a new prerender handle to us. We want to make
1033      // |existing_prerender_handle| active, so move the underlying
1034      // PrerenderData to our new handle.
1035      existing_prerender_handle->AdoptPrerenderDataFrom(
1036          new_prerender_handle.get());
1037      continue;
1038    }
1039  }
1040}
1041
1042void PrerenderManager::SourceNavigatedAway(PrerenderData* prerender_data) {
1043  // The expiry time of our prerender data will likely change because of
1044  // this navigation. This requires a resort of active_prerenders_.
1045  ScopedVector<PrerenderData>::iterator it =
1046      std::find(active_prerenders_.begin(), active_prerenders_.end(),
1047                prerender_data);
1048  if (it == active_prerenders_.end())
1049    return;
1050
1051  (*it)->set_expiry_time(
1052      std::min((*it)->expiry_time(),
1053               GetExpiryTimeForNavigatedAwayPrerender()));
1054  SortActivePrerenders();
1055}
1056
1057// private
1058PrerenderHandle* PrerenderManager::AddPrerender(
1059    Origin origin,
1060    int process_id,
1061    const GURL& url_arg,
1062    const content::Referrer& referrer,
1063    const gfx::Size& size,
1064    SessionStorageNamespace* session_storage_namespace) {
1065  DCHECK(CalledOnValidThread());
1066
1067  if (!IsEnabled())
1068    return NULL;
1069
1070  if ((origin == ORIGIN_LINK_REL_PRERENDER_CROSSDOMAIN ||
1071       origin == ORIGIN_LINK_REL_PRERENDER_SAMEDOMAIN) &&
1072      IsGoogleSearchResultURL(referrer.url)) {
1073    origin = ORIGIN_GWS_PRERENDER;
1074  }
1075
1076  GURL url = url_arg;
1077  GURL alias_url;
1078  uint8 experiment = GetQueryStringBasedExperiment(url_arg);
1079  if (IsControlGroup(experiment) &&
1080      MaybeGetQueryStringBasedAliasURL(url, &alias_url)) {
1081    url = alias_url;
1082  }
1083
1084  // From here on, we will record a FinalStatus so we need to register with the
1085  // histogram tracking.
1086  histograms_->RecordPrerender(origin, url_arg);
1087
1088  if (PrerenderData* preexisting_prerender_data =
1089          FindPrerenderData(url, session_storage_namespace)) {
1090    RecordFinalStatus(origin, experiment, FINAL_STATUS_DUPLICATE);
1091    return new PrerenderHandle(preexisting_prerender_data);
1092  }
1093
1094  // Do not prerender if there are too many render processes, and we would
1095  // have to use an existing one.  We do not want prerendering to happen in
1096  // a shared process, so that we can always reliably lower the CPU
1097  // priority for prerendering.
1098  // In single-process mode, ShouldTryToUseExistingProcessHost() always returns
1099  // true, so that case needs to be explicitly checked for.
1100  // TODO(tburkard): Figure out how to cancel prerendering in the opposite
1101  // case, when a new tab is added to a process used for prerendering.
1102  // On Android we do reuse processes as we have a limited number of them and we
1103  // still want the benefits of prerendering even when several tabs are open.
1104#if !defined(OS_ANDROID)
1105  if (content::RenderProcessHost::ShouldTryToUseExistingProcessHost(
1106          profile_, url) &&
1107      !content::RenderProcessHost::run_renderer_in_process()) {
1108    RecordFinalStatus(origin, experiment, FINAL_STATUS_TOO_MANY_PROCESSES);
1109    return NULL;
1110  }
1111#endif
1112
1113  // Check if enough time has passed since the last prerender.
1114  if (!DoesRateLimitAllowPrerender(origin)) {
1115    // Cancel the prerender. We could add it to the pending prerender list but
1116    // this doesn't make sense as the next prerender request will be triggered
1117    // by a navigation and is unlikely to be the same site.
1118    RecordFinalStatus(origin, experiment, FINAL_STATUS_RATE_LIMIT_EXCEEDED);
1119    return NULL;
1120  }
1121
1122  PrerenderContents* prerender_contents = CreatePrerenderContents(
1123      url, referrer, origin, experiment);
1124  DCHECK(prerender_contents);
1125  active_prerenders_.push_back(
1126      new PrerenderData(this, prerender_contents,
1127                        GetExpiryTimeForNewPrerender(origin)));
1128  if (!prerender_contents->Init()) {
1129    DCHECK(active_prerenders_.end() ==
1130           FindIteratorForPrerenderContents(prerender_contents));
1131    return NULL;
1132  }
1133
1134  histograms_->RecordPrerenderStarted(origin);
1135  DCHECK(!prerender_contents->prerendering_has_started());
1136
1137  PrerenderHandle* prerender_handle =
1138      new PrerenderHandle(active_prerenders_.back());
1139  SortActivePrerenders();
1140
1141  last_prerender_start_time_ = GetCurrentTimeTicks();
1142
1143  gfx::Size contents_size =
1144      size.IsEmpty() ? config_.default_tab_bounds.size() : size;
1145
1146  prerender_contents->StartPrerendering(process_id, contents_size,
1147                                        session_storage_namespace);
1148
1149  DCHECK(IsControlGroup(experiment) ||
1150         prerender_contents->prerendering_has_started());
1151
1152  if (GetMode() == PRERENDER_MODE_EXPERIMENT_MULTI_PRERENDER_GROUP)
1153    histograms_->RecordConcurrency(active_prerenders_.size());
1154
1155  StartSchedulingPeriodicCleanups();
1156  return prerender_handle;
1157}
1158
1159void PrerenderManager::StartSchedulingPeriodicCleanups() {
1160  DCHECK(CalledOnValidThread());
1161  if (repeating_timer_.IsRunning())
1162    return;
1163  repeating_timer_.Start(FROM_HERE,
1164      base::TimeDelta::FromMilliseconds(kPeriodicCleanupIntervalMs),
1165      this,
1166      &PrerenderManager::PeriodicCleanup);
1167}
1168
1169void PrerenderManager::StopSchedulingPeriodicCleanups() {
1170  DCHECK(CalledOnValidThread());
1171  repeating_timer_.Stop();
1172}
1173
1174void PrerenderManager::PeriodicCleanup() {
1175  DCHECK(CalledOnValidThread());
1176  DeleteOldWebContents();
1177  DeleteOldEntries();
1178  if (active_prerenders_.empty())
1179    StopSchedulingPeriodicCleanups();
1180
1181  // Grab a copy of the current PrerenderContents pointers, so that we
1182  // will not interfere with potential deletions of the list.
1183  std::vector<PrerenderContents*>
1184      prerender_contents(active_prerenders_.size());
1185  std::transform(active_prerenders_.begin(), active_prerenders_.end(),
1186                 prerender_contents.begin(),
1187                 std::mem_fun(&PrerenderData::contents));
1188
1189  // And now check for prerenders using too much memory.
1190  std::for_each(prerender_contents.begin(), prerender_contents.end(),
1191                std::mem_fun(
1192                    &PrerenderContents::DestroyWhenUsingTooManyResources));
1193
1194  to_delete_prerenders_.clear();
1195}
1196
1197void PrerenderManager::PostCleanupTask() {
1198  DCHECK(CalledOnValidThread());
1199  base::MessageLoop::current()->PostTask(
1200      FROM_HERE,
1201      base::Bind(&PrerenderManager::PeriodicCleanup, AsWeakPtr()));
1202}
1203
1204base::TimeTicks PrerenderManager::GetExpiryTimeForNewPrerender(
1205    Origin origin) const {
1206  base::TimeDelta ttl = config_.time_to_live;
1207  if (origin == ORIGIN_LOCAL_PREDICTOR)
1208    ttl = base::TimeDelta::FromSeconds(GetLocalPredictorTTLSeconds());
1209  return GetCurrentTimeTicks() + ttl;
1210}
1211
1212base::TimeTicks PrerenderManager::GetExpiryTimeForNavigatedAwayPrerender()
1213    const {
1214  return GetCurrentTimeTicks() + config_.abandon_time_to_live;
1215}
1216
1217void PrerenderManager::DeleteOldEntries() {
1218  DCHECK(CalledOnValidThread());
1219  while (!active_prerenders_.empty()) {
1220    PrerenderData* prerender_data = active_prerenders_.front();
1221    DCHECK(prerender_data);
1222    DCHECK(prerender_data->contents());
1223
1224    if (prerender_data->expiry_time() > GetCurrentTimeTicks())
1225      return;
1226    prerender_data->contents()->Destroy(FINAL_STATUS_TIMED_OUT);
1227  }
1228}
1229
1230base::Time PrerenderManager::GetCurrentTime() const {
1231  return base::Time::Now();
1232}
1233
1234base::TimeTicks PrerenderManager::GetCurrentTimeTicks() const {
1235  return base::TimeTicks::Now();
1236}
1237
1238PrerenderContents* PrerenderManager::CreatePrerenderContents(
1239    const GURL& url,
1240    const content::Referrer& referrer,
1241    Origin origin,
1242    uint8 experiment_id) {
1243  DCHECK(CalledOnValidThread());
1244  return prerender_contents_factory_->CreatePrerenderContents(
1245      this, profile_, url, referrer, origin, experiment_id);
1246}
1247
1248void PrerenderManager::SortActivePrerenders() {
1249  std::sort(active_prerenders_.begin(), active_prerenders_.end(),
1250            PrerenderData::OrderByExpiryTime());
1251}
1252
1253PrerenderManager::PrerenderData* PrerenderManager::FindPrerenderData(
1254    const GURL& url,
1255    const SessionStorageNamespace* session_storage_namespace) {
1256  for (ScopedVector<PrerenderData>::iterator it = active_prerenders_.begin();
1257       it != active_prerenders_.end(); ++it) {
1258    if ((*it)->contents()->Matches(url, session_storage_namespace))
1259      return *it;
1260  }
1261  return NULL;
1262}
1263
1264PrerenderManager::PrerenderData*
1265PrerenderManager::FindPrerenderDataForChildAndRoute(
1266    const int child_id, const int route_id) {
1267  for (ScopedVector<PrerenderData>::iterator it = active_prerenders_.begin();
1268       it != active_prerenders_.end(); ++it) {
1269    PrerenderContents* prerender_contents = (*it)->contents();
1270
1271    int contents_child_id;
1272    if (!prerender_contents->GetChildId(&contents_child_id))
1273      continue;
1274    int contents_route_id;
1275    if (!prerender_contents->GetRouteId(&contents_route_id))
1276      continue;
1277
1278    if (contents_child_id == child_id && contents_route_id == route_id)
1279      return *it;
1280  }
1281  return NULL;
1282}
1283
1284ScopedVector<PrerenderManager::PrerenderData>::iterator
1285PrerenderManager::FindIteratorForPrerenderContents(
1286    PrerenderContents* prerender_contents) {
1287  for (ScopedVector<PrerenderData>::iterator it = active_prerenders_.begin();
1288       it != active_prerenders_.end(); ++it) {
1289    if (prerender_contents == (*it)->contents())
1290      return it;
1291  }
1292  return active_prerenders_.end();
1293}
1294
1295bool PrerenderManager::DoesRateLimitAllowPrerender(Origin origin) const {
1296  DCHECK(CalledOnValidThread());
1297  base::TimeDelta elapsed_time =
1298      GetCurrentTimeTicks() - last_prerender_start_time_;
1299  histograms_->RecordTimeBetweenPrerenderRequests(origin, elapsed_time);
1300  if (!config_.rate_limit_enabled)
1301    return true;
1302  return elapsed_time >=
1303      base::TimeDelta::FromMilliseconds(kMinTimeBetweenPrerendersMs);
1304}
1305
1306void PrerenderManager::DeleteOldWebContents() {
1307  while (!old_web_contents_list_.empty()) {
1308    WebContents* web_contents = old_web_contents_list_.front();
1309    old_web_contents_list_.pop_front();
1310    // TODO(dominich): should we use Instant Unload Handler here?
1311    delete web_contents;
1312  }
1313}
1314
1315void PrerenderManager::CleanUpOldNavigations() {
1316  DCHECK(CalledOnValidThread());
1317
1318  // Cutoff.  Navigations before this cutoff can be discarded.
1319  base::TimeTicks cutoff = GetCurrentTimeTicks() -
1320      base::TimeDelta::FromMilliseconds(kNavigationRecordWindowMs);
1321  while (!navigations_.empty()) {
1322    if (navigations_.front().time > cutoff)
1323      break;
1324    navigations_.pop_front();
1325  }
1326}
1327
1328void PrerenderManager::ScheduleDeleteOldWebContents(
1329    WebContents* tab,
1330    OnCloseWebContentsDeleter* deleter) {
1331  old_web_contents_list_.push_back(tab);
1332  PostCleanupTask();
1333
1334  if (deleter) {
1335    ScopedVector<OnCloseWebContentsDeleter>::iterator i = std::find(
1336        on_close_web_contents_deleters_.begin(),
1337        on_close_web_contents_deleters_.end(),
1338        deleter);
1339    DCHECK(i != on_close_web_contents_deleters_.end());
1340    on_close_web_contents_deleters_.erase(i);
1341  }
1342}
1343
1344void PrerenderManager::AddToHistory(PrerenderContents* contents) {
1345  PrerenderHistory::Entry entry(contents->prerender_url(),
1346                                contents->final_status(),
1347                                contents->origin(),
1348                                base::Time::Now());
1349  prerender_history_->AddEntry(entry);
1350}
1351
1352Value* PrerenderManager::GetActivePrerendersAsValue() const {
1353  ListValue* list_value = new ListValue();
1354  for (ScopedVector<PrerenderData>::const_iterator it =
1355           active_prerenders_.begin();
1356       it != active_prerenders_.end(); ++it) {
1357    if (Value* prerender_value = (*it)->contents()->GetAsValue())
1358      list_value->Append(prerender_value);
1359  }
1360  return list_value;
1361}
1362
1363void PrerenderManager::DestroyAllContents(FinalStatus final_status) {
1364  DeleteOldWebContents();
1365  while (!active_prerenders_.empty()) {
1366    PrerenderContents* contents = active_prerenders_.front()->contents();
1367    contents->Destroy(final_status);
1368  }
1369  to_delete_prerenders_.clear();
1370}
1371
1372void PrerenderManager::DestroyAndMarkMatchCompleteAsUsed(
1373    PrerenderContents* prerender_contents,
1374    FinalStatus final_status) {
1375  prerender_contents->set_match_complete_status(
1376      PrerenderContents::MATCH_COMPLETE_REPLACED);
1377  histograms_->RecordFinalStatus(prerender_contents->origin(),
1378                                 prerender_contents->experiment_id(),
1379                                 PrerenderContents::MATCH_COMPLETE_REPLACEMENT,
1380                                 FINAL_STATUS_WOULD_HAVE_BEEN_USED);
1381  prerender_contents->Destroy(final_status);
1382}
1383
1384void PrerenderManager::RecordFinalStatus(Origin origin,
1385                                         uint8 experiment_id,
1386                                         FinalStatus final_status) const {
1387  RecordFinalStatusWithMatchCompleteStatus(
1388      origin, experiment_id,
1389      PrerenderContents::MATCH_COMPLETE_DEFAULT,
1390      final_status);
1391}
1392
1393bool PrerenderManager::IsEnabled() const {
1394  DCHECK(CalledOnValidThread());
1395  if (!enabled_)
1396    return false;
1397  for (std::list<const PrerenderCondition*>::const_iterator it =
1398           prerender_conditions_.begin();
1399       it != prerender_conditions_.end();
1400       ++it) {
1401    const PrerenderCondition* condition = *it;
1402    if (!condition->CanPrerender())
1403      return false;
1404  }
1405  return true;
1406}
1407
1408PrerenderManager* FindPrerenderManagerUsingRenderProcessId(
1409    int render_process_id) {
1410  DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
1411  content::RenderProcessHost* render_process_host =
1412      content::RenderProcessHost::FromID(render_process_id);
1413  // Each render process is guaranteed to only hold RenderViews owned by the
1414  // same BrowserContext. This is enforced by
1415  // RenderProcessHost::GetExistingProcessHost.
1416  if (!render_process_host || !render_process_host->GetBrowserContext())
1417    return NULL;
1418  Profile* profile = Profile::FromBrowserContext(
1419      render_process_host->GetBrowserContext());
1420  if (!profile)
1421    return NULL;
1422  return PrerenderManagerFactory::GetInstance()->GetForProfile(profile);
1423}
1424
1425void PrerenderManager::Observe(int type,
1426                               const content::NotificationSource& source,
1427                               const content::NotificationDetails& details) {
1428  Profile* profile = content::Source<Profile>(source).ptr();
1429  if (!profile || !profile_->IsSameProfile(profile) ||
1430      profile->IsOffTheRecord()) {
1431    return;
1432  }
1433  DCHECK(type == chrome::NOTIFICATION_COOKIE_CHANGED);
1434  CookieChanged(content::Details<ChromeCookieDetails>(details).ptr());
1435}
1436
1437void PrerenderManager::RecordLikelyLoginOnURL(const GURL& url) {
1438  DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
1439  if (!IsWebURL(url))
1440    return;
1441  if (logged_in_predictor_table_.get()) {
1442    BrowserThread::PostTask(
1443        BrowserThread::DB,
1444        FROM_HERE,
1445        base::Bind(&LoggedInPredictorTable::AddDomainFromURL,
1446                   logged_in_predictor_table_,
1447                   url));
1448  }
1449  std::string key = LoggedInPredictorTable::GetKey(url);
1450  if (!logged_in_state_.get())
1451    return;
1452  if (logged_in_state_->count(key))
1453    return;
1454  (*logged_in_state_)[key] = base::Time::Now().ToInternalValue();
1455}
1456
1457void PrerenderManager::CheckIfLikelyLoggedInOnURL(
1458    const GURL& url,
1459    bool* lookup_result,
1460    bool* database_was_present,
1461    const base::Closure& result_cb) {
1462  DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
1463  if (!logged_in_predictor_table_.get()) {
1464    *database_was_present = false;
1465    *lookup_result = false;
1466    BrowserThread::PostTask(BrowserThread::UI, FROM_HERE, result_cb);
1467    return;
1468  }
1469  BrowserThread::PostTaskAndReply(
1470      BrowserThread::DB, FROM_HERE,
1471      base::Bind(&LoggedInPredictorTable::HasUserLoggedIn,
1472                 logged_in_predictor_table_,
1473                 url,
1474                 lookup_result,
1475                 database_was_present),
1476      result_cb);
1477}
1478
1479
1480void PrerenderManager::CookieChanged(ChromeCookieDetails* details) {
1481  DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
1482
1483  if (!logged_in_predictor_table_.get())
1484    return;
1485
1486  // We only care when a cookie has been removed.
1487  if (!details->removed)
1488    return;
1489
1490  std::string domain_key =
1491      LoggedInPredictorTable::GetKeyFromDomain(details->cookie->Domain());
1492
1493  // If we have no record of this domain as a potentially logged in domain,
1494  // nothing to do here.
1495  if (logged_in_state_.get() && logged_in_state_->count(domain_key) < 1)
1496    return;
1497
1498  net::URLRequestContextGetter* rq_context = profile_->GetRequestContext();
1499  if (!rq_context)
1500    return;
1501
1502  BrowserThread::PostTask(
1503      BrowserThread::IO, FROM_HERE,
1504      base::Bind(&CheckIfCookiesExistForDomainOnIOThread,
1505                 base::Unretained(rq_context),
1506                 domain_key,
1507                 base::Bind(
1508                     &PrerenderManager::CookieChangedAnyCookiesLeftLookupResult,
1509                     AsWeakPtr(),
1510                     domain_key)
1511                 ));
1512}
1513
1514void PrerenderManager::CookieChangedAnyCookiesLeftLookupResult(
1515    const std::string& domain_key,
1516    bool cookies_exist) {
1517  DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
1518
1519  if (cookies_exist)
1520    return;
1521
1522  if (logged_in_predictor_table_.get()) {
1523    BrowserThread::PostTask(BrowserThread::DB,
1524                            FROM_HERE,
1525                            base::Bind(&LoggedInPredictorTable::DeleteDomain,
1526                                       logged_in_predictor_table_,
1527                                       domain_key));
1528  }
1529
1530  if (logged_in_state_.get())
1531    logged_in_state_->erase(domain_key);
1532}
1533
1534void PrerenderManager::LoggedInPredictorDataReceived(
1535    scoped_ptr<LoggedInStateMap> new_map) {
1536  DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
1537  logged_in_state_.swap(new_map);
1538}
1539
1540}  // namespace prerender
1541