Skip to main content

style/
style_resolver.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//! Style resolution for a given element or pseudo-element.
6
7use crate::applicable_declarations::ApplicableDeclarationList;
8use crate::computed_value_flags::ComputedValueFlags;
9use crate::context::{CascadeInputs, ElementCascadeInputs, StyleContext};
10use crate::data::{EagerPseudoStyles, ElementStyles};
11use crate::dom::{TElement, TNode};
12use crate::matching::MatchMethods;
13use crate::properties::longhands::display::computed_value::T as Display;
14use crate::properties::{AnimationDeclarations, ComputedValues, FirstLineReparenting};
15use crate::rule_tree::{RuleCascadeFlags, RuleTree, StrongRuleNode};
16use crate::selector_parser::{PseudoElement, SelectorImpl};
17use crate::stylist::RuleInclusion;
18use log::Level::Trace;
19use selectors::matching::{
20    MatchingContext, MatchingForInvalidation, MatchingMode, NeedsSelectorFlags, VisitedHandlingMode,
21};
22#[cfg(feature = "gecko")]
23use selectors::parser::PseudoElement as PseudoElementTrait;
24use servo_arc::Arc;
25
26/// Whether pseudo-elements should be resolved or not.
27#[derive(Clone, Copy, Debug, Eq, PartialEq)]
28pub enum PseudoElementResolution {
29    /// Only resolve pseudo-styles if possibly applicable.
30    IfApplicable,
31    /// Force pseudo-element resolution.
32    Force,
33}
34
35/// A struct that takes care of resolving the style of a given element.
36pub struct StyleResolverForElement<'a, 'ctx, 'le, E>
37where
38    'ctx: 'a,
39    'le: 'ctx,
40    E: TElement + MatchMethods + 'le,
41{
42    element: E,
43    context: &'a mut StyleContext<'ctx, E>,
44    rule_inclusion: RuleInclusion,
45    pseudo_resolution: PseudoElementResolution,
46    _marker: ::std::marker::PhantomData<&'le E>,
47}
48
49struct MatchingResults {
50    rule_node: StrongRuleNode,
51    flags: ComputedValueFlags,
52}
53
54/// A style returned from the resolver machinery.
55pub struct ResolvedStyle(pub Arc<ComputedValues>);
56
57impl ResolvedStyle {
58    /// Convenience accessor for the style.
59    #[inline]
60    pub fn style(&self) -> &ComputedValues {
61        &self.0
62    }
63}
64
65/// The primary style of an element or an element-backed pseudo-element.
66pub struct PrimaryStyle {
67    /// The style itself.
68    pub style: ResolvedStyle,
69    /// Whether the style was reused from another element via the rule node (see
70    /// `StyleSharingCache::lookup_by_rules`).
71    pub reused_via_rule_node: bool,
72}
73
74/// A set of style returned from the resolver machinery.
75pub struct ResolvedElementStyles {
76    /// Primary style.
77    pub primary: PrimaryStyle,
78    /// Pseudo styles.
79    pub pseudos: EagerPseudoStyles,
80}
81
82impl ResolvedElementStyles {
83    /// Convenience accessor for the primary style.
84    pub fn primary_style(&self) -> &Arc<ComputedValues> {
85        &self.primary.style.0
86    }
87
88    /// Convenience mutable accessor for the style.
89    pub fn primary_style_mut(&mut self) -> &mut Arc<ComputedValues> {
90        &mut self.primary.style.0
91    }
92}
93
94impl PrimaryStyle {
95    /// Convenience accessor for the style.
96    pub fn style(&self) -> &ComputedValues {
97        &self.style.0
98    }
99}
100
101impl From<ResolvedElementStyles> for ElementStyles {
102    fn from(r: ResolvedElementStyles) -> ElementStyles {
103        ElementStyles {
104            primary: Some(r.primary.style.0),
105            pseudos: r.pseudos,
106        }
107    }
108}
109
110pub(crate) fn with_default_parent_styles<E, F, R>(element: E, f: F) -> R
111where
112    E: TElement,
113    F: FnOnce(Option<&ComputedValues>, Option<&ComputedValues>) -> R,
114{
115    let parent_el = element.inheritance_parent();
116    let parent_data = parent_el.as_ref().and_then(|e| e.borrow_data());
117    let parent_style = parent_data.as_ref().map(|d| d.styles.primary());
118
119    let mut layout_parent_el = parent_el;
120    let layout_parent_data;
121    let mut layout_parent_style = parent_style;
122    if parent_style.is_some_and(|s| s.is_display_contents()) {
123        layout_parent_el = Some(layout_parent_el.unwrap().layout_parent());
124        layout_parent_data = layout_parent_el.as_ref().unwrap().borrow_data().unwrap();
125        layout_parent_style = Some(layout_parent_data.styles.primary());
126    }
127
128    f(
129        parent_style.map(|x| &**x),
130        layout_parent_style.map(|s| &**s),
131    )
132}
133
134fn layout_parent_style_for_pseudo<'a>(
135    primary_style: &'a PrimaryStyle,
136    layout_parent_style: Option<&'a ComputedValues>,
137) -> Option<&'a ComputedValues> {
138    if primary_style.style().is_display_contents() {
139        layout_parent_style
140    } else {
141        Some(primary_style.style())
142    }
143}
144
145fn eager_pseudo_is_definitely_not_generated(
146    pseudo: &PseudoElement,
147    style: &ComputedValues,
148) -> bool {
149    if !pseudo.is_before_or_after() {
150        return false;
151    }
152
153    if style
154        .flags
155        .intersects(ComputedValueFlags::DISPLAY_OR_CONTENT_DEPEND_ON_INHERITED_STYLE)
156    {
157        return false;
158    }
159
160    *style.get_box().get_display() == Display::None || style.ineffective_content_property()
161}
162
163impl<'a, 'ctx, 'le, E> StyleResolverForElement<'a, 'ctx, 'le, E>
164where
165    'ctx: 'a,
166    'le: 'ctx,
167    E: TElement + MatchMethods + 'le,
168{
169    /// Trivially construct a new StyleResolverForElement.
170    pub fn new(
171        element: E,
172        context: &'a mut StyleContext<'ctx, E>,
173        rule_inclusion: RuleInclusion,
174        pseudo_resolution: PseudoElementResolution,
175    ) -> Self {
176        debug_assert_eq!(
177            element.as_node().depth(),
178            context.thread_local.current_dom_depth
179        );
180        Self {
181            element,
182            context,
183            rule_inclusion,
184            pseudo_resolution,
185            _marker: ::std::marker::PhantomData,
186        }
187    }
188
189    /// Resolve just the style of a given element.
190    pub fn resolve_primary_style(
191        &mut self,
192        parent_style: Option<&ComputedValues>,
193        layout_parent_style: Option<&ComputedValues>,
194    ) -> PrimaryStyle {
195        let primary_results = self.match_primary(VisitedHandlingMode::AllLinksUnvisited);
196
197        let inside_link = parent_style.is_some_and(|s| s.visited_style().is_some());
198
199        let visited_rules = if self.context.shared.visited_styles_enabled
200            && (inside_link || self.element.is_link())
201        {
202            if self.needs_visited_matching() {
203                let visited_matching_results =
204                    self.match_primary(VisitedHandlingMode::RelevantLinkVisited);
205                Some(visited_matching_results.rule_node)
206            } else {
207                // Not `None`: the style sharing cache compares these against
208                // the candidate's visited rules, which are the same node.
209                Some(primary_results.rule_node.clone())
210            }
211        } else {
212            None
213        };
214
215        self.cascade_primary_style(
216            CascadeInputs {
217                rules: Some(primary_results.rule_node),
218                visited_rules,
219                flags: primary_results.flags,
220                included_cascade_flags: RuleCascadeFlags::empty(),
221            },
222            parent_style,
223            layout_parent_style,
224        )
225    }
226
227    /// Whether matching again with the relevant link treated as visited can give a different rule
228    /// node than the pass we already did.
229    ///
230    /// `:link` and `:visited` only match links, so for anything else other than them (and their
231    /// pseudo-elements), this needs a selector testing link state from a position that reaches a
232    /// non-link, like `a:visited span`.
233    fn needs_visited_matching(&self) -> bool {
234        if self.element.is_link() {
235            return true;
236        }
237        if self.element.implemented_pseudo_element().is_some()
238            && self
239                .element
240                .pseudo_element_originating_element()
241                .is_some_and(|e| e.is_link())
242        {
243            return true;
244        }
245        self.context
246            .shared
247            .stylist
248            .any_applicable_rule_data(self.element, |data| data.has_non_link_visited_dependency())
249    }
250
251    fn cascade_primary_style(
252        &mut self,
253        inputs: CascadeInputs,
254        parent_style: Option<&ComputedValues>,
255        layout_parent_style: Option<&ComputedValues>,
256    ) -> PrimaryStyle {
257        // Before doing the cascade, check the sharing cache and see if we can
258        // reuse the style via rule node identity.
259        let may_reuse = self.element.matches_user_and_content_rules()
260            && parent_style.is_some()
261            && inputs.rules.is_some()
262            && inputs.included_cascade_flags.is_empty();
263
264        if may_reuse {
265            let cached = self.context.thread_local.sharing_cache.lookup_by_rules(
266                self.context.shared,
267                parent_style.unwrap(),
268                &inputs,
269                self.element,
270                self.context.thread_local.current_dom_depth,
271            );
272            if let Some(mut primary_style) = cached {
273                self.context.thread_local.statistics.styles_reused += 1;
274                primary_style.reused_via_rule_node |= true;
275                return primary_style;
276            }
277        }
278
279        // No style to reuse. Cascade the style, starting with visited style
280        // if necessary.
281        PrimaryStyle {
282            style: self.cascade_style_and_visited(
283                inputs,
284                parent_style,
285                layout_parent_style,
286                /* pseudo = */ None,
287            ),
288            reused_via_rule_node: false,
289        }
290    }
291
292    /// Resolve the style of a given element, and all its eager pseudo-elements.
293    pub fn resolve_style(
294        &mut self,
295        parent_style: Option<&ComputedValues>,
296        layout_parent_style: Option<&ComputedValues>,
297    ) -> ResolvedElementStyles {
298        let primary_style = self.resolve_primary_style(parent_style, layout_parent_style);
299
300        let mut pseudo_styles = EagerPseudoStyles::default();
301
302        if self
303            .element
304            .implemented_pseudo_element()
305            .is_none_or(|p| p.is_element_backed())
306        {
307            let layout_parent_style_for_pseudo =
308                layout_parent_style_for_pseudo(&primary_style, layout_parent_style);
309            SelectorImpl::each_eagerly_cascaded_pseudo_element(|pseudo| {
310                let pseudo_style = self.resolve_pseudo_style(
311                    &pseudo,
312                    &primary_style,
313                    layout_parent_style_for_pseudo,
314                );
315
316                if let Some(style) = pseudo_style {
317                    if !matches!(self.pseudo_resolution, PseudoElementResolution::Force)
318                        && eager_pseudo_is_definitely_not_generated(&pseudo, &style.0)
319                    {
320                        return;
321                    }
322                    pseudo_styles.set(&pseudo, style.0);
323                }
324            })
325        }
326
327        ResolvedElementStyles {
328            primary: primary_style,
329            pseudos: pseudo_styles,
330        }
331    }
332
333    /// Resolve an element's styles with the default inheritance parent/layout
334    /// parents.
335    pub fn resolve_style_with_default_parents(&mut self) -> ResolvedElementStyles {
336        with_default_parent_styles(self.element, |parent_style, layout_parent_style| {
337            self.resolve_style(parent_style, layout_parent_style)
338        })
339    }
340
341    /// Cascade a set of rules, using the default parent for inheritance.
342    pub fn cascade_style_and_visited_with_default_parents(
343        &mut self,
344        inputs: CascadeInputs,
345    ) -> ResolvedStyle {
346        with_default_parent_styles(self.element, |parent_style, layout_parent_style| {
347            self.cascade_style_and_visited(
348                inputs,
349                parent_style,
350                layout_parent_style,
351                /* pseudo = */ None,
352            )
353        })
354    }
355
356    /// Cascade a set of rules for pseudo element, using the default parent for inheritance.
357    pub fn cascade_style_and_visited_for_pseudo_with_default_parents(
358        &mut self,
359        inputs: CascadeInputs,
360        pseudo: &PseudoElement,
361        primary_style: &PrimaryStyle,
362    ) -> ResolvedStyle {
363        with_default_parent_styles(self.element, |_, layout_parent_style| {
364            let layout_parent_style_for_pseudo =
365                layout_parent_style_for_pseudo(primary_style, layout_parent_style);
366
367            self.cascade_style_and_visited(
368                inputs,
369                Some(primary_style.style()),
370                layout_parent_style_for_pseudo,
371                Some(pseudo),
372            )
373        })
374    }
375
376    fn cascade_style_and_visited(
377        &mut self,
378        inputs: CascadeInputs,
379        parent_style: Option<&ComputedValues>,
380        layout_parent_style: Option<&ComputedValues>,
381        pseudo: Option<&PseudoElement>,
382    ) -> ResolvedStyle {
383        debug_assert!(pseudo.is_none_or(|p| p.is_eager()));
384
385        let mut conditions = Default::default();
386        let values = self.context.shared.stylist.cascade_style_and_visited(
387            Some(self.element),
388            pseudo,
389            &inputs,
390            &self.context.shared.guards,
391            parent_style,
392            layout_parent_style,
393            FirstLineReparenting::No,
394            /* try_tactic = */ &Default::default(),
395            Some(&self.context.thread_local.rule_cache),
396            &mut conditions,
397            &mut self.context.thread_local.tree_counting_caches,
398        );
399
400        self.context.thread_local.rule_cache.insert_if_possible(
401            &self.context.shared.guards,
402            &values,
403            pseudo,
404            &inputs,
405            &conditions,
406        );
407
408        ResolvedStyle(values)
409    }
410
411    /// Cascade the element and pseudo-element styles with the default parents.
412    pub fn cascade_styles_with_default_parents(
413        &mut self,
414        inputs: ElementCascadeInputs,
415    ) -> ResolvedElementStyles {
416        with_default_parent_styles(self.element, move |parent_style, layout_parent_style| {
417            let primary_style =
418                self.cascade_primary_style(inputs.primary, parent_style, layout_parent_style);
419
420            let mut pseudo_styles = EagerPseudoStyles::default();
421            if let Some(mut pseudo_array) = inputs.pseudos.into_array() {
422                let layout_parent_style_for_pseudo = if primary_style.style().is_display_contents()
423                {
424                    layout_parent_style
425                } else {
426                    Some(primary_style.style())
427                };
428
429                for (i, inputs) in pseudo_array.iter_mut().enumerate() {
430                    if let Some(inputs) = inputs.take() {
431                        let pseudo = PseudoElement::from_eager_index(i);
432
433                        let style = self.cascade_style_and_visited(
434                            inputs,
435                            Some(primary_style.style()),
436                            layout_parent_style_for_pseudo,
437                            Some(&pseudo),
438                        );
439
440                        if !matches!(self.pseudo_resolution, PseudoElementResolution::Force)
441                            && eager_pseudo_is_definitely_not_generated(&pseudo, &style.0)
442                        {
443                            continue;
444                        }
445
446                        pseudo_styles.set(&pseudo, style.0);
447                    }
448                }
449            }
450
451            ResolvedElementStyles {
452                primary: primary_style,
453                pseudos: pseudo_styles,
454            }
455        })
456    }
457
458    fn resolve_pseudo_style(
459        &mut self,
460        pseudo: &PseudoElement,
461        originating_element_style: &PrimaryStyle,
462        layout_parent_style: Option<&ComputedValues>,
463    ) -> Option<ResolvedStyle> {
464        let MatchingResults {
465            rule_node,
466            mut flags,
467        } = self.match_pseudo(
468            &originating_element_style.style.0,
469            pseudo,
470            VisitedHandlingMode::AllLinksUnvisited,
471        )?;
472
473        let mut visited_rules = None;
474        if originating_element_style.style().visited_style().is_some() {
475            visited_rules = self
476                .match_pseudo(
477                    &originating_element_style.style.0,
478                    pseudo,
479                    VisitedHandlingMode::RelevantLinkVisited,
480                )
481                .map(|results| {
482                    flags |= results.flags;
483                    results.rule_node
484                });
485        }
486
487        Some(self.cascade_style_and_visited(
488            CascadeInputs {
489                rules: Some(rule_node),
490                visited_rules,
491                flags,
492                included_cascade_flags: RuleCascadeFlags::empty(),
493            },
494            Some(originating_element_style.style()),
495            layout_parent_style,
496            Some(pseudo),
497        ))
498    }
499
500    fn match_primary(&mut self, visited_handling: VisitedHandlingMode) -> MatchingResults {
501        debug!(
502            "Match primary for {:?}, visited: {:?}",
503            self.element, visited_handling
504        );
505        let animation_declarations = self.element.animation_declarations(self.context.shared);
506        let mut applicable_declarations = ApplicableDeclarationList::new();
507
508        let bloom_filter = self.context.thread_local.bloom_filter.filter();
509        let selector_caches = &mut self.context.thread_local.selector_caches;
510        let mut matching_context = MatchingContext::new_for_visited(
511            MatchingMode::Normal,
512            Some(bloom_filter),
513            selector_caches,
514            visited_handling,
515            self.context.shared.quirks_mode(),
516            NeedsSelectorFlags::Yes,
517            MatchingForInvalidation::No,
518        );
519
520        let stylist = &self.context.shared.stylist;
521        // Compute the primary rule node.
522        stylist.push_applicable_declarations(
523            self.element,
524            None,
525            self.element.style_attribute(),
526            self.element.smil_override(),
527            &animation_declarations,
528            self.rule_inclusion,
529            &mut applicable_declarations,
530            &mut matching_context,
531        );
532
533        let rule_node = stylist
534            .rule_tree()
535            .compute_rule_node(&mut applicable_declarations, &self.context.shared.guards);
536
537        if log_enabled!(Trace) {
538            trace!("Matched rules for {:?}:", self.element);
539            for rn in rule_node.self_and_ancestors() {
540                let source = rn.style_source();
541                if source.is_some() {
542                    trace!(" > {:?}", source);
543                }
544            }
545        }
546
547        MatchingResults {
548            rule_node,
549            flags: matching_context.extra_data.cascade_input_flags,
550        }
551    }
552
553    fn match_pseudo(
554        &mut self,
555        originating_element_style: &ComputedValues,
556        pseudo_element: &PseudoElement,
557        visited_handling: VisitedHandlingMode,
558    ) -> Option<MatchingResults> {
559        debug!(
560            "Match pseudo {:?} for {:?}, visited: {:?}",
561            self.element, pseudo_element, visited_handling
562        );
563        debug_assert!(pseudo_element.is_eager());
564
565        let animation_declarations = AnimationDeclarations::default();
566        let mut applicable_declarations = ApplicableDeclarationList::new();
567
568        let stylist = &self.context.shared.stylist;
569
570        if !self
571            .element
572            .may_generate_pseudo(pseudo_element, originating_element_style)
573        {
574            return None;
575        }
576
577        let bloom_filter = self.context.thread_local.bloom_filter.filter();
578        let selector_caches = &mut self.context.thread_local.selector_caches;
579
580        let mut matching_context = MatchingContext::<'_, E::Impl>::new_for_visited(
581            MatchingMode::ForStatelessPseudoElement,
582            Some(bloom_filter),
583            selector_caches,
584            visited_handling,
585            self.context.shared.quirks_mode(),
586            NeedsSelectorFlags::Yes,
587            MatchingForInvalidation::No,
588        );
589        matching_context.extra_data.originating_element_style = Some(originating_element_style);
590
591        // NB: We handle animation rules for ::before and ::after when
592        // traversing them.
593        stylist.push_applicable_declarations(
594            self.element,
595            Some(pseudo_element),
596            None,
597            None,
598            &animation_declarations,
599            self.rule_inclusion,
600            &mut applicable_declarations,
601            &mut matching_context,
602        );
603
604        if applicable_declarations.is_empty() {
605            return None;
606        }
607
608        let rule_node = stylist
609            .rule_tree()
610            .compute_rule_node(&mut applicable_declarations, &self.context.shared.guards);
611
612        Some(MatchingResults {
613            rule_node,
614            flags: matching_context.extra_data.cascade_input_flags,
615        })
616    }
617
618    /// Resolve the starting style by recascading with @starting-style rules included, similar to
619    /// how after_change_style works.
620    pub fn resolve_starting_style(
621        &mut self,
622        primary_style: &Arc<ComputedValues>,
623    ) -> Option<ResolvedStyle> {
624        if !RuleTree::has_starting_style(primary_style.rules()) {
625            return None;
626        }
627        let inputs = CascadeInputs {
628            rules: Some(primary_style.rules().clone()),
629            visited_rules: primary_style.visited_rules().cloned(),
630            flags: primary_style.flags.for_cascade_inputs(),
631            included_cascade_flags: RuleCascadeFlags::STARTING_STYLE,
632        };
633        Some(self.cascade_style_and_visited_with_default_parents(inputs))
634    }
635
636    /// If there is no transition rule in the ComputedValues, it returns None.
637    pub fn after_change_style(
638        &mut self,
639        primary_style: &Arc<ComputedValues>,
640    ) -> Option<Arc<ComputedValues>> {
641        let rule_node = primary_style.rules();
642        let without_transition_rules = self
643            .context
644            .shared
645            .stylist
646            .rule_tree()
647            .remove_transition_rule_if_applicable(rule_node);
648        if without_transition_rules == *rule_node {
649            // We don't have transition rule in this case, so return None to let
650            // the caller use the original ComputedValues.
651            return None;
652        }
653
654        // FIXME(bug 868975): We probably need to transition visited style as well.
655        let inputs = CascadeInputs {
656            rules: Some(without_transition_rules),
657            visited_rules: primary_style.visited_rules().cloned(),
658            flags: primary_style.flags.for_cascade_inputs(),
659            included_cascade_flags: RuleCascadeFlags::empty(),
660        };
661
662        let style = self.cascade_style_and_visited_with_default_parents(inputs);
663        Some(style.0)
664    }
665}