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 "chrome/browser/chrome_notification_types.h"
11 #include "chrome/browser/extensions/api/declarative_content/content_constants.h"
12 #include "chrome/browser/extensions/extension_util.h"
13 #include "chrome/browser/profiles/profile.h"
14 #include "chrome/browser/ui/browser.h"
15 #include "chrome/browser/ui/browser_iterator.h"
16 #include "chrome/browser/ui/tabs/tab_strip_model.h"
17 #include "content/public/browser/navigation_details.h"
18 #include "content/public/browser/notification_service.h"
19 #include "content/public/browser/notification_source.h"
20 #include "content/public/browser/web_contents.h"
21 #include "extensions/browser/api/declarative/rules_registry_service.h"
22 #include "extensions/browser/extension_registry.h"
23 #include "extensions/browser/extension_system.h"
25 namespace extensions
{
31 // Used to coalesce multiple requests for evaluation into a zero or one actual
32 // evaluations (depending on the EvaluationDisposition). This is required for
33 // correctness when multiple trackers respond to the same event. Otherwise,
34 // executing the request from the first tracker will be done before the tracked
35 // state has been updated for the other trackers.
36 class ChromeContentRulesRegistry::EvaluationScope
{
38 // Default disposition is PERFORM_EVALUATION.
39 explicit EvaluationScope(ChromeContentRulesRegistry
* registry
);
40 EvaluationScope(ChromeContentRulesRegistry
* registry
,
41 EvaluationDisposition disposition
);
45 ChromeContentRulesRegistry
* const registry_
;
46 const EvaluationDisposition previous_disposition_
;
48 DISALLOW_COPY_AND_ASSIGN(EvaluationScope
);
51 ChromeContentRulesRegistry::EvaluationScope::EvaluationScope(
52 ChromeContentRulesRegistry
* registry
)
53 : EvaluationScope(registry
, DEFER_REQUESTS
) {}
55 ChromeContentRulesRegistry::EvaluationScope::EvaluationScope(
56 ChromeContentRulesRegistry
* registry
,
57 EvaluationDisposition disposition
)
58 : registry_(registry
),
59 previous_disposition_(registry_
->evaluation_disposition_
) {
60 DCHECK_NE(EVALUATE_REQUESTS
, disposition
);
61 registry_
->evaluation_disposition_
= disposition
;
64 ChromeContentRulesRegistry::EvaluationScope::~EvaluationScope() {
65 registry_
->evaluation_disposition_
= previous_disposition_
;
66 if (registry_
->evaluation_disposition_
== EVALUATE_REQUESTS
) {
67 for (content::WebContents
* tab
: registry_
->evaluation_pending_
)
68 registry_
->EvaluateConditionsForTab(tab
);
69 registry_
->evaluation_pending_
.clear();
74 // ChromeContentRulesRegistry
77 ChromeContentRulesRegistry::ChromeContentRulesRegistry(
78 content::BrowserContext
* browser_context
,
79 RulesCacheDelegate
* cache_delegate
,
80 const PredicateEvaluatorsFactory
& evaluators_factory
)
81 : ContentRulesRegistry(browser_context
,
82 declarative_content_constants::kOnPageChanged
,
83 content::BrowserThread::UI
,
85 RulesRegistryService::kDefaultRulesRegistryID
),
86 evaluators_(evaluators_factory
.Run(this)),
87 evaluation_disposition_(EVALUATE_REQUESTS
) {
89 content::NOTIFICATION_WEB_CONTENTS_DESTROYED
,
90 content::NotificationService::AllBrowserContextsAndSources());
93 void ChromeContentRulesRegistry::Observe(
95 const content::NotificationSource
& source
,
96 const content::NotificationDetails
& details
) {
98 case content::NOTIFICATION_WEB_CONTENTS_DESTROYED
: {
99 content::WebContents
* tab
=
100 content::Source
<content::WebContents
>(source
).ptr();
101 // Note that neither non-tab WebContents nor tabs from other browser
102 // contexts will be in the map.
103 active_rules_
.erase(tab
);
109 void ChromeContentRulesRegistry::RequestEvaluation(
110 content::WebContents
* contents
) {
111 switch (evaluation_disposition_
) {
112 case EVALUATE_REQUESTS
:
113 EvaluateConditionsForTab(contents
);
116 evaluation_pending_
.insert(contents
);
118 case IGNORE_REQUESTS
:
123 bool ChromeContentRulesRegistry::ShouldManageConditionsForBrowserContext(
124 content::BrowserContext
* context
) {
125 return ManagingRulesForBrowserContext(context
);
128 void ChromeContentRulesRegistry::MonitorWebContentsForRuleEvaluation(
129 content::WebContents
* contents
) {
130 // We rely on active_rules_ to have a key-value pair for |contents| to know
131 // which WebContents we are working with.
132 active_rules_
[contents
] = std::set
<const ContentRule
*>();
134 EvaluationScope
evaluation_scope(this);
135 for (ContentPredicateEvaluator
* evaluator
: evaluators_
)
136 evaluator
->TrackForWebContents(contents
);
139 void ChromeContentRulesRegistry::DidNavigateMainFrame(
140 content::WebContents
* contents
,
141 const content::LoadCommittedDetails
& details
,
142 const content::FrameNavigateParams
& params
) {
143 if (ContainsKey(active_rules_
, contents
)) {
144 EvaluationScope
evaluation_scope(this);
145 for (ContentPredicateEvaluator
* evaluator
: evaluators_
)
146 evaluator
->OnWebContentsNavigation(contents
, details
, params
);
150 ChromeContentRulesRegistry::ContentRule::ContentRule(
151 const Extension
* extension
,
152 ScopedVector
<const ContentCondition
> conditions
,
153 ScopedVector
<const ContentAction
> actions
,
155 : extension(extension
),
156 conditions(conditions
.Pass()),
157 actions(actions
.Pass()),
161 ChromeContentRulesRegistry::ContentRule::~ContentRule() {}
163 scoped_ptr
<const ChromeContentRulesRegistry::ContentRule
>
164 ChromeContentRulesRegistry::CreateRule(
165 const Extension
* extension
,
166 const std::map
<std::string
, ContentPredicateFactory
*>& predicate_factories
,
167 const api::events::Rule
& api_rule
,
168 std::string
* error
) {
169 ScopedVector
<const ContentCondition
> conditions
;
170 for (const linked_ptr
<base::Value
>& value
: api_rule
.conditions
) {
171 conditions
.push_back(
172 CreateContentCondition(extension
, predicate_factories
, *value
, error
));
174 return scoped_ptr
<ContentRule
>();
177 ScopedVector
<const ContentAction
> actions
;
178 for (const linked_ptr
<base::Value
>& value
: api_rule
.actions
) {
179 actions
.push_back(ContentAction::Create(browser_context(), extension
,
182 return scoped_ptr
<ContentRule
>();
185 // Note: |api_rule| may contain tags, but these are ignored.
187 return make_scoped_ptr(
188 new ContentRule(extension
, conditions
.Pass(), actions
.Pass(),
189 *api_rule
.priority
));
192 bool ChromeContentRulesRegistry::ManagingRulesForBrowserContext(
193 content::BrowserContext
* context
) {
194 // Manage both the normal context and incognito contexts associated with it.
195 return Profile::FromBrowserContext(context
)->GetOriginalProfile() ==
196 Profile::FromBrowserContext(browser_context());
200 bool ChromeContentRulesRegistry::EvaluateConditionForTab(
201 const ContentCondition
* condition
,
202 content::WebContents
* tab
) {
203 for (const ContentPredicate
* predicate
: condition
->predicates
) {
205 !predicate
->IsIgnored() &&
206 !predicate
->GetEvaluator()->EvaluatePredicate(predicate
, tab
)) {
214 std::set
<const ChromeContentRulesRegistry::ContentRule
*>
215 ChromeContentRulesRegistry::GetMatchingRules(content::WebContents
* tab
) const {
216 const bool is_incognito_tab
= tab
->GetBrowserContext()->IsOffTheRecord();
217 std::set
<const ContentRule
*> matching_rules
;
218 for (const RulesMap::value_type
& rule_id_rule_pair
: content_rules_
) {
219 const ContentRule
* rule
= rule_id_rule_pair
.second
.get();
220 if (is_incognito_tab
&&
221 !ShouldEvaluateExtensionRulesForIncognitoRenderer(rule
->extension
))
224 for (const ContentCondition
* condition
: rule
->conditions
) {
225 if (EvaluateConditionForTab(condition
, tab
))
226 matching_rules
.insert(rule
);
229 return matching_rules
;
232 std::string
ChromeContentRulesRegistry::AddRulesImpl(
233 const std::string
& extension_id
,
234 const std::vector
<linked_ptr
<api::events::Rule
>>& api_rules
) {
235 EvaluationScope
evaluation_scope(this);
236 const Extension
* extension
= ExtensionRegistry::Get(browser_context())
237 ->GetInstalledExtension(extension_id
);
242 std::map
<ContentPredicateEvaluator
*,
243 std::map
<const void*, std::vector
<const ContentPredicate
*>>>
246 std::map
<std::string
, ContentPredicateFactory
*> predicate_factories
;
247 for (ContentPredicateEvaluator
* evaluator
: evaluators_
)
248 predicate_factories
[evaluator
->GetPredicateApiAttributeName()] = evaluator
;
250 for (const linked_ptr
<api::events::Rule
>& api_rule
: api_rules
) {
251 ExtensionIdRuleIdPair
rule_id(extension_id
, *api_rule
->id
);
252 DCHECK(content_rules_
.find(rule_id
) == content_rules_
.end());
254 scoped_ptr
<const ContentRule
> rule(
255 CreateRule(extension
, predicate_factories
, *api_rule
, &error
));
256 if (!error
.empty()) {
257 // Notify evaluators that none of the created predicates will be tracked
259 for (ContentPredicateEvaluator
* evaluator
: evaluators_
) {
260 if (!new_predicates
[evaluator
].empty()) {
261 evaluator
->TrackPredicates(
262 std::map
<const void*, std::vector
<const ContentPredicate
*>>());
270 // Group predicates by evaluator and rule, so we can later notify the
271 // evaluators that they have new predicates to manage.
272 for (const ContentCondition
* condition
: rule
->conditions
) {
273 for (const ContentPredicate
* predicate
: condition
->predicates
) {
275 new_predicates
[predicate
->GetEvaluator()][rule
.get()].push_back(
281 new_rules
[rule_id
] = make_linked_ptr(rule
.release());
284 // Notify the evaluators about their new predicates.
285 for (ContentPredicateEvaluator
* evaluator
: evaluators_
)
286 evaluator
->TrackPredicates(new_predicates
[evaluator
]);
288 // Wohoo, everything worked fine.
289 content_rules_
.insert(new_rules
.begin(), new_rules
.end());
291 // Request evaluation for all WebContents, under the assumption that a
292 // non-empty condition has been added.
293 for (const auto& web_contents_rules_pair
: active_rules_
)
294 RequestEvaluation(web_contents_rules_pair
.first
);
296 return std::string();
299 std::string
ChromeContentRulesRegistry::RemoveRulesImpl(
300 const std::string
& extension_id
,
301 const std::vector
<std::string
>& rule_identifiers
) {
302 // Ignore evaluation requests in this function because it reverts actions on
303 // any active rules itself. Otherwise, we run the risk of reverting the same
304 // rule multiple times.
305 EvaluationScope
evaluation_scope(this, IGNORE_REQUESTS
);
307 std::vector
<RulesMap::iterator
> rules_to_erase
;
308 std::vector
<const void*> predicate_groups_to_stop_tracking
;
309 for (const std::string
& id
: rule_identifiers
) {
310 // Skip unknown rules.
311 RulesMap::iterator content_rules_entry
=
312 content_rules_
.find(std::make_pair(extension_id
, id
));
313 if (content_rules_entry
== content_rules_
.end())
316 const ContentRule
* rule
= content_rules_entry
->second
.get();
318 // Remove the ContentRule from active_rules_.
319 for (auto& tab_rules_pair
: active_rules_
) {
320 if (ContainsKey(tab_rules_pair
.second
, rule
)) {
321 ContentAction::ApplyInfo apply_info
=
322 {rule
->extension
, browser_context(), tab_rules_pair
.first
,
324 for (const ContentAction
* action
: rule
->actions
)
325 action
->Revert(apply_info
);
326 tab_rules_pair
.second
.erase(rule
);
330 rules_to_erase
.push_back(content_rules_entry
);
331 predicate_groups_to_stop_tracking
.push_back(rule
);
334 // Notify the evaluators to stop tracking the predicates that will be removed.
335 for (ContentPredicateEvaluator
* evaluator
: evaluators_
)
336 evaluator
->StopTrackingPredicates(predicate_groups_to_stop_tracking
);
339 for (RulesMap::iterator it
: rules_to_erase
)
340 content_rules_
.erase(it
);
342 return std::string();
345 std::string
ChromeContentRulesRegistry::RemoveAllRulesImpl(
346 const std::string
& extension_id
) {
347 // Search all identifiers of rules that belong to extension |extension_id|.
348 std::vector
<std::string
> rule_identifiers
;
349 for (const RulesMap::value_type
& id_rule_pair
: content_rules_
) {
350 const ExtensionIdRuleIdPair
& extension_id_rule_id_pair
= id_rule_pair
.first
;
351 if (extension_id_rule_id_pair
.first
== extension_id
)
352 rule_identifiers
.push_back(extension_id_rule_id_pair
.second
);
355 return RemoveRulesImpl(extension_id
, rule_identifiers
);
358 void ChromeContentRulesRegistry::EvaluateConditionsForTab(
359 content::WebContents
* tab
) {
360 std::set
<const ContentRule
*> matching_rules
= GetMatchingRules(tab
);
361 if (matching_rules
.empty() && !ContainsKey(active_rules_
, tab
))
364 std::set
<const ContentRule
*>& prev_matching_rules
= active_rules_
[tab
];
365 for (const ContentRule
* rule
: matching_rules
) {
366 ContentAction::ApplyInfo apply_info
=
367 {rule
->extension
, browser_context(), tab
, rule
->priority
};
368 if (!ContainsKey(prev_matching_rules
, rule
)) {
369 for (const ContentAction
* action
: rule
->actions
)
370 action
->Apply(apply_info
);
372 for (const ContentAction
* action
: rule
->actions
)
373 action
->Reapply(apply_info
);
376 for (const ContentRule
* rule
: prev_matching_rules
) {
377 if (!ContainsKey(matching_rules
, rule
)) {
378 ContentAction::ApplyInfo apply_info
=
379 {rule
->extension
, browser_context(), tab
, rule
->priority
};
380 for (const ContentAction
* action
: rule
->actions
)
381 action
->Revert(apply_info
);
385 if (matching_rules
.empty())
386 active_rules_
[tab
].clear();
388 swap(matching_rules
, prev_matching_rules
);
392 ChromeContentRulesRegistry::ShouldEvaluateExtensionRulesForIncognitoRenderer(
393 const Extension
* extension
) const {
394 if (!util::IsIncognitoEnabled(extension
->id(), browser_context()))
397 // Split-mode incognito extensions register their rules with separate
398 // RulesRegistries per Original/OffTheRecord browser contexts, whereas
399 // spanning-mode extensions share the Original browser context.
400 if (util::CanCrossIncognito(extension
, browser_context())) {
401 // The extension uses spanning mode incognito. No rules should have been
402 // registered for the extension in the OffTheRecord registry so
403 // execution for that registry should never reach this point.
404 CHECK(!browser_context()->IsOffTheRecord());
406 // The extension uses split mode incognito. Both the Original and
407 // OffTheRecord registries may have (separate) rules for this extension.
408 // Since we're looking at an incognito renderer, so only the OffTheRecord
409 // registry should process its rules.
410 if (!browser_context()->IsOffTheRecord())
417 size_t ChromeContentRulesRegistry::GetActiveRulesCountForTesting() {
419 for (const auto& web_contents_rules_pair
: active_rules_
)
420 count
+= web_contents_rules_pair
.second
.size();
424 ChromeContentRulesRegistry::~ChromeContentRulesRegistry() {
427 } // namespace extensions