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"
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
{
34 // TODO(jyasskin): improve error messaging to give more meaningful messages
35 // to the extension developer.
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'";
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
,
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
,
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
) {
99 } else if (predicate_name
== declarative_content_constants::kPageUrl
) {
100 page_url_predicate
= page_url_predicate_factory
.Run(extension
,
103 } else if (predicate_name
== declarative_content_constants::kCss
) {
104 css_predicate
= css_predicate_factory
.Run(extension
, predicate_value
,
106 } else if (predicate_name
== declarative_content_constants::kIsBookmarked
) {
107 is_bookmarked_predicate
= is_bookmarked_predicate_factory
.Run(
112 *error
= base::StringPrintf(kUnknownConditionAttribute
,
113 predicate_name
.c_str());
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()));
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
{
135 // Default disposition is PERFORM_EVALUATION.
136 explicit EvaluationScope(ChromeContentRulesRegistry
* registry
);
137 EvaluationScope(ChromeContentRulesRegistry
* registry
,
138 EvaluationDisposition disposition
);
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
,
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
) {
187 content::NOTIFICATION_WEB_CONTENTS_DESTROYED
,
188 content::NotificationService::AllBrowserContextsAndSources());
191 void ChromeContentRulesRegistry::Observe(
193 const content::NotificationSource
& source
,
194 const content::NotificationDetails
& details
) {
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
);
207 void ChromeContentRulesRegistry::RequestEvaluation(
208 content::WebContents
* contents
) {
209 switch (evaluation_disposition_
) {
210 case EVALUATE_REQUESTS
:
211 EvaluateConditionsForTab(contents
);
214 evaluation_pending_
.insert(contents
);
216 case IGNORE_REQUESTS
:
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
,
246 css_condition_tracker_
.OnWebContentsNavigation(contents
, details
, params
);
247 is_bookmarked_condition_tracker_
.OnWebContentsNavigation(contents
, details
,
252 ChromeContentRulesRegistry::ContentRule::ContentRule(
253 const Extension
* extension
,
254 ScopedVector
<const ContentCondition
> conditions
,
255 ScopedVector
<const ContentAction
> actions
,
257 : extension(extension
),
258 conditions(conditions
.Pass()),
259 actions(actions
.Pass()),
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
));
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
,
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
))
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(),
326 if (condition
->css_predicate
&&
327 !css_condition_tracker_
.EvaluatePredicate(
328 condition
->css_predicate
.get(),
333 if (condition
->is_bookmarked_predicate
&&
334 !condition
->is_bookmarked_predicate
->IsIgnored() &&
335 !is_bookmarked_condition_tracker_
.EvaluatePredicate(
336 condition
->is_bookmarked_predicate
.get(),
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
);
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
=
367 &DeclarativeContentIsBookmarkedConditionTracker::CreatePredicate
,
368 base::Unretained(&is_bookmarked_condition_tracker_
));
369 const PredicateFactory
<DeclarativeContentPageUrlPredicate
>
370 page_url_predicate_factory
=
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
,
383 if (!error
.empty()) {
384 // Clean up temporary condition sets created during rule creation.
385 page_url_condition_tracker_
.ClearUnusedConditionSets();
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())
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
,
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
))
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
);
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();
534 swap(matching_rules
, prev_matching_rules
);
538 ChromeContentRulesRegistry::ShouldEvaluateExtensionRulesForIncognitoRenderer(
539 const Extension
* extension
) const {
540 if (!util::IsIncognitoEnabled(extension
->id(), browser_context()))
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());
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())
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(
572 matching_css_selectors
);
575 size_t ChromeContentRulesRegistry::GetActiveRulesCountForTesting() {
577 for (auto web_contents_rules_pair
: active_rules_
)
578 count
+= web_contents_rules_pair
.second
.size();
582 ChromeContentRulesRegistry::~ChromeContentRulesRegistry() {
585 } // namespace extensions