910e62b5创建于 1月15日历史提交
// Copyright 2013 The Chromium Authors
// 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/functional/callback.h"
#include "base/memory/ptr_util.h"
#include "base/time/time.h"
#include "base/values.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;
}

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 Conditions = std::vector<std::unique_ptr<const ConditionT>>;
  using const_iterator = typename Conditions::const_iterator;

  DeclarativeConditionSet(const DeclarativeConditionSet&) = delete;
  DeclarativeConditionSet& operator=(const DeclarativeConditionSet&) = delete;

  // 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 base::Value::List& 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(base::MatcherStringPattern::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<base::MatcherStringPattern::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_;
};

// 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::Dict& definition,
//       std::string* error, bool* bad_message);
//   void Apply(const ExtensionId& 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 ExtensionId& 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 Actions = std::vector<scoped_refptr<const ActionT>>;

  explicit DeclarativeActionSet(const Actions& actions);

  DeclarativeActionSet(const DeclarativeActionSet&) = delete;
  DeclarativeActionSet& operator=(const DeclarativeActionSet&) = delete;

  // 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 base::Value::List& action_values,
      std::string* error,
      bool* bad_message);

  // Rules call this method when their conditions are fulfilled.
  void Apply(const ExtensionId& 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 ExtensionId& 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 ExtensionId& 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_;
};

// 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::OnceCallback<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);

  DeclarativeRule(const DeclarativeRule&) = delete;
  DeclarativeRule& operator=(const DeclarativeRule&) = delete;

  // 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 ExtensionId& 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_;
};

// Implementation details below here.

//
// DeclarativeConditionSet
//

template <typename ConditionT>
bool DeclarativeConditionSet<ConditionT>::IsFulfilled(
    base::MatcherStringPattern::ID url_match_trigger,
    const typename ConditionT::MatchData& match_data) const {
  if (url_match_trigger == base::MatcherStringPattern::kInvalidId) {
    // 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 base::Value::List& condition_values,
    std::string* error) {
  Conditions result;

  for (const auto& value : condition_values) {
    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 base::Value::List& action_values,
                                      std::string* error,
                                      bool* bad_message) {
  *error = "";
  *bad_message = false;
  Actions result;

  for (const auto& value : action_values) {
    if (!value.is_dict()) {
      *bad_message = true;
      *error = "Action must be an object.";
      return nullptr;
    }
    scoped_refptr<const ActionT> action = ActionT::Create(
        browser_context, extension, value.GetDict(), error, bad_message);
    if (!error->empty() || *bad_message)
      return nullptr;
    result.push_back(action);
  }

  return std::make_unique<DeclarativeActionSet>(result);
}

template <typename ActionT>
void DeclarativeActionSet<ActionT>::Apply(
    const ExtensionId& 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 ExtensionId& 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 ExtensionId& 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() &&
      !std::move(check_consistency)
           .Run(conditions.get(), actions.get(), error)) {
    DCHECK(!error->empty());
    return std::move(error_result);
  }

  CHECK(rule.priority);
  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__