| // 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 <utility> |
| #include <vector> |
| |
| #include "base/callback.h" |
| #include "base/macros.h" |
| #include "base/memory/ptr_util.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" |
| #include "extensions/common/extension_id.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 std::unique_ptr<ConditionT> Create( |
| // const Extension* extension, |
| // URLMatcherConditionFactory* url_matcher_condition_factory, |
| // // Except this argument gets elements of the Values array. |
| // 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: |
| using Values = std::vector<std::unique_ptr<base::Value>>; |
| using Conditions = std::vector<std::unique_ptr<const ConditionT>>; |
| using const_iterator = typename Conditions::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 std::unique_ptr<DeclarativeConditionSet> Create( |
| const Extension* extension, |
| url_matcher::URLMatcherConditionFactory* url_matcher_condition_factory, |
| const Values& condition_values, |
| 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: |
| using URLMatcherIdToCondition = |
| std::map<url_matcher::URLMatcherConditionSet::ID, const ConditionT*>; |
| |
| DeclarativeConditionSet( |
| 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 std::unique_ptr<ActionT> Create( |
| // const Extension* extension, |
| // // Except this argument gets elements of the Values array. |
| // 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: |
| using Values = std::vector<std::unique_ptr<base::Value>>; |
| using Actions = std::vector<scoped_refptr<const ActionT>>; |
| |
| 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 std::unique_ptr<DeclarativeActionSet> Create( |
| content::BrowserContext* browser_context, |
| const Extension* extension, |
| const Values& action_values, |
| 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: |
| using RuleId = std::string; |
| using GlobalRuleId = std::pair<ExtensionId, RuleId>; |
| using Priority = int; |
| using ConditionSet = DeclarativeConditionSet<ConditionT>; |
| using ActionSet = DeclarativeActionSet<ActionT>; |
| using JsonRule = extensions::api::events::Rule; |
| using Tags = std::vector<std::string>; |
| |
| // Checks whether the set of |conditions| and |actions| are consistent. |
| // Returns true in case of consistency and MUST set |error| otherwise. |
| using ConsistencyChecker = base::Callback<bool(const ConditionSet* conditions, |
| const ActionSet* actions, |
| std::string* error)>; |
| |
| DeclarativeRule(const GlobalRuleId& id, |
| const Tags& tags, |
| base::Time extension_installation_time, |
| std::unique_ptr<ConditionSet> conditions, |
| std::unique_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 std::unique_ptr<DeclarativeRule> Create( |
| url_matcher::URLMatcherConditionFactory* url_matcher_condition_factory, |
| content::BrowserContext* browser_context, |
| const Extension* extension, |
| base::Time extension_installation_time, |
| const 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. |
| std::unique_ptr<ConditionSet> conditions_; |
| std::unique_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 (const ConditionT* condition : conditions_without_urls_) { |
| if (condition->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 (const auto& condition : conditions_) |
| condition->GetURLMatcherConditionSets(condition_sets); |
| } |
| |
| // static |
| template <typename ConditionT> |
| std::unique_ptr<DeclarativeConditionSet<ConditionT>> |
| DeclarativeConditionSet<ConditionT>::Create( |
| const Extension* extension, |
| url_matcher::URLMatcherConditionFactory* url_matcher_condition_factory, |
| const Values& condition_values, |
| std::string* error) { |
| Conditions result; |
| |
| for (const auto& value : condition_values) { |
| CHECK(value.get()); |
| std::unique_ptr<ConditionT> condition = ConditionT::Create( |
| extension, url_matcher_condition_factory, *value, error); |
| if (!error->empty()) |
| return nullptr; |
| result.push_back(std::move(condition)); |
| } |
| |
| URLMatcherIdToCondition match_id_to_condition; |
| std::vector<const ConditionT*> conditions_without_urls; |
| url_matcher::URLMatcherConditionSet::Vector condition_sets; |
| |
| for (const auto& condition : result) { |
| condition_sets.clear(); |
| condition->GetURLMatcherConditionSets(&condition_sets); |
| if (condition_sets.empty()) { |
| conditions_without_urls.push_back(condition.get()); |
| } else { |
| for (const scoped_refptr<url_matcher::URLMatcherConditionSet>& match_set : |
| condition_sets) |
| match_id_to_condition[match_set->id()] = condition.get(); |
| } |
| } |
| |
| return base::WrapUnique(new DeclarativeConditionSet( |
| std::move(result), match_id_to_condition, conditions_without_urls)); |
| } |
| |
| template <typename ConditionT> |
| DeclarativeConditionSet<ConditionT>::DeclarativeConditionSet( |
| 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_(std::move(conditions)), |
| conditions_without_urls_(conditions_without_urls) {} |
| |
| // |
| // DeclarativeActionSet |
| // |
| |
| template<typename ActionT> |
| DeclarativeActionSet<ActionT>::DeclarativeActionSet(const Actions& actions) |
| : actions_(actions) {} |
| |
| // static |
| template <typename ActionT> |
| std::unique_ptr<DeclarativeActionSet<ActionT>> |
| DeclarativeActionSet<ActionT>::Create(content::BrowserContext* browser_context, |
| const Extension* extension, |
| const Values& action_values, |
| std::string* error, |
| bool* bad_message) { |
| *error = ""; |
| *bad_message = false; |
| Actions result; |
| |
| for (const auto& value : action_values) { |
| CHECK(value.get()); |
| scoped_refptr<const ActionT> action = |
| ActionT::Create(browser_context, extension, *value, error, bad_message); |
| if (!error->empty() || *bad_message) |
| return std::unique_ptr<DeclarativeActionSet>(); |
| result.push_back(action); |
| } |
| |
| return std::make_unique<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 (const scoped_refptr<const ActionT>& action : actions_) |
| action->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 (const scoped_refptr<const ActionT>& action : actions_) |
| action->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 (const scoped_refptr<const ActionT>& action : actions_) |
| action->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, |
| std::unique_ptr<ConditionSet> conditions, |
| std::unique_ptr<ActionSet> actions, |
| Priority priority) |
| : id_(id), |
| tags_(tags), |
| extension_installation_time_(extension_installation_time), |
| conditions_(std::move(conditions)), |
| actions_(std::move(actions)), |
| priority_(priority) { |
| CHECK(conditions_.get()); |
| CHECK(actions_.get()); |
| } |
| |
| // static |
| template <typename ConditionT, typename ActionT> |
| std::unique_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, |
| const JsonRule& rule, |
| ConsistencyChecker check_consistency, |
| std::string* error) { |
| std::unique_ptr<DeclarativeRule> error_result; |
| |
| std::unique_ptr<ConditionSet> conditions = ConditionSet::Create( |
| extension, url_matcher_condition_factory, rule.conditions, error); |
| if (!error->empty()) |
| return std::move(error_result); |
| CHECK(conditions.get()); |
| |
| bool bad_message = false; |
| std::unique_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 std::move(error_result); |
| } |
| if (!error->empty() || bad_message) |
| return std::move(error_result); |
| CHECK(actions.get()); |
| |
| if (!check_consistency.is_null() && |
| !check_consistency.Run(conditions.get(), actions.get(), error)) { |
| DCHECK(!error->empty()); |
| return std::move(error_result); |
| } |
| |
| CHECK(rule.priority.get()); |
| int priority = *(rule.priority); |
| |
| GlobalRuleId rule_id(extension->id(), *(rule.id)); |
| Tags tags = rule.tags ? *rule.tags : Tags(); |
| return std::make_unique<DeclarativeRule>( |
| rule_id, tags, extension_installation_time, std::move(conditions), |
| std::move(actions), 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__ |