style/
rule_collector.rs

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
7use crate::applicable_declarations::{ApplicableDeclarationBlock, ApplicableDeclarationList};
8use crate::dom::{TElement, TNode, TShadowRoot};
9use crate::properties::{AnimationDeclarations, PropertyDeclarationBlock};
10use crate::rule_tree::{CascadeLevel, CascadeOrigin, ShadowCascadeOrder};
11use crate::selector_map::SelectorMap;
12use crate::selector_parser::PseudoElement;
13use crate::shared_lock::Locked;
14use crate::stylesheets::{layer_rule::LayerOrder, Origin};
15use crate::stylist::{AuthorStylesEnabled, CascadeData, Rule, RuleInclusion, Stylist};
16use selectors::matching::MatchingContext;
17use servo_arc::ArcBorrow;
18use 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]
38pub 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/// An object that we use with all the intermediate state needed for the
58/// cascade.
59///
60/// This is done basically to be able to organize the cascade in smaller
61/// functions, and be able to reason about it easily.
62pub struct RuleCollector<'a, 'b: 'a, E>
63where
64    E: TElement,
65{
66    element: E,
67    rule_hash_target: E,
68    stylist: &'a Stylist,
69    pseudo_elements: SmallVec<[PseudoElement; 1]>,
70    style_attribute: Option<ArcBorrow<'a, Locked<PropertyDeclarationBlock>>>,
71    smil_override: Option<ArcBorrow<'a, Locked<PropertyDeclarationBlock>>>,
72    animation_declarations: AnimationDeclarations,
73    rule_inclusion: RuleInclusion,
74    rules: &'a mut ApplicableDeclarationList,
75    context: &'a mut MatchingContext<'b, E::Impl>,
76    matches_user_and_content_rules: bool,
77    matches_document_author_rules: bool,
78    in_sort_scope: bool,
79}
80
81impl<'a, 'b: 'a, E> RuleCollector<'a, 'b, E>
82where
83    E: TElement,
84{
85    /// Trivially construct a new collector.
86    pub fn new(
87        stylist: &'a Stylist,
88        element: E,
89        pseudo_elements: SmallVec<[PseudoElement; 1]>,
90        style_attribute: Option<ArcBorrow<'a, Locked<PropertyDeclarationBlock>>>,
91        smil_override: Option<ArcBorrow<'a, Locked<PropertyDeclarationBlock>>>,
92        animation_declarations: AnimationDeclarations,
93        rule_inclusion: RuleInclusion,
94        rules: &'a mut ApplicableDeclarationList,
95        context: &'a mut MatchingContext<'b, E::Impl>,
96    ) -> Self {
97        let rule_hash_target = element.rule_hash_target();
98        let matches_user_and_content_rules = rule_hash_target.matches_user_and_content_rules();
99
100        debug_assert!(pseudo_elements.iter().all(|p| !p.is_precomputed()));
101
102        Self {
103            element,
104            rule_hash_target,
105            stylist,
106            pseudo_elements,
107            style_attribute,
108            smil_override,
109            animation_declarations,
110            rule_inclusion,
111            context,
112            rules,
113            matches_user_and_content_rules,
114            matches_document_author_rules: matches_user_and_content_rules,
115            in_sort_scope: false,
116        }
117    }
118
119    /// Sets up the state necessary to collect rules from a given DOM tree
120    /// (either the document tree, or a shadow tree).
121    ///
122    /// All rules in the same tree need to be matched together, and this
123    /// function takes care of sorting them by specificity and source order.
124    #[inline]
125    fn in_tree(&mut self, host: Option<E>, f: impl FnOnce(&mut Self)) {
126        debug_assert!(!self.in_sort_scope, "Nested sorting makes no sense");
127        let start = self.rules.len();
128        self.in_sort_scope = true;
129        let old_host = self.context.current_host.take();
130        self.context.current_host = host.map(|e| e.opaque());
131        f(self);
132        if start != self.rules.len() {
133            self.rules[start..].sort_unstable_by_key(|block| block.sort_key());
134        }
135        self.context.current_host = old_host;
136        self.in_sort_scope = false;
137    }
138
139    #[inline]
140    fn in_shadow_tree(&mut self, host: E, f: impl FnOnce(&mut Self)) {
141        self.in_tree(Some(host), f);
142    }
143
144    fn collect_stylist_rules(&mut self, origin: Origin) {
145        let cascade_level = match origin {
146            Origin::UserAgent => CascadeLevel::new(CascadeOrigin::UA),
147            Origin::User => CascadeLevel::new(CascadeOrigin::User),
148            Origin::Author => CascadeLevel::same_tree_author_normal(),
149        };
150
151        let cascade_data = self.stylist.cascade_data().borrow_for_origin(origin);
152        let map = match cascade_data.normal_rules(&self.pseudo_elements) {
153            Some(m) => m,
154            None => return,
155        };
156
157        self.in_tree(None, |collector| {
158            collector.collect_rules_in_map(map, cascade_level, cascade_data);
159        });
160    }
161
162    fn collect_user_agent_rules(&mut self) {
163        self.collect_stylist_rules(Origin::UserAgent);
164        #[cfg(feature = "gecko")]
165        self.collect_view_transition_dynamic_rules();
166    }
167
168    #[cfg(feature = "gecko")]
169    fn collect_view_transition_dynamic_rules(&mut self) {
170        if !self
171            .pseudo_elements
172            .first()
173            .is_some_and(|p| p.is_named_view_transition())
174        {
175            return;
176        }
177        let len_before_vt_rules = self.rules.len();
178        self.element
179            .synthesize_view_transition_dynamic_rules(self.rules);
180        if cfg!(debug_assertions) && self.rules.len() != len_before_vt_rules {
181            for declaration in &self.rules[len_before_vt_rules..] {
182                assert_eq!(declaration.level(), CascadeLevel::new(CascadeOrigin::UA));
183            }
184        }
185    }
186
187    fn collect_user_rules(&mut self) {
188        if !self.matches_user_and_content_rules {
189            return;
190        }
191
192        self.collect_stylist_rules(Origin::User);
193    }
194
195    /// Presentational hints.
196    ///
197    /// These go before author rules, but after user rules, see:
198    /// https://drafts.csswg.org/css-cascade/#preshint
199    fn collect_presentational_hints(&mut self) {
200        if !self.pseudo_elements.is_empty() {
201            return;
202        }
203
204        let length_before_preshints = self.rules.len();
205        self.element
206            .synthesize_presentational_hints_for_legacy_attributes(
207                self.context.visited_handling(),
208                self.rules,
209            );
210        if cfg!(debug_assertions) && self.rules.len() != length_before_preshints {
211            for declaration in &self.rules[length_before_preshints..] {
212                assert_eq!(
213                    declaration.level(),
214                    CascadeLevel::new(CascadeOrigin::PresHints)
215                );
216            }
217        }
218    }
219
220    #[inline]
221    fn collect_rules_in_list(
222        &mut self,
223        part_rules: &[Rule],
224        cascade_level: CascadeLevel,
225        cascade_data: &CascadeData,
226    ) {
227        debug_assert!(self.in_sort_scope, "Rules gotta be sorted");
228        SelectorMap::get_matching_rules(
229            self.element,
230            part_rules,
231            &mut self.rules,
232            &mut self.context,
233            cascade_level,
234            cascade_data,
235            &self.stylist,
236        );
237    }
238
239    #[inline]
240    fn collect_rules_in_map(
241        &mut self,
242        map: &SelectorMap<Rule>,
243        cascade_level: CascadeLevel,
244        cascade_data: &CascadeData,
245    ) {
246        debug_assert!(self.in_sort_scope, "Rules gotta be sorted");
247        map.get_all_matching_rules(
248            self.element,
249            self.rule_hash_target,
250            &mut self.rules,
251            &mut self.context,
252            cascade_level,
253            cascade_data,
254            &self.stylist,
255        );
256    }
257
258    /// Collects the rules for the ::slotted pseudo-element and the :host
259    /// pseudo-class.
260    fn collect_host_and_slotted_rules(&mut self) {
261        let mut slots = SmallVec::<[_; 3]>::new();
262        let mut current = self.rule_hash_target.assigned_slot();
263        let mut shadow_cascade_order = ShadowCascadeOrder::for_outermost_shadow_tree();
264
265        while let Some(slot) = current {
266            debug_assert!(
267                self.matches_user_and_content_rules,
268                "We should not slot NAC anywhere"
269            );
270            slots.push(slot);
271            current = slot.assigned_slot();
272            shadow_cascade_order.dec();
273        }
274
275        self.collect_host_rules(shadow_cascade_order);
276
277        // Match slotted rules in reverse order, so that the outer slotted rules
278        // come before the inner rules (and thus have less priority).
279        for slot in slots.iter().rev() {
280            shadow_cascade_order.inc();
281
282            let shadow = slot.containing_shadow().unwrap();
283            let data = match shadow.style_data() {
284                Some(d) => d,
285                None => continue,
286            };
287            let slotted_rules = match data.slotted_rules(&self.pseudo_elements) {
288                Some(r) => r,
289                None => continue,
290            };
291
292            self.in_shadow_tree(shadow.host(), |collector| {
293                let cascade_level = CascadeLevel::author_normal(shadow_cascade_order);
294                collector.collect_rules_in_map(slotted_rules, cascade_level, data);
295            });
296        }
297    }
298
299    fn collect_rules_from_containing_shadow_tree(&mut self) {
300        if !self.matches_user_and_content_rules {
301            return;
302        }
303
304        let containing_shadow = containing_shadow_ignoring_svg_use(self.rule_hash_target);
305        let containing_shadow = match containing_shadow {
306            Some(s) => s,
307            None => return,
308        };
309
310        self.matches_document_author_rules = false;
311
312        let cascade_data = match containing_shadow.style_data() {
313            Some(c) => c,
314            None => return,
315        };
316
317        let cascade_level = CascadeLevel::same_tree_author_normal();
318        self.in_shadow_tree(containing_shadow.host(), |collector| {
319            if let Some(map) = cascade_data.normal_rules(&collector.pseudo_elements) {
320                collector.collect_rules_in_map(map, cascade_level, cascade_data);
321            }
322
323            // Collect rules from :host::part() and such
324            let hash_target = collector.rule_hash_target;
325            if !hash_target.has_part_attr() {
326                return;
327            }
328
329            let part_rules = match cascade_data.part_rules(&collector.pseudo_elements) {
330                Some(p) => p,
331                None => return,
332            };
333
334            hash_target.each_part(|part| {
335                if let Some(part_rules) = part_rules.get(&part.0) {
336                    collector.collect_rules_in_list(part_rules, cascade_level, cascade_data);
337                }
338            });
339        });
340    }
341
342    /// Collects the rules for the :host pseudo-class.
343    fn collect_host_rules(&mut self, shadow_cascade_order: ShadowCascadeOrder) {
344        let shadow = match self.rule_hash_target.shadow_root() {
345            Some(s) => s,
346            None => return,
347        };
348
349        let style_data = match shadow.style_data() {
350            Some(d) => d,
351            None => return,
352        };
353
354        let host_rules = match style_data.featureless_host_rules(&self.pseudo_elements) {
355            Some(rules) => rules,
356            None => return,
357        };
358
359        let rule_hash_target = self.rule_hash_target;
360        self.in_shadow_tree(rule_hash_target, |collector| {
361            let cascade_level = CascadeLevel::author_normal(shadow_cascade_order);
362            debug_assert!(!collector.context.featureless(), "How?");
363            collector.context.featureless = true;
364            collector.collect_rules_in_map(host_rules, cascade_level, style_data);
365            collector.context.featureless = false;
366        });
367    }
368
369    fn collect_document_author_rules(&mut self) {
370        if !self.matches_document_author_rules {
371            return;
372        }
373
374        self.collect_stylist_rules(Origin::Author);
375    }
376
377    fn collect_part_rules_from_outer_trees(&mut self) {
378        if !self.rule_hash_target.has_part_attr() {
379            return;
380        }
381
382        let mut inner_shadow = match self.rule_hash_target.containing_shadow() {
383            Some(s) => s,
384            None => return,
385        };
386
387        let mut shadow_cascade_order = ShadowCascadeOrder::for_innermost_containing_tree();
388
389        let mut parts = SmallVec::<[_; 3]>::new();
390        self.rule_hash_target.each_part(|p| parts.push(p.clone()));
391
392        loop {
393            if parts.is_empty() {
394                return;
395            }
396
397            let inner_shadow_host = inner_shadow.host();
398            let outer_shadow = inner_shadow_host.containing_shadow();
399            let cascade_data = match outer_shadow {
400                Some(shadow) => shadow.style_data(),
401                None => Some(
402                    self.stylist
403                        .cascade_data()
404                        .borrow_for_origin(Origin::Author),
405                ),
406            };
407
408            if let Some(cascade_data) = cascade_data {
409                if let Some(part_rules) = cascade_data.part_rules(&self.pseudo_elements) {
410                    let containing_host = outer_shadow.map(|s| s.host());
411                    let cascade_level = CascadeLevel::author_normal(shadow_cascade_order);
412                    self.in_tree(containing_host, |collector| {
413                        for p in &parts {
414                            if let Some(part_rules) = part_rules.get(&p.0) {
415                                collector.collect_rules_in_list(
416                                    part_rules,
417                                    cascade_level,
418                                    cascade_data,
419                                );
420                            }
421                        }
422                    });
423                    shadow_cascade_order.inc();
424                }
425            }
426
427            inner_shadow = match outer_shadow {
428                Some(s) => s,
429                None => break, // Nowhere to export to.
430            };
431
432            let mut new_parts = SmallVec::new();
433            for part in &parts {
434                inner_shadow_host.each_exported_part(part, |exported_part| {
435                    new_parts.push(exported_part.clone());
436                });
437            }
438            parts = new_parts;
439        }
440    }
441
442    fn collect_style_attribute(&mut self) {
443        if let Some(sa) = self.style_attribute {
444            self.rules
445                .push(ApplicableDeclarationBlock::from_declarations(
446                    sa.clone_arc(),
447                    CascadeLevel::same_tree_author_normal(),
448                    LayerOrder::style_attribute(),
449                ));
450        }
451    }
452
453    fn collect_animation_rules(&mut self) {
454        if let Some(so) = self.smil_override {
455            self.rules
456                .push(ApplicableDeclarationBlock::from_declarations(
457                    so.clone_arc(),
458                    CascadeLevel::new(CascadeOrigin::SMILOverride),
459                    LayerOrder::root(),
460                ));
461        }
462
463        // The animations sheet (CSS animations, script-generated
464        // animations, and CSS transitions that are no longer tied to CSS
465        // markup).
466        if let Some(anim) = self.animation_declarations.animations.take() {
467            self.rules
468                .push(ApplicableDeclarationBlock::from_declarations(
469                    anim,
470                    CascadeLevel::new(CascadeOrigin::Animations),
471                    LayerOrder::root(),
472                ));
473        }
474
475        // The transitions sheet (CSS transitions that are tied to CSS
476        // markup).
477        if let Some(anim) = self.animation_declarations.transitions.take() {
478            self.rules
479                .push(ApplicableDeclarationBlock::from_declarations(
480                    anim,
481                    CascadeLevel::new(CascadeOrigin::Transitions),
482                    LayerOrder::root(),
483                ));
484        }
485    }
486
487    /// Collects all the rules, leaving the result in `self.rules`.
488    ///
489    /// Note that `!important` rules are handled during rule tree insertion.
490    pub fn collect_all(mut self) {
491        self.collect_user_agent_rules();
492        self.collect_user_rules();
493        if self.rule_inclusion == RuleInclusion::DefaultOnly {
494            return;
495        }
496        self.collect_presentational_hints();
497        // FIXME(emilio): Should the author styles enabled stuff avoid the
498        // presentational hints from getting pushed? See bug 1505770.
499        if self.stylist.author_styles_enabled() == AuthorStylesEnabled::No {
500            return;
501        }
502        self.collect_host_and_slotted_rules();
503        self.collect_rules_from_containing_shadow_tree();
504        self.collect_document_author_rules();
505        self.collect_style_attribute();
506        self.collect_part_rules_from_outer_trees();
507        self.collect_animation_rules();
508    }
509}