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