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