blob: d1dab32782b65566239953d9b7a7330ec6ab6d81 [file] [log] [blame]
// 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 <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 "chrome/common/extensions/api/events.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<ConditionT> Create(
// 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 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(
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<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(
// // 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 according to
// |actions| which represents the array of actions received from the
// extension API.
static scoped_ptr<DeclarativeActionSet> Create(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<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::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 an extension given a json definition. The
// format of each condition and action's json is up to the specific ConditionT
// and ActionT.
//
// 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(
URLMatcherConditionFactory* url_matcher_condition_factory,
const std::string& extension_id,
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(
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(
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(
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(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;
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<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(
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(**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>::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(
URLMatcherConditionFactory* url_matcher_condition_factory,
const std::string& extension_id,
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(
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(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 // CHROME_BROWSER_EXTENSIONS_API_DECLARATIVE_DECLARATIVE_RULE_H__