// Copyright (c) 2011 The Chromium Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. #include "chrome/browser/prerender/prerender_contents.h" #include "base/process_util.h" #include "base/task.h" #include "base/utf_string_conversions.h" #include "chrome/browser/background_contents_service.h" #include "chrome/browser/browser_process.h" #include "chrome/browser/prerender/prerender_final_status.h" #include "chrome/browser/prerender/prerender_manager.h" #include "chrome/browser/profiles/profile.h" #include "chrome/browser/renderer_preferences_util.h" #include "chrome/browser/ui/login/login_prompt.h" #include "chrome/common/extensions/extension_constants.h" #include "chrome/common/icon_messages.h" #include "chrome/common/render_messages.h" #include "chrome/common/extensions/extension_messages.h" #include "chrome/common/url_constants.h" #include "chrome/common/view_types.h" #include "content/browser/browsing_instance.h" #include "content/browser/renderer_host/render_view_host.h" #include "content/browser/renderer_host/resource_dispatcher_host.h" #include "content/browser/renderer_host/resource_request_details.h" #include "content/browser/site_instance.h" #include "content/common/notification_service.h" #include "content/common/view_messages.h" #include "ui/gfx/rect.h" #if defined(OS_MACOSX) #include "chrome/browser/mach_broker_mac.h" #endif namespace prerender { void AddChildRoutePair(ResourceDispatcherHost* rdh, int child_id, int route_id) { DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO)); rdh->AddPrerenderChildRoutePair(child_id, route_id); } void RemoveChildRoutePair(ResourceDispatcherHost* rdh, int child_id, int route_id) { DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO)); rdh->RemovePrerenderChildRoutePair(child_id, route_id); } class PrerenderContentsFactoryImpl : public PrerenderContents::Factory { public: virtual PrerenderContents* CreatePrerenderContents( PrerenderManager* prerender_manager, Profile* profile, const GURL& url, const std::vector<GURL>& alias_urls, const GURL& referrer) { return new PrerenderContents(prerender_manager, profile, url, alias_urls, referrer); } }; PrerenderContents::PrerenderContents(PrerenderManager* prerender_manager, Profile* profile, const GURL& url, const std::vector<GURL>& alias_urls, const GURL& referrer) : prerender_manager_(prerender_manager), render_view_host_(NULL), prerender_url_(url), referrer_(referrer), profile_(profile), page_id_(0), has_stopped_loading_(false), final_status_(FINAL_STATUS_MAX), prerendering_has_started_(false) { DCHECK(prerender_manager != NULL); if (!AddAliasURL(prerender_url_)) LOG(DFATAL) << "PrerenderContents given invalid URL " << prerender_url_; for (std::vector<GURL>::const_iterator it = alias_urls.begin(); it != alias_urls.end(); ++it) { if (!AddAliasURL(*it)) LOG(DFATAL) << "PrerenderContents given invalid URL " << prerender_url_; } } // static PrerenderContents::Factory* PrerenderContents::CreateFactory() { return new PrerenderContentsFactoryImpl(); } void PrerenderContents::StartPrerendering() { DCHECK(profile_ != NULL); DCHECK(!prerendering_has_started_); prerendering_has_started_ = true; SiteInstance* site_instance = SiteInstance::CreateSiteInstance(profile_); render_view_host_ = new RenderViewHost(site_instance, this, MSG_ROUTING_NONE, NULL); int process_id = render_view_host_->process()->id(); int view_id = render_view_host_->routing_id(); std::pair<int, int> process_view_pair = std::make_pair(process_id, view_id); NotificationService::current()->Notify( NotificationType::PRERENDER_CONTENTS_STARTED, Source<std::pair<int, int> >(&process_view_pair), NotificationService::NoDetails()); // Create the RenderView, so it can receive messages. render_view_host_->CreateRenderView(string16()); // Hide the RVH, so that we will run at a lower CPU priority. // Once the RVH is being swapped into a tab, we will Restore it again. render_view_host_->WasHidden(); // Register this with the ResourceDispatcherHost as a prerender // RenderViewHost. This must be done before the Navigate message to catch all // resource requests, but as it is on the same thread as the Navigate message // (IO) there is no race condition. ResourceDispatcherHost* rdh = g_browser_process->resource_dispatcher_host(); BrowserThread::PostTask(BrowserThread::IO, FROM_HERE, NewRunnableFunction(&AddChildRoutePair, rdh, process_id, view_id)); // Close ourselves when the application is shutting down. registrar_.Add(this, NotificationType::APP_TERMINATING, NotificationService::AllSources()); // Register for our parent profile to shutdown, so we can shut ourselves down // as well (should only be called for OTR profiles, as we should receive // APP_TERMINATING before non-OTR profiles are destroyed). // TODO(tburkard): figure out if this is needed. registrar_.Add(this, NotificationType::PROFILE_DESTROYED, Source<Profile>(profile_)); // Register to cancel if Authentication is required. registrar_.Add(this, NotificationType::AUTH_NEEDED, NotificationService::AllSources()); registrar_.Add(this, NotificationType::AUTH_CANCELLED, NotificationService::AllSources()); // Register all responses to see if we should cancel. registrar_.Add(this, NotificationType::DOWNLOAD_INITIATED, NotificationService::AllSources()); // Register for redirect notifications sourced from |this|. registrar_.Add(this, NotificationType::RESOURCE_RECEIVED_REDIRECT, Source<RenderViewHostDelegate>(this)); DCHECK(load_start_time_.is_null()); load_start_time_ = base::TimeTicks::Now(); ViewMsg_Navigate_Params params; params.page_id = -1; params.pending_history_list_offset = -1; params.current_history_list_offset = -1; params.current_history_list_length = 0; params.url = prerender_url_; params.transition = PageTransition::LINK; params.navigation_type = ViewMsg_Navigate_Type::PRERENDER; params.referrer = referrer_; render_view_host_->Navigate(params); } bool PrerenderContents::GetChildId(int* child_id) const { CHECK(child_id); if (render_view_host_) { *child_id = render_view_host_->process()->id(); return true; } return false; } bool PrerenderContents::GetRouteId(int* route_id) const { CHECK(route_id); if (render_view_host_) { *route_id = render_view_host_->routing_id(); return true; } return false; } void PrerenderContents::set_final_status(FinalStatus final_status) { DCHECK(final_status >= FINAL_STATUS_USED && final_status < FINAL_STATUS_MAX); DCHECK_EQ(FINAL_STATUS_MAX, final_status_); final_status_ = final_status; } FinalStatus PrerenderContents::final_status() const { return final_status_; } PrerenderContents::~PrerenderContents() { DCHECK(final_status_ != FINAL_STATUS_MAX); // If we haven't even started prerendering, we were just in the control // group, which means we do not want to record the status. if (prerendering_has_started()) RecordFinalStatus(final_status_); if (!render_view_host_) // Will be null for unit tests. return; int process_id = render_view_host_->process()->id(); int view_id = render_view_host_->routing_id(); std::pair<int, int> process_view_pair = std::make_pair(process_id, view_id); NotificationService::current()->Notify( NotificationType::PRERENDER_CONTENTS_DESTROYED, Source<std::pair<int, int> >(&process_view_pair), NotificationService::NoDetails()); ResourceDispatcherHost* rdh = g_browser_process->resource_dispatcher_host(); BrowserThread::PostTask(BrowserThread::IO, FROM_HERE, NewRunnableFunction(&RemoveChildRoutePair, rdh, process_id, view_id)); render_view_host_->Shutdown(); // deletes render_view_host } RenderViewHostDelegate::View* PrerenderContents::GetViewDelegate() { return this; } const GURL& PrerenderContents::GetURL() const { return url_; } ViewType::Type PrerenderContents::GetRenderViewType() const { return ViewType::BACKGROUND_CONTENTS; } int PrerenderContents::GetBrowserWindowID() const { return extension_misc::kUnknownWindowId; } void PrerenderContents::DidNavigate( RenderViewHost* render_view_host, const ViewHostMsg_FrameNavigate_Params& params) { // We only care when the outer frame changes. if (!PageTransition::IsMainFrame(params.transition)) return; // Store the navigation params. ViewHostMsg_FrameNavigate_Params* p = new ViewHostMsg_FrameNavigate_Params(); *p = params; navigate_params_.reset(p); if (!AddAliasURL(params.url)) { Destroy(FINAL_STATUS_HTTPS); return; } url_ = params.url; } void PrerenderContents::UpdateTitle(RenderViewHost* render_view_host, int32 page_id, const std::wstring& title) { if (title.empty()) { return; } title_ = WideToUTF16Hack(title); page_id_ = page_id; } void PrerenderContents::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) { // Always suppress JavaScript messages if they're triggered by a page being // prerendered. *did_suppress_message = true; // We still want to show the user the message when they navigate to this // page, so cancel this prerender. Destroy(FINAL_STATUS_JAVASCRIPT_ALERT); } bool PrerenderContents::PreHandleKeyboardEvent( const NativeWebKeyboardEvent& event, bool* is_keyboard_shortcut) { return false; } void PrerenderContents::Observe(NotificationType type, const NotificationSource& source, const NotificationDetails& details) { switch (type.value) { case NotificationType::PROFILE_DESTROYED: Destroy(FINAL_STATUS_PROFILE_DESTROYED); return; case NotificationType::APP_TERMINATING: Destroy(FINAL_STATUS_APP_TERMINATING); return; case NotificationType::AUTH_NEEDED: case NotificationType::AUTH_CANCELLED: { // Prerendered pages have a NULL controller and the login handler should // be referencing us as the render view host delegate. NavigationController* controller = Source<NavigationController>(source).ptr(); LoginNotificationDetails* details_ptr = Details<LoginNotificationDetails>(details).ptr(); LoginHandler* handler = details_ptr->handler(); DCHECK(handler != NULL); RenderViewHostDelegate* delegate = handler->GetRenderViewHostDelegate(); if (controller == NULL && delegate == this) { Destroy(FINAL_STATUS_AUTH_NEEDED); return; } break; } case NotificationType::DOWNLOAD_INITIATED: { // If the download is started from a RenderViewHost that we are // delegating, kill the prerender. This cancels any pending requests // though the download never actually started thanks to the // DownloadRequestLimiter. DCHECK(NotificationService::NoDetails() == details); RenderViewHost* rvh = Source<RenderViewHost>(source).ptr(); CHECK(rvh != NULL); if (rvh->delegate() == this) { Destroy(FINAL_STATUS_DOWNLOAD); return; } break; } case NotificationType::RESOURCE_RECEIVED_REDIRECT: { // RESOURCE_RECEIVED_REDIRECT can come for any resource on a page. // If it's a redirect on the top-level resource, the name needs // to be remembered for future matching, and if it redirects to // an https resource, it needs to be canceled. If a subresource // is redirected, nothing changes. DCHECK(Source<RenderViewHostDelegate>(source).ptr() == this); ResourceRedirectDetails* resource_redirect_details = Details<ResourceRedirectDetails>(details).ptr(); CHECK(resource_redirect_details); if (resource_redirect_details->resource_type() == ResourceType::MAIN_FRAME) { if (!AddAliasURL(resource_redirect_details->new_url())) Destroy(FINAL_STATUS_HTTPS); } break; } default: NOTREACHED() << "Unexpected notification sent."; break; } } void PrerenderContents::OnMessageBoxClosed(IPC::Message* reply_msg, bool success, const std::wstring& prompt) { render_view_host_->JavaScriptMessageBoxClosed(reply_msg, success, prompt); } gfx::NativeWindow PrerenderContents::GetMessageBoxRootWindow() { NOTIMPLEMENTED(); return NULL; } TabContents* PrerenderContents::AsTabContents() { return NULL; } ExtensionHost* PrerenderContents::AsExtensionHost() { return NULL; } void PrerenderContents::UpdateInspectorSetting(const std::string& key, const std::string& value) { RenderViewHostDelegateHelper::UpdateInspectorSetting(profile_, key, value); } void PrerenderContents::ClearInspectorSettings() { RenderViewHostDelegateHelper::ClearInspectorSettings(profile_); } void PrerenderContents::Close(RenderViewHost* render_view_host) { Destroy(FINAL_STATUS_CLOSED); } RendererPreferences PrerenderContents::GetRendererPrefs( Profile* profile) const { RendererPreferences preferences; renderer_preferences_util::UpdateFromSystemSettings(&preferences, profile); return preferences; } WebPreferences PrerenderContents::GetWebkitPrefs() { return RenderViewHostDelegateHelper::GetWebkitPrefs(profile_, false); // is_web_ui } void PrerenderContents::CreateNewWindow( int route_id, const ViewHostMsg_CreateWindow_Params& params) { // Since we don't want to permit child windows that would have a // window.opener property, terminate prerendering. Destroy(FINAL_STATUS_CREATE_NEW_WINDOW); } void PrerenderContents::CreateNewWidget(int route_id, WebKit::WebPopupType popup_type) { NOTREACHED(); } void PrerenderContents::CreateNewFullscreenWidget(int route_id) { NOTREACHED(); } void PrerenderContents::ShowCreatedWindow(int route_id, WindowOpenDisposition disposition, const gfx::Rect& initial_pos, bool user_gesture) { // TODO(tburkard): need to figure out what the correct behavior here is NOTIMPLEMENTED(); } void PrerenderContents::ShowCreatedWidget(int route_id, const gfx::Rect& initial_pos) { NOTIMPLEMENTED(); } void PrerenderContents::ShowCreatedFullscreenWidget(int route_id) { NOTIMPLEMENTED(); } bool PrerenderContents::OnMessageReceived(const IPC::Message& message) { bool handled = true; bool message_is_ok = true; IPC_BEGIN_MESSAGE_MAP_EX(PrerenderContents, message, message_is_ok) IPC_MESSAGE_HANDLER(ViewHostMsg_DidStartProvisionalLoadForFrame, OnDidStartProvisionalLoadForFrame) IPC_MESSAGE_HANDLER(IconHostMsg_UpdateFaviconURL, OnUpdateFaviconURL) IPC_MESSAGE_HANDLER(ViewHostMsg_MaybeCancelPrerenderForHTML5Media, OnMaybeCancelPrerenderForHTML5Media) IPC_MESSAGE_UNHANDLED(handled = false) IPC_END_MESSAGE_MAP_EX() return handled; } void PrerenderContents::OnDidStartProvisionalLoadForFrame(int64 frame_id, bool is_main_frame, const GURL& url) { if (is_main_frame) { if (!AddAliasURL(url)) { Destroy(FINAL_STATUS_HTTPS); return; } // Usually, this event fires if the user clicks or enters a new URL. // Neither of these can happen in the case of an invisible prerender. // So the cause is: Some JavaScript caused a new URL to be loaded. In that // case, the spinner would start again in the browser, so we must reset // has_stopped_loading_ so that the spinner won't be stopped. has_stopped_loading_ = false; } } void PrerenderContents::OnUpdateFaviconURL( int32 page_id, const std::vector<FaviconURL>& urls) { LOG(INFO) << "PrerenderContents::OnUpdateFaviconURL" << icon_url_; for (std::vector<FaviconURL>::const_iterator i = urls.begin(); i != urls.end(); ++i) { if (i->icon_type == FaviconURL::FAVICON) { icon_url_ = i->icon_url; LOG(INFO) << icon_url_; return; } } } void PrerenderContents::OnMaybeCancelPrerenderForHTML5Media() { Destroy(FINAL_STATUS_HTML5_MEDIA); } bool PrerenderContents::AddAliasURL(const GURL& url) { if (!url.SchemeIs("http")) return false; alias_urls_.push_back(url); return true; } bool PrerenderContents::MatchesURL(const GURL& url) const { return std::find(alias_urls_.begin(), alias_urls_.end(), url) != alias_urls_.end(); } void PrerenderContents::DidStopLoading() { has_stopped_loading_ = true; } void PrerenderContents::Destroy(FinalStatus final_status) { prerender_manager_->RemoveEntry(this); set_final_status(final_status); delete this; } void PrerenderContents::OnJSOutOfMemory() { Destroy(FINAL_STATUS_JS_OUT_OF_MEMORY); } void PrerenderContents::RendererUnresponsive(RenderViewHost* render_view_host, bool is_during_unload) { Destroy(FINAL_STATUS_RENDERER_UNRESPONSIVE); } base::ProcessMetrics* PrerenderContents::MaybeGetProcessMetrics() { if (process_metrics_.get() == NULL) { // If a PrenderContents hasn't started prerending, don't be fully formed. if (!render_view_host_ || !render_view_host_->process()) return NULL; base::ProcessHandle handle = render_view_host_->process()->GetHandle(); if (handle == base::kNullProcessHandle) return NULL; #if !defined(OS_MACOSX) process_metrics_.reset(base::ProcessMetrics::CreateProcessMetrics(handle)); #else process_metrics_.reset(base::ProcessMetrics::CreateProcessMetrics( handle, MachBroker::GetInstance())); #endif } return process_metrics_.get(); } void PrerenderContents::DestroyWhenUsingTooManyResources() { base::ProcessMetrics* metrics = MaybeGetProcessMetrics(); if (metrics == NULL) return; size_t private_bytes, shared_bytes; if (metrics->GetMemoryBytes(&private_bytes, &shared_bytes)) { if (private_bytes > kMaxPrerenderPrivateMB * 1024 * 1024) Destroy(FINAL_STATUS_MEMORY_LIMIT_EXCEEDED); } } } // namespace prerender