blob: 82d05658afb54590ed828746a0b6399498dc11f3 [file] [log] [blame]
// Copyright (c) 2013 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 "content/browser/browser_plugin/browser_plugin_guest_manager.h"
#include "content/browser/browser_plugin/browser_plugin_guest.h"
#include "content/browser/browser_plugin/browser_plugin_host_factory.h"
#include "content/browser/renderer_host/render_view_host_impl.h"
#include "content/browser/web_contents/web_contents_impl.h"
#include "content/common/browser_plugin/browser_plugin_constants.h"
#include "content/common/browser_plugin/browser_plugin_messages.h"
#include "content/common/content_export.h"
#include "content/public/browser/user_metrics.h"
#include "content/public/common/result_codes.h"
#include "content/public/common/url_constants.h"
#include "content/public/common/url_utils.h"
#include "net/base/escape.h"
#include "ui/events/keycodes/keyboard_codes.h"
namespace content {
// static
BrowserPluginHostFactory* BrowserPluginGuestManager::factory_ = NULL;
BrowserPluginGuestManager::BrowserPluginGuestManager()
: next_instance_id_(browser_plugin::kInstanceIDNone) {
}
BrowserPluginGuestManager::~BrowserPluginGuestManager() {
}
// static
BrowserPluginGuestManager* BrowserPluginGuestManager::Create() {
if (factory_)
return factory_->CreateBrowserPluginGuestManager();
return new BrowserPluginGuestManager();
}
BrowserPluginGuest* BrowserPluginGuestManager::CreateGuest(
SiteInstance* embedder_site_instance,
int instance_id,
const BrowserPluginHostMsg_Attach_Params& params,
scoped_ptr<base::DictionaryValue> extra_params) {
RenderProcessHost* embedder_process_host =
embedder_site_instance->GetProcess();
// Validate that the partition id coming from the renderer is valid UTF-8,
// since we depend on this in other parts of the code, such as FilePath
// creation. If the validation fails, treat it as a bad message and kill the
// renderer process.
if (!IsStringUTF8(params.storage_partition_id)) {
content::RecordAction(UserMetricsAction("BadMessageTerminate_BPGM"));
base::KillProcess(
embedder_process_host->GetHandle(),
content::RESULT_CODE_KILLED_BAD_MESSAGE, false);
return NULL;
}
// We usually require BrowserPlugins to be hosted by a storage isolated
// extension. We treat WebUI pages as a special case if they host the
// BrowserPlugin in a component extension iframe. In that case, we use the
// iframe's URL to determine the extension.
const GURL& embedder_site_url = embedder_site_instance->GetSiteURL();
GURL validated_frame_url(params.embedder_frame_url);
RenderViewHost::FilterURL(
embedder_process_host, false, &validated_frame_url);
const std::string& host = content::HasWebUIScheme(embedder_site_url) ?
validated_frame_url.host() : embedder_site_url.host();
std::string url_encoded_partition = net::EscapeQueryParamValue(
params.storage_partition_id, false);
// The SiteInstance of a given webview tag is based on the fact that it's
// a guest process in addition to which platform application the tag
// belongs to and what storage partition is in use, rather than the URL
// that the tag is being navigated to.
GURL guest_site(base::StringPrintf("%s://%s/%s?%s",
kGuestScheme,
host.c_str(),
params.persist_storage ? "persist" : "",
url_encoded_partition.c_str()));
// If we already have a webview tag in the same app using the same storage
// partition, we should use the same SiteInstance so the existing tag and
// the new tag can script each other.
SiteInstance* guest_site_instance = GetGuestSiteInstance(guest_site);
if (!guest_site_instance) {
// Create the SiteInstance in a new BrowsingInstance, which will ensure
// that webview tags are also not allowed to send messages across
// different partitions.
guest_site_instance = SiteInstance::CreateForURL(
embedder_site_instance->GetBrowserContext(), guest_site);
}
return WebContentsImpl::CreateGuest(
embedder_site_instance->GetBrowserContext(),
guest_site_instance,
instance_id,
extra_params.Pass());
}
BrowserPluginGuest* BrowserPluginGuestManager::GetGuestByInstanceID(
int instance_id,
int embedder_render_process_id) const {
if (!CanEmbedderAccessInstanceIDMaybeKill(embedder_render_process_id,
instance_id)) {
return NULL;
}
GuestInstanceMap::const_iterator it =
guest_web_contents_by_instance_id_.find(instance_id);
if (it == guest_web_contents_by_instance_id_.end())
return NULL;
return static_cast<WebContentsImpl*>(it->second)->GetBrowserPluginGuest();
}
void BrowserPluginGuestManager::AddGuest(int instance_id,
WebContentsImpl* guest_web_contents) {
DCHECK(guest_web_contents_by_instance_id_.find(instance_id) ==
guest_web_contents_by_instance_id_.end());
guest_web_contents_by_instance_id_[instance_id] = guest_web_contents;
}
void BrowserPluginGuestManager::RemoveGuest(int instance_id) {
DCHECK(guest_web_contents_by_instance_id_.find(instance_id) !=
guest_web_contents_by_instance_id_.end());
guest_web_contents_by_instance_id_.erase(instance_id);
}
bool BrowserPluginGuestManager::CanEmbedderAccessInstanceIDMaybeKill(
int embedder_render_process_id,
int instance_id) const {
if (!CanEmbedderAccessInstanceID(embedder_render_process_id, instance_id)) {
// The embedder process is trying to access a guest it does not own.
content::RecordAction(UserMetricsAction("BadMessageTerminate_BPGM"));
base::KillProcess(
RenderProcessHost::FromID(embedder_render_process_id)->GetHandle(),
content::RESULT_CODE_KILLED_BAD_MESSAGE, false);
return false;
}
return true;
}
void BrowserPluginGuestManager::OnMessageReceived(const IPC::Message& message,
int render_process_id) {
if (BrowserPluginGuest::ShouldForwardToBrowserPluginGuest(message)) {
int instance_id = 0;
// All allowed messages must have instance_id as their first parameter.
PickleIterator iter(message);
bool success = iter.ReadInt(&instance_id);
DCHECK(success);
BrowserPluginGuest* guest =
GetGuestByInstanceID(instance_id, render_process_id);
if (guest && guest->OnMessageReceivedFromEmbedder(message))
return;
}
IPC_BEGIN_MESSAGE_MAP(BrowserPluginGuestManager, message)
IPC_MESSAGE_HANDLER(BrowserPluginHostMsg_BuffersSwappedACK,
OnUnhandledSwapBuffersACK)
IPC_END_MESSAGE_MAP()
}
// static
bool BrowserPluginGuestManager::CanEmbedderAccessGuest(
int embedder_render_process_id,
BrowserPluginGuest* guest) {
// The embedder can access the guest if it has not been attached and its
// opener's embedder lives in the same process as the given embedder.
if (!guest->attached()) {
if (!guest->opener())
return false;
return embedder_render_process_id ==
guest->opener()->embedder_web_contents()->GetRenderProcessHost()->
GetID();
}
return embedder_render_process_id ==
guest->embedder_web_contents()->GetRenderProcessHost()->GetID();
}
bool BrowserPluginGuestManager::CanEmbedderAccessInstanceID(
int embedder_render_process_id,
int instance_id) const {
// The embedder is trying to access a guest with a negative or zero
// instance ID.
if (instance_id <= browser_plugin::kInstanceIDNone)
return false;
// The embedder is trying to access an instance ID that has not yet been
// allocated by BrowserPluginGuestManager. This could cause instance ID
// collisions in the future, and potentially give one embedder access to a
// guest it does not own.
if (instance_id > next_instance_id_)
return false;
GuestInstanceMap::const_iterator it =
guest_web_contents_by_instance_id_.find(instance_id);
if (it == guest_web_contents_by_instance_id_.end())
return true;
BrowserPluginGuest* guest =
static_cast<WebContentsImpl*>(it->second)->GetBrowserPluginGuest();
return CanEmbedderAccessGuest(embedder_render_process_id, guest);
}
SiteInstance* BrowserPluginGuestManager::GetGuestSiteInstance(
const GURL& guest_site) {
for (GuestInstanceMap::const_iterator it =
guest_web_contents_by_instance_id_.begin();
it != guest_web_contents_by_instance_id_.end(); ++it) {
if (it->second->GetSiteInstance()->GetSiteURL() == guest_site)
return it->second->GetSiteInstance();
}
return NULL;
}
// We only get here during teardown if we have one last buffer pending,
// otherwise the ACK is handled by the guest.
void BrowserPluginGuestManager::OnUnhandledSwapBuffersACK(
int instance_id,
int route_id,
int gpu_host_id,
const std::string& mailbox_name,
uint32 sync_point) {
BrowserPluginGuest::AcknowledgeBufferPresent(route_id,
gpu_host_id,
mailbox_name,
sync_point);
}
void BrowserPluginGuestManager::DidSendScreenRects(
WebContentsImpl* embedder_web_contents) {
// TODO(lazyboy): Generalize iterating over guest instances and performing
// actions on the guests.
for (GuestInstanceMap::iterator it =
guest_web_contents_by_instance_id_.begin();
it != guest_web_contents_by_instance_id_.end(); ++it) {
BrowserPluginGuest* guest = it->second->GetBrowserPluginGuest();
if (embedder_web_contents == guest->embedder_web_contents()) {
static_cast<RenderViewHostImpl*>(
guest->GetWebContents()->GetRenderViewHost())->SendScreenRects();
}
}
}
bool BrowserPluginGuestManager::UnlockMouseIfNecessary(
WebContentsImpl* embedder_web_contents,
const NativeWebKeyboardEvent& event) {
if ((event.type != blink::WebInputEvent::RawKeyDown) ||
(event.windowsKeyCode != ui::VKEY_ESCAPE) ||
(event.modifiers & blink::WebInputEvent::InputModifiers)) {
return false;
}
// TODO(lazyboy): Generalize iterating over guest instances and performing
// actions on the guests.
for (GuestInstanceMap::iterator it =
guest_web_contents_by_instance_id_.begin();
it != guest_web_contents_by_instance_id_.end(); ++it) {
BrowserPluginGuest* guest = it->second->GetBrowserPluginGuest();
if (embedder_web_contents == guest->embedder_web_contents()) {
if (guest->UnlockMouseIfNecessary(event))
return true;
}
}
return false;
}
} // namespace content