tor-browser

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

DOMIntersectionObserver.cpp (40586B)


      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 file,
      5 * You can obtain one at http://mozilla.org/MPL/2.0/. */
      6 
      7 #include "DOMIntersectionObserver.h"
      8 
      9 #include "NonCustomCSSPropertyId.h"
     10 #include "Units.h"
     11 #include "mozilla/PresShell.h"
     12 #include "mozilla/ScrollContainerFrame.h"
     13 #include "mozilla/ServoBindings.h"
     14 #include "mozilla/StaticPrefs_dom.h"
     15 #include "mozilla/StaticPrefs_layout.h"
     16 #include "mozilla/dom/BrowserChild.h"
     17 #include "mozilla/dom/BrowsingContext.h"
     18 #include "mozilla/dom/DocumentInlines.h"
     19 #include "mozilla/dom/Element.h"
     20 #include "mozilla/dom/ElementInlines.h"
     21 #include "mozilla/dom/HTMLIFrameElement.h"
     22 #include "mozilla/dom/HTMLImageElement.h"
     23 #include "nsContainerFrame.h"
     24 #include "nsContentUtils.h"
     25 #include "nsIFrame.h"
     26 #include "nsLayoutUtils.h"
     27 #include "nsRefreshDriver.h"
     28 
     29 namespace mozilla::dom {
     30 
     31 NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(DOMIntersectionObserverEntry)
     32  NS_WRAPPERCACHE_INTERFACE_MAP_ENTRY
     33  NS_INTERFACE_MAP_ENTRY(nsISupports)
     34 NS_INTERFACE_MAP_END
     35 
     36 NS_IMPL_CYCLE_COLLECTING_ADDREF(DOMIntersectionObserverEntry)
     37 NS_IMPL_CYCLE_COLLECTING_RELEASE(DOMIntersectionObserverEntry)
     38 
     39 NS_IMPL_CYCLE_COLLECTION_WRAPPERCACHE(DOMIntersectionObserverEntry, mOwner,
     40                                      mRootBounds, mBoundingClientRect,
     41                                      mIntersectionRect, mTarget)
     42 
     43 NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(DOMIntersectionObserver)
     44  NS_WRAPPERCACHE_INTERFACE_MAP_ENTRY
     45  NS_INTERFACE_MAP_ENTRY(nsISupports)
     46  NS_INTERFACE_MAP_ENTRY(DOMIntersectionObserver)
     47 NS_INTERFACE_MAP_END
     48 
     49 NS_IMPL_CYCLE_COLLECTING_ADDREF(DOMIntersectionObserver)
     50 NS_IMPL_CYCLE_COLLECTING_RELEASE(DOMIntersectionObserver)
     51 
     52 NS_IMPL_CYCLE_COLLECTION_CLASS(DOMIntersectionObserver)
     53 
     54 NS_IMPL_CYCLE_COLLECTION_TRACE_BEGIN(DOMIntersectionObserver)
     55  NS_IMPL_CYCLE_COLLECTION_TRACE_PRESERVED_WRAPPER
     56 NS_IMPL_CYCLE_COLLECTION_TRACE_END
     57 
     58 NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN(DOMIntersectionObserver)
     59  NS_IMPL_CYCLE_COLLECTION_UNLINK_PRESERVED_WRAPPER
     60  tmp->Disconnect();
     61  NS_IMPL_CYCLE_COLLECTION_UNLINK(mOwner)
     62  NS_IMPL_CYCLE_COLLECTION_UNLINK(mDocument)
     63  if (tmp->mCallback.is<RefPtr<dom::IntersectionCallback>>()) {
     64    ImplCycleCollectionUnlink(
     65        tmp->mCallback.as<RefPtr<dom::IntersectionCallback>>());
     66  }
     67  NS_IMPL_CYCLE_COLLECTION_UNLINK(mRoot)
     68  NS_IMPL_CYCLE_COLLECTION_UNLINK(mQueuedEntries)
     69 NS_IMPL_CYCLE_COLLECTION_UNLINK_END
     70 
     71 NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN(DOMIntersectionObserver)
     72  NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mOwner)
     73  NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mDocument)
     74  if (tmp->mCallback.is<RefPtr<dom::IntersectionCallback>>()) {
     75    ImplCycleCollectionTraverse(
     76        cb, tmp->mCallback.as<RefPtr<dom::IntersectionCallback>>(), "mCallback",
     77        0);
     78  }
     79  NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mRoot)
     80  NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mQueuedEntries)
     81 NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END
     82 
     83 DOMIntersectionObserver::DOMIntersectionObserver(
     84    already_AddRefed<nsPIDOMWindowInner>&& aOwner,
     85    dom::IntersectionCallback& aCb)
     86    : mOwner(aOwner),
     87      mDocument(mOwner->GetExtantDoc()),
     88      mCallback(RefPtr<dom::IntersectionCallback>(&aCb)) {}
     89 
     90 already_AddRefed<DOMIntersectionObserver> DOMIntersectionObserver::Constructor(
     91    const GlobalObject& aGlobal, dom::IntersectionCallback& aCb,
     92    ErrorResult& aRv) {
     93  return Constructor(aGlobal, aCb, IntersectionObserverInit(), aRv);
     94 }
     95 
     96 // https://w3c.github.io/IntersectionObserver/#initialize-new-intersection-observer
     97 already_AddRefed<DOMIntersectionObserver> DOMIntersectionObserver::Constructor(
     98    const GlobalObject& aGlobal, dom::IntersectionCallback& aCb,
     99    const IntersectionObserverInit& aOptions, ErrorResult& aRv) {
    100  nsCOMPtr<nsPIDOMWindowInner> window =
    101      do_QueryInterface(aGlobal.GetAsSupports());
    102  if (!window) {
    103    aRv.Throw(NS_ERROR_FAILURE);
    104    return nullptr;
    105  }
    106 
    107  // 1. Let this be a new IntersectionObserver object
    108  // 2. Set this’s internal [[callback]] slot to callback.
    109  RefPtr<DOMIntersectionObserver> observer =
    110      new DOMIntersectionObserver(window.forget(), aCb);
    111 
    112  if (!aOptions.mRoot.IsNull()) {
    113    if (aOptions.mRoot.Value().IsElement()) {
    114      observer->mRoot = aOptions.mRoot.Value().GetAsElement();
    115    } else {
    116      MOZ_ASSERT(aOptions.mRoot.Value().IsDocument());
    117      observer->mRoot = aOptions.mRoot.Value().GetAsDocument();
    118    }
    119  }
    120 
    121  // 3. Attempt to parse a margin from options.rootMargin. If a list is
    122  // returned, set this’s internal [[rootMargin]] slot to that. Otherwise, throw
    123  // a SyntaxError exception.
    124  if (!observer->SetRootMargin(aOptions.mRootMargin)) {
    125    aRv.ThrowSyntaxError("rootMargin must be specified in pixels or percent.");
    126    return nullptr;
    127  }
    128 
    129  // 4. Attempt to parse a margin from options.scrollMargin. If a list is
    130  // returned, set this’s internal [[scrollMargin]] slot to that. Otherwise,
    131  // throw a SyntaxError exception.
    132  if (!observer->SetScrollMargin(aOptions.mScrollMargin)) {
    133    aRv.ThrowSyntaxError(
    134        "scrollMargin must be specified in pixels or percent.");
    135    return nullptr;
    136  }
    137 
    138  // 5. Let thresholds be a list equal to options.threshold.
    139  if (aOptions.mThreshold.IsDoubleSequence()) {
    140    const Sequence<double>& thresholds =
    141        aOptions.mThreshold.GetAsDoubleSequence();
    142    observer->mThresholds.SetCapacity(thresholds.Length());
    143 
    144    // 6. If any value in thresholds is less than 0.0 or greater than 1.0, throw
    145    // a RangeError exception.
    146    for (const auto& thresh : thresholds) {
    147      if (thresh < 0.0 || thresh > 1.0) {
    148        aRv.ThrowRangeError<dom::MSG_THRESHOLD_RANGE_ERROR>();
    149        return nullptr;
    150      }
    151      observer->mThresholds.AppendElement(thresh);
    152    }
    153 
    154    // 7. Sort thresholds in ascending order.
    155    observer->mThresholds.Sort();
    156 
    157    // 8. If thresholds is empty, append 0 to thresholds.
    158    if (observer->mThresholds.IsEmpty()) {
    159      observer->mThresholds.AppendElement(0.0);
    160    }
    161  } else {
    162    double thresh = aOptions.mThreshold.GetAsDouble();
    163    if (thresh < 0.0 || thresh > 1.0) {
    164      aRv.ThrowRangeError<dom::MSG_THRESHOLD_RANGE_ERROR>();
    165      return nullptr;
    166    }
    167    observer->mThresholds.AppendElement(thresh);
    168  }
    169 
    170  // 9. The thresholds attribute getter will return this sorted thresholds list.
    171  // (This is implicit given `observer->mThresholds`)
    172 
    173  // 10. Let delay be the value of options.delay.
    174  // TODO: https://bugzilla.mozilla.org/show_bug.cgi?id=1896900
    175 
    176  // 11. If options.trackVisibility is true and delay is less than 100, set
    177  // delay to 100.
    178  // TODO: https://bugzilla.mozilla.org/show_bug.cgi?id=1896900
    179 
    180  // 12. Set this’s internal [[delay]] slot to options.delay to delay.
    181  // TODO: https://bugzilla.mozilla.org/show_bug.cgi?id=1896900
    182 
    183  // 13. Set this’s internal [[trackVisibility]] slot to
    184  // options.trackVisibility.
    185  // TODO: https://bugzilla.mozilla.org/show_bug.cgi?id=1896900
    186 
    187  // 14. Return this.
    188  return observer.forget();
    189 }
    190 
    191 static void LazyLoadCallback(
    192    const Sequence<OwningNonNull<DOMIntersectionObserverEntry>>& aEntries) {
    193  for (const auto& entry : aEntries) {
    194    Element* target = entry->Target();
    195    if (entry->IsIntersecting()) {
    196      if (auto* image = HTMLImageElement::FromNode(target)) {
    197        image->StopLazyLoading();
    198      } else if (auto* iframe = HTMLIFrameElement::FromNode(target)) {
    199        iframe->StopLazyLoading();
    200      } else {
    201        MOZ_ASSERT_UNREACHABLE(
    202            "Only <img> and <iframe> should be observed by lazy load observer");
    203      }
    204    }
    205  }
    206 }
    207 
    208 static LengthPercentage PrefMargin(float aValue, bool aIsPercentage) {
    209  return aIsPercentage ? LengthPercentage::FromPercentage(aValue / 100.0f)
    210                       : LengthPercentage::FromPixels(aValue);
    211 }
    212 
    213 static IntersectionObserverMargin LazyLoadingMargin() {
    214  IntersectionObserverMargin margin;
    215 #define SET_MARGIN(side_, side_lower_)                      \
    216  margin.Get(eSide##side_) = PrefMargin(                    \
    217      StaticPrefs::dom_lazy_loading_margin_##side_lower_(), \
    218      StaticPrefs::dom_lazy_loading_margin_##side_lower_##_percentage());
    219  SET_MARGIN(Top, top);
    220  SET_MARGIN(Right, right);
    221  SET_MARGIN(Bottom, bottom);
    222  SET_MARGIN(Left, left);
    223 #undef SET_MARGIN
    224  return margin;
    225 }
    226 
    227 DOMIntersectionObserver::DOMIntersectionObserver(Document& aDocument,
    228                                                 NativeCallback aCallback)
    229    : mOwner(aDocument.GetInnerWindow()),
    230      mDocument(&aDocument),
    231      mCallback(aCallback) {}
    232 
    233 already_AddRefed<DOMIntersectionObserver>
    234 DOMIntersectionObserver::CreateLazyLoadObserver(Document& aDocument) {
    235  RefPtr<DOMIntersectionObserver> observer =
    236      new DOMIntersectionObserver(aDocument, LazyLoadCallback);
    237  observer->mThresholds.AppendElement(0.0f);
    238  auto* margin = StaticPrefs::dom_lazy_loading_margin_is_scroll()
    239                     ? &observer->mScrollMargin
    240                     : &observer->mRootMargin;
    241  *margin = LazyLoadingMargin();
    242  return observer.forget();
    243 }
    244 
    245 bool DOMIntersectionObserver::SetRootMargin(const nsACString& aString) {
    246  return Servo_IntersectionObserverMargin_Parse(&aString, &mRootMargin);
    247 }
    248 
    249 bool DOMIntersectionObserver::SetScrollMargin(const nsACString& aString) {
    250  return Servo_IntersectionObserverMargin_Parse(&aString, &mScrollMargin);
    251 }
    252 
    253 nsISupports* DOMIntersectionObserver::GetParentObject() const { return mOwner; }
    254 
    255 void DOMIntersectionObserver::GetRootMargin(nsACString& aRetVal) {
    256  Servo_IntersectionObserverMargin_ToString(&mRootMargin, &aRetVal);
    257 }
    258 
    259 void DOMIntersectionObserver::GetScrollMargin(nsACString& aRetVal) {
    260  Servo_IntersectionObserverMargin_ToString(&mScrollMargin, &aRetVal);
    261 }
    262 
    263 void DOMIntersectionObserver::GetThresholds(nsTArray<double>& aRetVal) {
    264  aRetVal = mThresholds.Clone();
    265 }
    266 
    267 // https://w3c.github.io/IntersectionObserver/#observe-target-element
    268 void DOMIntersectionObserver::Observe(Element& aTarget) {
    269  // 1. If target is in observer’s internal [[ObservationTargets]] slot, return.
    270  bool wasPresent =
    271      mObservationTargetMap.WithEntryHandle(&aTarget, [](auto handle) {
    272        if (handle.HasEntry()) {
    273          return true;
    274        }
    275        handle.Insert(Uninitialized);
    276        return false;
    277      });
    278  if (wasPresent) {
    279    return;
    280  }
    281 
    282  // 2. Let intersectionObserverRegistration be an
    283  // IntersectionObserverRegistration record with an observer property set to
    284  // observer, a previousThresholdIndex property set to -1, a
    285  // previousIsIntersecting property set to false, and a previousIsVisible
    286  // property set to false.
    287  // 3. Append intersectionObserverRegistration to target’s internal
    288  // [[RegisteredIntersectionObservers]] slot.
    289  // 4. Add target to observer’s internal [[ObservationTargets]] slot.
    290  aTarget.BindObject(this, [](nsISupports* aObserver, nsINode* aTarget) {
    291    static_cast<DOMIntersectionObserver*>(aObserver)->UnlinkTarget(
    292        *aTarget->AsElement());
    293  });
    294  mObservationTargets.AppendElement(&aTarget);
    295 
    296  MOZ_ASSERT(mObservationTargets.Length() == mObservationTargetMap.Count());
    297 
    298  Connect();
    299  if (mDocument) {
    300    if (nsPresContext* pc = mDocument->GetPresContext()) {
    301      pc->RefreshDriver()->EnsureIntersectionObservationsUpdateHappens();
    302    }
    303  }
    304 }
    305 
    306 // https://w3c.github.io/IntersectionObserver/#unobserve-target-element
    307 void DOMIntersectionObserver::Unobserve(Element& aTarget) {
    308  // 1. Remove the IntersectionObserverRegistration record whose observer
    309  // property is equal to this from target’s internal
    310  // [[RegisteredIntersectionObservers]] slot, if present.
    311  if (!mObservationTargetMap.Remove(&aTarget)) {
    312    return;
    313  }
    314 
    315  // 2. Remove target from this’s internal [[ObservationTargets]] slot, if
    316  // present
    317  mObservationTargets.RemoveElement(&aTarget);
    318 
    319  aTarget.UnbindObject(this);
    320 
    321  MOZ_ASSERT(mObservationTargets.Length() == mObservationTargetMap.Count());
    322 
    323  if (mObservationTargets.IsEmpty()) {
    324    Disconnect();
    325  }
    326 }
    327 
    328 // Inner step of
    329 // https://w3c.github.io/IntersectionObserver/#dom-intersectionobserver-disconnect
    330 void DOMIntersectionObserver::UnlinkTarget(Element& aTarget) {
    331  // 1. Remove the IntersectionObserverRegistration record whose observer
    332  // property is equal to this from target’s internal
    333  // [[RegisteredIntersectionObservers]] slot.
    334  // 2. Remove target from this’s internal [[ObservationTargets]] slot.
    335  mObservationTargets.RemoveElement(&aTarget);
    336  mObservationTargetMap.Remove(&aTarget);
    337 
    338  if (mObservationTargets.IsEmpty()) {
    339    Disconnect();
    340  }
    341 }
    342 
    343 void DOMIntersectionObserver::Connect() {
    344  if (mConnected) {
    345    return;
    346  }
    347 
    348  mConnected = true;
    349  if (mDocument) {
    350    mDocument->AddIntersectionObserver(*this);
    351  }
    352 }
    353 
    354 // https://w3c.github.io/IntersectionObserver/#dom-intersectionobserver-disconnect
    355 void DOMIntersectionObserver::Disconnect() {
    356  if (!mConnected) {
    357    return;
    358  }
    359 
    360  mConnected = false;
    361  // 1. For each target in this’s internal [[ObservationTargets]] slot:
    362  for (Element* target : mObservationTargets) {
    363    // 2. Remove the IntersectionObserverRegistration record whose observer
    364    // property is equal to this from target’s internal
    365    // [[RegisteredIntersectionObservers]] slot.
    366    // 3. Remove target from this’s internal [[ObservationTargets]] slot.
    367    target->UnbindObject(this);
    368  }
    369 
    370  mObservationTargets.Clear();
    371  mObservationTargetMap.Clear();
    372  if (mDocument) {
    373    mDocument->RemoveIntersectionObserver(*this);
    374  }
    375 }
    376 
    377 // https://w3c.github.io/IntersectionObserver/#dom-intersectionobserver-takerecords
    378 void DOMIntersectionObserver::TakeRecords(
    379    nsTArray<RefPtr<DOMIntersectionObserverEntry>>& aRetVal) {
    380  aRetVal = std::move(mQueuedEntries);
    381 }
    382 
    383 enum class BrowsingContextOrigin { Similar, Different };
    384 
    385 // NOTE(emilio): Checking docgroup as per discussion in:
    386 // https://github.com/w3c/IntersectionObserver/issues/161
    387 static BrowsingContextOrigin SimilarOrigin(const nsIContent& aTarget,
    388                                           const nsINode* aRoot) {
    389  if (!aRoot) {
    390    return BrowsingContextOrigin::Different;
    391  }
    392  return aTarget.OwnerDoc()->GetDocGroup() == aRoot->OwnerDoc()->GetDocGroup()
    393             ? BrowsingContextOrigin::Similar
    394             : BrowsingContextOrigin::Different;
    395 }
    396 
    397 // NOTE: This returns nullptr if |aDocument| is in another process from the top
    398 // level content document.
    399 static const Document* GetTopLevelContentDocumentInThisProcess(
    400    const Document& aDocument) {
    401  auto* wc = aDocument.GetTopLevelWindowContext();
    402  return wc ? wc->GetExtantDoc() : nullptr;
    403 }
    404 
    405 static nsMargin ResolveMargin(const IntersectionObserverMargin& aMargin,
    406                              const nsSize& aPercentBasis) {
    407  nsMargin margin;
    408  for (const auto side : mozilla::AllPhysicalSides()) {
    409    nscoord basis = side == eSideTop || side == eSideBottom
    410                        ? aPercentBasis.Height()
    411                        : aPercentBasis.Width();
    412    margin.Side(side) = aMargin.Get(side).Resolve(
    413        basis, static_cast<nscoord (*)(float)>(NSToCoordRoundWithClamp));
    414  }
    415  return margin;
    416 }
    417 
    418 // https://w3c.github.io/IntersectionObserver/#compute-the-intersection
    419 //
    420 // TODO(emilio): Proof of this being equivalent to the spec welcome, seems
    421 // reasonably close.
    422 //
    423 // Also, it's unclear to me why the spec talks about browsing context while
    424 // discarding observations of targets of different documents.
    425 //
    426 // Both aRootBounds and the return value are relative to
    427 // nsLayoutUtils::GetContainingBlockForClientRect(aRoot).
    428 //
    429 // In case of out-of-process document, aRemoteDocumentVisibleRect is a rectangle
    430 // in the out-of-process document's coordinate system.
    431 static Maybe<nsRect> ComputeTheIntersection(
    432    nsIFrame* aTarget, const nsRect& aTargetRectRelativeToTarget,
    433    nsIFrame* aRoot, const nsRect& aRootBounds,
    434    const IntersectionObserverMargin& aScrollMargin,
    435    const Maybe<nsRect>& aRemoteDocumentVisibleRect,
    436    DOMIntersectionObserver::IsForProximityToViewport aIsForProximityToViewport,
    437    bool* aPreservesAxisAlignedRectangles) {
    438  nsIFrame* target = aTarget;
    439  // 1. Let intersectionRect be the result of running the
    440  // getBoundingClientRect() algorithm on the target.
    441  //
    442  // `intersectionRect` is kept relative to `target` during the loop.
    443  auto inflowRect = aTargetRectRelativeToTarget;
    444  Maybe<nsRect> intersectionRect = Some(inflowRect);
    445 
    446  // 2. Let container be the containing block of the target.
    447  // (We go through the parent chain and only look at scroll frames)
    448  //
    449  // FIXME(emilio): Spec uses containing blocks, we use scroll frames, but we
    450  // only apply overflow-clipping, not clip-path, so it's ~fine. We do need to
    451  // apply clip-path.
    452  //
    453  // 3. While container is not the intersection root:
    454  nsIFrame* containerFrame =
    455      nsLayoutUtils::GetCrossDocParentFrameInProcess(target);
    456  while (containerFrame && containerFrame != aRoot) {
    457    // FIXME(emilio): What about other scroll frames that inherit from
    458    // ScrollContainerFrame but have a different type, like nsListControlFrame?
    459    // This looks bogus in that case, but different bug.
    460    if (ScrollContainerFrame* scrollContainerFrame =
    461            do_QueryFrame(containerFrame)) {
    462      if (containerFrame->GetParent() == aRoot && !aRoot->GetParent()) {
    463        // This is subtle: if we're computing the intersection against the
    464        // viewport (the root frame), and this is its scroll frame, we really
    465        // want to skip this intersection (because we want to account for the
    466        // root margin, which is already in aRootBounds).
    467        break;
    468      }
    469      nsRect subFrameRect =
    470          scrollContainerFrame->GetScrollPortRectAccountingForDynamicToolbar();
    471 
    472      // 3.1 If container is the document of a nested browsing context, update
    473      // intersectionRect by clipping to the viewport of the document, and
    474      // update container to be the browsing context container of container.
    475      // XXX: Handled below by aRemoteDocumentVisibleRect & walking
    476      // CrossDocParentFrame.
    477 
    478      // 3.2 Map intersectionRect to the coordinate space of container.
    479      bool preservesAxisAlignedRectangles = false;
    480      nsRect intersectionRectRelativeToContainer =
    481          nsLayoutUtils::TransformFrameRectToAncestor(
    482              target, intersectionRect.value(), containerFrame,
    483              &preservesAxisAlignedRectangles);
    484      if (aPreservesAxisAlignedRectangles) {
    485        *aPreservesAxisAlignedRectangles |= preservesAxisAlignedRectangles;
    486      }
    487 
    488      // 3.3 If container is a scroll container, apply the
    489      // IntersectionObserver’s [[scrollMargin]] to the container’s clip rect as
    490      // described in apply scroll margin to a scrollport.
    491      subFrameRect.Inflate(ResolveMargin(aScrollMargin, subFrameRect.Size()));
    492 
    493      intersectionRect =
    494          intersectionRectRelativeToContainer.EdgeInclusiveIntersection(
    495              subFrameRect);
    496      if (!intersectionRect) {
    497        return Nothing();
    498      }
    499      target = containerFrame;
    500    } else {
    501      const auto& disp = *containerFrame->StyleDisplay();
    502      auto clipAxes = containerFrame->ShouldApplyOverflowClipping(&disp);
    503      // 3.4 If container has a content clip or a css clip-path property, update
    504      // intersectionRect by applying container’s clip.
    505 
    506      // 3.4 TODO: Apply clip-path.
    507      if (!clipAxes.isEmpty()) {
    508        // 3.2 Map intersectionRect to the coordinate space of container.
    509        bool preservesAxisAlignedRectangles = false;
    510        const nsRect intersectionRectRelativeToContainer =
    511            nsLayoutUtils::TransformFrameRectToAncestor(
    512                target, intersectionRect.value(), containerFrame,
    513                &preservesAxisAlignedRectangles);
    514        if (aPreservesAxisAlignedRectangles) {
    515          *aPreservesAxisAlignedRectangles |= preservesAxisAlignedRectangles;
    516        }
    517        const nsRect clipRect = OverflowAreas::GetOverflowClipRect(
    518            intersectionRectRelativeToContainer,
    519            containerFrame->GetRectRelativeToSelf(), clipAxes,
    520            containerFrame->OverflowClipMargin(clipAxes));
    521        intersectionRect =
    522            intersectionRectRelativeToContainer.EdgeInclusiveIntersection(
    523                clipRect);
    524        if (!intersectionRect) {
    525          return Nothing();
    526        }
    527        target = containerFrame;
    528      }
    529    }
    530    // 3.5 If container is the root element of a browsing context, update
    531    // container to be the browsing context’s document; otherwise, update
    532    // container to be the containing block of container.
    533    containerFrame =
    534        nsLayoutUtils::GetCrossDocParentFrameInProcess(containerFrame);
    535  }
    536  MOZ_ASSERT(intersectionRect);
    537 
    538  // 4. Map intersectionRect to the coordinate space of the intersection root.
    539  bool preservesAxisAlignedRectangles = false;
    540  nsRect intersectionRectRelativeToRoot =
    541      nsLayoutUtils::TransformFrameRectToAncestor(
    542          target, intersectionRect.value(),
    543          nsLayoutUtils::GetContainingBlockForClientRect(aRoot),
    544          &preservesAxisAlignedRectangles);
    545  if (aPreservesAxisAlignedRectangles) {
    546    *aPreservesAxisAlignedRectangles |= preservesAxisAlignedRectangles;
    547  }
    548 
    549  // 5.Update intersectionRect by intersecting it with the root intersection
    550  // rectangle.
    551  //
    552  // In out-of-process iframes we need to take an intersection with the remote
    553  // document visible rect which was already clipped by ancestor document's
    554  // viewports.
    555  if (aRemoteDocumentVisibleRect) {
    556    MOZ_ASSERT(aRoot->PresContext()->IsRootContentDocumentInProcess() &&
    557               !aRoot->PresContext()->IsRootContentDocumentCrossProcess());
    558 
    559    intersectionRect = intersectionRectRelativeToRoot.EdgeInclusiveIntersection(
    560        *aRemoteDocumentVisibleRect);
    561  } else if (aTarget->HasAnyStateBits(NS_FRAME_IN_POPUP)) {
    562    // Popups don't get clipped to the viewport, so avoid applying the root
    563    // intersection rect, see bug 1991410.
    564    intersectionRect = Some(intersectionRectRelativeToRoot);
    565  } else {
    566    intersectionRect =
    567        intersectionRectRelativeToRoot.EdgeInclusiveIntersection(aRootBounds);
    568  }
    569 
    570  if (intersectionRect.isNothing()) {
    571    return Nothing();
    572  }
    573  // 6. Map intersectionRect to the coordinate space of the viewport of the
    574  // Document containing the target.
    575  //
    576  // FIXME(emilio): I think this may not be correct if the root is explicit
    577  // and in the same document, since then the rectangle may not be relative to
    578  // the viewport already (but it's in the same document).
    579  nsRect rect = intersectionRect.value();
    580  if (aTarget->PresContext() != aRoot->PresContext()) {
    581    if (nsIFrame* rootScrollContainerFrame =
    582            aTarget->PresShell()->GetRootScrollContainerFrame()) {
    583      nsLayoutUtils::TransformRect(aRoot, rootScrollContainerFrame, rect);
    584    }
    585  }
    586 
    587  // 7. Return intersectionRect.
    588  return Some(rect);
    589 }
    590 
    591 struct OopIframeMetrics {
    592  nsIFrame* mInProcessRootFrame = nullptr;
    593  nsRect mInProcessRootRect;
    594  nsRect mRemoteDocumentVisibleRect;
    595 };
    596 
    597 static Maybe<OopIframeMetrics> GetOopIframeMetrics(
    598    const Document& aDocument, const Document* aRootDocument) {
    599  const Document* rootDoc =
    600      nsContentUtils::GetInProcessSubtreeRootDocument(&aDocument);
    601  MOZ_ASSERT(rootDoc);
    602 
    603  if (rootDoc->IsTopLevelContentDocument()) {
    604    return Nothing();
    605  }
    606 
    607  if (aRootDocument &&
    608      rootDoc ==
    609          nsContentUtils::GetInProcessSubtreeRootDocument(aRootDocument)) {
    610    // aRootDoc, if non-null, is either the implicit root
    611    // (top-level-content-document) or a same-origin document passed explicitly.
    612    //
    613    // In the former case, we should've returned above if there are no iframes
    614    // in between. This condition handles the explicit, same-origin root
    615    // document, when both are embedded in an OOP iframe.
    616    return Nothing();
    617  }
    618 
    619  PresShell* rootPresShell = rootDoc->GetPresShell();
    620  if (!rootPresShell || rootPresShell->IsDestroying()) {
    621    return Some(OopIframeMetrics{});
    622  }
    623 
    624  nsIFrame* inProcessRootFrame = rootPresShell->GetRootFrame();
    625  if (!inProcessRootFrame) {
    626    return Some(OopIframeMetrics{});
    627  }
    628 
    629  BrowserChild* browserChild = BrowserChild::GetFrom(rootDoc->GetDocShell());
    630  if (!browserChild) {
    631    return Some(OopIframeMetrics{});
    632  }
    633 
    634  if (MOZ_UNLIKELY(NS_WARN_IF(browserChild->IsTopLevel()))) {
    635    // FIXME(bug 1772083): This can be hit with popups, e.g. in
    636    // html/browsers/the-window-object/open-close/no_window_open_when_term_nesting_level_nonzero.window.html
    637    // temporarily while opening a new popup (on the about:blank doc).
    638    // MOZ_ASSERT_UNREACHABLE("Top level BrowserChild but non-top level doc?");
    639    return Nothing();
    640  }
    641 
    642  nsRect inProcessRootRect;
    643  if (ScrollContainerFrame* rootScrollContainerFrame =
    644          rootPresShell->GetRootScrollContainerFrame()) {
    645    inProcessRootRect = rootScrollContainerFrame
    646                            ->GetScrollPortRectAccountingForDynamicToolbar();
    647  }
    648 
    649  Maybe<LayoutDeviceRect> remoteDocumentVisibleRect =
    650      browserChild->GetTopLevelViewportVisibleRectInSelfCoords();
    651  if (!remoteDocumentVisibleRect) {
    652    return Some(OopIframeMetrics{});
    653  }
    654 
    655  return Some(OopIframeMetrics{
    656      inProcessRootFrame,
    657      inProcessRootRect,
    658      LayoutDeviceRect::ToAppUnits(
    659          *remoteDocumentVisibleRect,
    660          rootPresShell->GetPresContext()->AppUnitsPerDevPixel()),
    661  });
    662 }
    663 
    664 IntersectionInput DOMIntersectionObserver::ComputeInputForIframeThrottling(
    665    const Document& aEmbedderDocument) {
    666  auto margin = LazyLoadingMargin();
    667  // TODO: Consider not using exactly the same parameters as lazy-load?
    668  const bool useScroll = StaticPrefs::dom_lazy_loading_margin_is_scroll();
    669  return ComputeInput(aEmbedderDocument, /* aRoot = */ nullptr,
    670                      /* aRootMargin = */ useScroll ? nullptr : &margin,
    671                      /* aScrollMargin = */ useScroll ? &margin : nullptr);
    672 }
    673 
    674 // https://w3c.github.io/IntersectionObserver/#update-intersection-observations-algo
    675 // step 2.1
    676 IntersectionInput DOMIntersectionObserver::ComputeInput(
    677    const Document& aDocument, const nsINode* aRoot,
    678    const IntersectionObserverMargin* aRootMargin,
    679    const IntersectionObserverMargin* aScrollMargin) {
    680  // 1 - Let rootBounds be observer's root intersection rectangle.
    681  //  ... but since the intersection rectangle depends on the target, we defer
    682  //      the inflation until later.
    683  // NOTE: |rootRect| and |rootFrame| will be root in the same process. In
    684  // out-of-process iframes, they are NOT root ones of the top level content
    685  // document.
    686  nsRect rootRect;
    687  nsIFrame* rootFrame = nullptr;
    688  const nsINode* root = aRoot;
    689  const bool isImplicitRoot = !aRoot;
    690  Maybe<nsRect> remoteDocumentVisibleRect;
    691  if (aRoot && aRoot->IsElement()) {
    692    if ((rootFrame = aRoot->AsElement()->GetPrimaryFrame())) {
    693      nsRect rootRectRelativeToRootFrame;
    694      if (ScrollContainerFrame* scrollContainerFrame =
    695              do_QueryFrame(rootFrame)) {
    696        // rootRectRelativeToRootFrame should be the content rect of rootFrame,
    697        // not including the scrollbars.
    698        rootRectRelativeToRootFrame =
    699            scrollContainerFrame
    700                ->GetScrollPortRectAccountingForDynamicToolbar();
    701      } else {
    702        // rootRectRelativeToRootFrame should be the border rect of rootFrame.
    703        rootRectRelativeToRootFrame = rootFrame->GetRectRelativeToSelf();
    704      }
    705      nsIFrame* containingBlock =
    706          nsLayoutUtils::GetContainingBlockForClientRect(rootFrame);
    707      rootRect = nsLayoutUtils::TransformFrameRectToAncestor(
    708          rootFrame, rootRectRelativeToRootFrame, containingBlock);
    709    }
    710  } else {
    711    MOZ_ASSERT(!aRoot || aRoot->IsDocument());
    712    const Document* rootDocument =
    713        aRoot ? aRoot->AsDocument()
    714              : GetTopLevelContentDocumentInThisProcess(aDocument);
    715    root = rootDocument;
    716 
    717    if (rootDocument) {
    718      // We're in the same process as the root document, though note that there
    719      // could be an out-of-process iframe in between us and the root. Grab the
    720      // root frame and the root rect.
    721      //
    722      // Note that the root rect is always good (we assume no DPI changes in
    723      // between the two documents, and we don't need to convert coordinates).
    724      //
    725      // The root frame however we may need to tweak in the block below, if
    726      // there's any OOP iframe in between `rootDocument` and `aDocument`, to
    727      // handle the OOP iframe positions.
    728      if (PresShell* presShell = rootDocument->GetPresShell()) {
    729        rootFrame = presShell->GetRootFrame();
    730        // We use the root scroll container frame's scroll port to account the
    731        // scrollbars in rootRect, if needed.
    732        if (ScrollContainerFrame* rootScrollContainerFrame =
    733                presShell->GetRootScrollContainerFrame()) {
    734          rootRect = rootScrollContainerFrame
    735                         ->GetScrollPortRectAccountingForDynamicToolbar();
    736        } else if (rootFrame) {
    737          rootRect = rootFrame->GetRectRelativeToSelf();
    738        }
    739      }
    740    }
    741 
    742    if (Maybe<OopIframeMetrics> metrics =
    743            GetOopIframeMetrics(aDocument, rootDocument)) {
    744      rootFrame = metrics->mInProcessRootFrame;
    745      if (!rootDocument) {
    746        rootRect = metrics->mInProcessRootRect;
    747      }
    748      remoteDocumentVisibleRect = Some(metrics->mRemoteDocumentVisibleRect);
    749    }
    750  }
    751 
    752  nsMargin rootMargin;  // This root margin is NOT applied in `implicit root`
    753                        // case, e.g. in out-of-process iframes.
    754  if (aRootMargin) {
    755    rootMargin = ResolveMargin(*aRootMargin, rootRect.Size());
    756  }
    757 
    758  return {isImplicitRoot,
    759          root,
    760          rootFrame,
    761          rootRect,
    762          rootMargin,
    763          aScrollMargin ? *aScrollMargin : IntersectionObserverMargin(),
    764          remoteDocumentVisibleRect};
    765 }
    766 
    767 IntersectionOutput DOMIntersectionObserver::Intersect(
    768    const IntersectionInput& aInput, const Element& aTarget, BoxToUse aBoxToUse,
    769    IsForProximityToViewport aIsForProximityToViewport) {
    770  nsIFrame* targetFrame = aTarget.GetPrimaryFrame();
    771  if (!targetFrame) {
    772    return {SimilarOrigin(aTarget, aInput.mRootNode) ==
    773            BrowsingContextOrigin::Similar};
    774  }
    775  return Intersect(aInput, targetFrame, aBoxToUse, aIsForProximityToViewport);
    776 }
    777 
    778 // https://w3c.github.io/IntersectionObserver/#update-intersection-observations-algo
    779 // (steps 2.1 - 2.5)
    780 IntersectionOutput DOMIntersectionObserver::Intersect(
    781    const IntersectionInput& aInput, nsIFrame* aTargetFrame, BoxToUse aBoxToUse,
    782    IsForProximityToViewport aIsForProximityToViewport) {
    783  MOZ_ASSERT(aTargetFrame);
    784 
    785  const nsIContent* target = aTargetFrame->GetContent();
    786  const bool isSimilarOrigin =
    787      target && SimilarOrigin(*target, aInput.mRootNode) ==
    788                    BrowsingContextOrigin::Similar;
    789  if (!aInput.mRootFrame) {
    790    return {isSimilarOrigin};
    791  }
    792 
    793  // "From the perspective of an IntersectionObserver, the skipped contents
    794  // of an element are never intersecting the intersection root. This is
    795  // true even if both the root and the target elements are in the skipped
    796  // contents."
    797  // https://drafts.csswg.org/css-contain/#cv-notes
    798  //
    799  // Skip the intersection if the element is hidden, unless this is the
    800  // specifically to determine the proximity to the viewport for
    801  // `content-visibility: auto` elements.
    802  if (aIsForProximityToViewport == IsForProximityToViewport::No &&
    803      aTargetFrame->IsHiddenByContentVisibilityOnAnyAncestor()) {
    804    return {isSimilarOrigin};
    805  }
    806 
    807  // 2.2. If the intersection root is not the implicit root, and target is
    808  // not in the same Document as the intersection root, skip to step 11.
    809  if (!aInput.mIsImplicitRoot &&
    810      aInput.mRootNode->OwnerDoc() != target->OwnerDoc()) {
    811    return {isSimilarOrigin};
    812  }
    813 
    814  // 2.3. If the intersection root is an element and target is not a descendant
    815  // of the intersection root in the containing block chain, skip to step 11.
    816  //
    817  // NOTE(emilio): We also do this if target is the implicit root, pending
    818  // clarification in
    819  // https://github.com/w3c/IntersectionObserver/issues/456.
    820  if (aInput.mRootFrame == aTargetFrame ||
    821      !nsLayoutUtils::IsAncestorFrameCrossDocInProcess(aInput.mRootFrame,
    822                                                       aTargetFrame)) {
    823    return {isSimilarOrigin};
    824  }
    825 
    826  nsRect rootBounds = aInput.mRootRect;
    827  if (isSimilarOrigin) {
    828    rootBounds.Inflate(aInput.mRootMargin);
    829 
    830    // Implicit roots should apply the scrollMargin as well:
    831    if (aInput.mIsImplicitRoot) {
    832      rootBounds.Inflate(
    833          ResolveMargin(aInput.mScrollMargin, aInput.mRootRect.Size()));
    834    }
    835  }
    836 
    837  // 2.4. Set targetRect to the DOMRectReadOnly obtained by running the
    838  // getBoundingClientRect() algorithm on target. We compute the box relative to
    839  // self first, then transform.
    840  nsLayoutUtils::GetAllInFlowRectsFlags flags{
    841      nsLayoutUtils::GetAllInFlowRectsFlag::AccountForTransforms};
    842  if (aBoxToUse == BoxToUse::Content) {
    843    flags += nsLayoutUtils::GetAllInFlowRectsFlag::UseContentBox;
    844  }
    845  nsRect targetRectRelativeToTarget =
    846      nsLayoutUtils::GetAllInFlowRectsUnion(aTargetFrame, aTargetFrame, flags);
    847 
    848  if (aBoxToUse == BoxToUse::OverflowClip) {
    849    const auto& disp = *aTargetFrame->StyleDisplay();
    850    auto clipAxes = aTargetFrame->ShouldApplyOverflowClipping(&disp);
    851    if (!clipAxes.isEmpty()) {
    852      targetRectRelativeToTarget = OverflowAreas::GetOverflowClipRect(
    853          targetRectRelativeToTarget, targetRectRelativeToTarget, clipAxes,
    854          aTargetFrame->OverflowClipMargin(clipAxes,
    855                                           /* aAllowNegative = */ false));
    856    }
    857  }
    858 
    859  auto targetRect = nsLayoutUtils::TransformFrameRectToAncestor(
    860      aTargetFrame, targetRectRelativeToTarget,
    861      nsLayoutUtils::GetContainingBlockForClientRect(aTargetFrame));
    862 
    863  // For content-visibility, we need to observe the overflow clip edge,
    864  // https://drafts.csswg.org/css-contain-2/#close-to-the-viewport
    865  MOZ_ASSERT_IF(aIsForProximityToViewport == IsForProximityToViewport::Yes,
    866                aBoxToUse == BoxToUse::OverflowClip);
    867 
    868  // 2.5. Let intersectionRect be the result of running the compute the
    869  // intersection algorithm on target and observer’s intersection root.
    870  bool preservesAxisAlignedRectangles = false;
    871  Maybe<nsRect> intersectionRect = ComputeTheIntersection(
    872      aTargetFrame, targetRectRelativeToTarget, aInput.mRootFrame, rootBounds,
    873      aInput.mScrollMargin, aInput.mRemoteDocumentVisibleRect,
    874      aIsForProximityToViewport, &preservesAxisAlignedRectangles);
    875 
    876  return {isSimilarOrigin, rootBounds, targetRect, intersectionRect,
    877          preservesAxisAlignedRectangles};
    878 }
    879 
    880 IntersectionOutput DOMIntersectionObserver::Intersect(
    881    const IntersectionInput& aInput, const nsRect& aTargetRect) {
    882  nsRect rootBounds = aInput.mRootRect;
    883  rootBounds.Inflate(aInput.mRootMargin);
    884  auto intersectionRect =
    885      aInput.mRootRect.EdgeInclusiveIntersection(aTargetRect);
    886  if (intersectionRect && aInput.mRemoteDocumentVisibleRect) {
    887    intersectionRect = intersectionRect->EdgeInclusiveIntersection(
    888        *aInput.mRemoteDocumentVisibleRect);
    889  }
    890  return {true, rootBounds, aTargetRect, intersectionRect,
    891          /* mPreserverAxisAlignedRectangles= */ false};
    892 }
    893 
    894 // https://w3c.github.io/IntersectionObserver/#update-intersection-observations-algo
    895 // (step 2)
    896 void DOMIntersectionObserver::Update(Document& aDocument,
    897                                     DOMHighResTimeStamp time) {
    898  auto input = ComputeInput(aDocument, mRoot, &mRootMargin, &mScrollMargin);
    899 
    900  // 2. For each target in observer’s internal [[ObservationTargets]] slot,
    901  // processed in the same order that observe() was called on each target:
    902  for (Element* target : mObservationTargets) {
    903    // 2.1 - 2.4.
    904    IntersectionOutput output = Intersect(input, *target);
    905 
    906    // 2.5. Let targetArea be targetRect’s area.
    907    int64_t targetArea = (int64_t)output.mTargetRect.Width() *
    908                         (int64_t)output.mTargetRect.Height();
    909 
    910    // 2.6. Let intersectionArea be intersectionRect’s area.
    911    int64_t intersectionArea =
    912        !output.mIntersectionRect
    913            ? 0
    914            : (int64_t)output.mIntersectionRect->Width() *
    915                  (int64_t)output.mIntersectionRect->Height();
    916 
    917    // 2.7. Let isIntersecting be true if targetRect and rootBounds intersect or
    918    // are edge-adjacent, even if the intersection has zero area (because
    919    // rootBounds or targetRect have zero area); otherwise, let isIntersecting
    920    // be false.
    921    const bool isIntersecting = output.Intersects();
    922 
    923    // 2.8. If targetArea is non-zero, let intersectionRatio be intersectionArea
    924    // divided by targetArea. Otherwise, let intersectionRatio be 1 if
    925    // isIntersecting is true, or 0 if isIntersecting is false.
    926    double intersectionRatio;
    927    if (targetArea > 0.0) {
    928      intersectionRatio =
    929          std::min((double)intersectionArea / (double)targetArea, 1.0);
    930    } else {
    931      intersectionRatio = isIntersecting ? 1.0 : 0.0;
    932    }
    933 
    934    // 2.9 Let thresholdIndex be the index of the first entry in
    935    // observer.thresholds whose value is greater than intersectionRatio, or the
    936    // length of observer.thresholds if intersectionRatio is greater than or
    937    // equal to the last entry in observer.thresholds.
    938    int32_t thresholdIndex = -1;
    939 
    940    // If not intersecting, we can just shortcut, as we know that the thresholds
    941    // are always between 0 and 1.
    942    if (isIntersecting) {
    943      thresholdIndex = mThresholds.IndexOfFirstElementGt(intersectionRatio);
    944      if (thresholdIndex == 0) {
    945        // Per the spec, we should leave threshold at 0 and distinguish between
    946        // "less than all thresholds and intersecting" and "not intersecting"
    947        // (queuing observer entries as both cases come to pass). However,
    948        // neither Chrome nor the WPT tests expect this behavior, so treat these
    949        // two cases as one.
    950        //
    951        // See https://github.com/w3c/IntersectionObserver/issues/432 about
    952        // this.
    953        thresholdIndex = -1;
    954      }
    955    }
    956 
    957    // Steps 2.10 - 2.15.
    958    bool updated = false;
    959    if (auto entry = mObservationTargetMap.Lookup(target)) {
    960      updated = entry.Data() != thresholdIndex;
    961      entry.Data() = thresholdIndex;
    962    } else {
    963      MOZ_ASSERT_UNREACHABLE("Target not properly registered?");
    964    }
    965 
    966    if (updated) {
    967      // See https://github.com/w3c/IntersectionObserver/issues/432 about
    968      // why we use thresholdIndex > 0 rather than isIntersecting for the
    969      // entry's isIntersecting value.
    970      QueueIntersectionObserverEntry(
    971          target, time,
    972          output.mIsSimilarOrigin ? Some(output.mRootBounds) : Nothing(),
    973          output.mTargetRect, output.mIntersectionRect, thresholdIndex > 0,
    974          intersectionRatio);
    975    }
    976  }
    977 }
    978 
    979 void DOMIntersectionObserver::QueueIntersectionObserverEntry(
    980    Element* aTarget, DOMHighResTimeStamp time, const Maybe<nsRect>& aRootRect,
    981    const nsRect& aTargetRect, const Maybe<nsRect>& aIntersectionRect,
    982    bool aIsIntersecting, double aIntersectionRatio) {
    983  RefPtr<DOMRect> rootBounds;
    984  if (aRootRect.isSome()) {
    985    rootBounds = new DOMRect(mOwner);
    986    rootBounds->SetLayoutRect(aRootRect.value());
    987  }
    988  RefPtr<DOMRect> boundingClientRect = new DOMRect(mOwner);
    989  boundingClientRect->SetLayoutRect(aTargetRect);
    990  RefPtr<DOMRect> intersectionRect = new DOMRect(mOwner);
    991  if (aIntersectionRect.isSome()) {
    992    intersectionRect->SetLayoutRect(aIntersectionRect.value());
    993  }
    994  RefPtr<DOMIntersectionObserverEntry> entry = new DOMIntersectionObserverEntry(
    995      mOwner, time, rootBounds.forget(), boundingClientRect.forget(),
    996      intersectionRect.forget(), aIsIntersecting, aTarget, aIntersectionRatio);
    997  mQueuedEntries.AppendElement(entry.forget());
    998 }
    999 
   1000 void DOMIntersectionObserver::Notify() {
   1001  if (!mQueuedEntries.Length()) {
   1002    return;
   1003  }
   1004  Sequence<OwningNonNull<DOMIntersectionObserverEntry>> entries;
   1005  if (entries.SetCapacity(mQueuedEntries.Length(), mozilla::fallible)) {
   1006    for (size_t i = 0; i < mQueuedEntries.Length(); ++i) {
   1007      RefPtr<DOMIntersectionObserverEntry> next = mQueuedEntries[i];
   1008      *entries.AppendElement(mozilla::fallible) = next;
   1009    }
   1010  }
   1011  mQueuedEntries.Clear();
   1012 
   1013  if (mCallback.is<RefPtr<dom::IntersectionCallback>>()) {
   1014    RefPtr<dom::IntersectionCallback> callback(
   1015        mCallback.as<RefPtr<dom::IntersectionCallback>>());
   1016    callback->Call(this, entries, *this);
   1017  } else {
   1018    mCallback.as<NativeCallback>()(entries);
   1019  }
   1020 }
   1021 
   1022 }  // namespace mozilla::dom