Commit 70527de5 authored by ericzeng@chromium.org's avatar ericzeng@chromium.org

Implement support for <extensionoptions> embedding in WebUI

Modify permissions and checks in GuestView, WebUI, and
extensions to allow <extensionoptions> to be used within WebUI.
In the GuestView infrastructure, allow guest views in WebUI to
be created without extension ids. In extensions, allow extension
options-related APIs to be used in WebUI contexts. In WebUI,
relax the CSP to allow internal browser plugins.  


BUG=386842

Review URL: https://codereview.chromium.org/453613002

Cr-Commit-Position: refs/heads/master@{#289233}
git-svn-id: svn://svn.chromium.org/chrome/trunk/src@289233 0039d316-1c4b-4281-b951-d872f2087c98
parent 3da74d14
...@@ -40,9 +40,15 @@ bool GuestViewInternalCreateGuestFunction::RunAsync() { ...@@ -40,9 +40,15 @@ bool GuestViewInternalCreateGuestFunction::RunAsync() {
error_ = "Guest views can only be embedded in web content"; error_ = "Guest views can only be embedded in web content";
return false; return false;
} }
// If the guest is an <extensionoptions> to be embedded in a WebUI, then
// there is no extension, and extension() will be null. Use an empty string
// instead.
std::string embedder_extension_id;
if (extension())
embedder_extension_id = extension_id();
guest_view_manager->CreateGuest(view_type, guest_view_manager->CreateGuest(view_type,
extension_id(), embedder_extension_id,
embedder_web_contents, embedder_web_contents,
*create_params, *create_params,
callback); callback);
......
...@@ -18,6 +18,8 @@ ...@@ -18,6 +18,8 @@
#include "extensions/browser/event_router.h" #include "extensions/browser/event_router.h"
#include "extensions/common/api/test.h" #include "extensions/common/api/test.h"
#include "extensions/common/extension.h" #include "extensions/common/extension.h"
#include "extensions/common/feature_switch.h"
#include "extensions/common/switches.h"
#include "testing/gtest/include/gtest/gtest.h" #include "testing/gtest/include/gtest/gtest.h"
namespace extensions { namespace extensions {
...@@ -108,6 +110,18 @@ class ExtensionWebUITest : public ExtensionApiTest { ...@@ -108,6 +110,18 @@ class ExtensionWebUITest : public ExtensionApiTest {
base::Bind(&FindFrame, frame_url, &frame_host)); base::Bind(&FindFrame, frame_url, &frame_host));
return frame_host; return frame_host;
} }
virtual void SetUpCommandLine(CommandLine* command_line) OVERRIDE {
FeatureSwitch::ScopedOverride enable_options(
FeatureSwitch::embedded_extension_options(), true);
// Need to add a command line flag as well as a FeatureSwitch because the
// FeatureSwitch is not copied over to the renderer process from the
// browser process.
command_line->AppendSwitch(switches::kEnableEmbeddedExtensionOptions);
ExtensionApiTest::SetUpCommandLine(command_line);
}
scoped_ptr<FeatureSwitch::ScopedOverride> enable_options_;
}; };
IN_PROC_BROWSER_TEST_F(ExtensionWebUITest, SanityCheckAvailableAPIs) { IN_PROC_BROWSER_TEST_F(ExtensionWebUITest, SanityCheckAvailableAPIs) {
...@@ -167,6 +181,23 @@ IN_PROC_BROWSER_TEST_F(ExtensionWebUITest, RuntimeLastError) { ...@@ -167,6 +181,23 @@ IN_PROC_BROWSER_TEST_F(ExtensionWebUITest, RuntimeLastError) {
EXPECT_EQ("true", listener->message()); EXPECT_EQ("true", listener->message());
} }
IN_PROC_BROWSER_TEST_F(ExtensionWebUITest, CanEmbedExtensionOptions) {
scoped_ptr<ExtensionTestMessageListener> listener(
new ExtensionTestMessageListener("ready", true));
const Extension* extension = InstallExtension(
test_data_dir_.AppendASCII("extension_options").AppendASCII("embed_self"),
1);
ASSERT_TRUE(extension);
ASSERT_TRUE(RunTestOnExtensions("can_embed_extension_options.js"));
ASSERT_TRUE(listener->WaitUntilSatisfied());
listener->Reply(extension->id());
listener.reset(new ExtensionTestMessageListener("guest loaded", false));
ASSERT_TRUE(listener->WaitUntilSatisfied());
}
} // namespace } // namespace
} // namespace extensions } // namespace extensions
...@@ -64,7 +64,9 @@ void ExtensionOptionsGuest::CreateWebContents( ...@@ -64,7 +64,9 @@ void ExtensionOptionsGuest::CreateWebContents(
return; return;
} }
if (extension_id != embedder_extension_id) { if (extensions::Extension::IdIsValid(embedder_extension_id) &&
extension_id != embedder_extension_id) {
// Extensions cannot embed other extensions' options pages.
callback.Run(NULL); callback.Run(NULL);
return; return;
} }
......
...@@ -84,6 +84,10 @@ ExtensionsUI::ExtensionsUI(content::WebUI* web_ui) : WebUIController(web_ui) { ...@@ -84,6 +84,10 @@ ExtensionsUI::ExtensionsUI(content::WebUI* web_ui) : WebUIController(web_ui) {
web_ui->AddMessageHandler(new MetricsHandler()); web_ui->AddMessageHandler(new MetricsHandler());
// Need to allow <object> elements so that the <extensionoptions> browser
// plugin can be loaded within chrome://extensions.
source->OverrideContentSecurityPolicyObjectSrc("object-src 'self';");
content::WebUIDataSource::Add(profile, source); content::WebUIDataSource::Add(profile, source);
} }
......
...@@ -375,11 +375,16 @@ ...@@ -375,11 +375,16 @@
"extension.sendRequest": { "extension.sendRequest": {
"contexts": ["blessed_extension", "unblessed_extension", "content_script"] "contexts": ["blessed_extension", "unblessed_extension", "content_script"]
}, },
"extensionOptionsInternal": { "extensionOptionsInternal": [{
"internal": true, "internal": true,
"contexts": ["blessed_extension"], "contexts": ["blessed_extension"],
"dependencies": ["permission:embeddedExtensionOptions"] "dependencies": ["permission:embeddedExtensionOptions"]
}, }, {
"internal": true,
"channel": "trunk",
"contexts": ["webui"],
"matches": ["chrome://extensions-frame/*", "chrome://extensions/*"]
}],
// This is not a real API, only here for documentation purposes. // This is not a real API, only here for documentation purposes.
// See http://crbug.com/275944 for background. // See http://crbug.com/275944 for background.
"extensionsManifestTypes": { "extensionsManifestTypes": {
...@@ -453,6 +458,11 @@ ...@@ -453,6 +458,11 @@
"internal": true, "internal": true,
"dependencies": ["permission:webview"], "dependencies": ["permission:webview"],
"contexts": ["unblessed_extension"] "contexts": ["unblessed_extension"]
}, {
"internal": true,
"channel": "trunk",
"contexts": ["webui"],
"matches": ["chrome://extensions-frame/*", "chrome://extensions/*"]
}], }],
"hangoutsPrivate": { "hangoutsPrivate": {
"channel": "stable", "channel": "stable",
......
...@@ -245,6 +245,14 @@ bool ShouldUseJavaScriptSettingForPlugin(const WebPluginInfo& plugin) { ...@@ -245,6 +245,14 @@ bool ShouldUseJavaScriptSettingForPlugin(const WebPluginInfo& plugin) {
return false; return false;
} }
void IsGuestViewApiAvailableToScriptContext(
bool* api_is_available,
extensions::ScriptContext* context) {
if (context->GetAvailability("guestViewInternal").is_available()) {
*api_is_available = true;
}
}
} // namespace } // namespace
ChromeContentRendererClient::ChromeContentRendererClient() { ChromeContentRendererClient::ChromeContentRendererClient() {
...@@ -520,20 +528,13 @@ bool ChromeContentRendererClient::OverrideCreatePlugin( ...@@ -520,20 +528,13 @@ bool ChromeContentRendererClient::OverrideCreatePlugin(
WebPlugin** plugin) { WebPlugin** plugin) {
std::string orig_mime_type = params.mimeType.utf8(); std::string orig_mime_type = params.mimeType.utf8();
if (orig_mime_type == content::kBrowserPluginMimeType) { if (orig_mime_type == content::kBrowserPluginMimeType) {
WebDocument document = frame->document(); bool guest_view_api_available = false;
const Extension* extension = extension_dispatcher_->script_context_set().ForEach(
GetExtensionByOrigin(document.securityOrigin()); render_frame->GetRenderView(),
if (extension) { base::Bind(&IsGuestViewApiAvailableToScriptContext,
const extensions::APIPermission::ID perms[] = { &guest_view_api_available));
extensions::APIPermission::kAppView, if (guest_view_api_available)
extensions::APIPermission::kEmbeddedExtensionOptions, return false;
extensions::APIPermission::kWebView,
};
for (size_t i = 0; i < arraysize(perms); ++i) {
if (extension->permissions_data()->HasAPIPermission(perms[i]))
return false;
}
}
} }
ChromeViewHostMsg_GetPluginInfo_Output output; ChromeViewHostMsg_GetPluginInfo_Output output;
......
...@@ -289,6 +289,7 @@ void ChromeExtensionsDispatcherDelegate::RequireAdditionalModules( ...@@ -289,6 +289,7 @@ void ChromeExtensionsDispatcherDelegate::RequireAdditionalModules(
// The API will be automatically set up when first used. // The API will be automatically set up when first used.
if (context_type == extensions::Feature::BLESSED_EXTENSION_CONTEXT || if (context_type == extensions::Feature::BLESSED_EXTENSION_CONTEXT ||
context_type == extensions::Feature::UNBLESSED_EXTENSION_CONTEXT) { context_type == extensions::Feature::UNBLESSED_EXTENSION_CONTEXT) {
// TODO(fsamuel): Use context->GetAvailability("webViewInternal").
if (extension->permissions_data()->HasAPIPermission( if (extension->permissions_data()->HasAPIPermission(
extensions::APIPermission::kWebView)) { extensions::APIPermission::kWebView)) {
module_system->Require("webView"); module_system->Require("webView");
...@@ -313,6 +314,7 @@ void ChromeExtensionsDispatcherDelegate::RequireAdditionalModules( ...@@ -313,6 +314,7 @@ void ChromeExtensionsDispatcherDelegate::RequireAdditionalModules(
} }
if (context_type == extensions::Feature::BLESSED_EXTENSION_CONTEXT) { if (context_type == extensions::Feature::BLESSED_EXTENSION_CONTEXT) {
// TODO(fsamuel): Use context->GetAvailability("appViewInternal").
if (CommandLine::ForCurrentProcess()->HasSwitch(switches::kEnableAppView) && if (CommandLine::ForCurrentProcess()->HasSwitch(switches::kEnableAppView) &&
extension->permissions_data()->HasAPIPermission( extension->permissions_data()->HasAPIPermission(
extensions::APIPermission::kAppView)) { extensions::APIPermission::kAppView)) {
...@@ -322,10 +324,8 @@ void ChromeExtensionsDispatcherDelegate::RequireAdditionalModules( ...@@ -322,10 +324,8 @@ void ChromeExtensionsDispatcherDelegate::RequireAdditionalModules(
} }
} }
if (context_type == extensions::Feature::BLESSED_EXTENSION_CONTEXT && if (extensions::FeatureSwitch::embedded_extension_options()->IsEnabled() &&
extensions::FeatureSwitch::embedded_extension_options()->IsEnabled() && context->GetAvailability("extensionOptionsInternal").is_available()) {
extension->permissions_data()->HasAPIPermission(
extensions::APIPermission::kEmbeddedExtensionOptions)) {
module_system->Require("extensionOptions"); module_system->Require("extensionOptions");
} }
} }
......
// Copyright 2014 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.
// out/Debug/browser_tests
// --gtest_filter=ExtensionWebUITest.CanEmbedExtensionOptions
if (!chrome || !chrome.test || !chrome.test.sendMessage) {
console.error('chrome.test.sendMessage is unavailable on ' +
document.location.href);
domAutomationController.send(false);
return;
}
chrome.test.sendMessage('ready', function(reply) {
var extensionoptions = document.createElement('extensionoptions');
extensionoptions.addEventListener('load', function() {
chrome.test.sendMessage('guest loaded');
});
extensionoptions.setAttribute('extension', reply);
document.body.appendChild(extensionoptions);
});
domAutomationController.send(true);
...@@ -397,10 +397,6 @@ GuestViewBase::~GuestViewBase() { ...@@ -397,10 +397,6 @@ GuestViewBase::~GuestViewBase() {
void GuestViewBase::DispatchEventToEmbedder(Event* event) { void GuestViewBase::DispatchEventToEmbedder(Event* event) {
scoped_ptr<Event> event_ptr(event); scoped_ptr<Event> event_ptr(event);
if (!in_extension()) {
NOTREACHED();
return;
}
if (!attached()) { if (!attached()) {
pending_events_.push_back(linked_ptr<Event>(event_ptr.release())); pending_events_.push_back(linked_ptr<Event>(event_ptr.release()));
......
...@@ -210,10 +210,6 @@ void EventBindings::AttachFilteredEvent( ...@@ -210,10 +210,6 @@ void EventBindings::AttachFilteredEvent(
return; return;
std::string extension_id = context()->GetExtensionID(); std::string extension_id = context()->GetExtensionID();
if (extension_id.empty()) {
args.GetReturnValue().Set(static_cast<int32_t>(-1));
return;
}
scoped_ptr<base::DictionaryValue> filter; scoped_ptr<base::DictionaryValue> filter;
scoped_ptr<content::V8ValueConverter> converter( scoped_ptr<content::V8ValueConverter> converter(
...@@ -256,8 +252,6 @@ void EventBindings::DetachFilteredEvent( ...@@ -256,8 +252,6 @@ void EventBindings::DetachFilteredEvent(
bool is_manual = args[1]->BooleanValue(); bool is_manual = args[1]->BooleanValue();
std::string extension_id = context()->GetExtensionID(); std::string extension_id = context()->GetExtensionID();
if (extension_id.empty())
return;
int matcher_id = args[0]->Int32Value(); int matcher_id = args[0]->Int32Value();
EventFilter& event_filter = g_event_filter.Get(); EventFilter& event_filter = g_event_filter.Get();
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment