diff options
author | aa@chromium.org <aa@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2012-04-04 18:55:19 +0000 |
---|---|---|
committer | aa@chromium.org <aa@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2012-04-04 18:55:19 +0000 |
commit | 310311c35845cdb7addc03473e4b6dc1d9e5264b (patch) | |
tree | 64db561ee7533f5ed8c31fec4d9e1f6aee1b432d /chrome | |
parent | da22aa6bdeec162e8eb81d90798398e31db1fd58 (diff) | |
download | chromium_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')
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"] + } + ] + } +] |