2316 lines
101 KiB
C++
2316 lines
101 KiB
C++
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||
|
||
/*
|
||
* code for managing absolutely positioned children of a rendering
|
||
* object that is a containing block for them
|
||
*/
|
||
|
||
#include "mozilla/AbsoluteContainingBlock.h"
|
||
|
||
#include "AnchorPositioningUtils.h"
|
||
#include "mozilla/CSSAlignUtils.h"
|
||
#include "mozilla/DebugOnly.h"
|
||
#include "mozilla/PresShell.h"
|
||
#include "mozilla/ReflowInput.h"
|
||
#include "mozilla/ScrollContainerFrame.h"
|
||
#include "mozilla/ServoStyleSet.h"
|
||
#include "mozilla/ViewportFrame.h"
|
||
#include "mozilla/WritingModes.h"
|
||
#include "mozilla/dom/ViewTransition.h"
|
||
#include "nsBlockFrame.h"
|
||
#include "nsCSSFrameConstructor.h"
|
||
#include "nsContainerFrame.h"
|
||
#include "nsGridContainerFrame.h"
|
||
#include "nsIFrameInlines.h"
|
||
#include "nsLayoutUtils.h"
|
||
#include "nsPlaceholderFrame.h"
|
||
#include "nsPresContext.h"
|
||
#include "nsPresContextInlines.h"
|
||
|
||
#ifdef DEBUG
|
||
# include "fmt/format.h"
|
||
# include "nsBlockFrame.h"
|
||
#endif
|
||
|
||
using namespace mozilla;
|
||
|
||
void AbsoluteContainingBlock::SetInitialChildList(nsIFrame* aDelegatingFrame,
|
||
FrameChildListID aListID,
|
||
nsFrameList&& aChildList) {
|
||
MOZ_ASSERT(aListID == FrameChildListID::Absolute, "unexpected child list");
|
||
#ifdef DEBUG
|
||
nsIFrame::VerifyDirtyBitSet(aChildList);
|
||
for (nsIFrame* f : aChildList) {
|
||
MOZ_ASSERT(f->GetParent() == aDelegatingFrame, "Unexpected parent");
|
||
}
|
||
#endif
|
||
mAbsoluteFrames = std::move(aChildList);
|
||
}
|
||
|
||
void AbsoluteContainingBlock::AppendFrames(nsIFrame* aDelegatingFrame,
|
||
FrameChildListID aListID,
|
||
nsFrameList&& aFrameList) {
|
||
MOZ_ASSERT(aListID == FrameChildListID::Absolute, "unexpected child list");
|
||
|
||
// Append the frames to our list of absolutely positioned frames
|
||
#ifdef DEBUG
|
||
nsIFrame::VerifyDirtyBitSet(aFrameList);
|
||
#endif
|
||
mAbsoluteFrames.AppendFrames(nullptr, std::move(aFrameList));
|
||
|
||
// no damage to intrinsic widths, since absolutely positioned frames can't
|
||
// change them
|
||
aDelegatingFrame->PresShell()->FrameNeedsReflow(
|
||
aDelegatingFrame, IntrinsicDirty::None, NS_FRAME_HAS_DIRTY_CHILDREN);
|
||
}
|
||
|
||
void AbsoluteContainingBlock::InsertFrames(nsIFrame* aDelegatingFrame,
|
||
FrameChildListID aListID,
|
||
nsIFrame* aPrevFrame,
|
||
nsFrameList&& aFrameList) {
|
||
MOZ_ASSERT(aListID == FrameChildListID::Absolute, "unexpected child list");
|
||
NS_ASSERTION(!aPrevFrame || aPrevFrame->GetParent() == aDelegatingFrame,
|
||
"inserting after sibling frame with different parent");
|
||
|
||
#ifdef DEBUG
|
||
nsIFrame::VerifyDirtyBitSet(aFrameList);
|
||
#endif
|
||
mAbsoluteFrames.InsertFrames(nullptr, aPrevFrame, std::move(aFrameList));
|
||
|
||
// no damage to intrinsic widths, since absolutely positioned frames can't
|
||
// change them
|
||
aDelegatingFrame->PresShell()->FrameNeedsReflow(
|
||
aDelegatingFrame, IntrinsicDirty::None, NS_FRAME_HAS_DIRTY_CHILDREN);
|
||
}
|
||
|
||
void AbsoluteContainingBlock::RemoveFrame(FrameDestroyContext& aContext,
|
||
FrameChildListID aListID,
|
||
nsIFrame* aOldFrame) {
|
||
MOZ_ASSERT(aListID == FrameChildListID::Absolute, "unexpected child list");
|
||
|
||
AutoTArray<nsIFrame*, 8> delFrames;
|
||
for (nsIFrame* f = aOldFrame; f; f = f->GetNextInFlow()) {
|
||
delFrames.AppendElement(f);
|
||
}
|
||
for (nsIFrame* delFrame : Reversed(delFrames)) {
|
||
delFrame->GetParent()->GetAbsoluteContainingBlock()->StealFrame(delFrame);
|
||
delFrame->Destroy(aContext);
|
||
}
|
||
}
|
||
|
||
// In a fragmented context, for an absolutely positioned frame, this property
|
||
// stores the logical border-box position that the frame would have, if its
|
||
// abspos containing block were not being fragmented. The value for this
|
||
// property is determined by performing a special reflow on the abspos
|
||
// containing block (or a larger subtree that includes it), with an
|
||
// unconstrained available block-size.
|
||
//
|
||
// The position is relative to the absolute containing block's border-box, and
|
||
// is stored in the containing block's writing mode.
|
||
//
|
||
// Note: caller should use GetUnfragmentedPosition() helper to get the property.
|
||
NS_DECLARE_FRAME_PROPERTY_DELETABLE(UnfragmentedPositionProperty, LogicalPoint)
|
||
|
||
// Corresponding property to above, for the size of an absolutely positioned
|
||
// frame. However, there are important distinctions to note:
|
||
// 1. Writing mode is that of the absolutely positioned frame's.
|
||
// 2. Stores border-box size for box-sizing: border-box, or content box size for
|
||
// box-sizing: content-box.
|
||
NS_DECLARE_FRAME_PROPERTY_DELETABLE(UnfragmentedSizeProperty, LogicalSize)
|
||
|
||
// In a fragmented context, for an absolute containing block, this property
|
||
// stores the unfragmented containing block rects. This is used to allow
|
||
// proper percentage-sizing of its children.
|
||
NS_DECLARE_FRAME_PROPERTY_DELETABLE(
|
||
UnfragmentedContainingBlockProperty,
|
||
AbsoluteContainingBlock::ContainingBlockRects)
|
||
|
||
static LogicalPoint* GetUnfragmentedPosition(const ReflowInput& aCBReflowInput,
|
||
const nsIFrame* aFrame) {
|
||
// If the absolute containing block is in a measuring reflow, then aFrame's
|
||
// unfragmented position is going to be updated. Don't return the obsolete
|
||
// value in the property.
|
||
return aCBReflowInput.mFlags.mIsInFragmentainerMeasuringReflow
|
||
? nullptr
|
||
: aFrame->GetProperty(UnfragmentedPositionProperty());
|
||
}
|
||
|
||
static LogicalSize* GetUnfragmentedSize(const ReflowInput& aCBReflowInput,
|
||
const nsIFrame* aFrame) {
|
||
return aCBReflowInput.mFlags.mIsInFragmentainerMeasuringReflow
|
||
? nullptr
|
||
// Later fragment frames need to know the size for resolving
|
||
// automatic sizes.
|
||
: aFrame->FirstInFlow()->GetProperty(UnfragmentedSizeProperty());
|
||
}
|
||
|
||
// Walk aInlineFrame's continuation chain and return the *first* continuation
|
||
// (near the start-most edges) of the previous fragmentainer (not the first
|
||
// fragment we encountered during the walk). Or return nullptr if no such
|
||
// continuation in the previous fragmentainer or no previous fragmentainer.
|
||
static nsIFrame* GetFirstInlineContinuationInPrevFragmentainer(
|
||
nsIFrame* aInlineFrame) {
|
||
MOZ_ASSERT(aInlineFrame->IsInlineFrameOrSubclass());
|
||
// An inline always has a block ancestor, and that block is continued per
|
||
// fragmentainer, so two continuations share a fragmentainer iff their nearest
|
||
// block ancestors are the same frame.
|
||
const nsBlockFrame* myBlock =
|
||
nsLayoutUtils::FindNearestBlockAncestor(aInlineFrame);
|
||
nsIFrame* candidate = nullptr;
|
||
const nsBlockFrame* candidateBlock = nullptr;
|
||
for (nsIFrame* prev =
|
||
nsLayoutUtils::GetPrevContinuationOrIBSplitSibling(aInlineFrame);
|
||
prev; prev = nsLayoutUtils::GetPrevContinuationOrIBSplitSibling(prev)) {
|
||
if (prev->IsBlockFrameOrSubclass()) {
|
||
// Skip IB-split block siblings.
|
||
continue;
|
||
}
|
||
const nsBlockFrame* prevBlock =
|
||
nsLayoutUtils::FindNearestBlockAncestor(prev);
|
||
if (prevBlock == myBlock) {
|
||
continue;
|
||
}
|
||
if (!candidate) {
|
||
candidate = prev;
|
||
candidateBlock = prevBlock;
|
||
} else if (prevBlock == candidateBlock) {
|
||
// Continue to walk until we reach the first fragment in the previous
|
||
// fragmentainer.
|
||
candidate = prev;
|
||
} else {
|
||
// Walked back past the candidate fragmentainer. Stop here.
|
||
break;
|
||
}
|
||
}
|
||
return candidate;
|
||
}
|
||
|
||
// Walk aInlineFrame's continuation chain and return the *first* continuation
|
||
// near the start-most edges of the next fragmentainer. Or return nullptr if no
|
||
// such fragment in the next fragmentainer or no next fragmentainer.
|
||
static nsIFrame* GetFirstInlineContinuationInNextFragmentainer(
|
||
nsIFrame* aInlineFrame) {
|
||
MOZ_ASSERT(aInlineFrame->IsInlineFrameOrSubclass());
|
||
const nsBlockFrame* myBlock =
|
||
nsLayoutUtils::FindNearestBlockAncestor(aInlineFrame);
|
||
for (nsIFrame* next =
|
||
nsLayoutUtils::GetNextContinuationOrIBSplitSibling(aInlineFrame);
|
||
next; next = nsLayoutUtils::GetNextContinuationOrIBSplitSibling(next)) {
|
||
if (next->IsBlockFrameOrSubclass()) {
|
||
// Skip IB-split block siblings.
|
||
continue;
|
||
}
|
||
if (nsLayoutUtils::FindNearestBlockAncestor(next) != myBlock) {
|
||
return next;
|
||
}
|
||
}
|
||
return nullptr;
|
||
}
|
||
|
||
// Return aFrame's first fragment in the previous fragmentainer, or nullptr if
|
||
// there is none. For a block CB this is just the prev-in-flow. For an inline
|
||
// CB, multiple fragments can live in a fragmentainer, but only the first
|
||
// fragment in each fragmentainer owns and reflows the abspos children, so we
|
||
// walk back to that first fragment rather than the immediate prev-in-flow.
|
||
static nsIFrame* GetFirstContinuationInPrevFragmentainer(nsIFrame* aFrame) {
|
||
return StaticPrefs::layout_abspos_fragment_aware_inline_cb_enabled() &&
|
||
aFrame->IsInlineFrameOrSubclass()
|
||
? GetFirstInlineContinuationInPrevFragmentainer(aFrame)
|
||
: aFrame->GetPrevInFlow();
|
||
}
|
||
|
||
// See the comment in GetFirstContinuationInPrevFragmentainer().
|
||
static nsIFrame* GetFirstContinuationInNextFragmentainer(nsIFrame* aFrame) {
|
||
return StaticPrefs::layout_abspos_fragment_aware_inline_cb_enabled() &&
|
||
aFrame->IsInlineFrameOrSubclass()
|
||
? GetFirstInlineContinuationInNextFragmentainer(aFrame)
|
||
: aFrame->GetNextInFlow();
|
||
}
|
||
|
||
nsFrameList AbsoluteContainingBlock::StealPushedChildList() {
|
||
return std::move(mPushedAbsoluteFrames);
|
||
}
|
||
|
||
void AbsoluteContainingBlock::DrainPushedChildList(
|
||
const nsIFrame* aDelegatingFrame) {
|
||
MOZ_ASSERT(aDelegatingFrame->GetAbsoluteContainingBlock() == this,
|
||
"aDelegatingFrame's absCB should be us!");
|
||
|
||
// 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.
|
||
for (auto iter = mPushedAbsoluteFrames.begin();
|
||
iter != mPushedAbsoluteFrames.end();) {
|
||
// Advance the iterator first, so it's safe to move |child|.
|
||
nsIFrame* const child = *iter++;
|
||
if (!child->GetPrevInFlow() ||
|
||
child->GetPrevInFlow()->GetParent() != aDelegatingFrame) {
|
||
mPushedAbsoluteFrames.RemoveFrame(child);
|
||
mAbsoluteFrames.AppendFrame(nullptr, child);
|
||
if (!child->GetPrevInFlow()) {
|
||
child->RemoveStateBits(NS_FRAME_IS_PUSHED_OUT_OF_FLOW);
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
bool AbsoluteContainingBlock::PrepareAbsoluteFrames(
|
||
nsContainerFrame* aDelegatingFrame) {
|
||
if (const nsIFrame* prev =
|
||
GetFirstContinuationInPrevFragmentainer(aDelegatingFrame)) {
|
||
AbsoluteContainingBlock* prevAbsCB = prev->GetAbsoluteContainingBlock();
|
||
MOZ_ASSERT(prevAbsCB,
|
||
"If this delegating frame has an absCB, |prev| 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));
|
||
|
||
// After stealing children from the previous absCB, traverse our children
|
||
// and see if any child has a prev-in-flow that is also in our child list.
|
||
// If so, we insert them at the front of our pushed child list.
|
||
nsFrameList newPushedAbsoluteFrames;
|
||
for (auto iter = mAbsoluteFrames.begin();
|
||
iter != mAbsoluteFrames.end();) {
|
||
// Advance the iterator first, so it's safe to move |child|.
|
||
nsIFrame* const child = *iter++;
|
||
nsIFrame* const childPrevInFlow = child->GetPrevInFlow();
|
||
if (childPrevInFlow &&
|
||
childPrevInFlow->GetParent() == aDelegatingFrame) {
|
||
mAbsoluteFrames.RemoveFrame(child);
|
||
newPushedAbsoluteFrames.AppendFrame(nullptr, child);
|
||
}
|
||
}
|
||
if (newPushedAbsoluteFrames.NotEmpty()) {
|
||
// Prepend the new pushed frames to the front of mPushedAbsoluteFrames.
|
||
mPushedAbsoluteFrames.InsertFrames(nullptr, nullptr,
|
||
std::move(newPushedAbsoluteFrames));
|
||
}
|
||
}
|
||
}
|
||
|
||
// Similarly, for any children in our pushed child list that don't have a
|
||
// prev-in-flow in our regular child list, we move those children back into
|
||
// our child list.
|
||
DrainPushedChildList(aDelegatingFrame);
|
||
|
||
// Steal absolute frame's first-in-flow from the child list of our
|
||
// continuations that appear as the first continuation in each fragmentainer.
|
||
for (nsIFrame* next =
|
||
GetFirstContinuationInNextFragmentainer(aDelegatingFrame);
|
||
next; next = GetFirstContinuationInNextFragmentainer(next)) {
|
||
AbsoluteContainingBlock* nextAbsCB = next->GetAbsoluteContainingBlock();
|
||
MOZ_ASSERT(nextAbsCB,
|
||
"If this delegating frame has an absCB, |next| must "
|
||
"have one, too!");
|
||
|
||
nextAbsCB->DrainPushedChildList(next);
|
||
|
||
for (auto iter = nextAbsCB->GetChildList().begin();
|
||
iter != nextAbsCB->GetChildList().end();) {
|
||
// Advance the iterator first, so it's safe to move |child|.
|
||
nsIFrame* const child = *iter++;
|
||
if (!child->GetPrevInFlow()) {
|
||
nextAbsCB->StealFrame(child);
|
||
mAbsoluteFrames.AppendFrame(aDelegatingFrame, child);
|
||
child->RemoveStateBits(NS_FRAME_IS_PUSHED_OUT_OF_FLOW);
|
||
}
|
||
}
|
||
}
|
||
|
||
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!");
|
||
}
|
||
|
||
#ifdef DEBUG
|
||
void AbsoluteContainingBlock::SanityCheckChildListsBeforeReflow(
|
||
const nsIFrame* aDelegatingFrame) const {
|
||
// TODO(TYLin): This is potentially O(N^2), where N is the number of
|
||
// continuations that an abspos frame gets. Consider putting this behind an
|
||
// about:config pref if it turns out to slow down debug builds too much.
|
||
for (const nsIFrame* child : mAbsoluteFrames) {
|
||
for (nsIFrame* prev = child->GetPrevInFlow(); prev;
|
||
prev = prev->GetPrevInFlow()) {
|
||
MOZ_ASSERT(!GetChildList().ContainsFrame(prev),
|
||
"It is wrong that both a child and its prev-in-flow are in "
|
||
"our child list!");
|
||
}
|
||
}
|
||
|
||
{
|
||
// Verify that continuations are ordered across both lists concatenated.
|
||
// If a frame and its continuation are both present, the continuation must
|
||
// appear later.
|
||
nsTHashSet<const nsIFrame*> allFrames;
|
||
for (const nsFrameList* list : {&mAbsoluteFrames, &mPushedAbsoluteFrames}) {
|
||
for (const nsIFrame* child : *list) {
|
||
allFrames.Insert(child);
|
||
}
|
||
}
|
||
|
||
nsTHashSet<const nsIFrame*> seen;
|
||
auto CheckOrder = [&](const nsIFrame* child) {
|
||
seen.Insert(child);
|
||
const nsIFrame* prev = child->GetPrevInFlow();
|
||
if (prev && allFrames.Contains(prev)) {
|
||
MOZ_ASSERT(seen.Contains(prev),
|
||
"A frame's continuation appears before the frame in "
|
||
"mAbsoluteFrames + mPushedAbsoluteFrames!");
|
||
}
|
||
};
|
||
for (const nsFrameList* list : {&mAbsoluteFrames, &mPushedAbsoluteFrames}) {
|
||
for (const nsIFrame* child : *list) {
|
||
CheckOrder(child);
|
||
}
|
||
}
|
||
}
|
||
|
||
for (const nsIFrame* next = aDelegatingFrame->GetNextInFlow(); next;
|
||
next = next->GetNextInFlow()) {
|
||
auto* nextAbsCB = next->GetAbsoluteContainingBlock();
|
||
MOZ_ASSERT(nextAbsCB,
|
||
"Delegating frame's next-in-flow should have "
|
||
"AbsoluteContainingBlock!");
|
||
for (nsIFrame* child : nextAbsCB->GetChildList()) {
|
||
MOZ_ASSERT(
|
||
child->GetPrevInFlow(),
|
||
"We should've pulled all abspos first-in-flows to our child list!");
|
||
}
|
||
}
|
||
}
|
||
#endif
|
||
|
||
static void MaybeMarkAncestorsAsHavingDescendantDependentOnItsStaticPos(
|
||
nsIFrame* aFrame, nsIFrame* aContainingBlockFrame) {
|
||
MOZ_ASSERT(aFrame->HasAnyStateBits(NS_FRAME_OUT_OF_FLOW));
|
||
if (!aFrame->StylePosition()->NeedsHypotheticalPositionIfAbsPos()) {
|
||
return;
|
||
}
|
||
// We should have set the bit when reflowing the previous continuations
|
||
// already.
|
||
if (aFrame->GetPrevContinuation()) {
|
||
return;
|
||
}
|
||
|
||
auto* placeholder = aFrame->GetPlaceholderFrame();
|
||
MOZ_ASSERT(placeholder);
|
||
|
||
// Only fixed-pos frames can escape their containing block.
|
||
if (!placeholder->HasAnyStateBits(PLACEHOLDER_FOR_FIXEDPOS)) {
|
||
return;
|
||
}
|
||
|
||
for (nsIFrame* ancestor = placeholder->GetParent(); ancestor;
|
||
ancestor = ancestor->GetParent()) {
|
||
// Walk towards the ancestor's first continuation. That's the only one that
|
||
// really matters, since it's the only one restyling will look at. We also
|
||
// flag the following continuations just so it's caught on the first
|
||
// early-return ones just to avoid walking them over and over.
|
||
do {
|
||
if (ancestor->DescendantMayDependOnItsStaticPosition()) {
|
||
return;
|
||
}
|
||
// Moving the containing block or anything above it would move our static
|
||
// position as well, so no need to flag it or any of its ancestors.
|
||
if (aFrame == aContainingBlockFrame) {
|
||
return;
|
||
}
|
||
ancestor->SetDescendantMayDependOnItsStaticPosition(true);
|
||
nsIFrame* prev = ancestor->GetPrevContinuation();
|
||
if (!prev) {
|
||
break;
|
||
}
|
||
ancestor = prev;
|
||
} while (true);
|
||
}
|
||
}
|
||
|
||
static bool IsSnapshotContainingBlock(const nsIFrame* aFrame) {
|
||
return aFrame->Style()->GetPseudoType() ==
|
||
PseudoStyleType::MozSnapshotContainingBlock;
|
||
}
|
||
|
||
static PhysicalAxes CheckEarlyCompensatingForScroll(const nsIFrame* aKidFrame) {
|
||
// Three conditions to compensate for scroll, once a default anchor
|
||
// exists:
|
||
// * Used alignment property is `anchor-center`,
|
||
// * `position-area` is not `none`, or
|
||
// * `anchor()` function refers to default anchor, or an anchor that
|
||
// shares the same scroller with it.
|
||
// First two conditions are checkable right now, so do that.
|
||
if (!aKidFrame->StylePosition()->mPositionArea.IsNone()) {
|
||
return PhysicalAxes{PhysicalAxis::Horizontal, PhysicalAxis::Vertical};
|
||
}
|
||
PhysicalAxes result;
|
||
const auto cbwm = aKidFrame->GetParent()->GetWritingMode();
|
||
// We don't concern ourselves with align/justify-items here, because
|
||
// they don't apply to absolute positioned boxes [1].
|
||
// [1]: https://drafts.csswg.org/css-align-3/#justify-self-property
|
||
if (aKidFrame->StylePosition()->mAlignSelf._0 &
|
||
StyleAlignFlags::ANCHOR_CENTER) {
|
||
result +=
|
||
cbwm.IsVertical() ? PhysicalAxis::Horizontal : PhysicalAxis::Vertical;
|
||
}
|
||
if (aKidFrame->StylePosition()->mJustifySelf._0 &
|
||
StyleAlignFlags::ANCHOR_CENTER) {
|
||
result +=
|
||
cbwm.IsVertical() ? PhysicalAxis::Vertical : PhysicalAxis::Horizontal;
|
||
}
|
||
return result;
|
||
}
|
||
|
||
static AnchorPosResolutionCache PopulateAnchorResolutionCache(
|
||
const nsIFrame* aKidFrame, AnchorPosReferenceData* aData,
|
||
bool aReuseUnfragmentedAnchorPosReferences) {
|
||
MOZ_ASSERT(aKidFrame->HasAnchorPosReference());
|
||
if (aReuseUnfragmentedAnchorPosReferences) [[unlikely]] {
|
||
MOZ_ASSERT(
|
||
aKidFrame->FirstInFlow()->HasProperty(UnfragmentedPositionProperty()));
|
||
// We inherited reference data from unfragmented reflow, but still need to
|
||
// repopulate the cache.
|
||
AnchorPosDefaultAnchorCache cache;
|
||
if (aData->mDefaultAnchorName) {
|
||
const auto* presShell = aKidFrame->PresShell();
|
||
cache.mAnchor = presShell->GetAnchorPosAnchor(
|
||
ScopedNameRef{aData->mDefaultAnchorName, aData->mAnchorTreeScope},
|
||
aKidFrame->FirstInFlow());
|
||
MOZ_ASSERT(cache.mAnchor);
|
||
cache.mScrollContainer =
|
||
AnchorPositioningUtils::GetNearestScrollFrame(cache.mAnchor)
|
||
.mScrollContainer;
|
||
}
|
||
return {aData, cache};
|
||
}
|
||
|
||
// If the default anchor exists, it will likely be referenced (Except when
|
||
// authors then use `anchor()` without referring to anchors whose nearest
|
||
// scroller that of the default anchor, but that seems
|
||
// counter-productive). This is a prerequisite for scroll compensation. We
|
||
// also need to check for `anchor()` resolutions, so cache information for
|
||
// default anchor and its scrollers right now.
|
||
AnchorPosResolutionCache result{aData, {}};
|
||
// Let this call populate the cache.
|
||
const auto defaultAnchorInfo = AnchorPositioningUtils::ResolveAnchorPosRect(
|
||
aKidFrame, aKidFrame->GetParent(),
|
||
{nullptr, StyleCascadeLevel::Default()}, false, &result);
|
||
if (defaultAnchorInfo) {
|
||
aData->AdjustCompensatingForScroll(
|
||
CheckEarlyCompensatingForScroll(aKidFrame));
|
||
}
|
||
return result;
|
||
}
|
||
|
||
static nsRect ComputeScrollableContainingBlock(
|
||
const nsContainerFrame* aDelegatingFrame, const nsRect& aContainingBlock,
|
||
const OverflowAreas* aOverflowAreas) {
|
||
if (aOverflowAreas && aDelegatingFrame->Style()->GetPseudoType() ==
|
||
PseudoStyleType::MozScrolledContent) {
|
||
// FIXME(bug 2004432): This is close enough to what we want. In practice
|
||
// we don't want to account for relative positioning and so on, but this
|
||
// seems good enough for now.
|
||
ScrollContainerFrame* sf = do_QueryFrame(aDelegatingFrame->GetParent());
|
||
// Clamp to the scrollable range.
|
||
return sf->GetUnsnappedScrolledRectInternal(
|
||
aOverflowAreas->ScrollableOverflow(), aContainingBlock.Size());
|
||
}
|
||
return aContainingBlock;
|
||
}
|
||
|
||
static SideBits GetScrollCompensatedSidesFor(
|
||
const StylePositionArea& aPositionArea) {
|
||
SideBits sides{SideBits::eNone};
|
||
// The opposite side of the direction keyword is attached to the
|
||
// position-anchor grid, which is then attached to the anchor, and so is
|
||
// scroll compensated. `center` is constrained by the position-area grid
|
||
// on both sides. `span-all` is unconstrained in that axis.
|
||
if (aPositionArea.first == StylePositionAreaKeyword::Left ||
|
||
aPositionArea.first == StylePositionAreaKeyword::SpanLeft) {
|
||
sides |= SideBits::eRight;
|
||
} else if (aPositionArea.first == StylePositionAreaKeyword::Right ||
|
||
aPositionArea.first == StylePositionAreaKeyword::SpanRight) {
|
||
sides |= SideBits::eLeft;
|
||
} else if (aPositionArea.first == StylePositionAreaKeyword::Center) {
|
||
sides |= SideBits::eLeftRight;
|
||
}
|
||
|
||
if (aPositionArea.second == StylePositionAreaKeyword::Top ||
|
||
aPositionArea.second == StylePositionAreaKeyword::SpanTop) {
|
||
sides |= SideBits::eBottom;
|
||
} else if (aPositionArea.second == StylePositionAreaKeyword::Bottom ||
|
||
aPositionArea.second == StylePositionAreaKeyword::SpanBottom) {
|
||
sides |= SideBits::eTop;
|
||
} else if (aPositionArea.second == StylePositionAreaKeyword::Center) {
|
||
sides |= SideBits::eTopBottom;
|
||
}
|
||
|
||
return sides;
|
||
}
|
||
|
||
struct ModifiedContainingBlock {
|
||
using AnchorOffsetInfo = AbsoluteContainingBlock::AnchorOffsetInfo;
|
||
|
||
Maybe<AnchorOffsetInfo> mAnchorOffsetInfo;
|
||
// Unmodified scrollable or local containing block
|
||
nsRect mMaybeScrollableRect;
|
||
// Containing block after all its modifications e.g. By grid/position-area.
|
||
nsRect mFinalRect;
|
||
|
||
explicit ModifiedContainingBlock(const nsRect& aRect)
|
||
: mMaybeScrollableRect{aRect}, mFinalRect{aRect} {}
|
||
ModifiedContainingBlock(const nsRect& aMaybeScrollableRect,
|
||
const nsRect& aFinalRect)
|
||
: mMaybeScrollableRect{aMaybeScrollableRect}, mFinalRect{aFinalRect} {}
|
||
ModifiedContainingBlock(const nsPoint& aOffset,
|
||
const StylePositionArea& aResolvedArea,
|
||
const nsRect& aMaybeScrollableRect,
|
||
const nsRect& aFinalRect)
|
||
: mAnchorOffsetInfo{Some(AnchorOffsetInfo{aOffset, aResolvedArea})},
|
||
mMaybeScrollableRect{aMaybeScrollableRect},
|
||
mFinalRect{aFinalRect} {}
|
||
|
||
AnchorOffsetInfo GetAnchorOffsetInfo() const {
|
||
return mAnchorOffsetInfo.valueOr(AnchorOffsetInfo{});
|
||
}
|
||
StylePositionArea ResolvedPositionArea() const {
|
||
return mAnchorOffsetInfo
|
||
.map([](const AnchorOffsetInfo& aInfo) {
|
||
return aInfo.mResolvedPositionArea;
|
||
})
|
||
.valueOr(StylePositionArea{});
|
||
}
|
||
};
|
||
|
||
static ModifiedContainingBlock ComputeContainingBlock(
|
||
bool aIsGrid, const nsContainerFrame* aDelegatingFrame,
|
||
const ReflowInput& aReflowInput,
|
||
const AbsoluteContainingBlock::ContainingBlockRects& aContainingBlockRects,
|
||
nsIFrame* aKidFrame, AnchorPosResolutionCache* aAnchorPosResolutionCache,
|
||
bool aReuseUnfragmentedAnchorPosReferences) {
|
||
if (aReuseUnfragmentedAnchorPosReferences) {
|
||
MOZ_ASSERT(aAnchorPosResolutionCache);
|
||
const auto* referenceData = aAnchorPosResolutionCache->mReferenceData;
|
||
if (const auto positionArea = aKidFrame->StylePosition()->mPositionArea;
|
||
!positionArea.IsNone()) {
|
||
return ModifiedContainingBlock{
|
||
referenceData->mDefaultScrollShift,
|
||
AnchorPositioningUtils::PhysicalizePositionArea(positionArea,
|
||
aKidFrame),
|
||
referenceData->mOriginalContainingBlockRect,
|
||
referenceData->mAdjustedContainingBlock};
|
||
}
|
||
return ModifiedContainingBlock{referenceData->mOriginalContainingBlockRect,
|
||
referenceData->mAdjustedContainingBlock};
|
||
}
|
||
// The current containing block, with ongoing modifications.
|
||
// Starts as a local containing block.
|
||
nsRect containingBlock = aContainingBlockRects.mLocal;
|
||
nsRect scrollableContainingBlock = aContainingBlockRects.mScrollable;
|
||
const auto defaultAnchorInfo = [&]() -> Maybe<AnchorPosInfo> {
|
||
if (!aAnchorPosResolutionCache) {
|
||
return Nothing{};
|
||
}
|
||
return AnchorPositioningUtils::ResolveAnchorPosRect(
|
||
aKidFrame, aDelegatingFrame, {nullptr, StyleCascadeLevel::Default()},
|
||
false, aAnchorPosResolutionCache);
|
||
}();
|
||
if (defaultAnchorInfo) {
|
||
// Presence of a valid default anchor causes us to use the scrollable
|
||
// containing block.
|
||
// https://github.com/w3c/csswg-drafts/issues/12552#issuecomment-3210696721
|
||
containingBlock = aContainingBlockRects.mScrollable;
|
||
}
|
||
|
||
if (const ViewportFrame* viewport = do_QueryFrame(aDelegatingFrame)) {
|
||
if (IsSnapshotContainingBlock(aKidFrame)) {
|
||
return ModifiedContainingBlock{
|
||
dom::ViewTransition::SnapshotContainingBlockRect(
|
||
viewport->PresContext())};
|
||
}
|
||
MOZ_ASSERT(aContainingBlockRects.mScrollable ==
|
||
aContainingBlockRects.mLocal);
|
||
containingBlock = scrollableContainingBlock =
|
||
viewport->GetContainingBlockAdjustedForScrollbars(aReflowInput);
|
||
}
|
||
|
||
// https://drafts.csswg.org/css-position/#original-cb
|
||
// Handle grid-based adjustment first...
|
||
if (aIsGrid) {
|
||
const auto border = aDelegatingFrame->GetUsedBorder();
|
||
const nsPoint borderShift{border.left, border.top};
|
||
const nsRect preGridCB = containingBlock;
|
||
// Shift in by border of the overall grid container.
|
||
containingBlock = nsGridContainerFrame::GridItemCB(aKidFrame) + borderShift;
|
||
if (!defaultAnchorInfo) {
|
||
return ModifiedContainingBlock{preGridCB, containingBlock};
|
||
}
|
||
}
|
||
// ... Then the position-area based adjustment.
|
||
if (defaultAnchorInfo) {
|
||
auto positionArea = aKidFrame->StylePosition()->mPositionArea;
|
||
// Offset should be up to, but not including the containing block's
|
||
// scroll offset.
|
||
const auto offset = AnchorPositioningUtils::GetScrollOffsetFor(
|
||
aAnchorPosResolutionCache->mReferenceData->CompensatingForScrollAxes(),
|
||
aKidFrame, aAnchorPosResolutionCache->mDefaultAnchorCache);
|
||
StylePositionArea resolvedPositionArea{};
|
||
if (!positionArea.IsNone()) {
|
||
// Imagine an abspos container with a scroller in it, and then an
|
||
// anchor in it, where the anchor is visually in the middle of the
|
||
// scrollport. Then, when the scroller moves such that the anchor's
|
||
// left edge is on that of the scrollports, w.r.t. containing block,
|
||
// the anchor is zero left offset horizontally. The position-area
|
||
// grid needs to account for this.
|
||
const auto scrolledAnchorRect = defaultAnchorInfo->mRect - offset;
|
||
const auto scrolledAnchorCb = AnchorPositioningUtils::
|
||
AdjustAbsoluteContainingBlockRectForPositionArea(
|
||
scrolledAnchorRect + aContainingBlockRects.mLocal.TopLeft(),
|
||
containingBlock, aKidFrame->GetWritingMode(),
|
||
aDelegatingFrame->GetWritingMode(), positionArea,
|
||
&resolvedPositionArea);
|
||
// By definition, we're using the default anchor, and are scroll
|
||
// compensated.
|
||
aAnchorPosResolutionCache->mReferenceData->mScrollCompensatedSides =
|
||
GetScrollCompensatedSidesFor(resolvedPositionArea);
|
||
// Unscroll the CB by canceling out the previously applied
|
||
// scroll offset (See above), the offset will be applied later.
|
||
containingBlock = scrolledAnchorCb + offset;
|
||
}
|
||
return ModifiedContainingBlock{offset, resolvedPositionArea,
|
||
scrollableContainingBlock, containingBlock};
|
||
}
|
||
return ModifiedContainingBlock{containingBlock};
|
||
}
|
||
|
||
void AbsoluteContainingBlock::Reflow(nsContainerFrame* aDelegatingFrame,
|
||
nsPresContext* aPresContext,
|
||
const ReflowInput& aReflowInput,
|
||
nsReflowStatus& aReflowStatus,
|
||
const nsRect& aContainingBlock,
|
||
AbsPosReflowFlags aFlags,
|
||
OverflowAreas* aOverflowAreas) {
|
||
MOZ_ASSERT(aReflowStatus.IsEmpty(),
|
||
"Caller should pass a fresh reflow status!");
|
||
|
||
const auto scrollableContainingBlock = ComputeScrollableContainingBlock(
|
||
aDelegatingFrame, aContainingBlock, aOverflowAreas);
|
||
const ContainingBlockRects passedContainingBlock{aContainingBlock,
|
||
scrollableContainingBlock};
|
||
|
||
const auto* unfragmentedContainingBlockRects =
|
||
[&]() -> const ContainingBlockRects* {
|
||
if (aReflowInput.mFlags.mIsInFragmentainerMeasuringReflow) {
|
||
// Doing the measuring reflow, so set the unfragmented containing sizes
|
||
// here.
|
||
NS_WARNING_ASSERTION(aDelegatingFrame->FirstInFlow() == aDelegatingFrame,
|
||
"Saving unfragmented CB into non-first-in-flow");
|
||
aDelegatingFrame->SetOrUpdateDeletableProperty(
|
||
UnfragmentedContainingBlockProperty(), passedContainingBlock);
|
||
// Just reuse what was passed in.
|
||
return &passedContainingBlock;
|
||
}
|
||
if (const auto* unfragmented = aDelegatingFrame->FirstInFlow()->GetProperty(
|
||
UnfragmentedContainingBlockProperty())) {
|
||
return unfragmented;
|
||
}
|
||
return &passedContainingBlock;
|
||
}();
|
||
|
||
const auto* fragmentedContainingBlockRects =
|
||
unfragmentedContainingBlockRects != &passedContainingBlock
|
||
? &passedContainingBlock
|
||
: nullptr;
|
||
|
||
#ifdef DEBUG
|
||
SanityCheckChildListsBeforeReflow(aDelegatingFrame);
|
||
#endif
|
||
|
||
if (const nsIFrame* prev =
|
||
GetFirstContinuationInPrevFragmentainer(aDelegatingFrame)) {
|
||
const auto* prevAbsCB = prev->GetAbsoluteContainingBlock();
|
||
MOZ_ASSERT(prevAbsCB,
|
||
"If this delegating frame has an absCB, |prev| must "
|
||
"have one, too!");
|
||
mCumulativeContainingBlockBSize =
|
||
prevAbsCB->mCumulativeContainingBlockBSize;
|
||
} else {
|
||
mCumulativeContainingBlockBSize = 0;
|
||
}
|
||
|
||
// Assume all the kids may need a reflow when they are in a fragmented
|
||
// context. We'll perform more targeted check below. For example, skip reflow
|
||
// them when they are positioned in a later fragment.
|
||
const bool reflowAll = aReflowInput.ShouldReflowAllKids() ||
|
||
aReflowInput.IsInFragmentedContext();
|
||
const bool cbWidthChanged = aFlags.contains(AbsPosReflowFlag::CBWidthChanged);
|
||
const bool cbHeightChanged =
|
||
aFlags.contains(AbsPosReflowFlag::CBHeightChanged);
|
||
const nscoord availBSize = aReflowInput.AvailableBSize();
|
||
const WritingMode containerWM = aReflowInput.GetWritingMode();
|
||
nsFrameList newPushedAbsoluteFrames;
|
||
for (auto iter = mAbsoluteFrames.begin(); iter != mAbsoluteFrames.end();) {
|
||
// Advance the iterator first, so it's safe to move |kidFrame|.
|
||
nsIFrame* const kidFrame = *iter++;
|
||
bool reuseUnfragmentedAnchorPosReferences = false;
|
||
Maybe<AnchorPosResolutionCache> anchorPosResolutionCache;
|
||
if (kidFrame->HasAnchorPosReference()) {
|
||
AnchorPosReferenceData* referenceData = nullptr;
|
||
if (const auto* firstInFlow = kidFrame->FirstInFlow();
|
||
GetUnfragmentedPosition(aReflowInput, firstInFlow)) {
|
||
// Ok, we've done a measuring reflow with no fragmentation, and so the
|
||
// unfragmented position property is now set. Use the existing
|
||
// references, which contains the anchor lookup data from the measuring
|
||
// reflow.
|
||
referenceData =
|
||
firstInFlow->GetProperty(nsIFrame::AnchorPosReferences());
|
||
reuseUnfragmentedAnchorPosReferences = true;
|
||
}
|
||
if (!referenceData) {
|
||
referenceData = kidFrame->SetOrUpdateDeletableProperty(
|
||
nsIFrame::AnchorPosReferences());
|
||
}
|
||
anchorPosResolutionCache = Some(PopulateAnchorResolutionCache(
|
||
kidFrame, referenceData, reuseUnfragmentedAnchorPosReferences));
|
||
} else {
|
||
kidFrame->RemoveProperty(nsIFrame::AnchorPosReferences());
|
||
}
|
||
|
||
bool kidNeedsReflow =
|
||
reflowAll || kidFrame->IsSubtreeDirty() ||
|
||
FrameDependsOnContainer(kidFrame, cbWidthChanged, cbHeightChanged,
|
||
anchorPosResolutionCache.ptrOr(nullptr));
|
||
if (kidFrame->IsSubtreeDirty()) {
|
||
MaybeMarkAncestorsAsHavingDescendantDependentOnItsStaticPos(
|
||
kidFrame, aDelegatingFrame);
|
||
}
|
||
if (kidNeedsReflow && !aPresContext->HasPendingInterrupt()) {
|
||
const LogicalSize cbSize(containerWM,
|
||
unfragmentedContainingBlockRects->mLocal.Size());
|
||
const LogicalMargin border =
|
||
aDelegatingFrame->GetLogicalUsedBorder(containerWM)
|
||
.ApplySkipSides(
|
||
aDelegatingFrame->PreReflowBlockLevelLogicalSkipSides());
|
||
const nsSize cbBorderBoxSize =
|
||
(cbSize + border.Size(containerWM)).GetPhysicalSize(containerWM);
|
||
|
||
bool kidFrameNeedsPush = false;
|
||
if (const auto* unfragPos =
|
||
GetUnfragmentedPosition(aReflowInput, kidFrame);
|
||
unfragPos && availBSize != NS_UNCONSTRAINEDSIZE) {
|
||
// If kidFrame's position in this fragment is beyond the end of this
|
||
// fragmentainer, push it to the next fragmentainer.
|
||
const nscoord kidBPosInThisFragment =
|
||
unfragPos->B(containerWM) - mCumulativeContainingBlockBSize;
|
||
if (kidBPosInThisFragment > availBSize) {
|
||
kidFrameNeedsPush = true;
|
||
}
|
||
}
|
||
|
||
OverflowAreas kidOverflowAreas;
|
||
nsReflowStatus kidStatus;
|
||
if (!kidFrameNeedsPush) {
|
||
ReflowAbsoluteFrame(aDelegatingFrame, aPresContext, aReflowInput,
|
||
*unfragmentedContainingBlockRects, aFlags, kidFrame,
|
||
kidStatus, aOverflowAreas,
|
||
fragmentedContainingBlockRects,
|
||
anchorPosResolutionCache.ptrOr(nullptr),
|
||
reuseUnfragmentedAnchorPosReferences);
|
||
|
||
if (aReflowInput.mFlags.mIsInFragmentainerMeasuringReflow) {
|
||
LogicalPoint unfragPos(containerWM);
|
||
if (StaticPrefs::layout_abspos_fragment_aware_inline_cb_enabled() &&
|
||
aDelegatingFrame->IsInlineFrameOrSubclass()) {
|
||
// Translate the kid's unfragmented position to the block ancestor's
|
||
// coordinate space so that it is easier to compute its position
|
||
// later in the normal fragmented reflow.
|
||
nsIFrame* blockAncestor =
|
||
nsLayoutUtils::FindNearestBlockAncestor(aDelegatingFrame);
|
||
nsSize blockAncestorSize =
|
||
aReflowInput.mContainingBlockSize.GetPhysicalSize(containerWM);
|
||
nsRect kidRect = kidFrame->GetRectRelativeToSelf() +
|
||
kidFrame->GetOffsetTo(blockAncestor);
|
||
unfragPos = LogicalRect(containerWM, kidRect, blockAncestorSize)
|
||
.Origin(containerWM);
|
||
} else {
|
||
unfragPos =
|
||
kidFrame->GetLogicalPosition(containerWM, cbBorderBoxSize);
|
||
}
|
||
kidFrame->SetOrUpdateDeletableProperty(UnfragmentedPositionProperty(),
|
||
unfragPos);
|
||
|
||
const LogicalSize kidSize =
|
||
kidFrame->StylePosition()->mBoxSizing == StyleBoxSizing::BorderBox
|
||
? kidFrame->GetLogicalSize()
|
||
: kidFrame->ContentSize();
|
||
kidFrame->SetOrUpdateDeletableProperty(UnfragmentedSizeProperty(),
|
||
kidSize);
|
||
|
||
// kidFrame must be a first-in-flow here. In a measuring reflow
|
||
// starting in the first column, we only see first-in-flows (either
|
||
// unsplit or pulled back from later continuations of this absolute
|
||
// containing block). However, in an incremental measuring reflow, if
|
||
// the first-in-flow is not fully-complete, it is possible that we
|
||
// still reflow continuations here.
|
||
NS_ASSERTION(
|
||
!kidFrame->GetPrevInFlow(),
|
||
"UnfragmentedPositionProperty and UnfragmentedSizeProperty "
|
||
"should only be set on first-in-flow!");
|
||
}
|
||
MOZ_ASSERT(!kidStatus.IsInlineBreakBefore(),
|
||
"ShouldAvoidBreakInside should prevent this from happening");
|
||
}
|
||
|
||
nsIFrame* nextFrame = kidFrame->GetNextInFlow();
|
||
if (kidFrameNeedsPush) {
|
||
StealFrame(kidFrame);
|
||
kidFrame->AddStateBits(NS_FRAME_IS_PUSHED_OUT_OF_FLOW);
|
||
newPushedAbsoluteFrames.AppendFrame(nullptr, kidFrame);
|
||
} else if (!kidStatus.IsFullyComplete()) {
|
||
if (!nextFrame) {
|
||
nextFrame = aPresContext->PresShell()
|
||
->FrameConstructor()
|
||
->CreateContinuingFrame(kidFrame, aDelegatingFrame);
|
||
nextFrame->AddStateBits(NS_FRAME_IS_PUSHED_OUT_OF_FLOW);
|
||
newPushedAbsoluteFrames.AppendFrame(nullptr, nextFrame);
|
||
} else if (nextFrame->GetParent() !=
|
||
aDelegatingFrame->GetNextInFlow()) {
|
||
nextFrame->GetParent()->GetAbsoluteContainingBlock()->StealFrame(
|
||
nextFrame);
|
||
// nextFrame is in a later absCB continuation. To keep the
|
||
// continuations in order, append it to mPushedAbsoluteFrames.
|
||
mPushedAbsoluteFrames.AppendFrame(aDelegatingFrame, nextFrame);
|
||
}
|
||
aReflowStatus.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 (aOverflowAreas) {
|
||
aDelegatingFrame->ConsiderChildOverflow(*aOverflowAreas, kidFrame);
|
||
}
|
||
}
|
||
|
||
// Make a CheckForInterrupt call, here, not just HasPendingInterrupt. That
|
||
// will make sure that we end up reflowing aDelegatingFrame in cases when
|
||
// one of our kids interrupted. Otherwise we'd set the dirty or
|
||
// dirty-children bit on the kid in the condition below, and then when
|
||
// reflow completes and we go to mark dirty bits on all ancestors of that
|
||
// kid we'll immediately bail out, because the kid already has a dirty bit.
|
||
// In particular, we won't set any dirty bits on aDelegatingFrame, so when
|
||
// the following reflow happens we won't reflow the kid in question. This
|
||
// might be slightly suboptimal in cases where |kidFrame| itself did not
|
||
// interrupt, since we'll trigger a reflow of it too when it's not strictly
|
||
// needed. But the logic to not do that is enough more complicated, and
|
||
// the case enough of an edge case, that this is probably better.
|
||
if (kidNeedsReflow && aPresContext->CheckForInterrupt(aDelegatingFrame)) {
|
||
if (aDelegatingFrame->HasAnyStateBits(NS_FRAME_IS_DIRTY)) {
|
||
kidFrame->MarkSubtreeDirty();
|
||
} else {
|
||
kidFrame->AddStateBits(NS_FRAME_HAS_DIRTY_CHILDREN);
|
||
}
|
||
}
|
||
}
|
||
|
||
if (newPushedAbsoluteFrames.NotEmpty()) {
|
||
// Prepend the new pushed frames to the front of mPushedAbsoluteFrames.
|
||
mPushedAbsoluteFrames.InsertFrames(nullptr, nullptr,
|
||
std::move(newPushedAbsoluteFrames));
|
||
}
|
||
|
||
if (availBSize != NS_UNCONSTRAINEDSIZE) {
|
||
mCumulativeContainingBlockBSize += availBSize;
|
||
}
|
||
|
||
// Abspos frames can't cause their parent to be incomplete,
|
||
// only overflow incomplete.
|
||
if (aReflowStatus.IsIncomplete() || mPushedAbsoluteFrames.NotEmpty()) {
|
||
aReflowStatus.SetOverflowIncomplete();
|
||
aReflowStatus.SetNextInFlowNeedsReflow();
|
||
}
|
||
}
|
||
|
||
static inline bool IsFixedPaddingSize(const LengthPercentage& aCoord) {
|
||
return aCoord.ConvertsToLength();
|
||
}
|
||
static inline bool IsFixedMarginSize(const AnchorResolvedMargin& aCoord) {
|
||
return aCoord->ConvertsToLength();
|
||
}
|
||
static inline bool IsFixedOffset(const AnchorResolvedInset& aInset) {
|
||
// For anchor positioning functions, even if the computed value may be a
|
||
// fixed length, it depends on the absolute containing block's size.
|
||
return aInset->ConvertsToLength();
|
||
}
|
||
|
||
bool AbsoluteContainingBlock::FrameDependsOnContainer(
|
||
nsIFrame* f, bool aCBWidthChanged, bool aCBHeightChanged,
|
||
AnchorPosResolutionCache* aAnchorPosResolutionCache) {
|
||
const nsStylePosition* pos = f->StylePosition();
|
||
// See if f's position might have changed because it depends on a
|
||
// placeholder's position.
|
||
if (pos->NeedsHypotheticalPositionIfAbsPos()) {
|
||
return true;
|
||
}
|
||
if (!aCBWidthChanged && !aCBHeightChanged) {
|
||
// skip getting style data
|
||
return false;
|
||
}
|
||
const nsStylePadding* padding = f->StylePadding();
|
||
const nsStyleMargin* margin = f->StyleMargin();
|
||
WritingMode wm = f->GetWritingMode();
|
||
const auto anchorResolutionParams =
|
||
AnchorPosResolutionParams::From(f, aAnchorPosResolutionCache);
|
||
if (wm.IsVertical() ? aCBHeightChanged : aCBWidthChanged) {
|
||
// See if f's inline-size might have changed.
|
||
// If margin-inline-start/end, padding-inline-start/end,
|
||
// inline-size, min/max-inline-size are all lengths, 'none', or enumerated,
|
||
// then our frame isize does not depend on the parent isize.
|
||
// Note that borders never depend on the parent isize.
|
||
// XXX All of the enumerated values except -moz-available are ok too.
|
||
if (nsStylePosition::ISizeDependsOnContainer(
|
||
pos->ISize(wm, anchorResolutionParams)) ||
|
||
nsStylePosition::MinISizeDependsOnContainer(
|
||
pos->MinISize(wm, anchorResolutionParams)) ||
|
||
nsStylePosition::MaxISizeDependsOnContainer(
|
||
pos->MaxISize(wm, anchorResolutionParams)) ||
|
||
!IsFixedPaddingSize(padding->mPadding.GetIStart(wm)) ||
|
||
!IsFixedPaddingSize(padding->mPadding.GetIEnd(wm))) {
|
||
return true;
|
||
}
|
||
|
||
// See if f's position might have changed. If we're RTL then the
|
||
// rules are slightly different. We'll assume percentage or auto
|
||
// margins will always induce a dependency on the size
|
||
if (!IsFixedMarginSize(margin->GetMargin(LogicalSide::IStart, wm,
|
||
anchorResolutionParams)) ||
|
||
!IsFixedMarginSize(
|
||
margin->GetMargin(LogicalSide::IEnd, wm, anchorResolutionParams))) {
|
||
return true;
|
||
}
|
||
}
|
||
if (wm.IsVertical() ? aCBWidthChanged : aCBHeightChanged) {
|
||
// See if f's block-size might have changed.
|
||
// If margin-block-start/end, padding-block-start/end,
|
||
// min-block-size, and max-block-size are all lengths or 'none',
|
||
// and bsize is a length or bsize and bend are auto and bstart is not auto,
|
||
// then our frame bsize does not depend on the parent bsize.
|
||
// Note that borders never depend on the parent bsize.
|
||
//
|
||
// FIXME(emilio): Should the BSize(wm).IsAuto() check also for the extremum
|
||
// lengths?
|
||
const auto bSize = pos->BSize(wm, anchorResolutionParams);
|
||
const auto anchorOffsetResolutionParams =
|
||
AnchorPosOffsetResolutionParams::UseCBFrameSize(anchorResolutionParams);
|
||
if ((nsStylePosition::BSizeDependsOnContainer(bSize) &&
|
||
!(bSize->IsAuto() &&
|
||
pos->GetAnchorResolvedInset(LogicalSide::BEnd, wm,
|
||
anchorOffsetResolutionParams)
|
||
->IsAuto() &&
|
||
!pos->GetAnchorResolvedInset(LogicalSide::BStart, wm,
|
||
anchorOffsetResolutionParams)
|
||
->IsAuto())) ||
|
||
nsStylePosition::MinBSizeDependsOnContainer(
|
||
pos->MinBSize(wm, anchorResolutionParams)) ||
|
||
nsStylePosition::MaxBSizeDependsOnContainer(
|
||
pos->MaxBSize(wm, anchorResolutionParams)) ||
|
||
!IsFixedPaddingSize(padding->mPadding.GetBStart(wm)) ||
|
||
!IsFixedPaddingSize(padding->mPadding.GetBEnd(wm))) {
|
||
return true;
|
||
}
|
||
|
||
// See if f's position might have changed.
|
||
if (!IsFixedMarginSize(margin->GetMargin(LogicalSide::BStart, wm,
|
||
anchorResolutionParams)) ||
|
||
!IsFixedMarginSize(
|
||
margin->GetMargin(LogicalSide::BEnd, wm, anchorResolutionParams))) {
|
||
return true;
|
||
}
|
||
}
|
||
|
||
// Since we store coordinates relative to top and left, the position
|
||
// of a frame depends on that of its container if it is fixed relative
|
||
// to the right or bottom, or if it is positioned using percentages
|
||
// relative to the left or top. Because of the dependency on the
|
||
// sides (left and top) that we use to store coordinates, these tests
|
||
// are easier to do using physical coordinates rather than logical.
|
||
if (aCBWidthChanged) {
|
||
const auto anchorOffsetResolutionParams =
|
||
AnchorPosOffsetResolutionParams::UseCBFrameSize(anchorResolutionParams);
|
||
if (!IsFixedOffset(pos->GetAnchorResolvedInset(
|
||
eSideLeft, anchorOffsetResolutionParams))) {
|
||
return true;
|
||
}
|
||
// Note that even if 'left' is a length, our position can still
|
||
// depend on the containing block width, because if our direction or
|
||
// writing-mode moves from right to left (in either block or inline
|
||
// progression) and 'right' is not 'auto', we will discard 'left'
|
||
// and be positioned relative to the containing block right edge.
|
||
// 'left' length and 'right' auto is the only combination we can be
|
||
// sure of.
|
||
if ((wm.GetInlineDir() == WritingMode::InlineDir::RTL ||
|
||
wm.GetBlockDir() == WritingMode::BlockDir::RL) &&
|
||
!pos->GetAnchorResolvedInset(eSideRight, anchorOffsetResolutionParams)
|
||
->IsAuto()) {
|
||
return true;
|
||
}
|
||
}
|
||
if (aCBHeightChanged) {
|
||
const auto anchorOffsetResolutionParams =
|
||
AnchorPosOffsetResolutionParams::UseCBFrameSize(anchorResolutionParams);
|
||
if (!IsFixedOffset(pos->GetAnchorResolvedInset(
|
||
eSideTop, anchorOffsetResolutionParams))) {
|
||
return true;
|
||
}
|
||
// See comment above for width changes.
|
||
if (wm.GetInlineDir() == WritingMode::InlineDir::BTT &&
|
||
!pos->GetAnchorResolvedInset(eSideBottom, anchorOffsetResolutionParams)
|
||
->IsAuto()) {
|
||
return true;
|
||
}
|
||
}
|
||
|
||
return false;
|
||
}
|
||
|
||
void AbsoluteContainingBlock::DestroyFrames(DestroyContext& aContext) {
|
||
mAbsoluteFrames.DestroyFrames(aContext);
|
||
mPushedAbsoluteFrames.DestroyFrames(aContext);
|
||
}
|
||
|
||
void AbsoluteContainingBlock::MarkSizeDependentFramesDirty() {
|
||
DoMarkFramesDirty(false);
|
||
}
|
||
|
||
void AbsoluteContainingBlock::MarkAllFramesDirty() { DoMarkFramesDirty(true); }
|
||
|
||
void AbsoluteContainingBlock::DoMarkFramesDirty(bool aMarkAllDirty) {
|
||
for (nsIFrame* kidFrame : mAbsoluteFrames) {
|
||
if (aMarkAllDirty) {
|
||
kidFrame->MarkSubtreeDirty();
|
||
} else if (FrameDependsOnContainer(kidFrame, true, true)) {
|
||
// Add the weakest flags that will make sure we reflow this frame later
|
||
kidFrame->AddStateBits(NS_FRAME_HAS_DIRTY_CHILDREN);
|
||
}
|
||
}
|
||
}
|
||
|
||
// Given an out-of-flow frame, this method returns the parent frame of its
|
||
// placeholder frame or null if it doesn't have a placeholder for some reason.
|
||
static nsContainerFrame* GetPlaceholderContainer(nsIFrame* aPositionedFrame) {
|
||
nsIFrame* placeholder = aPositionedFrame->GetPlaceholderFrame();
|
||
return placeholder ? placeholder->GetParent() : nullptr;
|
||
}
|
||
|
||
struct NonAutoAlignParams {
|
||
nscoord mCurrentStartInset;
|
||
nscoord mCurrentEndInset;
|
||
|
||
NonAutoAlignParams(nscoord aStartInset, nscoord aEndInset)
|
||
: mCurrentStartInset(aStartInset), mCurrentEndInset(aEndInset) {}
|
||
};
|
||
|
||
/**
|
||
* This function returns the offset of an abs/fixed-pos child's static
|
||
* position, with respect to the "start" corner of its alignment container,
|
||
* according to CSS Box Alignment. This function only operates in a single
|
||
* axis at a time -- callers can choose which axis via the |aAbsPosCBAxis|
|
||
* parameter. This is called under two scenarios:
|
||
* 1. We're statically positioning this absolutely positioned box, meaning
|
||
* that the offsets are auto and will change depending on the alignment
|
||
* of the box.
|
||
* 2. The offsets are non-auto, but the element may not fill the inset-reduced
|
||
* containing block, so its margin box needs to be aligned in that axis.
|
||
* This is the step 4 of [1]. Should also be noted that, unlike static
|
||
* positioning, where we may confine the alignment area for flex/grid
|
||
* parent containers, we explicitly align to the inset-reduced absolute
|
||
* container size.
|
||
*
|
||
* [1]: https://drafts.csswg.org/css-position-3/#abspos-layout
|
||
*
|
||
* @param aKidReflowInput The ReflowInput for the to-be-aligned abspos child.
|
||
* @param aKidSizeInAbsPosCBWM The child frame's size (after it's been given
|
||
* the opportunity to reflow), in terms of
|
||
* aAbsPosCBWM.
|
||
* @param aAbsPosCBSize The abspos CB size, in terms of aAbsPosCBWM.
|
||
* @param aPlaceholderContainer The parent of the child frame's corresponding
|
||
* placeholder frame, cast to a nsContainerFrame.
|
||
* (This will help us choose which alignment enum
|
||
* we should use for the child.)
|
||
* @param aAbsPosCBWM The child frame's containing block's WritingMode.
|
||
* @param aAbsPosCBAxis The axis (of the containing block) that we should
|
||
* be doing this computation for.
|
||
* @param aNonAutoAlignParams Parameters, if specified, indicating that we're
|
||
* handling scenario 2.
|
||
*/
|
||
static nscoord OffsetToAlignedStaticPos(
|
||
const ReflowInput& aKidReflowInput, const LogicalSize& aKidSizeInAbsPosCBWM,
|
||
const LogicalSize& aAbsPosCBSize,
|
||
const nsContainerFrame* aPlaceholderContainer, WritingMode aAbsPosCBWM,
|
||
LogicalAxis aAbsPosCBAxis, Maybe<NonAutoAlignParams> aNonAutoAlignParams,
|
||
const AbsoluteContainingBlock::AnchorOffsetInfo& aAnchorOffsetInfo) {
|
||
if (!aPlaceholderContainer) {
|
||
// (The placeholder container should be the thing that kicks this whole
|
||
// process off, by setting PLACEHOLDER_STATICPOS_NEEDS_CSSALIGN. So it
|
||
// should exist... but bail gracefully if it doesn't.)
|
||
NS_ERROR(
|
||
"Missing placeholder-container when computing a "
|
||
"CSS Box Alignment static position");
|
||
return 0;
|
||
}
|
||
|
||
// (Most of this function is simply preparing args that we'll pass to
|
||
// AlignJustifySelf at the end.)
|
||
|
||
// NOTE: Our alignment container is aPlaceholderContainer's content-box
|
||
// (or an area within it, if aPlaceholderContainer is a grid). So, we'll
|
||
// perform most of our arithmetic/alignment in aPlaceholderContainer's
|
||
// WritingMode. For brevity, we use the abbreviation "pc" for "placeholder
|
||
// container" in variables below.
|
||
WritingMode pcWM = aPlaceholderContainer->GetWritingMode();
|
||
LogicalSize absPosCBSizeInPCWM = aAbsPosCBSize.ConvertTo(pcWM, aAbsPosCBWM);
|
||
|
||
// Find what axis aAbsPosCBAxis corresponds to, in placeholder's parent's
|
||
// writing-mode.
|
||
const LogicalAxis pcAxis = aAbsPosCBWM.ConvertAxisTo(aAbsPosCBAxis, pcWM);
|
||
const LogicalSize alignAreaSize = [&]() {
|
||
if (!aNonAutoAlignParams) {
|
||
const bool placeholderContainerIsContainingBlock =
|
||
aPlaceholderContainer == aKidReflowInput.mCBReflowInput->mFrame;
|
||
|
||
LayoutFrameType parentType = aPlaceholderContainer->Type();
|
||
LogicalSize alignAreaSize(pcWM);
|
||
if (parentType == LayoutFrameType::FlexContainer) {
|
||
// We store the frame rect in FinishAndStoreOverflow, which runs _after_
|
||
// reflowing the absolute frames, so handle the special case of the
|
||
// frame being the actual containing block here, by getting the size
|
||
// from aAbsPosCBSize.
|
||
//
|
||
// The alignment container is the flex container's content box.
|
||
if (placeholderContainerIsContainingBlock) {
|
||
alignAreaSize = aAbsPosCBSize.ConvertTo(pcWM, aAbsPosCBWM);
|
||
// aAbsPosCBSize is the padding-box, so substract the padding to get
|
||
// the content box.
|
||
alignAreaSize -=
|
||
aPlaceholderContainer->GetLogicalUsedPadding(pcWM).Size(pcWM);
|
||
} else {
|
||
alignAreaSize = aPlaceholderContainer->GetLogicalSize(pcWM);
|
||
LogicalMargin pcBorderPadding =
|
||
aPlaceholderContainer->GetLogicalUsedBorderAndPadding(pcWM);
|
||
alignAreaSize -= pcBorderPadding.Size(pcWM);
|
||
}
|
||
return alignAreaSize;
|
||
}
|
||
if (parentType == LayoutFrameType::GridContainer) {
|
||
// This abspos elem's parent is a grid container. Per CSS Grid 10.1
|
||
// & 10.2:
|
||
// - If the grid container *also* generates the abspos containing block
|
||
// (a
|
||
// grid area) for this abspos child, we use that abspos containing block
|
||
// as the alignment container, too. (And its size is aAbsPosCBSize.)
|
||
// - Otherwise, we use the grid's padding box as the alignment
|
||
// container.
|
||
// https://drafts.csswg.org/css-grid/#static-position
|
||
if (placeholderContainerIsContainingBlock) {
|
||
// The alignment container is the grid area that we're using as the
|
||
// absolute containing block.
|
||
alignAreaSize = aAbsPosCBSize.ConvertTo(pcWM, aAbsPosCBWM);
|
||
} else {
|
||
// The alignment container is a the grid container's content box
|
||
// (which we can get by subtracting away its border & padding from
|
||
// frame's size):
|
||
alignAreaSize = aPlaceholderContainer->GetLogicalSize(pcWM);
|
||
LogicalMargin pcBorderPadding =
|
||
aPlaceholderContainer->GetLogicalUsedBorderAndPadding(pcWM);
|
||
alignAreaSize -= pcBorderPadding.Size(pcWM);
|
||
}
|
||
return alignAreaSize;
|
||
}
|
||
}
|
||
// Either we're in scenario 1 but within a non-flex/grid parent, or in
|
||
// scenario 2.
|
||
return aAbsPosCBSize.ConvertTo(pcWM, aAbsPosCBWM);
|
||
}();
|
||
|
||
const nscoord existingOffset = aNonAutoAlignParams
|
||
? aNonAutoAlignParams->mCurrentStartInset +
|
||
aNonAutoAlignParams->mCurrentEndInset
|
||
: 0;
|
||
const nscoord alignAreaSizeInAxis =
|
||
((pcAxis == LogicalAxis::Inline) ? alignAreaSize.ISize(pcWM)
|
||
: alignAreaSize.BSize(pcWM)) -
|
||
existingOffset;
|
||
|
||
using AlignJustifyFlag = CSSAlignUtils::AlignJustifyFlag;
|
||
CSSAlignUtils::AlignJustifyFlags flags(AlignJustifyFlag::IgnoreAutoMargins);
|
||
// Given that scenario 2 ignores the parent container type, special handling
|
||
// of absolutely-positioned child is also ignored.
|
||
StyleAlignFlags alignConst =
|
||
aNonAutoAlignParams
|
||
? aPlaceholderContainer
|
||
->CSSAlignmentForAbsPosChildWithinContainingBlock(
|
||
aKidReflowInput, pcAxis,
|
||
aAnchorOffsetInfo.mResolvedPositionArea, absPosCBSizeInPCWM)
|
||
: aPlaceholderContainer->CSSAlignmentForAbsPosChild(aKidReflowInput,
|
||
pcAxis);
|
||
// If the safe bit in alignConst is set, set the safe flag in |flags|.
|
||
const auto safetyBits =
|
||
alignConst & (StyleAlignFlags::SAFE | StyleAlignFlags::UNSAFE);
|
||
alignConst &= ~StyleAlignFlags::FLAG_BITS;
|
||
if (safetyBits & StyleAlignFlags::SAFE) {
|
||
flags += AlignJustifyFlag::OverflowSafe;
|
||
}
|
||
|
||
// Find out if placeholder-container & the OOF child have the same start-sides
|
||
// in the placeholder-container's pcAxis.
|
||
WritingMode kidWM = aKidReflowInput.GetWritingMode();
|
||
if (pcWM.ParallelAxisStartsOnSameSide(pcAxis, kidWM)) {
|
||
flags += AlignJustifyFlag::SameSide;
|
||
}
|
||
|
||
if (aNonAutoAlignParams) {
|
||
flags += AlignJustifyFlag::AligningMarginBox;
|
||
}
|
||
|
||
// (baselineAdjust is unused. CSSAlignmentForAbsPosChild() should've
|
||
// converted 'baseline'/'last baseline' enums to their fallback values.)
|
||
const nscoord baselineAdjust = nscoord(0);
|
||
|
||
// AlignJustifySelf operates in the kid's writing mode, so we need to
|
||
// represent the child's size and the desired axis in that writing mode:
|
||
LogicalSize kidSizeInOwnWM =
|
||
aKidSizeInAbsPosCBWM.ConvertTo(kidWM, aAbsPosCBWM);
|
||
const LogicalAxis kidAxis = aAbsPosCBWM.ConvertAxisTo(aAbsPosCBAxis, kidWM);
|
||
|
||
// Build an Inset Modified anchor info from the anchor which can be used to
|
||
// align to the anchor-center, if AlignJustifySelf is AnchorCenter.
|
||
Maybe<CSSAlignUtils::AnchorAlignInfo> anchorAlignInfo;
|
||
if (alignConst == StyleAlignFlags::ANCHOR_CENTER &&
|
||
aKidReflowInput.mAnchorPosResolutionCache) {
|
||
AnchorPosReferenceData* referenceData =
|
||
aKidReflowInput.mAnchorPosResolutionCache->mReferenceData;
|
||
if (referenceData) {
|
||
const auto* cachedData = referenceData->Lookup(
|
||
{referenceData->mDefaultAnchorName, referenceData->mAnchorTreeScope});
|
||
if (cachedData && *cachedData) {
|
||
referenceData->AdjustCompensatingForScroll(
|
||
aAbsPosCBWM.PhysicalAxis(aAbsPosCBAxis));
|
||
const auto& data = cachedData->ref();
|
||
if (data.mOffsetData) {
|
||
const nsSize containerSize =
|
||
aAbsPosCBSize.GetPhysicalSize(aAbsPosCBWM);
|
||
// Adjust for position-area, grid, etc.
|
||
const auto cbOffset =
|
||
referenceData->mAdjustedContainingBlock.TopLeft() -
|
||
referenceData->mOriginalContainingBlockRect.TopLeft();
|
||
const nsRect anchorRect(data.mOffsetData->mOrigin - cbOffset,
|
||
data.mSize);
|
||
const LogicalRect logicalAnchorRect{aAbsPosCBWM, anchorRect,
|
||
containerSize};
|
||
const auto axisInAbsPosCBWM =
|
||
kidWM.ConvertAxisTo(kidAxis, aAbsPosCBWM);
|
||
const auto anchorStart =
|
||
logicalAnchorRect.Start(axisInAbsPosCBWM, aAbsPosCBWM);
|
||
const auto anchorSize =
|
||
logicalAnchorRect.Size(axisInAbsPosCBWM, aAbsPosCBWM);
|
||
anchorAlignInfo =
|
||
Some(CSSAlignUtils::AnchorAlignInfo{anchorStart, anchorSize});
|
||
if (aNonAutoAlignParams) {
|
||
anchorAlignInfo->mAnchorStart -=
|
||
aNonAutoAlignParams->mCurrentStartInset;
|
||
}
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
nscoord offset = CSSAlignUtils::AlignJustifySelf(
|
||
alignConst, kidAxis, flags, baselineAdjust, alignAreaSizeInAxis,
|
||
aKidReflowInput, kidSizeInOwnWM, anchorAlignInfo);
|
||
|
||
// Safe alignment clamping for anchor-center.
|
||
// When using anchor-center with the safe keyword, or when both insets are
|
||
// auto (which defaults to safe behavior), clamp the element to stay within
|
||
// the containing block.
|
||
if ((!aNonAutoAlignParams || (safetyBits & StyleAlignFlags::SAFE)) &&
|
||
alignConst == StyleAlignFlags::ANCHOR_CENTER) {
|
||
const auto cbSize = aAbsPosCBSize.Size(aAbsPosCBAxis, aAbsPosCBWM);
|
||
const auto kidSize = aKidSizeInAbsPosCBWM.Size(aAbsPosCBAxis, aAbsPosCBWM);
|
||
|
||
if (aNonAutoAlignParams) {
|
||
const nscoord currentStartInset = aNonAutoAlignParams->mCurrentStartInset;
|
||
const nscoord finalStart = currentStartInset + offset;
|
||
const nscoord clampedStart =
|
||
CSSMinMax(finalStart, nscoord(0), cbSize - kidSize);
|
||
offset = clampedStart - currentStartInset;
|
||
} else {
|
||
offset = CSSMinMax(offset, nscoord(0), cbSize - kidSize);
|
||
}
|
||
}
|
||
|
||
const auto rawAlignConst =
|
||
(pcAxis == LogicalAxis::Inline)
|
||
? aKidReflowInput.mStylePosition->mJustifySelf._0
|
||
: aKidReflowInput.mStylePosition->mAlignSelf._0;
|
||
if (aNonAutoAlignParams && !safetyBits &&
|
||
(rawAlignConst != StyleAlignFlags::AUTO ||
|
||
alignConst == StyleAlignFlags::ANCHOR_CENTER)) {
|
||
// No `safe` or `unsafe` specified - "in-between" behaviour for relevant
|
||
// alignment values: https://drafts.csswg.org/css-position-3/#abspos-layout
|
||
// Skip if the raw self alignment for this element is `auto` to preserve
|
||
// legacy behaviour, except in the case where the resolved value is
|
||
// anchor-center (where "legacy behavior" is not a concern).
|
||
// Follows https://drafts.csswg.org/css-align-3/#auto-safety-position
|
||
const auto cbSize = aAbsPosCBSize.Size(aAbsPosCBAxis, aAbsPosCBWM);
|
||
// IMCB stands for "Inset-Modified Containing Block."
|
||
const auto imcbStart = aNonAutoAlignParams->mCurrentStartInset;
|
||
const auto imcbEnd = cbSize - aNonAutoAlignParams->mCurrentEndInset;
|
||
// Need to pull the offset into the "current view," unless it already did.
|
||
const auto scrollOffset = aAnchorOffsetInfo.mResolvedPositionArea.IsNone()
|
||
? aAbsPosCBWM.PhysicalAxis(aAbsPosCBAxis) ==
|
||
PhysicalAxis::Horizontal
|
||
? aAnchorOffsetInfo.mScrollOffset.x
|
||
: aAnchorOffsetInfo.mScrollOffset.y
|
||
: 0;
|
||
const auto kidSize = aKidSizeInAbsPosCBWM.Size(aAbsPosCBAxis, aAbsPosCBWM);
|
||
const auto kidStart =
|
||
aNonAutoAlignParams->mCurrentStartInset + offset - scrollOffset;
|
||
const auto kidEnd = kidStart + kidSize;
|
||
// "[...] the overflow limit rect is the bounding rectangle of the alignment
|
||
// subject’s inset-modified containing block and its original containing
|
||
// block."
|
||
const auto overflowLimitRectStart = std::min(0, imcbStart);
|
||
const auto overflowLimitRectEnd = std::max(cbSize, imcbEnd);
|
||
|
||
if (kidStart >= imcbStart && kidEnd <= imcbEnd) {
|
||
// 1. We fit inside the IMCB, no action needed.
|
||
} else if (kidSize <= overflowLimitRectEnd - overflowLimitRectStart) {
|
||
// 2. We overflowed IMCB, try to cover IMCB completely, if it's not.
|
||
if (kidStart <= imcbStart && kidEnd >= imcbEnd) {
|
||
// IMCB already covered, ensure that we aren't escaping the limit rect.
|
||
if (kidStart < overflowLimitRectStart) {
|
||
offset += overflowLimitRectStart - kidStart;
|
||
} else if (kidEnd > overflowLimitRectEnd) {
|
||
offset -= kidEnd - overflowLimitRectEnd;
|
||
}
|
||
} else if (kidEnd < imcbEnd && kidStart < imcbStart) {
|
||
// Space to end, overflowing on start - nudge to end.
|
||
offset += std::min(imcbStart - kidStart, imcbEnd - kidEnd);
|
||
} else if (kidStart > imcbStart && kidEnd > imcbEnd) {
|
||
// Space to start, overflowing on end - nudge to start.
|
||
offset -= std::min(kidEnd - imcbEnd, kidStart - imcbStart);
|
||
}
|
||
} else {
|
||
// 3. We'll overflow the limit rect. Start align the subject int overflow
|
||
// limit rect.
|
||
offset =
|
||
-aNonAutoAlignParams->mCurrentStartInset + overflowLimitRectStart;
|
||
}
|
||
}
|
||
|
||
// "offset" is in terms of the CSS Box Alignment container (i.e. it's in
|
||
// terms of pcWM). But our return value needs to in terms of the containing
|
||
// block's writing mode, which might have the opposite directionality in the
|
||
// given axis. In that case, we just need to negate "offset" when returning,
|
||
// to make it have the right effect as an offset for coordinates in the
|
||
// containing block's writing mode.
|
||
if (!pcWM.ParallelAxisStartsOnSameSide(pcAxis, aAbsPosCBWM)) {
|
||
return -offset;
|
||
}
|
||
return offset;
|
||
}
|
||
|
||
void AbsoluteContainingBlock::ResolveSizeDependentOffsets(
|
||
ReflowInput& aKidReflowInput, const LogicalSize& aCBSize,
|
||
const LogicalSize& aKidSize, const LogicalMargin& aMargin,
|
||
const AnchorOffsetInfo& aAnchorOffsetInfo, LogicalMargin& aOffsets) {
|
||
WritingMode outerWM = aKidReflowInput.mParentReflowInput->GetWritingMode();
|
||
|
||
// Now that we know the child's size, we resolve any sentinel values in its
|
||
// IStart/BStart offset coordinates that depend on that size.
|
||
// * NS_AUTOOFFSET indicates that the child's position in the given axis
|
||
// is determined by its end-wards offset property, combined with its size and
|
||
// available space. e.g.: "top: auto; height: auto; bottom: 50px"
|
||
// * m{I,B}OffsetsResolvedAfterSize indicate that the child is using its
|
||
// static position in that axis, *and* its static position is determined by
|
||
// the axis-appropriate css-align property (which may require the child's
|
||
// size, e.g. to center it within the parent).
|
||
if ((NS_AUTOOFFSET == aOffsets.IStart(outerWM)) ||
|
||
(NS_AUTOOFFSET == aOffsets.BStart(outerWM)) ||
|
||
aKidReflowInput.mFlags.mIOffsetsNeedCSSAlign ||
|
||
aKidReflowInput.mFlags.mBOffsetsNeedCSSAlign) {
|
||
// placeholderContainer is used in each of the m{I,B}OffsetsNeedCSSAlign
|
||
// clauses. We declare it at this scope so we can avoid having to look
|
||
// it up twice (and only look it up if it's needed).
|
||
nsContainerFrame* placeholderContainer = nullptr;
|
||
|
||
if (NS_AUTOOFFSET == aOffsets.IStart(outerWM)) {
|
||
NS_ASSERTION(NS_AUTOOFFSET != aOffsets.IEnd(outerWM),
|
||
"Can't solve for both start and end");
|
||
aOffsets.IStart(outerWM) =
|
||
aCBSize.ISize(outerWM) - aOffsets.IEnd(outerWM) -
|
||
aMargin.IStartEnd(outerWM) - aKidSize.ISize(outerWM);
|
||
} else if (aKidReflowInput.mFlags.mIOffsetsNeedCSSAlign) {
|
||
placeholderContainer = GetPlaceholderContainer(aKidReflowInput.mFrame);
|
||
nscoord offset = OffsetToAlignedStaticPos(
|
||
aKidReflowInput, aKidSize, aCBSize, placeholderContainer, outerWM,
|
||
LogicalAxis::Inline, Nothing{}, aAnchorOffsetInfo);
|
||
// Shift IStart from its current position (at start corner of the
|
||
// alignment container) by the returned offset. And set IEnd to the
|
||
// distance between the kid's end edge to containing block's end edge.
|
||
aOffsets.IStart(outerWM) += offset;
|
||
aOffsets.IEnd(outerWM) =
|
||
aCBSize.ISize(outerWM) -
|
||
(aOffsets.IStart(outerWM) + aKidSize.ISize(outerWM));
|
||
}
|
||
|
||
if (NS_AUTOOFFSET == aOffsets.BStart(outerWM)) {
|
||
aOffsets.BStart(outerWM) =
|
||
aCBSize.BSize(outerWM) - aOffsets.BEnd(outerWM) -
|
||
aMargin.BStartEnd(outerWM) - aKidSize.BSize(outerWM);
|
||
} else if (aKidReflowInput.mFlags.mBOffsetsNeedCSSAlign) {
|
||
if (!placeholderContainer) {
|
||
placeholderContainer = GetPlaceholderContainer(aKidReflowInput.mFrame);
|
||
}
|
||
nscoord offset = OffsetToAlignedStaticPos(
|
||
aKidReflowInput, aKidSize, aCBSize, placeholderContainer, outerWM,
|
||
LogicalAxis::Block, Nothing{}, aAnchorOffsetInfo);
|
||
// Shift BStart from its current position (at start corner of the
|
||
// alignment container) by the returned offset. And set BEnd to the
|
||
// distance between the kid's end edge to containing block's end edge.
|
||
aOffsets.BStart(outerWM) += offset;
|
||
aOffsets.BEnd(outerWM) =
|
||
aCBSize.BSize(outerWM) -
|
||
(aOffsets.BStart(outerWM) + aKidSize.BSize(outerWM));
|
||
}
|
||
aKidReflowInput.SetComputedLogicalOffsets(outerWM, aOffsets);
|
||
}
|
||
}
|
||
|
||
void AbsoluteContainingBlock::ResolveAutoMarginsAfterLayout(
|
||
ReflowInput& aKidReflowInput, const LogicalSize& aCBSize,
|
||
const LogicalSize& aKidSize, LogicalMargin& aMargin,
|
||
const LogicalMargin& aOffsets) {
|
||
WritingMode outerWM = aKidReflowInput.mParentReflowInput->GetWritingMode();
|
||
const auto& styleMargin = aKidReflowInput.mStyleMargin;
|
||
const auto anchorResolutionParams =
|
||
AnchorPosResolutionParams::From(&aKidReflowInput);
|
||
|
||
auto ResolveMarginsInAxis = [&](LogicalAxis aAxis) {
|
||
const auto startSide = MakeLogicalSide(aAxis, LogicalEdge::Start);
|
||
const auto endSide = MakeLogicalSide(aAxis, LogicalEdge::End);
|
||
|
||
// No need to substract border sizes because aKidSize has it included
|
||
// already. Also, if any offset is auto, the auto margin resolves to zero.
|
||
// https://drafts.csswg.org/css-position-3/#abspos-margins
|
||
const bool autoOffset =
|
||
aOffsets.Side(startSide, outerWM) == NS_AUTOOFFSET ||
|
||
aOffsets.Side(endSide, outerWM) == NS_AUTOOFFSET;
|
||
|
||
nscoord availMarginSpace;
|
||
if (autoOffset) {
|
||
availMarginSpace = 0;
|
||
} else {
|
||
const nscoord stretchFitSize = std::max(
|
||
0, aCBSize.Size(aAxis, outerWM) - aOffsets.StartEnd(aAxis, outerWM) -
|
||
aMargin.StartEnd(aAxis, outerWM));
|
||
availMarginSpace = stretchFitSize - aKidSize.Size(aAxis, outerWM);
|
||
}
|
||
|
||
const bool startSideMarginIsAuto =
|
||
styleMargin->GetMargin(startSide, outerWM, anchorResolutionParams)
|
||
->IsAuto();
|
||
const bool endSideMarginIsAuto =
|
||
styleMargin->GetMargin(endSide, outerWM, anchorResolutionParams)
|
||
->IsAuto();
|
||
|
||
if (aAxis == LogicalAxis::Inline) {
|
||
ReflowInput::ComputeAbsPosInlineAutoMargin(availMarginSpace, outerWM,
|
||
startSideMarginIsAuto,
|
||
endSideMarginIsAuto, aMargin);
|
||
} else {
|
||
ReflowInput::ComputeAbsPosBlockAutoMargin(availMarginSpace, outerWM,
|
||
startSideMarginIsAuto,
|
||
endSideMarginIsAuto, aMargin);
|
||
}
|
||
};
|
||
|
||
ResolveMarginsInAxis(LogicalAxis::Inline);
|
||
ResolveMarginsInAxis(LogicalAxis::Block);
|
||
aKidReflowInput.SetComputedLogicalMargin(outerWM, aMargin);
|
||
|
||
nsMargin* propValue =
|
||
aKidReflowInput.mFrame->GetProperty(nsIFrame::UsedMarginProperty());
|
||
// InitOffsets should've created a UsedMarginProperty for us, if any margin is
|
||
// auto.
|
||
MOZ_ASSERT_IF(
|
||
styleMargin->HasInlineAxisAuto(outerWM, anchorResolutionParams) ||
|
||
styleMargin->HasBlockAxisAuto(outerWM, anchorResolutionParams),
|
||
propValue);
|
||
if (propValue) {
|
||
*propValue = aMargin.GetPhysicalMargin(outerWM);
|
||
}
|
||
}
|
||
|
||
struct None {};
|
||
using OldCacheState = Variant<None, AnchorPosResolutionCache::PositionTryBackup,
|
||
AnchorPosResolutionCache::PositionTryFullBackup>;
|
||
|
||
struct MOZ_STACK_CLASS MOZ_RAII AutoFallbackStyleSetter {
|
||
AutoFallbackStyleSetter(nsIFrame* aFrame, ComputedStyle* aFallbackStyle,
|
||
AnchorPosResolutionCache* aCache, bool aIsFirstTry)
|
||
: mFrame(aFrame), mCache{aCache}, mOldCacheState{None{}} {
|
||
if (aFallbackStyle) {
|
||
mOldStyle = aFrame->SetComputedStyleWithoutNotification(aFallbackStyle);
|
||
}
|
||
// We need to be able to "go back" to the old, first try (Which is not
|
||
// necessarily base style) cache.
|
||
if (!aIsFirstTry && aCache) {
|
||
// New fallback could just be a flip keyword.
|
||
if (mOldStyle && mOldStyle->StylePosition()->mPositionAnchor !=
|
||
aFrame->StylePosition()->mPositionAnchor) {
|
||
mOldCacheState =
|
||
OldCacheState{aCache->TryPositionWithDifferentDefaultAnchor()};
|
||
// TODO(dshin, bug 2014913): Fragmentation _can_ change the containing
|
||
// block size from its unfragmented version, and that may cause us to
|
||
// choose a different fallback, and hit this code path.
|
||
*aCache = PopulateAnchorResolutionCache(aFrame, aCache->mReferenceData,
|
||
false);
|
||
} else {
|
||
mOldCacheState =
|
||
OldCacheState{aCache->TryPositionWithSameDefaultAnchor()};
|
||
if (aCache->mDefaultAnchorCache.mAnchor) {
|
||
aCache->mReferenceData->AdjustCompensatingForScroll(
|
||
CheckEarlyCompensatingForScroll(aFrame));
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
~AutoFallbackStyleSetter() {
|
||
if (mOldStyle) {
|
||
mFrame->SetComputedStyleWithoutNotification(std::move(mOldStyle));
|
||
}
|
||
std::move(mOldCacheState)
|
||
.match(
|
||
[](None&&) {},
|
||
[&](AnchorPosResolutionCache::PositionTryBackup&& aBackup) {
|
||
mCache->UndoTryPositionWithSameDefaultAnchor(std::move(aBackup));
|
||
},
|
||
[&](AnchorPosResolutionCache::PositionTryFullBackup&& aBackup) {
|
||
mCache->UndoTryPositionWithDifferentDefaultAnchor(
|
||
std::move(aBackup));
|
||
});
|
||
}
|
||
|
||
void CommitCurrentFallback() {
|
||
mOldCacheState = OldCacheState{None{}};
|
||
// If we have a non-layout dependent margin / paddings, which are different
|
||
// from our original style, we need to make sure to commit it into the frame
|
||
// property so that it doesn't get lost after returning from reflow.
|
||
nsMargin margin;
|
||
if (mOldStyle &&
|
||
!mOldStyle->StyleMargin()->MarginEquals(*mFrame->StyleMargin()) &&
|
||
mFrame->StyleMargin()->GetMargin(margin)) {
|
||
mFrame->SetOrUpdateDeletableProperty(nsIFrame::UsedMarginProperty(),
|
||
margin);
|
||
}
|
||
}
|
||
|
||
private:
|
||
nsIFrame* const mFrame;
|
||
RefPtr<ComputedStyle> mOldStyle;
|
||
AnchorPosResolutionCache* const mCache;
|
||
OldCacheState mOldCacheState;
|
||
};
|
||
|
||
// XXX Optimize the case where it's a resize reflow and the absolutely
|
||
// positioned child has the exact same size and position and skip the
|
||
// reflow...
|
||
void AbsoluteContainingBlock::ReflowAbsoluteFrame(
|
||
nsContainerFrame* aDelegatingFrame, nsPresContext* aPresContext,
|
||
const ReflowInput& aReflowInput,
|
||
const ContainingBlockRects& aContainingBlockRects, AbsPosReflowFlags aFlags,
|
||
nsIFrame* aKidFrame, nsReflowStatus& aStatus, OverflowAreas* aOverflowAreas,
|
||
const ContainingBlockRects* aFragmentedContainingBlockRects,
|
||
AnchorPosResolutionCache* aAnchorPosResolutionCache,
|
||
bool aReuseUnfragmentedAnchorPosReferences) {
|
||
MOZ_ASSERT(aStatus.IsEmpty(), "Caller should pass a fresh reflow status!");
|
||
|
||
#ifdef DEBUG
|
||
if (nsBlockFrame::gNoisyReflow) {
|
||
nsIFrame::IndentBy(stdout, nsBlockFrame::gNoiseIndent);
|
||
fmt::println("abspos {}: begin reflow: availSize={}, orig cbRect={}",
|
||
aKidFrame->ListTag(), ToString(aReflowInput.AvailableSize()),
|
||
ToString(aContainingBlockRects.mLocal));
|
||
}
|
||
AutoNoisyIndenter indent(nsBlockFrame::gNoisy);
|
||
#endif // DEBUG
|
||
|
||
const WritingMode outerWM = aReflowInput.GetWritingMode();
|
||
const WritingMode wm = aKidFrame->GetWritingMode();
|
||
|
||
const bool isGrid = aFlags.contains(AbsPosReflowFlag::IsGridContainerCB);
|
||
const auto* kidStylePosition = aKidFrame->StylePosition();
|
||
auto fallbacks = kidStylePosition->mPositionTryFallbacks.value._0.AsSpan();
|
||
const auto fallbackScope = kidStylePosition->mPositionTryFallbacks.scope;
|
||
Maybe<uint32_t> currentFallbackIndex;
|
||
const StylePositionTryFallbacksItem* currentFallback = nullptr;
|
||
RefPtr<ComputedStyle> currentFallbackStyle;
|
||
RefPtr<ComputedStyle> firstTryStyle;
|
||
Maybe<uint32_t> firstTryIndex;
|
||
// If non-'normal' position-try-order is in effect, we keep track of the
|
||
// index of the "best" option seen, and its size in the relevant axis, so
|
||
// that once all fallbacks have been considered we can reset to the one
|
||
// that provided the most space.
|
||
Maybe<uint32_t> bestIndex;
|
||
nscoord bestSize = -1;
|
||
// Flag to indicate that we've determined which fallback to use and should
|
||
// exit the loop.
|
||
bool finalizing = false;
|
||
|
||
auto tryOrder = kidStylePosition->mPositionTryOrder;
|
||
// If position-try-order is a logical value, resolve to physical using
|
||
// the containing block's writing mode.
|
||
switch (tryOrder) {
|
||
case StylePositionTryOrder::MostInlineSize:
|
||
tryOrder = outerWM.IsVertical() ? StylePositionTryOrder::MostHeight
|
||
: StylePositionTryOrder::MostWidth;
|
||
break;
|
||
case StylePositionTryOrder::MostBlockSize:
|
||
tryOrder = outerWM.IsVertical() ? StylePositionTryOrder::MostWidth
|
||
: StylePositionTryOrder::MostHeight;
|
||
break;
|
||
default:
|
||
break;
|
||
}
|
||
|
||
const auto* baseStyle = aKidFrame->Style();
|
||
// Set the current fallback to the given index, or reset to the base position
|
||
// if Nothing() is passed.
|
||
auto SeekFallbackTo = [&](Maybe<uint32_t> aIndex) -> bool {
|
||
if (!aIndex) {
|
||
currentFallbackIndex = Nothing();
|
||
currentFallback = nullptr;
|
||
currentFallbackStyle = nullptr;
|
||
return true;
|
||
}
|
||
uint32_t index = *aIndex;
|
||
if (index >= fallbacks.Length()) {
|
||
return false;
|
||
}
|
||
|
||
const StylePositionTryFallbacksItem* nextFallback;
|
||
RefPtr<ComputedStyle> nextFallbackStyle;
|
||
while (true) {
|
||
nextFallback = &fallbacks[index];
|
||
nextFallbackStyle = aPresContext->StyleSet()->ResolvePositionTry(
|
||
fallbackScope, *aKidFrame->GetContent()->AsElement(), *baseStyle,
|
||
*nextFallback);
|
||
if (nextFallbackStyle) {
|
||
break;
|
||
}
|
||
// No @position-try rule for this name was found, per spec we should
|
||
// skip it.
|
||
index++;
|
||
if (index >= fallbacks.Length()) {
|
||
return false;
|
||
}
|
||
}
|
||
currentFallbackIndex = Some(index);
|
||
currentFallback = nextFallback;
|
||
currentFallbackStyle = std::move(nextFallbackStyle);
|
||
return true;
|
||
};
|
||
|
||
// Advance to the next fallback to be tried. Normally this is simply the next
|
||
// index in the position-try-fallbacks list, but we have some special cases:
|
||
// - if we're currently at the last-successful fallback (recorded as
|
||
// firstTryIndex), we "advance" to the base position
|
||
// - we skip the last-successful fallback when we reach its position again
|
||
auto TryAdvanceFallback = [&]() -> bool {
|
||
if (fallbacks.IsEmpty()) {
|
||
return false;
|
||
}
|
||
if (firstTryIndex && currentFallbackIndex == firstTryIndex) {
|
||
return SeekFallbackTo(Nothing());
|
||
}
|
||
uint32_t nextFallbackIndex =
|
||
currentFallbackIndex ? *currentFallbackIndex + 1 : 0;
|
||
if (firstTryIndex && nextFallbackIndex == *firstTryIndex) {
|
||
++nextFallbackIndex;
|
||
}
|
||
return SeekFallbackTo(Some(nextFallbackIndex));
|
||
};
|
||
|
||
Maybe<nsRect> firstTryRect;
|
||
if (auto* lastSuccessfulPosition =
|
||
aKidFrame->GetProperty(nsIFrame::LastSuccessfulPositionFallback());
|
||
lastSuccessfulPosition && lastSuccessfulPosition->mRecordedIndex &&
|
||
SeekFallbackTo(lastSuccessfulPosition->mRecordedIndex)) {
|
||
// Remember which fallback we're trying first; also record its style,
|
||
// in case we need to restore it later.
|
||
firstTryIndex = lastSuccessfulPosition->mRecordedIndex;
|
||
firstTryStyle = currentFallbackStyle;
|
||
}
|
||
|
||
// Assume we *are* overflowing the CB and if we find a fallback that doesn't
|
||
// overflow, we set this to false and break the loop.
|
||
bool isOverflowingCB = true;
|
||
|
||
do {
|
||
AutoFallbackStyleSetter fallback(aKidFrame, currentFallbackStyle,
|
||
aAnchorPosResolutionCache,
|
||
firstTryIndex == currentFallbackIndex);
|
||
auto cb = ComputeContainingBlock(isGrid, aDelegatingFrame, aReflowInput,
|
||
aContainingBlockRects, aKidFrame,
|
||
aAnchorPosResolutionCache,
|
||
aReuseUnfragmentedAnchorPosReferences);
|
||
PhysicalAxes earlyScrollCompensation;
|
||
if (aAnchorPosResolutionCache) {
|
||
const auto& originalCb = cb.mMaybeScrollableRect;
|
||
aAnchorPosResolutionCache->mReferenceData->mOriginalContainingBlockRect =
|
||
originalCb;
|
||
// Stash the adjusted containing block as well, since the insets need to
|
||
// resolve against the adjusted CB, e.g. With `position-area: bottom
|
||
// right;`, + `left: anchor(right);`
|
||
// resolves to 0.
|
||
aAnchorPosResolutionCache->mReferenceData->mAdjustedContainingBlock =
|
||
cb.mFinalRect;
|
||
// May need to recompute scroll compensation if e.g. anchor-center in one
|
||
// axis, then `anchor(--default-anchor)` in another.
|
||
earlyScrollCompensation = aAnchorPosResolutionCache->mReferenceData
|
||
->CompensatingForScrollAxes();
|
||
}
|
||
const LogicalSize cbSize(outerWM, cb.mFinalRect.Size());
|
||
|
||
ReflowInput::InitFlags initFlags;
|
||
const bool staticPosIsCBOrigin = [&] {
|
||
if (aFlags.contains(AbsPosReflowFlag::IsGridContainerCB)) {
|
||
// When a grid container generates the abs.pos. CB for a *child* then
|
||
// the static position is determined via CSS Box Alignment within the
|
||
// abs.pos. CB (a grid area, i.e. a piece of the grid). In this
|
||
// scenario, due to the multiple coordinate spaces in play, we use a
|
||
// convenience flag to simply have the child's ReflowInput give it a
|
||
// static position at its abs.pos. CB origin, and then we'll align &
|
||
// offset it from there.
|
||
nsIFrame* placeholder = aKidFrame->GetPlaceholderFrame();
|
||
if (placeholder && placeholder->GetParent() == aDelegatingFrame) {
|
||
return true;
|
||
}
|
||
}
|
||
if (aKidFrame->IsMenuPopupFrame()) {
|
||
// Popups never use their static pos.
|
||
return true;
|
||
}
|
||
// TODO(emilio): Either reparent the top layer placeholder frames to the
|
||
// viewport, or return true here for top layer frames more generally (not
|
||
// only menupopups), see https://github.com/w3c/csswg-drafts/issues/8040.
|
||
return false;
|
||
}();
|
||
|
||
if (staticPosIsCBOrigin) {
|
||
initFlags += ReflowInput::InitFlag::StaticPosIsCBOrigin;
|
||
}
|
||
|
||
const bool kidFrameMaySplit =
|
||
aReflowInput.AvailableBSize() != NS_UNCONSTRAINEDSIZE &&
|
||
|
||
// Don't split if told not to (e.g. for fixed frames)
|
||
aFlags.contains(AbsPosReflowFlag::AllowFragmentation) &&
|
||
|
||
// In the legacy (pref-off) behavior, don't split abspos frames whose
|
||
// containing block is formed by an inline frame.
|
||
(StaticPrefs::layout_abspos_fragment_aware_inline_cb_enabled() ||
|
||
!aDelegatingFrame->IsInlineFrameOrSubclass()) &&
|
||
|
||
// Bug 1588623: Support splitting absolute positioned multicol
|
||
// containers.
|
||
!aKidFrame->IsColumnSetWrapperFrame();
|
||
|
||
// Get the border values
|
||
const LogicalMargin border =
|
||
aDelegatingFrame->GetLogicalUsedBorder(outerWM).ApplySkipSides(
|
||
aDelegatingFrame->PreReflowBlockLevelLogicalSkipSides());
|
||
|
||
const nsIFrame* kidPrevInFlow = aKidFrame->GetPrevInFlow();
|
||
const LogicalPoint* const unfragmentedPosition =
|
||
GetUnfragmentedPosition(aReflowInput, aKidFrame);
|
||
nscoord availBSize;
|
||
if (kidFrameMaySplit) {
|
||
if (unfragmentedPosition) {
|
||
// The unfragmented position is relative to the absolute containing
|
||
// block's first fragment, so we subtract
|
||
// mCumulativeContainingBlockBSize to get the position in this fragment.
|
||
const nscoord kidBPosInThisFragment =
|
||
unfragmentedPosition->B(outerWM) - mCumulativeContainingBlockBSize;
|
||
availBSize = aReflowInput.AvailableBSize() - kidBPosInThisFragment;
|
||
NS_ASSERTION(availBSize >= 0, "Why is available block-size < 0?");
|
||
} else if (!aDelegatingFrame->GetPrevInFlow()) {
|
||
// aDelegatingFrame 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.
|
||
availBSize = aReflowInput.AvailableBSize() - border.BStart(outerWM);
|
||
} else {
|
||
// aDelegatingFrame 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').
|
||
availBSize = aReflowInput.AvailableBSize();
|
||
}
|
||
} else {
|
||
availBSize = NS_UNCONSTRAINEDSIZE;
|
||
}
|
||
StyleSizeOverrides sizeOverrides;
|
||
Maybe<nscoord> unfragmentedBSizeAsMinBSize;
|
||
if (const auto* unfragmentedSize =
|
||
GetUnfragmentedSize(aReflowInput, aKidFrame)) {
|
||
// ReflowInput for fragmented absolute frames will not compute absolute
|
||
// constraints - it'd be redundant anyway, so just use the unfragmented
|
||
// size and skip it.
|
||
auto resolutionParams =
|
||
AnchorPosResolutionParams::From(aKidFrame, aAnchorPosResolutionCache);
|
||
const auto* stylePos = aKidFrame->StylePosition();
|
||
if (stylePos->ISize(wm, resolutionParams)->IsAuto()) {
|
||
sizeOverrides.mStyleISize.emplace(
|
||
StyleSize::FromAppUnits(unfragmentedSize->ISize(wm)));
|
||
}
|
||
if (stylePos->BSize(wm, resolutionParams)->IsAuto()) {
|
||
unfragmentedBSizeAsMinBSize = Some(unfragmentedSize->BSize(wm));
|
||
}
|
||
}
|
||
const LogicalSize availSize(outerWM, cbSize.ISize(outerWM), availBSize);
|
||
ReflowInput kidReflowInput(aPresContext, aReflowInput, aKidFrame,
|
||
availSize.ConvertTo(wm, outerWM),
|
||
Some(cbSize.ConvertTo(wm, outerWM)), initFlags,
|
||
sizeOverrides, {}, aAnchorPosResolutionCache);
|
||
|
||
if (unfragmentedBSizeAsMinBSize) {
|
||
// The kid has 'auto' block-size. Instead of setting unfragmented
|
||
// block-size to sizeOverrides above, use it as a min-block-size lower
|
||
// bound to keep allowing fragmentation-imposed block-size growth.
|
||
const nscoord contentBSize =
|
||
*unfragmentedBSizeAsMinBSize -
|
||
(kidReflowInput.mStylePosition->mBoxSizing ==
|
||
StyleBoxSizing::BorderBox
|
||
? kidReflowInput.ComputedLogicalBorderPadding(wm).BStartEnd(wm)
|
||
: 0);
|
||
kidReflowInput.SetComputedMinBSize(contentBSize);
|
||
}
|
||
|
||
if (unfragmentedPosition) {
|
||
// Do nothing. If aKidFrame may split, we've adjusted availBSize before
|
||
// creating kidReflowInput.
|
||
} else if (!kidPrevInFlow) {
|
||
// 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.
|
||
nscoord kidAvailBSize = kidReflowInput.AvailableBSize();
|
||
if (kidAvailBSize != NS_UNCONSTRAINEDSIZE) {
|
||
kidAvailBSize -= kidReflowInput.ComputedLogicalMargin(wm).BStart(wm);
|
||
nscoord kidOffsetBStart =
|
||
kidReflowInput.ComputedLogicalOffsets(wm).BStart(wm);
|
||
if (kidOffsetBStart != NS_AUTOOFFSET) {
|
||
kidOffsetBStart -= mCumulativeContainingBlockBSize;
|
||
kidAvailBSize -= kidOffsetBStart;
|
||
}
|
||
kidReflowInput.SetAvailableBSize(kidAvailBSize);
|
||
}
|
||
}
|
||
|
||
// Do the reflow
|
||
ReflowOutput kidDesiredSize(kidReflowInput);
|
||
aKidFrame->Reflow(aPresContext, kidDesiredSize, kidReflowInput, aStatus);
|
||
|
||
nsMargin insets;
|
||
if (aKidFrame->IsMenuPopupFrame()) {
|
||
// Do nothing. Popup frame will handle its own positioning.
|
||
} else if (unfragmentedPosition || kidPrevInFlow) {
|
||
LogicalPoint kidPos(outerWM);
|
||
if (unfragmentedPosition) {
|
||
MOZ_ASSERT(!kidPrevInFlow, "aKidFrame should be a first-in-flow!");
|
||
|
||
// aKidFrame is a first-in-flow. Place it at its unfragmented position
|
||
// with the block-start position adjusted.
|
||
kidPos = *unfragmentedPosition;
|
||
kidPos.B(outerWM) -= mCumulativeContainingBlockBSize;
|
||
} else {
|
||
// aKidFrame is a next-in-flow. Place it at the block-edge start of its
|
||
// containing block, with the same inline-position as in its
|
||
// unfragmented position.
|
||
const LogicalPoint* unfragPos =
|
||
GetUnfragmentedPosition(aReflowInput, aKidFrame->FirstInFlow());
|
||
MOZ_ASSERT(unfragPos,
|
||
"A first-in-flow should have stored an unfragmented "
|
||
"position during a measuring reflow!");
|
||
if (unfragPos) [[likely]] {
|
||
kidPos = *unfragPos;
|
||
kidPos.B(outerWM) = 0;
|
||
}
|
||
}
|
||
const LogicalSize kidSize = kidDesiredSize.Size(outerWM);
|
||
nsRect kidRect;
|
||
if (StaticPrefs::layout_abspos_fragment_aware_inline_cb_enabled() &&
|
||
aDelegatingFrame->IsInlineFrameOrSubclass()) {
|
||
nsIFrame* blockAncestor =
|
||
nsLayoutUtils::FindNearestBlockAncestor(aDelegatingFrame);
|
||
|
||
// The block ancestor's size, stashed in the inline reflow input's
|
||
// containing block size, used to translate the coordinate spaces
|
||
// between the block ancestor and aDelegatingFrame (Note we cannot use
|
||
// the block ancestor's GetSize() because it is still in reflow).
|
||
const nsSize blockAncestorSize =
|
||
aReflowInput.mContainingBlockSize.GetPhysicalSize(outerWM);
|
||
kidRect = LogicalRect(outerWM, kidPos, kidSize)
|
||
.GetPhysicalRect(outerWM, blockAncestorSize) -
|
||
aDelegatingFrame->GetOffsetTo(blockAncestor);
|
||
} else {
|
||
// We can have reflows in a spanner that is also a multicol.
|
||
const auto maybeFragmentedCbSize =
|
||
(aFragmentedContainingBlockRects ? *aFragmentedContainingBlockRects
|
||
: aContainingBlockRects)
|
||
.mLocal.Size();
|
||
// TODO(dshin): Fix this up for anchor positioning. Scroll containers
|
||
// are monolithic and will not fragment, but an anchor-positioned
|
||
// frame's percentage size still needs to resolve against the correct
|
||
// containing block.
|
||
const LogicalSize unmodifiedCBSize(outerWM, maybeFragmentedCbSize);
|
||
const nsSize cbBorderBoxSize =
|
||
(unmodifiedCBSize + border.Size(outerWM)).GetPhysicalSize(outerWM);
|
||
kidRect = LogicalRect(outerWM, kidPos, kidSize)
|
||
.GetPhysicalRect(outerWM, cbBorderBoxSize);
|
||
}
|
||
aKidFrame->SetRect(kidRect);
|
||
} else {
|
||
// Position the child relative to our padding edge.
|
||
const LogicalSize kidSize = kidDesiredSize.Size(outerWM);
|
||
|
||
LogicalMargin offsets = kidReflowInput.ComputedLogicalOffsets(outerWM);
|
||
LogicalMargin margin = kidReflowInput.ComputedLogicalMargin(outerWM);
|
||
|
||
// If we're doing CSS Box Alignment in either axis, that will apply the
|
||
// margin for us in that axis (since the thing that's aligned is the
|
||
// margin box). So, we clear out the margin here to avoid applying it
|
||
// twice.
|
||
if (kidReflowInput.mFlags.mIOffsetsNeedCSSAlign) {
|
||
margin.IStart(outerWM) = margin.IEnd(outerWM) = 0;
|
||
}
|
||
if (kidReflowInput.mFlags.mBOffsetsNeedCSSAlign) {
|
||
margin.BStart(outerWM) = margin.BEnd(outerWM) = 0;
|
||
}
|
||
|
||
// If we're solving for start in either inline or block direction,
|
||
// then compute it now that we know the dimensions.
|
||
ResolveSizeDependentOffsets(kidReflowInput, cbSize, kidSize, margin,
|
||
cb.GetAnchorOffsetInfo(), offsets);
|
||
|
||
ResolveAutoMarginsAfterLayout(kidReflowInput, cbSize, kidSize, margin,
|
||
offsets);
|
||
|
||
// If the inset is constrained as non-auto, we may have a child that does
|
||
// not fill out the inset-reduced containing block. In this case, we need
|
||
// to align the child by its margin box:
|
||
// https://drafts.csswg.org/css-position-3/#abspos-layout
|
||
const auto* stylePos = aKidFrame->StylePosition();
|
||
const auto anchorResolutionParams =
|
||
AnchorPosOffsetResolutionParams::ExplicitCBFrameSize(
|
||
AnchorPosResolutionParams::From(aKidFrame,
|
||
aAnchorPosResolutionCache),
|
||
&cbSize);
|
||
const bool iStartInsetAuto =
|
||
stylePos
|
||
->GetAnchorResolvedInset(LogicalSide::IStart, outerWM,
|
||
anchorResolutionParams)
|
||
->IsAuto();
|
||
const bool iEndInsetAuto =
|
||
stylePos
|
||
->GetAnchorResolvedInset(LogicalSide::IEnd, outerWM,
|
||
anchorResolutionParams)
|
||
->IsAuto();
|
||
const bool iInsetAuto = iStartInsetAuto || iEndInsetAuto;
|
||
|
||
const bool bStartInsetAuto =
|
||
stylePos
|
||
->GetAnchorResolvedInset(LogicalSide::BStart, outerWM,
|
||
anchorResolutionParams)
|
||
->IsAuto();
|
||
const bool bEndInsetAuto =
|
||
stylePos
|
||
->GetAnchorResolvedInset(LogicalSide::BEnd, outerWM,
|
||
anchorResolutionParams)
|
||
->IsAuto();
|
||
const bool bInsetAuto = bStartInsetAuto || bEndInsetAuto;
|
||
const LogicalSize kidMarginBox{
|
||
outerWM, margin.IStartEnd(outerWM) + kidSize.ISize(outerWM),
|
||
margin.BStartEnd(outerWM) + kidSize.BSize(outerWM)};
|
||
const auto* placeholderContainer =
|
||
GetPlaceholderContainer(kidReflowInput.mFrame);
|
||
|
||
insets = [&]() {
|
||
auto result = offsets;
|
||
// Zero out weaker insets, if one exists - This offset gets forced to
|
||
// the margin edge of the child on that side, and for the purposes of
|
||
// overflow checks, we consider them to be zero.
|
||
if (iStartInsetAuto && !iEndInsetAuto) {
|
||
result.IStart(outerWM) = 0;
|
||
} else if (iInsetAuto) {
|
||
result.IEnd(outerWM) = 0;
|
||
}
|
||
if (bStartInsetAuto && !bEndInsetAuto) {
|
||
result.BStart(outerWM) = 0;
|
||
} else if (bInsetAuto) {
|
||
result.BEnd(outerWM) = 0;
|
||
}
|
||
return result.GetPhysicalMargin(outerWM);
|
||
}();
|
||
if (aAnchorPosResolutionCache) {
|
||
aAnchorPosResolutionCache->mReferenceData->mInsets = insets;
|
||
}
|
||
if (!iInsetAuto) {
|
||
MOZ_ASSERT(
|
||
!kidReflowInput.mFlags.mIOffsetsNeedCSSAlign,
|
||
"Non-auto inline inset but requires CSS alignment for static "
|
||
"position?");
|
||
auto alignOffset = OffsetToAlignedStaticPos(
|
||
kidReflowInput, kidMarginBox, cbSize, placeholderContainer, outerWM,
|
||
LogicalAxis::Inline,
|
||
Some(NonAutoAlignParams{
|
||
offsets.IStart(outerWM),
|
||
offsets.IEnd(outerWM),
|
||
}),
|
||
cb.GetAnchorOffsetInfo());
|
||
|
||
offsets.IStart(outerWM) += alignOffset;
|
||
offsets.IEnd(outerWM) =
|
||
cbSize.ISize(outerWM) -
|
||
(offsets.IStart(outerWM) + kidMarginBox.ISize(outerWM));
|
||
}
|
||
if (!bInsetAuto) {
|
||
MOZ_ASSERT(!kidReflowInput.mFlags.mBOffsetsNeedCSSAlign,
|
||
"Non-auto block inset but requires CSS alignment for static "
|
||
"position?");
|
||
auto alignOffset = OffsetToAlignedStaticPos(
|
||
kidReflowInput, kidMarginBox, cbSize, placeholderContainer, outerWM,
|
||
LogicalAxis::Block,
|
||
Some(NonAutoAlignParams{
|
||
offsets.BStart(outerWM),
|
||
offsets.BEnd(outerWM),
|
||
}),
|
||
cb.GetAnchorOffsetInfo());
|
||
offsets.BStart(outerWM) += alignOffset;
|
||
offsets.BEnd(outerWM) =
|
||
cbSize.BSize(outerWM) -
|
||
(offsets.BStart(outerWM) + kidMarginBox.BSize(outerWM));
|
||
}
|
||
|
||
LogicalRect rect(
|
||
outerWM, offsets.StartOffset(outerWM) + margin.StartOffset(outerWM),
|
||
kidSize);
|
||
nsRect r = rect.GetPhysicalRect(outerWM, cbSize.GetPhysicalSize(outerWM));
|
||
|
||
// So far, we've positioned against the padding edge of the containing
|
||
// block, which is necessary for inset computation. However, the position
|
||
// of a frame originates against the border box.
|
||
r += cb.mFinalRect.TopLeft();
|
||
|
||
const auto scrollShift = [&]() -> nsPoint {
|
||
if (!aAnchorPosResolutionCache) {
|
||
return {};
|
||
}
|
||
auto* referenceData = aAnchorPosResolutionCache->mReferenceData;
|
||
if (referenceData->CompensatingForScrollAxes().isEmpty()) {
|
||
return {};
|
||
}
|
||
if (cb.mAnchorOffsetInfo &&
|
||
earlyScrollCompensation ==
|
||
referenceData->CompensatingForScrollAxes()) {
|
||
// Able to use the already-resolved value.
|
||
return cb.mAnchorOffsetInfo->mScrollOffset;
|
||
}
|
||
return AnchorPositioningUtils::GetScrollOffsetFor(
|
||
referenceData->CompensatingForScrollAxes(), aKidFrame,
|
||
aAnchorPosResolutionCache->mDefaultAnchorCache);
|
||
}();
|
||
if (aAnchorPosResolutionCache) {
|
||
aAnchorPosResolutionCache->mReferenceData->mDefaultScrollShift =
|
||
scrollShift;
|
||
}
|
||
r -= scrollShift;
|
||
aKidFrame->SetRect(r);
|
||
}
|
||
|
||
aKidFrame->DidReflow(aPresContext, &kidReflowInput);
|
||
|
||
if (!firstTryRect) {
|
||
firstTryRect.emplace(aKidFrame->GetRect());
|
||
}
|
||
|
||
const auto FitsInContainingBlock = [&]() {
|
||
if (aAnchorPosResolutionCache) {
|
||
return AnchorPositioningUtils::FitsInContainingBlock(
|
||
aKidFrame, *aAnchorPosResolutionCache->mReferenceData);
|
||
}
|
||
auto imcbSize = cb.mFinalRect.Size();
|
||
imcbSize -= nsSize{insets.LeftRight(), insets.TopBottom()};
|
||
return aKidFrame->GetMarginRectRelativeToSelf().Size() <= imcbSize;
|
||
};
|
||
|
||
// FIXME(bug 2004495): Per spec this should be the inset-modified
|
||
// containing-block, see:
|
||
// https://drafts.csswg.org/css-anchor-position-1/#fallback-apply
|
||
const auto fits = aStatus.IsComplete() && FitsInContainingBlock();
|
||
if (fallbacks.IsEmpty() || finalizing ||
|
||
(fits && (tryOrder == StylePositionTryOrder::Normal ||
|
||
currentFallbackIndex == firstTryIndex))) {
|
||
// We completed the reflow - Either we had a fallback that fit, or we
|
||
// didn't have any to try in the first place.
|
||
isOverflowingCB = !fits;
|
||
fallback.CommitCurrentFallback();
|
||
if (currentFallbackIndex.isNothing()) {
|
||
if (auto* prop = aKidFrame->GetProperty(
|
||
nsIFrame::LastSuccessfulPositionFallback())) {
|
||
// When the fallback list changes, we clear the recorded fallback data
|
||
// as per spec, so we shouldn't get there in this case.
|
||
MOZ_ASSERT(!fallbacks.IsEmpty(), "how?");
|
||
prop->mLastIndex.reset();
|
||
prop->mLastStyle = nullptr;
|
||
prop->mTriedAllFallbacks = isOverflowingCB;
|
||
}
|
||
}
|
||
break;
|
||
}
|
||
|
||
if (fits) {
|
||
auto imcbSize = cb.mFinalRect.Size();
|
||
imcbSize -= nsSize{insets.LeftRight(), insets.TopBottom()};
|
||
switch (tryOrder) {
|
||
case StylePositionTryOrder::MostWidth:
|
||
if (imcbSize.Width() > bestSize) {
|
||
bestSize = imcbSize.Width();
|
||
bestIndex = currentFallbackIndex;
|
||
}
|
||
break;
|
||
case StylePositionTryOrder::MostHeight:
|
||
if (imcbSize.Height() > bestSize) {
|
||
bestSize = imcbSize.Height();
|
||
bestIndex = currentFallbackIndex;
|
||
}
|
||
break;
|
||
default:
|
||
MOZ_ASSERT_UNREACHABLE("unexpected try-order value");
|
||
break;
|
||
}
|
||
}
|
||
|
||
if (!TryAdvanceFallback()) {
|
||
// If there are no further fallbacks, we're done.
|
||
if (bestSize >= 0) {
|
||
SeekFallbackTo(bestIndex);
|
||
} else {
|
||
// If we're going to roll back to the first try position, and the
|
||
// target's size was different, we need to do a "finalizing" reflow
|
||
// to ensure the inner layout is correct. If the size is unchanged,
|
||
// we can just break the fallback loop now.
|
||
if (isOverflowingCB && firstTryRect &&
|
||
firstTryRect->Size() != aKidFrame->GetSize()) {
|
||
SeekFallbackTo(firstTryIndex);
|
||
} else {
|
||
break;
|
||
}
|
||
}
|
||
// The fallback we've just selected is the final choice, regardless of
|
||
// whether it overflows.
|
||
finalizing = true;
|
||
}
|
||
|
||
// Try with the next fallback.
|
||
aKidFrame->AddStateBits(NS_FRAME_IS_DIRTY);
|
||
aStatus.Reset();
|
||
} while (true);
|
||
|
||
[&]() {
|
||
if (!isOverflowingCB || !firstTryRect) {
|
||
return;
|
||
}
|
||
// We gave up applying fallbacks. Recover previous values, if changed, and
|
||
// reset currentFallbackIndex/Style to match.
|
||
// Because we rolled back to first try data, our cache should be up-to-date.
|
||
currentFallbackIndex = firstTryIndex;
|
||
currentFallbackStyle = firstTryStyle;
|
||
auto rect = *firstTryRect;
|
||
if (isOverflowingCB &&
|
||
!aKidFrame->StylePosition()->mPositionArea.IsNone()) {
|
||
// The anchored element overflows the IMCB of its position-area. Would it
|
||
// have fit within the original CB? If so, shift it to stay within that.
|
||
if (rect.width <= aContainingBlockRects.mLocal.width &&
|
||
rect.height <= aContainingBlockRects.mLocal.height) {
|
||
if (rect.x < aContainingBlockRects.mLocal.x) {
|
||
rect.x = aContainingBlockRects.mLocal.x;
|
||
} else if (rect.XMost() > aContainingBlockRects.mLocal.XMost()) {
|
||
rect.x = aContainingBlockRects.mLocal.XMost() - rect.width;
|
||
}
|
||
if (rect.y < aContainingBlockRects.mLocal.y) {
|
||
rect.y = aContainingBlockRects.mLocal.y;
|
||
} else if (rect.YMost() > aContainingBlockRects.mLocal.YMost()) {
|
||
rect.y = aContainingBlockRects.mLocal.YMost() - rect.height;
|
||
}
|
||
}
|
||
}
|
||
if (rect.TopLeft() == aKidFrame->GetPosition()) {
|
||
return;
|
||
}
|
||
aKidFrame->SetPosition(rect.TopLeft());
|
||
if (aKidFrame->FrameMaintainsOverflow()) {
|
||
aKidFrame->UpdateOverflow();
|
||
}
|
||
}();
|
||
|
||
if (currentFallbackIndex) {
|
||
auto* lastSuccessfulPosition = aKidFrame->GetOrCreateDeletableProperty(
|
||
nsIFrame::LastSuccessfulPositionFallback());
|
||
// NOTE: We don't touch the last recorded index, that's done at resize
|
||
// observer time.
|
||
lastSuccessfulPosition->mLastIndex = currentFallbackIndex;
|
||
lastSuccessfulPosition->mLastStyle = std::move(currentFallbackStyle);
|
||
lastSuccessfulPosition->mTriedAllFallbacks = isOverflowingCB;
|
||
}
|
||
|
||
#ifdef DEBUG
|
||
if (nsBlockFrame::gNoisyReflow) {
|
||
nsIFrame::IndentBy(stdout, nsBlockFrame::gNoiseIndent - 1);
|
||
fmt::println("abspos {}: rect {}", aKidFrame->ListTag().get(),
|
||
ToString(aKidFrame->GetRect()));
|
||
}
|
||
#endif
|
||
// If author asked for `position-visibility: no-overflow` and we overflow
|
||
// `usedCB`, treat as "strongly hidden". Note that for anchored frames this
|
||
// happens in ComputePositionVisibility. But no-overflow also applies to
|
||
// non-anchored frames.
|
||
if (!aAnchorPosResolutionCache) {
|
||
aKidFrame->AddOrRemoveStateBits(
|
||
NS_FRAME_POSITION_VISIBILITY_HIDDEN,
|
||
isOverflowingCB && aKidFrame->StylePosition()->mPositionVisibility &
|
||
StylePositionVisibility::NO_OVERFLOW);
|
||
}
|
||
|
||
if (aOverflowAreas) {
|
||
aDelegatingFrame->ConsiderChildOverflow(
|
||
*aOverflowAreas, aKidFrame, OverflowAreaUnionFlags::ChildIsAbsPos);
|
||
}
|
||
}
|