diff options
author | wjmaclean <wjmaclean@chromium.org> | 2014-09-10 10:32:22 -0700 |
---|---|---|
committer | Commit bot <commit-bot@chromium.org> | 2014-09-10 17:36:12 +0000 |
commit | 97b60ee352ca47814774ffe9a4bebfc6d44b3e05 (patch) | |
tree | a67c2fda61dcd67253fa46fcf2258e211d398474 /extensions | |
parent | 05d507c5ff8d67311e264c2ee9c9edc4729346b1 (diff) | |
download | chromium_src-97b60ee352ca47814774ffe9a4bebfc6d44b3e05.zip chromium_src-97b60ee352ca47814774ffe9a4bebfc6d44b3e05.tar.gz chromium_src-97b60ee352ca47814774ffe9a4bebfc6d44b3e05.tar.bz2 |
Move declarative api files to extensions/browser/api/declarative.
This cl also includes some small changes to remove
chrome/extensions dependencies in these files, e.g.
it gets IsIncognitoEnabled() from ExtensionPrefs()
instead of util::IsIncognitoEnabled().
BUG=352293
Review URL: https://codereview.chromium.org/550403003
Cr-Commit-Position: refs/heads/master@{#294186}
Diffstat (limited to 'extensions')
17 files changed, 2688 insertions, 0 deletions
diff --git a/extensions/BUILD.gn b/extensions/BUILD.gn index 13e7a50..21477fd 100644 --- a/extensions/BUILD.gn +++ b/extensions/BUILD.gn @@ -165,6 +165,7 @@ test("unittests") { sources = [ "browser/admin_policy_unittest.cc", "browser/api/api_resource_manager_unittest.cc", + "browser/api/declarative/declarative_rule_unittest.cc", "browser/api/declarative/deduping_factory_unittest.cc", "browser/api/sockets_tcp/sockets_tcp_api_unittest.cc", "browser/api/storage/settings_quota_unittest.cc", diff --git a/extensions/browser/BUILD.gn b/extensions/browser/BUILD.gn index 764e40b..84c5c0c 100644 --- a/extensions/browser/BUILD.gn +++ b/extensions/browser/BUILD.gn @@ -89,6 +89,16 @@ source_set("browser") { "api/cast_channel/logger.h", "api/cast_channel/logger_util.cc", "api/cast_channel/logger_util.h", + "api/declarative/declarative_api.cc", + "api/declarative/declarative_api.h", + "api/declarative/declarative_rule.h", + "api/declarative/deduping_factory.h", + "api/declarative/rules_cache_delegate.cc", + "api/declarative/rules_cache_delegate.h", + "api/declarative/rules_registry.cc", + "api/declarative/rules_registry.h", + "api/declarative/test_rules_registry.cc", + "api/declarative/test_rules_registry.h", "api/declarative_webrequest/request_stage.cc", "api/declarative_webrequest/request_stage.h", "api/declarative_webrequest/webrequest_constants.cc", diff --git a/extensions/browser/api/declarative/declarative_api.cc b/extensions/browser/api/declarative/declarative_api.cc new file mode 100644 index 0000000..5794f38 --- /dev/null +++ b/extensions/browser/api/declarative/declarative_api.cc @@ -0,0 +1,165 @@ +// 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 "extensions/browser/api/declarative/declarative_api.h" + +#include "base/bind.h" +#include "base/bind_helpers.h" +#include "base/task_runner_util.h" +#include "base/values.h" +#include "content/public/browser/browser_thread.h" +#include "content/public/browser/render_process_host.h" +#include "content/public/browser/render_view_host.h" +#include "content/public/browser/web_contents.h" +#include "extensions/browser/api/extensions_api_client.h" +#include "extensions/browser/extension_system.h" +#include "extensions/browser/guest_view/web_view/web_view_constants.h" +#include "extensions/browser/guest_view/web_view/web_view_guest.h" +#include "extensions/common/api/events.h" +#include "extensions/common/extension_api.h" +#include "extensions/common/permissions/permissions_data.h" + +using extensions::core_api::events::Rule; + +namespace AddRules = extensions::core_api::events::Event::AddRules; +namespace GetRules = extensions::core_api::events::Event::GetRules; +namespace RemoveRules = extensions::core_api::events::Event::RemoveRules; + + +namespace extensions { + +namespace { + +const char kWebRequest[] = "declarativeWebRequest."; +const char kWebViewExpectedError[] = "Webview event with Webview ID expected."; + +bool IsWebViewEvent(const std::string& event_name) { + // Sample event names: + // webViewInternal.onRequest. + // webViewInternal.onMessage. + return event_name.compare(0, + strlen(webview::kWebViewEventPrefix), + webview::kWebViewEventPrefix) == 0; +} + +std::string GetWebRequestEventName(const std::string& event_name) { + std::string web_request_event_name(event_name); + if (IsWebViewEvent(web_request_event_name)) { + web_request_event_name.replace( + 0, strlen(webview::kWebViewEventPrefix), kWebRequest); + } + return web_request_event_name; +} + +} // namespace + +RulesFunction::RulesFunction() + : rules_registry_(NULL) { +} + +RulesFunction::~RulesFunction() {} + +bool RulesFunction::HasPermission() { + std::string event_name; + EXTENSION_FUNCTION_VALIDATE(args_->GetString(0, &event_name)); + if (IsWebViewEvent(event_name) && + extension_->permissions_data()->HasAPIPermission( + extensions::APIPermission::kWebView)) + return true; + Feature::Availability availability = + ExtensionAPI::GetSharedInstance()->IsAvailable( + event_name, + extension_.get(), + Feature::BLESSED_EXTENSION_CONTEXT, + source_url()); + return availability.is_available(); +} + +bool RulesFunction::RunAsync() { + std::string event_name; + EXTENSION_FUNCTION_VALIDATE(args_->GetString(0, &event_name)); + + int webview_instance_id = 0; + EXTENSION_FUNCTION_VALIDATE(args_->GetInteger(1, &webview_instance_id)); + int embedder_process_id = render_view_host()->GetProcess()->GetID(); + + bool has_webview = webview_instance_id != 0; + if (has_webview != IsWebViewEvent(event_name)) + EXTENSION_FUNCTION_ERROR(kWebViewExpectedError); + event_name = GetWebRequestEventName(event_name); + + // If we are not operating on a particular <webview>, then the key is (0, 0). + RulesRegistry::WebViewKey key( + webview_instance_id ? embedder_process_id : 0, webview_instance_id); + + // The following call will return a NULL pointer for apps_shell, but should + // never be called there anyways. + rules_registry_ = ExtensionsAPIClient::Get()->GetRulesRegistry( + browser_context(), key, event_name); + DCHECK(rules_registry_.get()); + // Raw access to this function is not available to extensions, therefore + // there should never be a request for a nonexisting rules registry. + EXTENSION_FUNCTION_VALIDATE(rules_registry_.get()); + + if (content::BrowserThread::CurrentlyOn(rules_registry_->owner_thread())) { + bool success = RunAsyncOnCorrectThread(); + SendResponse(success); + } else { + scoped_refptr<base::MessageLoopProxy> message_loop_proxy = + content::BrowserThread::GetMessageLoopProxyForThread( + rules_registry_->owner_thread()); + base::PostTaskAndReplyWithResult( + message_loop_proxy.get(), + FROM_HERE, + base::Bind(&RulesFunction::RunAsyncOnCorrectThread, this), + base::Bind(&RulesFunction::SendResponse, this)); + } + + return true; +} + +bool EventsEventAddRulesFunction::RunAsyncOnCorrectThread() { + scoped_ptr<AddRules::Params> params(AddRules::Params::Create(*args_)); + EXTENSION_FUNCTION_VALIDATE(params.get()); + + error_ = rules_registry_->AddRules(extension_id(), params->rules); + + if (error_.empty()) + results_ = AddRules::Results::Create(params->rules); + + return error_.empty(); +} + +bool EventsEventRemoveRulesFunction::RunAsyncOnCorrectThread() { + scoped_ptr<RemoveRules::Params> params(RemoveRules::Params::Create(*args_)); + EXTENSION_FUNCTION_VALIDATE(params.get()); + + if (params->rule_identifiers.get()) { + error_ = rules_registry_->RemoveRules(extension_id(), + *params->rule_identifiers); + } else { + error_ = rules_registry_->RemoveAllRules(extension_id()); + } + + return error_.empty(); +} + +bool EventsEventGetRulesFunction::RunAsyncOnCorrectThread() { + scoped_ptr<GetRules::Params> params(GetRules::Params::Create(*args_)); + EXTENSION_FUNCTION_VALIDATE(params.get()); + + std::vector<linked_ptr<Rule> > rules; + if (params->rule_identifiers.get()) { + rules_registry_->GetRules( + extension_id(), *params->rule_identifiers, &rules); + } else { + rules_registry_->GetAllRules(extension_id(), &rules); + } + + results_ = GetRules::Results::Create(rules); + + return true; +} + +} // namespace extensions diff --git a/extensions/browser/api/declarative/declarative_api.h b/extensions/browser/api/declarative/declarative_api.h new file mode 100644 index 0000000..72180dd --- /dev/null +++ b/extensions/browser/api/declarative/declarative_api.h @@ -0,0 +1,69 @@ +// 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 EXTENSIONS_BROWSER_API_DECLARATIVE_DECLARATIVE_API_H_ +#define EXTENSIONS_BROWSER_API_DECLARATIVE_DECLARATIVE_API_H_ + +#include "base/compiler_specific.h" +#include "base/memory/ref_counted.h" +#include "extensions/browser/api/declarative/rules_registry.h" +#include "extensions/browser/extension_function.h" + +namespace extensions { + +class RulesFunction : public AsyncExtensionFunction { + public: + RulesFunction(); + + protected: + virtual ~RulesFunction(); + + // ExtensionFunction: + virtual bool HasPermission() OVERRIDE; + virtual bool RunAsync() OVERRIDE; + + // Concrete implementation of the RulesFunction that is being called + // on the thread on which the respective RulesRegistry lives. + // Returns false in case of errors. + virtual bool RunAsyncOnCorrectThread() = 0; + + scoped_refptr<RulesRegistry> rules_registry_; +}; + +class EventsEventAddRulesFunction : public RulesFunction { + public: + DECLARE_EXTENSION_FUNCTION("events.addRules", EVENTS_ADDRULES) + + protected: + virtual ~EventsEventAddRulesFunction() {} + + // RulesFunction: + virtual bool RunAsyncOnCorrectThread() OVERRIDE; +}; + +class EventsEventRemoveRulesFunction : public RulesFunction { + public: + DECLARE_EXTENSION_FUNCTION("events.removeRules", EVENTS_REMOVERULES) + + protected: + virtual ~EventsEventRemoveRulesFunction() {} + + // RulesFunction: + virtual bool RunAsyncOnCorrectThread() OVERRIDE; +}; + +class EventsEventGetRulesFunction : public RulesFunction { + public: + DECLARE_EXTENSION_FUNCTION("events.getRules", EVENTS_GETRULES) + + protected: + virtual ~EventsEventGetRulesFunction() {} + + // RulesFunction: + virtual bool RunAsyncOnCorrectThread() OVERRIDE; +}; + +} // namespace extensions + +#endif // EXTENSIONS_BROWSER_API_DECLARATIVE_DECLARATIVE_API_H_ diff --git a/extensions/browser/api/declarative/declarative_rule.h b/extensions/browser/api/declarative/declarative_rule.h new file mode 100644 index 0000000..b612f27 --- /dev/null +++ b/extensions/browser/api/declarative/declarative_rule.h @@ -0,0 +1,517 @@ +// Copyright (c) 2013 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. +// +// DeclarativeRule<>, DeclarativeConditionSet<>, and DeclarativeActionSet<> +// templates usable with multiple different declarativeFoo systems. These are +// templated on the Condition and Action types that define the behavior of a +// particular declarative event. + +#ifndef EXTENSIONS_BROWSER_API_DECLARATIVE_DECLARATIVE_RULE_H__ +#define EXTENSIONS_BROWSER_API_DECLARATIVE_DECLARATIVE_RULE_H__ + +#include <limits> +#include <set> +#include <string> +#include <vector> + +#include "base/callback.h" +#include "base/memory/linked_ptr.h" +#include "base/memory/scoped_vector.h" +#include "base/stl_util.h" +#include "base/time/time.h" +#include "components/url_matcher/url_matcher.h" +#include "extensions/common/api/events.h" +#include "extensions/common/extension.h" + +namespace base { +class Time; +class Value; +} + +namespace content { +class BrowserContext; +} + +namespace extensions { + +// This class stores a set of conditions that may be part of a DeclarativeRule. +// If any condition is fulfilled, the Actions of the DeclarativeRule can be +// triggered. +// +// ConditionT should be immutable after creation. It must define the following +// members: +// +// // Arguments passed through from DeclarativeConditionSet::Create. +// static scoped_ptr<ConditionT> Create( +// const Extension* extension, +// URLMatcherConditionFactory* url_matcher_condition_factory, +// // Except this argument gets elements of the AnyVector. +// const base::Value& definition, +// std::string* error); +// // If the Condition needs to be filtered by some URLMatcherConditionSets, +// // append them to |condition_sets|. +// // DeclarativeConditionSet::GetURLMatcherConditionSets forwards here. +// void GetURLMatcherConditionSets( +// URLMatcherConditionSet::Vector* condition_sets); +// // |match_data| passed through from DeclarativeConditionSet::IsFulfilled. +// bool IsFulfilled(const ConditionT::MatchData& match_data); +template<typename ConditionT> +class DeclarativeConditionSet { + public: + typedef std::vector<linked_ptr<base::Value> > AnyVector; + typedef std::vector<linked_ptr<const ConditionT> > Conditions; + typedef typename Conditions::const_iterator const_iterator; + + // Factory method that creates a DeclarativeConditionSet for |extension| + // according to the JSON array |conditions| passed by the extension API. Sets + // |error| and returns NULL in case of an error. + static scoped_ptr<DeclarativeConditionSet> Create( + const Extension* extension, + url_matcher::URLMatcherConditionFactory* url_matcher_condition_factory, + const AnyVector& conditions, + std::string* error); + + const Conditions& conditions() const { + return conditions_; + } + + const_iterator begin() const { return conditions_.begin(); } + const_iterator end() const { return conditions_.end(); } + + // If |url_match_trigger| is not -1, this function looks for a condition + // with this URLMatcherConditionSet, and forwards to that condition's + // IsFulfilled(|match_data|). If there is no such condition, then false is + // returned. If |url_match_trigger| is -1, this function returns whether any + // of the conditions without URL attributes is satisfied. + bool IsFulfilled(url_matcher::URLMatcherConditionSet::ID url_match_trigger, + const typename ConditionT::MatchData& match_data) const; + + // Appends the URLMatcherConditionSet from all conditions to |condition_sets|. + void GetURLMatcherConditionSets( + url_matcher::URLMatcherConditionSet::Vector* condition_sets) const; + + // Returns whether there are some conditions without UrlFilter attributes. + bool HasConditionsWithoutUrls() const { + return !conditions_without_urls_.empty(); + } + + private: + typedef std::map<url_matcher::URLMatcherConditionSet::ID, const ConditionT*> + URLMatcherIdToCondition; + + DeclarativeConditionSet( + const Conditions& conditions, + const URLMatcherIdToCondition& match_id_to_condition, + const std::vector<const ConditionT*>& conditions_without_urls); + + const URLMatcherIdToCondition match_id_to_condition_; + const Conditions conditions_; + const std::vector<const ConditionT*> conditions_without_urls_; + + DISALLOW_COPY_AND_ASSIGN(DeclarativeConditionSet); +}; + +// Immutable container for multiple actions. +// +// ActionT should be immutable after creation. It must define the following +// members: +// +// // Arguments passed through from ActionSet::Create. +// static scoped_ptr<ActionT> Create( +// const Extension* extension, +// // Except this argument gets elements of the AnyVector. +// const base::Value& definition, +// std::string* error, bool* bad_message); +// void Apply(const std::string& extension_id, +// const base::Time& extension_install_time, +// // Contains action-type-specific in/out parameters. +// typename ActionT::ApplyInfo* apply_info) const; +// // Only needed if the RulesRegistry calls DeclarativeActionSet::Revert(). +// void Revert(const std::string& extension_id, +// const base::Time& extension_install_time, +// // Contains action-type-specific in/out parameters. +// typename ActionT::ApplyInfo* apply_info) const; +// // Return the minimum priority of rules that can be evaluated after this +// // action runs. A suitable default value is MIN_INT. +// int minimum_priority() const; +// +// TODO(battre): As DeclarativeActionSet can become the single owner of all +// actions, we can optimize here by making some of them singletons (e.g. Cancel +// actions). +template<typename ActionT> +class DeclarativeActionSet { + public: + typedef std::vector<linked_ptr<base::Value> > AnyVector; + typedef std::vector<scoped_refptr<const ActionT> > Actions; + + explicit DeclarativeActionSet(const Actions& actions); + + // Factory method that instantiates a DeclarativeActionSet for |extension| + // according to |actions| which represents the array of actions received from + // the extension API. + static scoped_ptr<DeclarativeActionSet> Create( + content::BrowserContext* browser_context, + const Extension* extension, + const AnyVector& actions, + std::string* error, + bool* bad_message); + + // Rules call this method when their conditions are fulfilled. + void Apply(const std::string& extension_id, + const base::Time& extension_install_time, + typename ActionT::ApplyInfo* apply_info) const; + + // Rules call this method when their conditions are fulfilled, but Apply has + // already been called. + void Reapply(const std::string& extension_id, + const base::Time& extension_install_time, + typename ActionT::ApplyInfo* apply_info) const; + + // Rules call this method when they have stateful conditions, and those + // conditions stop being fulfilled. Rules with event-based conditions (e.g. a + // network request happened) will never Revert() an action. + void Revert(const std::string& extension_id, + const base::Time& extension_install_time, + typename ActionT::ApplyInfo* apply_info) const; + + // Returns the minimum priority of rules that may be evaluated after + // this rule. Defaults to MIN_INT. + int GetMinimumPriority() const; + + const Actions& actions() const { return actions_; } + + private: + const Actions actions_; + + DISALLOW_COPY_AND_ASSIGN(DeclarativeActionSet); +}; + +// Representation of a rule of a declarative API: +// https://developer.chrome.com/beta/extensions/events.html#declarative. +// Generally a RulesRegistry will hold a collection of Rules for a given +// declarative API and contain the logic for matching and applying them. +// +// See DeclarativeConditionSet and DeclarativeActionSet for the requirements on +// ConditionT and ActionT. +template<typename ConditionT, typename ActionT> +class DeclarativeRule { + public: + typedef std::string ExtensionId; + typedef std::string RuleId; + typedef std::pair<ExtensionId, RuleId> GlobalRuleId; + typedef int Priority; + typedef DeclarativeConditionSet<ConditionT> ConditionSet; + typedef DeclarativeActionSet<ActionT> ActionSet; + typedef extensions::core_api::events::Rule JsonRule; + typedef std::vector<std::string> Tags; + + // Checks whether the set of |conditions| and |actions| are consistent. + // Returns true in case of consistency and MUST set |error| otherwise. + typedef base::Callback<bool(const ConditionSet* conditions, + const ActionSet* actions, + std::string* error)> ConsistencyChecker; + + DeclarativeRule(const GlobalRuleId& id, + const Tags& tags, + base::Time extension_installation_time, + scoped_ptr<ConditionSet> conditions, + scoped_ptr<ActionSet> actions, + Priority priority); + + // Creates a DeclarativeRule for |extension| given a json definition. The + // format of each condition and action's json is up to the specific ConditionT + // and ActionT. |extension| may be NULL in tests. + // + // Before constructing the final rule, calls check_consistency(conditions, + // actions, error) and returns NULL if it fails. Pass NULL if no consistency + // check is needed. If |error| is empty, the translation was successful and + // the returned rule is internally consistent. + static scoped_ptr<DeclarativeRule> Create( + url_matcher::URLMatcherConditionFactory* url_matcher_condition_factory, + content::BrowserContext* browser_context, + const Extension* extension, + base::Time extension_installation_time, + linked_ptr<JsonRule> rule, + ConsistencyChecker check_consistency, + std::string* error); + + const GlobalRuleId& id() const { return id_; } + const Tags& tags() const { return tags_; } + const std::string& extension_id() const { return id_.first; } + const ConditionSet& conditions() const { return *conditions_; } + const ActionSet& actions() const { return *actions_; } + Priority priority() const { return priority_; } + + // Calls actions().Apply(extension_id(), extension_installation_time_, + // apply_info). This function should only be called when the conditions_ are + // fulfilled (from a semantic point of view; no harm is done if this function + // is called at other times for testing purposes). + void Apply(typename ActionT::ApplyInfo* apply_info) const; + + // Returns the minimum priority of rules that may be evaluated after + // this rule. Defaults to MIN_INT. Only valid if the conditions of this rule + // are fulfilled. + Priority GetMinimumPriority() const; + + private: + GlobalRuleId id_; + Tags tags_; + base::Time extension_installation_time_; // For precedences of rules. + scoped_ptr<ConditionSet> conditions_; + scoped_ptr<ActionSet> actions_; + Priority priority_; + + DISALLOW_COPY_AND_ASSIGN(DeclarativeRule); +}; + +// Implementation details below here. + +// +// DeclarativeConditionSet +// + +template<typename ConditionT> +bool DeclarativeConditionSet<ConditionT>::IsFulfilled( + url_matcher::URLMatcherConditionSet::ID url_match_trigger, + const typename ConditionT::MatchData& match_data) const { + if (url_match_trigger == -1) { + // Invalid trigger -- indication that we should only check conditions + // without URL attributes. + for (typename std::vector<const ConditionT*>::const_iterator it = + conditions_without_urls_.begin(); + it != conditions_without_urls_.end(); ++it) { + if ((*it)->IsFulfilled(match_data)) + return true; + } + return false; + } + + typename URLMatcherIdToCondition::const_iterator triggered = + match_id_to_condition_.find(url_match_trigger); + return (triggered != match_id_to_condition_.end() && + triggered->second->IsFulfilled(match_data)); +} + +template<typename ConditionT> +void DeclarativeConditionSet<ConditionT>::GetURLMatcherConditionSets( + url_matcher::URLMatcherConditionSet::Vector* condition_sets) const { + for (typename Conditions::const_iterator i = conditions_.begin(); + i != conditions_.end(); ++i) { + (*i)->GetURLMatcherConditionSets(condition_sets); + } +} + +// static +template<typename ConditionT> +scoped_ptr<DeclarativeConditionSet<ConditionT> > +DeclarativeConditionSet<ConditionT>::Create( + const Extension* extension, + url_matcher::URLMatcherConditionFactory* url_matcher_condition_factory, + const AnyVector& conditions, + std::string* error) { + Conditions result; + + for (AnyVector::const_iterator i = conditions.begin(); + i != conditions.end(); ++i) { + CHECK(i->get()); + scoped_ptr<ConditionT> condition = ConditionT::Create( + extension, url_matcher_condition_factory, **i, error); + if (!error->empty()) + return scoped_ptr<DeclarativeConditionSet>(); + result.push_back(make_linked_ptr(condition.release())); + } + + URLMatcherIdToCondition match_id_to_condition; + std::vector<const ConditionT*> conditions_without_urls; + url_matcher::URLMatcherConditionSet::Vector condition_sets; + + for (typename Conditions::const_iterator i = result.begin(); + i != result.end(); ++i) { + condition_sets.clear(); + (*i)->GetURLMatcherConditionSets(&condition_sets); + if (condition_sets.empty()) { + conditions_without_urls.push_back(i->get()); + } else { + for (url_matcher::URLMatcherConditionSet::Vector::const_iterator + match_set = condition_sets.begin(); + match_set != condition_sets.end(); ++match_set) + match_id_to_condition[(*match_set)->id()] = i->get(); + } + } + + return make_scoped_ptr(new DeclarativeConditionSet( + result, match_id_to_condition, conditions_without_urls)); +} + +template<typename ConditionT> +DeclarativeConditionSet<ConditionT>::DeclarativeConditionSet( + const Conditions& conditions, + const URLMatcherIdToCondition& match_id_to_condition, + const std::vector<const ConditionT*>& conditions_without_urls) + : match_id_to_condition_(match_id_to_condition), + conditions_(conditions), + conditions_without_urls_(conditions_without_urls) {} + +// +// DeclarativeActionSet +// + +template<typename ActionT> +DeclarativeActionSet<ActionT>::DeclarativeActionSet(const Actions& actions) + : actions_(actions) {} + +// static +template<typename ActionT> +scoped_ptr<DeclarativeActionSet<ActionT> > +DeclarativeActionSet<ActionT>::Create( + content::BrowserContext* browser_context, + const Extension* extension, + const AnyVector& actions, + std::string* error, + bool* bad_message) { + *error = ""; + *bad_message = false; + Actions result; + + for (AnyVector::const_iterator i = actions.begin(); + i != actions.end(); ++i) { + CHECK(i->get()); + scoped_refptr<const ActionT> action = + ActionT::Create(browser_context, extension, **i, error, bad_message); + if (!error->empty() || *bad_message) + return scoped_ptr<DeclarativeActionSet>(); + result.push_back(action); + } + + return scoped_ptr<DeclarativeActionSet>(new DeclarativeActionSet(result)); +} + +template<typename ActionT> +void DeclarativeActionSet<ActionT>::Apply( + const std::string& extension_id, + const base::Time& extension_install_time, + typename ActionT::ApplyInfo* apply_info) const { + for (typename Actions::const_iterator i = actions_.begin(); + i != actions_.end(); ++i) + (*i)->Apply(extension_id, extension_install_time, apply_info); +} + +template<typename ActionT> +void DeclarativeActionSet<ActionT>::Reapply( + const std::string& extension_id, + const base::Time& extension_install_time, + typename ActionT::ApplyInfo* apply_info) const { + for (typename Actions::const_iterator i = actions_.begin(); + i != actions_.end(); ++i) + (*i)->Reapply(extension_id, extension_install_time, apply_info); +} + +template<typename ActionT> +void DeclarativeActionSet<ActionT>::Revert( + const std::string& extension_id, + const base::Time& extension_install_time, + typename ActionT::ApplyInfo* apply_info) const { + for (typename Actions::const_iterator i = actions_.begin(); + i != actions_.end(); ++i) + (*i)->Revert(extension_id, extension_install_time, apply_info); +} + +template<typename ActionT> +int DeclarativeActionSet<ActionT>::GetMinimumPriority() const { + int minimum_priority = std::numeric_limits<int>::min(); + for (typename Actions::const_iterator i = actions_.begin(); + i != actions_.end(); ++i) { + minimum_priority = std::max(minimum_priority, (*i)->minimum_priority()); + } + return minimum_priority; +} + +// +// DeclarativeRule +// + +template<typename ConditionT, typename ActionT> +DeclarativeRule<ConditionT, ActionT>::DeclarativeRule( + const GlobalRuleId& id, + const Tags& tags, + base::Time extension_installation_time, + scoped_ptr<ConditionSet> conditions, + scoped_ptr<ActionSet> actions, + Priority priority) + : id_(id), + tags_(tags), + extension_installation_time_(extension_installation_time), + conditions_(conditions.release()), + actions_(actions.release()), + priority_(priority) { + CHECK(conditions_.get()); + CHECK(actions_.get()); +} + +// static +template<typename ConditionT, typename ActionT> +scoped_ptr<DeclarativeRule<ConditionT, ActionT> > +DeclarativeRule<ConditionT, ActionT>::Create( + url_matcher::URLMatcherConditionFactory* url_matcher_condition_factory, + content::BrowserContext* browser_context, + const Extension* extension, + base::Time extension_installation_time, + linked_ptr<JsonRule> rule, + ConsistencyChecker check_consistency, + std::string* error) { + scoped_ptr<DeclarativeRule> error_result; + + scoped_ptr<ConditionSet> conditions = ConditionSet::Create( + extension, url_matcher_condition_factory, rule->conditions, error); + if (!error->empty()) + return error_result.Pass(); + CHECK(conditions.get()); + + bool bad_message = false; + scoped_ptr<ActionSet> actions = + ActionSet::Create( + browser_context, extension, rule->actions, error, &bad_message); + if (bad_message) { + // TODO(battre) Export concept of bad_message to caller, the extension + // should be killed in case it is true. + *error = "An action of a rule set had an invalid " + "structure that should have been caught by the JSON validator."; + return error_result.Pass(); + } + if (!error->empty() || bad_message) + return error_result.Pass(); + CHECK(actions.get()); + + if (!check_consistency.is_null() && + !check_consistency.Run(conditions.get(), actions.get(), error)) { + DCHECK(!error->empty()); + return error_result.Pass(); + } + + CHECK(rule->priority.get()); + int priority = *(rule->priority); + + GlobalRuleId rule_id(extension->id(), *(rule->id)); + Tags tags = rule->tags ? *rule->tags : Tags(); + return scoped_ptr<DeclarativeRule>( + new DeclarativeRule(rule_id, tags, extension_installation_time, + conditions.Pass(), actions.Pass(), priority)); +} + +template<typename ConditionT, typename ActionT> +void DeclarativeRule<ConditionT, ActionT>::Apply( + typename ActionT::ApplyInfo* apply_info) const { + return actions_->Apply(extension_id(), + extension_installation_time_, + apply_info); +} + +template<typename ConditionT, typename ActionT> +int DeclarativeRule<ConditionT, ActionT>::GetMinimumPriority() const { + return actions_->GetMinimumPriority(); +} + +} // namespace extensions + +#endif // EXTENSIONS_BROWSER_API_DECLARATIVE_DECLARATIVE_RULE_H__ diff --git a/extensions/browser/api/declarative/declarative_rule_unittest.cc b/extensions/browser/api/declarative/declarative_rule_unittest.cc new file mode 100644 index 0000000..552145d --- /dev/null +++ b/extensions/browser/api/declarative/declarative_rule_unittest.cc @@ -0,0 +1,442 @@ +// Copyright (c) 2013 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 "extensions/browser/api/declarative/declarative_rule.h" + +#include "base/bind.h" +#include "base/message_loop/message_loop.h" +#include "base/test/values_test_util.h" +#include "base/values.h" +#include "components/url_matcher/url_matcher_constants.h" +#include "extensions/common/extension_builder.h" +#include "testing/gmock/include/gmock/gmock.h" +#include "testing/gtest/include/gtest/gtest.h" + +using base::test::ParseJson; +using url_matcher::URLMatcher; +using url_matcher::URLMatcherConditionFactory; +using url_matcher::URLMatcherConditionSet; + +namespace extensions { + +namespace { + +template<typename T> +linked_ptr<T> ScopedToLinkedPtr(scoped_ptr<T> ptr) { + return linked_ptr<T>(ptr.release()); +} + +scoped_ptr<base::DictionaryValue> SimpleManifest() { + return DictionaryBuilder() + .Set("name", "extension") + .Set("manifest_version", 2) + .Set("version", "1.0") + .Build(); +} + +} // namespace + +struct RecordingCondition { + typedef int MatchData; + + URLMatcherConditionFactory* factory; + scoped_ptr<base::Value> value; + + void GetURLMatcherConditionSets( + URLMatcherConditionSet::Vector* condition_sets) const { + // No condition sets. + } + + static scoped_ptr<RecordingCondition> Create( + const Extension* extension, + URLMatcherConditionFactory* url_matcher_condition_factory, + const base::Value& condition, + std::string* error) { + const base::DictionaryValue* dict = NULL; + if (condition.GetAsDictionary(&dict) && dict->HasKey("bad_key")) { + *error = "Found error key"; + return scoped_ptr<RecordingCondition>(); + } + + scoped_ptr<RecordingCondition> result(new RecordingCondition()); + result->factory = url_matcher_condition_factory; + result->value.reset(condition.DeepCopy()); + return result.Pass(); + } +}; +typedef DeclarativeConditionSet<RecordingCondition> RecordingConditionSet; + +TEST(DeclarativeConditionTest, ErrorConditionSet) { + URLMatcher matcher; + RecordingConditionSet::AnyVector conditions; + conditions.push_back(ScopedToLinkedPtr(ParseJson("{\"key\": 1}"))); + conditions.push_back(ScopedToLinkedPtr(ParseJson("{\"bad_key\": 2}"))); + + std::string error; + scoped_ptr<RecordingConditionSet> result = RecordingConditionSet::Create( + NULL, matcher.condition_factory(), conditions, &error); + EXPECT_EQ("Found error key", error); + ASSERT_FALSE(result); +} + +TEST(DeclarativeConditionTest, CreateConditionSet) { + URLMatcher matcher; + RecordingConditionSet::AnyVector conditions; + conditions.push_back(ScopedToLinkedPtr(ParseJson("{\"key\": 1}"))); + conditions.push_back(ScopedToLinkedPtr(ParseJson("[\"val1\", 2]"))); + + // Test insertion + std::string error; + scoped_ptr<RecordingConditionSet> result = RecordingConditionSet::Create( + NULL, matcher.condition_factory(), conditions, &error); + EXPECT_EQ("", error); + ASSERT_TRUE(result); + EXPECT_EQ(2u, result->conditions().size()); + + EXPECT_EQ(matcher.condition_factory(), result->conditions()[0]->factory); + EXPECT_TRUE(ParseJson("{\"key\": 1}")->Equals( + result->conditions()[0]->value.get())); +} + +struct FulfillableCondition { + struct MatchData { + int value; + const std::set<URLMatcherConditionSet::ID>& url_matches; + }; + + scoped_refptr<URLMatcherConditionSet> condition_set; + int condition_set_id; + int max_value; + + URLMatcherConditionSet::ID url_matcher_condition_set_id() const { + return condition_set_id; + } + + scoped_refptr<URLMatcherConditionSet> url_matcher_condition_set() const { + return condition_set; + } + + void GetURLMatcherConditionSets( + URLMatcherConditionSet::Vector* condition_sets) const { + if (condition_set.get()) + condition_sets->push_back(condition_set); + } + + bool IsFulfilled(const MatchData& match_data) const { + if (condition_set_id != -1 && + !ContainsKey(match_data.url_matches, condition_set_id)) + return false; + return match_data.value <= max_value; + } + + static scoped_ptr<FulfillableCondition> Create( + const Extension* extension, + URLMatcherConditionFactory* url_matcher_condition_factory, + const base::Value& condition, + std::string* error) { + scoped_ptr<FulfillableCondition> result(new FulfillableCondition()); + const base::DictionaryValue* dict; + if (!condition.GetAsDictionary(&dict)) { + *error = "Expected dict"; + return result.Pass(); + } + if (!dict->GetInteger("url_id", &result->condition_set_id)) + result->condition_set_id = -1; + if (!dict->GetInteger("max", &result->max_value)) + *error = "Expected integer at ['max']"; + if (result->condition_set_id != -1) { + result->condition_set = new URLMatcherConditionSet( + result->condition_set_id, + URLMatcherConditionSet::Conditions()); + } + return result.Pass(); + } +}; + +TEST(DeclarativeConditionTest, FulfillConditionSet) { + typedef DeclarativeConditionSet<FulfillableCondition> FulfillableConditionSet; + FulfillableConditionSet::AnyVector conditions; + conditions.push_back(ScopedToLinkedPtr(ParseJson( + "{\"url_id\": 1, \"max\": 3}"))); + conditions.push_back(ScopedToLinkedPtr(ParseJson( + "{\"url_id\": 2, \"max\": 5}"))); + conditions.push_back(ScopedToLinkedPtr(ParseJson( + "{\"url_id\": 3, \"max\": 1}"))); + conditions.push_back(ScopedToLinkedPtr(ParseJson( + "{\"max\": -5}"))); // No url. + + // Test insertion + std::string error; + scoped_ptr<FulfillableConditionSet> result = + FulfillableConditionSet::Create(NULL, NULL, conditions, &error); + ASSERT_EQ("", error); + ASSERT_TRUE(result); + EXPECT_EQ(4u, result->conditions().size()); + + std::set<URLMatcherConditionSet::ID> url_matches; + FulfillableCondition::MatchData match_data = { 0, url_matches }; + EXPECT_FALSE(result->IsFulfilled(1, match_data)) + << "Testing an ID that's not in url_matches forwards to the Condition, " + << "which doesn't match."; + EXPECT_FALSE(result->IsFulfilled(-1, match_data)) + << "Testing the 'no ID' value tries to match the 4th condition, but " + << "its max is too low."; + match_data.value = -5; + EXPECT_TRUE(result->IsFulfilled(-1, match_data)) + << "Testing the 'no ID' value tries to match the 4th condition, and " + << "this value is low enough."; + + url_matches.insert(1); + match_data.value = 3; + EXPECT_TRUE(result->IsFulfilled(1, match_data)) + << "Tests a condition with a url matcher, for a matching value."; + match_data.value = 4; + EXPECT_FALSE(result->IsFulfilled(1, match_data)) + << "Tests a condition with a url matcher, for a non-matching value " + << "that would match a different condition."; + url_matches.insert(2); + EXPECT_TRUE(result->IsFulfilled(2, match_data)) + << "Tests with 2 elements in the match set."; + + // Check the condition sets: + URLMatcherConditionSet::Vector condition_sets; + result->GetURLMatcherConditionSets(&condition_sets); + ASSERT_EQ(3U, condition_sets.size()); + EXPECT_EQ(1, condition_sets[0]->id()); + EXPECT_EQ(2, condition_sets[1]->id()); + EXPECT_EQ(3, condition_sets[2]->id()); +} + +// DeclarativeAction + +class SummingAction : public base::RefCounted<SummingAction> { + public: + typedef int ApplyInfo; + + SummingAction(int increment, int min_priority) + : increment_(increment), min_priority_(min_priority) {} + + static scoped_refptr<const SummingAction> Create( + content::BrowserContext* browser_context, + const Extension* extension, + const base::Value& action, + std::string* error, + bool* bad_message) { + int increment = 0; + int min_priority = 0; + const base::DictionaryValue* dict = NULL; + EXPECT_TRUE(action.GetAsDictionary(&dict)); + if (dict->HasKey("error")) { + EXPECT_TRUE(dict->GetString("error", error)); + return scoped_refptr<const SummingAction>(NULL); + } + if (dict->HasKey("bad")) { + *bad_message = true; + return scoped_refptr<const SummingAction>(NULL); + } + + EXPECT_TRUE(dict->GetInteger("value", &increment)); + dict->GetInteger("priority", &min_priority); + return scoped_refptr<const SummingAction>( + new SummingAction(increment, min_priority)); + } + + void Apply(const std::string& extension_id, + const base::Time& install_time, + int* sum) const { + *sum += increment_; + } + + int increment() const { return increment_; } + int minimum_priority() const { + return min_priority_; + } + + private: + friend class base::RefCounted<SummingAction>; + virtual ~SummingAction() {} + + int increment_; + int min_priority_; +}; +typedef DeclarativeActionSet<SummingAction> SummingActionSet; + +TEST(DeclarativeActionTest, ErrorActionSet) { + SummingActionSet::AnyVector actions; + actions.push_back(ScopedToLinkedPtr(ParseJson("{\"value\": 1}"))); + actions.push_back(ScopedToLinkedPtr(ParseJson("{\"error\": \"the error\"}"))); + + std::string error; + bool bad = false; + scoped_ptr<SummingActionSet> result = + SummingActionSet::Create(NULL, NULL, actions, &error, &bad); + EXPECT_EQ("the error", error); + EXPECT_FALSE(bad); + EXPECT_FALSE(result); + + actions.clear(); + actions.push_back(ScopedToLinkedPtr(ParseJson("{\"value\": 1}"))); + actions.push_back(ScopedToLinkedPtr(ParseJson("{\"bad\": 3}"))); + result = SummingActionSet::Create(NULL, NULL, actions, &error, &bad); + EXPECT_EQ("", error); + EXPECT_TRUE(bad); + EXPECT_FALSE(result); +} + +TEST(DeclarativeActionTest, ApplyActionSet) { + SummingActionSet::AnyVector actions; + actions.push_back(ScopedToLinkedPtr(ParseJson( + "{\"value\": 1," + " \"priority\": 5}"))); + actions.push_back(ScopedToLinkedPtr(ParseJson("{\"value\": 2}"))); + + // Test insertion + std::string error; + bool bad = false; + scoped_ptr<SummingActionSet> result = + SummingActionSet::Create(NULL, NULL, actions, &error, &bad); + EXPECT_EQ("", error); + EXPECT_FALSE(bad); + ASSERT_TRUE(result); + EXPECT_EQ(2u, result->actions().size()); + + int sum = 0; + result->Apply("ext_id", base::Time(), &sum); + EXPECT_EQ(3, sum); + EXPECT_EQ(5, result->GetMinimumPriority()); +} + +TEST(DeclarativeRuleTest, Create) { + typedef DeclarativeRule<FulfillableCondition, SummingAction> Rule; + linked_ptr<Rule::JsonRule> json_rule(new Rule::JsonRule); + ASSERT_TRUE(Rule::JsonRule::Populate( + *ParseJson("{ \n" + " \"id\": \"rule1\", \n" + " \"conditions\": [ \n" + " {\"url_id\": 1, \"max\": 3}, \n" + " {\"url_id\": 2, \"max\": 5}, \n" + " ], \n" + " \"actions\": [ \n" + " { \n" + " \"value\": 2 \n" + " } \n" + " ], \n" + " \"priority\": 200 \n" + "}"), + json_rule.get())); + + const char kExtensionId[] = "ext1"; + scoped_refptr<Extension> extension = ExtensionBuilder() + .SetManifest(SimpleManifest()) + .SetID(kExtensionId) + .Build(); + + base::Time install_time = base::Time::Now(); + + URLMatcher matcher; + std::string error; + scoped_ptr<Rule> rule(Rule::Create(matcher.condition_factory(), + NULL, + extension.get(), + install_time, + json_rule, + Rule::ConsistencyChecker(), + &error)); + EXPECT_EQ("", error); + ASSERT_TRUE(rule.get()); + + EXPECT_EQ(kExtensionId, rule->id().first); + EXPECT_EQ("rule1", rule->id().second); + + EXPECT_EQ(200, rule->priority()); + + const Rule::ConditionSet& condition_set = rule->conditions(); + const Rule::ConditionSet::Conditions& conditions = + condition_set.conditions(); + ASSERT_EQ(2u, conditions.size()); + EXPECT_EQ(3, conditions[0]->max_value); + EXPECT_EQ(5, conditions[1]->max_value); + + const Rule::ActionSet& action_set = rule->actions(); + const Rule::ActionSet::Actions& actions = action_set.actions(); + ASSERT_EQ(1u, actions.size()); + EXPECT_EQ(2, actions[0]->increment()); + + int sum = 0; + rule->Apply(&sum); + EXPECT_EQ(2, sum); +} + +bool AtLeastOneCondition( + const DeclarativeConditionSet<FulfillableCondition>* conditions, + const DeclarativeActionSet<SummingAction>* actions, + std::string* error) { + if (conditions->conditions().empty()) { + *error = "No conditions"; + return false; + } + return true; +} + +TEST(DeclarativeRuleTest, CheckConsistency) { + typedef DeclarativeRule<FulfillableCondition, SummingAction> Rule; + URLMatcher matcher; + std::string error; + linked_ptr<Rule::JsonRule> json_rule(new Rule::JsonRule); + const char kExtensionId[] = "ext1"; + scoped_refptr<Extension> extension = ExtensionBuilder() + .SetManifest(SimpleManifest()) + .SetID(kExtensionId) + .Build(); + + ASSERT_TRUE(Rule::JsonRule::Populate( + *ParseJson("{ \n" + " \"id\": \"rule1\", \n" + " \"conditions\": [ \n" + " {\"url_id\": 1, \"max\": 3}, \n" + " {\"url_id\": 2, \"max\": 5}, \n" + " ], \n" + " \"actions\": [ \n" + " { \n" + " \"value\": 2 \n" + " } \n" + " ], \n" + " \"priority\": 200 \n" + "}"), + json_rule.get())); + scoped_ptr<Rule> rule(Rule::Create(matcher.condition_factory(), + NULL, + extension.get(), + base::Time(), + json_rule, + base::Bind(AtLeastOneCondition), + &error)); + EXPECT_TRUE(rule); + EXPECT_EQ("", error); + + ASSERT_TRUE(Rule::JsonRule::Populate( + *ParseJson("{ \n" + " \"id\": \"rule1\", \n" + " \"conditions\": [ \n" + " ], \n" + " \"actions\": [ \n" + " { \n" + " \"value\": 2 \n" + " } \n" + " ], \n" + " \"priority\": 200 \n" + "}"), + json_rule.get())); + rule = Rule::Create(matcher.condition_factory(), + NULL, + extension.get(), + base::Time(), + json_rule, + base::Bind(AtLeastOneCondition), + &error); + EXPECT_FALSE(rule); + EXPECT_EQ("No conditions", error); +} + +} // namespace extensions diff --git a/extensions/browser/api/declarative/rules_cache_delegate.cc b/extensions/browser/api/declarative/rules_cache_delegate.cc new file mode 100644 index 0000000..03a4b49 --- /dev/null +++ b/extensions/browser/api/declarative/rules_cache_delegate.cc @@ -0,0 +1,230 @@ +// Copyright 2013 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 "extensions/browser/api/declarative/rules_cache_delegate.h" + +#include "content/public/browser/browser_context.h" +#include "content/public/browser/notification_details.h" +#include "content/public/browser/notification_source.h" +#include "extensions/browser/api/declarative/rules_registry.h" +#include "extensions/browser/extension_prefs.h" +#include "extensions/browser/extension_registry.h" +#include "extensions/browser/extension_system.h" +#include "extensions/browser/info_map.h" +#include "extensions/browser/state_store.h" +#include "extensions/common/permissions/permissions_data.h" + +namespace { + +// Returns the key to use for storing declarative rules in the state store. +std::string GetDeclarativeRuleStorageKey(const std::string& event_name, + bool incognito) { + if (incognito) + return "declarative_rules.incognito." + event_name; + else + return "declarative_rules." + event_name; +} + + +} // namespace + +namespace extensions { + +// RulesCacheDelegate + +const char RulesCacheDelegate::kRulesStoredKey[] = + "has_declarative_rules"; + +RulesCacheDelegate::RulesCacheDelegate(bool log_storage_init_delay) + : browser_context_(NULL), + log_storage_init_delay_(log_storage_init_delay), + notified_registry_(false), + weak_ptr_factory_(this) { +} + +RulesCacheDelegate::~RulesCacheDelegate() {} + +// Returns the key to use for storing whether the rules have been stored. +// static +std::string RulesCacheDelegate::GetRulesStoredKey(const std::string& event_name, + bool incognito) { + std::string result(kRulesStoredKey); + result += incognito ? ".incognito." : "."; + return result + event_name; +} + +// This is called from the constructor of RulesRegistry, so it is +// important that it both +// 1. calls no (in particular virtual) methods of the rules registry, and +// 2. does not create scoped_refptr holding the registry. (A short-lived +// scoped_refptr might delete the rules registry before it is constructed.) +void RulesCacheDelegate::Init(RulesRegistry* registry) { + DCHECK_CURRENTLY_ON(content::BrowserThread::UI); + + // WARNING: The first use of |registry_| will bind it to the calling thread + // so don't use this here. + registry_ = registry->GetWeakPtr(); + + browser_context_ = registry->browser_context(); + storage_key_ = + GetDeclarativeRuleStorageKey(registry->event_name(), + browser_context_->IsOffTheRecord()); + rules_stored_key_ = GetRulesStoredKey(registry->event_name(), + browser_context_->IsOffTheRecord()); + rules_registry_thread_ = registry->owner_thread(); + + ExtensionSystem& system = *ExtensionSystem::Get(browser_context_); + StateStore* store = system.rules_store(); + if (store) + store->RegisterKey(storage_key_); + + if (browser_context_->IsOffTheRecord()) + log_storage_init_delay_ = false; + + system.ready().Post( + FROM_HERE, + base::Bind(&RulesCacheDelegate::ReadRulesForInstalledExtensions, + weak_ptr_factory_.GetWeakPtr())); + system.ready().Post(FROM_HERE, + base::Bind(&RulesCacheDelegate::CheckIfReady, + weak_ptr_factory_.GetWeakPtr())); +} + +void RulesCacheDelegate::WriteToStorage(const std::string& extension_id, + scoped_ptr<base::Value> value) { + DCHECK_CURRENTLY_ON(content::BrowserThread::UI); + if (!browser_context_) + return; + + const base::ListValue* rules = NULL; + CHECK(value->GetAsList(&rules)); + bool rules_stored_previously = GetDeclarativeRulesStored(extension_id); + bool store_rules = !rules->empty(); + SetDeclarativeRulesStored(extension_id, store_rules); + if (!rules_stored_previously && !store_rules) + return; + + StateStore* store = ExtensionSystem::Get(browser_context_)->rules_store(); + if (store) + store->SetExtensionValue(extension_id, storage_key_, value.Pass()); +} + +void RulesCacheDelegate::CheckIfReady() { + DCHECK_CURRENTLY_ON(content::BrowserThread::UI); + if (notified_registry_ || !waiting_for_extensions_.empty()) + return; + + content::BrowserThread::PostTask( + rules_registry_thread_, + FROM_HERE, + base::Bind( + &RulesRegistry::MarkReady, registry_, storage_init_time_)); + notified_registry_ = true; +} + +void RulesCacheDelegate::ReadRulesForInstalledExtensions() { + bool is_ready = ExtensionSystem::Get(browser_context_)->ready().is_signaled(); + // In an OTR context, we start on top of a normal context already, so the + // extension service should be ready. + DCHECK(!browser_context_->IsOffTheRecord() || is_ready); + if (is_ready) { + const ExtensionSet& extensions = + ExtensionRegistry::Get(browser_context_)->enabled_extensions(); + const ExtensionPrefs* extension_prefs = + ExtensionPrefs::Get(browser_context_); + for (ExtensionSet::const_iterator i = extensions.begin(); + i != extensions.end(); + ++i) { + bool needs_apis_storing_rules = + (*i)->permissions_data()->HasAPIPermission( + APIPermission::kDeclarativeContent) || + (*i)->permissions_data()->HasAPIPermission( + APIPermission::kDeclarativeWebRequest); + bool respects_off_the_record = + !(browser_context_->IsOffTheRecord()) || + extension_prefs->IsIncognitoEnabled((*i)->id()); + if (needs_apis_storing_rules && respects_off_the_record) + ReadFromStorage((*i)->id()); + } + } +} + +void RulesCacheDelegate::ReadFromStorage(const std::string& extension_id) { + DCHECK_CURRENTLY_ON(content::BrowserThread::UI); + if (!browser_context_) + return; + + if (log_storage_init_delay_ && storage_init_time_.is_null()) + storage_init_time_ = base::Time::Now(); + + if (!GetDeclarativeRulesStored(extension_id)) { + ExtensionSystem::Get(browser_context_)->ready().Post( + FROM_HERE, base::Bind(&RulesCacheDelegate::CheckIfReady, + weak_ptr_factory_.GetWeakPtr())); + return; + } + + StateStore* store = ExtensionSystem::Get(browser_context_)->rules_store(); + if (!store) + return; + waiting_for_extensions_.insert(extension_id); + store->GetExtensionValue( + extension_id, + storage_key_, + base::Bind(&RulesCacheDelegate::ReadFromStorageCallback, + weak_ptr_factory_.GetWeakPtr(), + extension_id)); +} + +void RulesCacheDelegate::ReadFromStorageCallback( + const std::string& extension_id, + scoped_ptr<base::Value> value) { + DCHECK_CURRENTLY_ON(content::BrowserThread::UI); + content::BrowserThread::PostTask( + rules_registry_thread_, + FROM_HERE, + base::Bind(&RulesRegistry::DeserializeAndAddRules, + registry_, + extension_id, + base::Passed(&value))); + + waiting_for_extensions_.erase(extension_id); + + if (waiting_for_extensions_.empty()) + ExtensionSystem::Get(browser_context_)->ready().Post( + FROM_HERE, base::Bind(&RulesCacheDelegate::CheckIfReady, + weak_ptr_factory_.GetWeakPtr())); +} + +bool RulesCacheDelegate::GetDeclarativeRulesStored( + const std::string& extension_id) const { + CHECK(browser_context_); + const ExtensionScopedPrefs* extension_prefs = + ExtensionPrefs::Get(browser_context_); + + bool rules_stored = true; + if (extension_prefs->ReadPrefAsBoolean( + extension_id, rules_stored_key_, &rules_stored)) + return rules_stored; + + // Safe default -- if we don't know that the rules are not stored, we force + // a read by returning true. + return true; +} + +void RulesCacheDelegate::SetDeclarativeRulesStored( + const std::string& extension_id, + bool rules_stored) { + CHECK(browser_context_); + DCHECK(ExtensionRegistry::Get(browser_context_) + ->GetExtensionById(extension_id, ExtensionRegistry::EVERYTHING)); + + ExtensionScopedPrefs* extension_prefs = ExtensionPrefs::Get(browser_context_); + extension_prefs->UpdateExtensionPref( + extension_id, + rules_stored_key_, + new base::FundamentalValue(rules_stored)); +} + +} // namespace extensions diff --git a/extensions/browser/api/declarative/rules_cache_delegate.h b/extensions/browser/api/declarative/rules_cache_delegate.h new file mode 100644 index 0000000..560eac2 --- /dev/null +++ b/extensions/browser/api/declarative/rules_cache_delegate.h @@ -0,0 +1,112 @@ +// Copyright 2013 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 EXTENSIONS_BROWSER_API_DECLARATIVE_RULES_CACHE_DELEGATE_H__ +#define EXTENSIONS_BROWSER_API_DECLARATIVE_RULES_CACHE_DELEGATE_H__ + +#include <set> +#include <string> + +#include "base/gtest_prod_util.h" +#include "base/values.h" +#include "content/public/browser/browser_thread.h" + +namespace content { +class BrowserContext; +} + +namespace extensions { + +class RulesRegistry; + +// RulesCacheDelegate implements the part of the RulesRegistry which works on +// the UI thread. It should only be used on the UI thread. +// If |log_storage_init_delay| is set, the delay caused by loading and +// registering rules on initialization will be logged with UMA. +class RulesCacheDelegate { + public: + + explicit RulesCacheDelegate(bool log_storage_init_delay); + + virtual ~RulesCacheDelegate(); + + // Returns a key for the state store. The associated preference is a boolean + // indicating whether there are some declarative rules stored in the rule + // store. + static std::string GetRulesStoredKey(const std::string& event_name, + bool incognito); + + // Initialize the storage functionality. + void Init(RulesRegistry* registry); + + void WriteToStorage(const std::string& extension_id, + scoped_ptr<base::Value> value); + + base::WeakPtr<RulesCacheDelegate> GetWeakPtr() { + DCHECK_CURRENTLY_ON(content::BrowserThread::UI); + return weak_ptr_factory_.GetWeakPtr(); + } + + private: + FRIEND_TEST_ALL_PREFIXES(RulesRegistryWithCacheTest, + DeclarativeRulesStored); + FRIEND_TEST_ALL_PREFIXES(RulesRegistryWithCacheTest, + RulesStoredFlagMultipleRegistries); + + static const char kRulesStoredKey[]; + + // Check if we are done reading all data from storage on startup, and notify + // the RulesRegistry on its thread if so. The notification is delivered + // exactly once. + void CheckIfReady(); + + // Schedules retrieving rules for already loaded extensions where + // appropriate. + void ReadRulesForInstalledExtensions(); + + // Read/write a list of rules serialized to Values. + void ReadFromStorage(const std::string& extension_id); + void ReadFromStorageCallback(const std::string& extension_id, + scoped_ptr<base::Value> value); + + // Check the preferences whether the extension with |extension_id| has some + // rules stored on disk. If this information is not in the preferences, true + // is returned as a safe default value. + bool GetDeclarativeRulesStored(const std::string& extension_id) const; + // Modify the preference to |rules_stored|. + void SetDeclarativeRulesStored(const std::string& extension_id, + bool rules_stored); + + content::BrowserContext* browser_context_; + + // The key under which rules are stored. + std::string storage_key_; + + // The key under which we store whether the rules have been stored. + std::string rules_stored_key_; + + // A set of extension IDs that have rules we are reading from storage. + std::set<std::string> waiting_for_extensions_; + + // We measure the time spent on loading rules on init. The result is logged + // with UMA once per each RulesCacheDelegate instance, unless in Incognito. + base::Time storage_init_time_; + bool log_storage_init_delay_; + + // Weak pointer to post tasks to the owning rules registry. + base::WeakPtr<RulesRegistry> registry_; + + // The thread |registry_| lives on. + content::BrowserThread::ID rules_registry_thread_; + + // We notified the RulesRegistry that the rules are loaded. + bool notified_registry_; + + // Use this factory to generate weak pointers bound to the UI thread. + base::WeakPtrFactory<RulesCacheDelegate> weak_ptr_factory_; +}; + +} // namespace extensions + +#endif // EXTENSIONS_BROWSER_API_DECLARATIVE_RULES_CACHE_DELEGATE_H__ diff --git a/extensions/browser/api/declarative/rules_registry.cc b/extensions/browser/api/declarative/rules_registry.cc new file mode 100644 index 0000000..2c1c5d3 --- /dev/null +++ b/extensions/browser/api/declarative/rules_registry.cc @@ -0,0 +1,377 @@ +// 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 "extensions/browser/api/declarative/rules_registry.h" + +#include <utility> + +#include "base/bind.h" +#include "base/logging.h" +#include "base/message_loop/message_loop.h" +#include "base/metrics/histogram.h" +#include "base/stl_util.h" +#include "base/strings/stringprintf.h" +#include "base/time/time.h" +#include "base/values.h" +#include "content/public/browser/browser_thread.h" +#include "content/public/browser/notification_details.h" +#include "content/public/browser/notification_source.h" +#include "extensions/browser/api/declarative/rules_cache_delegate.h" +#include "extensions/browser/extension_prefs.h" +#include "extensions/browser/extension_system.h" +#include "extensions/browser/state_store.h" +#include "extensions/common/extension.h" + +namespace { + +const char kSuccess[] = ""; +const char kDuplicateRuleId[] = "Duplicate rule ID: %s"; + +scoped_ptr<base::Value> RulesToValue( + const std::vector<linked_ptr<extensions::RulesRegistry::Rule> >& rules) { + scoped_ptr<base::ListValue> list(new base::ListValue()); + for (size_t i = 0; i < rules.size(); ++i) + list->Append(rules[i]->ToValue().release()); + return list.PassAs<base::Value>(); +} + +std::vector<linked_ptr<extensions::RulesRegistry::Rule> > RulesFromValue( + const base::Value* value) { + std::vector<linked_ptr<extensions::RulesRegistry::Rule> > rules; + + const base::ListValue* list = NULL; + if (!value || !value->GetAsList(&list)) + return rules; + + rules.reserve(list->GetSize()); + for (size_t i = 0; i < list->GetSize(); ++i) { + const base::DictionaryValue* dict = NULL; + if (!list->GetDictionary(i, &dict)) + continue; + linked_ptr<extensions::RulesRegistry::Rule> rule( + new extensions::RulesRegistry::Rule()); + if (extensions::RulesRegistry::Rule::Populate(*dict, rule.get())) + rules.push_back(rule); + } + + return rules; +} + +std::string ToId(int identifier) { + return base::StringPrintf("_%d_", identifier); +} + +} // namespace + + +namespace extensions { + +// RulesRegistry + +RulesRegistry::RulesRegistry(content::BrowserContext* browser_context, + const std::string& event_name, + content::BrowserThread::ID owner_thread, + RulesCacheDelegate* cache_delegate, + const WebViewKey& webview_key) + : browser_context_(browser_context), + owner_thread_(owner_thread), + event_name_(event_name), + webview_key_(webview_key), + ready_(/*signaled=*/!cache_delegate), // Immediately ready if no cache + // delegate to wait for. + weak_ptr_factory_(browser_context_ ? this : NULL), + last_generated_rule_identifier_id_(0) { + if (cache_delegate) { + cache_delegate_ = cache_delegate->GetWeakPtr(); + cache_delegate->Init(this); + } +} + +std::string RulesRegistry::AddRulesNoFill( + const std::string& extension_id, + const std::vector<linked_ptr<Rule> >& rules) { + DCHECK_CURRENTLY_ON(owner_thread()); + + // Verify that all rule IDs are new. + for (std::vector<linked_ptr<Rule> >::const_iterator i = + rules.begin(); i != rules.end(); ++i) { + const RuleId& rule_id = *((*i)->id); + // Every rule should have a priority assigned. + DCHECK((*i)->priority); + RulesDictionaryKey key(extension_id, rule_id); + if (rules_.find(key) != rules_.end()) + return base::StringPrintf(kDuplicateRuleId, rule_id.c_str()); + } + + std::string error = AddRulesImpl(extension_id, rules); + + if (!error.empty()) + return error; + + // Commit all rules into |rules_| on success. + for (std::vector<linked_ptr<Rule> >::const_iterator i = + rules.begin(); i != rules.end(); ++i) { + const RuleId& rule_id = *((*i)->id); + RulesDictionaryKey key(extension_id, rule_id); + rules_[key] = *i; + } + + MaybeProcessChangedRules(extension_id); + return kSuccess; +} + +std::string RulesRegistry::AddRules( + const std::string& extension_id, + const std::vector<linked_ptr<Rule> >& rules) { + DCHECK_CURRENTLY_ON(owner_thread()); + + std::string error = CheckAndFillInOptionalRules(extension_id, rules); + if (!error.empty()) + return error; + FillInOptionalPriorities(rules); + + return AddRulesNoFill(extension_id, rules); +} + +std::string RulesRegistry::RemoveRules( + const std::string& extension_id, + const std::vector<std::string>& rule_identifiers) { + DCHECK_CURRENTLY_ON(owner_thread()); + + std::string error = RemoveRulesImpl(extension_id, rule_identifiers); + + if (!error.empty()) + return error; + + for (std::vector<std::string>::const_iterator i = rule_identifiers.begin(); + i != rule_identifiers.end(); + ++i) { + RulesDictionaryKey lookup_key(extension_id, *i); + rules_.erase(lookup_key); + } + + MaybeProcessChangedRules(extension_id); + RemoveUsedRuleIdentifiers(extension_id, rule_identifiers); + return kSuccess; +} + +std::string RulesRegistry::RemoveAllRules(const std::string& extension_id) { + std::string result = RulesRegistry::RemoveAllRulesNoStoreUpdate(extension_id); + MaybeProcessChangedRules(extension_id); // Now update the prefs and store. + return result; +} + +std::string RulesRegistry::RemoveAllRulesNoStoreUpdate( + const std::string& extension_id) { + DCHECK_CURRENTLY_ON(owner_thread()); + + std::string error = RemoveAllRulesImpl(extension_id); + + if (!error.empty()) + return error; + + for (RulesDictionary::const_iterator i = rules_.begin(); + i != rules_.end();) { + const RulesDictionaryKey& key = i->first; + ++i; + if (key.first == extension_id) + rules_.erase(key); + } + + RemoveAllUsedRuleIdentifiers(extension_id); + return kSuccess; +} + +void RulesRegistry::GetRules(const std::string& extension_id, + const std::vector<std::string>& rule_identifiers, + std::vector<linked_ptr<Rule> >* out) { + DCHECK_CURRENTLY_ON(owner_thread()); + + for (std::vector<std::string>::const_iterator i = rule_identifiers.begin(); + i != rule_identifiers.end(); ++i) { + RulesDictionaryKey lookup_key(extension_id, *i); + RulesDictionary::iterator entry = rules_.find(lookup_key); + if (entry != rules_.end()) + out->push_back(entry->second); + } +} + +void RulesRegistry::GetAllRules(const std::string& extension_id, + std::vector<linked_ptr<Rule> >* out) { + DCHECK_CURRENTLY_ON(owner_thread()); + + for (RulesDictionary::const_iterator i = rules_.begin(); + i != rules_.end(); ++i) { + const RulesDictionaryKey& key = i->first; + if (key.first == extension_id) + out->push_back(i->second); + } +} + +void RulesRegistry::OnExtensionUnloaded(const std::string& extension_id) { + DCHECK_CURRENTLY_ON(owner_thread()); + std::string error = RemoveAllRulesImpl(extension_id); + if (!error.empty()) + LOG(ERROR) << error; +} + +void RulesRegistry::OnExtensionUninstalled(const std::string& extension_id) { + DCHECK_CURRENTLY_ON(owner_thread()); + std::string error = RemoveAllRulesNoStoreUpdate(extension_id); + if (!error.empty()) + LOG(ERROR) << error; +} + +void RulesRegistry::OnExtensionLoaded(const std::string& extension_id) { + DCHECK_CURRENTLY_ON(owner_thread()); + std::vector<linked_ptr<Rule> > rules; + GetAllRules(extension_id, &rules); + std::string error = AddRulesImpl(extension_id, rules); + if (!error.empty()) + LOG(ERROR) << error; +} + +size_t RulesRegistry::GetNumberOfUsedRuleIdentifiersForTesting() const { + size_t entry_count = 0u; + for (RuleIdentifiersMap::const_iterator extension = + used_rule_identifiers_.begin(); + extension != used_rule_identifiers_.end(); + ++extension) { + // Each extension is counted as 1 just for being there. Otherwise we miss + // keys with empty values. + entry_count += 1u + extension->second.size(); + } + return entry_count; +} + +void RulesRegistry::DeserializeAndAddRules( + const std::string& extension_id, + scoped_ptr<base::Value> rules) { + DCHECK_CURRENTLY_ON(owner_thread()); + + AddRulesNoFill(extension_id, RulesFromValue(rules.get())); +} + +RulesRegistry::~RulesRegistry() { +} + +void RulesRegistry::MarkReady(base::Time storage_init_time) { + DCHECK_CURRENTLY_ON(owner_thread()); + + if (!storage_init_time.is_null()) { + UMA_HISTOGRAM_TIMES("Extensions.DeclarativeRulesStorageInitialization", + base::Time::Now() - storage_init_time); + } + + ready_.Signal(); +} + +void RulesRegistry::ProcessChangedRules(const std::string& extension_id) { + DCHECK_CURRENTLY_ON(owner_thread()); + + DCHECK(ContainsKey(process_changed_rules_requested_, extension_id)); + process_changed_rules_requested_[extension_id] = NOT_SCHEDULED_FOR_PROCESSING; + + std::vector<linked_ptr<Rule> > new_rules; + GetAllRules(extension_id, &new_rules); + content::BrowserThread::PostTask( + content::BrowserThread::UI, + FROM_HERE, + base::Bind(&RulesCacheDelegate::WriteToStorage, + cache_delegate_, + extension_id, + base::Passed(RulesToValue(new_rules)))); +} + +void RulesRegistry::MaybeProcessChangedRules(const std::string& extension_id) { + // Read and initialize |process_changed_rules_requested_[extension_id]| if + // necessary. (Note that the insertion below will not overwrite + // |process_changed_rules_requested_[extension_id]| if that already exists. + std::pair<ProcessStateMap::iterator, bool> insertion = + process_changed_rules_requested_.insert(std::make_pair( + extension_id, + browser_context_ ? NOT_SCHEDULED_FOR_PROCESSING : NEVER_PROCESS)); + if (insertion.first->second != NOT_SCHEDULED_FOR_PROCESSING) + return; + + process_changed_rules_requested_[extension_id] = SCHEDULED_FOR_PROCESSING; + ready_.Post(FROM_HERE, + base::Bind(&RulesRegistry::ProcessChangedRules, + weak_ptr_factory_.GetWeakPtr(), + extension_id)); +} + +bool RulesRegistry::IsUniqueId(const std::string& extension_id, + const std::string& rule_id) const { + RuleIdentifiersMap::const_iterator identifiers = + used_rule_identifiers_.find(extension_id); + if (identifiers == used_rule_identifiers_.end()) + return true; + return identifiers->second.find(rule_id) == identifiers->second.end(); +} + +std::string RulesRegistry::GenerateUniqueId(const std::string& extension_id) { + while (!IsUniqueId(extension_id, ToId(last_generated_rule_identifier_id_))) + ++last_generated_rule_identifier_id_; + return ToId(last_generated_rule_identifier_id_); +} + +std::string RulesRegistry::CheckAndFillInOptionalRules( + const std::string& extension_id, + const std::vector<linked_ptr<Rule> >& rules) { + // IDs we have inserted, in case we need to rollback this operation. + std::vector<std::string> rollback_log; + + // First we insert all rules with existing identifier, so that generated + // identifiers cannot collide with identifiers passed by the caller. + for (std::vector<linked_ptr<Rule> >::const_iterator i = rules.begin(); + i != rules.end(); + ++i) { + Rule* rule = i->get(); + if (rule->id.get()) { + std::string id = *(rule->id); + if (!IsUniqueId(extension_id, id)) { + RemoveUsedRuleIdentifiers(extension_id, rollback_log); + return "Id " + id + " was used multiple times."; + } + used_rule_identifiers_[extension_id].insert(id); + } + } + // Now we generate IDs in case they were not specified in the rules. This + // cannot fail so we do not need to keep track of a rollback log. + for (std::vector<linked_ptr<Rule> >::const_iterator i = rules.begin(); + i != rules.end(); + ++i) { + Rule* rule = i->get(); + if (!rule->id.get()) { + rule->id.reset(new std::string(GenerateUniqueId(extension_id))); + used_rule_identifiers_[extension_id].insert(*(rule->id)); + } + } + return std::string(); +} + +void RulesRegistry::FillInOptionalPriorities( + const std::vector<linked_ptr<Rule> >& rules) { + std::vector<linked_ptr<Rule> >::const_iterator i; + for (i = rules.begin(); i != rules.end(); ++i) { + if (!(*i)->priority.get()) + (*i)->priority.reset(new int(DEFAULT_PRIORITY)); + } +} + +void RulesRegistry::RemoveUsedRuleIdentifiers( + const std::string& extension_id, + const std::vector<std::string>& identifiers) { + std::vector<std::string>::const_iterator i; + for (i = identifiers.begin(); i != identifiers.end(); ++i) + used_rule_identifiers_[extension_id].erase(*i); +} + +void RulesRegistry::RemoveAllUsedRuleIdentifiers( + const std::string& extension_id) { + used_rule_identifiers_.erase(extension_id); +} + +} // namespace extensions diff --git a/extensions/browser/api/declarative/rules_registry.h b/extensions/browser/api/declarative/rules_registry.h new file mode 100644 index 0000000..fa0e745 --- /dev/null +++ b/extensions/browser/api/declarative/rules_registry.h @@ -0,0 +1,296 @@ +// 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 EXTENSIONS_BROWSER_API_DECLARATIVE_RULES_REGISTRY_H__ +#define EXTENSIONS_BROWSER_API_DECLARATIVE_RULES_REGISTRY_H__ + +#include <map> +#include <set> +#include <string> +#include <vector> + +#include "base/callback_forward.h" +#include "base/compiler_specific.h" +#include "base/gtest_prod_util.h" +#include "base/memory/scoped_ptr.h" +#include "base/memory/weak_ptr.h" +#include "content/public/browser/browser_thread.h" +#include "content/public/browser/notification_observer.h" +#include "content/public/browser/notification_registrar.h" +#include "extensions/common/api/events.h" +#include "extensions/common/one_shot_event.h" + +namespace content { +class BrowserContext; +} + +namespace base { +class Value; +} // namespace base + +namespace extensions { + +class RulesCacheDelegate; + +// A base class for RulesRegistries that takes care of storing the +// RulesRegistry::Rule objects. It contains all the methods that need to run on +// the registry thread; methods that need to run on the UI thread are separated +// in the RulesCacheDelegate object. +class RulesRegistry : public base::RefCountedThreadSafe<RulesRegistry> { + public: + typedef extensions::core_api::events::Rule Rule; + struct WebViewKey { + int embedder_process_id; + int webview_instance_id; + WebViewKey(int embedder_process_id, int webview_instance_id) + : embedder_process_id(embedder_process_id), + webview_instance_id(webview_instance_id) {} + bool operator<(const WebViewKey& other) const { + return embedder_process_id < other.embedder_process_id || + ((embedder_process_id == other.embedder_process_id) && + (webview_instance_id < other.webview_instance_id)); + } + }; + + enum Defaults { DEFAULT_PRIORITY = 100 }; + // After the RulesCacheDelegate object (the part of the registry which runs on + // the UI thread) is created, a pointer to it is passed to |*ui_part|. + // In tests, |browser_context| and |ui_part| can be NULL (at the same time). + // In that case the storage functionality disabled (no RulesCacheDelegate + // object created). + RulesRegistry(content::BrowserContext* browser_context, + const std::string& event_name, + content::BrowserThread::ID owner_thread, + RulesCacheDelegate* cache_delegate, + const WebViewKey& webview_key); + + const OneShotEvent& ready() const { + return ready_; + } + + // RulesRegistry implementation: + + // Registers |rules|, owned by |extension_id| to this RulesRegistry. + // If a concrete RuleRegistry does not support some of the rules, + // it may ignore them. + // + // |rules| is a list of Rule instances following the definition of the + // declarative extension APIs. It is guaranteed that each rule in |rules| has + // a unique name within the scope of |extension_id| that has not been + // registered before, unless it has been removed again. + // The ownership of rules remains with the caller. + // + // Returns an empty string if the function is successful or an error + // message otherwise. + // + // IMPORTANT: This function is atomic. Either all rules that are deemed + // relevant are added or none. + std::string AddRules( + const std::string& extension_id, + const std::vector<linked_ptr<RulesRegistry::Rule> >& rules); + + // Unregisters all rules listed in |rule_identifiers| and owned by + // |extension_id| from this RulesRegistry. + // Some or all IDs in |rule_identifiers| may not be stored in this + // RulesRegistry and are ignored. + // + // Returns an empty string if the function is successful or an error + // message otherwise. + // + // IMPORTANT: This function is atomic. Either all rules that are deemed + // relevant are removed or none. + std::string RemoveRules( + const std::string& extension_id, + const std::vector<std::string>& rule_identifiers); + + // Same as RemoveAllRules but acts on all rules owned by |extension_id|. + std::string RemoveAllRules(const std::string& extension_id); + + // Returns all rules listed in |rule_identifiers| and owned by |extension_id| + // registered in this RuleRegistry. Entries in |rule_identifiers| that + // are unknown are ignored. + // + // The returned rules are stored in |out|. Ownership is passed to the caller. + void GetRules(const std::string& extension_id, + const std::vector<std::string>& rule_identifiers, + std::vector<linked_ptr<RulesRegistry::Rule> >* out); + + // Same as GetRules but returns all rules owned by |extension_id|. + void GetAllRules(const std::string& extension_id, + std::vector<linked_ptr<RulesRegistry::Rule> >* out); + + // Called to notify the RulesRegistry that the extension availability has + // changed, so that the registry can update which rules are active. + void OnExtensionUnloaded(const std::string& extension_id); + void OnExtensionUninstalled(const std::string& extension_id); + void OnExtensionLoaded(const std::string& extension_id); + + // Returns the number of entries in used_rule_identifiers_ for leak detection. + // Every ExtensionId counts as one entry, even if it contains no rules. + size_t GetNumberOfUsedRuleIdentifiersForTesting() const; + + // Returns the RulesCacheDelegate. This is used for testing. + RulesCacheDelegate* rules_cache_delegate_for_testing() const { + return cache_delegate_.get(); + } + + // Returns the context where the rules registry lives. + content::BrowserContext* browser_context() { return browser_context_; } + + // Returns the ID of the thread on which the rules registry lives. + // It is safe to call this function from any thread. + content::BrowserThread::ID owner_thread() const { return owner_thread_; } + + // The name of the event with which rules are registered. + const std::string& event_name() const { return event_name_; } + + // The key that identifies the webview (or tabs) in which these rules apply. + // If the rules apply to the main browser, then this returns the tuple (0, 0). + const WebViewKey& webview_key() const { + return webview_key_; + } + + protected: + virtual ~RulesRegistry(); + + // The precondition for calling this method is that all rules have unique IDs. + // AddRules establishes this precondition and calls into this method. + // Stored rules already meet this precondition and so they avoid calling + // CheckAndFillInOptionalRules for improved performance. + // + // Returns an empty string if the function is successful or an error + // message otherwise. + std::string AddRulesNoFill( + const std::string& extension_id, + const std::vector<linked_ptr<RulesRegistry::Rule> >& rules); + + // These functions need to apply the rules to the browser, while the base + // class will handle defaulting empty fields before calling *Impl, and will + // automatically cache the rules and re-call *Impl on browser startup. + virtual std::string AddRulesImpl( + const std::string& extension_id, + const std::vector<linked_ptr<RulesRegistry::Rule> >& rules) = 0; + virtual std::string RemoveRulesImpl( + const std::string& extension_id, + const std::vector<std::string>& rule_identifiers) = 0; + virtual std::string RemoveAllRulesImpl( + const std::string& extension_id) = 0; + + private: + friend class base::RefCountedThreadSafe<RulesRegistry>; + friend class RulesCacheDelegate; + + typedef std::string ExtensionId; + typedef std::string RuleId; + typedef std::pair<ExtensionId, RuleId> RulesDictionaryKey; + typedef std::map<RulesDictionaryKey, linked_ptr<RulesRegistry::Rule> > + RulesDictionary; + enum ProcessChangedRulesState { + // ProcessChangedRules can never be called, |cache_delegate_| is NULL. + NEVER_PROCESS, + // A task to call ProcessChangedRules is scheduled for future execution. + SCHEDULED_FOR_PROCESSING, + // No task to call ProcessChangedRules is scheduled yet, but it is possible + // to schedule one. + NOT_SCHEDULED_FOR_PROCESSING + }; + typedef std::map<ExtensionId, ProcessChangedRulesState> ProcessStateMap; + + base::WeakPtr<RulesRegistry> GetWeakPtr() { + DCHECK_CURRENTLY_ON(content::BrowserThread::UI); + return weak_ptr_factory_.GetWeakPtr(); + } + + // Common processing after extension's rules have changed. + void ProcessChangedRules(const std::string& extension_id); + + // Calls ProcessChangedRules if + // |process_changed_rules_requested_(extension_id)| == + // NOT_SCHEDULED_FOR_PROCESSING. + void MaybeProcessChangedRules(const std::string& extension_id); + + // This method implements the functionality of RemoveAllRules, except for not + // calling MaybeProcessChangedRules. That way updating the rules store and + // extension prefs is avoided. This method is called when an extension is + // uninstalled, that way there is no clash with the preferences being wiped. + std::string RemoveAllRulesNoStoreUpdate(const std::string& extension_id); + + void MarkReady(base::Time storage_init_time); + + // Deserialize the rules from the given Value object and add them to the + // RulesRegistry. + void DeserializeAndAddRules(const std::string& extension_id, + scoped_ptr<base::Value> rules); + + // The context to which this rules registry belongs. + content::BrowserContext* browser_context_; + + // The ID of the thread on which the rules registry lives. + const content::BrowserThread::ID owner_thread_; + + // The name of the event with which rules are registered. + const std::string event_name_; + + // The key that identifies the context in which these rules apply. + WebViewKey webview_key_; + + RulesDictionary rules_; + + // Signaled when we have finished reading from storage for all extensions that + // are loaded on startup. + OneShotEvent ready_; + + // The factory needs to be declared before |cache_delegate_|, so that it can + // produce a pointer as a construction argument for |cache_delegate_|. + base::WeakPtrFactory<RulesRegistry> weak_ptr_factory_; + + // |cache_delegate_| is owned by the registry service. If |cache_delegate_| is + // NULL, then the storage functionality is disabled (this is used in tests). + // This registry cannot own |cache_delegate_| because during the time after + // rules registry service shuts down on UI thread, and the registry is + // destroyed on its thread, the use of the |cache_delegate_| would not be + // safe. The registry only ever associates with one RulesCacheDelegate + // instance. + base::WeakPtr<RulesCacheDelegate> cache_delegate_; + + ProcessStateMap process_changed_rules_requested_; + + // Returns whether any existing rule is registered with identifier |rule_id| + // for extension |extension_id|. + bool IsUniqueId(const std::string& extension_id, + const std::string& rule_id) const; + + // Creates an ID that is unique within the scope of|extension_id|. + std::string GenerateUniqueId(const std::string& extension_id); + + // Verifies that all |rules| have unique IDs or initializes them with + // unique IDs if they don't have one. In case of duplicate IDs, this function + // returns a non-empty error message. + std::string CheckAndFillInOptionalRules( + const std::string& extension_id, + const std::vector<linked_ptr<RulesRegistry::Rule> >& rules); + + // Initializes the priority fields in case they have not been set. + void FillInOptionalPriorities( + const std::vector<linked_ptr<RulesRegistry::Rule> >& rules); + + // Removes all |identifiers| of |extension_id| from |used_rule_identifiers_|. + void RemoveUsedRuleIdentifiers(const std::string& extension_id, + const std::vector<std::string>& identifiers); + + // Same as RemoveUsedRuleIdentifiers but operates on all rules of + // |extension_id|. + void RemoveAllUsedRuleIdentifiers(const std::string& extension_id); + + typedef std::string RuleIdentifier; + typedef std::map<ExtensionId, std::set<RuleIdentifier> > RuleIdentifiersMap; + RuleIdentifiersMap used_rule_identifiers_; + int last_generated_rule_identifier_id_; + + DISALLOW_COPY_AND_ASSIGN(RulesRegistry); +}; + +} // namespace extensions + +#endif // EXTENSIONS_BROWSER_API_DECLARATIVE_RULES_REGISTRY_H__ diff --git a/extensions/browser/api/declarative/test_rules_registry.cc b/extensions/browser/api/declarative/test_rules_registry.cc new file mode 100644 index 0000000..030a22a --- /dev/null +++ b/extensions/browser/api/declarative/test_rules_registry.cc @@ -0,0 +1,55 @@ +// 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 <string> + +#include "extensions/browser/api/declarative/test_rules_registry.h" + +namespace extensions { + +TestRulesRegistry::TestRulesRegistry(content::BrowserThread::ID owner_thread, + const std::string& event_name, + const WebViewKey& webview_key) + : RulesRegistry(NULL /*profile*/, + event_name, + owner_thread, + NULL, + webview_key) {} + +TestRulesRegistry::TestRulesRegistry( + content::BrowserContext* browser_context, + const std::string& event_name, + content::BrowserThread::ID owner_thread, + RulesCacheDelegate* cache_delegate, + const WebViewKey& webview_key) + : RulesRegistry(browser_context, + event_name, + owner_thread, + cache_delegate, + webview_key) {} + +std::string TestRulesRegistry::AddRulesImpl( + const std::string& extension_id, + const std::vector<linked_ptr<RulesRegistry::Rule> >& rules) { + return result_; +} + +std::string TestRulesRegistry::RemoveRulesImpl( + const std::string& extension_id, + const std::vector<std::string>& rule_identifiers) { + return result_; +} + +std::string TestRulesRegistry::RemoveAllRulesImpl( + const std::string& extension_id) { + return result_; +} + +void TestRulesRegistry::SetResult(const std::string& result) { + result_ = result; +} + +TestRulesRegistry::~TestRulesRegistry() {} + +} // namespace extensions diff --git a/extensions/browser/api/declarative/test_rules_registry.h b/extensions/browser/api/declarative/test_rules_registry.h new file mode 100644 index 0000000..ebeaf52 --- /dev/null +++ b/extensions/browser/api/declarative/test_rules_registry.h @@ -0,0 +1,52 @@ +// 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 EXTENSIONS_BROWSER_API_DECLARATIVE_TEST_RULES_REGISTRY_H__ +#define EXTENSIONS_BROWSER_API_DECLARATIVE_TEST_RULES_REGISTRY_H__ + +#include "extensions/browser/api/declarative/rules_registry.h" + +namespace extensions { + +// This is a trivial test RulesRegistry that can only store and retrieve rules. +class TestRulesRegistry : public RulesRegistry { + public: + TestRulesRegistry(content::BrowserThread::ID owner_thread, + const std::string& event_name, + const WebViewKey& webview_key); + TestRulesRegistry( + content::BrowserContext* browser_context, + const std::string& event_name, + content::BrowserThread::ID owner_thread, + RulesCacheDelegate* cache_delegate, + const WebViewKey& webview_key); + + // RulesRegistry implementation: + virtual std::string AddRulesImpl( + const std::string& extension_id, + const std::vector<linked_ptr<RulesRegistry::Rule> >& rules) OVERRIDE; + virtual std::string RemoveRulesImpl( + const std::string& extension_id, + const std::vector<std::string>& rule_identifiers) OVERRIDE; + virtual std::string RemoveAllRulesImpl( + const std::string& extension_id) OVERRIDE; + + // Sets the result message that will be returned by the next call of + // AddRulesImpl, RemoveRulesImpl and RemoveAllRulesImpl. + void SetResult(const std::string& result); + + protected: + virtual ~TestRulesRegistry(); + + private: + // The string that gets returned by the implementation functions of + // RulesRegistry. Defaults to "". + std::string result_; + + DISALLOW_COPY_AND_ASSIGN(TestRulesRegistry); +}; + +} // namespace extensions + +#endif // EXTENSIONS_BROWSER_API_DECLARATIVE_TEST_RULES_REGISTRY_H__ diff --git a/extensions/browser/api/extensions_api_client.cc b/extensions/browser/api/extensions_api_client.cc index 0fb3a79..2f809ef 100644 --- a/extensions/browser/api/extensions_api_client.cc +++ b/extensions/browser/api/extensions_api_client.cc @@ -48,4 +48,11 @@ WebViewPermissionHelperDelegate* ExtensionsAPIClient:: return NULL; } +scoped_refptr<RulesRegistry> ExtensionsAPIClient::GetRulesRegistry( + content::BrowserContext* browser_context, + const RulesRegistry::WebViewKey& webview_key, + const std::string& event_name) { + return scoped_refptr<RulesRegistry>(); +} + } // namespace extensions diff --git a/extensions/browser/api/extensions_api_client.h b/extensions/browser/api/extensions_api_client.h index 5cd3cee..62f187d 100644 --- a/extensions/browser/api/extensions_api_client.h +++ b/extensions/browser/api/extensions_api_client.h @@ -9,6 +9,7 @@ #include "base/memory/ref_counted.h" #include "base/memory/scoped_ptr.h" +#include "extensions/browser/api/declarative/rules_registry.h" #include "extensions/browser/api/storage/settings_namespace.h" class GURL; @@ -77,6 +78,13 @@ class ExtensionsAPIClient { virtual void RegisterGuestViewTypes() {} + // TODO(wjmaclean): Remove this as soon as rules_registry_service.* moves to + // extensions/browser/api/declarative/. + virtual scoped_refptr<RulesRegistry> GetRulesRegistry( + content::BrowserContext* browser_context, + const RulesRegistry::WebViewKey& webview_key, + const std::string& event_name); + // NOTE: If this interface gains too many methods (perhaps more than 20) it // should be split into one interface per API. }; diff --git a/extensions/common/api/events.json b/extensions/common/api/events.json new file mode 100644 index 0000000..e62ea09 --- /dev/null +++ b/extensions/common/api/events.json @@ -0,0 +1,335 @@ +// 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. + +[ + { + "namespace": "events", + "description": "The <code>chrome.events</code> namespace contains common types used by APIs dispatching events to notify you when something interesting happens.", + "compiler_options": { + "implemented_in": "extensions/browser/api/declarative/declarative_api.h" + }, + "types": [ + { + "id": "Rule", + "type": "object", + "description": "Description of a declarative rule for handling events.", + "properties": { + "id": { + "type": "string", + "optional": true, + "description": "Optional identifier that allows referencing this rule." + }, + "tags": { + "type": "array", + "items": {"type": "string"}, + "optional": true, + "description": "Tags can be used to annotate rules and perform operations on sets of rules." + }, + "conditions": { + "type": "array", + "items": {"type": "any"}, + "description": "List of conditions that can trigger the actions." + }, + "actions": { + "type": "array", + "items": {"type": "any"}, + "description": "List of actions that are triggered if one of the condtions is fulfilled." + }, + "priority": { + "type": "integer", + "optional": true, + "description": "Optional priority of this rule. Defaults to 100." + } + } + }, + { + "id": "Event", + "type": "object", + "description": "An object which allows the addition and removal of listeners for a Chrome event.", + "additionalProperties": { "type": "any"}, + "functions": [ + { + "name": "addListener", + "nocompile": true, + "type": "function", + "description": "Registers an event listener <em>callback</em> to an event.", + "parameters": [ + { + "name": "callback", + "type": "function", + "description": "Called when an event occurs. The parameters of this function depend on the type of event." + } + ] + }, + { + "name": "removeListener", + "nocompile": true, + "type": "function", + "description": "Deregisters an event listener <em>callback</em> from an event.", + "parameters": [ + { + "name": "callback", + "type": "function", + "description": "Listener that shall be unregistered." + } + ] + }, + { + "name": "hasListener", + "nocompile": true, + "type": "function", + "parameters": [ + { + "name": "callback", + "type": "function", + "description": "Listener whose registration status shall be tested." + } + ], + "returns": { + "type": "boolean", + "description": "True if <em>callback</em> is registered to the event." + } + }, + { + "name": "hasListeners", + "nocompile": true, + "type": "function", + "parameters": [], + "returns": { + "type": "boolean", + "description": "True if any event listeners are registered to the event." + } + }, + { + "name": "addRules", + "type": "function", + "description": "Registers rules to handle events.", + "parameters": [ + { + "nodoc": "true", + "name": "eventName", + "type": "string", + "description": "Name of the event this function affects." + }, + { + "name": "webViewInstanceId", + "type": "integer", + "nodoc": true, + "description": "If provided, this is an integer that uniquely identfies the <webview> associated with this function call." + }, + { + "name": "rules", + "type": "array", + "items": {"$ref": "Rule"}, + "description": "Rules to be registered. These do not replace previously registered rules." + }, + { + "name": "callback", + "optional": true, + "type": "function", + "parameters": [ + { + "name": "rules", + "type": "array", + "items": {"$ref": "Rule"}, + "description": "Rules that were registered, the optional parameters are filled with values." + } + ], + "description": "Called with registered rules." + } + ] + }, + { + "name": "getRules", + "type": "function", + "description": "Returns currently registered rules.", + "parameters": [ + { + "nodoc": "true", + "name": "eventName", + "type": "string", + "description": "Name of the event this function affects." + }, + { + "name": "webViewInstanceId", + "type": "integer", + "nodoc": true, + "description": "If provided, this is an integer that uniquely identfies the <webview> associated with this function call." + }, + { + "name": "ruleIdentifiers", + "optional": true, + "type": "array", + "items": {"type": "string"}, + "description": "If an array is passed, only rules with identifiers contained in this array are returned." + }, + { + "name": "callback", + "type": "function", + "parameters": [ + { + "name": "rules", + "type": "array", + "items": {"$ref": "Rule"}, + "description": "Rules that were registered, the optional parameters are filled with values." + } + ], + "description": "Called with registered rules." + } + ] + }, + { + "name": "removeRules", + "type": "function", + "description": "Unregisters currently registered rules.", + "parameters": [ + { + "nodoc": "true", + "name": "eventName", + "type": "string", + "description": "Name of the event this function affects." + }, + { + "name": "webViewInstanceId", + "type": "integer", + "nodoc": true, + "description": "If provided, this is an integer that uniquely identfies the <webview> associated with this function call." + }, + { + "name": "ruleIdentifiers", + "optional": true, + "type": "array", + "items": {"type": "string"}, + "description": "If an array is passed, only rules with identifiers contained in this array are unregistered." + }, + { + "name": "callback", + "optional": true, + "type": "function", + "parameters": [], + "description": "Called when rules were unregistered." + } + ] + } + ] + }, + { + "id": "UrlFilter", + "type": "object", + "description": "Filters URLs for various criteria. See <a href='#filtered'>event filtering</a>. All criteria are case sensitive.", + "nocompile": true, + "properties": { + "hostContains": { + "type": "string", + "description": "Matches if the host name of the URL contains a specified string. To test whether a host name component has a prefix 'foo', use hostContains: '.foo'. This matches 'www.foobar.com' and 'foo.com', because an implicit dot is added at the beginning of the host name. Similarly, hostContains can be used to match against component suffix ('foo.') and to exactly match against components ('.foo.'). Suffix- and exact-matching for the last components need to be done separately using hostSuffix, because no implicit dot is added at the end of the host name.", + "optional": true + }, + "hostEquals": { + "type": "string", + "description": "Matches if the host name of the URL is equal to a specified string.", + "optional": true + }, + "hostPrefix": { + "type": "string", + "description": "Matches if the host name of the URL starts with a specified string.", + "optional": true + }, + "hostSuffix": { + "type": "string", + "description": "Matches if the host name of the URL ends with a specified string.", + "optional": true + }, + "pathContains": { + "type": "string", + "description": "Matches if the path segment of the URL contains a specified string.", + "optional": true + }, + "pathEquals": { + "type": "string", + "description": "Matches if the path segment of the URL is equal to a specified string.", + "optional": true + }, + "pathPrefix": { + "type": "string", + "description": "Matches if the path segment of the URL starts with a specified string.", + "optional": true + }, + "pathSuffix": { + "type": "string", + "description": "Matches if the path segment of the URL ends with a specified string.", + "optional": true + }, + "queryContains": { + "type": "string", + "description": "Matches if the query segment of the URL contains a specified string.", + "optional": true + }, + "queryEquals": { + "type": "string", + "description": "Matches if the query segment of the URL is equal to a specified string.", + "optional": true + }, + "queryPrefix": { + "type": "string", + "description": "Matches if the query segment of the URL starts with a specified string.", + "optional": true + }, + "querySuffix": { + "type": "string", + "description": "Matches if the query segment of the URL ends with a specified string.", + "optional": true + }, + "urlContains": { + "type": "string", + "description": "Matches if the URL (without fragment identifier) contains a specified string. Port numbers are stripped from the URL if they match the default port number.", + "optional": true + }, + "urlEquals": { + "type": "string", + "description": "Matches if the URL (without fragment identifier) is equal to a specified string. Port numbers are stripped from the URL if they match the default port number.", + "optional": true + }, + "urlMatches": { + "type": "string", + "description": "Matches if the URL (without fragment identifier) matches a specified regular expression. Port numbers are stripped from the URL if they match the default port number. The regular expressions use the <a href=\"http://code.google.com/p/re2/wiki/Syntax\">RE2 syntax</a>.", + "optional": true + }, + "originAndPathMatches": { + "type": "string", + "description": "Matches if the URL without query segment and fragment identifier matches a specified regular expression. Port numbers are stripped from the URL if they match the default port number. The regular expressions use the <a href=\"http://code.google.com/p/re2/wiki/Syntax\">RE2 syntax</a>.", + "optional": true + }, + "urlPrefix": { + "type": "string", + "description": "Matches if the URL (without fragment identifier) starts with a specified string. Port numbers are stripped from the URL if they match the default port number.", + "optional": true + }, + "urlSuffix": { + "type": "string", + "description": "Matches if the URL (without fragment identifier) ends with a specified string. Port numbers are stripped from the URL if they match the default port number.", + "optional": true + }, + "schemes": { + "type": "array", + "description": "Matches if the scheme of the URL is equal to any of the schemes specified in the array.", + "optional": true, + "items": { "type": "string" } + }, + "ports": { + "type": "array", + "description": "Matches if the port of the URL is contained in any of the specified port lists. For example <code>[80, 443, [1000, 1200]]</code> matches all requests on port 80, 443 and in the range 1000-1200.", + "optional": true, + "items": { + "choices": [ + {"type": "integer", "description": "A specific port."}, + {"type": "array", "items": {"type": "integer"}, "description": "A pair of integers identiying the start and end (both inclusive) of a port range."} + ] + } + } + } + } + ] + } +] + diff --git a/extensions/common/api/schemas.gypi b/extensions/common/api/schemas.gypi index 18b43cc..7648532 100644 --- a/extensions/common/api/schemas.gypi +++ b/extensions/common/api/schemas.gypi @@ -23,6 +23,7 @@ 'bluetooth_socket.idl', 'cast_channel.idl', 'dns.idl', + 'events.json', 'extensions_manifest_types.json', 'extension_types.json', 'guest_view_internal.json', diff --git a/extensions/extensions.gyp b/extensions/extensions.gyp index e888dc7..4ea4161 100644 --- a/extensions/extensions.gyp +++ b/extensions/extensions.gyp @@ -357,6 +357,16 @@ 'browser/api/cast_channel/logger_util.cc', 'browser/api/cast_channel/logger_util.h', 'browser/api/declarative/deduping_factory.h', + 'browser/api/declarative/declarative_api.cc', + 'browser/api/declarative/declarative_api.h', + 'browser/api/declarative/declarative_rule.h', + 'browser/api/declarative/rules_cache_delegate.cc', + 'browser/api/declarative/rules_cache_delegate.h', + 'browser/api/declarative/rules_registry.cc', + 'browser/api/declarative/rules_registry.h', + 'browser/api/declarative/rules_registry_service_interface_.h', + 'browser/api/declarative/test_rules_registry.cc', + 'browser/api/declarative/test_rules_registry.h', 'browser/api/declarative_webrequest/request_stage.cc', 'browser/api/declarative_webrequest/request_stage.h', 'browser/api/declarative_webrequest/webrequest_constants.cc', @@ -1022,6 +1032,7 @@ 'browser/api/cast_channel/cast_channel_api_unittest.cc', 'browser/api/cast_channel/cast_socket_unittest.cc', 'browser/api/cast_channel/logger_unittest.cc', + 'browser/api/declarative/declarative_rule_unittest.cc', 'browser/api/declarative/deduping_factory_unittest.cc', 'browser/api/power/power_api_unittest.cc', 'browser/api/sockets_tcp/sockets_tcp_api_unittest.cc', |