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 draft
authorDaniel Holbert <dholbert@cs.stanford.edu>
Sun, 30 Oct 2016 22:12:54 -0700
changeset 431690 62a4dde39d8e561427edc6986d7511147680caf9
parent 431584 0b1deadb8b64e4c2a5384e904253e453250f8fc9
child 431691 0edc6c3c120594dea82bcad534bd987a1186d35e
push id34081
push userdholbert@mozilla.com
push dateMon, 31 Oct 2016 05:22:31 +0000
reviewersmats
bugs1269046
milestone52.0a1
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
layout/generic/nsAbsoluteContainingBlock.cpp
layout/generic/nsContainerFrame.cpp
layout/generic/nsContainerFrame.h
--- 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)