Bug 1474959 part 2 - Set visited_style and visited bit after ComputedValues is built. r?emilio draft
authorXidorn Quan <me@upsuper.org>
Thu, 12 Jul 2018 21:19:52 +1000
changeset 817332 0832a4a9fb22be2083acf1f6729434ab6e030b0f
parent 817331 807dbf53afda5a89e1a1ad1b7b84f488efe58481
child 817333 589ef910cbc71ebd1ed83c40bde81e0fc4c2add5
push id116022
push userxquan@mozilla.com
push dateThu, 12 Jul 2018 12:09:47 +0000
reviewersemilio
bugs1474959
milestone63.0a1
Bug 1474959 part 2 - Set visited_style and visited bit after ComputedValues is built. r?emilio MozReview-Commit-ID: 6DeBpQuJGDZ
servo/components/style/animation.rs
servo/components/style/properties/gecko.mako.rs
servo/components/style/properties/properties.mako.rs
servo/components/style/style_adjuster.rs
servo/components/style/stylist.rs
servo/ports/geckolib/glue.rs
--- a/servo/components/style/animation.rs
+++ b/servo/components/style/animation.rs
@@ -499,17 +499,16 @@ where
                 context.stylist.device(),
                 /* pseudo = */ None,
                 previous_style.rules(),
                 &context.guards,
                 iter,
                 Some(previous_style),
                 Some(previous_style),
                 Some(previous_style),
-                /* visited_style = */ None,
                 font_metrics_provider,
                 CascadeFlags::empty(),
                 context.quirks_mode(),
                 /* rule_cache = */ None,
                 &mut Default::default(),
                 /* element = */ None,
             );
             computed.shareable()
--- a/servo/components/style/properties/gecko.mako.rs
+++ b/servo/components/style/properties/gecko.mako.rs
@@ -8,16 +8,17 @@
     from data import to_camel_case, to_camel_case_lower
     from data import Keyword
 %>
 <%namespace name="helpers" file="/helpers.mako.rs" />
 
 use Atom;
 use app_units::Au;
 use custom_properties::CustomPropertiesMap;
+use dom::TElement;
 use gecko_bindings::bindings;
 % for style_struct in data.style_structs:
 use gecko_bindings::structs::${style_struct.gecko_ffi_name};
 use gecko_bindings::bindings::Gecko_Construct_Default_${style_struct.gecko_ffi_name};
 use gecko_bindings::bindings::Gecko_CopyConstruct_${style_struct.gecko_ffi_name};
 use gecko_bindings::bindings::Gecko_Destroy_${style_struct.gecko_ffi_name};
 % endfor
 use gecko_bindings::bindings::Gecko_CopyCounterStyle;
@@ -45,16 +46,17 @@ use gecko_bindings::sugar::refptr::RefPt
 use gecko::values::convert_nscolor_to_rgba;
 use gecko::values::convert_rgba_to_nscolor;
 use gecko::values::GeckoStyleCoordConvertible;
 use gecko::values::round_border_to_device_pixels;
 use logical_geometry::WritingMode;
 use media_queries::Device;
 use properties::computed_value_flags::*;
 use properties::longhands;
+use properties::StyleBuilder;
 use rule_tree::StrongRuleNode;
 use selector_parser::PseudoElement;
 use servo_arc::{Arc, RawOffsetArc, UniqueArc};
 use std::marker::PhantomData;
 use std::mem::{forget, uninitialized, transmute, zeroed};
 use std::{cmp, ops, ptr};
 use values::{self, CustomIdent, Either, KeyframesName, None_};
 use values::computed::{NonNegativeLength, Percentage, TransitionProperty};
@@ -84,44 +86,41 @@ impl ComputedValues {
     pub fn new(
         device: &Device,
         parent: Option<<&ComputedValues>,
         pseudo: Option<<&PseudoElement>,
         custom_properties: Option<Arc<CustomPropertiesMap>>,
         writing_mode: WritingMode,
         flags: ComputedValueFlags,
         rules: Option<StrongRuleNode>,
-        visited_style: Option<Arc<ComputedValues>>,
         % for style_struct in data.style_structs:
         ${style_struct.ident}: Arc<style_structs::${style_struct.name}>,
         % endfor
     ) -> UniqueArc<Self> {
         ComputedValuesInner::new(
             custom_properties,
             writing_mode,
             flags,
             rules,
-            visited_style,
             % for style_struct in data.style_structs:
             ${style_struct.ident},
             % endfor
         ).to_outer(
             device.pres_context(),
             parent,
             pseudo.map(|p| p.pseudo_info())
         )
     }
 
     pub fn default_values(pres_context: RawGeckoPresContextBorrowed) -> Arc<Self> {
         ComputedValuesInner::new(
             /* custom_properties = */ None,
             /* writing_mode = */ WritingMode::empty(), // FIXME(bz): This seems dubious
             ComputedValueFlags::empty(),
             /* rules = */ None,
-            /* visited_style = */ None,
             % for style_struct in data.style_structs:
             style_structs::${style_struct.name}::default(pres_context),
             % endfor
         ).to_outer(pres_context, None, None).shareable()
     }
 
     pub fn pseudo(&self) -> Option<PseudoElement> {
         let atom = (self.0).mPseudoTag.mRawPtr;
@@ -155,16 +154,82 @@ impl ComputedValues {
         old_values.map_or(false, |old| {
             let old_display_style = old.get_box().clone_display();
             let new_display_style = self.get_box().clone_display();
             old_display_style == Display::None &&
             new_display_style != Display::None
         })
     }
 
+    fn has_visited_bit(&self) -> bool {
+        self.0.mBits & structs::ComputedStyleBit_RelevantLinkVisited != 0
+    }
+
+    fn set_visited_style_internal(
+        &mut self,
+        visited_style: Arc<ComputedValues>,
+        visited_bit: bool,
+    ) {
+        const VISITED: structs::ComputedStyleBit =
+            structs::ComputedStyleBit_RelevantLinkVisited;
+        debug_assert!(self.0.mBits & VISITED == 0,
+                      "RelevantLinkVisited bit should not have been set");
+        let visited_style = Arc::into_raw_offset(visited_style);
+        self.0.mSource.visited_style = Some(visited_style);
+        if visited_bit {
+            self.0.mBits |= VISITED;
+        }
+    }
+
+    /// Setup visited style of this ComputedValues.
+    pub fn set_visited_style<E>(
+        &mut self,
+        parent_style: Option< &ComputedValues>,
+        visited_style: Arc<ComputedValues>,
+        element: Option<E>,
+    )
+    where
+        E: TElement
+    {
+        let mut visited_bit =
+            parent_style.map_or(false, |style| style.has_visited_bit());
+        if let Some(e) = element {
+            if e.is_link() && self.pseudo().is_none() {
+                visited_bit = e.is_visited_link();
+            }
+        }
+        self.set_visited_style_internal(visited_style, visited_bit);
+    }
+
+    /// Handle inherited visited style of this ComputedValues.
+    ///
+    /// This should only be called for pseudo-elements.
+    pub fn inherit_visited(
+        &mut self,
+        device: &Device,
+        parent: &ComputedValues,
+    ) {
+        debug_assert!(self.pseudo().is_some(),
+                      "inherit_visited should only be called for pseudos");
+        let parent_visited_style = match parent.visited_style() {
+            Some(style) => style,
+            None => return,
+        };
+        // Rebuild the visited style from the parent, ensuring that it will also
+        // not have rules.  This matches this unvisited style.  This assumes
+        // that the caller doesn't need to adjust or process visited style, so
+        // we can just build visited style here for simplicity.
+        let visited_style = StyleBuilder::for_inheritance(
+            device,
+            Some(parent_visited_style),
+            self.pseudo().as_ref(),
+        ).build().shareable();
+        let visited_bit = parent.has_visited_bit();
+        self.set_visited_style_internal(visited_style, visited_bit);
+    }
 }
 
 impl Drop for ComputedValues {
     fn drop(&mut self) {
         unsafe {
             bindings::Gecko_ComputedStyle_Destroy(&mut self.0);
         }
     }
@@ -197,26 +262,25 @@ impl Clone for ComputedValuesInner {
 type PseudoInfo = (*mut structs::nsAtom, structs::CSSPseudoElementType);
 type ParentComputedStyleInfo<'a> = Option< &'a ComputedValues>;
 
 impl ComputedValuesInner {
     pub fn new(custom_properties: Option<Arc<CustomPropertiesMap>>,
                writing_mode: WritingMode,
                flags: ComputedValueFlags,
                rules: Option<StrongRuleNode>,
-               visited_style: Option<Arc<ComputedValues>>,
                % for style_struct in data.style_structs:
                ${style_struct.ident}: Arc<style_structs::${style_struct.name}>,
                % endfor
     ) -> Self {
         ComputedValuesInner {
             custom_properties: custom_properties,
             writing_mode: writing_mode,
             rules: rules,
-            visited_style: visited_style.map(|x| Arc::into_raw_offset(x)),
+            visited_style: None,
             flags: flags,
             % for style_struct in data.style_structs:
             ${style_struct.gecko_name}: Arc::into_raw_offset(${style_struct.ident}),
             % endfor
         }
     }
 
     fn to_outer(
--- a/servo/components/style/properties/properties.mako.rs
+++ b/servo/components/style/properties/properties.mako.rs
@@ -2722,17 +2722,16 @@ impl ComputedValues {
     pub fn new(
         _: &Device,
         _: Option<<&ComputedValues>,
         _: Option<<&PseudoElement>,
         custom_properties: Option<Arc<::custom_properties::CustomPropertiesMap>>,
         writing_mode: WritingMode,
         flags: ComputedValueFlags,
         rules: Option<StrongRuleNode>,
-        visited_style: Option<Arc<ComputedValues>>,
         % for style_struct in data.active_style_structs():
         ${style_struct.ident}: Arc<style_structs::${style_struct.name}>,
         % endfor
     ) -> UniqueArc<Self> {
         UniqueArc::new(Self {
             inner: ComputedValuesInner {
                 custom_properties,
                 writing_mode,
@@ -2763,16 +2762,28 @@ impl ComputedValues {
             PropertyDeclarationId::Custom(name) => {
                 self.custom_properties
                     .as_ref()
                     .and_then(|map| map.get(name))
                     .map_or(String::new(), |value| value.to_css_string())
             }
         }
     }
+
+    /// Setup visited style of this ComputedValues.
+    pub fn set_visited_style<E>(
+        &mut self,
+        visited_style: Arc<ComputedValues>,
+        _element: Option<E>
+    )
+    where
+        E: TElement,
+    {
+        self.visited_style = Some(visited_style);
+    }
 }
 
 #[cfg(feature = "servo")]
 impl ops::Deref for ComputedValues {
     type Target = ComputedValuesInner;
     fn deref(&self) -> &ComputedValuesInner {
         &self.inner
     }
@@ -3148,36 +3159,32 @@ pub struct StyleBuilder<'a> {
     modified_reset: bool,
 
     /// The writing mode flags.
     ///
     /// TODO(emilio): Make private.
     pub writing_mode: WritingMode,
     /// Flags for the computed value.
     pub flags: ComputedValueFlags,
-    /// The element's style if visited, only computed if there's a relevant link
-    /// for this element.  A element's "relevant link" is the element being
-    /// matched if it is a link or the nearest ancestor link.
-    visited_style: Option<Arc<ComputedValues>>,
+
     % for style_struct in data.active_style_structs():
         ${style_struct.ident}: StyleStructRef<'a, style_structs::${style_struct.name}>,
     % endfor
 }
 
 impl<'a> StyleBuilder<'a> {
     /// Trivially construct a `StyleBuilder`.
     fn new(
         device: &'a Device,
         parent_style: Option<<&'a ComputedValues>,
         parent_style_ignoring_first_line: Option<<&'a ComputedValues>,
         pseudo: Option<<&'a PseudoElement>,
         cascade_flags: CascadeFlags,
         rules: Option<StrongRuleNode>,
         custom_properties: Option<Arc<::custom_properties::CustomPropertiesMap>>,
-        visited_style: Option<Arc<ComputedValues>>,
     ) -> Self {
         debug_assert_eq!(parent_style.is_some(), parent_style_ignoring_first_line.is_some());
         #[cfg(feature = "gecko")]
         debug_assert!(parent_style.is_none() ||
                       ::std::ptr::eq(parent_style.unwrap(),
                                      parent_style_ignoring_first_line.unwrap()) ||
                       parent_style.unwrap().pseudo() == Some(PseudoElement::FirstLine));
         let reset_style = device.default_computed_values();
@@ -3205,17 +3212,16 @@ impl<'a> StyleBuilder<'a> {
             inherited_style_ignoring_first_line,
             reset_style,
             pseudo,
             rules,
             modified_reset: false,
             custom_properties,
             writing_mode: inherited_style.writing_mode,
             flags,
-            visited_style,
             % for style_struct in data.active_style_structs():
             % if style_struct.inherited:
             ${style_struct.ident}: StyleStructRef::Borrowed(inherited_style.${style_struct.name_lower}_arc()),
             % else:
             ${style_struct.ident}: StyleStructRef::Borrowed(reset_style.${style_struct.name_lower}_arc()),
             % endif
             % endfor
         }
@@ -3250,17 +3256,16 @@ impl<'a> StyleBuilder<'a> {
             inherited_style_ignoring_first_line: inherited_style,
             reset_style,
             pseudo: None,
             modified_reset: false,
             rules: None,
             custom_properties: style_to_derive_from.custom_properties().cloned(),
             writing_mode: style_to_derive_from.writing_mode,
             flags: style_to_derive_from.flags,
-            visited_style: None,
             % for style_struct in data.active_style_structs():
             ${style_struct.ident}: StyleStructRef::Borrowed(
                 style_to_derive_from.${style_struct.name_lower}_arc()
             ),
             % endfor
         }
     }
 
@@ -3357,47 +3362,27 @@ impl<'a> StyleBuilder<'a> {
 
     /// Inherits style from the parent element, accounting for the default
     /// computed values that need to be provided as well.
     pub fn for_inheritance(
         device: &'a Device,
         parent: Option<<&'a ComputedValues>,
         pseudo: Option<<&'a PseudoElement>,
     ) -> Self {
-        // Rebuild the visited style from the parent, ensuring that it will also
-        // not have rules.  This matches the unvisited style that will be
-        // produced by this builder.  This assumes that the caller doesn't need
-        // to adjust or process visited style, so we can just build visited
-        // style here for simplicity.
-        let visited_style = parent.and_then(|parent| {
-            parent.visited_style().map(|style| {
-                Self::for_inheritance(
-                    device,
-                    Some(style),
-                    pseudo,
-                ).build().shareable()
-            })
-        });
         Self::new(
             device,
             parent,
             parent,
             pseudo,
             CascadeFlags::empty(),
             /* rules = */ None,
             parent.and_then(|p| p.custom_properties().cloned()),
-            visited_style,
         )
     }
 
-    /// Returns whether we have a visited style.
-    pub fn has_visited_style(&self) -> bool {
-        self.visited_style.is_some()
-    }
-
     /// Returns whether we're a pseudo-elements style.
     pub fn is_pseudo_element(&self) -> bool {
         self.pseudo.map_or(false, |p| !p.is_anon_box())
     }
 
     /// Returns the style we're getting reset properties from.
     pub fn default_style(&self) -> &'a ComputedValues {
         self.reset_style
@@ -3487,17 +3472,16 @@ impl<'a> StyleBuilder<'a> {
         ComputedValues::new(
             self.device,
             self.parent_style,
             self.pseudo,
             self.custom_properties,
             self.writing_mode,
             self.flags,
             self.rules,
-            self.visited_style,
             % for style_struct in data.active_style_structs():
             self.${style_struct.ident}.build(),
             % endfor
         )
     }
 
     /// Get the custom properties map if necessary.
     ///
@@ -3626,17 +3610,16 @@ bitflags! {
 pub fn cascade<E>(
     device: &Device,
     pseudo: Option<<&PseudoElement>,
     rule_node: &StrongRuleNode,
     guards: &StylesheetGuards,
     parent_style: Option<<&ComputedValues>,
     parent_style_ignoring_first_line: Option<<&ComputedValues>,
     layout_parent_style: Option<<&ComputedValues>,
-    visited_style: Option<Arc<ComputedValues>>,
     font_metrics_provider: &FontMetricsProvider,
     flags: CascadeFlags,
     quirks_mode: QuirksMode,
     rule_cache: Option<<&RuleCache>,
     rule_cache_conditions: &mut RuleCacheConditions,
     element: Option<E>,
 ) -> UniqueArc<ComputedValues>
 where
@@ -3689,17 +3672,16 @@ where
         device,
         pseudo,
         rule_node,
         guards,
         iter_declarations,
         parent_style,
         parent_style_ignoring_first_line,
         layout_parent_style,
-        visited_style,
         font_metrics_provider,
         flags,
         quirks_mode,
         rule_cache,
         rule_cache_conditions,
         element,
     )
 }
@@ -3710,17 +3692,16 @@ pub fn apply_declarations<'a, E, F, I>(
     device: &Device,
     pseudo: Option<<&PseudoElement>,
     rules: &StrongRuleNode,
     guards: &StylesheetGuards,
     iter_declarations: F,
     parent_style: Option<<&ComputedValues>,
     parent_style_ignoring_first_line: Option<<&ComputedValues>,
     layout_parent_style: Option<<&ComputedValues>,
-    visited_style: Option<Arc<ComputedValues>>,
     font_metrics_provider: &FontMetricsProvider,
     flags: CascadeFlags,
     quirks_mode: QuirksMode,
     rule_cache: Option<<&RuleCache>,
     rule_cache_conditions: &mut RuleCacheConditions,
     element: Option<E>,
 ) -> UniqueArc<ComputedValues>
 where
@@ -3767,17 +3748,16 @@ where
         builder: StyleBuilder::new(
             device,
             parent_style,
             parent_style_ignoring_first_line,
             pseudo,
             flags,
             Some(rules.clone()),
             custom_properties,
-            visited_style,
         ),
         cached_system_font: None,
         in_media_query: false,
         for_smil_animation: false,
         for_non_inherited_property: None,
         font_metrics_provider,
         quirks_mode,
         rule_cache_conditions: RefCell::new(rule_cache_conditions),
--- a/servo/components/style/style_adjuster.rs
+++ b/servo/components/style/style_adjuster.rs
@@ -600,51 +600,16 @@ impl<'a, 'b: 'a> StyleAdjuster<'a, 'b> {
                 _ => None,
             };
             if let Some(new_value) = new_value {
                 self.style.mutate_text().set_unicode_bidi(new_value);
             }
         }
     }
 
-    /// Computes the RELEVANT_LINK_VISITED flag based on the parent style and on
-    /// whether we're a relevant link.
-    ///
-    /// NOTE(emilio): We don't do this for text styles, which is... dubious, but
-    /// Gecko doesn't seem to do it either. It's extremely easy to do if needed
-    /// though.
-    ///
-    /// FIXME(emilio): This isn't technically a style adjustment thingie, could
-    /// it move somewhere else?
-    fn adjust_for_visited<E>(&mut self, element: Option<E>)
-    where
-        E: TElement,
-    {
-        if !self.style.has_visited_style() {
-            return;
-        }
-
-        let is_link_element = self.style.pseudo.is_none() && element.map_or(false, |e| e.is_link());
-
-        if !is_link_element {
-            return;
-        }
-
-        if element.unwrap().is_visited_link() {
-            self.style
-                .flags
-                .insert(ComputedValueFlags::IS_RELEVANT_LINK_VISITED);
-        } else {
-            // Need to remove to handle unvisited link inside visited.
-            self.style
-                .flags
-                .remove(ComputedValueFlags::IS_RELEVANT_LINK_VISITED);
-        }
-    }
-
     /// Resolves "justify-items: legacy" based on the inherited style if needed
     /// to comply with:
     ///
     /// <https://drafts.csswg.org/css-align/#valdef-justify-items-legacy>
     #[cfg(feature = "gecko")]
     fn adjust_for_justify_items(&mut self) {
         use values::specified::align;
         let justify_items = self.style.get_position().clone_justify_items();
@@ -709,17 +674,16 @@ impl<'a, 'b: 'a> StyleAdjuster<'a, 'b> {
         // affected by these adjustments and it'd be just wasted work anyway.
         //
         // It also doesn't make much sense to adjust them, since we don't
         // cascade most properties anyway, and they wouldn't be looked up.
         if flags.contains(CascadeFlags::VISITED_DEPENDENT_ONLY) {
             return;
         }
 
-        self.adjust_for_visited(element);
         #[cfg(feature = "gecko")]
         {
             self.adjust_for_prohibited_display_contents(element);
             self.adjust_for_fieldset_content(layout_parent_style);
         }
         self.adjust_for_top_layer();
         self.blockify_if_necessary(layout_parent_style, element);
         self.adjust_for_position();
--- a/servo/components/style/stylist.rs
+++ b/servo/components/style/stylist.rs
@@ -842,19 +842,40 @@ impl Stylist {
     ) -> Arc<ComputedValues>
     where
         E: TElement,
     {
         debug_assert!(pseudo.is_some() || element.is_some(), "Huh?");
 
         let cascade_flags = pseudo.map_or(CascadeFlags::empty(), |p| p.cascade_flags());
 
+        // Read the comment on `precomputed_values_for_pseudo` to see why it's
+        // difficult to assert that display: contents nodes never arrive here
+        // (tl;dr: It doesn't apply for replaced elements and such, but the
+        // computed value is still "contents").
+        //
+        // FIXME(emilio): We should assert that it holds if pseudo.is_none()!
+        let mut values = properties::cascade::<E>(
+            &self.device,
+            pseudo,
+            inputs.rules.as_ref().unwrap_or(self.rule_tree.root()),
+            guards,
+            parent_style,
+            parent_style_ignoring_first_line,
+            layout_parent_style,
+            font_metrics,
+            cascade_flags,
+            self.quirks_mode,
+            rule_cache,
+            rule_cache_conditions,
+            element,
+        );
+
         // We need to compute visited values if we have visited rules or if our
         // parent has visited values.
-        let mut visited_values = None;
         if inputs.visited_rules.is_some() || parent_style.and_then(|s| s.visited_style()).is_some()
         {
             // At this point inputs may have visited rules, or rules.
             let rule_node = match inputs.visited_rules.as_ref() {
                 Some(rules) => rules,
                 None => inputs.rules.as_ref().unwrap_or(self.rule_tree.root()),
             };
 
@@ -872,56 +893,35 @@ impl Stylist {
                 inherited_style = parent_style
                     .map(|parent_style| parent_style.visited_style().unwrap_or(parent_style));
                 inherited_style_ignoring_first_line = parent_style_ignoring_first_line
                     .map(|parent_style| parent_style.visited_style().unwrap_or(parent_style));
                 layout_parent_style_for_visited = layout_parent_style
                     .map(|parent_style| parent_style.visited_style().unwrap_or(parent_style));
             }
 
-            visited_values = Some(properties::cascade::<E>(
+            let visited_style = properties::cascade::<E>(
                 &self.device,
                 pseudo,
                 rule_node,
                 guards,
                 inherited_style,
                 inherited_style_ignoring_first_line,
                 layout_parent_style_for_visited,
-                None,
                 font_metrics,
                 cascade_flags | CascadeFlags::VISITED_DEPENDENT_ONLY,
                 self.quirks_mode,
                 rule_cache,
                 rule_cache_conditions,
                 element,
-            ).shareable());
+            ).shareable();
+            values.set_visited_style(parent_style, visited_style, element);
         }
 
-        // Read the comment on `precomputed_values_for_pseudo` to see why it's
-        // difficult to assert that display: contents nodes never arrive here
-        // (tl;dr: It doesn't apply for replaced elements and such, but the
-        // computed value is still "contents").
-        //
-        // FIXME(emilio): We should assert that it holds if pseudo.is_none()!
-        properties::cascade::<E>(
-            &self.device,
-            pseudo,
-            inputs.rules.as_ref().unwrap_or(self.rule_tree.root()),
-            guards,
-            parent_style,
-            parent_style_ignoring_first_line,
-            layout_parent_style,
-            visited_values,
-            font_metrics,
-            cascade_flags,
-            self.quirks_mode,
-            rule_cache,
-            rule_cache_conditions,
-            element,
-        ).shareable()
+        values.shareable()
     }
 
     /// Computes the cascade inputs for a lazily-cascaded pseudo-element.
     ///
     /// See the documentation on lazy pseudo-elements in
     /// docs/components/style.md
     fn lazy_pseudo_rules<E>(
         &self,
@@ -1579,17 +1579,16 @@ impl Stylist {
             &self.device,
             /* pseudo = */ None,
             self.rule_tree.root(),
             guards,
             iter_declarations,
             Some(parent_style),
             Some(parent_style),
             Some(parent_style),
-            None,
             &metrics,
             CascadeFlags::empty(),
             self.quirks_mode,
             /* rule_cache = */ None,
             &mut Default::default(),
             /* element = */ None,
         ).shareable()
     }
--- a/servo/ports/geckolib/glue.rs
+++ b/servo/ports/geckolib/glue.rs
@@ -3074,21 +3074,24 @@ fn get_pseudo_style(
         },
     };
 
     if is_probe {
         return style;
     }
 
     Some(style.unwrap_or_else(|| {
-        StyleBuilder::for_inheritance(
-            doc_data.stylist.device(),
+        let device = doc_data.stylist.device();
+        let mut style = StyleBuilder::for_inheritance(
+            device,
             Some(styles.primary()),
             Some(pseudo),
-        ).build().shareable()
+        ).build();
+        style.inherit_visited(device, styles.primary());
+        style.shareable()
     }))
 }
 
 #[no_mangle]
 pub unsafe extern "C" fn Servo_ComputedValues_Inherit(
     raw_data: RawServoStyleSetBorrowed,
     pseudo_tag: *mut nsAtom,
     parent_style_context: ComputedStyleBorrowedOrNull,
@@ -3096,27 +3099,32 @@ pub unsafe extern "C" fn Servo_ComputedV
 ) -> ComputedStyleStrong {
     let data = PerDocumentStyleData::from_ffi(raw_data).borrow();
 
     let for_text = target == structs::InheritTarget::Text;
     let atom = Atom::from_raw(pseudo_tag);
     let pseudo = PseudoElement::from_anon_box_atom(&atom)
         .expect("Not an anon-box? Gah!");
 
+    let device = data.stylist.device();
     let mut style = StyleBuilder::for_inheritance(
-        data.stylist.device(),
+        device,
         parent_style_context,
         Some(&pseudo)
     );
 
     if for_text {
         StyleAdjuster::new(&mut style).adjust_for_text();
     }
 
-    style.build().shareable().into()
+    let mut style = style.build();
+    if let Some(parent) = parent_style_context {
+        style.inherit_visited(device, parent);
+    }
+    style.shareable().into()
 }
 
 #[no_mangle]
 pub extern "C" fn Servo_ComputedValues_GetStyleBits(values: ComputedStyleBorrowed) -> u8 {
     use style::properties::computed_value_flags::ComputedValueFlags;
     // FIXME(emilio): We could do this more efficiently I'm quite sure.
     let flags = values.flags;
     let mut result = 0;