Elim cr-checkbox
[chromium-blink-merge.git] / chrome / browser / extensions / api / declarative_content / chrome_content_rules_registry.cc
blob4a21a4059d469328047b8e8ce813f0df0382d090
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 "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 {
28 // EvaluationScope
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 {
37 public:
38 // Default disposition is PERFORM_EVALUATION.
39 explicit EvaluationScope(ChromeContentRulesRegistry* registry);
40 EvaluationScope(ChromeContentRulesRegistry* registry,
41 EvaluationDisposition disposition);
42 ~EvaluationScope();
44 private:
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,
84 cache_delegate,
85 RulesRegistryService::kDefaultRulesRegistryID),
86 evaluators_(evaluators_factory.Run(this)),
87 evaluation_disposition_(EVALUATE_REQUESTS) {
88 registrar_.Add(this,
89 content::NOTIFICATION_WEB_CONTENTS_DESTROYED,
90 content::NotificationService::AllBrowserContextsAndSources());
93 void ChromeContentRulesRegistry::Observe(
94 int type,
95 const content::NotificationSource& source,
96 const content::NotificationDetails& details) {
97 switch (type) {
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);
104 break;
109 void ChromeContentRulesRegistry::RequestEvaluation(
110 content::WebContents* contents) {
111 switch (evaluation_disposition_) {
112 case EVALUATE_REQUESTS:
113 EvaluateConditionsForTab(contents);
114 break;
115 case DEFER_REQUESTS:
116 evaluation_pending_.insert(contents);
117 break;
118 case IGNORE_REQUESTS:
119 break;
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,
154 int priority)
155 : extension(extension),
156 conditions(conditions.Pass()),
157 actions(actions.Pass()),
158 priority(priority) {
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));
173 if (!error->empty())
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,
180 *value, error));
181 if (!error->empty())
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());
199 // static
200 bool ChromeContentRulesRegistry::EvaluateConditionForTab(
201 const ContentCondition* condition,
202 content::WebContents* tab) {
203 for (const ContentPredicate* predicate : condition->predicates) {
204 if (predicate &&
205 !predicate->IsIgnored() &&
206 !predicate->GetEvaluator()->EvaluatePredicate(predicate, tab)) {
207 return false;
211 return true;
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))
222 continue;
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);
238 DCHECK(extension);
240 std::string error;
241 RulesMap new_rules;
242 std::map<ContentPredicateEvaluator*,
243 std::map<const void*, std::vector<const ContentPredicate*>>>
244 new_predicates;
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
258 // after all.
259 for (ContentPredicateEvaluator* evaluator : evaluators_) {
260 if (!new_predicates[evaluator].empty()) {
261 evaluator->TrackPredicates(
262 std::map<const void*, std::vector<const ContentPredicate*>>());
266 return error;
268 DCHECK(rule);
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) {
274 if (predicate) {
275 new_predicates[predicate->GetEvaluator()][rule.get()].push_back(
276 predicate);
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())
314 continue;
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,
323 rule->priority};
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);
338 // Remove the rules.
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))
362 return;
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);
371 } else {
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();
387 else
388 swap(matching_rules, prev_matching_rules);
391 bool
392 ChromeContentRulesRegistry::ShouldEvaluateExtensionRulesForIncognitoRenderer(
393 const Extension* extension) const {
394 if (!util::IsIncognitoEnabled(extension->id(), browser_context()))
395 return false;
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());
405 } else {
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())
411 return false;
414 return true;
417 size_t ChromeContentRulesRegistry::GetActiveRulesCountForTesting() {
418 size_t count = 0;
419 for (const auto& web_contents_rules_pair : active_rules_)
420 count += web_contents_rules_pair.second.size();
421 return count;
424 ChromeContentRulesRegistry::~ChromeContentRulesRegistry() {
427 } // namespace extensions