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/. */
8 * code for managing absolutely positioned children of a rendering
9 * object that is a containing block for them
12 #include "nsAbsoluteContainingBlock.h"
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"
24 #include "mozilla/Sprintf.h"
27 # include "nsBlockFrame.h"
29 static void PrettyUC(nscoord aSize
, char* aBuf
, int aBufSize
) {
30 if (NS_UNCONSTRAINEDSIZE
== aSize
) {
33 if ((int32_t)0xdeadbeef == aSize
) {
34 strcpy(aBuf
, "deadbeef");
36 snprintf(aBuf
, aBufSize
, "%d", aSize
);
42 using namespace mozilla
;
44 typedef mozilla::CSSAlignUtils::AlignJustifyFlags AlignJustifyFlags
;
46 void nsAbsoluteContainingBlock::SetInitialChildList(nsIFrame
* aDelegatingFrame
,
48 nsFrameList
& aChildList
) {
49 MOZ_ASSERT(mChildListID
== aListID
, "unexpected child list name");
51 nsIFrame::VerifyDirtyBitSet(aChildList
);
52 for (nsIFrame
* f
: aChildList
) {
53 MOZ_ASSERT(f
->GetParent() == aDelegatingFrame
, "Unexpected parent");
56 mAbsoluteFrames
.SetFrames(aChildList
);
59 void nsAbsoluteContainingBlock::AppendFrames(nsIFrame
* aDelegatingFrame
,
61 nsFrameList
& aFrameList
) {
62 NS_ASSERTION(mChildListID
== aListID
, "unexpected child list");
64 // Append the frames to our list of absolutely positioned frames
66 nsIFrame::VerifyDirtyBitSet(aFrameList
);
68 mAbsoluteFrames
.AppendFrames(nullptr, aFrameList
);
70 // no damage to intrinsic widths, since absolutely positioned frames can't
72 aDelegatingFrame
->PresShell()->FrameNeedsReflow(
73 aDelegatingFrame
, IntrinsicDirty::Resize
, NS_FRAME_HAS_DIRTY_CHILDREN
);
76 void nsAbsoluteContainingBlock::InsertFrames(nsIFrame
* aDelegatingFrame
,
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");
85 nsIFrame::VerifyDirtyBitSet(aFrameList
);
87 mAbsoluteFrames
.InsertFrames(nullptr, aPrevFrame
, aFrameList
);
89 // no damage to intrinsic widths, since absolutely positioned frames can't
91 aDelegatingFrame
->PresShell()->FrameNeedsReflow(
92 aDelegatingFrame
, IntrinsicDirty::Resize
, NS_FRAME_HAS_DIRTY_CHILDREN
);
95 void nsAbsoluteContainingBlock::RemoveFrame(nsIFrame
* aDelegatingFrame
,
97 nsIFrame
* aOldFrame
) {
98 NS_ASSERTION(mChildListID
== aListID
, "unexpected child list");
99 nsIFrame
* nif
= aOldFrame
->GetNextInFlow();
101 nif
->GetParent()->DeleteNextInFlowChild(nif
, false);
104 mAbsoluteFrames
.DestroyFrame(aOldFrame
);
107 static void MaybeMarkAncestorsAsHavingDescendantDependentOnItsStaticPos(
108 nsIFrame
* aFrame
, nsIFrame
* aContainingBlockFrame
) {
109 MOZ_ASSERT(aFrame
->HasAnyStateBits(NS_FRAME_OUT_OF_FLOW
));
110 if (!aFrame
->StylePosition()->NeedsHypotheticalPositionIfAbsPos()) {
113 // We should have set the bit when reflowing the previous continuations
115 if (aFrame
->GetPrevContinuation()) {
119 auto* placeholder
= aFrame
->GetPlaceholderFrame();
120 MOZ_ASSERT(placeholder
);
122 // Only fixed-pos frames can escape their containing block.
123 if (!placeholder
->HasAnyStateBits(PLACEHOLDER_FOR_FIXEDPOS
)) {
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.
134 if (ancestor
->DescendantMayDependOnItsStaticPosition()) {
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
) {
142 ancestor
->SetDescendantMayDependOnItsStaticPosition(true);
143 nsIFrame
* prev
= ancestor
->GetPrevContinuation();
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;
167 nsReflowStatus reflowStatus
;
168 const bool reflowAll
= aReflowInput
.ShouldReflowAllKids();
169 const bool isGrid
= !!(aFlags
& AbsPosReflowFlags::IsGridContainerCB
);
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
));
180 if (kidFrame
->IsSubtreeDirty()) {
181 MaybeMarkAncestorsAsHavingDescendantDependentOnItsStaticPos(
182 kidFrame
, aDelegatingFrame
);
185 nscoord availBSize
= aReflowInput
.AvailableBSize();
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;
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())
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;
217 if (kidNeedsReflow
&& !aPresContext
->HasPendingInterrupt()) {
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
230 nextFrame
= aPresContext
->PresShell()
232 ->CreateContinuingFrame(kidFrame
, aDelegatingFrame
);
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
);
241 // Delete any continuations
243 nsOverflowContinuationTracker::AutoFinish
fini(&tracker
, kidFrame
);
244 nextFrame
->GetParent()->DeleteNextInFlowChild(nextFrame
, true);
248 tracker
.Skip(kidFrame
, reflowStatus
);
249 if (aOverflowAreas
) {
250 aDelegatingFrame
->ConsiderChildOverflow(*aOverflowAreas
, kidFrame
);
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();
270 kidFrame
->AddStateBits(NS_FRAME_HAS_DIRTY_CHILDREN
);
275 // Abspos frames can't cause their parent to be incomplete,
276 // only overflow incomplete.
277 if (reflowStatus
.IsIncomplete()) reflowStatus
.SetOverflowIncomplete();
279 aReflowStatus
.MergeCompletionStatusFrom(reflowStatus
);
282 static inline bool IsFixedPaddingSize(const LengthPercentage
& aCoord
) {
283 return aCoord
.ConvertsToLength();
285 static inline bool IsFixedMarginSize(const LengthPercentageOrAuto
& aCoord
) {
286 return aCoord
.ConvertsToLength();
288 static inline bool IsFixedOffset(const LengthPercentageOrAuto
& aCoord
) {
289 return aCoord
.ConvertsToLength();
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()) {
301 if (!aCBWidthChanged
&& !aCBHeightChanged
) {
302 // skip getting style data
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
))) {
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
))) {
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.
339 // FIXME(emilio): Should the BSize(wm).IsAuto() check also for the extremum
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
))) {
351 // See if f's position might have changed.
352 if (!IsFixedMarginSize(margin
->mMargin
.GetBStart(wm
)) ||
353 !IsFixedMarginSize(margin
->mMargin
.GetBEnd(wm
))) {
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
))) {
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
375 if ((wm
.GetInlineDir() == WritingMode::eInlineRTL
||
376 wm
.GetBlockDir() == WritingMode::eBlockRL
) &&
377 !pos
->mOffset
.Get(eSideRight
).IsAuto()) {
381 if (aCBHeightChanged
) {
382 if (!IsFixedOffset(pos
->mOffset
.Get(eSideTop
))) {
385 // See comment above for width changes.
386 if (wm
.GetInlineDir() == WritingMode::eInlineBTT
&&
387 !pos
->mOffset
.Get(eSideBottom
).IsAuto()) {
395 void nsAbsoluteContainingBlock::DestroyFrames(
396 nsIFrame
* aDelegatingFrame
, nsIFrame
* aDestructRoot
,
397 PostDestroyData
& aPostDestroyData
) {
398 mAbsoluteFrames
.DestroyFramesFrom(aDestructRoot
, aPostDestroyData
);
401 void nsAbsoluteContainingBlock::MarkSizeDependentFramesDirty() {
402 DoMarkFramesDirty(false);
405 void nsAbsoluteContainingBlock::MarkAllFramesDirty() {
406 DoMarkFramesDirty(true);
409 void nsAbsoluteContainingBlock::DoMarkFramesDirty(bool aMarkAllDirty
) {
410 for (nsIFrame
* kidFrame
: mAbsoluteFrames
) {
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
);
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.
422 static nsContainerFrame
* GetPlaceholderContainer(nsIFrame
* aPositionedFrame
) {
423 nsIFrame
* placeholder
= aPositionedFrame
->GetPlaceholderFrame();
424 return placeholder
? placeholder
->GetParent() : nullptr;
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|
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
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.
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.)
458 "Missing placeholder-container when computing a "
459 "CSS Box Alignment static position");
463 // (Most of this function is simply preparing args that we'll pass to
464 // AlignJustifySelf at the end.)
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();
473 // Find what axis aAbsPosCBAxis corresponds to, in placeholder's parent's
476 (pcWM
.IsOrthogonalTo(aAbsPosCBWM
) ? GetOrthogonalAxis(aAbsPosCBAxis
)
479 const bool placeholderContainerIsContainingBlock
=
480 aPlaceholderContainer
== aKidReflowInput
.mCBReflowInput
->mFrame
;
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
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
496 aPlaceholderContainer
->GetLogicalUsedPadding(pcWM
).Size(pcWM
);
498 alignAreaSize
= aPlaceholderContainer
->GetLogicalSize(pcWM
);
499 LogicalMargin pcBorderPadding
=
500 aPlaceholderContainer
->GetLogicalUsedBorderAndPadding(pcWM
);
501 alignAreaSize
-= pcBorderPadding
.Size(pcWM
);
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
);
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
);
523 NS_ERROR("Unsupported container for abpsos CSS Box Alignment");
524 return 0; // (leave the child at the start of its alignment container)
527 nscoord alignAreaSizeInAxis
= (pcAxis
== eLogicalAxisInline
)
528 ? alignAreaSize
.ISize(pcWM
)
529 : alignAreaSize
.BSize(pcWM
);
531 AlignJustifyFlags flags
= AlignJustifyFlags::IgnoreAutoMargins
;
532 StyleAlignFlags alignConst
=
533 aPlaceholderContainer
->CSSAlignmentForAbsPosChild(aKidReflowInput
,
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
;
542 alignConst
&= ~StyleAlignFlags::FLAG_BITS
;
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
;
551 // (baselineAdjust is unused. CSSAlignmentForAbsPosChild() should've
552 // converted 'baseline'/'last baseline' enums to their fallback values.)
553 const nscoord baselineAdjust
= nscoord(0);
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
)
563 nscoord offset
= CSSAlignUtils::AlignJustifySelf(
564 alignConst
, kidAxis
, flags
, baselineAdjust
, alignAreaSizeInAxis
,
565 aKidReflowInput
, kidSizeInOwnWM
);
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
)) {
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();
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
);
606 const LogicalSize logicalCBSizeOuterWM
=
607 aLogicalCBSize
->ConvertTo(outerWM
, wm
);
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;
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
));
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
);
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
));
653 aKidReflowInput
.SetComputedLogicalOffsets(outerWM
, *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());
663 WritingMode wm
= aKidReflowInput
.GetWritingMode();
664 WritingMode outerWM
= aKidReflowInput
.mParentReflowInput
->GetWritingMode();
666 const LogicalSize kidSizeInWM
= aKidSize
.ConvertTo(wm
, outerWM
);
667 LogicalMargin marginInWM
= aMargin
.ConvertTo(wm
, outerWM
);
668 LogicalMargin offsetsInWM
= aOffsets
.ConvertTo(wm
, outerWM
);
670 // No need to substract border sizes because aKidSize has it included
672 nscoord availMarginSpace
= aLogicalCBSize
->BSize(wm
) - kidSizeInWM
.BSize(wm
) -
673 offsetsInWM
.BStartEnd(wm
) -
674 marginInWM
.BStartEnd(wm
);
676 if (availMarginSpace
< 0) {
677 availMarginSpace
= 0;
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
);
687 ReflowInput::ComputeAbsPosBlockAutoMargin(
688 availMarginSpace
, outerWM
,
689 styleMargin
->mMargin
.GetBStart(outerWM
).IsAuto(),
690 styleMargin
->mMargin
.GetBEnd(outerWM
).IsAuto(), aMargin
, aOffsets
);
693 aKidReflowInput
.SetComputedLogicalMargin(outerWM
, aMargin
);
694 aKidReflowInput
.SetComputedLogicalOffsets(outerWM
, aOffsets
);
696 nsMargin
* propValue
=
697 aKidReflowInput
.mFrame
->GetProperty(nsIFrame::UsedMarginProperty());
698 // InitOffsets should've created a UsedMarginProperty for us, if any margin is
700 MOZ_ASSERT_IF(styleMargin
->HasInlineAxisAuto(outerWM
) ||
701 styleMargin
->HasBlockAxisAuto(outerWM
),
704 *propValue
= aMargin
.GetPhysicalMargin(outerWM
);
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
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.
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!");
724 if (nsBlockFrame::gNoisyReflow
) {
725 nsIFrame::IndentBy(stdout
, nsBlockFrame::gNoiseIndent
);
728 aKidFrame
->GetFrameName(name
);
729 printf("%s ", NS_LossyConvertUTF16toASCII(name
).get());
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
);
740 AutoNoisyIndenter
indent(nsBlockFrame::gNoisy
);
743 WritingMode wm
= aKidFrame
->GetWritingMode();
744 LogicalSize
logicalCBSize(wm
, aContainingBlock
.Size());
745 nscoord availISize
= logicalCBSize
.ISize(wm
);
746 if (availISize
== -1) {
748 aReflowInput
.ComputedSize(wm
).ISize(wm
) != NS_UNCONSTRAINEDSIZE
,
749 "Must have a useful inline-size _somewhere_");
750 availISize
= aReflowInput
.ComputedSizeWithPadding(wm
).ISize(wm
);
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
;
767 bool constrainBSize
=
768 (aReflowInput
.AvailableBSize() != NS_UNCONSTRAINEDSIZE
) &&
770 // Don't split if told not to (e.g. for fixed frames)
771 (aFlags
& AbsPosReflowFlags::ConstrainHeight
) &&
773 // XXX we don't handle splitting frames for inline absolute containing
775 !aDelegatingFrame
->IsInlineFrame() &&
777 // Bug 1588623: Support splitting absolute positioned multicol containers.
778 !aKidFrame
->IsColumnSetWrapperFrame() &&
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());
786 // Get the border values
787 const WritingMode outerWM
= aReflowInput
.GetWritingMode();
788 const LogicalMargin border
= aDelegatingFrame
->GetLogicalUsedBorder(outerWM
);
790 const nscoord availBSize
= constrainBSize
791 ? aReflowInput
.AvailableBSize() -
792 border
.ConvertTo(wm
, outerWM
).BStart(wm
)
793 : NS_UNCONSTRAINEDSIZE
;
795 ReflowInput
kidReflowInput(aPresContext
, aReflowInput
, aKidFrame
,
796 LogicalSize(wm
, availISize
, availBSize
),
797 Some(logicalCBSize
), initFlags
);
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
;
811 ReflowOutput
kidDesiredSize(kidReflowInput
);
812 aKidFrame
->Reflow(aPresContext
, kidDesiredSize
, kidReflowInput
, aStatus
);
814 const LogicalSize kidSize
= kidDesiredSize
.Size(outerWM
);
816 LogicalMargin offsets
= kidReflowInput
.ComputedLogicalOffsets(outerWM
);
817 LogicalMargin margin
= kidReflowInput
.ComputedLogicalMargin(outerWM
);
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;
825 if (kidReflowInput
.mFlags
.mBOffsetsNeedCSSAlign
) {
826 margin
.BStart(outerWM
) = margin
.BEnd(outerWM
) = 0;
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
);
834 if (kidReflowInput
.mFrame
->HasIntrinsicKeywordForBSize()) {
835 ResolveAutoMarginsAfterLayout(kidReflowInput
, &logicalCBSize
, kidSize
,
839 // Position the child relative to our padding edge
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
));
849 // Offset the frame rect by the given origin of the absolute containing block.
850 r
.x
+= aContainingBlock
.x
;
851 r
.y
+= aContainingBlock
.y
;
853 aKidFrame
->SetRect(r
);
855 nsView
* view
= aKidFrame
->GetView();
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());
862 nsContainerFrame::PositionChildViews(aKidFrame
);
865 aKidFrame
->DidReflow(aPresContext
, &kidReflowInput
);
868 if (nsBlockFrame::gNoisyReflow
) {
869 nsIFrame::IndentBy(stdout
, nsBlockFrame::gNoiseIndent
- 1);
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
,
879 if (aOverflowAreas
) {
880 aOverflowAreas
->UnionWith(kidDesiredSize
.mOverflowAreas
+ r
.TopLeft());