Bug 1591219 - [css-align] Synthesize a baseline from the content edges for empty table cells. r=dholbert
authorMats Palmgren <mats@mozilla.com>
Tue, 12 Nov 2019 15:31:57 +0000
changeset 501575 fec55267c096603f0862ff30b68b966c28045d79
parent 501574 7ea00823df0a73594a8aa409ac450ea84a70dc4f
child 501576 9517978401adb68546042cfc076ea624afceae7f
push id114172
push userdluca@mozilla.com
push dateTue, 19 Nov 2019 11:31:10 +0000
treeherdermozilla-inbound@b5c5ba07d3db [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersdholbert
bugs1591219
milestone72.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 1591219 - [css-align] Synthesize a baseline from the content edges for empty table cells. r=dholbert Differential Revision: https://phabricator.services.mozilla.com/D50711
layout/generic/nsIFrame.h
layout/generic/nsIFrameInlines.h
layout/tables/nsTableCellFrame.cpp
layout/tables/nsTableCellFrame.h
layout/tables/nsTableRowFrame.cpp
testing/web-platform/meta/css/CSS2/normal-flow/width-applies-to-006.xht.ini
testing/web-platform/tests/css/css-align/baseline-rules/synthesized-baseline-table-cell-001-ref.html
testing/web-platform/tests/css/css-align/baseline-rules/synthesized-baseline-table-cell-001.html
--- a/layout/generic/nsIFrame.h
+++ b/layout/generic/nsIFrame.h
@@ -1411,48 +1411,63 @@ class nsIFrame : public nsQueryFrame {
    * @note You should only call this on frames with a WM that's parallel to aWM.
    * @param aWM the writing-mode of the alignment context, with the ltr/rtl
    * direction tweak done by nsIFrame::GetWritingMode(nsIFrame*) in inline
    * contexts (see that method).
    */
   virtual nscoord GetLogicalBaseline(mozilla::WritingMode aWM) const = 0;
 
   /**
-   * Synthesize a first(last) inline-axis baseline from our margin-box.
+   * Synthesize a first(last) inline-axis baseline based on our margin-box.
    * An alphabetical baseline is at the start(end) edge and a central baseline
    * is at the center of our block-axis margin-box (aWM tells which to use).
    * https://drafts.csswg.org/css-align-3/#synthesize-baselines
    * @note You should only call this on frames with a WM that's parallel to aWM.
    * @param aWM the writing-mode of the alignment context
    * @return an offset from our border-box block-axis start(end) edge for
    * a first(last) baseline respectively
    * (implemented in nsIFrameInlines.h)
    */
   inline nscoord SynthesizeBaselineBOffsetFromMarginBox(
       mozilla::WritingMode aWM, BaselineSharingGroup aGroup) const;
 
   /**
-   * Synthesize a first(last) inline-axis baseline from our border-box.
+   * Synthesize a first(last) inline-axis baseline based on our border-box.
    * An alphabetical baseline is at the start(end) edge and a central baseline
    * is at the center of our block-axis border-box (aWM tells which to use).
    * https://drafts.csswg.org/css-align-3/#synthesize-baselines
    * @note The returned value is only valid when reflow is not needed.
    * @note You should only call this on frames with a WM that's parallel to aWM.
    * @param aWM the writing-mode of the alignment context
    * @return an offset from our border-box block-axis start(end) edge for
    * a first(last) baseline respectively
    * (implemented in nsIFrameInlines.h)
    */
   inline nscoord SynthesizeBaselineBOffsetFromBorderBox(
       mozilla::WritingMode aWM, BaselineSharingGroup aGroup) const;
 
   /**
+   * Synthesize a first(last) inline-axis baseline based on our content-box.
+   * An alphabetical baseline is at the start(end) edge and a central baseline
+   * is at the center of our block-axis content-box (aWM tells which to use).
+   * https://drafts.csswg.org/css-align-3/#synthesize-baselines
+   * @note The returned value is only valid when reflow is not needed.
+   * @note You should only call this on frames with a WM that's parallel to aWM.
+   * @param aWM the writing-mode of the alignment context
+   * @return an offset from our border-box block-axis start(end) edge for
+   * a first(last) baseline respectively
+   * (implemented in nsIFrameInlines.h)
+   */
+  inline nscoord SynthesizeBaselineBOffsetFromContentBox(
+      mozilla::WritingMode aWM, BaselineSharingGroup aGroup) const;
+
+  /**
    * Return the position of the frame's inline-axis baseline, or synthesize one
    * for the given alignment context. The returned baseline is the distance from
-   * the block-axis border-box start(end) edge for aBaselineGroup eFirst(eLast).
+   * the block-axis border-box start(end) edge for aBaselineGroup ::First(Last).
    * @note The returned value is only valid when reflow is not needed.
    * @note You should only call this on frames with a WM that's parallel to aWM.
    * @param aWM the writing-mode of the alignment context
    * @param aBaselineOffset out-param, only valid if the method returns true
    * (implemented in nsIFrameInlines.h)
    */
   inline nscoord BaselineBOffset(mozilla::WritingMode aWM,
                                  BaselineSharingGroup aBaselineGroup,
@@ -1473,17 +1488,17 @@ class nsIFrame : public nsQueryFrame {
   virtual bool GetVerticalAlignBaseline(mozilla::WritingMode aWM,
                                         nscoord* aBaseline) const {
     return false;
   }
 
   /**
    * Return true if the frame has a first(last) inline-axis natural baseline per
    * CSS Box Alignment.  If so, then the returned baseline is the distance from
-   * the block-axis border-box start(end) edge for aBaselineGroup eFirst(eLast).
+   * the block-axis border-box start(end) edge for aBaselineGroup ::First(Last).
    * https://drafts.csswg.org/css-align-3/#natural-baseline
    * @note The returned value is only valid when reflow is not needed.
    * @note You should only call this on frames with a WM that's parallel to aWM.
    * @param aWM the writing-mode of the alignment context
    * @param aBaseline the baseline offset, only valid if the method returns true
    */
   virtual bool GetNaturalBaselineBOffset(mozilla::WritingMode aWM,
                                          BaselineSharingGroup aBaselineGroup,
--- a/layout/generic/nsIFrameInlines.h
+++ b/layout/generic/nsIFrameInlines.h
@@ -104,48 +104,77 @@ nscoord nsIFrame::SynthesizeBaselineBOff
   }
   MOZ_ASSERT(aGroup == BaselineSharingGroup::Last);
   if (aWM.IsAlphabeticalBaseline()) {
     // Last baseline for inverted-line content is the block-start margin edge,
     // as the frame is in effect "flipped" for alignment purposes.
     return MOZ_UNLIKELY(aWM.IsLineInverted()) ? BSize(aWM) + margin.BStart(aWM)
                                               : -margin.BEnd(aWM);
   }
-  // Round up for central baseline offset, to be consistent with eFirst.
+  // Round up for central baseline offset, to be consistent with ::First.
   nscoord marginBoxSize = BSize(aWM) + margin.BStartEnd(aWM);
   nscoord marginBoxCenter = (marginBoxSize / 2) + (marginBoxSize % 2);
   return marginBoxCenter - margin.BEnd(aWM);
 }
 
 nscoord nsIFrame::SynthesizeBaselineBOffsetFromBorderBox(
     mozilla::WritingMode aWM, BaselineSharingGroup aGroup) const {
   MOZ_ASSERT(!aWM.IsOrthogonalTo(GetWritingMode()));
   nscoord borderBoxSize = BSize(aWM);
   if (aGroup == BaselineSharingGroup::First) {
     return MOZ_LIKELY(aWM.IsAlphabeticalBaseline()) ? borderBoxSize
                                                     : borderBoxSize / 2;
   }
   MOZ_ASSERT(aGroup == BaselineSharingGroup::Last);
-  // Round up for central baseline offset, to be consistent with eFirst.
+  // Round up for central baseline offset, to be consistent with ::First.
   auto borderBoxCenter = (borderBoxSize / 2) + (borderBoxSize % 2);
   return MOZ_LIKELY(aWM.IsAlphabeticalBaseline()) ? 0 : borderBoxCenter;
 }
 
+nscoord nsIFrame::SynthesizeBaselineBOffsetFromContentBox(
+    mozilla::WritingMode aWM, BaselineSharingGroup aGroup) const {
+  MOZ_ASSERT(!aWM.IsOrthogonalTo(GetWritingMode()));
+  auto bp = GetLogicalUsedBorderAndPadding(aWM);
+  bp.ApplySkipSides(GetLogicalSkipSides());
+
+  if (MOZ_UNLIKELY(aWM.IsCentralBaseline())) {
+    nscoord contentBoxBSize = BSize(aWM) - bp.BStartEnd(aWM);
+    if (aGroup == BaselineSharingGroup::First) {
+      return contentBoxBSize / 2 + bp.BStart(aWM);
+    }
+    // Return the same center position as for ::First, but as offset from end:
+    nscoord halfContentBoxBSize = (contentBoxBSize / 2) + (contentBoxBSize % 2);
+    return halfContentBoxBSize + bp.BEnd(aWM);
+  }
+  if (aGroup == BaselineSharingGroup::First) {
+    // First baseline for inverted-line content is the block-start content
+    // edge, as the frame is in effect "flipped" for alignment purposes.
+    return MOZ_UNLIKELY(aWM.IsLineInverted()) ? bp.BStart(aWM)
+                                              : BSize(aWM) - bp.BEnd(aWM);
+  }
+  // Last baseline for inverted-line content is the block-start content edge,
+  // as the frame is in effect "flipped" for alignment purposes.
+  return MOZ_UNLIKELY(aWM.IsLineInverted()) ? BSize(aWM) - bp.BStart(aWM)
+                                            : bp.BEnd(aWM);
+}
+
 nscoord nsIFrame::BaselineBOffset(mozilla::WritingMode aWM,
                                   BaselineSharingGroup aBaselineGroup,
                                   AlignmentContext aAlignmentContext) const {
   MOZ_ASSERT(!aWM.IsOrthogonalTo(GetWritingMode()));
   nscoord baseline;
   if (GetNaturalBaselineBOffset(aWM, aBaselineGroup, &baseline)) {
     return baseline;
   }
   if (aAlignmentContext == AlignmentContext::Inline) {
     return SynthesizeBaselineBOffsetFromMarginBox(aWM, aBaselineGroup);
   }
-  // XXX AlignmentContext::Table should use content box?
+  if (aAlignmentContext == AlignmentContext::Table) {
+    return SynthesizeBaselineBOffsetFromContentBox(aWM, aBaselineGroup);
+  }
   return SynthesizeBaselineBOffsetFromBorderBox(aWM, aBaselineGroup);
 }
 
 void nsIFrame::PropagateWritingModeToSelfAndAncestors(
     mozilla::WritingMode aWM) {
   MOZ_ASSERT(IsCanvasFrame());
   for (auto f = this; f; f = f->GetParent()) {
     f->mWritingMode = aWM;
--- a/layout/tables/nsTableCellFrame.cpp
+++ b/layout/tables/nsTableCellFrame.cpp
@@ -566,21 +566,25 @@ void nsTableCellFrame::BlockDirAlignChil
                "an inner cell frame");
   LogicalRect kidRect = firstKid->GetLogicalRect(aWM, containerSize);
   nscoord childBSize = kidRect.BSize(aWM);
 
   // Vertically align the child
   nscoord kidBStart = 0;
   switch (GetVerticalAlign()) {
     case StyleVerticalAlignKeyword::Baseline:
-      // Align the baselines of the child frame with the baselines of
-      // other children in the same row which have 'vertical-align: baseline'
-      kidBStart = bStartInset + aMaxAscent - GetCellBaseline();
-      break;
-
+      if (!GetContentEmpty()) {
+        // Align the baselines of the child frame with the baselines of
+        // other children in the same row which have 'vertical-align: baseline'
+        kidBStart = bStartInset + aMaxAscent - GetCellBaseline();
+        break;
+      }
+      // Empty cells don't participate in baseline alignment -
+      // fallback to start alignment.
+      MOZ_FALLTHROUGH;
     case StyleVerticalAlignKeyword::Top:
       // Align the top of the child frame with the top of the content area,
       kidBStart = bStartInset;
       break;
 
     case StyleVerticalAlignKeyword::Bottom:
       // Align the bottom of the child frame with the bottom of the content
       // area,
--- a/layout/tables/nsTableCellFrame.h
+++ b/layout/tables/nsTableCellFrame.h
@@ -125,17 +125,18 @@ class nsTableCellFrame : public nsContai
   /*
    * Get the value of vertical-align adjusted for CSS 2's rules for a
    * table cell, which means the result is always
    * StyleVerticalAlignKeyword::{Top,Middle,Bottom,Baseline}.
    */
   virtual mozilla::StyleVerticalAlignKeyword GetVerticalAlign() const;
 
   bool HasVerticalAlignBaseline() const {
-    return GetVerticalAlign() == mozilla::StyleVerticalAlignKeyword::Baseline;
+    return GetVerticalAlign() == mozilla::StyleVerticalAlignKeyword::Baseline &&
+           !GetContentEmpty();
   }
 
   bool CellHasVisibleContent(nscoord aBSize, nsTableFrame* tableFrame,
                              nsIFrame* kidFrame);
 
   /**
    * Get the first-line baseline of the cell relative to its block-start border
    * edge, as if the cell were vertically aligned to the top of the row.
--- a/layout/tables/nsTableRowFrame.cpp
+++ b/layout/tables/nsTableRowFrame.cpp
@@ -386,44 +386,24 @@ void nsTableRowFrame::DidResize() {
 // *including* cells with rowspans
 nscoord nsTableRowFrame::GetMaxCellAscent() const { return mMaxCellAscent; }
 
 nscoord nsTableRowFrame::GetRowBaseline(WritingMode aWM) {
   if (mMaxCellAscent) {
     return mMaxCellAscent;
   }
 
-  // If we don't have a baseline on any of the cells we go for the lowest
-  // content edge of the inner block frames.
-  // Every table cell has a cell frame with its border and padding. Inside
-  // the cell is a block frame. The cell is as high as the tallest cell in
-  // the parent row. As a consequence the block frame might not touch both
-  // the top and the bottom padding of it parent cell frame at the same time.
-  //
-  // bbbbbbbbbbbbbbbbbb             cell border:  b
-  // bppppppppppppppppb             cell padding: p
-  // bpxxxxxxxxxxxxxxpb             inner block:  x
-  // bpx            xpb
-  // bpx            xpb
-  // bpx            xpb
-  // bpxxxxxxxxxxxxxxpb  base line
-  // bp              pb
-  // bp              pb
-  // bppppppppppppppppb
-  // bbbbbbbbbbbbbbbbbb
+  // If we get here, we don't have a baseline on any of the cells in this row.
 
   nscoord ascent = 0;
-  nsSize containerSize = GetSize();
   for (nsIFrame* childFrame : mFrames) {
-    if (childFrame->IsTableCellFrame()) {
-      nsIFrame* firstKid = childFrame->PrincipalChildList().FirstChild();
-      ascent = std::max(
-          ascent,
-          LogicalRect(aWM, firstKid->GetNormalRect(), containerSize).BEnd(aWM));
-    }
+    MOZ_ASSERT(childFrame->IsTableCellFrame());
+    nscoord s = childFrame->SynthesizeBaselineBOffsetFromContentBox(
+        aWM, BaselineSharingGroup::First);
+    ascent = std::max(ascent, s);
   }
   return ascent;
 }
 
 nscoord nsTableRowFrame::GetInitialBSize(nscoord aPctBasis) const {
   nscoord bsize = 0;
   if ((aPctBasis > 0) && HasPctBSize()) {
     bsize = NSToCoordRound(GetPctBSize() * (float)aPctBasis);
deleted file mode 100644
--- a/testing/web-platform/meta/css/CSS2/normal-flow/width-applies-to-006.xht.ini
+++ /dev/null
@@ -1,2 +0,0 @@
-[width-applies-to-006.xht]
-  expected: FAIL
new file mode 100644
--- /dev/null
+++ b/testing/web-platform/tests/css/css-align/baseline-rules/synthesized-baseline-table-cell-001-ref.html
@@ -0,0 +1,32 @@
+<!DOCTYPE html>
+<html>
+<head>
+<meta charset="utf-8">
+<title>CSS Box Alignment Reference: Synthesized baseline table cell</title>
+<link rel="author" title="Mats Palmgren" href="mailto:mats@mozilla.com">
+<style>
+* { vertical-align: baseline; }
+table { display: inline-table; border: 3px solid; }
+td { width:10px; background-color:pink; background-clip:content-box; }
+.p { padding: 0 10px 0 0; height:150px; }
+.size { height:80px; }
+.w { background: white; }
+x { display: block; overflow: hidden; height:50px; background: white; }
+.xp { height:80px; background-color:pink; }
+.m50 { margin-top: 50px; }
+.top { vertical-align:top; }
+</style>
+</head>
+<body>
+  X
+  <table border=5><tr><td class="p w"><x></x></td></tr></table>
+  <table border=5><tr><td class="p w top"></td><td>X</td></tr></table>
+  <table border=2><tr><td><x style="height:0"></x></td></tr></table>
+  <table border=5><tr><td class="p w" style="height:230px"><x class="xp m50"></x></td></tr></table>
+  <table border=5><tr><td class="p w top" style="height:230px"><x class="xp m50"></x></td><td>X</td></tr></table>
+  <table border=2><tr><td><x class="xp"></x></td></tr></table>
+  <table border=2><tr><td class="top"><x class="xp" style="height:20px"></x></td><td><x class="xp" style="height:20px"></td></tr></table>
+  <table border=2><tr><td class="top"><x class="xp" style="height:20px"</td><td rowspan=2></td></tr><tr><td class="top"><x class="xp" style="height:20px"</td></tr></table>
+  <table border=2><tr><td>X</td><td rowspan=2 class="top"></td></tr><tr><td style="height:20px" class="top"></td></tr></table>
+</body>
+</html>
new file mode 100644
--- /dev/null
+++ b/testing/web-platform/tests/css/css-align/baseline-rules/synthesized-baseline-table-cell-001.html
@@ -0,0 +1,30 @@
+<!DOCTYPE html>
+<html>
+<head>
+<meta charset="utf-8">
+<title>CSS Box Alignment Test: Synthesized baseline table cell</title>
+<link rel="author" title="Mats Palmgren" href="mailto:mats@mozilla.com">
+<link rel="help" href="https://drafts.csswg.org/css-align-3/#synthesize-baseline">
+<link rel="help" href="https://drafts.csswg.org/css-align-3/#baseline-export">
+<link rel="match" href="synthesized-baseline-table-cell-001-ref.html">
+<style>
+* { vertical-align: baseline; }
+table { display: inline-table; border: 3px solid; }
+td { width:10px; background-color:pink; background-clip:content-box; }
+.p { padding: 50px 10px 100px 0; }
+.size { height:80px; }
+</style>
+</head>
+<body>
+  X
+  <table border=5><tr><td class="p"></td></tr></table>
+  <table border=5><tr><td class="p"></td><td>X</td></tr></table>
+  <table border=2><tr><td          ></td></tr></table>
+  <table border=5><tr><td class="p size"></td></tr></table>
+  <table border=5><tr><td class="p size"></td><td>X</td></tr></table>
+  <table border=2><tr><td class="size"  ></td></tr></table>
+  <table border=2><tr><td style="height:10px"></td><td style="height:20px"></td></tr></table>
+  <table border=2><tr><td style="height:20px"></td><td rowspan=2></td></tr><tr><td style="height:20px"></td></tr></table>
+  <table border=2><tr><td>X</td><td rowspan=2></td></tr><tr><td style="height:20px"></td></tr></table>
+</body>
+</html>