Pin Chrome's shortcut to the Win10 Start menu on install and OS upgrade.
[chromium-blink-merge.git] / chrome / browser / extensions / api / declarative_content / chrome_content_rules_registry.cc
blobe9bda2e6e59578f80dd21cf3c7daa80ad0c1e986
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 "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 {
30 namespace {
33 } // namespace
36 // EvaluationScope
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 {
45 public:
46 // Default disposition is PERFORM_EVALUATION.
47 explicit EvaluationScope(ChromeContentRulesRegistry* registry);
48 EvaluationScope(ChromeContentRulesRegistry* registry,
49 EvaluationDisposition disposition);
50 ~EvaluationScope();
52 private:
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,
91 cache_delegate,
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) {
97 registrar_.Add(this,
98 content::NOTIFICATION_WEB_CONTENTS_DESTROYED,
99 content::NotificationService::AllBrowserContextsAndSources());
102 void ChromeContentRulesRegistry::Observe(
103 int type,
104 const content::NotificationSource& source,
105 const content::NotificationDetails& details) {
106 switch (type) {
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);
113 break;
118 void ChromeContentRulesRegistry::RequestEvaluation(
119 content::WebContents* contents) {
120 switch (evaluation_disposition_) {
121 case EVALUATE_REQUESTS:
122 EvaluateConditionsForTab(contents);
123 break;
124 case DEFER_REQUESTS:
125 evaluation_pending_.insert(contents);
126 break;
127 case IGNORE_REQUESTS:
128 break;
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,
156 params);
157 css_condition_tracker_.OnWebContentsNavigation(contents, details, params);
158 is_bookmarked_condition_tracker_.OnWebContentsNavigation(contents, details,
159 params);
163 ChromeContentRulesRegistry::ContentRule::ContentRule(
164 const Extension* extension,
165 ScopedVector<const ContentCondition> conditions,
166 ScopedVector<const ContentAction> actions,
167 int priority)
168 : extension(extension),
169 conditions(conditions.Pass()),
170 actions(actions.Pass()),
171 priority(priority) {
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(
184 extension,
185 page_url_condition_tracker_.condition_factory(),
186 *value,
187 error));
188 if (!error->empty())
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,
195 *value, error));
196 if (!error->empty())
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()))
234 continue;
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());
244 } else {
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())
250 continue;
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;
269 std::string error;
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(
277 extension,
278 *rule,
279 &error));
280 if (!error.empty()) {
281 // Clean up temporary condition sets created during rule creation.
282 page_url_condition_tracker_.ClearUnusedConditionSets();
283 return error;
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())
336 continue;
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,
353 rule->priority};
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))
412 return;
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);
421 } else {
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();
437 else
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(
450 contents,
451 matching_css_selectors);
454 size_t ChromeContentRulesRegistry::GetActiveRulesCountForTesting() {
455 size_t count = 0;
456 for (auto web_contents_rules_pair : active_rules_)
457 count += web_contents_rules_pair.second.size();
458 return count;
461 ChromeContentRulesRegistry::~ChromeContentRulesRegistry() {
464 } // namespace extensions