Bug 1341083: Implement dynamic restyling for display: contents. r?heycam draft
authorEmilio Cobos Álvarez <emilio@crisal.io>
Tue, 21 Feb 2017 11:25:11 +0100
changeset 489456 462c35542bbbb5b5a4e38f92f516a3ed7e7ab8d5
parent 489455 f41819399a7f4494fae4dbdced2462502136f846
child 489457 44edb4ab6e243a665d3f72b9b5c958f7d9603a3f
push id46822
push userbmo:emilio+bugs@crisal.io
push dateFri, 24 Feb 2017 20:50:08 +0000
reviewersheycam
bugs1341083
milestone54.0a1
Bug 1341083: Implement dynamic restyling for display: contents. r?heycam MozReview-Commit-ID: KimTU2j4V4p
layout/base/ServoRestyleManager.cpp
layout/base/nsFrameManager.cpp
layout/base/nsFrameManager.h
layout/reftests/css-grid/reftest-stylo.list
servo/components/style/animation.rs
servo/components/style/gecko/media_queries.rs
servo/components/style/matching.rs
servo/components/style/properties/gecko.mako.rs
servo/components/style/properties/properties.mako.rs
servo/components/style/stylist.rs
servo/components/style/values/computed/mod.rs
servo/components/style/viewport.rs
servo/ports/geckolib/glue.rs
--- a/layout/base/ServoRestyleManager.cpp
+++ b/layout/base/ServoRestyleManager.cpp
@@ -6,16 +6,17 @@
 
 #include "mozilla/ServoRestyleManager.h"
 
 #include "mozilla/DocumentStyleRootIterator.h"
 #include "mozilla/ServoBindings.h"
 #include "mozilla/ServoStyleSet.h"
 #include "mozilla/dom/ChildIterator.h"
 #include "nsContentUtils.h"
+#include "nsCSSFrameConstructor.h"
 #include "nsPrintfCString.h"
 #include "nsRefreshDriver.h"
 #include "nsStyleChangeList.h"
 
 using namespace mozilla::dom;
 
 namespace mozilla {
 
@@ -191,34 +192,47 @@ ServoRestyleManager::RecreateStyleContex
   //
   // Hold the old style context alive, because it could become a dangling
   // pointer during the replacement. In practice it's not a huge deal (on
   // GetNextContinuationWithSameStyle the pointer is not dereferenced, only
   // compared), but better not playing with dangling pointers if not needed.
   RefPtr<nsStyleContext> oldStyleContext =
     styleFrame ? styleFrame->StyleContext() : nullptr;
 
+  UndisplayedNode* displayContentsNode = nullptr;
+  // FIXME(emilio, bug 1303605): This can be simpler for Servo.
+  // Note that we intentionally don't check for display: none content.
+  if (!oldStyleContext) {
+    displayContentsNode =
+      PresContext()->FrameConstructor()->GetDisplayContentsNodeFor(aElement);
+    if (displayContentsNode) {
+      oldStyleContext = displayContentsNode->mStyle;
+    }
+  }
+
   RefPtr<ServoComputedValues> computedValues =
     aStyleSet->ResolveServoStyle(aElement);
 
   // Note that we rely in the fact that we don't cascade pseudo-element styles
   // separately right now (that is, if a pseudo style changes, the normal style
   // changes too).
   //
   // Otherwise we should probably encode that information somehow to avoid
   // expensive checks in the common case.
   //
   // Also, we're going to need to check for pseudos of display: contents
   // elements, though that is buggy right now even in non-stylo mode, see
   // bug 1251799.
   const bool recreateContext = oldStyleContext &&
     oldStyleContext->StyleSource().AsServoComputedValues() != computedValues;
 
+  RefPtr<nsStyleContext> newContext = nullptr;
   if (recreateContext) {
-    RefPtr<nsStyleContext> newContext =
+    MOZ_ASSERT(styleFrame || displayContentsNode);
+    newContext =
       aStyleSet->GetContext(computedValues.forget(), aParentContext, nullptr,
                             CSSPseudoElementType::NotPseudo, aElement);
 
     newContext->EnsureStructsForServo(oldStyleContext);
 
     // XXX This could not always work as expected: there are kinds of content
     // with the first split and the last sharing style, but others not. We
     // should handle those properly.
@@ -230,16 +244,21 @@ ServoRestyleManager::RecreateStyleContex
     if (isTable) {
       nsIFrame* primaryFrame = aElement->GetPrimaryFrame();
       MOZ_ASSERT(primaryFrame->StyleContext()->GetPseudo() ==
                    nsCSSAnonBoxes::tableWrapper,
                  "What sort of frame is this?");
       UpdateStyleContextForTableWrapper(primaryFrame, newContext, aStyleSet);
     }
 
+    if (MOZ_UNLIKELY(displayContentsNode)) {
+      MOZ_ASSERT(!styleFrame);
+      displayContentsNode->mStyle = newContext;
+    }
+
     // Update pseudo-elements state if appropriate.
     const static CSSPseudoElementType pseudosToRestyle[] = {
       CSSPseudoElementType::before,
       CSSPseudoElementType::after,
     };
 
     for (CSSPseudoElementType pseudoType : pseudosToRestyle) {
       nsIAtom* pseudoTag = nsCSSPseudoElements::GetPseudoAtom(pseudoType);
@@ -268,34 +287,26 @@ ServoRestyleManager::RecreateStyleContex
         }
       }
     }
   }
 
   bool traverseElementChildren = aElement->HasDirtyDescendantsForServo();
   bool traverseTextChildren = recreateContext;
   if (traverseElementChildren || traverseTextChildren) {
+    nsStyleContext* upToDateContext =
+      recreateContext ? newContext : oldStyleContext;
+
     StyleChildrenIterator it(aElement);
     for (nsIContent* n = it.GetNextChild(); n; n = it.GetNextChild()) {
       if (traverseElementChildren && n->IsElement()) {
-        if (!styleFrame) {
-          // The frame constructor presumably decided to suppress frame
-          // construction on this subtree. Just clear the dirty descendants
-          // bit from the subtree, since there's no point in harvesting the
-          // change hints.
-          MOZ_ASSERT(!n->AsElement()->GetPrimaryFrame(),
-                     "Only display:contents should do this, and we don't handle that yet");
-          ClearDirtyDescendantsFromSubtree(n->AsElement());
-        } else {
-          RecreateStyleContexts(n->AsElement(), styleFrame->StyleContext(),
-                                aStyleSet, aChangeListToProcess);
-        }
+        RecreateStyleContexts(n->AsElement(), upToDateContext,
+                              aStyleSet, aChangeListToProcess);
       } else if (traverseTextChildren && n->IsNodeOfType(nsINode::eTEXT)) {
-        RecreateStyleContextsForText(n, styleFrame->StyleContext(),
-                                     aStyleSet);
+        RecreateStyleContextsForText(n, upToDateContext, aStyleSet);
       }
     }
   }
 
   aElement->UnsetHasDirtyDescendantsForServo();
 }
 
 void
@@ -322,16 +333,17 @@ ServoRestyleManager::FrameForPseudoEleme
 {
   MOZ_ASSERT_IF(aPseudoTagOrNull, aContent->IsElement());
   nsIFrame* primaryFrame = aContent->GetPrimaryFrame();
 
   if (!aPseudoTagOrNull) {
     return primaryFrame;
   }
 
+  // FIXME(emilio): Need to take into account display: contents pseudos!
   if (!primaryFrame) {
     return nullptr;
   }
 
   // NOTE: we probably need to special-case display: contents here. Gecko's
   // RestyleManager passes the primary frame of the parent instead.
   if (aPseudoTagOrNull == nsCSSPseudoElements::before) {
     return nsLayoutUtils::GetBeforeFrameForContent(primaryFrame, aContent);
--- a/layout/base/nsFrameManager.cpp
+++ b/layout/base/nsFrameManager.cpp
@@ -201,33 +201,52 @@ nsFrameManager::ClearPlaceholderFrameMap
     auto entry = static_cast<PlaceholderMapEntry*>(iter.Get());
     entry->placeholderFrame->SetOutOfFlowFrame(nullptr);
   }
   mPlaceholderMap.Clear();
 }
 
 //----------------------------------------------------------------------
 
+static nsIContent*
+ParentForUndisplayedMap(const nsIContent* aContent)
+{
+  MOZ_ASSERT(aContent);
+
+  nsIContent* parent = aContent->GetParentElementCrossingShadowRoot();
+  MOZ_ASSERT(parent || !aContent->GetParent(), "no non-elements");
+
+  return parent;
+}
+
 /* static */ nsStyleContext*
 nsFrameManager::GetStyleContextInMap(UndisplayedMap* aMap, nsIContent* aContent)
 {
+  UndisplayedNode* node = GetUndisplayedNodeInMapFor(aMap, aContent);
+  return node ? node->mStyle.get() : nullptr;
+}
+
+/* static */ UndisplayedNode*
+nsFrameManager::GetUndisplayedNodeInMapFor(UndisplayedMap* aMap,
+                                           const nsIContent* aContent)
+{
   if (!aContent) {
     return nullptr;
   }
-  nsIContent* parent = aContent->GetParentElementCrossingShadowRoot();
-  MOZ_ASSERT(parent || !aContent->GetParent(), "no non-elements");
+  nsIContent* parent = ParentForUndisplayedMap(aContent);
   for (UndisplayedNode* node = aMap->GetFirstNode(parent);
          node; node = node->mNext) {
     if (node->mContent == aContent)
-      return node->mStyle;
+      return node;
   }
 
   return nullptr;
 }
 
+
 /* static */ UndisplayedNode*
 nsFrameManager::GetAllUndisplayedNodesInMapFor(UndisplayedMap* aMap,
                                                nsIContent* aParentContent)
 {
   return aMap ? aMap->GetFirstNode(aParentContent) : nullptr;
 }
 
 UndisplayedNode*
@@ -247,18 +266,17 @@ nsFrameManager::SetStyleContextInMap(Und
 #if defined(DEBUG_UNDISPLAYED_MAP) || defined(DEBUG_DISPLAY_BOX_CONTENTS_MAP)
   static int i = 0;
   printf("SetStyleContextInMap(%d): p=%p \n", i++, (void *)aContent);
 #endif
 
   NS_ASSERTION(!GetStyleContextInMap(aMap, aContent),
                "Already have an entry for aContent");
 
-  nsIContent* parent = aContent->GetParentElementCrossingShadowRoot();
-  MOZ_ASSERT(parent || !aContent->GetParent(), "no non-elements");
+  nsIContent* parent = ParentForUndisplayedMap(aContent);
 #ifdef DEBUG
   nsIPresShell* shell = aStyleContext->PresContext()->PresShell();
   NS_ASSERTION(parent || (shell && shell->GetDocument() &&
                           shell->GetDocument()->GetRootElement() == aContent),
                "undisplayed content must have a parent, unless it's the root "
                "element");
 #endif
   aMap->AddNodeFor(parent, aContent, aStyleContext);
--- a/layout/base/nsFrameManager.h
+++ b/layout/base/nsFrameManager.h
@@ -135,16 +135,29 @@ public:
     return GetStyleContextInMap(mDisplayContentsMap, aContent);
   }
 
   /**
    * Return the linked list of UndisplayedNodes containing the registered
    * display:contents children of aParentContent, if any.
    */
   mozilla::UndisplayedNode* GetAllDisplayContentsIn(nsIContent* aParentContent);
+
+  /**
+   * Return the relevant undisplayed node for a given content with display:
+   * contents style.
+   */
+  mozilla::UndisplayedNode* GetDisplayContentsNodeFor(
+      const nsIContent* aContent) {
+    if (!mDisplayContentsMap) {
+      return nullptr;
+    }
+    return GetUndisplayedNodeInMapFor(mDisplayContentsMap, aContent);
+  }
+
   /**
    * Register aContent having a display:contents style context.
    */
   void SetDisplayContents(nsIContent* aContent,
                           nsStyleContext* aStyleContext);
   /**
    * Change the registered style context for aContent to aStyleContext.
    */
@@ -204,16 +217,19 @@ public:
                                         nsILayoutHistoryState* aState);
 
   void RestoreFrameStateFor(nsIFrame*              aFrame,
                                         nsILayoutHistoryState* aState);
 protected:
   static nsStyleContext* GetStyleContextInMap(UndisplayedMap* aMap,
                                               nsIContent* aContent);
   static mozilla::UndisplayedNode*
+    GetUndisplayedNodeInMapFor(UndisplayedMap* aMap,
+                               const nsIContent* aContent);
+  static mozilla::UndisplayedNode*
     GetAllUndisplayedNodesInMapFor(UndisplayedMap* aMap,
                                    nsIContent* aParentContent);
   static void SetStyleContextInMap(UndisplayedMap* aMap,
                                    nsIContent* aContent,
                                    nsStyleContext* aStyleContext);
   static void ChangeStyleContextInMap(UndisplayedMap* aMap,
                                       nsIContent* aContent,
                                       nsStyleContext* aStyleContext);
--- a/layout/reftests/css-grid/reftest-stylo.list
+++ b/layout/reftests/css-grid/reftest-stylo.list
@@ -170,17 +170,17 @@ fails == grid-repeat-auto-fill-fit-003.h
 fails == grid-repeat-auto-fill-fit-004.html grid-repeat-auto-fill-fit-004.html
 fails == grid-repeat-auto-fill-fit-005.html grid-repeat-auto-fill-fit-005.html
 fails == grid-repeat-auto-fill-fit-006.html grid-repeat-auto-fill-fit-006.html
 fails == grid-repeat-auto-fill-fit-007.html grid-repeat-auto-fill-fit-007.html
 fails == grid-repeat-auto-fill-fit-008.html grid-repeat-auto-fill-fit-008.html
 fails == grid-repeat-auto-fill-fit-009.html grid-repeat-auto-fill-fit-009.html
 fails == grid-repeat-auto-fill-fit-010.html grid-repeat-auto-fill-fit-010.html
 fails == grid-repeat-auto-fill-fit-011.html grid-repeat-auto-fill-fit-011.html
-fails asserts-if(stylo,48) == grid-item-blockifying-001.html grid-item-blockifying-001.html # bug 1335339
+fails == grid-item-blockifying-001.html grid-item-blockifying-001.html
 fails == grid-fragmentation-001.html grid-fragmentation-001.html
 fails == grid-fragmentation-002.html grid-fragmentation-002.html
 fails == grid-fragmentation-003.html grid-fragmentation-003.html
 fails == grid-fragmentation-004.html grid-fragmentation-004.html
 fails == grid-fragmentation-005.html grid-fragmentation-005.html
 fails == grid-fragmentation-006.html grid-fragmentation-006.html
 fails == grid-fragmentation-007.html grid-fragmentation-007.html
 fails == grid-fragmentation-008.html grid-fragmentation-008.html
--- a/servo/components/style/animation.rs
+++ b/servo/components/style/animation.rs
@@ -425,16 +425,17 @@ fn compute_style_for_animation_step(cont
                 guard.declarations.iter().rev().map(|&(ref decl, _importance)| decl)
             };
 
             let computed =
                 properties::apply_declarations(context.viewport_size,
                                                /* is_root = */ false,
                                                iter,
                                                previous_style,
+                                               previous_style,
                                                &context.default_computed_values,
                                                /* cascade_info = */ None,
                                                context.error_reporter.clone(),
                                                /* Metrics provider */ None,
                                                CascadeFlags::empty());
             computed
         }
     }
--- a/servo/components/style/gecko/media_queries.rs
+++ b/servo/components/style/gecko/media_queries.rs
@@ -487,16 +487,17 @@ impl Expression {
         let default_values = device.default_values();
 
         // http://dev.w3.org/csswg/mediaqueries3/#units
         // em units are relative to the initial font-size.
         let context = computed::Context {
             is_root_element: false,
             viewport_size: device.au_viewport_size(),
             inherited_style: default_values,
+            layout_parent_style: default_values,
             // This cloning business is kind of dumb.... It's because Context
             // insists on having an actual ComputedValues inside itself.
             style: default_values.clone(),
             font_metrics_provider: None
         };
 
         let required_value = match self.value {
             Some(ref v) => v,
--- a/servo/components/style/matching.rs
+++ b/servo/components/style/matching.rs
@@ -438,16 +438,43 @@ pub enum StyleSharingResult {
 ///
 /// FIXME(pcwalton): Unify with `CascadeFlags`, perhaps?
 struct CascadeBooleans {
     shareable: bool,
     animate: bool,
 }
 
 trait PrivateMatchMethods: TElement {
+    /// Returns the closest parent element that doesn't have a display: contents
+    /// style (and thus generates a box).
+    ///
+    /// This is needed to correctly handle blockification of flex and grid
+    /// items.
+    ///
+    /// Returns itself if the element has no parent. In practice this doesn't
+    /// happen because the root element is blockified per spec, but it could
+    /// happen if we decide to not blockify for roots of disconnected subtrees,
+    /// which is a kind of dubious beahavior.
+    fn layout_parent(&self) -> Self {
+        let mut current = self.clone();
+        loop {
+            current = match current.parent_element() {
+                Some(el) => el,
+                None => return current,
+            };
+
+            let is_display_contents =
+                current.borrow_data().unwrap().styles().primary.values().is_display_contents();
+
+            if !is_display_contents {
+                return current;
+            }
+        }
+    }
+
     fn cascade_internal(&self,
                         context: &StyleContext<Self>,
                         primary_style: &ComputedStyle,
                         pseudo_style: &mut Option<(&PseudoElement, &mut ComputedStyle)>,
                         booleans: &CascadeBooleans)
                         -> Arc<ComputedValues> {
         let shared_context = context.shared;
         let mut cascade_info = CascadeInfo::new();
@@ -462,47 +489,68 @@ trait PrivateMatchMethods: TElement {
         // Grab the rule node.
         let rule_node = &pseudo_style.as_ref().map_or(primary_style, |p| &*p.1).rules;
 
         // Grab the inherited values.
         let parent_el;
         let parent_data;
         let inherited_values_ = if pseudo_style.is_none() {
             parent_el = self.parent_element();
-            parent_data = parent_el.as_ref().and_then(|x| x.borrow_data());
+            parent_data = parent_el.as_ref().and_then(|e| e.borrow_data());
             let parent_values = parent_data.as_ref().map(|d| {
                 // Sometimes Gecko eagerly styles things without processing
                 // pending restyles first. In general we'd like to avoid this,
                 // but there can be good reasons (for example, needing to
                 // construct a frame for some small piece of newly-added
                 // content in order to do something specific with that frame,
                 // but not wanting to flush all of layout).
                 debug_assert!(cfg!(feature = "gecko") || d.has_current_styles());
                 d.styles().primary.values()
             });
 
-            // Propagate the "can be fragmented" bit. It would be nice to
-            // encapsulate this better.
-            if let Some(ref p) = parent_values {
+            parent_values
+        } else {
+            parent_el = Some(self.clone());
+            Some(primary_style.values())
+        };
+
+
+        // FIXME(emilio): This looks slightly awful, can we make this cleaner?
+        let mut layout_parent_el = parent_el.clone();
+        let layout_parent_data;
+        let mut layout_parent_style = inherited_values_;
+        if inherited_values_.map_or(false, |s| s.is_display_contents()) {
+            layout_parent_el = Some(layout_parent_el.unwrap().layout_parent());
+            layout_parent_data = layout_parent_el.as_ref().unwrap().borrow_data().unwrap();
+            layout_parent_style = Some(layout_parent_data.styles().primary.values())
+        }
+
+        let inherited_values = inherited_values_.map(|x| &**x);
+        let layout_parent_style = layout_parent_style.map(|x| &**x);
+
+        // Propagate the "can be fragmented" bit. It would be nice to
+        // encapsulate this better.
+        //
+        // Note that this is not needed for pseudos since we already do that
+        // when we resolve the non-pseudo style.
+        if pseudo_style.is_none() {
+            if let Some(ref p) = layout_parent_style {
                 let can_be_fragmented =
-                    p.is_multicol() || parent_el.unwrap().as_node().can_be_fragmented();
+                    p.is_multicol() ||
+                    layout_parent_el.as_ref().unwrap().as_node().can_be_fragmented();
                 unsafe { self.as_node().set_can_be_fragmented(can_be_fragmented); }
             }
-
-            parent_values
-        } else {
-            Some(primary_style.values())
-        };
-        let inherited_values = inherited_values_.map(|x| &**x);
+        }
 
         // Invoke the cascade algorithm.
         let values =
             Arc::new(cascade(shared_context.viewport_size,
                              rule_node,
                              inherited_values,
+                             layout_parent_style,
                              &shared_context.default_computed_values,
                              Some(&mut cascade_info),
                              shared_context.error_reporter.clone(),
                              cascade_flags));
 
         cascade_info.finish(&self.as_node());
         values
     }
--- a/servo/components/style/properties/gecko.mako.rs
+++ b/servo/components/style/properties/gecko.mako.rs
@@ -125,16 +125,21 @@ impl ComputedValues {
             writing_mode: WritingMode::empty(), // FIXME(bz): This seems dubious
             root_font_size: longhands::font_size::get_initial_value(), // FIXME(bz): Also seems dubious?
             % for style_struct in data.style_structs:
                 ${style_struct.ident}: style_structs::${style_struct.name}::default(pres_context),
             % endfor
         })
     }
 
+    #[inline]
+    pub fn is_display_contents(&self) -> bool {
+        self.get_box().clone_display() == longhands::display::computed_value::T::contents
+    }
+
     % for style_struct in data.style_structs:
     #[inline]
     pub fn clone_${style_struct.name_lower}(&self) -> Arc<style_structs::${style_struct.name}> {
         self.${style_struct.ident}.clone()
     }
     #[inline]
     pub fn get_${style_struct.name_lower}(&self) -> &style_structs::${style_struct.name} {
         &self.${style_struct.ident}
--- a/servo/components/style/properties/properties.mako.rs
+++ b/servo/components/style/properties/properties.mako.rs
@@ -1419,16 +1419,21 @@ impl ComputedValues {
     fn custom_properties(&self) -> Option<Arc<::custom_properties::ComputedValuesMap>> {
         self.custom_properties.as_ref().map(|x| x.clone())
     }
 
     /// Whether this style has a -moz-binding value. This is always false for
     /// Servo for obvious reasons.
     pub fn has_moz_binding(&self) -> bool { false }
 
+    /// Returns whether this style's display value is equal to contents.
+    ///
+    /// Since this isn't supported in Servo, this is always false for Servo.
+    pub fn is_display_contents(&self) -> bool { false }
+
     /// Get the root font size.
     fn root_font_size(&self) -> Au { self.root_font_size }
 
     /// Set the root font size.
     fn set_root_font_size(&mut self, size: Au) { self.root_font_size = size }
     /// Set the writing mode for this style.
     pub fn set_writing_mode(&mut self, mode: WritingMode) { self.writing_mode = mode; }
 
@@ -1756,24 +1761,26 @@ bitflags! {
 ///   * `parent_style`: The parent style, if applicable; if `None`, this is the root node.
 ///
 /// Returns the computed values.
 ///   * `flags`: Various flags.
 ///
 pub fn cascade(viewport_size: Size2D<Au>,
                rule_node: &StrongRuleNode,
                parent_style: Option<<&ComputedValues>,
+               layout_parent_style: Option<<&ComputedValues>,
                default_style: &Arc<ComputedValues>,
                cascade_info: Option<<&mut CascadeInfo>,
                error_reporter: StdBox<ParseErrorReporter + Send>,
                flags: CascadeFlags)
                -> ComputedValues {
-    let (is_root_element, inherited_style) = match parent_style {
-        Some(parent_style) => (false, parent_style),
-        None => (true, &**default_style),
+    debug_assert_eq!(parent_style.is_some(), layout_parent_style.is_some());
+    let (is_root_element, inherited_style, layout_parent_style) = match parent_style {
+        Some(parent_style) => (false, parent_style, layout_parent_style.unwrap()),
+        None => (true, &**default_style, &**default_style),
     };
     // Hold locks until after the apply_declarations() call returns.
     // Use filter_map because the root node has no style source.
     let lock_guards = rule_node.self_and_ancestors().filter_map(|node| {
         node.style_source().map(|source| (source.read(), node.importance()))
     }).collect::<Vec<_>>();
     let iter_declarations = || {
         lock_guards.iter().flat_map(|&(ref source, source_importance)| {
@@ -1788,29 +1795,31 @@ pub fn cascade(viewport_size: Size2D<Au>
                 }
             })
         })
     };
     apply_declarations(viewport_size,
                        is_root_element,
                        iter_declarations,
                        inherited_style,
+                       layout_parent_style,
                        default_style,
                        cascade_info,
                        error_reporter,
                        None,
                        flags)
 }
 
 /// NOTE: This function expects the declaration with more priority to appear
 /// first.
 pub fn apply_declarations<'a, F, I>(viewport_size: Size2D<Au>,
                                     is_root_element: bool,
                                     iter_declarations: F,
                                     inherited_style: &ComputedValues,
+                                    layout_parent_style: &ComputedValues,
                                     default_style: &Arc<ComputedValues>,
                                     mut cascade_info: Option<<&mut CascadeInfo>,
                                     mut error_reporter: StdBox<ParseErrorReporter + Send>,
                                     font_metrics_provider: Option<<&FontMetricsProvider>,
                                     flags: CascadeFlags)
                                     -> ComputedValues
     where F: Fn() -> I,
           I: Iterator<Item = &'a PropertyDeclaration>,
@@ -1856,16 +1865,17 @@ pub fn apply_declarations<'a, F, I>(view
                             % endfor
                             )
     };
 
     let mut context = computed::Context {
         is_root_element: is_root_element,
         viewport_size: viewport_size,
         inherited_style: inherited_style,
+        layout_parent_style: layout_parent_style,
         style: starting_style,
         font_metrics_provider: font_metrics_provider,
     };
 
     // Set computed values, overwriting earlier declarations for the same
     // property.
     //
     // NB: The cacheable boolean is not used right now, but will be once we
@@ -1938,20 +1948,17 @@ pub fn apply_declarations<'a, F, I>(view
     % endfor
 
     let mut style = context.style;
 
     let positioned = matches!(style.get_box().clone_position(),
         longhands::position::SpecifiedValue::absolute |
         longhands::position::SpecifiedValue::fixed);
     let floated = style.get_box().clone_float() != longhands::float::computed_value::T::none;
-    // FIXME(heycam): We should look past any display:contents ancestors to
-    // determine if we are a flex or grid item, but we don't have access to
-    // grandparent or higher style here.
-    let is_item = matches!(context.inherited_style.get_box().clone_display(),
+    let is_item = matches!(context.layout_parent_style.get_box().clone_display(),
         % if product == "gecko":
         computed_values::display::T::grid |
         computed_values::display::T::inline_grid |
         % endif
         computed_values::display::T::flex |
         computed_values::display::T::inline_flex);
     let (blockify_root, blockify_item) = match flags.contains(SKIP_ROOT_AND_ITEM_BASED_DISPLAY_FIXUP) {
         false => (is_root_element, is_item),
@@ -2018,17 +2025,17 @@ pub fn apply_declarations<'a, F, I>(view
     //
     // See https://github.com/servo/servo/issues/15229
     % if product == "servo" and "align-items" in data.longhands_by_name:
     {
         use computed_values::align_self::T as align_self;
         use computed_values::align_items::T as align_items;
         if style.get_position().clone_align_self() == computed_values::align_self::T::auto && !positioned {
             let self_align =
-                match context.inherited_style.get_position().clone_align_items() {
+                match context.layout_parent_style.get_position().clone_align_items() {
                     align_items::stretch => align_self::stretch,
                     align_items::baseline => align_self::baseline,
                     align_items::flex_start => align_self::flex_start,
                     align_items::flex_end => align_self::flex_end,
                     align_items::center => align_self::center,
                 };
             style.mutate_position().set_align_self(self_align);
         }
--- a/servo/components/style/stylist.rs
+++ b/servo/components/style/stylist.rs
@@ -308,20 +308,35 @@ impl Stylist {
             None => self.rule_tree.root(),
         };
 
         let mut flags = CascadeFlags::empty();
         if inherit_all {
             flags.insert(INHERIT_ALL)
         }
 
+        // NOTE(emilio): We skip calculating the proper layout parent style
+        // here.
+        //
+        // It'd be fine to assert that this isn't called with a parent style
+        // where display contents is in effect, but in practice this is hard to
+        // do for stuff like :-moz-fieldset-content with a
+        // <fieldset style="display: contents">. That is, the computed value of
+        // display for the fieldset is "contents", even though it's not the used
+        // value, so we don't need to adjust in a different way anyway.
+        //
+        // In practice, I don't think any anonymous content can be a direct
+        // descendant of a display: contents element where display: contents is
+        // the actual used value, and the computed value of it would need
+        // blockification.
         let computed =
             properties::cascade(self.device.au_viewport_size(),
                                 &rule_node,
                                 parent.map(|p| &**p),
+                                parent.map(|p| &**p),
                                 default,
                                 None,
                                 Box::new(StdoutErrorReporter),
                                 flags);
         ComputedStyle::new(rule_node, Arc::new(computed))
     }
 
     /// Returns the style for an anonymous box of the given type.
@@ -384,20 +399,25 @@ impl Stylist {
                                           Some(pseudo),
                                           &mut declarations,
                                           &mut flags);
 
         let rule_node =
             self.rule_tree.insert_ordered_rules(
                 declarations.into_iter().map(|a| (a.source, a.level)));
 
+        // 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").
         let computed =
             properties::cascade(self.device.au_viewport_size(),
                                 &rule_node,
                                 Some(&**parent),
+                                Some(&**parent),
                                 default,
                                 None,
                                 Box::new(StdoutErrorReporter),
                                 CascadeFlags::empty());
 
         // Apply the selector flags. We should be in sequential mode already,
         // so we can directly apply the parent flags.
         if cfg!(feature = "servo") {
--- a/servo/components/style/values/computed/mod.rs
+++ b/servo/components/style/values/computed/mod.rs
@@ -37,16 +37,22 @@ pub struct Context<'a> {
     pub is_root_element: bool,
 
     /// The current viewport size.
     pub viewport_size: Size2D<Au>,
 
     /// The style we're inheriting from.
     pub inherited_style: &'a ComputedValues,
 
+    /// The style of the layout parent node. This will almost always be
+    /// `inherited_style`, except when `display: contents` is at play, in which
+    /// case it's the style of the last ancestor with a `display` value that
+    /// isn't `contents`.
+    pub layout_parent_style: &'a ComputedValues,
+
     /// Values access through this need to be in the properties "computed
     /// early": color, text-decoration, font-size, display, position, float,
     /// border-*-style, outline-style, font-family, writing-mode...
     pub style: ComputedValues,
 
     /// A font metrics provider, used to access font metrics to implement
     /// font-relative units.
     ///
--- a/servo/components/style/viewport.rs
+++ b/servo/components/style/viewport.rs
@@ -684,16 +684,17 @@ impl MaybeNew for ViewportConstraints {
         // resolved against initial values
         let initial_viewport = device.au_viewport_size();
 
         // TODO(emilio): Stop cloning `ComputedValues` around!
         let context = Context {
             is_root_element: false,
             viewport_size: initial_viewport,
             inherited_style: device.default_values(),
+            layout_parent_style: device.default_values(),
             style: device.default_values().clone(),
             font_metrics_provider: None, // TODO: Should have!
         };
 
         // DEVICE-ADAPT ยง 9.3 Resolving 'extend-to-zoom'
         let extend_width;
         let extend_height;
         if let Some(extend_zoom) = max!(initial_zoom, max_zoom) {
--- a/servo/ports/geckolib/glue.rs
+++ b/servo/ports/geckolib/glue.rs
@@ -640,17 +640,20 @@ fn get_pseudo_style(element: GeckoElemen
 {
     let pseudo = PseudoElement::from_atom_unchecked(Atom::from(pseudo_tag), false);
     match SelectorImpl::pseudo_element_cascade_type(&pseudo) {
         PseudoElementCascadeType::Eager => styles.pseudos.get(&pseudo).map(|s| s.values().clone()),
         PseudoElementCascadeType::Precomputed => unreachable!("No anonymous boxes"),
         PseudoElementCascadeType::Lazy => {
             let d = doc_data.borrow_mut();
             let base = styles.primary.values();
-            d.stylist.lazily_compute_pseudo_element_style(&element, &pseudo, base, &d.default_computed_values())
+            d.stylist.lazily_compute_pseudo_element_style(&element,
+                                                          &pseudo,
+                                                          base,
+                                                          &d.default_computed_values())
                      .map(|s| s.values().clone())
         },
     }
 }
 
 #[no_mangle]
 pub extern "C" fn Servo_ComputedValues_Inherit(
   raw_data: RawServoStyleSetBorrowed,
@@ -1343,16 +1346,17 @@ pub extern "C" fn Servo_GetComputedKeyfr
     let parent_style = parent_style.as_ref().map(|r| &**ComputedValues::as_arc(&r));
     let init = ComputedValues::default_values(pres_context);
 
     let context = Context {
         is_root_element: false,
         // FIXME (bug 1303229): Use the actual viewport size here
         viewport_size: Size2D::new(Au(0), Au(0)),
         inherited_style: parent_style.unwrap_or(&init),
+        layout_parent_style: parent_style.unwrap_or(&init),
         style: (**style).clone(),
         font_metrics_provider: None,
     };
 
     for (index, keyframe) in keyframes.iter().enumerate() {
         let ref mut animation_values = computed_keyframes[index];
 
         let mut seen = PropertyBitField::new();