• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 // Copyright (c) 2011 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 "base/process_util.h"
8 #include "base/task.h"
9 #include "base/utf_string_conversions.h"
10 #include "chrome/browser/background_contents_service.h"
11 #include "chrome/browser/browser_process.h"
12 #include "chrome/browser/prerender/prerender_final_status.h"
13 #include "chrome/browser/prerender/prerender_manager.h"
14 #include "chrome/browser/profiles/profile.h"
15 #include "chrome/browser/renderer_preferences_util.h"
16 #include "chrome/browser/ui/login/login_prompt.h"
17 #include "chrome/common/extensions/extension_constants.h"
18 #include "chrome/common/icon_messages.h"
19 #include "chrome/common/render_messages.h"
20 #include "chrome/common/extensions/extension_messages.h"
21 #include "chrome/common/url_constants.h"
22 #include "chrome/common/view_types.h"
23 #include "content/browser/browsing_instance.h"
24 #include "content/browser/renderer_host/render_view_host.h"
25 #include "content/browser/renderer_host/resource_dispatcher_host.h"
26 #include "content/browser/renderer_host/resource_request_details.h"
27 #include "content/browser/site_instance.h"
28 #include "content/common/notification_service.h"
29 #include "content/common/view_messages.h"
30 #include "ui/gfx/rect.h"
31 
32 #if defined(OS_MACOSX)
33 #include "chrome/browser/mach_broker_mac.h"
34 #endif
35 
36 namespace prerender {
37 
AddChildRoutePair(ResourceDispatcherHost * rdh,int child_id,int route_id)38 void AddChildRoutePair(ResourceDispatcherHost* rdh,
39                        int child_id, int route_id) {
40   DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
41   rdh->AddPrerenderChildRoutePair(child_id, route_id);
42 }
43 
RemoveChildRoutePair(ResourceDispatcherHost * rdh,int child_id,int route_id)44 void RemoveChildRoutePair(ResourceDispatcherHost* rdh,
45                           int child_id, int route_id) {
46   DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
47   rdh->RemovePrerenderChildRoutePair(child_id, route_id);
48 }
49 
50 class PrerenderContentsFactoryImpl : public PrerenderContents::Factory {
51  public:
CreatePrerenderContents(PrerenderManager * prerender_manager,Profile * profile,const GURL & url,const std::vector<GURL> & alias_urls,const GURL & referrer)52   virtual PrerenderContents* CreatePrerenderContents(
53       PrerenderManager* prerender_manager, Profile* profile, const GURL& url,
54       const std::vector<GURL>& alias_urls, const GURL& referrer) {
55     return new PrerenderContents(prerender_manager, profile, url, alias_urls,
56                                  referrer);
57   }
58 };
59 
PrerenderContents(PrerenderManager * prerender_manager,Profile * profile,const GURL & url,const std::vector<GURL> & alias_urls,const GURL & referrer)60 PrerenderContents::PrerenderContents(PrerenderManager* prerender_manager,
61                                      Profile* profile,
62                                      const GURL& url,
63                                      const std::vector<GURL>& alias_urls,
64                                      const GURL& referrer)
65     : prerender_manager_(prerender_manager),
66       render_view_host_(NULL),
67       prerender_url_(url),
68       referrer_(referrer),
69       profile_(profile),
70       page_id_(0),
71       has_stopped_loading_(false),
72       final_status_(FINAL_STATUS_MAX),
73       prerendering_has_started_(false) {
74   DCHECK(prerender_manager != NULL);
75   if (!AddAliasURL(prerender_url_))
76     LOG(DFATAL) << "PrerenderContents given invalid URL " << prerender_url_;
77   for (std::vector<GURL>::const_iterator it = alias_urls.begin();
78        it != alias_urls.end();
79        ++it) {
80     if (!AddAliasURL(*it))
81       LOG(DFATAL) << "PrerenderContents given invalid URL " << prerender_url_;
82   }
83 }
84 
85 // static
CreateFactory()86 PrerenderContents::Factory* PrerenderContents::CreateFactory() {
87   return new PrerenderContentsFactoryImpl();
88 }
89 
StartPrerendering()90 void PrerenderContents::StartPrerendering() {
91   DCHECK(profile_ != NULL);
92   DCHECK(!prerendering_has_started_);
93   prerendering_has_started_ = true;
94   SiteInstance* site_instance = SiteInstance::CreateSiteInstance(profile_);
95   render_view_host_ = new RenderViewHost(site_instance, this, MSG_ROUTING_NONE,
96                                          NULL);
97 
98   int process_id = render_view_host_->process()->id();
99   int view_id = render_view_host_->routing_id();
100   std::pair<int, int> process_view_pair = std::make_pair(process_id, view_id);
101   NotificationService::current()->Notify(
102       NotificationType::PRERENDER_CONTENTS_STARTED,
103       Source<std::pair<int, int> >(&process_view_pair),
104       NotificationService::NoDetails());
105 
106   // Create the RenderView, so it can receive messages.
107   render_view_host_->CreateRenderView(string16());
108 
109   // Hide the RVH, so that we will run at a lower CPU priority.
110   // Once the RVH is being swapped into a tab, we will Restore it again.
111   render_view_host_->WasHidden();
112 
113   // Register this with the ResourceDispatcherHost as a prerender
114   // RenderViewHost. This must be done before the Navigate message to catch all
115   // resource requests, but as it is on the same thread as the Navigate message
116   // (IO) there is no race condition.
117   ResourceDispatcherHost* rdh = g_browser_process->resource_dispatcher_host();
118   BrowserThread::PostTask(BrowserThread::IO, FROM_HERE,
119                           NewRunnableFunction(&AddChildRoutePair, rdh,
120                                               process_id, view_id));
121 
122 
123   // Close ourselves when the application is shutting down.
124   registrar_.Add(this, NotificationType::APP_TERMINATING,
125                  NotificationService::AllSources());
126 
127   // Register for our parent profile to shutdown, so we can shut ourselves down
128   // as well (should only be called for OTR profiles, as we should receive
129   // APP_TERMINATING before non-OTR profiles are destroyed).
130   // TODO(tburkard): figure out if this is needed.
131   registrar_.Add(this, NotificationType::PROFILE_DESTROYED,
132                  Source<Profile>(profile_));
133 
134   // Register to cancel if Authentication is required.
135   registrar_.Add(this, NotificationType::AUTH_NEEDED,
136                  NotificationService::AllSources());
137 
138   registrar_.Add(this, NotificationType::AUTH_CANCELLED,
139                  NotificationService::AllSources());
140 
141   // Register all responses to see if we should cancel.
142   registrar_.Add(this, NotificationType::DOWNLOAD_INITIATED,
143                  NotificationService::AllSources());
144 
145   // Register for redirect notifications sourced from |this|.
146   registrar_.Add(this, NotificationType::RESOURCE_RECEIVED_REDIRECT,
147                  Source<RenderViewHostDelegate>(this));
148 
149   DCHECK(load_start_time_.is_null());
150   load_start_time_ = base::TimeTicks::Now();
151 
152   ViewMsg_Navigate_Params params;
153   params.page_id = -1;
154   params.pending_history_list_offset = -1;
155   params.current_history_list_offset = -1;
156   params.current_history_list_length = 0;
157   params.url = prerender_url_;
158   params.transition = PageTransition::LINK;
159   params.navigation_type = ViewMsg_Navigate_Type::PRERENDER;
160   params.referrer = referrer_;
161 
162   render_view_host_->Navigate(params);
163 }
164 
GetChildId(int * child_id) const165 bool PrerenderContents::GetChildId(int* child_id) const {
166   CHECK(child_id);
167   if (render_view_host_) {
168     *child_id = render_view_host_->process()->id();
169     return true;
170   }
171   return false;
172 }
173 
GetRouteId(int * route_id) const174 bool PrerenderContents::GetRouteId(int* route_id) const {
175   CHECK(route_id);
176   if (render_view_host_) {
177     *route_id = render_view_host_->routing_id();
178     return true;
179   }
180   return false;
181 }
182 
set_final_status(FinalStatus final_status)183 void PrerenderContents::set_final_status(FinalStatus final_status) {
184   DCHECK(final_status >= FINAL_STATUS_USED && final_status < FINAL_STATUS_MAX);
185   DCHECK_EQ(FINAL_STATUS_MAX, final_status_);
186 
187   final_status_ = final_status;
188 }
189 
final_status() const190 FinalStatus PrerenderContents::final_status() const {
191   return final_status_;
192 }
193 
~PrerenderContents()194 PrerenderContents::~PrerenderContents() {
195   DCHECK(final_status_ != FINAL_STATUS_MAX);
196 
197   // If we haven't even started prerendering, we were just in the control
198   // group, which means we do not want to record the status.
199   if (prerendering_has_started())
200     RecordFinalStatus(final_status_);
201 
202   if (!render_view_host_)   // Will be null for unit tests.
203     return;
204 
205   int process_id = render_view_host_->process()->id();
206   int view_id = render_view_host_->routing_id();
207   std::pair<int, int> process_view_pair = std::make_pair(process_id, view_id);
208   NotificationService::current()->Notify(
209       NotificationType::PRERENDER_CONTENTS_DESTROYED,
210       Source<std::pair<int, int> >(&process_view_pair),
211       NotificationService::NoDetails());
212 
213   ResourceDispatcherHost* rdh = g_browser_process->resource_dispatcher_host();
214   BrowserThread::PostTask(BrowserThread::IO, FROM_HERE,
215                           NewRunnableFunction(&RemoveChildRoutePair, rdh,
216                                               process_id, view_id));
217   render_view_host_->Shutdown();  // deletes render_view_host
218 }
219 
GetViewDelegate()220 RenderViewHostDelegate::View* PrerenderContents::GetViewDelegate() {
221   return this;
222 }
223 
GetURL() const224 const GURL& PrerenderContents::GetURL() const {
225   return url_;
226 }
227 
GetRenderViewType() const228 ViewType::Type PrerenderContents::GetRenderViewType() const {
229   return ViewType::BACKGROUND_CONTENTS;
230 }
231 
GetBrowserWindowID() const232 int PrerenderContents::GetBrowserWindowID() const {
233   return extension_misc::kUnknownWindowId;
234 }
235 
DidNavigate(RenderViewHost * render_view_host,const ViewHostMsg_FrameNavigate_Params & params)236 void PrerenderContents::DidNavigate(
237     RenderViewHost* render_view_host,
238     const ViewHostMsg_FrameNavigate_Params& params) {
239   // We only care when the outer frame changes.
240   if (!PageTransition::IsMainFrame(params.transition))
241     return;
242 
243   // Store the navigation params.
244   ViewHostMsg_FrameNavigate_Params* p = new ViewHostMsg_FrameNavigate_Params();
245   *p = params;
246   navigate_params_.reset(p);
247 
248   if (!AddAliasURL(params.url)) {
249     Destroy(FINAL_STATUS_HTTPS);
250     return;
251   }
252 
253   url_ = params.url;
254 }
255 
UpdateTitle(RenderViewHost * render_view_host,int32 page_id,const std::wstring & title)256 void PrerenderContents::UpdateTitle(RenderViewHost* render_view_host,
257                                     int32 page_id,
258                                     const std::wstring& title) {
259   if (title.empty()) {
260     return;
261   }
262 
263   title_ = WideToUTF16Hack(title);
264   page_id_ = page_id;
265 }
266 
RunJavaScriptMessage(const std::wstring & message,const std::wstring & default_prompt,const GURL & frame_url,const int flags,IPC::Message * reply_msg,bool * did_suppress_message)267 void PrerenderContents::RunJavaScriptMessage(
268     const std::wstring& message,
269     const std::wstring& default_prompt,
270     const GURL& frame_url,
271     const int flags,
272     IPC::Message* reply_msg,
273     bool* did_suppress_message) {
274   // Always suppress JavaScript messages if they're triggered by a page being
275   // prerendered.
276   *did_suppress_message = true;
277   // We still want to show the user the message when they navigate to this
278   // page, so cancel this prerender.
279   Destroy(FINAL_STATUS_JAVASCRIPT_ALERT);
280 }
281 
PreHandleKeyboardEvent(const NativeWebKeyboardEvent & event,bool * is_keyboard_shortcut)282 bool PrerenderContents::PreHandleKeyboardEvent(
283     const NativeWebKeyboardEvent& event,
284     bool* is_keyboard_shortcut) {
285   return false;
286 }
287 
Observe(NotificationType type,const NotificationSource & source,const NotificationDetails & details)288 void PrerenderContents::Observe(NotificationType type,
289                                 const NotificationSource& source,
290                                 const NotificationDetails& details) {
291   switch (type.value) {
292     case NotificationType::PROFILE_DESTROYED:
293       Destroy(FINAL_STATUS_PROFILE_DESTROYED);
294       return;
295 
296     case NotificationType::APP_TERMINATING:
297       Destroy(FINAL_STATUS_APP_TERMINATING);
298       return;
299 
300     case NotificationType::AUTH_NEEDED:
301     case NotificationType::AUTH_CANCELLED: {
302       // Prerendered pages have a NULL controller and the login handler should
303       // be referencing us as the render view host delegate.
304       NavigationController* controller =
305           Source<NavigationController>(source).ptr();
306       LoginNotificationDetails* details_ptr =
307           Details<LoginNotificationDetails>(details).ptr();
308       LoginHandler* handler = details_ptr->handler();
309       DCHECK(handler != NULL);
310       RenderViewHostDelegate* delegate = handler->GetRenderViewHostDelegate();
311       if (controller == NULL && delegate == this) {
312         Destroy(FINAL_STATUS_AUTH_NEEDED);
313         return;
314       }
315       break;
316     }
317 
318     case NotificationType::DOWNLOAD_INITIATED: {
319       // If the download is started from a RenderViewHost that we are
320       // delegating, kill the prerender. This cancels any pending requests
321       // though the download never actually started thanks to the
322       // DownloadRequestLimiter.
323       DCHECK(NotificationService::NoDetails() == details);
324       RenderViewHost* rvh = Source<RenderViewHost>(source).ptr();
325       CHECK(rvh != NULL);
326       if (rvh->delegate() == this) {
327         Destroy(FINAL_STATUS_DOWNLOAD);
328         return;
329       }
330       break;
331     }
332 
333     case NotificationType::RESOURCE_RECEIVED_REDIRECT: {
334       // RESOURCE_RECEIVED_REDIRECT can come for any resource on a page.
335       // If it's a redirect on the top-level resource, the name needs
336       // to be remembered for future matching, and if it redirects to
337       // an https resource, it needs to be canceled. If a subresource
338       // is redirected, nothing changes.
339       DCHECK(Source<RenderViewHostDelegate>(source).ptr() == this);
340       ResourceRedirectDetails* resource_redirect_details =
341           Details<ResourceRedirectDetails>(details).ptr();
342       CHECK(resource_redirect_details);
343       if (resource_redirect_details->resource_type() ==
344           ResourceType::MAIN_FRAME) {
345         if (!AddAliasURL(resource_redirect_details->new_url()))
346           Destroy(FINAL_STATUS_HTTPS);
347       }
348       break;
349     }
350 
351     default:
352       NOTREACHED() << "Unexpected notification sent.";
353       break;
354   }
355 }
356 
OnMessageBoxClosed(IPC::Message * reply_msg,bool success,const std::wstring & prompt)357 void PrerenderContents::OnMessageBoxClosed(IPC::Message* reply_msg,
358                                            bool success,
359                                            const std::wstring& prompt) {
360   render_view_host_->JavaScriptMessageBoxClosed(reply_msg, success, prompt);
361 }
362 
GetMessageBoxRootWindow()363 gfx::NativeWindow PrerenderContents::GetMessageBoxRootWindow() {
364   NOTIMPLEMENTED();
365   return NULL;
366 }
367 
AsTabContents()368 TabContents* PrerenderContents::AsTabContents() {
369   return NULL;
370 }
371 
AsExtensionHost()372 ExtensionHost* PrerenderContents::AsExtensionHost() {
373   return NULL;
374 }
375 
UpdateInspectorSetting(const std::string & key,const std::string & value)376 void PrerenderContents::UpdateInspectorSetting(const std::string& key,
377                                                const std::string& value) {
378   RenderViewHostDelegateHelper::UpdateInspectorSetting(profile_, key, value);
379 }
380 
ClearInspectorSettings()381 void PrerenderContents::ClearInspectorSettings() {
382   RenderViewHostDelegateHelper::ClearInspectorSettings(profile_);
383 }
384 
Close(RenderViewHost * render_view_host)385 void PrerenderContents::Close(RenderViewHost* render_view_host) {
386   Destroy(FINAL_STATUS_CLOSED);
387 }
388 
GetRendererPrefs(Profile * profile) const389 RendererPreferences PrerenderContents::GetRendererPrefs(
390     Profile* profile) const {
391   RendererPreferences preferences;
392   renderer_preferences_util::UpdateFromSystemSettings(&preferences, profile);
393   return preferences;
394 }
395 
GetWebkitPrefs()396 WebPreferences PrerenderContents::GetWebkitPrefs() {
397   return RenderViewHostDelegateHelper::GetWebkitPrefs(profile_,
398                                                       false);  // is_web_ui
399 }
400 
CreateNewWindow(int route_id,const ViewHostMsg_CreateWindow_Params & params)401 void PrerenderContents::CreateNewWindow(
402     int route_id,
403     const ViewHostMsg_CreateWindow_Params& params) {
404   // Since we don't want to permit child windows that would have a
405   // window.opener property, terminate prerendering.
406   Destroy(FINAL_STATUS_CREATE_NEW_WINDOW);
407 }
408 
CreateNewWidget(int route_id,WebKit::WebPopupType popup_type)409 void PrerenderContents::CreateNewWidget(int route_id,
410                                         WebKit::WebPopupType popup_type) {
411   NOTREACHED();
412 }
413 
CreateNewFullscreenWidget(int route_id)414 void PrerenderContents::CreateNewFullscreenWidget(int route_id) {
415   NOTREACHED();
416 }
417 
ShowCreatedWindow(int route_id,WindowOpenDisposition disposition,const gfx::Rect & initial_pos,bool user_gesture)418 void PrerenderContents::ShowCreatedWindow(int route_id,
419                                           WindowOpenDisposition disposition,
420                                           const gfx::Rect& initial_pos,
421                                           bool user_gesture) {
422   // TODO(tburkard): need to figure out what the correct behavior here is
423   NOTIMPLEMENTED();
424 }
425 
ShowCreatedWidget(int route_id,const gfx::Rect & initial_pos)426 void PrerenderContents::ShowCreatedWidget(int route_id,
427                                           const gfx::Rect& initial_pos) {
428   NOTIMPLEMENTED();
429 }
430 
ShowCreatedFullscreenWidget(int route_id)431 void PrerenderContents::ShowCreatedFullscreenWidget(int route_id) {
432   NOTIMPLEMENTED();
433 }
434 
OnMessageReceived(const IPC::Message & message)435 bool PrerenderContents::OnMessageReceived(const IPC::Message& message) {
436   bool handled = true;
437   bool message_is_ok = true;
438   IPC_BEGIN_MESSAGE_MAP_EX(PrerenderContents, message, message_is_ok)
439     IPC_MESSAGE_HANDLER(ViewHostMsg_DidStartProvisionalLoadForFrame,
440                         OnDidStartProvisionalLoadForFrame)
441     IPC_MESSAGE_HANDLER(IconHostMsg_UpdateFaviconURL, OnUpdateFaviconURL)
442     IPC_MESSAGE_HANDLER(ViewHostMsg_MaybeCancelPrerenderForHTML5Media,
443                         OnMaybeCancelPrerenderForHTML5Media)
444     IPC_MESSAGE_UNHANDLED(handled = false)
445   IPC_END_MESSAGE_MAP_EX()
446 
447   return handled;
448 }
449 
OnDidStartProvisionalLoadForFrame(int64 frame_id,bool is_main_frame,const GURL & url)450 void PrerenderContents::OnDidStartProvisionalLoadForFrame(int64 frame_id,
451                                                           bool is_main_frame,
452                                                           const GURL& url) {
453   if (is_main_frame) {
454     if (!AddAliasURL(url)) {
455       Destroy(FINAL_STATUS_HTTPS);
456       return;
457     }
458 
459     // Usually, this event fires if the user clicks or enters a new URL.
460     // Neither of these can happen in the case of an invisible prerender.
461     // So the cause is: Some JavaScript caused a new URL to be loaded.  In that
462     // case, the spinner would start again in the browser, so we must reset
463     // has_stopped_loading_ so that the spinner won't be stopped.
464     has_stopped_loading_ = false;
465   }
466 }
467 
OnUpdateFaviconURL(int32 page_id,const std::vector<FaviconURL> & urls)468 void PrerenderContents::OnUpdateFaviconURL(
469     int32 page_id,
470     const std::vector<FaviconURL>& urls) {
471   LOG(INFO) << "PrerenderContents::OnUpdateFaviconURL" << icon_url_;
472   for (std::vector<FaviconURL>::const_iterator i = urls.begin();
473        i != urls.end(); ++i) {
474     if (i->icon_type == FaviconURL::FAVICON) {
475       icon_url_ = i->icon_url;
476       LOG(INFO) << icon_url_;
477       return;
478     }
479   }
480 }
481 
OnMaybeCancelPrerenderForHTML5Media()482 void PrerenderContents::OnMaybeCancelPrerenderForHTML5Media() {
483   Destroy(FINAL_STATUS_HTML5_MEDIA);
484 }
485 
AddAliasURL(const GURL & url)486 bool PrerenderContents::AddAliasURL(const GURL& url) {
487   if (!url.SchemeIs("http"))
488     return false;
489   alias_urls_.push_back(url);
490   return true;
491 }
492 
MatchesURL(const GURL & url) const493 bool PrerenderContents::MatchesURL(const GURL& url) const {
494   return std::find(alias_urls_.begin(), alias_urls_.end(), url)
495       != alias_urls_.end();
496 }
497 
DidStopLoading()498 void PrerenderContents::DidStopLoading() {
499   has_stopped_loading_ = true;
500 }
501 
Destroy(FinalStatus final_status)502 void PrerenderContents::Destroy(FinalStatus final_status) {
503   prerender_manager_->RemoveEntry(this);
504   set_final_status(final_status);
505   delete this;
506 }
507 
OnJSOutOfMemory()508 void PrerenderContents::OnJSOutOfMemory() {
509   Destroy(FINAL_STATUS_JS_OUT_OF_MEMORY);
510 }
511 
RendererUnresponsive(RenderViewHost * render_view_host,bool is_during_unload)512 void PrerenderContents::RendererUnresponsive(RenderViewHost* render_view_host,
513                                              bool is_during_unload) {
514   Destroy(FINAL_STATUS_RENDERER_UNRESPONSIVE);
515 }
516 
517 
MaybeGetProcessMetrics()518 base::ProcessMetrics* PrerenderContents::MaybeGetProcessMetrics() {
519   if (process_metrics_.get() == NULL) {
520     // If a PrenderContents hasn't started prerending, don't be fully formed.
521     if (!render_view_host_ || !render_view_host_->process())
522       return NULL;
523     base::ProcessHandle handle = render_view_host_->process()->GetHandle();
524     if (handle == base::kNullProcessHandle)
525       return NULL;
526 #if !defined(OS_MACOSX)
527     process_metrics_.reset(base::ProcessMetrics::CreateProcessMetrics(handle));
528 #else
529     process_metrics_.reset(base::ProcessMetrics::CreateProcessMetrics(
530         handle,
531         MachBroker::GetInstance()));
532 #endif
533   }
534 
535   return process_metrics_.get();
536 }
537 
DestroyWhenUsingTooManyResources()538 void PrerenderContents::DestroyWhenUsingTooManyResources() {
539   base::ProcessMetrics* metrics = MaybeGetProcessMetrics();
540   if (metrics == NULL)
541     return;
542 
543   size_t private_bytes, shared_bytes;
544   if (metrics->GetMemoryBytes(&private_bytes, &shared_bytes)) {
545     if (private_bytes > kMaxPrerenderPrivateMB * 1024 * 1024)
546       Destroy(FINAL_STATUS_MEMORY_LIMIT_EXCEEDED);
547   }
548 }
549 
550 }  // namespace prerender
551