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