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 "chrome/browser/chrome_notification_types.h"
8 #include "chrome/browser/extensions/api/declarative_content/content_action.h"
9 #include "chrome/browser/extensions/api/declarative_content/content_condition.h"
10 #include "chrome/browser/extensions/api/declarative_content/content_constants.h"
11 #include "chrome/browser/extensions/extension_tab_util.h"
12 #include "content/public/browser/navigation_details.h"
13 #include "content/public/browser/notification_service.h"
14 #include "content/public/browser/notification_source.h"
15 #include "content/public/browser/render_process_host.h"
16 #include "content/public/browser/web_contents.h"
17 #include "extensions/browser/api/declarative/rules_registry_service.h"
18 #include "extensions/browser/extension_registry.h"
19 #include "extensions/browser/extension_system.h"
20 #include "extensions/common/extension_messages.h"
22 using url_matcher::URLMatcherConditionSet
;
24 namespace extensions
{
26 ChromeContentRulesRegistry::ChromeContentRulesRegistry(
27 content::BrowserContext
* browser_context
,
28 RulesCacheDelegate
* cache_delegate
)
29 : ContentRulesRegistry(browser_context
,
30 declarative_content_constants::kOnPageChanged
,
31 content::BrowserThread::UI
,
33 RulesRegistryService::kDefaultRulesRegistryID
) {
34 extension_info_map_
= ExtensionSystem::Get(browser_context
)->info_map();
37 content::NOTIFICATION_RENDERER_PROCESS_CREATED
,
38 content::NotificationService::AllBrowserContextsAndSources());
40 content::NOTIFICATION_WEB_CONTENTS_DESTROYED
,
41 content::NotificationService::AllBrowserContextsAndSources());
44 void ChromeContentRulesRegistry::Observe(
46 const content::NotificationSource
& source
,
47 const content::NotificationDetails
& details
) {
49 case content::NOTIFICATION_RENDERER_PROCESS_CREATED
: {
50 content::RenderProcessHost
* process
=
51 content::Source
<content::RenderProcessHost
>(source
).ptr();
52 if (process
->GetBrowserContext() == browser_context())
53 InstructRenderProcess(process
);
56 case content::NOTIFICATION_WEB_CONTENTS_DESTROYED
: {
57 content::WebContents
* tab
=
58 content::Source
<content::WebContents
>(source
).ptr();
59 // GetTabId() returns -1 for non-tab WebContents, which won't be
60 // in the map. Similarly, tabs from other browser_contexts won't be in
62 active_rules_
.erase(ExtensionTabUtil::GetTabId(tab
));
68 void ChromeContentRulesRegistry::Apply(
69 content::WebContents
* contents
,
70 const std::vector
<std::string
>& matching_css_selectors
) {
71 const int tab_id
= ExtensionTabUtil::GetTabId(contents
);
72 RendererContentMatchData renderer_data
;
73 renderer_data
.page_url_matches
= url_matcher_
.MatchURL(contents
->GetURL());
74 renderer_data
.css_selectors
.insert(matching_css_selectors
.begin(),
75 matching_css_selectors
.end());
76 std::set
<ContentRule
*> matching_rules
= GetMatches(renderer_data
);
77 if (matching_rules
.empty() && !ContainsKey(active_rules_
, tab_id
))
80 std::set
<ContentRule
*>& prev_matching_rules
= active_rules_
[tab_id
];
81 ContentAction::ApplyInfo apply_info
= {browser_context(), contents
};
82 for (std::set
<ContentRule
*>::const_iterator it
= matching_rules
.begin();
83 it
!= matching_rules
.end();
85 apply_info
.priority
= (*it
)->priority();
86 if (!ContainsKey(prev_matching_rules
, *it
)) {
87 (*it
)->actions().Apply((*it
)->extension_id(), base::Time(), &apply_info
);
89 (*it
)->actions().Reapply(
90 (*it
)->extension_id(), base::Time(), &apply_info
);
93 for (std::set
<ContentRule
*>::const_iterator it
= prev_matching_rules
.begin();
94 it
!= prev_matching_rules
.end();
96 if (!ContainsKey(matching_rules
, *it
)) {
97 apply_info
.priority
= (*it
)->priority();
98 (*it
)->actions().Revert((*it
)->extension_id(), base::Time(), &apply_info
);
102 if (matching_rules
.empty())
103 active_rules_
.erase(tab_id
);
105 swap(matching_rules
, prev_matching_rules
);
108 void ChromeContentRulesRegistry::DidNavigateMainFrame(
109 content::WebContents
* contents
,
110 const content::LoadCommittedDetails
& details
,
111 const content::FrameNavigateParams
& params
) {
112 if (details
.is_in_page
) {
113 // Within-page navigations don't change the set of elements that
114 // exist, and we only support filtering on the top-level URL, so
115 // this can't change which rules match.
119 // Top-level navigation produces a new document. Initially, the
120 // document's empty, so no CSS rules match. The renderer will send
121 // an ExtensionHostMsg_OnWatchedPageChange later if any CSS rules
123 std::vector
<std::string
> no_css_selectors
;
124 Apply(contents
, no_css_selectors
);
127 std::set
<ContentRule
*> ChromeContentRulesRegistry::GetMatches(
128 const RendererContentMatchData
& renderer_data
) const {
129 std::set
<ContentRule
*> result
;
131 // Then we need to check for each of these, whether the other
132 // attributes are also fulfilled.
133 for (std::set
<URLMatcherConditionSet::ID
>::iterator url_match
=
134 renderer_data
.page_url_matches
.begin();
135 url_match
!= renderer_data
.page_url_matches
.end();
137 URLMatcherIdToRule::const_iterator rule_iter
=
138 match_id_to_rule_
.find(*url_match
);
139 CHECK(rule_iter
!= match_id_to_rule_
.end());
141 ContentRule
* rule
= rule_iter
->second
;
142 if (rule
->conditions().IsFulfilled(*url_match
, renderer_data
))
148 std::string
ChromeContentRulesRegistry::AddRulesImpl(
149 const std::string
& extension_id
,
150 const std::vector
<linked_ptr
<RulesRegistry::Rule
> >& rules
) {
151 const Extension
* extension
=
152 ExtensionRegistry::Get(browser_context())
153 ->GetExtensionById(extension_id
, ExtensionRegistry::EVERYTHING
);
154 DCHECK(extension
) << "Must have extension with id " << extension_id
;
156 base::Time extension_installation_time
=
157 GetExtensionInstallationTime(extension_id
);
160 RulesMap new_content_rules
;
162 for (std::vector
<linked_ptr
<RulesRegistry::Rule
> >::const_iterator rule
=
166 ContentRule::GlobalRuleId
rule_id(extension_id
, *(*rule
)->id
);
167 DCHECK(content_rules_
.find(rule_id
) == content_rules_
.end());
169 scoped_ptr
<ContentRule
> content_rule(
170 ContentRule::Create(url_matcher_
.condition_factory(), browser_context(),
171 extension
, extension_installation_time
, *rule
,
172 ContentRule::ConsistencyChecker(), &error
));
173 if (!error
.empty()) {
174 // Clean up temporary condition sets created during rule creation.
175 url_matcher_
.ClearUnusedConditionSets();
178 DCHECK(content_rule
);
180 new_content_rules
[rule_id
] = make_linked_ptr(content_rule
.release());
183 // Wohoo, everything worked fine.
184 content_rules_
.insert(new_content_rules
.begin(), new_content_rules
.end());
186 // Create the triggers.
187 for (RulesMap::iterator i
= new_content_rules
.begin();
188 i
!= new_content_rules
.end();
190 URLMatcherConditionSet::Vector url_condition_sets
;
191 const ContentConditionSet
& conditions
= i
->second
->conditions();
192 conditions
.GetURLMatcherConditionSets(&url_condition_sets
);
193 for (URLMatcherConditionSet::Vector::iterator j
=
194 url_condition_sets
.begin();
195 j
!= url_condition_sets
.end();
197 match_id_to_rule_
[(*j
)->id()] = i
->second
.get();
201 // Register url patterns in url_matcher_.
202 URLMatcherConditionSet::Vector all_new_condition_sets
;
203 for (RulesMap::iterator i
= new_content_rules
.begin();
204 i
!= new_content_rules
.end();
206 i
->second
->conditions().GetURLMatcherConditionSets(&all_new_condition_sets
);
208 url_matcher_
.AddConditionSets(all_new_condition_sets
);
210 UpdateConditionCache();
212 return std::string();
215 std::string
ChromeContentRulesRegistry::RemoveRulesImpl(
216 const std::string
& extension_id
,
217 const std::vector
<std::string
>& rule_identifiers
) {
218 // URLMatcherConditionSet IDs that can be removed from URLMatcher.
219 std::vector
<URLMatcherConditionSet::ID
> remove_from_url_matcher
;
221 for (std::vector
<std::string
>::const_iterator i
= rule_identifiers
.begin();
222 i
!= rule_identifiers
.end();
224 ContentRule::GlobalRuleId
rule_id(extension_id
, *i
);
226 // Skip unknown rules.
227 RulesMap::iterator content_rules_entry
= content_rules_
.find(rule_id
);
228 if (content_rules_entry
== content_rules_
.end())
231 // Remove all triggers but collect their IDs.
232 URLMatcherConditionSet::Vector condition_sets
;
233 ContentRule
* rule
= content_rules_entry
->second
.get();
234 rule
->conditions().GetURLMatcherConditionSets(&condition_sets
);
235 for (URLMatcherConditionSet::Vector::iterator j
= condition_sets
.begin();
236 j
!= condition_sets
.end();
238 remove_from_url_matcher
.push_back((*j
)->id());
239 match_id_to_rule_
.erase((*j
)->id());
242 // Remove the ContentRule from active_rules_.
243 for (std::map
<int, std::set
<ContentRule
*> >::iterator it
=
244 active_rules_
.begin();
245 it
!= active_rules_
.end();
247 if (ContainsKey(it
->second
, rule
)) {
248 content::WebContents
* tab
;
249 if (!ExtensionTabUtil::GetTabById(
250 it
->first
, browser_context(), true, NULL
, NULL
, &tab
, NULL
)) {
251 LOG(DFATAL
) << "Tab id " << it
->first
252 << " still in active_rules_, but tab has been destroyed";
255 ContentAction::ApplyInfo apply_info
= {browser_context(), tab
};
256 rule
->actions().Revert(rule
->extension_id(), base::Time(), &apply_info
);
257 it
->second
.erase(rule
);
261 // Remove reference to actual rule.
262 content_rules_
.erase(content_rules_entry
);
265 // Clear URLMatcher based on condition_set_ids that are not needed any more.
266 url_matcher_
.RemoveConditionSets(remove_from_url_matcher
);
268 UpdateConditionCache();
270 return std::string();
273 std::string
ChromeContentRulesRegistry::RemoveAllRulesImpl(
274 const std::string
& extension_id
) {
275 // Search all identifiers of rules that belong to extension |extension_id|.
276 std::vector
<std::string
> rule_identifiers
;
277 for (RulesMap::iterator i
= content_rules_
.begin(); i
!= content_rules_
.end();
279 const ContentRule::GlobalRuleId
& global_rule_id
= i
->first
;
280 if (global_rule_id
.first
== extension_id
)
281 rule_identifiers
.push_back(global_rule_id
.second
);
284 return RemoveRulesImpl(extension_id
, rule_identifiers
);
287 void ChromeContentRulesRegistry::UpdateConditionCache() {
288 std::set
<std::string
> css_selectors
; // We rely on this being sorted.
289 for (RulesMap::const_iterator i
= content_rules_
.begin();
290 i
!= content_rules_
.end();
292 ContentRule
& rule
= *i
->second
;
293 for (ContentConditionSet::const_iterator condition
=
294 rule
.conditions().begin();
295 condition
!= rule
.conditions().end();
297 const std::vector
<std::string
>& condition_css_selectors
=
298 (*condition
)->css_selectors();
299 css_selectors
.insert(condition_css_selectors
.begin(),
300 condition_css_selectors
.end());
304 if (css_selectors
.size() != watched_css_selectors_
.size() ||
305 !std::equal(css_selectors
.begin(),
307 watched_css_selectors_
.begin())) {
308 watched_css_selectors_
.assign(css_selectors
.begin(), css_selectors
.end());
310 for (content::RenderProcessHost::iterator
it(
311 content::RenderProcessHost::AllHostsIterator());
314 content::RenderProcessHost
* process
= it
.GetCurrentValue();
315 if (process
->GetBrowserContext() == browser_context())
316 InstructRenderProcess(process
);
321 void ChromeContentRulesRegistry::InstructRenderProcess(
322 content::RenderProcessHost
* process
) {
323 process
->Send(new ExtensionMsg_WatchPages(watched_css_selectors_
));
326 bool ChromeContentRulesRegistry::IsEmpty() const {
327 return match_id_to_rule_
.empty() && content_rules_
.empty() &&
328 url_matcher_
.IsEmpty();
331 ChromeContentRulesRegistry::~ChromeContentRulesRegistry() {
334 base::Time
ChromeContentRulesRegistry::GetExtensionInstallationTime(
335 const std::string
& extension_id
) const {
336 if (!extension_info_map_
.get()) // May be NULL during testing.
339 return extension_info_map_
->GetInstallTime(extension_id
);
342 } // namespace extensions