Bug 1269046 part 5: If an abspos child's offset depends on CSS Box Alignment, ask nsContainerFrame for the alignment enum to use, and align with CSSAlignUtils. r=mats
Right now, this method has only one stub impl, in nsContainerFrame; a later
patch will add a more interesting (overriding) impl in nsFlexContainerFrame.
MozReview-Commit-ID: 3U3vTTX4vdm
--- a/layout/generic/nsAbsoluteContainingBlock.cpp
+++ b/layout/generic/nsAbsoluteContainingBlock.cpp
@@ -8,16 +8,17 @@
* object that is a containing block for them
*/
#include "nsAbsoluteContainingBlock.h"
#include "nsContainerFrame.h"
#include "nsGkAtoms.h"
#include "nsIPresShell.h"
+#include "mozilla/CSSAlignUtils.h"
#include "mozilla/ReflowInput.h"
#include "nsPresContext.h"
#include "nsCSSFrameConstructor.h"
#include "nsGridContainerFrame.h"
#include "mozilla/Sprintf.h"
#ifdef DEBUG
@@ -327,57 +328,233 @@ nsAbsoluteContainingBlock::DoMarkFramesD
kidFrame->AddStateBits(NS_FRAME_IS_DIRTY);
} else if (FrameDependsOnContainer(kidFrame, true, true)) {
// Add the weakest flags that will make sure we reflow this frame later
kidFrame->AddStateBits(NS_FRAME_HAS_DIRTY_CHILDREN);
}
}
}
+// Given an out-of-flow frame, this method returns the parent frame of
+// its placeholder frame, if that parent is a nsContainerFrame.
+static nsContainerFrame*
+GetPlaceholderContainer(nsPresContext* aPresContext,
+ nsIFrame* aPositionedFrame)
+{
+ MOZ_ASSERT(aPositionedFrame, "need non-null frame");
+ MOZ_ASSERT(aPositionedFrame->HasAnyStateBits(NS_FRAME_OUT_OF_FLOW),
+ "expecting abspos frame");
+ MOZ_ASSERT(aPresContext && aPresContext == aPositionedFrame->PresContext(),
+ "need non-null pres context which matches our frame");
+
+ nsIFrame* placeholder =
+ aPresContext->PresShell()->GetPlaceholderFrameFor(aPositionedFrame);
+
+ if (!placeholder) {
+ return nullptr;
+ }
+ return do_QueryFrame(placeholder->GetParent());
+}
+
+/**
+ * This function returns the offset of an abs/fixed-pos child's static
+ * position, with respect to the "start" corner of its alignment container,
+ * according to CSS Box Alignment. This function only operates in a single
+ * axis at a time -- callers can choose which axis via the |aAbsPosCBAxis|
+ * parameter.
+ *
+ * @param aKidReflowInput The ReflowInput for the to-be-aligned abspos child.
+ * @param aKidSizeInAbsPosCBWM The child frame's size (after it's been given
+ * the opportunity to reflow), in terms of the
+ * containing block's WritingMode.
+ * @param aPlaceholderContainer The parent of the child frame's corresponding
+ * placeholder frame, cast to a nsContainerFrame.
+ * (This will help us choose which alignment enum
+ * we should use for the child.)
+ * @param aAbsPosCBWM The child frame's containing block's WritingMode.
+ * @param aAbsPosCBAxis The axis (of the containing block) that we should
+ * be doing this computation for.
+ */
+static nscoord
+OffsetToAlignedStaticPos(const ReflowInput& aKidReflowInput,
+ const LogicalSize& aKidSizeInAbsPosCBWM,
+ nsContainerFrame* aPlaceholderContainer,
+ WritingMode aAbsPosCBWM,
+ LogicalAxis aAbsPosCBAxis)
+{
+ if (!aPlaceholderContainer) {
+ // (The placeholder container should be the thing that kicks this whole
+ // process off, by setting PLACEHOLDER_STATICPOS_NEEDS_CSSALIGN. So it
+ // should exist... but bail gracefully if it doesn't.)
+ NS_ERROR("Missing placeholder-container when computing a "
+ "CSS Box Alignment static position");
+ return 0;
+ }
+
+ // (Most of this function is simply preparing args that we'll pass to
+ // AlignJustifySelf at the end.)
+
+ // NOTE: Our alignment container is aPlaceholderContainer's content-box
+ // (or an area within it, if aPlaceholderContainer is a grid). So, we'll
+ // perform most of our arithmetic/alignment in aPlaceholderContainer's
+ // WritingMode. For brevity, we use the abbreviation "pc" for "placeholder
+ // container" in variables below.
+ WritingMode pcWM = aPlaceholderContainer->GetWritingMode();
+
+ // Find what axis aAbsPosCBAxis corresponds to, in placeholder's parent's
+ // writing-mode.
+ LogicalAxis pcAxis = (pcWM.IsOrthogonalTo(aAbsPosCBWM)
+ ? GetOrthogonalAxis(aAbsPosCBAxis)
+ : aAbsPosCBAxis);
+
+ nsIAtom* parentType = aPlaceholderContainer->GetType();
+ LogicalSize alignAreaSize(pcWM);
+ if (parentType == nsGkAtoms::flexContainerFrame) {
+ alignAreaSize = aPlaceholderContainer->GetLogicalSize(pcWM);
+ LogicalMargin pcBorderPadding =
+ aPlaceholderContainer->GetLogicalUsedBorderAndPadding(pcWM);
+ alignAreaSize -= pcBorderPadding.Size(pcWM);
+ } else {
+ NS_ERROR("Unsupported container for abpsos CSS Box Alignment");
+ return 0; // (leave the child at the start of its alignment container)
+ }
+
+ nscoord alignAreaSizeInAxis = (pcAxis == eLogicalAxisInline)
+ ? alignAreaSize.ISize(pcWM)
+ : alignAreaSize.BSize(pcWM);
+ // XXXdholbert: Handle <overflow-position> in bug 1311892. For now, behave
+ // as if "unsafe" was the specified value (which is basically equivalent to
+ // the default behavior, when no value is specified -- though the default
+ // behavior also has some [at-risk] extra nuance about scroll containers...)
+ const bool overflowSafe = false;
+
+ uint16_t alignConst =
+ aPlaceholderContainer->CSSAlignmentForAbsPosChild(aKidReflowInput, pcAxis);
+ // XXX strip off <overflow-position> bits until we implement it (bug 1311892)
+ alignConst &= ~NS_STYLE_ALIGN_FLAG_BITS;
+
+ // Find out if placeholder-container & the OOF child have the same start-sides
+ // in the placeholder-container's pcAxis.
+ WritingMode kidWM = aKidReflowInput.GetWritingMode();
+ bool sameSidePCAndKid = pcWM.ParallelAxisStartsOnSameSide(pcAxis, kidWM);
+
+ // (baselineAdjust is unused. CSSAlignmentForAbsPosChild() should've
+ // converted 'baseline'/'last-baseline' enums to their fallback values.)
+ const nscoord baselineAdjust = nscoord(0);
+
+ // AlignJustifySelf operates in the kid's writing mode, so we need to
+ // represent the child's size and the desired axis in that writing mode:
+ LogicalSize kidSizeInOwnWM = aKidSizeInAbsPosCBWM.ConvertTo(kidWM,
+ aAbsPosCBWM);
+ LogicalAxis kidAxis = (kidWM.IsOrthogonalTo(aAbsPosCBWM)
+ ? GetOrthogonalAxis(aAbsPosCBAxis)
+ : aAbsPosCBAxis);
+
+ nscoord offset =
+ CSSAlignUtils::AlignJustifySelf(alignConst, overflowSafe,
+ kidAxis, sameSidePCAndKid, baselineAdjust,
+ alignAreaSizeInAxis, aKidReflowInput,
+ kidSizeInOwnWM);
+
+ // "offset" is in terms of the CSS Box Alignment container (i.e. it's in
+ // terms of pcWM). But our return value needs to in terms of the containing
+ // block's writing mode, which might have the opposite directionality in the
+ // given axis. In that case, we just need to negate "offset" when returning,
+ // to make it have the right effect as an offset for coordinates in the
+ // containing block's writing mode.
+ if (!pcWM.ParallelAxisStartsOnSameSide(pcAxis, aAbsPosCBWM)) {
+ return -offset;
+ }
+ return offset;
+}
+
void
nsAbsoluteContainingBlock::ResolveSizeDependentOffsets(
nsPresContext* aPresContext,
ReflowInput& aKidReflowInput,
const LogicalSize& aKidSize,
const LogicalMargin& aMargin,
LogicalMargin* aOffsets,
LogicalSize* aLogicalCBSize)
{
WritingMode wm = aKidReflowInput.GetWritingMode();
WritingMode outerWM = aKidReflowInput.mParentReflowInput->GetWritingMode();
- bool didResolveOffsets = false;
+ // Now that we know the child's size, we resolve any sentinel values in its
+ // IStart/BStart offset coordinates that depend on that size.
+ // * NS_AUTOOFFSET indicates that the child's position in the given axis
+ // is determined by its end-wards offset property, combined with its size and
+ // available space. e.g.: "top: auto; height: auto; bottom: 50px"
+ // * m{I,B}OffsetsResolvedAfterSize indicate that the child is using its
+ // static position in that axis, *and* its static position is determined by
+ // the axis-appropriate css-align property (which may require the child's
+ // size, e.g. to center it within the parent).
if ((NS_AUTOOFFSET == aOffsets->IStart(outerWM)) ||
- (NS_AUTOOFFSET == aOffsets->BStart(outerWM))) {
+ (NS_AUTOOFFSET == aOffsets->BStart(outerWM)) ||
+ aKidReflowInput.mFlags.mIOffsetsNeedCSSAlign ||
+ aKidReflowInput.mFlags.mBOffsetsNeedCSSAlign) {
if (-1 == aLogicalCBSize->ISize(wm)) {
// Get the containing block width/height
const ReflowInput* parentRI = aKidReflowInput.mParentReflowInput;
*aLogicalCBSize =
aKidReflowInput.ComputeContainingBlockRectangle(aPresContext,
parentRI);
}
+ const LogicalSize logicalCBSizeOuterWM = aLogicalCBSize->ConvertTo(outerWM,
+ wm);
+
+ // placeholderContainer is used in each of the m{I,B}OffsetsNeedCSSAlign
+ // clauses. We declare it at this scope so we can avoid having to look
+ // it up twice (and only look it up if it's needed).
+ nsContainerFrame* placeholderContainer = nullptr;
+
if (NS_AUTOOFFSET == aOffsets->IStart(outerWM)) {
NS_ASSERTION(NS_AUTOOFFSET != aOffsets->IEnd(outerWM),
"Can't solve for both start and end");
aOffsets->IStart(outerWM) =
- aLogicalCBSize->ConvertTo(outerWM, wm).ISize(outerWM) -
+ logicalCBSizeOuterWM.ISize(outerWM) -
aOffsets->IEnd(outerWM) - aMargin.IStartEnd(outerWM) -
aKidSize.ISize(outerWM);
+ } else if (aKidReflowInput.mFlags.mIOffsetsNeedCSSAlign) {
+ placeholderContainer = GetPlaceholderContainer(aPresContext,
+ aKidReflowInput.mFrame);
+ nscoord offset = OffsetToAlignedStaticPos(aKidReflowInput, aKidSize,
+ placeholderContainer,
+ outerWM, eLogicalAxisInline);
+ // Shift IStart from its current position (at start corner of the
+ // alignment container) by the returned offset. And set IEnd to the
+ // distance between the kid's end edge to containing block's end edge.
+ aOffsets->IStart(outerWM) += offset;
+ aOffsets->IEnd(outerWM) =
+ logicalCBSizeOuterWM.ISize(outerWM) -
+ (aOffsets->IStart(outerWM) + aKidSize.ISize(outerWM));
}
+
if (NS_AUTOOFFSET == aOffsets->BStart(outerWM)) {
aOffsets->BStart(outerWM) =
- aLogicalCBSize->ConvertTo(outerWM, wm).BSize(outerWM) -
+ logicalCBSizeOuterWM.BSize(outerWM) -
aOffsets->BEnd(outerWM) - aMargin.BStartEnd(outerWM) -
aKidSize.BSize(outerWM);
+ } else if (aKidReflowInput.mFlags.mBOffsetsNeedCSSAlign) {
+ if (!placeholderContainer) {
+ placeholderContainer = GetPlaceholderContainer(aPresContext,
+ aKidReflowInput.mFrame);
+ }
+ nscoord offset = OffsetToAlignedStaticPos(aKidReflowInput, aKidSize,
+ placeholderContainer,
+ outerWM, eLogicalAxisBlock);
+ // Shift BStart from its current position (at start corner of the
+ // alignment container) by the returned offset. And set BEnd to the
+ // distance between the kid's end edge to containing block's end edge.
+ aOffsets->BStart(outerWM) += offset;
+ aOffsets->BEnd(outerWM) =
+ logicalCBSizeOuterWM.BSize(outerWM) -
+ (aOffsets->BStart(outerWM) + aKidSize.BSize(outerWM));
}
- didResolveOffsets = true;
- }
-
- if (didResolveOffsets) {
aKidReflowInput.SetComputedLogicalOffsets(aOffsets->ConvertTo(wm, outerWM));
}
}
// XXX Optimize the case where it's a resize reflow and the absolutely
// positioned child has the exact same size and position and skip the
// reflow...
--- a/layout/generic/nsContainerFrame.cpp
+++ b/layout/generic/nsContainerFrame.cpp
@@ -1976,16 +1976,30 @@ nsContainerFrame::RenumberChildFrames(in
// bit not be set yet.
if (renumbered && aDepth != 0) {
AddStateBits(NS_FRAME_HAS_DIRTY_CHILDREN);
}
return renumbered;
}
+uint16_t
+nsContainerFrame::CSSAlignmentForAbsPosChild(const ReflowInput& aChildRI,
+ LogicalAxis aLogicalAxis) const
+{
+ MOZ_ASSERT(aChildRI.mFrame->IsAbsolutelyPositioned(),
+ "This method should only be called for abspos children");
+ NS_ERROR("Child classes that use css box alignment for abspos children "
+ "should provide their own implementation of this method!");
+
+ // In the unexpected/unlikely event that this implementation gets invoked,
+ // just use "start" alignment.
+ return NS_STYLE_ALIGN_START;
+}
+
nsresult
nsContainerFrame::AttributeChanged(int32_t aNameSpaceID,
nsIAtom* aAttribute,
int32_t aModType)
{
nsresult rv = nsSplittableFrame::AttributeChanged(aNameSpaceID,
aAttribute, aModType);
if (NS_FAILED(rv)) {
--- a/layout/generic/nsContainerFrame.h
+++ b/layout/generic/nsContainerFrame.h
@@ -505,16 +505,40 @@ public:
* Renumber the child frames using RenumberFrameAndDescendants.
* See RenumberFrameAndDescendants for description of parameters.
*/
virtual bool RenumberChildFrames(int32_t* aOrdinal,
int32_t aDepth,
int32_t aIncrement,
bool aForCounting);
+ /**
+ * Returns a CSS Box Alignment constant which the caller can use to align
+ * the absolutely-positioned child (whose ReflowInput is aChildRI) within
+ * a CSS Box Alignment area associated with this container.
+ *
+ * The lower 8 bits of the returned value are guaranteed to form a valid
+ * argument for CSSAlignUtils::AlignJustifySelf(). (The upper 8 bits may
+ * encode an <overflow-position>.)
+ *
+ * NOTE: This default nsContainerFrame implementation is a stub, and isn't
+ * meant to be called. Subclasses must provide their own implementations, if
+ * they use CSS Box Alignment to determine the static position of their
+ * absolutely-positioned children. (Though: if subclasses share enough code,
+ * maybe this nsContainerFrame impl should include some shared code.)
+ *
+ * @param aChildRI A ReflowInput for the positioned child frame that's being
+ * aligned.
+ * @param aLogicalAxis The axis (of this container frame) in which the caller
+ * would like to align the child frame.
+ */
+ virtual uint16_t CSSAlignmentForAbsPosChild(
+ const ReflowInput& aChildRI,
+ mozilla::LogicalAxis aLogicalAxis) const;
+
#define NS_DECLARE_FRAME_PROPERTY_FRAMELIST(prop) \
NS_DECLARE_FRAME_PROPERTY_WITH_DTOR_NEVER_CALLED(prop, nsFrameList)
typedef PropertyDescriptor<nsFrameList> FrameListPropertyDescriptor;
NS_DECLARE_FRAME_PROPERTY_FRAMELIST(OverflowProperty)
NS_DECLARE_FRAME_PROPERTY_FRAMELIST(OverflowContainersProperty)
NS_DECLARE_FRAME_PROPERTY_FRAMELIST(ExcessOverflowContainersProperty)