Skip to main content

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