Bug 1151204 part 4 - [css-grid] Implement Grid Container Baselines. r=dholbert
authorMats Palmgren <mats@mozilla.com>
Sat, 01 Oct 2016 02:26:39 +0200
changeset 419837 1e1c048cf01beec96ced7e826f09258c33c2293e
parent 419836 5d2110357137d05ff2b9695cff79329cf907f98c
child 419838 69e356ace5532f61c97a36615ecb4a681f9d2138
push id31023
push usercykesiopka.bmo@gmail.com
push dateSat, 01 Oct 2016 01:30:33 +0000
reviewersdholbert
bugs1151204
milestone52.0a1
Bug 1151204 part 4 - [css-grid] Implement Grid Container Baselines. r=dholbert
layout/generic/nsGridContainerFrame.cpp
layout/generic/nsGridContainerFrame.h
--- a/layout/generic/nsGridContainerFrame.cpp
+++ b/layout/generic/nsGridContainerFrame.cpp
@@ -36,24 +36,16 @@
 using namespace mozilla;
 typedef nsAbsoluteContainingBlock::AbsPosReflowFlags AbsPosReflowFlags;
 typedef nsGridContainerFrame::TrackSize TrackSize;
 const uint32_t nsGridContainerFrame::kTranslatedMaxLine =
   uint32_t(nsStyleGridLine::kMaxLine - nsStyleGridLine::kMinLine);
 const uint32_t nsGridContainerFrame::kAutoLine = kTranslatedMaxLine + 3457U;
 typedef nsTHashtable< nsPtrHashKey<nsIFrame> > FrameHashtable;
 
-// https://drafts.csswg.org/css-align-3/#baseline-sharing-group
-enum BaselineSharingGroup
-{
-  // NOTE Used as an array index so must be 0 and 1.
-  eFirst = 0,
-  eLast = 1,
-};
-
 // https://drafts.csswg.org/css-sizing/#constraints
 enum class SizingConstraint
 {
   eMinContent,  // sizing under min-content constraint
   eMaxContent,  // sizing under max-content constraint
   eNoConstraint // no constraint, used during Reflow
 };
 
@@ -125,16 +117,36 @@ ResolveToDefiniteSize(const nsStyleCoord
   MOZ_ASSERT(aCoord.IsCoordPercentCalcUnit());
   if (::IsPercentOfIndefiniteSize(aCoord, aPercentBasis)) {
     return nscoord(0);
   }
   return std::max(nscoord(0),
                   nsRuleNode::ComputeCoordPercentCalc(aCoord, aPercentBasis));
 }
 
+// Synthesize a baseline from a border box.  For an alphabetical baseline
+// this is the end edge of the border box.  For a central baseline it's
+// the center of the border box.
+// https://drafts.csswg.org/css-align-3/#synthesize-baselines
+// For a first-baseline the measure is from the border-box start edge and
+// for a last-baseline the measure is from the border-box end edge.
+static nscoord
+SynthesizeBaselineFromBorderBox(BaselineSharingGroup aGroup,
+                                WritingMode aWM,
+                                nscoord aBorderBoxSize)
+{
+  if (aGroup == BaselineSharingGroup::eFirst) {
+    return aWM.IsAlphabeticalBaseline() ? aBorderBoxSize : aBorderBoxSize / 2;
+  }
+  MOZ_ASSERT(aGroup == BaselineSharingGroup::eLast);
+  // Round up for central baseline offset, to be consistent with eFirst.
+  return aWM.IsAlphabeticalBaseline() ? 0 :
+    (aBorderBoxSize / 2) + (aBorderBoxSize % 2);
+}
+
 enum class GridLineSide
 {
   eBeforeGridGap,
   eAfterGridGap,
 };
 
 struct nsGridContainerFrame::TrackSize
 {
@@ -1205,16 +1217,18 @@ struct nsGridContainerFrame::TrackSizing
 struct nsGridContainerFrame::Tracks
 {
   explicit Tracks(LogicalAxis aAxis)
     : mAxis(aAxis)
     , mCanResolveLineRangeSize(false)
   {
     mBaselineSubtreeAlign[BaselineSharingGroup::eFirst] = NS_STYLE_ALIGN_AUTO;
     mBaselineSubtreeAlign[BaselineSharingGroup::eLast] = NS_STYLE_ALIGN_AUTO;
+    mBaseline[BaselineSharingGroup::eFirst] = NS_INTRINSIC_WIDTH_UNKNOWN;
+    mBaseline[BaselineSharingGroup::eLast] = NS_INTRINSIC_WIDTH_UNKNOWN;
   }
 
   void Initialize(const TrackSizingFunctions& aFunctions,
                   const nsStyleCoord&         aGridGap,
                   uint32_t                    aNumTracks,
                   nscoord                     aContentBoxSize);
 
   /**
@@ -1783,16 +1797,18 @@ struct nsGridContainerFrame::Tracks
       printf("\n");
     }
   }
 #endif
 
   AutoTArray<TrackSize, 32> mSizes;
   nscoord mContentBoxSize;
   nscoord mGridGap;
+  // The first(last)-baseline for the first(last) track in this axis.
+  nscoord mBaseline[2]; // index by BaselineSharingGroup
   LogicalAxis mAxis;
   // Used for aligning a baseline-aligned subtree of items.  The only possible
   // values are NS_STYLE_ALIGN_{START,END,CENTER,AUTO}.  AUTO means there are
   // no baseline-aligned items in any track in that axis.
   // There is one alignment value for each BaselineSharingGroup.
   uint8_t mBaselineSubtreeAlign[2];
   // True if track positions and sizes are final in this axis.
   bool mCanResolveLineRangeSize;
@@ -3978,20 +3994,28 @@ nsGridContainerFrame::Tracks::CalculateI
     }
     // Iterate the current track again and update the baseline offsets making
     // all items baseline-aligned within this group in this track.
     for (uint32_t j = trackStartIndex; j < i; ++j) {
       const ItemBaselineData& item = aBaselineItems[j];
       item.mGridItem->mBaselineOffset[mAxis] = maxBaseline - item.mBaseline;
       MOZ_ASSERT(item.mGridItem->mBaselineOffset[mAxis] >= 0);
     }
-    // Store the size of this baseline-aligned subtree.
     if (i != 0) {
+      // Store the size of this baseline-aligned subtree.
       mSizes[currentTrack].mBaselineSubtreeSize[aBaselineGroup] =
         maxBaseline + maxDescent;
+      // Record the first(last) baseline for the first(last) track.
+      if (currentTrack == 0 && aBaselineGroup == BaselineSharingGroup::eFirst) {
+        mBaseline[aBaselineGroup] = maxBaseline;
+      }
+      if (currentTrack + 1 == len &&
+          aBaselineGroup == BaselineSharingGroup::eLast) {
+        mBaseline[aBaselineGroup] = maxBaseline;
+      }
     }
     if (i == len) {
       break;
     }
     // Initialize data for the next track with baseline-aligned items.
     const ItemBaselineData& item = aBaselineItems[i];
     currentTrack = item.mBaselineTrack;
     trackStartIndex = i;
@@ -4107,34 +4131,58 @@ nsGridContainerFrame::Tracks::Initialize
     if (state & ItemState::eIsBaselineAligned) {
       // XXX available size issue
       LogicalSize avail(childWM, INFINITE_ISIZE_COORD, NS_UNCONSTRAINEDSIZE);
       auto* rc = &aState.mRenderingContext;
       // XXX figure out if we can avoid/merge this reflow with the main reflow.
       // XXX (after bug 1174569 is sorted out)
       ::MeasuringReflow(child, aState.mReflowInput, rc, avail);
       nscoord baseline;
+      nsGridContainerFrame* grid = do_QueryFrame(child);
       if (state & ItemState::eFirstBaseline) {
-        if (nsLayoutUtils::GetFirstLineBaseline(wm, child, &baseline)) {
+        if (grid) {
+          if (isOrthogonal == isInlineAxis) {
+            grid->GetBBaseline(BaselineSharingGroup::eFirst, &baseline);
+          } else {
+            grid->GetIBaseline(BaselineSharingGroup::eFirst, &baseline);
+          }
+        }
+        if (grid ||
+            nsLayoutUtils::GetFirstLineBaseline(wm, child, &baseline)) {
+          NS_ASSERTION(baseline != NS_INTRINSIC_WIDTH_UNKNOWN,
+                       "about to use an unknown baseline");
           auto frameSize = isInlineAxis ? child->ISize(wm) : child->BSize(wm);
           auto m = child->GetLogicalUsedMargin(wm);
           baseline += isInlineAxis ? m.IStart(wm) : m.BStart(wm);
           auto alignSize = frameSize + (isInlineAxis ? m.IStartEnd(wm)
                                                      : m.BStartEnd(wm));
           firstBaselineItems.AppendElement(ItemBaselineData(
             { baselineTrack, baseline, alignSize, &gridItem }));
         } else {
           state &= ~ItemState::eAllBaselineBits;
         }
       } else {
-        if (nsLayoutUtils::GetLastLineBaseline(wm, child, &baseline)) {
+        if (grid) {
+          if (isOrthogonal == isInlineAxis) {
+            grid->GetBBaseline(BaselineSharingGroup::eLast, &baseline);
+          } else {
+            grid->GetIBaseline(BaselineSharingGroup::eLast, &baseline);
+          }
+        }
+        if (grid ||
+            nsLayoutUtils::GetLastLineBaseline(wm, child, &baseline)) {
+          NS_ASSERTION(baseline != NS_INTRINSIC_WIDTH_UNKNOWN,
+                       "about to use an unknown baseline");
           auto frameSize = isInlineAxis ? child->ISize(wm) : child->BSize(wm);
           auto m = child->GetLogicalUsedMargin(wm);
-          auto descent = frameSize - baseline + (isInlineAxis ? m.IEnd(wm)
-                                                              : m.BEnd(wm));
+          if (!grid) {
+            // Convert to distance from border-box end.
+            baseline = frameSize - baseline;
+          }
+          auto descent = baseline + (isInlineAxis ? m.IEnd(wm) : m.BEnd(wm));
           auto alignSize = frameSize + (isInlineAxis ? m.IStartEnd(wm)
                                                      : m.BStartEnd(wm));
           lastBaselineItems.AppendElement(ItemBaselineData(
             { baselineTrack, descent, alignSize, &gridItem }));
         } else {
           state &= ~ItemState::eAllBaselineBits;
         }
       }
@@ -5820,16 +5868,21 @@ nsGridContainerFrame::Reflow(nsPresConte
 
   RenumberList();
 
 #ifdef DEBUG
   mDidPushItemsBitMayLie = false;
   SanityCheckGridItemsBeforeReflow();
 #endif // DEBUG
 
+  mBaseline[0][0] = NS_INTRINSIC_WIDTH_UNKNOWN;
+  mBaseline[0][1] = NS_INTRINSIC_WIDTH_UNKNOWN;
+  mBaseline[1][0] = NS_INTRINSIC_WIDTH_UNKNOWN;
+  mBaseline[1][1] = NS_INTRINSIC_WIDTH_UNKNOWN;
+
   const nsStylePosition* stylePos = aReflowInput.mStylePosition;
   if (!prevInFlow) {
     InitImplicitNamedAreas(stylePos);
   }
   GridReflowInput gridReflowInput(this, aReflowInput);
   if (gridReflowInput.mIter.ItemsAreAlreadyInOrder()) {
     AddStateBits(NS_STATE_GRID_NORMAL_FLOW_CHILDREN_IN_CSS_ORDER);
   } else {
@@ -5893,31 +5946,95 @@ nsGridContainerFrame::Reflow(nsPresConte
       StyleBorder()->mBoxDecorationBreak !=
         StyleBoxDecorationBreak::Clone) {
     bp.BEnd(wm) = nscoord(0);
   }
 
   LogicalSize desiredSize(wm, computedISize + bp.IStartEnd(wm),
                               bSize         + bp.BStartEnd(wm));
   aDesiredSize.SetSize(wm, desiredSize);
-  aDesiredSize.mOverflowAreas.UnionAllWith(nsRect(0, 0,
-                                                  aDesiredSize.Width(),
-                                                  aDesiredSize.Height()));
+  nsRect frameRect(0, 0, aDesiredSize.Width(), aDesiredSize.Height());
+  aDesiredSize.mOverflowAreas.UnionAllWith(frameRect);
 
   // Convert INCOMPLETE -> OVERFLOW_INCOMPLETE and zero bsize if we're an OC.
   if (HasAnyStateBits(NS_FRAME_IS_OVERFLOW_CONTAINER)) {
     if (!NS_FRAME_IS_COMPLETE(aStatus)) {
       NS_FRAME_SET_OVERFLOW_INCOMPLETE(aStatus);
       aStatus |= NS_FRAME_REFLOW_NEXTINFLOW;
     }
     bSize = 0;
     desiredSize.BSize(wm) = bSize + bp.BStartEnd(wm);
     aDesiredSize.SetSize(wm, desiredSize);
   }
 
+  if (!gridReflowInput.mInFragmentainer) {
+    MOZ_ASSERT(gridReflowInput.mIter.IsValid());
+    auto sz = frameRect.Size();
+    CalculateBaselines(BaselineSet::eBoth, &gridReflowInput.mIter,
+                       &gridReflowInput.mGridItems, gridReflowInput.mCols,
+                       0, gridReflowInput.mCols.mSizes.Length(),
+                       wm, sz, bp.IStart(wm),
+                       bp.IEnd(wm), desiredSize.ISize(wm));
+    CalculateBaselines(BaselineSet::eBoth, &gridReflowInput.mIter,
+                       &gridReflowInput.mGridItems, gridReflowInput.mRows,
+                       0, gridReflowInput.mRows.mSizes.Length(),
+                       wm, sz, bp.BStart(wm),
+                       bp.BEnd(wm), desiredSize.BSize(wm));
+  } else {
+    // Only compute first-baseline if this fragment contains the first track.
+    // XXXmats maybe remove this condition? bug 1306499
+    BaselineSet baselines = BaselineSet::eNone;
+    if (gridReflowInput.mStartRow == 0 &&
+        gridReflowInput.mStartRow != gridReflowInput.mNextFragmentStartRow) {
+      baselines = BaselineSet::eFirst;
+    }
+    // Only compute last-baseline if this fragment contains the last track.
+    // XXXmats maybe remove this condition? bug 1306499
+    uint32_t len = gridReflowInput.mRows.mSizes.Length();
+    if (gridReflowInput.mStartRow != len &&
+        gridReflowInput.mNextFragmentStartRow == len) {
+      baselines = BaselineSet(baselines | BaselineSet::eLast);
+    }
+    Maybe<GridItemCSSOrderIterator> iter;
+    Maybe<nsTArray<GridItemInfo>> gridItems;
+    if (baselines != BaselineSet::eNone) {
+      // We need to create a new iterator and GridItemInfo array because we
+      // might have pushed some children at this point.
+      // Even if the gridReflowInput iterator is invalid we can reuse its
+      // state about order to optimize initialization of the new iterator.
+      // An ordered child list can't become unordered by pushing frames.
+      // An unordered list can become ordered in a number of cases, but we
+      // ignore that here and guess that the child list is still unordered.
+      // XXX this is O(n^2) in the number of items in this fragment: bug 1306705
+      using Filter = GridItemCSSOrderIterator::ChildFilter;
+      using Order = GridItemCSSOrderIterator::OrderState;
+      bool ordered = gridReflowInput.mIter.ItemsAreAlreadyInOrder();
+      auto orderState = ordered ? Order::eKnownOrdered : Order::eKnownUnordered;
+      iter.emplace(this, kPrincipalList, Filter::eSkipPlaceholders, orderState);
+      gridItems.emplace();
+      for (; !iter->AtEnd(); iter->Next()) {
+        auto child = **iter;
+        for (const auto& info : gridReflowInput.mGridItems) {
+          if (info.mFrame == child) {
+            gridItems->AppendElement(info);
+          }
+        }
+      }
+    }
+    auto sz = frameRect.Size();
+    CalculateBaselines(baselines, iter.ptrOr(nullptr), gridItems.ptrOr(nullptr),
+                       gridReflowInput.mCols, 0,
+                       gridReflowInput.mCols.mSizes.Length(), wm, sz,
+                       bp.IStart(wm), bp.IEnd(wm), desiredSize.ISize(wm));
+    CalculateBaselines(baselines, iter.ptrOr(nullptr), gridItems.ptrOr(nullptr),
+                       gridReflowInput.mRows, gridReflowInput.mStartRow,
+                       gridReflowInput.mNextFragmentStartRow, wm, sz,
+                       bp.BStart(wm), bp.BEnd(wm), desiredSize.BSize(wm));
+  }
+
   if (HasAnyStateBits(NS_STATE_GRID_GENERATE_COMPUTED_VALUES)) {
     // This state bit will never be cleared, since reflow can be called
     // multiple times in fragmented grids, and it's challenging to scope
     // the bit to only that sequence of calls. This is relatively harmless
     // since this bit is only set by accessing a ChromeOnly property, and
     // therefore can't unduly slow down normal web browsing.
 
     // Now that we know column and row sizes and positions, set
@@ -6220,16 +6337,20 @@ nsGridContainerFrame::GetPrefISize(nsRen
   return mCachedPrefISize;
 }
 
 void
 nsGridContainerFrame::MarkIntrinsicISizesDirty()
 {
   mCachedMinISize = NS_INTRINSIC_WIDTH_UNKNOWN;
   mCachedPrefISize = NS_INTRINSIC_WIDTH_UNKNOWN;
+  mBaseline[0][0] = NS_INTRINSIC_WIDTH_UNKNOWN;
+  mBaseline[0][1] = NS_INTRINSIC_WIDTH_UNKNOWN;
+  mBaseline[1][0] = NS_INTRINSIC_WIDTH_UNKNOWN;
+  mBaseline[1][1] = NS_INTRINSIC_WIDTH_UNKNOWN;
   nsContainerFrame::MarkIntrinsicISizesDirty();
 }
 
 nsIAtom*
 nsGridContainerFrame::GetType() const
 {
   return nsGkAtoms::gridContainerFrame;
 }
@@ -6310,16 +6431,151 @@ nsGridContainerFrame::RemoveFrame(ChildL
         frameThatMayLie->GetPrevInFlow());
     } while (frameThatMayLie);
   }
 #endif
 
   nsContainerFrame::RemoveFrame(aListID, aOldFrame);
 }
 
+nscoord
+nsGridContainerFrame::SynthesizeBaseline(
+  const FindItemInGridOrderResult& aGridOrderItem,
+  LogicalAxis          aAxis,
+  BaselineSharingGroup aGroup,
+  const nsSize&        aCBPhysicalSize,
+  nscoord              aCBSize,
+  WritingMode          aCBWM)
+{
+  if (MOZ_UNLIKELY(!aGridOrderItem.mItem)) {
+    // No item in this fragment - synthesize a baseline from our border-box.
+    return ::SynthesizeBaselineFromBorderBox(aGroup, aCBWM, aCBSize);
+  }
+  nsIFrame* child = aGridOrderItem.mItem->mFrame;
+  nsGridContainerFrame* grid = do_QueryFrame(child);
+  auto childWM = child->GetWritingMode();
+  bool isOrthogonal = aCBWM.IsOrthogonalTo(childWM);
+  nscoord baseline;
+  nscoord start;
+  nscoord size;
+  if (aAxis == eLogicalAxisBlock) {
+    start = child->GetLogicalNormalPosition(aCBWM, aCBPhysicalSize).B(aCBWM);
+    size = child->BSize(aCBWM);
+    if (grid && aGridOrderItem.mIsInEdgeTrack) {
+      isOrthogonal ? grid->GetIBaseline(aGroup, &baseline) :
+                     grid->GetBBaseline(aGroup, &baseline);
+    } else if (!isOrthogonal && aGridOrderItem.mIsInEdgeTrack &&
+               nsLayoutUtils::GetLastLineBaseline(childWM, child, &baseline)) {
+      if (aGroup == BaselineSharingGroup::eLast) {
+        baseline = size - baseline; // convert to distance from border-box end
+      }
+    } else {
+      baseline = ::SynthesizeBaselineFromBorderBox(aGroup, childWM, size);
+    }
+  } else {
+    start = child->GetLogicalNormalPosition(aCBWM, aCBPhysicalSize).I(aCBWM);
+    size = child->ISize(aCBWM);
+    if (grid && aGridOrderItem.mIsInEdgeTrack) {
+      isOrthogonal ? grid->GetBBaseline(aGroup, &baseline) :
+                     grid->GetIBaseline(aGroup, &baseline);
+    } else if (isOrthogonal && aGridOrderItem.mIsInEdgeTrack &&
+               nsLayoutUtils::GetLastLineBaseline(childWM, child, &baseline)) {
+      if (aGroup == BaselineSharingGroup::eLast) {
+        baseline = size - baseline; // convert to distance from border-box end
+      }
+    } else {
+      baseline = ::SynthesizeBaselineFromBorderBox(aGroup, childWM, size);
+    }
+  }
+  return aGroup == BaselineSharingGroup::eFirst ? start + baseline :
+    aCBSize - start - size + baseline;
+}
+
+void
+nsGridContainerFrame::CalculateBaselines(
+  BaselineSet                   aBaselineSet,
+  GridItemCSSOrderIterator*     aIter,
+  const nsTArray<GridItemInfo>* aGridItems,
+  const Tracks&    aTracks,
+  uint32_t         aFragmentStartTrack,
+  uint32_t         aFirstExcludedTrack,
+  WritingMode      aWM,
+  const nsSize&    aCBPhysicalSize,
+  nscoord          aCBBorderPaddingStart,
+  nscoord          aCBBorderPaddingEnd,
+  nscoord          aCBSize)
+{
+  const auto axis = aTracks.mAxis;
+  auto firstBaseline = aTracks.mBaseline[BaselineSharingGroup::eFirst];
+  if (!(aBaselineSet & BaselineSet::eFirst)) {
+    mBaseline[axis][BaselineSharingGroup::eFirst] =
+      ::SynthesizeBaselineFromBorderBox(BaselineSharingGroup::eFirst, aWM,
+                                        aCBSize);
+  } else if (firstBaseline == NS_INTRINSIC_WIDTH_UNKNOWN) {
+    FindItemInGridOrderResult gridOrderFirstItem =
+      FindFirstItemInGridOrder(*aIter, *aGridItems,
+        axis == eLogicalAxisBlock ? &GridArea::mRows : &GridArea::mCols,
+        axis == eLogicalAxisBlock ? &GridArea::mCols : &GridArea::mRows,
+        aFragmentStartTrack);
+    mBaseline[axis][BaselineSharingGroup::eFirst] =
+      SynthesizeBaseline(gridOrderFirstItem,
+                         axis,
+                         BaselineSharingGroup::eFirst,
+                         aCBPhysicalSize,
+                         aCBSize,
+                         aWM);
+  } else {
+    // We have a first-baseline group in the start track in this fragment.
+    // Convert it from track to grid container border-box coordinates.
+    MOZ_ASSERT(!aGridItems->IsEmpty());
+    nscoord gapBeforeStartTrack = aFragmentStartTrack == 0 ?
+      aTracks.GridLineEdge(aFragmentStartTrack, GridLineSide::eAfterGridGap) :
+      nscoord(0); // no content gap at start of fragment
+    mBaseline[axis][BaselineSharingGroup::eFirst] =
+      aCBBorderPaddingStart + gapBeforeStartTrack + firstBaseline;
+  }
+
+  auto lastBaseline = aTracks.mBaseline[BaselineSharingGroup::eLast];
+  if (!(aBaselineSet & BaselineSet::eLast)) {
+    mBaseline[axis][BaselineSharingGroup::eLast] =
+      ::SynthesizeBaselineFromBorderBox(BaselineSharingGroup::eLast, aWM,
+                                        aCBSize);
+  } else if (lastBaseline == NS_INTRINSIC_WIDTH_UNKNOWN) {
+    // For finding items for the last-baseline we need to create a reverse
+    // iterator ('aIter' is the forward iterator from the GridReflowInput).
+    using Iter = ReverseGridItemCSSOrderIterator;
+    auto orderState = aIter->ItemsAreAlreadyInOrder() ?
+      Iter::OrderState::eKnownOrdered : Iter::OrderState::eKnownUnordered;
+    Iter iter(this, kPrincipalList, Iter::ChildFilter::eSkipPlaceholders,
+              orderState);
+    iter.SetGridItemCount(aGridItems->Length());
+    FindItemInGridOrderResult gridOrderLastItem =
+      FindLastItemInGridOrder(iter, *aGridItems,
+        axis == eLogicalAxisBlock ? &GridArea::mRows : &GridArea::mCols,
+        axis == eLogicalAxisBlock ? &GridArea::mCols : &GridArea::mRows,
+        aFragmentStartTrack, aFirstExcludedTrack);
+    mBaseline[axis][BaselineSharingGroup::eLast] =
+      SynthesizeBaseline(gridOrderLastItem,
+                         axis,
+                         BaselineSharingGroup::eLast,
+                         aCBPhysicalSize,
+                         aCBSize,
+                         aWM);
+  } else {
+    // We have a last-baseline group in the end track in this fragment.
+    // Convert it from track to grid container border-box coordinates.
+    MOZ_ASSERT(!aGridItems->IsEmpty());
+    auto borderBoxStartToEndOfEndTrack = aCBBorderPaddingStart +
+      aTracks.GridLineEdge(aFirstExcludedTrack, GridLineSide::eBeforeGridGap) -
+      aTracks.GridLineEdge(aFragmentStartTrack, GridLineSide::eBeforeGridGap);
+    mBaseline[axis][BaselineSharingGroup::eLast] =
+      (aCBSize - borderBoxStartToEndOfEndTrack) + lastBaseline;
+  }
+}
+
 #ifdef DEBUG_FRAME_DUMP
 nsresult
 nsGridContainerFrame::GetFrameName(nsAString& aResult) const
 {
   return MakeFrameName(NS_LITERAL_STRING("GridContainer"), aResult);
 }
 #endif
 
--- a/layout/generic/nsGridContainerFrame.h
+++ b/layout/generic/nsGridContainerFrame.h
@@ -10,16 +10,24 @@
 #define nsGridContainerFrame_h___
 
 #include "mozilla/Maybe.h"
 #include "mozilla/TypeTraits.h"
 #include "nsContainerFrame.h"
 #include "nsHashKeys.h"
 #include "nsTHashtable.h"
 
+// https://drafts.csswg.org/css-align-3/#baseline-sharing-group
+enum BaselineSharingGroup
+{
+  // NOTE Used as an array index so must be 0 and 1.
+  eFirst = 0,
+  eLast = 1,
+};
+
 /**
  * Factory function.
  * @return a newly allocated nsGridContainerFrame (infallible)
  */
 nsContainerFrame* NS_NewGridContainerFrame(nsIPresShell* aPresShell,
                                            nsStyleContext* aContext);
 
 namespace mozilla {
@@ -96,16 +104,23 @@ public:
     return nsContainerFrame::IsFrameOfType(aFlags &
              ~nsIFrame::eCanContainOverflowContainers);
   }
 
   void BuildDisplayList(nsDisplayListBuilder*   aBuilder,
                         const nsRect&           aDirtyRect,
                         const nsDisplayListSet& aLists) override;
 
+  nscoord GetLogicalBaseline(mozilla::WritingMode aWM) const override
+  {
+    nscoord b;
+    GetBBaseline(BaselineSharingGroup::eFirst, &b);
+    return b;
+  }
+
 #ifdef DEBUG_FRAME_DUMP
   nsresult GetFrameName(nsAString& aResult) const override;
 #endif
 
   // nsContainerFrame overrides
   bool DrainSelfOverflowList() override;
   void AppendFrames(ChildListID aListID, nsFrameList& aFrameList) override;
   void InsertFrames(ChildListID aListID, nsIFrame* aPrevFrame,
@@ -221,17 +236,22 @@ protected:
   struct Tracks;
   struct TranslatedLineRange;
   friend nsContainerFrame* NS_NewGridContainerFrame(nsIPresShell* aPresShell,
                                                     nsStyleContext* aContext);
   explicit nsGridContainerFrame(nsStyleContext* aContext)
     : nsContainerFrame(aContext)
     , mCachedMinISize(NS_INTRINSIC_WIDTH_UNKNOWN)
     , mCachedPrefISize(NS_INTRINSIC_WIDTH_UNKNOWN)
-  {}
+  {
+    mBaseline[0][0] = NS_INTRINSIC_WIDTH_UNKNOWN;
+    mBaseline[0][1] = NS_INTRINSIC_WIDTH_UNKNOWN;
+    mBaseline[1][0] = NS_INTRINSIC_WIDTH_UNKNOWN;
+    mBaseline[1][1] = NS_INTRINSIC_WIDTH_UNKNOWN;
+  }
 
   /**
    * XXX temporary - move the ImplicitNamedAreas stuff to the style system.
    * The implicit area names that come from x-start .. x-end lines in
    * grid-template-columns / grid-template-rows are stored in this frame
    * property when needed, as a ImplicitNamedAreas* value.
    */
   void InitImplicitNamedAreas(const nsStylePosition* aStyle);
@@ -256,16 +276,64 @@ protected:
   // Helper for AppendFrames / InsertFrames.
   void NoteNewChildren(ChildListID aListID, const nsFrameList& aFrameList);
 
   // Helper to move child frames into the kOverflowList.
   void MergeSortedOverflow(nsFrameList& aList);
   // Helper to move child frames into the kExcessOverflowContainersList:.
   void MergeSortedExcessOverflowContainers(nsFrameList& aList);
 
+  bool GetBBaseline(BaselineSharingGroup aBaselineGroup, nscoord* aResult) const
+  {
+    *aResult = mBaseline[mozilla::eLogicalAxisBlock][aBaselineGroup];
+    return true;
+  }
+  bool GetIBaseline(BaselineSharingGroup aBaselineGroup, nscoord* aResult) const
+  {
+    *aResult = mBaseline[mozilla::eLogicalAxisInline][aBaselineGroup];
+    return true;
+  }
+
+  /**
+   * Calculate this grid container's baselines.
+   * @param aBaselineSet which baseline(s) to derive from a baseline-group or
+   * items; a baseline not included is synthesized from the border-box instead.
+   * @param aFragmentStartTrack is the first track in this fragment in the same
+   * axis as aMajor.  Pass zero if that's not the axis we're fragmenting in.
+   * @param aFirstExcludedTrack should be the first track in the next fragment
+   * or one beyond the final track in the last fragment, in aMajor's axis.
+   * Pass the number of tracks if that's not the axis we're fragmenting in.
+   */
+  enum BaselineSet : uint32_t {
+    eNone =  0x0,
+    eFirst = 0x1,
+    eLast  = 0x2,
+    eBoth  = eFirst | eLast,
+  };
+  void CalculateBaselines(BaselineSet                   aBaselineSet,
+                          GridItemCSSOrderIterator*     aIter,
+                          const nsTArray<GridItemInfo>* aGridItems,
+                          const Tracks&    aTracks,
+                          uint32_t         aFragmentStartTrack,
+                          uint32_t         aFirstExcludedTrack,
+                          WritingMode      aWM,
+                          const nsSize&    aCBPhysicalSize,
+                          nscoord          aCBBorderPaddingStart,
+                          nscoord          aCBBorderPaddingStartEnd,
+                          nscoord          aCBSize);
+
+  /**
+   * Synthesize a Grid container baseline for aGroup.
+   */
+  nscoord SynthesizeBaseline(const FindItemInGridOrderResult& aItem,
+                             mozilla::LogicalAxis aAxis,
+                             BaselineSharingGroup aGroup,
+                             const nsSize&        aCBPhysicalSize,
+                             nscoord              aCBSize,
+                             WritingMode          aCBWM);
   /**
    * Find the first item in Grid Order in this fragment.
    * https://drafts.csswg.org/css-grid/#grid-order
    * @param aFragmentStartTrack is the first track in this fragment in the same
    * axis as aMajor.  Pass zero if that's not the axis we're fragmenting in.
    */
   static FindItemInGridOrderResult
   FindFirstItemInGridOrder(GridItemCSSOrderIterator& aIter,
@@ -356,16 +424,19 @@ private:
                          nsReflowStatus&         aStatus);
 
   /**
    * Cached values to optimize GetMinISize/GetPrefISize.
    */
   nscoord mCachedMinISize;
   nscoord mCachedPrefISize;
 
+  // Our baselines, one per BaselineSharingGroup per axis.
+  nscoord mBaseline[2/*LogicalAxis*/][2/*BaselineSharingGroup*/];
+
 #ifdef DEBUG
   // If true, NS_STATE_GRID_DID_PUSH_ITEMS may be set even though all pushed
   // frames may have been removed.  This is used to suppress an assertion
   // in case RemoveFrame removed all associated child frames.
   bool mDidPushItemsBitMayLie;
 #endif
 };