1 /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
2 /* vim: set ts=8 sts=2 et sw=2 tw=80: */
3 /* This Source Code Form is subject to the terms of the Mozilla Public
4  * License, v. 2.0. If a copy of the MPL was not distributed with this
5  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
6 
7 /*
8  * code for managing absolutely positioned children of a rendering
9  * object that is a containing block for them
10  */
11 
12 #include "nsAbsoluteContainingBlock.h"
13 
14 #include "nsAtomicContainerFrame.h"
15 #include "nsContainerFrame.h"
16 #include "nsGkAtoms.h"
17 #include "mozilla/CSSAlignUtils.h"
18 #include "mozilla/PresShell.h"
19 #include "mozilla/ReflowInput.h"
20 #include "nsPlaceholderFrame.h"
21 #include "nsPresContext.h"
22 #include "nsCSSFrameConstructor.h"
23 #include "nsGridContainerFrame.h"
24 
25 #include "mozilla/Sprintf.h"
26 
27 #ifdef DEBUG
28 #  include "nsBlockFrame.h"
29 
PrettyUC(nscoord aSize,char * aBuf,int aBufSize)30 static void PrettyUC(nscoord aSize, char* aBuf, int aBufSize) {
31   if (NS_UNCONSTRAINEDSIZE == aSize) {
32     strcpy(aBuf, "UC");
33   } else {
34     if ((int32_t)0xdeadbeef == aSize) {
35       strcpy(aBuf, "deadbeef");
36     } else {
37       snprintf(aBuf, aBufSize, "%d", aSize);
38     }
39   }
40 }
41 #endif
42 
43 using namespace mozilla;
44 
45 typedef mozilla::CSSAlignUtils::AlignJustifyFlags AlignJustifyFlags;
46 
SetInitialChildList(nsIFrame * aDelegatingFrame,ChildListID aListID,nsFrameList & aChildList)47 void nsAbsoluteContainingBlock::SetInitialChildList(nsIFrame* aDelegatingFrame,
48                                                     ChildListID aListID,
49                                                     nsFrameList& aChildList) {
50   MOZ_ASSERT(mChildListID == aListID, "unexpected child list name");
51 #ifdef DEBUG
52   nsIFrame::VerifyDirtyBitSet(aChildList);
53   for (nsIFrame* f : aChildList) {
54     MOZ_ASSERT(f->GetParent() == aDelegatingFrame, "Unexpected parent");
55   }
56 #endif
57   mAbsoluteFrames.SetFrames(aChildList);
58 }
59 
AppendFrames(nsIFrame * aDelegatingFrame,ChildListID aListID,nsFrameList & aFrameList)60 void nsAbsoluteContainingBlock::AppendFrames(nsIFrame* aDelegatingFrame,
61                                              ChildListID aListID,
62                                              nsFrameList& aFrameList) {
63   NS_ASSERTION(mChildListID == aListID, "unexpected child list");
64 
65   // Append the frames to our list of absolutely positioned frames
66 #ifdef DEBUG
67   nsIFrame::VerifyDirtyBitSet(aFrameList);
68 #endif
69   mAbsoluteFrames.AppendFrames(nullptr, aFrameList);
70 
71   // no damage to intrinsic widths, since absolutely positioned frames can't
72   // change them
73   aDelegatingFrame->PresShell()->FrameNeedsReflow(
74       aDelegatingFrame, IntrinsicDirty::Resize, NS_FRAME_HAS_DIRTY_CHILDREN);
75 }
76 
InsertFrames(nsIFrame * aDelegatingFrame,ChildListID aListID,nsIFrame * aPrevFrame,nsFrameList & aFrameList)77 void nsAbsoluteContainingBlock::InsertFrames(nsIFrame* aDelegatingFrame,
78                                              ChildListID aListID,
79                                              nsIFrame* aPrevFrame,
80                                              nsFrameList& aFrameList) {
81   NS_ASSERTION(mChildListID == aListID, "unexpected child list");
82   NS_ASSERTION(!aPrevFrame || aPrevFrame->GetParent() == aDelegatingFrame,
83                "inserting after sibling frame with different parent");
84 
85 #ifdef DEBUG
86   nsIFrame::VerifyDirtyBitSet(aFrameList);
87 #endif
88   mAbsoluteFrames.InsertFrames(nullptr, aPrevFrame, aFrameList);
89 
90   // no damage to intrinsic widths, since absolutely positioned frames can't
91   // change them
92   aDelegatingFrame->PresShell()->FrameNeedsReflow(
93       aDelegatingFrame, IntrinsicDirty::Resize, NS_FRAME_HAS_DIRTY_CHILDREN);
94 }
95 
RemoveFrame(nsIFrame * aDelegatingFrame,ChildListID aListID,nsIFrame * aOldFrame)96 void nsAbsoluteContainingBlock::RemoveFrame(nsIFrame* aDelegatingFrame,
97                                             ChildListID aListID,
98                                             nsIFrame* aOldFrame) {
99   NS_ASSERTION(mChildListID == aListID, "unexpected child list");
100   nsIFrame* nif = aOldFrame->GetNextInFlow();
101   if (nif) {
102     nif->GetParent()->DeleteNextInFlowChild(nif, false);
103   }
104 
105   mAbsoluteFrames.DestroyFrame(aOldFrame);
106 }
107 
MaybeMarkAncestorsAsHavingDescendantDependentOnItsStaticPos(nsIFrame * aFrame,nsIFrame * aContainingBlockFrame)108 static void MaybeMarkAncestorsAsHavingDescendantDependentOnItsStaticPos(
109     nsIFrame* aFrame, nsIFrame* aContainingBlockFrame) {
110   MOZ_ASSERT(aFrame->HasAnyStateBits(NS_FRAME_OUT_OF_FLOW));
111   if (!aFrame->StylePosition()->NeedsHypotheticalPositionIfAbsPos()) {
112     return;
113   }
114   // We should have set the bit when reflowing the previous continuations
115   // already.
116   if (aFrame->GetPrevContinuation()) {
117     return;
118   }
119 
120   auto* placeholder = aFrame->GetPlaceholderFrame();
121   MOZ_ASSERT(placeholder);
122 
123   // Only fixed-pos frames can escape their containing block.
124   if (!placeholder->HasAnyStateBits(PLACEHOLDER_FOR_FIXEDPOS)) {
125     return;
126   }
127 
128   for (nsIFrame* ancestor = placeholder->GetParent(); ancestor;
129        ancestor = ancestor->GetParent()) {
130     // Walk towards the ancestor's first continuation. That's the only one that
131     // really matters, since it's the only one restyling will look at. We also
132     // flag the following continuations just so it's caught on the first
133     // early-return ones just to avoid walking them over and over.
134     do {
135       if (ancestor->DescendantMayDependOnItsStaticPosition()) {
136         return;
137       }
138       // Moving the containing block or anything above it would move our static
139       // position as well, so no need to flag it or any of its ancestors.
140       if (aFrame == aContainingBlockFrame) {
141         return;
142       }
143       ancestor->SetDescendantMayDependOnItsStaticPosition(true);
144       nsIFrame* prev = ancestor->GetPrevContinuation();
145       if (!prev) {
146         break;
147       }
148       ancestor = prev;
149     } while (true);
150   }
151 }
152 
Reflow(nsContainerFrame * aDelegatingFrame,nsPresContext * aPresContext,const ReflowInput & aReflowInput,nsReflowStatus & aReflowStatus,const nsRect & aContainingBlock,AbsPosReflowFlags aFlags,OverflowAreas * aOverflowAreas)153 void nsAbsoluteContainingBlock::Reflow(nsContainerFrame* aDelegatingFrame,
154                                        nsPresContext* aPresContext,
155                                        const ReflowInput& aReflowInput,
156                                        nsReflowStatus& aReflowStatus,
157                                        const nsRect& aContainingBlock,
158                                        AbsPosReflowFlags aFlags,
159                                        OverflowAreas* aOverflowAreas) {
160   // PageContentFrame replicates fixed pos children so we really don't want
161   // them contributing to overflow areas because that means we'll create new
162   // pages ad infinitum if one of them overflows the page.
163   if (aDelegatingFrame->IsPageContentFrame()) {
164     MOZ_ASSERT(mChildListID == nsAtomicContainerFrame::kFixedList);
165     aOverflowAreas = nullptr;
166   }
167 
168   nsReflowStatus reflowStatus;
169   const bool reflowAll = aReflowInput.ShouldReflowAllKids();
170   const bool isGrid = !!(aFlags & AbsPosReflowFlags::IsGridContainerCB);
171   nsIFrame* kidFrame;
172   nsOverflowContinuationTracker tracker(aDelegatingFrame, true);
173   for (kidFrame = mAbsoluteFrames.FirstChild(); kidFrame;
174        kidFrame = kidFrame->GetNextSibling()) {
175     bool kidNeedsReflow =
176         reflowAll || kidFrame->IsSubtreeDirty() ||
177         FrameDependsOnContainer(
178             kidFrame, !!(aFlags & AbsPosReflowFlags::CBWidthChanged),
179             !!(aFlags & AbsPosReflowFlags::CBHeightChanged));
180 
181     if (kidFrame->IsSubtreeDirty()) {
182       MaybeMarkAncestorsAsHavingDescendantDependentOnItsStaticPos(
183           kidFrame, aDelegatingFrame);
184     }
185 
186     nscoord availBSize = aReflowInput.AvailableBSize();
187     const nsRect& cb =
188         isGrid ? nsGridContainerFrame::GridItemCB(kidFrame) : aContainingBlock;
189     WritingMode containerWM = aReflowInput.GetWritingMode();
190     if (!kidNeedsReflow && availBSize != NS_UNCONSTRAINEDSIZE) {
191       // If we need to redo pagination on the kid, we need to reflow it.
192       // This can happen either if the available height shrunk and the
193       // kid (or its overflow that creates overflow containers) is now
194       // too large to fit in the available height, or if the available
195       // height has increased and the kid has a next-in-flow that we
196       // might need to pull from.
197       WritingMode kidWM = kidFrame->GetWritingMode();
198       if (containerWM.GetBlockDir() != kidWM.GetBlockDir()) {
199         // Not sure what the right test would be here.
200         kidNeedsReflow = true;
201       } else {
202         nscoord kidBEnd = kidFrame->GetLogicalRect(cb.Size()).BEnd(kidWM);
203         nscoord kidOverflowBEnd =
204             LogicalRect(containerWM,
205                         // Use ...RelativeToSelf to ignore transforms
206                         kidFrame->ScrollableOverflowRectRelativeToSelf() +
207                             kidFrame->GetPosition(),
208                         aContainingBlock.Size())
209                 .BEnd(containerWM);
210         NS_ASSERTION(kidOverflowBEnd >= kidBEnd,
211                      "overflow area should be at least as large as frame rect");
212         if (kidOverflowBEnd > availBSize ||
213             (kidBEnd < availBSize && kidFrame->GetNextInFlow())) {
214           kidNeedsReflow = true;
215         }
216       }
217     }
218     if (kidNeedsReflow && !aPresContext->HasPendingInterrupt()) {
219       // Reflow the frame
220       nsReflowStatus kidStatus;
221       ReflowAbsoluteFrame(aDelegatingFrame, aPresContext, aReflowInput, cb,
222                           aFlags, kidFrame, kidStatus, aOverflowAreas);
223       MOZ_ASSERT(!kidStatus.IsInlineBreakBefore(),
224                  "ShouldAvoidBreakInside should prevent this from happening");
225       nsIFrame* nextFrame = kidFrame->GetNextInFlow();
226       if (!kidStatus.IsFullyComplete() &&
227           aDelegatingFrame->IsFrameOfType(
228               nsIFrame::eCanContainOverflowContainers)) {
229         // Need a continuation
230         if (!nextFrame) {
231           nextFrame = aPresContext->PresShell()
232                           ->FrameConstructor()
233                           ->CreateContinuingFrame(kidFrame, aDelegatingFrame);
234         }
235         // Add it as an overflow container.
236         // XXXfr This is a hack to fix some of our printing dataloss.
237         // See bug 154892. Not sure how to do it "right" yet; probably want
238         // to keep continuations within an nsAbsoluteContainingBlock eventually.
239         tracker.Insert(nextFrame, kidStatus);
240         reflowStatus.MergeCompletionStatusFrom(kidStatus);
241       } else {
242         // Delete any continuations
243         if (nextFrame) {
244           nsOverflowContinuationTracker::AutoFinish fini(&tracker, kidFrame);
245           nextFrame->GetParent()->DeleteNextInFlowChild(nextFrame, true);
246         }
247       }
248     } else {
249       tracker.Skip(kidFrame, reflowStatus);
250       if (aOverflowAreas) {
251         aDelegatingFrame->ConsiderChildOverflow(*aOverflowAreas, kidFrame);
252       }
253     }
254 
255     // Make a CheckForInterrupt call, here, not just HasPendingInterrupt.  That
256     // will make sure that we end up reflowing aDelegatingFrame in cases when
257     // one of our kids interrupted.  Otherwise we'd set the dirty or
258     // dirty-children bit on the kid in the condition below, and then when
259     // reflow completes and we go to mark dirty bits on all ancestors of that
260     // kid we'll immediately bail out, because the kid already has a dirty bit.
261     // In particular, we won't set any dirty bits on aDelegatingFrame, so when
262     // the following reflow happens we won't reflow the kid in question.  This
263     // might be slightly suboptimal in cases where |kidFrame| itself did not
264     // interrupt, since we'll trigger a reflow of it too when it's not strictly
265     // needed.  But the logic to not do that is enough more complicated, and
266     // the case enough of an edge case, that this is probably better.
267     if (kidNeedsReflow && aPresContext->CheckForInterrupt(aDelegatingFrame)) {
268       if (aDelegatingFrame->HasAnyStateBits(NS_FRAME_IS_DIRTY)) {
269         kidFrame->MarkSubtreeDirty();
270       } else {
271         kidFrame->AddStateBits(NS_FRAME_HAS_DIRTY_CHILDREN);
272       }
273     }
274   }
275 
276   // Abspos frames can't cause their parent to be incomplete,
277   // only overflow incomplete.
278   if (reflowStatus.IsIncomplete()) reflowStatus.SetOverflowIncomplete();
279 
280   aReflowStatus.MergeCompletionStatusFrom(reflowStatus);
281 }
282 
IsFixedPaddingSize(const LengthPercentage & aCoord)283 static inline bool IsFixedPaddingSize(const LengthPercentage& aCoord) {
284   return aCoord.ConvertsToLength();
285 }
IsFixedMarginSize(const LengthPercentageOrAuto & aCoord)286 static inline bool IsFixedMarginSize(const LengthPercentageOrAuto& aCoord) {
287   return aCoord.ConvertsToLength();
288 }
IsFixedOffset(const LengthPercentageOrAuto & aCoord)289 static inline bool IsFixedOffset(const LengthPercentageOrAuto& aCoord) {
290   return aCoord.ConvertsToLength();
291 }
292 
FrameDependsOnContainer(nsIFrame * f,bool aCBWidthChanged,bool aCBHeightChanged)293 bool nsAbsoluteContainingBlock::FrameDependsOnContainer(nsIFrame* f,
294                                                         bool aCBWidthChanged,
295                                                         bool aCBHeightChanged) {
296   const nsStylePosition* pos = f->StylePosition();
297   // See if f's position might have changed because it depends on a
298   // placeholder's position.
299   if (pos->NeedsHypotheticalPositionIfAbsPos()) {
300     return true;
301   }
302   if (!aCBWidthChanged && !aCBHeightChanged) {
303     // skip getting style data
304     return false;
305   }
306   const nsStylePadding* padding = f->StylePadding();
307   const nsStyleMargin* margin = f->StyleMargin();
308   WritingMode wm = f->GetWritingMode();
309   if (wm.IsVertical() ? aCBHeightChanged : aCBWidthChanged) {
310     // See if f's inline-size might have changed.
311     // If margin-inline-start/end, padding-inline-start/end,
312     // inline-size, min/max-inline-size are all lengths, 'none', or enumerated,
313     // then our frame isize does not depend on the parent isize.
314     // Note that borders never depend on the parent isize.
315     // XXX All of the enumerated values except -moz-available are ok too.
316     if (pos->ISizeDependsOnContainer(wm) ||
317         pos->MinISizeDependsOnContainer(wm) ||
318         pos->MaxISizeDependsOnContainer(wm) ||
319         !IsFixedPaddingSize(padding->mPadding.GetIStart(wm)) ||
320         !IsFixedPaddingSize(padding->mPadding.GetIEnd(wm))) {
321       return true;
322     }
323 
324     // See if f's position might have changed. If we're RTL then the
325     // rules are slightly different. We'll assume percentage or auto
326     // margins will always induce a dependency on the size
327     if (!IsFixedMarginSize(margin->mMargin.GetIStart(wm)) ||
328         !IsFixedMarginSize(margin->mMargin.GetIEnd(wm))) {
329       return true;
330     }
331   }
332   if (wm.IsVertical() ? aCBWidthChanged : aCBHeightChanged) {
333     // See if f's block-size might have changed.
334     // If margin-block-start/end, padding-block-start/end,
335     // min-block-size, and max-block-size are all lengths or 'none',
336     // and bsize is a length or bsize and bend are auto and bstart is not auto,
337     // then our frame bsize does not depend on the parent bsize.
338     // Note that borders never depend on the parent bsize.
339     //
340     // FIXME(emilio): Should the BSize(wm).IsAuto() check also for the extremum
341     // lengths?
342     if ((pos->BSizeDependsOnContainer(wm) &&
343          !(pos->BSize(wm).IsAuto() && pos->mOffset.GetBEnd(wm).IsAuto() &&
344            !pos->mOffset.GetBStart(wm).IsAuto())) ||
345         pos->MinBSizeDependsOnContainer(wm) ||
346         pos->MaxBSizeDependsOnContainer(wm) ||
347         !IsFixedPaddingSize(padding->mPadding.GetBStart(wm)) ||
348         !IsFixedPaddingSize(padding->mPadding.GetBEnd(wm))) {
349       return true;
350     }
351 
352     // See if f's position might have changed.
353     if (!IsFixedMarginSize(margin->mMargin.GetBStart(wm)) ||
354         !IsFixedMarginSize(margin->mMargin.GetBEnd(wm))) {
355       return true;
356     }
357   }
358 
359   // Since we store coordinates relative to top and left, the position
360   // of a frame depends on that of its container if it is fixed relative
361   // to the right or bottom, or if it is positioned using percentages
362   // relative to the left or top.  Because of the dependency on the
363   // sides (left and top) that we use to store coordinates, these tests
364   // are easier to do using physical coordinates rather than logical.
365   if (aCBWidthChanged) {
366     if (!IsFixedOffset(pos->mOffset.Get(eSideLeft))) {
367       return true;
368     }
369     // Note that even if 'left' is a length, our position can still
370     // depend on the containing block width, because if our direction or
371     // writing-mode moves from right to left (in either block or inline
372     // progression) and 'right' is not 'auto', we will discard 'left'
373     // and be positioned relative to the containing block right edge.
374     // 'left' length and 'right' auto is the only combination we can be
375     // sure of.
376     if ((wm.GetInlineDir() == WritingMode::eInlineRTL ||
377          wm.GetBlockDir() == WritingMode::eBlockRL) &&
378         !pos->mOffset.Get(eSideRight).IsAuto()) {
379       return true;
380     }
381   }
382   if (aCBHeightChanged) {
383     if (!IsFixedOffset(pos->mOffset.Get(eSideTop))) {
384       return true;
385     }
386     // See comment above for width changes.
387     if (wm.GetInlineDir() == WritingMode::eInlineBTT &&
388         !pos->mOffset.Get(eSideBottom).IsAuto()) {
389       return true;
390     }
391   }
392 
393   return false;
394 }
395 
DestroyFrames(nsIFrame * aDelegatingFrame,nsIFrame * aDestructRoot,PostDestroyData & aPostDestroyData)396 void nsAbsoluteContainingBlock::DestroyFrames(
397     nsIFrame* aDelegatingFrame, nsIFrame* aDestructRoot,
398     PostDestroyData& aPostDestroyData) {
399   mAbsoluteFrames.DestroyFramesFrom(aDestructRoot, aPostDestroyData);
400 }
401 
MarkSizeDependentFramesDirty()402 void nsAbsoluteContainingBlock::MarkSizeDependentFramesDirty() {
403   DoMarkFramesDirty(false);
404 }
405 
MarkAllFramesDirty()406 void nsAbsoluteContainingBlock::MarkAllFramesDirty() {
407   DoMarkFramesDirty(true);
408 }
409 
DoMarkFramesDirty(bool aMarkAllDirty)410 void nsAbsoluteContainingBlock::DoMarkFramesDirty(bool aMarkAllDirty) {
411   for (nsIFrame* kidFrame : mAbsoluteFrames) {
412     if (aMarkAllDirty) {
413       kidFrame->MarkSubtreeDirty();
414     } else if (FrameDependsOnContainer(kidFrame, true, true)) {
415       // Add the weakest flags that will make sure we reflow this frame later
416       kidFrame->AddStateBits(NS_FRAME_HAS_DIRTY_CHILDREN);
417     }
418   }
419 }
420 
421 // Given an out-of-flow frame, this method returns the parent frame of its
422 // placeholder frame or null if it doesn't have a placeholder for some reason.
GetPlaceholderContainer(nsIFrame * aPositionedFrame)423 static nsContainerFrame* GetPlaceholderContainer(nsIFrame* aPositionedFrame) {
424   nsIFrame* placeholder = aPositionedFrame->GetPlaceholderFrame();
425   return placeholder ? placeholder->GetParent() : nullptr;
426 }
427 
428 /**
429  * This function returns the offset of an abs/fixed-pos child's static
430  * position, with respect to the "start" corner of its alignment container,
431  * according to CSS Box Alignment.  This function only operates in a single
432  * axis at a time -- callers can choose which axis via the |aAbsPosCBAxis|
433  * parameter.
434  *
435  * @param aKidReflowInput The ReflowInput for the to-be-aligned abspos child.
436  * @param aKidSizeInAbsPosCBWM The child frame's size (after it's been given
437  *                             the opportunity to reflow), in terms of
438  *                             aAbsPosCBWM.
439  * @param aAbsPosCBSize The abspos CB size, in terms of aAbsPosCBWM.
440  * @param aPlaceholderContainer The parent of the child frame's corresponding
441  *                              placeholder frame, cast to a nsContainerFrame.
442  *                              (This will help us choose which alignment enum
443  *                              we should use for the child.)
444  * @param aAbsPosCBWM The child frame's containing block's WritingMode.
445  * @param aAbsPosCBAxis The axis (of the containing block) that we should
446  *                      be doing this computation for.
447  */
OffsetToAlignedStaticPos(const ReflowInput & aKidReflowInput,const LogicalSize & aKidSizeInAbsPosCBWM,const LogicalSize & aAbsPosCBSize,nsContainerFrame * aPlaceholderContainer,WritingMode aAbsPosCBWM,LogicalAxis aAbsPosCBAxis)448 static nscoord OffsetToAlignedStaticPos(const ReflowInput& aKidReflowInput,
449                                         const LogicalSize& aKidSizeInAbsPosCBWM,
450                                         const LogicalSize& aAbsPosCBSize,
451                                         nsContainerFrame* aPlaceholderContainer,
452                                         WritingMode aAbsPosCBWM,
453                                         LogicalAxis aAbsPosCBAxis) {
454   if (!aPlaceholderContainer) {
455     // (The placeholder container should be the thing that kicks this whole
456     // process off, by setting PLACEHOLDER_STATICPOS_NEEDS_CSSALIGN.  So it
457     // should exist... but bail gracefully if it doesn't.)
458     NS_ERROR(
459         "Missing placeholder-container when computing a "
460         "CSS Box Alignment static position");
461     return 0;
462   }
463 
464   // (Most of this function is simply preparing args that we'll pass to
465   // AlignJustifySelf at the end.)
466 
467   // NOTE: Our alignment container is aPlaceholderContainer's content-box
468   // (or an area within it, if aPlaceholderContainer is a grid). So, we'll
469   // perform most of our arithmetic/alignment in aPlaceholderContainer's
470   // WritingMode. For brevity, we use the abbreviation "pc" for "placeholder
471   // container" in variables below.
472   WritingMode pcWM = aPlaceholderContainer->GetWritingMode();
473 
474   // Find what axis aAbsPosCBAxis corresponds to, in placeholder's parent's
475   // writing-mode.
476   LogicalAxis pcAxis =
477       (pcWM.IsOrthogonalTo(aAbsPosCBWM) ? GetOrthogonalAxis(aAbsPosCBAxis)
478                                         : aAbsPosCBAxis);
479 
480   const bool placeholderContainerIsContainingBlock =
481       aPlaceholderContainer == aKidReflowInput.mCBReflowInput->mFrame;
482 
483   LayoutFrameType parentType = aPlaceholderContainer->Type();
484   LogicalSize alignAreaSize(pcWM);
485   if (parentType == LayoutFrameType::FlexContainer) {
486     // We store the frame rect in FinishAndStoreOverflow, which runs _after_
487     // reflowing the absolute frames, so handle the special case of the frame
488     // being the actual containing block here, by getting the size from
489     // aAbsPosCBSize.
490     //
491     // The alignment container is the flex container's content box.
492     if (placeholderContainerIsContainingBlock) {
493       alignAreaSize = aAbsPosCBSize.ConvertTo(pcWM, aAbsPosCBWM);
494       // aAbsPosCBSize is the padding-box, so substract the padding to get the
495       // content box.
496       alignAreaSize -=
497           aPlaceholderContainer->GetLogicalUsedPadding(pcWM).Size(pcWM);
498     } else {
499       alignAreaSize = aPlaceholderContainer->GetLogicalSize(pcWM);
500       LogicalMargin pcBorderPadding =
501           aPlaceholderContainer->GetLogicalUsedBorderAndPadding(pcWM);
502       alignAreaSize -= pcBorderPadding.Size(pcWM);
503     }
504   } else if (parentType == LayoutFrameType::GridContainer) {
505     // This abspos elem's parent is a grid container. Per CSS Grid 10.1 & 10.2:
506     //  - If the grid container *also* generates the abspos containing block (a
507     // grid area) for this abspos child, we use that abspos containing block as
508     // the alignment container, too. (And its size is aAbsPosCBSize.)
509     //  - Otherwise, we use the grid's padding box as the alignment container.
510     // https://drafts.csswg.org/css-grid/#static-position
511     if (placeholderContainerIsContainingBlock) {
512       // The alignment container is the grid area that we're using as the
513       // absolute containing block.
514       alignAreaSize = aAbsPosCBSize.ConvertTo(pcWM, aAbsPosCBWM);
515     } else {
516       // The alignment container is a the grid container's content box (which
517       // we can get by subtracting away its border & padding from frame's size):
518       alignAreaSize = aPlaceholderContainer->GetLogicalSize(pcWM);
519       LogicalMargin pcBorderPadding =
520           aPlaceholderContainer->GetLogicalUsedBorderAndPadding(pcWM);
521       alignAreaSize -= pcBorderPadding.Size(pcWM);
522     }
523   } else {
524     NS_ERROR("Unsupported container for abpsos CSS Box Alignment");
525     return 0;  // (leave the child at the start of its alignment container)
526   }
527 
528   nscoord alignAreaSizeInAxis = (pcAxis == eLogicalAxisInline)
529                                     ? alignAreaSize.ISize(pcWM)
530                                     : alignAreaSize.BSize(pcWM);
531 
532   AlignJustifyFlags flags = AlignJustifyFlags::IgnoreAutoMargins;
533   StyleAlignFlags alignConst =
534       aPlaceholderContainer->CSSAlignmentForAbsPosChild(aKidReflowInput,
535                                                         pcAxis);
536   // If the safe bit in alignConst is set, set the safe flag in |flags|.
537   // Note: If no <overflow-position> is specified, we behave as 'unsafe'.
538   // This doesn't quite match the css-align spec, which has an [at-risk]
539   // "smart default" behavior with some extra nuance about scroll containers.
540   if (alignConst & StyleAlignFlags::SAFE) {
541     flags |= AlignJustifyFlags::OverflowSafe;
542   }
543   alignConst &= ~StyleAlignFlags::FLAG_BITS;
544 
545   // Find out if placeholder-container & the OOF child have the same start-sides
546   // in the placeholder-container's pcAxis.
547   WritingMode kidWM = aKidReflowInput.GetWritingMode();
548   if (pcWM.ParallelAxisStartsOnSameSide(pcAxis, kidWM)) {
549     flags |= AlignJustifyFlags::SameSide;
550   }
551 
552   // (baselineAdjust is unused. CSSAlignmentForAbsPosChild() should've
553   // converted 'baseline'/'last baseline' enums to their fallback values.)
554   const nscoord baselineAdjust = nscoord(0);
555 
556   // AlignJustifySelf operates in the kid's writing mode, so we need to
557   // represent the child's size and the desired axis in that writing mode:
558   LogicalSize kidSizeInOwnWM =
559       aKidSizeInAbsPosCBWM.ConvertTo(kidWM, aAbsPosCBWM);
560   LogicalAxis kidAxis =
561       (kidWM.IsOrthogonalTo(aAbsPosCBWM) ? GetOrthogonalAxis(aAbsPosCBAxis)
562                                          : aAbsPosCBAxis);
563 
564   nscoord offset = CSSAlignUtils::AlignJustifySelf(
565       alignConst, kidAxis, flags, baselineAdjust, alignAreaSizeInAxis,
566       aKidReflowInput, kidSizeInOwnWM);
567 
568   // "offset" is in terms of the CSS Box Alignment container (i.e. it's in
569   // terms of pcWM). But our return value needs to in terms of the containing
570   // block's writing mode, which might have the opposite directionality in the
571   // given axis. In that case, we just need to negate "offset" when returning,
572   // to make it have the right effect as an offset for coordinates in the
573   // containing block's writing mode.
574   if (!pcWM.ParallelAxisStartsOnSameSide(pcAxis, aAbsPosCBWM)) {
575     return -offset;
576   }
577   return offset;
578 }
579 
ResolveSizeDependentOffsets(nsPresContext * aPresContext,ReflowInput & aKidReflowInput,const LogicalSize & aKidSize,const LogicalMargin & aMargin,LogicalMargin * aOffsets,LogicalSize * aLogicalCBSize)580 void nsAbsoluteContainingBlock::ResolveSizeDependentOffsets(
581     nsPresContext* aPresContext, ReflowInput& aKidReflowInput,
582     const LogicalSize& aKidSize, const LogicalMargin& aMargin,
583     LogicalMargin* aOffsets, LogicalSize* aLogicalCBSize) {
584   WritingMode wm = aKidReflowInput.GetWritingMode();
585   WritingMode outerWM = aKidReflowInput.mParentReflowInput->GetWritingMode();
586 
587   // Now that we know the child's size, we resolve any sentinel values in its
588   // IStart/BStart offset coordinates that depend on that size.
589   //  * NS_AUTOOFFSET indicates that the child's position in the given axis
590   // is determined by its end-wards offset property, combined with its size and
591   // available space. e.g.: "top: auto; height: auto; bottom: 50px"
592   //  * m{I,B}OffsetsResolvedAfterSize indicate that the child is using its
593   // static position in that axis, *and* its static position is determined by
594   // the axis-appropriate css-align property (which may require the child's
595   // size, e.g. to center it within the parent).
596   if ((NS_AUTOOFFSET == aOffsets->IStart(outerWM)) ||
597       (NS_AUTOOFFSET == aOffsets->BStart(outerWM)) ||
598       aKidReflowInput.mFlags.mIOffsetsNeedCSSAlign ||
599       aKidReflowInput.mFlags.mBOffsetsNeedCSSAlign) {
600     if (-1 == aLogicalCBSize->ISize(wm)) {
601       // Get the containing block width/height
602       const ReflowInput* parentRI = aKidReflowInput.mParentReflowInput;
603       *aLogicalCBSize = aKidReflowInput.ComputeContainingBlockRectangle(
604           aPresContext, parentRI);
605     }
606 
607     const LogicalSize logicalCBSizeOuterWM =
608         aLogicalCBSize->ConvertTo(outerWM, wm);
609 
610     // placeholderContainer is used in each of the m{I,B}OffsetsNeedCSSAlign
611     // clauses. We declare it at this scope so we can avoid having to look
612     // it up twice (and only look it up if it's needed).
613     nsContainerFrame* placeholderContainer = nullptr;
614 
615     if (NS_AUTOOFFSET == aOffsets->IStart(outerWM)) {
616       NS_ASSERTION(NS_AUTOOFFSET != aOffsets->IEnd(outerWM),
617                    "Can't solve for both start and end");
618       aOffsets->IStart(outerWM) =
619           logicalCBSizeOuterWM.ISize(outerWM) - aOffsets->IEnd(outerWM) -
620           aMargin.IStartEnd(outerWM) - aKidSize.ISize(outerWM);
621     } else if (aKidReflowInput.mFlags.mIOffsetsNeedCSSAlign) {
622       placeholderContainer = GetPlaceholderContainer(aKidReflowInput.mFrame);
623       nscoord offset = OffsetToAlignedStaticPos(
624           aKidReflowInput, aKidSize, logicalCBSizeOuterWM, placeholderContainer,
625           outerWM, eLogicalAxisInline);
626       // Shift IStart from its current position (at start corner of the
627       // alignment container) by the returned offset.  And set IEnd to the
628       // distance between the kid's end edge to containing block's end edge.
629       aOffsets->IStart(outerWM) += offset;
630       aOffsets->IEnd(outerWM) =
631           logicalCBSizeOuterWM.ISize(outerWM) -
632           (aOffsets->IStart(outerWM) + aKidSize.ISize(outerWM));
633     }
634 
635     if (NS_AUTOOFFSET == aOffsets->BStart(outerWM)) {
636       aOffsets->BStart(outerWM) =
637           logicalCBSizeOuterWM.BSize(outerWM) - aOffsets->BEnd(outerWM) -
638           aMargin.BStartEnd(outerWM) - aKidSize.BSize(outerWM);
639     } else if (aKidReflowInput.mFlags.mBOffsetsNeedCSSAlign) {
640       if (!placeholderContainer) {
641         placeholderContainer = GetPlaceholderContainer(aKidReflowInput.mFrame);
642       }
643       nscoord offset = OffsetToAlignedStaticPos(
644           aKidReflowInput, aKidSize, logicalCBSizeOuterWM, placeholderContainer,
645           outerWM, eLogicalAxisBlock);
646       // Shift BStart from its current position (at start corner of the
647       // alignment container) by the returned offset.  And set BEnd to the
648       // distance between the kid's end edge to containing block's end edge.
649       aOffsets->BStart(outerWM) += offset;
650       aOffsets->BEnd(outerWM) =
651           logicalCBSizeOuterWM.BSize(outerWM) -
652           (aOffsets->BStart(outerWM) + aKidSize.BSize(outerWM));
653     }
654     aKidReflowInput.SetComputedLogicalOffsets(outerWM, *aOffsets);
655   }
656 }
657 
ResolveAutoMarginsAfterLayout(ReflowInput & aKidReflowInput,const LogicalSize * aLogicalCBSize,const LogicalSize & aKidSize,LogicalMargin & aMargin,LogicalMargin & aOffsets)658 void nsAbsoluteContainingBlock::ResolveAutoMarginsAfterLayout(
659     ReflowInput& aKidReflowInput, const LogicalSize* aLogicalCBSize,
660     const LogicalSize& aKidSize, LogicalMargin& aMargin,
661     LogicalMargin& aOffsets) {
662   MOZ_ASSERT(aKidReflowInput.mFrame->HasIntrinsicKeywordForBSize());
663 
664   WritingMode wm = aKidReflowInput.GetWritingMode();
665   WritingMode outerWM = aKidReflowInput.mParentReflowInput->GetWritingMode();
666 
667   const LogicalSize kidSizeInWM = aKidSize.ConvertTo(wm, outerWM);
668   LogicalMargin marginInWM = aMargin.ConvertTo(wm, outerWM);
669   LogicalMargin offsetsInWM = aOffsets.ConvertTo(wm, outerWM);
670 
671   // No need to substract border sizes because aKidSize has it included
672   // already
673   nscoord availMarginSpace = aLogicalCBSize->BSize(wm) - kidSizeInWM.BSize(wm) -
674                              offsetsInWM.BStartEnd(wm) -
675                              marginInWM.BStartEnd(wm);
676 
677   if (availMarginSpace < 0) {
678     availMarginSpace = 0;
679   }
680 
681   const auto& styleMargin = aKidReflowInput.mStyleMargin;
682   if (wm.IsOrthogonalTo(outerWM)) {
683     ReflowInput::ComputeAbsPosInlineAutoMargin(
684         availMarginSpace, outerWM,
685         styleMargin->mMargin.GetIStart(outerWM).IsAuto(),
686         styleMargin->mMargin.GetIEnd(outerWM).IsAuto(), aMargin, aOffsets);
687   } else {
688     ReflowInput::ComputeAbsPosBlockAutoMargin(
689         availMarginSpace, outerWM,
690         styleMargin->mMargin.GetBStart(outerWM).IsAuto(),
691         styleMargin->mMargin.GetBEnd(outerWM).IsAuto(), aMargin, aOffsets);
692   }
693 
694   aKidReflowInput.SetComputedLogicalMargin(outerWM, aMargin);
695   aKidReflowInput.SetComputedLogicalOffsets(outerWM, aOffsets);
696 
697   nsMargin* propValue =
698       aKidReflowInput.mFrame->GetProperty(nsIFrame::UsedMarginProperty());
699   // InitOffsets should've created a UsedMarginProperty for us, if any margin is
700   // auto.
701   MOZ_ASSERT_IF(styleMargin->HasInlineAxisAuto(outerWM) ||
702                     styleMargin->HasBlockAxisAuto(outerWM),
703                 propValue);
704   if (propValue) {
705     *propValue = aMargin.GetPhysicalMargin(outerWM);
706   }
707 }
708 
709 // XXX Optimize the case where it's a resize reflow and the absolutely
710 // positioned child has the exact same size and position and skip the
711 // reflow...
712 
713 // When bug 154892 is checked in, make sure that when
714 // mChildListID == kFixedList, the height is unconstrained.
715 // since we don't allow replicated frames to split.
716 
ReflowAbsoluteFrame(nsIFrame * aDelegatingFrame,nsPresContext * aPresContext,const ReflowInput & aReflowInput,const nsRect & aContainingBlock,AbsPosReflowFlags aFlags,nsIFrame * aKidFrame,nsReflowStatus & aStatus,OverflowAreas * aOverflowAreas)717 void nsAbsoluteContainingBlock::ReflowAbsoluteFrame(
718     nsIFrame* aDelegatingFrame, nsPresContext* aPresContext,
719     const ReflowInput& aReflowInput, const nsRect& aContainingBlock,
720     AbsPosReflowFlags aFlags, nsIFrame* aKidFrame, nsReflowStatus& aStatus,
721     OverflowAreas* aOverflowAreas) {
722   MOZ_ASSERT(aStatus.IsEmpty(), "Caller should pass a fresh reflow status!");
723 
724 #ifdef DEBUG
725   if (nsBlockFrame::gNoisyReflow) {
726     nsIFrame::IndentBy(stdout, nsBlockFrame::gNoiseIndent);
727     printf("abs pos ");
728     nsAutoString name;
729     aKidFrame->GetFrameName(name);
730     printf("%s ", NS_LossyConvertUTF16toASCII(name).get());
731 
732     char width[16];
733     char height[16];
734     PrettyUC(aReflowInput.AvailableWidth(), width, 16);
735     PrettyUC(aReflowInput.AvailableHeight(), height, 16);
736     printf(" a=%s,%s ", width, height);
737     PrettyUC(aReflowInput.ComputedWidth(), width, 16);
738     PrettyUC(aReflowInput.ComputedHeight(), height, 16);
739     printf("c=%s,%s \n", width, height);
740   }
741   AutoNoisyIndenter indent(nsBlockFrame::gNoisy);
742 #endif  // DEBUG
743 
744   WritingMode wm = aKidFrame->GetWritingMode();
745   LogicalSize logicalCBSize(wm, aContainingBlock.Size());
746   nscoord availISize = logicalCBSize.ISize(wm);
747   if (availISize == -1) {
748     NS_ASSERTION(
749         aReflowInput.ComputedSize(wm).ISize(wm) != NS_UNCONSTRAINEDSIZE,
750         "Must have a useful inline-size _somewhere_");
751     availISize = aReflowInput.ComputedSizeWithPadding(wm).ISize(wm);
752   }
753 
754   ReflowInput::InitFlags initFlags;
755   if (aFlags & AbsPosReflowFlags::IsGridContainerCB) {
756     // When a grid container generates the abs.pos. CB for a *child* then
757     // the static position is determined via CSS Box Alignment within the
758     // abs.pos. CB (a grid area, i.e. a piece of the grid). In this scenario,
759     // due to the multiple coordinate spaces in play, we use a convenience flag
760     // to simply have the child's ReflowInput give it a static position at its
761     // abs.pos. CB origin, and then we'll align & offset it from there.
762     nsIFrame* placeholder = aKidFrame->GetPlaceholderFrame();
763     if (placeholder && placeholder->GetParent() == aDelegatingFrame) {
764       initFlags += ReflowInput::InitFlag::StaticPosIsCBOrigin;
765     }
766   }
767 
768   bool constrainBSize =
769       (aReflowInput.AvailableBSize() != NS_UNCONSTRAINEDSIZE) &&
770 
771       // Don't split if told not to (e.g. for fixed frames)
772       (aFlags & AbsPosReflowFlags::ConstrainHeight) &&
773 
774       // XXX we don't handle splitting frames for inline absolute containing
775       // blocks yet
776       !aDelegatingFrame->IsInlineFrame() &&
777 
778       // Bug 1588623: Support splitting absolute positioned multicol containers.
779       !aKidFrame->IsColumnSetWrapperFrame() &&
780 
781       // Don't split things below the fold. (Ideally we shouldn't *have*
782       // anything totally below the fold, but we can't position frames
783       // across next-in-flow breaks yet.
784       (aKidFrame->GetLogicalRect(aContainingBlock.Size()).BStart(wm) <=
785        aReflowInput.AvailableBSize());
786 
787   // Get the border values
788   const WritingMode outerWM = aReflowInput.GetWritingMode();
789   const LogicalMargin border = aDelegatingFrame->GetLogicalUsedBorder(outerWM);
790 
791   const nscoord availBSize = constrainBSize
792                                  ? aReflowInput.AvailableBSize() -
793                                        border.ConvertTo(wm, outerWM).BStart(wm)
794                                  : NS_UNCONSTRAINEDSIZE;
795 
796   ReflowInput kidReflowInput(aPresContext, aReflowInput, aKidFrame,
797                              LogicalSize(wm, availISize, availBSize),
798                              Some(logicalCBSize), initFlags);
799 
800   if (kidReflowInput.AvailableBSize() != NS_UNCONSTRAINEDSIZE) {
801     // Shrink available block-size if it's constrained.
802     kidReflowInput.AvailableBSize() -=
803         kidReflowInput.ComputedLogicalMargin(wm).BStart(wm);
804     const nscoord kidOffsetBStart =
805         kidReflowInput.ComputedLogicalOffsets(wm).BStart(wm);
806     if (NS_AUTOOFFSET != kidOffsetBStart) {
807       kidReflowInput.AvailableBSize() -= kidOffsetBStart;
808     }
809   }
810 
811   // Do the reflow
812   ReflowOutput kidDesiredSize(kidReflowInput);
813   aKidFrame->Reflow(aPresContext, kidDesiredSize, kidReflowInput, aStatus);
814 
815   const LogicalSize kidSize = kidDesiredSize.Size(outerWM);
816 
817   LogicalMargin offsets = kidReflowInput.ComputedLogicalOffsets(outerWM);
818   LogicalMargin margin = kidReflowInput.ComputedLogicalMargin(outerWM);
819 
820   // If we're doing CSS Box Alignment in either axis, that will apply the
821   // margin for us in that axis (since the thing that's aligned is the margin
822   // box).  So, we clear out the margin here to avoid applying it twice.
823   if (kidReflowInput.mFlags.mIOffsetsNeedCSSAlign) {
824     margin.IStart(outerWM) = margin.IEnd(outerWM) = 0;
825   }
826   if (kidReflowInput.mFlags.mBOffsetsNeedCSSAlign) {
827     margin.BStart(outerWM) = margin.BEnd(outerWM) = 0;
828   }
829 
830   // If we're solving for start in either inline or block direction,
831   // then compute it now that we know the dimensions.
832   ResolveSizeDependentOffsets(aPresContext, kidReflowInput, kidSize, margin,
833                               &offsets, &logicalCBSize);
834 
835   if (kidReflowInput.mFrame->HasIntrinsicKeywordForBSize()) {
836     ResolveAutoMarginsAfterLayout(kidReflowInput, &logicalCBSize, kidSize,
837                                   margin, offsets);
838   }
839 
840   // Position the child relative to our padding edge
841   LogicalRect rect(
842       outerWM,
843       border.IStart(outerWM) + offsets.IStart(outerWM) + margin.IStart(outerWM),
844       border.BStart(outerWM) + offsets.BStart(outerWM) + margin.BStart(outerWM),
845       kidSize.ISize(outerWM), kidSize.BSize(outerWM));
846   nsRect r = rect.GetPhysicalRect(
847       outerWM, logicalCBSize.GetPhysicalSize(wm) +
848                    border.Size(outerWM).GetPhysicalSize(outerWM));
849 
850   // Offset the frame rect by the given origin of the absolute containing block.
851   r.x += aContainingBlock.x;
852   r.y += aContainingBlock.y;
853 
854   aKidFrame->SetRect(r);
855 
856   nsView* view = aKidFrame->GetView();
857   if (view) {
858     // Size and position the view and set its opacity, visibility, content
859     // transparency, and clip
860     nsContainerFrame::SyncFrameViewAfterReflow(aPresContext, aKidFrame, view,
861                                                kidDesiredSize.InkOverflow());
862   } else {
863     nsContainerFrame::PositionChildViews(aKidFrame);
864   }
865 
866   aKidFrame->DidReflow(aPresContext, &kidReflowInput);
867 
868 #ifdef DEBUG
869   if (nsBlockFrame::gNoisyReflow) {
870     nsIFrame::IndentBy(stdout, nsBlockFrame::gNoiseIndent - 1);
871     printf("abs pos ");
872     nsAutoString name;
873     aKidFrame->GetFrameName(name);
874     printf("%s ", NS_LossyConvertUTF16toASCII(name).get());
875     printf("%p rect=%d,%d,%d,%d\n", static_cast<void*>(aKidFrame), r.x, r.y,
876            r.width, r.height);
877   }
878 #endif
879 
880   if (aOverflowAreas) {
881     aOverflowAreas->UnionWith(kidDesiredSize.mOverflowAreas + r.TopLeft());
882   }
883 }
884