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: 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: 86 explicit WebContentsDelegateImpl(PrerenderContents* prerender_contents) 87 : prerender_contents_(prerender_contents) { 88 } 89 90 // content::WebContentsDelegate implementation: 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 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 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 123 virtual void JSOutOfMemory(WebContents* tab) OVERRIDE { 124 prerender_contents_->Destroy(FINAL_STATUS_JS_OUT_OF_MEMORY); 125 } 126 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 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 146 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 157 void PrerenderContents::Observer::OnPrerenderStopLoading( 158 PrerenderContents* contents) { 159 } 160 161 void PrerenderContents::Observer::OnPrerenderCreatedMatchCompleteReplacement( 162 PrerenderContents* contents, PrerenderContents* replacement) { 163 } 164 165 PrerenderContents::Observer::Observer() { 166 } 167 168 PrerenderContents::Observer::~Observer() { 169 } 170 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 184 PrerenderContents::PendingPrerenderInfo::~PendingPrerenderInfo() { 185 } 186 187 void PrerenderContents::AddPendingPrerender( 188 scoped_ptr<PendingPrerenderInfo> pending_prerender_info) { 189 pending_prerenders_.push_back(pending_prerender_info.release()); 190 } 191 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 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 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 260 bool PrerenderContents::Init() { 261 return AddAliasURL(prerender_url_); 262 } 263 264 // static 265 PrerenderContents::Factory* PrerenderContents::CreateFactory() { 266 return new PrerenderContentsFactoryImpl(); 267 } 268 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 366 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 373 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 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 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 420 void PrerenderContents::AddObserver(Observer* observer) { 421 DCHECK_EQ(FINAL_STATUS_MAX, final_status_); 422 observer_list_.AddObserver(observer); 423 } 424 425 void PrerenderContents::RemoveObserver(Observer* observer) { 426 observer_list_.RemoveObserver(observer); 427 } 428 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 467 void PrerenderContents::OnRenderViewHostCreated( 468 RenderViewHost* new_render_view_host) { 469 } 470 471 size_t PrerenderContents::pending_prerender_count() const { 472 return pending_prerenders_.size(); 473 } 474 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 485 void PrerenderContents::NotifyPrerenderStart() { 486 DCHECK_EQ(FINAL_STATUS_MAX, final_status_); 487 FOR_EACH_OBSERVER(Observer, observer_list_, OnPrerenderStart(this)); 488 } 489 490 void PrerenderContents::NotifyPrerenderStopLoading() { 491 FOR_EACH_OBSERVER(Observer, observer_list_, OnPrerenderStopLoading(this)); 492 } 493 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 500 void PrerenderContents::NotifyPrerenderCreatedMatchCompleteReplacement( 501 PrerenderContents* replacement) { 502 FOR_EACH_OBSERVER(Observer, observer_list_, 503 OnPrerenderCreatedMatchCompleteReplacement(this, 504 replacement)); 505 } 506 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 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 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 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 571 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 582 void PrerenderContents::RenderProcessGone(base::TerminationStatus status) { 583 Destroy(FINAL_STATUS_RENDERER_CRASHED); 584 } 585 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 597 void PrerenderContents::RenderFrameDeleted( 598 content::RenderFrameHost* render_frame_host) { 599 render_frame_hosts_.erase(render_frame_host); 600 } 601 602 void PrerenderContents::DidStopLoading( 603 content::RenderViewHost* render_view_host) { 604 has_stopped_loading_ = true; 605 NotifyPrerenderStopLoading(); 606 } 607 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 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 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 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 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 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 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 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 756 RenderViewHost* PrerenderContents::GetRenderViewHostMutable() { 757 return const_cast<RenderViewHost*>(GetRenderViewHost()); 758 } 759 760 const RenderViewHost* PrerenderContents::GetRenderViewHost() const { 761 if (!prerender_contents_.get()) 762 return NULL; 763 return prerender_contents_->GetRenderViewHost(); 764 } 765 766 void PrerenderContents::DidNavigate( 767 const history::HistoryAddPageArgs& add_page_args) { 768 add_page_vector_.push_back(add_page_args); 769 } 770 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 777 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 790 bool PrerenderContents::IsCrossSiteNavigationPending() const { 791 if (!prerender_contents_) 792 return false; 793 return (prerender_contents_->GetSiteInstance() != 794 prerender_contents_->GetPendingSiteInstance()); 795 } 796 797 SessionStorageNamespace* PrerenderContents::GetSessionStorageNamespace() const { 798 if (!prerender_contents()) 799 return NULL; 800 return prerender_contents()->GetController(). 801 GetDefaultSessionStorageNamespace(); 802 } 803 804 void PrerenderContents::OnCancelPrerenderForPrinting() { 805 Destroy(FINAL_STATUS_WINDOW_PRINT); 806 } 807 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