tor-browser

The Tor Browser
git clone https://git.dasho.dev/tor-browser.git
Log | Files | Refs | README | LICENSE

style_resolver.rs (23875B)


      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 
      7 use crate::applicable_declarations::ApplicableDeclarationList;
      8 use crate::computed_value_flags::ComputedValueFlags;
      9 use crate::context::{CascadeInputs, ElementCascadeInputs, StyleContext};
     10 use crate::data::{EagerPseudoStyles, ElementStyles};
     11 use crate::dom::TElement;
     12 use crate::matching::MatchMethods;
     13 use crate::properties::longhands::display::computed_value::T as Display;
     14 use crate::properties::{ComputedValues, FirstLineReparenting};
     15 use crate::rule_tree::StrongRuleNode;
     16 use crate::selector_parser::{PseudoElement, SelectorImpl};
     17 use crate::stylist::RuleInclusion;
     18 use log::Level::Trace;
     19 use selectors::matching::{
     20    IncludeStartingStyle, MatchingContext, MatchingForInvalidation, MatchingMode,
     21    NeedsSelectorFlags, VisitedHandlingMode,
     22 };
     23 use selectors::parser::PseudoElement as PseudoElementTrait;
     24 use servo_arc::Arc;
     25 
     26 /// Whether pseudo-elements should be resolved or not.
     27 #[derive(Clone, Copy, Debug, Eq, PartialEq)]
     28 pub 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.
     36 pub struct StyleResolverForElement<'a, 'ctx, 'le, E>
     37 where
     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 
     49 struct MatchingResults {
     50    rule_node: StrongRuleNode,
     51    flags: ComputedValueFlags,
     52    has_starting_style: bool,
     53 }
     54 
     55 /// A style returned from the resolver machinery.
     56 pub struct ResolvedStyle(pub Arc<ComputedValues>);
     57 
     58 impl 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.
     67 pub 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.
     81 pub struct ResolvedElementStyles {
     82    /// Primary style.
     83    pub primary: PrimaryStyle,
     84    /// Pseudo styles.
     85    pub pseudos: EagerPseudoStyles,
     86 }
     87 
     88 impl 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 
    106 impl PrimaryStyle {
    107    /// Convenience accessor for the style.
    108    pub fn style(&self) -> &ComputedValues {
    109        &*self.style.0
    110    }
    111 }
    112 
    113 impl 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 
    122 pub(crate) fn with_default_parent_styles<E, F, R>(element: E, f: F) -> R
    123 where
    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 
    146 fn 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 
    157 fn 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 
    184 impl<'a, 'ctx, 'le, E> StyleResolverForElement<'a, 'ctx, 'le, E>
    185 where
    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 }