Bug 1151213 part 1 - [css-grid][css-align] Implement layout for the 'align-self' and 'justify-self' properties on grid items. r=dholbert

This commit is contained in:
Mats Palmgren 2015-11-03 17:52:40 +01:00
Родитель 6f1fa89293
Коммит 8abe331366
2 изменённых файлов: 292 добавлений и 37 удалений

Просмотреть файл

@ -829,6 +829,243 @@ GetDisplayFlagsForGridItem(nsIFrame* aFrame)
return nsIFrame::DISPLAY_CHILD_FORCE_PSEUDO_STACKING_CONTEXT; 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 <overflow-position> = '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<LogicalAxis>
AlignSelf(uint8_t aAlignSelf, const LogicalRect& aCB, const WritingMode aCBWM,
const nsHTMLReflowState& aRS, const LogicalSize& aSize,
LogicalSize* aContentSize, LogicalPoint* aPos)
{
Maybe<LogicalAxis> 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<LogicalAxis>
JustifySelf(uint8_t aJustifySelf, const LogicalRect& aCB, const WritingMode aCBWM,
const nsHTMLReflowState& aRS, const LogicalSize& aSize,
LogicalSize* aContentSize, LogicalPoint* aPos)
{
Maybe<LogicalAxis> 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 // Frame class boilerplate
@ -2367,6 +2604,7 @@ nsGridContainerFrame::ReflowChildren(GridReflowState& aState,
(aContentArea.Size(wm) + (aContentArea.Size(wm) +
aState.mReflowState->ComputedLogicalBorderPadding().Size(wm)).GetPhysicalSize(wm); aState.mReflowState->ComputedLogicalBorderPadding().Size(wm)).GetPhysicalSize(wm);
nsPresContext* pc = PresContext(); nsPresContext* pc = PresContext();
nsStyleContext* containerSC = StyleContext();
for (; !aState.mIter.AtEnd(); aState.mIter.Next()) { for (; !aState.mIter.AtEnd(); aState.mIter.Next()) {
nsIFrame* child = *aState.mIter; nsIFrame* child = *aState.mIter;
const bool isGridItem = child->GetType() != nsGkAtoms::placeholderFrame; const bool isGridItem = child->GetType() != nsGkAtoms::placeholderFrame;
@ -2383,36 +2621,63 @@ nsGridContainerFrame::ReflowChildren(GridReflowState& aState,
} }
WritingMode childWM = child->GetWritingMode(); WritingMode childWM = child->GetWritingMode();
LogicalSize childCBSize = cb.Size(wm).ConvertTo(childWM, wm); LogicalSize childCBSize = cb.Size(wm).ConvertTo(childWM, wm);
nsHTMLReflowState childRS(pc, *aState.mReflowState, child, childCBSize); // XXX temporary workaround to avoid being INCOMPLETE until we have
const LogicalMargin margin = childRS.ComputedLogicalMargin(); // support for fragmentation (bug 1144096)
if (childRS.ComputedBSize() == NS_AUTOHEIGHT && MOZ_LIKELY(isGridItem)) { childCBSize.BSize(childWM) = NS_UNCONSTRAINEDSIZE;
// XXX the start of an align-self:stretch impl. Needs min-/max-bsize
// clamping though, and check the prop value is actually 'stretch'! Maybe<nsHTMLReflowState> childRS; // Maybe<> so we can reuse the space
LogicalMargin bp = childRS.ComputedLogicalBorderPadding(); childRS.emplace(pc, *aState.mReflowState, child, childCBSize);
bp.ApplySkipSides(child->GetLogicalSkipSides());
nscoord bSize = childCBSize.BSize(childWM) - bp.BStartEnd(childWM) -
margin.BStartEnd(childWM);
childRS.SetComputedBSize(std::max(bSize, 0));
}
// We need the width of the child before we can correctly convert // 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 // the writing-mode of its origin, so we reflow at (0, 0) using a dummy
// containerSize, and then pass the correct position to FinishReflowChild. // containerSize, and then pass the correct position to FinishReflowChild.
nsHTMLReflowMetrics childSize(childRS); Maybe<nsHTMLReflowMetrics> childSize; // Maybe<> so we can reuse the space
childSize.emplace(*childRS);
nsReflowStatus childStatus; nsReflowStatus childStatus;
const nsSize dummyContainerSize; const nsSize dummyContainerSize;
ReflowChild(child, pc, childSize, childRS, childWM, LogicalPoint(childWM), ReflowChild(child, pc, *childSize, *childRS, childWM, LogicalPoint(childWM),
dummyContainerSize, 0, childStatus); dummyContainerSize, 0, childStatus);
LogicalPoint childPos = LogicalPoint childPos =
cb.Origin(wm).ConvertTo(childWM, wm, cb.Origin(wm).ConvertTo(childWM, wm,
containerSize - childSize.PhysicalSize() - containerSize - childSize->PhysicalSize());
margin.Size(childWM).GetPhysicalSize(childWM)); // Apply align/justify-self and reflow again if that affects the size.
childPos.I(childWM) += margin.IStart(childWM); if (isGridItem) {
childPos.B(childWM) += margin.BStart(childWM); LogicalSize oldSize = childSize->Size(childWM); // from the ReflowChild()
childRS.ApplyRelativePositioning(&childPos, containerSize); LogicalSize newContentSize(childWM);
FinishReflowChild(child, pc, childSize, &childRS, childWM, childPos, auto align = childRS->mStylePosition->ComputedAlignSelf(
childRS->mStyleDisplay, containerSC);
Maybe<LogicalAxis> alignResize =
AlignSelf(align, cb, wm, *childRS, oldSize, &newContentSize, &childPos);
auto justify = childRS->mStylePosition->ComputedJustifySelf(
childRS->mStyleDisplay, containerSC);
Maybe<LogicalAxis> 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); containerSize, 0);
ConsiderChildOverflow(aDesiredSize.mOverflowAreas, child); ConsiderChildOverflow(aDesiredSize.mOverflowAreas, child);
// XXX deal with 'childStatus' not being COMPLETE // XXX deal with 'childStatus' not being COMPLETE (bug 1144096)
} }
if (IsAbsoluteContainer()) { if (IsAbsoluteContainer()) {

Просмотреть файл

@ -18,7 +18,6 @@
#include "nsFontMetrics.h" #include "nsFontMetrics.h"
#include "nsBlockFrame.h" #include "nsBlockFrame.h"
#include "nsLineBox.h" #include "nsLineBox.h"
#include "nsFlexContainerFrame.h"
#include "nsImageFrame.h" #include "nsImageFrame.h"
#include "nsTableFrame.h" #include "nsTableFrame.h"
#include "nsTableCellFrame.h" #include "nsTableCellFrame.h"
@ -2037,18 +2036,6 @@ IsSideCaption(nsIFrame* aFrame, const nsStyleDisplay* aStyleDisplay,
captionSide == NS_STYLE_CAPTION_SIDE_RIGHT; 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<nsFlexContainerFrame*>(parent);
}
// Flex items resolve block-axis percentage margin & padding against the flex // Flex items resolve block-axis percentage margin & padding against the flex
// container's block-size (which is the containing block block-size). // container's block-size (which is the containing block block-size).
// For everything else: the CSS21 spec requires that margin and padding // For everything else: the CSS21 spec requires that margin and padding
@ -2306,8 +2293,9 @@ nsHTMLReflowState::InitConstraints(nsPresContext* aPresContext,
ComputeSizeFlags(computeSizeFlags | ComputeSizeFlags::eShrinkWrap); ComputeSizeFlags(computeSizeFlags | ComputeSizeFlags::eShrinkWrap);
} }
const nsFlexContainerFrame* flexContainerFrame = GetFlexContainer(frame); nsIFrame* parent = frame->GetParent();
if (flexContainerFrame) { nsIAtom* parentFrameType = parent ? parent->GetType() : nullptr;
if (parentFrameType == nsGkAtoms::flexContainerFrame) {
computeSizeFlags = computeSizeFlags =
ComputeSizeFlags(computeSizeFlags | ComputeSizeFlags::eShrinkWrap); ComputeSizeFlags(computeSizeFlags | ComputeSizeFlags::eShrinkWrap);
@ -2343,11 +2331,13 @@ nsHTMLReflowState::InitConstraints(nsPresContext* aPresContext,
NS_ASSERTION(ComputedBSize() == NS_UNCONSTRAINEDSIZE || NS_ASSERTION(ComputedBSize() == NS_UNCONSTRAINEDSIZE ||
ComputedBSize() >= 0, "Bogus block-size"); 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 && if (isBlock &&
!IsSideCaption(frame, mStyleDisplay, cbwm) && !IsSideCaption(frame, mStyleDisplay, cbwm) &&
mStyleDisplay->mDisplay != NS_STYLE_DISPLAY_INLINE_TABLE && mStyleDisplay->mDisplay != NS_STYLE_DISPLAY_INLINE_TABLE &&
!flexContainerFrame) { parentFrameType != nsGkAtoms::flexContainerFrame &&
parentFrameType != nsGkAtoms::gridContainerFrame) {
CalculateBlockSideMargins(aFrameType); CalculateBlockSideMargins(aFrameType);
} }
} }