blob: d2f06ef54f64b51101db1a98f9ab0aafffc8b0aa [file] [log] [blame]
// 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/renderer/extensions/extension_dispatcher.h"
#include "base/command_line.h"
#include "chrome/common/child_process_logging.h"
#include "chrome/common/chrome_switches.h"
#include "chrome/common/extensions/extension.h"
#include "chrome/common/extensions/extension_messages.h"
#include "chrome/common/extensions/extension_permission_set.h"
#include "chrome/common/url_constants.h"
#include "chrome/renderer/extensions/chrome_app_bindings.h"
#include "chrome/renderer/extensions/chrome_webstore_bindings.h"
#include "chrome/renderer/extensions/event_bindings.h"
#include "chrome/renderer/extensions/extension_groups.h"
#include "chrome/renderer/extensions/extension_process_bindings.h"
#include "chrome/renderer/extensions/js_only_v8_extensions.h"
#include "chrome/renderer/extensions/renderer_extension_bindings.h"
#include "chrome/renderer/extensions/user_script_slave.h"
#include "content/renderer/render_thread.h"
#include "third_party/WebKit/Source/WebKit/chromium/public/WebDataSource.h"
#include "third_party/WebKit/Source/WebKit/chromium/public/WebFrame.h"
#include "third_party/WebKit/Source/WebKit/chromium/public/WebSecurityPolicy.h"
#include "third_party/WebKit/Source/WebKit/chromium/public/WebString.h"
#include "third_party/WebKit/Source/WebKit/chromium/public/WebURLRequest.h"
#include "v8/include/v8.h"
namespace {
static const double kInitialExtensionIdleHandlerDelayS = 5.0 /* seconds */;
static const int64 kMaxExtensionIdleHandlerDelayS = 5*60 /* seconds */;
}
using WebKit::WebDataSource;
using WebKit::WebFrame;
using WebKit::WebSecurityPolicy;
using WebKit::WebString;
ExtensionDispatcher::ExtensionDispatcher()
: is_webkit_initialized_(false) {
std::string type_str = CommandLine::ForCurrentProcess()->GetSwitchValueASCII(
switches::kProcessType);
is_extension_process_ = type_str == switches::kExtensionProcess ||
CommandLine::ForCurrentProcess()->HasSwitch(switches::kSingleProcess);
if (is_extension_process_) {
RenderThread::current()->set_idle_notification_delay_in_s(
kInitialExtensionIdleHandlerDelayS);
}
user_script_slave_.reset(new UserScriptSlave(&extensions_));
}
ExtensionDispatcher::~ExtensionDispatcher() {
}
bool ExtensionDispatcher::OnControlMessageReceived(
const IPC::Message& message) {
bool handled = true;
IPC_BEGIN_MESSAGE_MAP(ExtensionDispatcher, message)
IPC_MESSAGE_HANDLER(ExtensionMsg_MessageInvoke, OnMessageInvoke)
IPC_MESSAGE_HANDLER(ExtensionMsg_SetFunctionNames, OnSetFunctionNames)
IPC_MESSAGE_HANDLER(ExtensionMsg_Loaded, OnLoaded)
IPC_MESSAGE_HANDLER(ExtensionMsg_Unloaded, OnUnloaded)
IPC_MESSAGE_HANDLER(ExtensionMsg_SetScriptingWhitelist,
OnSetScriptingWhitelist)
IPC_MESSAGE_HANDLER(ExtensionMsg_ActivateExtension, OnActivateExtension)
IPC_MESSAGE_HANDLER(ExtensionMsg_ActivateApplication, OnActivateApplication)
IPC_MESSAGE_HANDLER(ExtensionMsg_UpdatePermissions, OnUpdatePermissions)
IPC_MESSAGE_HANDLER(ExtensionMsg_UpdateUserScripts, OnUpdateUserScripts)
IPC_MESSAGE_UNHANDLED(handled = false)
IPC_END_MESSAGE_MAP()
return handled;
}
void ExtensionDispatcher::WebKitInitialized() {
// For extensions, we want to ensure we call the IdleHandler every so often,
// even if the extension keeps up activity.
if (is_extension_process_) {
forced_idle_timer_.Start(FROM_HERE,
base::TimeDelta::FromSeconds(kMaxExtensionIdleHandlerDelayS),
RenderThread::current(), &RenderThread::IdleHandler);
}
RegisterExtension(extensions_v8::ChromeAppExtension::Get(this), false);
if (CommandLine::ForCurrentProcess()->HasSwitch(
switches::kEnableInlineWebstoreInstall)) {
RegisterExtension(extensions_v8::ChromeWebstoreExtension::Get(), false);
}
// Add v8 extensions related to chrome extensions.
RegisterExtension(ExtensionProcessBindings::Get(this), true);
RegisterExtension(JsonSchemaJsV8Extension::Get(), true);
RegisterExtension(EventBindings::Get(this), true);
RegisterExtension(RendererExtensionBindings::Get(this), true);
RegisterExtension(ExtensionApiTestV8Extension::Get(), true);
// Initialize host permissions for any extensions that were activated before
// WebKit was initialized.
for (std::set<std::string>::iterator iter = active_extension_ids_.begin();
iter != active_extension_ids_.end(); ++iter) {
const Extension* extension = extensions_.GetByID(*iter);
if (extension)
InitOriginPermissions(extension);
}
is_webkit_initialized_ = true;
}
void ExtensionDispatcher::IdleNotification() {
if (is_extension_process_) {
// Dampen the forced delay as well if the extension stays idle for long
// periods of time.
int64 forced_delay_s = std::max(static_cast<int64>(
RenderThread::current()->idle_notification_delay_in_s()),
kMaxExtensionIdleHandlerDelayS);
forced_idle_timer_.Stop();
forced_idle_timer_.Start(FROM_HERE,
base::TimeDelta::FromSeconds(forced_delay_s),
RenderThread::current(), &RenderThread::IdleHandler);
}
}
void ExtensionDispatcher::OnSetFunctionNames(
const std::vector<std::string>& names) {
function_names_.clear();
for (size_t i = 0; i < names.size(); ++i)
function_names_.insert(names[i]);
}
void ExtensionDispatcher::OnMessageInvoke(const std::string& extension_id,
const std::string& function_name,
const ListValue& args,
const GURL& event_url) {
EventBindings::CallFunction(
extension_id, function_name, args, NULL, event_url);
// Reset the idle handler each time there's any activity like event or message
// dispatch, for which Invoke is the chokepoint.
if (is_extension_process_) {
RenderThread::current()->ScheduleIdleHandler(
kInitialExtensionIdleHandlerDelayS);
}
}
void ExtensionDispatcher::OnLoaded(const ExtensionMsg_Loaded_Params& params) {
scoped_refptr<const Extension> extension(params.ConvertToExtension());
if (!extension) {
// This can happen if extension parsing fails for any reason. One reason
// this can legitimately happen is if the
// --enable-experimental-extension-apis changes at runtime, which happens
// during browser tests. Existing renderers won't know about the change.
return;
}
extensions_.Insert(extension);
}
void ExtensionDispatcher::OnUnloaded(const std::string& id) {
extensions_.Remove(id);
// If the extension is later reloaded with a different set of permissions,
// we'd like it to get a new isolated world ID, so that it can pick up the
// changed origin whitelist.
user_script_slave_->RemoveIsolatedWorld(id);
}
void ExtensionDispatcher::OnSetScriptingWhitelist(
const Extension::ScriptingWhitelist& extension_ids) {
Extension::SetScriptingWhitelist(extension_ids);
}
bool ExtensionDispatcher::IsApplicationActive(
const std::string& extension_id) const {
return active_application_ids_.find(extension_id) !=
active_application_ids_.end();
}
bool ExtensionDispatcher::IsExtensionActive(
const std::string& extension_id) const {
return active_extension_ids_.find(extension_id) !=
active_extension_ids_.end();
}
bool ExtensionDispatcher::AllowScriptExtension(
WebFrame* frame,
const std::string& v8_extension_name,
int extension_group) {
// NULL in unit tests.
if (!RenderThread::current())
return true;
// If we don't know about it, it was added by WebCore, so we should allow it.
if (!RenderThread::current()->IsRegisteredExtension(v8_extension_name))
return true;
// If the V8 extension is not restricted, allow it to run anywhere.
if (!restricted_v8_extensions_.count(v8_extension_name))
return true;
// Note: we prefer the provisional URL here instead of the document URL
// because we might be currently loading an URL into a blank page.
// See http://code.google.com/p/chromium/issues/detail?id=10924
WebDataSource* ds = frame->provisionalDataSource();
if (!ds)
ds = frame->dataSource();
// Extension-only bindings should be restricted to content scripts and
// extension-blessed URLs.
if (extension_group == EXTENSION_GROUP_CONTENT_SCRIPTS ||
extensions_.ExtensionBindingsAllowed(ds->request().url())) {
return true;
}
return false;
}
void ExtensionDispatcher::OnActivateApplication(
const std::string& extension_id) {
active_application_ids_.insert(extension_id);
}
void ExtensionDispatcher::OnActivateExtension(
const std::string& extension_id) {
active_extension_ids_.insert(extension_id);
// This is called when starting a new extension page, so start the idle
// handler ticking.
RenderThread::current()->ScheduleIdleHandler(
kInitialExtensionIdleHandlerDelayS);
UpdateActiveExtensions();
const Extension* extension = extensions_.GetByID(extension_id);
if (!extension)
return;
if (is_webkit_initialized_)
InitOriginPermissions(extension);
}
void ExtensionDispatcher::InitOriginPermissions(const Extension* extension) {
// TODO(jstritar): We should try to remove this special case. Also, these
// whitelist entries need to be updated when the kManagement permission
// changes.
if (extension->HasAPIPermission(ExtensionAPIPermission::kManagement)) {
WebSecurityPolicy::addOriginAccessWhitelistEntry(
extension->url(),
WebString::fromUTF8(chrome::kChromeUIScheme),
WebString::fromUTF8(chrome::kChromeUIExtensionIconHost),
false);
}
UpdateOriginPermissions(UpdatedExtensionPermissionsInfo::ADDED,
extension,
extension->GetActivePermissions()->explicit_hosts());
}
void ExtensionDispatcher::UpdateOriginPermissions(
UpdatedExtensionPermissionsInfo::Reason reason,
const Extension* extension,
const URLPatternSet& origins) {
for (URLPatternSet::const_iterator i = origins.begin();
i != origins.end(); ++i) {
const char* schemes[] = {
chrome::kHttpScheme,
chrome::kHttpsScheme,
chrome::kFileScheme,
chrome::kChromeUIScheme,
};
for (size_t j = 0; j < arraysize(schemes); ++j) {
if (i->MatchesScheme(schemes[j])) {
((reason == UpdatedExtensionPermissionsInfo::REMOVED) ?
WebSecurityPolicy::removeOriginAccessWhitelistEntry :
WebSecurityPolicy::addOriginAccessWhitelistEntry)(
extension->url(),
WebString::fromUTF8(schemes[j]),
WebString::fromUTF8(i->host()),
i->match_subdomains());
}
}
}
}
void ExtensionDispatcher::OnUpdatePermissions(
int reason_id,
const std::string& extension_id,
const ExtensionAPIPermissionSet& apis,
const URLPatternSet& explicit_hosts,
const URLPatternSet& scriptable_hosts) {
const Extension* extension = extensions_.GetByID(extension_id);
if (!extension)
return;
scoped_refptr<const ExtensionPermissionSet> delta =
new ExtensionPermissionSet(apis, explicit_hosts, scriptable_hosts);
scoped_refptr<const ExtensionPermissionSet> old_active =
extension->GetActivePermissions();
UpdatedExtensionPermissionsInfo::Reason reason =
static_cast<UpdatedExtensionPermissionsInfo::Reason>(reason_id);
const ExtensionPermissionSet* new_active = NULL;
if (reason == UpdatedExtensionPermissionsInfo::ADDED) {
new_active = ExtensionPermissionSet::CreateUnion(old_active, delta);
} else {
CHECK_EQ(UpdatedExtensionPermissionsInfo::REMOVED, reason);
new_active = ExtensionPermissionSet::CreateDifference(old_active, delta);
}
extension->SetActivePermissions(new_active);
UpdateOriginPermissions(reason, extension, explicit_hosts);
}
void ExtensionDispatcher::OnUpdateUserScripts(
base::SharedMemoryHandle scripts) {
DCHECK(base::SharedMemory::IsHandleValid(scripts)) << "Bad scripts handle";
user_script_slave_->UpdateScripts(scripts);
UpdateActiveExtensions();
}
void ExtensionDispatcher::UpdateActiveExtensions() {
// In single-process mode, the browser process reports the active extensions.
if (CommandLine::ForCurrentProcess()->HasSwitch(switches::kSingleProcess))
return;
std::set<std::string> active_extensions = active_extension_ids_;
user_script_slave_->GetActiveExtensions(&active_extensions);
child_process_logging::SetActiveExtensions(active_extensions);
}
void ExtensionDispatcher::RegisterExtension(v8::Extension* extension,
bool restrict_to_extensions) {
if (restrict_to_extensions)
restricted_v8_extensions_.insert(extension->name());
RenderThread::current()->RegisterExtension(extension);
}