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/bind.h"
12#include "base/strings/utf_string_conversions.h"
13#include "chrome/browser/chrome_notification_types.h"
14#include "chrome/browser/history/history_tab_helper.h"
15#include "chrome/browser/prerender/prerender_field_trial.h"
16#include "chrome/browser/prerender/prerender_final_status.h"
17#include "chrome/browser/prerender/prerender_handle.h"
18#include "chrome/browser/prerender/prerender_manager.h"
19#include "chrome/browser/prerender/prerender_manager_factory.h"
20#include "chrome/browser/prerender/prerender_resource_throttle.h"
21#include "chrome/browser/prerender/prerender_tracker.h"
22#include "chrome/browser/profiles/profile.h"
23#include "chrome/browser/ui/browser.h"
24#include "chrome/browser/ui/tab_helpers.h"
25#include "chrome/browser/ui/web_contents_sizer.h"
26#include "chrome/common/prerender_messages.h"
27#include "chrome/common/render_messages.h"
28#include "chrome/common/url_constants.h"
29#include "components/history/core/browser/history_types.h"
30#include "content/public/browser/browser_child_process_host.h"
31#include "content/public/browser/browser_thread.h"
32#include "content/public/browser/notification_service.h"
33#include "content/public/browser/render_frame_host.h"
34#include "content/public/browser/render_process_host.h"
35#include "content/public/browser/render_view_host.h"
36#include "content/public/browser/resource_request_details.h"
37#include "content/public/browser/session_storage_namespace.h"
38#include "content/public/browser/web_contents.h"
39#include "content/public/browser/web_contents_delegate.h"
40#include "content/public/common/frame_navigate_params.h"
41#include "net/url_request/url_request_context_getter.h"
42#include "ui/base/page_transition_types.h"
43#include "ui/gfx/rect.h"
44
45using content::BrowserThread;
46using content::DownloadItem;
47using content::OpenURLParams;
48using content::RenderViewHost;
49using content::ResourceRedirectDetails;
50using content::ResourceType;
51using content::SessionStorageNamespace;
52using content::WebContents;
53
54namespace prerender {
55
56namespace {
57
58// Internal cookie event.
59// Whenever a prerender interacts with the cookie store, either sending
60// existing cookies that existed before the prerender started, or when a cookie
61// is changed, we record these events for histogramming purposes.
62enum InternalCookieEvent {
63  INTERNAL_COOKIE_EVENT_MAIN_FRAME_SEND = 0,
64  INTERNAL_COOKIE_EVENT_MAIN_FRAME_CHANGE = 1,
65  INTERNAL_COOKIE_EVENT_OTHER_SEND = 2,
66  INTERNAL_COOKIE_EVENT_OTHER_CHANGE = 3,
67  INTERNAL_COOKIE_EVENT_MAX
68};
69
70// Indicates whether existing cookies were sent, and if they were third party
71// cookies, and whether they were for blocking resources.
72// Each value may be inclusive of previous values. We only care about the
73// value with the highest index that has ever occurred in the course of a
74// prerender.
75enum CookieSendType {
76  COOKIE_SEND_TYPE_NONE = 0,
77  COOKIE_SEND_TYPE_FIRST_PARTY = 1,
78  COOKIE_SEND_TYPE_THIRD_PARTY = 2,
79  COOKIE_SEND_TYPE_THIRD_PARTY_BLOCKING_RESOURCE = 3,
80  COOKIE_SEND_TYPE_MAX
81};
82
83void ResumeThrottles(
84    std::vector<base::WeakPtr<PrerenderResourceThrottle> > throttles) {
85  for (size_t i = 0; i < throttles.size(); i++) {
86    if (throttles[i])
87      throttles[i]->Resume();
88  }
89}
90
91}  // namespace
92
93// static
94const int PrerenderContents::kNumCookieStatuses =
95    (1 << INTERNAL_COOKIE_EVENT_MAX);
96
97// static
98const int PrerenderContents::kNumCookieSendTypes = COOKIE_SEND_TYPE_MAX;
99
100class PrerenderContentsFactoryImpl : public PrerenderContents::Factory {
101 public:
102  virtual PrerenderContents* CreatePrerenderContents(
103      PrerenderManager* prerender_manager, Profile* profile,
104      const GURL& url, const content::Referrer& referrer,
105      Origin origin, uint8 experiment_id) OVERRIDE {
106    return new PrerenderContents(prerender_manager, profile,
107                                 url, referrer, origin, experiment_id);
108  }
109};
110
111// WebContentsDelegateImpl -----------------------------------------------------
112
113class PrerenderContents::WebContentsDelegateImpl
114    : public content::WebContentsDelegate {
115 public:
116  explicit WebContentsDelegateImpl(PrerenderContents* prerender_contents)
117      : prerender_contents_(prerender_contents) {
118  }
119
120  // content::WebContentsDelegate implementation:
121  virtual WebContents* OpenURLFromTab(WebContents* source,
122                                      const OpenURLParams& params) OVERRIDE {
123    // |OpenURLFromTab| is typically called when a frame performs a navigation
124    // that requires the browser to perform the transition instead of WebKit.
125    // Examples include prerendering a site that redirects to an app URL,
126    // or if --enable-strict-site-isolation is specified and the prerendered
127    // frame redirects to a different origin.
128    // TODO(cbentzel): Consider supporting this if it is a common case during
129    // prerenders.
130    prerender_contents_->Destroy(FINAL_STATUS_OPEN_URL);
131    return NULL;
132  }
133
134  virtual void CloseContents(content::WebContents* contents) OVERRIDE {
135    prerender_contents_->Destroy(FINAL_STATUS_CLOSED);
136  }
137
138  virtual void CanDownload(
139      RenderViewHost* render_view_host,
140      const GURL& url,
141      const std::string& request_method,
142      const base::Callback<void(bool)>& callback) OVERRIDE {
143    prerender_contents_->Destroy(FINAL_STATUS_DOWNLOAD);
144    // Cancel the download.
145    callback.Run(false);
146  }
147
148   virtual bool ShouldCreateWebContents(
149       WebContents* web_contents,
150       int route_id,
151       WindowContainerType window_container_type,
152       const base::string16& frame_name,
153       const GURL& target_url,
154       const std::string& partition_id,
155       SessionStorageNamespace* session_storage_namespace) OVERRIDE {
156    // Since we don't want to permit child windows that would have a
157    // window.opener property, terminate prerendering.
158    prerender_contents_->Destroy(FINAL_STATUS_CREATE_NEW_WINDOW);
159    // Cancel the popup.
160    return false;
161  }
162
163  virtual bool OnGoToEntryOffset(int offset) OVERRIDE {
164    // This isn't allowed because the history merge operation
165    // does not work if there are renderer issued challenges.
166    // TODO(cbentzel): Cancel in this case? May not need to do
167    // since render-issued offset navigations are not guaranteed,
168    // but indicates that the page cares about the history.
169    return false;
170  }
171
172  virtual bool ShouldSuppressDialogs() OVERRIDE {
173    // We still want to show the user the message when they navigate to this
174    // page, so cancel this prerender.
175    prerender_contents_->Destroy(FINAL_STATUS_JAVASCRIPT_ALERT);
176    // Always suppress JavaScript messages if they're triggered by a page being
177    // prerendered.
178    return true;
179  }
180
181  virtual void RegisterProtocolHandler(WebContents* web_contents,
182                                       const std::string& protocol,
183                                       const GURL& url,
184                                       bool user_gesture) OVERRIDE {
185    // TODO(mmenke): Consider supporting this if it is a common case during
186    // prerenders.
187    prerender_contents_->Destroy(FINAL_STATUS_REGISTER_PROTOCOL_HANDLER);
188  }
189
190  virtual gfx::Size GetSizeForNewRenderView(
191      WebContents* web_contents) const OVERRIDE {
192    // Have to set the size of the RenderView on initialization to be sure it is
193    // set before the RenderView is hidden on all platforms (esp. Android).
194    return prerender_contents_->size_;
195  }
196
197 private:
198  PrerenderContents* prerender_contents_;
199};
200
201void PrerenderContents::Observer::OnPrerenderStopLoading(
202    PrerenderContents* contents) {
203}
204
205void PrerenderContents::Observer::OnPrerenderDomContentLoaded(
206    PrerenderContents* contents) {
207}
208
209void PrerenderContents::Observer::OnPrerenderCreatedMatchCompleteReplacement(
210    PrerenderContents* contents, PrerenderContents* replacement) {
211}
212
213PrerenderContents::Observer::Observer() {
214}
215
216PrerenderContents::Observer::~Observer() {
217}
218
219PrerenderContents::PrerenderContents(
220    PrerenderManager* prerender_manager,
221    Profile* profile,
222    const GURL& url,
223    const content::Referrer& referrer,
224    Origin origin,
225    uint8 experiment_id)
226    : prerendering_has_started_(false),
227      session_storage_namespace_id_(-1),
228      prerender_manager_(prerender_manager),
229      prerender_url_(url),
230      referrer_(referrer),
231      profile_(profile),
232      page_id_(0),
233      has_stopped_loading_(false),
234      has_finished_loading_(false),
235      final_status_(FINAL_STATUS_MAX),
236      match_complete_status_(MATCH_COMPLETE_DEFAULT),
237      prerendering_has_been_cancelled_(false),
238      child_id_(-1),
239      route_id_(-1),
240      origin_(origin),
241      experiment_id_(experiment_id),
242      creator_child_id_(-1),
243      cookie_status_(0),
244      cookie_send_type_(COOKIE_SEND_TYPE_NONE),
245      network_bytes_(0) {
246  DCHECK(prerender_manager != NULL);
247}
248
249PrerenderContents* PrerenderContents::CreateMatchCompleteReplacement() {
250  PrerenderContents* new_contents = prerender_manager_->CreatePrerenderContents(
251      prerender_url(), referrer(), origin(), experiment_id());
252
253  new_contents->load_start_time_ = load_start_time_;
254  new_contents->session_storage_namespace_id_ = session_storage_namespace_id_;
255  new_contents->set_match_complete_status(
256      PrerenderContents::MATCH_COMPLETE_REPLACEMENT_PENDING);
257
258  const bool did_init = new_contents->Init();
259  DCHECK(did_init);
260  DCHECK_EQ(alias_urls_.front(), new_contents->alias_urls_.front());
261  DCHECK_EQ(1u, new_contents->alias_urls_.size());
262  new_contents->alias_urls_ = alias_urls_;
263  // Erase all but the first alias URL; the replacement has adopted the
264  // remainder without increasing the renderer-side reference count.
265  alias_urls_.resize(1);
266  new_contents->set_match_complete_status(
267      PrerenderContents::MATCH_COMPLETE_REPLACEMENT);
268  NotifyPrerenderCreatedMatchCompleteReplacement(new_contents);
269  return new_contents;
270}
271
272bool PrerenderContents::Init() {
273  return AddAliasURL(prerender_url_);
274}
275
276// static
277PrerenderContents::Factory* PrerenderContents::CreateFactory() {
278  return new PrerenderContentsFactoryImpl();
279}
280
281// static
282PrerenderContents* PrerenderContents::FromWebContents(
283    content::WebContents* web_contents) {
284  if (!web_contents)
285    return NULL;
286  PrerenderManager* prerender_manager = PrerenderManagerFactory::GetForProfile(
287      Profile::FromBrowserContext(web_contents->GetBrowserContext()));
288  if (!prerender_manager)
289    return NULL;
290  return prerender_manager->GetPrerenderContents(web_contents);
291}
292
293void PrerenderContents::StartPrerendering(
294    int creator_child_id,
295    const gfx::Size& size,
296    SessionStorageNamespace* session_storage_namespace,
297    net::URLRequestContextGetter* request_context) {
298  DCHECK(profile_ != NULL);
299  DCHECK(!size.IsEmpty());
300  DCHECK(!prerendering_has_started_);
301  DCHECK(prerender_contents_.get() == NULL);
302  DCHECK_EQ(-1, creator_child_id_);
303  DCHECK(size_.IsEmpty());
304  DCHECK_EQ(1U, alias_urls_.size());
305
306  creator_child_id_ = creator_child_id;
307  session_storage_namespace_id_ = session_storage_namespace->id();
308  size_ = size;
309
310  DCHECK(load_start_time_.is_null());
311  load_start_time_ = base::TimeTicks::Now();
312  start_time_ = base::Time::Now();
313
314  // Everything after this point sets up the WebContents object and associated
315  // RenderView for the prerender page. Don't do this for members of the
316  // control group.
317  if (prerender_manager_->IsControlGroup(experiment_id()))
318    return;
319
320  if (origin_ == ORIGIN_LOCAL_PREDICTOR &&
321      IsLocalPredictorPrerenderAlwaysControlEnabled()) {
322    return;
323  }
324
325  prerendering_has_started_ = true;
326
327  alias_session_storage_namespace = session_storage_namespace->CreateAlias();
328  prerender_contents_.reset(
329      CreateWebContents(alias_session_storage_namespace.get()));
330  TabHelpers::AttachTabHelpers(prerender_contents_.get());
331  content::WebContentsObserver::Observe(prerender_contents_.get());
332
333  web_contents_delegate_.reset(new WebContentsDelegateImpl(this));
334  prerender_contents_.get()->SetDelegate(web_contents_delegate_.get());
335  // Set the size of the prerender WebContents.
336  ResizeWebContents(prerender_contents_.get(), size_);
337
338  child_id_ = GetRenderViewHost()->GetProcess()->GetID();
339  route_id_ = GetRenderViewHost()->GetRoutingID();
340
341  // Log transactions to see if we could merge session storage namespaces in
342  // the event of a mismatch.
343  alias_session_storage_namespace->AddTransactionLogProcessId(child_id_);
344
345  // Add the RenderProcessHost to the Prerender Manager.
346  prerender_manager()->AddPrerenderProcessHost(
347      GetRenderViewHost()->GetProcess());
348
349  // In the prerender tracker, create a Prerender Cookie Store to keep track of
350  // cookie changes performed by the prerender. Once the prerender is shown,
351  // the cookie changes will be committed to the actual cookie store,
352  // otherwise, they will be discarded.
353  // If |request_context| is NULL, the feature must be disabled, so the
354  // operation will not be performed.
355  if (request_context) {
356    BrowserThread::PostTask(
357        BrowserThread::IO, FROM_HERE,
358        base::Bind(&PrerenderTracker::AddPrerenderCookieStoreOnIOThread,
359                   base::Unretained(prerender_manager()->prerender_tracker()),
360                   GetRenderViewHost()->GetProcess()->GetID(),
361                   make_scoped_refptr(request_context),
362                   base::Bind(&PrerenderContents::Destroy,
363                              AsWeakPtr(),
364                              FINAL_STATUS_COOKIE_CONFLICT)));
365  }
366
367  NotifyPrerenderStart();
368
369  // Close ourselves when the application is shutting down.
370  notification_registrar_.Add(this, chrome::NOTIFICATION_APP_TERMINATING,
371                              content::NotificationService::AllSources());
372
373  // Register to inform new RenderViews that we're prerendering.
374  notification_registrar_.Add(
375      this, content::NOTIFICATION_WEB_CONTENTS_RENDER_VIEW_HOST_CREATED,
376      content::Source<WebContents>(prerender_contents_.get()));
377
378  // Transfer over the user agent override.
379  prerender_contents_.get()->SetUserAgentOverride(
380      prerender_manager_->config().user_agent_override);
381
382  content::NavigationController::LoadURLParams load_url_params(
383      prerender_url_);
384  load_url_params.referrer = referrer_;
385  load_url_params.transition_type = ui::PAGE_TRANSITION_LINK;
386  if (origin_ == ORIGIN_OMNIBOX) {
387    load_url_params.transition_type = ui::PageTransitionFromInt(
388        ui::PAGE_TRANSITION_TYPED |
389        ui::PAGE_TRANSITION_FROM_ADDRESS_BAR);
390  } else if (origin_ == ORIGIN_INSTANT) {
391    load_url_params.transition_type = ui::PageTransitionFromInt(
392        ui::PAGE_TRANSITION_GENERATED |
393        ui::PAGE_TRANSITION_FROM_ADDRESS_BAR);
394  }
395  load_url_params.override_user_agent =
396      prerender_manager_->config().is_overriding_user_agent ?
397      content::NavigationController::UA_OVERRIDE_TRUE :
398      content::NavigationController::UA_OVERRIDE_FALSE;
399  prerender_contents_.get()->GetController().LoadURLWithParams(load_url_params);
400}
401
402bool PrerenderContents::GetChildId(int* child_id) const {
403  CHECK(child_id);
404  DCHECK_GE(child_id_, -1);
405  *child_id = child_id_;
406  return child_id_ != -1;
407}
408
409bool PrerenderContents::GetRouteId(int* route_id) const {
410  CHECK(route_id);
411  DCHECK_GE(route_id_, -1);
412  *route_id = route_id_;
413  return route_id_ != -1;
414}
415
416void PrerenderContents::SetFinalStatus(FinalStatus final_status) {
417  DCHECK_GE(final_status, FINAL_STATUS_USED);
418  DCHECK_LT(final_status, FINAL_STATUS_MAX);
419
420  DCHECK_EQ(FINAL_STATUS_MAX, final_status_);
421
422  final_status_ = final_status;
423}
424
425PrerenderContents::~PrerenderContents() {
426  DCHECK_NE(FINAL_STATUS_MAX, final_status());
427  DCHECK(
428      prerendering_has_been_cancelled() || final_status() == FINAL_STATUS_USED);
429  DCHECK_NE(ORIGIN_MAX, origin());
430  // Since a lot of prerenders terminate before any meaningful cookie action
431  // would have happened, only record the cookie status for prerenders who
432  // were used, cancelled, or timed out.
433  if (prerendering_has_started_ && final_status() == FINAL_STATUS_USED) {
434    prerender_manager_->RecordCookieStatus(origin(), experiment_id(),
435                                           cookie_status_);
436    prerender_manager_->RecordCookieSendType(origin(), experiment_id(),
437                                             cookie_send_type_);
438  }
439  prerender_manager_->RecordFinalStatusWithMatchCompleteStatus(
440      origin(), experiment_id(), match_complete_status(), final_status());
441
442  bool used = final_status() == FINAL_STATUS_USED ||
443              final_status() == FINAL_STATUS_WOULD_HAVE_BEEN_USED;
444  prerender_manager_->RecordNetworkBytes(origin(), used, network_bytes_);
445
446  // Broadcast the removal of aliases.
447  for (content::RenderProcessHost::iterator host_iterator =
448           content::RenderProcessHost::AllHostsIterator();
449       !host_iterator.IsAtEnd();
450       host_iterator.Advance()) {
451    content::RenderProcessHost* host = host_iterator.GetCurrentValue();
452    host->Send(new PrerenderMsg_OnPrerenderRemoveAliases(alias_urls_));
453  }
454
455  // If we still have a WebContents, clean up anything we need to and then
456  // destroy it.
457  if (prerender_contents_.get())
458    delete ReleasePrerenderContents();
459}
460
461void PrerenderContents::AddObserver(Observer* observer) {
462  DCHECK_EQ(FINAL_STATUS_MAX, final_status_);
463  observer_list_.AddObserver(observer);
464}
465
466void PrerenderContents::RemoveObserver(Observer* observer) {
467  observer_list_.RemoveObserver(observer);
468}
469
470void PrerenderContents::Observe(int type,
471                                const content::NotificationSource& source,
472                                const content::NotificationDetails& details) {
473  switch (type) {
474    // TODO(davidben): Try to remove this in favor of relying on
475    // FINAL_STATUS_PROFILE_DESTROYED.
476    case chrome::NOTIFICATION_APP_TERMINATING:
477      Destroy(FINAL_STATUS_APP_TERMINATING);
478      return;
479
480    case content::NOTIFICATION_WEB_CONTENTS_RENDER_VIEW_HOST_CREATED: {
481      if (prerender_contents_.get()) {
482        DCHECK_EQ(content::Source<WebContents>(source).ptr(),
483                  prerender_contents_.get());
484
485        content::Details<RenderViewHost> new_render_view_host(details);
486        OnRenderViewHostCreated(new_render_view_host.ptr());
487
488        // Make sure the size of the RenderViewHost has been passed to the new
489        // RenderView.  Otherwise, the size may not be sent until the
490        // RenderViewReady event makes it from the render process to the UI
491        // thread of the browser process.  When the RenderView receives its
492        // size, is also sets itself to be visible, which would then break the
493        // visibility API.
494        new_render_view_host->WasResized();
495        prerender_contents_->WasHidden();
496      }
497      break;
498    }
499
500    default:
501      NOTREACHED() << "Unexpected notification sent.";
502      break;
503  }
504}
505
506void PrerenderContents::OnRenderViewHostCreated(
507    RenderViewHost* new_render_view_host) {
508}
509
510WebContents* PrerenderContents::CreateWebContents(
511    SessionStorageNamespace* session_storage_namespace) {
512  // TODO(ajwong): Remove the temporary map once prerendering is aware of
513  // multiple session storage namespaces per tab.
514  content::SessionStorageNamespaceMap session_storage_namespace_map;
515  session_storage_namespace_map[std::string()] = session_storage_namespace;
516  return WebContents::CreateWithSessionStorage(
517      WebContents::CreateParams(profile_), session_storage_namespace_map);
518}
519
520void PrerenderContents::NotifyPrerenderStart() {
521  DCHECK_EQ(FINAL_STATUS_MAX, final_status_);
522  FOR_EACH_OBSERVER(Observer, observer_list_, OnPrerenderStart(this));
523}
524
525void PrerenderContents::NotifyPrerenderStopLoading() {
526  FOR_EACH_OBSERVER(Observer, observer_list_, OnPrerenderStopLoading(this));
527}
528
529void PrerenderContents::NotifyPrerenderDomContentLoaded() {
530  FOR_EACH_OBSERVER(Observer, observer_list_,
531                    OnPrerenderDomContentLoaded(this));
532}
533
534void PrerenderContents::NotifyPrerenderStop() {
535  DCHECK_NE(FINAL_STATUS_MAX, final_status_);
536  FOR_EACH_OBSERVER(Observer, observer_list_, OnPrerenderStop(this));
537  observer_list_.Clear();
538}
539
540void PrerenderContents::NotifyPrerenderCreatedMatchCompleteReplacement(
541    PrerenderContents* replacement) {
542  FOR_EACH_OBSERVER(Observer, observer_list_,
543                    OnPrerenderCreatedMatchCompleteReplacement(this,
544                                                               replacement));
545}
546
547bool PrerenderContents::OnMessageReceived(const IPC::Message& message) {
548  bool handled = true;
549  // The following messages we do want to consume.
550  IPC_BEGIN_MESSAGE_MAP(PrerenderContents, message)
551    IPC_MESSAGE_HANDLER(ChromeViewHostMsg_CancelPrerenderForPrinting,
552                        OnCancelPrerenderForPrinting)
553    IPC_MESSAGE_UNHANDLED(handled = false)
554  IPC_END_MESSAGE_MAP()
555
556  return handled;
557}
558
559bool PrerenderContents::CheckURL(const GURL& url) {
560  if (!url.SchemeIsHTTPOrHTTPS()) {
561    DCHECK_NE(MATCH_COMPLETE_REPLACEMENT_PENDING, match_complete_status_);
562    Destroy(FINAL_STATUS_UNSUPPORTED_SCHEME);
563    return false;
564  }
565  if (match_complete_status_ != MATCH_COMPLETE_REPLACEMENT_PENDING &&
566      prerender_manager_->HasRecentlyBeenNavigatedTo(origin(), url)) {
567    Destroy(FINAL_STATUS_RECENTLY_VISITED);
568    return false;
569  }
570  return true;
571}
572
573bool PrerenderContents::AddAliasURL(const GURL& url) {
574  if (!CheckURL(url))
575    return false;
576
577  alias_urls_.push_back(url);
578
579  for (content::RenderProcessHost::iterator host_iterator =
580           content::RenderProcessHost::AllHostsIterator();
581       !host_iterator.IsAtEnd();
582       host_iterator.Advance()) {
583    content::RenderProcessHost* host = host_iterator.GetCurrentValue();
584    host->Send(new PrerenderMsg_OnPrerenderAddAlias(url));
585  }
586
587  return true;
588}
589
590bool PrerenderContents::Matches(
591    const GURL& url,
592    const SessionStorageNamespace* session_storage_namespace) const {
593  if (session_storage_namespace &&
594      session_storage_namespace_id_ != session_storage_namespace->id()) {
595    return false;
596  }
597  return std::count_if(alias_urls_.begin(), alias_urls_.end(),
598                       std::bind2nd(std::equal_to<GURL>(), url)) != 0;
599}
600
601void PrerenderContents::RenderProcessGone(base::TerminationStatus status) {
602  Destroy(FINAL_STATUS_RENDERER_CRASHED);
603}
604
605void PrerenderContents::RenderFrameCreated(
606    content::RenderFrameHost* render_frame_host) {
607  // When a new RenderFrame is created for a prerendering WebContents, tell the
608  // new RenderFrame it's being used for prerendering before any navigations
609  // occur.  Note that this is always triggered before the first navigation, so
610  // there's no need to send the message just after the WebContents is created.
611  render_frame_host->Send(new PrerenderMsg_SetIsPrerendering(
612      render_frame_host->GetRoutingID(), true));
613}
614
615void PrerenderContents::DidStopLoading(
616    content::RenderViewHost* render_view_host) {
617  has_stopped_loading_ = true;
618  NotifyPrerenderStopLoading();
619}
620
621void PrerenderContents::DocumentLoadedInFrame(
622    content::RenderFrameHost* render_frame_host) {
623  if (!render_frame_host->GetParent())
624    NotifyPrerenderDomContentLoaded();
625}
626
627void PrerenderContents::DidStartProvisionalLoadForFrame(
628    content::RenderFrameHost* render_frame_host,
629    const GURL& validated_url,
630    bool is_error_page,
631    bool is_iframe_srcdoc) {
632  if (!render_frame_host->GetParent()) {
633    if (!CheckURL(validated_url))
634      return;
635
636    // Usually, this event fires if the user clicks or enters a new URL.
637    // Neither of these can happen in the case of an invisible prerender.
638    // So the cause is: Some JavaScript caused a new URL to be loaded.  In that
639    // case, the spinner would start again in the browser, so we must reset
640    // has_stopped_loading_ so that the spinner won't be stopped.
641    has_stopped_loading_ = false;
642    has_finished_loading_ = false;
643  }
644}
645
646void PrerenderContents::DidFinishLoad(
647    content::RenderFrameHost* render_frame_host,
648    const GURL& validated_url) {
649  if (!render_frame_host->GetParent())
650    has_finished_loading_ = true;
651}
652
653void PrerenderContents::DidNavigateMainFrame(
654    const content::LoadCommittedDetails& details,
655    const content::FrameNavigateParams& params) {
656  // If the prerender made a second navigation entry, abort the prerender. This
657  // avoids having to correctly implement a complex history merging case (this
658  // interacts with location.replace) and correctly synchronize with the
659  // renderer. The final status may be monitored to see we need to revisit this
660  // decision. This does not affect client redirects as those do not push new
661  // history entries. (Calls to location.replace, navigations before onload, and
662  // <meta http-equiv=refresh> with timeouts under 1 second do not create
663  // entries in Blink.)
664  if (prerender_contents_->GetController().GetEntryCount() > 1) {
665    Destroy(FINAL_STATUS_NEW_NAVIGATION_ENTRY);
666    return;
667  }
668
669  // Add each redirect as an alias. |params.url| is included in
670  // |params.redirects|.
671  //
672  // TODO(davidben): We do not correctly patch up history for renderer-initated
673  // navigations which add history entries. http://crbug.com/305660.
674  for (size_t i = 0; i < params.redirects.size(); i++) {
675    if (!AddAliasURL(params.redirects[i]))
676      return;
677  }
678}
679
680void PrerenderContents::DidGetRedirectForResourceRequest(
681    RenderViewHost* render_view_host,
682    const content::ResourceRedirectDetails& details) {
683  // DidGetRedirectForResourceRequest can come for any resource on a page.  If
684  // it's a redirect on the top-level resource, the name needs to be remembered
685  // for future matching, and if it redirects to an https resource, it needs to
686  // be canceled. If a subresource is redirected, nothing changes.
687  if (details.resource_type != content::RESOURCE_TYPE_MAIN_FRAME)
688    return;
689  CheckURL(details.new_url);
690}
691
692void PrerenderContents::Destroy(FinalStatus final_status) {
693  DCHECK_NE(final_status, FINAL_STATUS_USED);
694
695  if (prerendering_has_been_cancelled_)
696    return;
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.get())
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
777base::Value* PrerenderContents::GetAsValue() const {
778  if (!prerender_contents_.get())
779    return NULL;
780  base::DictionaryValue* dict_value = new base::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
797void PrerenderContents::PrepareForUse() {
798  SetFinalStatus(FINAL_STATUS_USED);
799
800  if (prerender_contents_.get()) {
801    prerender_contents_->SendToAllFrames(
802        new PrerenderMsg_SetIsPrerendering(MSG_ROUTING_NONE, false));
803  }
804
805  NotifyPrerenderStop();
806
807  BrowserThread::PostTask(
808      BrowserThread::IO,
809      FROM_HERE,
810      base::Bind(&ResumeThrottles, resource_throttles_));
811  resource_throttles_.clear();
812}
813
814SessionStorageNamespace* PrerenderContents::GetSessionStorageNamespace() const {
815  if (!prerender_contents())
816    return NULL;
817  return prerender_contents()->GetController().
818      GetDefaultSessionStorageNamespace();
819}
820
821void PrerenderContents::OnCancelPrerenderForPrinting() {
822  Destroy(FINAL_STATUS_WINDOW_PRINT);
823}
824
825void PrerenderContents::RecordCookieEvent(CookieEvent event,
826                                          bool is_main_frame_http_request,
827                                          bool is_third_party_cookie,
828                                          bool is_for_blocking_resource,
829                                          base::Time earliest_create_date) {
830  // We don't care about sent cookies that were created after this prerender
831  // started.
832  // The reason is that for the purpose of the histograms emitted, we only care
833  // about cookies that existed before the prerender was started, but not
834  // about cookies that were created as part of the prerender. Using the
835  // earliest creation timestamp of all cookies provided by the cookie monster
836  // is a heuristic that yields the desired result pretty closely.
837  // In particular, we pretend no other WebContents make changes to the cookies
838  // relevant to the prerender, which may not actually always be the case, but
839  // hopefully most of the times.
840  if (event == COOKIE_EVENT_SEND && earliest_create_date > start_time_)
841    return;
842
843  InternalCookieEvent internal_event = INTERNAL_COOKIE_EVENT_MAX;
844
845  if (is_main_frame_http_request) {
846    if (event == COOKIE_EVENT_SEND) {
847      internal_event = INTERNAL_COOKIE_EVENT_MAIN_FRAME_SEND;
848    } else {
849      internal_event = INTERNAL_COOKIE_EVENT_MAIN_FRAME_CHANGE;
850    }
851  } else {
852    if (event == COOKIE_EVENT_SEND) {
853      internal_event = INTERNAL_COOKIE_EVENT_OTHER_SEND;
854    } else {
855      internal_event = INTERNAL_COOKIE_EVENT_OTHER_CHANGE;
856    }
857  }
858
859  DCHECK_GE(internal_event, 0);
860  DCHECK_LT(internal_event, INTERNAL_COOKIE_EVENT_MAX);
861
862  cookie_status_ |= (1 << internal_event);
863
864  DCHECK_GE(cookie_status_, 0);
865  DCHECK_LT(cookie_status_, kNumCookieStatuses);
866
867  CookieSendType send_type = COOKIE_SEND_TYPE_NONE;
868  if (event == COOKIE_EVENT_SEND) {
869    if (!is_third_party_cookie) {
870      send_type = COOKIE_SEND_TYPE_FIRST_PARTY;
871    } else {
872      if (is_for_blocking_resource) {
873        send_type = COOKIE_SEND_TYPE_THIRD_PARTY_BLOCKING_RESOURCE;
874      } else {
875        send_type = COOKIE_SEND_TYPE_THIRD_PARTY;
876      }
877    }
878  }
879  DCHECK_GE(send_type, 0);
880  DCHECK_LT(send_type, COOKIE_SEND_TYPE_MAX);
881
882  if (cookie_send_type_ < send_type)
883    cookie_send_type_ = send_type;
884}
885
886 void PrerenderContents::AddResourceThrottle(
887     const base::WeakPtr<PrerenderResourceThrottle>& throttle) {
888   resource_throttles_.push_back(throttle);
889 }
890
891 void PrerenderContents::AddNetworkBytes(int64 bytes) {
892   network_bytes_ += bytes;
893 }
894
895}  // namespace prerender
896