// 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 CHROME_BROWSER_EXTENSIONS_API_DECLARATIVE_DECLARATIVE_RULE_H__ #define CHROME_BROWSER_EXTENSIONS_API_DECLARATIVE_DECLARATIVE_RULE_H__ #include #include #include #include #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 "chrome/common/extensions/api/events.h" #include "extensions/common/extension.h" #include "extensions/common/matcher/url_matcher.h" namespace base { class Time; class Value; } 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 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 class DeclarativeConditionSet { public: typedef std::vector > AnyVector; typedef std::vector > 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 Create( const Extension* extension, 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(URLMatcherConditionSet::ID url_match_trigger, const typename ConditionT::MatchData& match_data) const; // Appends the URLMatcherConditionSet from all conditions to |condition_sets|. void GetURLMatcherConditionSets( 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 URLMatcherIdToCondition; DeclarativeConditionSet( const Conditions& conditions, const URLMatcherIdToCondition& match_id_to_condition, const std::vector& conditions_without_urls); const URLMatcherIdToCondition match_id_to_condition_; const Conditions conditions_; const std::vector 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 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 class DeclarativeActionSet { public: typedef std::vector > AnyVector; typedef std::vector > 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 Create(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 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 class DeclarativeRule { public: typedef std::string ExtensionId; typedef std::string RuleId; typedef std::pair GlobalRuleId; typedef int Priority; typedef DeclarativeConditionSet ConditionSet; typedef DeclarativeActionSet ActionSet; typedef extensions::api::events::Rule JsonRule; typedef std::vector 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 ConsistencyChecker; DeclarativeRule(const GlobalRuleId& id, const Tags& tags, base::Time extension_installation_time, scoped_ptr conditions, scoped_ptr 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 Create( URLMatcherConditionFactory* url_matcher_condition_factory, const Extension* extension, base::Time extension_installation_time, linked_ptr 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 conditions_; scoped_ptr actions_; Priority priority_; DISALLOW_COPY_AND_ASSIGN(DeclarativeRule); }; // Implementation details below here. // // DeclarativeConditionSet // template bool DeclarativeConditionSet::IsFulfilled( 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_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 void DeclarativeConditionSet::GetURLMatcherConditionSets( URLMatcherConditionSet::Vector* condition_sets) const { for (typename Conditions::const_iterator i = conditions_.begin(); i != conditions_.end(); ++i) { (*i)->GetURLMatcherConditionSets(condition_sets); } } // static template scoped_ptr > DeclarativeConditionSet::Create( const Extension* extension, 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 condition = ConditionT::Create( extension, url_matcher_condition_factory, **i, error); if (!error->empty()) return scoped_ptr(); result.push_back(make_linked_ptr(condition.release())); } URLMatcherIdToCondition match_id_to_condition; std::vector conditions_without_urls; 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 (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 DeclarativeConditionSet::DeclarativeConditionSet( const Conditions& conditions, const URLMatcherIdToCondition& match_id_to_condition, const std::vector& conditions_without_urls) : match_id_to_condition_(match_id_to_condition), conditions_(conditions), conditions_without_urls_(conditions_without_urls) {} // // DeclarativeActionSet // template DeclarativeActionSet::DeclarativeActionSet(const Actions& actions) : actions_(actions) {} // static template scoped_ptr > DeclarativeActionSet::Create( 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 action = ActionT::Create(extension, **i, error, bad_message); if (!error->empty() || *bad_message) return scoped_ptr(); result.push_back(action); } return scoped_ptr(new DeclarativeActionSet(result)); } template void DeclarativeActionSet::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 void DeclarativeActionSet::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 int DeclarativeActionSet::GetMinimumPriority() const { int minimum_priority = std::numeric_limits::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 DeclarativeRule::DeclarativeRule( const GlobalRuleId& id, const Tags& tags, base::Time extension_installation_time, scoped_ptr conditions, scoped_ptr 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 scoped_ptr > DeclarativeRule::Create( URLMatcherConditionFactory* url_matcher_condition_factory, const Extension* extension, base::Time extension_installation_time, linked_ptr rule, ConsistencyChecker check_consistency, std::string* error) { scoped_ptr error_result; scoped_ptr 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 actions = ActionSet::Create(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( new DeclarativeRule(rule_id, tags, extension_installation_time, conditions.Pass(), actions.Pass(), priority)); } template void DeclarativeRule::Apply( typename ActionT::ApplyInfo* apply_info) const { return actions_->Apply(extension_id(), extension_installation_time_, apply_info); } template int DeclarativeRule::GetMinimumPriority() const { return actions_->GetMinimumPriority(); } } // namespace extensions #endif // CHROME_BROWSER_EXTENSIONS_API_DECLARATIVE_DECLARATIVE_RULE_H__