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;
12use crate::matching::MatchMethods;
13use crate::properties::longhands::display::computed_value::T as Display;
14use crate::properties::{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.clone();
120    let layout_parent_data;
121    let mut layout_parent_style = parent_style;
122    if parent_style.map_or(false, |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().clone_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        Self {
177            element,
178            context,
179            rule_inclusion,
180            pseudo_resolution,
181            _marker: ::std::marker::PhantomData,
182        }
183    }
184
185    /// Resolve just the style of a given element.
186    pub fn resolve_primary_style(
187        &mut self,
188        parent_style: Option<&ComputedValues>,
189        layout_parent_style: Option<&ComputedValues>,
190    ) -> PrimaryStyle {
191        let primary_results = self.match_primary(VisitedHandlingMode::AllLinksUnvisited);
192
193        let inside_link = parent_style.map_or(false, |s| s.visited_style().is_some());
194
195        let visited_rules = if self.context.shared.visited_styles_enabled
196            && (inside_link || self.element.is_link())
197        {
198            let visited_matching_results =
199                self.match_primary(VisitedHandlingMode::RelevantLinkVisited);
200            Some(visited_matching_results.rule_node)
201        } else {
202            None
203        };
204
205        self.cascade_primary_style(
206            CascadeInputs {
207                rules: Some(primary_results.rule_node),
208                visited_rules,
209                flags: primary_results.flags,
210                included_cascade_flags: RuleCascadeFlags::empty(),
211            },
212            parent_style,
213            layout_parent_style,
214        )
215    }
216
217    fn cascade_primary_style(
218        &mut self,
219        inputs: CascadeInputs,
220        parent_style: Option<&ComputedValues>,
221        layout_parent_style: Option<&ComputedValues>,
222    ) -> PrimaryStyle {
223        // Before doing the cascade, check the sharing cache and see if we can
224        // reuse the style via rule node identity.
225        let may_reuse = self.element.matches_user_and_content_rules()
226            && parent_style.is_some()
227            && inputs.rules.is_some()
228            && inputs.included_cascade_flags.is_empty();
229
230        if may_reuse {
231            let cached = self.context.thread_local.sharing_cache.lookup_by_rules(
232                self.context.shared,
233                parent_style.unwrap(),
234                &inputs,
235                self.element,
236            );
237            if let Some(mut primary_style) = cached {
238                self.context.thread_local.statistics.styles_reused += 1;
239                primary_style.reused_via_rule_node |= true;
240                return primary_style;
241            }
242        }
243
244        // No style to reuse. Cascade the style, starting with visited style
245        // if necessary.
246        PrimaryStyle {
247            style: self.cascade_style_and_visited(
248                inputs,
249                parent_style,
250                layout_parent_style,
251                /* pseudo = */ None,
252            ),
253            reused_via_rule_node: false,
254        }
255    }
256
257    /// Resolve the style of a given element, and all its eager pseudo-elements.
258    pub fn resolve_style(
259        &mut self,
260        parent_style: Option<&ComputedValues>,
261        layout_parent_style: Option<&ComputedValues>,
262    ) -> ResolvedElementStyles {
263        let primary_style = self.resolve_primary_style(parent_style, layout_parent_style);
264
265        let mut pseudo_styles = EagerPseudoStyles::default();
266
267        if !self
268            .element
269            .implemented_pseudo_element()
270            .is_some_and(|p| !p.is_element_backed())
271        {
272            let layout_parent_style_for_pseudo =
273                layout_parent_style_for_pseudo(&primary_style, layout_parent_style);
274            SelectorImpl::each_eagerly_cascaded_pseudo_element(|pseudo| {
275                let pseudo_style = self.resolve_pseudo_style(
276                    &pseudo,
277                    &primary_style,
278                    layout_parent_style_for_pseudo,
279                );
280
281                if let Some(style) = pseudo_style {
282                    if !matches!(self.pseudo_resolution, PseudoElementResolution::Force)
283                        && eager_pseudo_is_definitely_not_generated(&pseudo, &style.0)
284                    {
285                        return;
286                    }
287                    pseudo_styles.set(&pseudo, style.0);
288                }
289            })
290        }
291
292        ResolvedElementStyles {
293            primary: primary_style,
294            pseudos: pseudo_styles,
295        }
296    }
297
298    /// Resolve an element's styles with the default inheritance parent/layout
299    /// parents.
300    pub fn resolve_style_with_default_parents(&mut self) -> ResolvedElementStyles {
301        with_default_parent_styles(self.element, |parent_style, layout_parent_style| {
302            self.resolve_style(parent_style, layout_parent_style)
303        })
304    }
305
306    /// Cascade a set of rules, using the default parent for inheritance.
307    pub fn cascade_style_and_visited_with_default_parents(
308        &mut self,
309        inputs: CascadeInputs,
310    ) -> ResolvedStyle {
311        with_default_parent_styles(self.element, |parent_style, layout_parent_style| {
312            self.cascade_style_and_visited(
313                inputs,
314                parent_style,
315                layout_parent_style,
316                /* pseudo = */ None,
317            )
318        })
319    }
320
321    /// Cascade a set of rules for pseudo element, using the default parent for inheritance.
322    pub fn cascade_style_and_visited_for_pseudo_with_default_parents(
323        &mut self,
324        inputs: CascadeInputs,
325        pseudo: &PseudoElement,
326        primary_style: &PrimaryStyle,
327    ) -> ResolvedStyle {
328        with_default_parent_styles(self.element, |_, layout_parent_style| {
329            let layout_parent_style_for_pseudo =
330                layout_parent_style_for_pseudo(primary_style, layout_parent_style);
331
332            self.cascade_style_and_visited(
333                inputs,
334                Some(primary_style.style()),
335                layout_parent_style_for_pseudo,
336                Some(pseudo),
337            )
338        })
339    }
340
341    fn cascade_style_and_visited(
342        &mut self,
343        inputs: CascadeInputs,
344        parent_style: Option<&ComputedValues>,
345        layout_parent_style: Option<&ComputedValues>,
346        pseudo: Option<&PseudoElement>,
347    ) -> ResolvedStyle {
348        debug_assert!(pseudo.map_or(true, |p| p.is_eager()));
349
350        let mut conditions = Default::default();
351        let values = self.context.shared.stylist.cascade_style_and_visited(
352            Some(self.element),
353            pseudo,
354            &inputs,
355            &self.context.shared.guards,
356            parent_style,
357            layout_parent_style,
358            FirstLineReparenting::No,
359            /* try_tactic = */ &Default::default(),
360            Some(&self.context.thread_local.rule_cache),
361            &mut conditions,
362        );
363
364        self.context.thread_local.rule_cache.insert_if_possible(
365            &self.context.shared.guards,
366            &values,
367            pseudo,
368            &inputs,
369            &conditions,
370        );
371
372        ResolvedStyle(values)
373    }
374
375    /// Cascade the element and pseudo-element styles with the default parents.
376    pub fn cascade_styles_with_default_parents(
377        &mut self,
378        inputs: ElementCascadeInputs,
379    ) -> ResolvedElementStyles {
380        with_default_parent_styles(self.element, move |parent_style, layout_parent_style| {
381            let primary_style =
382                self.cascade_primary_style(inputs.primary, parent_style, layout_parent_style);
383
384            let mut pseudo_styles = EagerPseudoStyles::default();
385            if let Some(mut pseudo_array) = inputs.pseudos.into_array() {
386                let layout_parent_style_for_pseudo = if primary_style.style().is_display_contents()
387                {
388                    layout_parent_style
389                } else {
390                    Some(primary_style.style())
391                };
392
393                for (i, inputs) in pseudo_array.iter_mut().enumerate() {
394                    if let Some(inputs) = inputs.take() {
395                        let pseudo = PseudoElement::from_eager_index(i);
396
397                        let style = self.cascade_style_and_visited(
398                            inputs,
399                            Some(primary_style.style()),
400                            layout_parent_style_for_pseudo,
401                            Some(&pseudo),
402                        );
403
404                        if !matches!(self.pseudo_resolution, PseudoElementResolution::Force)
405                            && eager_pseudo_is_definitely_not_generated(&pseudo, &style.0)
406                        {
407                            continue;
408                        }
409
410                        pseudo_styles.set(&pseudo, style.0);
411                    }
412                }
413            }
414
415            ResolvedElementStyles {
416                primary: primary_style,
417                pseudos: pseudo_styles,
418            }
419        })
420    }
421
422    fn resolve_pseudo_style(
423        &mut self,
424        pseudo: &PseudoElement,
425        originating_element_style: &PrimaryStyle,
426        layout_parent_style: Option<&ComputedValues>,
427    ) -> Option<ResolvedStyle> {
428        let MatchingResults {
429            rule_node,
430            mut flags,
431        } = self.match_pseudo(
432            &originating_element_style.style.0,
433            pseudo,
434            VisitedHandlingMode::AllLinksUnvisited,
435        )?;
436
437        let mut visited_rules = None;
438        if originating_element_style.style().visited_style().is_some() {
439            visited_rules = self
440                .match_pseudo(
441                    &originating_element_style.style.0,
442                    pseudo,
443                    VisitedHandlingMode::RelevantLinkVisited,
444                )
445                .map(|results| {
446                    flags |= results.flags;
447                    results.rule_node
448                });
449        }
450
451        Some(self.cascade_style_and_visited(
452            CascadeInputs {
453                rules: Some(rule_node),
454                visited_rules,
455                flags,
456                included_cascade_flags: RuleCascadeFlags::empty(),
457            },
458            Some(originating_element_style.style()),
459            layout_parent_style,
460            Some(pseudo),
461        ))
462    }
463
464    fn match_primary(&mut self, visited_handling: VisitedHandlingMode) -> MatchingResults {
465        debug!(
466            "Match primary for {:?}, visited: {:?}",
467            self.element, visited_handling
468        );
469        let mut applicable_declarations = ApplicableDeclarationList::new();
470
471        let bloom_filter = self.context.thread_local.bloom_filter.filter();
472        let selector_caches = &mut self.context.thread_local.selector_caches;
473        let mut matching_context = MatchingContext::new_for_visited(
474            MatchingMode::Normal,
475            Some(bloom_filter),
476            selector_caches,
477            visited_handling,
478            self.context.shared.quirks_mode(),
479            NeedsSelectorFlags::Yes,
480            MatchingForInvalidation::No,
481        );
482
483        let stylist = &self.context.shared.stylist;
484        // Compute the primary rule node.
485        stylist.push_applicable_declarations(
486            self.element,
487            None,
488            self.element.style_attribute(),
489            self.element.smil_override(),
490            self.element.animation_declarations(self.context.shared),
491            self.rule_inclusion,
492            &mut applicable_declarations,
493            &mut matching_context,
494        );
495
496        // FIXME(emilio): This is a hack for animations, and should go away.
497        self.element.unset_dirty_style_attribute();
498
499        let rule_node = stylist
500            .rule_tree()
501            .compute_rule_node(&mut applicable_declarations, &self.context.shared.guards);
502
503        if log_enabled!(Trace) {
504            trace!("Matched rules for {:?}:", self.element);
505            for rn in rule_node.self_and_ancestors() {
506                let source = rn.style_source();
507                if source.is_some() {
508                    trace!(" > {:?}", source);
509                }
510            }
511        }
512
513        MatchingResults {
514            rule_node,
515            flags: matching_context.extra_data.cascade_input_flags,
516        }
517    }
518
519    fn match_pseudo(
520        &mut self,
521        originating_element_style: &ComputedValues,
522        pseudo_element: &PseudoElement,
523        visited_handling: VisitedHandlingMode,
524    ) -> Option<MatchingResults> {
525        debug!(
526            "Match pseudo {:?} for {:?}, visited: {:?}",
527            self.element, pseudo_element, visited_handling
528        );
529        debug_assert!(pseudo_element.is_eager());
530
531        let mut applicable_declarations = ApplicableDeclarationList::new();
532
533        let stylist = &self.context.shared.stylist;
534
535        if !self
536            .element
537            .may_generate_pseudo(pseudo_element, originating_element_style)
538        {
539            return None;
540        }
541
542        let bloom_filter = self.context.thread_local.bloom_filter.filter();
543        let selector_caches = &mut self.context.thread_local.selector_caches;
544
545        let mut matching_context = MatchingContext::<'_, E::Impl>::new_for_visited(
546            MatchingMode::ForStatelessPseudoElement,
547            Some(bloom_filter),
548            selector_caches,
549            visited_handling,
550            self.context.shared.quirks_mode(),
551            NeedsSelectorFlags::Yes,
552            MatchingForInvalidation::No,
553        );
554        matching_context.extra_data.originating_element_style = Some(originating_element_style);
555
556        // NB: We handle animation rules for ::before and ::after when
557        // traversing them.
558        stylist.push_applicable_declarations(
559            self.element,
560            Some(pseudo_element),
561            None,
562            None,
563            /* animation_declarations = */ Default::default(),
564            self.rule_inclusion,
565            &mut applicable_declarations,
566            &mut matching_context,
567        );
568
569        if applicable_declarations.is_empty() {
570            return None;
571        }
572
573        let rule_node = stylist
574            .rule_tree()
575            .compute_rule_node(&mut applicable_declarations, &self.context.shared.guards);
576
577        Some(MatchingResults {
578            rule_node,
579            flags: matching_context.extra_data.cascade_input_flags,
580        })
581    }
582
583    /// Resolve the starting style by recascading with @starting-style rules included, similar to
584    /// how after_change_style works.
585    pub fn resolve_starting_style(
586        &mut self,
587        primary_style: &Arc<ComputedValues>,
588    ) -> Option<ResolvedStyle> {
589        if !RuleTree::has_starting_style(primary_style.rules()) {
590            return None;
591        }
592        let inputs = CascadeInputs {
593            rules: Some(primary_style.rules().clone()),
594            visited_rules: primary_style.visited_rules().cloned(),
595            flags: primary_style.flags.for_cascade_inputs(),
596            included_cascade_flags: RuleCascadeFlags::STARTING_STYLE,
597        };
598        Some(self.cascade_style_and_visited_with_default_parents(inputs))
599    }
600
601    /// If there is no transition rule in the ComputedValues, it returns None.
602    pub fn after_change_style(
603        &mut self,
604        primary_style: &Arc<ComputedValues>,
605    ) -> Option<Arc<ComputedValues>> {
606        let rule_node = primary_style.rules();
607        let without_transition_rules = RuleTree::remove_transition_rule_if_applicable(rule_node);
608        if without_transition_rules == *rule_node {
609            // We don't have transition rule in this case, so return None to let
610            // the caller use the original ComputedValues.
611            return None;
612        }
613
614        // FIXME(bug 868975): We probably need to transition visited style as well.
615        let inputs = CascadeInputs {
616            rules: Some(without_transition_rules),
617            visited_rules: primary_style.visited_rules().cloned(),
618            flags: primary_style.flags.for_cascade_inputs(),
619            included_cascade_flags: RuleCascadeFlags::empty(),
620        };
621
622        let style = self.cascade_style_and_visited_with_default_parents(inputs);
623        Some(style.0)
624    }
625}