// Copyright (c) 2012 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/callback.h" #include "base/command_line.h" #include "base/memory/scoped_ptr.h" #include "base/string_piece.h" #include "chrome/common/child_process_logging.h" #include "chrome/common/chrome_switches.h" #include "chrome/common/chrome_version_info.h" #include "chrome/common/extensions/api/extension_api.h" #include "chrome/common/extensions/extension.h" #include "chrome/common/extensions/extension_messages.h" #include "chrome/common/extensions/permissions/permission_set.h" #include "chrome/common/url_constants.h" #include "chrome/common/view_type.h" #include "chrome/renderer/chrome_render_process_observer.h" #include "chrome/renderer/extensions/api_definitions_natives.h" #include "chrome/renderer/extensions/app_bindings.h" #include "chrome/renderer/extensions/app_window_custom_bindings.h" #include "chrome/renderer/extensions/chrome_v8_context.h" #include "chrome/renderer/extensions/chrome_v8_extension.h" #include "chrome/renderer/extensions/context_menus_custom_bindings.h" #include "chrome/renderer/extensions/event_bindings.h" #include "chrome/renderer/extensions/experimental.app_custom_bindings.h" #include "chrome/renderer/extensions/experimental.usb_custom_bindings.h" #include "chrome/renderer/extensions/extension_custom_bindings.h" #include "chrome/renderer/extensions/extension_groups.h" #include "chrome/renderer/extensions/extension_helper.h" #include "chrome/renderer/extensions/extension_request_sender.h" #include "chrome/renderer/extensions/file_browser_handler_custom_bindings.h" #include "chrome/renderer/extensions/file_browser_private_custom_bindings.h" #include "chrome/renderer/extensions/file_system_natives.h" #include "chrome/renderer/extensions/i18n_custom_bindings.h" #include "chrome/renderer/extensions/media_gallery_custom_bindings.h" #include "chrome/renderer/extensions/miscellaneous_bindings.h" #include "chrome/renderer/extensions/page_actions_custom_bindings.h" #include "chrome/renderer/extensions/page_capture_custom_bindings.h" #include "chrome/renderer/extensions/runtime_custom_bindings.h" #include "chrome/renderer/extensions/send_request_natives.h" #include "chrome/renderer/extensions/set_icon_natives.h" #include "chrome/renderer/extensions/tab_finder.h" #include "chrome/renderer/extensions/tabs_custom_bindings.h" #include "chrome/renderer/extensions/tts_custom_bindings.h" #include "chrome/renderer/extensions/user_script_slave.h" #include "chrome/renderer/extensions/web_request_custom_bindings.h" #include "chrome/renderer/extensions/webstore_bindings.h" #include "chrome/renderer/module_system.h" #include "chrome/renderer/native_handler.h" #include "chrome/renderer/resource_bundle_source_map.h" #include "content/public/renderer/render_thread.h" #include "content/public/renderer/render_view.h" #include "grit/renderer_resources.h" #include "third_party/WebKit/Source/WebKit/chromium/public/WebDataSource.h" #include "third_party/WebKit/Source/WebKit/chromium/public/WebDocument.h" #include "third_party/WebKit/Source/WebKit/chromium/public/WebFrame.h" #include "third_party/WebKit/Source/WebKit/chromium/public/WebScopedUserGesture.h" #include "third_party/WebKit/Source/WebKit/chromium/public/WebSecurityPolicy.h" #include "third_party/WebKit/Source/WebKit/chromium/public/WebView.h" #include "third_party/WebKit/Source/WebKit/chromium/public/platform/WebString.h" #include "third_party/WebKit/Source/WebKit/chromium/public/platform/WebURLRequest.h" #include "ui/base/layout.h" #include "ui/base/resource/resource_bundle.h" #include "v8/include/v8.h" using WebKit::WebDataSource; using WebKit::WebDocument; using WebKit::WebFrame; using WebKit::WebScopedUserGesture; using WebKit::WebSecurityPolicy; using WebKit::WebString; using WebKit::WebVector; using WebKit::WebView; using content::RenderThread; using content::RenderView; using extensions::APIPermission; using extensions::ApiDefinitionsNatives; using extensions::AppWindowCustomBindings; using extensions::ContextMenusCustomBindings; using extensions::Extension; using extensions::ExperimentalAppCustomBindings; using extensions::ExperimentalUsbCustomBindings; using extensions::ExtensionAPI; using extensions::ExtensionCustomBindings; using extensions::Feature; using extensions::FileBrowserHandlerCustomBindings; using extensions::FileBrowserPrivateCustomBindings; using extensions::FileSystemNatives; using extensions::I18NCustomBindings; using extensions::MiscellaneousBindings; using extensions::MediaGalleryCustomBindings; using extensions::PageActionsCustomBindings; using extensions::PageCaptureCustomBindings; using extensions::PermissionSet; using extensions::RuntimeCustomBindings; using extensions::SendRequestNatives; using extensions::SetIconNatives; using extensions::TTSCustomBindings; using extensions::TabFinder; using extensions::TabsCustomBindings; using extensions::UpdatedExtensionPermissionsInfo; using extensions::WebRequestCustomBindings; namespace { static const int64 kInitialExtensionIdleHandlerDelayMs = 5*1000; static const int64 kMaxExtensionIdleHandlerDelayMs = 5*60*1000; static const char kEventDispatchFunction[] = "Event.dispatchJSON"; static const char kOnUnloadEvent[] = "runtime.onSuspend"; class ChromeHiddenNativeHandler : public NativeHandler { public: ChromeHiddenNativeHandler() { RouteFunction("GetChromeHidden", base::Bind(&ChromeHiddenNativeHandler::GetChromeHidden, base::Unretained(this))); } v8::Handle<v8::Value> GetChromeHidden(const v8::Arguments& args) { return ChromeV8Context::GetOrCreateChromeHidden(v8::Context::GetCurrent()); } }; class PrintNativeHandler : public NativeHandler { public: PrintNativeHandler() { RouteFunction("Print", base::Bind(&PrintNativeHandler::Print, base::Unretained(this))); } v8::Handle<v8::Value> Print(const v8::Arguments& args) { if (args.Length() < 1) return v8::Undefined(); std::vector<std::string> components; for (int i = 0; i < args.Length(); ++i) components.push_back(*v8::String::Utf8Value(args[i]->ToString())); LOG(ERROR) << JoinString(components, ','); return v8::Undefined(); } }; class LazyBackgroundPageNativeHandler : public ChromeV8Extension { public: explicit LazyBackgroundPageNativeHandler(ExtensionDispatcher* dispatcher) : ChromeV8Extension(dispatcher) { RouteFunction("IncrementKeepaliveCount", base::Bind(&LazyBackgroundPageNativeHandler::IncrementKeepaliveCount, base::Unretained(this))); RouteFunction("DecrementKeepaliveCount", base::Bind(&LazyBackgroundPageNativeHandler::DecrementKeepaliveCount, base::Unretained(this))); } v8::Handle<v8::Value> IncrementKeepaliveCount(const v8::Arguments& args) { ChromeV8Context* context = extension_dispatcher()->v8_context_set().GetCurrent(); if (!context) return v8::Undefined(); RenderView* render_view = context->GetRenderView(); if (IsContextLazyBackgroundPage(render_view, context->extension())) { render_view->Send(new ExtensionHostMsg_IncrementLazyKeepaliveCount( render_view->GetRoutingID())); } return v8::Undefined(); } v8::Handle<v8::Value> DecrementKeepaliveCount(const v8::Arguments& args) { ChromeV8Context* context = extension_dispatcher()->v8_context_set().GetCurrent(); if (!context) return v8::Undefined(); RenderView* render_view = context->GetRenderView(); if (IsContextLazyBackgroundPage(render_view, context->extension())) { render_view->Send(new ExtensionHostMsg_DecrementLazyKeepaliveCount( render_view->GetRoutingID())); } return v8::Undefined(); } private: bool IsContextLazyBackgroundPage(RenderView* render_view, const Extension* extension) { if (!render_view) return false; ExtensionHelper* helper = ExtensionHelper::Get(render_view); return (extension && extension->has_lazy_background_page() && helper->view_type() == chrome::VIEW_TYPE_EXTENSION_BACKGROUND_PAGE); } }; class ChannelNativeHandler : public NativeHandler { public: explicit ChannelNativeHandler(chrome::VersionInfo::Channel channel) : channel_(channel) { RouteFunction("IsDevChannel", base::Bind(&ChannelNativeHandler::IsDevChannel, base::Unretained(this))); } v8::Handle<v8::Value> IsDevChannel(const v8::Arguments& args) { return v8::Boolean::New(channel_ <= chrome::VersionInfo::CHANNEL_DEV); } chrome::VersionInfo::Channel channel_; }; void InstallAppBindings(ModuleSystem* module_system, v8::Handle<v8::Object> chrome, v8::Handle<v8::Object> chrome_hidden) { module_system->SetLazyField(chrome, "app", "app", "chromeApp"); module_system->SetLazyField(chrome, "appNotifications", "app", "chromeAppNotifications"); module_system->SetLazyField(chrome_hidden, "app", "app", "chromeHiddenApp"); } void InstallWebstoreBindings(ModuleSystem* module_system, v8::Handle<v8::Object> chrome, v8::Handle<v8::Object> chrome_hidden) { module_system->SetLazyField(chrome, "webstore", "webstore", "chromeWebstore"); module_system->SetLazyField(chrome_hidden, "webstore", "webstore", "chromeHiddenWebstore"); } } ExtensionDispatcher::ExtensionDispatcher() : is_webkit_initialized_(false), webrequest_adblock_(false), webrequest_adblock_plus_(false), webrequest_other_(false), source_map_(&ResourceBundle::GetSharedInstance()), chrome_channel_(chrome::VersionInfo::CHANNEL_UNKNOWN), event_filter_(new extensions::EventFilter) { const CommandLine& command_line = *(CommandLine::ForCurrentProcess()); is_extension_process_ = command_line.HasSwitch(switches::kExtensionProcess) || command_line.HasSwitch(switches::kSingleProcess); if (is_extension_process_) { RenderThread::Get()->SetIdleNotificationDelayInMs( kInitialExtensionIdleHandlerDelayMs); } user_script_slave_.reset(new UserScriptSlave(&extensions_)); request_sender_.reset(new ExtensionRequestSender(this, &v8_context_set_)); PopulateSourceMap(); PopulateLazyBindingsMap(); } ExtensionDispatcher::~ExtensionDispatcher() { } bool ExtensionDispatcher::OnControlMessageReceived( const IPC::Message& message) { bool handled = true; IPC_BEGIN_MESSAGE_MAP(ExtensionDispatcher, message) IPC_MESSAGE_HANDLER(ExtensionMsg_SetChannel, OnSetChannel) IPC_MESSAGE_HANDLER(ExtensionMsg_MessageInvoke, OnMessageInvoke) IPC_MESSAGE_HANDLER(ExtensionMsg_DispatchOnConnect, OnDispatchOnConnect) IPC_MESSAGE_HANDLER(ExtensionMsg_DeliverMessage, OnDeliverMessage) IPC_MESSAGE_HANDLER(ExtensionMsg_DispatchOnDisconnect, OnDispatchOnDisconnect) 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_UpdatePermissions, OnUpdatePermissions) IPC_MESSAGE_HANDLER(ExtensionMsg_UpdateTabSpecificPermissions, OnUpdateTabSpecificPermissions) IPC_MESSAGE_HANDLER(ExtensionMsg_ClearTabSpecificPermissions, OnClearTabSpecificPermissions) IPC_MESSAGE_HANDLER(ExtensionMsg_UpdateUserScripts, OnUpdateUserScripts) IPC_MESSAGE_HANDLER(ExtensionMsg_UsingWebRequestAPI, OnUsingWebRequestAPI) IPC_MESSAGE_HANDLER(ExtensionMsg_ShouldUnload, OnShouldUnload) IPC_MESSAGE_HANDLER(ExtensionMsg_Unload, OnUnload) 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::FromMilliseconds(kMaxExtensionIdleHandlerDelayMs), RenderThread::Get(), &RenderThread::IdleHandler); } // 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_ms = std::max( RenderThread::Get()->GetIdleNotificationDelayInMs(), kMaxExtensionIdleHandlerDelayMs); forced_idle_timer_.Stop(); forced_idle_timer_.Start(FROM_HERE, base::TimeDelta::FromMilliseconds(forced_delay_ms), RenderThread::Get(), &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::OnSetChannel(int channel) { chrome_channel_ = channel; } void ExtensionDispatcher::OnMessageInvoke(const std::string& extension_id, const std::string& function_name, const ListValue& args, const GURL& event_url, bool user_gesture) { scoped_ptr<WebScopedUserGesture> web_user_gesture; if (user_gesture) { web_user_gesture.reset(new WebScopedUserGesture); } v8_context_set_.DispatchChromeHiddenMethod( 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::Get()->ScheduleIdleHandler( kInitialExtensionIdleHandlerDelayMs); } // Tell the browser process when an event has been dispatched with a lazy // background page active. const Extension* extension = extensions_.GetByID(extension_id); if (extension && extension->has_lazy_background_page() && function_name == kEventDispatchFunction) { RenderView* background_view = ExtensionHelper::GetBackgroundPage(extension_id); if (background_view) { background_view->Send(new ExtensionHostMsg_EventAck( background_view->GetRoutingID())); } } } void ExtensionDispatcher::OnDispatchOnConnect( int target_port_id, const std::string& channel_name, const std::string& tab_json, const std::string& source_extension_id, const std::string& target_extension_id) { MiscellaneousBindings::DispatchOnConnect( v8_context_set_.GetAll(), target_port_id, channel_name, tab_json, source_extension_id, target_extension_id, NULL); // All render views. } void ExtensionDispatcher::OnDeliverMessage(int target_port_id, const std::string& message) { MiscellaneousBindings::DeliverMessage( v8_context_set_.GetAll(), target_port_id, message, NULL); // All render views. } void ExtensionDispatcher::OnDispatchOnDisconnect(int port_id, bool connection_error) { MiscellaneousBindings::DispatchOnDisconnect( v8_context_set_.GetAll(), port_id, connection_error, NULL); // All render views. } void ExtensionDispatcher::OnLoaded( const std::vector<ExtensionMsg_Loaded_Params>& loaded_extensions) { std::vector<WebString> platform_app_patterns; std::vector<ExtensionMsg_Loaded_Params>::const_iterator i; for (i = loaded_extensions.begin(); i != loaded_extensions.end(); ++i) { scoped_refptr<const Extension> extension(i->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. continue; } extensions_.Insert(extension); if (extension->is_platform_app()) { platform_app_patterns.push_back( WebString::fromUTF8(extension->url().spec() + "*")); } } if (!platform_app_patterns.empty()) { // We have collected a set of platform-app extensions, so let's tell WebKit // about them so that it can provide a default stylesheet for them. // // TODO(miket): consider enhancing WebView to allow removing // single stylesheets, or else to edit the pattern set associated // with one. WebVector<WebString> patterns; patterns.assign(platform_app_patterns); WebView::addUserStyleSheet( WebString::fromUTF8(ResourceBundle::GetSharedInstance(). GetRawDataResource(IDR_PLATFORM_APP_CSS, ui::SCALE_FACTOR_NONE)), patterns, WebView::UserContentInjectInAllFrames, WebView::UserStyleInjectInExistingDocuments); } } 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); // We don't do anything with existing platform-app stylesheets. They will // stay resident, but the URL pattern corresponding to the unloaded // extension's URL just won't match anything anymore. } void ExtensionDispatcher::OnSetScriptingWhitelist( const Extension::ScriptingWhitelist& extension_ids) { Extension::SetScriptingWhitelist(extension_ids); } 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) { return AllowScriptExtension(frame, v8_extension_name, extension_group, 0); } namespace { // This is what the extension_group variable will be when DidCreateScriptContext // is called. We know because it's the same as what AllowScriptExtension gets // passed, and the two functions are called sequentially from WebKit. // // TODO(koz): Plumb extension_group through to AllowScriptExtension() from // WebKit. static int g_hack_extension_group = 0; } bool ExtensionDispatcher::AllowScriptExtension( WebFrame* frame, const std::string& v8_extension_name, int extension_group, int world_id) { g_hack_extension_group = extension_group; return true; } void ExtensionDispatcher::RegisterNativeHandlers(ModuleSystem* module_system, ChromeV8Context* context) { module_system->RegisterNativeHandler("event_bindings", scoped_ptr<NativeHandler>(EventBindings::Get(this, event_filter_.get()))); module_system->RegisterNativeHandler("miscellaneous_bindings", scoped_ptr<NativeHandler>(MiscellaneousBindings::Get(this))); module_system->RegisterNativeHandler("apiDefinitions", scoped_ptr<NativeHandler>(new ApiDefinitionsNatives(this))); module_system->RegisterNativeHandler("sendRequest", scoped_ptr<NativeHandler>( new SendRequestNatives(this, request_sender_.get()))); module_system->RegisterNativeHandler("setIcon", scoped_ptr<NativeHandler>( new SetIconNatives(this, request_sender_.get()))); // Natives used by multiple APIs. module_system->RegisterNativeHandler("file_system_natives", scoped_ptr<NativeHandler>(new FileSystemNatives())); // Custom bindings. module_system->RegisterNativeHandler("app", scoped_ptr<NativeHandler>(new AppBindings(this, context))); module_system->RegisterNativeHandler("app_window", scoped_ptr<NativeHandler>(new AppWindowCustomBindings(this))); module_system->RegisterNativeHandler("context_menus", scoped_ptr<NativeHandler>(new ContextMenusCustomBindings())); module_system->RegisterNativeHandler("extension", scoped_ptr<NativeHandler>( new ExtensionCustomBindings(this))); module_system->RegisterNativeHandler("experimental_mediaGalleries", scoped_ptr<NativeHandler>(new MediaGalleryCustomBindings())); module_system->RegisterNativeHandler("experimental_app", scoped_ptr<NativeHandler>(new ExperimentalAppCustomBindings())); module_system->RegisterNativeHandler("experimental_usb", scoped_ptr<NativeHandler>(new ExperimentalUsbCustomBindings())); module_system->RegisterNativeHandler("file_browser_handler", scoped_ptr<NativeHandler>(new FileBrowserHandlerCustomBindings())); module_system->RegisterNativeHandler("file_browser_private", scoped_ptr<NativeHandler>(new FileBrowserPrivateCustomBindings())); module_system->RegisterNativeHandler("i18n", scoped_ptr<NativeHandler>(new I18NCustomBindings())); module_system->RegisterNativeHandler("page_actions", scoped_ptr<NativeHandler>( new PageActionsCustomBindings(this))); module_system->RegisterNativeHandler("page_capture", scoped_ptr<NativeHandler>(new PageCaptureCustomBindings())); module_system->RegisterNativeHandler("runtime", scoped_ptr<NativeHandler>(new RuntimeCustomBindings(context))); module_system->RegisterNativeHandler("tabs", scoped_ptr<NativeHandler>(new TabsCustomBindings())); module_system->RegisterNativeHandler("tts", scoped_ptr<NativeHandler>(new TTSCustomBindings())); module_system->RegisterNativeHandler("web_request", scoped_ptr<NativeHandler>(new WebRequestCustomBindings())); module_system->RegisterNativeHandler("webstore", scoped_ptr<NativeHandler>(new WebstoreBindings(this, context))); } void ExtensionDispatcher::PopulateSourceMap() { source_map_.RegisterSource("event_bindings", IDR_EVENT_BINDINGS_JS); source_map_.RegisterSource("miscellaneous_bindings", IDR_MISCELLANEOUS_BINDINGS_JS); source_map_.RegisterSource("schema_generated_bindings", IDR_SCHEMA_GENERATED_BINDINGS_JS); source_map_.RegisterSource("json_schema", IDR_JSON_SCHEMA_JS); source_map_.RegisterSource("apitest", IDR_EXTENSION_APITEST_JS); // Libraries. source_map_.RegisterSource("lastError", IDR_LAST_ERROR_JS); source_map_.RegisterSource("schemaUtils", IDR_SCHEMA_UTILS_JS); source_map_.RegisterSource("sendRequest", IDR_SEND_REQUEST_JS); source_map_.RegisterSource("setIcon", IDR_SET_ICON_JS); source_map_.RegisterSource("utils", IDR_UTILS_JS); // Custom bindings. source_map_.RegisterSource("app", IDR_APP_CUSTOM_BINDINGS_JS); source_map_.RegisterSource("appWindow", IDR_APP_WINDOW_CUSTOM_BINDINGS_JS); source_map_.RegisterSource("browserAction", IDR_BROWSER_ACTION_CUSTOM_BINDINGS_JS); source_map_.RegisterSource("contentSettings", IDR_CONTENT_SETTINGS_CUSTOM_BINDINGS_JS); source_map_.RegisterSource("contextMenus", IDR_CONTEXT_MENUS_CUSTOM_BINDINGS_JS); source_map_.RegisterSource("declarativeWebRequest", IDR_DECLARATIVE_WEBREQUEST_CUSTOM_BINDINGS_JS); source_map_.RegisterSource("devtools", IDR_DEVTOOLS_CUSTOM_BINDINGS_JS); source_map_.RegisterSource("experimental.app", IDR_EXPERIMENTAL_APP_CUSTOM_BINDINGS_JS); source_map_.RegisterSource("experimental.bluetooth", IDR_EXPERIMENTAL_BLUETOOTH_CUSTOM_BINDINGS_JS); source_map_.RegisterSource("experimental.mediaGalleries", IDR_MEDIA_GALLERY_CUSTOM_BINDINGS_JS); source_map_.RegisterSource("experimental.offscreen", IDR_EXPERIMENTAL_OFFSCREENTABS_CUSTOM_BINDINGS_JS); source_map_.RegisterSource("experimental.usb", IDR_EXPERIMENTAL_USB_CUSTOM_BINDINGS_JS); source_map_.RegisterSource("extension", IDR_EXTENSION_CUSTOM_BINDINGS_JS); source_map_.RegisterSource("fileBrowserHandler", IDR_FILE_BROWSER_HANDLER_CUSTOM_BINDINGS_JS); source_map_.RegisterSource("fileBrowserPrivate", IDR_FILE_BROWSER_PRIVATE_CUSTOM_BINDINGS_JS); source_map_.RegisterSource("fileSystem", IDR_FILE_SYSTEM_CUSTOM_BINDINGS_JS); source_map_.RegisterSource("i18n", IDR_I18N_CUSTOM_BINDINGS_JS); source_map_.RegisterSource("input.ime", IDR_INPUT_IME_CUSTOM_BINDINGS_JS); source_map_.RegisterSource("omnibox", IDR_OMNIBOX_CUSTOM_BINDINGS_JS); source_map_.RegisterSource("pageActions", IDR_PAGE_ACTIONS_CUSTOM_BINDINGS_JS); source_map_.RegisterSource("pageAction", IDR_PAGE_ACTION_CUSTOM_BINDINGS_JS); source_map_.RegisterSource("pageCapture", IDR_PAGE_CAPTURE_CUSTOM_BINDINGS_JS); source_map_.RegisterSource("runtime", IDR_RUNTIME_CUSTOM_BINDINGS_JS); source_map_.RegisterSource("storage", IDR_STORAGE_CUSTOM_BINDINGS_JS); source_map_.RegisterSource("tabs", IDR_TABS_CUSTOM_BINDINGS_JS); source_map_.RegisterSource("tts", IDR_TTS_CUSTOM_BINDINGS_JS); source_map_.RegisterSource("ttsEngine", IDR_TTS_ENGINE_CUSTOM_BINDINGS_JS); source_map_.RegisterSource("types", IDR_TYPES_CUSTOM_BINDINGS_JS); source_map_.RegisterSource("webRequest", IDR_WEB_REQUEST_CUSTOM_BINDINGS_JS); source_map_.RegisterSource("webRequestInternal", IDR_WEB_REQUEST_INTERNAL_CUSTOM_BINDINGS_JS); source_map_.RegisterSource("webstore", IDR_WEBSTORE_CUSTOM_BINDINGS_JS); // Platform app sources that are not API-specific.. source_map_.RegisterSource("browserTag", IDR_BROWSER_TAG_JS); source_map_.RegisterSource("platformApp", IDR_PLATFORM_APP_JS); } void ExtensionDispatcher::PopulateLazyBindingsMap() { lazy_bindings_map_["app"] = InstallAppBindings; lazy_bindings_map_["webstore"] = InstallWebstoreBindings; } void ExtensionDispatcher::InstallBindings(ModuleSystem* module_system, v8::Handle<v8::Context> v8_context, const std::string& api) { std::map<std::string, BindingInstaller>::const_iterator lazy_binding = lazy_bindings_map_.find(api); if (lazy_binding != lazy_bindings_map_.end()) { v8::Handle<v8::Object> global(v8_context->Global()); v8::Handle<v8::Object> chrome = global->Get(v8::String::New("chrome"))->ToObject(); v8::Handle<v8::Object> chrome_hidden = ChromeV8Context::GetOrCreateChromeHidden(v8_context)->ToObject(); (*lazy_binding->second)(module_system, chrome, chrome_hidden); } else { module_system->Require(api); } } void ExtensionDispatcher::DidCreateScriptContext( WebFrame* frame, v8::Handle<v8::Context> v8_context, int extension_group, int world_id) { // TODO(koz): If the caller didn't pass extension_group, use the last value. if (extension_group == -1) extension_group = g_hack_extension_group; std::string extension_id = GetExtensionID(frame, world_id); const Extension* extension = extensions_.GetByID(extension_id); if (!extension && !extension_id.empty()) { // There are conditions where despite a context being associated with an // extension, no extension actually gets found. Ignore "invalid" because // CSP blocks extension page loading by switching the extension ID to // "invalid". This isn't interesting. if (extension_id != "invalid") { LOG(ERROR) << "Extension \"" << extension_id << "\" not found"; RenderThread::Get()->RecordUserMetrics("ExtensionNotFound_ED"); } extension_id = ""; } ExtensionURLInfo url_info(frame->document().securityOrigin(), UserScriptSlave::GetDataSourceURLForFrame(frame)); Feature::Context context_type = ClassifyJavaScriptContext(extension_id, extension_group, url_info); ChromeV8Context* context = new ChromeV8Context(v8_context, frame, extension, context_type); v8_context_set_.Add(context); scoped_ptr<ModuleSystem> module_system(new ModuleSystem(v8_context, &source_map_)); // Enable natives in startup. ModuleSystem::NativesEnabledScope natives_enabled_scope(module_system.get()); RegisterNativeHandlers(module_system.get(), context); module_system->RegisterNativeHandler("chrome_hidden", scoped_ptr<NativeHandler>(new ChromeHiddenNativeHandler())); module_system->RegisterNativeHandler("print", scoped_ptr<NativeHandler>(new PrintNativeHandler())); module_system->RegisterNativeHandler("lazy_background_page", scoped_ptr<NativeHandler>(new LazyBackgroundPageNativeHandler(this))); module_system->RegisterNativeHandler("channel", scoped_ptr<NativeHandler>(new ChannelNativeHandler( static_cast<chrome::VersionInfo::Channel>(chrome_channel_)))); // Create the 'chrome' variable if it doesn't already exist. { v8::HandleScope handle_scope; v8::Handle<v8::String> chrome_string(v8::String::New("chrome")); v8::Handle<v8::Object> global(v8::Context::GetCurrent()->Global()); if (global->Get(chrome_string)->IsUndefined()) global->Set(chrome_string, v8::Object::New()); } // Loading JavaScript is expensive, so only run the full API bindings // generation mechanisms in extension pages (NOT all web pages). switch (context_type) { case Feature::UNSPECIFIED_CONTEXT: case Feature::WEB_PAGE_CONTEXT: // TODO(kalman): see comment below about ExtensionAPI. InstallBindings(module_system.get(), v8_context, "app"); InstallBindings(module_system.get(), v8_context, "webstore"); break; case Feature::BLESSED_EXTENSION_CONTEXT: case Feature::UNBLESSED_EXTENSION_CONTEXT: case Feature::CONTENT_SCRIPT_CONTEXT: { CHECK(extension); if (!extension->is_platform_app()) module_system->Require("miscellaneous_bindings"); module_system->Require("schema_generated_bindings"); module_system->Require("apitest"); // TODO(kalman): move this code back out of the switch and execute it // regardless of |context_type|. ExtensionAPI knows how to return the // correct APIs, however, until it doesn't have a 2MB overhead we can't // load it in every process. const std::set<std::string>& apis = context->GetAvailableExtensionAPIs(); for (std::set<std::string>::const_iterator i = apis.begin(); i != apis.end(); ++i) { InstallBindings(module_system.get(), v8_context, *i); } break; } } // Inject custom JS into the platform app context. if (IsWithinPlatformApp(frame)) module_system->Require("platformApp"); if (context_type == Feature::BLESSED_EXTENSION_CONTEXT && extension && extension->HasAPIPermission(APIPermission::kBrowserTag)) { module_system->Require("browserTag"); } context->set_module_system(module_system.Pass()); int manifest_version = 1; if (extension) manifest_version = extension->manifest_version(); context->DispatchOnLoadEvent( is_extension_process_, ChromeRenderProcessObserver::is_incognito_process(), manifest_version); VLOG(1) << "Num tracked contexts: " << v8_context_set_.size(); } std::string ExtensionDispatcher::GetExtensionID(const WebFrame* frame, int world_id) { if (world_id != 0) { // Isolated worlds (content script). return user_script_slave_->GetExtensionIdForIsolatedWorld(world_id); } // Extension pages (chrome-extension:// URLs). GURL frame_url = UserScriptSlave::GetDataSourceURLForFrame(frame); return extensions_.GetExtensionOrAppIDByURL( ExtensionURLInfo(frame->document().securityOrigin(), frame_url)); } bool ExtensionDispatcher::IsWithinPlatformApp(const WebFrame* frame) { const Extension* extension = extensions_.GetByID(GetExtensionID(frame->top(), 0)); return extension && extension->is_platform_app(); } void ExtensionDispatcher::WillReleaseScriptContext( WebFrame* frame, v8::Handle<v8::Context> v8_context, int world_id) { ChromeV8Context* context = v8_context_set_.GetByV8Context(v8_context); if (!context) return; context->DispatchOnUnloadEvent(); v8_context_set_.Remove(context); VLOG(1) << "Num tracked contexts: " << v8_context_set_.size(); } 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::Get()->ScheduleIdleHandler(kInitialExtensionIdleHandlerDelayMs); 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(APIPermission::kManagement)) { WebSecurityPolicy::addOriginAccessWhitelistEntry( extension->url(), WebString::fromUTF8(chrome::kChromeUIScheme), WebString::fromUTF8(chrome::kChromeUIExtensionIconHost), false); } AddOrRemoveOriginPermissions( UpdatedExtensionPermissionsInfo::ADDED, extension, extension->GetActivePermissions()->explicit_hosts()); } void ExtensionDispatcher::AddOrRemoveOriginPermissions( 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 extensions::APIPermissionSet& apis, const URLPatternSet& explicit_hosts, const URLPatternSet& scriptable_hosts) { const Extension* extension = extensions_.GetByID(extension_id); if (!extension) return; scoped_refptr<const PermissionSet> delta = new PermissionSet(apis, explicit_hosts, scriptable_hosts); scoped_refptr<const PermissionSet> old_active = extension->GetActivePermissions(); UpdatedExtensionPermissionsInfo::Reason reason = static_cast<UpdatedExtensionPermissionsInfo::Reason>(reason_id); const PermissionSet* new_active = NULL; switch (reason) { case UpdatedExtensionPermissionsInfo::ADDED: new_active = PermissionSet::CreateUnion(old_active, delta); break; case UpdatedExtensionPermissionsInfo::REMOVED: new_active = PermissionSet::CreateDifference(old_active, delta); break; } extension->SetActivePermissions(new_active); AddOrRemoveOriginPermissions(reason, extension, explicit_hosts); } void ExtensionDispatcher::OnUpdateTabSpecificPermissions( int page_id, int tab_id, const std::string& extension_id, const URLPatternSet& origin_set) { RenderView* view = TabFinder::Find(tab_id); // For now, the message should only be sent to the render view that contains // the target tab. This may change. Either way, if this is the target tab it // gives us the chance to check against the page ID to avoid races. DCHECK(view); if (view && view->GetPageId() != page_id) return; const Extension* extension = extensions_.GetByID(extension_id); if (!extension) return; extension->SetTabSpecificHostPermissions(tab_id, origin_set); } void ExtensionDispatcher::OnClearTabSpecificPermissions( int tab_id, const std::vector<std::string>& extension_ids) { for (std::vector<std::string>::const_iterator it = extension_ids.begin(); it != extension_ids.end(); ++it) { const Extension* extension = extensions_.GetByID(*it); if (extension) extension->ClearTabSpecificHostPermissions(tab_id); } } 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::Get()->RegisterExtension(extension); } void ExtensionDispatcher::OnUsingWebRequestAPI( bool adblock, bool adblock_plus, bool other) { webrequest_adblock_ = adblock; webrequest_adblock_plus_ = adblock_plus; webrequest_other_ = other; } void ExtensionDispatcher::OnShouldUnload(const std::string& extension_id, int sequence_id) { RenderThread::Get()->Send( new ExtensionHostMsg_ShouldUnloadAck(extension_id, sequence_id)); } void ExtensionDispatcher::OnUnload(const std::string& extension_id) { // Dispatch the unload event. This doesn't go through the standard event // dispatch machinery because it requires special handling. We need to let // the browser know when we are starting and stopping the event dispatch, so // that it still considers the extension idle despite any activity the unload // event creates. ListValue args; args.Set(0, Value::CreateStringValue(kOnUnloadEvent)); args.Set(1, Value::CreateStringValue("[]")); v8_context_set_.DispatchChromeHiddenMethod( extension_id, kEventDispatchFunction, args, NULL, GURL()); RenderThread::Get()->Send(new ExtensionHostMsg_UnloadAck(extension_id)); } Feature::Context ExtensionDispatcher::ClassifyJavaScriptContext( const std::string& extension_id, int extension_group, const ExtensionURLInfo& url_info) { if (extension_group == EXTENSION_GROUP_CONTENT_SCRIPTS) return Feature::CONTENT_SCRIPT_CONTEXT; // We have an explicit check for sandboxed pages first since: // 1. Sandboxed pages run in the same process as regular extension pages, so // the extension is considered active. // 2. ScriptContext creation (which triggers bindings injection) happens // before the SecurityContext is updated with the sandbox flags (after // reading the CSP header), so url_info.url().securityOrigin() is not // unique yet. if (extensions_.IsSandboxedPage(url_info)) return Feature::WEB_PAGE_CONTEXT; if (IsExtensionActive(extension_id)) return Feature::BLESSED_EXTENSION_CONTEXT; if (extensions_.ExtensionBindingsAllowed(url_info)) return Feature::UNBLESSED_EXTENSION_CONTEXT; if (url_info.url().is_valid()) return Feature::WEB_PAGE_CONTEXT; return Feature::UNSPECIFIED_CONTEXT; } void ExtensionDispatcher::OnExtensionResponse(int request_id, bool success, const base::ListValue& response, const std::string& error) { request_sender_->HandleResponse(request_id, success, response, error); } bool ExtensionDispatcher::CheckCurrentContextAccessToExtensionAPI( const std::string& function_name) const { ChromeV8Context* context = v8_context_set().GetCurrent(); if (!context) { DLOG(ERROR) << "Not in a v8::Context"; return false; } if (!context->extension() || !context->extension()->HasAPIPermission(function_name)) { static const char kMessage[] = "You do not have permission to use '%s'. Be sure to declare" " in your manifest what permissions you need."; std::string error_msg = base::StringPrintf(kMessage, function_name.c_str()); v8::ThrowException( v8::Exception::Error(v8::String::New(error_msg.c_str()))); return false; } if (ExtensionAPI::GetSharedInstance()->IsPrivileged(function_name) && context->context_type() != Feature::BLESSED_EXTENSION_CONTEXT) { static const char kMessage[] = "%s can only be used in an extension process."; std::string error_msg = base::StringPrintf(kMessage, function_name.c_str()); v8::ThrowException( v8::Exception::Error(v8::String::New(error_msg.c_str()))); return false; } // We should never end up with sandboxed contexts trying to invoke extension // APIs, they don't get extension bindings injected. If we end up here it // means that a sandboxed page somehow managed to invoke an API anyway, so // we should abort. WebKit::WebFrame* frame = context->web_frame(); ExtensionURLInfo url_info(frame->document().securityOrigin(), UserScriptSlave::GetDataSourceURLForFrame(frame)); CHECK(!extensions_.IsSandboxedPage(url_info)); return true; }