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_contents.h"
6
7#include <algorithm>
8#include <functional>
9#include <utility>
10
11#include "base/strings/utf_string_conversions.h"
12#include "chrome/browser/chrome_notification_types.h"
13#include "chrome/browser/favicon/favicon_tab_helper.h"
14#include "chrome/browser/history/history_tab_helper.h"
15#include "chrome/browser/history/history_types.h"
16#include "chrome/browser/prerender/prerender_field_trial.h"
17#include "chrome/browser/prerender/prerender_final_status.h"
18#include "chrome/browser/prerender/prerender_handle.h"
19#include "chrome/browser/prerender/prerender_manager.h"
20#include "chrome/browser/prerender/prerender_tracker.h"
21#include "chrome/browser/profiles/profile.h"
22#include "chrome/browser/ui/browser.h"
23#include "chrome/browser/ui/browser_tab_contents.h"
24#include "chrome/common/prerender_messages.h"
25#include "chrome/common/render_messages.h"
26#include "chrome/common/url_constants.h"
27#include "content/public/browser/browser_child_process_host.h"
28#include "content/public/browser/notification_service.h"
29#include "content/public/browser/render_frame_host.h"
30#include "content/public/browser/render_process_host.h"
31#include "content/public/browser/render_view_host.h"
32#include "content/public/browser/resource_request_details.h"
33#include "content/public/browser/session_storage_namespace.h"
34#include "content/public/browser/web_contents.h"
35#include "content/public/browser/web_contents_delegate.h"
36#include "content/public/browser/web_contents_view.h"
37#include "content/public/common/favicon_url.h"
38#include "content/public/common/frame_navigate_params.h"
39#include "ui/gfx/rect.h"
40
41using content::DownloadItem;
42using content::OpenURLParams;
43using content::RenderViewHost;
44using content::ResourceRedirectDetails;
45using content::SessionStorageNamespace;
46using content::WebContents;
47
48namespace prerender {
49
50namespace {
51
52// Internal cookie event.
53// Whenever a prerender interacts with the cookie store, either sending
54// existing cookies that existed before the prerender started, or when a cookie
55// is changed, we record these events for histogramming purposes.
56enum InternalCookieEvent {
57  INTERNAL_COOKIE_EVENT_MAIN_FRAME_SEND = 0,
58  INTERNAL_COOKIE_EVENT_MAIN_FRAME_CHANGE = 1,
59  INTERNAL_COOKIE_EVENT_OTHER_SEND = 2,
60  INTERNAL_COOKIE_EVENT_OTHER_CHANGE = 3,
61  INTERNAL_COOKIE_EVENT_MAX
62};
63
64}  // namespace
65
66// static
67const int PrerenderContents::kNumCookieStatuses =
68    (1 << INTERNAL_COOKIE_EVENT_MAX);
69
70class PrerenderContentsFactoryImpl : public PrerenderContents::Factory {
71 public:
72  virtual PrerenderContents* CreatePrerenderContents(
73      PrerenderManager* prerender_manager, Profile* profile,
74      const GURL& url, const content::Referrer& referrer,
75      Origin origin, uint8 experiment_id) OVERRIDE {
76    return new PrerenderContents(prerender_manager, profile,
77                                 url, referrer, origin, experiment_id);
78  }
79};
80
81// WebContentsDelegateImpl -----------------------------------------------------
82
83class PrerenderContents::WebContentsDelegateImpl
84    : public content::WebContentsDelegate {
85 public:
86  explicit WebContentsDelegateImpl(PrerenderContents* prerender_contents)
87      : prerender_contents_(prerender_contents) {
88  }
89
90  // content::WebContentsDelegate implementation:
91  virtual WebContents* OpenURLFromTab(WebContents* source,
92                                      const OpenURLParams& params) OVERRIDE {
93    // |OpenURLFromTab| is typically called when a frame performs a navigation
94    // that requires the browser to perform the transition instead of WebKit.
95    // Examples include prerendering a site that redirects to an app URL,
96    // or if --enable-strict-site-isolation is specified and the prerendered
97    // frame redirects to a different origin.
98    // TODO(cbentzel): Consider supporting this if it is a common case during
99    // prerenders.
100    prerender_contents_->Destroy(FINAL_STATUS_OPEN_URL);
101    return NULL;
102  }
103
104  virtual void CanDownload(
105      RenderViewHost* render_view_host,
106      int request_id,
107      const std::string& request_method,
108      const base::Callback<void(bool)>& callback) OVERRIDE {
109    prerender_contents_->Destroy(FINAL_STATUS_DOWNLOAD);
110    // Cancel the download.
111    callback.Run(false);
112  }
113
114  virtual bool OnGoToEntryOffset(int offset) OVERRIDE {
115    // This isn't allowed because the history merge operation
116    // does not work if there are renderer issued challenges.
117    // TODO(cbentzel): Cancel in this case? May not need to do
118    // since render-issued offset navigations are not guaranteed,
119    // but indicates that the page cares about the history.
120    return false;
121  }
122
123  virtual void JSOutOfMemory(WebContents* tab) OVERRIDE {
124    prerender_contents_->Destroy(FINAL_STATUS_JS_OUT_OF_MEMORY);
125  }
126
127  virtual bool ShouldSuppressDialogs() OVERRIDE {
128    // We still want to show the user the message when they navigate to this
129    // page, so cancel this prerender.
130    prerender_contents_->Destroy(FINAL_STATUS_JAVASCRIPT_ALERT);
131    // Always suppress JavaScript messages if they're triggered by a page being
132    // prerendered.
133    return true;
134  }
135
136  virtual void RegisterProtocolHandler(WebContents* web_contents,
137                                       const std::string& protocol,
138                                       const GURL& url,
139                                       const base::string16& title,
140                                       bool user_gesture) OVERRIDE {
141    // TODO(mmenke): Consider supporting this if it is a common case during
142    // prerenders.
143    prerender_contents_->Destroy(FINAL_STATUS_REGISTER_PROTOCOL_HANDLER);
144  }
145
146  virtual gfx::Size GetSizeForNewRenderView(
147      const WebContents* web_contents) const OVERRIDE {
148    // Have to set the size of the RenderView on initialization to be sure it is
149    // set before the RenderView is hidden on all platforms (esp. Android).
150    return prerender_contents_->size_;
151  }
152
153 private:
154  PrerenderContents* prerender_contents_;
155};
156
157void PrerenderContents::Observer::OnPrerenderStopLoading(
158    PrerenderContents* contents) {
159}
160
161void PrerenderContents::Observer::OnPrerenderCreatedMatchCompleteReplacement(
162    PrerenderContents* contents, PrerenderContents* replacement) {
163}
164
165PrerenderContents::Observer::Observer() {
166}
167
168PrerenderContents::Observer::~Observer() {
169}
170
171PrerenderContents::PendingPrerenderInfo::PendingPrerenderInfo(
172    base::WeakPtr<PrerenderHandle> weak_prerender_handle,
173    Origin origin,
174    const GURL& url,
175    const content::Referrer& referrer,
176    const gfx::Size& size)
177    : weak_prerender_handle(weak_prerender_handle),
178      origin(origin),
179      url(url),
180      referrer(referrer),
181      size(size) {
182}
183
184PrerenderContents::PendingPrerenderInfo::~PendingPrerenderInfo() {
185}
186
187void PrerenderContents::AddPendingPrerender(
188    scoped_ptr<PendingPrerenderInfo> pending_prerender_info) {
189  pending_prerenders_.push_back(pending_prerender_info.release());
190}
191
192void PrerenderContents::PrepareForUse() {
193  for (std::set<content::RenderFrameHost*>::iterator i =
194           render_frame_hosts_.begin(); i != render_frame_hosts_.end(); ++i) {
195    (*i)->Send(new PrerenderMsg_SetIsPrerendering((*i)->GetRoutingID(), false));
196  }
197  render_frame_hosts_.clear();
198
199  NotifyPrerenderStop();
200
201  SessionStorageNamespace* session_storage_namespace = NULL;
202  if (prerender_contents_) {
203    // TODO(ajwong): This does not correctly handle storage for isolated apps.
204    session_storage_namespace = prerender_contents_->
205        GetController().GetDefaultSessionStorageNamespace();
206  }
207  prerender_manager_->StartPendingPrerenders(
208      child_id_, &pending_prerenders_, session_storage_namespace);
209  pending_prerenders_.clear();
210}
211
212PrerenderContents::PrerenderContents(
213    PrerenderManager* prerender_manager,
214    Profile* profile,
215    const GURL& url,
216    const content::Referrer& referrer,
217    Origin origin,
218    uint8 experiment_id)
219    : prerendering_has_started_(false),
220      session_storage_namespace_id_(-1),
221      prerender_manager_(prerender_manager),
222      prerender_url_(url),
223      referrer_(referrer),
224      profile_(profile),
225      page_id_(0),
226      has_stopped_loading_(false),
227      has_finished_loading_(false),
228      final_status_(FINAL_STATUS_MAX),
229      match_complete_status_(MATCH_COMPLETE_DEFAULT),
230      prerendering_has_been_cancelled_(false),
231      child_id_(-1),
232      route_id_(-1),
233      origin_(origin),
234      experiment_id_(experiment_id),
235      creator_child_id_(-1),
236      cookie_status_(0) {
237  DCHECK(prerender_manager != NULL);
238}
239
240PrerenderContents* PrerenderContents::CreateMatchCompleteReplacement() {
241  PrerenderContents* new_contents = prerender_manager_->CreatePrerenderContents(
242      prerender_url(), referrer(), origin(), experiment_id());
243
244  new_contents->load_start_time_ = load_start_time_;
245  new_contents->session_storage_namespace_id_ = session_storage_namespace_id_;
246  new_contents->set_match_complete_status(
247      PrerenderContents::MATCH_COMPLETE_REPLACEMENT_PENDING);
248
249  const bool did_init = new_contents->Init();
250  DCHECK(did_init);
251  DCHECK_EQ(alias_urls_.front(), new_contents->alias_urls_.front());
252  DCHECK_EQ(1u, new_contents->alias_urls_.size());
253  new_contents->alias_urls_ = alias_urls_;
254  new_contents->set_match_complete_status(
255      PrerenderContents::MATCH_COMPLETE_REPLACEMENT);
256  NotifyPrerenderCreatedMatchCompleteReplacement(new_contents);
257  return new_contents;
258}
259
260bool PrerenderContents::Init() {
261  return AddAliasURL(prerender_url_);
262}
263
264// static
265PrerenderContents::Factory* PrerenderContents::CreateFactory() {
266  return new PrerenderContentsFactoryImpl();
267}
268
269void PrerenderContents::StartPrerendering(
270    int creator_child_id,
271    const gfx::Size& size,
272    SessionStorageNamespace* session_storage_namespace) {
273  DCHECK(profile_ != NULL);
274  DCHECK(!size.IsEmpty());
275  DCHECK(!prerendering_has_started_);
276  DCHECK(prerender_contents_.get() == NULL);
277  DCHECK_EQ(-1, creator_child_id_);
278  DCHECK(size_.IsEmpty());
279  DCHECK_EQ(1U, alias_urls_.size());
280
281  creator_child_id_ = creator_child_id;
282  session_storage_namespace_id_ = session_storage_namespace->id();
283  size_ = size;
284
285  DCHECK(load_start_time_.is_null());
286  load_start_time_ = base::TimeTicks::Now();
287  start_time_ = base::Time::Now();
288
289  // Everything after this point sets up the WebContents object and associated
290  // RenderView for the prerender page. Don't do this for members of the
291  // control group.
292  if (prerender_manager_->IsControlGroup(experiment_id()))
293    return;
294
295  if (origin_ == ORIGIN_LOCAL_PREDICTOR &&
296      IsLocalPredictorPrerenderAlwaysControlEnabled()) {
297    return;
298  }
299
300  prerendering_has_started_ = true;
301
302  alias_session_storage_namespace = session_storage_namespace->CreateAlias();
303  prerender_contents_.reset(
304      CreateWebContents(alias_session_storage_namespace.get()));
305  BrowserTabContents::AttachTabHelpers(prerender_contents_.get());
306#if defined(OS_ANDROID)
307  // Delay icon fetching until the contents are getting swapped in
308  // to conserve network usage in mobile devices.
309  FaviconTabHelper::FromWebContents(
310      prerender_contents_.get())->set_should_fetch_icons(false);
311#endif  // defined(OS_ANDROID)
312  content::WebContentsObserver::Observe(prerender_contents_.get());
313
314  web_contents_delegate_.reset(new WebContentsDelegateImpl(this));
315  prerender_contents_.get()->SetDelegate(web_contents_delegate_.get());
316  // Set the size of the prerender WebContents.
317  prerender_contents_->GetView()->SizeContents(size_);
318
319  child_id_ = GetRenderViewHost()->GetProcess()->GetID();
320  route_id_ = GetRenderViewHost()->GetRoutingID();
321
322  // Log transactions to see if we could merge session storage namespaces in
323  // the event of a mismatch.
324  alias_session_storage_namespace->AddTransactionLogProcessId(child_id_);
325
326  // Register this with the ResourceDispatcherHost as a prerender
327  // RenderViewHost. This must be done before the Navigate message to catch all
328  // resource requests, but as it is on the same thread as the Navigate message
329  // (IO) there is no race condition.
330  AddObserver(prerender_manager()->prerender_tracker());
331  NotifyPrerenderStart();
332
333  // Close ourselves when the application is shutting down.
334  notification_registrar_.Add(this, chrome::NOTIFICATION_APP_TERMINATING,
335                              content::NotificationService::AllSources());
336
337  // Register for our parent profile to shutdown, so we can shut ourselves down
338  // as well (should only be called for OTR profiles, as we should receive
339  // APP_TERMINATING before non-OTR profiles are destroyed).
340  // TODO(tburkard): figure out if this is needed.
341  notification_registrar_.Add(this, chrome::NOTIFICATION_PROFILE_DESTROYED,
342                              content::Source<Profile>(profile_));
343
344  // Register to inform new RenderViews that we're prerendering.
345  notification_registrar_.Add(
346      this, content::NOTIFICATION_WEB_CONTENTS_RENDER_VIEW_HOST_CREATED,
347      content::Source<WebContents>(prerender_contents_.get()));
348
349  // Transfer over the user agent override.
350  prerender_contents_.get()->SetUserAgentOverride(
351      prerender_manager_->config().user_agent_override);
352
353  content::NavigationController::LoadURLParams load_url_params(
354      prerender_url_);
355  load_url_params.referrer = referrer_;
356  load_url_params.transition_type =
357      ((origin_ == ORIGIN_OMNIBOX || origin_ == ORIGIN_INSTANT) ?
358          content::PAGE_TRANSITION_TYPED : content::PAGE_TRANSITION_LINK);
359  load_url_params.override_user_agent =
360      prerender_manager_->config().is_overriding_user_agent ?
361      content::NavigationController::UA_OVERRIDE_TRUE :
362      content::NavigationController::UA_OVERRIDE_FALSE;
363  prerender_contents_.get()->GetController().LoadURLWithParams(load_url_params);
364}
365
366bool PrerenderContents::GetChildId(int* child_id) const {
367  CHECK(child_id);
368  DCHECK_GE(child_id_, -1);
369  *child_id = child_id_;
370  return child_id_ != -1;
371}
372
373bool PrerenderContents::GetRouteId(int* route_id) const {
374  CHECK(route_id);
375  DCHECK_GE(route_id_, -1);
376  *route_id = route_id_;
377  return route_id_ != -1;
378}
379
380void PrerenderContents::SetFinalStatus(FinalStatus final_status) {
381  DCHECK(final_status >= FINAL_STATUS_USED && final_status < FINAL_STATUS_MAX);
382  DCHECK(final_status_ == FINAL_STATUS_MAX);
383
384  final_status_ = final_status;
385}
386
387PrerenderContents::~PrerenderContents() {
388  DCHECK_NE(FINAL_STATUS_MAX, final_status());
389  DCHECK(
390      prerendering_has_been_cancelled() || final_status() == FINAL_STATUS_USED);
391  DCHECK_NE(ORIGIN_MAX, origin());
392  // Since a lot of prerenders terminate before any meaningful cookie action
393  // would have happened, only record the cookie status for prerenders who
394  // were used, cancelled, or timed out.
395  if (prerendering_has_started_ &&
396      (final_status() == FINAL_STATUS_USED ||
397       final_status() == FINAL_STATUS_TIMED_OUT ||
398       final_status() == FINAL_STATUS_CANCELLED)) {
399    prerender_manager_->RecordCookieStatus(origin(), experiment_id(),
400                                           cookie_status_);
401  }
402  prerender_manager_->RecordFinalStatusWithMatchCompleteStatus(
403      origin(), experiment_id(), match_complete_status(), final_status());
404
405  // Broadcast the removal of aliases.
406  for (content::RenderProcessHost::iterator host_iterator =
407           content::RenderProcessHost::AllHostsIterator();
408       !host_iterator.IsAtEnd();
409       host_iterator.Advance()) {
410    content::RenderProcessHost* host = host_iterator.GetCurrentValue();
411    host->Send(new PrerenderMsg_OnPrerenderRemoveAliases(alias_urls_));
412  }
413
414  // If we still have a WebContents, clean up anything we need to and then
415  // destroy it.
416  if (prerender_contents_.get())
417    delete ReleasePrerenderContents();
418}
419
420void PrerenderContents::AddObserver(Observer* observer) {
421  DCHECK_EQ(FINAL_STATUS_MAX, final_status_);
422  observer_list_.AddObserver(observer);
423}
424
425void PrerenderContents::RemoveObserver(Observer* observer) {
426  observer_list_.RemoveObserver(observer);
427}
428
429void PrerenderContents::Observe(int type,
430                                const content::NotificationSource& source,
431                                const content::NotificationDetails& details) {
432  switch (type) {
433    case chrome::NOTIFICATION_PROFILE_DESTROYED:
434      Destroy(FINAL_STATUS_PROFILE_DESTROYED);
435      return;
436
437    case chrome::NOTIFICATION_APP_TERMINATING:
438      Destroy(FINAL_STATUS_APP_TERMINATING);
439      return;
440
441    case content::NOTIFICATION_WEB_CONTENTS_RENDER_VIEW_HOST_CREATED: {
442      if (prerender_contents_.get()) {
443        DCHECK_EQ(content::Source<WebContents>(source).ptr(),
444                  prerender_contents_.get());
445
446        content::Details<RenderViewHost> new_render_view_host(details);
447        OnRenderViewHostCreated(new_render_view_host.ptr());
448
449        // Make sure the size of the RenderViewHost has been passed to the new
450        // RenderView.  Otherwise, the size may not be sent until the
451        // RenderViewReady event makes it from the render process to the UI
452        // thread of the browser process.  When the RenderView receives its
453        // size, is also sets itself to be visible, which would then break the
454        // visibility API.
455        new_render_view_host->WasResized();
456        prerender_contents_->WasHidden();
457      }
458      break;
459    }
460
461    default:
462      NOTREACHED() << "Unexpected notification sent.";
463      break;
464  }
465}
466
467void PrerenderContents::OnRenderViewHostCreated(
468    RenderViewHost* new_render_view_host) {
469}
470
471size_t PrerenderContents::pending_prerender_count() const {
472  return pending_prerenders_.size();
473}
474
475WebContents* PrerenderContents::CreateWebContents(
476    SessionStorageNamespace* session_storage_namespace) {
477  // TODO(ajwong): Remove the temporary map once prerendering is aware of
478  // multiple session storage namespaces per tab.
479  content::SessionStorageNamespaceMap session_storage_namespace_map;
480  session_storage_namespace_map[std::string()] = session_storage_namespace;
481  return WebContents::CreateWithSessionStorage(
482      WebContents::CreateParams(profile_), session_storage_namespace_map);
483}
484
485void PrerenderContents::NotifyPrerenderStart() {
486  DCHECK_EQ(FINAL_STATUS_MAX, final_status_);
487  FOR_EACH_OBSERVER(Observer, observer_list_, OnPrerenderStart(this));
488}
489
490void PrerenderContents::NotifyPrerenderStopLoading() {
491  FOR_EACH_OBSERVER(Observer, observer_list_, OnPrerenderStopLoading(this));
492}
493
494void PrerenderContents::NotifyPrerenderStop() {
495  DCHECK_NE(FINAL_STATUS_MAX, final_status_);
496  FOR_EACH_OBSERVER(Observer, observer_list_, OnPrerenderStop(this));
497  observer_list_.Clear();
498}
499
500void PrerenderContents::NotifyPrerenderCreatedMatchCompleteReplacement(
501    PrerenderContents* replacement) {
502  FOR_EACH_OBSERVER(Observer, observer_list_,
503                    OnPrerenderCreatedMatchCompleteReplacement(this,
504                                                               replacement));
505}
506
507void PrerenderContents::DidUpdateFaviconURL(
508    int32 page_id,
509    const std::vector<content::FaviconURL>& urls) {
510  VLOG(1) << "PrerenderContents::OnUpdateFaviconURL" << icon_url_;
511  for (std::vector<content::FaviconURL>::const_iterator it = urls.begin();
512       it != urls.end(); ++it) {
513    if (it->icon_type == content::FaviconURL::FAVICON) {
514      icon_url_ = it->icon_url;
515      VLOG(1) << icon_url_;
516      return;
517    }
518  }
519}
520
521bool PrerenderContents::OnMessageReceived(const IPC::Message& message) {
522  bool handled = true;
523  // The following messages we do want to consume.
524  IPC_BEGIN_MESSAGE_MAP(PrerenderContents, message)
525    IPC_MESSAGE_HANDLER(ChromeViewHostMsg_CancelPrerenderForPrinting,
526                        OnCancelPrerenderForPrinting)
527    IPC_MESSAGE_UNHANDLED(handled = false)
528  IPC_END_MESSAGE_MAP()
529
530  return handled;
531}
532
533bool PrerenderContents::CheckURL(const GURL& url) {
534  const bool http = url.SchemeIs(content::kHttpScheme);
535  const bool https = url.SchemeIs(content::kHttpsScheme);
536  if (!http && !https) {
537    DCHECK_NE(MATCH_COMPLETE_REPLACEMENT_PENDING, match_complete_status_);
538    Destroy(FINAL_STATUS_UNSUPPORTED_SCHEME);
539    return false;
540  }
541  if (https && !prerender_manager_->config().https_allowed) {
542    DCHECK_NE(MATCH_COMPLETE_REPLACEMENT_PENDING, match_complete_status_);
543    Destroy(FINAL_STATUS_HTTPS);
544    return false;
545  }
546  if (match_complete_status_ != MATCH_COMPLETE_REPLACEMENT_PENDING &&
547      prerender_manager_->HasRecentlyBeenNavigatedTo(origin(), url)) {
548    Destroy(FINAL_STATUS_RECENTLY_VISITED);
549    return false;
550  }
551  return true;
552}
553
554bool PrerenderContents::AddAliasURL(const GURL& url) {
555  if (!CheckURL(url))
556    return false;
557
558  alias_urls_.push_back(url);
559
560  for (content::RenderProcessHost::iterator host_iterator =
561           content::RenderProcessHost::AllHostsIterator();
562       !host_iterator.IsAtEnd();
563       host_iterator.Advance()) {
564    content::RenderProcessHost* host = host_iterator.GetCurrentValue();
565    host->Send(new PrerenderMsg_OnPrerenderAddAlias(url));
566  }
567
568  return true;
569}
570
571bool PrerenderContents::Matches(
572    const GURL& url,
573    const SessionStorageNamespace* session_storage_namespace) const {
574  if (session_storage_namespace &&
575      session_storage_namespace_id_ != session_storage_namespace->id()) {
576    return false;
577  }
578  return std::count_if(alias_urls_.begin(), alias_urls_.end(),
579                       std::bind2nd(std::equal_to<GURL>(), url)) != 0;
580}
581
582void PrerenderContents::RenderProcessGone(base::TerminationStatus status) {
583  Destroy(FINAL_STATUS_RENDERER_CRASHED);
584}
585
586void PrerenderContents::RenderFrameCreated(
587    content::RenderFrameHost* render_frame_host) {
588  render_frame_hosts_.insert(render_frame_host);
589  // When a new RenderFrame is created for a prerendering WebContents, tell the
590  // new RenderFrame it's being used for prerendering before any navigations
591  // occur.  Note that this is always triggered before the first navigation, so
592  // there's no need to send the message just after the WebContents is created.
593  render_frame_host->Send(new PrerenderMsg_SetIsPrerendering(
594      render_frame_host->GetRoutingID(), true));
595}
596
597void PrerenderContents::RenderFrameDeleted(
598    content::RenderFrameHost* render_frame_host) {
599  render_frame_hosts_.erase(render_frame_host);
600}
601
602void PrerenderContents::DidStopLoading(
603    content::RenderViewHost* render_view_host) {
604  has_stopped_loading_ = true;
605  NotifyPrerenderStopLoading();
606}
607
608void PrerenderContents::DidStartProvisionalLoadForFrame(
609    int64 frame_id,
610    int64 parent_frame_id,
611    bool is_main_frame,
612    const GURL& validated_url,
613    bool is_error_page,
614    bool is_iframe_srcdoc,
615    RenderViewHost* render_view_host) {
616  if (is_main_frame) {
617    if (!CheckURL(validated_url))
618      return;
619
620    // Usually, this event fires if the user clicks or enters a new URL.
621    // Neither of these can happen in the case of an invisible prerender.
622    // So the cause is: Some JavaScript caused a new URL to be loaded.  In that
623    // case, the spinner would start again in the browser, so we must reset
624    // has_stopped_loading_ so that the spinner won't be stopped.
625    has_stopped_loading_ = false;
626    has_finished_loading_ = false;
627  }
628}
629
630void PrerenderContents::DidFinishLoad(int64 frame_id,
631                                      const GURL& validated_url,
632                                      bool is_main_frame,
633                                      RenderViewHost* render_view_host) {
634  if (is_main_frame)
635    has_finished_loading_ = true;
636}
637
638void PrerenderContents::DidNavigateMainFrame(
639    const content::LoadCommittedDetails& details,
640    const content::FrameNavigateParams& params) {
641  // If the prerender made a second navigation entry, abort the prerender. This
642  // avoids having to correctly implement a complex history merging case (this
643  // interacts with location.replace) and correctly synchronize with the
644  // renderer. The final status may be monitored to see we need to revisit this
645  // decision. This does not affect client redirects as those do not push new
646  // history entries. (Calls to location.replace, navigations before onload, and
647  // <meta http-equiv=refresh> with timeouts under 1 second do not create
648  // entries in Blink.)
649  if (prerender_contents_->GetController().GetEntryCount() > 1) {
650    Destroy(FINAL_STATUS_NEW_NAVIGATION_ENTRY);
651    return;
652  }
653
654  // Add each redirect as an alias. |params.url| is included in
655  // |params.redirects|.
656  //
657  // TODO(davidben): We do not correctly patch up history for renderer-initated
658  // navigations which add history entries. http://crbug.com/305660.
659  for (size_t i = 0; i < params.redirects.size(); i++) {
660    if (!AddAliasURL(params.redirects[i]))
661      return;
662  }
663}
664
665void PrerenderContents::DidGetRedirectForResourceRequest(
666    const content::ResourceRedirectDetails& details) {
667  // DidGetRedirectForResourceRequest can come for any resource on a page.  If
668  // it's a redirect on the top-level resource, the name needs to be remembered
669  // for future matching, and if it redirects to an https resource, it needs to
670  // be canceled. If a subresource is redirected, nothing changes.
671  if (details.resource_type != ResourceType::MAIN_FRAME)
672    return;
673  CheckURL(details.new_url);
674}
675
676void PrerenderContents::Destroy(FinalStatus final_status) {
677  DCHECK_NE(final_status, FINAL_STATUS_USED);
678
679  if (prerendering_has_been_cancelled_)
680    return;
681
682  if (child_id_ != -1 && route_id_ != -1) {
683    // Cancel the prerender in the PrerenderTracker.  This is needed
684    // because destroy may be called directly from the UI thread without calling
685    // TryCancel().  This is difficult to completely avoid, since prerendering
686    // can be cancelled before a RenderView is created.
687    bool is_cancelled = prerender_manager()->prerender_tracker()->TryCancel(
688        child_id_, route_id_, final_status);
689    CHECK(is_cancelled);
690
691    // A different final status may have been set already from another thread.
692    // If so, use it instead.
693    if (!prerender_manager()->prerender_tracker()->
694            GetFinalStatus(child_id_, route_id_, &final_status)) {
695      NOTREACHED();
696    }
697  }
698  SetFinalStatus(final_status);
699
700  prerendering_has_been_cancelled_ = true;
701  prerender_manager_->AddToHistory(this);
702  prerender_manager_->MoveEntryToPendingDelete(this, final_status);
703
704  // Note that if this PrerenderContents was made into a MatchComplete
705  // replacement by MoveEntryToPendingDelete, NotifyPrerenderStop will
706  // not reach the PrerenderHandle. Rather
707  // OnPrerenderCreatedMatchCompleteReplacement will propogate that
708  // information to the referer.
709  if (!prerender_manager_->IsControlGroup(experiment_id()) &&
710      (prerendering_has_started() ||
711       match_complete_status() == MATCH_COMPLETE_REPLACEMENT)) {
712    NotifyPrerenderStop();
713  }
714}
715
716base::ProcessMetrics* PrerenderContents::MaybeGetProcessMetrics() {
717  if (process_metrics_.get() == NULL) {
718    // If a PrenderContents hasn't started prerending, don't be fully formed.
719    if (!GetRenderViewHost() || !GetRenderViewHost()->GetProcess())
720      return NULL;
721    base::ProcessHandle handle = GetRenderViewHost()->GetProcess()->GetHandle();
722    if (handle == base::kNullProcessHandle)
723      return NULL;
724#if !defined(OS_MACOSX)
725    process_metrics_.reset(base::ProcessMetrics::CreateProcessMetrics(handle));
726#else
727    process_metrics_.reset(base::ProcessMetrics::CreateProcessMetrics(
728        handle,
729        content::BrowserChildProcessHost::GetPortProvider()));
730#endif
731  }
732
733  return process_metrics_.get();
734}
735
736void PrerenderContents::DestroyWhenUsingTooManyResources() {
737  base::ProcessMetrics* metrics = MaybeGetProcessMetrics();
738  if (metrics == NULL)
739    return;
740
741  size_t private_bytes, shared_bytes;
742  if (metrics->GetMemoryBytes(&private_bytes, &shared_bytes) &&
743      private_bytes > prerender_manager_->config().max_bytes) {
744    Destroy(FINAL_STATUS_MEMORY_LIMIT_EXCEEDED);
745  }
746}
747
748WebContents* PrerenderContents::ReleasePrerenderContents() {
749  prerender_contents_->SetDelegate(NULL);
750  content::WebContentsObserver::Observe(NULL);
751  if (alias_session_storage_namespace)
752    alias_session_storage_namespace->RemoveTransactionLogProcessId(child_id_);
753  return prerender_contents_.release();
754}
755
756RenderViewHost* PrerenderContents::GetRenderViewHostMutable() {
757  return const_cast<RenderViewHost*>(GetRenderViewHost());
758}
759
760const RenderViewHost* PrerenderContents::GetRenderViewHost() const {
761  if (!prerender_contents_.get())
762    return NULL;
763  return prerender_contents_->GetRenderViewHost();
764}
765
766void PrerenderContents::DidNavigate(
767    const history::HistoryAddPageArgs& add_page_args) {
768  add_page_vector_.push_back(add_page_args);
769}
770
771void PrerenderContents::CommitHistory(WebContents* tab) {
772  HistoryTabHelper* history_tab_helper = HistoryTabHelper::FromWebContents(tab);
773  for (size_t i = 0; i < add_page_vector_.size(); ++i)
774    history_tab_helper->UpdateHistoryForNavigation(add_page_vector_[i]);
775}
776
777Value* PrerenderContents::GetAsValue() const {
778  if (!prerender_contents_.get())
779    return NULL;
780  DictionaryValue* dict_value = new DictionaryValue();
781  dict_value->SetString("url", prerender_url_.spec());
782  base::TimeTicks current_time = base::TimeTicks::Now();
783  base::TimeDelta duration = current_time - load_start_time_;
784  dict_value->SetInteger("duration", duration.InSeconds());
785  dict_value->SetBoolean("is_loaded", prerender_contents_ &&
786                                      !prerender_contents_->IsLoading());
787  return dict_value;
788}
789
790bool PrerenderContents::IsCrossSiteNavigationPending() const {
791  if (!prerender_contents_)
792    return false;
793  return (prerender_contents_->GetSiteInstance() !=
794          prerender_contents_->GetPendingSiteInstance());
795}
796
797SessionStorageNamespace* PrerenderContents::GetSessionStorageNamespace() const {
798  if (!prerender_contents())
799    return NULL;
800  return prerender_contents()->GetController().
801      GetDefaultSessionStorageNamespace();
802}
803
804void PrerenderContents::OnCancelPrerenderForPrinting() {
805  Destroy(FINAL_STATUS_WINDOW_PRINT);
806}
807
808void PrerenderContents::RecordCookieEvent(CookieEvent event,
809                                          bool is_main_frame_http_request,
810                                          base::Time earliest_create_date) {
811  // We don't care about sent cookies that were created after this prerender
812  // started.
813  // The reason is that for the purpose of the histograms emitted, we only care
814  // about cookies that existed before the prerender was started, but not
815  // about cookies that were created as part of the prerender. Using the
816  // earliest creation timestamp of all cookies provided by the cookie monster
817  // is a heuristic that yields the desired result pretty closely.
818  // In particular, we pretend no other WebContents make changes to the cookies
819  // relevant to the prerender, which may not actually always be the case, but
820  // hopefully most of the times.
821  if (event == COOKIE_EVENT_SEND && earliest_create_date > start_time_)
822    return;
823
824  InternalCookieEvent internal_event = INTERNAL_COOKIE_EVENT_MAX;
825
826  if (is_main_frame_http_request) {
827    if (event == COOKIE_EVENT_SEND) {
828      internal_event = INTERNAL_COOKIE_EVENT_MAIN_FRAME_SEND;
829    } else {
830      internal_event = INTERNAL_COOKIE_EVENT_MAIN_FRAME_CHANGE;
831    }
832  } else {
833    if (event == COOKIE_EVENT_SEND) {
834      internal_event = INTERNAL_COOKIE_EVENT_OTHER_SEND;
835    } else {
836      internal_event = INTERNAL_COOKIE_EVENT_OTHER_CHANGE;
837    }
838  }
839
840  DCHECK_GE(internal_event, 0);
841  DCHECK_LT(internal_event, INTERNAL_COOKIE_EVENT_MAX);
842
843  cookie_status_ |= (1 << internal_event);
844
845  DCHECK_GE(cookie_status_, 0);
846  DCHECK_LT(cookie_status_, kNumCookieStatuses);
847}
848
849}  // namespace prerender
850