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