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"
9 #include "chrome/browser/chrome_notification_types.h"
10 #include "chrome/browser/extensions/api/declarative_content/content_action.h"
11 #include "chrome/browser/extensions/api/declarative_content/content_condition.h"
12 #include "chrome/browser/extensions/api/declarative_content/content_constants.h"
13 #include "chrome/browser/extensions/extension_util.h"
14 #include "chrome/browser/profiles/profile.h"
15 #include "chrome/browser/ui/browser.h"
16 #include "chrome/browser/ui/browser_iterator.h"
17 #include "chrome/browser/ui/tabs/tab_strip_model.h"
18 #include "content/public/browser/navigation_details.h"
19 #include "content/public/browser/notification_service.h"
20 #include "content/public/browser/notification_source.h"
21 #include "content/public/browser/web_contents.h"
22 #include "extensions/browser/api/declarative/rules_registry_service.h"
23 #include "extensions/browser/extension_registry.h"
24 #include "extensions/browser/extension_system.h"
26 using url_matcher::URLMatcherConditionSet
;
28 namespace extensions
{
39 // Used to coalesce multiple requests for evaluation into a zero or one actual
40 // evaluations (depending on the EvaluationDisposition). This is required for
41 // correctness when multiple trackers respond to the same event. Otherwise,
42 // executing the request from the first tracker will be done before the tracked
43 // state has been updated for the other trackers.
44 class ChromeContentRulesRegistry::EvaluationScope
{
46 // Default disposition is PERFORM_EVALUATION.
47 explicit EvaluationScope(ChromeContentRulesRegistry
* registry
);
48 EvaluationScope(ChromeContentRulesRegistry
* registry
,
49 EvaluationDisposition disposition
);
53 ChromeContentRulesRegistry
* const registry_
;
54 const EvaluationDisposition previous_disposition_
;
56 DISALLOW_COPY_AND_ASSIGN(EvaluationScope
);
59 ChromeContentRulesRegistry::EvaluationScope::EvaluationScope(
60 ChromeContentRulesRegistry
* registry
)
61 : EvaluationScope(registry
, DEFER_REQUESTS
) {}
63 ChromeContentRulesRegistry::EvaluationScope::EvaluationScope(
64 ChromeContentRulesRegistry
* registry
,
65 EvaluationDisposition disposition
)
66 : registry_(registry
),
67 previous_disposition_(registry_
->evaluation_disposition_
) {
68 DCHECK_NE(EVALUATE_REQUESTS
, disposition
);
69 registry_
->evaluation_disposition_
= disposition
;
72 ChromeContentRulesRegistry::EvaluationScope::~EvaluationScope() {
73 registry_
->evaluation_disposition_
= previous_disposition_
;
74 if (registry_
->evaluation_disposition_
== EVALUATE_REQUESTS
) {
75 for (content::WebContents
* tab
: registry_
->evaluation_pending_
)
76 registry_
->EvaluateConditionsForTab(tab
);
77 registry_
->evaluation_pending_
.clear();
82 // ChromeContentRulesRegistry
85 ChromeContentRulesRegistry::ChromeContentRulesRegistry(
86 content::BrowserContext
* browser_context
,
87 RulesCacheDelegate
* cache_delegate
)
88 : ContentRulesRegistry(browser_context
,
89 declarative_content_constants::kOnPageChanged
,
90 content::BrowserThread::UI
,
92 RulesRegistryService::kDefaultRulesRegistryID
),
93 page_url_condition_tracker_(browser_context
, this),
94 css_condition_tracker_(browser_context
, this),
95 is_bookmarked_condition_tracker_(browser_context
, this),
96 evaluation_disposition_(EVALUATE_REQUESTS
) {
98 content::NOTIFICATION_WEB_CONTENTS_DESTROYED
,
99 content::NotificationService::AllBrowserContextsAndSources());
102 void ChromeContentRulesRegistry::Observe(
104 const content::NotificationSource
& source
,
105 const content::NotificationDetails
& details
) {
107 case content::NOTIFICATION_WEB_CONTENTS_DESTROYED
: {
108 content::WebContents
* tab
=
109 content::Source
<content::WebContents
>(source
).ptr();
110 // Note that neither non-tab WebContents nor tabs from other browser
111 // contexts will be in the map.
112 active_rules_
.erase(tab
);
118 void ChromeContentRulesRegistry::RequestEvaluation(
119 content::WebContents
* contents
) {
120 switch (evaluation_disposition_
) {
121 case EVALUATE_REQUESTS
:
122 EvaluateConditionsForTab(contents
);
125 evaluation_pending_
.insert(contents
);
127 case IGNORE_REQUESTS
:
132 bool ChromeContentRulesRegistry::ShouldManageConditionsForBrowserContext(
133 content::BrowserContext
* context
) {
134 return ManagingRulesForBrowserContext(context
);
137 void ChromeContentRulesRegistry::MonitorWebContentsForRuleEvaluation(
138 content::WebContents
* contents
) {
139 // We rely on active_rules_ to have a key-value pair for |contents| to know
140 // which WebContents we are working with.
141 active_rules_
[contents
] = std::set
<const ContentRule
*>();
143 EvaluationScope
evaluation_scope(this);
144 page_url_condition_tracker_
.TrackForWebContents(contents
);
145 css_condition_tracker_
.TrackForWebContents(contents
);
146 is_bookmarked_condition_tracker_
.TrackForWebContents(contents
);
149 void ChromeContentRulesRegistry::DidNavigateMainFrame(
150 content::WebContents
* contents
,
151 const content::LoadCommittedDetails
& details
,
152 const content::FrameNavigateParams
& params
) {
153 if (ContainsKey(active_rules_
, contents
)) {
154 EvaluationScope
evaluation_scope(this);
155 page_url_condition_tracker_
.OnWebContentsNavigation(contents
, details
,
157 css_condition_tracker_
.OnWebContentsNavigation(contents
, details
, params
);
158 is_bookmarked_condition_tracker_
.OnWebContentsNavigation(contents
, details
,
163 ChromeContentRulesRegistry::ContentRule::ContentRule(
164 const Extension
* extension
,
165 ScopedVector
<const ContentCondition
> conditions
,
166 ScopedVector
<const ContentAction
> actions
,
168 : extension(extension
),
169 conditions(conditions
.Pass()),
170 actions(actions
.Pass()),
174 ChromeContentRulesRegistry::ContentRule::~ContentRule() {}
176 scoped_ptr
<const ChromeContentRulesRegistry::ContentRule
>
177 ChromeContentRulesRegistry::CreateRule(const Extension
* extension
,
178 const api::events::Rule
& api_rule
,
179 std::string
* error
) {
180 ScopedVector
<const ContentCondition
> conditions
;
181 for (const linked_ptr
<base::Value
>& value
: api_rule
.conditions
) {
182 conditions
.push_back(
183 ContentCondition::Create(
185 page_url_condition_tracker_
.condition_factory(),
189 return scoped_ptr
<ContentRule
>();
192 ScopedVector
<const ContentAction
> actions
;
193 for (const linked_ptr
<base::Value
>& value
: api_rule
.actions
) {
194 actions
.push_back(ContentAction::Create(browser_context(), extension
,
197 return scoped_ptr
<ContentRule
>();
200 // Note: |api_rule| may contain tags, but these are ignored.
202 return make_scoped_ptr(
203 new ContentRule(extension
, conditions
.Pass(), actions
.Pass(),
204 *api_rule
.priority
));
207 bool ChromeContentRulesRegistry::ManagingRulesForBrowserContext(
208 content::BrowserContext
* context
) {
209 // Manage both the normal context and incognito contexts associated with it.
210 return Profile::FromBrowserContext(context
)->GetOriginalProfile() ==
211 Profile::FromBrowserContext(browser_context());
214 std::set
<const ChromeContentRulesRegistry::ContentRule
*>
215 ChromeContentRulesRegistry::GetMatches(
216 const RendererContentMatchData
& renderer_data
,
217 bool is_incognito_renderer
) const {
218 // Create the set of conditions that have matching page URL predicates
219 // according to |renderer_data.page_url_matches|.
220 std::set
<const ContentCondition
*> conditions_with_page_url_match
;
221 for (URLMatcherConditionSet::ID url_match
: renderer_data
.page_url_matches
) {
222 RuleAndConditionForURLMatcherId::const_iterator rule_condition_iter
=
223 rule_and_conditions_for_match_id_
.find(url_match
);
224 CHECK(rule_condition_iter
!= rule_and_conditions_for_match_id_
.end());
226 const ContentCondition
* condition
= rule_condition_iter
->second
.second
;
227 conditions_with_page_url_match
.insert(condition
);
230 std::set
<const ContentRule
*> matching_rules
;
231 for (const RulesMap::value_type
& rule_id_rule_pair
: content_rules_
) {
232 const ContentRule
* rule
= rule_id_rule_pair
.second
.get();
233 if (is_incognito_renderer
&&
234 !ShouldEvaluateExtensionRulesForIncognitoRenderer(rule
->extension
))
237 for (const ContentCondition
* condition
: rule
->conditions
) {
238 // If we already know the URL matcher predicate within |condition| doesn't
239 // match, we don't need to check whether the other predicates are
241 if (ContainsKey(url_matcher_conditions_
, condition
) &&
242 !ContainsKey(conditions_with_page_url_match
, condition
))
245 if (!condition
->IsFulfilled(renderer_data
))
248 matching_rules
.insert(rule
);
251 return matching_rules
;
254 std::string
ChromeContentRulesRegistry::AddRulesImpl(
255 const std::string
& extension_id
,
256 const std::vector
<linked_ptr
<api::events::Rule
>>& rules
) {
257 EvaluationScope
evaluation_scope(this);
258 const Extension
* extension
= ExtensionRegistry::Get(browser_context())
259 ->GetInstalledExtension(extension_id
);
260 DCHECK(extension
) << "Must have extension with id " << extension_id
;
263 RulesMap new_content_rules
;
265 for (const linked_ptr
<api::events::Rule
>& rule
: rules
) {
266 ExtensionRuleIdPair
rule_id(extension
, *rule
->id
);
267 DCHECK(content_rules_
.find(rule_id
) == content_rules_
.end());
269 scoped_ptr
<const ContentRule
> content_rule(CreateRule(
273 if (!error
.empty()) {
274 // Clean up temporary condition sets created during rule creation.
275 page_url_condition_tracker_
.ClearUnusedConditionSets();
278 DCHECK(content_rule
);
280 new_content_rules
[rule_id
] = make_linked_ptr(content_rule
.release());
283 // Wohoo, everything worked fine.
284 content_rules_
.insert(new_content_rules
.begin(), new_content_rules
.end());
286 // Record the URL matcher condition set to rule condition pair mappings, and
287 // register URL patterns in the URL matcher.
288 URLMatcherConditionSet::Vector all_new_condition_sets
;
289 for (const RulesMap::value_type
& rule_id_rule_pair
: new_content_rules
) {
290 const linked_ptr
<const ContentRule
>& rule
= rule_id_rule_pair
.second
;
291 for (const ContentCondition
* condition
: rule
->conditions
) {
292 if (condition
->url_matcher_condition_set()) {
293 URLMatcherConditionSet::ID condition_set_id
=
294 condition
->url_matcher_condition_set()->id();
295 rule_and_conditions_for_match_id_
[condition_set_id
] =
296 std::make_pair(rule
.get(), condition
);
297 all_new_condition_sets
.push_back(
298 condition
->url_matcher_condition_set());
299 url_matcher_conditions_
.insert(condition
);
303 page_url_condition_tracker_
.AddConditionSets(
304 all_new_condition_sets
);
306 UpdateCssSelectorsFromRules();
308 // Request evaluation for all WebContents, under the assumption that a
309 // non-empty condition has been added.
310 for (auto web_contents_rules_pair
: active_rules_
)
311 EvaluateConditionsForTab(web_contents_rules_pair
.first
);
313 return std::string();
316 std::string
ChromeContentRulesRegistry::RemoveRulesImpl(
317 const std::string
& extension_id
,
318 const std::vector
<std::string
>& rule_identifiers
) {
319 // Ignore evaluation requests in this function because it reverts actions on
320 // any active rules itself. Otherwise, we run the risk of reverting the same
321 // rule multiple times.
322 EvaluationScope
evaluation_scope(this, IGNORE_REQUESTS
);
323 // URLMatcherConditionSet IDs that can be removed from URLMatcher.
324 std::vector
<URLMatcherConditionSet::ID
> condition_set_ids_to_remove
;
326 const Extension
* extension
= ExtensionRegistry::Get(browser_context())
327 ->GetInstalledExtension(extension_id
);
328 for (const std::string
& id
: rule_identifiers
) {
329 // Skip unknown rules.
330 RulesMap::iterator content_rules_entry
=
331 content_rules_
.find(std::make_pair(extension
, id
));
332 if (content_rules_entry
== content_rules_
.end())
335 // Remove state associated with URL matcher conditions, and collect the
336 // URLMatcherConditionSet::IDs to remove later.
337 URLMatcherConditionSet::Vector condition_sets
;
338 const ContentRule
* rule
= content_rules_entry
->second
.get();
339 for (const ContentCondition
* condition
: rule
->conditions
) {
340 if (condition
->url_matcher_condition_set()) {
341 URLMatcherConditionSet::ID condition_set_id
=
342 condition
->url_matcher_condition_set()->id();
343 condition_set_ids_to_remove
.push_back(condition_set_id
);
344 rule_and_conditions_for_match_id_
.erase(condition_set_id
);
345 url_matcher_conditions_
.erase(condition
);
349 // Remove the ContentRule from active_rules_.
350 for (auto& tab_rules_pair
: active_rules_
) {
351 if (ContainsKey(tab_rules_pair
.second
, rule
)) {
352 ContentAction::ApplyInfo apply_info
=
353 {rule
->extension
, browser_context(), tab_rules_pair
.first
,
355 for (const ContentAction
* action
: rule
->actions
)
356 action
->Revert(apply_info
);
357 tab_rules_pair
.second
.erase(rule
);
361 // Remove reference to actual rule.
362 content_rules_
.erase(content_rules_entry
);
365 // Clear URLMatcher of condition sets that are not needed any more.
366 page_url_condition_tracker_
.RemoveConditionSets(
367 condition_set_ids_to_remove
);
369 UpdateCssSelectorsFromRules();
371 // Request evaluation for all WebContents, under the assumption that a
372 // non-empty condition has been removed.
373 for (auto web_contents_rules_pair
: active_rules_
)
374 EvaluateConditionsForTab(web_contents_rules_pair
.first
);
376 return std::string();
379 std::string
ChromeContentRulesRegistry::RemoveAllRulesImpl(
380 const std::string
& extension_id
) {
381 // Search all identifiers of rules that belong to extension |extension_id|.
382 std::vector
<std::string
> rule_identifiers
;
383 for (const RulesMap::value_type
& id_rule_pair
: content_rules_
) {
384 const ExtensionRuleIdPair
& extension_rule_id_pair
= id_rule_pair
.first
;
385 if (extension_rule_id_pair
.first
->id() == extension_id
)
386 rule_identifiers
.push_back(extension_rule_id_pair
.second
);
389 return RemoveRulesImpl(extension_id
, rule_identifiers
);
392 void ChromeContentRulesRegistry::UpdateCssSelectorsFromRules() {
393 std::set
<std::string
> css_selectors
; // We rely on this being sorted.
394 for (const RulesMap::value_type
& id_rule_pair
: content_rules_
) {
395 const ContentRule
* rule
= id_rule_pair
.second
.get();
396 for (const ContentCondition
* condition
: rule
->conditions
) {
397 const std::vector
<std::string
>& condition_css_selectors
=
398 condition
->css_selectors();
399 css_selectors
.insert(condition_css_selectors
.begin(),
400 condition_css_selectors
.end());
404 css_condition_tracker_
.SetWatchedCssSelectors(css_selectors
);
407 void ChromeContentRulesRegistry::EvaluateConditionsForTab(
408 content::WebContents
* tab
) {
409 extensions::RendererContentMatchData renderer_data
;
410 page_url_condition_tracker_
.GetMatches(tab
, &renderer_data
.page_url_matches
);
411 css_condition_tracker_
.GetMatchingCssSelectors(tab
,
412 &renderer_data
.css_selectors
);
413 renderer_data
.is_bookmarked
=
414 is_bookmarked_condition_tracker_
.IsUrlBookmarked(tab
);
415 std::set
<const ContentRule
*> matching_rules
=
416 GetMatches(renderer_data
, tab
->GetBrowserContext()->IsOffTheRecord());
417 if (matching_rules
.empty() && !ContainsKey(active_rules_
, tab
))
420 std::set
<const ContentRule
*>& prev_matching_rules
= active_rules_
[tab
];
421 for (const ContentRule
* rule
: matching_rules
) {
422 ContentAction::ApplyInfo apply_info
=
423 {rule
->extension
, browser_context(), tab
, rule
->priority
};
424 if (!ContainsKey(prev_matching_rules
, rule
)) {
425 for (const ContentAction
* action
: rule
->actions
)
426 action
->Apply(apply_info
);
428 for (const ContentAction
* action
: rule
->actions
)
429 action
->Reapply(apply_info
);
432 for (const ContentRule
* rule
: prev_matching_rules
) {
433 if (!ContainsKey(matching_rules
, rule
)) {
434 ContentAction::ApplyInfo apply_info
=
435 {rule
->extension
, browser_context(), tab
, rule
->priority
};
436 for (const ContentAction
* action
: rule
->actions
)
437 action
->Revert(apply_info
);
441 if (matching_rules
.empty())
442 active_rules_
[tab
].clear();
444 swap(matching_rules
, prev_matching_rules
);
448 ChromeContentRulesRegistry::ShouldEvaluateExtensionRulesForIncognitoRenderer(
449 const Extension
* extension
) const {
450 if (!util::IsIncognitoEnabled(extension
->id(), browser_context()))
453 // Split-mode incognito extensions register their rules with separate
454 // RulesRegistries per Original/OffTheRecord browser contexts, whereas
455 // spanning-mode extensions share the Original browser context.
456 if (util::CanCrossIncognito(extension
, browser_context())) {
457 // The extension uses spanning mode incognito. No rules should have been
458 // registered for the extension in the OffTheRecord registry so
459 // execution for that registry should never reach this point.
460 CHECK(!browser_context()->IsOffTheRecord());
462 // The extension uses split mode incognito. Both the Original and
463 // OffTheRecord registries may have (separate) rules for this extension.
464 // Since we're looking at an incognito renderer, so only the OffTheRecord
465 // registry should process its rules.
466 if (!browser_context()->IsOffTheRecord())
473 bool ChromeContentRulesRegistry::IsEmpty() const {
474 return rule_and_conditions_for_match_id_
.empty() && content_rules_
.empty() &&
475 page_url_condition_tracker_
.IsEmpty();
478 void ChromeContentRulesRegistry::UpdateMatchingCssSelectorsForTesting(
479 content::WebContents
* contents
,
480 const std::vector
<std::string
>& matching_css_selectors
) {
481 css_condition_tracker_
.UpdateMatchingCssSelectorsForTesting(
483 matching_css_selectors
);
486 size_t ChromeContentRulesRegistry::GetActiveRulesCountForTesting() {
488 for (auto web_contents_rules_pair
: active_rules_
)
489 count
+= web_contents_rules_pair
.second
.size();
493 ChromeContentRulesRegistry::~ChromeContentRulesRegistry() {
496 } // namespace extensions