• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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 
41 using content::DownloadItem;
42 using content::OpenURLParams;
43 using content::RenderViewHost;
44 using content::ResourceRedirectDetails;
45 using content::SessionStorageNamespace;
46 using content::WebContents;
47 
48 namespace prerender {
49 
50 namespace {
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.
56 enum 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
67 const int PrerenderContents::kNumCookieStatuses =
68     (1 << INTERNAL_COOKIE_EVENT_MAX);
69 
70 class PrerenderContentsFactoryImpl : public PrerenderContents::Factory {
71  public:
CreatePrerenderContents(PrerenderManager * prerender_manager,Profile * profile,const GURL & url,const content::Referrer & referrer,Origin origin,uint8 experiment_id)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 
83 class PrerenderContents::WebContentsDelegateImpl
84     : public content::WebContentsDelegate {
85  public:
WebContentsDelegateImpl(PrerenderContents * prerender_contents)86   explicit WebContentsDelegateImpl(PrerenderContents* prerender_contents)
87       : prerender_contents_(prerender_contents) {
88   }
89 
90   // content::WebContentsDelegate implementation:
OpenURLFromTab(WebContents * source,const OpenURLParams & params)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 
CanDownload(RenderViewHost * render_view_host,int request_id,const std::string & request_method,const base::Callback<void (bool)> & callback)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 
OnGoToEntryOffset(int offset)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 
JSOutOfMemory(WebContents * tab)123   virtual void JSOutOfMemory(WebContents* tab) OVERRIDE {
124     prerender_contents_->Destroy(FINAL_STATUS_JS_OUT_OF_MEMORY);
125   }
126 
ShouldSuppressDialogs()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 
RegisterProtocolHandler(WebContents * web_contents,const std::string & protocol,const GURL & url,const base::string16 & title,bool user_gesture)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 
GetSizeForNewRenderView(const WebContents * web_contents) const146   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 
OnPrerenderStopLoading(PrerenderContents * contents)157 void PrerenderContents::Observer::OnPrerenderStopLoading(
158     PrerenderContents* contents) {
159 }
160 
OnPrerenderCreatedMatchCompleteReplacement(PrerenderContents * contents,PrerenderContents * replacement)161 void PrerenderContents::Observer::OnPrerenderCreatedMatchCompleteReplacement(
162     PrerenderContents* contents, PrerenderContents* replacement) {
163 }
164 
Observer()165 PrerenderContents::Observer::Observer() {
166 }
167 
~Observer()168 PrerenderContents::Observer::~Observer() {
169 }
170 
PendingPrerenderInfo(base::WeakPtr<PrerenderHandle> weak_prerender_handle,Origin origin,const GURL & url,const content::Referrer & referrer,const gfx::Size & size)171 PrerenderContents::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 
~PendingPrerenderInfo()184 PrerenderContents::PendingPrerenderInfo::~PendingPrerenderInfo() {
185 }
186 
AddPendingPrerender(scoped_ptr<PendingPrerenderInfo> pending_prerender_info)187 void PrerenderContents::AddPendingPrerender(
188     scoped_ptr<PendingPrerenderInfo> pending_prerender_info) {
189   pending_prerenders_.push_back(pending_prerender_info.release());
190 }
191 
PrepareForUse()192 void 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 
PrerenderContents(PrerenderManager * prerender_manager,Profile * profile,const GURL & url,const content::Referrer & referrer,Origin origin,uint8 experiment_id)212 PrerenderContents::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 
CreateMatchCompleteReplacement()240 PrerenderContents* 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 
Init()260 bool PrerenderContents::Init() {
261   return AddAliasURL(prerender_url_);
262 }
263 
264 // static
CreateFactory()265 PrerenderContents::Factory* PrerenderContents::CreateFactory() {
266   return new PrerenderContentsFactoryImpl();
267 }
268 
StartPrerendering(int creator_child_id,const gfx::Size & size,SessionStorageNamespace * session_storage_namespace)269 void 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 
GetChildId(int * child_id) const366 bool 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 
GetRouteId(int * route_id) const373 bool 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 
SetFinalStatus(FinalStatus final_status)380 void 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 
~PrerenderContents()387 PrerenderContents::~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 
AddObserver(Observer * observer)420 void PrerenderContents::AddObserver(Observer* observer) {
421   DCHECK_EQ(FINAL_STATUS_MAX, final_status_);
422   observer_list_.AddObserver(observer);
423 }
424 
RemoveObserver(Observer * observer)425 void PrerenderContents::RemoveObserver(Observer* observer) {
426   observer_list_.RemoveObserver(observer);
427 }
428 
Observe(int type,const content::NotificationSource & source,const content::NotificationDetails & details)429 void 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 
OnRenderViewHostCreated(RenderViewHost * new_render_view_host)467 void PrerenderContents::OnRenderViewHostCreated(
468     RenderViewHost* new_render_view_host) {
469 }
470 
pending_prerender_count() const471 size_t PrerenderContents::pending_prerender_count() const {
472   return pending_prerenders_.size();
473 }
474 
CreateWebContents(SessionStorageNamespace * session_storage_namespace)475 WebContents* 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 
NotifyPrerenderStart()485 void PrerenderContents::NotifyPrerenderStart() {
486   DCHECK_EQ(FINAL_STATUS_MAX, final_status_);
487   FOR_EACH_OBSERVER(Observer, observer_list_, OnPrerenderStart(this));
488 }
489 
NotifyPrerenderStopLoading()490 void PrerenderContents::NotifyPrerenderStopLoading() {
491   FOR_EACH_OBSERVER(Observer, observer_list_, OnPrerenderStopLoading(this));
492 }
493 
NotifyPrerenderStop()494 void 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 
NotifyPrerenderCreatedMatchCompleteReplacement(PrerenderContents * replacement)500 void PrerenderContents::NotifyPrerenderCreatedMatchCompleteReplacement(
501     PrerenderContents* replacement) {
502   FOR_EACH_OBSERVER(Observer, observer_list_,
503                     OnPrerenderCreatedMatchCompleteReplacement(this,
504                                                                replacement));
505 }
506 
DidUpdateFaviconURL(int32 page_id,const std::vector<content::FaviconURL> & urls)507 void 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 
OnMessageReceived(const IPC::Message & message)521 bool 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 
CheckURL(const GURL & url)533 bool 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 
AddAliasURL(const GURL & url)554 bool 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 
Matches(const GURL & url,const SessionStorageNamespace * session_storage_namespace) const571 bool 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 
RenderProcessGone(base::TerminationStatus status)582 void PrerenderContents::RenderProcessGone(base::TerminationStatus status) {
583   Destroy(FINAL_STATUS_RENDERER_CRASHED);
584 }
585 
RenderFrameCreated(content::RenderFrameHost * render_frame_host)586 void 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 
RenderFrameDeleted(content::RenderFrameHost * render_frame_host)597 void PrerenderContents::RenderFrameDeleted(
598     content::RenderFrameHost* render_frame_host) {
599   render_frame_hosts_.erase(render_frame_host);
600 }
601 
DidStopLoading(content::RenderViewHost * render_view_host)602 void PrerenderContents::DidStopLoading(
603     content::RenderViewHost* render_view_host) {
604   has_stopped_loading_ = true;
605   NotifyPrerenderStopLoading();
606 }
607 
DidStartProvisionalLoadForFrame(int64 frame_id,int64 parent_frame_id,bool is_main_frame,const GURL & validated_url,bool is_error_page,bool is_iframe_srcdoc,RenderViewHost * render_view_host)608 void 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 
DidFinishLoad(int64 frame_id,const GURL & validated_url,bool is_main_frame,RenderViewHost * render_view_host)630 void 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 
DidNavigateMainFrame(const content::LoadCommittedDetails & details,const content::FrameNavigateParams & params)638 void 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 
DidGetRedirectForResourceRequest(const content::ResourceRedirectDetails & details)665 void 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 
Destroy(FinalStatus final_status)676 void 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 
MaybeGetProcessMetrics()716 base::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 
DestroyWhenUsingTooManyResources()736 void 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 
ReleasePrerenderContents()748 WebContents* 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 
GetRenderViewHostMutable()756 RenderViewHost* PrerenderContents::GetRenderViewHostMutable() {
757   return const_cast<RenderViewHost*>(GetRenderViewHost());
758 }
759 
GetRenderViewHost() const760 const RenderViewHost* PrerenderContents::GetRenderViewHost() const {
761   if (!prerender_contents_.get())
762     return NULL;
763   return prerender_contents_->GetRenderViewHost();
764 }
765 
DidNavigate(const history::HistoryAddPageArgs & add_page_args)766 void PrerenderContents::DidNavigate(
767     const history::HistoryAddPageArgs& add_page_args) {
768   add_page_vector_.push_back(add_page_args);
769 }
770 
CommitHistory(WebContents * tab)771 void 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 
GetAsValue() const777 Value* 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 
IsCrossSiteNavigationPending() const790 bool PrerenderContents::IsCrossSiteNavigationPending() const {
791   if (!prerender_contents_)
792     return false;
793   return (prerender_contents_->GetSiteInstance() !=
794           prerender_contents_->GetPendingSiteInstance());
795 }
796 
GetSessionStorageNamespace() const797 SessionStorageNamespace* PrerenderContents::GetSessionStorageNamespace() const {
798   if (!prerender_contents())
799     return NULL;
800   return prerender_contents()->GetController().
801       GetDefaultSessionStorageNamespace();
802 }
803 
OnCancelPrerenderForPrinting()804 void PrerenderContents::OnCancelPrerenderForPrinting() {
805   Destroy(FINAL_STATUS_WINDOW_PRINT);
806 }
807 
RecordCookieEvent(CookieEvent event,bool is_main_frame_http_request,base::Time earliest_create_date)808 void 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