Source code

Revision control

Other Tools

1
/* This Source Code Form is subject to the terms of the Mozilla Public
2
* License, v. 2.0. If a copy of the MPL was not distributed with this
3
* file, You can obtain one at https://mozilla.org/MPL/2.0/. */
4
5
//! Collects a series of applicable rules for a given element.
6
7
use crate::applicable_declarations::{ApplicableDeclarationBlock, ApplicableDeclarationList};
8
use crate::dom::{TElement, TNode, TShadowRoot};
9
use crate::properties::{AnimationRules, PropertyDeclarationBlock};
10
use crate::rule_tree::{CascadeLevel, ShadowCascadeOrder};
11
use crate::selector_map::SelectorMap;
12
use crate::selector_parser::PseudoElement;
13
use crate::shared_lock::Locked;
14
use crate::stylesheets::Origin;
15
use crate::stylist::{AuthorStylesEnabled, Rule, RuleInclusion, Stylist};
16
use selectors::matching::{ElementSelectorFlags, MatchingContext, MatchingMode};
17
use servo_arc::ArcBorrow;
18
use smallvec::SmallVec;
19
20
/// This is a bit of a hack so <svg:use> matches the rules of the enclosing
21
/// tree.
22
///
23
/// This function returns the containing shadow host ignoring <svg:use> shadow
24
/// trees, since those match the enclosing tree's rules.
25
///
26
/// Only a handful of places need to really care about this. This is not a
27
/// problem for invalidation and that kind of stuff because they still don't
28
/// match rules based on elements outside of the shadow tree, and because the
29
/// <svg:use> subtrees are immutable and recreated each time the source tree
30
/// changes.
31
///
32
/// We historically allow cross-document <svg:use> to have these rules applied,
33
/// but I think that's not great. Gecko is the only engine supporting that.
34
///
35
/// See https://github.com/w3c/svgwg/issues/504 for the relevant spec
36
/// discussion.
37
#[inline]
38
pub fn containing_shadow_ignoring_svg_use<E: TElement>(
39
element: E,
40
) -> Option<<E::ConcreteNode as TNode>::ConcreteShadowRoot> {
41
let mut shadow = element.containing_shadow()?;
42
loop {
43
let host = shadow.host();
44
let host_is_svg_use_element =
45
host.is_svg_element() && host.local_name() == &*local_name!("use");
46
if !host_is_svg_use_element {
47
return Some(shadow);
48
}
49
debug_assert!(
50
shadow.style_data().is_none(),
51
"We allow no stylesheets in <svg:use> subtrees"
52
);
53
shadow = host.containing_shadow()?;
54
}
55
}
56
57
#[inline]
58
fn sort_rules_from(rules: &mut ApplicableDeclarationList, start: usize) {
59
rules[start..].sort_unstable_by_key(|block| (block.specificity, block.source_order()));
60
}
61
62
/// An object that we use with all the intermediate state needed for the
63
/// cascade.
64
///
65
/// This is done basically to be able to organize the cascade in smaller
66
/// functions, and be able to reason about it easily.
67
pub struct RuleCollector<'a, 'b: 'a, E, F: 'a>
68
where
69
E: TElement,
70
{
71
element: E,
72
rule_hash_target: E,
73
stylist: &'a Stylist,
74
pseudo_element: Option<&'a PseudoElement>,
75
style_attribute: Option<ArcBorrow<'a, Locked<PropertyDeclarationBlock>>>,
76
smil_override: Option<ArcBorrow<'a, Locked<PropertyDeclarationBlock>>>,
77
animation_rules: AnimationRules,
78
rule_inclusion: RuleInclusion,
79
rules: &'a mut ApplicableDeclarationList,
80
context: &'a mut MatchingContext<'b, E::Impl>,
81
flags_setter: &'a mut F,
82
shadow_cascade_order: ShadowCascadeOrder,
83
matches_user_and_author_rules: bool,
84
matches_document_author_rules: bool,
85
}
86
87
impl<'a, 'b: 'a, E, F: 'a> RuleCollector<'a, 'b, E, F>
88
where
89
E: TElement,
90
F: FnMut(&E, ElementSelectorFlags),
91
{
92
/// Trivially construct a new collector.
93
pub fn new(
94
stylist: &'a Stylist,
95
element: E,
96
pseudo_element: Option<&'a PseudoElement>,
97
style_attribute: Option<ArcBorrow<'a, Locked<PropertyDeclarationBlock>>>,
98
smil_override: Option<ArcBorrow<'a, Locked<PropertyDeclarationBlock>>>,
99
animation_rules: AnimationRules,
100
rule_inclusion: RuleInclusion,
101
rules: &'a mut ApplicableDeclarationList,
102
context: &'a mut MatchingContext<'b, E::Impl>,
103
flags_setter: &'a mut F,
104
) -> Self {
105
// When we're matching with matching_mode =
106
// `ForStatelessPseudoeElement`, the "target" for the rule hash is the
107
// element itself, since it's what's generating the pseudo-element.
108
let rule_hash_target = match context.matching_mode() {
109
MatchingMode::ForStatelessPseudoElement => element,
110
MatchingMode::Normal => element.rule_hash_target(),
111
};
112
113
let matches_user_and_author_rules = rule_hash_target.matches_user_and_author_rules();
114
115
// Gecko definitely has pseudo-elements with style attributes, like
116
// ::-moz-color-swatch.
117
debug_assert!(
118
cfg!(feature = "gecko") || style_attribute.is_none() || pseudo_element.is_none(),
119
"Style attributes do not apply to pseudo-elements"
120
);
121
debug_assert!(pseudo_element.map_or(true, |p| !p.is_precomputed()));
122
123
Self {
124
element,
125
rule_hash_target,
126
stylist,
127
pseudo_element,
128
style_attribute,
129
smil_override,
130
animation_rules,
131
rule_inclusion,
132
context,
133
flags_setter,
134
rules,
135
shadow_cascade_order: 0,
136
matches_user_and_author_rules,
137
matches_document_author_rules: matches_user_and_author_rules,
138
}
139
}
140
141
fn collect_stylist_rules(&mut self, origin: Origin) {
142
let cascade_level = match origin {
143
Origin::UserAgent => CascadeLevel::UANormal,
144
Origin::User => CascadeLevel::UserNormal,
145
Origin::Author => CascadeLevel::SameTreeAuthorNormal,
146
};
147
148
let cascade_data = self.stylist.cascade_data().borrow_for_origin(origin);
149
let map = match cascade_data.normal_rules(self.pseudo_element) {
150
Some(m) => m,
151
None => return,
152
};
153
154
self.collect_rules_internal(None, map, cascade_level);
155
}
156
157
fn collect_user_agent_rules(&mut self) {
158
self.collect_stylist_rules(Origin::UserAgent);
159
}
160
161
fn collect_user_rules(&mut self) {
162
if !self.matches_user_and_author_rules {
163
return;
164
}
165
166
self.collect_stylist_rules(Origin::User);
167
}
168
169
/// Presentational hints.
170
///
171
/// These go before author rules, but after user rules, see:
173
fn collect_presentational_hints(&mut self) {
174
if self.pseudo_element.is_some() {
175
return;
176
}
177
178
let length_before_preshints = self.rules.len();
179
self.element
180
.synthesize_presentational_hints_for_legacy_attributes(
181
self.context.visited_handling(),
182
self.rules,
183
);
184
if cfg!(debug_assertions) {
185
if self.rules.len() != length_before_preshints {
186
for declaration in &self.rules[length_before_preshints..] {
187
assert_eq!(declaration.level(), CascadeLevel::PresHints);
188
}
189
}
190
}
191
}
192
193
fn collect_rules_in_shadow_tree(
194
&mut self,
195
shadow_host: E,
196
map: &SelectorMap<Rule>,
197
cascade_level: CascadeLevel,
198
) {
199
debug_assert!(shadow_host.shadow_root().is_some());
200
self.collect_rules_internal(Some(shadow_host), map, cascade_level);
201
self.shadow_cascade_order += 1;
202
}
203
204
#[inline]
205
fn collect_rules_internal(
206
&mut self,
207
shadow_host: Option<E>,
208
map: &SelectorMap<Rule>,
209
cascade_level: CascadeLevel,
210
) {
211
let element = self.element;
212
let rule_hash_target = self.rule_hash_target;
213
let rules = &mut self.rules;
214
let flags_setter = &mut self.flags_setter;
215
let shadow_cascade_order = self.shadow_cascade_order;
216
let start = rules.len();
217
self.context.with_shadow_host(shadow_host, |context| {
218
map.get_all_matching_rules(
219
element,
220
rule_hash_target,
221
rules,
222
context,
223
flags_setter,
224
cascade_level,
225
shadow_cascade_order,
226
);
227
});
228
sort_rules_from(rules, start);
229
}
230
231
/// Collects the rules for the ::slotted pseudo-element.
232
fn collect_slotted_rules(&mut self) {
233
let mut slots = SmallVec::<[_; 3]>::new();
234
let mut current = self.rule_hash_target.assigned_slot();
235
while let Some(slot) = current {
236
debug_assert!(
237
self.matches_user_and_author_rules,
238
"We should not slot NAC anywhere"
239
);
240
slots.push(slot);
241
current = slot.assigned_slot();
242
}
243
244
// Match slotted rules in reverse order, so that the outer slotted rules
245
// come before the inner rules (and thus have less priority).
246
for slot in slots.iter().rev() {
247
let shadow = slot.containing_shadow().unwrap();
248
let data = match shadow.style_data() {
249
Some(d) => d,
250
None => continue,
251
};
252
let slotted_rules = match data.slotted_rules(self.pseudo_element) {
253
Some(r) => r,
254
None => continue,
255
};
256
self.collect_rules_in_shadow_tree(
257
shadow.host(),
258
slotted_rules,
259
CascadeLevel::InnerShadowNormal,
260
);
261
}
262
}
263
264
fn collect_normal_rules_from_containing_shadow_tree(&mut self) {
265
if !self.matches_user_and_author_rules {
266
return;
267
}
268
269
let containing_shadow = containing_shadow_ignoring_svg_use(self.rule_hash_target);
270
let containing_shadow = match containing_shadow {
271
Some(s) => s,
272
None => return,
273
};
274
275
self.matches_document_author_rules = false;
276
277
let cascade_data = containing_shadow.style_data();
278
let host = containing_shadow.host();
279
if let Some(map) = cascade_data.and_then(|data| data.normal_rules(self.pseudo_element)) {
280
self.collect_rules_in_shadow_tree(host, map, CascadeLevel::SameTreeAuthorNormal);
281
}
282
}
283
284
/// Collects the rules for the :host pseudo-class.
285
fn collect_host_rules(&mut self) {
286
let shadow = match self.rule_hash_target.shadow_root() {
287
Some(s) => s,
288
None => return,
289
};
290
291
debug_assert!(
292
self.matches_user_and_author_rules,
293
"NAC should not be a shadow host"
294
);
295
296
let style_data = match shadow.style_data() {
297
Some(d) => d,
298
None => return,
299
};
300
301
let host_rules = match style_data.host_rules(self.pseudo_element) {
302
Some(rules) => rules,
303
None => return,
304
};
305
306
let rule_hash_target = self.rule_hash_target;
307
self.collect_rules_in_shadow_tree(
308
rule_hash_target,
309
host_rules,
310
CascadeLevel::InnerShadowNormal,
311
);
312
}
313
314
fn collect_document_author_rules(&mut self) {
315
if !self.matches_document_author_rules {
316
return;
317
}
318
319
self.collect_stylist_rules(Origin::Author);
320
}
321
322
fn collect_part_rules(&mut self) {
323
if !self.rule_hash_target.has_part_attr() {
324
return;
325
}
326
327
let shadow = match self.rule_hash_target.containing_shadow() {
328
Some(s) => s,
329
None => return,
330
};
331
332
let host = shadow.host();
333
let containing_shadow = host.containing_shadow();
334
let part_rules = match containing_shadow {
335
Some(shadow) => shadow
336
.style_data()
337
.and_then(|data| data.part_rules(self.pseudo_element)),
338
None => self
339
.stylist
340
.cascade_data()
341
.borrow_for_origin(Origin::Author)
342
.part_rules(self.pseudo_element),
343
};
344
345
// TODO(emilio): SameTreeAuthorNormal is a bit of a lie here, we may
346
// need an OuterTreeAuthorNormal cascade level or such, and change the
347
// cascade order, if we allow to forward parts to even outer trees.
348
//
349
// Though the current thing kinda works because we apply them after
350
// the outer tree, so as long as we don't allow forwarding we're
351
// good.
352
if let Some(part_rules) = part_rules {
353
let containing_host = containing_shadow.map(|s| s.host());
354
let element = self.element;
355
let rule_hash_target = self.rule_hash_target;
356
let rules = &mut self.rules;
357
let flags_setter = &mut self.flags_setter;
358
let shadow_cascade_order = self.shadow_cascade_order;
359
let cascade_level = CascadeLevel::SameTreeAuthorNormal;
360
let start = rules.len();
361
self.context.with_shadow_host(containing_host, |context| {
362
rule_hash_target.each_part(|p| {
363
if let Some(part_rules) = part_rules.get(p) {
364
SelectorMap::get_matching_rules(
365
element,
366
&part_rules,
367
rules,
368
context,
369
flags_setter,
370
cascade_level,
371
shadow_cascade_order,
372
);
373
}
374
});
375
});
376
sort_rules_from(rules, start);
377
}
378
}
379
380
fn collect_style_attribute_and_animation_rules(&mut self) {
381
if let Some(sa) = self.style_attribute {
382
self.rules
383
.push(ApplicableDeclarationBlock::from_declarations(
384
sa.clone_arc(),
385
CascadeLevel::StyleAttributeNormal,
386
));
387
}
388
389
if let Some(so) = self.smil_override {
390
self.rules
391
.push(ApplicableDeclarationBlock::from_declarations(
392
so.clone_arc(),
393
CascadeLevel::SMILOverride,
394
));
395
}
396
397
// The animations sheet (CSS animations, script-generated
398
// animations, and CSS transitions that are no longer tied to CSS
399
// markup).
400
if let Some(anim) = self.animation_rules.0.take() {
401
self.rules
402
.push(ApplicableDeclarationBlock::from_declarations(
403
anim,
404
CascadeLevel::Animations,
405
));
406
}
407
408
// The transitions sheet (CSS transitions that are tied to CSS
409
// markup).
410
if let Some(anim) = self.animation_rules.1.take() {
411
self.rules
412
.push(ApplicableDeclarationBlock::from_declarations(
413
anim,
414
CascadeLevel::Transitions,
415
));
416
}
417
}
418
419
/// Collects all the rules, leaving the result in `self.rules`.
420
///
421
/// Note that `!important` rules are handled during rule tree insertion.
422
pub fn collect_all(mut self) {
423
self.collect_user_agent_rules();
424
self.collect_user_rules();
425
if self.rule_inclusion == RuleInclusion::DefaultOnly {
426
return;
427
}
428
self.collect_presentational_hints();
429
// FIXME(emilio): Should the author styles enabled stuff avoid the
430
// presentational hints from getting pushed? See bug 1505770.
431
if self.stylist.author_styles_enabled() == AuthorStylesEnabled::No {
432
return;
433
}
434
self.collect_host_rules();
435
self.collect_slotted_rules();
436
self.collect_normal_rules_from_containing_shadow_tree();
437
self.collect_document_author_rules();
438
self.collect_part_rules();
439
self.collect_style_attribute_and_animation_rules();
440
}
441
}