From 8abe3313666bf41cbec548b66f0077bf300132aa Mon Sep 17 00:00:00 2001 From: Mats Palmgren Date: Tue, 3 Nov 2015 17:52:40 +0100 Subject: [PATCH] Bug 1151213 part 1 - [css-grid][css-align] Implement layout for the 'align-self' and 'justify-self' properties on grid items. r=dholbert --- layout/generic/nsGridContainerFrame.cpp | 305 ++++++++++++++++++++++-- layout/generic/nsHTMLReflowState.cpp | 24 +- 2 files changed, 292 insertions(+), 37 deletions(-) diff --git a/layout/generic/nsGridContainerFrame.cpp b/layout/generic/nsGridContainerFrame.cpp index ddfbe776a9ac..4f26d6b17163 100644 --- a/layout/generic/nsGridContainerFrame.cpp +++ b/layout/generic/nsGridContainerFrame.cpp @@ -829,6 +829,243 @@ GetDisplayFlagsForGridItem(nsIFrame* aFrame) return nsIFrame::DISPLAY_CHILD_FORCE_PSEUDO_STACKING_CONTEXT; } +static nscoord +SpaceToFill(WritingMode aWM, const LogicalSize& aSize, nscoord aMargin, + LogicalAxis aAxis, nscoord aCBSize) +{ + nscoord size = aAxis == eLogicalAxisBlock ? aSize.BSize(aWM) + : aSize.ISize(aWM); + return aCBSize - (size + aMargin); +} + +static bool +AlignJustifySelf(uint8_t aAlignment, bool aOverflowSafe, LogicalAxis aAxis, + bool aSameSide, nscoord aCBSize, const nsHTMLReflowState& aRS, + const LogicalSize& aChildSize, LogicalSize* aContentSize, + 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'. + switch (aAlignment) { + case NS_STYLE_ALIGN_SELF_START: // align/justify-self: self-start + aAlignment = MOZ_LIKELY(aSameSide) ? NS_STYLE_ALIGN_START + : NS_STYLE_ALIGN_END; + break; + case NS_STYLE_ALIGN_SELF_END: // align/justify-self: self-end + aAlignment = MOZ_LIKELY(aSameSide) ? NS_STYLE_ALIGN_END + : NS_STYLE_ALIGN_START; + break; + case NS_STYLE_ALIGN_FLEX_START: // same as 'start' for Grid + aAlignment = NS_STYLE_ALIGN_START; + break; + case NS_STYLE_ALIGN_FLEX_END: // same as 'end' for Grid + aAlignment = NS_STYLE_ALIGN_END; + break; + } + + // XXX try to condense this code a bit by adding the necessary convenience + // methods? (bug 1209710) + + // Get the item's margin corresponding to the container's start/end side. + const LogicalMargin margin = aRS.ComputedLogicalMargin(); + WritingMode wm = aRS.GetWritingMode(); + nscoord marginStart, marginEnd; + if (aAxis == eLogicalAxisBlock) { + if (MOZ_LIKELY(aSameSide)) { + marginStart = margin.BStart(wm); + marginEnd = margin.BEnd(wm); + } else { + marginStart = margin.BEnd(wm); + marginEnd = margin.BStart(wm); + } + } else { + if (MOZ_LIKELY(aSameSide)) { + marginStart = margin.IStart(wm); + marginEnd = margin.IEnd(wm); + } else { + marginStart = margin.IEnd(wm); + marginEnd = margin.IStart(wm); + } + } + + // https://drafts.csswg.org/css-align-3/#overflow-values + // This implements = 'safe'. + if (MOZ_UNLIKELY(aOverflowSafe) && aAlignment != NS_STYLE_ALIGN_START) { + nscoord space = SpaceToFill(wm, aChildSize, marginStart + marginEnd, + aAxis, aCBSize); + // XXX we might want to include == 0 here as an optimization - + // I need to see what the baseline/last-baseline code looks like first. + if (space < 0) { + aAlignment = NS_STYLE_ALIGN_START; + } + } + + // Set the position and size (aPos/aContentSize) for the requested alignment. + bool didResize = false; + nscoord offset = 0; + switch (aAlignment) { + case NS_STYLE_ALIGN_BASELINE: + case NS_STYLE_ALIGN_LAST_BASELINE: + NS_WARNING("NYI: baseline/last-baseline for grid (bug 1151204)"); // XXX + case NS_STYLE_ALIGN_START: + offset = marginStart; + break; + case NS_STYLE_ALIGN_END: { + nscoord size = aAxis == eLogicalAxisBlock ? aChildSize.BSize(wm) + : aChildSize.ISize(wm); + offset = aCBSize - (size + marginEnd); + break; + } + case NS_STYLE_ALIGN_CENTER: + offset = SpaceToFill(wm, aChildSize, marginStart + marginEnd, + aAxis, aCBSize) / 2; + break; + case NS_STYLE_ALIGN_STRETCH: { + offset = marginStart; + const auto& styleMargin = aRS.mStyleMargin->mMargin; + if (aAxis == eLogicalAxisBlock + ? (aRS.mStylePosition->BSize(wm).GetUnit() == eStyleUnit_Auto && + styleMargin.GetBStartUnit(wm) != eStyleUnit_Auto && + styleMargin.GetBEndUnit(wm) != eStyleUnit_Auto) + : (aRS.mStylePosition->ISize(wm).GetUnit() == eStyleUnit_Auto && + styleMargin.GetIStartUnit(wm) != eStyleUnit_Auto && + styleMargin.GetIEndUnit(wm) != eStyleUnit_Auto)) { + nscoord size = aAxis == eLogicalAxisBlock ? aChildSize.BSize(wm) + : aChildSize.ISize(wm); + nscoord gap = aCBSize - (size + marginStart + marginEnd); + if (gap > 0) { + // Note: The ComputedMax* values are always content-box max values, + // even for box-sizing:border-box. + LogicalMargin bp = aRS.ComputedLogicalBorderPadding(); + // XXX ApplySkipSides is probably not very useful here since we + // might not have created any next-in-flow yet. Use the reflow status + // instead? Do all fragments stretch? (bug 1144096). + bp.ApplySkipSides(aRS.frame->GetLogicalSkipSides()); + nscoord bpInAxis = aAxis == eLogicalAxisBlock ? bp.BStartEnd(wm) + : bp.IStartEnd(wm); + nscoord contentSize = size - bpInAxis; + NS_ASSERTION(contentSize >= 0, "huh?"); + const nscoord unstretchedContentSize = contentSize; + contentSize += gap; + nscoord max = aAxis == eLogicalAxisBlock ? aRS.ComputedMaxBSize() + : aRS.ComputedMaxISize(); + if (MOZ_UNLIKELY(contentSize > max)) { + contentSize = max; + gap = contentSize - unstretchedContentSize; + } + // |gap| is now how much the content size is actually allowed to grow. + didResize = gap > 0; + if (didResize) { + (aAxis == eLogicalAxisBlock ? aContentSize->BSize(wm) + : aContentSize->ISize(wm)) = contentSize; + if (MOZ_UNLIKELY(!aSameSide)) { + offset += gap; + } + } + } + } + break; + } + default: + MOZ_ASSERT_UNREACHABLE("unknown align-/justify-self value"); + } + if (offset != 0) { + nscoord& pos = aAxis == eLogicalAxisBlock ? aPos->B(wm) : aPos->I(wm); + pos += MOZ_LIKELY(aSameSide) ? offset : -offset; + } + return didResize; +} + +static bool +SameSide(WritingMode aContainerWM, LogicalSide aContainerSide, + WritingMode aChildWM, LogicalSide aChildSide) +{ + MOZ_ASSERT(aContainerWM.PhysicalAxis(GetAxis(aContainerSide)) == + aChildWM.PhysicalAxis(GetAxis(aChildSide))); + return aContainerWM.PhysicalSide(aContainerSide) == + aChildWM.PhysicalSide(aChildSide); +} + +static Maybe +AlignSelf(uint8_t aAlignSelf, const LogicalRect& aCB, const WritingMode aCBWM, + const nsHTMLReflowState& aRS, const LogicalSize& aSize, + LogicalSize* aContentSize, LogicalPoint* aPos) +{ + Maybe resizedAxis; + auto alignSelf = aAlignSelf; + bool overflowSafe = alignSelf & NS_STYLE_ALIGN_SAFE; + alignSelf &= ~NS_STYLE_ALIGN_FLAG_BITS; + MOZ_ASSERT(alignSelf != NS_STYLE_ALIGN_LEFT && + alignSelf != NS_STYLE_ALIGN_RIGHT, + "Grid's 'align-self' axis is never parallel to the container's " + "inline axis, so these should've computed to 'start' already"); + if (MOZ_UNLIKELY(alignSelf == NS_STYLE_ALIGN_AUTO)) { + // Happens in rare edge cases when 'position' was ignored by the frame + // constructor (and the style system computed 'auto' based on 'position'). + alignSelf = 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, eLogicalSideBStart, + childWM, isOrthogonal ? eLogicalSideIStart + : eLogicalSideBStart); + LogicalAxis axis = isOrthogonal ? eLogicalAxisInline : eLogicalAxisBlock; + if (AlignJustifySelf(alignSelf, overflowSafe, axis, sameSide, + aCB.BSize(aCBWM), aRS, aSize, aContentSize, aPos)) { + resizedAxis.emplace(axis); + } + return resizedAxis; +} + +static Maybe +JustifySelf(uint8_t aJustifySelf, const LogicalRect& aCB, const WritingMode aCBWM, + const nsHTMLReflowState& aRS, const LogicalSize& aSize, + LogicalSize* aContentSize, LogicalPoint* aPos) +{ + Maybe resizedAxis; + auto justifySelf = aJustifySelf; + bool overflowSafe = justifySelf & NS_STYLE_JUSTIFY_SAFE; + justifySelf &= ~NS_STYLE_JUSTIFY_FLAG_BITS; + if (MOZ_UNLIKELY(justifySelf == NS_STYLE_ALIGN_AUTO)) { + // Happens in rare edge cases when 'position' was ignored by the frame + // constructor (and the style system computed 'auto' based on 'position'). + 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); + // 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; + case NS_STYLE_JUSTIFY_RIGHT: + justifySelf = aCBWM.IsBidiLTR() ? NS_STYLE_JUSTIFY_END + : NS_STYLE_JUSTIFY_START; + break; + } + + LogicalAxis axis = isOrthogonal ? eLogicalAxisBlock : eLogicalAxisInline; + if (AlignJustifySelf(justifySelf, overflowSafe, axis, sameSide, + aCB.ISize(aCBWM), aRS, aSize, aContentSize, aPos)) { + resizedAxis.emplace(axis); + } + return resizedAxis; +} + //---------------------------------------------------------------------- // Frame class boilerplate @@ -2367,6 +2604,7 @@ nsGridContainerFrame::ReflowChildren(GridReflowState& aState, (aContentArea.Size(wm) + aState.mReflowState->ComputedLogicalBorderPadding().Size(wm)).GetPhysicalSize(wm); nsPresContext* pc = PresContext(); + nsStyleContext* containerSC = StyleContext(); for (; !aState.mIter.AtEnd(); aState.mIter.Next()) { nsIFrame* child = *aState.mIter; const bool isGridItem = child->GetType() != nsGkAtoms::placeholderFrame; @@ -2383,36 +2621,63 @@ nsGridContainerFrame::ReflowChildren(GridReflowState& aState, } WritingMode childWM = child->GetWritingMode(); LogicalSize childCBSize = cb.Size(wm).ConvertTo(childWM, wm); - nsHTMLReflowState childRS(pc, *aState.mReflowState, child, childCBSize); - const LogicalMargin margin = childRS.ComputedLogicalMargin(); - if (childRS.ComputedBSize() == NS_AUTOHEIGHT && MOZ_LIKELY(isGridItem)) { - // XXX the start of an align-self:stretch impl. Needs min-/max-bsize - // clamping though, and check the prop value is actually 'stretch'! - LogicalMargin bp = childRS.ComputedLogicalBorderPadding(); - bp.ApplySkipSides(child->GetLogicalSkipSides()); - nscoord bSize = childCBSize.BSize(childWM) - bp.BStartEnd(childWM) - - margin.BStartEnd(childWM); - childRS.SetComputedBSize(std::max(bSize, 0)); - } + // XXX temporary workaround to avoid being INCOMPLETE until we have + // support for fragmentation (bug 1144096) + childCBSize.BSize(childWM) = NS_UNCONSTRAINEDSIZE; + + Maybe childRS; // Maybe<> so we can reuse the space + childRS.emplace(pc, *aState.mReflowState, child, childCBSize); // We need the width of the child before we can correctly convert // the writing-mode of its origin, so we reflow at (0, 0) using a dummy // containerSize, and then pass the correct position to FinishReflowChild. - nsHTMLReflowMetrics childSize(childRS); + Maybe childSize; // Maybe<> so we can reuse the space + childSize.emplace(*childRS); nsReflowStatus childStatus; const nsSize dummyContainerSize; - ReflowChild(child, pc, childSize, childRS, childWM, LogicalPoint(childWM), + ReflowChild(child, pc, *childSize, *childRS, childWM, LogicalPoint(childWM), dummyContainerSize, 0, childStatus); LogicalPoint childPos = cb.Origin(wm).ConvertTo(childWM, wm, - containerSize - childSize.PhysicalSize() - - margin.Size(childWM).GetPhysicalSize(childWM)); - childPos.I(childWM) += margin.IStart(childWM); - childPos.B(childWM) += margin.BStart(childWM); - childRS.ApplyRelativePositioning(&childPos, containerSize); - FinishReflowChild(child, pc, childSize, &childRS, childWM, childPos, + containerSize - childSize->PhysicalSize()); + // Apply align/justify-self and reflow again if that affects the size. + if (isGridItem) { + LogicalSize oldSize = childSize->Size(childWM); // from the ReflowChild() + LogicalSize newContentSize(childWM); + auto align = childRS->mStylePosition->ComputedAlignSelf( + childRS->mStyleDisplay, containerSC); + Maybe alignResize = + AlignSelf(align, cb, wm, *childRS, oldSize, &newContentSize, &childPos); + auto justify = childRS->mStylePosition->ComputedJustifySelf( + childRS->mStyleDisplay, containerSC); + Maybe justifyResize = + JustifySelf(justify, cb, wm, *childRS, oldSize, &newContentSize, &childPos); + if (alignResize || justifyResize) { + FinishReflowChild(child, pc, *childSize, childRS.ptr(), childWM, + LogicalPoint(childWM), containerSize, + NS_FRAME_NO_MOVE_FRAME | NS_FRAME_NO_SIZE_VIEW); + childSize.reset(); // In reverse declaration order since it runs + childRS.reset(); // destructors. + childRS.emplace(pc, *aState.mReflowState, child, childCBSize); + if ((alignResize && alignResize.value() == eLogicalAxisBlock) || + (justifyResize && justifyResize.value() == eLogicalAxisBlock)) { + childRS->SetComputedBSize(newContentSize.BSize(childWM)); + childRS->SetBResize(true); + } + if ((alignResize && alignResize.value() == eLogicalAxisInline) || + (justifyResize && justifyResize.value() == eLogicalAxisInline)) { + childRS->SetComputedISize(newContentSize.ISize(childWM)); + childRS->SetIResize(true); + } + childSize.emplace(*childRS); + ReflowChild(child, pc, *childSize, *childRS, childWM, + LogicalPoint(childWM), dummyContainerSize, 0, childStatus); + } + } + childRS->ApplyRelativePositioning(&childPos, containerSize); + FinishReflowChild(child, pc, *childSize, childRS.ptr(), childWM, childPos, containerSize, 0); ConsiderChildOverflow(aDesiredSize.mOverflowAreas, child); - // XXX deal with 'childStatus' not being COMPLETE + // XXX deal with 'childStatus' not being COMPLETE (bug 1144096) } if (IsAbsoluteContainer()) { diff --git a/layout/generic/nsHTMLReflowState.cpp b/layout/generic/nsHTMLReflowState.cpp index 5ec61ea07f1e..dc191660d193 100644 --- a/layout/generic/nsHTMLReflowState.cpp +++ b/layout/generic/nsHTMLReflowState.cpp @@ -18,7 +18,6 @@ #include "nsFontMetrics.h" #include "nsBlockFrame.h" #include "nsLineBox.h" -#include "nsFlexContainerFrame.h" #include "nsImageFrame.h" #include "nsTableFrame.h" #include "nsTableCellFrame.h" @@ -2037,18 +2036,6 @@ IsSideCaption(nsIFrame* aFrame, const nsStyleDisplay* aStyleDisplay, captionSide == NS_STYLE_CAPTION_SIDE_RIGHT; } -static nsFlexContainerFrame* -GetFlexContainer(nsIFrame* aFrame) -{ - nsIFrame* parent = aFrame->GetParent(); - if (!parent || - parent->GetType() != nsGkAtoms::flexContainerFrame) { - return nullptr; - } - - return static_cast(parent); -} - // Flex items resolve block-axis percentage margin & padding against the flex // container's block-size (which is the containing block block-size). // For everything else: the CSS21 spec requires that margin and padding @@ -2306,8 +2293,9 @@ nsHTMLReflowState::InitConstraints(nsPresContext* aPresContext, ComputeSizeFlags(computeSizeFlags | ComputeSizeFlags::eShrinkWrap); } - const nsFlexContainerFrame* flexContainerFrame = GetFlexContainer(frame); - if (flexContainerFrame) { + nsIFrame* parent = frame->GetParent(); + nsIAtom* parentFrameType = parent ? parent->GetType() : nullptr; + if (parentFrameType == nsGkAtoms::flexContainerFrame) { computeSizeFlags = ComputeSizeFlags(computeSizeFlags | ComputeSizeFlags::eShrinkWrap); @@ -2343,11 +2331,13 @@ nsHTMLReflowState::InitConstraints(nsPresContext* aPresContext, NS_ASSERTION(ComputedBSize() == NS_UNCONSTRAINEDSIZE || ComputedBSize() >= 0, "Bogus block-size"); - // Exclude inline tables and flex items from the block margin calculations + // Exclude inline tables, side captions, flex and grid items from block + // margin calculations. if (isBlock && !IsSideCaption(frame, mStyleDisplay, cbwm) && mStyleDisplay->mDisplay != NS_STYLE_DISPLAY_INLINE_TABLE && - !flexContainerFrame) { + parentFrameType != nsGkAtoms::flexContainerFrame && + parentFrameType != nsGkAtoms::gridContainerFrame) { CalculateBlockSideMargins(aFrameType); } }