tor-browser

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

AnchorPositioningUtils.cpp (42181B)


      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 #include "AnchorPositioningUtils.h"
      8 
      9 #include "DisplayPortUtils.h"
     10 #include "ScrollContainerFrame.h"
     11 #include "mozilla/Maybe.h"
     12 #include "mozilla/PresShell.h"
     13 #include "mozilla/StaticPrefs_apz.h"
     14 #include "mozilla/dom/DOMIntersectionObserver.h"
     15 #include "mozilla/dom/Document.h"
     16 #include "mozilla/dom/Element.h"
     17 #include "nsCanvasFrame.h"
     18 #include "nsContainerFrame.h"
     19 #include "nsDisplayList.h"
     20 #include "nsIContent.h"
     21 #include "nsIFrame.h"
     22 #include "nsIFrameInlines.h"
     23 #include "nsINode.h"
     24 #include "nsLayoutUtils.h"
     25 #include "nsPlaceholderFrame.h"
     26 #include "nsStyleStruct.h"
     27 #include "nsTArray.h"
     28 
     29 namespace mozilla {
     30 
     31 namespace {
     32 
     33 bool IsScrolled(const nsIFrame* aFrame) {
     34  switch (aFrame->Style()->GetPseudoType()) {
     35    case PseudoStyleType::scrolledContent:
     36    case PseudoStyleType::scrolledCanvas:
     37      return true;
     38    default:
     39      return false;
     40  }
     41 }
     42 
     43 bool DoTreeScopedPropertiesOfElementApplyToContent(
     44    const nsINode* aStylePropertyElement, const nsINode* aStyledContent) {
     45  // XXX: The proper implementation is deferred to bug 1988038
     46  // concerning tree-scoped name resolution. For now, we just
     47  // keep the shadow and light trees separate.
     48  return aStylePropertyElement->GetContainingDocumentOrShadowRoot() ==
     49         aStyledContent->GetContainingDocumentOrShadowRoot();
     50 }
     51 
     52 /**
     53 * Checks for the implementation of `anchor-scope`:
     54 * https://drafts.csswg.org/css-anchor-position-1/#anchor-scope
     55 *
     56 * TODO: Consider caching the ancestors, see bug 1986347
     57 */
     58 bool IsAnchorInScopeForPositionedElement(const nsAtom* aName,
     59                                         const nsIFrame* aPossibleAnchorFrame,
     60                                         const nsIFrame* aPositionedFrame) {
     61  // We don't need to look beyond positioned element's containing block.
     62  const auto* positionedContainingBlockContent =
     63      aPositionedFrame->GetParent()->GetContent();
     64 
     65  auto getAnchorPosNearestScope =
     66      [&](const nsAtom* aName, const nsIFrame* aFrame) -> const nsIContent* {
     67    // We need to traverse the DOM, not the frame tree, since `anchor-scope`
     68    // may be present on elements with `display: contents` (in which case its
     69    // frame is in the `::before` list and won't be found by walking the frame
     70    // tree parent chain).
     71    for (nsIContent* cp = aFrame->GetContent();
     72         cp && cp != positionedContainingBlockContent;
     73         cp = cp->GetFlattenedTreeParentElementForStyle()) {
     74      const auto* anchorScope = [&]() -> const StyleAnchorScope* {
     75        const nsIFrame* f = nsLayoutUtils::GetStyleFrame(cp);
     76        if (MOZ_LIKELY(f)) {
     77          return &f->StyleDisplay()->mAnchorScope;
     78        }
     79        if (cp->AsElement()->IsDisplayContents()) {
     80          const auto* style =
     81              Servo_Element_GetMaybeOutOfDateStyle(cp->AsElement());
     82          MOZ_ASSERT(style);
     83          return &style->StyleDisplay()->mAnchorScope;
     84        }
     85        return nullptr;
     86      }();
     87 
     88      if (!anchorScope || anchorScope->IsNone()) {
     89        continue;
     90      }
     91 
     92      if (anchorScope->IsAll()) {
     93        return cp;
     94      }
     95 
     96      MOZ_ASSERT(anchorScope->IsIdents());
     97      for (const StyleAtom& ident : anchorScope->AsIdents().AsSpan()) {
     98        if (aName == ident.AsAtom()) {
     99          return cp;
    100        }
    101      }
    102    }
    103    return nullptr;
    104  };
    105 
    106  const nsIContent* nearestScopeForAnchor =
    107      getAnchorPosNearestScope(aName, aPossibleAnchorFrame);
    108  const nsIContent* nearestScopeForPositioned =
    109      getAnchorPosNearestScope(aName, aPositionedFrame);
    110  if (!nearestScopeForAnchor) {
    111    // Anchor is not scoped and positioned element also should
    112    // not be gated by a scope.
    113    return !nearestScopeForPositioned ||
    114           aPossibleAnchorFrame->GetContent() == nearestScopeForPositioned;
    115  }
    116 
    117  // There may not be any other scopes between the positioned element
    118  // and the nearest scope of the anchor.
    119  return nearestScopeForAnchor == nearestScopeForPositioned;
    120 };
    121 
    122 bool IsFullyStyleableTreeAbidingOrNotPseudoElement(const nsIFrame* aFrame) {
    123  if (!aFrame->Style()->IsPseudoElement()) {
    124    return true;
    125  }
    126 
    127  const PseudoStyleType pseudoElementType = aFrame->Style()->GetPseudoType();
    128 
    129  // See https://www.w3.org/TR/css-pseudo-4/#treelike
    130  return pseudoElementType == PseudoStyleType::before ||
    131         pseudoElementType == PseudoStyleType::after ||
    132         pseudoElementType == PseudoStyleType::marker;
    133 }
    134 
    135 size_t GetTopLayerIndex(const nsIFrame* aFrame) {
    136  MOZ_ASSERT(aFrame);
    137 
    138  const nsIContent* frameContent = aFrame->GetContent();
    139 
    140  if (!frameContent) {
    141    return 0;
    142  }
    143 
    144  // Within the array returned by Document::GetTopLayer,
    145  // a higher index means the layer sits higher in the stack,
    146  // matching Document::GetTopLayerTop()’s top-to-bottom logic.
    147  // See https://drafts.csswg.org/css-position-4/#in-a-higher-top-layer
    148  const nsTArray<dom::Element*>& topLayers =
    149      frameContent->OwnerDoc()->GetTopLayer();
    150 
    151  for (size_t index = 0; index < topLayers.Length(); ++index) {
    152    const auto& topLayer = topLayers.ElementAt(index);
    153    if (nsContentUtils::ContentIsFlattenedTreeDescendantOfForStyle(
    154            /* aPossibleDescendant */ frameContent,
    155            /* aPossibleAncestor */ topLayer)) {
    156      return 1 + index;
    157    }
    158  }
    159 
    160  return 0;
    161 }
    162 
    163 bool IsInitialContainingBlock(const nsIFrame* aContainingBlock) {
    164  // Initial containing block: The containing block of the root element.
    165  // https://drafts.csswg.org/css-display-4/#initial-containing-block
    166  return aContainingBlock == aContainingBlock->PresShell()
    167                                 ->FrameConstructor()
    168                                 ->GetDocElementContainingBlock();
    169 }
    170 
    171 bool IsContainingBlockGeneratedByElement(const nsIFrame* aContainingBlock) {
    172  // 2.1. Containing Blocks of Positioned Boxes
    173  // https://www.w3.org/TR/css-position-3/#def-cb
    174  return !(!aContainingBlock || aContainingBlock->IsViewportFrame() ||
    175           IsInitialContainingBlock(aContainingBlock));
    176 }
    177 
    178 bool IsAnchorLaidOutStrictlyBeforeElement(
    179    const nsIFrame* aPossibleAnchorFrame, const nsIFrame* aPositionedFrame,
    180    const nsTArray<const nsIFrame*>& aPositionedFrameAncestors) {
    181  // 1. positioned el is in a higher top layer than possible anchor,
    182  // see https://drafts.csswg.org/css-position-4/#in-a-higher-top-layer
    183  const size_t positionedTopLayerIndex = GetTopLayerIndex(aPositionedFrame);
    184  const size_t anchorTopLayerIndex = GetTopLayerIndex(aPossibleAnchorFrame);
    185 
    186  if (anchorTopLayerIndex != positionedTopLayerIndex) {
    187    return anchorTopLayerIndex < positionedTopLayerIndex;
    188  }
    189 
    190  // Note: The containing block of an absolutely positioned element
    191  // is just the parent frame.
    192  const nsIFrame* positionedContainingBlock = aPositionedFrame->GetParent();
    193  // Note(dshin, bug 1985654): Spec strictly uses the term "containing block,"
    194  // corresponding to `GetContainingBlock()`. However, this leads to cases
    195  // where an anchor's non-inline containing block prevents it from being a
    196  // valid anchor for a absolutely positioned element (Which can explicitly
    197  // have inline elements as a containing block). Some WPT rely on inline
    198  // containing blocks as well.
    199  // See also: https://github.com/w3c/csswg-drafts/issues/12674
    200  const nsIFrame* anchorContainingBlock = aPossibleAnchorFrame->GetParent();
    201 
    202  // 2. Both elements are in the same top layer but have different
    203  // containing blocks and positioned el's containing block is an
    204  // ancestor of possible anchor's containing block in the containing
    205  // block chain, aka one of the following:
    206  if (anchorContainingBlock != positionedContainingBlock) {
    207    // 2.1 positioned el's containing block is the viewport, and
    208    // possible anchor's containing block isn't.
    209    if (positionedContainingBlock->IsViewportFrame() &&
    210        !anchorContainingBlock->IsViewportFrame()) {
    211      return !nsLayoutUtils::IsProperAncestorFrame(aPositionedFrame,
    212                                                   aPossibleAnchorFrame);
    213    }
    214 
    215    auto isLastContainingBlockOrderable =
    216        [&aPositionedFrame, &aPositionedFrameAncestors, &anchorContainingBlock,
    217         &positionedContainingBlock]() -> bool {
    218      const nsIFrame* it = anchorContainingBlock;
    219      while (it) {
    220        const nsIFrame* parentContainingBlock = it->GetParent();
    221        if (!parentContainingBlock) {
    222          return false;
    223        }
    224 
    225        if (parentContainingBlock == positionedContainingBlock) {
    226          return !it->IsAbsolutelyPositioned() ||
    227                 nsLayoutUtils::CompareTreePosition(it, aPositionedFrame,
    228                                                    aPositionedFrameAncestors,
    229                                                    nullptr) < 0;
    230        }
    231 
    232        it = parentContainingBlock;
    233      }
    234 
    235      return false;
    236    };
    237 
    238    // 2.2 positioned el's containing block is the initial containing
    239    // block, and possible anchor's containing block is generated by an
    240    // element, and the last containing block in possible anchor's containing
    241    // block chain before reaching positioned el's containing block is either
    242    // not absolutely positioned or precedes positioned el in the tree order,
    243    const bool isAnchorContainingBlockGenerated =
    244        IsContainingBlockGeneratedByElement(anchorContainingBlock);
    245    if (isAnchorContainingBlockGenerated &&
    246        IsInitialContainingBlock(positionedContainingBlock)) {
    247      return isLastContainingBlockOrderable();
    248    }
    249 
    250    // 2.3 both elements' containing blocks are generated by elements,
    251    // and positioned el's containing block is an ancestor in the flat
    252    // tree to that of possible anchor's containing block, and the last
    253    // containing block in possible anchor’s containing block chain before
    254    // reaching positioned el’s containing block is either not absolutely
    255    // positioned or precedes positioned el in the tree order.
    256    if (isAnchorContainingBlockGenerated &&
    257        IsContainingBlockGeneratedByElement(positionedContainingBlock)) {
    258      return isLastContainingBlockOrderable();
    259    }
    260 
    261    return false;
    262  }
    263 
    264  // 3. Both elements are in the same top layer and have the same
    265  // containing block, and are both absolutely positioned, and possible
    266  // anchor is earlier in flat tree order than positioned el.
    267  const bool isAnchorAbsolutelyPositioned =
    268      aPossibleAnchorFrame->IsAbsolutelyPositioned();
    269  if (isAnchorAbsolutelyPositioned) {
    270    // We must have checked that the positioned element is absolutely
    271    // positioned by now.
    272    return nsLayoutUtils::CompareTreePosition(
    273               aPossibleAnchorFrame, aPositionedFrame,
    274               aPositionedFrameAncestors, nullptr) < 0;
    275  }
    276 
    277  // 4. Both elements are in the same top layer and have the same
    278  // containing block, but possible anchor isn't absolutely positioned.
    279  return !isAnchorAbsolutelyPositioned;
    280 }
    281 
    282 /**
    283 * https://drafts.csswg.org/css-contain-2/#skips-its-contents
    284 */
    285 bool IsPositionedElementAlsoSkippedWhenAnchorIsSkipped(
    286    const nsIFrame* aPossibleAnchorFrame, const nsIFrame* aPositionedFrame) {
    287  // If potential anchor is skipped and a root of a visibility subtree,
    288  // it can never be acceptable.
    289  if (aPossibleAnchorFrame->HidesContentForLayout()) {
    290    return false;
    291  }
    292 
    293  // If possible anchor is in the skipped contents of another element,
    294  // then positioned el shall be in the skipped contents of that same element.
    295  const nsIFrame* visibilityAncestor = aPossibleAnchorFrame->GetParent();
    296  while (visibilityAncestor) {
    297    // If anchor is skipped via auto or hidden, it cannot be acceptable,
    298    // be it a root or a non-root of a visibility subtree.
    299    if (visibilityAncestor->HidesContentForLayout()) {
    300      break;
    301    }
    302 
    303    visibilityAncestor = visibilityAncestor->GetParent();
    304  }
    305 
    306  // If positioned el is skipped and a root of a visibility subtree,
    307  // an anchor can never be acceptable.
    308  if (aPositionedFrame->HidesContentForLayout()) {
    309    return false;
    310  }
    311 
    312  const nsIFrame* ancestor = aPositionedFrame;
    313  while (ancestor) {
    314    if (ancestor->HidesContentForLayout()) {
    315      return ancestor == visibilityAncestor;
    316    }
    317 
    318    ancestor = ancestor->GetParent();
    319  }
    320 
    321  return true;
    322 }
    323 
    324 class LazyAncestorHolder {
    325  const nsIFrame* mFrame;
    326  AutoTArray<const nsIFrame*, 8> mAncestors;
    327  bool mFilled = false;
    328 
    329 public:
    330  const nsTArray<const nsIFrame*>& GetAncestors() {
    331    if (!mFilled) {
    332      nsLayoutUtils::FillAncestors(mFrame, nullptr, &mAncestors);
    333      mFilled = true;
    334    }
    335    return mAncestors;
    336  }
    337 
    338  explicit LazyAncestorHolder(const nsIFrame* aFrame) : mFrame(aFrame) {}
    339 };
    340 
    341 bool IsAcceptableAnchorElement(
    342    const nsIFrame* aPossibleAnchorFrame, const nsAtom* aName,
    343    const nsIFrame* aPositionedFrame,
    344    LazyAncestorHolder& aPositionedFrameAncestorHolder) {
    345  MOZ_ASSERT(aPossibleAnchorFrame);
    346  MOZ_ASSERT(aPositionedFrame);
    347 
    348  // An element possible anchor is an acceptable anchor element for an
    349  // absolutely positioned element positioned el if all of the following are
    350  // true:
    351  // - possible anchor is either an element or a fully styleable
    352  // tree-abiding pseudo-element.
    353  // - possible anchor is in scope for positioned el, per the effects of
    354  // anchor-scope on positioned el or its ancestors.
    355  // - possible anchor is laid out strictly before positioned el
    356  //
    357  // Note: Frames having an anchor name contain elements.
    358  // The phrase "element or a fully styleable tree-abiding pseudo-element"
    359  // used by the spec is taken to mean
    360  // "either not a pseudo-element or a pseudo-element of a specific kind".
    361  return (IsFullyStyleableTreeAbidingOrNotPseudoElement(aPossibleAnchorFrame) &&
    362          IsAnchorLaidOutStrictlyBeforeElement(
    363              aPossibleAnchorFrame, aPositionedFrame,
    364              aPositionedFrameAncestorHolder.GetAncestors()) &&
    365          IsAnchorInScopeForPositionedElement(aName, aPossibleAnchorFrame,
    366                                              aPositionedFrame) &&
    367          IsPositionedElementAlsoSkippedWhenAnchorIsSkipped(
    368              aPossibleAnchorFrame, aPositionedFrame));
    369 }
    370 
    371 }  // namespace
    372 
    373 AnchorPosReferenceData::Result AnchorPosReferenceData::InsertOrModify(
    374    const nsAtom* aAnchorName, bool aNeedOffset) {
    375  bool exists = true;
    376  auto* result = &mMap.LookupOrInsertWith(aAnchorName, [&exists]() {
    377    exists = false;
    378    return Nothing{};
    379  });
    380 
    381  if (!exists) {
    382    return {false, result};
    383  }
    384 
    385  // We tried to resolve before.
    386  if (result->isNothing()) {
    387    // We know this reference is invalid.
    388    return {true, result};
    389  }
    390  // Previous resolution found a valid anchor.
    391  if (!aNeedOffset) {
    392    // Size is guaranteed to be populated on resolution.
    393    return {true, result};
    394  }
    395 
    396  // Previous resolution may have been for size only, in which case another
    397  // anchor resolution is still required.
    398  return {result->ref().mOffsetData.isSome(), result};
    399 }
    400 
    401 const AnchorPosReferenceData::Value* AnchorPosReferenceData::Lookup(
    402    const nsAtom* aAnchorName) const {
    403  return mMap.Lookup(aAnchorName).DataPtrOrNull();
    404 }
    405 
    406 AnchorPosDefaultAnchorCache::AnchorPosDefaultAnchorCache(
    407    const nsIFrame* aAnchor, const nsIFrame* aScrollContainer)
    408    : mAnchor{aAnchor}, mScrollContainer{aScrollContainer} {
    409  MOZ_ASSERT_IF(
    410      aAnchor,
    411      nsLayoutUtils::GetNearestScrollContainerFrame(
    412          const_cast<nsContainerFrame*>(aAnchor->GetParent()),
    413          nsLayoutUtils::SCROLLABLE_SAME_DOC |
    414              nsLayoutUtils::SCROLLABLE_INCLUDE_HIDDEN) == mScrollContainer);
    415 }
    416 
    417 nsIFrame* AnchorPositioningUtils::FindFirstAcceptableAnchor(
    418    const nsAtom* aName, const nsIFrame* aPositionedFrame,
    419    const nsTArray<nsIFrame*>& aPossibleAnchorFrames) {
    420  LazyAncestorHolder positionedFrameAncestorHolder(aPositionedFrame);
    421  const auto* positionedContent = aPositionedFrame->GetContent();
    422 
    423  for (auto it = aPossibleAnchorFrames.rbegin();
    424       it != aPossibleAnchorFrames.rend(); ++it) {
    425    const nsIFrame* possibleAnchorFrame = *it;
    426    if (!DoTreeScopedPropertiesOfElementApplyToContent(
    427            possibleAnchorFrame->GetContent(), positionedContent)) {
    428      // Skip anchors in different shadow trees.
    429      continue;
    430    }
    431 
    432    // Check if the possible anchor is an acceptable anchor element.
    433    if (IsAcceptableAnchorElement(*it, aName, aPositionedFrame,
    434                                  positionedFrameAncestorHolder)) {
    435      return *it;
    436    }
    437  }
    438 
    439  // If we reach here, we didn't find any acceptable anchor.
    440  return nullptr;
    441 }
    442 
    443 // Find the aContainer's child that is the ancestor of aDescendant.
    444 static const nsIFrame* TraverseUpToContainerChild(const nsIFrame* aContainer,
    445                                                  const nsIFrame* aDescendant) {
    446  const auto* current = aDescendant;
    447  while (true) {
    448    const auto* parent = current->GetParent();
    449    if (!parent) {
    450      return nullptr;
    451    }
    452    if (parent == aContainer) {
    453      return current;
    454    }
    455    current = parent;
    456  }
    457 }
    458 
    459 static const nsIFrame* GetAnchorOf(const nsIFrame* aPositioned,
    460                                   const nsAtom* aAnchorName) {
    461  const auto* presShell = aPositioned->PresShell();
    462  MOZ_ASSERT(presShell, "No PresShell for frame?");
    463  return presShell->GetAnchorPosAnchor(aAnchorName, aPositioned);
    464 }
    465 
    466 Maybe<nsRect> AnchorPositioningUtils::GetAnchorPosRect(
    467    const nsIFrame* aAbsoluteContainingBlock, const nsIFrame* aAnchor,
    468    bool aCBRectIsvalid) {
    469  auto rect = [&]() -> Maybe<nsRect> {
    470    if (aCBRectIsvalid) {
    471      const nsRect result =
    472          nsLayoutUtils::GetCombinedFragmentRects(aAnchor, true);
    473      const auto offset =
    474          aAnchor->GetOffsetToIgnoringScrolling(aAbsoluteContainingBlock);
    475      // Easy, just use the existing function.
    476      return Some(result + offset);
    477    }
    478 
    479    // Ok, containing block doesn't have its rect fully resolved. Figure out
    480    // rect relative to the child of containing block that is also the ancestor
    481    // of the anchor, and manually compute the offset.
    482    // TODO(dshin): This wouldn't handle anchor in a previous top layer.
    483    const auto* containerChild =
    484        TraverseUpToContainerChild(aAbsoluteContainingBlock, aAnchor);
    485    if (!containerChild) {
    486      return Nothing{};
    487    }
    488 
    489    if (aAnchor == containerChild) {
    490      // Anchor is the direct child of anchor's CBWM.
    491      return Some(nsLayoutUtils::GetCombinedFragmentRects(aAnchor, false));
    492    }
    493 
    494    // TODO(dshin): Already traversed up to find `containerChild`, and we're
    495    // going to do it again here, which feels a little wasteful.
    496    const nsRect rectToContainerChild =
    497        nsLayoutUtils::GetCombinedFragmentRects(aAnchor, true);
    498    const auto offset = aAnchor->GetOffsetToIgnoringScrolling(containerChild);
    499    return Some(rectToContainerChild + offset + containerChild->GetPosition());
    500  }();
    501  return rect.map([&](const nsRect& aRect) {
    502    // We need to position the border box of the anchor within the abspos
    503    // containing block's size - So the rectangle's size (i.e. Anchor size)
    504    // stays the same, while "the outer rectangle" (i.e. The abspos cb size)
    505    // "shrinks" by shifting the position.
    506    const auto border = aAbsoluteContainingBlock->GetUsedBorder();
    507    const nsPoint borderTopLeft{border.left, border.top};
    508    const auto rect = aRect - borderTopLeft;
    509    return rect;
    510  });
    511 }
    512 
    513 Maybe<AnchorPosInfo> AnchorPositioningUtils::ResolveAnchorPosRect(
    514    const nsIFrame* aPositioned, const nsIFrame* aAbsoluteContainingBlock,
    515    const nsAtom* aAnchorName, bool aCBRectIsvalid,
    516    AnchorPosResolutionCache* aResolutionCache) {
    517  if (!aPositioned) {
    518    return Nothing{};
    519  }
    520 
    521  if (!aPositioned->HasAnyStateBits(NS_FRAME_OUT_OF_FLOW)) {
    522    return Nothing{};
    523  }
    524 
    525  MOZ_ASSERT(aPositioned->GetParent() == aAbsoluteContainingBlock);
    526 
    527  const auto* anchorName = GetUsedAnchorName(aPositioned, aAnchorName);
    528  if (!anchorName) {
    529    return Nothing{};
    530  }
    531 
    532  Maybe<AnchorPosResolutionData>* entry = nullptr;
    533  if (aResolutionCache) {
    534    const auto result =
    535        aResolutionCache->mReferenceData->InsertOrModify(anchorName, true);
    536    if (result.mAlreadyResolved) {
    537      MOZ_ASSERT(result.mEntry, "Entry exists but null?");
    538      return result.mEntry->map([&](const AnchorPosResolutionData& aData) {
    539        MOZ_ASSERT(aData.mOffsetData, "Missing anchor offset resolution.");
    540        const auto& offsetData = aData.mOffsetData.ref();
    541        return AnchorPosInfo{nsRect{offsetData.mOrigin, aData.mSize},
    542                             offsetData.mCompensatesForScroll};
    543      });
    544    }
    545    entry = result.mEntry;
    546  }
    547 
    548  const auto* anchor = GetAnchorOf(aPositioned, anchorName);
    549  if (!anchor) {
    550    // If we have a cached entry, just check that it resolved to nothing last
    551    // time as well.
    552    MOZ_ASSERT_IF(entry, entry->isNothing());
    553    return Nothing{};
    554  }
    555 
    556  const auto result =
    557      GetAnchorPosRect(aAbsoluteContainingBlock, anchor, aCBRectIsvalid);
    558  return result.map([&](const nsRect& aRect) {
    559    bool compensatesForScroll = false;
    560    DistanceToNearestScrollContainer distanceToNearestScrollContainer;
    561    if (aResolutionCache) {
    562      MOZ_ASSERT(entry);
    563      // Update the cache.
    564      compensatesForScroll = [&]() {
    565        auto& defaultAnchorCache = aResolutionCache->mDefaultAnchorCache;
    566        if (!aAnchorName) {
    567          // Explicitly resolved default anchor for the first time - populate
    568          // the cache.
    569          defaultAnchorCache.mAnchor = anchor;
    570          const auto [scrollContainer, distance] =
    571              AnchorPositioningUtils::GetNearestScrollFrame(anchor);
    572          distanceToNearestScrollContainer = distance;
    573          defaultAnchorCache.mScrollContainer = scrollContainer;
    574          aResolutionCache->mReferenceData->mDistanceToDefaultScrollContainer =
    575              distance;
    576          aResolutionCache->mReferenceData->mDefaultAnchorName = anchorName;
    577          // This is the default anchor, so scroll compensated by definition.
    578          return true;
    579        }
    580        if (defaultAnchorCache.mAnchor == anchor) {
    581          // This is referring to the default anchor, so scroll compensated by
    582          // definition.
    583          return true;
    584        }
    585        const auto [scrollContainer, distance] =
    586            AnchorPositioningUtils::GetNearestScrollFrame(anchor);
    587        distanceToNearestScrollContainer = distance;
    588        return scrollContainer ==
    589               aResolutionCache->mDefaultAnchorCache.mScrollContainer;
    590      }();
    591      // If a partially resolved entry exists, make sure that it matches what we
    592      // have now.
    593      MOZ_ASSERT_IF(*entry, entry->ref().mSize == aRect.Size());
    594      *entry = Some(AnchorPosResolutionData{
    595          aRect.Size(),
    596          Some(AnchorPosOffsetData{aRect.TopLeft(), compensatesForScroll,
    597                                   distanceToNearestScrollContainer}),
    598      });
    599    }
    600    return AnchorPosInfo{aRect, compensatesForScroll};
    601  });
    602 }
    603 
    604 Maybe<nsSize> AnchorPositioningUtils::ResolveAnchorPosSize(
    605    const nsIFrame* aPositioned, const nsAtom* aAnchorName,
    606    AnchorPosResolutionCache* aResolutionCache) {
    607  const auto* anchorName = GetUsedAnchorName(aPositioned, aAnchorName);
    608  if (!anchorName) {
    609    return Nothing{};
    610  }
    611  Maybe<AnchorPosResolutionData>* entry = nullptr;
    612  auto* referencedAnchors =
    613      aResolutionCache ? aResolutionCache->mReferenceData : nullptr;
    614  if (referencedAnchors) {
    615    const auto result = referencedAnchors->InsertOrModify(anchorName, false);
    616    if (result.mAlreadyResolved) {
    617      MOZ_ASSERT(result.mEntry, "Entry exists but null?");
    618      return result.mEntry->map(
    619          [](const AnchorPosResolutionData& aData) { return aData.mSize; });
    620    }
    621    entry = result.mEntry;
    622  }
    623  const auto* anchor = GetAnchorOf(aPositioned, anchorName);
    624  if (!anchor) {
    625    return Nothing{};
    626  }
    627  const auto size = nsLayoutUtils::GetCombinedFragmentRects(anchor).Size();
    628  if (entry) {
    629    *entry = Some(AnchorPosResolutionData{size, Nothing{}});
    630  }
    631  return Some(size);
    632 }
    633 
    634 /**
    635 * Returns an equivalent StylePositionArea that contains:
    636 * [
    637 *   [ left | center | right | span-left | span-right | span-all]
    638 *   [ top | center | bottom | span-top | span-bottom | span-all]
    639 * ]
    640 */
    641 static StylePositionArea ToPhysicalPositionArea(StylePositionArea aPosArea,
    642                                                WritingMode aCbWM,
    643                                                WritingMode aPosWM) {
    644  StyleWritingMode cbwm{aCbWM.GetBits()};
    645  StyleWritingMode wm{aPosWM.GetBits()};
    646  Servo_PhysicalizePositionArea(&aPosArea, &cbwm, &wm);
    647  return aPosArea;
    648 }
    649 
    650 nsRect AnchorPositioningUtils::AdjustAbsoluteContainingBlockRectForPositionArea(
    651    const nsRect& aAnchorRect, const nsRect& aCBRect, WritingMode aPositionedWM,
    652    WritingMode aCBWM, const StylePositionArea& aPosArea,
    653    StylePositionArea* aOutResolvedArea) {
    654  // Get the boundaries of 3x3 grid in CB's frame space. The edges of the
    655  // default anchor box are clamped to the bounds of the CB, even if that
    656  // results in zero width/height cells.
    657  //
    658  //          ltrEdges[0]  ltrEdges[1]  ltrEdges[2]  ltrEdges[3]
    659  //              |            |            |            |
    660  // ttbEdges[0]  +------------+------------+------------+
    661  //              |            |            |            |
    662  // ttbEdges[1]  +------------+------------+------------+
    663  //              |            |            |            |
    664  // ttbEdges[2]  +------------+------------+------------+
    665  //              |            |            |            |
    666  // ttbEdges[3]  +------------+------------+------------+
    667 
    668  const nsRect gridRect = aCBRect.Union(aAnchorRect);
    669  nscoord ltrEdges[4] = {gridRect.x, aAnchorRect.x,
    670                         aAnchorRect.x + aAnchorRect.width,
    671                         gridRect.x + gridRect.width};
    672  nscoord ttbEdges[4] = {gridRect.y, aAnchorRect.y,
    673                         aAnchorRect.y + aAnchorRect.height,
    674                         gridRect.y + gridRect.height};
    675  ltrEdges[1] = std::clamp(ltrEdges[1], ltrEdges[0], ltrEdges[3]);
    676  ltrEdges[2] = std::clamp(ltrEdges[2], ltrEdges[0], ltrEdges[3]);
    677  ttbEdges[1] = std::clamp(ttbEdges[1], ttbEdges[0], ttbEdges[3]);
    678  ttbEdges[2] = std::clamp(ttbEdges[2], ttbEdges[0], ttbEdges[3]);
    679 
    680  nsRect res = gridRect;
    681 
    682  // PositionArea, resolved to only contain Left/Right/Top/Bottom values.
    683  StylePositionArea posArea =
    684      ToPhysicalPositionArea(aPosArea, aCBWM, aPositionedWM);
    685  *aOutResolvedArea = posArea;
    686 
    687  nscoord right = ltrEdges[3];
    688  if (posArea.first == StylePositionAreaKeyword::Left) {
    689    right = ltrEdges[1];
    690  } else if (posArea.first == StylePositionAreaKeyword::SpanLeft) {
    691    right = ltrEdges[2];
    692  } else if (posArea.first == StylePositionAreaKeyword::Center) {
    693    res.x = ltrEdges[1];
    694    right = ltrEdges[2];
    695  } else if (posArea.first == StylePositionAreaKeyword::SpanRight) {
    696    res.x = ltrEdges[1];
    697  } else if (posArea.first == StylePositionAreaKeyword::Right) {
    698    res.x = ltrEdges[2];
    699  } else if (posArea.first == StylePositionAreaKeyword::SpanAll) {
    700    // no adjustment
    701  } else {
    702    MOZ_ASSERT_UNREACHABLE("Bad value from ToPhysicalPositionArea");
    703  }
    704  res.width = right - res.x;
    705 
    706  nscoord bottom = ttbEdges[3];
    707  if (posArea.second == StylePositionAreaKeyword::Top) {
    708    bottom = ttbEdges[1];
    709  } else if (posArea.second == StylePositionAreaKeyword::SpanTop) {
    710    bottom = ttbEdges[2];
    711  } else if (posArea.second == StylePositionAreaKeyword::Center) {
    712    res.y = ttbEdges[1];
    713    bottom = ttbEdges[2];
    714  } else if (posArea.second == StylePositionAreaKeyword::SpanBottom) {
    715    res.y = ttbEdges[1];
    716  } else if (posArea.second == StylePositionAreaKeyword::Bottom) {
    717    res.y = ttbEdges[2];
    718  } else if (posArea.second == StylePositionAreaKeyword::SpanAll) {
    719    // no adjustment
    720  } else {
    721    MOZ_ASSERT_UNREACHABLE("Bad value from ToPhysicalPositionArea");
    722  }
    723  res.height = bottom - res.y;
    724 
    725  return res;
    726 }
    727 
    728 AnchorPositioningUtils::NearestScrollFrameInfo
    729 AnchorPositioningUtils::GetNearestScrollFrame(const nsIFrame* aFrame) {
    730  if (!aFrame) {
    731    return {nullptr, {}};
    732  }
    733  uint32_t distance = 1;
    734  // `GetNearestScrollContainerFrame` will return the incoming frame if it's a
    735  // scroll frame, so nudge to parent.
    736  for (const nsIFrame* f = aFrame->GetParent(); f; f = f->GetParent()) {
    737    if (f->IsScrollContainerOrSubclass()) {
    738      return {f, DistanceToNearestScrollContainer{distance}};
    739    }
    740    distance++;
    741  }
    742  return {nullptr, {}};
    743 }
    744 
    745 nsPoint AnchorPositioningUtils::GetScrollOffsetFor(
    746    PhysicalAxes aAxes, const nsIFrame* aPositioned,
    747    const AnchorPosDefaultAnchorCache& aDefaultAnchorCache) {
    748  MOZ_ASSERT(aPositioned);
    749  if (!aDefaultAnchorCache.mAnchor || aAxes.isEmpty()) {
    750    return nsPoint{};
    751  }
    752  nsPoint offset;
    753  const bool trackHorizontal = aAxes.contains(PhysicalAxis::Horizontal);
    754  const bool trackVertical = aAxes.contains(PhysicalAxis::Vertical);
    755  // TODO(dshin, bug 1991489): Traverse properly, in case anchor and positioned
    756  // elements are in different continuation frames of the absolute containing
    757  // block.
    758  const auto* absoluteContainingBlock = aPositioned->GetParent();
    759  if (GetNearestScrollFrame(aPositioned).mScrollContainer ==
    760      aDefaultAnchorCache.mScrollContainer) {
    761    // Would scroll together anyway, skip.
    762    return nsPoint{};
    763  }
    764  // Grab the accumulated offset up to, but not including, the abspos
    765  // container.
    766  for (const auto* f = aDefaultAnchorCache.mScrollContainer;
    767       f && f != absoluteContainingBlock; f = f->GetParent()) {
    768    if (const ScrollContainerFrame* scrollFrame = do_QueryFrame(f)) {
    769      const auto o = scrollFrame->GetScrollPosition();
    770      if (trackHorizontal) {
    771        offset.x += o.x;
    772      }
    773      if (trackVertical) {
    774        offset.y += o.y;
    775      }
    776    }
    777  }
    778  return offset;
    779 }
    780 
    781 // Out of line to avoid having to include AnchorPosReferenceData from nsIFrame.h
    782 void DeleteAnchorPosReferenceData(AnchorPosReferenceData* aData) {
    783  delete aData;
    784 }
    785 
    786 void DeleteLastSuccessfulPositionData(LastSuccessfulPositionData* aData) {
    787  delete aData;
    788 }
    789 
    790 const nsAtom* AnchorPositioningUtils::GetUsedAnchorName(
    791    const nsIFrame* aPositioned, const nsAtom* aAnchorName) {
    792  if (aAnchorName && !aAnchorName->IsEmpty()) {
    793    return aAnchorName;
    794  }
    795 
    796  const auto& defaultAnchor = aPositioned->StylePosition()->mPositionAnchor;
    797  if (defaultAnchor.IsNone()) {
    798    return nullptr;
    799  }
    800 
    801  if (defaultAnchor.IsIdent()) {
    802    return defaultAnchor.AsIdent().AsAtom();
    803  }
    804 
    805  if (aPositioned->Style()->IsPseudoElement()) {
    806    return nsGkAtoms::AnchorPosImplicitAnchor;
    807  }
    808 
    809  if (const nsIContent* content = aPositioned->GetContent()) {
    810    if (const auto* element = content->AsElement()) {
    811      if (element->GetPopoverData()) {
    812        return nsGkAtoms::AnchorPosImplicitAnchor;
    813      }
    814    }
    815  }
    816 
    817  return nullptr;
    818 }
    819 
    820 nsIFrame* AnchorPositioningUtils::GetAnchorPosImplicitAnchor(
    821    const nsIFrame* aFrame) {
    822  const auto* frameContent = aFrame->GetContent();
    823  const bool hasElement = frameContent && frameContent->IsElement();
    824  if (!aFrame->Style()->IsPseudoElement() && !hasElement) {
    825    return nullptr;
    826  }
    827 
    828  if (MOZ_LIKELY(hasElement)) {
    829    const auto* element = frameContent->AsElement();
    830    MOZ_ASSERT(element);
    831    const dom::PopoverData* popoverData = element->GetPopoverData();
    832    if (MOZ_UNLIKELY(popoverData)) {
    833      if (const RefPtr<dom::Element>& invoker = popoverData->GetInvoker()) {
    834        return invoker->GetPrimaryFrame();
    835      }
    836    }
    837  }
    838 
    839  const auto* pseudoRoot = aFrame->GetClosestNativeAnonymousSubtreeRoot();
    840  if (!pseudoRoot) {
    841    return nullptr;
    842  }
    843 
    844  auto* pseudoRootFrame = pseudoRoot->GetPrimaryFrame();
    845  if (!pseudoRootFrame) {
    846    return nullptr;
    847  }
    848 
    849  return pseudoRootFrame->HasAnyStateBits(NS_FRAME_OUT_OF_FLOW)
    850             ? pseudoRootFrame->GetPlaceholderFrame()->GetParent()
    851             : pseudoRootFrame->GetParent();
    852 }
    853 
    854 AnchorPositioningUtils::ContainingBlockInfo
    855 AnchorPositioningUtils::ContainingBlockInfo::ExplicitCBFrameSize(
    856    const nsRect& aContainingBlockRect) {
    857  // TODO(dshin, bug 1989292): Ideally, this takes both local containing rect +
    858  // scrollable containing rect, and one is picked here.
    859  return ContainingBlockInfo{aContainingBlockRect};
    860 }
    861 
    862 AnchorPositioningUtils::ContainingBlockInfo
    863 AnchorPositioningUtils::ContainingBlockInfo::UseCBFrameSize(
    864    const nsIFrame* aPositioned) {
    865  // TODO(dshin, bug 1989292): This just gets local containing block.
    866  const auto* cb = aPositioned->GetParent();
    867  MOZ_ASSERT(cb);
    868  if (IsScrolled(cb)) {
    869    cb = aPositioned->GetParent();
    870  }
    871  return ContainingBlockInfo{cb->GetPaddingRectRelativeToSelf()};
    872 }
    873 
    874 bool AnchorPositioningUtils::FitsInContainingBlock(
    875    const nsIFrame* aPositioned, const AnchorPosReferenceData& aReferenceData) {
    876  MOZ_ASSERT(aPositioned->GetProperty(nsIFrame::AnchorPosReferences()) ==
    877             &aReferenceData);
    878 
    879  const auto& scrollShift = aReferenceData.mDefaultScrollShift;
    880  const auto scrollCompensatedSides = aReferenceData.mScrollCompensatedSides;
    881  nsSize checkSize = [&]() {
    882    const auto& adjustedCB = aReferenceData.mAdjustedContainingBlock;
    883    if (scrollShift == nsPoint{} || scrollCompensatedSides == SideBits::eNone) {
    884      return adjustedCB.Size();
    885    }
    886 
    887    // We now know that this frame's anchor has moved in relation to
    888    // the original containing block, and that at least one side of our
    889    // IMCB is attached to it.
    890 
    891    // Scroll shift the adjusted containing block.
    892    const auto shifted = aReferenceData.mAdjustedContainingBlock - scrollShift;
    893    const auto& originalCB = aReferenceData.mOriginalContainingBlockRect;
    894 
    895    // Now, move edges that are not attached to the anchors and pin it
    896    // to the original containing block.
    897    const nsPoint pt{
    898        scrollCompensatedSides & SideBits::eLeft ? shifted.X() : originalCB.X(),
    899        scrollCompensatedSides & SideBits::eTop ? shifted.Y() : originalCB.Y()};
    900    const nsPoint ptMost{
    901        scrollCompensatedSides & SideBits::eRight ? shifted.XMost()
    902                                                  : originalCB.XMost(),
    903        scrollCompensatedSides & SideBits::eBottom ? shifted.YMost()
    904                                                   : originalCB.YMost()};
    905 
    906    return nsSize{ptMost.x - pt.x, ptMost.y - pt.y};
    907  }();
    908 
    909  // Finally, reduce by inset.
    910  checkSize -= nsSize{aReferenceData.mInsets.LeftRight(),
    911                      aReferenceData.mInsets.TopBottom()};
    912 
    913  return aPositioned->GetMarginRectRelativeToSelf().Size() <= checkSize;
    914 }
    915 
    916 nsIFrame* AnchorPositioningUtils::GetAnchorThatFrameScrollsWith(
    917    nsIFrame* aFrame, nsDisplayListBuilder* aBuilder,
    918    bool aSkipAsserts /* = false */) {
    919 #ifdef DEBUG
    920  if (!aSkipAsserts) {
    921    MOZ_ASSERT(!aBuilder || aBuilder->IsPaintingToWindow());
    922    MOZ_ASSERT_IF(!aBuilder, aFrame->PresContext()->LayoutPhaseCount(
    923                                 nsLayoutPhase::DisplayListBuilding) == 0);
    924  }
    925 #endif
    926 
    927  if (!StaticPrefs::apz_async_scroll_css_anchor_pos_AtStartup()) {
    928    return nullptr;
    929  }
    930  PhysicalAxes axes = aFrame->GetAnchorPosCompensatingForScroll();
    931  if (axes.isEmpty()) {
    932    return nullptr;
    933  }
    934 
    935  const auto* pos = aFrame->StylePosition();
    936  if (!pos->mPositionAnchor.IsIdent()) {
    937    return nullptr;
    938  }
    939 
    940  const nsAtom* defaultAnchorName = pos->mPositionAnchor.AsIdent().AsAtom();
    941  nsIFrame* anchor = const_cast<nsIFrame*>(
    942      aFrame->PresShell()->GetAnchorPosAnchor(defaultAnchorName, aFrame));
    943  // TODO Bug 1997026 We need to update the anchor finding code so this can't
    944  // happen. For now we just detect it and reject it.
    945  if (anchor && !nsLayoutUtils::IsProperAncestorFrameConsideringContinuations(
    946                    aFrame->GetParent(), anchor)) {
    947    return nullptr;
    948  }
    949  if (!aBuilder) {
    950    return anchor;
    951  }
    952  // TODO for now ShouldAsyncScrollWithAnchor will return false if we are
    953  // compensating in only one axis and there is a scroll frame between the
    954  // anchor and the positioned's containing block that can scroll in the "wrong"
    955  // axis so that we don't async scroll in the wrong axis because ASRs/APZ only
    956  // support scrolling in both axes. This is not fully spec compliant, bug
    957  // 1988034 tracks this.
    958  return DisplayPortUtils::ShouldAsyncScrollWithAnchor(aFrame, anchor, aBuilder,
    959                                                       axes)
    960             ? anchor
    961             : nullptr;
    962 }
    963 
    964 static bool TriggerFallbackReflow(PresShell* aPresShell, nsIFrame* aPositioned,
    965                                  AnchorPosReferenceData& aReferencedAnchors,
    966                                  bool aEvaluateAllFallbacksIfNeeded) {
    967  auto totalFallbacks =
    968      aPositioned->StylePosition()->mPositionTryFallbacks._0.Length();
    969  if (!totalFallbacks) {
    970    // No fallbacks specified.
    971    return false;
    972  }
    973 
    974  const bool positionedFitsInCB = AnchorPositioningUtils::FitsInContainingBlock(
    975      aPositioned, aReferencedAnchors);
    976  if (positionedFitsInCB) {
    977    return false;
    978  }
    979 
    980  // TODO(bug 1987964): Try to only do this when the scroll offset changes?
    981  auto* lastSuccessfulPosition =
    982      aPositioned->GetProperty(nsIFrame::LastSuccessfulPositionFallback());
    983  const bool needsRetry =
    984      aEvaluateAllFallbacksIfNeeded ||
    985      (lastSuccessfulPosition && !lastSuccessfulPosition->mTriedAllFallbacks);
    986  if (!needsRetry) {
    987    return false;
    988  }
    989  aPresShell->MarkPositionedFrameForReflow(aPositioned);
    990  return true;
    991 }
    992 
    993 static bool AnchorIsEffectivelyHidden(nsIFrame* aAnchor) {
    994  if (!aAnchor->StyleVisibility()->IsVisible()) {
    995    return true;
    996  }
    997  for (auto* anchor = aAnchor; anchor; anchor = anchor->GetParent()) {
    998    if (anchor->HasAnyStateBits(NS_FRAME_POSITION_VISIBILITY_HIDDEN)) {
    999      return true;
   1000    }
   1001  }
   1002  return false;
   1003 }
   1004 
   1005 static bool ComputePositionVisibility(
   1006    PresShell* aPresShell, nsIFrame* aPositioned,
   1007    AnchorPosReferenceData& aReferencedAnchors) {
   1008  auto vis = aPositioned->StylePosition()->mPositionVisibility;
   1009  if (vis & StylePositionVisibility::ALWAYS) {
   1010    MOZ_ASSERT(vis == StylePositionVisibility::ALWAYS,
   1011               "always can't be combined");
   1012    return true;
   1013  }
   1014  if (vis & StylePositionVisibility::ANCHORS_VALID) {
   1015    for (const auto& ref : aReferencedAnchors) {
   1016      if (ref.GetData().isNothing()) {
   1017        return false;
   1018      }
   1019    }
   1020  }
   1021  if (vis & StylePositionVisibility::NO_OVERFLOW) {
   1022    const bool positionedFitsInCB =
   1023        AnchorPositioningUtils::FitsInContainingBlock(aPositioned,
   1024                                                      aReferencedAnchors);
   1025    if (!positionedFitsInCB) {
   1026      return false;
   1027    }
   1028  }
   1029  if (vis & StylePositionVisibility::ANCHORS_VISIBLE) {
   1030    const auto* defaultAnchorName = aReferencedAnchors.mDefaultAnchorName.get();
   1031    if (defaultAnchorName) {
   1032      auto* defaultAnchor =
   1033          aPresShell->GetAnchorPosAnchor(defaultAnchorName, aPositioned);
   1034      if (defaultAnchor && AnchorIsEffectivelyHidden(defaultAnchor)) {
   1035        return false;
   1036      }
   1037      // If both are in the same cb the expectation is that this doesn't apply
   1038      // because there are no intervening clips. I think that's broken, see
   1039      // https://github.com/w3c/csswg-drafts/issues/13176
   1040      if (defaultAnchor &&
   1041          defaultAnchor->GetParent() != aPositioned->GetParent()) {
   1042        auto* intersectionRoot = aPositioned->GetParent();
   1043        nsRect rootRect = intersectionRoot->InkOverflowRectRelativeToSelf();
   1044        if (IsScrolled(intersectionRoot)) {
   1045          intersectionRoot = intersectionRoot->GetParent();
   1046          ScrollContainerFrame* sc = do_QueryFrame(intersectionRoot);
   1047          rootRect = sc->GetScrollPortRectAccountingForDynamicToolbar();
   1048        }
   1049        const auto* doc = aPositioned->PresContext()->Document();
   1050        const nsINode* root =
   1051            intersectionRoot->GetContent()
   1052                ? static_cast<nsINode*>(intersectionRoot->GetContent())
   1053                : doc;
   1054        rootRect = nsLayoutUtils::TransformFrameRectToAncestor(
   1055            intersectionRoot, rootRect,
   1056            nsLayoutUtils::GetContainingBlockForClientRect(intersectionRoot));
   1057        const auto input = dom::IntersectionInput{
   1058            .mIsImplicitRoot = false,
   1059            .mRootNode = root,
   1060            .mRootFrame = intersectionRoot,
   1061            .mRootRect = rootRect,
   1062            .mRootMargin = {},
   1063            .mScrollMargin = {},
   1064            .mRemoteDocumentVisibleRect = {},
   1065        };
   1066        const auto output =
   1067            dom::DOMIntersectionObserver::Intersect(input, defaultAnchor);
   1068        // NOTE(emilio): It is a bit weird to also check that mIntersectionRect
   1069        // is non-empty, see https://github.com/w3c/csswg-drafts/issues/13176.
   1070        if (!output.Intersects() || (output.mIntersectionRect->IsEmpty() &&
   1071                                     !defaultAnchor->GetRect().IsEmpty())) {
   1072          return false;
   1073        }
   1074      }
   1075    }
   1076  }
   1077  return true;
   1078 }
   1079 
   1080 bool AnchorPositioningUtils::TriggerLayoutOnOverflow(
   1081    PresShell* aPresShell, bool aEvaluateAllFallbacksIfNeeded) {
   1082  bool didLayoutPositionedItems = false;
   1083 
   1084  for (auto* positioned : aPresShell->GetAnchorPosPositioned()) {
   1085    AnchorPosReferenceData* referencedAnchors =
   1086        positioned->GetProperty(nsIFrame::AnchorPosReferences());
   1087    if (NS_WARN_IF(!referencedAnchors)) {
   1088      continue;
   1089    }
   1090 
   1091    if (TriggerFallbackReflow(aPresShell, positioned, *referencedAnchors,
   1092                              aEvaluateAllFallbacksIfNeeded)) {
   1093      didLayoutPositionedItems = true;
   1094    }
   1095 
   1096    if (didLayoutPositionedItems) {
   1097      // We'll come back to evaluate position-visibility later.
   1098      continue;
   1099    }
   1100    const bool shouldBeVisible =
   1101        ComputePositionVisibility(aPresShell, positioned, *referencedAnchors);
   1102    const bool isVisible =
   1103        !positioned->HasAnyStateBits(NS_FRAME_POSITION_VISIBILITY_HIDDEN);
   1104    if (shouldBeVisible != isVisible) {
   1105      positioned->AddOrRemoveStateBits(NS_FRAME_POSITION_VISIBILITY_HIDDEN,
   1106                                       !shouldBeVisible);
   1107      positioned->InvalidateFrameSubtree();
   1108    }
   1109  }
   1110  return didLayoutPositionedItems;
   1111 }
   1112 
   1113 }  // namespace mozilla