diff options
author | cduvall@chromium.org <cduvall@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2013-03-27 06:53:45 +0000 |
---|---|---|
committer | cduvall@chromium.org <cduvall@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2013-03-27 06:53:45 +0000 |
commit | d6e42fa5e90c85aa093b012b6836ed287411f652 (patch) | |
tree | 73823e4202084edd62cbeee9e48cc1ca04dbc278 /chrome | |
parent | e2f9fd10fc89e70f6aaad4901da3240b2a51691a (diff) | |
download | chromium_src-d6e42fa5e90c85aa093b012b6836ed287411f652.zip chromium_src-d6e42fa5e90c85aa093b012b6836ed287411f652.tar.gz chromium_src-d6e42fa5e90c85aa093b012b6836ed287411f652.tar.bz2 |
Implement API features for the Extension API feature system
Now features can be declared in the _api_features.json file, and dependencies
of the form "api:<api_name>" will refer to these features.
This patch also prevents schemas for APIs using the feature system from being
loaded when calling ExtensionAPI::IsAvailable().
TBR=ben@chromium.org
BUG=55316, 120070
Review URL: https://chromiumcodereview.appspot.com/12846011
git-svn-id: svn://svn.chromium.org/chrome/trunk/src@190836 0039d316-1c4b-4281-b951-d872f2087c98
Diffstat (limited to 'chrome')
28 files changed, 396 insertions, 307 deletions
diff --git a/chrome/chrome_common.gypi b/chrome/chrome_common.gypi index de40865..044a1d8 100644 --- a/chrome/chrome_common.gypi +++ b/chrome/chrome_common.gypi @@ -223,6 +223,8 @@ 'common/extensions/extension_set.h', 'common/extensions/feature_switch.cc', 'common/extensions/feature_switch.h', + 'common/extensions/features/api_feature.cc', + 'common/extensions/features/api_feature.h', 'common/extensions/features/base_feature_provider.cc', 'common/extensions/features/base_feature_provider.h', 'common/extensions/features/complex_feature.cc', diff --git a/chrome/common/common_resources.grd b/chrome/common/common_resources.grd index bc7c186..06f100f 100644 --- a/chrome/common/common_resources.grd +++ b/chrome/common/common_resources.grd @@ -9,6 +9,7 @@ </outputs> <release seq="1"> <includes> + <include name="IDR_EXTENSION_API_FEATURES" file="extensions\api\_api_features.json" type="BINDATA" /> <include name="IDR_EXTENSION_MANIFEST_FEATURES" file="extensions\api\_manifest_features.json" type="BINDATA" /> <include name="IDR_EXTENSION_PERMISSION_FEATURES" file="extensions\api\_permission_features.json" type="BINDATA" /> <include name="IDR_WEB_APP_SCHEMA" file="web_app_schema.json" type="BINDATA" /> diff --git a/chrome/common/extensions/api/_api_features.json b/chrome/common/extensions/api/_api_features.json new file mode 100644 index 0000000..8657cdc --- /dev/null +++ b/chrome/common/extensions/api/_api_features.json @@ -0,0 +1,19 @@ +// 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. + +{ + "bookmarks": { + "channel": "stable", + "dependencies": ["permission:bookmarks"], + "contexts": ["blessed_extension"] + }, + "webstore": { + // Hosted apps can use the webstore API from within a blessed context. + "channel": "stable", + "extension_types": ["hosted_app"], + "contexts": ["blessed_extension", "web_page"], + // Any webpage can use the webstore API. + "matches": [ "http://*/*", "https://*/*" ] + } +} diff --git a/chrome/common/extensions/api/bookmarks.json b/chrome/common/extensions/api/bookmarks.json index 3ade90c..61ce218 100644 --- a/chrome/common/extensions/api/bookmarks.json +++ b/chrome/common/extensions/api/bookmarks.json @@ -5,10 +5,6 @@ [ { "namespace": "bookmarks", - "uses_feature_system": true, - "channel": "stable", - "dependencies": ["permission:bookmarks"], - "contexts": ["blessed_extension"], "properties": { "MAX_WRITE_OPERATIONS_PER_HOUR": { "value": 100, diff --git a/chrome/common/extensions/api/extension_api.cc b/chrome/common/extensions/api/extension_api.cc index c2a9cce..d2c2b78 100644 --- a/chrome/common/extensions/api/extension_api.cc +++ b/chrome/common/extensions/api/extension_api.cc @@ -234,11 +234,6 @@ void ExtensionAPI::SplitDependencyName(const std::string& full_name, *feature_name = full_name.substr(colon_index + 1); } -bool ExtensionAPI::UsesFeatureSystem(const std::string& full_name) { - std::string api_name = GetAPINameFromFullName(full_name, NULL); - return features_.find(api_name) != features_.end(); -} - void ExtensionAPI::LoadSchema(const std::string& name, const base::StringPiece& schema) { scoped_ptr<ListValue> schema_list(LoadSchemaList(name, schema)); @@ -332,10 +327,6 @@ void ExtensionAPI::LoadSchema(const std::string& name, } ExtensionAPI::ExtensionAPI() { - RegisterDependencyProvider("api", this); - - // TODO(aa): Can remove this when all JSON files are converted. - RegisterDependencyProvider("", this); } ExtensionAPI::~ExtensionAPI() { @@ -343,6 +334,8 @@ ExtensionAPI::~ExtensionAPI() { void ExtensionAPI::InitDefaultConfiguration() { RegisterDependencyProvider( + "api", BaseFeatureProvider::GetApiFeatures()); + RegisterDependencyProvider( "manifest", BaseFeatureProvider::GetManifestFeatures()); RegisterDependencyProvider( "permission", BaseFeatureProvider::GetPermissionFeatures()); @@ -422,27 +415,34 @@ Feature::Availability ExtensionAPI::IsAvailable(const std::string& full_name, const Extension* extension, Feature::Context context, const GURL& url) { - std::set<std::string> dependency_names; - dependency_names.insert(full_name); - ResolveDependencies(&dependency_names); + std::string feature_type; + std::string feature_name; + SplitDependencyName(full_name, &feature_type, &feature_name); + + std::string child_name; + std::string api_name = GetAPINameFromFullName(feature_name, &child_name); + + Feature* feature = GetFeatureDependency(full_name); // Check APIs not using the feature system first. - if (!UsesFeatureSystem(full_name)) { - return IsNonFeatureAPIAvailable(full_name, context, extension, url) ? + if (!feature) { + return IsNonFeatureAPIAvailable(api_name, context, extension, url) ? Feature::CreateAvailability(Feature::IS_AVAILABLE, "") : Feature::CreateAvailability(Feature::INVALID_CONTEXT, kUnavailableMessage); } - for (std::set<std::string>::iterator iter = dependency_names.begin(); - iter != dependency_names.end(); ++iter) { - Feature* feature = GetFeatureDependency(*iter); - CHECK(feature) << *iter; - - Feature::Availability availability = - feature->IsAvailableToContext(extension, context, url); - if (!availability.is_available()) - return availability; + Feature::Availability availability = + feature->IsAvailableToContext(extension, context, url); + if (!availability.is_available()) + return availability; + + for (std::set<std::string>::iterator iter = feature->dependencies().begin(); + iter != feature->dependencies().end(); ++iter) { + Feature::Availability dependency_availability = + IsAvailable(*iter, extension, context, url); + if (!dependency_availability.is_available()) + return dependency_availability; } return Feature::CreateAvailability(Feature::IS_AVAILABLE, ""); @@ -451,33 +451,21 @@ Feature::Availability ExtensionAPI::IsAvailable(const std::string& full_name, bool ExtensionAPI::IsPrivileged(const std::string& full_name) { std::string child_name; std::string api_name = GetAPINameFromFullName(full_name, &child_name); + Feature* feature = GetFeatureDependency(full_name); // First try to use the feature system. - Feature* feature(GetFeature(full_name)); if (feature) { - // An API is 'privileged' if it or any of its dependencies can only be run - // in a blessed context. - std::set<std::string> resolved_dependencies; - resolved_dependencies.insert(full_name); - ResolveDependencies(&resolved_dependencies); - for (std::set<std::string>::iterator iter = resolved_dependencies.begin(); - iter != resolved_dependencies.end(); ++iter) { - Feature* dependency = GetFeatureDependency(*iter); - for (std::set<Feature::Context>::iterator context = - dependency->GetContexts()->begin(); - context != dependency->GetContexts()->end(); ++context) { - if (*context != Feature::BLESSED_EXTENSION_CONTEXT) - return false; - } - } - return true; + // An API is 'privileged' if it can only be run in a blessed context. + return feature->GetContexts()->size() == + feature->GetContexts()->count(Feature::BLESSED_EXTENSION_CONTEXT); } + // Get the schema now to populate |completely_unprivileged_apis_|. + const DictionaryValue* schema = GetSchema(api_name); // If this API hasn't been converted yet, fall back to the old system. if (completely_unprivileged_apis_.count(api_name)) return false; - const DictionaryValue* schema = GetSchema(api_name); if (partially_unprivileged_apis_.count(api_name)) return IsChildNamePrivileged(schema, child_name); @@ -549,6 +537,8 @@ bool ExtensionAPI::IsNonFeatureAPIAvailable(const std::string& name, Feature::Context context, const Extension* extension, const GURL& url) { + // Make sure schema is loaded. + GetSchema(name); switch (context) { case Feature::UNSPECIFIED_CONTEXT: break; @@ -597,36 +587,6 @@ std::set<std::string> ExtensionAPI::GetAllAPINames() { return result; } -Feature* ExtensionAPI::GetFeature(const std::string& full_name) { - // Ensure it's loaded. - GetSchema(full_name); - - std::string child_name; - std::string api_namespace = GetAPINameFromFullName(full_name, &child_name); - - APIFeatureMap::iterator feature_map = features_.find(api_namespace); - if (feature_map == features_.end()) - return NULL; - - Feature* result = NULL; - FeatureMap::iterator child_feature = feature_map->second->find(child_name); - if (child_feature != feature_map->second->end()) { - result = child_feature->second.get(); - } else { - FeatureMap::iterator parent_feature = feature_map->second->find(""); - CHECK(parent_feature != feature_map->second->end()); - result = parent_feature->second.get(); - } - - if (result->GetContexts()->empty()) { - LOG(ERROR) << "API feature '" << full_name - << "' must specify at least one context."; - return NULL; - } - - return result; -} - Feature* ExtensionAPI::GetFeatureDependency(const std::string& full_name) { std::string feature_type; std::string feature_name; @@ -634,11 +594,16 @@ Feature* ExtensionAPI::GetFeatureDependency(const std::string& full_name) { FeatureProviderMap::iterator provider = dependency_providers_.find(feature_type); - CHECK(provider != dependency_providers_.end()) << full_name; + if (provider == dependency_providers_.end()) + return NULL; Feature* feature = provider->second->GetFeature(feature_name); - CHECK(feature) << full_name; - + // Try getting the feature for the parent API, if this was a child. + if (!feature) { + std::string child_name; + feature = provider->second->GetFeature( + GetAPINameFromFullName(feature_name, &child_name)); + } return feature; } @@ -678,47 +643,6 @@ bool ExtensionAPI::IsAPIAllowed(const std::string& name, extension->optional_permission_set()->HasAnyAccessToAPI(name); } -void ExtensionAPI::ResolveDependencies(std::set<std::string>* out) { - std::set<std::string> missing_dependencies; - for (std::set<std::string>::iterator i = out->begin(); i != out->end(); ++i) - GetMissingDependencies(*i, *out, &missing_dependencies); - - while (missing_dependencies.size()) { - std::string next = *missing_dependencies.begin(); - missing_dependencies.erase(next); - out->insert(next); - GetMissingDependencies(next, *out, &missing_dependencies); - } -} - -void ExtensionAPI::GetMissingDependencies( - const std::string& api_name, - const std::set<std::string>& excluding, - std::set<std::string>* out) { - std::string feature_type; - std::string feature_name; - SplitDependencyName(api_name, &feature_type, &feature_name); - - // Only API features can have dependencies for now. - if (feature_type != "api") - return; - - const DictionaryValue* schema = GetSchema(feature_name); - CHECK(schema) << "Schema for " << feature_name << " not found"; - - const ListValue* dependencies = NULL; - if (!schema->GetList("dependencies", &dependencies)) - return; - - for (size_t i = 0; i < dependencies->GetSize(); ++i) { - std::string dependency_name; - if (dependencies->GetString(i, &dependency_name) && - !excluding.count(dependency_name)) { - out->insert(dependency_name); - } - } -} - bool ExtensionAPI::IsPrivilegedAPI(const std::string& name) { return completely_unprivileged_apis_.count(name) || partially_unprivileged_apis_.count(name); diff --git a/chrome/common/extensions/api/extension_api.h b/chrome/common/extensions/api/extension_api.h index 924ee8c..2e1f7ef 100644 --- a/chrome/common/extensions/api/extension_api.h +++ b/chrome/common/extensions/api/extension_api.h @@ -37,7 +37,7 @@ class Feature; // WARNING: This class is accessed on multiple threads in the browser process // (see ExtensionFunctionDispatcher). No state should be modified after // construction. -class ExtensionAPI : public FeatureProvider { +class ExtensionAPI { public: // Returns a single shared instance of this class. This is the typical use // case in Chrome. @@ -88,11 +88,6 @@ class ExtensionAPI : public FeatureProvider { std::set<std::string> GetAllAPINames(); - // Gets a Feature object describing the API with the specified |full_name|. - // This can be either an API namespace (like history, or - // experimental.bookmarks), or it can be an individual function or event. - virtual Feature* GetFeature(const std::string& full_name) OVERRIDE; - // Splits a full name from the extension API into its API and child name // parts. Some examples: // @@ -107,6 +102,10 @@ class ExtensionAPI : public FeatureProvider { void InitDefaultConfiguration(); + // Gets a feature from any dependency provider registered with ExtensionAPI. + // Returns NULL if the feature could not be found. + Feature* GetFeatureDependency(const std::string& dependency_name); + private: friend struct DefaultSingletonTraits<ExtensionAPI>; @@ -134,24 +133,6 @@ class ExtensionAPI : public FeatureProvider { const Extension* extension, const GURL& url); - // Returns true if the API uses the feature system. - bool UsesFeatureSystem(const std::string& full_name); - - // Gets a feature from any dependency provider. - Feature* GetFeatureDependency(const std::string& dependency_name); - - // Adds dependent schemas to |out| as determined by the "dependencies" - // property. - // TODO(aa): Consider making public and adding tests. - void ResolveDependencies(std::set<std::string>* out); - - // Adds any APIs listed in "dependencies" found in the schema for |api_name| - // but not in |excluding| to |out|. - void GetMissingDependencies( - const std::string& api_name, - const std::set<std::string>& excluding, - std::set<std::string>* out); - // Checks if an API is *entirely* privileged. This won't include APIs such as // "storage" which is entirely unprivileged, nor "extension" which has // unprivileged components. diff --git a/chrome/common/extensions/api/extension_api_unittest.cc b/chrome/common/extensions/api/extension_api_unittest.cc index d18bd14..a7f99d0 100644 --- a/chrome/common/extensions/api/extension_api_unittest.cc +++ b/chrome/common/extensions/api/extension_api_unittest.cc @@ -9,13 +9,17 @@ #include "base/file_util.h" #include "base/files/file_path.h" +#include "base/json/json_reader.h" #include "base/json/json_writer.h" #include "base/memory/ref_counted.h" #include "base/memory/scoped_ptr.h" #include "base/path_service.h" +#include "base/stringprintf.h" #include "base/values.h" #include "chrome/common/chrome_paths.h" #include "chrome/common/extensions/extension.h" +#include "chrome/common/extensions/features/api_feature.h" +#include "chrome/common/extensions/features/base_feature_provider.h" #include "chrome/common/extensions/features/simple_feature.h" #include "chrome/common/extensions/manifest.h" #include "testing/gtest/include/gtest/gtest.h" @@ -23,25 +27,9 @@ namespace extensions { namespace { -class TestFeatureProvider : public FeatureProvider { - public: - explicit TestFeatureProvider(Feature::Context context) - : context_(context) { - } - - virtual Feature* GetFeature(const std::string& name) OVERRIDE { - SimpleFeature* result = new SimpleFeature(); - result->set_name(name); - result->extension_types()->insert(Manifest::TYPE_EXTENSION); - result->GetContexts()->insert(context_); - to_destroy_.push_back(make_linked_ptr(result)); - return result; - } - - private: - std::vector<linked_ptr<Feature> > to_destroy_; - Feature::Context context_; -}; +SimpleFeature* CreateAPIFeature() { + return new APIFeature(); +} TEST(ExtensionAPI, Creation) { ExtensionAPI* shared_instance = ExtensionAPI::GetSharedInstance(); @@ -125,46 +113,107 @@ TEST(ExtensionAPI, IsPrivileged) { TEST(ExtensionAPI, IsPrivilegedFeatures) { struct { - std::string filename; std::string api_full_name; bool expect_is_privilged; - Feature::Context test2_contexts; } test_data[] = { - { "is_privileged_features_1.json", "test", false, - Feature::UNSPECIFIED_CONTEXT }, - { "is_privileged_features_2.json", "test", true, - Feature::UNSPECIFIED_CONTEXT }, - { "is_privileged_features_3.json", "test", false, - Feature::UNSPECIFIED_CONTEXT }, - { "is_privileged_features_4.json", "test.bar", false, - Feature::UNSPECIFIED_CONTEXT }, - { "is_privileged_features_5.json", "test.bar", true, - Feature::BLESSED_EXTENSION_CONTEXT }, - { "is_privileged_features_5.json", "test.bar", false, - Feature::UNBLESSED_EXTENSION_CONTEXT } + { "test1", false }, + { "test1.foo", true }, + { "test2", true }, + { "test2.foo", false }, + { "test2.bar", false }, + { "test2.baz", true }, + { "test3", false }, + { "test3.foo", true }, + { "test4", false } }; - for (size_t i = 0; i < ARRAYSIZE_UNSAFE(test_data); ++i) { - base::FilePath manifest_path; - PathService::Get(chrome::DIR_TEST_DATA, &manifest_path); - manifest_path = manifest_path.AppendASCII("extensions") - .AppendASCII("extension_api_unittest") - .AppendASCII(test_data[i].filename); + base::FilePath api_features_path; + PathService::Get(chrome::DIR_TEST_DATA, &api_features_path); + api_features_path = api_features_path.AppendASCII("extensions") + .AppendASCII("extension_api_unittest") + .AppendASCII("privileged_api_features.json"); + + std::string api_features_str; + ASSERT_TRUE(file_util::ReadFileToString( + api_features_path, &api_features_str)) << "privileged_api_features.json"; - std::string manifest_str; - ASSERT_TRUE(file_util::ReadFileToString(manifest_path, &manifest_str)) - << test_data[i].filename; + base::DictionaryValue* value = static_cast<DictionaryValue*>( + base::JSONReader::Read(api_features_str)); + BaseFeatureProvider api_feature_provider(*value, CreateAPIFeature); + for (size_t i = 0; i < ARRAYSIZE_UNSAFE(test_data); ++i) { ExtensionAPI api; - api.RegisterSchema("test", manifest_str); + api.RegisterDependencyProvider("api", &api_feature_provider); + EXPECT_EQ(test_data[i].expect_is_privilged, + api.IsPrivileged(test_data[i].api_full_name)) << i; + } +} + +TEST(ExtensionAPI, APIFeatures) { + struct { + std::string api_full_name; + bool expect_is_available; + Feature::Context context; + GURL url; + } test_data[] = { + { "test1", false, Feature::WEB_PAGE_CONTEXT, GURL() }, + { "test1", true, Feature::BLESSED_EXTENSION_CONTEXT, GURL() }, + { "test1", true, Feature::UNBLESSED_EXTENSION_CONTEXT, GURL() }, + { "test1", true, Feature::CONTENT_SCRIPT_CONTEXT, GURL() }, + { "test2", true, Feature::WEB_PAGE_CONTEXT, GURL("http://google.com") }, + { "test2", false, Feature::BLESSED_EXTENSION_CONTEXT, + GURL("http://google.com") }, + { "test2.foo", false, Feature::WEB_PAGE_CONTEXT, + GURL("http://google.com") }, + { "test2.foo", true, Feature::CONTENT_SCRIPT_CONTEXT, GURL() }, + { "test3", false, Feature::WEB_PAGE_CONTEXT, GURL("http://google.com") }, + { "test3.foo", true, Feature::WEB_PAGE_CONTEXT, GURL("http://google.com") }, + { "test3.foo", true, Feature::BLESSED_EXTENSION_CONTEXT, GURL() }, + { "test4", true, Feature::BLESSED_EXTENSION_CONTEXT, GURL() }, + { "test4.foo", false, Feature::BLESSED_EXTENSION_CONTEXT, GURL() }, + { "test4.foo", false, Feature::UNBLESSED_EXTENSION_CONTEXT, GURL() }, + { "test4.foo.foo", true, Feature::CONTENT_SCRIPT_CONTEXT, GURL() }, + { "test5", true, Feature::WEB_PAGE_CONTEXT, GURL("http://foo.com") }, + { "test5", false, Feature::WEB_PAGE_CONTEXT, GURL("http://bar.com") }, + { "test5.blah", true, Feature::WEB_PAGE_CONTEXT, GURL("http://foo.com") }, + { "test5.blah", false, Feature::WEB_PAGE_CONTEXT, GURL("http://bar.com") }, + { "test6", false, Feature::BLESSED_EXTENSION_CONTEXT, GURL() }, + { "test6.foo", true, Feature::BLESSED_EXTENSION_CONTEXT, GURL() }, + { "test7", true, Feature::WEB_PAGE_CONTEXT, GURL("http://foo.com") }, + { "test7.foo", false, Feature::WEB_PAGE_CONTEXT, GURL("http://bar.com") }, + { "test7.foo", true, Feature::WEB_PAGE_CONTEXT, GURL("http://foo.com") }, + { "test7.bar", false, Feature::WEB_PAGE_CONTEXT, GURL("http://bar.com") }, + { "test7.bar", false, Feature::WEB_PAGE_CONTEXT, GURL("http://foo.com") } + }; + + base::FilePath api_features_path; + PathService::Get(chrome::DIR_TEST_DATA, &api_features_path); + api_features_path = api_features_path.AppendASCII("extensions") + .AppendASCII("extension_api_unittest") + .AppendASCII("api_features.json"); - TestFeatureProvider test2_provider(test_data[i].test2_contexts); - if (test_data[i].test2_contexts != Feature::UNSPECIFIED_CONTEXT) { - api.RegisterDependencyProvider("test2", &test2_provider); + std::string api_features_str; + ASSERT_TRUE(file_util::ReadFileToString( + api_features_path, &api_features_str)) << "api_features.json"; + + base::DictionaryValue* value = static_cast<DictionaryValue*>( + base::JSONReader::Read(api_features_str)); + BaseFeatureProvider api_feature_provider(*value, CreateAPIFeature); + + for (size_t i = 0; i < ARRAYSIZE_UNSAFE(test_data); ++i) { + ExtensionAPI api; + api.RegisterDependencyProvider("api", &api_feature_provider); + for (base::DictionaryValue::Iterator iter(*value); !iter.IsAtEnd(); + iter.Advance()) { + if (iter.key().find(".") == std::string::npos) + api.RegisterSchema(iter.key(), ""); } - EXPECT_EQ(test_data[i].expect_is_privilged, - api.IsPrivileged(test_data[i].api_full_name)) << i; + EXPECT_EQ(test_data[i].expect_is_available, + api.IsAvailable(test_data[i].api_full_name, + NULL, + test_data[i].context, + test_data[i].url).is_available()) << i; } } @@ -376,10 +425,10 @@ TEST(ExtensionAPI, GetAPINameFromFullName) { TEST(ExtensionAPI, DefaultConfigurationFeatures) { scoped_ptr<ExtensionAPI> api(ExtensionAPI::CreateWithDefaultConfiguration()); - SimpleFeature* bookmarks = - static_cast<SimpleFeature*>(api->GetFeature("bookmarks")); - SimpleFeature* bookmarks_create = - static_cast<SimpleFeature*>(api->GetFeature("bookmarks.create")); + SimpleFeature* bookmarks = static_cast<SimpleFeature*>( + api->GetFeatureDependency("api:bookmarks")); + SimpleFeature* bookmarks_create = static_cast<SimpleFeature*>( + api->GetFeatureDependency("api:bookmarks.create")); struct { SimpleFeature* feature; @@ -408,34 +457,30 @@ TEST(ExtensionAPI, DefaultConfigurationFeatures) { } TEST(ExtensionAPI, FeaturesRequireContexts) { - scoped_ptr<base::ListValue> schema1(new base::ListValue()); - base::DictionaryValue* feature_definition = new base::DictionaryValue(); - schema1->Append(feature_definition); - feature_definition->SetString("namespace", "test"); - feature_definition->SetBoolean("uses_feature_system", true); - - scoped_ptr<base::ListValue> schema2(schema1->DeepCopy()); - + // TODO(cduvall): Make this check API featues. + scoped_ptr<base::DictionaryValue> api_features1(new base::DictionaryValue()); + scoped_ptr<base::DictionaryValue> api_features2(new base::DictionaryValue()); + base::DictionaryValue* test1 = new base::DictionaryValue(); + base::DictionaryValue* test2 = new base::DictionaryValue(); base::ListValue* contexts = new base::ListValue(); contexts->Append(new base::StringValue("content_script")); - feature_definition->Set("contexts", contexts); + test1->Set("contexts", contexts); + api_features1->Set("test", test1); + api_features2->Set("test", test2); struct { - base::ListValue* schema; + base::DictionaryValue* api_features; bool expect_success; } test_data[] = { - { schema1.get(), true }, - { schema2.get(), false } + { api_features1.get(), true }, + { api_features2.get(), false } }; - for (size_t i = 0; i < ARRAYSIZE_UNSAFE(test_data); ++i) { - std::string schema_source; - base::JSONWriter::Write(test_data[i].schema, &schema_source); - - ExtensionAPI api; - api.RegisterSchema("test", base::StringPiece(schema_source)); - Feature* feature = api.GetFeature("test"); + for (size_t i = 0; i < ARRAYSIZE_UNSAFE(test_data); ++i) { + BaseFeatureProvider api_feature_provider(*test_data[i].api_features, + CreateAPIFeature); + Feature* feature = api_feature_provider.GetFeature("test"); EXPECT_EQ(test_data[i].expect_success, feature != NULL) << i; } } diff --git a/chrome/common/extensions/api/webstore.json b/chrome/common/extensions/api/webstore.json index 18b1c25..2b72c0c 100644 --- a/chrome/common/extensions/api/webstore.json +++ b/chrome/common/extensions/api/webstore.json @@ -5,17 +5,6 @@ [ { "namespace": "webstore", - - // Any webpage can use the webstore API. - "matches": [ "http://*/*", "https://*/*" ], - - // Hosted apps can use the webstore API from within a blessed context. - "uses_feature_system": true, - "channel": "stable", - "extension_types": ["hosted_app"], - "contexts": ["blessed_extension"], - "dependencies": [], - "functions": [ { "name": "install", diff --git a/chrome/common/extensions/features/api_feature.cc b/chrome/common/extensions/features/api_feature.cc new file mode 100644 index 0000000..09b8c19 --- /dev/null +++ b/chrome/common/extensions/features/api_feature.cc @@ -0,0 +1,26 @@ +// 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/common/extensions/features/api_feature.h" + +namespace extensions { + +APIFeature::APIFeature() { +} + +APIFeature::~APIFeature() { +} + +std::string APIFeature::Parse(const DictionaryValue* value) { + std::string error = SimpleFeature::Parse(value); + if (!error.empty()) + return error; + + if (GetContexts()->empty()) + return name() + ": API features must specify at least one context."; + + return ""; +} + +} // namespace diff --git a/chrome/common/extensions/features/api_feature.h b/chrome/common/extensions/features/api_feature.h new file mode 100644 index 0000000..e6484ae4 --- /dev/null +++ b/chrome/common/extensions/features/api_feature.h @@ -0,0 +1,22 @@ +// 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. + +#ifndef CHROME_COMMON_EXTENSIONS_FEATURES_API_FEATURE_H_ +#define CHROME_COMMON_EXTENSIONS_FEATURES_API_FEATURE_H_ + +#include "chrome/common/extensions/features/simple_feature.h" + +namespace extensions { + +class APIFeature : public SimpleFeature { + public: + APIFeature(); + virtual ~APIFeature(); + + virtual std::string Parse(const DictionaryValue* value) OVERRIDE; +}; + +} // extensions + +#endif // CHROME_COMMON_EXTENSIONS_FEATURES_API_FEATURE_H_ diff --git a/chrome/common/extensions/features/base_feature_provider.cc b/chrome/common/extensions/features/base_feature_provider.cc index 44cc454..3e2c5e5 100644 --- a/chrome/common/extensions/features/base_feature_provider.cc +++ b/chrome/common/extensions/features/base_feature_provider.cc @@ -6,6 +6,7 @@ #include "base/json/json_reader.h" #include "base/lazy_instance.h" +#include "chrome/common/extensions/features/api_feature.h" #include "chrome/common/extensions/features/complex_feature.h" #include "chrome/common/extensions/features/manifest_feature.h" #include "chrome/common/extensions/features/permission_feature.h" @@ -23,7 +24,11 @@ SimpleFeature* CreateFeature() { struct Static { Static() - : manifest_features( + : api_features( + LoadProvider("api", + &CreateFeature<APIFeature>, + IDR_EXTENSION_API_FEATURES)), + manifest_features( LoadProvider("manifest", &CreateFeature<ManifestFeature>, IDR_EXTENSION_MANIFEST_FEATURES)), @@ -33,6 +38,7 @@ struct Static { IDR_EXTENSION_PERMISSION_FEATURES)) { } + scoped_ptr<BaseFeatureProvider> api_features; scoped_ptr<BaseFeatureProvider> manifest_features; scoped_ptr<BaseFeatureProvider> permission_features; @@ -67,20 +73,10 @@ bool ParseFeature(const DictionaryValue* value, const std::string& name, SimpleFeature* feature) { feature->set_name(name); - feature->Parse(value); - - if (feature->extension_types()->empty()) { - LOG(ERROR) << name << ": Simple features must specify at least one " - << "value for extension_types."; - return false; - } - - if (!feature->GetContexts()->empty()) { - LOG(ERROR) << name << ": Simple features do not support contexts."; - return false; - } - - return true; + std::string error = feature->Parse(value); + if (!error.empty()) + LOG(ERROR) << error; + return error.empty(); } base::LazyInstance<Static> g_static = LAZY_INSTANCE_INITIALIZER; @@ -141,6 +137,11 @@ BaseFeatureProvider::~BaseFeatureProvider() { } // static +BaseFeatureProvider* BaseFeatureProvider::GetApiFeatures() { + return g_static.Get().api_features.get(); +} + +// static BaseFeatureProvider* BaseFeatureProvider::GetManifestFeatures() { return g_static.Get().manifest_features.get(); } diff --git a/chrome/common/extensions/features/base_feature_provider.h b/chrome/common/extensions/features/base_feature_provider.h index 1e71b0d..5a68f6f 100644 --- a/chrome/common/extensions/features/base_feature_provider.h +++ b/chrome/common/extensions/features/base_feature_provider.h @@ -26,6 +26,10 @@ class BaseFeatureProvider : public FeatureProvider { BaseFeatureProvider(const DictionaryValue& root, FeatureFactory factory); virtual ~BaseFeatureProvider(); + // Gets an instance for the _api_features.json file that is baked into + // Chrome as a resource. + static BaseFeatureProvider* GetApiFeatures(); + // Gets an instance for the _manifest_features.json file that is baked into // Chrome as a resource. static BaseFeatureProvider* GetManifestFeatures(); diff --git a/chrome/common/extensions/features/base_feature_provider_unittest.cc b/chrome/common/extensions/features/base_feature_provider_unittest.cc index 17284f9..df876dc 100644 --- a/chrome/common/extensions/features/base_feature_provider_unittest.cc +++ b/chrome/common/extensions/features/base_feature_provider_unittest.cc @@ -4,6 +4,7 @@ #include "chrome/common/extensions/features/base_feature_provider.h" +#include "chrome/common/extensions/features/permission_feature.h" #include "chrome/common/extensions/value_builder.h" #include "testing/gtest/include/gtest/gtest.h" @@ -14,6 +15,7 @@ using extensions::Extension; using extensions::Feature; using extensions::ListBuilder; using extensions::Manifest; +using extensions::PermissionFeature; using extensions::SimpleFeature; TEST(BaseFeatureProvider, ManifestFeatures) { @@ -100,6 +102,10 @@ TEST(BaseFeatureProvider, PermissionFeatures) { extension.get(), Feature::UNSPECIFIED_CONTEXT).result()); } +SimpleFeature* CreatePermissionFeature() { + return new PermissionFeature(); +} + TEST(BaseFeatureProvider, Validation) { scoped_ptr<base::DictionaryValue> value(new base::DictionaryValue()); @@ -116,14 +122,14 @@ TEST(BaseFeatureProvider, Validation) { value->Set("feature2", feature2); scoped_ptr<BaseFeatureProvider> provider( - new BaseFeatureProvider(*value, NULL)); + new BaseFeatureProvider(*value, CreatePermissionFeature)); // feature1 won't validate because it lacks an extension type. EXPECT_FALSE(provider->GetFeature("feature1")); // If we add one, it works. feature1->Set("extension_types", extension_types->DeepCopy()); - provider.reset(new BaseFeatureProvider(*value, NULL)); + provider.reset(new BaseFeatureProvider(*value, CreatePermissionFeature)); EXPECT_TRUE(provider->GetFeature("feature1")); // feature2 won't validate because of the presence of "contexts". @@ -131,7 +137,7 @@ TEST(BaseFeatureProvider, Validation) { // If we remove it, it works. feature2->Remove("contexts", NULL); - provider.reset(new BaseFeatureProvider(*value, NULL)); + provider.reset(new BaseFeatureProvider(*value, CreatePermissionFeature)); EXPECT_TRUE(provider->GetFeature("feature2")); } diff --git a/chrome/common/extensions/features/feature.cc b/chrome/common/extensions/features/feature.cc index f5eb37c..5a93bf8 100644 --- a/chrome/common/extensions/features/feature.cc +++ b/chrome/common/extensions/features/feature.cc @@ -61,6 +61,8 @@ Feature::Availability Feature::CreateAvailability(AvailabilityResult result, return Availability(result, message); } +Feature::Feature() {} + Feature::~Feature() {} } // namespace extensions diff --git a/chrome/common/extensions/features/feature.h b/chrome/common/extensions/features/feature.h index 53fa06b..748d04f 100644 --- a/chrome/common/extensions/features/feature.h +++ b/chrome/common/extensions/features/feature.h @@ -88,6 +88,7 @@ class Feature { const std::string message_; }; + Feature(); virtual ~Feature(); // Used by ChromeV8Context until the feature system is fully functional. @@ -124,6 +125,7 @@ class Feature { const std::string& name() const { return name_; } void set_name(const std::string& name) { name_ = name; } + const std::set<std::string>& dependencies() { return dependencies_; } // Gets the platform the code is currently running on. static Platform GetCurrentPlatform(); @@ -131,7 +133,6 @@ class Feature { // Gets the Feature::Location value for the specified Manifest::Location. static Location ConvertLocation(Manifest::Location extension_location); - // TODO(justinlin): Remove and move to APIFeature when it exists. virtual std::set<Context>* GetContexts() = 0; // Returns true if the feature is available to be parsed into a new extension @@ -167,6 +168,7 @@ class Feature { protected: std::string name_; + std::set<std::string> dependencies_; }; } // namespace extensions diff --git a/chrome/common/extensions/features/manifest_feature.cc b/chrome/common/extensions/features/manifest_feature.cc index 51d9fd5..cd4a53f 100644 --- a/chrome/common/extensions/features/manifest_feature.cc +++ b/chrome/common/extensions/features/manifest_feature.cc @@ -34,4 +34,20 @@ Feature::Availability ManifestFeature::IsAvailableToContext( return CreateAvailability(IS_AVAILABLE); } +std::string ManifestFeature::Parse(const DictionaryValue* value) { + std::string error = SimpleFeature::Parse(value); + if (!error.empty()) + return error; + + if (extension_types()->empty()) { + return name() + ": Manifest features must specify at least one " + + "value for extension_types."; + } + + if (!GetContexts()->empty()) + return name() + ": Manifest features do not support contexts."; + + return ""; +} + } // namespace diff --git a/chrome/common/extensions/features/manifest_feature.h b/chrome/common/extensions/features/manifest_feature.h index ca2060a..6fa75bc 100644 --- a/chrome/common/extensions/features/manifest_feature.h +++ b/chrome/common/extensions/features/manifest_feature.h @@ -19,6 +19,8 @@ class ManifestFeature : public SimpleFeature { Feature::Context context, const GURL& url, Feature::Platform platform) const OVERRIDE; + + virtual std::string Parse(const DictionaryValue* value) OVERRIDE; }; } // extensions diff --git a/chrome/common/extensions/features/permission_feature.cc b/chrome/common/extensions/features/permission_feature.cc index 1f5da62..72b261f 100644 --- a/chrome/common/extensions/features/permission_feature.cc +++ b/chrome/common/extensions/features/permission_feature.cc @@ -36,4 +36,20 @@ Feature::Availability PermissionFeature::IsAvailableToContext( return CreateAvailability(IS_AVAILABLE); } +std::string PermissionFeature::Parse(const DictionaryValue* value) { + std::string error = SimpleFeature::Parse(value); + if (!error.empty()) + return error; + + if (extension_types()->empty()) { + return name() + ": Permission features must specify at least one " + + "value for extension_types."; + } + + if (!GetContexts()->empty()) + return name() + ": Permission features do not support contexts."; + + return ""; +} + } // namespace diff --git a/chrome/common/extensions/features/permission_feature.h b/chrome/common/extensions/features/permission_feature.h index 8c4b52f..47892ae 100644 --- a/chrome/common/extensions/features/permission_feature.h +++ b/chrome/common/extensions/features/permission_feature.h @@ -19,6 +19,8 @@ class PermissionFeature : public SimpleFeature { Feature::Context context, const GURL& url, Feature::Platform platform) const OVERRIDE; + + virtual std::string Parse(const DictionaryValue* value) OVERRIDE; }; } // extensions diff --git a/chrome/common/extensions/features/simple_feature.cc b/chrome/common/extensions/features/simple_feature.cc index de97340..9011e35 100644 --- a/chrome/common/extensions/features/simple_feature.cc +++ b/chrome/common/extensions/features/simple_feature.cc @@ -208,9 +208,10 @@ bool SimpleFeature::Equals(const SimpleFeature& other) const { channel_ == other.channel_; } -void SimpleFeature::Parse(const DictionaryValue* value) { +std::string SimpleFeature::Parse(const DictionaryValue* value) { ParseURLPatterns(value, "matches", &matches_); ParseSet(value, "whitelist", &whitelist_); + ParseSet(value, "dependencies", &dependencies_); ParseEnumSet<Manifest::Type>(value, "extension_types", &extension_types_, g_mappings.Get().extension_types); ParseEnumSet<Context>(value, "contexts", &contexts_, @@ -224,6 +225,11 @@ void SimpleFeature::Parse(const DictionaryValue* value) { ParseEnum<VersionInfo::Channel>( value, "channel", &channel_, g_mappings.Get().channels); + if (matches_.is_empty() && contexts_.count(WEB_PAGE_CONTEXT) != 0) { + return name() + ": Allowing web_page contexts requires supplying a value " + + "for matches."; + } + return ""; } Feature::Availability SimpleFeature::IsAvailableToManifest( diff --git a/chrome/common/extensions/features/simple_feature.h b/chrome/common/extensions/features/simple_feature.h index 5b34c6e..4d1581d4 100644 --- a/chrome/common/extensions/features/simple_feature.h +++ b/chrome/common/extensions/features/simple_feature.h @@ -30,8 +30,9 @@ class SimpleFeature : public Feature { // Parses the JSON representation of a feature into the fields of this object. // Unspecified values in the JSON are not modified in the object. This allows - // us to implement inheritance by parsing one value after another. - void Parse(const DictionaryValue* value); + // us to implement inheritance by parsing one value after another. Returns + // the error found, or an empty string on success. + virtual std::string Parse(const DictionaryValue* value); // Returns true if the feature contains the same values as another. bool Equals(const SimpleFeature& other) const; diff --git a/chrome/test/data/extensions/extension_api_unittest/api_features.json b/chrome/test/data/extensions/extension_api_unittest/api_features.json new file mode 100644 index 0000000..476525d --- /dev/null +++ b/chrome/test/data/extensions/extension_api_unittest/api_features.json @@ -0,0 +1,54 @@ +{ + "test1": { + "contexts": ["content_script", "blessed_extension", "unblessed_extension"] + }, + "test2": { + "contexts": ["web_page"], + "matches": ["<all_urls>"] + }, + "test2.foo": { + "contexts": ["content_script"] + }, + "test3": { + "contexts": ["content_script"] + }, + "test3.foo": { + "contexts": ["web_page", "blessed_extension"], + "matches": ["<all_urls>"] + }, + "test4": { + "contexts": ["blessed_extension"], + "dependencies": ["api:test3.foo"] + }, + "test4.foo": { + "contexts": ["unblessed_extension"], + "dependencies": ["api:test4"] + }, + "test4.foo.foo": { + "contexts": ["content_script"] + }, + "test5": { + "contexts": ["web_page"], + "matches": ["http://foo.com/*"] + }, + "test6": { + "contexts": ["content_script"] + }, + "test6.foo": { + "contexts": ["blessed_extension"] + }, + "test7": { + "contexts": ["web_page"], + "matches": ["http://foo.com/*"] + }, + "test7.foo": { + "contexts": ["web_page"], + "matches": ["<all_urls>"], + "dependencies": ["test7"] + }, + "test7.bar": { + "contexts": ["web_page"], + "matches": ["http://bar.com/*"], + "dependencies": ["test7.foo"] + } +} diff --git a/chrome/test/data/extensions/extension_api_unittest/is_privileged_features_1.json b/chrome/test/data/extensions/extension_api_unittest/is_privileged_features_1.json deleted file mode 100644 index ae509f69..0000000 --- a/chrome/test/data/extensions/extension_api_unittest/is_privileged_features_1.json +++ /dev/null @@ -1,8 +0,0 @@ -[ - { - "namespace": "test", - "uses_feature_system": true, - "extension_types": ["extension"], - "contexts": ["content_script"] - } -] diff --git a/chrome/test/data/extensions/extension_api_unittest/is_privileged_features_2.json b/chrome/test/data/extensions/extension_api_unittest/is_privileged_features_2.json deleted file mode 100644 index 91b9d13..0000000 --- a/chrome/test/data/extensions/extension_api_unittest/is_privileged_features_2.json +++ /dev/null @@ -1,8 +0,0 @@ -[ - { - "namespace": "test", - "uses_feature_system": true, - "extension_types": ["extension"], - "contexts": ["blessed_extension"] - } -] diff --git a/chrome/test/data/extensions/extension_api_unittest/is_privileged_features_3.json b/chrome/test/data/extensions/extension_api_unittest/is_privileged_features_3.json deleted file mode 100644 index 47b1290e..0000000 --- a/chrome/test/data/extensions/extension_api_unittest/is_privileged_features_3.json +++ /dev/null @@ -1,8 +0,0 @@ -[ - { - "namespace": "test", - "uses_feature_system": true, - "extension_types": ["extension"], - "contexts": ["content_script", "blessed_extension"] - } -] diff --git a/chrome/test/data/extensions/extension_api_unittest/is_privileged_features_4.json b/chrome/test/data/extensions/extension_api_unittest/is_privileged_features_4.json deleted file mode 100644 index 8839ce1..0000000 --- a/chrome/test/data/extensions/extension_api_unittest/is_privileged_features_4.json +++ /dev/null @@ -1,18 +0,0 @@ -[ - { - "namespace": "test", - "uses_feature_system": true, - "extension_types": ["extension"], - "contexts": ["blessed_extension"], - "functions": [ - { - "name": "foo", - "contexts": ["content_script", "blessed_extension"] - }, - { - "name": "bar", - "dependencies": ["api:test.foo"] - } - ] - } -] diff --git a/chrome/test/data/extensions/extension_api_unittest/is_privileged_features_5.json b/chrome/test/data/extensions/extension_api_unittest/is_privileged_features_5.json deleted file mode 100644 index 9db5b2d..0000000 --- a/chrome/test/data/extensions/extension_api_unittest/is_privileged_features_5.json +++ /dev/null @@ -1,18 +0,0 @@ -[ - { - "namespace": "test", - "uses_feature_system": true, - "extension_types": ["extension"], - "contexts": ["blessed_extension"], - "functions": [ - { - "name": "foo", - "dependencies": ["test2:monkey"] - }, - { - "name": "bar", - "dependencies": ["api:test.foo"] - } - ] - } -] diff --git a/chrome/test/data/extensions/extension_api_unittest/privileged_api_features.json b/chrome/test/data/extensions/extension_api_unittest/privileged_api_features.json new file mode 100644 index 0000000..b32781d --- /dev/null +++ b/chrome/test/data/extensions/extension_api_unittest/privileged_api_features.json @@ -0,0 +1,32 @@ +{ + "test1": { + "contexts": ["content_script"] + }, + "test1.foo": { + "contexts": ["blessed_extension"] + }, + "test2": { + "contexts": ["blessed_extension"], + "dependencies": ["api:test1"] + }, + "test2.foo": { + "contexts": ["content_script"] + }, + "test2.bar": { + "contexts": ["content_script", "blessed_extension"] + }, + "test2.baz": { + "contexts": ["blessed_extension"], + "dependencies": ["api:test2.foo"] + }, + "test3": { + "contexts": ["content_script", "blessed_extension"] + }, + "test3.foo": { + "contexts": ["blessed_extension"] + }, + "test4": { + "contexts": ["content_script"], + "dependencies": ["api:test1.foo"] + } +} |