root/chrome/browser/prerender/prerender_manager.cc

/* [<][>][^][v][top][bottom][index][help] */

DEFINITIONS

This source file includes following definitions.
  1. NeedMatchCompleteDummyForFinalStatus
  2. CheckIfCookiesExistForDomainResultOnUIThread
  3. CheckIfCookiesExistForDomainResultOnIOThread
  4. CheckIfCookiesExistForDomainOnIOThread
  5. suppressed_dialog_
  6. CloseContents
  7. SwappedOut
  8. ShouldSuppressDialogs
  9. ScheduleWebContentsForDeletion
  10. last_recorded_profile_network_bytes_
  11. Shutdown
  12. AddPrerenderFromLinkRelPrerender
  13. AddPrerenderFromOmnibox
  14. AddPrerenderFromLocalPredictor
  15. AddPrerenderFromExternalRequest
  16. AddPrerenderForInstant
  17. CancelAllPrerenders
  18. MaybeUsePrerenderedPage
  19. SwapInternal
  20. MoveEntryToPendingDelete
  21. RecordPageLoadTimeNotSwappedIn
  22. RecordPerceivedPageLoadTime
  23. set_enabled
  24. GetMode
  25. SetMode
  26. GetModeString
  27. IsPrerenderingPossible
  28. ActuallyPrerendering
  29. IsControlGroup
  30. IsNoUseGroup
  31. IsWebContentsPrerendering
  32. HasPrerenderedUrl
  33. GetPrerenderContents
  34. GetPrerenderContentsForRoute
  35. GetAllPrerenderingContents
  36. HasRecentlyBeenNavigatedTo
  37. IsValidHttpMethod
  38. DoesURLHaveValidScheme
  39. DoesSubresourceURLHaveValidScheme
  40. GetAsValue
  41. ClearData
  42. RecordFinalStatusWithMatchCompleteStatus
  43. AddCondition
  44. RecordNavigation
  45. expiry_time_
  46. MakeIntoMatchCompleteReplacement
  47. OnHandleCreated
  48. OnHandleNavigatedAway
  49. OnHandleCanceled
  50. ClearPendingSwap
  51. ReleaseContents
  52. weak_factory_
  53. target_contents
  54. BeginSwap
  55. AboutToNavigateRenderView
  56. ProvisionalChangeToMainFrameUrl
  57. DidCommitProvisionalLoadForFrame
  58. DidFailProvisionalLoad
  59. WebContentsDestroyed
  60. RecordEvent
  61. OnMergeCompleted
  62. OnMergeTimeout
  63. SetPrerenderContentsFactory
  64. SourceNavigatedAway
  65. AddPrerender
  66. StartSchedulingPeriodicCleanups
  67. StopSchedulingPeriodicCleanups
  68. PeriodicCleanup
  69. PostCleanupTask
  70. GetExpiryTimeForNewPrerender
  71. GetExpiryTimeForNavigatedAwayPrerender
  72. DeleteOldEntries
  73. GetCurrentTime
  74. GetCurrentTimeTicks
  75. CreatePrerenderContents
  76. SortActivePrerenders
  77. FindPrerenderData
  78. FindPrerenderDataForTargetContents
  79. FindIteratorForPrerenderContents
  80. DoesRateLimitAllowPrerender
  81. DeleteOldWebContents
  82. CleanUpOldNavigations
  83. ScheduleDeleteOldWebContents
  84. AddToHistory
  85. GetActivePrerendersAsValue
  86. DestroyAllContents
  87. DestroyAndMarkMatchCompleteAsUsed
  88. RecordFinalStatus
  89. IsEnabled
  90. Observe
  91. OnCreatingAudioStream
  92. RecordLikelyLoginOnURL
  93. CheckIfLikelyLoggedInOnURL
  94. CookieChanged
  95. CookieChangedAnyCookiesLeftLookupResult
  96. LoggedInPredictorDataReceived
  97. RecordEvent
  98. RecordCookieEvent
  99. RecordCookieStatus
  100. RecordCookieSendType
  101. OnHistoryServiceDidQueryURL
  102. HangSessionStorageMergesForTesting
  103. RecordNetworkBytes
  104. AddProfileNetworkBytesIfEnabled

// 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/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 "chrome/common/prerender_types.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_frame_host.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/url_constants.h"
#include "extensions/common/constants.h"
#include "net/base/registry_controlled_domains/registry_controlled_domain.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::RenderFrameHost;
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;

// If true, all session storage merges hang indefinitely.
bool g_hang_session_storage_merges_for_testing = false;

// 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_PROFILE_DESTROYED &&
      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::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()),
      profile_network_bytes_(0),
      last_recorded_profile_network_bytes_(0) {
  // 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());

  notification_registrar_.Add(
      this, chrome::NOTIFICATION_PROFILE_DESTROYED,
      content::Source<Profile>(profile_));

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

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

  // The earlier call to KeyedService::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 uint32 rel_types,
    const content::Referrer& referrer,
    const gfx::Size& size) {
  Origin origin = rel_types & PrerenderRelTypePrerender ?
                      ORIGIN_LINK_REL_PRERENDER_CROSSDOMAIN :
                      ORIGIN_LINK_REL_NEXT;
  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 (origin == ORIGIN_LINK_REL_PRERENDER_CROSSDOMAIN &&
        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();
  }

  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::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, don't swap.
  if (prerender_data->pending_swap())
    return false;

  // Abort any existing pending swap on the target contents.
  PrerenderData* pending_swap =
      FindPrerenderDataForTargetContents(web_contents);
  if (pending_swap) {
    pending_swap->ClearPendingSwap();
    DCHECK(FindPrerenderDataForTargetContents(web_contents) == NULL);
  }

  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;
  }

  PrerenderTabHelper* target_tab_helper =
      PrerenderTabHelper::FromWebContents(web_contents);
  if (!target_tab_helper) {
    NOTREACHED();
    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()) {
    target_tab_helper->WouldHavePrerenderedNextLoad(
        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) {
    target_tab_helper->WouldHavePrerenderedNextLoad(
        prerender_data->contents()->origin());
    prerender_data->contents()->Destroy(FINAL_STATUS_WOULD_HAVE_BEEN_USED);
    return NULL;
  }

  // At this point, we've determined that we will use the prerender.
  if (!prerender_data->contents()->load_start_time().is_null()) {
    histograms_->RecordTimeUntilUsed(
        prerender_data->contents()->origin(),
        GetCurrentTimeTicks() - prerender_data->contents()->load_start_time());
  }
  histograms_->RecordAbandonTimeUntilUsed(
      prerender_data->contents()->origin(),
      prerender_data->abandon_time().is_null() ?
          base::TimeDelta() :
          GetCurrentTimeTicks() - prerender_data->abandon_time());

  histograms_->RecordPerSessionCount(prerender_data->contents()->origin(),
                                     ++prerenders_per_session_count_);
  histograms_->RecordUsedPrerender(prerender_data->contents()->origin());

  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);

  // Mark prerender as used.
  prerender_contents->PrepareForUse();

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

  // 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,
                      true,
                      prerender_contents->has_finished_loading());
  prerender_contents->CommitHistory(new_web_contents);

  // 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->GetMainFrame()->DispatchBeforeUnload(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);
    (*it)->ClearPendingSwap();
    active_prerenders_.weak_erase(it);
  }

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

void PrerenderManager::RecordPageLoadTimeNotSwappedIn(
    Origin origin,
    base::TimeDelta page_load_time,
    const GURL& url) {
  DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
  histograms_->RecordPageLoadTimeNotSwappedIn(origin, page_load_time, url);
}

void PrerenderManager::RecordPerceivedPageLoadTime(
    Origin origin,
    NavigationType navigation_type,
    base::TimeDelta perceived_page_load_time,
    double fraction_plt_elapsed_at_swap_in,
    const GURL& url) {
  DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
  if (!IsEnabled())
    return;

  histograms_->RecordPerceivedPageLoadTime(
      origin, perceived_page_load_time, navigation_type, url);

  if (navigation_type == NAVIGATION_TYPE_PRERENDERED) {
    histograms_->RecordPercentLoadDoneAtSwapin(
        origin, fraction_plt_elapsed_at_swap_in);
  }
  if (local_predictor_) {
    local_predictor_->OnPLTEventForURL(url, perceived_page_load_time);
  }
}

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;
  }
  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();
    }
  }

  // Also check the pending-deletion list. If the prerender is in pending
  // delete, anyone with a handle on the WebContents needs to know.
  for (ScopedVector<PrerenderData>::const_iterator it =
           to_delete_prerenders_.begin();
       it != to_delete_prerenders_.end(); ++it) {
    WebContents* prerender_web_contents =
        (*it)->contents()->prerender_contents();
    if (prerender_web_contents == web_contents) {
      return (*it)->contents();
    }
  }
  return NULL;
}

PrerenderContents* PrerenderManager::GetPrerenderContentsForRoute(
    int child_id,
    int route_id) const {
  content::WebContents* web_contents =
      tab_util::GetWebContentsByID(child_id, route_id);
  if (web_contents == NULL)
    return NULL;
  return GetPrerenderContents(web_contents);
}

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;
}

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);
}

base::DictionaryValue* PrerenderManager::GetAsValue() const {
  DCHECK(CalledOnValidThread());
  base::DictionaryValue* dict_value = new base::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_);
  if (abandon_time_.is_null())
    abandon_time_ = base::TimeTicks::Now();
  // 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),
      prerender_data_(prerender_data),
      url_(url),
      should_replace_current_entry_(should_replace_current_entry),
      start_time_(base::TimeTicks::Now()),
      seen_target_route_id_(false),
      swap_successful_(false),
      weak_factory_(this) {
}

PrerenderManager::PendingSwap::~PendingSwap() {
  manager_->prerender_tracker()->RemovePrerenderPendingSwap(
      target_route_id_, swap_successful_);
}

WebContents* PrerenderManager::PendingSwap::target_contents() const {
  return web_contents();
}

void PrerenderManager::PendingSwap::BeginSwap() {
  if (g_hang_session_storage_merges_for_testing)
    return;

  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::AboutToNavigateRenderView(
    RenderViewHost* render_view_host) {
  if (seen_target_route_id_) {
    // A second navigation began browser-side.
    prerender_data_->ClearPendingSwap();
    return;
  }

  seen_target_route_id_ = true;
  target_route_id_ = PrerenderTracker::ChildRouteIdPair(
      render_view_host->GetMainFrame()->GetProcess()->GetID(),
      render_view_host->GetMainFrame()->GetRoutingID());
  manager_->prerender_tracker()->AddPrerenderPendingSwap(
      target_route_id_, url_);
}

void PrerenderManager::PendingSwap::ProvisionalChangeToMainFrameUrl(
        const GURL& url,
        content::RenderFrameHost* render_frame_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::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 will, on success, delete |prerender_data_| and
  // |this|. It will also delete |this| in some failure cases. Pass in a new
  // GURL object rather than a reference to |url_|. Also hold on to |manager_|
  // and |prerender_data_|.
  //
  // TODO(davidben): Can we make this less fragile?
  PrerenderManager* manager = manager_;
  PrerenderData* prerender_data = prerender_data_;
  WebContents* new_web_contents = manager_->SwapInternal(
        GURL(url_), target_contents(), prerender_data_,
        should_replace_current_entry_);
  if (!new_web_contents) {
    manager->RecordEvent(prerender_data->contents(),
                         PRERENDER_EVENT_MERGE_RESULT_SWAPIN_FAILED);
    // Depending on whether SwapInternal called Destroy() or simply failed to
    // swap, |this| may or may not be deleted. Either way, if the swap failed,
    // |prerender_data| is deleted asynchronously, so this call is a no-op if
    // |this| is already gone.
    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::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.
  // TODO(ppi): Check whether there are usually enough render processes
  // available on Android. If not, kill an existing renderers so that we can
  // create a new one.
  if (content::RenderProcessHost::ShouldTryToUseExistingProcessHost(
          profile_, url) &&
      !content::RenderProcessHost::run_renderer_in_process()) {
    RecordFinalStatus(origin, experiment, FINAL_STATUS_TOO_MANY_PROCESSES);
    return NULL;
  }

  // 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::FindPrerenderDataForTargetContents(
    WebContents* target_contents) {
  for (ScopedVector<PrerenderData>::iterator it = active_prerenders_.begin();
       it != active_prerenders_.end(); ++it) {
    if ((*it)->pending_swap() &&
        (*it)->pending_swap()->target_contents() == target_contents)
      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);
}

base::Value* PrerenderManager::GetActivePrerendersAsValue() const {
  base::ListValue* list_value = new base::ListValue();
  for (ScopedVector<PrerenderData>::const_iterator it =
           active_prerenders_.begin();
       it != active_prerenders_.end(); ++it) {
    if (base::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;
}

void PrerenderManager::Observe(int type,
                               const content::NotificationSource& source,
                               const content::NotificationDetails& details) {
  switch (type) {
    case chrome::NOTIFICATION_COOKIE_CHANGED: {
      Profile* profile = content::Source<Profile>(source).ptr();
      if (!profile || !profile_->IsSameProfile(profile) ||
          profile->IsOffTheRecord()) {
        return;
      }
      CookieChanged(content::Details<ChromeCookieDetails>(details).ptr());
      break;
    }
    case chrome::NOTIFICATION_PROFILE_DESTROYED:
      DestroyAllContents(FINAL_STATUS_PROFILE_DESTROYED);
      on_close_web_contents_deleters_.clear();
      break;
    default:
      NOTREACHED() << "Unexpected notification sent.";
      break;
  }
}

void PrerenderManager::OnCreatingAudioStream(int render_process_id,
                                             int render_frame_id) {
  content::RenderFrameHost* render_frame_host =
      content::RenderFrameHost::FromID(render_process_id, render_frame_id);
  WebContents* tab = WebContents::FromRenderFrameHost(render_frame_host);
  if (!tab)
    return;

  PrerenderContents* prerender_contents = GetPrerenderContents(tab);
  if (!prerender_contents)
    return;

  prerender_contents->Destroy(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 frame_id,
                                         const GURL& url,
                                         const GURL& frame_url,
                                         bool is_for_blocking_resource,
                                         PrerenderContents::CookieEvent event,
                                         const net::CookieList* cookie_list) {
  RenderFrameHost* rfh = RenderFrameHost::FromID(process_id, frame_id);
  WebContents* web_contents = WebContents::FromRenderFrameHost(rfh);
  if (!web_contents)
    return;

  bool is_main_frame = (rfh == web_contents->GetMainFrame());

  bool is_third_party_cookie =
    (!frame_url.is_empty() &&
     !net::registry_controlled_domains::SameDomainOrHost(
         url, frame_url,
         net::registry_controlled_domains::INCLUDE_PRIVATE_REGISTRIES));

  PrerenderContents* prerender_contents =
      PrerenderContents::FromWebContents(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,
                                        is_third_party_cookie,
                                        is_for_blocking_resource,
                                        earliest_create_date);
}

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

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

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);
}

// static
void PrerenderManager::HangSessionStorageMergesForTesting() {
  g_hang_session_storage_merges_for_testing = true;
}

void PrerenderManager::RecordNetworkBytes(bool used, int64 prerender_bytes) {
  if (!ActuallyPrerendering())
    return;
  int64 recent_profile_bytes =
      profile_network_bytes_ - last_recorded_profile_network_bytes_;
  last_recorded_profile_network_bytes_ = profile_network_bytes_;
  DCHECK_GE(recent_profile_bytes, 0);
  histograms_->RecordNetworkBytes(used, prerender_bytes, recent_profile_bytes);
}

void PrerenderManager::AddProfileNetworkBytesIfEnabled(int64 bytes) {
  DCHECK_GE(bytes, 0);
  if (IsEnabled() && ActuallyPrerendering())
    profile_network_bytes_ += bytes;
}

}  // namespace prerender

/* [<][>][^][v][top][bottom][index][help] */