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