diff options
Diffstat (limited to 'extensions/browser/api/declarative/declarative_rule.h')
-rw-r--r-- | extensions/browser/api/declarative/declarative_rule.h | 517 |
1 files changed, 517 insertions, 0 deletions
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__ |