tor-browser

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

AbsoluteContainingBlock.cpp (81460B)


      1 /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
      2 /* vim: set ts=8 sts=2 et sw=2 tw=80: */
      3 /* This Source Code Form is subject to the terms of the Mozilla Public
      4 * License, v. 2.0. If a copy of the MPL was not distributed with this
      5 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
      6 
      7 /*
      8 * code for managing absolutely positioned children of a rendering
      9 * object that is a containing block for them
     10 */
     11 
     12 #include "mozilla/AbsoluteContainingBlock.h"
     13 
     14 #include "AnchorPositioningUtils.h"
     15 #include "fmt/format.h"
     16 #include "mozilla/CSSAlignUtils.h"
     17 #include "mozilla/DebugOnly.h"
     18 #include "mozilla/PresShell.h"
     19 #include "mozilla/ReflowInput.h"
     20 #include "mozilla/ScrollContainerFrame.h"
     21 #include "mozilla/ViewportFrame.h"
     22 #include "mozilla/dom/ViewTransition.h"
     23 #include "nsCSSFrameConstructor.h"
     24 #include "nsContainerFrame.h"
     25 #include "nsGridContainerFrame.h"
     26 #include "nsIFrameInlines.h"
     27 #include "nsPlaceholderFrame.h"
     28 #include "nsPresContext.h"
     29 #include "nsPresContextInlines.h"
     30 
     31 #ifdef DEBUG
     32 #  include "nsBlockFrame.h"
     33 #endif
     34 
     35 using namespace mozilla;
     36 
     37 void AbsoluteContainingBlock::SetInitialChildList(nsIFrame* aDelegatingFrame,
     38                                                  FrameChildListID aListID,
     39                                                  nsFrameList&& aChildList) {
     40  MOZ_ASSERT(mChildListID == aListID, "unexpected child list name");
     41 #ifdef DEBUG
     42  nsIFrame::VerifyDirtyBitSet(aChildList);
     43  for (nsIFrame* f : aChildList) {
     44    MOZ_ASSERT(f->GetParent() == aDelegatingFrame, "Unexpected parent");
     45  }
     46 #endif
     47  mAbsoluteFrames = std::move(aChildList);
     48 }
     49 
     50 void AbsoluteContainingBlock::AppendFrames(nsIFrame* aDelegatingFrame,
     51                                           FrameChildListID aListID,
     52                                           nsFrameList&& aFrameList) {
     53  NS_ASSERTION(mChildListID == aListID, "unexpected child list");
     54 
     55  // Append the frames to our list of absolutely positioned frames
     56 #ifdef DEBUG
     57  nsIFrame::VerifyDirtyBitSet(aFrameList);
     58 #endif
     59  mAbsoluteFrames.AppendFrames(nullptr, std::move(aFrameList));
     60 
     61  // no damage to intrinsic widths, since absolutely positioned frames can't
     62  // change them
     63  aDelegatingFrame->PresShell()->FrameNeedsReflow(
     64      aDelegatingFrame, IntrinsicDirty::None, NS_FRAME_HAS_DIRTY_CHILDREN);
     65 }
     66 
     67 void AbsoluteContainingBlock::InsertFrames(nsIFrame* aDelegatingFrame,
     68                                           FrameChildListID aListID,
     69                                           nsIFrame* aPrevFrame,
     70                                           nsFrameList&& aFrameList) {
     71  NS_ASSERTION(mChildListID == aListID, "unexpected child list");
     72  NS_ASSERTION(!aPrevFrame || aPrevFrame->GetParent() == aDelegatingFrame,
     73               "inserting after sibling frame with different parent");
     74 
     75 #ifdef DEBUG
     76  nsIFrame::VerifyDirtyBitSet(aFrameList);
     77 #endif
     78  mAbsoluteFrames.InsertFrames(nullptr, aPrevFrame, std::move(aFrameList));
     79 
     80  // no damage to intrinsic widths, since absolutely positioned frames can't
     81  // change them
     82  aDelegatingFrame->PresShell()->FrameNeedsReflow(
     83      aDelegatingFrame, IntrinsicDirty::None, NS_FRAME_HAS_DIRTY_CHILDREN);
     84 }
     85 
     86 void AbsoluteContainingBlock::RemoveFrame(FrameDestroyContext& aContext,
     87                                          FrameChildListID aListID,
     88                                          nsIFrame* aOldFrame) {
     89  NS_ASSERTION(mChildListID == aListID, "unexpected child list");
     90 
     91  if (!aOldFrame->PresContext()->FragmentainerAwarePositioningEnabled()) {
     92    if (nsIFrame* nif = aOldFrame->GetNextInFlow()) {
     93      nif->GetParent()->DeleteNextInFlowChild(aContext, nif, false);
     94    }
     95    mAbsoluteFrames.DestroyFrame(aContext, aOldFrame);
     96    return;
     97  }
     98 
     99  AutoTArray<nsIFrame*, 8> delFrames;
    100  for (nsIFrame* f = aOldFrame; f; f = f->GetNextInFlow()) {
    101    delFrames.AppendElement(f);
    102  }
    103  for (nsIFrame* delFrame : Reversed(delFrames)) {
    104    delFrame->GetParent()->GetAbsoluteContainingBlock()->StealFrame(delFrame);
    105    delFrame->Destroy(aContext);
    106  }
    107 }
    108 
    109 nsFrameList AbsoluteContainingBlock::StealPushedChildList() {
    110  return std::move(mPushedAbsoluteFrames);
    111 }
    112 
    113 void AbsoluteContainingBlock::DrainPushedChildList(
    114    const nsIFrame* aDelegatingFrame) {
    115  MOZ_ASSERT(aDelegatingFrame->GetAbsoluteContainingBlock() == this,
    116             "aDelegatingFrame's absCB should be us!");
    117 
    118  // Our pushed absolute child list might be non-empty if our next-in-flow
    119  // hasn't reflowed yet. Move any child in that list that is a first-in-flow,
    120  // or whose prev-in-flow is not in our absolute child list, into our absolute
    121  // child list.
    122  for (auto iter = mPushedAbsoluteFrames.begin();
    123       iter != mPushedAbsoluteFrames.end();) {
    124    // Advance the iterator first, so it's safe to move |child|.
    125    nsIFrame* const child = *iter++;
    126    if (!child->GetPrevInFlow() ||
    127        child->GetPrevInFlow()->GetParent() != aDelegatingFrame) {
    128      mPushedAbsoluteFrames.RemoveFrame(child);
    129      mAbsoluteFrames.AppendFrame(nullptr, child);
    130    }
    131  }
    132 }
    133 
    134 bool AbsoluteContainingBlock::PrepareAbsoluteFrames(
    135    nsContainerFrame* aDelegatingFrame) {
    136  if (!aDelegatingFrame->PresContext()
    137           ->FragmentainerAwarePositioningEnabled()) {
    138    return HasAbsoluteFrames();
    139  }
    140 
    141  if (const nsIFrame* prevInFlow = aDelegatingFrame->GetPrevInFlow()) {
    142    AbsoluteContainingBlock* prevAbsCB =
    143        prevInFlow->GetAbsoluteContainingBlock();
    144    MOZ_ASSERT(prevAbsCB,
    145               "If this delegating frame has an absCB, its prev-in-flow must "
    146               "have one, too!");
    147 
    148    // Prepend the pushed absolute frames from the previous absCB to our
    149    // absolute child list.
    150    nsFrameList pushedFrames = prevAbsCB->StealPushedChildList();
    151    if (pushedFrames.NotEmpty()) {
    152      mAbsoluteFrames.InsertFrames(aDelegatingFrame, nullptr,
    153                                   std::move(pushedFrames));
    154    }
    155  }
    156 
    157  DrainPushedChildList(aDelegatingFrame);
    158 
    159  // Steal absolute frame's first-in-flow from our next-in-flow's child lists.
    160  for (const nsIFrame* nextInFlow = aDelegatingFrame->GetNextInFlow();
    161       nextInFlow; nextInFlow = nextInFlow->GetNextInFlow()) {
    162    AbsoluteContainingBlock* nextAbsCB =
    163        nextInFlow->GetAbsoluteContainingBlock();
    164    MOZ_ASSERT(nextAbsCB,
    165               "If this delegating frame has an absCB, its next-in-flow must "
    166               "have one, too!");
    167 
    168    nextAbsCB->DrainPushedChildList(nextInFlow);
    169 
    170    for (auto iter = nextAbsCB->GetChildList().begin();
    171         iter != nextAbsCB->GetChildList().end();) {
    172      nsIFrame* const child = *iter++;
    173      if (!child->GetPrevInFlow()) {
    174        nextAbsCB->StealFrame(child);
    175        mAbsoluteFrames.AppendFrame(aDelegatingFrame, child);
    176      }
    177    }
    178  }
    179 
    180  return HasAbsoluteFrames();
    181 }
    182 
    183 void AbsoluteContainingBlock::StealFrame(nsIFrame* aFrame) {
    184  const DebugOnly<bool> frameRemoved =
    185      mAbsoluteFrames.StartRemoveFrame(aFrame) ||
    186      mPushedAbsoluteFrames.ContinueRemoveFrame(aFrame);
    187  MOZ_ASSERT(frameRemoved, "Failed to find aFrame from our child lists!");
    188 }
    189 
    190 static void MaybeMarkAncestorsAsHavingDescendantDependentOnItsStaticPos(
    191    nsIFrame* aFrame, nsIFrame* aContainingBlockFrame) {
    192  MOZ_ASSERT(aFrame->HasAnyStateBits(NS_FRAME_OUT_OF_FLOW));
    193  if (!aFrame->StylePosition()->NeedsHypotheticalPositionIfAbsPos()) {
    194    return;
    195  }
    196  // We should have set the bit when reflowing the previous continuations
    197  // already.
    198  if (aFrame->GetPrevContinuation()) {
    199    return;
    200  }
    201 
    202  auto* placeholder = aFrame->GetPlaceholderFrame();
    203  MOZ_ASSERT(placeholder);
    204 
    205  // Only fixed-pos frames can escape their containing block.
    206  if (!placeholder->HasAnyStateBits(PLACEHOLDER_FOR_FIXEDPOS)) {
    207    return;
    208  }
    209 
    210  for (nsIFrame* ancestor = placeholder->GetParent(); ancestor;
    211       ancestor = ancestor->GetParent()) {
    212    // Walk towards the ancestor's first continuation. That's the only one that
    213    // really matters, since it's the only one restyling will look at. We also
    214    // flag the following continuations just so it's caught on the first
    215    // early-return ones just to avoid walking them over and over.
    216    do {
    217      if (ancestor->DescendantMayDependOnItsStaticPosition()) {
    218        return;
    219      }
    220      // Moving the containing block or anything above it would move our static
    221      // position as well, so no need to flag it or any of its ancestors.
    222      if (aFrame == aContainingBlockFrame) {
    223        return;
    224      }
    225      ancestor->SetDescendantMayDependOnItsStaticPosition(true);
    226      nsIFrame* prev = ancestor->GetPrevContinuation();
    227      if (!prev) {
    228        break;
    229      }
    230      ancestor = prev;
    231    } while (true);
    232  }
    233 }
    234 
    235 static bool IsSnapshotContainingBlock(const nsIFrame* aFrame) {
    236  return aFrame->Style()->GetPseudoType() ==
    237         PseudoStyleType::mozSnapshotContainingBlock;
    238 }
    239 
    240 static PhysicalAxes CheckEarlyCompensatingForScroll(const nsIFrame* aKidFrame) {
    241  // Three conditions to compensate for scroll, once a default anchor
    242  // exists:
    243  // * Used alignment property is `anchor-center`,
    244  // * `position-area` is not `none`, or
    245  // * `anchor()` function refers to default anchor, or an anchor that
    246  //   shares the same scroller with it.
    247  // Second condition is checkable right now, so do that.
    248  if (!aKidFrame->StylePosition()->mPositionArea.IsNone()) {
    249    return PhysicalAxes{PhysicalAxis::Horizontal, PhysicalAxis::Vertical};
    250  }
    251  return PhysicalAxes{};
    252 }
    253 
    254 static AnchorPosResolutionCache PopulateAnchorResolutionCache(
    255    const nsIFrame* aKidFrame, AnchorPosReferenceData* aData) {
    256  MOZ_ASSERT(aKidFrame->HasAnchorPosReference());
    257  // If the default anchor exists, it will likely be referenced (Except when
    258  // authors then use `anchor()` without referring to anchors whose nearest
    259  // scroller that of the default anchor, but that seems
    260  // counter-productive). This is a prerequisite for scroll compensation. We
    261  // also need to check for `anchor()` resolutions, so cache information for
    262  // default anchor and its scrollers right now.
    263  AnchorPosResolutionCache result{aData, {}};
    264  // Let this call populate the cache.
    265  const auto defaultAnchorInfo = AnchorPositioningUtils::ResolveAnchorPosRect(
    266      aKidFrame, aKidFrame->GetParent(), nullptr, false, &result);
    267  if (defaultAnchorInfo) {
    268    aData->AdjustCompensatingForScroll(
    269        CheckEarlyCompensatingForScroll(aKidFrame));
    270  }
    271  return result;
    272 }
    273 
    274 void AbsoluteContainingBlock::Reflow(nsContainerFrame* aDelegatingFrame,
    275                                     nsPresContext* aPresContext,
    276                                     const ReflowInput& aReflowInput,
    277                                     nsReflowStatus& aReflowStatus,
    278                                     const nsRect& aContainingBlock,
    279                                     AbsPosReflowFlags aFlags,
    280                                     OverflowAreas* aOverflowAreas) {
    281  const auto scrollableContainingBlock = [&]() -> nsRect {
    282    switch (aDelegatingFrame->Style()->GetPseudoType()) {
    283      case PseudoStyleType::scrolledContent:
    284      case PseudoStyleType::scrolledCanvas: {
    285        // FIXME(bug 2004432): This is close enough to what we want. In practice
    286        // we don't want to account for relative positioning and so on, but this
    287        // seems good enough for now.
    288        ScrollContainerFrame* sf = do_QueryFrame(aDelegatingFrame->GetParent());
    289        // Clamp to the scrollable range.
    290        return sf->GetUnsnappedScrolledRectInternal(
    291            aOverflowAreas->ScrollableOverflow(), aContainingBlock.Size());
    292      }
    293      default:
    294        break;
    295    }
    296    return aContainingBlock;
    297  }();
    298 
    299  nsReflowStatus reflowStatus;
    300  const bool reflowAll = aReflowInput.ShouldReflowAllKids();
    301  const bool cbWidthChanged = aFlags.contains(AbsPosReflowFlag::CBWidthChanged);
    302  const bool cbHeightChanged =
    303      aFlags.contains(AbsPosReflowFlag::CBHeightChanged);
    304  nsOverflowContinuationTracker tracker(aDelegatingFrame, true);
    305  for (nsIFrame* kidFrame : mAbsoluteFrames) {
    306    Maybe<AnchorPosResolutionCache> anchorPosResolutionCache;
    307    if (kidFrame->HasAnchorPosReference()) {
    308      auto* referenceData = kidFrame->SetOrUpdateDeletableProperty(
    309          nsIFrame::AnchorPosReferences());
    310      anchorPosResolutionCache =
    311          Some(PopulateAnchorResolutionCache(kidFrame, referenceData));
    312    } else {
    313      kidFrame->RemoveProperty(nsIFrame::AnchorPosReferences());
    314    }
    315 
    316    bool kidNeedsReflow =
    317        reflowAll || kidFrame->IsSubtreeDirty() ||
    318        FrameDependsOnContainer(kidFrame, cbWidthChanged, cbHeightChanged,
    319                                anchorPosResolutionCache.ptrOr(nullptr));
    320    if (kidFrame->IsSubtreeDirty()) {
    321      MaybeMarkAncestorsAsHavingDescendantDependentOnItsStaticPos(
    322          kidFrame, aDelegatingFrame);
    323    }
    324    const nscoord availBSize = aReflowInput.AvailableBSize();
    325    const WritingMode containerWM = aReflowInput.GetWritingMode();
    326    if (!kidNeedsReflow && availBSize != NS_UNCONSTRAINEDSIZE) {
    327      // If we need to redo pagination on the kid, we need to reflow it.
    328      // This can happen either if the available height shrunk and the
    329      // kid (or its overflow that creates overflow containers) is now
    330      // too large to fit in the available height, or if the available
    331      // height has increased and the kid has a next-in-flow that we
    332      // might need to pull from.
    333      WritingMode kidWM = kidFrame->GetWritingMode();
    334      if (containerWM.GetBlockDir() != kidWM.GetBlockDir()) {
    335        // Not sure what the right test would be here.
    336        kidNeedsReflow = true;
    337      } else {
    338        nscoord kidBEnd =
    339            kidFrame->GetLogicalRect(aContainingBlock.Size()).BEnd(kidWM);
    340        nscoord kidOverflowBEnd =
    341            LogicalRect(containerWM,
    342                        // Use ...RelativeToSelf to ignore transforms
    343                        kidFrame->ScrollableOverflowRectRelativeToSelf() +
    344                            kidFrame->GetPosition(),
    345                        aContainingBlock.Size())
    346                .BEnd(containerWM);
    347        NS_ASSERTION(kidOverflowBEnd >= kidBEnd,
    348                     "overflow area should be at least as large as frame rect");
    349        if (kidOverflowBEnd > availBSize ||
    350            (kidBEnd < availBSize && kidFrame->GetNextInFlow())) {
    351          kidNeedsReflow = true;
    352        }
    353      }
    354    }
    355    if (kidNeedsReflow && !aPresContext->HasPendingInterrupt()) {
    356      // Reflow the frame
    357      nsReflowStatus kidStatus;
    358      ReflowAbsoluteFrame(aDelegatingFrame, aPresContext, aReflowInput,
    359                          aContainingBlock, scrollableContainingBlock, aFlags,
    360                          kidFrame, kidStatus, aOverflowAreas,
    361                          anchorPosResolutionCache.ptrOr(nullptr));
    362      MOZ_ASSERT(!kidStatus.IsInlineBreakBefore(),
    363                 "ShouldAvoidBreakInside should prevent this from happening");
    364      nsIFrame* nextFrame = kidFrame->GetNextInFlow();
    365      if (aPresContext->FragmentainerAwarePositioningEnabled()) {
    366        if (!kidStatus.IsFullyComplete()) {
    367          if (!nextFrame) {
    368            nextFrame = aPresContext->PresShell()
    369                            ->FrameConstructor()
    370                            ->CreateContinuingFrame(kidFrame, aDelegatingFrame);
    371            nextFrame->AddStateBits(NS_FRAME_IS_PUSHED_OUT_OF_FLOW);
    372            mPushedAbsoluteFrames.AppendFrame(nullptr, nextFrame);
    373          } else if (nextFrame->GetParent() !=
    374                     aDelegatingFrame->GetNextInFlow()) {
    375            nextFrame->GetParent()->GetAbsoluteContainingBlock()->StealFrame(
    376                nextFrame);
    377            mPushedAbsoluteFrames.AppendFrame(aDelegatingFrame, nextFrame);
    378          }
    379          reflowStatus.MergeCompletionStatusFrom(kidStatus);
    380        } else if (nextFrame) {
    381          // kidFrame is fully-complete. Delete all its next-in-flows.
    382          FrameDestroyContext context(aPresContext->PresShell());
    383          nextFrame->GetParent()->GetAbsoluteContainingBlock()->RemoveFrame(
    384              context, FrameChildListID::Absolute, nextFrame);
    385        }
    386      } else {
    387        if (!kidStatus.IsFullyComplete() &&
    388            aDelegatingFrame->CanContainOverflowContainers()) {
    389          // Need a continuation
    390          if (!nextFrame) {
    391            nextFrame = aPresContext->PresShell()
    392                            ->FrameConstructor()
    393                            ->CreateContinuingFrame(kidFrame, aDelegatingFrame);
    394          }
    395          // Add it as an overflow container.
    396          // XXXfr This is a hack to fix some of our printing dataloss.
    397          // See bug 154892. Not sure how to do it "right" yet; probably want
    398          // to keep continuations within an AbsoluteContainingBlock eventually.
    399          //
    400          // NOTE(TYLin): we're now trying to conditionally do this "right" in
    401          // the other branch here, inside of the StaticPrefs pref-check.
    402          tracker.Insert(nextFrame, kidStatus);
    403          reflowStatus.MergeCompletionStatusFrom(kidStatus);
    404        } else if (nextFrame) {
    405          // Delete any continuations
    406          nsOverflowContinuationTracker::AutoFinish fini(&tracker, kidFrame);
    407          FrameDestroyContext context(aPresContext->PresShell());
    408          nextFrame->GetParent()->DeleteNextInFlowChild(context, nextFrame,
    409                                                        true);
    410        }
    411      }
    412    } else {
    413      if (aOverflowAreas) {
    414        if (!aPresContext->FragmentainerAwarePositioningEnabled()) {
    415          tracker.Skip(kidFrame, reflowStatus);
    416        }
    417        aDelegatingFrame->ConsiderChildOverflow(*aOverflowAreas, kidFrame);
    418      }
    419    }
    420 
    421    // Make a CheckForInterrupt call, here, not just HasPendingInterrupt.  That
    422    // will make sure that we end up reflowing aDelegatingFrame in cases when
    423    // one of our kids interrupted.  Otherwise we'd set the dirty or
    424    // dirty-children bit on the kid in the condition below, and then when
    425    // reflow completes and we go to mark dirty bits on all ancestors of that
    426    // kid we'll immediately bail out, because the kid already has a dirty bit.
    427    // In particular, we won't set any dirty bits on aDelegatingFrame, so when
    428    // the following reflow happens we won't reflow the kid in question.  This
    429    // might be slightly suboptimal in cases where |kidFrame| itself did not
    430    // interrupt, since we'll trigger a reflow of it too when it's not strictly
    431    // needed.  But the logic to not do that is enough more complicated, and
    432    // the case enough of an edge case, that this is probably better.
    433    if (kidNeedsReflow && aPresContext->CheckForInterrupt(aDelegatingFrame)) {
    434      if (aDelegatingFrame->HasAnyStateBits(NS_FRAME_IS_DIRTY)) {
    435        kidFrame->MarkSubtreeDirty();
    436      } else {
    437        kidFrame->AddStateBits(NS_FRAME_HAS_DIRTY_CHILDREN);
    438      }
    439    }
    440  }
    441 
    442  // Abspos frames can't cause their parent to be incomplete,
    443  // only overflow incomplete.
    444  if (reflowStatus.IsIncomplete()) {
    445    reflowStatus.SetOverflowIncomplete();
    446    reflowStatus.SetNextInFlowNeedsReflow();
    447  }
    448 
    449  aReflowStatus.MergeCompletionStatusFrom(reflowStatus);
    450 }
    451 
    452 static inline bool IsFixedPaddingSize(const LengthPercentage& aCoord) {
    453  return aCoord.ConvertsToLength();
    454 }
    455 static inline bool IsFixedMarginSize(const AnchorResolvedMargin& aCoord) {
    456  return aCoord->ConvertsToLength();
    457 }
    458 static inline bool IsFixedOffset(const AnchorResolvedInset& aInset) {
    459  // For anchor positioning functions, even if the computed value may be a
    460  // fixed length, it depends on the absolute containing block's size.
    461  return aInset->ConvertsToLength();
    462 }
    463 
    464 bool AbsoluteContainingBlock::FrameDependsOnContainer(
    465    nsIFrame* f, bool aCBWidthChanged, bool aCBHeightChanged,
    466    AnchorPosResolutionCache* aAnchorPosResolutionCache) {
    467  const nsStylePosition* pos = f->StylePosition();
    468  // See if f's position might have changed because it depends on a
    469  // placeholder's position.
    470  if (pos->NeedsHypotheticalPositionIfAbsPos()) {
    471    return true;
    472  }
    473  if (!aCBWidthChanged && !aCBHeightChanged) {
    474    // skip getting style data
    475    return false;
    476  }
    477  const nsStylePadding* padding = f->StylePadding();
    478  const nsStyleMargin* margin = f->StyleMargin();
    479  WritingMode wm = f->GetWritingMode();
    480  const auto anchorResolutionParams =
    481      AnchorPosResolutionParams::From(f, aAnchorPosResolutionCache);
    482  if (wm.IsVertical() ? aCBHeightChanged : aCBWidthChanged) {
    483    // See if f's inline-size might have changed.
    484    // If margin-inline-start/end, padding-inline-start/end,
    485    // inline-size, min/max-inline-size are all lengths, 'none', or enumerated,
    486    // then our frame isize does not depend on the parent isize.
    487    // Note that borders never depend on the parent isize.
    488    // XXX All of the enumerated values except -moz-available are ok too.
    489    if (nsStylePosition::ISizeDependsOnContainer(
    490            pos->ISize(wm, anchorResolutionParams)) ||
    491        nsStylePosition::MinISizeDependsOnContainer(
    492            pos->MinISize(wm, anchorResolutionParams)) ||
    493        nsStylePosition::MaxISizeDependsOnContainer(
    494            pos->MaxISize(wm, anchorResolutionParams)) ||
    495        !IsFixedPaddingSize(padding->mPadding.GetIStart(wm)) ||
    496        !IsFixedPaddingSize(padding->mPadding.GetIEnd(wm))) {
    497      return true;
    498    }
    499 
    500    // See if f's position might have changed. If we're RTL then the
    501    // rules are slightly different. We'll assume percentage or auto
    502    // margins will always induce a dependency on the size
    503    if (!IsFixedMarginSize(margin->GetMargin(LogicalSide::IStart, wm,
    504                                             anchorResolutionParams)) ||
    505        !IsFixedMarginSize(
    506            margin->GetMargin(LogicalSide::IEnd, wm, anchorResolutionParams))) {
    507      return true;
    508    }
    509  }
    510  if (wm.IsVertical() ? aCBWidthChanged : aCBHeightChanged) {
    511    // See if f's block-size might have changed.
    512    // If margin-block-start/end, padding-block-start/end,
    513    // min-block-size, and max-block-size are all lengths or 'none',
    514    // and bsize is a length or bsize and bend are auto and bstart is not auto,
    515    // then our frame bsize does not depend on the parent bsize.
    516    // Note that borders never depend on the parent bsize.
    517    //
    518    // FIXME(emilio): Should the BSize(wm).IsAuto() check also for the extremum
    519    // lengths?
    520    const auto bSize = pos->BSize(wm, anchorResolutionParams);
    521    const auto anchorOffsetResolutionParams =
    522        AnchorPosOffsetResolutionParams::UseCBFrameSize(anchorResolutionParams);
    523    if ((nsStylePosition::BSizeDependsOnContainer(bSize) &&
    524         !(bSize->IsAuto() &&
    525           pos->GetAnchorResolvedInset(LogicalSide::BEnd, wm,
    526                                       anchorOffsetResolutionParams)
    527               ->IsAuto() &&
    528           !pos->GetAnchorResolvedInset(LogicalSide::BStart, wm,
    529                                        anchorOffsetResolutionParams)
    530                ->IsAuto())) ||
    531        nsStylePosition::MinBSizeDependsOnContainer(
    532            pos->MinBSize(wm, anchorResolutionParams)) ||
    533        nsStylePosition::MaxBSizeDependsOnContainer(
    534            pos->MaxBSize(wm, anchorResolutionParams)) ||
    535        !IsFixedPaddingSize(padding->mPadding.GetBStart(wm)) ||
    536        !IsFixedPaddingSize(padding->mPadding.GetBEnd(wm))) {
    537      return true;
    538    }
    539 
    540    // See if f's position might have changed.
    541    if (!IsFixedMarginSize(margin->GetMargin(LogicalSide::BStart, wm,
    542                                             anchorResolutionParams)) ||
    543        !IsFixedMarginSize(
    544            margin->GetMargin(LogicalSide::BEnd, wm, anchorResolutionParams))) {
    545      return true;
    546    }
    547  }
    548 
    549  // Since we store coordinates relative to top and left, the position
    550  // of a frame depends on that of its container if it is fixed relative
    551  // to the right or bottom, or if it is positioned using percentages
    552  // relative to the left or top.  Because of the dependency on the
    553  // sides (left and top) that we use to store coordinates, these tests
    554  // are easier to do using physical coordinates rather than logical.
    555  if (aCBWidthChanged) {
    556    const auto anchorOffsetResolutionParams =
    557        AnchorPosOffsetResolutionParams::UseCBFrameSize(anchorResolutionParams);
    558    if (!IsFixedOffset(pos->GetAnchorResolvedInset(
    559            eSideLeft, anchorOffsetResolutionParams))) {
    560      return true;
    561    }
    562    // Note that even if 'left' is a length, our position can still
    563    // depend on the containing block width, because if our direction or
    564    // writing-mode moves from right to left (in either block or inline
    565    // progression) and 'right' is not 'auto', we will discard 'left'
    566    // and be positioned relative to the containing block right edge.
    567    // 'left' length and 'right' auto is the only combination we can be
    568    // sure of.
    569    if ((wm.GetInlineDir() == WritingMode::InlineDir::RTL ||
    570         wm.GetBlockDir() == WritingMode::BlockDir::RL) &&
    571        !pos->GetAnchorResolvedInset(eSideRight, anchorOffsetResolutionParams)
    572             ->IsAuto()) {
    573      return true;
    574    }
    575  }
    576  if (aCBHeightChanged) {
    577    const auto anchorOffsetResolutionParams =
    578        AnchorPosOffsetResolutionParams::UseCBFrameSize(anchorResolutionParams);
    579    if (!IsFixedOffset(pos->GetAnchorResolvedInset(
    580            eSideTop, anchorOffsetResolutionParams))) {
    581      return true;
    582    }
    583    // See comment above for width changes.
    584    if (wm.GetInlineDir() == WritingMode::InlineDir::BTT &&
    585        !pos->GetAnchorResolvedInset(eSideBottom, anchorOffsetResolutionParams)
    586             ->IsAuto()) {
    587      return true;
    588    }
    589  }
    590 
    591  return false;
    592 }
    593 
    594 void AbsoluteContainingBlock::DestroyFrames(DestroyContext& aContext) {
    595  mAbsoluteFrames.DestroyFrames(aContext);
    596  mPushedAbsoluteFrames.DestroyFrames(aContext);
    597 }
    598 
    599 void AbsoluteContainingBlock::MarkSizeDependentFramesDirty() {
    600  DoMarkFramesDirty(false);
    601 }
    602 
    603 void AbsoluteContainingBlock::MarkAllFramesDirty() { DoMarkFramesDirty(true); }
    604 
    605 void AbsoluteContainingBlock::DoMarkFramesDirty(bool aMarkAllDirty) {
    606  for (nsIFrame* kidFrame : mAbsoluteFrames) {
    607    if (aMarkAllDirty) {
    608      kidFrame->MarkSubtreeDirty();
    609    } else if (FrameDependsOnContainer(kidFrame, true, true)) {
    610      // Add the weakest flags that will make sure we reflow this frame later
    611      kidFrame->AddStateBits(NS_FRAME_HAS_DIRTY_CHILDREN);
    612    }
    613  }
    614 }
    615 
    616 // Given an out-of-flow frame, this method returns the parent frame of its
    617 // placeholder frame or null if it doesn't have a placeholder for some reason.
    618 static nsContainerFrame* GetPlaceholderContainer(nsIFrame* aPositionedFrame) {
    619  nsIFrame* placeholder = aPositionedFrame->GetPlaceholderFrame();
    620  return placeholder ? placeholder->GetParent() : nullptr;
    621 }
    622 
    623 struct NonAutoAlignParams {
    624  nscoord mCurrentStartInset;
    625  nscoord mCurrentEndInset;
    626 
    627  NonAutoAlignParams(nscoord aStartInset, nscoord aEndInset)
    628      : mCurrentStartInset(aStartInset), mCurrentEndInset(aEndInset) {}
    629 };
    630 
    631 /**
    632 * This function returns the offset of an abs/fixed-pos child's static
    633 * position, with respect to the "start" corner of its alignment container,
    634 * according to CSS Box Alignment.  This function only operates in a single
    635 * axis at a time -- callers can choose which axis via the |aAbsPosCBAxis|
    636 * parameter. This is called under two scenarios:
    637 * 1. We're statically positioning this absolutely positioned box, meaning
    638 *    that the offsets are auto and will change depending on the alignment
    639 *    of the box.
    640 * 2. The offsets are non-auto, but the element may not fill the inset-reduced
    641 *    containing block, so its margin box needs to be aligned in that axis.
    642 *    This is the step 4 of [1]. Should also be noted that, unlike static
    643 *    positioning, where we may confine the alignment area for flex/grid
    644 *    parent containers, we explicitly align to the inset-reduced absolute
    645 *    container size.
    646 *
    647 * [1]: https://drafts.csswg.org/css-position-3/#abspos-layout
    648 *
    649 * @param aKidReflowInput The ReflowInput for the to-be-aligned abspos child.
    650 * @param aKidSizeInAbsPosCBWM The child frame's size (after it's been given
    651 *                             the opportunity to reflow), in terms of
    652 *                             aAbsPosCBWM.
    653 * @param aAbsPosCBSize The abspos CB size, in terms of aAbsPosCBWM.
    654 * @param aPlaceholderContainer The parent of the child frame's corresponding
    655 *                              placeholder frame, cast to a nsContainerFrame.
    656 *                              (This will help us choose which alignment enum
    657 *                              we should use for the child.)
    658 * @param aAbsPosCBWM The child frame's containing block's WritingMode.
    659 * @param aAbsPosCBAxis The axis (of the containing block) that we should
    660 *                      be doing this computation for.
    661 * @param aNonAutoAlignParams Parameters, if specified, indicating that we're
    662 *                            handling scenario 2.
    663 */
    664 static nscoord OffsetToAlignedStaticPos(
    665    const ReflowInput& aKidReflowInput, const LogicalSize& aKidSizeInAbsPosCBWM,
    666    const LogicalSize& aAbsPosCBSize,
    667    const nsContainerFrame* aPlaceholderContainer, WritingMode aAbsPosCBWM,
    668    LogicalAxis aAbsPosCBAxis, Maybe<NonAutoAlignParams> aNonAutoAlignParams,
    669    const StylePositionArea& aPositionArea) {
    670  if (!aPlaceholderContainer) {
    671    // (The placeholder container should be the thing that kicks this whole
    672    // process off, by setting PLACEHOLDER_STATICPOS_NEEDS_CSSALIGN.  So it
    673    // should exist... but bail gracefully if it doesn't.)
    674    NS_ERROR(
    675        "Missing placeholder-container when computing a "
    676        "CSS Box Alignment static position");
    677    return 0;
    678  }
    679 
    680  // (Most of this function is simply preparing args that we'll pass to
    681  // AlignJustifySelf at the end.)
    682 
    683  // NOTE: Our alignment container is aPlaceholderContainer's content-box
    684  // (or an area within it, if aPlaceholderContainer is a grid). So, we'll
    685  // perform most of our arithmetic/alignment in aPlaceholderContainer's
    686  // WritingMode. For brevity, we use the abbreviation "pc" for "placeholder
    687  // container" in variables below.
    688  WritingMode pcWM = aPlaceholderContainer->GetWritingMode();
    689  LogicalSize absPosCBSizeInPCWM = aAbsPosCBSize.ConvertTo(pcWM, aAbsPosCBWM);
    690 
    691  // Find what axis aAbsPosCBAxis corresponds to, in placeholder's parent's
    692  // writing-mode.
    693  const LogicalAxis pcAxis = aAbsPosCBWM.ConvertAxisTo(aAbsPosCBAxis, pcWM);
    694  const LogicalSize alignAreaSize = [&]() {
    695    if (!aNonAutoAlignParams) {
    696      const bool placeholderContainerIsContainingBlock =
    697          aPlaceholderContainer == aKidReflowInput.mCBReflowInput->mFrame;
    698 
    699      LayoutFrameType parentType = aPlaceholderContainer->Type();
    700      LogicalSize alignAreaSize(pcWM);
    701      if (parentType == LayoutFrameType::FlexContainer) {
    702        // We store the frame rect in FinishAndStoreOverflow, which runs _after_
    703        // reflowing the absolute frames, so handle the special case of the
    704        // frame being the actual containing block here, by getting the size
    705        // from aAbsPosCBSize.
    706        //
    707        // The alignment container is the flex container's content box.
    708        if (placeholderContainerIsContainingBlock) {
    709          alignAreaSize = aAbsPosCBSize.ConvertTo(pcWM, aAbsPosCBWM);
    710          // aAbsPosCBSize is the padding-box, so substract the padding to get
    711          // the content box.
    712          alignAreaSize -=
    713              aPlaceholderContainer->GetLogicalUsedPadding(pcWM).Size(pcWM);
    714        } else {
    715          alignAreaSize = aPlaceholderContainer->GetLogicalSize(pcWM);
    716          LogicalMargin pcBorderPadding =
    717              aPlaceholderContainer->GetLogicalUsedBorderAndPadding(pcWM);
    718          alignAreaSize -= pcBorderPadding.Size(pcWM);
    719        }
    720        return alignAreaSize;
    721      }
    722      if (parentType == LayoutFrameType::GridContainer) {
    723        // This abspos elem's parent is a grid container. Per CSS Grid 10.1
    724        // & 10.2:
    725        //  - If the grid container *also* generates the abspos containing block
    726        //  (a
    727        // grid area) for this abspos child, we use that abspos containing block
    728        // as the alignment container, too. (And its size is aAbsPosCBSize.)
    729        //  - Otherwise, we use the grid's padding box as the alignment
    730        //  container.
    731        // https://drafts.csswg.org/css-grid/#static-position
    732        if (placeholderContainerIsContainingBlock) {
    733          // The alignment container is the grid area that we're using as the
    734          // absolute containing block.
    735          alignAreaSize = aAbsPosCBSize.ConvertTo(pcWM, aAbsPosCBWM);
    736        } else {
    737          // The alignment container is a the grid container's content box
    738          // (which we can get by subtracting away its border & padding from
    739          // frame's size):
    740          alignAreaSize = aPlaceholderContainer->GetLogicalSize(pcWM);
    741          LogicalMargin pcBorderPadding =
    742              aPlaceholderContainer->GetLogicalUsedBorderAndPadding(pcWM);
    743          alignAreaSize -= pcBorderPadding.Size(pcWM);
    744        }
    745        return alignAreaSize;
    746      }
    747    }
    748    // Either we're in scenario 1 but within a non-flex/grid parent, or in
    749    // scenario 2.
    750    return aAbsPosCBSize.ConvertTo(pcWM, aAbsPosCBWM);
    751  }();
    752 
    753  const nscoord existingOffset = aNonAutoAlignParams
    754                                     ? aNonAutoAlignParams->mCurrentStartInset +
    755                                           aNonAutoAlignParams->mCurrentEndInset
    756                                     : 0;
    757  const nscoord alignAreaSizeInAxis =
    758      ((pcAxis == LogicalAxis::Inline) ? alignAreaSize.ISize(pcWM)
    759                                       : alignAreaSize.BSize(pcWM)) -
    760      existingOffset;
    761 
    762  using AlignJustifyFlag = CSSAlignUtils::AlignJustifyFlag;
    763  CSSAlignUtils::AlignJustifyFlags flags(AlignJustifyFlag::IgnoreAutoMargins);
    764  // Given that scenario 2 ignores the parent container type, special handling
    765  // of absolutely-positioned child is also ignored.
    766  StyleAlignFlags alignConst =
    767      aNonAutoAlignParams
    768          ? aPlaceholderContainer
    769                ->CSSAlignmentForAbsPosChildWithinContainingBlock(
    770                    aKidReflowInput, pcAxis, aPositionArea, absPosCBSizeInPCWM)
    771          : aPlaceholderContainer->CSSAlignmentForAbsPosChild(aKidReflowInput,
    772                                                              pcAxis);
    773  // If the safe bit in alignConst is set, set the safe flag in |flags|.
    774  const auto safetyBits =
    775      alignConst & (StyleAlignFlags::SAFE | StyleAlignFlags::UNSAFE);
    776  alignConst &= ~StyleAlignFlags::FLAG_BITS;
    777  if (safetyBits & StyleAlignFlags::SAFE) {
    778    flags += AlignJustifyFlag::OverflowSafe;
    779  }
    780 
    781  // Find out if placeholder-container & the OOF child have the same start-sides
    782  // in the placeholder-container's pcAxis.
    783  WritingMode kidWM = aKidReflowInput.GetWritingMode();
    784  if (pcWM.ParallelAxisStartsOnSameSide(pcAxis, kidWM)) {
    785    flags += AlignJustifyFlag::SameSide;
    786  }
    787 
    788  if (aNonAutoAlignParams) {
    789    flags += AlignJustifyFlag::AligningMarginBox;
    790  }
    791 
    792  // (baselineAdjust is unused. CSSAlignmentForAbsPosChild() should've
    793  // converted 'baseline'/'last baseline' enums to their fallback values.)
    794  const nscoord baselineAdjust = nscoord(0);
    795 
    796  // AlignJustifySelf operates in the kid's writing mode, so we need to
    797  // represent the child's size and the desired axis in that writing mode:
    798  LogicalSize kidSizeInOwnWM =
    799      aKidSizeInAbsPosCBWM.ConvertTo(kidWM, aAbsPosCBWM);
    800  const LogicalAxis kidAxis = aAbsPosCBWM.ConvertAxisTo(aAbsPosCBAxis, kidWM);
    801 
    802  // Build an Inset Modified anchor info from the anchor which can be used to
    803  // align to the anchor-center, if AlignJustifySelf is AnchorCenter.
    804  Maybe<CSSAlignUtils::AnchorAlignInfo> anchorAlignInfo;
    805  if (alignConst == StyleAlignFlags::ANCHOR_CENTER &&
    806      aKidReflowInput.mAnchorPosResolutionCache) {
    807    auto* referenceData =
    808        aKidReflowInput.mAnchorPosResolutionCache->mReferenceData;
    809    if (referenceData) {
    810      const auto* cachedData =
    811          referenceData->Lookup(referenceData->mDefaultAnchorName);
    812      if (cachedData && *cachedData) {
    813        referenceData->AdjustCompensatingForScroll(
    814            aAbsPosCBWM.PhysicalAxis(aAbsPosCBAxis));
    815        const auto& data = cachedData->ref();
    816        if (data.mOffsetData) {
    817          const nsSize containerSize =
    818              aAbsPosCBSize.GetPhysicalSize(aAbsPosCBWM);
    819          const nsRect anchorRect(data.mOffsetData->mOrigin, data.mSize);
    820          const LogicalRect logicalAnchorRect{aAbsPosCBWM, anchorRect,
    821                                              containerSize};
    822          const auto axisInAbsPosCBWM =
    823              kidWM.ConvertAxisTo(kidAxis, aAbsPosCBWM);
    824          const auto anchorStart =
    825              logicalAnchorRect.Start(axisInAbsPosCBWM, aAbsPosCBWM);
    826          const auto anchorSize =
    827              logicalAnchorRect.Size(axisInAbsPosCBWM, aAbsPosCBWM);
    828          anchorAlignInfo =
    829              Some(CSSAlignUtils::AnchorAlignInfo{anchorStart, anchorSize});
    830          if (aNonAutoAlignParams) {
    831            anchorAlignInfo->mAnchorStart -=
    832                aNonAutoAlignParams->mCurrentStartInset;
    833          }
    834        }
    835      }
    836    }
    837  }
    838 
    839  nscoord offset = CSSAlignUtils::AlignJustifySelf(
    840      alignConst, kidAxis, flags, baselineAdjust, alignAreaSizeInAxis,
    841      aKidReflowInput, kidSizeInOwnWM, anchorAlignInfo);
    842 
    843  // Safe alignment clamping for anchor-center.
    844  // When using anchor-center with the safe keyword, or when both insets are
    845  // auto (which defaults to safe behavior), clamp the element to stay within
    846  // the containing block.
    847  if ((!aNonAutoAlignParams || (safetyBits & StyleAlignFlags::SAFE)) &&
    848      alignConst == StyleAlignFlags::ANCHOR_CENTER) {
    849    const auto cbSize = aAbsPosCBSize.Size(aAbsPosCBAxis, aAbsPosCBWM);
    850    const auto kidSize = aKidSizeInAbsPosCBWM.Size(aAbsPosCBAxis, aAbsPosCBWM);
    851 
    852    if (aNonAutoAlignParams) {
    853      const nscoord currentStartInset = aNonAutoAlignParams->mCurrentStartInset;
    854      const nscoord finalStart = currentStartInset + offset;
    855      const nscoord clampedStart =
    856          CSSMinMax(finalStart, nscoord(0), cbSize - kidSize);
    857      offset = clampedStart - currentStartInset;
    858    } else {
    859      offset = CSSMinMax(offset, nscoord(0), cbSize - kidSize);
    860    }
    861  }
    862 
    863  const auto rawAlignConst =
    864      (pcAxis == LogicalAxis::Inline)
    865          ? aKidReflowInput.mStylePosition->mJustifySelf._0
    866          : aKidReflowInput.mStylePosition->mAlignSelf._0;
    867  if (aNonAutoAlignParams && !safetyBits &&
    868      (rawAlignConst != StyleAlignFlags::AUTO ||
    869       alignConst == StyleAlignFlags::ANCHOR_CENTER)) {
    870    // No `safe` or `unsafe` specified - "in-between" behaviour for relevant
    871    // alignment values: https://drafts.csswg.org/css-position-3/#abspos-layout
    872    // Skip if the raw self alignment for this element is `auto` to preserve
    873    // legacy behaviour, except in the case where the resolved value is
    874    // anchor-center (where "legacy behavior" is not a concern).
    875    // Follows https://drafts.csswg.org/css-align-3/#auto-safety-position
    876    const auto cbSize = aAbsPosCBSize.Size(aAbsPosCBAxis, aAbsPosCBWM);
    877    // IMCB stands for "Inset-Modified Containing Block."
    878    const auto imcbStart = aNonAutoAlignParams->mCurrentStartInset;
    879    const auto imcbEnd = cbSize - aNonAutoAlignParams->mCurrentEndInset;
    880    const auto kidSize = aKidSizeInAbsPosCBWM.Size(aAbsPosCBAxis, aAbsPosCBWM);
    881    const auto kidStart = aNonAutoAlignParams->mCurrentStartInset + offset;
    882    const auto kidEnd = kidStart + kidSize;
    883    // "[...] the overflow limit rect is the bounding rectangle of the alignment
    884    // subject’s inset-modified containing block and its original containing
    885    // block."
    886    const auto overflowLimitRectStart = std::min(0, imcbStart);
    887    const auto overflowLimitRectEnd = std::max(cbSize, imcbEnd);
    888 
    889    if (kidStart >= imcbStart && kidEnd <= imcbEnd) {
    890      // 1. We fit inside the IMCB, no action needed.
    891    } else if (kidSize <= overflowLimitRectEnd - overflowLimitRectStart) {
    892      // 2. We overflowed IMCB, try to cover IMCB completely, if it's not.
    893      if (kidStart <= imcbStart && kidEnd >= imcbEnd) {
    894        // IMCB already covered, ensure that we aren't escaping the limit rect.
    895        if (kidStart < overflowLimitRectStart) {
    896          offset += overflowLimitRectStart - kidStart;
    897        } else if (kidEnd > overflowLimitRectEnd) {
    898          offset -= kidEnd - overflowLimitRectEnd;
    899        }
    900      } else if (kidEnd < imcbEnd && kidStart < imcbStart) {
    901        // Space to end, overflowing on start - nudge to end.
    902        offset += std::min(imcbStart - kidStart, imcbEnd - kidEnd);
    903      } else if (kidStart > imcbStart && kidEnd > imcbEnd) {
    904        // Space to start, overflowing on end - nudge to start.
    905        offset -= std::min(kidEnd - imcbEnd, kidStart - imcbStart);
    906      }
    907    } else {
    908      // 3. We'll overflow the limit rect. Start align the subject int overflow
    909      // limit rect.
    910      offset =
    911          -aNonAutoAlignParams->mCurrentStartInset + overflowLimitRectStart;
    912    }
    913  }
    914 
    915  // "offset" is in terms of the CSS Box Alignment container (i.e. it's in
    916  // terms of pcWM). But our return value needs to in terms of the containing
    917  // block's writing mode, which might have the opposite directionality in the
    918  // given axis. In that case, we just need to negate "offset" when returning,
    919  // to make it have the right effect as an offset for coordinates in the
    920  // containing block's writing mode.
    921  if (!pcWM.ParallelAxisStartsOnSameSide(pcAxis, aAbsPosCBWM)) {
    922    return -offset;
    923  }
    924  return offset;
    925 }
    926 
    927 void AbsoluteContainingBlock::ResolveSizeDependentOffsets(
    928    ReflowInput& aKidReflowInput, const LogicalSize& aCBSize,
    929    const LogicalSize& aKidSize, const LogicalMargin& aMargin,
    930    const StylePositionArea& aResolvedPositionArea, LogicalMargin& aOffsets) {
    931  WritingMode outerWM = aKidReflowInput.mParentReflowInput->GetWritingMode();
    932 
    933  // Now that we know the child's size, we resolve any sentinel values in its
    934  // IStart/BStart offset coordinates that depend on that size.
    935  //  * NS_AUTOOFFSET indicates that the child's position in the given axis
    936  // is determined by its end-wards offset property, combined with its size and
    937  // available space. e.g.: "top: auto; height: auto; bottom: 50px"
    938  //  * m{I,B}OffsetsResolvedAfterSize indicate that the child is using its
    939  // static position in that axis, *and* its static position is determined by
    940  // the axis-appropriate css-align property (which may require the child's
    941  // size, e.g. to center it within the parent).
    942  if ((NS_AUTOOFFSET == aOffsets.IStart(outerWM)) ||
    943      (NS_AUTOOFFSET == aOffsets.BStart(outerWM)) ||
    944      aKidReflowInput.mFlags.mIOffsetsNeedCSSAlign ||
    945      aKidReflowInput.mFlags.mBOffsetsNeedCSSAlign) {
    946    // placeholderContainer is used in each of the m{I,B}OffsetsNeedCSSAlign
    947    // clauses. We declare it at this scope so we can avoid having to look
    948    // it up twice (and only look it up if it's needed).
    949    nsContainerFrame* placeholderContainer = nullptr;
    950 
    951    if (NS_AUTOOFFSET == aOffsets.IStart(outerWM)) {
    952      NS_ASSERTION(NS_AUTOOFFSET != aOffsets.IEnd(outerWM),
    953                   "Can't solve for both start and end");
    954      aOffsets.IStart(outerWM) =
    955          aCBSize.ISize(outerWM) - aOffsets.IEnd(outerWM) -
    956          aMargin.IStartEnd(outerWM) - aKidSize.ISize(outerWM);
    957    } else if (aKidReflowInput.mFlags.mIOffsetsNeedCSSAlign) {
    958      placeholderContainer = GetPlaceholderContainer(aKidReflowInput.mFrame);
    959      nscoord offset = OffsetToAlignedStaticPos(
    960          aKidReflowInput, aKidSize, aCBSize, placeholderContainer, outerWM,
    961          LogicalAxis::Inline, Nothing{}, aResolvedPositionArea);
    962      // Shift IStart from its current position (at start corner of the
    963      // alignment container) by the returned offset.  And set IEnd to the
    964      // distance between the kid's end edge to containing block's end edge.
    965      aOffsets.IStart(outerWM) += offset;
    966      aOffsets.IEnd(outerWM) =
    967          aCBSize.ISize(outerWM) -
    968          (aOffsets.IStart(outerWM) + aKidSize.ISize(outerWM));
    969    }
    970 
    971    if (NS_AUTOOFFSET == aOffsets.BStart(outerWM)) {
    972      aOffsets.BStart(outerWM) =
    973          aCBSize.BSize(outerWM) - aOffsets.BEnd(outerWM) -
    974          aMargin.BStartEnd(outerWM) - aKidSize.BSize(outerWM);
    975    } else if (aKidReflowInput.mFlags.mBOffsetsNeedCSSAlign) {
    976      if (!placeholderContainer) {
    977        placeholderContainer = GetPlaceholderContainer(aKidReflowInput.mFrame);
    978      }
    979      nscoord offset = OffsetToAlignedStaticPos(
    980          aKidReflowInput, aKidSize, aCBSize, placeholderContainer, outerWM,
    981          LogicalAxis::Block, Nothing{}, aResolvedPositionArea);
    982      // Shift BStart from its current position (at start corner of the
    983      // alignment container) by the returned offset.  And set BEnd to the
    984      // distance between the kid's end edge to containing block's end edge.
    985      aOffsets.BStart(outerWM) += offset;
    986      aOffsets.BEnd(outerWM) =
    987          aCBSize.BSize(outerWM) -
    988          (aOffsets.BStart(outerWM) + aKidSize.BSize(outerWM));
    989    }
    990    aKidReflowInput.SetComputedLogicalOffsets(outerWM, aOffsets);
    991  }
    992 }
    993 
    994 void AbsoluteContainingBlock::ResolveAutoMarginsAfterLayout(
    995    ReflowInput& aKidReflowInput, const LogicalSize& aCBSize,
    996    const LogicalSize& aKidSize, LogicalMargin& aMargin,
    997    const LogicalMargin& aOffsets) {
    998  WritingMode outerWM = aKidReflowInput.mParentReflowInput->GetWritingMode();
    999  const auto& styleMargin = aKidReflowInput.mStyleMargin;
   1000  const auto anchorResolutionParams =
   1001      AnchorPosResolutionParams::From(&aKidReflowInput);
   1002 
   1003  auto ResolveMarginsInAxis = [&](LogicalAxis aAxis) {
   1004    const auto startSide = MakeLogicalSide(aAxis, LogicalEdge::Start);
   1005    const auto endSide = MakeLogicalSide(aAxis, LogicalEdge::End);
   1006 
   1007    // No need to substract border sizes because aKidSize has it included
   1008    // already. Also, if any offset is auto, the auto margin resolves to zero.
   1009    // https://drafts.csswg.org/css-position-3/#abspos-margins
   1010    const bool autoOffset =
   1011        aOffsets.Side(startSide, outerWM) == NS_AUTOOFFSET ||
   1012        aOffsets.Side(endSide, outerWM) == NS_AUTOOFFSET;
   1013 
   1014    nscoord availMarginSpace;
   1015    if (autoOffset) {
   1016      availMarginSpace = 0;
   1017    } else {
   1018      const nscoord stretchFitSize = std::max(
   1019          0, aCBSize.Size(aAxis, outerWM) - aOffsets.StartEnd(aAxis, outerWM) -
   1020                 aMargin.StartEnd(aAxis, outerWM));
   1021      availMarginSpace = stretchFitSize - aKidSize.Size(aAxis, outerWM);
   1022    }
   1023 
   1024    const bool startSideMarginIsAuto =
   1025        styleMargin->GetMargin(startSide, outerWM, anchorResolutionParams)
   1026            ->IsAuto();
   1027    const bool endSideMarginIsAuto =
   1028        styleMargin->GetMargin(endSide, outerWM, anchorResolutionParams)
   1029            ->IsAuto();
   1030 
   1031    if (aAxis == LogicalAxis::Inline) {
   1032      ReflowInput::ComputeAbsPosInlineAutoMargin(availMarginSpace, outerWM,
   1033                                                 startSideMarginIsAuto,
   1034                                                 endSideMarginIsAuto, aMargin);
   1035    } else {
   1036      ReflowInput::ComputeAbsPosBlockAutoMargin(availMarginSpace, outerWM,
   1037                                                startSideMarginIsAuto,
   1038                                                endSideMarginIsAuto, aMargin);
   1039    }
   1040  };
   1041 
   1042  ResolveMarginsInAxis(LogicalAxis::Inline);
   1043  ResolveMarginsInAxis(LogicalAxis::Block);
   1044  aKidReflowInput.SetComputedLogicalMargin(outerWM, aMargin);
   1045 
   1046  nsMargin* propValue =
   1047      aKidReflowInput.mFrame->GetProperty(nsIFrame::UsedMarginProperty());
   1048  // InitOffsets should've created a UsedMarginProperty for us, if any margin is
   1049  // auto.
   1050  MOZ_ASSERT_IF(
   1051      styleMargin->HasInlineAxisAuto(outerWM, anchorResolutionParams) ||
   1052          styleMargin->HasBlockAxisAuto(outerWM, anchorResolutionParams),
   1053      propValue);
   1054  if (propValue) {
   1055    *propValue = aMargin.GetPhysicalMargin(outerWM);
   1056  }
   1057 }
   1058 
   1059 struct None {};
   1060 using OldCacheState = Variant<None, AnchorPosResolutionCache::PositionTryBackup,
   1061                              AnchorPosResolutionCache::PositionTryFullBackup>;
   1062 
   1063 struct MOZ_STACK_CLASS MOZ_RAII AutoFallbackStyleSetter {
   1064  AutoFallbackStyleSetter(nsIFrame* aFrame, ComputedStyle* aFallbackStyle,
   1065                          AnchorPosResolutionCache* aCache, bool aIsFirstTry)
   1066      : mFrame(aFrame), mCache{aCache}, mOldCacheState{None{}} {
   1067    if (aFallbackStyle) {
   1068      mOldStyle = aFrame->SetComputedStyleWithoutNotification(aFallbackStyle);
   1069    }
   1070    // We need to be able to "go back" to the old, first try (Which is not
   1071    // necessarily base style) cache.
   1072    if (!aIsFirstTry && aCache) {
   1073      // New fallback could just be a flip keyword.
   1074      if (mOldStyle && mOldStyle->StylePosition()->mPositionAnchor !=
   1075                           aFrame->StylePosition()->mPositionAnchor) {
   1076        mOldCacheState =
   1077            OldCacheState{aCache->TryPositionWithDifferentDefaultAnchor()};
   1078        *aCache = PopulateAnchorResolutionCache(aFrame, aCache->mReferenceData);
   1079      } else {
   1080        mOldCacheState =
   1081            OldCacheState{aCache->TryPositionWithSameDefaultAnchor()};
   1082        if (aCache->mDefaultAnchorCache.mAnchor) {
   1083          aCache->mReferenceData->AdjustCompensatingForScroll(
   1084              CheckEarlyCompensatingForScroll(aFrame));
   1085        }
   1086      }
   1087    }
   1088  }
   1089 
   1090  ~AutoFallbackStyleSetter() {
   1091    if (mOldStyle) {
   1092      mFrame->SetComputedStyleWithoutNotification(std::move(mOldStyle));
   1093    }
   1094    std::move(mOldCacheState)
   1095        .match(
   1096            [](None&&) {},
   1097            [&](AnchorPosResolutionCache::PositionTryBackup&& aBackup) {
   1098              mCache->UndoTryPositionWithSameDefaultAnchor(std::move(aBackup));
   1099            },
   1100            [&](AnchorPosResolutionCache::PositionTryFullBackup&& aBackup) {
   1101              mCache->UndoTryPositionWithDifferentDefaultAnchor(
   1102                  std::move(aBackup));
   1103            });
   1104  }
   1105 
   1106  void CommitCurrentFallback() {
   1107    mOldCacheState = OldCacheState{None{}};
   1108    // If we have a non-layout dependent margin / paddings, which are different
   1109    // from our original style, we need to make sure to commit it into the frame
   1110    // property so that it doesn't get lost after returning from reflow.
   1111    nsMargin margin;
   1112    if (mOldStyle &&
   1113        !mOldStyle->StyleMargin()->MarginEquals(*mFrame->StyleMargin()) &&
   1114        mFrame->StyleMargin()->GetMargin(margin)) {
   1115      mFrame->SetOrUpdateDeletableProperty(nsIFrame::UsedMarginProperty(),
   1116                                           margin);
   1117    }
   1118  }
   1119 
   1120 private:
   1121  nsIFrame* const mFrame;
   1122  RefPtr<ComputedStyle> mOldStyle;
   1123  AnchorPosResolutionCache* const mCache;
   1124  OldCacheState mOldCacheState;
   1125 };
   1126 
   1127 struct AnchorShiftInfo {
   1128  nsPoint mOffset;
   1129  StylePositionArea mResolvedArea;
   1130 };
   1131 
   1132 struct ContainingBlockRect {
   1133  Maybe<AnchorShiftInfo> mAnchorShiftInfo;
   1134  nsRect mMaybeScrollableRect;
   1135  nsRect mFinalRect;
   1136 
   1137  explicit ContainingBlockRect(const nsRect& aRect)
   1138      : mMaybeScrollableRect{aRect}, mFinalRect{aRect} {}
   1139  ContainingBlockRect(const nsRect& aMaybeScrollableRect,
   1140                      const nsRect& aFinalRect)
   1141      : mMaybeScrollableRect{aMaybeScrollableRect}, mFinalRect{aFinalRect} {}
   1142  ContainingBlockRect(const nsPoint& aOffset,
   1143                      const StylePositionArea& aResolvedArea,
   1144                      const nsRect& aMaybeScrollableRect,
   1145                      const nsRect& aFinalRect)
   1146      : mAnchorShiftInfo{Some(AnchorShiftInfo{aOffset, aResolvedArea})},
   1147        mMaybeScrollableRect{aMaybeScrollableRect},
   1148        mFinalRect{aFinalRect} {}
   1149 
   1150  StylePositionArea ResolvedPositionArea() const {
   1151    return mAnchorShiftInfo
   1152        .map([](const AnchorShiftInfo& aInfo) { return aInfo.mResolvedArea; })
   1153        .valueOr(StylePositionArea{});
   1154  }
   1155 };
   1156 
   1157 static SideBits GetScrollCompensatedSidesFor(
   1158    const StylePositionArea& aPositionArea) {
   1159  SideBits sides{SideBits::eNone};
   1160  // The opposite side of the direction keyword is attached to the
   1161  // position-anchor grid, which is then attached to the anchor, and so is
   1162  // scroll compensated. `center` is constrained by the position-area grid
   1163  // on both sides. `span-all` is unconstrained in that axis.
   1164  if (aPositionArea.first == StylePositionAreaKeyword::Left ||
   1165      aPositionArea.first == StylePositionAreaKeyword::SpanLeft) {
   1166    sides |= SideBits::eRight;
   1167  } else if (aPositionArea.first == StylePositionAreaKeyword::Right ||
   1168             aPositionArea.first == StylePositionAreaKeyword::SpanRight) {
   1169    sides |= SideBits::eLeft;
   1170  } else if (aPositionArea.first == StylePositionAreaKeyword::Center) {
   1171    sides |= SideBits::eLeftRight;
   1172  }
   1173 
   1174  if (aPositionArea.second == StylePositionAreaKeyword::Top ||
   1175      aPositionArea.second == StylePositionAreaKeyword::SpanTop) {
   1176    sides |= SideBits::eBottom;
   1177  } else if (aPositionArea.second == StylePositionAreaKeyword::Bottom ||
   1178             aPositionArea.second == StylePositionAreaKeyword::SpanBottom) {
   1179    sides |= SideBits::eTop;
   1180  } else if (aPositionArea.first == StylePositionAreaKeyword::Center) {
   1181    sides |= SideBits::eTopBottom;
   1182  }
   1183 
   1184  return sides;
   1185 }
   1186 
   1187 // XXX Optimize the case where it's a resize reflow and the absolutely
   1188 // positioned child has the exact same size and position and skip the
   1189 // reflow...
   1190 void AbsoluteContainingBlock::ReflowAbsoluteFrame(
   1191    nsContainerFrame* aDelegatingFrame, nsPresContext* aPresContext,
   1192    const ReflowInput& aReflowInput, const nsRect& aOriginalContainingBlockRect,
   1193    const nsRect& aOriginalScrollableContainingBlockRect,
   1194    AbsPosReflowFlags aFlags, nsIFrame* aKidFrame, nsReflowStatus& aStatus,
   1195    OverflowAreas* aOverflowAreas,
   1196    AnchorPosResolutionCache* aAnchorPosResolutionCache) {
   1197  MOZ_ASSERT(aStatus.IsEmpty(), "Caller should pass a fresh reflow status!");
   1198 
   1199 #ifdef DEBUG
   1200  if (nsBlockFrame::gNoisyReflow) {
   1201    nsIFrame::IndentBy(stdout, nsBlockFrame::gNoiseIndent);
   1202    fmt::println(
   1203        FMT_STRING("abspos {}: begin reflow: availSize={}, orig cbRect={}"),
   1204        aKidFrame->ListTag(), ToString(aReflowInput.AvailableSize()),
   1205        ToString(aOriginalContainingBlockRect));
   1206  }
   1207  AutoNoisyIndenter indent(nsBlockFrame::gNoisy);
   1208 #endif  // DEBUG
   1209 
   1210  const WritingMode outerWM = aReflowInput.GetWritingMode();
   1211  const WritingMode wm = aKidFrame->GetWritingMode();
   1212 
   1213  const bool isGrid = aFlags.contains(AbsPosReflowFlag::IsGridContainerCB);
   1214  auto fallbacks =
   1215      aKidFrame->StylePosition()->mPositionTryFallbacks._0.AsSpan();
   1216  Maybe<uint32_t> currentFallbackIndex;
   1217  const StylePositionTryFallbacksItem* currentFallback = nullptr;
   1218  RefPtr<ComputedStyle> currentFallbackStyle;
   1219  RefPtr<ComputedStyle> firstTryStyle;
   1220  Maybe<uint32_t> firstTryIndex;
   1221  // If non-'normal' position-try-order is in effect, we keep track of the
   1222  // index of the "best" option seen, and its size in the relevant axis, so
   1223  // that once all fallbacks have been considered we can reset to the one
   1224  // that provided the most space.
   1225  Maybe<uint32_t> bestIndex;
   1226  nscoord bestSize = -1;
   1227  // Flag to indicate that we've determined which fallback to use and should
   1228  // exit the loop.
   1229  bool finalizing = false;
   1230 
   1231  auto tryOrder = aKidFrame->StylePosition()->mPositionTryOrder;
   1232  // If position-try-order is a logical value, resolve to physical using
   1233  // the containing block's writing mode.
   1234  switch (tryOrder) {
   1235    case StylePositionTryOrder::MostInlineSize:
   1236      tryOrder = outerWM.IsVertical() ? StylePositionTryOrder::MostHeight
   1237                                      : StylePositionTryOrder::MostWidth;
   1238      break;
   1239    case StylePositionTryOrder::MostBlockSize:
   1240      tryOrder = outerWM.IsVertical() ? StylePositionTryOrder::MostWidth
   1241                                      : StylePositionTryOrder::MostHeight;
   1242      break;
   1243    default:
   1244      break;
   1245  }
   1246 
   1247  // Set the current fallback to the given index, or reset to the base position
   1248  // if Nothing() is passed.
   1249  auto SeekFallbackTo = [&](Maybe<uint32_t> aIndex) -> bool {
   1250    if (!aIndex) {
   1251      currentFallbackIndex = Nothing();
   1252      currentFallback = nullptr;
   1253      currentFallbackStyle = nullptr;
   1254      return true;
   1255    }
   1256    uint32_t index = *aIndex;
   1257    if (index >= fallbacks.Length()) {
   1258      return false;
   1259    }
   1260 
   1261    const StylePositionTryFallbacksItem* nextFallback;
   1262    RefPtr<ComputedStyle> nextFallbackStyle;
   1263    while (true) {
   1264      nextFallback = &fallbacks[index];
   1265      nextFallbackStyle = aPresContext->StyleSet()->ResolvePositionTry(
   1266          *aKidFrame->GetContent()->AsElement(), *aKidFrame->Style(),
   1267          *nextFallback);
   1268      if (nextFallbackStyle) {
   1269        break;
   1270      }
   1271      // No @position-try rule for this name was found, per spec we should
   1272      // skip it.
   1273      index++;
   1274      if (index >= fallbacks.Length()) {
   1275        return false;
   1276      }
   1277    }
   1278    currentFallbackIndex = Some(index);
   1279    currentFallback = nextFallback;
   1280    currentFallbackStyle = std::move(nextFallbackStyle);
   1281    return true;
   1282  };
   1283 
   1284  // Advance to the next fallback to be tried. Normally this is simply the next
   1285  // index in the position-try-fallbacks list, but we have some special cases:
   1286  // - if we're currently at the last-successful fallback (recorded as
   1287  //   firstTryIndex), we "advance" to the base position
   1288  // - we skip the last-successful fallback when we reach its position again
   1289  auto TryAdvanceFallback = [&]() -> bool {
   1290    if (fallbacks.IsEmpty()) {
   1291      return false;
   1292    }
   1293    if (firstTryIndex && currentFallbackIndex == firstTryIndex) {
   1294      return SeekFallbackTo(Nothing());
   1295    }
   1296    uint32_t nextFallbackIndex =
   1297        currentFallbackIndex ? *currentFallbackIndex + 1 : 0;
   1298    if (firstTryIndex && nextFallbackIndex == *firstTryIndex) {
   1299      ++nextFallbackIndex;
   1300    }
   1301    return SeekFallbackTo(Some(nextFallbackIndex));
   1302  };
   1303 
   1304  Maybe<nsRect> firstTryNormalRect;
   1305  if (auto* lastSuccessfulPosition =
   1306          aKidFrame->GetProperty(nsIFrame::LastSuccessfulPositionFallback())) {
   1307    if (SeekFallbackTo(Some(lastSuccessfulPosition->mIndex))) {
   1308      // Remember which fallback we're trying first; also record its style,
   1309      // in case we need to restore it later.
   1310      firstTryIndex = Some(lastSuccessfulPosition->mIndex);
   1311      firstTryStyle = currentFallbackStyle;
   1312    } else {
   1313      aKidFrame->RemoveProperty(nsIFrame::LastSuccessfulPositionFallback());
   1314    }
   1315  }
   1316 
   1317  // Assume we *are* overflowing the CB and if we find a fallback that doesn't
   1318  // overflow, we set this to false and break the loop.
   1319  bool isOverflowingCB = true;
   1320 
   1321  do {
   1322    AutoFallbackStyleSetter fallback(aKidFrame, currentFallbackStyle,
   1323                                     aAnchorPosResolutionCache,
   1324                                     firstTryIndex == currentFallbackIndex);
   1325    auto cb = [&]() {
   1326      // The current containing block, with ongoing modifications.
   1327      // Starts as a local containing block.
   1328      nsRect containingBlock = aOriginalContainingBlockRect;
   1329      const auto defaultAnchorInfo = [&]() -> Maybe<AnchorPosInfo> {
   1330        if (!aAnchorPosResolutionCache) {
   1331          return Nothing{};
   1332        }
   1333        return AnchorPositioningUtils::ResolveAnchorPosRect(
   1334            aKidFrame, aDelegatingFrame, nullptr, false,
   1335            aAnchorPosResolutionCache);
   1336      }();
   1337      if (defaultAnchorInfo) {
   1338        // Presence of a valid default anchor causes us to use the scrollable
   1339        // containing block.
   1340        // https://github.com/w3c/csswg-drafts/issues/12552#issuecomment-3210696721
   1341        containingBlock = aOriginalScrollableContainingBlockRect;
   1342      }
   1343 
   1344      // https://drafts.csswg.org/css-position/#original-cb
   1345      // Handle grid-based adjustment first...
   1346      if (isGrid) {
   1347        const auto border = aDelegatingFrame->GetUsedBorder();
   1348        const nsPoint borderShift{border.left, border.top};
   1349        // Shift in by border of the overall grid container.
   1350        containingBlock =
   1351            nsGridContainerFrame::GridItemCB(aKidFrame) + borderShift;
   1352        if (!defaultAnchorInfo) {
   1353          return ContainingBlockRect{containingBlock};
   1354        }
   1355      }
   1356      // ... Then the position-area based adjustment.
   1357      if (defaultAnchorInfo) {
   1358        auto positionArea = aKidFrame->StylePosition()->mPositionArea;
   1359        if (!positionArea.IsNone()) {
   1360          // Offset should be up to, but not including the containing block's
   1361          // scroll offset.
   1362          const auto offset = AnchorPositioningUtils::GetScrollOffsetFor(
   1363              aAnchorPosResolutionCache->mReferenceData
   1364                  ->CompensatingForScrollAxes(),
   1365              aKidFrame, aAnchorPosResolutionCache->mDefaultAnchorCache);
   1366          // Imagine an abspos container with a scroller in it, and then an
   1367          // anchor in it, where the anchor is visually in the middle of the
   1368          // scrollport. Then, when the scroller moves such that the anchor's
   1369          // left edge is on that of the scrollports, w.r.t. containing block,
   1370          // the anchor is zero left offset horizontally. The position-area
   1371          // grid needs to account for this.
   1372          const auto scrolledAnchorRect = defaultAnchorInfo->mRect - offset;
   1373          StylePositionArea resolvedPositionArea{};
   1374          const auto scrolledAnchorCb = AnchorPositioningUtils::
   1375              AdjustAbsoluteContainingBlockRectForPositionArea(
   1376                  scrolledAnchorRect + aOriginalContainingBlockRect.TopLeft(),
   1377                  containingBlock, aKidFrame->GetWritingMode(),
   1378                  aDelegatingFrame->GetWritingMode(), positionArea,
   1379                  &resolvedPositionArea);
   1380          // By definition, we're using the default anchor, and are scroll
   1381          // compensated.
   1382          aAnchorPosResolutionCache->mReferenceData->mScrollCompensatedSides =
   1383              GetScrollCompensatedSidesFor(resolvedPositionArea);
   1384          return ContainingBlockRect{
   1385              offset, resolvedPositionArea,
   1386              aOriginalScrollableContainingBlockRect,
   1387              // Unscroll the CB by canceling out the previously applied
   1388              // scroll offset (See above), the offset will be applied later.
   1389              scrolledAnchorCb + offset};
   1390        }
   1391        return ContainingBlockRect{aOriginalScrollableContainingBlockRect,
   1392                                   containingBlock};
   1393      }
   1394 
   1395      if (ViewportFrame* viewport = do_QueryFrame(aDelegatingFrame)) {
   1396        if (!IsSnapshotContainingBlock(aKidFrame)) {
   1397          return ContainingBlockRect{
   1398              viewport->GetContainingBlockAdjustedForScrollbars(aReflowInput)};
   1399        }
   1400        return ContainingBlockRect{
   1401            dom::ViewTransition::SnapshotContainingBlockRect(
   1402                viewport->PresContext())};
   1403      }
   1404      return ContainingBlockRect{containingBlock};
   1405    }();
   1406    if (aAnchorPosResolutionCache) {
   1407      const auto& originalCb = cb.mMaybeScrollableRect;
   1408      aAnchorPosResolutionCache->mReferenceData->mOriginalContainingBlockRect =
   1409          originalCb;
   1410      // Stash the adjusted containing block as well, since the insets need to
   1411      // resolve against the adjusted CB, e.g. With `position-area: bottom
   1412      // right;`, + `left: anchor(right);`
   1413      // resolves to 0.
   1414      aAnchorPosResolutionCache->mReferenceData->mAdjustedContainingBlock =
   1415          cb.mFinalRect;
   1416    }
   1417    const LogicalSize cbSize(outerWM, cb.mFinalRect.Size());
   1418 
   1419    ReflowInput::InitFlags initFlags;
   1420    const bool staticPosIsCBOrigin = [&] {
   1421      if (aFlags.contains(AbsPosReflowFlag::IsGridContainerCB)) {
   1422        // When a grid container generates the abs.pos. CB for a *child* then
   1423        // the static position is determined via CSS Box Alignment within the
   1424        // abs.pos. CB (a grid area, i.e. a piece of the grid). In this
   1425        // scenario, due to the multiple coordinate spaces in play, we use a
   1426        // convenience flag to simply have the child's ReflowInput give it a
   1427        // static position at its abs.pos. CB origin, and then we'll align &
   1428        // offset it from there.
   1429        nsIFrame* placeholder = aKidFrame->GetPlaceholderFrame();
   1430        if (placeholder && placeholder->GetParent() == aDelegatingFrame) {
   1431          return true;
   1432        }
   1433      }
   1434      if (aKidFrame->IsMenuPopupFrame()) {
   1435        // Popups never use their static pos.
   1436        return true;
   1437      }
   1438      // TODO(emilio): Either reparent the top layer placeholder frames to the
   1439      // viewport, or return true here for top layer frames more generally (not
   1440      // only menupopups), see https://github.com/w3c/csswg-drafts/issues/8040.
   1441      return false;
   1442    }();
   1443 
   1444    if (staticPosIsCBOrigin) {
   1445      initFlags += ReflowInput::InitFlag::StaticPosIsCBOrigin;
   1446    }
   1447 
   1448    const bool kidFrameMaySplit =
   1449        aReflowInput.AvailableBSize() != NS_UNCONSTRAINEDSIZE &&
   1450 
   1451        // Don't split if told not to (e.g. for fixed frames)
   1452        aFlags.contains(AbsPosReflowFlag::AllowFragmentation) &&
   1453 
   1454        // XXX we don't handle splitting frames for inline absolute containing
   1455        // blocks yet
   1456        !aDelegatingFrame->IsInlineFrame() &&
   1457 
   1458        // Bug 1588623: Support splitting absolute positioned multicol
   1459        // containers.
   1460        !aKidFrame->IsColumnSetWrapperFrame() &&
   1461 
   1462        // Don't split things below the fold. (Ideally we shouldn't *have*
   1463        // anything totally below the fold, but we can't position frames
   1464        // across next-in-flow breaks yet. (Bug 1994346)
   1465        (aKidFrame->GetLogicalRect(cb.mFinalRect.Size()).BStart(wm) <=
   1466         aReflowInput.AvailableBSize());
   1467 
   1468    // Get the border values
   1469    const LogicalMargin border =
   1470        aDelegatingFrame->GetLogicalUsedBorder(outerWM).ApplySkipSides(
   1471            aDelegatingFrame->PreReflowBlockLevelLogicalSkipSides());
   1472 
   1473    const nsIFrame* kidPrevInFlow = aKidFrame->GetPrevInFlow();
   1474    nscoord availBSize;
   1475    if (kidFrameMaySplit) {
   1476      availBSize = aReflowInput.AvailableBSize();
   1477      // If aKidFrame is a first-in-flow, we subtract our containing block's
   1478      // border-block-start, to consider the available space as starting at the
   1479      // containing block's padding-edge.
   1480      //
   1481      // If aKidFrame is *not* a first-in-flow, then we don't need to subtract
   1482      // the containing block's border. Instead, we consider this whole fragment
   1483      // as our available space, i.e., we allow abspos continuations to overlap
   1484      // any border that their containing block parent might have (including
   1485      // borders generated by 'box-decoration-break:clone').
   1486      if (!kidPrevInFlow) {
   1487        availBSize -= border.BStart(outerWM);
   1488      }
   1489    } else {
   1490      availBSize = NS_UNCONSTRAINEDSIZE;
   1491    }
   1492    const LogicalSize availSize(outerWM, cbSize.ISize(outerWM), availBSize);
   1493    ReflowInput kidReflowInput(aPresContext, aReflowInput, aKidFrame,
   1494                               availSize.ConvertTo(wm, outerWM),
   1495                               Some(cbSize.ConvertTo(wm, outerWM)), initFlags,
   1496                               {}, {}, aAnchorPosResolutionCache);
   1497 
   1498    // ReflowInput's constructor may change the available block-size to
   1499    // unconstrained, e.g. in orthogonal reflow, so we retrieve it again and
   1500    // account for kid's constraints in its own writing-mode if needed.
   1501    if (!kidPrevInFlow) {
   1502      nscoord kidAvailBSize = kidReflowInput.AvailableBSize();
   1503      if (kidAvailBSize != NS_UNCONSTRAINEDSIZE) {
   1504        kidAvailBSize -= kidReflowInput.ComputedLogicalMargin(wm).BStart(wm);
   1505        const nscoord kidOffsetBStart =
   1506            kidReflowInput.ComputedLogicalOffsets(wm).BStart(wm);
   1507        if (kidOffsetBStart != NS_AUTOOFFSET) {
   1508          kidAvailBSize -= kidOffsetBStart;
   1509        }
   1510        kidReflowInput.SetAvailableBSize(kidAvailBSize);
   1511      }
   1512    }
   1513 
   1514    // Do the reflow
   1515    ReflowOutput kidDesiredSize(kidReflowInput);
   1516    aKidFrame->Reflow(aPresContext, kidDesiredSize, kidReflowInput, aStatus);
   1517 
   1518    nsMargin insets;
   1519    if (aKidFrame->IsMenuPopupFrame()) {
   1520      // Do nothing. Popup frame will handle its own positioning.
   1521    } else if (kidPrevInFlow) {
   1522      // aKidFrame is a next-in-flow. Place it at the block-edge start of its
   1523      // containing block, with the same inline-position as its prev-in-flow.
   1524      const nsSize cbBorderBoxSize =
   1525          (cbSize + border.Size(outerWM)).GetPhysicalSize(outerWM);
   1526      const LogicalPoint kidPos(
   1527          outerWM, kidPrevInFlow->IStart(outerWM, cbBorderBoxSize), 0);
   1528      const LogicalSize kidSize = kidDesiredSize.Size(outerWM);
   1529      const LogicalRect kidRect(outerWM, kidPos, kidSize);
   1530      aKidFrame->SetRect(outerWM, kidRect, cbBorderBoxSize);
   1531    } else {
   1532      // Position the child relative to our padding edge.
   1533      const LogicalSize kidSize = kidDesiredSize.Size(outerWM);
   1534 
   1535      LogicalMargin offsets = kidReflowInput.ComputedLogicalOffsets(outerWM);
   1536      LogicalMargin margin = kidReflowInput.ComputedLogicalMargin(outerWM);
   1537 
   1538      // If we're doing CSS Box Alignment in either axis, that will apply the
   1539      // margin for us in that axis (since the thing that's aligned is the
   1540      // margin box).  So, we clear out the margin here to avoid applying it
   1541      // twice.
   1542      if (kidReflowInput.mFlags.mIOffsetsNeedCSSAlign) {
   1543        margin.IStart(outerWM) = margin.IEnd(outerWM) = 0;
   1544      }
   1545      if (kidReflowInput.mFlags.mBOffsetsNeedCSSAlign) {
   1546        margin.BStart(outerWM) = margin.BEnd(outerWM) = 0;
   1547      }
   1548 
   1549      // If we're solving for start in either inline or block direction,
   1550      // then compute it now that we know the dimensions.
   1551      ResolveSizeDependentOffsets(kidReflowInput, cbSize, kidSize, margin,
   1552                                  cb.ResolvedPositionArea(), offsets);
   1553 
   1554      ResolveAutoMarginsAfterLayout(kidReflowInput, cbSize, kidSize, margin,
   1555                                    offsets);
   1556 
   1557      // If the inset is constrained as non-auto, we may have a child that does
   1558      // not fill out the inset-reduced containing block. In this case, we need
   1559      // to align the child by its margin box:
   1560      // https://drafts.csswg.org/css-position-3/#abspos-layout
   1561      const auto* stylePos = aKidFrame->StylePosition();
   1562      const auto anchorResolutionParams =
   1563          AnchorPosOffsetResolutionParams::ExplicitCBFrameSize(
   1564              AnchorPosResolutionParams::From(aKidFrame,
   1565                                              aAnchorPosResolutionCache),
   1566              &cbSize);
   1567      const bool iStartInsetAuto =
   1568          stylePos
   1569              ->GetAnchorResolvedInset(LogicalSide::IStart, outerWM,
   1570                                       anchorResolutionParams)
   1571              ->IsAuto();
   1572      const bool iEndInsetAuto =
   1573          stylePos
   1574              ->GetAnchorResolvedInset(LogicalSide::IEnd, outerWM,
   1575                                       anchorResolutionParams)
   1576              ->IsAuto();
   1577      const bool iInsetAuto = iStartInsetAuto || iEndInsetAuto;
   1578 
   1579      const bool bStartInsetAuto =
   1580          stylePos
   1581              ->GetAnchorResolvedInset(LogicalSide::BStart, outerWM,
   1582                                       anchorResolutionParams)
   1583              ->IsAuto();
   1584      const bool bEndInsetAuto =
   1585          stylePos
   1586              ->GetAnchorResolvedInset(LogicalSide::BEnd, outerWM,
   1587                                       anchorResolutionParams)
   1588              ->IsAuto();
   1589      const bool bInsetAuto = bStartInsetAuto || bEndInsetAuto;
   1590      const LogicalSize kidMarginBox{
   1591          outerWM, margin.IStartEnd(outerWM) + kidSize.ISize(outerWM),
   1592          margin.BStartEnd(outerWM) + kidSize.BSize(outerWM)};
   1593      const auto* placeholderContainer =
   1594          GetPlaceholderContainer(kidReflowInput.mFrame);
   1595 
   1596      insets = [&]() {
   1597        auto result = offsets;
   1598        // Zero out weaker insets, if one exists - This offset gets forced to
   1599        // the margin edge of the child on that side, and for the purposes of
   1600        // overflow checks, we consider them to be zero.
   1601        if (iStartInsetAuto && !iEndInsetAuto) {
   1602          result.IStart(outerWM) = 0;
   1603        } else if (iInsetAuto) {
   1604          result.IEnd(outerWM) = 0;
   1605        }
   1606        if (bStartInsetAuto && !bEndInsetAuto) {
   1607          result.BStart(outerWM) = 0;
   1608        } else if (bInsetAuto) {
   1609          result.BEnd(outerWM) = 0;
   1610        }
   1611        return result.GetPhysicalMargin(outerWM);
   1612      }();
   1613      if (aAnchorPosResolutionCache) {
   1614        aAnchorPosResolutionCache->mReferenceData->mInsets = insets;
   1615      }
   1616      if (!iInsetAuto) {
   1617        MOZ_ASSERT(
   1618            !kidReflowInput.mFlags.mIOffsetsNeedCSSAlign,
   1619            "Non-auto inline inset but requires CSS alignment for static "
   1620            "position?");
   1621        auto alignOffset = OffsetToAlignedStaticPos(
   1622            kidReflowInput, kidMarginBox, cbSize, placeholderContainer, outerWM,
   1623            LogicalAxis::Inline,
   1624            Some(NonAutoAlignParams{
   1625                offsets.IStart(outerWM),
   1626                offsets.IEnd(outerWM),
   1627            }),
   1628            cb.ResolvedPositionArea());
   1629 
   1630        offsets.IStart(outerWM) += alignOffset;
   1631        offsets.IEnd(outerWM) =
   1632            cbSize.ISize(outerWM) -
   1633            (offsets.IStart(outerWM) + kidMarginBox.ISize(outerWM));
   1634      }
   1635      if (!bInsetAuto) {
   1636        MOZ_ASSERT(!kidReflowInput.mFlags.mBOffsetsNeedCSSAlign,
   1637                   "Non-auto block inset but requires CSS alignment for static "
   1638                   "position?");
   1639        auto alignOffset = OffsetToAlignedStaticPos(
   1640            kidReflowInput, kidMarginBox, cbSize, placeholderContainer, outerWM,
   1641            LogicalAxis::Block,
   1642            Some(NonAutoAlignParams{
   1643                offsets.BStart(outerWM),
   1644                offsets.BEnd(outerWM),
   1645            }),
   1646            cb.ResolvedPositionArea());
   1647        offsets.BStart(outerWM) += alignOffset;
   1648        offsets.BEnd(outerWM) =
   1649            cbSize.BSize(outerWM) -
   1650            (offsets.BStart(outerWM) + kidMarginBox.BSize(outerWM));
   1651      }
   1652 
   1653      LogicalRect rect(
   1654          outerWM, offsets.StartOffset(outerWM) + margin.StartOffset(outerWM),
   1655          kidSize);
   1656      nsRect r = rect.GetPhysicalRect(outerWM, cbSize.GetPhysicalSize(outerWM));
   1657 
   1658      // So far, we've positioned against the padding edge of the containing
   1659      // block, which is necessary for inset computation. However, the position
   1660      // of a frame originates against the border box.
   1661      r += cb.mFinalRect.TopLeft();
   1662 
   1663      aKidFrame->SetRect(r);
   1664    }
   1665 
   1666    aKidFrame->DidReflow(aPresContext, &kidReflowInput);
   1667 
   1668    [&]() {
   1669      const auto rect = aKidFrame->GetRect();
   1670      if (!firstTryNormalRect) {
   1671        firstTryNormalRect = Some(rect);
   1672      }
   1673      if (!aAnchorPosResolutionCache) {
   1674        return;
   1675      }
   1676      auto* referenceData = aAnchorPosResolutionCache->mReferenceData;
   1677      if (referenceData->CompensatingForScrollAxes().isEmpty()) {
   1678        return;
   1679      }
   1680      // Now that all the anchor-related values are resolved, completing the
   1681      // scroll compensation flag, compute the scroll offsets.
   1682      const auto offset = [&]() {
   1683        if (cb.mAnchorShiftInfo) {
   1684          // Already resolved.
   1685          return cb.mAnchorShiftInfo->mOffset;
   1686        }
   1687        return AnchorPositioningUtils::GetScrollOffsetFor(
   1688            referenceData->CompensatingForScrollAxes(), aKidFrame,
   1689            aAnchorPosResolutionCache->mDefaultAnchorCache);
   1690      }();
   1691      // Apply the hypothetical scroll offset.
   1692      // Set initial scroll position. TODO(dshin, bug 1987962): Need
   1693      // additional work for remembered scroll offset here.
   1694      aKidFrame->SetProperty(nsIFrame::NormalPositionProperty(),
   1695                             rect.TopLeft());
   1696      if (offset != nsPoint{}) {
   1697        aKidFrame->SetPosition(rect.TopLeft() - offset);
   1698        // Ensure that the positioned frame's overflow is updated. Absolutely
   1699        // containing block's overflow will be updated shortly below.
   1700        aKidFrame->UpdateOverflow();
   1701      }
   1702      aAnchorPosResolutionCache->mReferenceData->mDefaultScrollShift = offset;
   1703    }();
   1704 
   1705    const auto FitsInContainingBlock = [&]() {
   1706      if (aAnchorPosResolutionCache) {
   1707        return AnchorPositioningUtils::FitsInContainingBlock(
   1708            aKidFrame, *aAnchorPosResolutionCache->mReferenceData);
   1709      }
   1710      auto imcbSize = cb.mFinalRect.Size();
   1711      imcbSize -= nsSize{insets.LeftRight(), insets.TopBottom()};
   1712      return aKidFrame->GetMarginRectRelativeToSelf().Size() <= imcbSize;
   1713    };
   1714 
   1715    // FIXME(bug 2004495): Per spec this should be the inset-modified
   1716    // containing-block, see:
   1717    // https://drafts.csswg.org/css-anchor-position-1/#fallback-apply
   1718    const auto fits = aStatus.IsComplete() && FitsInContainingBlock();
   1719    if (fallbacks.IsEmpty() || finalizing ||
   1720        (fits && (tryOrder == StylePositionTryOrder::Normal ||
   1721                  currentFallbackIndex == firstTryIndex))) {
   1722      // We completed the reflow - Either we had a fallback that fit, or we
   1723      // didn't have any to try in the first place.
   1724      isOverflowingCB = !fits;
   1725      fallback.CommitCurrentFallback();
   1726      if (currentFallbackIndex == Nothing()) {
   1727        aKidFrame->RemoveProperty(nsIFrame::LastSuccessfulPositionFallback());
   1728      }
   1729      break;
   1730    }
   1731 
   1732    if (fits) {
   1733      auto imcbSize = cb.mFinalRect.Size();
   1734      imcbSize -= nsSize{insets.LeftRight(), insets.TopBottom()};
   1735      switch (tryOrder) {
   1736        case StylePositionTryOrder::MostWidth:
   1737          if (imcbSize.Width() > bestSize) {
   1738            bestSize = imcbSize.Width();
   1739            bestIndex = currentFallbackIndex;
   1740          }
   1741          break;
   1742        case StylePositionTryOrder::MostHeight:
   1743          if (imcbSize.Height() > bestSize) {
   1744            bestSize = imcbSize.Height();
   1745            bestIndex = currentFallbackIndex;
   1746          }
   1747          break;
   1748        default:
   1749          MOZ_ASSERT_UNREACHABLE("unexpected try-order value");
   1750          break;
   1751      }
   1752    }
   1753 
   1754    if (!TryAdvanceFallback()) {
   1755      // If there are no further fallbacks, we're done.
   1756      if (bestSize >= 0) {
   1757        SeekFallbackTo(bestIndex);
   1758      } else {
   1759        // If we're going to roll back to the first try position, and the
   1760        // target's size was different, we need to do a "finalizing" reflow
   1761        // to ensure the inner layout is correct. If the size is unchanged,
   1762        // we can just break the fallback loop now.
   1763        if (isOverflowingCB && firstTryNormalRect &&
   1764            firstTryNormalRect->Size() != aKidFrame->GetSize()) {
   1765          SeekFallbackTo(firstTryIndex);
   1766        } else {
   1767          break;
   1768        }
   1769      }
   1770      // The fallback we've just selected is the final choice, regardless of
   1771      // whether it overflows.
   1772      finalizing = true;
   1773    }
   1774 
   1775    // Try with the next fallback.
   1776    aKidFrame->AddStateBits(NS_FRAME_IS_DIRTY);
   1777    aStatus.Reset();
   1778  } while (true);
   1779 
   1780  [&]() {
   1781    if (!isOverflowingCB || !firstTryNormalRect) {
   1782      return;
   1783    }
   1784    // We gave up applying fallbacks. Recover previous values, if changed, and
   1785    // reset currentFallbackIndex/Style to match.
   1786    // Because we rolled back to first try data, our cache should be up-to-date.
   1787    currentFallbackIndex = firstTryIndex;
   1788    currentFallbackStyle = firstTryStyle;
   1789    const auto normalRect = *firstTryNormalRect;
   1790    const auto oldNormalPosition = aKidFrame->GetNormalPosition();
   1791    if (normalRect.TopLeft() != oldNormalPosition) {
   1792      aKidFrame->SetProperty(nsIFrame::NormalPositionProperty(),
   1793                             normalRect.TopLeft());
   1794    }
   1795    auto rect = normalRect;
   1796    if (aAnchorPosResolutionCache) {
   1797      rect.MoveBy(
   1798          -aAnchorPosResolutionCache->mReferenceData->mDefaultScrollShift);
   1799    }
   1800 
   1801    if (isOverflowingCB &&
   1802        !aKidFrame->StylePosition()->mPositionArea.IsNone()) {
   1803      // The anchored element overflows the IMCB of its position-area. Would it
   1804      // have fit within the original CB? If so, shift it to stay within that.
   1805      if (rect.width <= aOriginalContainingBlockRect.width &&
   1806          rect.height <= aOriginalContainingBlockRect.height) {
   1807        if (rect.x < aOriginalContainingBlockRect.x) {
   1808          rect.x = aOriginalContainingBlockRect.x;
   1809        } else if (rect.XMost() > aOriginalContainingBlockRect.XMost()) {
   1810          rect.x = aOriginalContainingBlockRect.XMost() - rect.width;
   1811        }
   1812        if (rect.y < aOriginalContainingBlockRect.y) {
   1813          rect.y = aOriginalContainingBlockRect.y;
   1814        } else if (rect.YMost() > aOriginalContainingBlockRect.YMost()) {
   1815          rect.y = aOriginalContainingBlockRect.YMost() - rect.height;
   1816        }
   1817      }
   1818    }
   1819 
   1820    const auto oldPosition = aKidFrame->GetPosition();
   1821    if (rect.TopLeft() == oldPosition) {
   1822      return;
   1823    }
   1824    aKidFrame->SetPosition(rect.TopLeft());
   1825    aKidFrame->UpdateOverflow();
   1826  }();
   1827 
   1828  if (currentFallbackIndex) {
   1829    aKidFrame->SetOrUpdateDeletableProperty(
   1830        nsIFrame::LastSuccessfulPositionFallback(),
   1831        LastSuccessfulPositionData{currentFallbackStyle, *currentFallbackIndex,
   1832                                   isOverflowingCB});
   1833  }
   1834 
   1835 #ifdef DEBUG
   1836  if (nsBlockFrame::gNoisyReflow) {
   1837    nsIFrame::IndentBy(stdout, nsBlockFrame::gNoiseIndent - 1);
   1838    fmt::println(FMT_STRING("abspos {}: rect {}"), aKidFrame->ListTag().get(),
   1839                 ToString(aKidFrame->GetRect()));
   1840  }
   1841 #endif
   1842  // If author asked for `position-visibility: no-overflow` and we overflow
   1843  // `usedCB`, treat as "strongly hidden". Note that for anchored frames this
   1844  // happens in ComputePositionVisibility. But no-overflow also applies to
   1845  // non-anchored frames.
   1846  if (!aAnchorPosResolutionCache) {
   1847    aKidFrame->AddOrRemoveStateBits(
   1848        NS_FRAME_POSITION_VISIBILITY_HIDDEN,
   1849        isOverflowingCB && aKidFrame->StylePosition()->mPositionVisibility &
   1850                               StylePositionVisibility::NO_OVERFLOW);
   1851  }
   1852 
   1853  if (aOverflowAreas) {
   1854    aOverflowAreas->UnionWith(aKidFrame->GetOverflowAreasRelativeToParent());
   1855  }
   1856 }