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/. */
7 #include "DOMIntersectionObserver.h"
8 #include "nsCSSPropertyID.h"
10 #include "nsContainerFrame.h"
11 #include "nsIScrollableFrame.h"
12 #include "nsContentUtils.h"
13 #include "nsLayoutUtils.h"
14 #include "nsRefreshDriver.h"
15 #include "mozilla/PresShell.h"
16 #include "mozilla/StaticPrefs_dom.h"
17 #include "mozilla/ServoBindings.h"
18 #include "mozilla/StaticPrefs_dom.h"
19 #include "mozilla/dom/BrowserChild.h"
20 #include "mozilla/dom/BrowsingContext.h"
21 #include "mozilla/dom/DocumentInlines.h"
22 #include "mozilla/dom/HTMLImageElement.h"
25 namespace mozilla::dom
{
27 NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(DOMIntersectionObserverEntry
)
28 NS_WRAPPERCACHE_INTERFACE_MAP_ENTRY
29 NS_INTERFACE_MAP_ENTRY(nsISupports
)
32 NS_IMPL_CYCLE_COLLECTING_ADDREF(DOMIntersectionObserverEntry
)
33 NS_IMPL_CYCLE_COLLECTING_RELEASE(DOMIntersectionObserverEntry
)
35 NS_IMPL_CYCLE_COLLECTION_WRAPPERCACHE(DOMIntersectionObserverEntry
, mOwner
,
36 mRootBounds
, mBoundingClientRect
,
37 mIntersectionRect
, mTarget
)
39 NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(DOMIntersectionObserver
)
40 NS_WRAPPERCACHE_INTERFACE_MAP_ENTRY
41 NS_INTERFACE_MAP_ENTRY(nsISupports
)
42 NS_INTERFACE_MAP_ENTRY(DOMIntersectionObserver
)
45 NS_IMPL_CYCLE_COLLECTING_ADDREF(DOMIntersectionObserver
)
46 NS_IMPL_CYCLE_COLLECTING_RELEASE(DOMIntersectionObserver
)
48 NS_IMPL_CYCLE_COLLECTION_CLASS(DOMIntersectionObserver
)
50 NS_IMPL_CYCLE_COLLECTION_TRACE_BEGIN(DOMIntersectionObserver
)
51 NS_IMPL_CYCLE_COLLECTION_TRACE_PRESERVED_WRAPPER
52 NS_IMPL_CYCLE_COLLECTION_TRACE_END
54 NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN(DOMIntersectionObserver
)
55 NS_IMPL_CYCLE_COLLECTION_UNLINK_PRESERVED_WRAPPER
57 NS_IMPL_CYCLE_COLLECTION_UNLINK(mOwner
)
58 NS_IMPL_CYCLE_COLLECTION_UNLINK(mDocument
)
59 if (tmp
->mCallback
.is
<RefPtr
<dom::IntersectionCallback
>>()) {
60 ImplCycleCollectionUnlink(
61 tmp
->mCallback
.as
<RefPtr
<dom::IntersectionCallback
>>());
63 NS_IMPL_CYCLE_COLLECTION_UNLINK(mRoot
)
64 NS_IMPL_CYCLE_COLLECTION_UNLINK(mQueuedEntries
)
65 NS_IMPL_CYCLE_COLLECTION_UNLINK_END
67 NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN(DOMIntersectionObserver
)
68 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mOwner
)
69 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mDocument
)
70 if (tmp
->mCallback
.is
<RefPtr
<dom::IntersectionCallback
>>()) {
71 ImplCycleCollectionTraverse(
72 cb
, tmp
->mCallback
.as
<RefPtr
<dom::IntersectionCallback
>>(), "mCallback",
75 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mRoot
)
76 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mQueuedEntries
)
77 NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END
79 DOMIntersectionObserver::DOMIntersectionObserver(
80 already_AddRefed
<nsPIDOMWindowInner
>&& aOwner
,
81 dom::IntersectionCallback
& aCb
)
83 mDocument(mOwner
->GetExtantDoc()),
84 mCallback(RefPtr
<dom::IntersectionCallback
>(&aCb
)),
87 already_AddRefed
<DOMIntersectionObserver
> DOMIntersectionObserver::Constructor(
88 const GlobalObject
& aGlobal
, dom::IntersectionCallback
& aCb
,
90 return Constructor(aGlobal
, aCb
, IntersectionObserverInit(), aRv
);
93 already_AddRefed
<DOMIntersectionObserver
> DOMIntersectionObserver::Constructor(
94 const GlobalObject
& aGlobal
, dom::IntersectionCallback
& aCb
,
95 const IntersectionObserverInit
& aOptions
, ErrorResult
& aRv
) {
96 nsCOMPtr
<nsPIDOMWindowInner
> window
=
97 do_QueryInterface(aGlobal
.GetAsSupports());
99 aRv
.Throw(NS_ERROR_FAILURE
);
102 RefPtr
<DOMIntersectionObserver
> observer
=
103 new DOMIntersectionObserver(window
.forget(), aCb
);
105 if (!aOptions
.mRoot
.IsNull()) {
106 if (aOptions
.mRoot
.Value().IsElement()) {
107 observer
->mRoot
= aOptions
.mRoot
.Value().GetAsElement();
109 MOZ_ASSERT(aOptions
.mRoot
.Value().IsDocument());
111 dom_IntersectionObserverExplicitDocumentRoot_enabled()) {
112 aRv
.ThrowTypeError
<dom::MSG_DOES_NOT_IMPLEMENT_INTERFACE
>(
113 "'root' member of IntersectionObserverInit", "Element");
116 observer
->mRoot
= aOptions
.mRoot
.Value().GetAsDocument();
120 if (!observer
->SetRootMargin(aOptions
.mRootMargin
)) {
121 aRv
.ThrowSyntaxError("rootMargin must be specified in pixels or percent.");
125 if (aOptions
.mThreshold
.IsDoubleSequence()) {
126 const Sequence
<double>& thresholds
=
127 aOptions
.mThreshold
.GetAsDoubleSequence();
128 observer
->mThresholds
.SetCapacity(thresholds
.Length());
129 for (const auto& thresh
: thresholds
) {
130 if (thresh
< 0.0 || thresh
> 1.0) {
131 aRv
.ThrowRangeError
<dom::MSG_THRESHOLD_RANGE_ERROR
>();
134 observer
->mThresholds
.AppendElement(thresh
);
136 observer
->mThresholds
.Sort();
138 double thresh
= aOptions
.mThreshold
.GetAsDouble();
139 if (thresh
< 0.0 || thresh
> 1.0) {
140 aRv
.ThrowRangeError
<dom::MSG_THRESHOLD_RANGE_ERROR
>();
143 observer
->mThresholds
.AppendElement(thresh
);
146 return observer
.forget();
149 static void LazyLoadCallback(
150 const Sequence
<OwningNonNull
<DOMIntersectionObserverEntry
>>& aEntries
) {
151 for (const auto& entry
: aEntries
) {
152 MOZ_ASSERT(entry
->Target()->IsHTMLElement(nsGkAtoms::img
));
153 if (entry
->IsIntersecting()) {
154 static_cast<HTMLImageElement
*>(entry
->Target())
155 ->StopLazyLoadingAndStartLoadIfNeeded(true);
160 static void LazyLoadCallbackReachViewport(
161 const Sequence
<OwningNonNull
<DOMIntersectionObserverEntry
>>& aEntries
) {
162 for (const auto& entry
: aEntries
) {
163 MOZ_ASSERT(entry
->Target()->IsHTMLElement(nsGkAtoms::img
));
164 if (entry
->IsIntersecting()) {
165 static_cast<HTMLImageElement
*>(entry
->Target())
166 ->LazyLoadImageReachedViewport();
171 static LengthPercentage
PrefMargin(float aValue
, bool aIsPercentage
) {
172 return aIsPercentage
? LengthPercentage::FromPercentage(aValue
/ 100.0f
)
173 : LengthPercentage::FromPixels(aValue
);
176 DOMIntersectionObserver::DOMIntersectionObserver(Document
& aDocument
,
177 NativeCallback aCallback
)
178 : mOwner(aDocument
.GetInnerWindow()),
179 mDocument(&aDocument
),
180 mCallback(aCallback
),
183 already_AddRefed
<DOMIntersectionObserver
>
184 DOMIntersectionObserver::CreateLazyLoadObserver(Document
& aDocument
) {
185 RefPtr
<DOMIntersectionObserver
> observer
=
186 new DOMIntersectionObserver(aDocument
, LazyLoadCallback
);
187 observer
->mThresholds
.AppendElement(std::numeric_limits
<double>::min());
189 #define SET_MARGIN(side_, side_lower_) \
190 observer->mRootMargin.Get(eSide##side_) = PrefMargin( \
191 StaticPrefs::dom_image_lazy_loading_root_margin_##side_lower_(), \
193 dom_image_lazy_loading_root_margin_##side_lower_##_percentage());
194 SET_MARGIN(Top
, top
);
195 SET_MARGIN(Right
, right
);
196 SET_MARGIN(Bottom
, bottom
);
197 SET_MARGIN(Left
, left
);
200 return observer
.forget();
203 already_AddRefed
<DOMIntersectionObserver
>
204 DOMIntersectionObserver::CreateLazyLoadObserverViewport(Document
& aDocument
) {
205 RefPtr
<DOMIntersectionObserver
> observer
=
206 new DOMIntersectionObserver(aDocument
, LazyLoadCallbackReachViewport
);
207 observer
->mThresholds
.AppendElement(std::numeric_limits
<double>::min());
208 return observer
.forget();
211 bool DOMIntersectionObserver::SetRootMargin(const nsACString
& aString
) {
212 return Servo_IntersectionObserverRootMargin_Parse(&aString
, &mRootMargin
);
215 nsISupports
* DOMIntersectionObserver::GetParentObject() const { return mOwner
; }
217 void DOMIntersectionObserver::GetRootMargin(nsACString
& aRetVal
) {
218 Servo_IntersectionObserverRootMargin_ToString(&mRootMargin
, &aRetVal
);
221 void DOMIntersectionObserver::GetThresholds(nsTArray
<double>& aRetVal
) {
222 aRetVal
= mThresholds
.Clone();
225 void DOMIntersectionObserver::Observe(Element
& aTarget
) {
226 if (mObservationTargets
.Contains(&aTarget
)) {
229 aTarget
.RegisterIntersectionObserver(this);
230 mObservationTargets
.AppendElement(&aTarget
);
233 if (nsPresContext
* pc
= mDocument
->GetPresContext()) {
234 pc
->RefreshDriver()->EnsureIntersectionObservationsUpdateHappens();
239 void DOMIntersectionObserver::Unobserve(Element
& aTarget
) {
240 if (!mObservationTargets
.Contains(&aTarget
)) {
244 if (mObservationTargets
.Length() == 1) {
249 mObservationTargets
.RemoveElement(&aTarget
);
250 aTarget
.UnregisterIntersectionObserver(this);
253 void DOMIntersectionObserver::UnlinkTarget(Element
& aTarget
) {
254 mObservationTargets
.RemoveElement(&aTarget
);
255 if (mObservationTargets
.Length() == 0) {
260 void DOMIntersectionObserver::Connect() {
267 mDocument
->AddIntersectionObserver(this);
271 void DOMIntersectionObserver::Disconnect() {
277 for (size_t i
= 0; i
< mObservationTargets
.Length(); ++i
) {
278 Element
* target
= mObservationTargets
.ElementAt(i
);
279 target
->UnregisterIntersectionObserver(this);
281 mObservationTargets
.Clear();
283 mDocument
->RemoveIntersectionObserver(this);
287 void DOMIntersectionObserver::TakeRecords(
288 nsTArray
<RefPtr
<DOMIntersectionObserverEntry
>>& aRetVal
) {
289 aRetVal
= std::move(mQueuedEntries
);
292 static Maybe
<nsRect
> EdgeInclusiveIntersection(const nsRect
& aRect
,
293 const nsRect
& aOtherRect
) {
294 nscoord left
= std::max(aRect
.x
, aOtherRect
.x
);
295 nscoord top
= std::max(aRect
.y
, aOtherRect
.y
);
296 nscoord right
= std::min(aRect
.XMost(), aOtherRect
.XMost());
297 nscoord bottom
= std::min(aRect
.YMost(), aOtherRect
.YMost());
298 if (left
> right
|| top
> bottom
) {
301 return Some(nsRect(left
, top
, right
- left
, bottom
- top
));
304 enum class BrowsingContextOrigin
{ Similar
, Different
};
306 // NOTE(emilio): Checking docgroup as per discussion in:
307 // https://github.com/w3c/IntersectionObserver/issues/161
308 static BrowsingContextOrigin
SimilarOrigin(const Element
& aTarget
,
309 const nsINode
* aRoot
) {
311 return BrowsingContextOrigin::Different
;
313 return aTarget
.OwnerDoc()->GetDocGroup() == aRoot
->OwnerDoc()->GetDocGroup()
314 ? BrowsingContextOrigin::Similar
315 : BrowsingContextOrigin::Different
;
318 // NOTE: This returns nullptr if |aDocument| is in another process from the top
319 // level content document.
320 static Document
* GetTopLevelContentDocumentInThisProcess(Document
& aDocument
) {
321 auto* wc
= aDocument
.GetTopLevelWindowContext();
322 return wc
? wc
->GetExtantDoc() : nullptr;
325 // https://w3c.github.io/IntersectionObserver/#compute-the-intersection
327 // TODO(emilio): Proof of this being equivalent to the spec welcome, seems
330 // Also, it's unclear to me why the spec talks about browsing context while
331 // discarding observations of targets of different documents.
333 // Both aRootBounds and the return value are relative to
334 // nsLayoutUtils::GetContainingBlockForClientRect(aRoot).
336 // In case of out-of-process document, aRemoteDocumentVisibleRect is a rectangle
337 // in the out-of-process document's coordinate system.
338 static Maybe
<nsRect
> ComputeTheIntersection(
339 nsIFrame
* aTarget
, nsIFrame
* aRoot
, const nsRect
& aRootBounds
,
340 const Maybe
<nsRect
>& aRemoteDocumentVisibleRect
) {
341 nsIFrame
* target
= aTarget
;
342 // 1. Let intersectionRect be the result of running the
343 // getBoundingClientRect() algorithm on the target.
345 // `intersectionRect` is kept relative to `target` during the loop.
346 Maybe
<nsRect
> intersectionRect
= Some(nsLayoutUtils::GetAllInFlowRectsUnion(
347 target
, target
, nsLayoutUtils::RECTS_ACCOUNT_FOR_TRANSFORMS
));
349 // 2. Let container be the containing block of the target.
350 // (We go through the parent chain and only look at scroll frames)
352 // FIXME(emilio): Spec uses containing blocks, we use scroll frames, but we
353 // only apply overflow-clipping, not clip-path, so it's ~fine. We do need to
356 // 3. While container is not the intersection root:
357 nsIFrame
* containerFrame
=
358 nsLayoutUtils::GetCrossDocParentFrameInProcess(target
);
359 while (containerFrame
&& containerFrame
!= aRoot
) {
360 // FIXME(emilio): What about other scroll frames that inherit from
361 // nsHTMLScrollFrame but have a different type, like nsListControlFrame?
362 // This looks bogus in that case, but different bug.
363 if (nsIScrollableFrame
* scrollFrame
= do_QueryFrame(containerFrame
)) {
364 if (containerFrame
->GetParent() == aRoot
&& !aRoot
->GetParent()) {
365 // This is subtle: if we're computing the intersection against the
366 // viewport (the root frame), and this is its scroll frame, we really
367 // want to skip this intersection (because we want to account for the
368 // root margin, which is already in aRootBounds).
371 nsRect subFrameRect
= scrollFrame
->GetScrollPortRect();
373 // 3.1 Map intersectionRect to the coordinate space of container.
374 nsRect intersectionRectRelativeToContainer
=
375 nsLayoutUtils::TransformFrameRectToAncestor(
376 target
, intersectionRect
.value(), containerFrame
);
378 // 3.2 If container has overflow clipping or a css clip-path property,
379 // update intersectionRect by applying container's clip.
381 // TODO: Apply clip-path.
383 // 3.3 is handled, looks like, by this same clipping, given the root
384 // scroll-frame cannot escape the viewport, probably?
386 intersectionRect
= EdgeInclusiveIntersection(
387 intersectionRectRelativeToContainer
, subFrameRect
);
388 if (!intersectionRect
) {
391 target
= containerFrame
;
395 nsLayoutUtils::GetCrossDocParentFrameInProcess(containerFrame
);
397 MOZ_ASSERT(intersectionRect
);
399 // 4. Map intersectionRect to the coordinate space of the intersection root.
400 nsRect intersectionRectRelativeToRoot
=
401 nsLayoutUtils::TransformFrameRectToAncestor(
402 target
, intersectionRect
.value(),
403 nsLayoutUtils::GetContainingBlockForClientRect(aRoot
));
405 // 5.Update intersectionRect by intersecting it with the root intersection
408 EdgeInclusiveIntersection(intersectionRectRelativeToRoot
, aRootBounds
);
409 if (intersectionRect
.isNothing()) {
412 // 6. Map intersectionRect to the coordinate space of the viewport of the
413 // Document containing the target.
415 // FIXME(emilio): I think this may not be correct if the root is explicit
416 // and in the same document, since then the rectangle may not be relative to
417 // the viewport already (but it's in the same document).
418 nsRect rect
= intersectionRect
.value();
419 if (aTarget
->PresContext() != aRoot
->PresContext()) {
420 if (nsIFrame
* rootScrollFrame
=
421 aTarget
->PresShell()->GetRootScrollFrame()) {
422 nsLayoutUtils::TransformRect(aRoot
, rootScrollFrame
, rect
);
426 // In out-of-process iframes we need to take an intersection with the remote
427 // document visible rect which was already clipped by ancestor document's
429 if (aRemoteDocumentVisibleRect
) {
430 MOZ_ASSERT(aRoot
->PresContext()->IsRootContentDocumentInProcess() &&
431 !aRoot
->PresContext()->IsRootContentDocumentCrossProcess());
434 EdgeInclusiveIntersection(rect
, *aRemoteDocumentVisibleRect
);
435 if (intersectionRect
.isNothing()) {
438 rect
= intersectionRect
.value();
444 struct OopIframeMetrics
{
445 nsIFrame
* mInProcessRootFrame
= nullptr;
446 nsRect mInProcessRootRect
;
447 nsRect mRemoteDocumentVisibleRect
;
450 static Maybe
<OopIframeMetrics
> GetOopIframeMetrics(Document
& aDocument
,
451 Document
* aRootDocument
) {
452 Document
* rootDoc
= nsContentUtils::GetRootDocument(&aDocument
);
455 if (rootDoc
->IsTopLevelContentDocument()) {
460 rootDoc
== nsContentUtils::GetRootDocument(aRootDocument
)) {
461 // aRootDoc, if non-null, is either the implicit root
462 // (top-level-content-document) or a same-origin document passed explicitly.
464 // In the former case, we should've returned above if there are no iframes
465 // in between. This condition handles the explicit, same-origin root
466 // document, when both are embedded in an OOP iframe.
470 PresShell
* rootPresShell
= rootDoc
->GetPresShell();
471 if (!rootPresShell
|| rootPresShell
->IsDestroying()) {
472 return Some(OopIframeMetrics
{});
475 nsIFrame
* inProcessRootFrame
= rootPresShell
->GetRootFrame();
476 if (!inProcessRootFrame
) {
477 return Some(OopIframeMetrics
{});
480 BrowserChild
* browserChild
= BrowserChild::GetFrom(rootDoc
->GetDocShell());
482 return Some(OopIframeMetrics
{});
484 MOZ_DIAGNOSTIC_ASSERT(!browserChild
->IsTopLevel());
486 nsRect inProcessRootRect
;
487 if (nsIScrollableFrame
* scrollFrame
=
488 rootPresShell
->GetRootScrollFrameAsScrollable()) {
489 inProcessRootRect
= scrollFrame
->GetScrollPortRect();
492 Maybe
<LayoutDeviceRect
> remoteDocumentVisibleRect
=
493 browserChild
->GetTopLevelViewportVisibleRectInSelfCoords();
494 if (!remoteDocumentVisibleRect
) {
495 return Some(OopIframeMetrics
{});
498 return Some(OopIframeMetrics
{
501 LayoutDeviceRect::ToAppUnits(
502 *remoteDocumentVisibleRect
,
503 rootPresShell
->GetPresContext()->AppUnitsPerDevPixel()),
507 // https://w3c.github.io/IntersectionObserver/#update-intersection-observations-algo
509 void DOMIntersectionObserver::Update(Document
* aDocument
,
510 DOMHighResTimeStamp time
) {
511 // 1 - Let rootBounds be observer's root intersection rectangle.
512 // ... but since the intersection rectangle depends on the target, we defer
513 // the inflation until later.
514 // NOTE: |rootRect| and |rootFrame| will be root in the same process. In
515 // out-of-process iframes, they are NOT root ones of the top level content
518 nsIFrame
* rootFrame
= nullptr;
519 nsINode
* root
= mRoot
;
520 Maybe
<nsRect
> remoteDocumentVisibleRect
;
521 if (mRoot
&& mRoot
->IsElement()) {
522 if ((rootFrame
= mRoot
->AsElement()->GetPrimaryFrame())) {
523 nsRect rootRectRelativeToRootFrame
;
524 if (nsIScrollableFrame
* scrollFrame
= do_QueryFrame(rootFrame
)) {
525 // rootRectRelativeToRootFrame should be the content rect of rootFrame,
526 // not including the scrollbars.
527 rootRectRelativeToRootFrame
= scrollFrame
->GetScrollPortRect();
529 // rootRectRelativeToRootFrame should be the border rect of rootFrame.
530 rootRectRelativeToRootFrame
= rootFrame
->GetRectRelativeToSelf();
532 nsIFrame
* containingBlock
=
533 nsLayoutUtils::GetContainingBlockForClientRect(rootFrame
);
534 rootRect
= nsLayoutUtils::TransformFrameRectToAncestor(
535 rootFrame
, rootRectRelativeToRootFrame
, containingBlock
);
538 MOZ_ASSERT(!mRoot
|| mRoot
->IsDocument());
539 Document
* rootDocument
=
540 mRoot
? mRoot
->AsDocument()
541 : GetTopLevelContentDocumentInThisProcess(*aDocument
);
545 // We're in the same process as the root document, though note that there
546 // could be an out-of-process iframe in between us and the root. Grab the
547 // root frame and the root rect.
549 // Note that the root rect is always good (we assume no DPI changes in
550 // between the two documents, and we don't need to convert coordinates).
552 // The root frame however we may need to tweak in the block below, if
553 // there's any OOP iframe in between `rootDocument` and `aDocument`, to
554 // handle the OOP iframe positions.
555 if (PresShell
* presShell
= rootDocument
->GetPresShell()) {
556 rootFrame
= presShell
->GetRootFrame();
557 // We use the root scrollable frame's scroll port to account the
558 // scrollbars in rootRect, if needed.
559 if (nsIScrollableFrame
* scrollFrame
=
560 presShell
->GetRootScrollFrameAsScrollable()) {
561 rootRect
= scrollFrame
->GetScrollPortRect();
566 if (Maybe
<OopIframeMetrics
> metrics
=
567 GetOopIframeMetrics(*aDocument
, rootDocument
)) {
568 rootFrame
= metrics
->mInProcessRootFrame
;
570 rootRect
= metrics
->mInProcessRootRect
;
572 remoteDocumentVisibleRect
= Some(metrics
->mRemoteDocumentVisibleRect
);
576 nsMargin rootMargin
; // This root margin is NOT applied in `implicit root`
577 // case, e.g. in out-of-process iframes.
578 for (const auto side
: mozilla::AllPhysicalSides()) {
579 nscoord basis
= side
== eSideTop
|| side
== eSideBottom
? rootRect
.Height()
581 rootMargin
.Side(side
) =
582 mRootMargin
.Get(side
).Resolve(basis
, NSToCoordRoundWithClamp
);
585 // 2. For each target in observer’s internal [[ObservationTargets]] slot,
586 // processed in the same order that observe() was called on each target:
587 for (Element
* target
: mObservationTargets
) {
588 nsIFrame
* targetFrame
= target
->GetPrimaryFrame();
589 BrowsingContextOrigin origin
= SimilarOrigin(*target
, root
);
591 Maybe
<nsRect
> intersectionRect
;
595 const bool canComputeIntersection
= [&] {
596 if (!targetFrame
|| !rootFrame
) {
600 // 2.1. If the intersection root is not the implicit root and target is
601 // not a descendant of the intersection root in the containing block
602 // chain, skip further processing for target.
604 // NOTE(emilio): We don't just "skip further processing" because that
605 // violates the invariant that there's at least one observation for a
606 // target (though that is also violated by 2.2), but it also causes
607 // different behavior when `target` is `display: none`, or not, which is
608 // really really odd, see:
609 // https://github.com/w3c/IntersectionObserver/issues/457
611 // NOTE(emilio): We also do this if target is the implicit root, pending
613 // https://github.com/w3c/IntersectionObserver/issues/456.
614 if (rootFrame
== targetFrame
||
615 !nsLayoutUtils::IsAncestorFrameCrossDoc(rootFrame
, targetFrame
)) {
619 // 2.2. If the intersection root is not the implicit root, and target is
620 // not in the same Document as the intersection root, skip further
621 // processing for target.
623 // NOTE(emilio): We don't just "skip further processing", because that
624 // doesn't match reality and other browsers, see
625 // https://github.com/w3c/IntersectionObserver/issues/457.
626 if (mRoot
&& mRoot
->OwnerDoc() != target
->OwnerDoc()) {
633 if (canComputeIntersection
) {
634 rootBounds
= rootRect
;
635 if (origin
== BrowsingContextOrigin::Similar
) {
636 rootBounds
.Inflate(rootMargin
);
639 // 2.3. Let targetRect be a DOMRectReadOnly obtained by running the
640 // getBoundingClientRect() algorithm on target.
641 targetRect
= targetFrame
->GetBoundingClientRect();
643 // 2.4. Let intersectionRect be the result of running the compute the
644 // intersection algorithm on target.
645 intersectionRect
= ComputeTheIntersection(
646 targetFrame
, rootFrame
, rootBounds
, remoteDocumentVisibleRect
);
649 // 2.5. Let targetArea be targetRect’s area.
651 (int64_t)targetRect
.Width() * (int64_t)targetRect
.Height();
652 // 2.6. Let intersectionArea be intersectionRect’s area.
653 int64_t intersectionArea
= !intersectionRect
655 : (int64_t)intersectionRect
->Width() *
656 (int64_t)intersectionRect
->Height();
658 // 2.7. Let isIntersecting be true if targetRect and rootBounds intersect or
659 // are edge-adjacent, even if the intersection has zero area (because
660 // rootBounds or targetRect have zero area); otherwise, let isIntersecting
662 const bool isIntersecting
= intersectionRect
.isSome();
664 // 2.8. If targetArea is non-zero, let intersectionRatio be intersectionArea
665 // divided by targetArea. Otherwise, let intersectionRatio be 1 if
666 // isIntersecting is true, or 0 if isIntersecting is false.
667 double intersectionRatio
;
668 if (targetArea
> 0.0) {
670 std::min((double)intersectionArea
/ (double)targetArea
, 1.0);
672 intersectionRatio
= isIntersecting
? 1.0 : 0.0;
675 // 2.9 Let thresholdIndex be the index of the first entry in
676 // observer.thresholds whose value is greater than intersectionRatio, or the
677 // length of observer.thresholds if intersectionRatio is greater than or
678 // equal to the last entry in observer.thresholds.
679 int32_t thresholdIndex
= -1;
681 // If not intersecting, we can just shortcut, as we know that the thresholds
682 // are always between 0 and 1.
683 if (isIntersecting
) {
684 thresholdIndex
= mThresholds
.IndexOfFirstElementGt(intersectionRatio
);
685 if (thresholdIndex
== 0) {
686 // Per the spec, we should leave threshold at 0 and distinguish between
687 // "less than all thresholds and intersecting" and "not intersecting"
688 // (queuing observer entries as both cases come to pass). However,
689 // neither Chrome nor the WPT tests expect this behavior, so treat these
692 // See https://github.com/w3c/IntersectionObserver/issues/432 about
698 // Steps 2.10 - 2.15.
699 if (target
->UpdateIntersectionObservation(this, thresholdIndex
)) {
700 // See https://github.com/w3c/IntersectionObserver/issues/432 about
701 // why we use thresholdIndex > 0 rather than isIntersecting for the
702 // entry's isIntersecting value.
703 QueueIntersectionObserverEntry(
705 origin
== BrowsingContextOrigin::Similar
? Some(rootBounds
)
707 targetRect
, intersectionRect
, thresholdIndex
> 0, intersectionRatio
);
712 void DOMIntersectionObserver::QueueIntersectionObserverEntry(
713 Element
* aTarget
, DOMHighResTimeStamp time
, const Maybe
<nsRect
>& aRootRect
,
714 const nsRect
& aTargetRect
, const Maybe
<nsRect
>& aIntersectionRect
,
715 bool aIsIntersecting
, double aIntersectionRatio
) {
716 RefPtr
<DOMRect
> rootBounds
;
717 if (aRootRect
.isSome()) {
718 rootBounds
= new DOMRect(this);
719 rootBounds
->SetLayoutRect(aRootRect
.value());
721 RefPtr
<DOMRect
> boundingClientRect
= new DOMRect(this);
722 boundingClientRect
->SetLayoutRect(aTargetRect
);
723 RefPtr
<DOMRect
> intersectionRect
= new DOMRect(this);
724 if (aIntersectionRect
.isSome()) {
725 intersectionRect
->SetLayoutRect(aIntersectionRect
.value());
727 RefPtr
<DOMIntersectionObserverEntry
> entry
= new DOMIntersectionObserverEntry(
728 this, time
, rootBounds
.forget(), boundingClientRect
.forget(),
729 intersectionRect
.forget(), aIsIntersecting
, aTarget
, aIntersectionRatio
);
730 mQueuedEntries
.AppendElement(entry
.forget());
733 void DOMIntersectionObserver::Notify() {
734 if (!mQueuedEntries
.Length()) {
737 Sequence
<OwningNonNull
<DOMIntersectionObserverEntry
>> entries
;
738 if (entries
.SetCapacity(mQueuedEntries
.Length(), mozilla::fallible
)) {
739 for (size_t i
= 0; i
< mQueuedEntries
.Length(); ++i
) {
740 RefPtr
<DOMIntersectionObserverEntry
> next
= mQueuedEntries
[i
];
741 *entries
.AppendElement(mozilla::fallible
) = next
;
744 mQueuedEntries
.Clear();
746 if (mCallback
.is
<RefPtr
<dom::IntersectionCallback
>>()) {
747 RefPtr
<dom::IntersectionCallback
> callback(
748 mCallback
.as
<RefPtr
<dom::IntersectionCallback
>>());
749 callback
->Call(this, entries
, *this);
751 mCallback
.as
<NativeCallback
>()(entries
);
755 } // namespace mozilla::dom