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