普通文本  |  1956行  |  69.84 KB

// Copyright (c) 2012 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#include "chrome/browser/prerender/prerender_manager.h"

#include <algorithm>
#include <functional>
#include <string>
#include <vector>

#include "base/bind.h"
#include "base/bind_helpers.h"
#include "base/logging.h"
#include "base/memory/weak_ptr.h"
#include "base/metrics/histogram.h"
#include "base/prefs/pref_service.h"
#include "base/stl_util.h"
#include "base/strings/utf_string_conversions.h"
#include "base/time/time.h"
#include "base/timer/elapsed_timer.h"
#include "base/values.h"
#include "chrome/browser/browser_process.h"
#include "chrome/browser/chrome_notification_types.h"
#include "chrome/browser/common/cancelable_request.h"
#include "chrome/browser/favicon/favicon_tab_helper.h"
#include "chrome/browser/history/history_service_factory.h"
#include "chrome/browser/net/chrome_cookie_notification_details.h"
#include "chrome/browser/predictors/predictor_database.h"
#include "chrome/browser/predictors/predictor_database_factory.h"
#include "chrome/browser/prerender/prerender_condition.h"
#include "chrome/browser/prerender/prerender_contents.h"
#include "chrome/browser/prerender/prerender_field_trial.h"
#include "chrome/browser/prerender/prerender_final_status.h"
#include "chrome/browser/prerender/prerender_handle.h"
#include "chrome/browser/prerender/prerender_histograms.h"
#include "chrome/browser/prerender/prerender_history.h"
#include "chrome/browser/prerender/prerender_local_predictor.h"
#include "chrome/browser/prerender/prerender_manager_factory.h"
#include "chrome/browser/prerender/prerender_tab_helper.h"
#include "chrome/browser/prerender/prerender_tracker.h"
#include "chrome/browser/prerender/prerender_util.h"
#include "chrome/browser/profiles/profile.h"
#include "chrome/browser/search/search.h"
#include "chrome/browser/tab_contents/tab_util.h"
#include "chrome/browser/ui/browser_navigator.h"
#include "chrome/browser/ui/tab_contents/core_tab_helper.h"
#include "chrome/browser/ui/tab_contents/core_tab_helper_delegate.h"
#include "chrome/common/chrome_switches.h"
#include "chrome/common/pref_names.h"
#include "chrome/common/prerender_messages.h"
#include "content/public/browser/browser_thread.h"
#include "content/public/browser/devtools_agent_host.h"
#include "content/public/browser/navigation_controller.h"
#include "content/public/browser/notification_service.h"
#include "content/public/browser/notification_source.h"
#include "content/public/browser/render_process_host.h"
#include "content/public/browser/render_view_host.h"
#include "content/public/browser/session_storage_namespace.h"
#include "content/public/browser/web_contents.h"
#include "content/public/browser/web_contents_delegate.h"
#include "content/public/browser/web_contents_view.h"
#include "content/public/common/favicon_url.h"
#include "content/public/common/url_constants.h"
#include "extensions/common/constants.h"
#include "net/url_request/url_request_context.h"
#include "net/url_request/url_request_context_getter.h"

using content::BrowserThread;
using content::RenderViewHost;
using content::SessionStorageNamespace;
using content::WebContents;
using predictors::LoggedInPredictorTable;

namespace prerender {

namespace {

// Time interval at which periodic cleanups are performed.
const int kPeriodicCleanupIntervalMs = 1000;

// Valid HTTP methods for prerendering.
const char* const kValidHttpMethods[] = {
  "GET",
  "HEAD",
  "OPTIONS",
  "POST",
  "TRACE",
};

// Length of prerender history, for display in chrome://net-internals
const int kHistoryLength = 100;

// Timeout, in ms, for a session storage namespace merge.
const int kSessionStorageNamespaceMergeTimeoutMs = 500;

// Indicates whether a Prerender has been cancelled such that we need
// a dummy replacement for the purpose of recording the correct PPLT for
// the Match Complete case.
// Traditionally, "Match" means that a prerendered page was actually visited &
// the prerender was used.  Our goal is to have "Match" cases line up in the
// control group & the experiment group, so that we can make meaningful
// comparisons of improvements.  However, in the control group, since we don't
// actually perform prerenders, many of the cancellation reasons cannot be
// detected.  Therefore, in the Prerender group, when we cancel for one of these
// reasons, we keep track of a dummy Prerender representing what we would
// have in the control group.  If that dummy prerender in the prerender group
// would then be swapped in (but isn't actually b/c it's a dummy), we record
// this as a MatchComplete.  This allows us to compare MatchComplete's
// across Prerender & Control group which ideally should be lining up.
// This ensures that there is no bias in terms of the page load times
// of the pages forming the difference between the two sets.

bool NeedMatchCompleteDummyForFinalStatus(FinalStatus final_status) {
  return final_status != FINAL_STATUS_USED &&
      final_status != FINAL_STATUS_TIMED_OUT &&
      final_status != FINAL_STATUS_MANAGER_SHUTDOWN &&
      final_status != FINAL_STATUS_APP_TERMINATING &&
      final_status != FINAL_STATUS_WINDOW_OPENER &&
      final_status != FINAL_STATUS_CACHE_OR_HISTORY_CLEARED &&
      final_status != FINAL_STATUS_CANCELLED &&
      final_status != FINAL_STATUS_DEVTOOLS_ATTACHED &&
      final_status != FINAL_STATUS_CROSS_SITE_NAVIGATION_PENDING &&
      final_status != FINAL_STATUS_PAGE_BEING_CAPTURED &&
      final_status != FINAL_STATUS_NAVIGATION_UNCOMMITTED;
}

void CheckIfCookiesExistForDomainResultOnUIThread(
    const net::CookieMonster::HasCookiesForETLDP1Callback& callback,
    bool cookies_exist) {
  DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
  callback.Run(cookies_exist);
}

void CheckIfCookiesExistForDomainResultOnIOThread(
    const net::CookieMonster::HasCookiesForETLDP1Callback& callback,
    bool cookies_exist) {
  DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
  BrowserThread::PostTask(
      BrowserThread::UI,
      FROM_HERE,
      base::Bind(&CheckIfCookiesExistForDomainResultOnUIThread,
                 callback,
                 cookies_exist));
}

void CheckIfCookiesExistForDomainOnIOThread(
    net::URLRequestContextGetter* rq_context,
    const std::string& domain_key,
    const net::CookieMonster::HasCookiesForETLDP1Callback& callback) {
  DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
  net::CookieStore* cookie_store =
      rq_context->GetURLRequestContext()->cookie_store();
  cookie_store->GetCookieMonster()->HasCookiesForETLDP1Async(
      domain_key,
      base::Bind(&CheckIfCookiesExistForDomainResultOnIOThread, callback));
}

}  // namespace

class PrerenderManager::OnCloseWebContentsDeleter
    : public content::WebContentsDelegate,
      public base::SupportsWeakPtr<
          PrerenderManager::OnCloseWebContentsDeleter> {
 public:
  OnCloseWebContentsDeleter(PrerenderManager* manager,
                            WebContents* tab)
      : manager_(manager),
        tab_(tab),
        suppressed_dialog_(false) {
    tab_->SetDelegate(this);
    base::MessageLoop::current()->PostDelayedTask(FROM_HERE,
        base::Bind(&OnCloseWebContentsDeleter::ScheduleWebContentsForDeletion,
                   AsWeakPtr(), true),
        base::TimeDelta::FromSeconds(kDeleteWithExtremePrejudiceSeconds));
  }

  virtual void CloseContents(WebContents* source) OVERRIDE {
    DCHECK_EQ(tab_, source);
    ScheduleWebContentsForDeletion(false);
  }

  virtual void SwappedOut(WebContents* source) OVERRIDE {
    DCHECK_EQ(tab_, source);
    ScheduleWebContentsForDeletion(false);
  }

  virtual bool ShouldSuppressDialogs() OVERRIDE {
    // Use this as a proxy for getting statistics on how often we fail to honor
    // the beforeunload event.
    suppressed_dialog_ = true;
    return true;
  }

 private:
  static const int kDeleteWithExtremePrejudiceSeconds = 3;

  void ScheduleWebContentsForDeletion(bool timeout) {
    UMA_HISTOGRAM_BOOLEAN("Prerender.TabContentsDeleterTimeout", timeout);
    UMA_HISTOGRAM_BOOLEAN("Prerender.TabContentsDeleterSuppressedDialog",
                          suppressed_dialog_);
    tab_->SetDelegate(NULL);
    manager_->ScheduleDeleteOldWebContents(tab_.release(), this);
    // |this| is deleted at this point.
  }

  PrerenderManager* manager_;
  scoped_ptr<WebContents> tab_;
  bool suppressed_dialog_;

  DISALLOW_COPY_AND_ASSIGN(OnCloseWebContentsDeleter);
};

// static
int PrerenderManager::prerenders_per_session_count_ = 0;

// static
PrerenderManager::PrerenderManagerMode PrerenderManager::mode_ =
    PRERENDER_MODE_ENABLED;

struct PrerenderManager::NavigationRecord {
  NavigationRecord(const GURL& url, base::TimeTicks time)
      : url(url),
        time(time) {
  }

  GURL url;
  base::TimeTicks time;
};

PrerenderManager::PrerenderedWebContentsData::
PrerenderedWebContentsData(Origin origin) : origin(origin) {
}

PrerenderManager::WouldBePrerenderedWebContentsData::
WouldBePrerenderedWebContentsData(Origin origin)
    : origin(origin),
      state(WAITING_FOR_PROVISIONAL_LOAD) {
}

PrerenderManager::PrerenderManager(Profile* profile,
                                   PrerenderTracker* prerender_tracker)
    : enabled_(profile && profile->GetPrefs() &&
          profile->GetPrefs()->GetBoolean(prefs::kNetworkPredictionEnabled)),
      profile_(profile),
      prerender_tracker_(prerender_tracker),
      prerender_contents_factory_(PrerenderContents::CreateFactory()),
      last_prerender_start_time_(GetCurrentTimeTicks() -
          base::TimeDelta::FromMilliseconds(kMinTimeBetweenPrerendersMs)),
      prerender_history_(new PrerenderHistory(kHistoryLength)),
      histograms_(new PrerenderHistograms()) {
  // There are some assumptions that the PrerenderManager is on the UI thread.
  // Any other checks simply make sure that the PrerenderManager is accessed on
  // the same thread that it was created on.
  DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));

  if (IsLocalPredictorEnabled())
    local_predictor_.reset(new PrerenderLocalPredictor(this));

  if (IsLoggedInPredictorEnabled() && !profile_->IsOffTheRecord()) {
    predictors::PredictorDatabase* predictor_db =
        predictors::PredictorDatabaseFactory::GetForProfile(profile);
    if (predictor_db) {
      logged_in_predictor_table_ = predictor_db->logged_in_table();
      scoped_ptr<LoggedInStateMap> new_state_map(new LoggedInStateMap);
      LoggedInStateMap* new_state_map_ptr = new_state_map.get();
      BrowserThread::PostTaskAndReply(
          BrowserThread::DB, FROM_HERE,
          base::Bind(&LoggedInPredictorTable::GetAllData,
                     logged_in_predictor_table_,
                     new_state_map_ptr),
          base::Bind(&PrerenderManager::LoggedInPredictorDataReceived,
                     AsWeakPtr(),
                     base::Passed(&new_state_map)));
    }
  }

  // Certain experiments override our default config_ values.
  switch (PrerenderManager::GetMode()) {
    case PrerenderManager::PRERENDER_MODE_EXPERIMENT_MULTI_PRERENDER_GROUP:
      config_.max_link_concurrency = 4;
      config_.max_link_concurrency_per_launcher = 2;
      break;
    case PrerenderManager::PRERENDER_MODE_EXPERIMENT_15MIN_TTL_GROUP:
      config_.time_to_live = base::TimeDelta::FromMinutes(15);
      break;
    default:
      break;
  }

  notification_registrar_.Add(
      this, chrome::NOTIFICATION_COOKIE_CHANGED,
      content::NotificationService::AllBrowserContextsAndSources());

  MediaCaptureDevicesDispatcher::GetInstance()->AddObserver(this);
}

PrerenderManager::~PrerenderManager() {
  MediaCaptureDevicesDispatcher::GetInstance()->RemoveObserver(this);

  // The earlier call to BrowserContextKeyedService::Shutdown() should have
  // emptied these vectors already.
  DCHECK(active_prerenders_.empty());
  DCHECK(to_delete_prerenders_.empty());
}

void PrerenderManager::Shutdown() {
  DestroyAllContents(FINAL_STATUS_MANAGER_SHUTDOWN);
  STLDeleteElements(&prerender_conditions_);
  on_close_web_contents_deleters_.clear();
  // Must happen before |profile_| is set to NULL as
  // |local_predictor_| accesses it.
  if (local_predictor_)
    local_predictor_->Shutdown();
  profile_ = NULL;

  DCHECK(active_prerenders_.empty());
}

PrerenderHandle* PrerenderManager::AddPrerenderFromLinkRelPrerender(
    int process_id,
    int route_id,
    const GURL& url,
    const content::Referrer& referrer,
    const gfx::Size& size) {
  Origin origin = ORIGIN_LINK_REL_PRERENDER_CROSSDOMAIN;
  SessionStorageNamespace* session_storage_namespace = NULL;
  // Unit tests pass in a process_id == -1.
  if (process_id != -1) {
    RenderViewHost* source_render_view_host =
        RenderViewHost::FromID(process_id, route_id);
    if (!source_render_view_host)
      return NULL;
    WebContents* source_web_contents =
        WebContents::FromRenderViewHost(source_render_view_host);
    if (!source_web_contents)
      return NULL;
    if (source_web_contents->GetURL().host() == url.host())
      origin = ORIGIN_LINK_REL_PRERENDER_SAMEDOMAIN;
    // TODO(ajwong): This does not correctly handle storage for isolated apps.
    session_storage_namespace =
        source_web_contents->GetController()
            .GetDefaultSessionStorageNamespace();
  }

  // If the prerender request comes from a recently cancelled prerender that
  // |this| still owns, then abort the prerender.
  for (ScopedVector<PrerenderData>::iterator it = to_delete_prerenders_.begin();
       it != to_delete_prerenders_.end(); ++it) {
    PrerenderContents* prerender_contents = (*it)->contents();
    int contents_child_id;
    int contents_route_id;
    if (prerender_contents->GetChildId(&contents_child_id) &&
        prerender_contents->GetRouteId(&contents_route_id)) {
      if (contents_child_id == process_id && contents_route_id == route_id)
        return NULL;
    }
  }

  if (PrerenderData* parent_prerender_data =
          FindPrerenderDataForChildAndRoute(process_id, route_id)) {
    // Instead of prerendering from inside of a running prerender, we will defer
    // this request until its launcher is made visible.
    if (PrerenderContents* contents = parent_prerender_data->contents()) {
      PrerenderHandle* prerender_handle =
          new PrerenderHandle(static_cast<PrerenderData*>(NULL));
      scoped_ptr<PrerenderContents::PendingPrerenderInfo>
          pending_prerender_info(new PrerenderContents::PendingPrerenderInfo(
              prerender_handle->weak_ptr_factory_.GetWeakPtr(),
              origin, url, referrer, size));

      contents->AddPendingPrerender(pending_prerender_info.Pass());
      return prerender_handle;
    }
  }

  return AddPrerender(origin, process_id, url, referrer, size,
                      session_storage_namespace);
}

PrerenderHandle* PrerenderManager::AddPrerenderFromOmnibox(
    const GURL& url,
    SessionStorageNamespace* session_storage_namespace,
    const gfx::Size& size) {
  if (!IsOmniboxEnabled(profile_))
    return NULL;
  return AddPrerender(ORIGIN_OMNIBOX, -1, url, content::Referrer(), size,
                      session_storage_namespace);
}

PrerenderHandle* PrerenderManager::AddPrerenderFromLocalPredictor(
    const GURL& url,
    SessionStorageNamespace* session_storage_namespace,
    const gfx::Size& size) {
  return AddPrerender(ORIGIN_LOCAL_PREDICTOR, -1, url, content::Referrer(),
                      size, session_storage_namespace);
}

PrerenderHandle* PrerenderManager::AddPrerenderFromExternalRequest(
    const GURL& url,
    const content::Referrer& referrer,
    SessionStorageNamespace* session_storage_namespace,
    const gfx::Size& size) {
  return AddPrerender(ORIGIN_EXTERNAL_REQUEST, -1, url, referrer, size,
                      session_storage_namespace);
}

PrerenderHandle* PrerenderManager::AddPrerenderForInstant(
    const GURL& url,
    content::SessionStorageNamespace* session_storage_namespace,
    const gfx::Size& size) {
  DCHECK(chrome::ShouldPrefetchSearchResults());
  return AddPrerender(ORIGIN_INSTANT, -1, url, content::Referrer(), size,
                      session_storage_namespace);
}

void PrerenderManager::DestroyPrerenderForRenderView(
    int process_id, int view_id, FinalStatus final_status) {
  DCHECK(CalledOnValidThread());
  if (PrerenderData* prerender_data =
          FindPrerenderDataForChildAndRoute(process_id, view_id)) {
    prerender_data->contents()->Destroy(final_status);
  }
}

void PrerenderManager::CancelAllPrerenders() {
  DCHECK(CalledOnValidThread());
  while (!active_prerenders_.empty()) {
    PrerenderContents* prerender_contents =
        active_prerenders_.front()->contents();
    prerender_contents->Destroy(FINAL_STATUS_CANCELLED);
  }
}

bool PrerenderManager::MaybeUsePrerenderedPage(const GURL& url,
                                               chrome::NavigateParams* params) {
  DCHECK(CalledOnValidThread());

  content::WebContents* web_contents = params->target_contents;
  DCHECK(!IsWebContentsPrerendering(web_contents, NULL));

  // Don't prerender if the navigation involves some special parameters.
  if (params->uses_post || !params->extra_headers.empty())
    return false;

  DeleteOldEntries();
  to_delete_prerenders_.clear();

  // First, try to find prerender data with the correct session storage
  // namespace.
  // TODO(ajwong): This doesn't handle isolated apps correctly.
  PrerenderData* prerender_data = FindPrerenderData(
      url,
      web_contents->GetController().GetDefaultSessionStorageNamespace());

  // If this failed, we may still find a prerender for the same URL, but a
  // different session storage namespace. If we do, we might have to perform
  // a merge.
  if (!prerender_data) {
    prerender_data = FindPrerenderData(url, NULL);
  } else {
    RecordEvent(prerender_data->contents(),
                PRERENDER_EVENT_SWAPIN_CANDIDATE_NAMESPACE_MATCHES);
  }

  if (!prerender_data)
    return false;
  RecordEvent(prerender_data->contents(), PRERENDER_EVENT_SWAPIN_CANDIDATE);
  DCHECK(prerender_data->contents());

  // If there is currently a merge pending for this prerender data,
  // or this webcontents, do not swap in, but give the merge a chance to
  // finish and swap into the intended target webcontents.
  if (prerender_data->pending_swap())
    return false;

  RecordEvent(prerender_data->contents(),
              PRERENDER_EVENT_SWAPIN_NO_MERGE_PENDING);
  SessionStorageNamespace* target_namespace =
      web_contents->GetController().GetDefaultSessionStorageNamespace();
  SessionStorageNamespace* prerender_namespace =
      prerender_data->contents()->GetSessionStorageNamespace();
  // Only when actually prerendering is session storage namespace merging an
  // issue. For the control group, it will be assumed that the merge succeeded.
  if (prerender_namespace && prerender_namespace != target_namespace &&
      !prerender_namespace->IsAliasOf(target_namespace)) {
    if (!ShouldMergeSessionStorageNamespaces()) {
      RecordEvent(prerender_data->contents(),
                  PRERENDER_EVENT_SWAPIN_MERGING_DISABLED);
      return false;
    }
    RecordEvent(prerender_data->contents(),
                PRERENDER_EVENT_SWAPIN_ISSUING_MERGE);
    prerender_data->set_pending_swap(new PendingSwap(
        this, web_contents, prerender_data, url,
        params->should_replace_current_entry));
    prerender_data->pending_swap()->BeginSwap();
    // Although this returns false, creating a PendingSwap registers with
    // PrerenderTracker to throttle MAIN_FRAME navigations while the swap is
    // pending.
    return false;
  }

  // No need to merge; swap synchronously.
  WebContents* new_web_contents = SwapInternal(
      url, web_contents, prerender_data,
      params->should_replace_current_entry);
  if (!new_web_contents)
    return false;

  // Record the new target_contents for the callers.
  params->target_contents = new_web_contents;
  return true;
}

WebContents* PrerenderManager::SwapInternal(
    const GURL& url,
    WebContents* web_contents,
    PrerenderData* prerender_data,
    bool should_replace_current_entry) {
  DCHECK(CalledOnValidThread());
  DCHECK(!IsWebContentsPrerendering(web_contents, NULL));

  // Only swap if the target WebContents has a CoreTabHelper delegate to swap
  // out of it. For a normal WebContents, this is if it is in a TabStripModel.
  CoreTabHelper* core_tab_helper = CoreTabHelper::FromWebContents(web_contents);
  if (!core_tab_helper || !core_tab_helper->delegate()) {
    RecordEvent(prerender_data->contents(), PRERENDER_EVENT_SWAPIN_NO_DELEGATE);
    return NULL;
  }

  if (IsNoSwapInExperiment(prerender_data->contents()->experiment_id()))
    return NULL;

  if (WebContents* new_web_contents =
      prerender_data->contents()->prerender_contents()) {
    if (web_contents == new_web_contents)
      return NULL;  // Do not swap in to ourself.

    // We cannot swap in if there is no last committed entry, because we would
    // show a blank page under an existing entry from the current tab.  Even if
    // there is a pending entry, it may not commit.
    // TODO(creis): If there is a pending navigation and no last committed
    // entry, we might be able to transfer the network request instead.
    if (!new_web_contents->GetController().CanPruneAllButLastCommitted()) {
      // Abort this prerender so it is not used later. http://crbug.com/292121
      prerender_data->contents()->Destroy(FINAL_STATUS_NAVIGATION_UNCOMMITTED);
      return NULL;
    }
  }

  // Do not use the prerendered version if there is an opener object.
  if (web_contents->HasOpener()) {
    prerender_data->contents()->Destroy(FINAL_STATUS_WINDOW_OPENER);
    return NULL;
  }

  // Do not swap in the prerender if the current WebContents is being captured.
  if (web_contents->GetCapturerCount() > 0) {
    prerender_data->contents()->Destroy(FINAL_STATUS_PAGE_BEING_CAPTURED);
    return NULL;
  }

  // If we are just in the control group (which can be detected by noticing
  // that prerendering hasn't even started yet), record that |web_contents| now
  // would be showing a prerendered contents, but otherwise, don't do anything.
  if (!prerender_data->contents()->prerendering_has_started()) {
    MarkWebContentsAsWouldBePrerendered(web_contents,
                                        prerender_data->contents()->origin());
    prerender_data->contents()->Destroy(FINAL_STATUS_WOULD_HAVE_BEEN_USED);
    return NULL;
  }

  // Don't use prerendered pages if debugger is attached to the tab.
  // See http://crbug.com/98541
  if (content::DevToolsAgentHost::IsDebuggerAttached(web_contents)) {
    DestroyAndMarkMatchCompleteAsUsed(prerender_data->contents(),
                                      FINAL_STATUS_DEVTOOLS_ATTACHED);
    return NULL;
  }

  // If the prerendered page is in the middle of a cross-site navigation,
  // don't swap it in because there isn't a good way to merge histories.
  if (prerender_data->contents()->IsCrossSiteNavigationPending()) {
    DestroyAndMarkMatchCompleteAsUsed(
        prerender_data->contents(),
        FINAL_STATUS_CROSS_SITE_NAVIGATION_PENDING);
    return NULL;
  }

  // For bookkeeping purposes, we need to mark this WebContents to
  // reflect that it would have been prerendered.
  if (GetMode() == PRERENDER_MODE_EXPERIMENT_NO_USE_GROUP) {
    MarkWebContentsAsWouldBePrerendered(web_contents,
                                        prerender_data->contents()->origin());
    prerender_data->contents()->Destroy(FINAL_STATUS_WOULD_HAVE_BEEN_USED);
    return NULL;
  }

  int child_id, route_id;
  CHECK(prerender_data->contents()->GetChildId(&child_id));
  CHECK(prerender_data->contents()->GetRouteId(&route_id));

  // Try to set the prerendered page as used, so any subsequent attempts to
  // cancel on other threads will fail.  If this fails because the prerender
  // was already cancelled, possibly on another thread, fail.
  if (!prerender_tracker_->TryUse(child_id, route_id))
    return NULL;

  // At this point, we've determined that we will use the prerender.
  if (prerender_data->pending_swap())
    prerender_data->pending_swap()->set_swap_successful(true);
  ScopedVector<PrerenderData>::iterator to_erase =
      FindIteratorForPrerenderContents(prerender_data->contents());
  DCHECK(active_prerenders_.end() != to_erase);
  DCHECK_EQ(prerender_data, *to_erase);
  scoped_ptr<PrerenderContents>
      prerender_contents(prerender_data->ReleaseContents());
  active_prerenders_.erase(to_erase);

  if (!prerender_contents->load_start_time().is_null()) {
    histograms_->RecordTimeUntilUsed(
        prerender_contents->origin(),
        GetCurrentTimeTicks() - prerender_contents->load_start_time());
  }

  histograms_->RecordPerSessionCount(prerender_contents->origin(),
                                     ++prerenders_per_session_count_);
  histograms_->RecordUsedPrerender(prerender_contents->origin());
  prerender_contents->SetFinalStatus(FINAL_STATUS_USED);

  // Start pending prerender requests from the PrerenderContents, if there are
  // any.
  prerender_contents->PrepareForUse();

  WebContents* new_web_contents =
      prerender_contents->ReleasePrerenderContents();
  WebContents* old_web_contents = web_contents;
  DCHECK(new_web_contents);
  DCHECK(old_web_contents);

  MarkWebContentsAsPrerendered(new_web_contents, prerender_contents->origin());

  // Merge the browsing history.
  new_web_contents->GetController().CopyStateFromAndPrune(
      &old_web_contents->GetController(),
      should_replace_current_entry);
  CoreTabHelper::FromWebContents(old_web_contents)->delegate()->
      SwapTabContents(old_web_contents, new_web_contents);
  prerender_contents->CommitHistory(new_web_contents);

  GURL icon_url = prerender_contents->icon_url();

  if (!icon_url.is_empty()) {
#if defined(OS_ANDROID)
    // Do the delayed icon fetch since we didn't download
    // the favicon during prerendering on mobile devices.
    FaviconTabHelper * favicon_tap_helper =
        FaviconTabHelper::FromWebContents(new_web_contents);
    favicon_tap_helper->set_should_fetch_icons(true);
    favicon_tap_helper->FetchFavicon(icon_url);
#endif  // defined(OS_ANDROID)

    std::vector<content::FaviconURL> urls;
    urls.push_back(content::FaviconURL(icon_url, content::FaviconURL::FAVICON));
    FaviconTabHelper::FromWebContents(new_web_contents)->
        DidUpdateFaviconURL(prerender_contents->page_id(), urls);
  }

  // Update PPLT metrics:
  // If the tab has finished loading, record a PPLT of 0.
  // If the tab is still loading, reset its start time to the current time.
  PrerenderTabHelper* prerender_tab_helper =
      PrerenderTabHelper::FromWebContents(new_web_contents);
  DCHECK(prerender_tab_helper != NULL);
  prerender_tab_helper->PrerenderSwappedIn();

  if (old_web_contents->NeedToFireBeforeUnload()) {
    // Schedule the delete to occur after the tab has run its unload handlers.
    // TODO(davidben): Honor the beforeunload event. http://crbug.com/304932
    on_close_web_contents_deleters_.push_back(
        new OnCloseWebContentsDeleter(this, old_web_contents));
    old_web_contents->GetRenderViewHost()->
        FirePageBeforeUnload(false);
  } else {
    // No unload handler to run, so delete asap.
    ScheduleDeleteOldWebContents(old_web_contents, NULL);
  }

  // TODO(cbentzel): Should prerender_contents move to the pending delete
  //                 list, instead of deleting directly here?
  AddToHistory(prerender_contents.get());
  RecordNavigation(url);
  return new_web_contents;
}

void PrerenderManager::MoveEntryToPendingDelete(PrerenderContents* entry,
                                                FinalStatus final_status) {
  DCHECK(CalledOnValidThread());
  DCHECK(entry);

  ScopedVector<PrerenderData>::iterator it =
      FindIteratorForPrerenderContents(entry);
  DCHECK(it != active_prerenders_.end());

  // If this PrerenderContents is being deleted due to a cancellation any time
  // after the prerender has started then we need to create a dummy replacement
  // for PPLT accounting purposes for the Match Complete group. This is the case
  // if the cancellation is for any reason that would not occur in the control
  // group case.
  if (entry->prerendering_has_started() &&
      entry->match_complete_status() ==
          PrerenderContents::MATCH_COMPLETE_DEFAULT &&
      NeedMatchCompleteDummyForFinalStatus(final_status) &&
      ActuallyPrerendering()) {
    // TODO(tburkard): I'd like to DCHECK that we are actually prerendering.
    // However, what if new conditions are added and
    // NeedMatchCompleteDummyForFinalStatus is not being updated.  Not sure
    // what's the best thing to do here.  For now, I will just check whether
    // we are actually prerendering.
    (*it)->MakeIntoMatchCompleteReplacement();
  } else {
    to_delete_prerenders_.push_back(*it);
    active_prerenders_.weak_erase(it);
  }

  // Destroy the old WebContents relatively promptly to reduce resource usage.
  PostCleanupTask();
}

// static
void PrerenderManager::RecordPerceivedPageLoadTime(
    base::TimeDelta perceived_page_load_time,
    double fraction_plt_elapsed_at_swap_in,
    WebContents* web_contents,
    const GURL& url) {
  DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
  PrerenderManager* prerender_manager =
      PrerenderManagerFactory::GetForProfile(
          Profile::FromBrowserContext(web_contents->GetBrowserContext()));
  if (!prerender_manager)
    return;
  if (!prerender_manager->IsEnabled())
    return;

  Origin prerender_origin = ORIGIN_NONE;
  if (prerender_manager->IsWebContentsPrerendering(web_contents,
                                                   &prerender_origin)) {
    prerender_manager->histograms_->RecordPageLoadTimeNotSwappedIn(
        prerender_origin, perceived_page_load_time, url);
    return;
  }

  bool was_prerender = prerender_manager->IsWebContentsPrerendered(
      web_contents, &prerender_origin);
  bool was_complete_prerender = was_prerender ||
      prerender_manager->WouldWebContentsBePrerendered(web_contents,
                                                       &prerender_origin);
  prerender_manager->histograms_->RecordPerceivedPageLoadTime(
      prerender_origin, perceived_page_load_time, was_prerender,
      was_complete_prerender, url);

  if (was_prerender) {
    prerender_manager->histograms_->RecordPercentLoadDoneAtSwapin(
        prerender_origin, fraction_plt_elapsed_at_swap_in);
  }
  if (prerender_manager->local_predictor_.get()) {
    prerender_manager->local_predictor_->
        OnPLTEventForURL(url, perceived_page_load_time);
  }
}

void PrerenderManager::RecordFractionPixelsFinalAtSwapin(
    content::WebContents* web_contents,
    double fraction) {
  Origin origin = ORIGIN_NONE;
  bool is_prerendered = IsWebContentsPrerendered(web_contents, &origin);
  DCHECK(is_prerendered);
  histograms_->RecordFractionPixelsFinalAtSwapin(origin, fraction);
}

void PrerenderManager::set_enabled(bool enabled) {
  DCHECK(CalledOnValidThread());
  enabled_ = enabled;
}

// static
PrerenderManager::PrerenderManagerMode PrerenderManager::GetMode() {
  return mode_;
}

// static
void PrerenderManager::SetMode(PrerenderManagerMode mode) {
  mode_ = mode;
}

// static
const char* PrerenderManager::GetModeString() {
  switch (mode_) {
    case PRERENDER_MODE_DISABLED:
      return "_Disabled";
    case PRERENDER_MODE_ENABLED:
    case PRERENDER_MODE_EXPERIMENT_PRERENDER_GROUP:
      return "_Enabled";
    case PRERENDER_MODE_EXPERIMENT_CONTROL_GROUP:
      return "_Control";
    case PRERENDER_MODE_EXPERIMENT_MULTI_PRERENDER_GROUP:
      return "_Multi";
    case PRERENDER_MODE_EXPERIMENT_15MIN_TTL_GROUP:
      return "_15MinTTL";
    case PRERENDER_MODE_EXPERIMENT_NO_USE_GROUP:
      return "_NoUse";
    case PRERENDER_MODE_MAX:
    default:
      NOTREACHED() << "Invalid PrerenderManager mode.";
      break;
  };
  return "";
}

// static
bool PrerenderManager::IsPrerenderingPossible() {
  return GetMode() != PRERENDER_MODE_DISABLED;
}

// static
bool PrerenderManager::ActuallyPrerendering() {
  return IsPrerenderingPossible() && !IsControlGroup(kNoExperiment);
}

// static
bool PrerenderManager::IsControlGroup(uint8 experiment_id) {
  return GetMode() == PRERENDER_MODE_EXPERIMENT_CONTROL_GROUP ||
      IsControlGroupExperiment(experiment_id);
}

// static
bool PrerenderManager::IsNoUseGroup() {
  return GetMode() == PRERENDER_MODE_EXPERIMENT_NO_USE_GROUP;
}

bool PrerenderManager::IsWebContentsPrerendering(
    const WebContents* web_contents,
    Origin* origin) const {
  DCHECK(CalledOnValidThread());
  if (PrerenderContents* prerender_contents =
          GetPrerenderContents(web_contents)) {
    if (origin)
      *origin = prerender_contents->origin();
    return true;
  }

  // Also look through the pending-deletion list.
  for (ScopedVector<PrerenderData>::const_iterator it =
           to_delete_prerenders_.begin();
       it != to_delete_prerenders_.end();
       ++it) {
    if (PrerenderContents* prerender_contents = (*it)->contents()) {
      WebContents* prerender_web_contents =
          prerender_contents->prerender_contents();
      if (prerender_web_contents == web_contents) {
        if (origin)
          *origin = prerender_contents->origin();
        return true;
      }
    }
  }

  return false;
}

bool PrerenderManager::HasPrerenderedUrl(
    GURL url,
    content::WebContents* web_contents) const {
  content::SessionStorageNamespace* session_storage_namespace = web_contents->
      GetController().GetDefaultSessionStorageNamespace();

  for (ScopedVector<PrerenderData>::const_iterator it =
           active_prerenders_.begin();
       it != active_prerenders_.end(); ++it) {
    PrerenderContents* prerender_contents = (*it)->contents();
    if (prerender_contents->Matches(url, session_storage_namespace)) {
      return true;
    }
  }
  return false;
}

PrerenderContents* PrerenderManager::GetPrerenderContents(
    const content::WebContents* web_contents) const {
  DCHECK(CalledOnValidThread());
  for (ScopedVector<PrerenderData>::const_iterator it =
           active_prerenders_.begin();
       it != active_prerenders_.end(); ++it) {
    WebContents* prerender_web_contents =
        (*it)->contents()->prerender_contents();
    if (prerender_web_contents == web_contents) {
      return (*it)->contents();
    }
  }
  return NULL;
}

const std::vector<WebContents*>
PrerenderManager::GetAllPrerenderingContents() const {
  DCHECK(CalledOnValidThread());
  std::vector<WebContents*> result;

  for (ScopedVector<PrerenderData>::const_iterator it =
           active_prerenders_.begin();
       it != active_prerenders_.end(); ++it) {
    if (WebContents* contents = (*it)->contents()->prerender_contents())
      result.push_back(contents);
  }

  return result;
}

void PrerenderManager::MarkWebContentsAsPrerendered(WebContents* web_contents,
                                                    Origin origin) {
  DCHECK(CalledOnValidThread());
  prerendered_web_contents_data_.insert(
      base::hash_map<content::WebContents*,
                     PrerenderedWebContentsData>::value_type(
                         web_contents, PrerenderedWebContentsData(origin)));
}

void PrerenderManager::MarkWebContentsAsWouldBePrerendered(
    WebContents* web_contents,
    Origin origin) {
  DCHECK(CalledOnValidThread());
  would_be_prerendered_map_.insert(
      base::hash_map<content::WebContents*,
                     WouldBePrerenderedWebContentsData>::value_type(
                         web_contents,
                         WouldBePrerenderedWebContentsData(origin)));
}

void PrerenderManager::MarkWebContentsAsNotPrerendered(
    WebContents* web_contents) {
  DCHECK(CalledOnValidThread());
  prerendered_web_contents_data_.erase(web_contents);
  base::hash_map<content::WebContents*, WouldBePrerenderedWebContentsData>::
      iterator it = would_be_prerendered_map_.find(web_contents);
  if (it != would_be_prerendered_map_.end()) {
    if (it->second.state ==
            WouldBePrerenderedWebContentsData::WAITING_FOR_PROVISIONAL_LOAD) {
      it->second.state =
          WouldBePrerenderedWebContentsData::SEEN_PROVISIONAL_LOAD;
    } else {
      would_be_prerendered_map_.erase(it);
    }
  }
}

bool PrerenderManager::IsWebContentsPrerendered(
    content::WebContents* web_contents,
    Origin* origin) const {
  DCHECK(CalledOnValidThread());
  base::hash_map<content::WebContents*, PrerenderedWebContentsData>::
      const_iterator it = prerendered_web_contents_data_.find(web_contents);
  if (it == prerendered_web_contents_data_.end())
    return false;
  if (origin)
    *origin = it->second.origin;
  return true;
}

bool PrerenderManager::WouldWebContentsBePrerendered(
    WebContents* web_contents,
    Origin* origin) const {
  DCHECK(CalledOnValidThread());
  base::hash_map<content::WebContents*, WouldBePrerenderedWebContentsData>::
      const_iterator it = would_be_prerendered_map_.find(web_contents);
  if (it == would_be_prerendered_map_.end())
    return false;
  if (origin)
    *origin = it->second.origin;
  return true;
}

bool PrerenderManager::HasRecentlyBeenNavigatedTo(Origin origin,
                                                  const GURL& url) {
  DCHECK(CalledOnValidThread());

  CleanUpOldNavigations();
  std::list<NavigationRecord>::const_reverse_iterator end = navigations_.rend();
  for (std::list<NavigationRecord>::const_reverse_iterator it =
           navigations_.rbegin();
       it != end;
       ++it) {
    if (it->url == url) {
      base::TimeDelta delta = GetCurrentTimeTicks() - it->time;
      histograms_->RecordTimeSinceLastRecentVisit(origin, delta);
      return true;
    }
  }

  return false;
}

// static
bool PrerenderManager::IsValidHttpMethod(const std::string& method) {
  // method has been canonicalized to upper case at this point so we can just
  // compare them.
  DCHECK_EQ(method, StringToUpperASCII(method));
  for (size_t i = 0; i < arraysize(kValidHttpMethods); ++i) {
    if (method.compare(kValidHttpMethods[i]) == 0)
      return true;
  }

  return false;
}

// static
bool PrerenderManager::DoesURLHaveValidScheme(const GURL& url) {
  return (url.SchemeIsHTTPOrHTTPS() ||
          url.SchemeIs(extensions::kExtensionScheme) ||
          url.SchemeIs("data"));
}

// static
bool PrerenderManager::DoesSubresourceURLHaveValidScheme(const GURL& url) {
  return DoesURLHaveValidScheme(url) || url == GURL(content::kAboutBlankURL);
}

DictionaryValue* PrerenderManager::GetAsValue() const {
  DCHECK(CalledOnValidThread());
  DictionaryValue* dict_value = new DictionaryValue();
  dict_value->Set("history", prerender_history_->GetEntriesAsValue());
  dict_value->Set("active", GetActivePrerendersAsValue());
  dict_value->SetBoolean("enabled", enabled_);
  dict_value->SetBoolean("omnibox_enabled", IsOmniboxEnabled(profile_));
  // If prerender is disabled via a flag this method is not even called.
  std::string enabled_note;
  if (IsControlGroup(kNoExperiment))
    enabled_note += "(Control group: Not actually prerendering) ";
  if (IsNoUseGroup())
    enabled_note += "(No-use group: Not swapping in prerendered pages) ";
  if (GetMode() == PRERENDER_MODE_EXPERIMENT_15MIN_TTL_GROUP)
    enabled_note +=
        "(15 min TTL group: Extended prerender eviction to 15 mins) ";
  dict_value->SetString("enabled_note", enabled_note);
  return dict_value;
}

void PrerenderManager::ClearData(int clear_flags) {
  DCHECK_GE(clear_flags, 0);
  DCHECK_LT(clear_flags, CLEAR_MAX);
  if (clear_flags & CLEAR_PRERENDER_CONTENTS)
    DestroyAllContents(FINAL_STATUS_CACHE_OR_HISTORY_CLEARED);
  // This has to be second, since destroying prerenders can add to the history.
  if (clear_flags & CLEAR_PRERENDER_HISTORY)
    prerender_history_->Clear();
}

void PrerenderManager::RecordFinalStatusWithMatchCompleteStatus(
    Origin origin,
    uint8 experiment_id,
    PrerenderContents::MatchCompleteStatus mc_status,
    FinalStatus final_status) const {
  histograms_->RecordFinalStatus(origin,
                                 experiment_id,
                                 mc_status,
                                 final_status);
}

void PrerenderManager::AddCondition(const PrerenderCondition* condition) {
  prerender_conditions_.push_back(condition);
}

void PrerenderManager::RecordNavigation(const GURL& url) {
  DCHECK(CalledOnValidThread());

  navigations_.push_back(NavigationRecord(url, GetCurrentTimeTicks()));
  CleanUpOldNavigations();
}

// protected
struct PrerenderManager::PrerenderData::OrderByExpiryTime {
  bool operator()(const PrerenderData* a, const PrerenderData* b) const {
    return a->expiry_time() < b->expiry_time();
  }
};

PrerenderManager::PrerenderData::PrerenderData(PrerenderManager* manager,
                                               PrerenderContents* contents,
                                               base::TimeTicks expiry_time)
    : manager_(manager),
      contents_(contents),
      handle_count_(0),
      expiry_time_(expiry_time) {
  DCHECK_NE(static_cast<PrerenderContents*>(NULL), contents_);
}

PrerenderManager::PrerenderData::~PrerenderData() {
}

void PrerenderManager::PrerenderData::MakeIntoMatchCompleteReplacement() {
  DCHECK(contents_);
  contents_->set_match_complete_status(
      PrerenderContents::MATCH_COMPLETE_REPLACED);
  PrerenderData* to_delete = new PrerenderData(manager_, contents_.release(),
                                               expiry_time_);
  contents_.reset(to_delete->contents_->CreateMatchCompleteReplacement());
  manager_->to_delete_prerenders_.push_back(to_delete);
}

void PrerenderManager::PrerenderData::OnHandleCreated(PrerenderHandle* handle) {
  DCHECK_NE(static_cast<PrerenderContents*>(NULL), contents_);
  ++handle_count_;
  contents_->AddObserver(handle);
}

void PrerenderManager::PrerenderData::OnHandleNavigatedAway(
    PrerenderHandle* handle) {
  DCHECK_LT(0, handle_count_);
  DCHECK_NE(static_cast<PrerenderContents*>(NULL), contents_);
  // We intentionally don't decrement the handle count here, so that the
  // prerender won't be canceled until it times out.
  manager_->SourceNavigatedAway(this);
}

void PrerenderManager::PrerenderData::OnHandleCanceled(
    PrerenderHandle* handle) {
  DCHECK_LT(0, handle_count_);
  DCHECK_NE(static_cast<PrerenderContents*>(NULL), contents_);

  if (--handle_count_ == 0) {
    // This will eventually remove this object from active_prerenders_.
    contents_->Destroy(FINAL_STATUS_CANCELLED);
  }
}

void PrerenderManager::PrerenderData::ClearPendingSwap() {
  pending_swap_.reset(NULL);
}

PrerenderContents* PrerenderManager::PrerenderData::ReleaseContents() {
  return contents_.release();
}

PrerenderManager::PendingSwap::PendingSwap(
    PrerenderManager* manager,
    content::WebContents* target_contents,
    PrerenderData* prerender_data,
    const GURL& url,
    bool should_replace_current_entry)
    : content::WebContentsObserver(target_contents),
      manager_(manager),
      target_contents_(target_contents),
      prerender_data_(prerender_data),
      url_(url),
      should_replace_current_entry_(should_replace_current_entry),
      start_time_(base::TimeTicks::Now()),
      swap_successful_(false),
      weak_factory_(this) {
  RenderViewCreated(target_contents->GetRenderViewHost());
}

PrerenderManager::PendingSwap::~PendingSwap() {
  for (size_t i = 0; i < rvh_ids_.size(); i++) {
    manager_->prerender_tracker()->RemovePrerenderPendingSwap(
        rvh_ids_[i], swap_successful_);
  }
}

void PrerenderManager::PendingSwap::BeginSwap() {
  SessionStorageNamespace* target_namespace =
      target_contents_->GetController().GetDefaultSessionStorageNamespace();
  SessionStorageNamespace* prerender_namespace =
      prerender_data_->contents()->GetSessionStorageNamespace();

  prerender_namespace->Merge(
      true, prerender_data_->contents()->child_id(),
      target_namespace,
      base::Bind(&PrerenderManager::PendingSwap::OnMergeCompleted,
                 weak_factory_.GetWeakPtr()));

  merge_timeout_.Start(
      FROM_HERE,
      base::TimeDelta::FromMilliseconds(
          kSessionStorageNamespaceMergeTimeoutMs),
      this, &PrerenderManager::PendingSwap::OnMergeTimeout);
}

void PrerenderManager::PendingSwap::ProvisionalChangeToMainFrameUrl(
        const GURL& url,
        content::RenderViewHost* render_view_host) {
  // We must only cancel the pending swap if the |url| navigated to is not
  // the URL being attempted to be swapped in. That's because in the normal
  // flow, a ProvisionalChangeToMainFrameUrl will happen for the URL attempted
  // to be swapped in immediately after the pending swap has issued its merge.
  if (url != url_)
    prerender_data_->ClearPendingSwap();
}

void PrerenderManager::PendingSwap::DidCommitProvisionalLoadForFrame(
        int64 frame_id,
        const base::string16& frame_unique_name,
        bool is_main_frame,
        const GURL& validated_url,
        content::PageTransition transition_type,
        content::RenderViewHost* render_view_host){
  if (!is_main_frame)
    return;
  prerender_data_->ClearPendingSwap();
}

void PrerenderManager::PendingSwap::RenderViewCreated(
    content::RenderViewHost* render_view_host) {
  // Record the RVH id in the tracker to install throttles on MAIN_FRAME
  // requests from that route.
  int child_id = render_view_host->GetProcess()->GetID();
  int route_id = render_view_host->GetRoutingID();
  PrerenderTracker::ChildRouteIdPair child_route_id_pair(child_id, route_id);
  rvh_ids_.push_back(child_route_id_pair);
  manager_->prerender_tracker()->AddPrerenderPendingSwap(
      child_route_id_pair, url_);
}

void PrerenderManager::PendingSwap::DidFailProvisionalLoad(
        int64 frame_id,
        const base::string16& frame_unique_name,
        bool is_main_frame,
        const GURL& validated_url,
        int error_code,
        const base::string16& error_description,
        content::RenderViewHost* render_view_host) {
  if (!is_main_frame)
    return;
  prerender_data_->ClearPendingSwap();
}

void PrerenderManager::PendingSwap::WebContentsDestroyed(
    content::WebContents* web_contents) {
  prerender_data_->ClearPendingSwap();
}

void PrerenderManager::PendingSwap::RecordEvent(PrerenderEvent event) const {
  manager_->RecordEvent(prerender_data_->contents(), event);
}

void PrerenderManager::PendingSwap::OnMergeCompleted(
    SessionStorageNamespace::MergeResult result) {
  UMA_HISTOGRAM_TIMES("Prerender.SessionStorageNamespaceMergeTime",
                      base::TimeTicks::Now() - start_time_);
  RecordEvent(PRERENDER_EVENT_MERGE_RESULT_MERGE_DONE);

  // Log the exact merge result in a histogram.
  switch (result) {
    case SessionStorageNamespace::MERGE_RESULT_NAMESPACE_NOT_FOUND:
      RecordEvent(PRERENDER_EVENT_MERGE_RESULT_RESULT_NAMESPACE_NOT_FOUND);
      break;
    case SessionStorageNamespace::MERGE_RESULT_NAMESPACE_NOT_ALIAS:
      RecordEvent(PRERENDER_EVENT_MERGE_RESULT_RESULT_NAMESPACE_NOT_ALIAS);
      break;
    case SessionStorageNamespace::MERGE_RESULT_NOT_LOGGING:
      RecordEvent(PRERENDER_EVENT_MERGE_RESULT_RESULT_NOT_LOGGING);
      break;
    case SessionStorageNamespace::MERGE_RESULT_NO_TRANSACTIONS:
      RecordEvent(PRERENDER_EVENT_MERGE_RESULT_RESULT_NO_TRANSACTIONS);
      break;
    case SessionStorageNamespace::MERGE_RESULT_TOO_MANY_TRANSACTIONS:
      RecordEvent(PRERENDER_EVENT_MERGE_RESULT_RESULT_TOO_MANY_TRANSACTIONS);
      break;
    case SessionStorageNamespace::MERGE_RESULT_NOT_MERGEABLE:
      RecordEvent(PRERENDER_EVENT_MERGE_RESULT_RESULT_NOT_MERGEABLE);
      break;
    case SessionStorageNamespace::MERGE_RESULT_MERGEABLE:
      RecordEvent(PRERENDER_EVENT_MERGE_RESULT_RESULT_MERGEABLE);
      break;
    default:
      NOTREACHED();
  }

  if (result != SessionStorageNamespace::MERGE_RESULT_MERGEABLE &&
      result != SessionStorageNamespace::MERGE_RESULT_NO_TRANSACTIONS) {
    RecordEvent(PRERENDER_EVENT_MERGE_RESULT_MERGE_FAILED);
    prerender_data_->ClearPendingSwap();
    return;
  }

  RecordEvent(PRERENDER_EVENT_MERGE_RESULT_SWAPPING_IN);
  // Note that SwapInternal, on success, will delete |prerender_data_| and
  // |this|. Pass in a new GURL object rather than a reference to |url_|.
  //
  // TODO(davidben): See about deleting PrerenderData asynchronously so this
  // behavior is more reasonable.
  WebContents* new_web_contents =
      manager_->SwapInternal(GURL(url_), target_contents_, prerender_data_,
                             should_replace_current_entry_);
  if (!new_web_contents) {
    RecordEvent(PRERENDER_EVENT_MERGE_RESULT_SWAPIN_FAILED);
    prerender_data_->ClearPendingSwap();
  }
}

void PrerenderManager::PendingSwap::OnMergeTimeout() {
  RecordEvent(PRERENDER_EVENT_MERGE_RESULT_TIMED_OUT);
  prerender_data_->ClearPendingSwap();
}

void PrerenderManager::SetPrerenderContentsFactory(
    PrerenderContents::Factory* prerender_contents_factory) {
  DCHECK(CalledOnValidThread());
  prerender_contents_factory_.reset(prerender_contents_factory);
}


void PrerenderManager::StartPendingPrerenders(
    const int process_id,
    ScopedVector<PrerenderContents::PendingPrerenderInfo>* pending_prerenders,
    content::SessionStorageNamespace* session_storage_namespace) {
  for (ScopedVector<PrerenderContents::PendingPrerenderInfo>::iterator
           it = pending_prerenders->begin();
       it != pending_prerenders->end(); ++it) {
    PrerenderContents::PendingPrerenderInfo* info = *it;
    PrerenderHandle* existing_prerender_handle =
        info->weak_prerender_handle.get();
    if (!existing_prerender_handle)
      continue;

    DCHECK(!existing_prerender_handle->IsPrerendering());
    DCHECK(process_id == -1 || session_storage_namespace);

    scoped_ptr<PrerenderHandle> new_prerender_handle(AddPrerender(
        info->origin, process_id,
        info->url, info->referrer, info->size,
        session_storage_namespace));
    if (new_prerender_handle) {
      // AddPrerender has returned a new prerender handle to us. We want to make
      // |existing_prerender_handle| active, so move the underlying
      // PrerenderData to our new handle.
      existing_prerender_handle->AdoptPrerenderDataFrom(
          new_prerender_handle.get());
      continue;
    }
  }
}

void PrerenderManager::SourceNavigatedAway(PrerenderData* prerender_data) {
  // The expiry time of our prerender data will likely change because of
  // this navigation. This requires a resort of active_prerenders_.
  ScopedVector<PrerenderData>::iterator it =
      std::find(active_prerenders_.begin(), active_prerenders_.end(),
                prerender_data);
  if (it == active_prerenders_.end())
    return;

  (*it)->set_expiry_time(
      std::min((*it)->expiry_time(),
               GetExpiryTimeForNavigatedAwayPrerender()));
  SortActivePrerenders();
}

// private
PrerenderHandle* PrerenderManager::AddPrerender(
    Origin origin,
    int process_id,
    const GURL& url_arg,
    const content::Referrer& referrer,
    const gfx::Size& size,
    SessionStorageNamespace* session_storage_namespace) {
  DCHECK(CalledOnValidThread());

  if (!IsEnabled())
    return NULL;

  if ((origin == ORIGIN_LINK_REL_PRERENDER_CROSSDOMAIN ||
       origin == ORIGIN_LINK_REL_PRERENDER_SAMEDOMAIN) &&
      IsGoogleSearchResultURL(referrer.url)) {
    origin = ORIGIN_GWS_PRERENDER;
  }

  GURL url = url_arg;
  GURL alias_url;
  uint8 experiment = GetQueryStringBasedExperiment(url_arg);
  if (IsControlGroup(experiment) &&
      MaybeGetQueryStringBasedAliasURL(url, &alias_url)) {
    url = alias_url;
  }

  // From here on, we will record a FinalStatus so we need to register with the
  // histogram tracking.
  histograms_->RecordPrerender(origin, url_arg);

  if (PrerenderData* preexisting_prerender_data =
          FindPrerenderData(url, session_storage_namespace)) {
    RecordFinalStatus(origin, experiment, FINAL_STATUS_DUPLICATE);
    return new PrerenderHandle(preexisting_prerender_data);
  }

  // Do not prerender if there are too many render processes, and we would
  // have to use an existing one.  We do not want prerendering to happen in
  // a shared process, so that we can always reliably lower the CPU
  // priority for prerendering.
  // In single-process mode, ShouldTryToUseExistingProcessHost() always returns
  // true, so that case needs to be explicitly checked for.
  // TODO(tburkard): Figure out how to cancel prerendering in the opposite
  // case, when a new tab is added to a process used for prerendering.
  // On Android we do reuse processes as we have a limited number of them and we
  // still want the benefits of prerendering even when several tabs are open.
#if !defined(OS_ANDROID)
  if (content::RenderProcessHost::ShouldTryToUseExistingProcessHost(
          profile_, url) &&
      !content::RenderProcessHost::run_renderer_in_process()) {
    RecordFinalStatus(origin, experiment, FINAL_STATUS_TOO_MANY_PROCESSES);
    return NULL;
  }
#endif

  // Check if enough time has passed since the last prerender.
  if (!DoesRateLimitAllowPrerender(origin)) {
    // Cancel the prerender. We could add it to the pending prerender list but
    // this doesn't make sense as the next prerender request will be triggered
    // by a navigation and is unlikely to be the same site.
    RecordFinalStatus(origin, experiment, FINAL_STATUS_RATE_LIMIT_EXCEEDED);
    return NULL;
  }

  PrerenderContents* prerender_contents = CreatePrerenderContents(
      url, referrer, origin, experiment);
  DCHECK(prerender_contents);
  active_prerenders_.push_back(
      new PrerenderData(this, prerender_contents,
                        GetExpiryTimeForNewPrerender(origin)));
  if (!prerender_contents->Init()) {
    DCHECK(active_prerenders_.end() ==
           FindIteratorForPrerenderContents(prerender_contents));
    return NULL;
  }

  histograms_->RecordPrerenderStarted(origin);
  DCHECK(!prerender_contents->prerendering_has_started());

  PrerenderHandle* prerender_handle =
      new PrerenderHandle(active_prerenders_.back());
  SortActivePrerenders();

  last_prerender_start_time_ = GetCurrentTimeTicks();

  gfx::Size contents_size =
      size.IsEmpty() ? config_.default_tab_bounds.size() : size;

  prerender_contents->StartPrerendering(process_id, contents_size,
                                        session_storage_namespace);

  DCHECK(IsControlGroup(experiment) ||
         prerender_contents->prerendering_has_started());

  if (GetMode() == PRERENDER_MODE_EXPERIMENT_MULTI_PRERENDER_GROUP)
    histograms_->RecordConcurrency(active_prerenders_.size());

  // Query the history to see if the URL being prerendered has ever been
  // visited before.
  HistoryService* history_service = HistoryServiceFactory::GetForProfile(
      profile_, Profile::EXPLICIT_ACCESS);
  if (history_service) {
    history_service->QueryURL(
        url,
        false,
        &query_url_consumer_,
        base::Bind(&PrerenderManager::OnHistoryServiceDidQueryURL,
                   base::Unretained(this),
                   origin,
                   experiment));
  }

  StartSchedulingPeriodicCleanups();
  return prerender_handle;
}

void PrerenderManager::StartSchedulingPeriodicCleanups() {
  DCHECK(CalledOnValidThread());
  if (repeating_timer_.IsRunning())
    return;
  repeating_timer_.Start(FROM_HERE,
      base::TimeDelta::FromMilliseconds(kPeriodicCleanupIntervalMs),
      this,
      &PrerenderManager::PeriodicCleanup);
}

void PrerenderManager::StopSchedulingPeriodicCleanups() {
  DCHECK(CalledOnValidThread());
  repeating_timer_.Stop();
}

void PrerenderManager::PeriodicCleanup() {
  DCHECK(CalledOnValidThread());

  base::ElapsedTimer resource_timer;

  // Grab a copy of the current PrerenderContents pointers, so that we
  // will not interfere with potential deletions of the list.
  std::vector<PrerenderContents*>
      prerender_contents(active_prerenders_.size());
  std::transform(active_prerenders_.begin(), active_prerenders_.end(),
                 prerender_contents.begin(),
                 std::mem_fun(&PrerenderData::contents));

  // And now check for prerenders using too much memory.
  std::for_each(prerender_contents.begin(), prerender_contents.end(),
                std::mem_fun(
                    &PrerenderContents::DestroyWhenUsingTooManyResources));

  // Measure how long the resource checks took. http://crbug.com/305419.
  UMA_HISTOGRAM_TIMES("Prerender.PeriodicCleanupResourceCheckTime",
                      resource_timer.Elapsed());

  base::ElapsedTimer cleanup_timer;

  // Perform deferred cleanup work.
  DeleteOldWebContents();
  DeleteOldEntries();
  if (active_prerenders_.empty())
    StopSchedulingPeriodicCleanups();

  to_delete_prerenders_.clear();

  // Measure how long a the various cleanup tasks took. http://crbug.com/305419.
  UMA_HISTOGRAM_TIMES("Prerender.PeriodicCleanupDeleteContentsTime",
                      cleanup_timer.Elapsed());
}

void PrerenderManager::PostCleanupTask() {
  DCHECK(CalledOnValidThread());
  base::MessageLoop::current()->PostTask(
      FROM_HERE,
      base::Bind(&PrerenderManager::PeriodicCleanup, AsWeakPtr()));
}

base::TimeTicks PrerenderManager::GetExpiryTimeForNewPrerender(
    Origin origin) const {
  base::TimeDelta ttl = config_.time_to_live;
  if (origin == ORIGIN_LOCAL_PREDICTOR)
    ttl = base::TimeDelta::FromSeconds(GetLocalPredictorTTLSeconds());
  return GetCurrentTimeTicks() + ttl;
}

base::TimeTicks PrerenderManager::GetExpiryTimeForNavigatedAwayPrerender()
    const {
  return GetCurrentTimeTicks() + config_.abandon_time_to_live;
}

void PrerenderManager::DeleteOldEntries() {
  DCHECK(CalledOnValidThread());
  while (!active_prerenders_.empty()) {
    PrerenderData* prerender_data = active_prerenders_.front();
    DCHECK(prerender_data);
    DCHECK(prerender_data->contents());

    if (prerender_data->expiry_time() > GetCurrentTimeTicks())
      return;
    prerender_data->contents()->Destroy(FINAL_STATUS_TIMED_OUT);
  }
}

base::Time PrerenderManager::GetCurrentTime() const {
  return base::Time::Now();
}

base::TimeTicks PrerenderManager::GetCurrentTimeTicks() const {
  return base::TimeTicks::Now();
}

PrerenderContents* PrerenderManager::CreatePrerenderContents(
    const GURL& url,
    const content::Referrer& referrer,
    Origin origin,
    uint8 experiment_id) {
  DCHECK(CalledOnValidThread());
  return prerender_contents_factory_->CreatePrerenderContents(
      this, profile_, url, referrer, origin, experiment_id);
}

void PrerenderManager::SortActivePrerenders() {
  std::sort(active_prerenders_.begin(), active_prerenders_.end(),
            PrerenderData::OrderByExpiryTime());
}

PrerenderManager::PrerenderData* PrerenderManager::FindPrerenderData(
    const GURL& url,
    const SessionStorageNamespace* session_storage_namespace) {
  for (ScopedVector<PrerenderData>::iterator it = active_prerenders_.begin();
       it != active_prerenders_.end(); ++it) {
    if ((*it)->contents()->Matches(url, session_storage_namespace))
      return *it;
  }
  return NULL;
}

PrerenderManager::PrerenderData*
PrerenderManager::FindPrerenderDataForChildAndRoute(
    const int child_id, const int route_id) {
  for (ScopedVector<PrerenderData>::iterator it = active_prerenders_.begin();
       it != active_prerenders_.end(); ++it) {
    PrerenderContents* prerender_contents = (*it)->contents();

    int contents_child_id;
    if (!prerender_contents->GetChildId(&contents_child_id))
      continue;
    int contents_route_id;
    if (!prerender_contents->GetRouteId(&contents_route_id))
      continue;

    if (contents_child_id == child_id && contents_route_id == route_id)
      return *it;
  }
  return NULL;
}

ScopedVector<PrerenderManager::PrerenderData>::iterator
PrerenderManager::FindIteratorForPrerenderContents(
    PrerenderContents* prerender_contents) {
  for (ScopedVector<PrerenderData>::iterator it = active_prerenders_.begin();
       it != active_prerenders_.end(); ++it) {
    if (prerender_contents == (*it)->contents())
      return it;
  }
  return active_prerenders_.end();
}

bool PrerenderManager::DoesRateLimitAllowPrerender(Origin origin) const {
  DCHECK(CalledOnValidThread());
  base::TimeDelta elapsed_time =
      GetCurrentTimeTicks() - last_prerender_start_time_;
  histograms_->RecordTimeBetweenPrerenderRequests(origin, elapsed_time);
  if (!config_.rate_limit_enabled)
    return true;
  return elapsed_time >=
      base::TimeDelta::FromMilliseconds(kMinTimeBetweenPrerendersMs);
}

void PrerenderManager::DeleteOldWebContents() {
  while (!old_web_contents_list_.empty()) {
    WebContents* web_contents = old_web_contents_list_.front();
    old_web_contents_list_.pop_front();
    // TODO(dominich): should we use Instant Unload Handler here?
    delete web_contents;
  }
}

void PrerenderManager::CleanUpOldNavigations() {
  DCHECK(CalledOnValidThread());

  // Cutoff.  Navigations before this cutoff can be discarded.
  base::TimeTicks cutoff = GetCurrentTimeTicks() -
      base::TimeDelta::FromMilliseconds(kNavigationRecordWindowMs);
  while (!navigations_.empty()) {
    if (navigations_.front().time > cutoff)
      break;
    navigations_.pop_front();
  }
}

void PrerenderManager::ScheduleDeleteOldWebContents(
    WebContents* tab,
    OnCloseWebContentsDeleter* deleter) {
  old_web_contents_list_.push_back(tab);
  PostCleanupTask();

  if (deleter) {
    ScopedVector<OnCloseWebContentsDeleter>::iterator i = std::find(
        on_close_web_contents_deleters_.begin(),
        on_close_web_contents_deleters_.end(),
        deleter);
    DCHECK(i != on_close_web_contents_deleters_.end());
    on_close_web_contents_deleters_.erase(i);
  }
}

void PrerenderManager::AddToHistory(PrerenderContents* contents) {
  PrerenderHistory::Entry entry(contents->prerender_url(),
                                contents->final_status(),
                                contents->origin(),
                                base::Time::Now());
  prerender_history_->AddEntry(entry);
}

Value* PrerenderManager::GetActivePrerendersAsValue() const {
  ListValue* list_value = new ListValue();
  for (ScopedVector<PrerenderData>::const_iterator it =
           active_prerenders_.begin();
       it != active_prerenders_.end(); ++it) {
    if (Value* prerender_value = (*it)->contents()->GetAsValue())
      list_value->Append(prerender_value);
  }
  return list_value;
}

void PrerenderManager::DestroyAllContents(FinalStatus final_status) {
  DeleteOldWebContents();
  while (!active_prerenders_.empty()) {
    PrerenderContents* contents = active_prerenders_.front()->contents();
    contents->Destroy(final_status);
  }
  to_delete_prerenders_.clear();
}

void PrerenderManager::DestroyAndMarkMatchCompleteAsUsed(
    PrerenderContents* prerender_contents,
    FinalStatus final_status) {
  prerender_contents->set_match_complete_status(
      PrerenderContents::MATCH_COMPLETE_REPLACED);
  histograms_->RecordFinalStatus(prerender_contents->origin(),
                                 prerender_contents->experiment_id(),
                                 PrerenderContents::MATCH_COMPLETE_REPLACEMENT,
                                 FINAL_STATUS_WOULD_HAVE_BEEN_USED);
  prerender_contents->Destroy(final_status);
}

void PrerenderManager::RecordFinalStatus(Origin origin,
                                         uint8 experiment_id,
                                         FinalStatus final_status) const {
  RecordFinalStatusWithMatchCompleteStatus(
      origin, experiment_id,
      PrerenderContents::MATCH_COMPLETE_DEFAULT,
      final_status);
}

bool PrerenderManager::IsEnabled() const {
  DCHECK(CalledOnValidThread());
  if (!enabled_)
    return false;
  for (std::list<const PrerenderCondition*>::const_iterator it =
           prerender_conditions_.begin();
       it != prerender_conditions_.end();
       ++it) {
    const PrerenderCondition* condition = *it;
    if (!condition->CanPrerender())
      return false;
  }
  return true;
}

PrerenderManager* FindPrerenderManagerUsingRenderProcessId(
    int render_process_id) {
  DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
  content::RenderProcessHost* render_process_host =
      content::RenderProcessHost::FromID(render_process_id);
  // Each render process is guaranteed to only hold RenderViews owned by the
  // same BrowserContext. This is enforced by
  // RenderProcessHost::GetExistingProcessHost.
  if (!render_process_host || !render_process_host->GetBrowserContext())
    return NULL;
  Profile* profile = Profile::FromBrowserContext(
      render_process_host->GetBrowserContext());
  if (!profile)
    return NULL;
  return PrerenderManagerFactory::GetInstance()->GetForProfile(profile);
}

void PrerenderManager::Observe(int type,
                               const content::NotificationSource& source,
                               const content::NotificationDetails& details) {
  Profile* profile = content::Source<Profile>(source).ptr();
  if (!profile || !profile_->IsSameProfile(profile) ||
      profile->IsOffTheRecord()) {
    return;
  }
  DCHECK(type == chrome::NOTIFICATION_COOKIE_CHANGED);
  CookieChanged(content::Details<ChromeCookieDetails>(details).ptr());
}

void PrerenderManager::OnCreatingAudioStream(int render_process_id,
                                             int render_view_id) {
  WebContents* tab = tab_util::GetWebContentsByID(
      render_process_id, render_view_id);
  if (!tab)
    return;

  if (!IsWebContentsPrerendering(tab, NULL))
    return;

  prerender_tracker()->TryCancel(
      render_process_id, render_view_id,
      prerender::FINAL_STATUS_CREATING_AUDIO_STREAM);
}

void PrerenderManager::RecordLikelyLoginOnURL(const GURL& url) {
  DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
  if (!url.SchemeIsHTTPOrHTTPS())
    return;
  if (logged_in_predictor_table_.get()) {
    BrowserThread::PostTask(
        BrowserThread::DB,
        FROM_HERE,
        base::Bind(&LoggedInPredictorTable::AddDomainFromURL,
                   logged_in_predictor_table_,
                   url));
  }
  std::string key = LoggedInPredictorTable::GetKey(url);
  if (!logged_in_state_.get())
    return;
  if (logged_in_state_->count(key))
    return;
  (*logged_in_state_)[key] = base::Time::Now().ToInternalValue();
}

void PrerenderManager::CheckIfLikelyLoggedInOnURL(
    const GURL& url,
    bool* lookup_result,
    bool* database_was_present,
    const base::Closure& result_cb) {
  DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
  if (!logged_in_predictor_table_.get()) {
    *database_was_present = false;
    *lookup_result = false;
    BrowserThread::PostTask(BrowserThread::UI, FROM_HERE, result_cb);
    return;
  }
  BrowserThread::PostTaskAndReply(
      BrowserThread::DB, FROM_HERE,
      base::Bind(&LoggedInPredictorTable::HasUserLoggedIn,
                 logged_in_predictor_table_,
                 url,
                 lookup_result,
                 database_was_present),
      result_cb);
}


void PrerenderManager::CookieChanged(ChromeCookieDetails* details) {
  DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));

  if (!logged_in_predictor_table_.get())
    return;

  // We only care when a cookie has been removed.
  if (!details->removed)
    return;

  std::string domain_key =
      LoggedInPredictorTable::GetKeyFromDomain(details->cookie->Domain());

  // If we have no record of this domain as a potentially logged in domain,
  // nothing to do here.
  if (logged_in_state_.get() && logged_in_state_->count(domain_key) < 1)
    return;

  net::URLRequestContextGetter* rq_context = profile_->GetRequestContext();
  if (!rq_context)
    return;

  BrowserThread::PostTask(
      BrowserThread::IO, FROM_HERE,
      base::Bind(&CheckIfCookiesExistForDomainOnIOThread,
                 base::Unretained(rq_context),
                 domain_key,
                 base::Bind(
                     &PrerenderManager::CookieChangedAnyCookiesLeftLookupResult,
                     AsWeakPtr(),
                     domain_key)
                 ));
}

void PrerenderManager::CookieChangedAnyCookiesLeftLookupResult(
    const std::string& domain_key,
    bool cookies_exist) {
  DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));

  if (cookies_exist)
    return;

  if (logged_in_predictor_table_.get()) {
    BrowserThread::PostTask(BrowserThread::DB,
                            FROM_HERE,
                            base::Bind(&LoggedInPredictorTable::DeleteDomain,
                                       logged_in_predictor_table_,
                                       domain_key));
  }

  if (logged_in_state_.get())
    logged_in_state_->erase(domain_key);
}

void PrerenderManager::LoggedInPredictorDataReceived(
    scoped_ptr<LoggedInStateMap> new_map) {
  DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
  logged_in_state_.swap(new_map);
}

void PrerenderManager::RecordEvent(PrerenderContents* contents,
                                   PrerenderEvent event) const {
  if (!contents)
    histograms_->RecordEvent(ORIGIN_NONE, kNoExperiment, event);
  else
    histograms_->RecordEvent(contents->origin(), contents->experiment_id(),
                             event);
}

// static
void PrerenderManager::RecordCookieEvent(int process_id,
                                         int render_view_id,
                                         const GURL& url,
                                         const GURL& frame_url,
                                         PrerenderContents::CookieEvent event,
                                         const net::CookieList* cookie_list) {
  RenderViewHost* rvh = RenderViewHost::FromID(process_id, render_view_id);
  if (!rvh)
    return;

  WebContents* web_contents = WebContents::FromRenderViewHost(rvh);
  if (!web_contents)
    return;

  bool is_main_frame = true;

  PrerenderManager* prerender_manager = PrerenderManagerFactory::GetForProfile(
      Profile::FromBrowserContext(web_contents->GetBrowserContext()));

  if (!prerender_manager)
    return;

  PrerenderContents* prerender_contents =
      prerender_manager->GetPrerenderContents(web_contents);

  if (!prerender_contents)
    return;

  base::Time earliest_create_date;
  if (event == PrerenderContents::COOKIE_EVENT_SEND) {
    if (!cookie_list || cookie_list->empty())
      return;
    for (size_t i = 0; i < cookie_list->size(); i++) {
      if (earliest_create_date.is_null() ||
          (*cookie_list)[i].CreationDate() < earliest_create_date) {
        earliest_create_date = (*cookie_list)[i].CreationDate();
      }
    }
  }

  prerender_contents->RecordCookieEvent(event,
                                        is_main_frame && url == frame_url,
                                        earliest_create_date);
}

void PrerenderManager::RecordCookieStatus(Origin origin,
                                          uint8 experiment_id,
                                          int cookie_status) const {
  histograms_->RecordCookieStatus(origin, experiment_id, cookie_status);
}

void PrerenderManager::OnHistoryServiceDidQueryURL(
    Origin origin,
    uint8 experiment_id,
    CancelableRequestProvider::Handle handle,
    bool success,
    const history::URLRow* url_row,
    history::VisitVector* visists) {
  histograms_->RecordPrerenderPageVisitedStatus(origin, experiment_id, success);
}

}  // namespace prerender