commit 1b8e2204f5810b920ec9af46cbd6b6fe5fe2d006
parent 5cc32271f979b425a4614ab8fb085f47ae96979b
Author: Ting-Yu Lin <tlin@mozilla.com>
Date: Mon, 24 Nov 2025 19:58:03 +0000
Bug 1994083 - Allow AbsoluteContainingBlock to reflow all abspos continuations. r=dholbert
This patch consists of several parts:
- Introduce `PushedAbsolute` frame list (i.e. `mPushedAbsoluteFrames`) in
AbsoluteContainingBlock. This list is a temporary list used during reflow,
to store abspos frames that need to be reflowed by the delegating frame's
next-in-flow.
- For an absolute containing block frame, ensure its continuations also have
absolute containing block.
- Ensure that the position of abspos continuation is correct by adjusting
border, available block-size, etc. in
`AbsoluteContainingBlock::ReflowAbsoluteFrame()`.
- Cache `layout.abspos.fragmentainer-aware-positioning.enabled` pref at the time
when `nsPresContext` is initialized such that the pref value is stable across
the lifetime of `nsPresContext`. This is to ensure the frame tree structure
hygiene.
Differential Revision: https://phabricator.services.mozilla.com/D272400
Diffstat:
13 files changed, 327 insertions(+), 88 deletions(-)
diff --git a/layout/base/nsCSSFrameConstructor.cpp b/layout/base/nsCSSFrameConstructor.cpp
@@ -867,7 +867,14 @@ void nsFrameConstructorState::PushAbsoluteContainingBlock(
}();
if (aNewAbsoluteContainingBlock) {
- aNewAbsoluteContainingBlock->MarkAsAbsoluteContainingBlock();
+ if (!aNewAbsoluteContainingBlock->GetPrevContinuation()) {
+ aNewAbsoluteContainingBlock->MarkAsAbsoluteContainingBlock();
+ } else {
+ MOZ_ASSERT(
+ aNewAbsoluteContainingBlock->GetAbsoluteContainingBlock(),
+ "nsIFrame::Init() should've constructed AbsoluteContainingBlock in "
+ "this case, since the frame is a continuation!");
+ }
}
}
@@ -2979,12 +2986,20 @@ nsContainerFrame* nsCSSFrameConstructor::ConstructPageFrame(
if (!prevPageContentFrame) {
// The canvas is an inheriting anon box, so needs to be "owned" by the page
// content.
- pageContentFrame->AddStateBits(NS_FRAME_OWNS_ANON_BOXES);
+ pageContentFrame->AddStateBits(NS_FRAME_OWNS_ANON_BOXES |
+ NS_FRAME_CAN_HAVE_ABSPOS_CHILDREN);
+ // Make it an absolute container for fixed-pos elements
+ pageContentFrame->MarkAsAbsoluteContainingBlock();
+ } else {
+ MOZ_ASSERT(
+ pageContentFrame->HasAllStateBits(NS_FRAME_CAN_HAVE_ABSPOS_CHILDREN),
+ "This bit should've been carried over from the previous continuation "
+ "in nsIFrame::Init().");
+ MOZ_ASSERT(pageContentFrame->GetAbsoluteContainingBlock(),
+ "nsIFrame::Init() should've constructed AbsoluteContainingBlock "
+ "for continuations!");
}
SetInitialSingleChild(pageFrame, pageContentFrame);
- // Make it an absolute container for fixed-pos elements
- pageContentFrame->AddStateBits(NS_FRAME_CAN_HAVE_ABSPOS_CHILDREN);
- pageContentFrame->MarkAsAbsoluteContainingBlock();
RefPtr<ComputedStyle> canvasPseudoStyle =
styleSet->ResolveInheritingAnonymousBoxStyle(PseudoStyleType::canvas,
diff --git a/layout/base/nsPresContext.cpp b/layout/base/nsPresContext.cpp
@@ -709,6 +709,9 @@ nsresult nsPresContext::Init(nsDeviceContext* aDeviceContext) {
}
}
+ mFragmentainerAwarePositioningEnabled =
+ StaticPrefs::layout_abspos_fragmentainer_aware_positioning_enabled();
+
// Register callbacks so we're notified when the preferences change
Preferences::RegisterPrefixCallbacks(nsPresContext::PreferenceChanged,
gPrefixCallbackPrefs, this);
diff --git a/layout/base/nsPresContext.h b/layout/base/nsPresContext.h
@@ -1087,6 +1087,16 @@ class nsPresContext : public nsISupports,
}
}
+ // Return the cached value of the about:config pref
+ // 'layout.abspos.fragmentainer-aware-positioning.enabled'.
+ //
+ // Note: Layout code should use this helper rather than the actual "live" pref
+ // value. This ensures a given frame tree handles abspos fragmentation
+ // consistently even if the actual pref value changes.
+ bool FragmentainerAwarePositioningEnabled() const {
+ return mFragmentainerAwarePositioningEnabled;
+ }
+
protected:
void DoUpdateHiddenByContentVisibilityForAnimations();
friend class nsRunnableMethod<nsPresContext>;
@@ -1397,6 +1407,12 @@ class nsPresContext : public nsISupports,
unsigned mNeedsToUpdateHiddenByContentVisibilityForAnimations : 1;
unsigned mUserInputEventsAllowed : 1;
+
+ // Cached value of the about:config pref
+ // 'layout.abspos.fragmentainer-aware-positioning.enabled'
+ // from when this nsPresContext was initialized.
+ bool mFragmentainerAwarePositioningEnabled : 1 = false;
+
#ifdef DEBUG
unsigned mInitialized : 1;
#endif
diff --git a/layout/generic/AbsoluteContainingBlock.cpp b/layout/generic/AbsoluteContainingBlock.cpp
@@ -14,6 +14,7 @@
#include "AnchorPositioningUtils.h"
#include "fmt/format.h"
#include "mozilla/CSSAlignUtils.h"
+#include "mozilla/DebugOnly.h"
#include "mozilla/PresShell.h"
#include "mozilla/ReflowInput.h"
#include "mozilla/ViewportFrame.h"
@@ -87,10 +88,78 @@ void AbsoluteContainingBlock::RemoveFrame(FrameDestroyContext& aContext,
FrameChildListID aListID,
nsIFrame* aOldFrame) {
NS_ASSERTION(mChildListID == aListID, "unexpected child list");
- if (nsIFrame* nif = aOldFrame->GetNextInFlow()) {
- nif->GetParent()->DeleteNextInFlowChild(aContext, nif, false);
+
+ if (!aOldFrame->PresContext()->FragmentainerAwarePositioningEnabled()) {
+ if (nsIFrame* nif = aOldFrame->GetNextInFlow()) {
+ nif->GetParent()->DeleteNextInFlowChild(aContext, nif, false);
+ }
+ mAbsoluteFrames.DestroyFrame(aContext, aOldFrame);
+ return;
+ }
+
+ AutoTArray<nsIFrame*, 8> delFrames;
+ for (nsIFrame* f = aOldFrame; f; f = f->GetNextInFlow()) {
+ delFrames.AppendElement(f);
}
- mAbsoluteFrames.DestroyFrame(aContext, aOldFrame);
+ for (nsIFrame* delFrame : Reversed(delFrames)) {
+ delFrame->GetParent()->GetAbsoluteContainingBlock()->StealFrame(delFrame);
+ delFrame->Destroy(aContext);
+ }
+}
+
+nsFrameList AbsoluteContainingBlock::StealPushedChildList() {
+ return std::move(mPushedAbsoluteFrames);
+}
+
+bool AbsoluteContainingBlock::PrepareAbsoluteFrames(
+ nsContainerFrame* aDelegatingFrame) {
+ if (!aDelegatingFrame->PresContext()
+ ->FragmentainerAwarePositioningEnabled()) {
+ return HasAbsoluteFrames();
+ }
+
+ if (const nsIFrame* prevInFlow = aDelegatingFrame->GetPrevInFlow()) {
+ AbsoluteContainingBlock* prevAbsCB =
+ prevInFlow->GetAbsoluteContainingBlock();
+ MOZ_ASSERT(prevAbsCB,
+ "If this delegating frame has an absCB, its prev-in-flow must "
+ "have one, too!");
+
+ // Prepend the pushed absolute frames from the previous absCB to our
+ // absolute child list.
+ nsFrameList pushedFrames = prevAbsCB->StealPushedChildList();
+ if (pushedFrames.NotEmpty()) {
+ mAbsoluteFrames.InsertFrames(aDelegatingFrame, nullptr,
+ std::move(pushedFrames));
+ }
+ }
+
+ // Our pushed absolute child list might be non-empty if our next-in-flow
+ // hasn't reflowed yet. Move any child in that list that is a first-in-flow,
+ // or whose prev-in-flow is not in our absolute child list, into our absolute
+ // child list.
+ nsIFrame* child = mPushedAbsoluteFrames.FirstChild();
+ while (child) {
+ nsIFrame* next = child->GetNextInFlow();
+ if (!child->GetPrevInFlow() ||
+ child->GetPrevInFlow()->GetParent() != aDelegatingFrame) {
+ mPushedAbsoluteFrames.RemoveFrame(child);
+ mAbsoluteFrames.AppendFrame(nullptr, child);
+ }
+ child = next;
+ }
+
+ // TODO (Bug 1994346 or Bug 1997696): Consider stealing absolute frames from
+ // our next-in-flow's absolute child list.
+
+ return HasAbsoluteFrames();
+}
+
+void AbsoluteContainingBlock::StealFrame(nsIFrame* aFrame) {
+ const DebugOnly<bool> frameRemoved =
+ mAbsoluteFrames.StartRemoveFrame(aFrame) ||
+ mPushedAbsoluteFrames.ContinueRemoveFrame(aFrame);
+ MOZ_ASSERT(frameRemoved, "Failed to find aFrame from our child lists!");
}
static void MaybeMarkAncestorsAsHavingDescendantDependentOnItsStaticPos(
@@ -258,29 +327,57 @@ void AbsoluteContainingBlock::Reflow(nsContainerFrame* aDelegatingFrame,
MOZ_ASSERT(!kidStatus.IsInlineBreakBefore(),
"ShouldAvoidBreakInside should prevent this from happening");
nsIFrame* nextFrame = kidFrame->GetNextInFlow();
- if (!kidStatus.IsFullyComplete() &&
- aDelegatingFrame->CanContainOverflowContainers()) {
- // Need a continuation
- if (!nextFrame) {
- nextFrame = aPresContext->PresShell()
- ->FrameConstructor()
- ->CreateContinuingFrame(kidFrame, aDelegatingFrame);
+ if (aPresContext->FragmentainerAwarePositioningEnabled()) {
+ if (!kidStatus.IsFullyComplete()) {
+ if (!nextFrame) {
+ nextFrame = aPresContext->PresShell()
+ ->FrameConstructor()
+ ->CreateContinuingFrame(kidFrame, aDelegatingFrame);
+ mPushedAbsoluteFrames.AppendFrame(nullptr, nextFrame);
+ } else if (nextFrame->GetParent() !=
+ aDelegatingFrame->GetNextInFlow()) {
+ nextFrame->GetParent()->GetAbsoluteContainingBlock()->StealFrame(
+ nextFrame);
+ mPushedAbsoluteFrames.AppendFrame(nullptr, nextFrame);
+ }
+ reflowStatus.MergeCompletionStatusFrom(kidStatus);
+ } else if (nextFrame) {
+ // kidFrame is fully-complete. Delete all its next-in-flows.
+ FrameDestroyContext context(aPresContext->PresShell());
+ nextFrame->GetParent()->GetAbsoluteContainingBlock()->RemoveFrame(
+ context, FrameChildListID::Absolute, nextFrame);
+ }
+ } else {
+ if (!kidStatus.IsFullyComplete() &&
+ aDelegatingFrame->CanContainOverflowContainers()) {
+ // Need a continuation
+ if (!nextFrame) {
+ nextFrame = aPresContext->PresShell()
+ ->FrameConstructor()
+ ->CreateContinuingFrame(kidFrame, aDelegatingFrame);
+ }
+ // Add it as an overflow container.
+ // XXXfr This is a hack to fix some of our printing dataloss.
+ // See bug 154892. Not sure how to do it "right" yet; probably want
+ // to keep continuations within an AbsoluteContainingBlock eventually.
+ //
+ // NOTE(TYLin): we're now trying to conditionally do this "right" in
+ // the other branch here, inside of the StaticPrefs pref-check.
+ tracker.Insert(nextFrame, kidStatus);
+ reflowStatus.MergeCompletionStatusFrom(kidStatus);
+ } else if (nextFrame) {
+ // Delete any continuations
+ nsOverflowContinuationTracker::AutoFinish fini(&tracker, kidFrame);
+ FrameDestroyContext context(aPresContext->PresShell());
+ nextFrame->GetParent()->DeleteNextInFlowChild(context, nextFrame,
+ true);
}
- // Add it as an overflow container.
- // XXXfr This is a hack to fix some of our printing dataloss.
- // See bug 154892. Not sure how to do it "right" yet; probably want
- // to keep continuations within an AbsoluteContainingBlock eventually.
- tracker.Insert(nextFrame, kidStatus);
- reflowStatus.MergeCompletionStatusFrom(kidStatus);
- } else if (nextFrame) {
- // Delete any continuations
- nsOverflowContinuationTracker::AutoFinish fini(&tracker, kidFrame);
- FrameDestroyContext context(aPresContext->PresShell());
- nextFrame->GetParent()->DeleteNextInFlowChild(context, nextFrame, true);
}
} else {
- tracker.Skip(kidFrame, reflowStatus);
if (aOverflowAreas) {
+ if (!aPresContext->FragmentainerAwarePositioningEnabled()) {
+ tracker.Skip(kidFrame, reflowStatus);
+ }
aDelegatingFrame->ConsiderChildOverflow(*aOverflowAreas, kidFrame);
}
}
@@ -460,6 +557,7 @@ bool AbsoluteContainingBlock::FrameDependsOnContainer(
void AbsoluteContainingBlock::DestroyFrames(DestroyContext& aContext) {
mAbsoluteFrames.DestroyFrames(aContext);
+ mPushedAbsoluteFrames.DestroyFrames(aContext);
}
void AbsoluteContainingBlock::MarkSizeDependentFramesDirty() {
@@ -1203,43 +1301,74 @@ void AbsoluteContainingBlock::ReflowAbsoluteFrame(
// Don't split things below the fold. (Ideally we shouldn't *have*
// anything totally below the fold, but we can't position frames
- // across next-in-flow breaks yet.
+ // across next-in-flow breaks yet. (Bug 1994346)
(aKidFrame->GetLogicalRect(cb.mRect.Size()).BStart(wm) <=
aReflowInput.AvailableBSize());
// Get the border values
const LogicalMargin border =
- aDelegatingFrame->GetLogicalUsedBorder(outerWM);
- const LogicalSize availSize(
- outerWM, cbSize.ISize(outerWM),
- kidFrameMaySplit
- ? aReflowInput.AvailableBSize() - border.BStart(outerWM)
- : NS_UNCONSTRAINEDSIZE);
-
+ aDelegatingFrame->GetLogicalUsedBorder(outerWM).ApplySkipSides(
+ aDelegatingFrame->PreReflowBlockLevelLogicalSkipSides());
+
+ const nsIFrame* kidPrevInFlow = aKidFrame->GetPrevInFlow();
+ nscoord availBSize;
+ if (kidFrameMaySplit) {
+ availBSize = aReflowInput.AvailableBSize();
+ // If aKidFrame is a first-in-flow, we subtract our containing block's
+ // border-block-start, to consider the available space as starting at the
+ // containing block's padding-edge.
+ //
+ // If aKidFrame is *not* a first-in-flow, then we don't need to subtract
+ // the containing block's border. Instead, we consider this whole fragment
+ // as our available space, i.e., we allow abspos continuations to overlap
+ // any border that their containing block parent might have (including
+ // borders generated by 'box-decoration-break:clone').
+ if (!kidPrevInFlow) {
+ availBSize -= border.BStart(outerWM);
+ }
+ } else {
+ availBSize = NS_UNCONSTRAINEDSIZE;
+ }
+ const LogicalSize availSize(outerWM, cbSize.ISize(outerWM), availBSize);
ReflowInput kidReflowInput(aPresContext, aReflowInput, aKidFrame,
availSize.ConvertTo(wm, outerWM),
Some(cbSize.ConvertTo(wm, outerWM)), initFlags,
{}, {}, aAnchorPosResolutionCache);
- if (nscoord kidAvailBSize = kidReflowInput.AvailableBSize();
- kidAvailBSize != NS_UNCONSTRAINEDSIZE) {
- // Shrink available block-size if it's constrained.
- kidAvailBSize -= kidReflowInput.ComputedLogicalMargin(wm).BStart(wm);
- const nscoord kidOffsetBStart =
- kidReflowInput.ComputedLogicalOffsets(wm).BStart(wm);
- if (NS_AUTOOFFSET != kidOffsetBStart) {
- kidAvailBSize -= kidOffsetBStart;
+ // ReflowInput's constructor may change the available block-size to
+ // unconstrained, e.g. in orthogonal reflow, so we retrieve it again and
+ // account for kid's constraints in its own writing-mode if needed.
+ if (!kidPrevInFlow) {
+ nscoord kidAvailBSize = kidReflowInput.AvailableBSize();
+ if (kidAvailBSize != NS_UNCONSTRAINEDSIZE) {
+ kidAvailBSize -= kidReflowInput.ComputedLogicalMargin(wm).BStart(wm);
+ const nscoord kidOffsetBStart =
+ kidReflowInput.ComputedLogicalOffsets(wm).BStart(wm);
+ if (kidOffsetBStart != NS_AUTOOFFSET) {
+ kidAvailBSize -= kidOffsetBStart;
+ }
+ kidReflowInput.SetAvailableBSize(kidAvailBSize);
}
- kidReflowInput.SetAvailableBSize(kidAvailBSize);
}
// Do the reflow
ReflowOutput kidDesiredSize(kidReflowInput);
aKidFrame->Reflow(aPresContext, kidDesiredSize, kidReflowInput, aStatus);
- // Position the child relative to our padding edge. Don't do this for
- // popups, which handle their own positioning.
- if (!aKidFrame->IsMenuPopupFrame()) {
+ if (aKidFrame->IsMenuPopupFrame()) {
+ // Do nothing. Popup frame will handle its own positioning.
+ } else if (kidPrevInFlow) {
+ // aKidFrame is a next-in-flow. Place it at the block-edge start of its
+ // containing block, with the same inline-position as its prev-in-flow.
+ const nsSize cbBorderBoxSize =
+ (cbSize + border.Size(outerWM)).GetPhysicalSize(outerWM);
+ const LogicalPoint kidPos(
+ outerWM, kidPrevInFlow->IStart(outerWM, cbBorderBoxSize), 0);
+ const LogicalSize kidSize = kidDesiredSize.Size(outerWM);
+ const LogicalRect kidRect(outerWM, kidPos, kidSize);
+ aKidFrame->SetRect(outerWM, kidRect, cbBorderBoxSize);
+ } else {
+ // Position the child relative to our padding edge.
const LogicalSize kidSize = kidDesiredSize.Size(outerWM);
LogicalMargin offsets = kidReflowInput.ComputedLogicalOffsets(outerWM);
diff --git a/layout/generic/AbsoluteContainingBlock.h b/layout/generic/AbsoluteContainingBlock.h
@@ -57,6 +57,9 @@ class AbsoluteContainingBlock {
}
const nsFrameList& GetChildList() const { return mAbsoluteFrames; }
+ const nsFrameList& GetPushedChildList() const {
+ return mPushedAbsoluteFrames;
+ }
void SetInitialChildList(nsIFrame* aDelegatingFrame, FrameChildListID aListID,
nsFrameList&& aChildList);
@@ -67,6 +70,30 @@ class AbsoluteContainingBlock {
void RemoveFrame(FrameDestroyContext&, FrameChildListID, nsIFrame*);
/**
+ * Return the pushed absolute frames. The caller is responsible for passing
+ * the ownership of the frames to someone else, or destroying them.
+ */
+ [[nodiscard]] nsFrameList StealPushedChildList();
+
+ /**
+ * Prepare our absolute child list so that it is ready to reflow by moving all
+ * the pushed absolute frames in aDelegatingFrame's prev-in-flow's absCB, and
+ * some in our own pushed absolute child list, to our absolute child list.
+ *
+ * @return true if we have absolute frames after we return.
+ */
+ bool PrepareAbsoluteFrames(nsContainerFrame* aDelegatingFrame);
+
+ /**
+ * Return true if we have absolute frames.
+ *
+ * Note: During reflow, consider calling PrepareAbsoluteFrames() rather than
+ * this method; it moves absolute frames from other lists to mAbsoluteFrames,
+ * which may be needed to get the correct result.
+ */
+ bool HasAbsoluteFrames() const { return mAbsoluteFrames.NotEmpty(); }
+
+ /**
* Called by the delegating frame after it has done its reflow first. This
* function will reflow any absolutely positioned child frames that need to
* be reflowed, e.g., because the absolutely positioned child frame has
@@ -91,8 +118,6 @@ class AbsoluteContainingBlock {
using DestroyContext = nsIFrame::DestroyContext;
void DestroyFrames(DestroyContext&);
- bool HasAbsoluteFrames() const { return mAbsoluteFrames.NotEmpty(); }
-
/**
* Mark our size-dependent absolute frames with NS_FRAME_HAS_DIRTY_CHILDREN
* so that we'll make sure to reflow them.
@@ -163,8 +188,18 @@ class AbsoluteContainingBlock {
*/
void DoMarkFramesDirty(bool aMarkAllDirty);
- protected:
- nsFrameList mAbsoluteFrames; // additional named child list
+ /**
+ * Remove aFrame from one of our frame lists without destroying it.
+ */
+ void StealFrame(nsIFrame* aFrame);
+
+ // Stores the abspos frames that have been placed in this containing block.
+ nsFrameList mAbsoluteFrames;
+
+ // A temporary frame list used during reflow, storing abspos frames that need
+ // to be reflowed by the delegating frame's next-in-flow after transferring
+ // them to its own AbsoluteContainingBlock.
+ nsFrameList mPushedAbsoluteFrames;
#ifdef DEBUG
// FrameChildListID::Fixed or FrameChildListID::Absolute
diff --git a/layout/generic/ReflowInput.cpp b/layout/generic/ReflowInput.cpp
@@ -2106,15 +2106,6 @@ static nscoord CalcQuirkContainingBlockHeight(
LogicalSize ReflowInput::ComputeContainingBlockRectangle(
nsPresContext* aPresContext, const ReflowInput* aContainingBlockRI) const {
- MOZ_ASSERT(!mFrame->IsAbsolutelyPositioned(mStyleDisplay) ||
- // XXX: We have a hack putting abspos continuations in overflow
- // container lists (bug 154892), so they are not reflowed by
- // AbsoluteContainingBlock until we revisit the abspos
- // continuations handling.
- mFrame->GetPrevInFlow(),
- "AbsoluteContainingBlock always provides a containing-block size "
- "when creating ReflowInput for its children!");
-
LogicalSize cbSize = aContainingBlockRI->ComputedSize();
WritingMode wm = aContainingBlockRI->GetWritingMode();
@@ -2197,6 +2188,19 @@ void ReflowInput::InitConstraints(
// If we weren't given a containing block size, then compute one.
if (aContainingBlockSize.isNothing()) {
cbSize = ComputeContainingBlockRectangle(aPresContext, cbri);
+ } else if (aPresContext->FragmentainerAwarePositioningEnabled() &&
+ mFrame->IsAbsolutelyPositioned(mStyleDisplay) &&
+ mFrame->GetPrevInFlow()) {
+ // AbsoluteContainingBlock always provides a containing-block size to
+ // ReflowInput. However, if the delegating frame is a continuation or an
+ // overflow container (i.e. it has zero block-size), we'll need a
+ // containing-block size (padding-box size) suitable for resolving the
+ // abspos continuation's percentage block-size.
+ //
+ // Bug 1998818 is to fix the containing-block size for resolving
+ // percentage block-size for abspos's first-in-flow.
+ cbSize = ComputeContainingBlockRectangle(aPresContext, cbri) +
+ cbri->ComputedLogicalPadding(wm).Size(wm);
}
// See if the containing block height is based on the size of its
@@ -2360,8 +2364,8 @@ void ReflowInput::InitConstraints(
mComputedMaxSize.SizeTo(mWritingMode, NS_UNCONSTRAINEDSIZE,
NS_UNCONSTRAINEDSIZE);
} else if (mFrame->IsAbsolutelyPositioned(mStyleDisplay) &&
- // XXXfr hack for making frames behave properly when in overflow
- // container lists, see bug 154892; need to revisit later
+ // The absolute constraints are needed only for abspos
+ // first-in-flow, not continuations.
!mFrame->GetPrevInFlow()) {
InitAbsoluteConstraints(cbri,
cbSize.ConvertTo(cbri->GetWritingMode(), wm));
diff --git a/layout/generic/nsBlockFrame.cpp b/layout/generic/nsBlockFrame.cpp
@@ -1646,8 +1646,9 @@ void nsBlockFrame::Reflow(nsPresContext* aPresContext, ReflowOutput& aMetrics,
// resetting the size. Because of this, we must not reflow our abs-pos
// children in that situation --- what we think is our "new size" will not be
// our real new size. This also happens to be more efficient.
- if (HasAbsolutelyPositionedChildren()) {
- AbsoluteContainingBlock* absoluteContainer = GetAbsoluteContainingBlock();
+ AbsoluteContainingBlock* absoluteContainer =
+ IsAbsoluteContainer() ? GetAbsoluteContainingBlock() : nullptr;
+ if (absoluteContainer && absoluteContainer->PrepareAbsoluteFrames(this)) {
bool haveInterrupt = aPresContext->HasPendingInterrupt();
if (aReflowInput.WillReflowAgainForClearance() || haveInterrupt) {
// Make sure that when we reflow again we'll actually reflow all the abs
@@ -1694,11 +1695,6 @@ void nsBlockFrame::Reflow(nsPresContext* aPresContext, ReflowOutput& aMetrics,
!(isRoot && NS_UNCONSTRAINEDSIZE == aReflowInput.ComputedHeight()) &&
aMetrics.Height() != oldSize.height;
- const LogicalRect containingBlock = [&]() {
- LogicalRect rect(wm, LogicalPoint(wm), aMetrics.Size(wm));
- rect.Deflate(wm, aReflowInput.ComputedLogicalBorder(wm));
- return rect;
- }();
AbsPosReflowFlags flags{AbsPosReflowFlag::AllowFragmentation};
if (cbWidthChanged) {
flags += AbsPosReflowFlag::CBWidthChanged;
@@ -1710,9 +1706,13 @@ void nsBlockFrame::Reflow(nsPresContext* aPresContext, ReflowOutput& aMetrics,
// calculating hypothetical position of absolutely-positioned
// frames.
SetupLineCursorForQuery();
+
+ LogicalRect cbRect(wm, LogicalPoint(wm), aMetrics.Size(wm));
+ cbRect.Deflate(wm, aReflowInput.ComputedLogicalBorder(wm).ApplySkipSides(
+ PreReflowBlockLevelLogicalSkipSides()));
absoluteContainer->Reflow(
this, aPresContext, aReflowInput, reflowStatus,
- containingBlock.GetPhysicalRect(wm, aMetrics.PhysicalSize()), flags,
+ cbRect.GetPhysicalRect(wm, aMetrics.PhysicalSize()), flags,
&aMetrics.mOverflowAreas);
}
}
diff --git a/layout/generic/nsContainerFrame.cpp b/layout/generic/nsContainerFrame.cpp
@@ -846,21 +846,23 @@ void nsContainerFrame::ReflowAbsoluteFrames(nsPresContext* aPresContext,
ReflowOutput& aDesiredSize,
const ReflowInput& aReflowInput,
nsReflowStatus& aStatus) {
- if (HasAbsolutelyPositionedChildren()) {
- AbsoluteContainingBlock* absoluteContainer = GetAbsoluteContainingBlock();
-
+ AbsoluteContainingBlock* absoluteContainer =
+ IsAbsoluteContainer() ? GetAbsoluteContainingBlock() : nullptr;
+ if (absoluteContainer && absoluteContainer->PrepareAbsoluteFrames(this)) {
// The containing block for the abs pos kids is formed by our padding edge.
- nsMargin usedBorder = GetUsedBorder();
- nsRect containingBlock(nsPoint{}, aDesiredSize.PhysicalSize());
- containingBlock.Deflate(usedBorder);
+ const auto wm = GetWritingMode();
+ LogicalRect cbRect(wm, LogicalPoint(wm), aDesiredSize.Size(wm));
+ cbRect.Deflate(wm, GetLogicalUsedBorder(wm).ApplySkipSides(
+ PreReflowBlockLevelLogicalSkipSides()));
// XXX: To optimize the performance, set the flags only when the CB width or
// height actually changes.
AbsPosReflowFlags flags{AbsPosReflowFlag::AllowFragmentation,
AbsPosReflowFlag::CBWidthChanged,
AbsPosReflowFlag::CBHeightChanged};
- absoluteContainer->Reflow(this, aPresContext, aReflowInput, aStatus,
- containingBlock, flags,
- &aDesiredSize.mOverflowAreas);
+ absoluteContainer->Reflow(
+ this, aPresContext, aReflowInput, aStatus,
+ cbRect.GetPhysicalRect(wm, aDesiredSize.PhysicalSize()), flags,
+ &aDesiredSize.mOverflowAreas);
}
}
@@ -2827,9 +2829,10 @@ void nsContainerFrame::SanityCheckChildListsBeforeReflow() const {
const auto didPushItemsBit = IsFlexContainerFrame()
? NS_STATE_FLEX_DID_PUSH_ITEMS
: NS_STATE_GRID_DID_PUSH_ITEMS;
- ChildListIDs absLists = {FrameChildListID::Absolute, FrameChildListID::Fixed,
- FrameChildListID::OverflowContainers,
- FrameChildListID::ExcessOverflowContainers};
+ ChildListIDs absLists = {
+ FrameChildListID::Absolute, FrameChildListID::PushedAbsolute,
+ FrameChildListID::Fixed, FrameChildListID::OverflowContainers,
+ FrameChildListID::ExcessOverflowContainers};
ChildListIDs itemLists = {FrameChildListID::Principal,
FrameChildListID::Overflow};
for (const nsIFrame* f = this; f; f = f->GetNextInFlow()) {
diff --git a/layout/generic/nsFrameList.cpp b/layout/generic/nsFrameList.cpp
@@ -459,6 +459,8 @@ const char* ChildListName(FrameChildListID aListID) {
return "ColGroupList";
case FrameChildListID::Absolute:
return "AbsoluteList";
+ case FrameChildListID::PushedAbsolute:
+ return "PushedAbsoluteList";
case FrameChildListID::Fixed:
return "FixedList";
case FrameChildListID::Overflow:
diff --git a/layout/generic/nsFrameList.h b/layout/generic/nsFrameList.h
@@ -36,6 +36,7 @@ enum class FrameChildListID {
Principal,
ColGroup,
Absolute,
+ PushedAbsolute,
Fixed,
Overflow,
OverflowContainers,
diff --git a/layout/generic/nsGridContainerFrame.cpp b/layout/generic/nsGridContainerFrame.cpp
@@ -3276,6 +3276,11 @@ struct MOZ_STACK_CLASS nsGridContainerFrame::GridReflowInput {
MOZ_ASSERT(mGridItems.Length() == len + 1, "can't find GridItemInfo");
}
+ if (aGridContainerFrame->IsAbsoluteContainer()) {
+ // Prepare absolute frames before constructing GridItemInfo.
+ aGridContainerFrame->GetAbsoluteContainingBlock()->PrepareAbsoluteFrames(
+ aGridContainerFrame);
+ }
// XXX NOTE: This is O(n^2) in the number of abs.pos. items. (bug 1252186)
const nsFrameList& absPosChildren = aGridContainerFrame->GetChildList(
aGridContainerFrame->GetAbsoluteListID());
@@ -9301,6 +9306,7 @@ nscoord nsGridContainerFrame::ReflowChildren(GridReflowInput& aGridRI,
AbsoluteContainingBlock* absoluteContainer =
IsAbsoluteContainer() ? GetAbsoluteContainingBlock() : nullptr;
+ // We have prepared the absolute frames when initializing GridReflowInput.
if (absoluteContainer && absoluteContainer->HasAbsoluteFrames()) {
// 'gridOrigin' is the origin of the grid (the start of the first track),
// with respect to the grid container's padding-box (CB).
diff --git a/layout/generic/nsIFrame.cpp b/layout/generic/nsIFrame.cpp
@@ -644,6 +644,13 @@ void nsIFrame::Init(nsIContent* aContent, nsContainerFrame* aParent,
// Copy other bits in nsIFrame from prev-in-flow.
mHasColumnSpanSiblings = aPrevInFlow->HasColumnSpanSiblings();
+
+ // If our prev-in-flow is an absolute containing block, we must be one, too.
+ if (aPrevInFlow->IsAbsoluteContainer()) {
+ MOZ_ASSERT(HasAnyStateBits(NS_FRAME_CAN_HAVE_ABSPOS_CHILDREN),
+ "We should've carried this bit from our prev-in-flow!");
+ MarkAsAbsoluteContainingBlock();
+ }
} else {
PresContext()->ConstructedFrame();
}
@@ -2021,18 +2028,24 @@ nscoord nsIFrame::GetLogicalBaseline(
}
const nsFrameList& nsIFrame::GetChildList(ChildListID aListID) const {
- if (IsAbsoluteContainer() && aListID == GetAbsoluteListID()) {
- return GetAbsoluteContainingBlock()->GetChildList();
- } else {
- return nsFrameList::EmptyList();
+ if (IsAbsoluteContainer()) {
+ if (aListID == GetAbsoluteListID()) {
+ return GetAbsoluteContainingBlock()->GetChildList();
+ } else if (aListID == FrameChildListID::PushedAbsolute) {
+ return GetAbsoluteContainingBlock()->GetPushedChildList();
+ }
}
+ return nsFrameList::EmptyList();
}
void nsIFrame::GetChildLists(nsTArray<ChildList>* aLists) const {
if (IsAbsoluteContainer()) {
- const nsFrameList& absoluteList =
- GetAbsoluteContainingBlock()->GetChildList();
+ const auto* absCB = GetAbsoluteContainingBlock();
+ const nsFrameList& absoluteList = absCB->GetChildList();
absoluteList.AppendIfNonempty(aLists, GetAbsoluteListID());
+ const nsFrameList& pushedAbsoluteList = absCB->GetPushedChildList();
+ pushedAbsoluteList.AppendIfNonempty(aLists,
+ FrameChildListID::PushedAbsolute);
}
}
diff --git a/modules/libpref/init/StaticPrefList.yaml b/modules/libpref/init/StaticPrefList.yaml
@@ -11095,6 +11095,18 @@
#endif
mirror: always
+# Enable the support to position absoluetly positioned frames properly across
+# fragmentainers (Bug 1994316).
+#
+# Note: Layout code should use
+# nsPresContext::FragmentainerAwarePositioningEnabled() rather than checking
+# this pref directly. This ensures a given frame tree handles abspos
+# fragmentation consistently even if the actual pref value changes.
+- name: layout.abspos.fragmentainer-aware-positioning.enabled
+ type: bool
+ value: false
+ mirror: always
+
#---------------------------------------------------------------------------
# Prefs starting with "logging."
#---------------------------------------------------------------------------