| // 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 |