tor-browser

The Tor Browser
git clone https://git.dasho.dev/tor-browser.git
Log | Files | Refs | README | LICENSE

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:
Mlayout/base/nsCSSFrameConstructor.cpp | 25++++++++++++++++++++-----
Mlayout/base/nsPresContext.cpp | 3+++
Mlayout/base/nsPresContext.h | 16++++++++++++++++
Mlayout/generic/AbsoluteContainingBlock.cpp | 213+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++----------------
Mlayout/generic/AbsoluteContainingBlock.h | 43+++++++++++++++++++++++++++++++++++++++----
Mlayout/generic/ReflowInput.cpp | 26+++++++++++++++-----------
Mlayout/generic/nsBlockFrame.cpp | 16++++++++--------
Mlayout/generic/nsContainerFrame.cpp | 27+++++++++++++++------------
Mlayout/generic/nsFrameList.cpp | 2++
Mlayout/generic/nsFrameList.h | 1+
Mlayout/generic/nsGridContainerFrame.cpp | 6++++++
Mlayout/generic/nsIFrame.cpp | 25+++++++++++++++++++------
Mmodules/libpref/init/StaticPrefList.yaml | 12++++++++++++
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." #---------------------------------------------------------------------------