diff options
author | kalman@chromium.org <kalman@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2012-03-23 12:57:12 +0000 |
---|---|---|
committer | kalman@chromium.org <kalman@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2012-03-23 12:57:12 +0000 |
commit | 4dffaf29261a76ac6a560facc33d0bbe951629d9 (patch) | |
tree | 098cd9c4f1f0c43b9e46799748c52adb0210458d /chrome/common/extensions | |
parent | 9ec39638198bf85a5f19e74c81a0feb4326d5e73 (diff) | |
download | chromium_src-4dffaf29261a76ac6a560facc33d0bbe951629d9.zip chromium_src-4dffaf29261a76ac6a560facc33d0bbe951629d9.tar.gz chromium_src-4dffaf29261a76ac6a560facc33d0bbe951629d9.tar.bz2 |
Make ExtensionAPI load schemas lazily where possible.
BUG=118144
TEST=unit_tests --gtest_filter=ExtensionAPI.*
Committed: http://src.chromium.org/viewvc/chrome?view=rev&revision=128434
Review URL: http://codereview.chromium.org/9677069
git-svn-id: svn://svn.chromium.org/chrome/trunk/src@128466 0039d316-1c4b-4281-b951-d872f2087c98
Diffstat (limited to 'chrome/common/extensions')
-rw-r--r-- | chrome/common/extensions/api/extension_api.cc | 382 | ||||
-rw-r--r-- | chrome/common/extensions/api/extension_api.h | 46 | ||||
-rw-r--r-- | chrome/common/extensions/api/extension_api_unittest.cc | 104 |
3 files changed, 321 insertions, 211 deletions
diff --git a/chrome/common/extensions/api/extension_api.cc b/chrome/common/extensions/api/extension_api.cc index 91b785b..8571417 100644 --- a/chrome/common/extensions/api/extension_api.cc +++ b/chrome/common/extensions/api/extension_api.cc @@ -20,8 +20,14 @@ #include "grit/common_resources.h" #include "ui/base/resource/resource_bundle.h" +using base::DictionaryValue; +using base::ListValue; +using base::Value; + namespace extensions { +using api::GeneratedSchemas; + namespace { // Returns whether the list at |name_space_node|.|child_kind| contains any @@ -44,163 +50,215 @@ bool HasUnprivilegedChild(const DictionaryValue* name_space_node, return false; } -} // namespace - -// static -ExtensionAPI* ExtensionAPI::GetInstance() { - return Singleton<ExtensionAPI>::get(); +base::StringPiece ReadFromResource(int resource_id) { + return ResourceBundle::GetSharedInstance().GetRawDataResource(resource_id); } -static base::ListValue* LoadSchemaList(int resource_id) { - const bool kAllowTrailingCommas = false; +scoped_ptr<ListValue> LoadSchemaList(const base::StringPiece& schema) { std::string error_message; - Value* result = + scoped_ptr<Value> result( base::JSONReader::ReadAndReturnError( - ResourceBundle::GetSharedInstance().GetRawDataResource( - resource_id).as_string(), - kAllowTrailingCommas, + schema.as_string(), + false, // allow trailing commas NULL, // error code - &error_message); - CHECK(result) << error_message; - CHECK(result->IsType(base::Value::TYPE_LIST)); - return static_cast<base::ListValue*>(result); + &error_message)); + CHECK(result.get()) << error_message; + CHECK(result->IsType(Value::TYPE_LIST)); + return scoped_ptr<ListValue>(static_cast<ListValue*>(result.release())); } -void ExtensionAPI::LoadSchemaFromResource(int resource_id) { - RegisterSchema(LoadSchemaList(resource_id)); -} +} // namespace -void ExtensionAPI::RegisterSchema(base::ListValue* loaded_schema) { - // We take ownership of loaded_schema, so we need to delete it. - scoped_ptr<base::ListValue> scoped_loaded_schema(loaded_schema); - Value* value = NULL; - std::string schema_namespace; - while (!loaded_schema->empty()) { - loaded_schema->Remove(loaded_schema->GetSize() - 1, &value); - CHECK(value->IsType(Value::TYPE_DICTIONARY)); - const DictionaryValue* schema = static_cast<const DictionaryValue*>(value); - CHECK(schema->GetString("namespace", &schema_namespace)); - schemas_[schema_namespace] = linked_ptr<const DictionaryValue>(schema); - } +// static +ExtensionAPI* ExtensionAPI::GetInstance() { + return Singleton<ExtensionAPI>::get(); } -ExtensionAPI::ExtensionAPI() { - static int kJsonApiResourceIds[] = { - IDR_EXTENSION_API_JSON_APP, - IDR_EXTENSION_API_JSON_BOOKMARKS, - IDR_EXTENSION_API_JSON_BROWSERACTION, - IDR_EXTENSION_API_JSON_BROWSING_DATA, - IDR_EXTENSION_API_JSON_CHROMEAUTHPRIVATE, - IDR_EXTENSION_API_JSON_CHROMEOSINFOPRIVATE, - IDR_EXTENSION_API_JSON_CHROMEPRIVATE, - IDR_EXTENSION_API_JSON_CONTENTSETTINGS, - IDR_EXTENSION_API_JSON_CONTEXTMENUS, - IDR_EXTENSION_API_JSON_COOKIES, - IDR_EXTENSION_API_JSON_DEBUGGER, - IDR_EXTENSION_API_JSON_DEVTOOLS, - IDR_EXTENSION_API_JSON_EXPERIMENTAL_ACCESSIBILITY, - IDR_EXTENSION_API_JSON_EXPERIMENTAL_APP, - IDR_EXTENSION_API_JSON_EXPERIMENTAL_BOOKMARKMANAGER, - IDR_EXTENSION_API_JSON_EXPERIMENTAL_DECLARATIVE, - IDR_EXTENSION_API_JSON_EXPERIMENTAL_DOWNLOADS, - IDR_EXTENSION_API_JSON_EXPERIMENTAL_EXTENSIONS, - IDR_EXTENSION_API_JSON_EXPERIMENTAL_FONTS, - IDR_EXTENSION_API_JSON_EXPERIMENTAL_INFOBARS, - IDR_EXTENSION_API_JSON_EXPERIMENTAL_INPUT_UI, - IDR_EXTENSION_API_JSON_EXPERIMENTAL_INPUT_VIRTUALKEYBOARD, - IDR_EXTENSION_API_JSON_EXPERIMENTAL_KEYBINDING, - IDR_EXTENSION_API_JSON_EXPERIMENTAL_MANAGED_MODE, - IDR_EXTENSION_API_JSON_EXPERIMENTAL_OFFSCREENTABS, - IDR_EXTENSION_API_JSON_EXPERIMENTAL_PROCESSES, - IDR_EXTENSION_API_JSON_EXPERIMENTAL_RLZ, - IDR_EXTENSION_API_JSON_EXPERIMENTAL_SERIAL, - IDR_EXTENSION_API_JSON_EXPERIMENTAL_SOCKET, - IDR_EXTENSION_API_JSON_EXPERIMENTAL_SPEECHINPUT, - IDR_EXTENSION_API_JSON_EXPERIMENTAL_WEBREQUEST, - IDR_EXTENSION_API_JSON_EXTENSION, - IDR_EXTENSION_API_JSON_FILEBROWSERHANDLER, - IDR_EXTENSION_API_JSON_FILEBROWSERPRIVATE, - IDR_EXTENSION_API_JSON_HISTORY, - IDR_EXTENSION_API_JSON_I18N, - IDR_EXTENSION_API_JSON_IDLE, - IDR_EXTENSION_API_JSON_INPUT_IME, - IDR_EXTENSION_API_JSON_INPUTMETHODPRIVATE, - IDR_EXTENSION_API_JSON_MANAGEMENT, - IDR_EXTENSION_API_JSON_MEDIAPLAYERPRIVATE, - IDR_EXTENSION_API_JSON_METRICSPRIVATE, - IDR_EXTENSION_API_JSON_OFFERSPRIVATE, - IDR_EXTENSION_API_JSON_OMNIBOX, - IDR_EXTENSION_API_JSON_PAGEACTION, - IDR_EXTENSION_API_JSON_PAGEACTIONS, - IDR_EXTENSION_API_JSON_PAGECAPTURE, - IDR_EXTENSION_API_JSON_PERMISSIONS, - IDR_EXTENSION_API_JSON_PRIVACY, - IDR_EXTENSION_API_JSON_PROXY, - IDR_EXTENSION_API_JSON_STORAGE, - IDR_EXTENSION_API_JSON_SYSTEMPRIVATE, - IDR_EXTENSION_API_JSON_TABS, - IDR_EXTENSION_API_JSON_TERMINALPRIVATE, - IDR_EXTENSION_API_JSON_TEST, - IDR_EXTENSION_API_JSON_TOPSITES, - IDR_EXTENSION_API_JSON_TTS, - IDR_EXTENSION_API_JSON_TTSENGINE, - IDR_EXTENSION_API_JSON_TYPES, - IDR_EXTENSION_API_JSON_WEBNAVIGATION, - IDR_EXTENSION_API_JSON_WEBREQUEST, - IDR_EXTENSION_API_JSON_WEBSOCKETPROXYPRIVATE, - IDR_EXTENSION_API_JSON_WEBSTORE, - IDR_EXTENSION_API_JSON_WEBSTOREPRIVATE, - IDR_EXTENSION_API_JSON_WINDOWS, - }; - - for (size_t i = 0; i < arraysize(kJsonApiResourceIds); i++) { - LoadSchemaFromResource(kJsonApiResourceIds[i]); - } - RegisterSchema(api::GeneratedSchemas::Get()); +void ExtensionAPI::LoadSchema(const base::StringPiece& schema) { + scoped_ptr<ListValue> schema_list(LoadSchemaList(schema)); + std::string schema_namespace; - // Populate {completely,partially}_unprivileged_apis_. - for (SchemaMap::iterator it = schemas_.begin(); it != schemas_.end(); ++it) { - bool unprivileged = false; - it->second->GetBoolean("unprivileged", &unprivileged); - if (unprivileged) { - completely_unprivileged_apis_.insert(it->first); - continue; + while (!schema_list->empty()) { + const DictionaryValue* schema = NULL; + { + Value* value = NULL; + schema_list->Remove(schema_list->GetSize() - 1, &value); + CHECK(value->IsType(Value::TYPE_DICTIONARY)); + schema = static_cast<const DictionaryValue*>(value); } - // Only need to look at functions/events; even though there are unprivileged - // properties (e.g. in extensions), access to those never reaches C++ land. - if (HasUnprivilegedChild(it->second.get(), "functions") || - HasUnprivilegedChild(it->second.get(), "events")) { - partially_unprivileged_apis_.insert(it->first); + CHECK(schema->GetString("namespace", &schema_namespace)); + schemas_[schema_namespace] = make_linked_ptr(schema); + unloaded_schemas_.erase(schema_namespace); + + // Populate |{completely,partially}_unprivileged_apis_|. + // + // For "partially", only need to look at functions/events; even though + // there are unprivileged properties (e.g. in extensions), access to those + // never reaches C++ land. + if (schema->HasKey("unprivileged")) { + completely_unprivileged_apis_.insert(schema_namespace); + } else if (HasUnprivilegedChild(schema, "functions") || + HasUnprivilegedChild(schema, "events")) { + partially_unprivileged_apis_.insert(schema_namespace); } - } - // Populate |url_matching_apis_|. - for (SchemaMap::const_iterator it = schemas_.begin(); - it != schemas_.end(); ++it) { + // Populate |url_matching_apis_|. ListValue* matches = NULL; - { - Value* matches_value = NULL; - if (!it->second->Get("matches", &matches_value)) - continue; - CHECK_EQ(Value::TYPE_LIST, matches_value->GetType()); - matches = static_cast<ListValue*>(matches_value); - } - URLPatternSet pattern_set; - for (size_t i = 0; i < matches->GetSize(); ++i) { - std::string pattern; - CHECK(matches->GetString(i, &pattern)); - pattern_set.AddPattern( - URLPattern(UserScript::kValidUserScriptSchemes, pattern)); + if (schema->GetList("matches", &matches)) { + URLPatternSet pattern_set; + for (size_t i = 0; i < matches->GetSize(); ++i) { + std::string pattern; + CHECK(matches->GetString(i, &pattern)); + pattern_set.AddPattern( + URLPattern(UserScript::kValidUserScriptSchemes, pattern)); + } + url_matching_apis_[schema_namespace] = pattern_set; } - url_matching_apis_[it->first] = pattern_set; } } +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_["chromePrivate"] = ReadFromResource( + IDR_EXTENSION_API_JSON_CHROMEPRIVATE); + 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.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.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); + + // Schemas to be loaded via JSON generated from IDL files. + GeneratedSchemas::Get(&unloaded_schemas_); +} + ExtensionAPI::~ExtensionAPI() { } -bool ExtensionAPI::IsPrivileged(const std::string& full_name) const { +bool ExtensionAPI::IsPrivileged(const std::string& full_name) { std::string api_name; std::string child_name; @@ -224,11 +282,13 @@ bool ExtensionAPI::IsPrivileged(const std::string& full_name) const { child_name = split[0]; } + // GetSchema to ensure that it gets loaded before any checks. + const DictionaryValue* schema = GetSchema(api_name); + if (completely_unprivileged_apis_.count(api_name)) return false; if (partially_unprivileged_apis_.count(api_name)) { - const DictionaryValue* schema = GetSchema(api_name); return IsChildNamePrivileged(schema, "functions", child_name) && IsChildNamePrivileged(schema, "events", child_name); } @@ -237,9 +297,9 @@ bool ExtensionAPI::IsPrivileged(const std::string& full_name) const { } DictionaryValue* ExtensionAPI::FindListItem( - const base::ListValue* list, + const ListValue* list, const std::string& property_name, - const std::string& property_value) const { + const std::string& property_value) { for (size_t i = 0; i < list->GetSize(); ++i) { DictionaryValue* item = NULL; CHECK(list->GetDictionary(i, &item)) @@ -254,7 +314,7 @@ DictionaryValue* ExtensionAPI::FindListItem( bool ExtensionAPI::IsChildNamePrivileged(const DictionaryValue* name_space_node, const std::string& child_kind, - const std::string& child_name) const { + const std::string& child_name) { ListValue* child_list = NULL; name_space_node->GetList(child_kind, &child_list); if (!child_list) @@ -268,16 +328,32 @@ bool ExtensionAPI::IsChildNamePrivileged(const DictionaryValue* name_space_node, return !unprivileged; } -const base::DictionaryValue* ExtensionAPI::GetSchema( - const std::string& api_name) const { +const DictionaryValue* ExtensionAPI::GetSchema(const std::string& api_name) { SchemaMap::const_iterator maybe_schema = schemas_.find(api_name); - return maybe_schema != schemas_.end() ? maybe_schema->second.get() : NULL; + 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(); } scoped_ptr<std::set<std::string> > ExtensionAPI::GetAPIsForContext( - Feature::Context context, - const Extension* extension, - const GURL& url) const { + Feature::Context context, const Extension* extension, const GURL& url) { + // We're forced to load all schemas now because we need to know the metadata + // about every API -- and the metadata is stored in the schemas themselves. + // This is a shame. + // TODO(aa/kalman): store metadata in a separate file and don't load all + // schemas. + LoadAllSchemas(); + scoped_ptr<std::set<std::string> > result(new std::set<std::string>()); switch (context) { @@ -314,7 +390,7 @@ scoped_ptr<std::set<std::string> > ExtensionAPI::GetAPIsForContext( } void ExtensionAPI::GetAllowedAPIs( - const Extension* extension, std::set<std::string>* out) const { + const Extension* extension, std::set<std::string>* out) { for (SchemaMap::const_iterator i = schemas_.begin(); i != schemas_.end(); ++i) { if (extension->required_permission_set()->HasAnyAccessToAPI(i->first) || @@ -324,7 +400,7 @@ void ExtensionAPI::GetAllowedAPIs( } } -void ExtensionAPI::ResolveDependencies(std::set<std::string>* out) const { +void ExtensionAPI::ResolveDependencies(std::set<std::string>* out) { std::set<std::string> missing_dependencies; for (std::set<std::string>::iterator i = out->begin(); i != out->end(); ++i) GetMissingDependencies(*i, *out, &missing_dependencies); @@ -340,8 +416,8 @@ void ExtensionAPI::ResolveDependencies(std::set<std::string>* out) const { void ExtensionAPI::GetMissingDependencies( const std::string& api_name, const std::set<std::string>& excluding, - std::set<std::string>* out) const { - const base::DictionaryValue* schema = GetSchema(api_name); + std::set<std::string>* out) { + const DictionaryValue* schema = GetSchema(api_name); CHECK(schema) << "Schema for " << api_name << " not found"; ListValue* dependencies = NULL; @@ -355,7 +431,7 @@ void ExtensionAPI::GetMissingDependencies( } } -void ExtensionAPI::RemovePrivilegedAPIs(std::set<std::string>* apis) const { +void ExtensionAPI::RemovePrivilegedAPIs(std::set<std::string>* apis) { std::set<std::string> privileged_apis; for (std::set<std::string>::iterator i = apis->begin(); i != apis->end(); ++i) { @@ -371,7 +447,7 @@ void ExtensionAPI::RemovePrivilegedAPIs(std::set<std::string>* apis) const { } void ExtensionAPI::GetAPIsMatchingURL(const GURL& url, - std::set<std::string>* out) const { + 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 (i->second.MatchesURL(url)) @@ -379,4 +455,10 @@ void ExtensionAPI::GetAPIsMatchingURL(const GURL& url, } } +void ExtensionAPI::LoadAllSchemas() { + while (unloaded_schemas_.size()) { + LoadSchema(unloaded_schemas_.begin()->second); + } +} + } // namespace extensions diff --git a/chrome/common/extensions/api/extension_api.h b/chrome/common/extensions/api/extension_api.h index 4907152..1ae7824 100644 --- a/chrome/common/extensions/api/extension_api.h +++ b/chrome/common/extensions/api/extension_api.h @@ -14,6 +14,7 @@ #include "base/memory/linked_ptr.h" #include "base/memory/scoped_ptr.h" #include "base/memory/singleton.h" +#include "base/string_piece.h" #include "base/values.h" #include "chrome/common/extensions/feature.h" #include "chrome/common/extensions/url_pattern_set.h" @@ -36,76 +37,77 @@ class ExtensionAPI { // Returns the single instance of this class. static ExtensionAPI* GetInstance(); + // Public for construction from unit tests. Use GetInstance() normally. + ExtensionAPI(); + ~ExtensionAPI(); + // 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 // extension process. They cannot be called from extension code running in // content scripts, or other low-privileged contexts. - bool IsPrivileged(const std::string& name) const; + bool IsPrivileged(const std::string& name); // Gets the schema for the extension API with namespace |api_name|. // Ownership remains with this object. - const base::DictionaryValue* GetSchema(const std::string& api_name) const; + const base::DictionaryValue* GetSchema(const std::string& api_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 // NULL/empty. scoped_ptr<std::set<std::string> > GetAPIsForContext( - Feature::Context context, - const Extension* extension, - const GURL& url) const; + Feature::Context context, const Extension* extension, const GURL& url); private: friend struct DefaultSingletonTraits<ExtensionAPI>; - ExtensionAPI(); - ~ExtensionAPI(); - - // Loads a schema from a resource. - void LoadSchemaFromResource(int resource_id); - - // Given a schema in ListValue form, registers it in a map. Takes ownership - // of |loaded_schema|. - void RegisterSchema(base::ListValue* loaded_schema); + // 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) const; + 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) const; + const std::string& child_name); // Adds all APIs to |out| that |extension| has any permission (required or // optional) to use. - void GetAllowedAPIs( - const Extension* extension, std::set<std::string>* out) const; + void GetAllowedAPIs(const Extension* extension, std::set<std::string>* out); // Adds dependent schemas to |out| as determined by the "dependencies" // property. - void ResolveDependencies(std::set<std::string>* out) const; + void ResolveDependencies(std::set<std::string>* out); // Adds any APIs listed in "dependencies" found in the schema for |api_name| // but not in |excluding| to |out|. void GetMissingDependencies( const std::string& api_name, const std::set<std::string>& excluding, - std::set<std::string>* out) const; + std::set<std::string>* out); // Removes all APIs from |apis| which are *entirely* privileged. This won't // include APIs such as "storage" which is entirely unprivileged, nor // "extension" which has unprivileged components. - void RemovePrivilegedAPIs(std::set<std::string>* apis) const; + void RemovePrivilegedAPIs(std::set<std::string>* apis); // Adds an APIs that match |url| to |out|. - void GetAPIsMatchingURL(const GURL& url, std::set<std::string>* out) const; + 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_; + // Schemas for each namespace. typedef std::map<std::string, linked_ptr<const DictionaryValue> > SchemaMap; SchemaMap schemas_; diff --git a/chrome/common/extensions/api/extension_api_unittest.cc b/chrome/common/extensions/api/extension_api_unittest.cc index 1bcf33b..d6e324f 100644 --- a/chrome/common/extensions/api/extension_api_unittest.cc +++ b/chrome/common/extensions/api/extension_api_unittest.cc @@ -13,36 +13,57 @@ #include "chrome/common/extensions/extension.h" #include "testing/gtest/include/gtest/gtest.h" +namespace extensions { namespace { -using extensions::ExtensionAPI; -using extensions::Feature; - TEST(ExtensionAPI, IsPrivileged) { - ExtensionAPI* extension_api = ExtensionAPI::GetInstance(); - EXPECT_FALSE(extension_api->IsPrivileged("extension.connect")); - EXPECT_FALSE(extension_api->IsPrivileged("extension.onConnect")); + ExtensionAPI extension_api; + + 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("")); - 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, 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_refptr<Extension> CreateExtensionWithPermissions( @@ -84,16 +105,18 @@ TEST(ExtensionAPI, ExtensionWithUnprivilegedAPIs) { extension = CreateExtensionWithPermissions(permissions); } + ExtensionAPI extension_api; + scoped_ptr<std::set<std::string> > privileged_apis = - ExtensionAPI::GetInstance()->GetAPIsForContext( + extension_api.GetAPIsForContext( Feature::BLESSED_EXTENSION_CONTEXT, extension.get(), GURL()); scoped_ptr<std::set<std::string> > unprivileged_apis = - ExtensionAPI::GetInstance()->GetAPIsForContext( + extension_api.GetAPIsForContext( Feature::UNBLESSED_EXTENSION_CONTEXT, extension.get(), GURL()); scoped_ptr<std::set<std::string> > content_script_apis = - ExtensionAPI::GetInstance()->GetAPIsForContext( + extension_api.GetAPIsForContext( Feature::CONTENT_SCRIPT_CONTEXT, extension.get(), GURL()); // "storage" is completely unprivileged. @@ -119,7 +142,7 @@ TEST(ExtensionAPI, ExtensionWithDependencies) { scoped_refptr<Extension> extension = CreateExtensionWithPermission("ttsEngine"); scoped_ptr<std::set<std::string> > apis = - ExtensionAPI::GetInstance()->GetAPIsForContext( + ExtensionAPI().GetAPIsForContext( Feature::BLESSED_EXTENSION_CONTEXT, extension.get(), GURL()); EXPECT_EQ(1u, apis->count("ttsEngine")); EXPECT_EQ(1u, apis->count("tts")); @@ -131,39 +154,42 @@ TEST(ExtensionAPI, ExtensionWithDependencies) { scoped_refptr<Extension> extension = CreateExtensionWithPermission("tts"); scoped_ptr<std::set<std::string> > apis = - ExtensionAPI::GetInstance()->GetAPIsForContext( + ExtensionAPI().GetAPIsForContext( Feature::BLESSED_EXTENSION_CONTEXT, extension.get(), GURL()); EXPECT_EQ(0u, apis->count("ttsEngine")); EXPECT_EQ(1u, apis->count("tts")); } } -bool MatchesURL(const std::string& api_name, const std::string& url) { +bool MatchesURL( + ExtensionAPI* api, const std::string& api_name, const std::string& url) { scoped_ptr<std::set<std::string> > apis = - ExtensionAPI::GetInstance()->GetAPIsForContext( - Feature::WEB_PAGE_CONTEXT, NULL, GURL(url)); + api->GetAPIsForContext(Feature::WEB_PAGE_CONTEXT, NULL, GURL(url)); return apis->count(api_name); } TEST(ExtensionAPI, URLMatching) { + ExtensionAPI api; + // "app" API is available to all URLs that content scripts can be injected. - EXPECT_TRUE(MatchesURL("app", "http://example.com/example.html")); - EXPECT_TRUE(MatchesURL("app", "https://blah.net")); - EXPECT_TRUE(MatchesURL("app", "file://somefile.html")); + 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")); // But not internal URLs (for chrome-extension:// the app API is injected by // GetSchemasForExtension). - EXPECT_FALSE(MatchesURL("app", "about:flags")); - EXPECT_FALSE(MatchesURL("app", "chrome://flags")); - EXPECT_FALSE(MatchesURL("app", "chrome-extension://fakeextension")); + EXPECT_FALSE(MatchesURL(&api, "app", "about:flags")); + EXPECT_FALSE(MatchesURL(&api, "app", "chrome://flags")); + EXPECT_FALSE(MatchesURL(&api, "app", "chrome-extension://fakeextension")); // "storage" API (for example) isn't available to any URLs. - EXPECT_FALSE(MatchesURL("storage", "http://example.com/example.html")); - EXPECT_FALSE(MatchesURL("storage", "https://blah.net")); - EXPECT_FALSE(MatchesURL("storage", "file://somefile.html")); - EXPECT_FALSE(MatchesURL("storage", "about:flags")); - EXPECT_FALSE(MatchesURL("storage", "chrome://flags")); - EXPECT_FALSE(MatchesURL("storage", "chrome-extension://fakeextension")); + 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")); } } // namespace +} // namespace extensions |