Add new certificateProvider extension API.
[chromium-blink-merge.git] / chrome / browser / extensions / api / declarative_content / chrome_content_rules_registry.cc
blob46c9d6bd5309c45d97d5578750f2dfeb7c1a19af
1 // Copyright (c) 2012 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.
5 #include "chrome/browser/extensions/api/declarative_content/chrome_content_rules_registry.h"
7 #include <utility>
9 #include "base/bind.h"
10 #include "base/strings/stringprintf.h"
11 #include "base/values.h"
12 #include "chrome/browser/chrome_notification_types.h"
13 #include "chrome/browser/extensions/api/declarative_content/content_action.h"
14 #include "chrome/browser/extensions/api/declarative_content/content_constants.h"
15 #include "chrome/browser/extensions/extension_util.h"
16 #include "chrome/browser/profiles/profile.h"
17 #include "chrome/browser/ui/browser.h"
18 #include "chrome/browser/ui/browser_iterator.h"
19 #include "chrome/browser/ui/tabs/tab_strip_model.h"
20 #include "content/public/browser/navigation_details.h"
21 #include "content/public/browser/notification_service.h"
22 #include "content/public/browser/notification_source.h"
23 #include "content/public/browser/web_contents.h"
24 #include "extensions/browser/api/declarative/rules_registry_service.h"
25 #include "extensions/browser/extension_registry.h"
26 #include "extensions/browser/extension_system.h"
28 using url_matcher::URLMatcherConditionSet;
30 namespace extensions {
32 namespace {
34 // TODO(jyasskin): improve error messaging to give more meaningful messages
35 // to the extension developer.
36 // Error messages:
37 const char kExpectedDictionary[] = "A condition has to be a dictionary.";
38 const char kConditionWithoutInstanceType[] = "A condition had no instanceType";
39 const char kExpectedOtherConditionType[] = "Expected a condition of type "
40 "declarativeContent.PageStateMatcher";
41 const char kUnknownConditionAttribute[] = "Unknown condition attribute '%s'";
43 } // namespace
46 // ContentCondition
49 ContentCondition::ContentCondition(
50 scoped_ptr<DeclarativeContentPageUrlPredicate> page_url_predicate,
51 scoped_ptr<DeclarativeContentCssPredicate> css_predicate,
52 scoped_ptr<DeclarativeContentIsBookmarkedPredicate>
53 is_bookmarked_predicate)
54 : page_url_predicate(page_url_predicate.Pass()),
55 css_predicate(css_predicate.Pass()),
56 is_bookmarked_predicate(is_bookmarked_predicate.Pass()) {
59 ContentCondition::~ContentCondition() {}
61 scoped_ptr<ContentCondition> CreateContentCondition(
62 const Extension* extension,
63 const PredicateFactory<DeclarativeContentCssPredicate>&
64 css_predicate_factory,
65 const PredicateFactory<DeclarativeContentIsBookmarkedPredicate>&
66 is_bookmarked_predicate_factory,
67 const PredicateFactory<DeclarativeContentPageUrlPredicate>&
68 page_url_predicate_factory,
69 const base::Value& condition,
70 std::string* error) {
71 const base::DictionaryValue* condition_dict = NULL;
72 if (!condition.GetAsDictionary(&condition_dict)) {
73 *error = kExpectedDictionary;
74 return scoped_ptr<ContentCondition>();
77 // Verify that we are dealing with a Condition whose type we understand.
78 std::string instance_type;
79 if (!condition_dict->GetString(declarative_content_constants::kInstanceType,
80 &instance_type)) {
81 *error = kConditionWithoutInstanceType;
82 return scoped_ptr<ContentCondition>();
84 if (instance_type != declarative_content_constants::kPageStateMatcherType) {
85 *error = kExpectedOtherConditionType;
86 return scoped_ptr<ContentCondition>();
89 scoped_ptr<DeclarativeContentPageUrlPredicate> page_url_predicate;
90 scoped_ptr<DeclarativeContentCssPredicate> css_predicate;
91 scoped_ptr<DeclarativeContentIsBookmarkedPredicate> is_bookmarked_predicate;
93 for (base::DictionaryValue::Iterator iter(*condition_dict);
94 !iter.IsAtEnd(); iter.Advance()) {
95 const std::string& predicate_name = iter.key();
96 const base::Value& predicate_value = iter.value();
97 if (predicate_name == declarative_content_constants::kInstanceType) {
98 // Skip this.
99 } else if (predicate_name == declarative_content_constants::kPageUrl) {
100 page_url_predicate = page_url_predicate_factory.Run(extension,
101 predicate_value,
102 error);
103 } else if (predicate_name == declarative_content_constants::kCss) {
104 css_predicate = css_predicate_factory.Run(extension, predicate_value,
105 error);
106 } else if (predicate_name == declarative_content_constants::kIsBookmarked) {
107 is_bookmarked_predicate = is_bookmarked_predicate_factory.Run(
108 extension,
109 predicate_value,
110 error);
111 } else {
112 *error = base::StringPrintf(kUnknownConditionAttribute,
113 predicate_name.c_str());
115 if (!error->empty())
116 return scoped_ptr<ContentCondition>();
119 return make_scoped_ptr(new ContentCondition(page_url_predicate.Pass(),
120 css_predicate.Pass(),
121 is_bookmarked_predicate.Pass()));
125 // EvaluationScope
128 // Used to coalesce multiple requests for evaluation into a zero or one actual
129 // evaluations (depending on the EvaluationDisposition). This is required for
130 // correctness when multiple trackers respond to the same event. Otherwise,
131 // executing the request from the first tracker will be done before the tracked
132 // state has been updated for the other trackers.
133 class ChromeContentRulesRegistry::EvaluationScope {
134 public:
135 // Default disposition is PERFORM_EVALUATION.
136 explicit EvaluationScope(ChromeContentRulesRegistry* registry);
137 EvaluationScope(ChromeContentRulesRegistry* registry,
138 EvaluationDisposition disposition);
139 ~EvaluationScope();
141 private:
142 ChromeContentRulesRegistry* const registry_;
143 const EvaluationDisposition previous_disposition_;
145 DISALLOW_COPY_AND_ASSIGN(EvaluationScope);
148 ChromeContentRulesRegistry::EvaluationScope::EvaluationScope(
149 ChromeContentRulesRegistry* registry)
150 : EvaluationScope(registry, DEFER_REQUESTS) {}
152 ChromeContentRulesRegistry::EvaluationScope::EvaluationScope(
153 ChromeContentRulesRegistry* registry,
154 EvaluationDisposition disposition)
155 : registry_(registry),
156 previous_disposition_(registry_->evaluation_disposition_) {
157 DCHECK_NE(EVALUATE_REQUESTS, disposition);
158 registry_->evaluation_disposition_ = disposition;
161 ChromeContentRulesRegistry::EvaluationScope::~EvaluationScope() {
162 registry_->evaluation_disposition_ = previous_disposition_;
163 if (registry_->evaluation_disposition_ == EVALUATE_REQUESTS) {
164 for (content::WebContents* tab : registry_->evaluation_pending_)
165 registry_->EvaluateConditionsForTab(tab);
166 registry_->evaluation_pending_.clear();
171 // ChromeContentRulesRegistry
174 ChromeContentRulesRegistry::ChromeContentRulesRegistry(
175 content::BrowserContext* browser_context,
176 RulesCacheDelegate* cache_delegate)
177 : ContentRulesRegistry(browser_context,
178 declarative_content_constants::kOnPageChanged,
179 content::BrowserThread::UI,
180 cache_delegate,
181 RulesRegistryService::kDefaultRulesRegistryID),
182 page_url_condition_tracker_(browser_context, this),
183 css_condition_tracker_(browser_context, this),
184 is_bookmarked_condition_tracker_(browser_context, this),
185 evaluation_disposition_(EVALUATE_REQUESTS) {
186 registrar_.Add(this,
187 content::NOTIFICATION_WEB_CONTENTS_DESTROYED,
188 content::NotificationService::AllBrowserContextsAndSources());
191 void ChromeContentRulesRegistry::Observe(
192 int type,
193 const content::NotificationSource& source,
194 const content::NotificationDetails& details) {
195 switch (type) {
196 case content::NOTIFICATION_WEB_CONTENTS_DESTROYED: {
197 content::WebContents* tab =
198 content::Source<content::WebContents>(source).ptr();
199 // Note that neither non-tab WebContents nor tabs from other browser
200 // contexts will be in the map.
201 active_rules_.erase(tab);
202 break;
207 void ChromeContentRulesRegistry::RequestEvaluation(
208 content::WebContents* contents) {
209 switch (evaluation_disposition_) {
210 case EVALUATE_REQUESTS:
211 EvaluateConditionsForTab(contents);
212 break;
213 case DEFER_REQUESTS:
214 evaluation_pending_.insert(contents);
215 break;
216 case IGNORE_REQUESTS:
217 break;
221 bool ChromeContentRulesRegistry::ShouldManageConditionsForBrowserContext(
222 content::BrowserContext* context) {
223 return ManagingRulesForBrowserContext(context);
226 void ChromeContentRulesRegistry::MonitorWebContentsForRuleEvaluation(
227 content::WebContents* contents) {
228 // We rely on active_rules_ to have a key-value pair for |contents| to know
229 // which WebContents we are working with.
230 active_rules_[contents] = std::set<const ContentRule*>();
232 EvaluationScope evaluation_scope(this);
233 page_url_condition_tracker_.TrackForWebContents(contents);
234 css_condition_tracker_.TrackForWebContents(contents);
235 is_bookmarked_condition_tracker_.TrackForWebContents(contents);
238 void ChromeContentRulesRegistry::DidNavigateMainFrame(
239 content::WebContents* contents,
240 const content::LoadCommittedDetails& details,
241 const content::FrameNavigateParams& params) {
242 if (ContainsKey(active_rules_, contents)) {
243 EvaluationScope evaluation_scope(this);
244 page_url_condition_tracker_.OnWebContentsNavigation(contents, details,
245 params);
246 css_condition_tracker_.OnWebContentsNavigation(contents, details, params);
247 is_bookmarked_condition_tracker_.OnWebContentsNavigation(contents, details,
248 params);
252 ChromeContentRulesRegistry::ContentRule::ContentRule(
253 const Extension* extension,
254 ScopedVector<const ContentCondition> conditions,
255 ScopedVector<const ContentAction> actions,
256 int priority)
257 : extension(extension),
258 conditions(conditions.Pass()),
259 actions(actions.Pass()),
260 priority(priority) {
263 ChromeContentRulesRegistry::ContentRule::~ContentRule() {}
265 scoped_ptr<const ChromeContentRulesRegistry::ContentRule>
266 ChromeContentRulesRegistry::CreateRule(
267 const Extension* extension,
268 const PredicateFactory<DeclarativeContentCssPredicate>&
269 css_predicate_factory,
270 const PredicateFactory<DeclarativeContentIsBookmarkedPredicate>&
271 is_bookmarked_predicate_factory,
272 const PredicateFactory<DeclarativeContentPageUrlPredicate>&
273 page_url_predicate_factory,
274 const api::events::Rule& api_rule,
275 std::string* error) {
276 ScopedVector<const ContentCondition> conditions;
277 for (const linked_ptr<base::Value>& value : api_rule.conditions) {
278 conditions.push_back(
279 CreateContentCondition(extension, css_predicate_factory,
280 is_bookmarked_predicate_factory,
281 page_url_predicate_factory, *value, error));
282 if (!error->empty())
283 return scoped_ptr<ContentRule>();
286 ScopedVector<const ContentAction> actions;
287 for (const linked_ptr<base::Value>& value : api_rule.actions) {
288 actions.push_back(ContentAction::Create(browser_context(), extension,
289 *value, error));
290 if (!error->empty())
291 return scoped_ptr<ContentRule>();
294 // Note: |api_rule| may contain tags, but these are ignored.
296 return make_scoped_ptr(
297 new ContentRule(extension, conditions.Pass(), actions.Pass(),
298 *api_rule.priority));
301 bool ChromeContentRulesRegistry::ManagingRulesForBrowserContext(
302 content::BrowserContext* context) {
303 // Manage both the normal context and incognito contexts associated with it.
304 return Profile::FromBrowserContext(context)->GetOriginalProfile() ==
305 Profile::FromBrowserContext(browser_context());
308 std::set<const ChromeContentRulesRegistry::ContentRule*>
309 ChromeContentRulesRegistry::GetMatchingRules(content::WebContents* tab) const {
310 const bool is_incognito_tab = tab->GetBrowserContext()->IsOffTheRecord();
311 std::set<const ContentRule*> matching_rules;
312 for (const RulesMap::value_type& rule_id_rule_pair : content_rules_) {
313 const ContentRule* rule = rule_id_rule_pair.second.get();
314 if (is_incognito_tab &&
315 !ShouldEvaluateExtensionRulesForIncognitoRenderer(rule->extension))
316 continue;
318 for (const ContentCondition* condition : rule->conditions) {
319 if (condition->page_url_predicate &&
320 !page_url_condition_tracker_.EvaluatePredicate(
321 condition->page_url_predicate.get(),
322 tab)) {
323 continue;
326 if (condition->css_predicate &&
327 !css_condition_tracker_.EvaluatePredicate(
328 condition->css_predicate.get(),
329 tab)) {
330 continue;
333 if (condition->is_bookmarked_predicate &&
334 !condition->is_bookmarked_predicate->IsIgnored() &&
335 !is_bookmarked_condition_tracker_.EvaluatePredicate(
336 condition->is_bookmarked_predicate.get(),
337 tab)) {
338 continue;
341 matching_rules.insert(rule);
344 return matching_rules;
347 std::string ChromeContentRulesRegistry::AddRulesImpl(
348 const std::string& extension_id,
349 const std::vector<linked_ptr<api::events::Rule>>& api_rules) {
350 EvaluationScope evaluation_scope(this);
351 const Extension* extension = ExtensionRegistry::Get(browser_context())
352 ->GetInstalledExtension(extension_id);
353 DCHECK(extension);
355 std::string error;
356 RulesMap new_rules;
358 // These callbacks are only used during the CreateRule call and not stored, so
359 // it's safe to supply an Unretained tracker pointer.
360 const PredicateFactory<DeclarativeContentCssPredicate>
361 css_predicate_factory =
362 base::Bind(&DeclarativeContentCssConditionTracker::CreatePredicate,
363 base::Unretained(&css_condition_tracker_));
364 const PredicateFactory<DeclarativeContentIsBookmarkedPredicate>
365 is_bookmarked_predicate_factory =
366 base::Bind(
367 &DeclarativeContentIsBookmarkedConditionTracker::CreatePredicate,
368 base::Unretained(&is_bookmarked_condition_tracker_));
369 const PredicateFactory<DeclarativeContentPageUrlPredicate>
370 page_url_predicate_factory =
371 base::Bind(
372 &DeclarativeContentPageUrlConditionTracker::CreatePredicate,
373 base::Unretained(&page_url_condition_tracker_));
375 for (const linked_ptr<api::events::Rule>& api_rule : api_rules) {
376 ExtensionIdRuleIdPair rule_id(extension_id, *api_rule->id);
377 DCHECK(content_rules_.find(rule_id) == content_rules_.end());
379 scoped_ptr<const ContentRule> rule(
380 CreateRule(extension, css_predicate_factory,
381 is_bookmarked_predicate_factory, page_url_predicate_factory,
382 *api_rule, &error));
383 if (!error.empty()) {
384 // Clean up temporary condition sets created during rule creation.
385 page_url_condition_tracker_.ClearUnusedConditionSets();
386 return error;
388 DCHECK(rule);
390 new_rules[rule_id] = make_linked_ptr(rule.release());
393 // Wohoo, everything worked fine.
394 content_rules_.insert(new_rules.begin(), new_rules.end());
396 // Record the URL matcher condition set to rule condition pair mappings, and
397 // register URL patterns in the URL matcher.
398 URLMatcherConditionSet::Vector all_new_condition_sets;
399 for (const RulesMap::value_type& rule_id_rule_pair : new_rules) {
400 const linked_ptr<const ContentRule>& rule = rule_id_rule_pair.second;
401 for (const ContentCondition* condition : rule->conditions) {
402 if (condition->page_url_predicate) {
403 all_new_condition_sets.push_back(
404 condition->page_url_predicate->url_matcher_condition_set());
408 page_url_condition_tracker_.AddConditionSets(all_new_condition_sets);
410 UpdateCssSelectorsFromRules();
412 // Request evaluation for all WebContents, under the assumption that a
413 // non-empty condition has been added.
414 for (auto web_contents_rules_pair : active_rules_)
415 RequestEvaluation(web_contents_rules_pair.first);
417 return std::string();
420 std::string ChromeContentRulesRegistry::RemoveRulesImpl(
421 const std::string& extension_id,
422 const std::vector<std::string>& rule_identifiers) {
423 // Ignore evaluation requests in this function because it reverts actions on
424 // any active rules itself. Otherwise, we run the risk of reverting the same
425 // rule multiple times.
426 EvaluationScope evaluation_scope(this, IGNORE_REQUESTS);
427 // URLMatcherConditionSet IDs that can be removed from URLMatcher.
428 std::vector<URLMatcherConditionSet::ID> condition_set_ids_to_remove;
430 for (const std::string& id : rule_identifiers) {
431 // Skip unknown rules.
432 RulesMap::iterator content_rules_entry =
433 content_rules_.find(std::make_pair(extension_id, id));
434 if (content_rules_entry == content_rules_.end())
435 continue;
437 // Remove state associated with URL matcher conditions, and collect the
438 // URLMatcherConditionSet::IDs to remove later.
439 URLMatcherConditionSet::Vector condition_sets;
440 const ContentRule* rule = content_rules_entry->second.get();
441 for (const ContentCondition* condition : rule->conditions) {
442 if (condition->page_url_predicate) {
443 URLMatcherConditionSet::ID condition_set_id =
444 condition->page_url_predicate->url_matcher_condition_set()->id();
445 condition_set_ids_to_remove.push_back(condition_set_id);
449 // Remove the ContentRule from active_rules_.
450 for (auto& tab_rules_pair : active_rules_) {
451 if (ContainsKey(tab_rules_pair.second, rule)) {
452 ContentAction::ApplyInfo apply_info =
453 {rule->extension, browser_context(), tab_rules_pair.first,
454 rule->priority};
455 for (const ContentAction* action : rule->actions)
456 action->Revert(apply_info);
457 tab_rules_pair.second.erase(rule);
461 // Remove reference to actual rule.
462 content_rules_.erase(content_rules_entry);
465 // Clear URLMatcher of condition sets that are not needed any more.
466 page_url_condition_tracker_.RemoveConditionSets(
467 condition_set_ids_to_remove);
469 UpdateCssSelectorsFromRules();
471 return std::string();
474 std::string ChromeContentRulesRegistry::RemoveAllRulesImpl(
475 const std::string& extension_id) {
476 // Search all identifiers of rules that belong to extension |extension_id|.
477 std::vector<std::string> rule_identifiers;
478 for (const RulesMap::value_type& id_rule_pair : content_rules_) {
479 const ExtensionIdRuleIdPair& extension_id_rule_id_pair = id_rule_pair.first;
480 if (extension_id_rule_id_pair.first == extension_id)
481 rule_identifiers.push_back(extension_id_rule_id_pair.second);
484 return RemoveRulesImpl(extension_id, rule_identifiers);
487 void ChromeContentRulesRegistry::UpdateCssSelectorsFromRules() {
488 std::set<std::string> css_selectors; // We rely on this being sorted.
489 for (const RulesMap::value_type& id_rule_pair : content_rules_) {
490 const ContentRule* rule = id_rule_pair.second.get();
491 for (const ContentCondition* condition : rule->conditions) {
492 if (condition->css_predicate) {
493 const std::vector<std::string>& condition_css_selectors =
494 condition->css_predicate->css_selectors();
495 css_selectors.insert(condition_css_selectors.begin(),
496 condition_css_selectors.end());
501 css_condition_tracker_.SetWatchedCssSelectors(css_selectors);
504 void ChromeContentRulesRegistry::EvaluateConditionsForTab(
505 content::WebContents* tab) {
506 std::set<const ContentRule*> matching_rules = GetMatchingRules(tab);
507 if (matching_rules.empty() && !ContainsKey(active_rules_, tab))
508 return;
510 std::set<const ContentRule*>& prev_matching_rules = active_rules_[tab];
511 for (const ContentRule* rule : matching_rules) {
512 ContentAction::ApplyInfo apply_info =
513 {rule->extension, browser_context(), tab, rule->priority};
514 if (!ContainsKey(prev_matching_rules, rule)) {
515 for (const ContentAction* action : rule->actions)
516 action->Apply(apply_info);
517 } else {
518 for (const ContentAction* action : rule->actions)
519 action->Reapply(apply_info);
522 for (const ContentRule* rule : prev_matching_rules) {
523 if (!ContainsKey(matching_rules, rule)) {
524 ContentAction::ApplyInfo apply_info =
525 {rule->extension, browser_context(), tab, rule->priority};
526 for (const ContentAction* action : rule->actions)
527 action->Revert(apply_info);
531 if (matching_rules.empty())
532 active_rules_[tab].clear();
533 else
534 swap(matching_rules, prev_matching_rules);
537 bool
538 ChromeContentRulesRegistry::ShouldEvaluateExtensionRulesForIncognitoRenderer(
539 const Extension* extension) const {
540 if (!util::IsIncognitoEnabled(extension->id(), browser_context()))
541 return false;
543 // Split-mode incognito extensions register their rules with separate
544 // RulesRegistries per Original/OffTheRecord browser contexts, whereas
545 // spanning-mode extensions share the Original browser context.
546 if (util::CanCrossIncognito(extension, browser_context())) {
547 // The extension uses spanning mode incognito. No rules should have been
548 // registered for the extension in the OffTheRecord registry so
549 // execution for that registry should never reach this point.
550 CHECK(!browser_context()->IsOffTheRecord());
551 } else {
552 // The extension uses split mode incognito. Both the Original and
553 // OffTheRecord registries may have (separate) rules for this extension.
554 // Since we're looking at an incognito renderer, so only the OffTheRecord
555 // registry should process its rules.
556 if (!browser_context()->IsOffTheRecord())
557 return false;
560 return true;
563 bool ChromeContentRulesRegistry::IsEmpty() const {
564 return content_rules_.empty() && page_url_condition_tracker_.IsEmpty();
567 void ChromeContentRulesRegistry::UpdateMatchingCssSelectorsForTesting(
568 content::WebContents* contents,
569 const std::vector<std::string>& matching_css_selectors) {
570 css_condition_tracker_.UpdateMatchingCssSelectorsForTesting(
571 contents,
572 matching_css_selectors);
575 size_t ChromeContentRulesRegistry::GetActiveRulesCountForTesting() {
576 size_t count = 0;
577 for (auto web_contents_rules_pair : active_rules_)
578 count += web_contents_rules_pair.second.size();
579 return count;
582 ChromeContentRulesRegistry::~ChromeContentRulesRegistry() {
585 } // namespace extensions