Bug 1557825 part 1 - Implement 'display:block ruby'. r=emilio
authorMats Palmgren <mats@mozilla.com>
Wed, 14 Aug 2019 14:38:31 +0000
changeset 487951 299d7552c09b833be20e196eb46c483a4cbe286b
parent 487950 bc6ca73b757eb8fe29e700629213be2908944079
child 487952 282af999790d429705b33cb936c5e99e36e26b38
push id36433
push userbtara@mozilla.com
push dateWed, 14 Aug 2019 21:57:52 +0000
treeherdermozilla-central@7d9a2196d313 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersemilio
bugs1557825
milestone70.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Bug 1557825 part 1 - Implement 'display:block ruby'. r=emilio Differential Revision: https://phabricator.services.mozilla.com/D40211
layout/base/nsCSSFrameConstructor.cpp
layout/base/nsCSSFrameConstructor.h
layout/generic/RubyUtils.h
layout/generic/nsBlockFrame.cpp
layout/generic/nsBlockFrame.h
layout/style/nsCSSAnonBoxList.h
layout/style/test/property_database.js
servo/components/style/values/specified/box.rs
testing/web-platform/meta/css/css-display/parsing/display-valid.html.ini
xpcom/ds/StaticAtoms.py
--- a/layout/base/nsCSSFrameConstructor.cpp
+++ b/layout/base/nsCSSFrameConstructor.cpp
@@ -325,16 +325,22 @@ static inline nsContainerFrame* GetField
 
 #define FCDATA_DECL(_flags, _func) \
   { _flags, {(FrameCreationFunc)_func}, nullptr, PseudoStyleType::NotPseudo }
 #define FCDATA_WITH_WRAPPING_BLOCK(_flags, _func, _anon_box) \
   {                                                          \
     _flags | FCDATA_CREATE_BLOCK_WRAPPER_FOR_ALL_KIDS,       \
         {(FrameCreationFunc)_func}, nullptr, _anon_box       \
   }
+#define SIMPLE_FCDATA(_func) FCDATA_DECL(0, _func)
+#define FULL_CTOR_FCDATA(_flags, _func)                  \
+  {                                                      \
+    _flags | FCDATA_FUNC_IS_FULL_CTOR, {nullptr}, _func, \
+        PseudoStyleType::NotPseudo                       \
+  }
 
 /**
  * True if aFrame is an actual inline frame in the sense of non-replaced
  * display:inline CSS boxes.  In other words, it can be affected by {ib}
  * splitting and can contain first-letter frames.  Basically, this is either an
  * inline frame (positioned or otherwise) or an line frame (this last because
  * it can contain first-letter and because inserting blocks in the middle of it
  * needs to terminate it).
@@ -2353,17 +2359,17 @@ nsIFrame* nsCSSFrameConstructor::Constru
     // function in general.
     // Use a null PendingBinding, since our binding is not in fact pending.
     static const FrameConstructionData rootSVGData = FCDATA_DECL(0, nullptr);
     AutoFrameConstructionItem item(this, &rootSVGData, aDocElement, nullptr,
                                    do_AddRef(computedStyle), true);
 
     contentFrame = static_cast<nsContainerFrame*>(
         ConstructOuterSVG(state, item, mDocElementContainingBlock,
-                          computedStyle->StyleDisplay(), frameList));
+                          display, frameList));
   } else if (display->mDisplay == StyleDisplay::Flex ||
              display->mDisplay == StyleDisplay::WebkitBox ||
              display->mDisplay == StyleDisplay::Grid ||
              (StaticPrefs::layout_css_emulate_moz_box_with_flex() &&
               display->mDisplay == StyleDisplay::MozBox)) {
     auto func = display->mDisplay == StyleDisplay::Grid
                     ? NS_NewGridContainerFrame
                     : NS_NewFlexContainerFrame;
@@ -2389,17 +2395,26 @@ nsIFrame* nsCSSFrameConstructor::Constru
     // Use a null PendingBinding, since our binding is not in fact pending.
     static const FrameConstructionData rootTableData = FCDATA_DECL(0, nullptr);
     AutoFrameConstructionItem item(this, &rootTableData, aDocElement, nullptr,
                                    do_AddRef(computedStyle), true);
 
     // if the document is a table then just populate it.
     contentFrame = static_cast<nsContainerFrame*>(
         ConstructTable(state, item, mDocElementContainingBlock,
-                       computedStyle->StyleDisplay(), frameList));
+                       display, frameList));
+  } else if (display->DisplayInside() == StyleDisplayInside::Ruby) {
+    static const FrameConstructionData data =
+        FULL_CTOR_FCDATA(0, &nsCSSFrameConstructor::ConstructBlockRubyFrame);
+    AutoFrameConstructionItem item(this, &data, aDocElement, nullptr,
+                                   do_AddRef(computedStyle), true);
+    contentFrame = static_cast<nsContainerFrame*>(
+        ConstructBlockRubyFrame(state, item,
+            state.GetGeometricParent(*display, mDocElementContainingBlock),
+            display, frameList));
   } else {
     MOZ_ASSERT(display->mDisplay == StyleDisplay::Block ||
                    display->mDisplay == StyleDisplay::FlowRoot,
                "Unhandled display type for root element");
     contentFrame = NS_NewBlockFormattingContext(mPresShell, computedStyle);
     // Use a null PendingBinding, since our binding is not in fact pending.
     ConstructBlock(
         state, aDocElement,
@@ -3148,35 +3163,72 @@ nsIFrame* nsCSSFrameConstructor::Constru
   }
 
   // Build a scroll frame to wrap details frame if necessary.
   return ConstructScrollableBlockWithConstructor(aState, aItem, aParentFrame,
                                                  aStyleDisplay, aFrameList,
                                                  NS_NewDetailsFrame);
 }
 
+nsIFrame* nsCSSFrameConstructor::ConstructBlockRubyFrame(
+    nsFrameConstructorState& aState, FrameConstructionItem& aItem,
+    nsContainerFrame* aParentFrame, const nsStyleDisplay* aStyleDisplay,
+    nsFrameList& aFrameList) {
+  nsIContent* const content = aItem.mContent;
+  ComputedStyle* const computedStyle = aItem.mComputedStyle;
+
+  nsBlockFrame* blockFrame = NS_NewBlockFrame(mPresShell, computedStyle);
+  nsContainerFrame* newFrame = blockFrame;
+  if ((aItem.mFCData->mBits & FCDATA_MAY_NEED_SCROLLFRAME) &&
+      aStyleDisplay->IsScrollableOverflow()) {
+    nsContainerFrame* geometricParent =
+        aState.GetGeometricParent(*aStyleDisplay, aParentFrame);
+    nsContainerFrame* scrollframe = nullptr;
+    BuildScrollFrame(aState, content, computedStyle, blockFrame,
+                     geometricParent, scrollframe);
+    newFrame = scrollframe;
+  } else {
+    InitAndRestoreFrame(aState, content, aParentFrame, blockFrame);
+  }
+
+  RefPtr<ComputedStyle> rubyStyle =
+      mPresShell->StyleSet()->ResolveInheritingAnonymousBoxStyle(
+          PseudoStyleType::blockRubyContent, computedStyle);
+  nsContainerFrame* rubyFrame = NS_NewRubyFrame(mPresShell, rubyStyle);
+  InitAndRestoreFrame(aState, content, blockFrame, rubyFrame);
+  SetInitialSingleChild(blockFrame, rubyFrame);
+  blockFrame->AddStateBits(NS_FRAME_OWNS_ANON_BOXES);
+
+  aState.AddChild(newFrame, aFrameList, content, aParentFrame);
+
+  if (!mRootElementFrame) {
+    // The frame we're constructing will be the root element frame.
+    SetRootElementFrameAndConstructCanvasAnonContent(newFrame, aState,
+                                                     aFrameList);
+  }
+  nsFrameList childList;
+  ProcessChildren(aState, content, rubyStyle, rubyFrame, true, childList,
+                  false, nullptr);
+  rubyFrame->SetInitialChildList(kPrincipalList, childList);
+
+  return newFrame;
+}
+
 static nsIFrame* FindAncestorWithGeneratedContentPseudo(nsIFrame* aFrame) {
   for (nsIFrame* f = aFrame->GetParent(); f; f = f->GetParent()) {
     NS_ASSERTION(f->IsGeneratedContentFrame(),
                  "should not have exited generated content");
     auto pseudo = f->Style()->GetPseudoType();
     if (pseudo == PseudoStyleType::before || pseudo == PseudoStyleType::after ||
         pseudo == PseudoStyleType::marker)
       return f;
   }
   return nullptr;
 }
 
-#define SIMPLE_FCDATA(_func) FCDATA_DECL(0, _func)
-#define FULL_CTOR_FCDATA(_flags, _func)                  \
-  {                                                      \
-    _flags | FCDATA_FUNC_IS_FULL_CTOR, {nullptr}, _func, \
-        PseudoStyleType::NotPseudo                       \
-  }
-
 /* static */
 const nsCSSFrameConstructor::FrameConstructionData*
 nsCSSFrameConstructor::FindTextData(const Text& aTextContent,
                                     nsIFrame* aParentFrame) {
   if (aParentFrame && IsFrameForSVG(aParentFrame)) {
     nsIFrame* ancestorFrame =
         nsSVGUtils::GetFirstNonAAncestorFrame(aParentFrame);
     if (!ancestorFrame || !nsSVGUtils::IsInSVGTextSubtree(ancestorFrame)) {
@@ -3533,17 +3585,16 @@ void nsCSSFrameConstructor::ConstructFra
 
   // Some sets of bits are not compatible with each other
 #define CHECK_ONLY_ONE_BIT(_bit1, _bit2)           \
   NS_ASSERTION(!(bits & _bit1) || !(bits & _bit2), \
                "Only one of these bits should be set")
   CHECK_ONLY_ONE_BIT(FCDATA_FUNC_IS_FULL_CTOR,
                      FCDATA_FORCE_NULL_ABSPOS_CONTAINER);
   CHECK_ONLY_ONE_BIT(FCDATA_FUNC_IS_FULL_CTOR, FCDATA_WRAP_KIDS_IN_BLOCKS);
-  CHECK_ONLY_ONE_BIT(FCDATA_FUNC_IS_FULL_CTOR, FCDATA_MAY_NEED_SCROLLFRAME);
   CHECK_ONLY_ONE_BIT(FCDATA_FUNC_IS_FULL_CTOR, FCDATA_IS_POPUP);
   CHECK_ONLY_ONE_BIT(FCDATA_FUNC_IS_FULL_CTOR, FCDATA_SKIP_ABSPOS_PUSH);
   CHECK_ONLY_ONE_BIT(FCDATA_FUNC_IS_FULL_CTOR,
                      FCDATA_DISALLOW_GENERATED_CONTENT);
   CHECK_ONLY_ONE_BIT(FCDATA_FUNC_IS_FULL_CTOR, FCDATA_ALLOW_BLOCK_STYLES);
   CHECK_ONLY_ONE_BIT(FCDATA_FUNC_IS_FULL_CTOR,
                      FCDATA_CREATE_BLOCK_WRAPPER_FOR_ALL_KIDS);
   CHECK_ONLY_ONE_BIT(FCDATA_WRAP_KIDS_IN_BLOCKS,
@@ -4467,19 +4518,23 @@ nsCSSFrameConstructor::FindDisplayData(c
     case StyleDisplayInside::Grid: {
       static const FrameConstructionData nonScrollableData =
         FCDATA_DECL(0, NS_NewGridContainerFrame);
       static const FrameConstructionData data =
         FCDATA_DECL(FCDATA_MAY_NEED_SCROLLFRAME, NS_NewGridContainerFrame);
       return MOZ_UNLIKELY(propagatedScrollToViewport) ? &nonScrollableData : &data;
     }
     case StyleDisplayInside::Ruby: {
-      static const FrameConstructionData data =
-        FCDATA_DECL(FCDATA_IS_LINE_PARTICIPANT, NS_NewRubyFrame);
-      return &data;
+      static const FrameConstructionData data[] = {
+        FULL_CTOR_FCDATA(FCDATA_MAY_NEED_SCROLLFRAME,
+                         &nsCSSFrameConstructor::ConstructBlockRubyFrame),
+        FCDATA_DECL(FCDATA_IS_LINE_PARTICIPANT, NS_NewRubyFrame),
+      };
+      bool isInline = aDisplay.DisplayOutside() == StyleDisplayOutside::Inline;
+      return &data[isInline];
     }
     case StyleDisplayInside::RubyBase: {
       static const FrameConstructionData data =
         FCDATA_DECL(FCDATA_IS_LINE_PARTICIPANT |
                         FCDATA_DESIRED_PARENT_TYPE_TO_BITS(eTypeRubyBaseContainer),
                     NS_NewRubyBaseFrame);
       return &data;
     }
@@ -9140,17 +9195,18 @@ void nsCSSFrameConstructor::CreateNeeded
     nsFrameConstructorState& aState, FrameConstructionItemList& aItems,
     nsIFrame* aParentFrame) {
   const ParentType ourParentType = GetParentType(aParentFrame);
   if (!IsRubyParentType(ourParentType) ||
       aItems.AllWantParentType(ourParentType)) {
     return;
   }
 
-  if (!IsRubyPseudo(aParentFrame)) {
+  if (!IsRubyPseudo(aParentFrame) ||
+      ourParentType == eTypeRuby /* for 'display:block ruby' */) {
     // Normally, ruby pseudo frames start from and end at some elements,
     // which means they don't have leading and trailing whitespaces at
     // all.  But there are two cases where they do actually have leading
     // or trailing whitespaces:
     // 1. It is an inter-segment whitespace which in an individual ruby
     //    base container.
     // 2. The pseudo frame starts from or ends at consecutive inline
     //    content, which is not pure whitespace, but includes some.
--- a/layout/base/nsCSSFrameConstructor.h
+++ b/layout/base/nsCSSFrameConstructor.h
@@ -1356,16 +1356,23 @@ class nsCSSFrameConstructor final : publ
   // ConstructDetailsFrame puts the new frame in aFrameList and
   // handles the kids of the details.
   nsIFrame* ConstructDetailsFrame(nsFrameConstructorState& aState,
                                   FrameConstructionItem& aItem,
                                   nsContainerFrame* aParentFrame,
                                   const nsStyleDisplay* aStyleDisplay,
                                   nsFrameList& aFrameList);
 
+  // Creates a block frame wrapping an anonymous ruby frame.
+  nsIFrame* ConstructBlockRubyFrame(nsFrameConstructorState& aState,
+                                    FrameConstructionItem& aItem,
+                                    nsContainerFrame* aParentFrame,
+                                    const nsStyleDisplay* aStyleDisplay,
+                                    nsFrameList& aFrameList);
+
   void ConstructTextFrame(const FrameConstructionData* aData,
                           nsFrameConstructorState& aState, nsIContent* aContent,
                           nsContainerFrame* aParentFrame,
                           ComputedStyle* aComputedStyle,
                           nsFrameList& aFrameList);
 
   // If aPossibleTextContent is a text node and doesn't have a frame, append a
   // frame construction item for it to aItems.
--- a/layout/generic/RubyUtils.h
+++ b/layout/generic/RubyUtils.h
@@ -70,17 +70,18 @@ class RubyUtils {
   }
 
   static inline bool IsExpandableRubyBox(nsIFrame* aFrame) {
     mozilla::LayoutFrameType type = aFrame->Type();
     return IsRubyContentBox(type) || IsRubyContainerBox(type);
   }
 
   static inline bool IsRubyPseudo(PseudoStyleType aPseudo) {
-    return aPseudo == PseudoStyleType::ruby ||
+    return aPseudo == PseudoStyleType::blockRubyContent ||
+           aPseudo == PseudoStyleType::ruby ||
            aPseudo == PseudoStyleType::rubyBase ||
            aPseudo == PseudoStyleType::rubyText ||
            aPseudo == PseudoStyleType::rubyBaseContainer ||
            aPseudo == PseudoStyleType::rubyTextContainer;
   }
 
   static void SetReservedISize(nsIFrame* aFrame, nscoord aISize);
   static void ClearReservedISize(nsIFrame* aFrame);
--- a/layout/generic/nsBlockFrame.cpp
+++ b/layout/generic/nsBlockFrame.cpp
@@ -5614,16 +5614,40 @@ void nsBlockFrame::AddFrames(nsFrameList
   }
 
 #ifdef DEBUG
   MOZ_ASSERT(aFrameList.IsEmpty());
   VerifyLines(true);
 #endif
 }
 
+nsContainerFrame* nsBlockFrame::GetRubyContentPseudoFrame() {
+  auto* firstChild = PrincipalChildList().FirstChild();
+  if (firstChild && firstChild->IsRubyFrame() &&
+      firstChild->Style()->GetPseudoType() == mozilla::PseudoStyleType::blockRubyContent) {
+    return static_cast<nsContainerFrame*>(firstChild);
+  }
+  return nullptr;
+}
+
+nsContainerFrame* nsBlockFrame::GetContentInsertionFrame() {
+  // 'display:block ruby' use the inner (Ruby) frame for insertions.
+  if (auto* rubyContentPseudoFrame = GetRubyContentPseudoFrame()) {
+    return rubyContentPseudoFrame;
+  }
+  return this;
+}
+
+void nsBlockFrame::AppendDirectlyOwnedAnonBoxes(
+    nsTArray<OwnedAnonBox>& aResult) {
+  if (auto* rubyContentPseudoFrame = GetRubyContentPseudoFrame()) {
+    aResult.AppendElement(OwnedAnonBox(rubyContentPseudoFrame));
+  }
+}
+
 void nsBlockFrame::RemoveFloatFromFloatCache(nsIFrame* aFloat) {
   // Find which line contains the float, so we can update
   // the float cache.
   LineIterator line = LinesBegin(), line_end = LinesEnd();
   for (; line != line_end; ++line) {
     if (line->IsInline() && line->RemoveFloat(aFloat)) {
       break;
     }
--- a/layout/generic/nsBlockFrame.h
+++ b/layout/generic/nsBlockFrame.h
@@ -113,16 +113,18 @@ class nsBlockFrame : public nsContainerF
             nsIFrame* aPrevInFlow) override;
   void SetInitialChildList(ChildListID aListID,
                            nsFrameList& aChildList) override;
   void AppendFrames(ChildListID aListID, nsFrameList& aFrameList) override;
   void InsertFrames(ChildListID aListID, nsIFrame* aPrevFrame,
                     const nsLineList::iterator* aPrevFrameLine,
                     nsFrameList& aFrameList) override;
   void RemoveFrame(ChildListID aListID, nsIFrame* aOldFrame) override;
+  nsContainerFrame* GetContentInsertionFrame() override;
+  void AppendDirectlyOwnedAnonBoxes(nsTArray<OwnedAnonBox>& aResult) override;
   const nsFrameList& GetChildList(ChildListID aListID) const override;
   void GetChildLists(nsTArray<ChildList>* aLists) const override;
   nscoord GetLogicalBaseline(mozilla::WritingMode aWritingMode) const override;
   bool GetVerticalAlignBaseline(mozilla::WritingMode aWM,
                                 nscoord* aBaseline) const override {
     NS_ASSERTION(!aWM.IsOrthogonalTo(GetWritingMode()),
                  "You should only call this on frames with a WM that's "
                  "parallel to aWM");
@@ -480,16 +482,20 @@ class nsBlockFrame : public nsContainerF
    * This function will clear aFrameList.
    *
    * aPrevSiblingLine, if present, must be the line containing aPrevSibling.
    * Providing it will make this function faster.
    */
   void AddFrames(nsFrameList& aFrameList, nsIFrame* aPrevSibling,
                  const nsLineList::iterator* aPrevSiblingLine);
 
+  // Return the :-moz-block-ruby-content child frame, if any.
+  // (It's non-null only if this block frame is for 'display:block ruby'.)
+  nsContainerFrame* GetRubyContentPseudoFrame();
+
   /**
    * Perform Bidi resolution on this frame
    */
   nsresult ResolveBidi();
 
   /**
    * Test whether the frame is a form control in a visual Bidi page.
    * This is necessary for backwards-compatibility, because most visual
--- a/layout/style/nsCSSAnonBoxList.h
+++ b/layout/style/nsCSSAnonBoxList.h
@@ -128,16 +128,17 @@ CSS_ANON_BOX(viewportScroll, ":-moz-view
 // Inside a flex container, a contiguous run of text gets wrapped in
 // an anonymous block, which is then treated as a flex item.
 CSS_WRAPPER_ANON_BOX(anonymousFlexItem, ":-moz-anonymous-flex-item")
 
 // Inside a grid container, a contiguous run of text gets wrapped in
 // an anonymous block, which is then treated as a grid item.
 CSS_WRAPPER_ANON_BOX(anonymousGridItem, ":-moz-anonymous-grid-item")
 
+CSS_ANON_BOX(blockRubyContent, ":-moz-block-ruby-content")
 CSS_WRAPPER_ANON_BOX(ruby, ":-moz-ruby")
 CSS_WRAPPER_ANON_BOX(rubyBase, ":-moz-ruby-base")
 CSS_WRAPPER_ANON_BOX(rubyBaseContainer, ":-moz-ruby-base-container")
 CSS_WRAPPER_ANON_BOX(rubyText, ":-moz-ruby-text")
 CSS_WRAPPER_ANON_BOX(rubyTextContainer, ":-moz-ruby-text-container")
 
 #ifdef MOZ_XUL
 CSS_ANON_BOX(mozTreeColumn, ":-moz-tree-column")
--- a/layout/style/test/property_database.js
+++ b/layout/style/test/property_database.js
@@ -5474,16 +5474,17 @@ var gCSSProperties = {
       "table-row-group",
       "table-header-group",
       "table-footer-group",
       "table-row",
       "table-column-group",
       "table-column",
       "table-cell",
       "table-caption",
+      "block ruby",
       "ruby",
       "ruby-base",
       "ruby-base-container",
       "ruby-text",
       "ruby-text-container",
       "contents",
       "none",
     ],
--- a/servo/components/style/values/specified/box.rs
+++ b/servo/components/style/values/specified/box.rs
@@ -261,22 +261,22 @@ impl Display {
             *self,
             Display::RubyBaseContainer | Display::RubyTextContainer
         )
     }
 
     /// Returns whether this `display` value is one of the types for ruby.
     pub fn is_ruby_type(&self) -> bool {
         matches!(
-            *self,
-            Display::Ruby |
-                Display::RubyBase |
-                Display::RubyText |
-                Display::RubyBaseContainer |
-                Display::RubyTextContainer
+            self.inside(),
+            DisplayInside::Ruby |
+                DisplayInside::RubyBase |
+                DisplayInside::RubyText |
+                DisplayInside::RubyBaseContainer |
+                DisplayInside::RubyTextContainer
         )
     }
 }
 
 /// Servo version of Display only contains single-keyword values, and isn't
 /// using outside/inside values at all.
 #[allow(missing_docs)]
 #[derive(
@@ -400,18 +400,16 @@ impl Display {
             // Special handling for `contents` and `list-item`s on the root element.
             if _is_root_element && (*self == Display::Contents || self.is_list_item()) {
                 return Display::Block;
             }
             match self.outside() {
                 DisplayOutside::Inline => {
                     let inside =  match self.inside() {
                         DisplayInside::Inline | DisplayInside::FlowRoot => DisplayInside::Block,
-                        // FIXME: we don't handle `block ruby` in layout yet, remove this when we do:
-                        DisplayInside::Ruby => DisplayInside::Block,
                         inside => inside,
                     };
                     Display::from3(DisplayOutside::Block, inside, self.is_list_item())
                 },
                 DisplayOutside::Block | DisplayOutside::None => *self,
                 _ => Display::Block,
             }
         }
@@ -536,59 +534,30 @@ fn parse_display_inside<'i, 't>(
         "flow-root" => DisplayInside::FlowRoot,
         "table" => DisplayInside::Table,
         "flex" => DisplayInside::Flex,
         "grid" => DisplayInside::Grid,
         "ruby" => DisplayInside::Ruby,
     })
 }
 
-/// FIXME: this can be replaced with parse_display_inside once we
-/// support `block ruby`.
-#[cfg(feature = "gecko")]
-fn parse_display_inside_for_block<'i, 't>(
-    input: &mut Parser<'i, 't>,
-) -> Result<DisplayInside, ParseError<'i>> {
-    Ok(try_match_ident_ignore_ascii_case! { input,
-        "flow" => DisplayInside::Flow,
-        "flow-root" => DisplayInside::FlowRoot,
-        "table" => DisplayInside::Table,
-        "flex" => DisplayInside::Flex,
-        "grid" => DisplayInside::Grid,
-    })
-}
-
 /// <display-outside> = block | inline | run-in
 /// https://drafts.csswg.org/css-display/#typedef-display-outside
 #[cfg(feature = "gecko")]
 fn parse_display_outside<'i, 't>(
     input: &mut Parser<'i, 't>,
 ) -> Result<DisplayOutside, ParseError<'i>> {
     Ok(try_match_ident_ignore_ascii_case! { input,
         "block" => DisplayOutside::Block,
         "inline" => DisplayOutside::Inline,
         // FIXME(bug 2056): not supported in layout yet:
         //"run-in" => DisplayOutside::RunIn,
     })
 }
 
-/// FIXME: this can be replaced with parse_display_outside once we
-/// support all its values for `ruby`.
-#[cfg(feature = "gecko")]
-fn parse_display_outside_for_ruby<'i, 't>(
-    input: &mut Parser<'i, 't>,
-) -> Result<DisplayOutside, ParseError<'i>> {
-    Ok(try_match_ident_ignore_ascii_case! { input,
-        "inline" => DisplayOutside::Inline,
-        // FIXME: not supported in layout yet:
-        //"block" => DisplayOutside::Block,
-        //"run-in" => DisplayOutside::RunIn,
-    })
-}
-
 /// (flow | flow-root)?
 #[cfg(feature = "gecko")]
 fn parse_display_inside_for_list_item<'i, 't>(
     input: &mut Parser<'i, 't>,
 ) -> Result<DisplayInside, ParseError<'i>> {
     Ok(try_match_ident_ignore_ascii_case! { input,
         "flow" => DisplayInside::Flow,
         "flow-root" => DisplayInside::FlowRoot,
@@ -625,34 +594,26 @@ impl Parse for Display {
         } else {
             input.try(parse_display_inside)
         };
         // <display-listitem> = <display-outside>? && [ flow | flow-root ]? && list-item
         // https://drafts.csswg.org/css-display/#typedef-display-listitem
         if !got_list_item && is_valid_inside_for_list_item(&inside) {
             got_list_item = input.try(parse_list_item).is_ok();
         }
-        let outside = match inside {
-            // FIXME we don't handle `block ruby` in layout yet.
-            Ok(DisplayInside::Ruby) => input.try(parse_display_outside_for_ruby),
-            _ => input.try(parse_display_outside),
-        };
+        let outside = input.try(parse_display_outside);
         if outside.is_ok() {
             if !got_list_item && (inside.is_err() || is_valid_inside_for_list_item(&inside)) {
                 got_list_item = input.try(parse_list_item).is_ok();
             }
             if inside.is_err() {
                 inside = if got_list_item {
                     input.try(parse_display_inside_for_list_item)
                 } else {
-                    match outside {
-                        // FIXME we don't handle `block ruby` in layout yet.
-                        Ok(DisplayOutside::Block) => input.try(parse_display_inside_for_block),
-                        _ => input.try(parse_display_inside),
-                    }
+                    input.try(parse_display_inside)
                 };
                 if !got_list_item && is_valid_inside_for_list_item(&inside) {
                     got_list_item = input.try(parse_list_item).is_ok();
                 }
             }
         }
         if got_list_item || inside.is_ok() || outside.is_ok() {
             let inside = inside.unwrap_or(DisplayInside::Flow);
@@ -719,16 +680,17 @@ impl SpecifiedValueInfo for Display {
           "inline-block",
           "inline-flex",
           "inline-grid",
           "inline-table",
           "inline list-item",
           "inline flow-root list-item",
           "list-item",
           "none",
+          "block ruby",
           "ruby",
           "ruby-base",
           "ruby-base-container",
           "ruby-text",
           "ruby-text-container",
           "table",
           "table-caption",
           "table-cell",
--- a/testing/web-platform/meta/css/css-display/parsing/display-valid.html.ini
+++ b/testing/web-platform/meta/css/css-display/parsing/display-valid.html.ini
@@ -1,27 +1,21 @@
 [display-valid.html]
-  [e.style['display'\] = "ruby block" should set the property value]
-    expected: FAIL
-
   [e.style['display'\] = "run-in flow-root list-item" should set the property value]
     expected: FAIL
 
   [e.style['display'\] = "run-in table" should set the property value]
     expected: FAIL
 
   [e.style['display'\] = "run-in list-item flow" should set the property value]
     expected: FAIL
 
   [e.style['display'\] = "run-in flow" should set the property value]
     expected: FAIL
 
-  [e.style['display'\] = "block ruby" should set the property value]
-    expected: FAIL
-
   [e.style['display'\] = "run-in flow list-item" should set the property value]
     expected: FAIL
 
   [e.style['display'\] = "table run-in" should set the property value]
     expected: FAIL
 
   [e.style['display'\] = "list-item run-in" should set the property value]
     expected: FAIL
--- a/xpcom/ds/StaticAtoms.py
+++ b/xpcom/ds/StaticAtoms.py
@@ -2452,16 +2452,17 @@ STATIC_ATOMS = [
     InheritingAnonBoxAtom("AnonBox_scrolledCanvas", ":-moz-scrolled-canvas"),
     InheritingAnonBoxAtom("AnonBox_scrolledPageSequence", ":-moz-scrolled-page-sequence"),
     InheritingAnonBoxAtom("AnonBox_columnSet", ":-moz-column-set"),
     InheritingAnonBoxAtom("AnonBox_columnContent", ":-moz-column-content"),
     InheritingAnonBoxAtom("AnonBox_viewport", ":-moz-viewport"),
     InheritingAnonBoxAtom("AnonBox_viewportScroll", ":-moz-viewport-scroll"),
     InheritingAnonBoxAtom("AnonBox_anonymousFlexItem", ":-moz-anonymous-flex-item"),
     InheritingAnonBoxAtom("AnonBox_anonymousGridItem", ":-moz-anonymous-grid-item"),
+    InheritingAnonBoxAtom("AnonBox_blockRubyContent", ":-moz-block-ruby-content"),
     InheritingAnonBoxAtom("AnonBox_ruby", ":-moz-ruby"),
     InheritingAnonBoxAtom("AnonBox_rubyBase", ":-moz-ruby-base"),
     InheritingAnonBoxAtom("AnonBox_rubyBaseContainer", ":-moz-ruby-base-container"),
     InheritingAnonBoxAtom("AnonBox_rubyText", ":-moz-ruby-text"),
     InheritingAnonBoxAtom("AnonBox_rubyTextContainer", ":-moz-ruby-text-container"),
     InheritingAnonBoxAtom("AnonBox_mozTreeColumn", ":-moz-tree-column"),
     InheritingAnonBoxAtom("AnonBox_mozTreeRow", ":-moz-tree-row"),
     InheritingAnonBoxAtom("AnonBox_mozTreeSeparator", ":-moz-tree-separator"),