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