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