/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ /* vim: set ts=8 sts=2 et sw=2 tw=80: */ /* This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this file,
* You can obtain one at http://mozilla.org/MPL/2.0/. */
Connect(); if (mDocument) { if (nsPresContext* pc = mDocument->GetPresContext()) {
pc->RefreshDriver()->EnsureIntersectionObservationsUpdateHappens();
}
}
}
void DOMIntersectionObserver::Unobserve(Element& aTarget) { if (!mObservationTargetMap.Remove(&aTarget)) { return;
}
// NOTE: This returns nullptr if |aDocument| is in another process from the top // level content document. staticconst Document* GetTopLevelContentDocumentInThisProcess( const Document& aDocument) { auto* wc = aDocument.GetTopLevelWindowContext(); return wc ? wc->GetExtantDoc() : nullptr;
}
// https://w3c.github.io/IntersectionObserver/#compute-the-intersection // // TODO(emilio): Proof of this being equivalent to the spec welcome, seems // reasonably close. // // Also, it's unclear to me why the spec talks about browsing context while // discarding observations of targets of different documents. // // Both aRootBounds and the return value are relative to // nsLayoutUtils::GetContainingBlockForClientRect(aRoot). // // In case of out-of-process document, aRemoteDocumentVisibleRect is a rectangle // in the out-of-process document's coordinate system. static Maybe<nsRect> ComputeTheIntersection(
nsIFrame* aTarget, const nsRect& aTargetRectRelativeToTarget,
nsIFrame* aRoot, const nsRect& aRootBounds, const Maybe<nsRect>& aRemoteDocumentVisibleRect,
DOMIntersectionObserver::IsForProximityToViewport
aIsForProximityToViewport) {
nsIFrame* target = aTarget; // 1. Let intersectionRect be the result of running the // getBoundingClientRect() algorithm on the target. // // `intersectionRect` is kept relative to `target` during the loop. auto inflowRect = aTargetRectRelativeToTarget;
Maybe<nsRect> intersectionRect = Some(inflowRect);
// 2. Let container be the containing block of the target. // (We go through the parent chain and only look at scroll frames) // // FIXME(emilio): Spec uses containing blocks, we use scroll frames, but we // only apply overflow-clipping, not clip-path, so it's ~fine. We do need to // apply clip-path. // // 3. While container is not the intersection root:
nsIFrame* containerFrame =
nsLayoutUtils::GetCrossDocParentFrameInProcess(target); while (containerFrame && containerFrame != aRoot) { // FIXME(emilio): What about other scroll frames that inherit from // ScrollContainerFrame but have a different type, like nsListControlFrame? // This looks bogus in that case, but different bug. if (ScrollContainerFrame* scrollContainerFrame =
do_QueryFrame(containerFrame)) { if (containerFrame->GetParent() == aRoot && !aRoot->GetParent()) { // This is subtle: if we're computing the intersection against the // viewport (the root frame), and this is its scroll frame, we really // want to skip this intersection (because we want to account for the // root margin, which is already in aRootBounds). break;
}
nsRect subFrameRect =
scrollContainerFrame->GetScrollPortRectAccountingForDynamicToolbar();
// 3.1 Map intersectionRect to the coordinate space of container.
nsRect intersectionRectRelativeToContainer =
nsLayoutUtils::TransformFrameRectToAncestor(
target, intersectionRect.value(), containerFrame);
// 3.2 If container has overflow clipping or a css clip-path property, // update intersectionRect by applying container's clip. // // 3.3 is handled, looks like, by this same clipping, given the root // scroll-frame cannot escape the viewport, probably?
intersectionRect =
intersectionRectRelativeToContainer.EdgeInclusiveIntersection(
subFrameRect); if (!intersectionRect) { return Nothing();
}
target = containerFrame;
} else { constauto& disp = *containerFrame->StyleDisplay(); auto clipAxes = containerFrame->ShouldApplyOverflowClipping(&disp); // 3.2 TODO: Apply clip-path. if (!clipAxes.isEmpty()) { // 3.1 Map intersectionRect to the coordinate space of container. const nsRect intersectionRectRelativeToContainer =
nsLayoutUtils::TransformFrameRectToAncestor(
target, intersectionRect.value(), containerFrame); const nsRect clipRect = OverflowAreas::GetOverflowClipRect(
intersectionRectRelativeToContainer,
containerFrame->GetRectRelativeToSelf(), clipAxes,
containerFrame->OverflowClipMargin(clipAxes));
intersectionRect =
intersectionRectRelativeToContainer.EdgeInclusiveIntersection(
clipRect); if (!intersectionRect) { return Nothing();
}
target = containerFrame;
}
}
containerFrame =
nsLayoutUtils::GetCrossDocParentFrameInProcess(containerFrame);
}
MOZ_ASSERT(intersectionRect);
// 4. Map intersectionRect to the coordinate space of the intersection root.
nsRect intersectionRectRelativeToRoot =
nsLayoutUtils::TransformFrameRectToAncestor(
target, intersectionRect.value(),
nsLayoutUtils::GetContainingBlockForClientRect(aRoot));
// 5.Update intersectionRect by intersecting it with the root intersection // rectangle.
intersectionRect =
intersectionRectRelativeToRoot.EdgeInclusiveIntersection(aRootBounds); if (intersectionRect.isNothing()) { return Nothing();
} // 6. Map intersectionRect to the coordinate space of the viewport of the // Document containing the target. // // FIXME(emilio): I think this may not be correct if the root is explicit // and in the same document, since then the rectangle may not be relative to // the viewport already (but it's in the same document).
nsRect rect = intersectionRect.value(); if (aTarget->PresContext() != aRoot->PresContext()) { if (nsIFrame* rootScrollContainerFrame =
aTarget->PresShell()->GetRootScrollContainerFrame()) {
nsLayoutUtils::TransformRect(aRoot, rootScrollContainerFrame, rect);
}
}
// In out-of-process iframes we need to take an intersection with the remote // document visible rect which was already clipped by ancestor document's // viewports. if (aRemoteDocumentVisibleRect) {
MOZ_ASSERT(aRoot->PresContext()->IsRootContentDocumentInProcess() &&
!aRoot->PresContext()->IsRootContentDocumentCrossProcess());
intersectionRect =
rect.EdgeInclusiveIntersection(*aRemoteDocumentVisibleRect); if (intersectionRect.isNothing()) { return Nothing();
}
rect = intersectionRect.value();
}
if (rootDoc->IsTopLevelContentDocument()) { return Nothing();
}
if (aRootDocument &&
rootDoc ==
nsContentUtils::GetInProcessSubtreeRootDocument(aRootDocument)) { // aRootDoc, if non-null, is either the implicit root // (top-level-content-document) or a same-origin document passed explicitly. // // In the former case, we should've returned above if there are no iframes // in between. This condition handles the explicit, same-origin root // document, when both are embedded in an OOP iframe. return Nothing();
}
nsIFrame* inProcessRootFrame = rootPresShell->GetRootFrame(); if (!inProcessRootFrame) { return Some(OopIframeMetrics{});
}
BrowserChild* browserChild = BrowserChild::GetFrom(rootDoc->GetDocShell()); if (!browserChild) { return Some(OopIframeMetrics{});
}
if (MOZ_UNLIKELY(NS_WARN_IF(browserChild->IsTopLevel()))) { // FIXME(bug 1772083): This can be hit with popups, e.g. in // html/browsers/the-window-object/open-close/no_window_open_when_term_nesting_level_nonzero.window.html // temporarily while opening a new popup (on the about:blank doc). // MOZ_ASSERT_UNREACHABLE("Top level BrowserChild but non-top level doc?"); return Nothing();
}
// https://w3c.github.io/IntersectionObserver/#update-intersection-observations-algo // step 2.1
IntersectionInput DOMIntersectionObserver::ComputeInput( const Document& aDocument, const nsINode* aRoot, const StyleRect<LengthPercentage>* aRootMargin) { // 1 - Let rootBounds be observer's root intersection rectangle. // ... but since the intersection rectangle depends on the target, we defer // the inflation until later. // NOTE: |rootRect| and |rootFrame| will be root in the same process. In // out-of-process iframes, they are NOT root ones of the top level content // document.
nsRect rootRect;
nsIFrame* rootFrame = nullptr; const nsINode* root = aRoot; constbool isImplicitRoot = !aRoot;
Maybe<nsRect> remoteDocumentVisibleRect; if (aRoot && aRoot->IsElement()) { if ((rootFrame = aRoot->AsElement()->GetPrimaryFrame())) {
nsRect rootRectRelativeToRootFrame; if (ScrollContainerFrame* scrollContainerFrame =
do_QueryFrame(rootFrame)) { // rootRectRelativeToRootFrame should be the content rect of rootFrame, // not including the scrollbars.
rootRectRelativeToRootFrame =
scrollContainerFrame
->GetScrollPortRectAccountingForDynamicToolbar();
} else { // rootRectRelativeToRootFrame should be the border rect of rootFrame.
rootRectRelativeToRootFrame = rootFrame->GetRectRelativeToSelf();
}
nsIFrame* containingBlock =
nsLayoutUtils::GetContainingBlockForClientRect(rootFrame);
rootRect = nsLayoutUtils::TransformFrameRectToAncestor(
rootFrame, rootRectRelativeToRootFrame, containingBlock);
}
} else {
MOZ_ASSERT(!aRoot || aRoot->IsDocument()); const Document* rootDocument =
aRoot ? aRoot->AsDocument()
: GetTopLevelContentDocumentInThisProcess(aDocument);
root = rootDocument;
if (rootDocument) { // We're in the same process as the root document, though note that there // could be an out-of-process iframe in between us and the root. Grab the // root frame and the root rect. // // Note that the root rect is always good (we assume no DPI changes in // between the two documents, and we don't need to convert coordinates). // // The root frame however we may need to tweak in the block below, if // there's any OOP iframe in between `rootDocument` and `aDocument`, to // handle the OOP iframe positions. if (PresShell* presShell = rootDocument->GetPresShell()) {
rootFrame = presShell->GetRootFrame(); // We use the root scroll container frame's scroll port to account the // scrollbars in rootRect, if needed. if (ScrollContainerFrame* rootScrollContainerFrame =
presShell->GetRootScrollContainerFrame()) {
rootRect = rootScrollContainerFrame
->GetScrollPortRectAccountingForDynamicToolbar();
} elseif (rootFrame) {
rootRect = rootFrame->GetRectRelativeToSelf();
}
}
}
// "From the perspective of an IntersectionObserver, the skipped contents // of an element are never intersecting the intersection root. This is // true even if both the root and the target elements are in the skipped // contents." // https://drafts.csswg.org/css-contain/#cv-notes // // Skip the intersection if the element is hidden, unless this is the // specifically to determine the proximity to the viewport for // `content-visibility: auto` elements. if (aIsForProximityToViewport == IsForProximityToViewport::No &&
targetFrame->IsHiddenByContentVisibilityOnAnyAncestor()) { return {isSimilarOrigin};
}
// 2.2. If the intersection root is not the implicit root, and target is // not in the same Document as the intersection root, skip to step 11. if (!aInput.mIsImplicitRoot &&
aInput.mRootNode->OwnerDoc() != aTarget.OwnerDoc()) { return {isSimilarOrigin};
}
// 2.3. If the intersection root is an element and target is not a descendant // of the intersection root in the containing block chain, skip to step 11. // // NOTE(emilio): We also do this if target is the implicit root, pending // clarification in // https://github.com/w3c/IntersectionObserver/issues/456. if (aInput.mRootFrame == targetFrame ||
!nsLayoutUtils::IsAncestorFrameCrossDocInProcess(aInput.mRootFrame,
targetFrame)) { return {isSimilarOrigin};
}
nsRect rootBounds = aInput.mRootRect; if (isSimilarOrigin) {
rootBounds.Inflate(aInput.mRootMargin);
}
// 2.4. Set targetRect to the DOMRectReadOnly obtained by running the // getBoundingClientRect() algorithm on target. We compute the box relative to // self first, then transform.
nsLayoutUtils::GetAllInFlowRectsFlags flags{
nsLayoutUtils::GetAllInFlowRectsFlag::AccountForTransforms}; if (aBoxToUse == BoxToUse::Content) {
flags += nsLayoutUtils::GetAllInFlowRectsFlag::UseContentBox;
}
nsRect targetRectRelativeToTarget =
nsLayoutUtils::GetAllInFlowRectsUnion(targetFrame, targetFrame, flags);
if (aBoxToUse == BoxToUse::OverflowClip) { constauto& disp = *targetFrame->StyleDisplay(); auto clipAxes = targetFrame->ShouldApplyOverflowClipping(&disp); if (!clipAxes.isEmpty()) {
targetRectRelativeToTarget = OverflowAreas::GetOverflowClipRect(
targetRectRelativeToTarget, targetRectRelativeToTarget, clipAxes,
targetFrame->OverflowClipMargin(clipAxes));
}
}
auto targetRect = nsLayoutUtils::TransformFrameRectToAncestor(
targetFrame, targetRectRelativeToTarget,
nsLayoutUtils::GetContainingBlockForClientRect(targetFrame));
// For content-visibility, we need to observe the overflow clip edge, // https://drafts.csswg.org/css-contain-2/#close-to-the-viewport
MOZ_ASSERT_IF(aIsForProximityToViewport == IsForProximityToViewport::Yes,
aBoxToUse == BoxToUse::OverflowClip);
// 2.5. Let intersectionRect be the result of running the compute the // intersection algorithm on target and observer’s intersection root.
Maybe<nsRect> intersectionRect = ComputeTheIntersection(
targetFrame, targetRectRelativeToTarget, aInput.mRootFrame, rootBounds,
aInput.mRemoteDocumentVisibleRect, aIsForProximityToViewport);
// 2. For each target in observer’s internal [[ObservationTargets]] slot, // processed in the same order that observe() was called on each target: for (Element* target : mObservationTargets) { // 2.1 - 2.4.
IntersectionOutput output = Intersect(input, *target);
// 2.5. Let targetArea be targetRect’s area.
int64_t targetArea = (int64_t)output.mTargetRect.Width() *
(int64_t)output.mTargetRect.Height();
// 2.6. Let intersectionArea be intersectionRect’s area.
int64_t intersectionArea =
!output.mIntersectionRect
? 0
: (int64_t)output.mIntersectionRect->Width() *
(int64_t)output.mIntersectionRect->Height();
// 2.7. Let isIntersecting be true if targetRect and rootBounds intersect or // are edge-adjacent, even if the intersection has zero area (because // rootBounds or targetRect have zero area); otherwise, let isIntersecting // be false. constbool isIntersecting = output.Intersects();
// 2.8. If targetArea is non-zero, let intersectionRatio be intersectionArea // divided by targetArea. Otherwise, let intersectionRatio be 1 if // isIntersecting is true, or 0 if isIntersecting is false. double intersectionRatio; if (targetArea > 0.0) {
intersectionRatio =
std::min((double)intersectionArea / (double)targetArea, 1.0);
} else {
intersectionRatio = isIntersecting ? 1.0 : 0.0;
}
// 2.9 Let thresholdIndex be the index of the first entry in // observer.thresholds whose value is greater than intersectionRatio, or the // length of observer.thresholds if intersectionRatio is greater than or // equal to the last entry in observer.thresholds.
int32_t thresholdIndex = -1;
// If not intersecting, we can just shortcut, as we know that the thresholds // are always between 0 and 1. if (isIntersecting) {
thresholdIndex = mThresholds.IndexOfFirstElementGt(intersectionRatio); if (thresholdIndex == 0) { // Per the spec, we should leave threshold at 0 and distinguish between // "less than all thresholds and intersecting" and "not intersecting" // (queuing observer entries as both cases come to pass). However, // neither Chrome nor the WPT tests expect this behavior, so treat these // two cases as one. // // See https://github.com/w3c/IntersectionObserver/issues/432 about // this.
thresholdIndex = -1;
}
}
if (updated) { // See https://github.com/w3c/IntersectionObserver/issues/432 about // why we use thresholdIndex > 0 rather than isIntersecting for the // entry's isIntersecting value.
QueueIntersectionObserverEntry(
target, time,
output.mIsSimilarOrigin ? Some(output.mRootBounds) : Nothing(),
output.mTargetRect, output.mIntersectionRect, thresholdIndex > 0,
intersectionRatio);
}
}
}
void DOMIntersectionObserver::QueueIntersectionObserverEntry(
Element* aTarget, DOMHighResTimeStamp time, const Maybe<nsRect>& aRootRect, const nsRect& aTargetRect, const Maybe<nsRect>& aIntersectionRect, bool aIsIntersecting, double aIntersectionRatio) {
RefPtr<DOMRect> rootBounds; if (aRootRect.isSome()) {
rootBounds = new DOMRect(mOwner);
rootBounds->SetLayoutRect(aRootRect.value());
}
RefPtr<DOMRect> boundingClientRect = new DOMRect(mOwner);
boundingClientRect->SetLayoutRect(aTargetRect);
RefPtr<DOMRect> intersectionRect = new DOMRect(mOwner); if (aIntersectionRect.isSome()) {
intersectionRect->SetLayoutRect(aIntersectionRect.value());
}
RefPtr<DOMIntersectionObserverEntry> entry = new DOMIntersectionObserverEntry(
mOwner, time, rootBounds.forget(), boundingClientRect.forget(),
intersectionRect.forget(), aIsIntersecting, aTarget, aIntersectionRatio);
mQueuedEntries.AppendElement(entry.forget());
}
void DOMIntersectionObserver::Notify() { if (!mQueuedEntries.Length()) { return;
}
Sequence<OwningNonNull<DOMIntersectionObserverEntry>> entries; if (entries.SetCapacity(mQueuedEntries.Length(), mozilla::fallible)) { for (size_t i = 0; i < mQueuedEntries.Length(); ++i) {
RefPtr<DOMIntersectionObserverEntry> next = mQueuedEntries[i];
*entries.AppendElement(mozilla::fallible) = next;
}
}
mQueuedEntries.Clear();
Die Informationen auf dieser Webseite wurden
nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit,
noch Qualität der bereit gestellten Informationen zugesichert.
Bemerkung:
Die farbliche Syntaxdarstellung ist noch experimentell.