1 // Copyright 2015 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/declarative_content_css_condition_tracker.h"
7 #include "base/stl_util.h"
8 #include "base/strings/stringprintf.h"
9 #include "base/values.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/api/declarative_content/content_predicate_evaluator.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 "extensions/common/extension_messages.h"
19 #include "ipc/ipc_message.h"
20 #include "ipc/ipc_message_macros.h"
22 namespace extensions
{
26 const char kInvalidTypeOfParameter
[] = "Attribute '%s' has an invalid type";
31 // DeclarativeContentCssPredicate
34 DeclarativeContentCssPredicate::~DeclarativeContentCssPredicate() {
38 scoped_ptr
<DeclarativeContentCssPredicate
>
39 DeclarativeContentCssPredicate::Create(
40 ContentPredicateEvaluator
* evaluator
,
41 const base::Value
& value
,
43 std::vector
<std::string
> css_rules
;
44 const base::ListValue
* css_rules_value
= nullptr;
45 if (value
.GetAsList(&css_rules_value
)) {
46 for (size_t i
= 0; i
< css_rules_value
->GetSize(); ++i
) {
48 if (!css_rules_value
->GetString(i
, &css_rule
)) {
49 *error
= base::StringPrintf(kInvalidTypeOfParameter
,
50 declarative_content_constants::kCss
);
51 return scoped_ptr
<DeclarativeContentCssPredicate
>();
53 css_rules
.push_back(css_rule
);
56 *error
= base::StringPrintf(kInvalidTypeOfParameter
,
57 declarative_content_constants::kCss
);
58 return scoped_ptr
<DeclarativeContentCssPredicate
>();
61 return !css_rules
.empty() ?
63 new DeclarativeContentCssPredicate(evaluator
, css_rules
)) :
64 scoped_ptr
<DeclarativeContentCssPredicate
>();
67 ContentPredicateEvaluator
*
68 DeclarativeContentCssPredicate::GetEvaluator() const {
72 DeclarativeContentCssPredicate::DeclarativeContentCssPredicate(
73 ContentPredicateEvaluator
* evaluator
,
74 const std::vector
<std::string
>& css_selectors
)
75 : evaluator_(evaluator
),
76 css_selectors_(css_selectors
) {
77 DCHECK(!css_selectors
.empty());
81 // PerWebContentsTracker
84 DeclarativeContentCssConditionTracker::PerWebContentsTracker::
85 PerWebContentsTracker(
86 content::WebContents
* contents
,
87 const RequestEvaluationCallback
& request_evaluation
,
88 const WebContentsDestroyedCallback
& web_contents_destroyed
)
89 : WebContentsObserver(contents
),
90 request_evaluation_(request_evaluation
),
91 web_contents_destroyed_(web_contents_destroyed
) {
94 DeclarativeContentCssConditionTracker::PerWebContentsTracker::
95 ~PerWebContentsTracker() {
98 void DeclarativeContentCssConditionTracker::PerWebContentsTracker::
99 OnWebContentsNavigation(const content::LoadCommittedDetails
& details
,
100 const content::FrameNavigateParams
& params
) {
101 if (details
.is_in_page
) {
102 // Within-page navigations don't change the set of elements that
103 // exist, and we only support filtering on the top-level URL, so
104 // this can't change which rules match.
108 // Top-level navigation produces a new document. Initially, the
109 // document's empty, so no CSS rules match. The renderer will send
110 // an ExtensionHostMsg_OnWatchedPageChange later if any CSS rules
112 matching_css_selectors_
.clear();
113 request_evaluation_
.Run(web_contents());
117 DeclarativeContentCssConditionTracker::PerWebContentsTracker::
119 const IPC::Message
& message
) {
121 IPC_BEGIN_MESSAGE_MAP(PerWebContentsTracker
, message
)
122 IPC_MESSAGE_HANDLER(ExtensionHostMsg_OnWatchedPageChange
,
124 IPC_MESSAGE_UNHANDLED(handled
= false)
125 IPC_END_MESSAGE_MAP()
129 void DeclarativeContentCssConditionTracker::PerWebContentsTracker::
130 WebContentsDestroyed() {
131 web_contents_destroyed_
.Run(web_contents());
135 DeclarativeContentCssConditionTracker::PerWebContentsTracker::
137 const std::vector
<std::string
>& css_selectors
) {
138 matching_css_selectors_
.clear();
139 matching_css_selectors_
.insert(css_selectors
.begin(), css_selectors
.end());
140 request_evaluation_
.Run(web_contents());
144 // DeclarativeContentCssConditionTracker
147 DeclarativeContentCssConditionTracker::DeclarativeContentCssConditionTracker(
149 : delegate_(delegate
) {
151 content::NOTIFICATION_RENDERER_PROCESS_CREATED
,
152 content::NotificationService::AllBrowserContextsAndSources());
155 DeclarativeContentCssConditionTracker::
156 ~DeclarativeContentCssConditionTracker() {}
158 std::string
DeclarativeContentCssConditionTracker::
159 GetPredicateApiAttributeName() const {
160 return declarative_content_constants::kCss
;
163 scoped_ptr
<const ContentPredicate
> DeclarativeContentCssConditionTracker::
164 CreatePredicate(const Extension
* extension
,
165 const base::Value
& value
,
166 std::string
* error
) {
167 return DeclarativeContentCssPredicate::Create(this, value
, error
);
170 void DeclarativeContentCssConditionTracker::TrackPredicates(
171 const std::map
<const void*, std::vector
<const ContentPredicate
*>>&
173 bool watched_selectors_updated
= false;
174 for (const auto& group_predicates_pair
: predicates
) {
175 for (const ContentPredicate
* predicate
: group_predicates_pair
.second
) {
176 DCHECK_EQ(this, predicate
->GetEvaluator());
177 const DeclarativeContentCssPredicate
* typed_predicate
=
178 static_cast<const DeclarativeContentCssPredicate
*>(predicate
);
179 tracked_predicates_
[group_predicates_pair
.first
].push_back(
181 for (const std::string
& selector
: typed_predicate
->css_selectors()) {
182 if (watched_css_selector_predicate_count_
[selector
]++ == 0)
183 watched_selectors_updated
= true;
188 if (watched_selectors_updated
)
189 UpdateRenderersWatchedCssSelectors(GetWatchedCssSelectors());
192 void DeclarativeContentCssConditionTracker::StopTrackingPredicates(
193 const std::vector
<const void*>& predicate_groups
) {
194 bool watched_selectors_updated
= false;
195 for (const void* group
: predicate_groups
) {
196 auto loc
= tracked_predicates_
.find(group
);
197 if (loc
== tracked_predicates_
.end())
199 for (const DeclarativeContentCssPredicate
* predicate
: loc
->second
) {
200 for (const std::string
& selector
: predicate
->css_selectors()) {
201 std::map
<std::string
, int>::iterator loc
=
202 watched_css_selector_predicate_count_
.find(selector
);
203 DCHECK(loc
!= watched_css_selector_predicate_count_
.end());
204 if (--loc
->second
== 0) {
205 watched_css_selector_predicate_count_
.erase(loc
);
206 watched_selectors_updated
= true;
210 tracked_predicates_
.erase(group
);
213 if (watched_selectors_updated
)
214 UpdateRenderersWatchedCssSelectors(GetWatchedCssSelectors());
217 void DeclarativeContentCssConditionTracker::TrackForWebContents(
218 content::WebContents
* contents
) {
219 per_web_contents_tracker_
[contents
] =
220 make_linked_ptr(new PerWebContentsTracker(
222 base::Bind(&Delegate::RequestEvaluation
, base::Unretained(delegate_
)),
223 base::Bind(&DeclarativeContentCssConditionTracker::
224 DeletePerWebContentsTracker
,
225 base::Unretained(this))));
226 // Note: the condition is always false until we receive OnWatchedPageChange,
227 // so there's no need to evaluate it here.
230 void DeclarativeContentCssConditionTracker::OnWebContentsNavigation(
231 content::WebContents
* contents
,
232 const content::LoadCommittedDetails
& details
,
233 const content::FrameNavigateParams
& params
) {
234 DCHECK(ContainsKey(per_web_contents_tracker_
, contents
));
235 per_web_contents_tracker_
[contents
]->OnWebContentsNavigation(details
, params
);
238 bool DeclarativeContentCssConditionTracker::EvaluatePredicate(
239 const ContentPredicate
* predicate
,
240 content::WebContents
* tab
) const {
241 DCHECK_EQ(this, predicate
->GetEvaluator());
242 const DeclarativeContentCssPredicate
* typed_predicate
=
243 static_cast<const DeclarativeContentCssPredicate
*>(predicate
);
244 auto loc
= per_web_contents_tracker_
.find(tab
);
245 DCHECK(loc
!= per_web_contents_tracker_
.end());
246 const base::hash_set
<std::string
>& matching_css_selectors
=
247 loc
->second
->matching_css_selectors();
248 for (const std::string
& predicate_css_selector
:
249 typed_predicate
->css_selectors()) {
250 if (!ContainsKey(matching_css_selectors
, predicate_css_selector
))
257 void DeclarativeContentCssConditionTracker::Observe(
259 const content::NotificationSource
& source
,
260 const content::NotificationDetails
& details
) {
262 case content::NOTIFICATION_RENDERER_PROCESS_CREATED
: {
263 content::RenderProcessHost
* process
=
264 content::Source
<content::RenderProcessHost
>(source
).ptr();
265 InstructRenderProcessIfManagingBrowserContext(process
,
266 GetWatchedCssSelectors());
272 void DeclarativeContentCssConditionTracker::
273 UpdateRenderersWatchedCssSelectors(
274 const std::vector
<std::string
>& watched_css_selectors
) {
275 for (content::RenderProcessHost::iterator
it(
276 content::RenderProcessHost::AllHostsIterator());
279 InstructRenderProcessIfManagingBrowserContext(it
.GetCurrentValue(),
280 watched_css_selectors
);
284 std::vector
<std::string
> DeclarativeContentCssConditionTracker::
285 GetWatchedCssSelectors() const {
286 std::vector
<std::string
> selectors
;
287 selectors
.reserve(watched_css_selector_predicate_count_
.size());
288 for (const std::pair
<std::string
, int>& selector_pair
:
289 watched_css_selector_predicate_count_
) {
290 selectors
.push_back(selector_pair
.first
);
295 void DeclarativeContentCssConditionTracker::
296 InstructRenderProcessIfManagingBrowserContext(
297 content::RenderProcessHost
* process
,
298 std::vector
<std::string
> watched_css_selectors
) {
299 if (delegate_
->ShouldManageConditionsForBrowserContext(
300 process
->GetBrowserContext())) {
301 process
->Send(new ExtensionMsg_WatchPages(watched_css_selectors
));
305 void DeclarativeContentCssConditionTracker::DeletePerWebContentsTracker(
306 content::WebContents
* contents
) {
307 DCHECK(ContainsKey(per_web_contents_tracker_
, contents
));
308 per_web_contents_tracker_
.erase(contents
);
311 } // namespace extensions