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