Bug 1221525 part 4 - [css-grid] Implement Grid layout for align|justify-self:baseline|last-baseline (aka "Baseline Self-Alignment"). r=dholbert
authorMats Palmgren <mats@mozilla.com>
Thu, 02 Jun 2016 17:46:58 +0200
changeset 339220 017d5f6391838e1a00b9500770b30966c7914fb7
parent 339219 1dd828a285324256dbdd2448f32fb600dd507050
child 339221 be927454459860da4b24bf174312ae72b62c7e15
push id6249
push userjlund@mozilla.com
push dateMon, 01 Aug 2016 13:59:36 +0000
treeherdermozilla-beta@bad9d4f5bf7e [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersdholbert
bugs1221525
milestone49.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 1221525 part 4 - [css-grid] Implement Grid layout for align|justify-self:baseline|last-baseline (aka "Baseline Self-Alignment"). r=dholbert
layout/generic/nsGridContainerFrame.cpp
--- a/layout/generic/nsGridContainerFrame.cpp
+++ b/layout/generic/nsGridContainerFrame.cpp
@@ -1128,16 +1128,22 @@ struct nsGridContainerFrame::Tracks
 
   /**
    * Initialize grid item baseline state and offsets.
    */
   void InitializeItemBaselines(GridReflowState&        aState,
                                nsTArray<GridItemInfo>& aGridItems);
 
   /**
+   * Apply the additional alignment needed to align the baseline-aligned subtree
+   * the item belongs to within its baseline track.
+   */
+  void AlignBaselineSubtree(const GridItemInfo& aGridItem) const;
+
+  /**
    * Resolve Intrinsic Track Sizes.
    * http://dev.w3.org/csswg/css-grid/#algo-content
    */
   void ResolveIntrinsicSize(GridReflowState&            aState,
                             nsTArray<GridItemInfo>&     aGridItems,
                             const TrackSizingFunctions& aFunctions,
                             LineRange GridArea::*       aRange,
                             nscoord                     aPercentageBasis,
@@ -1681,17 +1687,23 @@ struct MOZ_STACK_CLASS nsGridContainerFr
     // XXX NOTE: This is O(n^2) in the number of items. (bug 1252186)
     mIter.Reset();
     for (; !mIter.AtEnd(); mIter.Next()) {
       nsIFrame* child = *mIter;
       nsIFrame* childFirstInFlow = child->FirstInFlow();
       DebugOnly<size_t> len = mGridItems.Length();
       for (auto& itemInfo : mSharedGridData->mGridItems) {
         if (itemInfo.mFrame == childFirstInFlow) {
-          mGridItems.AppendElement(GridItemInfo(child, itemInfo.mArea));
+          auto item = mGridItems.AppendElement(GridItemInfo(child, itemInfo.mArea));
+          // Copy the item's baseline data so that the item's last fragment can do
+          // last-baseline alignment if necessary.
+          item->mState[0] |= itemInfo.mState[0] & ItemState::eAllBaselineBits;
+          item->mState[1] |= itemInfo.mState[1] & ItemState::eAllBaselineBits;
+          item->mBaselineOffset[0] = itemInfo.mBaselineOffset[0];
+          item->mBaselineOffset[1] = itemInfo.mBaselineOffset[1];
           break;
         }
       }
       MOZ_ASSERT(mGridItems.Length() == len + 1, "can't find GridItemInfo");
     }
 
     // XXX NOTE: This is O(n^2) in the number of abs.pos. items. (bug 1252186)
     nsFrameList absPosChildren(aGridContainerFrame->GetChildList(
@@ -2221,18 +2233,19 @@ SpaceToFill(WritingMode aWM, const Logic
   nscoord size = aAxis == eLogicalAxisBlock ? aSize.BSize(aWM)
                                             : aSize.ISize(aWM);
   return aCBSize - (size + aMargin);
 }
 
 // Align an item's margin box in its aAxis inside aCBSize.
 static void
 AlignJustifySelf(uint8_t aAlignment, bool aOverflowSafe, LogicalAxis aAxis,
-                 bool aSameSide, nscoord aCBSize, const nsHTMLReflowState& aRS,
-                 const LogicalSize& aChildSize, LogicalPoint* aPos)
+                 bool aSameSide, nscoord aBaselineAdjust, nscoord aCBSize,
+                 const nsHTMLReflowState& aRS, const LogicalSize& aChildSize,
+                 LogicalPoint* aPos)
 {
   MOZ_ASSERT(aAlignment != NS_STYLE_ALIGN_AUTO, "unexpected 'auto' "
              "computed value for normal flow grid item");
   MOZ_ASSERT(aAlignment != NS_STYLE_ALIGN_LEFT &&
              aAlignment != NS_STYLE_ALIGN_RIGHT,
              "caller should map that to the corresponding START/END");
 
   // Map some alignment values to 'start' / 'end'.
@@ -2311,18 +2324,24 @@ AlignJustifySelf(uint8_t aAlignment, boo
     }
   }
 
   // Set the position (aPos) for the requested alignment.
   nscoord offset = 0; // NOTE: this is the resulting frame offset (border box).
   switch (aAlignment) {
     case NS_STYLE_ALIGN_BASELINE:
     case NS_STYLE_ALIGN_LAST_BASELINE:
-      NS_WARNING("NYI: baseline/last-baseline for grid (bug 1151204)"); // XXX
-      MOZ_FALLTHROUGH;
+      if (MOZ_LIKELY(aSameSide == (aAlignment == NS_STYLE_ALIGN_BASELINE))) {
+        offset = marginStart + aBaselineAdjust;
+      } else {
+        nscoord size = aAxis == eLogicalAxisBlock ? aChildSize.BSize(wm)
+                                                  : aChildSize.ISize(wm);
+        offset = aCBSize - (size + marginEnd) - aBaselineAdjust;
+      }
+      break;
     case NS_STYLE_ALIGN_STRETCH:
       MOZ_FALLTHROUGH; // ComputeSize() deals with it
     case NS_STYLE_ALIGN_START:
       offset = marginStart;
       break;
     case NS_STYLE_ALIGN_END: {
       nscoord size = aAxis == eLogicalAxisBlock ? aChildSize.BSize(wm)
                                                 : aChildSize.ISize(wm);
@@ -2350,17 +2369,18 @@ SameSide(WritingMode aContainerWM, Logic
 {
   MOZ_ASSERT(aContainerWM.PhysicalAxis(GetAxis(aContainerSide)) ==
                  aChildWM.PhysicalAxis(GetAxis(aChildSide)));
   return aContainerWM.PhysicalSide(aContainerSide) ==
              aChildWM.PhysicalSide(aChildSide);
 }
 
 static void
-AlignSelf(uint8_t aAlignSelf, const LogicalRect& aCB, const WritingMode aCBWM,
+AlignSelf(const nsGridContainerFrame::GridItemInfo& aGridItem,
+          uint8_t aAlignSelf, const LogicalRect& aCB, const WritingMode aCBWM,
           const nsHTMLReflowState& aRS, const LogicalSize& aSize,
           LogicalPoint* aPos)
 {
   auto alignSelf = aAlignSelf;
   bool overflowSafe = alignSelf & NS_STYLE_ALIGN_SAFE;
   alignSelf &= ~NS_STYLE_ALIGN_FLAG_BITS;
   // Grid's 'align-self' axis is never parallel to the container's inline axis.
   if (alignSelf == NS_STYLE_ALIGN_LEFT || alignSelf == NS_STYLE_ALIGN_RIGHT) {
@@ -2371,54 +2391,67 @@ AlignSelf(uint8_t aAlignSelf, const Logi
   }
   WritingMode childWM = aRS.GetWritingMode();
   bool isOrthogonal = aCBWM.IsOrthogonalTo(childWM);
   // |sameSide| is true if the container's start side in this axis is the same
   // as the child's start side, in the child's parallel axis.
   bool sameSide = SameSide(aCBWM, eLogicalSideBStart,
                            childWM, isOrthogonal ? eLogicalSideIStart
                                                  : eLogicalSideBStart);
+  nscoord baselineAdjust = 0;
+  if (alignSelf == NS_STYLE_ALIGN_BASELINE ||
+      alignSelf == NS_STYLE_ALIGN_LAST_BASELINE) {
+    alignSelf = aGridItem.GetSelfBaseline(alignSelf, eLogicalAxisBlock,
+                                          &baselineAdjust);
+  }
   LogicalAxis axis = isOrthogonal ? eLogicalAxisInline : eLogicalAxisBlock;
-  AlignJustifySelf(alignSelf, overflowSafe, axis, sameSide,
+  AlignJustifySelf(alignSelf, overflowSafe, axis, sameSide, baselineAdjust,
                    aCB.BSize(aCBWM), aRS, aSize, aPos);
 }
 
 static void
-JustifySelf(uint8_t aJustifySelf, const LogicalRect& aCB, const WritingMode aCBWM,
+JustifySelf(const nsGridContainerFrame::GridItemInfo& aGridItem,
+            uint8_t aJustifySelf, const LogicalRect& aCB, const WritingMode aCBWM,
             const nsHTMLReflowState& aRS, const LogicalSize& aSize,
             LogicalPoint* aPos)
 {
   auto justifySelf = aJustifySelf;
   bool overflowSafe = justifySelf & NS_STYLE_JUSTIFY_SAFE;
   justifySelf &= ~NS_STYLE_JUSTIFY_FLAG_BITS;
   if (MOZ_LIKELY(justifySelf == NS_STYLE_ALIGN_NORMAL)) {
     justifySelf = NS_STYLE_ALIGN_STRETCH;
   }
   WritingMode childWM = aRS.GetWritingMode();
   bool isOrthogonal = aCBWM.IsOrthogonalTo(childWM);
   // |sameSide| is true if the container's start side in this axis is the same
   // as the child's start side, in the child's parallel axis.
   bool sameSide = SameSide(aCBWM, eLogicalSideIStart,
                            childWM, isOrthogonal ? eLogicalSideBStart
                                                  : eLogicalSideIStart);
+  nscoord baselineAdjust = 0;
   // Grid's 'justify-self' axis is always parallel to the container's inline
   // axis, so justify-self:left|right always applies.
   switch (justifySelf) {
     case NS_STYLE_JUSTIFY_LEFT:
       justifySelf = aCBWM.IsBidiLTR() ? NS_STYLE_JUSTIFY_START
                                       : NS_STYLE_JUSTIFY_END;
-    break;
+      break;
     case NS_STYLE_JUSTIFY_RIGHT:
       justifySelf = aCBWM.IsBidiLTR() ? NS_STYLE_JUSTIFY_END
                                       : NS_STYLE_JUSTIFY_START;
-    break;
+      break;
+    case NS_STYLE_JUSTIFY_BASELINE:
+    case NS_STYLE_JUSTIFY_LAST_BASELINE:
+      justifySelf = aGridItem.GetSelfBaseline(justifySelf, eLogicalAxisInline,
+                                              &baselineAdjust);
+      break;
   }
 
   LogicalAxis axis = isOrthogonal ? eLogicalAxisBlock : eLogicalAxisInline;
-  AlignJustifySelf(justifySelf, overflowSafe, axis, sameSide,
+  AlignJustifySelf(justifySelf, overflowSafe, axis, sameSide, baselineAdjust,
                    aCB.ISize(aCBWM), aRS, aSize, aPos);
 }
 
 static uint16_t
 GetAlignJustifyValue(uint16_t aAlignment, const WritingMode aWM,
                      const bool aIsAlign, bool* aOverflowSafe)
 {
   *aOverflowSafe = aAlignment & NS_STYLE_ALIGN_SAFE;
@@ -3377,16 +3410,22 @@ ContentContribution(const GridItemInfo& 
     // We need to reflow the child to find its BSize contribution.
     // XXX this will give mostly correct results for now (until bug 1174569).
     LogicalSize availableSize(child->GetWritingMode(), INFINITE_ISIZE_COORD, NS_UNCONSTRAINEDSIZE);
     size = ::MeasuringReflow(child, aReflowState, aRC, availableSize);
     nsIFrame::IntrinsicISizeOffsetData offsets = child->IntrinsicBSizeOffsets();
     size += offsets.hMargin;
     size = nsLayoutUtils::AddPercents(aConstraint, size, offsets.hPctMargin);
   }
+  MOZ_ASSERT(aGridItem.mBaselineOffset[aAxis] >= 0,
+             "baseline offset should be non-negative at this point");
+  MOZ_ASSERT((aGridItem.mState[aAxis] & ItemState::eIsBaselineAligned) ||
+             aGridItem.mBaselineOffset[aAxis] == nscoord(0),
+             "baseline offset should be zero when not baseline-aligned");
+  size += aGridItem.mBaselineOffset[aAxis];
   return std::max(size, 0);
 }
 
 static nscoord
 MinContentContribution(const GridItemInfo&      aGridItem,
                        const nsHTMLReflowState* aRS,
                        nsRenderingContext*      aRC,
                        WritingMode              aCBWM,
@@ -3420,17 +3459,22 @@ MinSize(const GridItemInfo&      aGridIt
   const nsStyleCoord& style = axis == eAxisHorizontal ? stylePos->mMinWidth
                                                       : stylePos->mMinHeight;
   // https://drafts.csswg.org/css-grid/#min-size-auto
   // This calculates the min-content contribution from either a definite
   // min-width (or min-height depending on aAxis), or the "specified /
   // transferred size" for min-width:auto if overflow == visible (as min-width:0
   // otherwise), or NS_UNCONSTRAINEDSIZE for other min-width intrinsic values
   // (which results in always taking the "content size" part below).
-  nscoord sz =
+  MOZ_ASSERT(aGridItem.mBaselineOffset[aAxis] >= 0,
+             "baseline offset should be non-negative at this point");
+  MOZ_ASSERT((aGridItem.mState[aAxis] & ItemState::eIsBaselineAligned) ||
+             aGridItem.mBaselineOffset[aAxis] == nscoord(0),
+             "baseline offset should be zero when not baseline-aligned");
+  nscoord sz = aGridItem.mBaselineOffset[aAxis] +
     nsLayoutUtils::MinSizeContributionForAxis(axis, aRC, child,
                                               nsLayoutUtils::MIN_ISIZE);
   auto unit = style.GetUnit();
   if (unit == eStyleUnit_Enumerated ||
       (unit == eStyleUnit_Auto &&
        child->StyleDisplay()->mOverflowX == NS_STYLE_OVERFLOW_VISIBLE)) {
     // Now calculate the "content size" part and return whichever is smaller.
     MOZ_ASSERT(unit != eStyleUnit_Enumerated || sz == NS_UNCONSTRAINEDSIZE);
@@ -3668,27 +3712,25 @@ nsGridContainerFrame::Tracks::Initialize
           // XXX method instead that does this in a better way.
           auto side = isInlineAxis ? eLogicalSideBStart : eLogicalSideIStart;
           // XXX the "isInlineAxis == isOrthogonal" is currently redundant, but
           // might be relevant if we change the itemHasBaselineParallelToTrack
           // condition above?
           auto childSide = isInlineAxis == isOrthogonal ? eLogicalSideIStart
                                                         : eLogicalSideBStart;
           bool sameSide = SameSide(wm, side, childWM, childSide);
-          if (isInlineAxis) {
-            switch (selfAlignment) {
-              case NS_STYLE_ALIGN_LEFT:
-                selfAlignment = wm.IsBidiLTR() ? NS_STYLE_ALIGN_START
-                                               : NS_STYLE_ALIGN_END;
-                break;
-              case NS_STYLE_ALIGN_RIGHT:
-                selfAlignment = wm.IsBidiLTR() ? NS_STYLE_ALIGN_END
-                                               : NS_STYLE_ALIGN_START;
-                break;
-            }
+          switch (selfAlignment) {
+            case NS_STYLE_ALIGN_LEFT:
+              selfAlignment = !isInlineAxis || wm.IsBidiLTR() ? NS_STYLE_ALIGN_START
+                                                              : NS_STYLE_ALIGN_END;
+              break;
+            case NS_STYLE_ALIGN_RIGHT:
+              selfAlignment = isInlineAxis && wm.IsBidiLTR() ? NS_STYLE_ALIGN_END
+                                                             : NS_STYLE_ALIGN_START;
+              break;
           }
           switch (selfAlignment) {
             case NS_STYLE_ALIGN_START:
             case NS_STYLE_ALIGN_FLEX_START:
               validCombo = sameSide ==
                            (alignContent == NS_STYLE_ALIGN_BASELINE);
               break;
             case NS_STYLE_ALIGN_END:
@@ -3750,17 +3792,17 @@ nsGridContainerFrame::Tracks::Initialize
                 (ItemState::eFirstBaseline | ItemState::eLastBaseline)) !=
                (ItemState::eFirstBaseline | ItemState::eLastBaseline),
                "baseline and last-baseline bits are mutually exclusive");
     MOZ_ASSERT((state &
                 (ItemState::eSelfBaseline | ItemState::eContentBaseline)) !=
                (ItemState::eSelfBaseline | ItemState::eContentBaseline),
                "*-self and *-content baseline bits are mutually exclusive");
     MOZ_ASSERT(!(state &
-                 (ItemState::eSelfBaseline | ItemState::eContentBaseline)) ==
+                 (ItemState::eFirstBaseline | ItemState::eLastBaseline)) ==
                !(state &
                  (ItemState::eSelfBaseline | ItemState::eContentBaseline)),
                "first/last bit requires self/content bit and vice versa");
     gridItem.mState[mAxis] = state;
     gridItem.mBaselineOffset[mAxis] = nscoord(0);
   }
 
   if (firstBaselineItems.IsEmpty() && lastBaselineItems.IsEmpty()) {
@@ -3772,16 +3814,58 @@ nsGridContainerFrame::Tracks::Initialize
   mBaselineSubtreeAlign[BaselineSharingGroup::eFirst] = NS_STYLE_ALIGN_START;
   mBaselineSubtreeAlign[BaselineSharingGroup::eLast] = NS_STYLE_ALIGN_END;
 
   CalculateItemBaselines(firstBaselineItems, BaselineSharingGroup::eFirst);
   CalculateItemBaselines(lastBaselineItems, BaselineSharingGroup::eLast);
 }
 
 void
+nsGridContainerFrame::Tracks::AlignBaselineSubtree(
+  const GridItemInfo& aGridItem) const
+{
+  auto state = aGridItem.mState[mAxis];
+  if (!(state & ItemState::eIsBaselineAligned)) {
+    return;
+  }
+  const GridArea& area = aGridItem.mArea;
+  int32_t baselineTrack;
+  const bool isFirstBaseline = state & ItemState::eFirstBaseline;
+  if (isFirstBaseline) {
+    baselineTrack = mAxis == eLogicalAxisBlock ? area.mRows.mStart
+                                               : area.mCols.mStart;
+  } else {
+    baselineTrack = (mAxis == eLogicalAxisBlock ? area.mRows.mEnd
+                                                : area.mCols.mEnd) - 1;
+  }
+  const TrackSize& sz = mSizes[baselineTrack];
+  auto baselineGroup = isFirstBaseline ? BaselineSharingGroup::eFirst
+                                       : BaselineSharingGroup::eLast;
+  nscoord delta = sz.mBase - sz.mBaselineSubtreeSize[baselineGroup];
+  const auto subtreeAlign = mBaselineSubtreeAlign[baselineGroup];
+  switch (subtreeAlign) {
+    case NS_STYLE_ALIGN_START:
+      if (state & ItemState::eLastBaseline) {
+        aGridItem.mBaselineOffset[mAxis] += delta;
+      }
+      break;
+    case NS_STYLE_ALIGN_END:
+      if (isFirstBaseline) {
+        aGridItem.mBaselineOffset[mAxis] += delta;
+      }
+      break;
+    case NS_STYLE_ALIGN_CENTER:
+      aGridItem.mBaselineOffset[mAxis] += delta / 2;
+      break;
+    default:
+      MOZ_ASSERT_UNREACHABLE("unexpected baseline subtree alignment");
+  }
+}
+
+void
 nsGridContainerFrame::Tracks::ResolveIntrinsicSize(
   GridReflowState&            aState,
   nsTArray<GridItemInfo>&     aGridItems,
   const TrackSizingFunctions& aFunctions,
   LineRange GridArea::*       aRange,
   nscoord                     aPercentageBasis,
   IntrinsicISizeType          aConstraint)
 {
@@ -4473,16 +4557,18 @@ nsGridContainerFrame::ReflowInFlowChild(
     isConstrainedBSize = aFragmentainer && !wm.IsOrthogonalTo(childWM);
     if (isConstrainedBSize) {
       cb.BStart(wm) = std::max(0, cb.BStart(wm) - aState.mFragBStart);
       toFragmentainerEnd = aFragmentainer->mToFragmentainerEnd -
         aState.mFragBStart - cb.BStart(wm);
       toFragmentainerEnd = std::max(toFragmentainerEnd, 0);
     }
     cb += aContentArea.Origin(wm);
+    aState.mRows.AlignBaselineSubtree(*aGridItemInfo);
+    aState.mCols.AlignBaselineSubtree(*aGridItemInfo);
   } else {
     cb = aContentArea;
   }
 
   LogicalSize reflowSize(cb.Size(wm));
   if (isConstrainedBSize) {
     reflowSize.BSize(wm) = toFragmentainerEnd;
   }
@@ -4527,20 +4613,20 @@ nsGridContainerFrame::ReflowInFlowChild(
   LogicalPoint childPos =
     cb.Origin(wm).ConvertTo(childWM, wm,
                             aContainerSize - childSize.PhysicalSize());
   // Apply align/justify-self and reflow again if that affects the size.
   if (MOZ_LIKELY(isGridItem)) {
     LogicalSize size = childSize.Size(childWM); // from the ReflowChild()
     if (NS_FRAME_IS_COMPLETE(aStatus)) {
       auto align = childRS.mStylePosition->ComputedAlignSelf(containerSC);
-      AlignSelf(align, cb, wm, childRS, size, &childPos);
+      AlignSelf(*aGridItemInfo, align, cb, wm, childRS, size, &childPos);
     }
     auto justify = childRS.mStylePosition->ComputedJustifySelf(containerSC);
-    JustifySelf(justify, cb, wm, childRS, size, &childPos);
+    JustifySelf(*aGridItemInfo, justify, cb, wm, childRS, size, &childPos);
   } else {
     // Put a placeholder at the padding edge, in case an ancestor is its CB.
     childPos -= padStart;
   }
   childRS.ApplyRelativePositioning(&childPos, aContainerSize);
   FinishReflowChild(aChild, pc, childSize, &childRS, childWM, childPos,
                     aContainerSize, 0);
   ConsiderChildOverflow(aDesiredSize.mOverflowAreas, aChild);