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 std::set
<const ContentRule
*> matching_rules
;
220 // First get the (rule, condition) pairs that have URL matches. Then for
221 // those, evaluate whether all the sub-conditions are fulfilled. Since a rule
222 // matches if *any* of its conditions match, immediately record the rule
223 // as matching if all the sub-conditions are fulfilled.
224 for (URLMatcherConditionSet::ID url_match
: renderer_data
.page_url_matches
) {
225 RuleAndConditionForURLMatcherId::const_iterator rule_condition_iter
=
226 rule_and_conditions_for_match_id_
.find(url_match
);
227 CHECK(rule_condition_iter
!= rule_and_conditions_for_match_id_
.end());
229 const std::pair
<const ContentRule
*, const ContentCondition
*>&
230 rule_condition_pair
= rule_condition_iter
->second
;
231 const ContentRule
* rule
= rule_condition_pair
.first
;
232 if (is_incognito_renderer
) {
233 if (!util::IsIncognitoEnabled(rule
->extension
->id(), browser_context()))
236 // Split-mode incognito extensions register their rules with separate
237 // RulesRegistries per Original/OffTheRecord browser contexts, whereas
238 // spanning-mode extensions share the Original browser context.
239 if (util::CanCrossIncognito(rule
->extension
, browser_context())) {
240 // The extension uses spanning mode incognito. No rules should have been
241 // registered for the extension in the OffTheRecord registry so
242 // execution for that registry should never reach this point.
243 CHECK(!browser_context()->IsOffTheRecord());
245 // The extension uses split mode incognito. Both the Original and
246 // OffTheRecord registries may have (separate) rules for this extension.
247 // We've established above that we are looking at an incognito renderer,
248 // so only the OffTheRecord registry should process its rules.
249 if (!browser_context()->IsOffTheRecord())
254 const ContentCondition
* condition
= rule_condition_pair
.second
;
255 if (condition
->IsFulfilled(renderer_data
))
256 matching_rules
.insert(rule
);
258 return matching_rules
;
261 std::string
ChromeContentRulesRegistry::AddRulesImpl(
262 const std::string
& extension_id
,
263 const std::vector
<linked_ptr
<api::events::Rule
>>& rules
) {
264 EvaluationScope
evaluation_scope(this);
265 const Extension
* extension
= ExtensionRegistry::Get(browser_context())
266 ->GetInstalledExtension(extension_id
);
267 DCHECK(extension
) << "Must have extension with id " << extension_id
;
270 RulesMap new_content_rules
;
272 for (const linked_ptr
<api::events::Rule
>& rule
: rules
) {
273 ExtensionRuleIdPair
rule_id(extension
, *rule
->id
);
274 DCHECK(content_rules_
.find(rule_id
) == content_rules_
.end());
276 scoped_ptr
<const ContentRule
> content_rule(CreateRule(
280 if (!error
.empty()) {
281 // Clean up temporary condition sets created during rule creation.
282 page_url_condition_tracker_
.ClearUnusedConditionSets();
285 DCHECK(content_rule
);
287 new_content_rules
[rule_id
] = make_linked_ptr(content_rule
.release());
290 // Wohoo, everything worked fine.
291 content_rules_
.insert(new_content_rules
.begin(), new_content_rules
.end());
293 // Create the triggers.
294 for (const RulesMap::value_type
& rule_id_rule_pair
: new_content_rules
) {
295 const linked_ptr
<const ContentRule
>& rule
= rule_id_rule_pair
.second
;
296 for (const ContentCondition
* condition
: rule
->conditions
) {
297 URLMatcherConditionSet::ID condition_set_id
=
298 condition
->url_matcher_condition_set()->id();
299 rule_and_conditions_for_match_id_
[condition_set_id
] =
300 std::make_pair(rule
.get(), condition
);
304 // Register url patterns in the URL matcher.
305 URLMatcherConditionSet::Vector all_new_condition_sets
;
306 for (const RulesMap::value_type
& rule_id_rule_pair
: new_content_rules
) {
307 const linked_ptr
<const ContentRule
>& rule
= rule_id_rule_pair
.second
;
308 for (const ContentCondition
* condition
: rule
->conditions
)
309 all_new_condition_sets
.push_back(condition
->url_matcher_condition_set());
311 page_url_condition_tracker_
.AddConditionSets(
312 all_new_condition_sets
);
314 UpdateCssSelectorsFromRules();
316 return std::string();
319 std::string
ChromeContentRulesRegistry::RemoveRulesImpl(
320 const std::string
& extension_id
,
321 const std::vector
<std::string
>& rule_identifiers
) {
322 // Ignore evaluation requests in this function because it reverts actions on
323 // any active rules itself. Otherwise, we run the risk of reverting the same
324 // rule multiple times.
325 EvaluationScope
evaluation_scope(this, IGNORE_REQUESTS
);
326 // URLMatcherConditionSet IDs that can be removed from URLMatcher.
327 std::vector
<URLMatcherConditionSet::ID
> condition_set_ids_to_remove
;
329 const Extension
* extension
= ExtensionRegistry::Get(browser_context())
330 ->GetInstalledExtension(extension_id
);
331 for (const std::string
& id
: rule_identifiers
) {
332 // Skip unknown rules.
333 RulesMap::iterator content_rules_entry
=
334 content_rules_
.find(std::make_pair(extension
, id
));
335 if (content_rules_entry
== content_rules_
.end())
338 // Remove all triggers but collect their IDs.
339 URLMatcherConditionSet::Vector condition_sets
;
340 const ContentRule
* rule
= content_rules_entry
->second
.get();
341 for (const ContentCondition
* condition
: rule
->conditions
) {
342 URLMatcherConditionSet::ID condition_set_id
=
343 condition
->url_matcher_condition_set()->id();
344 condition_set_ids_to_remove
.push_back(condition_set_id
);
345 rule_and_conditions_for_match_id_
.erase(condition_set_id
);
348 // Remove the ContentRule from active_rules_.
349 for (auto& tab_rules_pair
: active_rules_
) {
350 if (ContainsKey(tab_rules_pair
.second
, rule
)) {
351 ContentAction::ApplyInfo apply_info
=
352 {rule
->extension
, browser_context(), tab_rules_pair
.first
,
354 for (const ContentAction
* action
: rule
->actions
)
355 action
->Revert(apply_info
);
356 tab_rules_pair
.second
.erase(rule
);
360 // Remove reference to actual rule.
361 content_rules_
.erase(content_rules_entry
);
364 // Clear URLMatcher of condition sets that are not needed any more.
365 page_url_condition_tracker_
.RemoveConditionSets(
366 condition_set_ids_to_remove
);
368 UpdateCssSelectorsFromRules();
370 return std::string();
373 std::string
ChromeContentRulesRegistry::RemoveAllRulesImpl(
374 const std::string
& extension_id
) {
375 // Search all identifiers of rules that belong to extension |extension_id|.
376 std::vector
<std::string
> rule_identifiers
;
377 for (const RulesMap::value_type
& id_rule_pair
: content_rules_
) {
378 const ExtensionRuleIdPair
& extension_rule_id_pair
= id_rule_pair
.first
;
379 if (extension_rule_id_pair
.first
->id() == extension_id
)
380 rule_identifiers
.push_back(extension_rule_id_pair
.second
);
383 return RemoveRulesImpl(extension_id
, rule_identifiers
);
386 void ChromeContentRulesRegistry::UpdateCssSelectorsFromRules() {
387 std::set
<std::string
> css_selectors
; // We rely on this being sorted.
388 for (const RulesMap::value_type
& id_rule_pair
: content_rules_
) {
389 const ContentRule
* rule
= id_rule_pair
.second
.get();
390 for (const ContentCondition
* condition
: rule
->conditions
) {
391 const std::vector
<std::string
>& condition_css_selectors
=
392 condition
->css_selectors();
393 css_selectors
.insert(condition_css_selectors
.begin(),
394 condition_css_selectors
.end());
398 css_condition_tracker_
.SetWatchedCssSelectors(css_selectors
);
401 void ChromeContentRulesRegistry::EvaluateConditionsForTab(
402 content::WebContents
* tab
) {
403 extensions::RendererContentMatchData renderer_data
;
404 page_url_condition_tracker_
.GetMatches(tab
, &renderer_data
.page_url_matches
);
405 css_condition_tracker_
.GetMatchingCssSelectors(tab
,
406 &renderer_data
.css_selectors
);
407 renderer_data
.is_bookmarked
=
408 is_bookmarked_condition_tracker_
.IsUrlBookmarked(tab
);
409 std::set
<const ContentRule
*> matching_rules
=
410 GetMatches(renderer_data
, tab
->GetBrowserContext()->IsOffTheRecord());
411 if (matching_rules
.empty() && !ContainsKey(active_rules_
, tab
))
414 std::set
<const ContentRule
*>& prev_matching_rules
= active_rules_
[tab
];
415 for (const ContentRule
* rule
: matching_rules
) {
416 ContentAction::ApplyInfo apply_info
=
417 {rule
->extension
, browser_context(), tab
, rule
->priority
};
418 if (!ContainsKey(prev_matching_rules
, rule
)) {
419 for (const ContentAction
* action
: rule
->actions
)
420 action
->Apply(apply_info
);
422 for (const ContentAction
* action
: rule
->actions
)
423 action
->Reapply(apply_info
);
426 for (const ContentRule
* rule
: prev_matching_rules
) {
427 if (!ContainsKey(matching_rules
, rule
)) {
428 ContentAction::ApplyInfo apply_info
=
429 {rule
->extension
, browser_context(), tab
, rule
->priority
};
430 for (const ContentAction
* action
: rule
->actions
)
431 action
->Revert(apply_info
);
435 if (matching_rules
.empty())
436 active_rules_
[tab
].clear();
438 swap(matching_rules
, prev_matching_rules
);
441 bool ChromeContentRulesRegistry::IsEmpty() const {
442 return rule_and_conditions_for_match_id_
.empty() && content_rules_
.empty() &&
443 page_url_condition_tracker_
.IsEmpty();
446 void ChromeContentRulesRegistry::UpdateMatchingCssSelectorsForTesting(
447 content::WebContents
* contents
,
448 const std::vector
<std::string
>& matching_css_selectors
) {
449 css_condition_tracker_
.UpdateMatchingCssSelectorsForTesting(
451 matching_css_selectors
);
454 size_t ChromeContentRulesRegistry::GetActiveRulesCountForTesting() {
456 for (auto web_contents_rules_pair
: active_rules_
)
457 count
+= web_contents_rules_pair
.second
.size();
461 ChromeContentRulesRegistry::~ChromeContentRulesRegistry() {
464 } // namespace extensions