summaryrefslogtreecommitdiffstats
path: root/chrome
diff options
context:
space:
mode:
authoraa@chromium.org <aa@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98>2012-04-04 18:55:19 +0000
committeraa@chromium.org <aa@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98>2012-04-04 18:55:19 +0000
commit310311c35845cdb7addc03473e4b6dc1d9e5264b (patch)
tree64db561ee7533f5ed8c31fec4d9e1f6aee1b432d /chrome
parentda22aa6bdeec162e8eb81d90798398e31db1fd58 (diff)
downloadchromium_src-310311c35845cdb7addc03473e4b6dc1d9e5264b.zip
chromium_src-310311c35845cdb7addc03473e4b6dc1d9e5264b.tar.gz
chromium_src-310311c35845cdb7addc03473e4b6dc1d9e5264b.tar.bz2
Reland r130462: Implement FeatureProvider for ExtensionAPI."
BUG=120069 TBR=mpcomplete@chromium.org Review URL: https://chromiumcodereview.appspot.com/9969136 git-svn-id: svn://svn.chromium.org/chrome/trunk/src@130697 0039d316-1c4b-4281-b951-d872f2087c98
Diffstat (limited to 'chrome')
-rw-r--r--chrome/browser/extensions/extension_event_router.cc2
-rw-r--r--chrome/browser/extensions/extension_function_dispatcher.cc25
-rw-r--r--chrome/browser/extensions/extension_function_dispatcher.h2
-rw-r--r--chrome/browser/extensions/settings/settings_frontend.cc2
-rw-r--r--chrome/chrome_common.gypi5
-rw-r--r--chrome/common/extensions/api/bookmarks.json3
-rw-r--r--chrome/common/extensions/api/extension_api.cc692
-rw-r--r--chrome/common/extensions/api/extension_api.h102
-rw-r--r--chrome/common/extensions/api/extension_api_unittest.cc343
-rw-r--r--chrome/common/extensions/extension.cc6
-rw-r--r--chrome/common/extensions/feature.cc96
-rw-r--r--chrome/common/extensions/feature.h75
-rw-r--r--chrome/common/extensions/feature_provider.h29
-rw-r--r--chrome/common/extensions/feature_unittest.cc333
-rw-r--r--chrome/common/extensions/manifest.cc4
-rw-r--r--chrome/common/extensions/manifest_feature.cc35
-rw-r--r--chrome/common/extensions/manifest_feature.h26
-rw-r--r--chrome/common/extensions/permission_feature.cc33
-rw-r--r--chrome/common/extensions/permission_feature.h26
-rw-r--r--chrome/common/extensions/simple_feature_provider.cc60
-rw-r--r--chrome/common/extensions/simple_feature_provider.h18
-rw-r--r--chrome/common/extensions/simple_feature_provider_unittest.cc74
-rw-r--r--chrome/renderer/extensions/extension_dispatcher.cc4
-rw-r--r--chrome/renderer/extensions/schema_generated_bindings.cc2
-rw-r--r--chrome/renderer/extensions/v8_schema_registry.cc2
-rw-r--r--chrome/test/data/extensions/api_test/webstore_private/bundle/extension2.json7
-rw-r--r--chrome/test/data/extensions/extension_api_unittest/is_privileged_features_1.json8
-rw-r--r--chrome/test/data/extensions/extension_api_unittest/is_privileged_features_2.json8
-rw-r--r--chrome/test/data/extensions/extension_api_unittest/is_privileged_features_3.json8
-rw-r--r--chrome/test/data/extensions/extension_api_unittest/is_privileged_features_4.json18
-rw-r--r--chrome/test/data/extensions/extension_api_unittest/is_privileged_features_5.json18
31 files changed, 1566 insertions, 500 deletions
diff --git a/chrome/browser/extensions/extension_event_router.cc b/chrome/browser/extensions/extension_event_router.cc
index cfe5fec..66e48a3 100644
--- a/chrome/browser/extensions/extension_event_router.cc
+++ b/chrome/browser/extensions/extension_event_router.cc
@@ -317,7 +317,7 @@ void ExtensionEventRouter::DispatchEventToListener(
listener_profile->GetExtensionService()->process_map();
// If the event is privileged, only send to extension processes. Otherwise,
// it's OK to send to normal renderers (e.g., for content scripts).
- if (ExtensionAPI::GetInstance()->IsPrivileged(event->event_name) &&
+ if (ExtensionAPI::GetSharedInstance()->IsPrivileged(event->event_name) &&
!process_map->Contains(extension->id(), listener.process->GetID())) {
return;
}
diff --git a/chrome/browser/extensions/extension_function_dispatcher.cc b/chrome/browser/extensions/extension_function_dispatcher.cc
index 0a95858..588d088 100644
--- a/chrome/browser/extensions/extension_function_dispatcher.cc
+++ b/chrome/browser/extensions/extension_function_dispatcher.cc
@@ -7,6 +7,7 @@
#include <map>
#include "base/json/json_string_value_serializer.h"
+#include "base/lazy_instance.h"
#include "base/memory/ref_counted.h"
#include "base/process_util.h"
#include "base/values.h"
@@ -39,6 +40,8 @@ using extensions::ExtensionAPI;
using content::RenderViewHost;
using WebKit::WebSecurityOrigin;
+namespace {
+
const char kAccessDenied[] = "access denied";
const char kQuotaExceeded[] = "quota exceeded";
@@ -80,6 +83,20 @@ void LogFailure(const Extension* extension,
}
}
+// Separate copy of ExtensionAPI used for IO thread extension functions. We need
+// this because ExtensionAPI has mutable data. It should be possible to remove
+// this once all the extension APIs are updated to the feature system.
+struct Static {
+ Static()
+ : api(extensions::ExtensionAPI::CreateWithDefaultConfiguration()) {
+ }
+ scoped_ptr<extensions::ExtensionAPI> api;
+};
+base::LazyInstance<Static> g_global_io_data = LAZY_INSTANCE_INITIALIZER;
+
+} // namespace
+
+
void ExtensionFunctionDispatcher::GetAllFunctionNames(
std::vector<std::string>* names) {
ExtensionFunctionRegistry::GetInstance()->GetAllNames(names);
@@ -108,7 +125,9 @@ void ExtensionFunctionDispatcher::DispatchOnIOThread(
scoped_refptr<ExtensionFunction> function(
CreateExtensionFunction(params, extension, render_process_id,
- extension_info_map->process_map(), profile,
+ extension_info_map->process_map(),
+ g_global_io_data.Get().api.get(),
+ profile,
ipc_sender, routing_id));
if (!function) {
LogFailure(extension, params.name, kAccessDenied);
@@ -191,6 +210,7 @@ void ExtensionFunctionDispatcher::Dispatch(
CreateExtensionFunction(params, extension,
render_view_host->GetProcess()->GetID(),
*(service->process_map()),
+ extensions::ExtensionAPI::GetSharedInstance(),
profile(), render_view_host,
render_view_host->GetRoutingID()));
if (!function) {
@@ -242,6 +262,7 @@ ExtensionFunction* ExtensionFunctionDispatcher::CreateExtensionFunction(
const Extension* extension,
int requesting_process_id,
const extensions::ProcessMap& process_map,
+ extensions::ExtensionAPI* api,
void* profile,
IPC::Message::Sender* ipc_sender,
int routing_id) {
@@ -251,7 +272,7 @@ ExtensionFunction* ExtensionFunctionDispatcher::CreateExtensionFunction(
return NULL;
}
- if (ExtensionAPI::GetInstance()->IsPrivileged(params.name) &&
+ if (api->IsPrivileged(params.name) &&
!process_map.Contains(extension->id(), requesting_process_id)) {
LOG(ERROR) << "Extension API called from incorrect process "
<< requesting_process_id
diff --git a/chrome/browser/extensions/extension_function_dispatcher.h b/chrome/browser/extensions/extension_function_dispatcher.h
index 13611ff..67c7656 100644
--- a/chrome/browser/extensions/extension_function_dispatcher.h
+++ b/chrome/browser/extensions/extension_function_dispatcher.h
@@ -28,6 +28,7 @@ class WebContents;
}
namespace extensions {
+class ExtensionAPI;
class ProcessMap;
}
@@ -126,6 +127,7 @@ class ExtensionFunctionDispatcher
const Extension* extension,
int requesting_process_id,
const extensions::ProcessMap& process_map,
+ extensions::ExtensionAPI* api,
void* profile,
IPC::Message::Sender* ipc_sender,
int routing_id);
diff --git a/chrome/browser/extensions/settings/settings_frontend.cc b/chrome/browser/extensions/settings/settings_frontend.cc
index a29fbc5..0c2c51d 100644
--- a/chrome/browser/extensions/settings/settings_frontend.cc
+++ b/chrome/browser/extensions/settings/settings_frontend.cc
@@ -106,7 +106,7 @@ size_t GetStringAsInteger(
SettingsStorageQuotaEnforcer::Limits GetLimitsFromExtensionAPI(
const std::string& storage_area_id) {
const DictionaryValue* storage_schema =
- ExtensionAPI::GetInstance()->GetSchema("storage");
+ ExtensionAPI::GetSharedInstance()->GetSchema("storage");
CHECK(storage_schema);
DictionaryValue* properties = NULL;
diff --git a/chrome/chrome_common.gypi b/chrome/chrome_common.gypi
index 7586ec3..178016e 100644
--- a/chrome/chrome_common.gypi
+++ b/chrome/chrome_common.gypi
@@ -145,10 +145,15 @@
'common/extensions/extension_unpacker.h',
'common/extensions/feature.cc',
'common/extensions/feature.h',
+ 'common/extensions/feature_provider.h',
'common/extensions/file_browser_handler.cc',
'common/extensions/file_browser_handler.h',
'common/extensions/manifest.cc',
'common/extensions/manifest.h',
+ 'common/extensions/manifest_feature.cc',
+ 'common/extensions/manifest_feature.h',
+ 'common/extensions/permission_feature.cc',
+ 'common/extensions/permission_feature.h',
'common/extensions/simple_feature_provider.cc',
'common/extensions/simple_feature_provider.h',
'common/extensions/update_manifest.cc',
diff --git a/chrome/common/extensions/api/bookmarks.json b/chrome/common/extensions/api/bookmarks.json
index 1648d41..0fe84b6 100644
--- a/chrome/common/extensions/api/bookmarks.json
+++ b/chrome/common/extensions/api/bookmarks.json
@@ -5,6 +5,9 @@
[
{
"namespace": "bookmarks",
+ "uses_feature_system": true,
+ "dependencies": ["permission:bookmarks"],
+ "contexts": ["blessed_extension"],
"types": [
{
"id": "BookmarkTreeNode",
diff --git a/chrome/common/extensions/api/extension_api.cc b/chrome/common/extensions/api/extension_api.cc
index 3178975..029e58c6 100644
--- a/chrome/common/extensions/api/extension_api.cc
+++ b/chrome/common/extensions/api/extension_api.cc
@@ -9,13 +9,18 @@
#include <vector>
#include "base/json/json_reader.h"
+#include "base/json/json_writer.h"
+#include "base/lazy_instance.h"
#include "base/logging.h"
+#include "base/string_number_conversions.h"
#include "base/string_split.h"
#include "base/string_util.h"
#include "base/values.h"
#include "chrome/common/extensions/api/generated_schemas.h"
#include "chrome/common/extensions/extension.h"
#include "chrome/common/extensions/extension_permission_set.h"
+#include "chrome/common/extensions/simple_feature_provider.h"
+#include "content/public/browser/browser_thread.h"
#include "googleurl/src/gurl.h"
#include "grit/common_resources.h"
#include "ui/base/resource/resource_bundle.h"
@@ -23,6 +28,7 @@
using base::DictionaryValue;
using base::ListValue;
using base::Value;
+using content::BrowserThread;
namespace extensions {
@@ -30,6 +36,11 @@ using api::GeneratedSchemas;
namespace {
+const char* kChildKinds[] = {
+ "functions",
+ "events"
+};
+
// Returns whether the list at |name_space_node|.|child_kind| contains any
// children with an { "unprivileged": true } property.
bool HasUnprivilegedChild(const DictionaryValue* name_space_node,
@@ -67,11 +78,74 @@ scoped_ptr<ListValue> LoadSchemaList(const base::StringPiece& schema) {
return scoped_ptr<ListValue>(static_cast<ListValue*>(result.release()));
}
+DictionaryValue* FindListItem(const ListValue* list,
+ const std::string& property_name,
+ const std::string& property_value) {
+ for (size_t i = 0; i < list->GetSize(); ++i) {
+ DictionaryValue* item = NULL;
+ CHECK(list->GetDictionary(i, &item))
+ << property_value << "/" << property_name;
+ std::string value;
+ if (item->GetString(property_name, &value) && value == property_value)
+ return item;
+ }
+
+ return NULL;
+}
+
+const DictionaryValue* GetSchemaChild(const DictionaryValue* schema_node,
+ const std::string& child_name) {
+ DictionaryValue* child_node = NULL;
+ for (size_t i = 0; i < arraysize(kChildKinds); ++i) {
+ ListValue* list_node = NULL;
+ if (!schema_node->GetList(kChildKinds[i], &list_node))
+ continue;
+ child_node = FindListItem(list_node, "name", child_name);
+ if (child_node)
+ return child_node;
+ }
+
+ return NULL;
+}
+
+struct Static {
+ Static()
+ : api(ExtensionAPI::CreateWithDefaultConfiguration()) {
+ }
+ scoped_ptr<ExtensionAPI> api;
+};
+
+base::LazyInstance<Static> g_lazy_instance = LAZY_INSTANCE_INITIALIZER;
+
} // namespace
// static
-ExtensionAPI* ExtensionAPI::GetInstance() {
- return Singleton<ExtensionAPI>::get();
+ExtensionAPI* ExtensionAPI::GetSharedInstance() {
+ DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
+ return g_lazy_instance.Get().api.get();
+}
+
+// static
+ExtensionAPI* ExtensionAPI::CreateWithDefaultConfiguration() {
+ ExtensionAPI* api = new ExtensionAPI();
+ api->InitDefaultConfiguration();
+ return api;
+}
+
+// static
+void ExtensionAPI::SplitDependencyName(const std::string& full_name,
+ std::string* feature_type,
+ std::string* feature_name) {
+ size_t colon_index = full_name.find(':');
+ if (colon_index == std::string::npos) {
+ // TODO(aa): Remove this code when all API descriptions have been updated.
+ *feature_type = "api";
+ *feature_name = full_name;
+ return;
+ }
+
+ *feature_type = full_name.substr(0, colon_index);
+ *feature_name = full_name.substr(colon_index + 1);
}
void ExtensionAPI::LoadSchema(const base::StringPiece& schema) {
@@ -115,238 +189,309 @@ void ExtensionAPI::LoadSchema(const base::StringPiece& schema) {
}
url_matching_apis_[schema_namespace] = pattern_set;
}
+
+ // Populate feature maps.
+ // TODO(aa): Consider not storing features that can never run on the current
+ // machine (e.g., because of platform restrictions).
+ bool uses_feature_system = false;
+ schema->GetBoolean("uses_feature_system", &uses_feature_system);
+ if (!uses_feature_system)
+ continue;
+
+ Feature* feature = new Feature();
+ feature->set_name(schema_namespace);
+ feature->Parse(schema);
+
+ FeatureMap* schema_features = new FeatureMap();
+ CHECK(features_.insert(
+ std::make_pair(schema_namespace,
+ make_linked_ptr(schema_features))).second);
+ CHECK(schema_features->insert(
+ std::make_pair("", make_linked_ptr(feature))).second);
+
+ for (size_t i = 0; i < arraysize(kChildKinds); ++i) {
+ ListValue* child_list = NULL;
+ schema->GetList(kChildKinds[i], &child_list);
+ if (!child_list)
+ continue;
+
+ for (size_t j = 0; j < child_list->GetSize(); ++j) {
+ DictionaryValue* child = NULL;
+ CHECK(child_list->GetDictionary(j, &child));
+
+ scoped_ptr<Feature> child_feature(new Feature(*feature));
+ child_feature->Parse(child);
+ if (child_feature->Equals(*feature))
+ continue; // no need to store no-op features
+
+ std::string child_name;
+ CHECK(child->GetString("name", &child_name));
+ child_feature->set_name(schema_namespace + "." + child_name);
+ CHECK(schema_features->insert(
+ std::make_pair(child_name,
+ make_linked_ptr(child_feature.release()))).second);
+ }
+ }
}
}
ExtensionAPI::ExtensionAPI() {
- // Schemas to be loaded from resources.
- unloaded_schemas_["app"] = ReadFromResource(
- IDR_EXTENSION_API_JSON_APP);
- unloaded_schemas_["bookmarks"] = ReadFromResource(
- IDR_EXTENSION_API_JSON_BOOKMARKS);
- unloaded_schemas_["browserAction"] = ReadFromResource(
- IDR_EXTENSION_API_JSON_BROWSERACTION);
- unloaded_schemas_["browsingData"] = ReadFromResource(
- IDR_EXTENSION_API_JSON_BROWSINGDATA);
- unloaded_schemas_["chromeAuthPrivate"] = ReadFromResource(
- IDR_EXTENSION_API_JSON_CHROMEAUTHPRIVATE);
- unloaded_schemas_["chromeosInfoPrivate"] = ReadFromResource(
- IDR_EXTENSION_API_JSON_CHROMEOSINFOPRIVATE);
- unloaded_schemas_["contentSettings"] = ReadFromResource(
- IDR_EXTENSION_API_JSON_CONTENTSETTINGS);
- unloaded_schemas_["contextMenus"] = ReadFromResource(
- IDR_EXTENSION_API_JSON_CONTEXTMENUS);
- unloaded_schemas_["cookies"] = ReadFromResource(
- IDR_EXTENSION_API_JSON_COOKIES);
- unloaded_schemas_["debugger"] = ReadFromResource(
- IDR_EXTENSION_API_JSON_DEBUGGER);
- unloaded_schemas_["devtools"] = ReadFromResource(
- IDR_EXTENSION_API_JSON_DEVTOOLS);
- unloaded_schemas_["experimental.accessibility"] = ReadFromResource(
- IDR_EXTENSION_API_JSON_EXPERIMENTAL_ACCESSIBILITY);
- unloaded_schemas_["experimental.alarms"] = ReadFromResource(
- IDR_EXTENSION_API_JSON_EXPERIMENTAL_ALARMS);
- unloaded_schemas_["experimental.app"] = ReadFromResource(
- IDR_EXTENSION_API_JSON_EXPERIMENTAL_APP);
- unloaded_schemas_["experimental.bookmarkManager"] = ReadFromResource(
- IDR_EXTENSION_API_JSON_EXPERIMENTAL_BOOKMARKMANAGER);
- unloaded_schemas_["experimental.declarative"] = ReadFromResource(
- IDR_EXTENSION_API_JSON_EXPERIMENTAL_DECLARATIVE);
- unloaded_schemas_["experimental.downloads"] = ReadFromResource(
- IDR_EXTENSION_API_JSON_EXPERIMENTAL_DOWNLOADS);
- unloaded_schemas_["experimental.extension"] = ReadFromResource(
- IDR_EXTENSION_API_JSON_EXPERIMENTAL_EXTENSION);
- unloaded_schemas_["experimental.fontSettings"] = ReadFromResource(
- IDR_EXTENSION_API_JSON_EXPERIMENTAL_FONTSSETTINGS);
- unloaded_schemas_["experimental.identity"] = ReadFromResource(
- IDR_EXTENSION_API_JSON_EXPERIMENTAL_IDENTITY);
- unloaded_schemas_["experimental.infobars"] = ReadFromResource(
- IDR_EXTENSION_API_JSON_EXPERIMENTAL_INFOBARS);
- unloaded_schemas_["experimental.input.ui"] = ReadFromResource(
- IDR_EXTENSION_API_JSON_EXPERIMENTAL_INPUT_UI);
- unloaded_schemas_["experimental.input.virtualKeyboard"] = ReadFromResource(
- IDR_EXTENSION_API_JSON_EXPERIMENTAL_INPUT_VIRTUALKEYBOARD);
- unloaded_schemas_["experimental.keybinding"] = ReadFromResource(
- IDR_EXTENSION_API_JSON_EXPERIMENTAL_KEYBINDING);
- unloaded_schemas_["experimental.managedMode"] = ReadFromResource(
- IDR_EXTENSION_API_JSON_EXPERIMENTAL_MANAGEDMODE);
- unloaded_schemas_["experimental.offscreenTabs"] = ReadFromResource(
- IDR_EXTENSION_API_JSON_EXPERIMENTAL_OFFSCREENTABS);
- unloaded_schemas_["experimental.processes"] = ReadFromResource(
- IDR_EXTENSION_API_JSON_EXPERIMENTAL_PROCESSES);
- unloaded_schemas_["experimental.rlz"] = ReadFromResource(
- IDR_EXTENSION_API_JSON_EXPERIMENTAL_RLZ);
- unloaded_schemas_["experimental.serial"] = ReadFromResource(
- IDR_EXTENSION_API_JSON_EXPERIMENTAL_SERIAL);
- unloaded_schemas_["experimental.socket"] = ReadFromResource(
- IDR_EXTENSION_API_JSON_EXPERIMENTAL_SOCKET);
- unloaded_schemas_["experimental.speechInput"] = ReadFromResource(
- IDR_EXTENSION_API_JSON_EXPERIMENTAL_SPEECHINPUT);
- unloaded_schemas_["experimental.webRequest"] = ReadFromResource(
- IDR_EXTENSION_API_JSON_EXPERIMENTAL_WEBREQUEST);
- unloaded_schemas_["extension"] = ReadFromResource(
- IDR_EXTENSION_API_JSON_EXTENSION);
- unloaded_schemas_["fileBrowserHandler"] = ReadFromResource(
- IDR_EXTENSION_API_JSON_FILEBROWSERHANDLER);
- unloaded_schemas_["fileBrowserPrivate"] = ReadFromResource(
- IDR_EXTENSION_API_JSON_FILEBROWSERPRIVATE);
- unloaded_schemas_["history"] = ReadFromResource(
- IDR_EXTENSION_API_JSON_HISTORY);
- unloaded_schemas_["i18n"] = ReadFromResource(
- IDR_EXTENSION_API_JSON_I18N);
- unloaded_schemas_["idle"] = ReadFromResource(
- IDR_EXTENSION_API_JSON_IDLE);
- unloaded_schemas_["input.ime"] = ReadFromResource(
- IDR_EXTENSION_API_JSON_INPUT_IME);
- unloaded_schemas_["inputMethodPrivate"] = ReadFromResource(
- IDR_EXTENSION_API_JSON_INPUTMETHODPRIVATE);
- unloaded_schemas_["management"] = ReadFromResource(
- IDR_EXTENSION_API_JSON_MANAGEMENT);
- unloaded_schemas_["mediaPlayerPrivate"] = ReadFromResource(
- IDR_EXTENSION_API_JSON_MEDIAPLAYERPRIVATE);
- unloaded_schemas_["metricsPrivate"] = ReadFromResource(
- IDR_EXTENSION_API_JSON_METRICSPRIVATE);
- unloaded_schemas_["offersPrivate"] = ReadFromResource(
- IDR_EXTENSION_API_JSON_OFFERSPRIVATE);
- unloaded_schemas_["omnibox"] = ReadFromResource(
- IDR_EXTENSION_API_JSON_OMNIBOX);
- unloaded_schemas_["pageAction"] = ReadFromResource(
- IDR_EXTENSION_API_JSON_PAGEACTION);
- unloaded_schemas_["pageActions"] = ReadFromResource(
- IDR_EXTENSION_API_JSON_PAGEACTIONS);
- unloaded_schemas_["pageCapture"] = ReadFromResource(
- IDR_EXTENSION_API_JSON_PAGECAPTURE);
- unloaded_schemas_["permissions"] = ReadFromResource(
- IDR_EXTENSION_API_JSON_PERMISSIONS);
- unloaded_schemas_["privacy"] = ReadFromResource(
- IDR_EXTENSION_API_JSON_PRIVACY);
- unloaded_schemas_["proxy"] = ReadFromResource(
- IDR_EXTENSION_API_JSON_PROXY);
- unloaded_schemas_["storage"] = ReadFromResource(
- IDR_EXTENSION_API_JSON_STORAGE);
- unloaded_schemas_["systemPrivate"] = ReadFromResource(
- IDR_EXTENSION_API_JSON_SYSTEMPRIVATE);
- unloaded_schemas_["tabs"] = ReadFromResource(
- IDR_EXTENSION_API_JSON_TABS);
- unloaded_schemas_["terminalPrivate"] = ReadFromResource(
- IDR_EXTENSION_API_JSON_TERMINALPRIVATE);
- unloaded_schemas_["test"] = ReadFromResource(
- IDR_EXTENSION_API_JSON_TEST);
- unloaded_schemas_["topSites"] = ReadFromResource(
- IDR_EXTENSION_API_JSON_TOPSITES);
- unloaded_schemas_["ttsEngine"] = ReadFromResource(
- IDR_EXTENSION_API_JSON_TTSENGINE);
- unloaded_schemas_["tts"] = ReadFromResource(
- IDR_EXTENSION_API_JSON_TTS);
- unloaded_schemas_["types"] = ReadFromResource(
- IDR_EXTENSION_API_JSON_TYPES);
- unloaded_schemas_["webNavigation"] = ReadFromResource(
- IDR_EXTENSION_API_JSON_WEBNAVIGATION);
- unloaded_schemas_["webRequest"] = ReadFromResource(
- IDR_EXTENSION_API_JSON_WEBREQUEST);
- unloaded_schemas_["webSocketProxyPrivate"] = ReadFromResource(
- IDR_EXTENSION_API_JSON_WEBSOCKETPROXYPRIVATE);
- unloaded_schemas_["webstore"] = ReadFromResource(
- IDR_EXTENSION_API_JSON_WEBSTORE);
- unloaded_schemas_["webstorePrivate"] = ReadFromResource(
- IDR_EXTENSION_API_JSON_WEBSTOREPRIVATE);
- unloaded_schemas_["windows"] = ReadFromResource(
- IDR_EXTENSION_API_JSON_WINDOWS);
+ RegisterDependencyProvider("api", this);
- // Schemas to be loaded via JSON generated from IDL files.
- GeneratedSchemas::Get(&unloaded_schemas_);
+ // TODO(aa): Can remove this when all JSON files are converted.
+ RegisterDependencyProvider("", this);
}
ExtensionAPI::~ExtensionAPI() {
}
-bool ExtensionAPI::IsPrivileged(const std::string& full_name) {
- std::string api_name;
- std::string child_name;
+void ExtensionAPI::InitDefaultConfiguration() {
+ RegisterDependencyProvider(
+ "manifest", SimpleFeatureProvider::GetManifestFeatures());
+ RegisterDependencyProvider(
+ "permission", SimpleFeatureProvider::GetPermissionFeatures());
- {
- std::vector<std::string> split;
- base::SplitString(full_name, '.', &split);
- std::reverse(split.begin(), split.end());
- CHECK(!split.empty()); // |full_name| was empty or only whitespace.
-
- api_name = split.back();
- split.pop_back();
- if (api_name == "experimental") {
- CHECK(!split.empty()); // |full_name| was "experimental" alone.
- api_name += "." + split.back();
- split.pop_back();
- }
+ // Schemas to be loaded from resources.
+ CHECK(unloaded_schemas_.empty());
+ RegisterSchema("app", ReadFromResource(
+ IDR_EXTENSION_API_JSON_APP));
+ RegisterSchema("bookmarks", ReadFromResource(
+ IDR_EXTENSION_API_JSON_BOOKMARKS));
+ RegisterSchema("browserAction", ReadFromResource(
+ IDR_EXTENSION_API_JSON_BROWSERACTION));
+ RegisterSchema("browsingData", ReadFromResource(
+ IDR_EXTENSION_API_JSON_BROWSINGDATA));
+ RegisterSchema("chromeAuthPrivate", ReadFromResource(
+ IDR_EXTENSION_API_JSON_CHROMEAUTHPRIVATE));
+ RegisterSchema("chromeosInfoPrivate", ReadFromResource(
+ IDR_EXTENSION_API_JSON_CHROMEOSINFOPRIVATE));
+ RegisterSchema("contentSettings", ReadFromResource(
+ IDR_EXTENSION_API_JSON_CONTENTSETTINGS));
+ RegisterSchema("contextMenus", ReadFromResource(
+ IDR_EXTENSION_API_JSON_CONTEXTMENUS));
+ RegisterSchema("cookies", ReadFromResource(
+ IDR_EXTENSION_API_JSON_COOKIES));
+ RegisterSchema("debugger", ReadFromResource(
+ IDR_EXTENSION_API_JSON_DEBUGGER));
+ RegisterSchema("devtools", ReadFromResource(
+ IDR_EXTENSION_API_JSON_DEVTOOLS));
+ RegisterSchema("experimental.accessibility", ReadFromResource(
+ IDR_EXTENSION_API_JSON_EXPERIMENTAL_ACCESSIBILITY));
+ RegisterSchema("experimental.alarms", ReadFromResource(
+ IDR_EXTENSION_API_JSON_EXPERIMENTAL_ALARMS));
+ RegisterSchema("experimental.app", ReadFromResource(
+ IDR_EXTENSION_API_JSON_EXPERIMENTAL_APP));
+ RegisterSchema("experimental.bookmarkManager", ReadFromResource(
+ IDR_EXTENSION_API_JSON_EXPERIMENTAL_BOOKMARKMANAGER));
+ RegisterSchema("experimental.declarative", ReadFromResource(
+ IDR_EXTENSION_API_JSON_EXPERIMENTAL_DECLARATIVE));
+ RegisterSchema("experimental.downloads", ReadFromResource(
+ IDR_EXTENSION_API_JSON_EXPERIMENTAL_DOWNLOADS));
+ RegisterSchema("experimental.extension", ReadFromResource(
+ IDR_EXTENSION_API_JSON_EXPERIMENTAL_EXTENSION));
+ RegisterSchema("experimental.fontSettings", ReadFromResource(
+ IDR_EXTENSION_API_JSON_EXPERIMENTAL_FONTSSETTINGS));
+ RegisterSchema("experimental.identity", ReadFromResource(
+ IDR_EXTENSION_API_JSON_EXPERIMENTAL_IDENTITY));
+ RegisterSchema("experimental.infobars", ReadFromResource(
+ IDR_EXTENSION_API_JSON_EXPERIMENTAL_INFOBARS));
+ RegisterSchema("experimental.input.ui", ReadFromResource(
+ IDR_EXTENSION_API_JSON_EXPERIMENTAL_INPUT_UI));
+ RegisterSchema("experimental.input.virtualKeyboard", ReadFromResource(
+ IDR_EXTENSION_API_JSON_EXPERIMENTAL_INPUT_VIRTUALKEYBOARD));
+ RegisterSchema("experimental.keybinding", ReadFromResource(
+ IDR_EXTENSION_API_JSON_EXPERIMENTAL_KEYBINDING));
+ RegisterSchema("experimental.managedMode", ReadFromResource(
+ IDR_EXTENSION_API_JSON_EXPERIMENTAL_MANAGEDMODE));
+ RegisterSchema("experimental.offscreenTabs", ReadFromResource(
+ IDR_EXTENSION_API_JSON_EXPERIMENTAL_OFFSCREENTABS));
+ RegisterSchema("experimental.processes", ReadFromResource(
+ IDR_EXTENSION_API_JSON_EXPERIMENTAL_PROCESSES));
+ RegisterSchema("experimental.rlz", ReadFromResource(
+ IDR_EXTENSION_API_JSON_EXPERIMENTAL_RLZ));
+ RegisterSchema("experimental.serial", ReadFromResource(
+ IDR_EXTENSION_API_JSON_EXPERIMENTAL_SERIAL));
+ RegisterSchema("experimental.socket", ReadFromResource(
+ IDR_EXTENSION_API_JSON_EXPERIMENTAL_SOCKET));
+ RegisterSchema("experimental.speechInput", ReadFromResource(
+ IDR_EXTENSION_API_JSON_EXPERIMENTAL_SPEECHINPUT));
+ RegisterSchema("experimental.webRequest", ReadFromResource(
+ IDR_EXTENSION_API_JSON_EXPERIMENTAL_WEBREQUEST));
+ RegisterSchema("extension", ReadFromResource(
+ IDR_EXTENSION_API_JSON_EXTENSION));
+ RegisterSchema("fileBrowserHandler", ReadFromResource(
+ IDR_EXTENSION_API_JSON_FILEBROWSERHANDLER));
+ RegisterSchema("fileBrowserPrivate", ReadFromResource(
+ IDR_EXTENSION_API_JSON_FILEBROWSERPRIVATE));
+ RegisterSchema("history", ReadFromResource(
+ IDR_EXTENSION_API_JSON_HISTORY));
+ RegisterSchema("i18n", ReadFromResource(
+ IDR_EXTENSION_API_JSON_I18N));
+ RegisterSchema("idle", ReadFromResource(
+ IDR_EXTENSION_API_JSON_IDLE));
+ RegisterSchema("input.ime", ReadFromResource(
+ IDR_EXTENSION_API_JSON_INPUT_IME));
+ RegisterSchema("inputMethodPrivate", ReadFromResource(
+ IDR_EXTENSION_API_JSON_INPUTMETHODPRIVATE));
+ RegisterSchema("management", ReadFromResource(
+ IDR_EXTENSION_API_JSON_MANAGEMENT));
+ RegisterSchema("mediaPlayerPrivate", ReadFromResource(
+ IDR_EXTENSION_API_JSON_MEDIAPLAYERPRIVATE));
+ RegisterSchema("metricsPrivate", ReadFromResource(
+ IDR_EXTENSION_API_JSON_METRICSPRIVATE));
+ RegisterSchema("offersPrivate", ReadFromResource(
+ IDR_EXTENSION_API_JSON_OFFERSPRIVATE));
+ RegisterSchema("omnibox", ReadFromResource(
+ IDR_EXTENSION_API_JSON_OMNIBOX));
+ RegisterSchema("pageAction", ReadFromResource(
+ IDR_EXTENSION_API_JSON_PAGEACTION));
+ RegisterSchema("pageActions", ReadFromResource(
+ IDR_EXTENSION_API_JSON_PAGEACTIONS));
+ RegisterSchema("pageCapture", ReadFromResource(
+ IDR_EXTENSION_API_JSON_PAGECAPTURE));
+ RegisterSchema("permissions", ReadFromResource(
+ IDR_EXTENSION_API_JSON_PERMISSIONS));
+ RegisterSchema("privacy", ReadFromResource(
+ IDR_EXTENSION_API_JSON_PRIVACY));
+ RegisterSchema("proxy", ReadFromResource(
+ IDR_EXTENSION_API_JSON_PROXY));
+ RegisterSchema("storage", ReadFromResource(
+ IDR_EXTENSION_API_JSON_STORAGE));
+ RegisterSchema("systemPrivate", ReadFromResource(
+ IDR_EXTENSION_API_JSON_SYSTEMPRIVATE));
+ RegisterSchema("tabs", ReadFromResource(
+ IDR_EXTENSION_API_JSON_TABS));
+ RegisterSchema("terminalPrivate", ReadFromResource(
+ IDR_EXTENSION_API_JSON_TERMINALPRIVATE));
+ RegisterSchema("test", ReadFromResource(
+ IDR_EXTENSION_API_JSON_TEST));
+ RegisterSchema("topSites", ReadFromResource(
+ IDR_EXTENSION_API_JSON_TOPSITES));
+ RegisterSchema("ttsEngine", ReadFromResource(
+ IDR_EXTENSION_API_JSON_TTSENGINE));
+ RegisterSchema("tts", ReadFromResource(
+ IDR_EXTENSION_API_JSON_TTS));
+ RegisterSchema("types", ReadFromResource(
+ IDR_EXTENSION_API_JSON_TYPES));
+ RegisterSchema("webNavigation", ReadFromResource(
+ IDR_EXTENSION_API_JSON_WEBNAVIGATION));
+ RegisterSchema("webRequest", ReadFromResource(
+ IDR_EXTENSION_API_JSON_WEBREQUEST));
+ RegisterSchema("webSocketProxyPrivate", ReadFromResource(
+ IDR_EXTENSION_API_JSON_WEBSOCKETPROXYPRIVATE));
+ RegisterSchema("webstore", ReadFromResource(
+ IDR_EXTENSION_API_JSON_WEBSTORE));
+ RegisterSchema("webstorePrivate", ReadFromResource(
+ IDR_EXTENSION_API_JSON_WEBSTOREPRIVATE));
+ RegisterSchema("windows", ReadFromResource(
+ IDR_EXTENSION_API_JSON_WINDOWS));
- // This only really works properly if split.size() == 1, however:
- // - if it's empty, it's ok to leave child_name empty; presumably there's
- // no functions or events with empty names.
- // - if it's > 1, we can just do our best.
- if (split.size() > 0)
- child_name = split[0];
- }
+ // Schemas to be loaded via JSON generated from IDL files.
+ GeneratedSchemas::Get(&unloaded_schemas_);
+}
- // GetSchema to ensure that it gets loaded before any checks.
- const DictionaryValue* schema = GetSchema(api_name);
+void ExtensionAPI::RegisterSchema(const std::string& name,
+ const base::StringPiece& source) {
+ unloaded_schemas_[name] = source;
+}
- if (completely_unprivileged_apis_.count(api_name))
- return false;
+void ExtensionAPI::RegisterDependencyProvider(const std::string& name,
+ FeatureProvider* provider) {
+ dependency_providers_[name] = provider;
+}
- if (partially_unprivileged_apis_.count(api_name)) {
- return IsChildNamePrivileged(schema, "functions", child_name) &&
- IsChildNamePrivileged(schema, "events", child_name);
+bool ExtensionAPI::IsAvailable(const std::string& full_name,
+ const Extension* extension,
+ Feature::Context context) {
+ std::set<std::string> dependency_names;
+ dependency_names.insert(full_name);
+ ResolveDependencies(&dependency_names);
+
+ for (std::set<std::string>::iterator iter = dependency_names.begin();
+ iter != dependency_names.end(); ++iter) {
+ scoped_ptr<Feature> feature(GetFeatureDependency(full_name));
+ CHECK(feature.get()) << *iter;
+
+ Feature::Availability availability =
+ feature->IsAvailableToContext(extension, context);
+ if (availability != Feature::IS_AVAILABLE)
+ return false;
}
return true;
}
-DictionaryValue* ExtensionAPI::FindListItem(
- const ListValue* list,
- const std::string& property_name,
- const std::string& property_value) {
- for (size_t i = 0; i < list->GetSize(); ++i) {
- DictionaryValue* item = NULL;
- CHECK(list->GetDictionary(i, &item))
- << property_value << "/" << property_name;
- std::string value;
- if (item->GetString(property_name, &value) && value == property_value)
- return item;
+bool ExtensionAPI::IsPrivileged(const std::string& full_name) {
+ std::string child_name;
+ std::string api_name = GetAPINameFromFullName(full_name, &child_name);
+
+ // First try to use the feature system.
+ scoped_ptr<Feature> feature(GetFeature(full_name));
+ if (feature.get()) {
+ // 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) {
+ scoped_ptr<Feature> dependency(GetFeatureDependency(*iter));
+ for (std::set<Feature::Context>::iterator context =
+ dependency->contexts()->begin();
+ context != dependency->contexts()->end(); ++context) {
+ if (*context != Feature::BLESSED_EXTENSION_CONTEXT)
+ return false;
+ }
+ }
+ return true;
}
- return NULL;
+ // 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);
+
+ return true;
}
bool ExtensionAPI::IsChildNamePrivileged(const DictionaryValue* name_space_node,
- const std::string& child_kind,
const std::string& child_name) {
- ListValue* child_list = NULL;
- name_space_node->GetList(child_kind, &child_list);
- if (!child_list)
- return true;
-
bool unprivileged = false;
- DictionaryValue* child = FindListItem(child_list, "name", child_name);
+ const DictionaryValue* child = GetSchemaChild(name_space_node, child_name);
if (!child || !child->GetBoolean("unprivileged", &unprivileged))
return true;
return !unprivileged;
}
-const DictionaryValue* ExtensionAPI::GetSchema(const std::string& api_name) {
- SchemaMap::const_iterator maybe_schema = schemas_.find(api_name);
- if (maybe_schema != schemas_.end())
- return maybe_schema->second.get();
-
- // Might not have loaded yet; or might just not exist.
- std::map<std::string, base::StringPiece>::iterator maybe_schema_resource =
- unloaded_schemas_.find(api_name);
- if (maybe_schema_resource == unloaded_schemas_.end())
- return NULL;
-
- LoadSchema(maybe_schema_resource->second);
- maybe_schema = schemas_.find(api_name);
- CHECK(schemas_.end() != maybe_schema);
- return maybe_schema->second.get();
+const DictionaryValue* ExtensionAPI::GetSchema(const std::string& full_name) {
+ std::string child_name;
+ std::string api_name = GetAPINameFromFullName(full_name, &child_name);
+
+ const DictionaryValue* result = NULL;
+ SchemaMap::iterator maybe_schema = schemas_.find(api_name);
+ if (maybe_schema != schemas_.end()) {
+ result = maybe_schema->second.get();
+ } else {
+ // Might not have loaded yet; or might just not exist.
+ std::map<std::string, base::StringPiece>::iterator maybe_schema_resource =
+ unloaded_schemas_.find(api_name);
+ if (maybe_schema_resource == unloaded_schemas_.end())
+ return NULL;
+
+ LoadSchema(maybe_schema_resource->second);
+ maybe_schema = schemas_.find(api_name);
+ CHECK(schemas_.end() != maybe_schema);
+ result = maybe_schema->second.get();
+ }
+
+ if (!child_name.empty())
+ result = GetSchemaChild(result, child_name);
+
+ return result;
}
scoped_ptr<std::set<std::string> > ExtensionAPI::GetAPIsForContext(
@@ -358,8 +503,19 @@ scoped_ptr<std::set<std::string> > ExtensionAPI::GetAPIsForContext(
// schemas.
LoadAllSchemas();
- scoped_ptr<std::set<std::string> > result(new std::set<std::string>());
+ std::set<std::string> temp_result;
+
+ // First handle all the APIs that have been converted to the feature system.
+ if (extension) {
+ for (APIFeatureMap::iterator iter = features_.begin();
+ iter != features_.end(); ++iter) {
+ if (IsAvailable(iter->first, extension, context))
+ temp_result.insert(iter->first);
+ }
+ }
+ // Second, fall back to the old way.
+ // TODO(aa): Remove this when all APIs have been converted.
switch (context) {
case Feature::UNSPECIFIED_CONTEXT:
break;
@@ -367,8 +523,8 @@ scoped_ptr<std::set<std::string> > ExtensionAPI::GetAPIsForContext(
case Feature::BLESSED_EXTENSION_CONTEXT:
// Availability is determined by the permissions of the extension.
CHECK(extension);
- GetAllowedAPIs(extension, result.get());
- ResolveDependencies(result.get());
+ GetAllowedAPIs(extension, &temp_result);
+ ResolveDependencies(&temp_result);
break;
case Feature::UNBLESSED_EXTENSION_CONTEXT:
@@ -376,27 +532,122 @@ scoped_ptr<std::set<std::string> > ExtensionAPI::GetAPIsForContext(
// Same as BLESSED_EXTENSION_CONTEXT, but only those APIs that are
// unprivileged.
CHECK(extension);
- GetAllowedAPIs(extension, result.get());
+ GetAllowedAPIs(extension, &temp_result);
// Resolving dependencies before removing unprivileged APIs means that
// some unprivileged APIs may have unrealised dependencies. Too bad!
- ResolveDependencies(result.get());
- RemovePrivilegedAPIs(result.get());
+ ResolveDependencies(&temp_result);
+ RemovePrivilegedAPIs(&temp_result);
break;
case Feature::WEB_PAGE_CONTEXT:
// Availablility is determined by the url.
CHECK(url.is_valid());
- GetAPIsMatchingURL(url, result.get());
+ GetAPIsMatchingURL(url, &temp_result);
break;
}
+ // Filter out all non-API features and remove the feature type part of the
+ // name.
+ scoped_ptr<std::set<std::string> > result(new std::set<std::string>());
+ for (std::set<std::string>::iterator iter = temp_result.begin();
+ iter != temp_result.end(); ++iter) {
+ std::string feature_type;
+ std::string feature_name;
+ SplitDependencyName(*iter, &feature_type, &feature_name);
+ if (feature_type == "api")
+ result->insert(feature_name);
+ }
+
+ return result.Pass();
+}
+
+scoped_ptr<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 api_features = features_.find(api_namespace);
+ if (api_features == features_.end())
+ return scoped_ptr<Feature>(NULL);
+
+ scoped_ptr<Feature> result;
+ FeatureMap::iterator child_feature = api_features->second->find(child_name);
+ if (child_feature != api_features->second->end()) {
+ // TODO(aa): Argh, having FeatureProvider return a scoped pointer was a
+ // mistake. See: crbug.com/120068.
+ result.reset(new Feature(*child_feature->second));
+ } else {
+ FeatureMap::iterator parent_feature = api_features->second->find("");
+ CHECK(parent_feature != api_features->second->end());
+ result.reset(new Feature(*parent_feature->second));
+ }
+
+ if (result->contexts()->empty()) {
+ result.reset();
+ LOG(ERROR) << "API feature '" << full_name
+ << "' must specify at least one context.";
+ }
+
return result.Pass();
}
+scoped_ptr<Feature> ExtensionAPI::GetFeatureDependency(
+ const std::string& full_name) {
+ std::string feature_type;
+ std::string feature_name;
+ SplitDependencyName(full_name, &feature_type, &feature_name);
+
+ FeatureProviderMap::iterator provider =
+ dependency_providers_.find(feature_type);
+ CHECK(provider != dependency_providers_.end()) << full_name;
+
+ scoped_ptr<Feature> feature(provider->second->GetFeature(feature_name));
+ CHECK(feature.get()) << full_name;
+
+ return feature.Pass();
+}
+
+std::string ExtensionAPI::GetAPINameFromFullName(const std::string& full_name,
+ std::string* child_name) {
+ std::string api_name_candidate = full_name;
+ while (true) {
+ if (features_.find(api_name_candidate) != features_.end() ||
+ schemas_.find(api_name_candidate) != schemas_.end() ||
+ unloaded_schemas_.find(api_name_candidate) != unloaded_schemas_.end()) {
+ std::string result = api_name_candidate;
+
+ if (child_name) {
+ if (result.length() < full_name.length())
+ *child_name = full_name.substr(result.length() + 1);
+ else
+ *child_name = "";
+ }
+
+ return result;
+ }
+
+ size_t last_dot_index = api_name_candidate.rfind('.');
+ if (last_dot_index == std::string::npos)
+ break;
+
+ api_name_candidate = api_name_candidate.substr(0, last_dot_index);
+ }
+
+ *child_name = "";
+ return "";
+}
+
void ExtensionAPI::GetAllowedAPIs(
const Extension* extension, std::set<std::string>* out) {
for (SchemaMap::const_iterator i = schemas_.begin(); i != schemas_.end();
++i) {
+ if (features_.find(i->first) != features_.end()) {
+ // This API is controlled by the feature system. Nothing to do here.
+ continue;
+ }
+
if (extension->required_permission_set()->HasAnyAccessToAPI(i->first) ||
extension->optional_permission_set()->HasAnyAccessToAPI(i->first)) {
out->insert(i->first);
@@ -421,17 +672,27 @@ void ExtensionAPI::GetMissingDependencies(
const std::string& api_name,
const std::set<std::string>& excluding,
std::set<std::string>* out) {
- const DictionaryValue* schema = GetSchema(api_name);
- CHECK(schema) << "Schema for " << api_name << " not found";
+ 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";
ListValue* dependencies = NULL;
if (!schema->GetList("dependencies", &dependencies))
return;
for (size_t i = 0; i < dependencies->GetSize(); ++i) {
- std::string api_name;
- if (dependencies->GetString(i, &api_name) && !excluding.count(api_name))
- out->insert(api_name);
+ std::string dependency_name;
+ if (dependencies->GetString(i, &dependency_name) &&
+ !excluding.count(dependency_name)) {
+ out->insert(dependency_name);
+ }
}
}
@@ -454,6 +715,11 @@ void ExtensionAPI::GetAPIsMatchingURL(const GURL& url,
std::set<std::string>* out) {
for (std::map<std::string, URLPatternSet>::const_iterator i =
url_matching_apis_.begin(); i != url_matching_apis_.end(); ++i) {
+ if (features_.find(i->first) != features_.end()) {
+ // This API is controlled by the feature system. Nothing to do.
+ continue;
+ }
+
if (i->second.MatchesURL(url))
out->insert(i->first);
}
diff --git a/chrome/common/extensions/api/extension_api.h b/chrome/common/extensions/api/extension_api.h
index 1ae7824..b2da190 100644
--- a/chrome/common/extensions/api/extension_api.h
+++ b/chrome/common/extensions/api/extension_api.h
@@ -17,6 +17,7 @@
#include "base/string_piece.h"
#include "base/values.h"
#include "chrome/common/extensions/feature.h"
+#include "chrome/common/extensions/feature_provider.h"
#include "chrome/common/extensions/url_pattern_set.h"
namespace base {
@@ -31,15 +32,50 @@ class ExtensionPermissionSet;
namespace extensions {
+class Feature;
+
// C++ Wrapper for the JSON API definitions in chrome/common/extensions/api/.
-class ExtensionAPI {
+//
+// 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 {
public:
- // Returns the single instance of this class.
- static ExtensionAPI* GetInstance();
-
- // Public for construction from unit tests. Use GetInstance() normally.
+ // Returns a single shared instance of this class. This is the typical use
+ // case in Chrome.
+ //
+ // TODO(aa): Make this const to enforce thread-safe usage.
+ static ExtensionAPI* GetSharedInstance();
+
+ // Creates a new instance configured the way ExtensionAPI typically is in
+ // Chrome. Use the default constructor to get a clean instance.
+ static ExtensionAPI* CreateWithDefaultConfiguration();
+
+ // Splits a name like "permission:bookmark" into ("permission", "bookmark").
+ // The first part refers to a type of feature, for example "manifest",
+ // "permission", or "api". The second part is the full name of the feature.
+ static void SplitDependencyName(const std::string& full_name,
+ std::string* feature_type,
+ std::string* feature_name);
+
+ // Creates a completely clean instance. Configure using RegisterSchema() and
+ // RegisterDependencyProvider before use.
ExtensionAPI();
- ~ExtensionAPI();
+ virtual ~ExtensionAPI();
+
+ void RegisterSchema(const std::string& api_name,
+ const base::StringPiece& source);
+
+ void RegisterDependencyProvider(const std::string& name,
+ FeatureProvider* provider);
+
+ // Returns true if the specified API is available. |api_full_name| can be
+ // either a namespace name (like "bookmarks") or a member name (like
+ // "bookmarks.create"). Returns true if the feature and all of its
+ // dependencies are available to the specified context.
+ bool IsAvailable(const std::string& api_full_name,
+ const Extension* extension,
+ Feature::Context context);
// Returns true if |name| is a privileged API path. Privileged paths can only
// be called from extension code which is running in its own designated
@@ -47,9 +83,9 @@ class ExtensionAPI {
// content scripts, or other low-privileged contexts.
bool IsPrivileged(const std::string& name);
- // Gets the schema for the extension API with namespace |api_name|.
+ // Gets the schema for the extension API with namespace |full_name|.
// Ownership remains with this object.
- const base::DictionaryValue* GetSchema(const std::string& api_name);
+ const base::DictionaryValue* GetSchema(const std::string& full_name);
// Gets the APIs available to |context| given an |extension| and |url|. The
// extension or URL may not be relevant to all contexts, and may be left
@@ -57,31 +93,52 @@ class ExtensionAPI {
scoped_ptr<std::set<std::string> > GetAPIsForContext(
Feature::Context context, const Extension* extension, const GURL& url);
+ // 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 scoped_ptr<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:
+ //
+ // "bookmarks.create" -> ("bookmarks", "create")
+ // "experimental.input.ui.cursorUp" -> ("experimental.input.ui", "cursorUp")
+ // "storage.sync.set" -> ("storage", "sync.get")
+ // "<unknown-api>.monkey" -> ("", "")
+ //
+ // The |child_name| parameter can be be NULL if you don't need that part.
+ std::string GetAPINameFromFullName(const std::string& full_name,
+ std::string* child_name);
+
+ void InitDefaultConfiguration();
+
+ // Loads the schemas registered with RegisterSchema().
+ void LoadAllSchemas();
+
private:
friend struct DefaultSingletonTraits<ExtensionAPI>;
// Loads a schema.
void LoadSchema(const base::StringPiece& schema);
- // Find an item in |list| with the specified property name and value, or NULL
- // if no such item exists.
- base::DictionaryValue* FindListItem(const base::ListValue* list,
- const std::string& property_name,
- const std::string& property_value);
-
// Returns true if the function or event under |namespace_node| with
// the specified |child_name| is privileged, or false otherwise. If the name
// is not found, defaults to privileged.
bool IsChildNamePrivileged(const base::DictionaryValue* namespace_node,
- const std::string& child_kind,
const std::string& child_name);
// Adds all APIs to |out| that |extension| has any permission (required or
// optional) to use.
+ // NOTE: This only works for non-feature-controlled APIs.
+ // TODO(aa): Remove this when all APIs are converted to the feature system.
void GetAllowedAPIs(const Extension* extension, std::set<std::string>* out);
+ // Gets a feature from any dependency provider.
+ scoped_ptr<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|
@@ -97,13 +154,10 @@ class ExtensionAPI {
void RemovePrivilegedAPIs(std::set<std::string>* apis);
// Adds an APIs that match |url| to |out|.
+ // NOTE: This only works for non-feature-controlled APIs.
+ // TODO(aa): Remove this when all APIs are converted to the feature system.
void GetAPIsMatchingURL(const GURL& url, std::set<std::string>* out);
- // Loads all remaining resources from |unloaded_schemas_|.
- void LoadAllSchemas();
-
- static ExtensionAPI* instance_;
-
// Map from each API that hasn't been loaded yet to the schema which defines
// it. Note that there may be multiple APIs per schema.
std::map<std::string, base::StringPiece> unloaded_schemas_;
@@ -121,6 +175,14 @@ class ExtensionAPI {
// APIs that have URL matching permissions.
std::map<std::string, URLPatternSet> url_matching_apis_;
+ typedef std::map<std::string, linked_ptr<Feature> > FeatureMap;
+ typedef std::map<std::string, linked_ptr<FeatureMap> > APIFeatureMap;
+ APIFeatureMap features_;
+
+ // FeatureProviders used for resolving dependencies.
+ typedef std::map<std::string, FeatureProvider*> FeatureProviderMap;
+ FeatureProviderMap dependency_providers_;
+
DISALLOW_COPY_AND_ASSIGN(ExtensionAPI);
};
diff --git a/chrome/common/extensions/api/extension_api_unittest.cc b/chrome/common/extensions/api/extension_api_unittest.cc
index 1c5a22e3..b0d8b6c 100644
--- a/chrome/common/extensions/api/extension_api_unittest.cc
+++ b/chrome/common/extensions/api/extension_api_unittest.cc
@@ -7,62 +7,190 @@
#include <string>
#include "base/file_path.h"
+#include "base/file_util.h"
+#include "base/json/json_writer.h"
+#include "base/message_loop.h"
#include "base/memory/ref_counted.h"
#include "base/memory/scoped_ptr.h"
+#include "base/path_service.h"
#include "base/values.h"
+#include "chrome/common/chrome_paths.h"
#include "chrome/common/extensions/extension.h"
+#include "content/test/test_browser_thread.h"
#include "testing/gtest/include/gtest/gtest.h"
+using content::BrowserThread;
+
namespace extensions {
namespace {
+class TestFeatureProvider : public FeatureProvider {
+ public:
+ explicit TestFeatureProvider(Feature::Context context)
+ : context_(context) {
+ }
+
+ virtual scoped_ptr<Feature> GetFeature(const std::string& name) OVERRIDE {
+ scoped_ptr<Feature> result(new Feature());
+ result->set_name(name);
+ result->extension_types()->insert(Extension::TYPE_EXTENSION);
+ result->contexts()->insert(context_);
+ return result.Pass();
+ }
+
+ private:
+ Feature::Context context_;
+};
+
+TEST(ExtensionAPI, Creation) {
+ MessageLoop loop(MessageLoop::TYPE_DEFAULT);
+ content::TestBrowserThread ui_thread(BrowserThread::UI, &loop);
+
+ ExtensionAPI* shared_instance = ExtensionAPI::GetSharedInstance();
+ EXPECT_EQ(shared_instance, ExtensionAPI::GetSharedInstance());
+
+ scoped_ptr<ExtensionAPI> new_instance(
+ ExtensionAPI::CreateWithDefaultConfiguration());
+ EXPECT_NE(new_instance.get(),
+ scoped_ptr<ExtensionAPI>(
+ ExtensionAPI::CreateWithDefaultConfiguration()).get());
+
+ ExtensionAPI empty_instance;
+
+ struct {
+ ExtensionAPI* api;
+ bool expect_populated;
+ } test_data[] = {
+ { shared_instance, true },
+ { new_instance.get(), true },
+ { &empty_instance, false }
+ };
+
+ for (size_t i = 0; i < ARRAYSIZE_UNSAFE(test_data); ++i) {
+ EXPECT_EQ(test_data[i].expect_populated,
+ test_data[i].api->GetSchema("bookmarks.create") != NULL);
+ }
+}
+
+TEST(ExtensionAPI, SplitDependencyName) {
+ struct {
+ std::string input;
+ std::string expected_feature_type;
+ std::string expected_feature_name;
+ } test_data[] = {
+ { "", "api", "" }, // assumes "api" when no type is present
+ { "foo", "api", "foo" },
+ { "foo:", "foo", "" },
+ { ":foo", "", "foo" },
+ { "foo:bar", "foo", "bar" },
+ { "foo:bar.baz", "foo", "bar.baz" }
+ };
+
+ for (size_t i = 0; i < ARRAYSIZE_UNSAFE(test_data); ++i) {
+ std::string feature_type;
+ std::string feature_name;
+ ExtensionAPI::SplitDependencyName(test_data[i].input, &feature_type,
+ &feature_name);
+ EXPECT_EQ(test_data[i].expected_feature_type, feature_type) << i;
+ EXPECT_EQ(test_data[i].expected_feature_name, feature_name) << i;
+ }
+}
+
TEST(ExtensionAPI, IsPrivileged) {
- ExtensionAPI extension_api;
+ scoped_ptr<ExtensionAPI> extension_api(
+ ExtensionAPI::CreateWithDefaultConfiguration());
- EXPECT_FALSE(extension_api.IsPrivileged("extension.connect"));
- EXPECT_FALSE(extension_api.IsPrivileged("extension.onConnect"));
+ EXPECT_FALSE(extension_api->IsPrivileged("extension.connect"));
+ EXPECT_FALSE(extension_api->IsPrivileged("extension.onConnect"));
// Properties are not supported yet.
- EXPECT_TRUE(extension_api.IsPrivileged("extension.lastError"));
+ EXPECT_TRUE(extension_api->IsPrivileged("extension.lastError"));
// Default unknown names to privileged for paranoia's sake.
- EXPECT_TRUE(extension_api.IsPrivileged("<unknown-namespace>"));
- EXPECT_TRUE(extension_api.IsPrivileged("extension.<unknown-member>"));
+ EXPECT_TRUE(extension_api->IsPrivileged(""));
+ EXPECT_TRUE(extension_api->IsPrivileged("<unknown-namespace>"));
+ EXPECT_TRUE(extension_api->IsPrivileged("extension.<unknown-member>"));
// Exists, but privileged.
- EXPECT_TRUE(extension_api.IsPrivileged("extension.getViews"));
- EXPECT_TRUE(extension_api.IsPrivileged("history.search"));
+ EXPECT_TRUE(extension_api->IsPrivileged("extension.getViews"));
+ EXPECT_TRUE(extension_api->IsPrivileged("history.search"));
// Whole APIs that are unprivileged.
- EXPECT_FALSE(extension_api.IsPrivileged("app.getDetails"));
- EXPECT_FALSE(extension_api.IsPrivileged("app.isInstalled"));
- EXPECT_FALSE(extension_api.IsPrivileged("storage.local"));
- EXPECT_FALSE(extension_api.IsPrivileged("storage.local.onChanged"));
- EXPECT_FALSE(extension_api.IsPrivileged("storage.local.set"));
- EXPECT_FALSE(extension_api.IsPrivileged("storage.local.MAX_ITEMS"));
- EXPECT_FALSE(extension_api.IsPrivileged("storage.set"));
+ EXPECT_FALSE(extension_api->IsPrivileged("app.getDetails"));
+ EXPECT_FALSE(extension_api->IsPrivileged("app.isInstalled"));
+ EXPECT_FALSE(extension_api->IsPrivileged("storage.local"));
+ EXPECT_FALSE(extension_api->IsPrivileged("storage.local.onChanged"));
+ EXPECT_FALSE(extension_api->IsPrivileged("storage.local.set"));
+ EXPECT_FALSE(extension_api->IsPrivileged("storage.local.MAX_ITEMS"));
+ EXPECT_FALSE(extension_api->IsPrivileged("storage.set"));
+}
+
+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 }
+ };
+
+ for (size_t i = 0; i < ARRAYSIZE_UNSAFE(test_data); ++i) {
+ 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);
+
+ std::string manifest_str;
+ ASSERT_TRUE(file_util::ReadFileToString(manifest_path, &manifest_str))
+ << test_data[i].filename;
+
+ ExtensionAPI api;
+ api.RegisterSchema("test", manifest_str);
+
+ TestFeatureProvider test2_provider(test_data[i].test2_contexts);
+ if (test_data[i].test2_contexts != Feature::UNSPECIFIED_CONTEXT) {
+ api.RegisterDependencyProvider("test2", &test2_provider);
+ }
+
+ api.LoadAllSchemas();
+ EXPECT_EQ(test_data[i].expect_is_privilged,
+ api.IsPrivileged(test_data[i].api_full_name)) << i;
+ }
}
TEST(ExtensionAPI, LazyGetSchema) {
- ExtensionAPI apis;
-
- EXPECT_EQ(NULL, apis.GetSchema(""));
- EXPECT_EQ(NULL, apis.GetSchema(""));
- EXPECT_EQ(NULL, apis.GetSchema("experimental"));
- EXPECT_EQ(NULL, apis.GetSchema("experimental"));
- EXPECT_EQ(NULL, apis.GetSchema("foo"));
- EXPECT_EQ(NULL, apis.GetSchema("foo"));
-
- EXPECT_TRUE(apis.GetSchema("experimental.dns"));
- EXPECT_TRUE(apis.GetSchema("experimental.dns"));
- EXPECT_TRUE(apis.GetSchema("experimental.infobars"));
- EXPECT_TRUE(apis.GetSchema("experimental.infobars"));
- EXPECT_TRUE(apis.GetSchema("extension"));
- EXPECT_TRUE(apis.GetSchema("extension"));
- EXPECT_TRUE(apis.GetSchema("omnibox"));
- EXPECT_TRUE(apis.GetSchema("omnibox"));
- EXPECT_TRUE(apis.GetSchema("storage"));
- EXPECT_TRUE(apis.GetSchema("storage"));
+ scoped_ptr<ExtensionAPI> apis(ExtensionAPI::CreateWithDefaultConfiguration());
+
+ EXPECT_EQ(NULL, apis->GetSchema(""));
+ EXPECT_EQ(NULL, apis->GetSchema(""));
+ EXPECT_EQ(NULL, apis->GetSchema("experimental"));
+ EXPECT_EQ(NULL, apis->GetSchema("experimental"));
+ EXPECT_EQ(NULL, apis->GetSchema("foo"));
+ EXPECT_EQ(NULL, apis->GetSchema("foo"));
+
+ EXPECT_TRUE(apis->GetSchema("experimental.dns"));
+ EXPECT_TRUE(apis->GetSchema("experimental.dns"));
+ EXPECT_TRUE(apis->GetSchema("experimental.infobars"));
+ EXPECT_TRUE(apis->GetSchema("experimental.infobars"));
+ EXPECT_TRUE(apis->GetSchema("extension"));
+ EXPECT_TRUE(apis->GetSchema("extension"));
+ EXPECT_TRUE(apis->GetSchema("omnibox"));
+ EXPECT_TRUE(apis->GetSchema("omnibox"));
+ EXPECT_TRUE(apis->GetSchema("storage"));
+ EXPECT_TRUE(apis->GetSchema("storage"));
}
scoped_refptr<Extension> CreateExtensionWithPermissions(
@@ -105,18 +233,19 @@ TEST(ExtensionAPI, ExtensionWithUnprivilegedAPIs) {
extension = CreateExtensionWithPermissions(permissions);
}
- ExtensionAPI extension_api;
+ scoped_ptr<ExtensionAPI> extension_api(
+ ExtensionAPI::CreateWithDefaultConfiguration());
scoped_ptr<std::set<std::string> > privileged_apis =
- extension_api.GetAPIsForContext(
+ extension_api->GetAPIsForContext(
Feature::BLESSED_EXTENSION_CONTEXT, extension.get(), GURL());
scoped_ptr<std::set<std::string> > unprivileged_apis =
- extension_api.GetAPIsForContext(
+ extension_api->GetAPIsForContext(
Feature::UNBLESSED_EXTENSION_CONTEXT, extension.get(), GURL());
scoped_ptr<std::set<std::string> > content_script_apis =
- extension_api.GetAPIsForContext(
+ extension_api->GetAPIsForContext(
Feature::CONTENT_SCRIPT_CONTEXT, extension.get(), GURL());
// "storage" is completely unprivileged.
@@ -141,9 +270,10 @@ TEST(ExtensionAPI, ExtensionWithDependencies) {
{
scoped_refptr<Extension> extension =
CreateExtensionWithPermission("ttsEngine");
- scoped_ptr<std::set<std::string> > apis =
- ExtensionAPI().GetAPIsForContext(
- Feature::BLESSED_EXTENSION_CONTEXT, extension.get(), GURL());
+ scoped_ptr<ExtensionAPI> api(
+ ExtensionAPI::CreateWithDefaultConfiguration());
+ scoped_ptr<std::set<std::string> > apis = api->GetAPIsForContext(
+ Feature::BLESSED_EXTENSION_CONTEXT, extension.get(), GURL());
EXPECT_EQ(1u, apis->count("ttsEngine"));
EXPECT_EQ(1u, apis->count("tts"));
}
@@ -153,9 +283,10 @@ TEST(ExtensionAPI, ExtensionWithDependencies) {
{
scoped_refptr<Extension> extension =
CreateExtensionWithPermission("tts");
- scoped_ptr<std::set<std::string> > apis =
- ExtensionAPI().GetAPIsForContext(
- Feature::BLESSED_EXTENSION_CONTEXT, extension.get(), GURL());
+ scoped_ptr<ExtensionAPI> api(
+ ExtensionAPI::CreateWithDefaultConfiguration());
+ scoped_ptr<std::set<std::string> > apis = api->GetAPIsForContext(
+ Feature::BLESSED_EXTENSION_CONTEXT, extension.get(), GURL());
EXPECT_EQ(0u, apis->count("ttsEngine"));
EXPECT_EQ(1u, apis->count("tts"));
}
@@ -169,26 +300,124 @@ bool MatchesURL(
}
TEST(ExtensionAPI, URLMatching) {
- ExtensionAPI api;
+ scoped_ptr<ExtensionAPI> api(ExtensionAPI::CreateWithDefaultConfiguration());
// "app" API is available to all URLs that content scripts can be injected.
- EXPECT_TRUE(MatchesURL(&api, "app", "http://example.com/example.html"));
- EXPECT_TRUE(MatchesURL(&api, "app", "https://blah.net"));
- EXPECT_TRUE(MatchesURL(&api, "app", "file://somefile.html"));
+ EXPECT_TRUE(MatchesURL(api.get(), "app", "http://example.com/example.html"));
+ EXPECT_TRUE(MatchesURL(api.get(), "app", "https://blah.net"));
+ EXPECT_TRUE(MatchesURL(api.get(), "app", "file://somefile.html"));
// But not internal URLs (for chrome-extension:// the app API is injected by
// GetSchemasForExtension).
- EXPECT_FALSE(MatchesURL(&api, "app", "about:flags"));
- EXPECT_FALSE(MatchesURL(&api, "app", "chrome://flags"));
- EXPECT_FALSE(MatchesURL(&api, "app", "chrome-extension://fakeextension"));
+ EXPECT_FALSE(MatchesURL(api.get(), "app", "about:flags"));
+ EXPECT_FALSE(MatchesURL(api.get(), "app", "chrome://flags"));
+ EXPECT_FALSE(MatchesURL(api.get(), "app",
+ "chrome-extension://fakeextension"));
// "storage" API (for example) isn't available to any URLs.
- EXPECT_FALSE(MatchesURL(&api, "storage", "http://example.com/example.html"));
- EXPECT_FALSE(MatchesURL(&api, "storage", "https://blah.net"));
- EXPECT_FALSE(MatchesURL(&api, "storage", "file://somefile.html"));
- EXPECT_FALSE(MatchesURL(&api, "storage", "about:flags"));
- EXPECT_FALSE(MatchesURL(&api, "storage", "chrome://flags"));
- EXPECT_FALSE(MatchesURL(&api, "storage", "chrome-extension://fakeextension"));
+ EXPECT_FALSE(MatchesURL(api.get(), "storage",
+ "http://example.com/example.html"));
+ EXPECT_FALSE(MatchesURL(api.get(), "storage", "https://blah.net"));
+ EXPECT_FALSE(MatchesURL(api.get(), "storage", "file://somefile.html"));
+ EXPECT_FALSE(MatchesURL(api.get(), "storage", "about:flags"));
+ EXPECT_FALSE(MatchesURL(api.get(), "storage", "chrome://flags"));
+ EXPECT_FALSE(MatchesURL(api.get(), "storage",
+ "chrome-extension://fakeextension"));
+}
+
+TEST(ExtensionAPI, GetAPINameFromFullName) {
+ struct {
+ std::string input;
+ std::string api_name;
+ std::string child_name;
+ } test_data[] = {
+ { "", "", "" },
+ { "unknown", "", "" },
+ { "bookmarks", "bookmarks", "" },
+ { "bookmarks.", "bookmarks", "" },
+ { ".bookmarks", "", "" },
+ { "bookmarks.create", "bookmarks", "create" },
+ { "bookmarks.create.", "bookmarks", "create." },
+ { "bookmarks.create.monkey", "bookmarks", "create.monkey" },
+ { "experimental.bookmarkManager", "experimental.bookmarkManager", "" },
+ { "experimental.bookmarkManager.copy", "experimental.bookmarkManager",
+ "copy" }
+ };
+
+ scoped_ptr<ExtensionAPI> api(ExtensionAPI::CreateWithDefaultConfiguration());
+ for (size_t i = 0; i < ARRAYSIZE_UNSAFE(test_data); ++i) {
+ std::string child_name;
+ std::string api_name = api->GetAPINameFromFullName(test_data[i].input,
+ &child_name);
+ EXPECT_EQ(test_data[i].api_name, api_name) << test_data[i].input;
+ EXPECT_EQ(test_data[i].child_name, child_name) << test_data[i].input;
+ }
+}
+
+TEST(ExtensionAPI, DefaultConfigurationFeatures) {
+ scoped_ptr<ExtensionAPI> api(ExtensionAPI::CreateWithDefaultConfiguration());
+
+ scoped_ptr<Feature> bookmarks(api->GetFeature("bookmarks"));
+ scoped_ptr<Feature> bookmarks_create(api->GetFeature("bookmarks.create"));
+
+ struct {
+ Feature* feature;
+ // TODO(aa): More stuff to test over time.
+ } test_data[] = {
+ { bookmarks.get() },
+ { bookmarks_create.get() }
+ };
+
+ for (size_t i = 0; i < ARRAYSIZE_UNSAFE(test_data); ++i) {
+ Feature* feature = test_data[i].feature;
+ ASSERT_TRUE(feature) << i;
+
+ EXPECT_TRUE(feature->whitelist()->empty());
+ EXPECT_TRUE(feature->extension_types()->empty());
+
+ EXPECT_EQ(1u, feature->contexts()->size());
+ EXPECT_TRUE(feature->contexts()->count(
+ Feature::BLESSED_EXTENSION_CONTEXT));
+
+ EXPECT_EQ(Feature::UNSPECIFIED_LOCATION, feature->location());
+ EXPECT_EQ(Feature::UNSPECIFIED_PLATFORM, feature->platform());
+ EXPECT_EQ(0, feature->min_manifest_version());
+ EXPECT_EQ(0, feature->max_manifest_version());
+ }
+}
+
+TEST(ExtensionAPI, FeaturesRequireContexts) {
+ scoped_ptr<ListValue> schema1(new ListValue());
+ DictionaryValue* feature_definition = new DictionaryValue();
+ schema1->Append(feature_definition);
+ feature_definition->SetString("namespace", "test");
+ feature_definition->SetBoolean("uses_feature_system", true);
+
+ scoped_ptr<ListValue> schema2(schema1->DeepCopy());
+
+ ListValue* contexts = new ListValue();
+ contexts->Append(Value::CreateStringValue("content_script"));
+ feature_definition->Set("contexts", contexts);
+
+ struct {
+ ListValue* schema;
+ bool expect_success;
+ } test_data[] = {
+ { schema1.get(), true },
+ { schema2.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));
+ api.LoadAllSchemas();
+
+ scoped_ptr<Feature> feature(api.GetFeature("test"));
+ EXPECT_EQ(test_data[i].expect_success, feature.get() != NULL) << i;
+ }
}
} // namespace
diff --git a/chrome/common/extensions/extension.cc b/chrome/common/extensions/extension.cc
index f8262b3..3869df1 100644
--- a/chrome/common/extensions/extension.cc
+++ b/chrome/common/extensions/extension.cc
@@ -3314,7 +3314,11 @@ bool Extension::ParsePermissions(const char* key,
CHECK(feature.get());
extensions::Feature::Availability availability =
- feature->IsAvailable(this);
+ feature->IsAvailableToManifest(
+ id(),
+ GetType(),
+ extensions::Feature::ConvertLocation(location()),
+ manifest_version());
if (availability != extensions::Feature::IS_AVAILABLE) {
// We special case hosted apps because some old versions of Chrome did
// not return errors here and we ended up with extensions in the store
diff --git a/chrome/common/extensions/feature.cc b/chrome/common/extensions/feature.cc
index 1bda59b..9f75435 100644
--- a/chrome/common/extensions/feature.cc
+++ b/chrome/common/extensions/feature.cc
@@ -51,6 +51,7 @@ void ParseSet(const DictionaryValue* value,
if (!value->GetList(property, &list_value))
return;
+ set->clear();
for (size_t i = 0; i < list_value->GetSize(); ++i) {
std::string str_val;
CHECK(list_value->GetString(i, &str_val)) << property << " " << i;
@@ -76,6 +77,7 @@ void ParseEnum(const DictionaryValue* value,
std::string string_value;
if (!value->GetString(property, &string_value))
return;
+
ParseEnum(string_value, enum_value, mapping);
}
@@ -84,6 +86,11 @@ void ParseEnumSet(const DictionaryValue* value,
const std::string& property,
std::set<T>* enum_set,
const std::map<std::string, T>& mapping) {
+ if (!value->HasKey(property))
+ return;
+
+ enum_set->clear();
+
std::string property_string;
if (value->GetString(property, &property_string)) {
if (property_string == "all") {
@@ -116,28 +123,27 @@ Feature::Feature()
max_manifest_version_(0) {
}
-Feature::~Feature() {
+Feature::Feature(const Feature& other)
+ : whitelist_(other.whitelist_),
+ extension_types_(other.extension_types_),
+ contexts_(other.contexts_),
+ location_(other.location_),
+ platform_(other.platform_),
+ min_manifest_version_(other.min_manifest_version_),
+ max_manifest_version_(other.max_manifest_version_) {
}
-// static
-scoped_ptr<Feature> Feature::Parse(const DictionaryValue* value) {
- scoped_ptr<Feature> feature(new Feature());
-
- ParseSet(value, "whitelist", feature->whitelist());
- ParseEnumSet<Extension::Type>(value, "extension_types",
- feature->extension_types(),
- g_mappings.Get().extension_types);
- ParseEnumSet<Context>(value, "contexts", feature->contexts(),
- g_mappings.Get().contexts);
- ParseEnum<Location>(value, "location", &feature->location_,
- g_mappings.Get().locations);
- ParseEnum<Platform>(value, "platform", &feature->platform_,
- g_mappings.Get().platforms);
-
- value->GetInteger("min_manifest_version", &feature->min_manifest_version_);
- value->GetInteger("max_manifest_version", &feature->max_manifest_version_);
+Feature::~Feature() {
+}
- return feature.Pass();
+bool Feature::Equals(const Feature& other) const {
+ return whitelist_ == other.whitelist_ &&
+ extension_types_ == other.extension_types_ &&
+ contexts_ == other.contexts_ &&
+ location_ == other.location_ &&
+ platform_ == other.platform_ &&
+ min_manifest_version_ == other.min_manifest_version_ &&
+ max_manifest_version_ == other.max_manifest_version_;
}
// static
@@ -157,6 +163,20 @@ Feature::Location Feature::ConvertLocation(Extension::Location location) {
return UNSPECIFIED_LOCATION;
}
+void Feature::Parse(const DictionaryValue* value) {
+ ParseSet(value, "whitelist", &whitelist_);
+ ParseEnumSet<Extension::Type>(value, "extension_types", &extension_types_,
+ g_mappings.Get().extension_types);
+ ParseEnumSet<Context>(value, "contexts", &contexts_,
+ g_mappings.Get().contexts);
+ ParseEnum<Location>(value, "location", &location_,
+ g_mappings.Get().locations);
+ ParseEnum<Platform>(value, "platform", &platform_,
+ g_mappings.Get().platforms);
+ value->GetInteger("min_manifest_version", &min_manifest_version_);
+ value->GetInteger("max_manifest_version", &max_manifest_version_);
+}
+
std::string Feature::GetErrorMessage(Feature::Availability result) {
switch (result) {
case IS_AVAILABLE:
@@ -184,12 +204,12 @@ std::string Feature::GetErrorMessage(Feature::Availability result) {
}
}
-Feature::Availability Feature::IsAvailable(const std::string& extension_id,
- Extension::Type type,
- Location location,
- Context context,
- Platform platform,
- int manifest_version) {
+Feature::Availability Feature::IsAvailableToManifest(
+ const std::string& extension_id,
+ Extension::Type type,
+ Location location,
+ int manifest_version,
+ Platform platform) const {
// Component extensions can access any feature.
if (location == COMPONENT_LOCATION)
return IS_AVAILABLE;
@@ -215,11 +235,6 @@ Feature::Availability Feature::IsAvailable(const std::string& extension_id,
return INVALID_TYPE;
}
- if (!contexts_.empty() &&
- contexts_.find(context) == contexts_.end()) {
- return INVALID_CONTEXT;
- }
-
if (location_ != UNSPECIFIED_LOCATION && location_ != location)
return INVALID_LOCATION;
@@ -235,4 +250,25 @@ Feature::Availability Feature::IsAvailable(const std::string& extension_id,
return IS_AVAILABLE;
}
+Feature::Availability Feature::IsAvailableToContext(
+ const Extension* extension,
+ Feature::Context context,
+ Feature::Platform platform) const {
+ Availability result = IsAvailableToManifest(
+ extension->id(),
+ extension->GetType(),
+ ConvertLocation(extension->location()),
+ extension->manifest_version(),
+ platform);
+ if (result != IS_AVAILABLE)
+ return result;
+
+ if (!contexts_.empty() &&
+ contexts_.find(context) == contexts_.end()) {
+ return INVALID_CONTEXT;
+ }
+
+ return IS_AVAILABLE;
+}
+
} // namespace
diff --git a/chrome/common/extensions/feature.h b/chrome/common/extensions/feature.h
index 6423ccb..3747670 100644
--- a/chrome/common/extensions/feature.h
+++ b/chrome/common/extensions/feature.h
@@ -60,14 +60,17 @@ class Feature {
INVALID_LOCATION,
INVALID_PLATFORM,
INVALID_MIN_MANIFEST_VERSION,
- INVALID_MAX_MANIFEST_VERSION
+ INVALID_MAX_MANIFEST_VERSION,
+ NOT_PRESENT,
+ DEPENDENCY_NOT_PRESENT
};
Feature();
- ~Feature();
+ Feature(const Feature& other);
+ virtual ~Feature();
- // Parses a feature from its JSON representation.
- static scoped_ptr<Feature> Parse(const DictionaryValue* value);
+ const std::string& name() const { return name_; }
+ void set_name(const std::string& name) { name_ = name; }
// Gets the platform the code is currently running on.
static Platform GetCurrentPlatform();
@@ -95,43 +98,45 @@ class Feature {
max_manifest_version_ = max_manifest_version;
}
- // Returns true if the feature is available to the specified extension. Use
- // this overload for features that are not associated with a specific context.
- Availability IsAvailable(const Extension* extension) {
- return IsAvailable(extension, UNSPECIFIED_CONTEXT);
+ // 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);
+
+ // Returns true if the feature contains the same values as another.
+ bool Equals(const Feature& other) const;
+
+ // Returns true if the feature is available to be parsed into a new extension
+ // manifest.
+ Availability IsAvailableToManifest(const std::string& extension_id,
+ Extension::Type type,
+ Location location,
+ int manifest_version) const {
+ return IsAvailableToManifest(extension_id, type, location, manifest_version,
+ GetCurrentPlatform());
}
-
- // Returns true if the feature is available to the specified extension, in the
- // specified context type.
- Availability IsAvailable(const Extension* extension, Context context) {
- return IsAvailable(extension->id(), extension->GetType(),
- ConvertLocation(extension->location()), context,
- GetCurrentPlatform(),
- extension->manifest_version());
- }
-
- // Returns true if the feature is available to extensions with the specified
- // properties. Use this overload for features that are not associated with a
- // specific context, and when a full Extension object is not available.
- Availability IsAvailable(const std::string& extension_id,
- Extension::Type type, Location location,
- int manifest_version) {
- return IsAvailable(extension_id, type, location, UNSPECIFIED_CONTEXT,
- GetCurrentPlatform(), manifest_version);
+ Availability IsAvailableToManifest(const std::string& extension_id,
+ Extension::Type type,
+ Location location,
+ int manifest_version,
+ Platform platform) const;
+
+ // Returns true if the feature is available to be used in the specified
+ // extension and context.
+ Availability IsAvailableToContext(const Extension* extension,
+ Context context) const {
+ return IsAvailableToContext(extension, context, GetCurrentPlatform());
}
-
- // Returns true if the feature is available to extensions with the specified
- // properties, in the specified context type, and on the specified platform.
- // This overload is mainly used for testing.
- Availability IsAvailable(const std::string& extension_id,
- Extension::Type type, Location location,
- Context context, Platform platform,
- int manifest_version);
+ virtual Availability IsAvailableToContext(const Extension* extension,
+ Context context,
+ Platform platform) const;
// Returns an error message for an Availability code.
std::string GetErrorMessage(Availability result);
private:
+ std::string name_;
+
// For clarify and consistency, we handle the default value of each of these
// members the same way: it matches everything. It is up to the higher level
// code that reads Features out of static data to validate that data and set
@@ -143,8 +148,6 @@ class Feature {
Platform platform_; // we only care about chromeos/not-chromeos now
int min_manifest_version_;
int max_manifest_version_;
-
- DISALLOW_COPY_AND_ASSIGN(Feature);
};
} // namespace extensions
diff --git a/chrome/common/extensions/feature_provider.h b/chrome/common/extensions/feature_provider.h
new file mode 100644
index 0000000..0db0468
--- /dev/null
+++ b/chrome/common/extensions/feature_provider.h
@@ -0,0 +1,29 @@
+// 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_FEATURE_PROVIDER_H_
+#define CHROME_COMMON_EXTENSIONS_FEATURE_PROVIDER_H_
+#pragma once
+
+#include <string>
+
+#include "base/memory/scoped_ptr.h"
+
+namespace extensions {
+
+class Feature;
+
+// Implemented by classes that can vend features.
+class FeatureProvider {
+ public:
+ FeatureProvider() {}
+ virtual ~FeatureProvider() {}
+
+ // Returns the feature with the specified name.
+ virtual scoped_ptr<Feature> GetFeature(const std::string& name) = 0;
+};
+
+} // namespace extensions
+
+#endif // CHROME_COMMON_EXTENSIONS_FEATURE_PROVIDER_H_
diff --git a/chrome/common/extensions/feature_unittest.cc b/chrome/common/extensions/feature_unittest.cc
index 4442744..c4cc1a3 100644
--- a/chrome/common/extensions/feature_unittest.cc
+++ b/chrome/common/extensions/feature_unittest.cc
@@ -13,7 +13,6 @@ namespace {
struct IsAvailableTestData {
std::string extension_id;
Extension::Type extension_type;
- Feature::Context context;
Feature::Location location;
Feature::Platform platform;
int manifest_version;
@@ -24,25 +23,25 @@ struct IsAvailableTestData {
TEST(ExtensionFeatureTest, IsAvailableNullCase) {
const IsAvailableTestData tests[] = {
- { "", Extension::TYPE_UNKNOWN, Feature::UNSPECIFIED_CONTEXT,
+ { "", Extension::TYPE_UNKNOWN,
Feature::UNSPECIFIED_LOCATION, Feature::UNSPECIFIED_PLATFORM, -1,
Feature::IS_AVAILABLE },
- { "random-extension", Extension::TYPE_UNKNOWN, Feature::UNSPECIFIED_CONTEXT,
+ { "random-extension", Extension::TYPE_UNKNOWN,
Feature::UNSPECIFIED_LOCATION, Feature::UNSPECIFIED_PLATFORM, -1,
Feature::IS_AVAILABLE },
- { "", Extension::TYPE_PACKAGED_APP, Feature::UNSPECIFIED_CONTEXT,
+ { "", Extension::TYPE_PACKAGED_APP,
Feature::UNSPECIFIED_LOCATION, Feature::UNSPECIFIED_PLATFORM, -1,
Feature::IS_AVAILABLE },
- { "", Extension::TYPE_UNKNOWN, Feature::BLESSED_EXTENSION_CONTEXT,
+ { "", Extension::TYPE_UNKNOWN,
Feature::UNSPECIFIED_LOCATION, Feature::UNSPECIFIED_PLATFORM, -1,
Feature::IS_AVAILABLE },
- { "", Extension::TYPE_UNKNOWN, Feature::UNSPECIFIED_CONTEXT,
+ { "", Extension::TYPE_UNKNOWN,
Feature::COMPONENT_LOCATION, Feature::UNSPECIFIED_PLATFORM, -1,
Feature::IS_AVAILABLE },
- { "", Extension::TYPE_UNKNOWN, Feature::UNSPECIFIED_CONTEXT,
+ { "", Extension::TYPE_UNKNOWN,
Feature::UNSPECIFIED_LOCATION, Feature::CHROMEOS_PLATFORM, -1,
Feature::IS_AVAILABLE },
- { "", Extension::TYPE_UNKNOWN, Feature::UNSPECIFIED_CONTEXT,
+ { "", Extension::TYPE_UNKNOWN,
Feature::UNSPECIFIED_LOCATION, Feature::UNSPECIFIED_PLATFORM, 25,
Feature::IS_AVAILABLE }
};
@@ -51,9 +50,11 @@ TEST(ExtensionFeatureTest, IsAvailableNullCase) {
for (size_t i = 0; i < arraysize(tests); ++i) {
const IsAvailableTestData& test = tests[i];
EXPECT_EQ(test.expected_result,
- feature.IsAvailable(test.extension_id, test.extension_type,
- test.location, test.context, test.platform,
- test.manifest_version));
+ feature.IsAvailableToManifest(test.extension_id,
+ test.extension_type,
+ test.location,
+ test.manifest_version,
+ test.platform));
}
}
@@ -62,24 +63,24 @@ TEST(ExtensionFeatureTest, Whitelist) {
feature.whitelist()->insert("foo");
feature.whitelist()->insert("bar");
- EXPECT_EQ(Feature::IS_AVAILABLE, feature.IsAvailable(
- "foo", Extension::TYPE_UNKNOWN, Feature::UNSPECIFIED_LOCATION,
- Feature::UNSPECIFIED_CONTEXT, Feature::UNSPECIFIED_PLATFORM, -1));
- EXPECT_EQ(Feature::IS_AVAILABLE, feature.IsAvailable(
- "bar", Extension::TYPE_UNKNOWN, Feature::UNSPECIFIED_LOCATION,
- Feature::UNSPECIFIED_CONTEXT, Feature::UNSPECIFIED_PLATFORM, -1));
-
- EXPECT_EQ(Feature::NOT_FOUND_IN_WHITELIST, feature.IsAvailable(
- "baz", Extension::TYPE_UNKNOWN, Feature::UNSPECIFIED_LOCATION,
- Feature::UNSPECIFIED_CONTEXT, Feature::UNSPECIFIED_PLATFORM, -1));
- EXPECT_EQ(Feature::NOT_FOUND_IN_WHITELIST, feature.IsAvailable(
- "", Extension::TYPE_UNKNOWN, Feature::UNSPECIFIED_LOCATION,
- Feature::UNSPECIFIED_CONTEXT, Feature::UNSPECIFIED_PLATFORM, -1));
+ EXPECT_EQ(Feature::IS_AVAILABLE, feature.IsAvailableToManifest(
+ "foo", Extension::TYPE_UNKNOWN, Feature::UNSPECIFIED_LOCATION, -1,
+ Feature::UNSPECIFIED_PLATFORM));
+ EXPECT_EQ(Feature::IS_AVAILABLE, feature.IsAvailableToManifest(
+ "bar", Extension::TYPE_UNKNOWN, Feature::UNSPECIFIED_LOCATION, -1,
+ Feature::UNSPECIFIED_PLATFORM));
+
+ EXPECT_EQ(Feature::NOT_FOUND_IN_WHITELIST, feature.IsAvailableToManifest(
+ "baz", Extension::TYPE_UNKNOWN, Feature::UNSPECIFIED_LOCATION, -1,
+ Feature::UNSPECIFIED_PLATFORM));
+ EXPECT_EQ(Feature::NOT_FOUND_IN_WHITELIST, feature.IsAvailableToManifest(
+ "", Extension::TYPE_UNKNOWN, Feature::UNSPECIFIED_LOCATION, -1,
+ Feature::UNSPECIFIED_PLATFORM));
feature.extension_types()->insert(Extension::TYPE_PACKAGED_APP);
- EXPECT_EQ(Feature::NOT_FOUND_IN_WHITELIST, feature.IsAvailable(
- "baz", Extension::TYPE_PACKAGED_APP, Feature::UNSPECIFIED_LOCATION,
- Feature::UNSPECIFIED_CONTEXT, Feature::UNSPECIFIED_PLATFORM, -1));
+ EXPECT_EQ(Feature::NOT_FOUND_IN_WHITELIST, feature.IsAvailableToManifest(
+ "baz", Extension::TYPE_PACKAGED_APP, Feature::UNSPECIFIED_LOCATION, -1,
+ Feature::UNSPECIFIED_PLATFORM));
}
TEST(ExtensionFeatureTest, PackageType) {
@@ -87,39 +88,84 @@ TEST(ExtensionFeatureTest, PackageType) {
feature.extension_types()->insert(Extension::TYPE_EXTENSION);
feature.extension_types()->insert(Extension::TYPE_PACKAGED_APP);
- EXPECT_EQ(Feature::IS_AVAILABLE, feature.IsAvailable(
- "", Extension::TYPE_EXTENSION, Feature::UNSPECIFIED_LOCATION,
- Feature::UNSPECIFIED_CONTEXT, Feature::UNSPECIFIED_PLATFORM, -1));
- EXPECT_EQ(Feature::IS_AVAILABLE, feature.IsAvailable(
- "", Extension::TYPE_PACKAGED_APP, Feature::UNSPECIFIED_LOCATION,
- Feature::UNSPECIFIED_CONTEXT, Feature::UNSPECIFIED_PLATFORM, -1));
-
- EXPECT_EQ(Feature::INVALID_TYPE, feature.IsAvailable(
- "", Extension::TYPE_UNKNOWN, Feature::UNSPECIFIED_LOCATION,
- Feature::UNSPECIFIED_CONTEXT, Feature::UNSPECIFIED_PLATFORM, -1));
- EXPECT_EQ(Feature::INVALID_TYPE, feature.IsAvailable(
- "", Extension::TYPE_THEME, Feature::UNSPECIFIED_LOCATION,
- Feature::UNSPECIFIED_CONTEXT, Feature::UNSPECIFIED_PLATFORM, -1));
+ EXPECT_EQ(Feature::IS_AVAILABLE, feature.IsAvailableToManifest(
+ "", Extension::TYPE_EXTENSION, Feature::UNSPECIFIED_LOCATION, -1,
+ Feature::UNSPECIFIED_PLATFORM));
+ EXPECT_EQ(Feature::IS_AVAILABLE, feature.IsAvailableToManifest(
+ "", Extension::TYPE_PACKAGED_APP, Feature::UNSPECIFIED_LOCATION, -1,
+ Feature::UNSPECIFIED_PLATFORM));
+
+ EXPECT_EQ(Feature::INVALID_TYPE, feature.IsAvailableToManifest(
+ "", Extension::TYPE_UNKNOWN, Feature::UNSPECIFIED_LOCATION, -1,
+ Feature::UNSPECIFIED_PLATFORM));
+ EXPECT_EQ(Feature::INVALID_TYPE, feature.IsAvailableToManifest(
+ "", Extension::TYPE_THEME, Feature::UNSPECIFIED_LOCATION, -1,
+ Feature::UNSPECIFIED_PLATFORM));
}
TEST(ExtensionFeatureTest, Context) {
Feature feature;
feature.contexts()->insert(Feature::BLESSED_EXTENSION_CONTEXT);
- feature.contexts()->insert(Feature::CONTENT_SCRIPT_CONTEXT);
+ feature.extension_types()->insert(Extension::TYPE_PACKAGED_APP);
+ feature.set_platform(Feature::CHROMEOS_PLATFORM);
+ feature.set_min_manifest_version(21);
+ feature.set_max_manifest_version(25);
+
+ DictionaryValue manifest;
+ manifest.SetString("name", "test");
+ manifest.SetString("version", "1");
+ manifest.SetInteger("manifest_version", 21);
+ manifest.SetString("app.launch.local_path", "foo.html");
+
+ std::string error;
+ scoped_refptr<const Extension> extension(Extension::Create(
+ FilePath(), Extension::INTERNAL, manifest, Extension::NO_FLAGS, &error));
+ EXPECT_EQ("", error);
+ ASSERT_TRUE(extension.get());
+
+ feature.whitelist()->insert("monkey");
+ EXPECT_EQ(Feature::NOT_FOUND_IN_WHITELIST, feature.IsAvailableToContext(
+ extension.get(), Feature::BLESSED_EXTENSION_CONTEXT,
+ Feature::CHROMEOS_PLATFORM));
+ feature.whitelist()->clear();
+
+ feature.extension_types()->clear();
+ feature.extension_types()->insert(Extension::TYPE_THEME);
+ EXPECT_EQ(Feature::INVALID_TYPE, feature.IsAvailableToContext(
+ extension.get(), Feature::BLESSED_EXTENSION_CONTEXT,
+ Feature::CHROMEOS_PLATFORM));
+ feature.extension_types()->clear();
+ feature.extension_types()->insert(Extension::TYPE_PACKAGED_APP);
- EXPECT_EQ(Feature::IS_AVAILABLE, feature.IsAvailable(
- "", Extension::TYPE_UNKNOWN, Feature::UNSPECIFIED_LOCATION,
- Feature::BLESSED_EXTENSION_CONTEXT, Feature::UNSPECIFIED_PLATFORM, -1));
- EXPECT_EQ(Feature::IS_AVAILABLE, feature.IsAvailable(
- "", Extension::TYPE_UNKNOWN, Feature::UNSPECIFIED_LOCATION,
- Feature::CONTENT_SCRIPT_CONTEXT, Feature::UNSPECIFIED_PLATFORM, -1));
+ feature.contexts()->clear();
+ feature.contexts()->insert(Feature::UNBLESSED_EXTENSION_CONTEXT);
+ EXPECT_EQ(Feature::INVALID_CONTEXT, feature.IsAvailableToContext(
+ extension.get(), Feature::BLESSED_EXTENSION_CONTEXT,
+ Feature::CHROMEOS_PLATFORM));
+ feature.contexts()->clear();
+ feature.contexts()->insert(Feature::BLESSED_EXTENSION_CONTEXT);
- EXPECT_EQ(Feature::INVALID_CONTEXT, feature.IsAvailable(
- "", Extension::TYPE_UNKNOWN, Feature::UNSPECIFIED_LOCATION,
- Feature::UNBLESSED_EXTENSION_CONTEXT, Feature::UNSPECIFIED_PLATFORM, -1));
- EXPECT_EQ(Feature::INVALID_CONTEXT, feature.IsAvailable(
- "", Extension::TYPE_UNKNOWN, Feature::UNSPECIFIED_LOCATION,
- Feature::UNSPECIFIED_CONTEXT, Feature::UNSPECIFIED_PLATFORM, -1));
+ feature.set_location(Feature::COMPONENT_LOCATION);
+ EXPECT_EQ(Feature::INVALID_LOCATION, feature.IsAvailableToContext(
+ extension.get(), Feature::BLESSED_EXTENSION_CONTEXT,
+ Feature::CHROMEOS_PLATFORM));
+ feature.set_location(Feature::UNSPECIFIED_LOCATION);
+
+ EXPECT_EQ(Feature::INVALID_PLATFORM, feature.IsAvailableToContext(
+ extension.get(), Feature::BLESSED_EXTENSION_CONTEXT,
+ Feature::UNSPECIFIED_PLATFORM));
+
+ feature.set_min_manifest_version(22);
+ EXPECT_EQ(Feature::INVALID_MIN_MANIFEST_VERSION, feature.IsAvailableToContext(
+ extension.get(), Feature::BLESSED_EXTENSION_CONTEXT,
+ Feature::CHROMEOS_PLATFORM));
+ feature.set_min_manifest_version(21);
+
+ feature.set_max_manifest_version(18);
+ EXPECT_EQ(Feature::INVALID_MAX_MANIFEST_VERSION, feature.IsAvailableToContext(
+ extension.get(), Feature::BLESSED_EXTENSION_CONTEXT,
+ Feature::CHROMEOS_PLATFORM));
+ feature.set_max_manifest_version(25);
}
TEST(ExtensionFeatureTest, Location) {
@@ -128,65 +174,70 @@ TEST(ExtensionFeatureTest, Location) {
// If the feature specifies "component" as its location, then only component
// extensions can access it.
feature.set_location(Feature::COMPONENT_LOCATION);
- EXPECT_EQ(Feature::IS_AVAILABLE, feature.IsAvailable(
- "", Extension::TYPE_UNKNOWN, Feature::COMPONENT_LOCATION,
- Feature::UNSPECIFIED_CONTEXT, Feature::UNSPECIFIED_PLATFORM, -1));
- EXPECT_EQ(Feature::INVALID_LOCATION, feature.IsAvailable(
- "", Extension::TYPE_UNKNOWN, Feature::UNSPECIFIED_LOCATION,
- Feature::UNSPECIFIED_CONTEXT, Feature::UNSPECIFIED_PLATFORM, -1));
+ EXPECT_EQ(Feature::IS_AVAILABLE, feature.IsAvailableToManifest(
+ "", Extension::TYPE_UNKNOWN, Feature::COMPONENT_LOCATION, -1,
+ Feature::UNSPECIFIED_PLATFORM));
+ EXPECT_EQ(Feature::INVALID_LOCATION, feature.IsAvailableToManifest(
+ "", Extension::TYPE_UNKNOWN, Feature::UNSPECIFIED_LOCATION, -1,
+ Feature::UNSPECIFIED_PLATFORM));
// But component extensions can access anything else, whatever their location.
feature.set_location(Feature::UNSPECIFIED_LOCATION);
- EXPECT_EQ(Feature::IS_AVAILABLE, feature.IsAvailable(
- "", Extension::TYPE_UNKNOWN, Feature::COMPONENT_LOCATION,
- Feature::UNSPECIFIED_CONTEXT, Feature::UNSPECIFIED_PLATFORM, -1));
+ EXPECT_EQ(Feature::IS_AVAILABLE, feature.IsAvailableToManifest(
+ "", Extension::TYPE_UNKNOWN, Feature::COMPONENT_LOCATION, -1,
+ Feature::UNSPECIFIED_PLATFORM));
}
TEST(ExtensionFeatureTest, Platform) {
Feature feature;
feature.set_platform(Feature::CHROMEOS_PLATFORM);
- EXPECT_EQ(Feature::IS_AVAILABLE, feature.IsAvailable(
- "", Extension::TYPE_UNKNOWN, Feature::UNSPECIFIED_LOCATION,
- Feature::UNSPECIFIED_CONTEXT, Feature::CHROMEOS_PLATFORM, -1));
- EXPECT_EQ(Feature::INVALID_PLATFORM, feature.IsAvailable(
- "", Extension::TYPE_UNKNOWN, Feature::UNSPECIFIED_LOCATION,
- Feature::UNSPECIFIED_CONTEXT, Feature::UNSPECIFIED_PLATFORM, -1));
+ EXPECT_EQ(Feature::IS_AVAILABLE, feature.IsAvailableToManifest(
+ "", Extension::TYPE_UNKNOWN, Feature::UNSPECIFIED_LOCATION, -1,
+ Feature::CHROMEOS_PLATFORM));
+ EXPECT_EQ(Feature::INVALID_PLATFORM, feature.IsAvailableToManifest(
+ "", Extension::TYPE_UNKNOWN, Feature::UNSPECIFIED_LOCATION, -1,
+ Feature::UNSPECIFIED_PLATFORM));
}
TEST(ExtensionFeatureTest, Version) {
Feature feature;
feature.set_min_manifest_version(5);
- EXPECT_EQ(Feature::INVALID_MIN_MANIFEST_VERSION, feature.IsAvailable(
- "", Extension::TYPE_UNKNOWN, Feature::UNSPECIFIED_LOCATION,
- Feature::UNSPECIFIED_CONTEXT, Feature::UNSPECIFIED_PLATFORM, 0));
- EXPECT_EQ(Feature::INVALID_MIN_MANIFEST_VERSION, feature.IsAvailable(
- "", Extension::TYPE_UNKNOWN, Feature::UNSPECIFIED_LOCATION,
- Feature::UNSPECIFIED_CONTEXT, Feature::UNSPECIFIED_PLATFORM, 4));
+ EXPECT_EQ(Feature::INVALID_MIN_MANIFEST_VERSION,
+ feature.IsAvailableToManifest(
+ "", Extension::TYPE_UNKNOWN, Feature::UNSPECIFIED_LOCATION,
+ 0, Feature::UNSPECIFIED_PLATFORM));
+ EXPECT_EQ(Feature::INVALID_MIN_MANIFEST_VERSION,
+ feature.IsAvailableToManifest(
+ "", Extension::TYPE_UNKNOWN, Feature::UNSPECIFIED_LOCATION,
+ 4, Feature::UNSPECIFIED_PLATFORM));
- EXPECT_EQ(Feature::IS_AVAILABLE, feature.IsAvailable(
+ EXPECT_EQ(Feature::IS_AVAILABLE, feature.IsAvailableToManifest(
"", Extension::TYPE_UNKNOWN, Feature::UNSPECIFIED_LOCATION,
- Feature::UNSPECIFIED_CONTEXT, Feature::UNSPECIFIED_PLATFORM, 5));
- EXPECT_EQ(Feature::IS_AVAILABLE, feature.IsAvailable(
+ 5, Feature::UNSPECIFIED_PLATFORM));
+ EXPECT_EQ(Feature::IS_AVAILABLE, feature.IsAvailableToManifest(
"", Extension::TYPE_UNKNOWN, Feature::UNSPECIFIED_LOCATION,
- Feature::UNSPECIFIED_CONTEXT, Feature::UNSPECIFIED_PLATFORM, 10));
+ 10, Feature::UNSPECIFIED_PLATFORM));
feature.set_max_manifest_version(8);
- EXPECT_EQ(Feature::INVALID_MAX_MANIFEST_VERSION, feature.IsAvailable(
+ EXPECT_EQ(Feature::INVALID_MAX_MANIFEST_VERSION,
+ feature.IsAvailableToManifest(
+ "", Extension::TYPE_UNKNOWN, Feature::UNSPECIFIED_LOCATION,
+ 10, Feature::UNSPECIFIED_PLATFORM));
+ EXPECT_EQ(Feature::IS_AVAILABLE,
+ feature.IsAvailableToManifest(
+ "", Extension::TYPE_UNKNOWN, Feature::UNSPECIFIED_LOCATION,
+ 8, Feature::UNSPECIFIED_PLATFORM));
+ EXPECT_EQ(Feature::IS_AVAILABLE, feature.IsAvailableToManifest(
"", Extension::TYPE_UNKNOWN, Feature::UNSPECIFIED_LOCATION,
- Feature::UNSPECIFIED_CONTEXT, Feature::UNSPECIFIED_PLATFORM, 10));
- EXPECT_EQ(Feature::IS_AVAILABLE, feature.IsAvailable(
- "", Extension::TYPE_UNKNOWN, Feature::UNSPECIFIED_LOCATION,
- Feature::UNSPECIFIED_CONTEXT, Feature::UNSPECIFIED_PLATFORM, 8));
- EXPECT_EQ(Feature::IS_AVAILABLE, feature.IsAvailable(
- "", Extension::TYPE_UNKNOWN, Feature::UNSPECIFIED_LOCATION,
- Feature::UNSPECIFIED_CONTEXT, Feature::UNSPECIFIED_PLATFORM, 7));
+ 7, Feature::UNSPECIFIED_PLATFORM));
}
TEST(ExtensionFeatureTest, ParseNull) {
scoped_ptr<DictionaryValue> value(new DictionaryValue());
- scoped_ptr<Feature> feature(Feature::Parse(value.get()));
+ scoped_ptr<Feature> feature(new Feature());
+ feature->Parse(value.get());
EXPECT_TRUE(feature->whitelist()->empty());
EXPECT_TRUE(feature->extension_types()->empty());
EXPECT_TRUE(feature->contexts()->empty());
@@ -202,7 +253,8 @@ TEST(ExtensionFeatureTest, ParseWhitelist) {
whitelist->Append(Value::CreateStringValue("foo"));
whitelist->Append(Value::CreateStringValue("bar"));
value->Set("whitelist", whitelist);
- scoped_ptr<Feature> feature(Feature::Parse(value.get()));
+ scoped_ptr<Feature> feature(new Feature());
+ feature->Parse(value.get());
EXPECT_EQ(2u, feature->whitelist()->size());
EXPECT_TRUE(feature->whitelist()->count("foo"));
EXPECT_TRUE(feature->whitelist()->count("bar"));
@@ -217,7 +269,8 @@ TEST(ExtensionFeatureTest, ParsePackageTypes) {
extension_types->Append(Value::CreateStringValue("hosted_app"));
extension_types->Append(Value::CreateStringValue("platform_app"));
value->Set("extension_types", extension_types);
- scoped_ptr<Feature> feature(Feature::Parse(value.get()));
+ scoped_ptr<Feature> feature(new Feature());
+ feature->Parse(value.get());
EXPECT_EQ(5u, feature->extension_types()->size());
EXPECT_TRUE(feature->extension_types()->count(Extension::TYPE_EXTENSION));
EXPECT_TRUE(feature->extension_types()->count(Extension::TYPE_THEME));
@@ -226,7 +279,8 @@ TEST(ExtensionFeatureTest, ParsePackageTypes) {
EXPECT_TRUE(feature->extension_types()->count(Extension::TYPE_PLATFORM_APP));
value->SetString("extension_types", "all");
- scoped_ptr<Feature> feature2(Feature::Parse(value.get()));
+ scoped_ptr<Feature> feature2(new Feature());
+ feature2->Parse(value.get());
EXPECT_EQ(*(feature->extension_types()), *(feature2->extension_types()));
}
@@ -238,7 +292,8 @@ TEST(ExtensionFeatureTest, ParseContexts) {
contexts->Append(Value::CreateStringValue("content_script"));
contexts->Append(Value::CreateStringValue("web_page"));
value->Set("contexts", contexts);
- scoped_ptr<Feature> feature(Feature::Parse(value.get()));
+ scoped_ptr<Feature> feature(new Feature());
+ feature->Parse(value.get());
EXPECT_EQ(4u, feature->contexts()->size());
EXPECT_TRUE(feature->contexts()->count(Feature::BLESSED_EXTENSION_CONTEXT));
EXPECT_TRUE(feature->contexts()->count(Feature::UNBLESSED_EXTENSION_CONTEXT));
@@ -246,21 +301,24 @@ TEST(ExtensionFeatureTest, ParseContexts) {
EXPECT_TRUE(feature->contexts()->count(Feature::WEB_PAGE_CONTEXT));
value->SetString("contexts", "all");
- scoped_ptr<Feature> feature2(Feature::Parse(value.get()));
+ scoped_ptr<Feature> feature2(new Feature());
+ feature2->Parse(value.get());
EXPECT_EQ(*(feature->contexts()), *(feature2->contexts()));
}
TEST(ExtensionFeatureTest, ParseLocation) {
scoped_ptr<DictionaryValue> value(new DictionaryValue());
value->SetString("location", "component");
- scoped_ptr<Feature> feature(Feature::Parse(value.get()));
+ scoped_ptr<Feature> feature(new Feature());
+ feature->Parse(value.get());
EXPECT_EQ(Feature::COMPONENT_LOCATION, feature->location());
}
TEST(ExtensionFeatureTest, ParsePlatform) {
scoped_ptr<DictionaryValue> value(new DictionaryValue());
value->SetString("platform", "chromeos");
- scoped_ptr<Feature> feature(Feature::Parse(value.get()));
+ scoped_ptr<Feature> feature(new Feature());
+ feature->Parse(value.get());
EXPECT_EQ(Feature::CHROMEOS_PLATFORM, feature->platform());
}
@@ -268,7 +326,92 @@ TEST(ExtensionFeatureTest, ManifestVersion) {
scoped_ptr<DictionaryValue> value(new DictionaryValue());
value->SetInteger("min_manifest_version", 1);
value->SetInteger("max_manifest_version", 5);
- scoped_ptr<Feature> feature(Feature::Parse(value.get()));
+ scoped_ptr<Feature> feature(new Feature());
+ feature->Parse(value.get());
EXPECT_EQ(1, feature->min_manifest_version());
EXPECT_EQ(5, feature->max_manifest_version());
}
+
+TEST(ExtensionFeatureTest, Inheritance) {
+ Feature feature;
+ feature.whitelist()->insert("foo");
+ feature.extension_types()->insert(Extension::TYPE_THEME);
+ feature.contexts()->insert(Feature::BLESSED_EXTENSION_CONTEXT);
+ feature.set_location(Feature::COMPONENT_LOCATION);
+ feature.set_platform(Feature::CHROMEOS_PLATFORM);
+ feature.set_min_manifest_version(1);
+ feature.set_max_manifest_version(2);
+
+ Feature feature2 = feature;
+ EXPECT_TRUE(feature2.Equals(feature));
+
+ DictionaryValue definition;
+ feature2.Parse(&definition);
+ EXPECT_TRUE(feature2.Equals(feature));
+
+ ListValue* whitelist = new ListValue();
+ ListValue* extension_types = new ListValue();
+ ListValue* contexts = new ListValue();
+ whitelist->Append(Value::CreateStringValue("bar"));
+ extension_types->Append(Value::CreateStringValue("extension"));
+ contexts->Append(Value::CreateStringValue("unblessed_extension"));
+ definition.Set("whitelist", whitelist);
+ definition.Set("extension_types", extension_types);
+ definition.Set("contexts", contexts);
+ // Can't test location or platform because we only have one value so far.
+ definition.Set("min_manifest_version", Value::CreateIntegerValue(2));
+ definition.Set("max_manifest_version", Value::CreateIntegerValue(3));
+
+ feature2.Parse(&definition);
+ EXPECT_FALSE(feature2.Equals(feature));
+ EXPECT_EQ(1u, feature2.whitelist()->size());
+ EXPECT_EQ(1u, feature2.extension_types()->size());
+ EXPECT_EQ(1u, feature2.contexts()->size());
+ EXPECT_EQ(1u, feature2.whitelist()->count("bar"));
+ EXPECT_EQ(1u, feature2.extension_types()->count(Extension::TYPE_EXTENSION));
+ EXPECT_EQ(1u,
+ feature2.contexts()->count(Feature::UNBLESSED_EXTENSION_CONTEXT));
+ EXPECT_EQ(2, feature2.min_manifest_version());
+ EXPECT_EQ(3, feature2.max_manifest_version());
+}
+
+TEST(ExtensionFeatureTest, Equals) {
+ Feature feature;
+ feature.whitelist()->insert("foo");
+ feature.extension_types()->insert(Extension::TYPE_THEME);
+ feature.contexts()->insert(Feature::UNBLESSED_EXTENSION_CONTEXT);
+ feature.set_location(Feature::COMPONENT_LOCATION);
+ feature.set_platform(Feature::CHROMEOS_PLATFORM);
+ feature.set_min_manifest_version(18);
+ feature.set_max_manifest_version(25);
+
+ Feature feature2(feature);
+ EXPECT_TRUE(feature2.Equals(feature));
+
+ feature2.whitelist()->clear();
+ EXPECT_FALSE(feature2.Equals(feature));
+
+ feature2 = feature;
+ feature2.extension_types()->clear();
+ EXPECT_FALSE(feature2.Equals(feature));
+
+ feature2 = feature;
+ feature2.contexts()->clear();
+ EXPECT_FALSE(feature2.Equals(feature));
+
+ feature2 = feature;
+ feature2.set_location(Feature::UNSPECIFIED_LOCATION);
+ EXPECT_FALSE(feature2.Equals(feature));
+
+ feature2 = feature;
+ feature2.set_platform(Feature::UNSPECIFIED_PLATFORM);
+ EXPECT_FALSE(feature2.Equals(feature));
+
+ feature2 = feature;
+ feature2.set_min_manifest_version(0);
+ EXPECT_FALSE(feature2.Equals(feature));
+
+ feature2 = feature;
+ feature2.set_max_manifest_version(0);
+ EXPECT_FALSE(feature2.Equals(feature));
+}
diff --git a/chrome/common/extensions/manifest.cc b/chrome/common/extensions/manifest.cc
index 2449d44..aa4a495 100644
--- a/chrome/common/extensions/manifest.cc
+++ b/chrome/common/extensions/manifest.cc
@@ -38,7 +38,7 @@ bool Manifest::ValidateManifest(string16* error) const {
continue;
}
- Feature::Availability result = feature->IsAvailable(
+ Feature::Availability result = feature->IsAvailableToManifest(
extension_id_, GetType(), Feature::ConvertLocation(location_),
GetManifestVersion());
if (result != Feature::IS_AVAILABLE) {
@@ -155,7 +155,7 @@ bool Manifest::CanAccessKey(const std::string& key) const {
if (!feature.get())
return false;
- return Feature::IS_AVAILABLE == feature->IsAvailable(
+ return Feature::IS_AVAILABLE == feature->IsAvailableToManifest(
extension_id_, GetType(), Feature::ConvertLocation(location_),
GetManifestVersion());
}
diff --git a/chrome/common/extensions/manifest_feature.cc b/chrome/common/extensions/manifest_feature.cc
new file mode 100644
index 0000000..c9a62da
--- /dev/null
+++ b/chrome/common/extensions/manifest_feature.cc
@@ -0,0 +1,35 @@
+// 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/manifest_feature.h"
+
+#include "chrome/common/extensions/manifest.h"
+
+namespace extensions {
+
+ManifestFeature::ManifestFeature() {
+}
+
+ManifestFeature::~ManifestFeature() {
+}
+
+Feature::Availability ManifestFeature::IsAvailableToContext(
+ const Extension* extension,
+ Feature::Context context,
+ Feature::Platform platform) const {
+ Availability availability = Feature::IsAvailableToContext(extension,
+ context,
+ platform);
+ if (availability != IS_AVAILABLE)
+ return availability;
+
+ // We know we can skip manifest()->GetKey() here because we just did the same
+ // validation it would do above.
+ if (!extension->manifest()->value()->HasKey(name()))
+ return NOT_PRESENT;
+
+ return IS_AVAILABLE;
+}
+
+} // namespace
diff --git a/chrome/common/extensions/manifest_feature.h b/chrome/common/extensions/manifest_feature.h
new file mode 100644
index 0000000..d156759
--- /dev/null
+++ b/chrome/common/extensions/manifest_feature.h
@@ -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.
+
+#ifndef CHROME_COMMON_EXTENSIONS_MANIFEST_FEATURE_H_
+#define CHROME_COMMON_EXTENSIONS_MANIFEST_FEATURE_H_
+#pragma once
+
+#include "chrome/common/extensions/feature.h"
+
+namespace extensions {
+
+class ManifestFeature : public Feature {
+ public:
+ ManifestFeature();
+ virtual ~ManifestFeature();
+
+ virtual Feature::Availability IsAvailableToContext(
+ const Extension* extension,
+ Feature::Context context,
+ Feature::Platform platform) const OVERRIDE;
+};
+
+} // extensions
+
+#endif // CHROME_COMMON_EXTENSIONS_MANIFEST_FEATURE_H_
diff --git a/chrome/common/extensions/permission_feature.cc b/chrome/common/extensions/permission_feature.cc
new file mode 100644
index 0000000..f093192
--- /dev/null
+++ b/chrome/common/extensions/permission_feature.cc
@@ -0,0 +1,33 @@
+// 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/permission_feature.h"
+
+#include "chrome/common/extensions/extension_permission_set.h"
+
+namespace extensions {
+
+PermissionFeature::PermissionFeature() {
+}
+
+PermissionFeature::~PermissionFeature() {
+}
+
+Feature::Availability PermissionFeature::IsAvailableToContext(
+ const Extension* extension,
+ Feature::Context context,
+ Feature::Platform platform) const {
+ Availability availability = Feature::IsAvailableToContext(extension,
+ context,
+ platform);
+ if (availability != IS_AVAILABLE)
+ return availability;
+
+ if (!extension->HasAPIPermission(name()))
+ return NOT_PRESENT;
+
+ return IS_AVAILABLE;
+}
+
+} // namespace
diff --git a/chrome/common/extensions/permission_feature.h b/chrome/common/extensions/permission_feature.h
new file mode 100644
index 0000000..0cf4f19
--- /dev/null
+++ b/chrome/common/extensions/permission_feature.h
@@ -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.
+
+#ifndef CHROME_COMMON_EXTENSIONS_PERMISSION_FEATURE_H_
+#define CHROME_COMMON_EXTENSIONS_PERMISSION_FEATURE_H_
+#pragma once
+
+#include "chrome/common/extensions/feature.h"
+
+namespace extensions {
+
+class PermissionFeature : public Feature {
+ public:
+ PermissionFeature();
+ virtual ~PermissionFeature();
+
+ virtual Feature::Availability IsAvailableToContext(
+ const Extension* extension,
+ Feature::Context context,
+ Feature::Platform platform) const OVERRIDE;
+};
+
+} // extensions
+
+#endif // CHROME_COMMON_EXTENSIONS_PERMISSION_FEATURE_H_
diff --git a/chrome/common/extensions/simple_feature_provider.cc b/chrome/common/extensions/simple_feature_provider.cc
index 71be30c..e5acf36 100644
--- a/chrome/common/extensions/simple_feature_provider.cc
+++ b/chrome/common/extensions/simple_feature_provider.cc
@@ -6,27 +6,42 @@
#include "base/json/json_reader.h"
#include "base/lazy_instance.h"
+#include "chrome/common/extensions/manifest_feature.h"
+#include "chrome/common/extensions/permission_feature.h"
#include "grit/common_resources.h"
#include "ui/base/resource/resource_bundle.h"
+namespace extensions {
+
namespace {
const bool kAllowTrailingComma = false;
+template<class FeatureClass>
+Feature* CreateFeature() {
+ return new FeatureClass();
+}
+
struct Static {
Static()
: manifest_features(
- LoadProvider("manifest", IDR_EXTENSION_MANIFEST_FEATURES)),
+ LoadProvider("manifest",
+ &CreateFeature<ManifestFeature>,
+ IDR_EXTENSION_MANIFEST_FEATURES)),
permission_features(
- LoadProvider("permissions", IDR_EXTENSION_PERMISSION_FEATURES)) {
+ LoadProvider("permissions",
+ &CreateFeature<PermissionFeature>,
+ IDR_EXTENSION_PERMISSION_FEATURES)) {
}
- scoped_ptr<extensions::SimpleFeatureProvider> manifest_features;
- scoped_ptr<extensions::SimpleFeatureProvider> permission_features;
+ scoped_ptr<SimpleFeatureProvider> manifest_features;
+ scoped_ptr<SimpleFeatureProvider> permission_features;
private:
- scoped_ptr<extensions::SimpleFeatureProvider> LoadProvider(
- const std::string& debug_string, int resource_id) {
+ scoped_ptr<SimpleFeatureProvider> LoadProvider(
+ const std::string& debug_string,
+ SimpleFeatureProvider::FeatureFactory factory,
+ int resource_id) {
std::string manifest_features =
ResourceBundle::GetSharedInstance().GetRawDataResource(
resource_id).as_string();
@@ -39,8 +54,8 @@ struct Static {
CHECK(value->IsType(Value::TYPE_DICTIONARY)) << debug_string;
scoped_ptr<DictionaryValue> dictionary_value(
static_cast<DictionaryValue*>(value));
- return scoped_ptr<extensions::SimpleFeatureProvider>(
- new extensions::SimpleFeatureProvider(dictionary_value.Pass()));
+ return scoped_ptr<SimpleFeatureProvider>(
+ new SimpleFeatureProvider(dictionary_value.Pass(), factory));
}
};
@@ -48,11 +63,11 @@ base::LazyInstance<Static> g_static = LAZY_INSTANCE_INITIALIZER;
} // namespace
-namespace extensions {
-
-SimpleFeatureProvider::SimpleFeatureProvider(
- scoped_ptr<DictionaryValue> root)
- : root_(root.release()) {
+SimpleFeatureProvider::SimpleFeatureProvider(scoped_ptr<DictionaryValue> root,
+ FeatureFactory factory)
+ : root_(root.release()),
+ factory_(factory ? factory :
+ static_cast<FeatureFactory>(&CreateFeature<Feature>)) {
}
SimpleFeatureProvider::~SimpleFeatureProvider() {
@@ -77,21 +92,18 @@ std::set<std::string> SimpleFeatureProvider::GetAllFeatureNames() const {
return result;
}
-scoped_ptr<Feature> SimpleFeatureProvider::GetFeature(
- const std::string& name) const {
- scoped_ptr<Feature> feature;
-
+scoped_ptr<Feature> SimpleFeatureProvider::GetFeature(const std::string& name) {
DictionaryValue* description = NULL;
- if (root_->GetDictionary(name, &description))
- feature = Feature::Parse(description);
-
- if (!feature.get()) {
- // Have to use DLOG here because this happens in a lot of unit tests that
- // use ancient compiled crx files with unknown keys.
- DLOG(ERROR) << name;
+ if (!root_->GetDictionary(name, &description)) {
+ LOG(ERROR) << name << ": Definition not found.";
return scoped_ptr<Feature>();
}
+ scoped_ptr<Feature> feature(new Feature());
+ feature.reset((*factory_)());
+ feature->set_name(name);
+ feature->Parse(description);
+
if (feature->extension_types()->empty()) {
LOG(ERROR) << name << ": Simple features must specify atleast one value "
<< "for extension_types.";
diff --git a/chrome/common/extensions/simple_feature_provider.h b/chrome/common/extensions/simple_feature_provider.h
index 2ba5e52..0b9172c 100644
--- a/chrome/common/extensions/simple_feature_provider.h
+++ b/chrome/common/extensions/simple_feature_provider.h
@@ -12,16 +12,20 @@
#include "base/memory/scoped_ptr.h"
#include "base/values.h"
#include "chrome/common/extensions/feature.h"
+#include "chrome/common/extensions/feature_provider.h"
namespace extensions {
// Reads Features out of a simple JSON file description.
-class SimpleFeatureProvider {
+class SimpleFeatureProvider : public FeatureProvider {
public:
- // Create an instance for an arbitrary hunk of JSON. This is typically used
- // during tests.
- explicit SimpleFeatureProvider(scoped_ptr<DictionaryValue> root);
- ~SimpleFeatureProvider();
+ typedef Feature*(*FeatureFactory)();
+
+ // Creates a new SimpleFeatureProvider. Pass null to |factory| to have the
+ // provider create plain old Feature instances.
+ SimpleFeatureProvider(scoped_ptr<DictionaryValue> root,
+ FeatureFactory factory);
+ virtual ~SimpleFeatureProvider();
// Gets an instance for the _manifest_features.json file that is baked into
// Chrome as a resource.
@@ -35,10 +39,12 @@ class SimpleFeatureProvider {
std::set<std::string> GetAllFeatureNames() const;
// Gets the feature |feature_name|, if it exists.
- scoped_ptr<Feature> GetFeature(const std::string& feature_name) const;
+ virtual scoped_ptr<Feature> GetFeature(
+ const std::string& feature_name) OVERRIDE;
private:
scoped_ptr<DictionaryValue> root_;
+ FeatureFactory factory_;
};
} // namespace extensions
diff --git a/chrome/common/extensions/simple_feature_provider_unittest.cc b/chrome/common/extensions/simple_feature_provider_unittest.cc
index f6bffef..401828e 100644
--- a/chrome/common/extensions/simple_feature_provider_unittest.cc
+++ b/chrome/common/extensions/simple_feature_provider_unittest.cc
@@ -12,9 +12,79 @@ using extensions::SimpleFeatureProvider;
TEST(SimpleFeatureProvider, ManifestFeatures) {
SimpleFeatureProvider* provider =
SimpleFeatureProvider::GetManifestFeatures();
- scoped_ptr<Feature> feature = provider->GetFeature("name");
+ scoped_ptr<Feature> feature = provider->GetFeature("description");
ASSERT_TRUE(feature.get());
EXPECT_EQ(5u, feature->extension_types()->size());
+ EXPECT_EQ(1u, feature->extension_types()->count(Extension::TYPE_EXTENSION));
+ EXPECT_EQ(1u,
+ feature->extension_types()->count(Extension::TYPE_PACKAGED_APP));
+ EXPECT_EQ(1u,
+ feature->extension_types()->count(Extension::TYPE_PLATFORM_APP));
+ EXPECT_EQ(1u, feature->extension_types()->count(Extension::TYPE_HOSTED_APP));
+ EXPECT_EQ(1u, feature->extension_types()->count(Extension::TYPE_THEME));
+
+ DictionaryValue manifest;
+ manifest.SetString("name", "test extension");
+ manifest.SetString("version", "1");
+ manifest.SetString("description", "hello there");
+
+ std::string error;
+ scoped_refptr<const Extension> extension(Extension::Create(
+ FilePath(), Extension::INTERNAL, manifest, Extension::NO_FLAGS,
+ &error));
+
+ ASSERT_TRUE(extension.get());
+ EXPECT_EQ(Feature::IS_AVAILABLE, feature->IsAvailableToContext(
+ extension.get(), Feature::UNSPECIFIED_CONTEXT));
+
+ feature = provider->GetFeature("theme");
+ ASSERT_TRUE(feature.get());
+ EXPECT_EQ(Feature::INVALID_TYPE, feature->IsAvailableToContext(
+ extension.get(), Feature::UNSPECIFIED_CONTEXT));
+
+ feature = provider->GetFeature("devtools_page");
+ ASSERT_TRUE(feature.get());
+ EXPECT_EQ(Feature::NOT_PRESENT, feature->IsAvailableToContext(
+ extension.get(), Feature::UNSPECIFIED_CONTEXT));
+}
+
+TEST(SimpleFeatureProvider, PermissionFeatures) {
+ SimpleFeatureProvider* provider =
+ SimpleFeatureProvider::GetPermissionFeatures();
+ scoped_ptr<Feature> feature = provider->GetFeature("browsingData");
+ ASSERT_TRUE(feature.get());
+ EXPECT_EQ(3u, feature->extension_types()->size());
+ EXPECT_EQ(1u, feature->extension_types()->count(Extension::TYPE_EXTENSION));
+ EXPECT_EQ(1u,
+ feature->extension_types()->count(Extension::TYPE_PACKAGED_APP));
+ EXPECT_EQ(1u,
+ feature->extension_types()->count(Extension::TYPE_PLATFORM_APP));
+
+ DictionaryValue manifest;
+ manifest.SetString("name", "test extension");
+ manifest.SetString("version", "1");
+ ListValue* permissions = new ListValue();
+ manifest.Set("permissions", permissions);
+ permissions->Append(Value::CreateStringValue("browsingData"));
+
+ std::string error;
+ scoped_refptr<const Extension> extension(Extension::Create(
+ FilePath(), Extension::INTERNAL, manifest, Extension::NO_FLAGS,
+ &error));
+
+ ASSERT_TRUE(extension.get());
+ EXPECT_EQ(Feature::IS_AVAILABLE, feature->IsAvailableToContext(
+ extension.get(), Feature::UNSPECIFIED_CONTEXT));
+
+ feature = provider->GetFeature("chromePrivate");
+ ASSERT_TRUE(feature.get());
+ EXPECT_EQ(Feature::NOT_FOUND_IN_WHITELIST, feature->IsAvailableToContext(
+ extension.get(), Feature::UNSPECIFIED_CONTEXT));
+
+ feature = provider->GetFeature("clipboardWrite");
+ ASSERT_TRUE(feature.get());
+ EXPECT_EQ(Feature::NOT_PRESENT, feature->IsAvailableToContext(
+ extension.get(), Feature::UNSPECIFIED_CONTEXT));
}
TEST(SimpleFeatureProvider, Validation) {
@@ -32,7 +102,7 @@ TEST(SimpleFeatureProvider, Validation) {
feature2->Set("contexts", contexts);
value->Set("feature2", feature2);
- SimpleFeatureProvider provider(value.Pass());
+ SimpleFeatureProvider provider(value.Pass(), NULL);
// feature1 won't validate because it lacks an extension type.
EXPECT_FALSE(provider.GetFeature("feature1").get());
diff --git a/chrome/renderer/extensions/extension_dispatcher.cc b/chrome/renderer/extensions/extension_dispatcher.cc
index 3e8af8d..603cd69 100644
--- a/chrome/renderer/extensions/extension_dispatcher.cc
+++ b/chrome/renderer/extensions/extension_dispatcher.cc
@@ -576,7 +576,7 @@ void ExtensionDispatcher::DidCreateScriptContext(
// correct APIs, however, until it doesn't have a 2MB overhead we can't
// load it in every process.
scoped_ptr<std::set<std::string> > apis =
- ExtensionAPI::GetInstance()->GetAPIsForContext(
+ ExtensionAPI::GetSharedInstance()->GetAPIsForContext(
context_type, extension, url_info.url());
for (std::set<std::string>::iterator i = apis->begin(); i != apis->end();
++i) {
@@ -834,7 +834,7 @@ bool ExtensionDispatcher::CheckCurrentContextAccessToExtensionAPI(
}
if (!IsExtensionActive(extension->id()) &&
- ExtensionAPI::GetInstance()->IsPrivileged(function_name)) {
+ ExtensionAPI::GetSharedInstance()->IsPrivileged(function_name)) {
static const char kMessage[] =
"%s can only be used in an extension process.";
std::string error_msg = base::StringPrintf(kMessage, function_name.c_str());
diff --git a/chrome/renderer/extensions/schema_generated_bindings.cc b/chrome/renderer/extensions/schema_generated_bindings.cc
index e58e100..c33aae2 100644
--- a/chrome/renderer/extensions/schema_generated_bindings.cc
+++ b/chrome/renderer/extensions/schema_generated_bindings.cc
@@ -89,7 +89,7 @@ v8::Handle<v8::Value> SchemaGeneratedBindings::GetExtensionAPIDefinition(
// The minimal set of APIs that tests need.
apis->insert("extension");
} else {
- apis = ExtensionAPI::GetInstance()->GetAPIsForContext(
+ apis = ExtensionAPI::GetSharedInstance()->GetAPIsForContext(
v8_context->context_type(),
extension_dispatcher()->extensions()->GetByID(extension_id),
UserScriptSlave::GetDataSourceURLForFrame(v8_context->web_frame()));
diff --git a/chrome/renderer/extensions/v8_schema_registry.cc b/chrome/renderer/extensions/v8_schema_registry.cc
index 35a22b2..ba4dcb6 100644
--- a/chrome/renderer/extensions/v8_schema_registry.cc
+++ b/chrome/renderer/extensions/v8_schema_registry.cc
@@ -41,7 +41,7 @@ v8::Handle<v8::Object> V8SchemaRegistry::GetSchema(const std::string& api) {
return maybe_schema->second;
const base::DictionaryValue* schema =
- ExtensionAPI::GetInstance()->GetSchema(api);
+ ExtensionAPI::GetSharedInstance()->GetSchema(api);
CHECK(schema) << api;
scoped_ptr<V8ValueConverter> v8_value_converter(V8ValueConverter::create());
diff --git a/chrome/test/data/extensions/api_test/webstore_private/bundle/extension2.json b/chrome/test/data/extensions/api_test/webstore_private/bundle/extension2.json
index 09ced87..567fb13 100644
--- a/chrome/test/data/extensions/api_test/webstore_private/bundle/extension2.json
+++ b/chrome/test/data/extensions/api_test/webstore_private/bundle/extension2.json
@@ -2,10 +2,5 @@
"name": "extension.2",
"version": "1",
"manifest_version": 2,
- "permissions": ["management", "http://google.com" ],
- "content_script": [{
- "matches": [ "http://www.example.com/*" ],
- "js": [ "content_script.js" ],
- "run_at": "document_start"
- }]
+ "permissions": ["management", "http://google.com" ]
}
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
new file mode 100644
index 0000000..ae509f69
--- /dev/null
+++ b/chrome/test/data/extensions/extension_api_unittest/is_privileged_features_1.json
@@ -0,0 +1,8 @@
+[
+ {
+ "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
new file mode 100644
index 0000000..91b9d13
--- /dev/null
+++ b/chrome/test/data/extensions/extension_api_unittest/is_privileged_features_2.json
@@ -0,0 +1,8 @@
+[
+ {
+ "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
new file mode 100644
index 0000000..47b1290e
--- /dev/null
+++ b/chrome/test/data/extensions/extension_api_unittest/is_privileged_features_3.json
@@ -0,0 +1,8 @@
+[
+ {
+ "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
new file mode 100644
index 0000000..8839ce1
--- /dev/null
+++ b/chrome/test/data/extensions/extension_api_unittest/is_privileged_features_4.json
@@ -0,0 +1,18 @@
+[
+ {
+ "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
new file mode 100644
index 0000000..9db5b2d
--- /dev/null
+++ b/chrome/test/data/extensions/extension_api_unittest/is_privileged_features_5.json
@@ -0,0 +1,18 @@
+[
+ {
+ "namespace": "test",
+ "uses_feature_system": true,
+ "extension_types": ["extension"],
+ "contexts": ["blessed_extension"],
+ "functions": [
+ {
+ "name": "foo",
+ "dependencies": ["test2:monkey"]
+ },
+ {
+ "name": "bar",
+ "dependencies": ["api:test.foo"]
+ }
+ ]
+ }
+]