Commit be5f0078 authored by groby@chromium.org's avatar groby@chromium.org

Extension loading extracts intents from Manifest data.

BUG=none
TEST=ExtensionManifestTest.WebIntents


Review URL: http://codereview.chromium.org/7917006

git-svn-id: svn://svn.chromium.org/chrome/trunk/src@102409 0039d316-1c4b-4281-b951-d872f2087c98
parent ea8455ce
......@@ -109,9 +109,10 @@ TEST_F(ExtensionIconManagerTest, LoadRemoveLoad) {
static_cast<DictionaryValue*>(serializer.Deserialize(NULL, NULL)));
ASSERT_TRUE(manifest.get() != NULL);
std::string error;
scoped_refptr<Extension> extension(Extension::Create(
manifest_path.DirName(), Extension::INVALID, *manifest.get(),
Extension::STRICT_ERROR_CHECKS, NULL));
Extension::STRICT_ERROR_CHECKS, &error));
ASSERT_TRUE(extension.get());
TestIconManager icon_manager(this);
......
......@@ -44,6 +44,7 @@
#include "ui/base/l10n/l10n_util.h"
#include "ui/base/resource/resource_bundle.h"
#include "webkit/glue/image_decoder.h"
#include "webkit/glue/web_intent_service_data.h"
namespace keys = extension_manifest_keys;
namespace values = extension_manifest_values;
......@@ -1162,6 +1163,74 @@ bool Extension::LoadAppIsolation(const DictionaryValue* manifest,
return true;
}
bool Extension::LoadWebIntents(const base::DictionaryValue& manifest,
std::string* error) {
DCHECK(error);
if (!CommandLine::ForCurrentProcess()->HasSwitch(switches::kEnableWebIntents))
return true;
if (!manifest.HasKey(keys::kIntents))
return true;
DictionaryValue* all_intents = NULL;
if (!manifest.GetDictionary(keys::kIntents, &all_intents)) {
*error = errors::kInvalidIntents;
return false;
}
std::string value;
for (DictionaryValue::key_iterator iter(all_intents->begin_keys());
iter != all_intents->end_keys(); ++iter) {
WebIntentServiceData intent;
DictionaryValue* one_intent = NULL;
if (!all_intents->GetDictionaryWithoutPathExpansion(*iter, &one_intent)) {
*error = errors::kInvalidIntent;
return false;
}
intent.action = UTF8ToUTF16(*iter);
// TODO(groby): Support an array of types.
if (one_intent->HasKey(keys::kIntentType) &&
!one_intent->GetString(keys::kIntentType, &intent.type)) {
*error = errors::kInvalidIntentType;
return false;
}
if (one_intent->HasKey(keys::kIntentPath)) {
if (!one_intent->GetString(keys::kIntentPath, &value)) {
*error = errors::kInvalidIntentPath;
return false;
}
intent.service_url = GetResourceURL(value);
}
if (one_intent->HasKey(keys::kIntentTitle) &&
!one_intent->GetString(keys::kIntentTitle, &intent.title)) {
*error = errors::kInvalidIntentTitle;
return false;
}
if (one_intent->HasKey(keys::kIntentDisposition)) {
if (!one_intent->GetString(keys::kIntentDisposition, &value) ||
(value != values::kIntentDispositionWindow &&
value != values::kIntentDispositionInline)) {
*error = errors::kInvalidIntentDisposition;
return false;
}
if (value == values::kIntentDispositionInline)
intent.disposition = WebIntentServiceData::DISPOSITION_INLINE;
else
intent.disposition = WebIntentServiceData::DISPOSITION_WINDOW;
}
intents_.push_back(intent);
}
return true;
}
bool Extension::EnsureNotHybridApp(const DictionaryValue* manifest,
std::string* error) {
if (web_extent().is_empty())
......@@ -2272,6 +2341,10 @@ bool Extension::InitFromValue(const DictionaryValue& source, int flags,
}
}
// Initialize web intents (optional).
if (!LoadWebIntents(source, error))
return false;
// Initialize incognito behavior. Apps default to split mode, extensions
// default to spanning.
incognito_split_mode_ = is_app();
......
......@@ -25,6 +25,7 @@
#include "chrome/common/extensions/url_pattern_set.h"
#include "googleurl/src/gurl.h"
#include "ui/gfx/size.h"
#include "webkit/glue/web_intent_service_data.h"
class ExtensionAction;
class ExtensionResource;
......@@ -533,6 +534,7 @@ class Extension : public base::RefCountedThreadSafe<Extension> {
bool incognito_split_mode() const { return incognito_split_mode_; }
bool offline_enabled() const { return offline_enabled_; }
const std::vector<TtsVoice>& tts_voices() const { return tts_voices_; }
const std::vector<WebIntentServiceData>& intents() const { return intents_; }
bool wants_file_access() const { return wants_file_access_; }
int creation_flags() const { return creation_flags_; }
......@@ -641,6 +643,8 @@ class Extension : public base::RefCountedThreadSafe<Extension> {
std::string* error);
bool LoadAppIsolation(const base::DictionaryValue* manifest,
std::string* error);
bool LoadWebIntents(const base::DictionaryValue& manifest,
std::string* error);
bool EnsureNotHybridApp(const base::DictionaryValue* manifest,
std::string* error);
......@@ -843,6 +847,9 @@ class Extension : public base::RefCountedThreadSafe<Extension> {
// List of text-to-speech voices that this extension provides, if any.
std::vector<TtsVoice> tts_voices_;
// List of intents that this extension provides, if any.
std::vector<WebIntentServiceData> intents_;
// Whether the extension has host permissions or user script patterns that
// imply access to file:/// scheme URLs (the user may not have actually
// granted it that access).
......
......@@ -38,6 +38,11 @@ const char* kId = "id";
const char* kIncognito = "incognito";
const char* kIncludeGlobs = "include_globs";
const char* kInputComponents = "input_components";
const char* kIntents = "intents";
const char* kIntentType = "type";
const char* kIntentPath = "path";
const char* kIntentTitle = "title";
const char* kIntentDisposition = "disposition";
const char* kIsolation = "app.isolation";
const char* kJs = "js";
const char* kKeycode = "keyCode";
......@@ -113,6 +118,8 @@ const char* kWebURLs = "app.urls";
namespace extension_manifest_values {
const char* kIncognitoSplit = "split";
const char* kIncognitoSpanning = "spanning";
const char* kIntentDispositionWindow = "window";
const char* kIntentDispositionInline = "inline";
const char* kIsolatedStorage = "storage";
const char* kRunAtDocumentStart = "document_start";
const char* kRunAtDocumentEnd = "document_end";
......@@ -226,6 +233,18 @@ const char* kInvalidInputComponentShortcutKeycode =
"Invalid value for 'input_conponents[*].shortcutKey.keyCode";
const char* kInvalidInputComponentType =
"Invalid value for 'input_conponents[*].type";
const char* kInvalidIntent =
"Invalid value for intents[*]";
const char* kInvalidIntentDisposition =
"Invalid value for intents[*].disposition";
const char* kInvalidIntentPath =
"Invalid value for intents[*].path";
const char* kInvalidIntents =
"Invalid value for intents";
const char* kInvalidIntentType =
"Invalid value for intents[*].type";
const char* kInvalidIntentTitle =
"Invalid value for intents[*].title";
const char* kInvalidIsolation =
"Invalid value for 'app.isolation'.";
const char* kInvalidIsolationValue =
......
......@@ -39,6 +39,11 @@ namespace extension_manifest_keys {
extern const char* kIncognito;
extern const char* kIncludeGlobs;
extern const char* kInputComponents;
extern const char* kIntents;
extern const char* kIntentType;
extern const char* kIntentPath;
extern const char* kIntentTitle;
extern const char* kIntentDisposition;
extern const char* kIsolation;
extern const char* kJs;
extern const char* kKeycode;
......@@ -115,6 +120,8 @@ namespace extension_manifest_keys {
namespace extension_manifest_values {
extern const char* kIncognitoSplit;
extern const char* kIncognitoSpanning;
extern const char* kIntentDispositionWindow;
extern const char* kIntentDispositionInline;
extern const char* kIsolatedStorage;
extern const char* kLaunchContainerPanel;
extern const char* kLaunchContainerTab;
......@@ -176,6 +183,12 @@ namespace extension_manifest_errors {
extern const char* kInvalidInputComponentShortcutKey;
extern const char* kInvalidInputComponentShortcutKeycode;
extern const char* kInvalidInputComponentType;
extern const char* kInvalidIntent;
extern const char* kInvalidIntentDisposition;
extern const char* kInvalidIntentPath;
extern const char* kInvalidIntents;
extern const char* kInvalidIntentType;
extern const char* kInvalidIntentTitle;
extern const char* kInvalidIsolation;
extern const char* kInvalidIsolationValue;
extern const char* kInvalidJs;
......
......@@ -747,6 +747,51 @@ TEST_F(ExtensionManifestTest, TtsEngine) {
EXPECT_EQ(3U, extension->tts_voices()[0].event_types.size());
}
TEST_F(ExtensionManifestTest, WebIntents) {
CommandLine::ForCurrentProcess()->AppendSwitch("--enable-web-intents");
LoadAndExpectError("intent_invalid_1.json",
extension_manifest_errors::kInvalidIntents);
LoadAndExpectError("intent_invalid_2.json",
extension_manifest_errors::kInvalidIntent);
LoadAndExpectError("intent_invalid_3.json",
extension_manifest_errors::kInvalidIntentPath);
LoadAndExpectError("intent_invalid_4.json",
extension_manifest_errors::kInvalidIntentDisposition);
LoadAndExpectError("intent_invalid_5.json",
extension_manifest_errors::kInvalidIntentType);
LoadAndExpectError("intent_invalid_6.json",
extension_manifest_errors::kInvalidIntentTitle);
scoped_refptr<Extension> extension(
LoadAndExpectSuccess("intent_valid.json"));
ASSERT_TRUE(extension.get() != NULL);
ASSERT_EQ(1u, extension->intents().size());
EXPECT_EQ("image/png", UTF16ToUTF8(extension->intents()[0].type));
EXPECT_EQ("http://webintents.org/share",
UTF16ToUTF8(extension->intents()[0].action));
EXPECT_EQ("chrome-extension", extension->intents()[0].service_url.scheme());
EXPECT_EQ("///services/share", extension->intents()[0].service_url.path());
EXPECT_EQ("Sample Sharing Intent",
UTF16ToUTF8(extension->intents()[0].title));
EXPECT_EQ(WebIntentServiceData::DISPOSITION_INLINE,
extension->intents()[0].disposition);
// Verify that optional fields are filled with defaults.
extension = LoadAndExpectSuccess("intent_valid_minimal.json");
ASSERT_TRUE(extension.get() != NULL);
ASSERT_EQ(1u, extension->intents().size());
EXPECT_EQ("", UTF16ToUTF8(extension->intents()[0].type));
EXPECT_EQ("http://webintents.org/share",
UTF16ToUTF8(extension->intents()[0].action));
EXPECT_TRUE(extension->intents()[0].service_url.is_empty());
EXPECT_EQ("", UTF16ToUTF8(extension->intents()[0].title));
EXPECT_EQ(WebIntentServiceData::DISPOSITION_WINDOW,
extension->intents()[0].disposition);
}
TEST_F(ExtensionManifestTest, ForbidPortsInPermissions) {
// Loading as a user would shoud not trigger an error.
LoadAndExpectSuccess("forbid_ports_in_permissions.json");
......
{
"name": "test",
"version": "1",
"intents": "shouldBeADict"
}
{
"name": "test",
"version": "1",
"intents": {
"http://webintents.org/" : "shouldBeADict"
}
}
{
"name": "test",
"version": "1",
"intents": {
"http://webintents.org/intent/share" : {
"path" : { "shouldBeA" : "string" }
}
}
}
{
"name": "test",
"version": "1",
"intents": {
"http://webintents.org/intent/share" : {
"disposition" : "gibberish"
}
}
}
{
"name": "test",
"version": "1",
"intents": {
"http://webintents.org/intent/share" : {
"type" : { "shouldBeA" : "string" }
}
}
}
{
"name": "test",
"version": "1",
"intents": {
"http://webintents.org/intent/share" : {
"title" : { "shouldBeA" : "string" }
}
}
}
{
"name": "test",
"version": "1",
"intents": {
"http://webintents.org/share" : {
"type" : "image/png",
"path" : "//services/share",
"title" : "Sample Sharing Intent",
"disposition" : "inline"
}
}
}
{
"name": "test",
"version": "1",
"intents": {
"http://webintents.org/share" : {
}
}
}
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