declarative_rule.h revision 1320f92c476a1ad9d19dba2a48c72b75566198e9
1// Copyright (c) 2013 The Chromium Authors. All rights reserved.
2// Use of this source code is governed by a BSD-style license that can be
3// found in the LICENSE file.
4//
5// DeclarativeRule<>, DeclarativeConditionSet<>, and DeclarativeActionSet<>
6// templates usable with multiple different declarativeFoo systems.  These are
7// templated on the Condition and Action types that define the behavior of a
8// particular declarative event.
9
10#ifndef EXTENSIONS_BROWSER_API_DECLARATIVE_DECLARATIVE_RULE_H__
11#define EXTENSIONS_BROWSER_API_DECLARATIVE_DECLARATIVE_RULE_H__
12
13#include <limits>
14#include <set>
15#include <string>
16#include <vector>
17
18#include "base/callback.h"
19#include "base/memory/linked_ptr.h"
20#include "base/memory/scoped_vector.h"
21#include "base/stl_util.h"
22#include "base/time/time.h"
23#include "components/url_matcher/url_matcher.h"
24#include "extensions/common/api/events.h"
25#include "extensions/common/extension.h"
26
27namespace base {
28class Time;
29class Value;
30}
31
32namespace content {
33class BrowserContext;
34}
35
36namespace extensions {
37
38// This class stores a set of conditions that may be part of a DeclarativeRule.
39// If any condition is fulfilled, the Actions of the DeclarativeRule can be
40// triggered.
41//
42// ConditionT should be immutable after creation.  It must define the following
43// members:
44//
45//   // Arguments passed through from DeclarativeConditionSet::Create.
46//   static scoped_ptr<ConditionT> Create(
47//       const Extension* extension,
48//       URLMatcherConditionFactory* url_matcher_condition_factory,
49//       // Except this argument gets elements of the AnyVector.
50//       const base::Value& definition,
51//       std::string* error);
52//   // If the Condition needs to be filtered by some URLMatcherConditionSets,
53//   // append them to |condition_sets|.
54//   // DeclarativeConditionSet::GetURLMatcherConditionSets forwards here.
55//   void GetURLMatcherConditionSets(
56//       URLMatcherConditionSet::Vector* condition_sets);
57//   // |match_data| passed through from DeclarativeConditionSet::IsFulfilled.
58//   bool IsFulfilled(const ConditionT::MatchData& match_data);
59template<typename ConditionT>
60class DeclarativeConditionSet {
61 public:
62  typedef std::vector<linked_ptr<base::Value> > AnyVector;
63  typedef std::vector<linked_ptr<const ConditionT> > Conditions;
64  typedef typename Conditions::const_iterator const_iterator;
65
66  // Factory method that creates a DeclarativeConditionSet for |extension|
67  // according to the JSON array |conditions| passed by the extension API. Sets
68  // |error| and returns NULL in case of an error.
69  static scoped_ptr<DeclarativeConditionSet> Create(
70      const Extension* extension,
71      url_matcher::URLMatcherConditionFactory* url_matcher_condition_factory,
72      const AnyVector& conditions,
73      std::string* error);
74
75  const Conditions& conditions() const {
76    return conditions_;
77  }
78
79  const_iterator begin() const { return conditions_.begin(); }
80  const_iterator end() const { return conditions_.end(); }
81
82  // If |url_match_trigger| is not -1, this function looks for a condition
83  // with this URLMatcherConditionSet, and forwards to that condition's
84  // IsFulfilled(|match_data|). If there is no such condition, then false is
85  // returned. If |url_match_trigger| is -1, this function returns whether any
86  // of the conditions without URL attributes is satisfied.
87  bool IsFulfilled(url_matcher::URLMatcherConditionSet::ID url_match_trigger,
88                   const typename ConditionT::MatchData& match_data) const;
89
90  // Appends the URLMatcherConditionSet from all conditions to |condition_sets|.
91  void GetURLMatcherConditionSets(
92      url_matcher::URLMatcherConditionSet::Vector* condition_sets) const;
93
94  // Returns whether there are some conditions without UrlFilter attributes.
95  bool HasConditionsWithoutUrls() const {
96    return !conditions_without_urls_.empty();
97  }
98
99 private:
100  typedef std::map<url_matcher::URLMatcherConditionSet::ID, const ConditionT*>
101      URLMatcherIdToCondition;
102
103  DeclarativeConditionSet(
104      const Conditions& conditions,
105      const URLMatcherIdToCondition& match_id_to_condition,
106      const std::vector<const ConditionT*>& conditions_without_urls);
107
108  const URLMatcherIdToCondition match_id_to_condition_;
109  const Conditions conditions_;
110  const std::vector<const ConditionT*> conditions_without_urls_;
111
112  DISALLOW_COPY_AND_ASSIGN(DeclarativeConditionSet);
113};
114
115// Immutable container for multiple actions.
116//
117// ActionT should be immutable after creation.  It must define the following
118// members:
119//
120//   // Arguments passed through from ActionSet::Create.
121//   static scoped_ptr<ActionT> Create(
122//       const Extension* extension,
123//       // Except this argument gets elements of the AnyVector.
124//       const base::Value& definition,
125//       std::string* error, bool* bad_message);
126//   void Apply(const std::string& extension_id,
127//              const base::Time& extension_install_time,
128//              // Contains action-type-specific in/out parameters.
129//              typename ActionT::ApplyInfo* apply_info) const;
130//   // Only needed if the RulesRegistry calls DeclarativeActionSet::Revert().
131//   void Revert(const std::string& extension_id,
132//               const base::Time& extension_install_time,
133//               // Contains action-type-specific in/out parameters.
134//               typename ActionT::ApplyInfo* apply_info) const;
135//   // Return the minimum priority of rules that can be evaluated after this
136//   // action runs.  A suitable default value is MIN_INT.
137//   int minimum_priority() const;
138//
139// TODO(battre): As DeclarativeActionSet can become the single owner of all
140// actions, we can optimize here by making some of them singletons (e.g. Cancel
141// actions).
142template<typename ActionT>
143class DeclarativeActionSet {
144 public:
145  typedef std::vector<linked_ptr<base::Value> > AnyVector;
146  typedef std::vector<scoped_refptr<const ActionT> > Actions;
147
148  explicit DeclarativeActionSet(const Actions& actions);
149
150  // Factory method that instantiates a DeclarativeActionSet for |extension|
151  // according to |actions| which represents the array of actions received from
152  // the extension API.
153  static scoped_ptr<DeclarativeActionSet> Create(
154      content::BrowserContext* browser_context,
155      const Extension* extension,
156      const AnyVector& actions,
157      std::string* error,
158      bool* bad_message);
159
160  // Rules call this method when their conditions are fulfilled.
161  void Apply(const std::string& extension_id,
162             const base::Time& extension_install_time,
163             typename ActionT::ApplyInfo* apply_info) const;
164
165  // Rules call this method when their conditions are fulfilled, but Apply has
166  // already been called.
167  void Reapply(const std::string& extension_id,
168               const base::Time& extension_install_time,
169               typename ActionT::ApplyInfo* apply_info) const;
170
171  // Rules call this method when they have stateful conditions, and those
172  // conditions stop being fulfilled.  Rules with event-based conditions (e.g. a
173  // network request happened) will never Revert() an action.
174  void Revert(const std::string& extension_id,
175              const base::Time& extension_install_time,
176              typename ActionT::ApplyInfo* apply_info) const;
177
178  // Returns the minimum priority of rules that may be evaluated after
179  // this rule. Defaults to MIN_INT.
180  int GetMinimumPriority() const;
181
182  const Actions& actions() const { return actions_; }
183
184 private:
185  const Actions actions_;
186
187  DISALLOW_COPY_AND_ASSIGN(DeclarativeActionSet);
188};
189
190// Representation of a rule of a declarative API:
191// https://developer.chrome.com/beta/extensions/events.html#declarative.
192// Generally a RulesRegistry will hold a collection of Rules for a given
193// declarative API and contain the logic for matching and applying them.
194//
195// See DeclarativeConditionSet and DeclarativeActionSet for the requirements on
196// ConditionT and ActionT.
197template<typename ConditionT, typename ActionT>
198class DeclarativeRule {
199 public:
200  typedef std::string ExtensionId;
201  typedef std::string RuleId;
202  typedef std::pair<ExtensionId, RuleId> GlobalRuleId;
203  typedef int Priority;
204  typedef DeclarativeConditionSet<ConditionT> ConditionSet;
205  typedef DeclarativeActionSet<ActionT> ActionSet;
206  typedef extensions::core_api::events::Rule JsonRule;
207  typedef std::vector<std::string> Tags;
208
209  // Checks whether the set of |conditions| and |actions| are consistent.
210  // Returns true in case of consistency and MUST set |error| otherwise.
211  typedef base::Callback<bool(const ConditionSet* conditions,
212                              const ActionSet* actions,
213                              std::string* error)> ConsistencyChecker;
214
215  DeclarativeRule(const GlobalRuleId& id,
216                  const Tags& tags,
217                  base::Time extension_installation_time,
218                  scoped_ptr<ConditionSet> conditions,
219                  scoped_ptr<ActionSet> actions,
220                  Priority priority);
221
222  // Creates a DeclarativeRule for |extension| given a json definition.  The
223  // format of each condition and action's json is up to the specific ConditionT
224  // and ActionT.  |extension| may be NULL in tests.
225  //
226  // Before constructing the final rule, calls check_consistency(conditions,
227  // actions, error) and returns NULL if it fails.  Pass NULL if no consistency
228  // check is needed.  If |error| is empty, the translation was successful and
229  // the returned rule is internally consistent.
230  static scoped_ptr<DeclarativeRule> Create(
231      url_matcher::URLMatcherConditionFactory* url_matcher_condition_factory,
232      content::BrowserContext* browser_context,
233      const Extension* extension,
234      base::Time extension_installation_time,
235      linked_ptr<JsonRule> rule,
236      ConsistencyChecker check_consistency,
237      std::string* error);
238
239  const GlobalRuleId& id() const { return id_; }
240  const Tags& tags() const { return tags_; }
241  const std::string& extension_id() const { return id_.first; }
242  const ConditionSet& conditions() const { return *conditions_; }
243  const ActionSet& actions() const { return *actions_; }
244  Priority priority() const { return priority_; }
245
246  // Calls actions().Apply(extension_id(), extension_installation_time_,
247  // apply_info). This function should only be called when the conditions_ are
248  // fulfilled (from a semantic point of view; no harm is done if this function
249  // is called at other times for testing purposes).
250  void Apply(typename ActionT::ApplyInfo* apply_info) const;
251
252  // Returns the minimum priority of rules that may be evaluated after
253  // this rule. Defaults to MIN_INT. Only valid if the conditions of this rule
254  // are fulfilled.
255  Priority GetMinimumPriority() const;
256
257 private:
258  GlobalRuleId id_;
259  Tags tags_;
260  base::Time extension_installation_time_;  // For precedences of rules.
261  scoped_ptr<ConditionSet> conditions_;
262  scoped_ptr<ActionSet> actions_;
263  Priority priority_;
264
265  DISALLOW_COPY_AND_ASSIGN(DeclarativeRule);
266};
267
268// Implementation details below here.
269
270//
271// DeclarativeConditionSet
272//
273
274template<typename ConditionT>
275bool DeclarativeConditionSet<ConditionT>::IsFulfilled(
276    url_matcher::URLMatcherConditionSet::ID url_match_trigger,
277    const typename ConditionT::MatchData& match_data) const {
278  if (url_match_trigger == -1) {
279    // Invalid trigger -- indication that we should only check conditions
280    // without URL attributes.
281    for (typename std::vector<const ConditionT*>::const_iterator it =
282             conditions_without_urls_.begin();
283         it != conditions_without_urls_.end(); ++it) {
284      if ((*it)->IsFulfilled(match_data))
285        return true;
286    }
287    return false;
288  }
289
290  typename URLMatcherIdToCondition::const_iterator triggered =
291      match_id_to_condition_.find(url_match_trigger);
292  return (triggered != match_id_to_condition_.end() &&
293          triggered->second->IsFulfilled(match_data));
294}
295
296template<typename ConditionT>
297void DeclarativeConditionSet<ConditionT>::GetURLMatcherConditionSets(
298    url_matcher::URLMatcherConditionSet::Vector* condition_sets) const {
299  for (typename Conditions::const_iterator i = conditions_.begin();
300       i != conditions_.end(); ++i) {
301    (*i)->GetURLMatcherConditionSets(condition_sets);
302  }
303}
304
305// static
306template<typename ConditionT>
307scoped_ptr<DeclarativeConditionSet<ConditionT> >
308DeclarativeConditionSet<ConditionT>::Create(
309    const Extension* extension,
310    url_matcher::URLMatcherConditionFactory* url_matcher_condition_factory,
311    const AnyVector& conditions,
312    std::string* error) {
313  Conditions result;
314
315  for (AnyVector::const_iterator i = conditions.begin();
316       i != conditions.end(); ++i) {
317    CHECK(i->get());
318    scoped_ptr<ConditionT> condition = ConditionT::Create(
319        extension, url_matcher_condition_factory, **i, error);
320    if (!error->empty())
321      return scoped_ptr<DeclarativeConditionSet>();
322    result.push_back(make_linked_ptr(condition.release()));
323  }
324
325  URLMatcherIdToCondition match_id_to_condition;
326  std::vector<const ConditionT*> conditions_without_urls;
327  url_matcher::URLMatcherConditionSet::Vector condition_sets;
328
329  for (typename Conditions::const_iterator i = result.begin();
330       i != result.end(); ++i) {
331    condition_sets.clear();
332    (*i)->GetURLMatcherConditionSets(&condition_sets);
333    if (condition_sets.empty()) {
334      conditions_without_urls.push_back(i->get());
335    } else {
336      for (url_matcher::URLMatcherConditionSet::Vector::const_iterator
337               match_set = condition_sets.begin();
338           match_set != condition_sets.end(); ++match_set)
339        match_id_to_condition[(*match_set)->id()] = i->get();
340    }
341  }
342
343  return make_scoped_ptr(new DeclarativeConditionSet(
344      result, match_id_to_condition, conditions_without_urls));
345}
346
347template<typename ConditionT>
348DeclarativeConditionSet<ConditionT>::DeclarativeConditionSet(
349    const Conditions& conditions,
350    const URLMatcherIdToCondition& match_id_to_condition,
351    const std::vector<const ConditionT*>& conditions_without_urls)
352    : match_id_to_condition_(match_id_to_condition),
353      conditions_(conditions),
354      conditions_without_urls_(conditions_without_urls) {}
355
356//
357// DeclarativeActionSet
358//
359
360template<typename ActionT>
361DeclarativeActionSet<ActionT>::DeclarativeActionSet(const Actions& actions)
362    : actions_(actions) {}
363
364// static
365template<typename ActionT>
366scoped_ptr<DeclarativeActionSet<ActionT> >
367DeclarativeActionSet<ActionT>::Create(
368    content::BrowserContext* browser_context,
369    const Extension* extension,
370    const AnyVector& actions,
371    std::string* error,
372    bool* bad_message) {
373  *error = "";
374  *bad_message = false;
375  Actions result;
376
377  for (AnyVector::const_iterator i = actions.begin();
378       i != actions.end(); ++i) {
379    CHECK(i->get());
380    scoped_refptr<const ActionT> action =
381        ActionT::Create(browser_context, extension, **i, error, bad_message);
382    if (!error->empty() || *bad_message)
383      return scoped_ptr<DeclarativeActionSet>();
384    result.push_back(action);
385  }
386
387  return scoped_ptr<DeclarativeActionSet>(new DeclarativeActionSet(result));
388}
389
390template<typename ActionT>
391void DeclarativeActionSet<ActionT>::Apply(
392    const std::string& extension_id,
393    const base::Time& extension_install_time,
394    typename ActionT::ApplyInfo* apply_info) const {
395  for (typename Actions::const_iterator i = actions_.begin();
396       i != actions_.end(); ++i)
397    (*i)->Apply(extension_id, extension_install_time, apply_info);
398}
399
400template<typename ActionT>
401void DeclarativeActionSet<ActionT>::Reapply(
402    const std::string& extension_id,
403    const base::Time& extension_install_time,
404    typename ActionT::ApplyInfo* apply_info) const {
405  for (typename Actions::const_iterator i = actions_.begin();
406       i != actions_.end(); ++i)
407    (*i)->Reapply(extension_id, extension_install_time, apply_info);
408}
409
410template<typename ActionT>
411void DeclarativeActionSet<ActionT>::Revert(
412    const std::string& extension_id,
413    const base::Time& extension_install_time,
414    typename ActionT::ApplyInfo* apply_info) const {
415  for (typename Actions::const_iterator i = actions_.begin();
416       i != actions_.end(); ++i)
417    (*i)->Revert(extension_id, extension_install_time, apply_info);
418}
419
420template<typename ActionT>
421int DeclarativeActionSet<ActionT>::GetMinimumPriority() const {
422  int minimum_priority = std::numeric_limits<int>::min();
423  for (typename Actions::const_iterator i = actions_.begin();
424       i != actions_.end(); ++i) {
425    minimum_priority = std::max(minimum_priority, (*i)->minimum_priority());
426  }
427  return minimum_priority;
428}
429
430//
431// DeclarativeRule
432//
433
434template<typename ConditionT, typename ActionT>
435DeclarativeRule<ConditionT, ActionT>::DeclarativeRule(
436    const GlobalRuleId& id,
437    const Tags& tags,
438    base::Time extension_installation_time,
439    scoped_ptr<ConditionSet> conditions,
440    scoped_ptr<ActionSet> actions,
441    Priority priority)
442    : id_(id),
443      tags_(tags),
444      extension_installation_time_(extension_installation_time),
445      conditions_(conditions.release()),
446      actions_(actions.release()),
447      priority_(priority) {
448  CHECK(conditions_.get());
449  CHECK(actions_.get());
450}
451
452// static
453template<typename ConditionT, typename ActionT>
454scoped_ptr<DeclarativeRule<ConditionT, ActionT> >
455DeclarativeRule<ConditionT, ActionT>::Create(
456    url_matcher::URLMatcherConditionFactory* url_matcher_condition_factory,
457    content::BrowserContext* browser_context,
458    const Extension* extension,
459    base::Time extension_installation_time,
460    linked_ptr<JsonRule> rule,
461    ConsistencyChecker check_consistency,
462    std::string* error) {
463  scoped_ptr<DeclarativeRule> error_result;
464
465  scoped_ptr<ConditionSet> conditions = ConditionSet::Create(
466      extension, url_matcher_condition_factory, rule->conditions, error);
467  if (!error->empty())
468    return error_result.Pass();
469  CHECK(conditions.get());
470
471  bool bad_message = false;
472  scoped_ptr<ActionSet> actions =
473      ActionSet::Create(
474          browser_context, extension, rule->actions, error, &bad_message);
475  if (bad_message) {
476    // TODO(battre) Export concept of bad_message to caller, the extension
477    // should be killed in case it is true.
478    *error = "An action of a rule set had an invalid "
479        "structure that should have been caught by the JSON validator.";
480    return error_result.Pass();
481  }
482  if (!error->empty() || bad_message)
483    return error_result.Pass();
484  CHECK(actions.get());
485
486  if (!check_consistency.is_null() &&
487      !check_consistency.Run(conditions.get(), actions.get(), error)) {
488    DCHECK(!error->empty());
489    return error_result.Pass();
490  }
491
492  CHECK(rule->priority.get());
493  int priority = *(rule->priority);
494
495  GlobalRuleId rule_id(extension->id(), *(rule->id));
496  Tags tags = rule->tags ? *rule->tags : Tags();
497  return scoped_ptr<DeclarativeRule>(
498      new DeclarativeRule(rule_id, tags, extension_installation_time,
499                          conditions.Pass(), actions.Pass(), priority));
500}
501
502template<typename ConditionT, typename ActionT>
503void DeclarativeRule<ConditionT, ActionT>::Apply(
504    typename ActionT::ApplyInfo* apply_info) const {
505  return actions_->Apply(extension_id(),
506                         extension_installation_time_,
507                         apply_info);
508}
509
510template<typename ConditionT, typename ActionT>
511int DeclarativeRule<ConditionT, ActionT>::GetMinimumPriority() const {
512  return actions_->GetMinimumPriority();
513}
514
515}  // namespace extensions
516
517#endif  // EXTENSIONS_BROWSER_API_DECLARATIVE_DECLARATIVE_RULE_H__
518