Bug 1839315: part 4) Link from `SheetLoadData::mWasAlternate` to spec. r=emilio DONTBUILD
[gecko.git] / layout / generic / nsAbsoluteContainingBlock.cpp
blob0bee284d8d480b834c3dac671b0e724688d4f683
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/. */
7 /*
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 "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"
25 #include "mozilla/Sprintf.h"
27 #ifdef DEBUG
28 # include "nsBlockFrame.h"
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);
41 #endif
43 using namespace mozilla;
45 typedef mozilla::CSSAlignUtils::AlignJustifyFlags AlignJustifyFlags;
47 void nsAbsoluteContainingBlock::SetInitialChildList(nsIFrame* aDelegatingFrame,
48 FrameChildListID 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");
56 #endif
57 mAbsoluteFrames = std::move(aChildList);
60 void nsAbsoluteContainingBlock::AppendFrames(nsIFrame* aDelegatingFrame,
61 FrameChildListID aListID,
62 nsFrameList&& aFrameList) {
63 NS_ASSERTION(mChildListID == aListID, "unexpected child list");
65 // Append the frames to our list of absolutely positioned frames
66 #ifdef DEBUG
67 nsIFrame::VerifyDirtyBitSet(aFrameList);
68 #endif
69 mAbsoluteFrames.AppendFrames(nullptr, std::move(aFrameList));
71 // no damage to intrinsic widths, since absolutely positioned frames can't
72 // change them
73 aDelegatingFrame->PresShell()->FrameNeedsReflow(
74 aDelegatingFrame, IntrinsicDirty::None, NS_FRAME_HAS_DIRTY_CHILDREN);
77 void nsAbsoluteContainingBlock::InsertFrames(nsIFrame* aDelegatingFrame,
78 FrameChildListID 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");
85 #ifdef DEBUG
86 nsIFrame::VerifyDirtyBitSet(aFrameList);
87 #endif
88 mAbsoluteFrames.InsertFrames(nullptr, aPrevFrame, std::move(aFrameList));
90 // no damage to intrinsic widths, since absolutely positioned frames can't
91 // change them
92 aDelegatingFrame->PresShell()->FrameNeedsReflow(
93 aDelegatingFrame, IntrinsicDirty::None, NS_FRAME_HAS_DIRTY_CHILDREN);
96 void nsAbsoluteContainingBlock::RemoveFrame(FrameDestroyContext& aContext,
97 FrameChildListID aListID,
98 nsIFrame* aOldFrame) {
99 NS_ASSERTION(mChildListID == aListID, "unexpected child list");
100 if (nsIFrame* nif = aOldFrame->GetNextInFlow()) {
101 nif->GetParent()->DeleteNextInFlowChild(aContext, nif, false);
103 mAbsoluteFrames.DestroyFrame(aContext, aOldFrame);
106 static void MaybeMarkAncestorsAsHavingDescendantDependentOnItsStaticPos(
107 nsIFrame* aFrame, nsIFrame* aContainingBlockFrame) {
108 MOZ_ASSERT(aFrame->HasAnyStateBits(NS_FRAME_OUT_OF_FLOW));
109 if (!aFrame->StylePosition()->NeedsHypotheticalPositionIfAbsPos()) {
110 return;
112 // We should have set the bit when reflowing the previous continuations
113 // already.
114 if (aFrame->GetPrevContinuation()) {
115 return;
118 auto* placeholder = aFrame->GetPlaceholderFrame();
119 MOZ_ASSERT(placeholder);
121 // Only fixed-pos frames can escape their containing block.
122 if (!placeholder->HasAnyStateBits(PLACEHOLDER_FOR_FIXEDPOS)) {
123 return;
126 for (nsIFrame* ancestor = placeholder->GetParent(); ancestor;
127 ancestor = ancestor->GetParent()) {
128 // Walk towards the ancestor's first continuation. That's the only one that
129 // really matters, since it's the only one restyling will look at. We also
130 // flag the following continuations just so it's caught on the first
131 // early-return ones just to avoid walking them over and over.
132 do {
133 if (ancestor->DescendantMayDependOnItsStaticPosition()) {
134 return;
136 // Moving the containing block or anything above it would move our static
137 // position as well, so no need to flag it or any of its ancestors.
138 if (aFrame == aContainingBlockFrame) {
139 return;
141 ancestor->SetDescendantMayDependOnItsStaticPosition(true);
142 nsIFrame* prev = ancestor->GetPrevContinuation();
143 if (!prev) {
144 break;
146 ancestor = prev;
147 } while (true);
151 void nsAbsoluteContainingBlock::Reflow(nsContainerFrame* aDelegatingFrame,
152 nsPresContext* aPresContext,
153 const ReflowInput& aReflowInput,
154 nsReflowStatus& aReflowStatus,
155 const nsRect& aContainingBlock,
156 AbsPosReflowFlags aFlags,
157 OverflowAreas* aOverflowAreas) {
158 // PageContentFrame replicates fixed pos children so we really don't want
159 // them contributing to overflow areas because that means we'll create new
160 // pages ad infinitum if one of them overflows the page.
161 if (aDelegatingFrame->IsPageContentFrame()) {
162 MOZ_ASSERT(mChildListID == FrameChildListID::Fixed);
163 aOverflowAreas = nullptr;
166 nsReflowStatus reflowStatus;
167 const bool reflowAll = aReflowInput.ShouldReflowAllKids();
168 const bool isGrid = !!(aFlags & AbsPosReflowFlags::IsGridContainerCB);
169 nsIFrame* kidFrame;
170 nsOverflowContinuationTracker tracker(aDelegatingFrame, true);
171 for (kidFrame = mAbsoluteFrames.FirstChild(); kidFrame;
172 kidFrame = kidFrame->GetNextSibling()) {
173 bool kidNeedsReflow =
174 reflowAll || kidFrame->IsSubtreeDirty() ||
175 FrameDependsOnContainer(
176 kidFrame, !!(aFlags & AbsPosReflowFlags::CBWidthChanged),
177 !!(aFlags & AbsPosReflowFlags::CBHeightChanged));
179 if (kidFrame->IsSubtreeDirty()) {
180 MaybeMarkAncestorsAsHavingDescendantDependentOnItsStaticPos(
181 kidFrame, aDelegatingFrame);
184 nscoord availBSize = aReflowInput.AvailableBSize();
185 const nsRect& cb =
186 isGrid ? nsGridContainerFrame::GridItemCB(kidFrame) : aContainingBlock;
187 WritingMode containerWM = aReflowInput.GetWritingMode();
188 if (!kidNeedsReflow && availBSize != NS_UNCONSTRAINEDSIZE) {
189 // If we need to redo pagination on the kid, we need to reflow it.
190 // This can happen either if the available height shrunk and the
191 // kid (or its overflow that creates overflow containers) is now
192 // too large to fit in the available height, or if the available
193 // height has increased and the kid has a next-in-flow that we
194 // might need to pull from.
195 WritingMode kidWM = kidFrame->GetWritingMode();
196 if (containerWM.GetBlockDir() != kidWM.GetBlockDir()) {
197 // Not sure what the right test would be here.
198 kidNeedsReflow = true;
199 } else {
200 nscoord kidBEnd = kidFrame->GetLogicalRect(cb.Size()).BEnd(kidWM);
201 nscoord kidOverflowBEnd =
202 LogicalRect(containerWM,
203 // Use ...RelativeToSelf to ignore transforms
204 kidFrame->ScrollableOverflowRectRelativeToSelf() +
205 kidFrame->GetPosition(),
206 aContainingBlock.Size())
207 .BEnd(containerWM);
208 NS_ASSERTION(kidOverflowBEnd >= kidBEnd,
209 "overflow area should be at least as large as frame rect");
210 if (kidOverflowBEnd > availBSize ||
211 (kidBEnd < availBSize && kidFrame->GetNextInFlow())) {
212 kidNeedsReflow = true;
216 if (kidNeedsReflow && !aPresContext->HasPendingInterrupt()) {
217 // Reflow the frame
218 nsReflowStatus kidStatus;
219 ReflowAbsoluteFrame(aDelegatingFrame, aPresContext, aReflowInput, cb,
220 aFlags, kidFrame, kidStatus, aOverflowAreas);
221 MOZ_ASSERT(!kidStatus.IsInlineBreakBefore(),
222 "ShouldAvoidBreakInside should prevent this from happening");
223 nsIFrame* nextFrame = kidFrame->GetNextInFlow();
224 if (!kidStatus.IsFullyComplete() &&
225 aDelegatingFrame->IsFrameOfType(
226 nsIFrame::eCanContainOverflowContainers)) {
227 // Need a continuation
228 if (!nextFrame) {
229 nextFrame = aPresContext->PresShell()
230 ->FrameConstructor()
231 ->CreateContinuingFrame(kidFrame, aDelegatingFrame);
233 // Add it as an overflow container.
234 // XXXfr This is a hack to fix some of our printing dataloss.
235 // See bug 154892. Not sure how to do it "right" yet; probably want
236 // to keep continuations within an nsAbsoluteContainingBlock eventually.
237 tracker.Insert(nextFrame, kidStatus);
238 reflowStatus.MergeCompletionStatusFrom(kidStatus);
239 } else if (nextFrame) {
240 // Delete any continuations
241 nsOverflowContinuationTracker::AutoFinish fini(&tracker, kidFrame);
242 FrameDestroyContext context(aPresContext->PresShell());
243 nextFrame->GetParent()->DeleteNextInFlowChild(context, nextFrame, true);
245 } else {
246 tracker.Skip(kidFrame, reflowStatus);
247 if (aOverflowAreas) {
248 aDelegatingFrame->ConsiderChildOverflow(*aOverflowAreas, kidFrame);
252 // Make a CheckForInterrupt call, here, not just HasPendingInterrupt. That
253 // will make sure that we end up reflowing aDelegatingFrame in cases when
254 // one of our kids interrupted. Otherwise we'd set the dirty or
255 // dirty-children bit on the kid in the condition below, and then when
256 // reflow completes and we go to mark dirty bits on all ancestors of that
257 // kid we'll immediately bail out, because the kid already has a dirty bit.
258 // In particular, we won't set any dirty bits on aDelegatingFrame, so when
259 // the following reflow happens we won't reflow the kid in question. This
260 // might be slightly suboptimal in cases where |kidFrame| itself did not
261 // interrupt, since we'll trigger a reflow of it too when it's not strictly
262 // needed. But the logic to not do that is enough more complicated, and
263 // the case enough of an edge case, that this is probably better.
264 if (kidNeedsReflow && aPresContext->CheckForInterrupt(aDelegatingFrame)) {
265 if (aDelegatingFrame->HasAnyStateBits(NS_FRAME_IS_DIRTY)) {
266 kidFrame->MarkSubtreeDirty();
267 } else {
268 kidFrame->AddStateBits(NS_FRAME_HAS_DIRTY_CHILDREN);
273 // Abspos frames can't cause their parent to be incomplete,
274 // only overflow incomplete.
275 if (reflowStatus.IsIncomplete()) {
276 reflowStatus.SetOverflowIncomplete();
277 reflowStatus.SetNextInFlowNeedsReflow();
280 aReflowStatus.MergeCompletionStatusFrom(reflowStatus);
283 static inline bool IsFixedPaddingSize(const LengthPercentage& aCoord) {
284 return aCoord.ConvertsToLength();
286 static inline bool IsFixedMarginSize(const LengthPercentageOrAuto& aCoord) {
287 return aCoord.ConvertsToLength();
289 static inline bool IsFixedOffset(const LengthPercentageOrAuto& aCoord) {
290 return aCoord.ConvertsToLength();
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;
302 if (!aCBWidthChanged && !aCBHeightChanged) {
303 // skip getting style data
304 return false;
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;
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;
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.
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;
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;
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;
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;
382 if (aCBHeightChanged) {
383 if (!IsFixedOffset(pos->mOffset.Get(eSideTop))) {
384 return true;
386 // See comment above for width changes.
387 if (wm.GetInlineDir() == WritingMode::eInlineBTT &&
388 !pos->mOffset.Get(eSideBottom).IsAuto()) {
389 return true;
393 return false;
396 void nsAbsoluteContainingBlock::DestroyFrames(DestroyContext& aContext) {
397 mAbsoluteFrames.DestroyFrames(aContext);
400 void nsAbsoluteContainingBlock::MarkSizeDependentFramesDirty() {
401 DoMarkFramesDirty(false);
404 void nsAbsoluteContainingBlock::MarkAllFramesDirty() {
405 DoMarkFramesDirty(true);
408 void nsAbsoluteContainingBlock::DoMarkFramesDirty(bool aMarkAllDirty) {
409 for (nsIFrame* kidFrame : mAbsoluteFrames) {
410 if (aMarkAllDirty) {
411 kidFrame->MarkSubtreeDirty();
412 } else if (FrameDependsOnContainer(kidFrame, true, true)) {
413 // Add the weakest flags that will make sure we reflow this frame later
414 kidFrame->AddStateBits(NS_FRAME_HAS_DIRTY_CHILDREN);
419 // Given an out-of-flow frame, this method returns the parent frame of its
420 // placeholder frame or null if it doesn't have a placeholder for some reason.
421 static nsContainerFrame* GetPlaceholderContainer(nsIFrame* aPositionedFrame) {
422 nsIFrame* placeholder = aPositionedFrame->GetPlaceholderFrame();
423 return placeholder ? placeholder->GetParent() : nullptr;
427 * This function returns the offset of an abs/fixed-pos child's static
428 * position, with respect to the "start" corner of its alignment container,
429 * according to CSS Box Alignment. This function only operates in a single
430 * axis at a time -- callers can choose which axis via the |aAbsPosCBAxis|
431 * parameter.
433 * @param aKidReflowInput The ReflowInput for the to-be-aligned abspos child.
434 * @param aKidSizeInAbsPosCBWM The child frame's size (after it's been given
435 * the opportunity to reflow), in terms of
436 * aAbsPosCBWM.
437 * @param aAbsPosCBSize The abspos CB size, in terms of aAbsPosCBWM.
438 * @param aPlaceholderContainer The parent of the child frame's corresponding
439 * placeholder frame, cast to a nsContainerFrame.
440 * (This will help us choose which alignment enum
441 * we should use for the child.)
442 * @param aAbsPosCBWM The child frame's containing block's WritingMode.
443 * @param aAbsPosCBAxis The axis (of the containing block) that we should
444 * be doing this computation for.
446 static nscoord OffsetToAlignedStaticPos(const ReflowInput& aKidReflowInput,
447 const LogicalSize& aKidSizeInAbsPosCBWM,
448 const LogicalSize& aAbsPosCBSize,
449 nsContainerFrame* aPlaceholderContainer,
450 WritingMode aAbsPosCBWM,
451 LogicalAxis aAbsPosCBAxis) {
452 if (!aPlaceholderContainer) {
453 // (The placeholder container should be the thing that kicks this whole
454 // process off, by setting PLACEHOLDER_STATICPOS_NEEDS_CSSALIGN. So it
455 // should exist... but bail gracefully if it doesn't.)
456 NS_ERROR(
457 "Missing placeholder-container when computing a "
458 "CSS Box Alignment static position");
459 return 0;
462 // (Most of this function is simply preparing args that we'll pass to
463 // AlignJustifySelf at the end.)
465 // NOTE: Our alignment container is aPlaceholderContainer's content-box
466 // (or an area within it, if aPlaceholderContainer is a grid). So, we'll
467 // perform most of our arithmetic/alignment in aPlaceholderContainer's
468 // WritingMode. For brevity, we use the abbreviation "pc" for "placeholder
469 // container" in variables below.
470 WritingMode pcWM = aPlaceholderContainer->GetWritingMode();
472 // Find what axis aAbsPosCBAxis corresponds to, in placeholder's parent's
473 // writing-mode.
474 LogicalAxis pcAxis =
475 (pcWM.IsOrthogonalTo(aAbsPosCBWM) ? GetOrthogonalAxis(aAbsPosCBAxis)
476 : aAbsPosCBAxis);
478 const bool placeholderContainerIsContainingBlock =
479 aPlaceholderContainer == aKidReflowInput.mCBReflowInput->mFrame;
481 LayoutFrameType parentType = aPlaceholderContainer->Type();
482 LogicalSize alignAreaSize(pcWM);
483 if (parentType == LayoutFrameType::FlexContainer) {
484 // We store the frame rect in FinishAndStoreOverflow, which runs _after_
485 // reflowing the absolute frames, so handle the special case of the frame
486 // being the actual containing block here, by getting the size from
487 // aAbsPosCBSize.
489 // The alignment container is the flex container's content box.
490 if (placeholderContainerIsContainingBlock) {
491 alignAreaSize = aAbsPosCBSize.ConvertTo(pcWM, aAbsPosCBWM);
492 // aAbsPosCBSize is the padding-box, so substract the padding to get the
493 // content box.
494 alignAreaSize -=
495 aPlaceholderContainer->GetLogicalUsedPadding(pcWM).Size(pcWM);
496 } else {
497 alignAreaSize = aPlaceholderContainer->GetLogicalSize(pcWM);
498 LogicalMargin pcBorderPadding =
499 aPlaceholderContainer->GetLogicalUsedBorderAndPadding(pcWM);
500 alignAreaSize -= pcBorderPadding.Size(pcWM);
502 } else if (parentType == LayoutFrameType::GridContainer) {
503 // This abspos elem's parent is a grid container. Per CSS Grid 10.1 & 10.2:
504 // - If the grid container *also* generates the abspos containing block (a
505 // grid area) for this abspos child, we use that abspos containing block as
506 // the alignment container, too. (And its size is aAbsPosCBSize.)
507 // - Otherwise, we use the grid's padding box as the alignment container.
508 // https://drafts.csswg.org/css-grid/#static-position
509 if (placeholderContainerIsContainingBlock) {
510 // The alignment container is the grid area that we're using as the
511 // absolute containing block.
512 alignAreaSize = aAbsPosCBSize.ConvertTo(pcWM, aAbsPosCBWM);
513 } else {
514 // The alignment container is a the grid container's content box (which
515 // we can get by subtracting away its border & padding from frame's size):
516 alignAreaSize = aPlaceholderContainer->GetLogicalSize(pcWM);
517 LogicalMargin pcBorderPadding =
518 aPlaceholderContainer->GetLogicalUsedBorderAndPadding(pcWM);
519 alignAreaSize -= pcBorderPadding.Size(pcWM);
521 } else {
522 NS_ERROR("Unsupported container for abpsos CSS Box Alignment");
523 return 0; // (leave the child at the start of its alignment container)
526 nscoord alignAreaSizeInAxis = (pcAxis == eLogicalAxisInline)
527 ? alignAreaSize.ISize(pcWM)
528 : alignAreaSize.BSize(pcWM);
530 AlignJustifyFlags flags = AlignJustifyFlags::IgnoreAutoMargins;
531 StyleAlignFlags alignConst =
532 aPlaceholderContainer->CSSAlignmentForAbsPosChild(aKidReflowInput,
533 pcAxis);
534 // If the safe bit in alignConst is set, set the safe flag in |flags|.
535 // Note: If no <overflow-position> is specified, we behave as 'unsafe'.
536 // This doesn't quite match the css-align spec, which has an [at-risk]
537 // "smart default" behavior with some extra nuance about scroll containers.
538 if (alignConst & StyleAlignFlags::SAFE) {
539 flags |= AlignJustifyFlags::OverflowSafe;
541 alignConst &= ~StyleAlignFlags::FLAG_BITS;
543 // Find out if placeholder-container & the OOF child have the same start-sides
544 // in the placeholder-container's pcAxis.
545 WritingMode kidWM = aKidReflowInput.GetWritingMode();
546 if (pcWM.ParallelAxisStartsOnSameSide(pcAxis, kidWM)) {
547 flags |= AlignJustifyFlags::SameSide;
550 // (baselineAdjust is unused. CSSAlignmentForAbsPosChild() should've
551 // converted 'baseline'/'last baseline' enums to their fallback values.)
552 const nscoord baselineAdjust = nscoord(0);
554 // AlignJustifySelf operates in the kid's writing mode, so we need to
555 // represent the child's size and the desired axis in that writing mode:
556 LogicalSize kidSizeInOwnWM =
557 aKidSizeInAbsPosCBWM.ConvertTo(kidWM, aAbsPosCBWM);
558 LogicalAxis kidAxis =
559 (kidWM.IsOrthogonalTo(aAbsPosCBWM) ? GetOrthogonalAxis(aAbsPosCBAxis)
560 : aAbsPosCBAxis);
562 nscoord offset = CSSAlignUtils::AlignJustifySelf(
563 alignConst, kidAxis, flags, baselineAdjust, alignAreaSizeInAxis,
564 aKidReflowInput, kidSizeInOwnWM);
566 // "offset" is in terms of the CSS Box Alignment container (i.e. it's in
567 // terms of pcWM). But our return value needs to in terms of the containing
568 // block's writing mode, which might have the opposite directionality in the
569 // given axis. In that case, we just need to negate "offset" when returning,
570 // to make it have the right effect as an offset for coordinates in the
571 // containing block's writing mode.
572 if (!pcWM.ParallelAxisStartsOnSameSide(pcAxis, aAbsPosCBWM)) {
573 return -offset;
575 return offset;
578 void nsAbsoluteContainingBlock::ResolveSizeDependentOffsets(
579 nsPresContext* aPresContext, ReflowInput& aKidReflowInput,
580 const LogicalSize& aKidSize, const LogicalMargin& aMargin,
581 LogicalMargin* aOffsets, LogicalSize* aLogicalCBSize) {
582 WritingMode wm = aKidReflowInput.GetWritingMode();
583 WritingMode outerWM = aKidReflowInput.mParentReflowInput->GetWritingMode();
585 // Now that we know the child's size, we resolve any sentinel values in its
586 // IStart/BStart offset coordinates that depend on that size.
587 // * NS_AUTOOFFSET indicates that the child's position in the given axis
588 // is determined by its end-wards offset property, combined with its size and
589 // available space. e.g.: "top: auto; height: auto; bottom: 50px"
590 // * m{I,B}OffsetsResolvedAfterSize indicate that the child is using its
591 // static position in that axis, *and* its static position is determined by
592 // the axis-appropriate css-align property (which may require the child's
593 // size, e.g. to center it within the parent).
594 if ((NS_AUTOOFFSET == aOffsets->IStart(outerWM)) ||
595 (NS_AUTOOFFSET == aOffsets->BStart(outerWM)) ||
596 aKidReflowInput.mFlags.mIOffsetsNeedCSSAlign ||
597 aKidReflowInput.mFlags.mBOffsetsNeedCSSAlign) {
598 if (-1 == aLogicalCBSize->ISize(wm)) {
599 // Get the containing block width/height
600 const ReflowInput* parentRI = aKidReflowInput.mParentReflowInput;
601 *aLogicalCBSize = aKidReflowInput.ComputeContainingBlockRectangle(
602 aPresContext, parentRI);
605 const LogicalSize logicalCBSizeOuterWM =
606 aLogicalCBSize->ConvertTo(outerWM, wm);
608 // placeholderContainer is used in each of the m{I,B}OffsetsNeedCSSAlign
609 // clauses. We declare it at this scope so we can avoid having to look
610 // it up twice (and only look it up if it's needed).
611 nsContainerFrame* placeholderContainer = nullptr;
613 if (NS_AUTOOFFSET == aOffsets->IStart(outerWM)) {
614 NS_ASSERTION(NS_AUTOOFFSET != aOffsets->IEnd(outerWM),
615 "Can't solve for both start and end");
616 aOffsets->IStart(outerWM) =
617 logicalCBSizeOuterWM.ISize(outerWM) - aOffsets->IEnd(outerWM) -
618 aMargin.IStartEnd(outerWM) - aKidSize.ISize(outerWM);
619 } else if (aKidReflowInput.mFlags.mIOffsetsNeedCSSAlign) {
620 placeholderContainer = GetPlaceholderContainer(aKidReflowInput.mFrame);
621 nscoord offset = OffsetToAlignedStaticPos(
622 aKidReflowInput, aKidSize, logicalCBSizeOuterWM, placeholderContainer,
623 outerWM, eLogicalAxisInline);
624 // Shift IStart from its current position (at start corner of the
625 // alignment container) by the returned offset. And set IEnd to the
626 // distance between the kid's end edge to containing block's end edge.
627 aOffsets->IStart(outerWM) += offset;
628 aOffsets->IEnd(outerWM) =
629 logicalCBSizeOuterWM.ISize(outerWM) -
630 (aOffsets->IStart(outerWM) + aKidSize.ISize(outerWM));
633 if (NS_AUTOOFFSET == aOffsets->BStart(outerWM)) {
634 aOffsets->BStart(outerWM) =
635 logicalCBSizeOuterWM.BSize(outerWM) - aOffsets->BEnd(outerWM) -
636 aMargin.BStartEnd(outerWM) - aKidSize.BSize(outerWM);
637 } else if (aKidReflowInput.mFlags.mBOffsetsNeedCSSAlign) {
638 if (!placeholderContainer) {
639 placeholderContainer = GetPlaceholderContainer(aKidReflowInput.mFrame);
641 nscoord offset = OffsetToAlignedStaticPos(
642 aKidReflowInput, aKidSize, logicalCBSizeOuterWM, placeholderContainer,
643 outerWM, eLogicalAxisBlock);
644 // Shift BStart from its current position (at start corner of the
645 // alignment container) by the returned offset. And set BEnd to the
646 // distance between the kid's end edge to containing block's end edge.
647 aOffsets->BStart(outerWM) += offset;
648 aOffsets->BEnd(outerWM) =
649 logicalCBSizeOuterWM.BSize(outerWM) -
650 (aOffsets->BStart(outerWM) + aKidSize.BSize(outerWM));
652 aKidReflowInput.SetComputedLogicalOffsets(outerWM, *aOffsets);
656 void nsAbsoluteContainingBlock::ResolveAutoMarginsAfterLayout(
657 ReflowInput& aKidReflowInput, const LogicalSize* aLogicalCBSize,
658 const LogicalSize& aKidSize, LogicalMargin& aMargin,
659 LogicalMargin& aOffsets) {
660 MOZ_ASSERT(aKidReflowInput.mFrame->HasIntrinsicKeywordForBSize());
662 WritingMode wm = aKidReflowInput.GetWritingMode();
663 WritingMode outerWM = aKidReflowInput.mParentReflowInput->GetWritingMode();
665 const LogicalSize kidSizeInWM = aKidSize.ConvertTo(wm, outerWM);
666 LogicalMargin marginInWM = aMargin.ConvertTo(wm, outerWM);
667 LogicalMargin offsetsInWM = aOffsets.ConvertTo(wm, outerWM);
669 // No need to substract border sizes because aKidSize has it included
670 // already. Also, if any offset is auto, the auto margin resolves to zero.
671 // https://drafts.csswg.org/css-position-3/#abspos-margins
672 const bool autoOffset = offsetsInWM.BEnd(wm) == NS_AUTOOFFSET ||
673 offsetsInWM.BStart(wm) == NS_AUTOOFFSET;
674 nscoord availMarginSpace =
675 autoOffset ? 0
676 : aLogicalCBSize->BSize(wm) - kidSizeInWM.BSize(wm) -
677 offsetsInWM.BStartEnd(wm) - marginInWM.BStartEnd(wm);
679 const auto& styleMargin = aKidReflowInput.mStyleMargin;
680 if (wm.IsOrthogonalTo(outerWM)) {
681 ReflowInput::ComputeAbsPosInlineAutoMargin(
682 availMarginSpace, outerWM,
683 styleMargin->mMargin.GetIStart(outerWM).IsAuto(),
684 styleMargin->mMargin.GetIEnd(outerWM).IsAuto(), aMargin, aOffsets);
685 } else {
686 ReflowInput::ComputeAbsPosBlockAutoMargin(
687 availMarginSpace, outerWM,
688 styleMargin->mMargin.GetBStart(outerWM).IsAuto(),
689 styleMargin->mMargin.GetBEnd(outerWM).IsAuto(), aMargin, aOffsets);
692 aKidReflowInput.SetComputedLogicalMargin(outerWM, aMargin);
693 aKidReflowInput.SetComputedLogicalOffsets(outerWM, aOffsets);
695 nsMargin* propValue =
696 aKidReflowInput.mFrame->GetProperty(nsIFrame::UsedMarginProperty());
697 // InitOffsets should've created a UsedMarginProperty for us, if any margin is
698 // auto.
699 MOZ_ASSERT_IF(styleMargin->HasInlineAxisAuto(outerWM) ||
700 styleMargin->HasBlockAxisAuto(outerWM),
701 propValue);
702 if (propValue) {
703 *propValue = aMargin.GetPhysicalMargin(outerWM);
707 // XXX Optimize the case where it's a resize reflow and the absolutely
708 // positioned child has the exact same size and position and skip the
709 // reflow...
711 // When bug 154892 is checked in, make sure that when
712 // mChildListID == FrameChildListID::Fixed, the height is unconstrained.
713 // since we don't allow replicated frames to split.
715 void nsAbsoluteContainingBlock::ReflowAbsoluteFrame(
716 nsIFrame* aDelegatingFrame, nsPresContext* aPresContext,
717 const ReflowInput& aReflowInput, const nsRect& aContainingBlock,
718 AbsPosReflowFlags aFlags, nsIFrame* aKidFrame, nsReflowStatus& aStatus,
719 OverflowAreas* aOverflowAreas) {
720 MOZ_ASSERT(aStatus.IsEmpty(), "Caller should pass a fresh reflow status!");
722 #ifdef DEBUG
723 if (nsBlockFrame::gNoisyReflow) {
724 nsIFrame::IndentBy(stdout, nsBlockFrame::gNoiseIndent);
725 printf("abs pos ");
726 nsAutoString name;
727 aKidFrame->GetFrameName(name);
728 printf("%s ", NS_LossyConvertUTF16toASCII(name).get());
730 char width[16];
731 char height[16];
732 PrettyUC(aReflowInput.AvailableWidth(), width, 16);
733 PrettyUC(aReflowInput.AvailableHeight(), height, 16);
734 printf(" a=%s,%s ", width, height);
735 PrettyUC(aReflowInput.ComputedWidth(), width, 16);
736 PrettyUC(aReflowInput.ComputedHeight(), height, 16);
737 printf("c=%s,%s \n", width, height);
739 AutoNoisyIndenter indent(nsBlockFrame::gNoisy);
740 #endif // DEBUG
742 WritingMode wm = aKidFrame->GetWritingMode();
743 LogicalSize logicalCBSize(wm, aContainingBlock.Size());
744 nscoord availISize = logicalCBSize.ISize(wm);
745 if (availISize == -1) {
746 NS_ASSERTION(
747 aReflowInput.ComputedSize(wm).ISize(wm) != NS_UNCONSTRAINEDSIZE,
748 "Must have a useful inline-size _somewhere_");
749 availISize = aReflowInput.ComputedSizeWithPadding(wm).ISize(wm);
752 ReflowInput::InitFlags initFlags;
753 if (aFlags & AbsPosReflowFlags::IsGridContainerCB) {
754 // When a grid container generates the abs.pos. CB for a *child* then
755 // the static position is determined via CSS Box Alignment within the
756 // abs.pos. CB (a grid area, i.e. a piece of the grid). In this scenario,
757 // due to the multiple coordinate spaces in play, we use a convenience flag
758 // to simply have the child's ReflowInput give it a static position at its
759 // abs.pos. CB origin, and then we'll align & offset it from there.
760 nsIFrame* placeholder = aKidFrame->GetPlaceholderFrame();
761 if (placeholder && placeholder->GetParent() == aDelegatingFrame) {
762 initFlags += ReflowInput::InitFlag::StaticPosIsCBOrigin;
766 bool constrainBSize =
767 (aReflowInput.AvailableBSize() != NS_UNCONSTRAINEDSIZE) &&
769 // Don't split if told not to (e.g. for fixed frames)
770 (aFlags & AbsPosReflowFlags::ConstrainHeight) &&
772 // XXX we don't handle splitting frames for inline absolute containing
773 // blocks yet
774 !aDelegatingFrame->IsInlineFrame() &&
776 // Bug 1588623: Support splitting absolute positioned multicol containers.
777 !aKidFrame->IsColumnSetWrapperFrame() &&
779 // Don't split things below the fold. (Ideally we shouldn't *have*
780 // anything totally below the fold, but we can't position frames
781 // across next-in-flow breaks yet.
782 (aKidFrame->GetLogicalRect(aContainingBlock.Size()).BStart(wm) <=
783 aReflowInput.AvailableBSize());
785 // Get the border values
786 const WritingMode outerWM = aReflowInput.GetWritingMode();
787 const LogicalMargin border = aDelegatingFrame->GetLogicalUsedBorder(outerWM);
789 const nscoord availBSize = constrainBSize
790 ? aReflowInput.AvailableBSize() -
791 border.ConvertTo(wm, outerWM).BStart(wm)
792 : NS_UNCONSTRAINEDSIZE;
794 ReflowInput kidReflowInput(aPresContext, aReflowInput, aKidFrame,
795 LogicalSize(wm, availISize, availBSize),
796 Some(logicalCBSize), initFlags);
798 if (nscoord kidAvailBSize = kidReflowInput.AvailableBSize();
799 kidAvailBSize != NS_UNCONSTRAINEDSIZE) {
800 // Shrink available block-size if it's constrained.
801 kidAvailBSize -= kidReflowInput.ComputedLogicalMargin(wm).BStart(wm);
802 const nscoord kidOffsetBStart =
803 kidReflowInput.ComputedLogicalOffsets(wm).BStart(wm);
804 if (NS_AUTOOFFSET != kidOffsetBStart) {
805 kidAvailBSize -= kidOffsetBStart;
807 kidReflowInput.SetAvailableBSize(kidAvailBSize);
810 // Do the reflow
811 ReflowOutput kidDesiredSize(kidReflowInput);
812 aKidFrame->Reflow(aPresContext, kidDesiredSize, kidReflowInput, aStatus);
814 // Position the child relative to our padding edge. Don't do this for popups,
815 // which handle their own positioning.
816 if (!aKidFrame->IsMenuPopupFrame()) {
817 const LogicalSize kidSize = kidDesiredSize.Size(outerWM);
819 LogicalMargin offsets = kidReflowInput.ComputedLogicalOffsets(outerWM);
820 LogicalMargin margin = kidReflowInput.ComputedLogicalMargin(outerWM);
822 // If we're doing CSS Box Alignment in either axis, that will apply the
823 // margin for us in that axis (since the thing that's aligned is the margin
824 // box). So, we clear out the margin here to avoid applying it twice.
825 if (kidReflowInput.mFlags.mIOffsetsNeedCSSAlign) {
826 margin.IStart(outerWM) = margin.IEnd(outerWM) = 0;
828 if (kidReflowInput.mFlags.mBOffsetsNeedCSSAlign) {
829 margin.BStart(outerWM) = margin.BEnd(outerWM) = 0;
832 // If we're solving for start in either inline or block direction,
833 // then compute it now that we know the dimensions.
834 ResolveSizeDependentOffsets(aPresContext, kidReflowInput, kidSize, margin,
835 &offsets, &logicalCBSize);
837 if (kidReflowInput.mFrame->HasIntrinsicKeywordForBSize()) {
838 ResolveAutoMarginsAfterLayout(kidReflowInput, &logicalCBSize, kidSize,
839 margin, offsets);
842 LogicalRect rect(outerWM,
843 border.StartOffset(outerWM) +
844 offsets.StartOffset(outerWM) +
845 margin.StartOffset(outerWM),
846 kidSize);
847 nsRect r = rect.GetPhysicalRect(
848 outerWM, logicalCBSize.GetPhysicalSize(wm) +
849 border.Size(outerWM).GetPhysicalSize(outerWM));
851 // Offset the frame rect by the given origin of the absolute containing
852 // block.
853 r.x += aContainingBlock.x;
854 r.y += aContainingBlock.y;
856 aKidFrame->SetRect(r);
858 nsView* view = aKidFrame->GetView();
859 if (view) {
860 // Size and position the view and set its opacity, visibility, content
861 // transparency, and clip
862 nsContainerFrame::SyncFrameViewAfterReflow(aPresContext, aKidFrame, view,
863 kidDesiredSize.InkOverflow());
864 } else {
865 nsContainerFrame::PositionChildViews(aKidFrame);
869 aKidFrame->DidReflow(aPresContext, &kidReflowInput);
871 const nsRect r = aKidFrame->GetRect();
872 #ifdef DEBUG
873 if (nsBlockFrame::gNoisyReflow) {
874 nsIFrame::IndentBy(stdout, nsBlockFrame::gNoiseIndent - 1);
875 printf("abs pos ");
876 nsAutoString name;
877 aKidFrame->GetFrameName(name);
878 printf("%s ", NS_LossyConvertUTF16toASCII(name).get());
879 printf("%p rect=%d,%d,%d,%d\n", static_cast<void*>(aKidFrame), r.x, r.y,
880 r.width, r.height);
882 #endif
884 if (aOverflowAreas) {
885 aOverflowAreas->UnionWith(kidDesiredSize.mOverflowAreas + r.TopLeft());