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 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 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: 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 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 86 PrerenderContents::Factory* PrerenderContents::CreateFactory() { 87 return new PrerenderContentsFactoryImpl(); 88 } 89 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 165 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 174 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 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 190 FinalStatus PrerenderContents::final_status() const { 191 return final_status_; 192 } 193 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 220 RenderViewHostDelegate::View* PrerenderContents::GetViewDelegate() { 221 return this; 222 } 223 224 const GURL& PrerenderContents::GetURL() const { 225 return url_; 226 } 227 228 ViewType::Type PrerenderContents::GetRenderViewType() const { 229 return ViewType::BACKGROUND_CONTENTS; 230 } 231 232 int PrerenderContents::GetBrowserWindowID() const { 233 return extension_misc::kUnknownWindowId; 234 } 235 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 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 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 282 bool PrerenderContents::PreHandleKeyboardEvent( 283 const NativeWebKeyboardEvent& event, 284 bool* is_keyboard_shortcut) { 285 return false; 286 } 287 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 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 363 gfx::NativeWindow PrerenderContents::GetMessageBoxRootWindow() { 364 NOTIMPLEMENTED(); 365 return NULL; 366 } 367 368 TabContents* PrerenderContents::AsTabContents() { 369 return NULL; 370 } 371 372 ExtensionHost* PrerenderContents::AsExtensionHost() { 373 return NULL; 374 } 375 376 void PrerenderContents::UpdateInspectorSetting(const std::string& key, 377 const std::string& value) { 378 RenderViewHostDelegateHelper::UpdateInspectorSetting(profile_, key, value); 379 } 380 381 void PrerenderContents::ClearInspectorSettings() { 382 RenderViewHostDelegateHelper::ClearInspectorSettings(profile_); 383 } 384 385 void PrerenderContents::Close(RenderViewHost* render_view_host) { 386 Destroy(FINAL_STATUS_CLOSED); 387 } 388 389 RendererPreferences PrerenderContents::GetRendererPrefs( 390 Profile* profile) const { 391 RendererPreferences preferences; 392 renderer_preferences_util::UpdateFromSystemSettings(&preferences, profile); 393 return preferences; 394 } 395 396 WebPreferences PrerenderContents::GetWebkitPrefs() { 397 return RenderViewHostDelegateHelper::GetWebkitPrefs(profile_, 398 false); // is_web_ui 399 } 400 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 409 void PrerenderContents::CreateNewWidget(int route_id, 410 WebKit::WebPopupType popup_type) { 411 NOTREACHED(); 412 } 413 414 void PrerenderContents::CreateNewFullscreenWidget(int route_id) { 415 NOTREACHED(); 416 } 417 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 426 void PrerenderContents::ShowCreatedWidget(int route_id, 427 const gfx::Rect& initial_pos) { 428 NOTIMPLEMENTED(); 429 } 430 431 void PrerenderContents::ShowCreatedFullscreenWidget(int route_id) { 432 NOTIMPLEMENTED(); 433 } 434 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 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 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 482 void PrerenderContents::OnMaybeCancelPrerenderForHTML5Media() { 483 Destroy(FINAL_STATUS_HTML5_MEDIA); 484 } 485 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 493 bool PrerenderContents::MatchesURL(const GURL& url) const { 494 return std::find(alias_urls_.begin(), alias_urls_.end(), url) 495 != alias_urls_.end(); 496 } 497 498 void PrerenderContents::DidStopLoading() { 499 has_stopped_loading_ = true; 500 } 501 502 void PrerenderContents::Destroy(FinalStatus final_status) { 503 prerender_manager_->RemoveEntry(this); 504 set_final_status(final_status); 505 delete this; 506 } 507 508 void PrerenderContents::OnJSOutOfMemory() { 509 Destroy(FINAL_STATUS_JS_OUT_OF_MEMORY); 510 } 511 512 void PrerenderContents::RendererUnresponsive(RenderViewHost* render_view_host, 513 bool is_during_unload) { 514 Destroy(FINAL_STATUS_RENDERER_UNRESPONSIVE); 515 } 516 517 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 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