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/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_service.h"
12 #include "chrome/browser/extensions/extension_tab_util.h"
13 #include "chrome/browser/profiles/profile.h"
14 #include "content/public/browser/navigation_details.h"
15 #include "content/public/browser/notification_service.h"
16 #include "content/public/browser/notification_source.h"
17 #include "content/public/browser/render_process_host.h"
18 #include "content/public/browser/web_contents.h"
19 #include "extensions/browser/extension_system.h"
20 #include "extensions/common/extension_messages.h"
22 using url_matcher::URLMatcherConditionSet
;
24 namespace extensions
{
26 ContentRulesRegistry::ContentRulesRegistry(Profile
* profile
,
27 RulesCacheDelegate
* cache_delegate
)
28 : RulesRegistry(profile
,
29 declarative_content_constants::kOnPageChanged
,
30 content::BrowserThread::UI
,
33 extension_info_map_
= ExtensionSystem::Get(profile
)->info_map();
35 registrar_
.Add(this, content::NOTIFICATION_RENDERER_PROCESS_CREATED
,
36 content::NotificationService::AllBrowserContextsAndSources());
37 registrar_
.Add(this, content::NOTIFICATION_WEB_CONTENTS_DESTROYED
,
38 content::NotificationService::AllBrowserContextsAndSources());
41 void ContentRulesRegistry::Observe(
43 const content::NotificationSource
& source
,
44 const content::NotificationDetails
& details
) {
46 case content::NOTIFICATION_RENDERER_PROCESS_CREATED
: {
47 content::RenderProcessHost
* process
=
48 content::Source
<content::RenderProcessHost
>(source
).ptr();
49 if (process
->GetBrowserContext() == profile())
50 InstructRenderProcess(process
);
53 case content::NOTIFICATION_WEB_CONTENTS_DESTROYED
: {
54 content::WebContents
* tab
=
55 content::Source
<content::WebContents
>(source
).ptr();
56 // GetTabId() returns -1 for non-tab WebContents, which won't be
57 // in the map. Similarly, tabs from other profiles won't be in
59 active_rules_
.erase(ExtensionTabUtil::GetTabId(tab
));
65 void ContentRulesRegistry::Apply(
66 content::WebContents
* contents
,
67 const std::vector
<std::string
>& matching_css_selectors
) {
68 const int tab_id
= ExtensionTabUtil::GetTabId(contents
);
69 RendererContentMatchData renderer_data
;
70 renderer_data
.page_url_matches
= url_matcher_
.MatchURL(contents
->GetURL());
71 renderer_data
.css_selectors
.insert(matching_css_selectors
.begin(),
72 matching_css_selectors
.end());
73 std::set
<ContentRule
*> matching_rules
= GetMatches(renderer_data
);
74 if (matching_rules
.empty() && !ContainsKey(active_rules_
, tab_id
))
77 std::set
<ContentRule
*>& prev_matching_rules
= active_rules_
[tab_id
];
78 ContentAction::ApplyInfo apply_info
= {
81 for (std::set
<ContentRule
*>::const_iterator it
= matching_rules
.begin();
82 it
!= matching_rules
.end(); ++it
) {
83 if (!ContainsKey(prev_matching_rules
, *it
))
84 (*it
)->actions().Apply((*it
)->extension_id(), base::Time(), &apply_info
);
86 for (std::set
<ContentRule
*>::const_iterator it
= prev_matching_rules
.begin();
87 it
!= prev_matching_rules
.end(); ++it
) {
88 if (!ContainsKey(matching_rules
, *it
))
89 (*it
)->actions().Revert((*it
)->extension_id(), base::Time(), &apply_info
);
92 if (matching_rules
.empty())
93 active_rules_
.erase(tab_id
);
95 swap(matching_rules
, prev_matching_rules
);
98 void ContentRulesRegistry::DidNavigateMainFrame(
99 content::WebContents
* contents
,
100 const content::LoadCommittedDetails
& details
,
101 const content::FrameNavigateParams
& params
) {
102 if (details
.is_in_page
) {
103 // Within-page navigations don't change the set of elements that
104 // exist, and we only support filtering on the top-level URL, so
105 // this can't change which rules match.
109 // Top-level navigation produces a new document. Initially, the
110 // document's empty, so no CSS rules match. The renderer will send
111 // an ExtensionHostMsg_OnWatchedPageChange later if any CSS rules
113 std::vector
<std::string
> no_css_selectors
;
114 Apply(contents
, no_css_selectors
);
117 std::set
<ContentRule
*>
118 ContentRulesRegistry::GetMatches(
119 const RendererContentMatchData
& renderer_data
) const {
120 std::set
<ContentRule
*> result
;
122 // Then we need to check for each of these, whether the other
123 // attributes are also fulfilled.
124 for (std::set
<URLMatcherConditionSet::ID
>::iterator
125 url_match
= renderer_data
.page_url_matches
.begin();
126 url_match
!= renderer_data
.page_url_matches
.end(); ++url_match
) {
127 URLMatcherIdToRule::const_iterator rule_iter
=
128 match_id_to_rule_
.find(*url_match
);
129 CHECK(rule_iter
!= match_id_to_rule_
.end());
131 ContentRule
* rule
= rule_iter
->second
;
132 if (rule
->conditions().IsFulfilled(*url_match
, renderer_data
))
138 std::string
ContentRulesRegistry::AddRulesImpl(
139 const std::string
& extension_id
,
140 const std::vector
<linked_ptr
<RulesRegistry::Rule
> >& rules
) {
141 ExtensionService
* service
=
142 ExtensionSystem::Get(profile())->extension_service();
143 const Extension
* extension
= service
->GetInstalledExtension(extension_id
);
144 DCHECK(extension
) << "Must have extension with id " << extension_id
;
146 base::Time extension_installation_time
=
147 GetExtensionInstallationTime(extension_id
);
150 RulesMap new_content_rules
;
152 for (std::vector
<linked_ptr
<RulesRegistry::Rule
> >::const_iterator rule
=
153 rules
.begin(); rule
!= rules
.end(); ++rule
) {
154 ContentRule::GlobalRuleId
rule_id(extension_id
, *(*rule
)->id
);
155 DCHECK(content_rules_
.find(rule_id
) == content_rules_
.end());
157 scoped_ptr
<ContentRule
> content_rule(
158 ContentRule::Create(url_matcher_
.condition_factory(),
160 extension_installation_time
,
162 ContentRule::ConsistencyChecker(),
164 if (!error
.empty()) {
165 // Clean up temporary condition sets created during rule creation.
166 url_matcher_
.ClearUnusedConditionSets();
169 DCHECK(content_rule
);
171 new_content_rules
[rule_id
] = make_linked_ptr(content_rule
.release());
174 // Wohoo, everything worked fine.
175 content_rules_
.insert(new_content_rules
.begin(), new_content_rules
.end());
177 // Create the triggers.
178 for (RulesMap::iterator i
= new_content_rules
.begin();
179 i
!= new_content_rules
.end(); ++i
) {
180 URLMatcherConditionSet::Vector url_condition_sets
;
181 const ContentConditionSet
& conditions
= i
->second
->conditions();
182 conditions
.GetURLMatcherConditionSets(&url_condition_sets
);
183 for (URLMatcherConditionSet::Vector::iterator j
=
184 url_condition_sets
.begin(); j
!= url_condition_sets
.end(); ++j
) {
185 match_id_to_rule_
[(*j
)->id()] = i
->second
.get();
189 // Register url patterns in url_matcher_.
190 URLMatcherConditionSet::Vector all_new_condition_sets
;
191 for (RulesMap::iterator i
= new_content_rules
.begin();
192 i
!= new_content_rules
.end(); ++i
) {
193 i
->second
->conditions().GetURLMatcherConditionSets(&all_new_condition_sets
);
195 url_matcher_
.AddConditionSets(all_new_condition_sets
);
197 UpdateConditionCache();
199 return std::string();
202 std::string
ContentRulesRegistry::RemoveRulesImpl(
203 const std::string
& extension_id
,
204 const std::vector
<std::string
>& rule_identifiers
) {
205 // URLMatcherConditionSet IDs that can be removed from URLMatcher.
206 std::vector
<URLMatcherConditionSet::ID
> remove_from_url_matcher
;
208 for (std::vector
<std::string
>::const_iterator i
= rule_identifiers
.begin();
209 i
!= rule_identifiers
.end(); ++i
) {
210 ContentRule::GlobalRuleId
rule_id(extension_id
, *i
);
212 // Skip unknown rules.
213 RulesMap::iterator content_rules_entry
= content_rules_
.find(rule_id
);
214 if (content_rules_entry
== content_rules_
.end())
217 // Remove all triggers but collect their IDs.
218 URLMatcherConditionSet::Vector condition_sets
;
219 ContentRule
* rule
= content_rules_entry
->second
.get();
220 rule
->conditions().GetURLMatcherConditionSets(&condition_sets
);
221 for (URLMatcherConditionSet::Vector::iterator j
= condition_sets
.begin();
222 j
!= condition_sets
.end(); ++j
) {
223 remove_from_url_matcher
.push_back((*j
)->id());
224 match_id_to_rule_
.erase((*j
)->id());
227 // Remove the ContentRule from active_rules_.
228 for (std::map
<int, std::set
<ContentRule
*> >::iterator
229 it
= active_rules_
.begin();
230 it
!= active_rules_
.end(); ++it
) {
231 if (ContainsKey(it
->second
, rule
)) {
232 content::WebContents
* tab
;
233 if (!ExtensionTabUtil::GetTabById(
234 it
->first
, profile(), true, NULL
, NULL
, &tab
, NULL
)) {
235 LOG(DFATAL
) << "Tab id " << it
->first
236 << " still in active_rules_, but tab has been destroyed";
239 ContentAction::ApplyInfo apply_info
= {profile(), tab
};
240 rule
->actions().Revert(rule
->extension_id(), base::Time(), &apply_info
);
241 it
->second
.erase(rule
);
245 // Remove reference to actual rule.
246 content_rules_
.erase(content_rules_entry
);
249 // Clear URLMatcher based on condition_set_ids that are not needed any more.
250 url_matcher_
.RemoveConditionSets(remove_from_url_matcher
);
252 UpdateConditionCache();
254 return std::string();
257 std::string
ContentRulesRegistry::RemoveAllRulesImpl(
258 const std::string
& extension_id
) {
259 // Search all identifiers of rules that belong to extension |extension_id|.
260 std::vector
<std::string
> rule_identifiers
;
261 for (RulesMap::iterator i
= content_rules_
.begin();
262 i
!= content_rules_
.end(); ++i
) {
263 const ContentRule::GlobalRuleId
& global_rule_id
= i
->first
;
264 if (global_rule_id
.first
== extension_id
)
265 rule_identifiers
.push_back(global_rule_id
.second
);
268 return RemoveRulesImpl(extension_id
, rule_identifiers
);
271 void ContentRulesRegistry::UpdateConditionCache() {
272 std::set
<std::string
> css_selectors
; // We rely on this being sorted.
273 for (RulesMap::const_iterator i
= content_rules_
.begin();
274 i
!= content_rules_
.end(); ++i
) {
275 ContentRule
& rule
= *i
->second
;
276 for (ContentConditionSet::const_iterator
277 condition
= rule
.conditions().begin();
278 condition
!= rule
.conditions().end(); ++condition
) {
279 const std::vector
<std::string
>& condition_css_selectors
=
280 (*condition
)->css_selectors();
281 css_selectors
.insert(condition_css_selectors
.begin(),
282 condition_css_selectors
.end());
286 if (css_selectors
.size() != watched_css_selectors_
.size() ||
287 !std::equal(css_selectors
.begin(), css_selectors
.end(),
288 watched_css_selectors_
.begin())) {
289 watched_css_selectors_
.assign(css_selectors
.begin(), css_selectors
.end());
291 for (content::RenderProcessHost::iterator
it(
292 content::RenderProcessHost::AllHostsIterator());
293 !it
.IsAtEnd(); it
.Advance()) {
294 content::RenderProcessHost
* process
= it
.GetCurrentValue();
295 if (process
->GetBrowserContext() == profile())
296 InstructRenderProcess(process
);
301 void ContentRulesRegistry::InstructRenderProcess(
302 content::RenderProcessHost
* process
) {
303 process
->Send(new ExtensionMsg_WatchPages(watched_css_selectors_
));
306 bool ContentRulesRegistry::IsEmpty() const {
307 return match_id_to_rule_
.empty() && content_rules_
.empty() &&
308 url_matcher_
.IsEmpty();
311 ContentRulesRegistry::~ContentRulesRegistry() {}
313 base::Time
ContentRulesRegistry::GetExtensionInstallationTime(
314 const std::string
& extension_id
) const {
315 if (!extension_info_map_
.get()) // May be NULL during testing.
318 return extension_info_map_
->GetInstallTime(extension_id
);
321 } // namespace extensions