Bug 1730284 - Factor out some IntersectionObserver code. r=smaug,sefeng

Differential Revision: https://phabricator.services.mozilla.com/D146573
This commit is contained in:
Emilio Cobos Álvarez 2022-05-25 20:45:55 +00:00
Родитель e0b7dc04e6
Коммит 6edd8dc1c9
4 изменённых файлов: 144 добавлений и 103 удалений

Просмотреть файл

@ -316,7 +316,8 @@ static BrowsingContextOrigin SimilarOrigin(const Element& aTarget,
// NOTE: This returns nullptr if |aDocument| is in another process from the top
// level content document.
static Document* GetTopLevelContentDocumentInThisProcess(Document& aDocument) {
static const Document* GetTopLevelContentDocumentInThisProcess(
const Document& aDocument) {
auto* wc = aDocument.GetTopLevelWindowContext();
return wc ? wc->GetExtantDoc() : nullptr;
}
@ -462,9 +463,9 @@ struct OopIframeMetrics {
nsRect mRemoteDocumentVisibleRect;
};
static Maybe<OopIframeMetrics> GetOopIframeMetrics(Document& aDocument,
Document* aRootDocument) {
Document* rootDoc =
static Maybe<OopIframeMetrics> GetOopIframeMetrics(
const Document& aDocument, const Document* aRootDocument) {
const Document* rootDoc =
nsContentUtils::GetInProcessSubtreeRootDocument(&aDocument);
MOZ_ASSERT(rootDoc);
@ -522,9 +523,10 @@ static Maybe<OopIframeMetrics> GetOopIframeMetrics(Document& aDocument,
}
// https://w3c.github.io/IntersectionObserver/#update-intersection-observations-algo
// (step 2)
void DOMIntersectionObserver::Update(Document* aDocument,
DOMHighResTimeStamp time) {
// 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.
@ -533,10 +535,11 @@ void DOMIntersectionObserver::Update(Document* aDocument,
// document.
nsRect rootRect;
nsIFrame* rootFrame = nullptr;
nsINode* root = mRoot;
const nsINode* root = aRoot;
const bool isImplicitRoot = !aRoot;
Maybe<nsRect> remoteDocumentVisibleRect;
if (mRoot && mRoot->IsElement()) {
if ((rootFrame = mRoot->AsElement()->GetPrimaryFrame())) {
if (aRoot && aRoot->IsElement()) {
if ((rootFrame = aRoot->AsElement()->GetPrimaryFrame())) {
nsRect rootRectRelativeToRootFrame;
if (nsIScrollableFrame* scrollFrame = do_QueryFrame(rootFrame)) {
// rootRectRelativeToRootFrame should be the content rect of rootFrame,
@ -552,10 +555,10 @@ void DOMIntersectionObserver::Update(Document* aDocument,
rootFrame, rootRectRelativeToRootFrame, containingBlock);
}
} else {
MOZ_ASSERT(!mRoot || mRoot->IsDocument());
Document* rootDocument =
mRoot ? mRoot->AsDocument()
: GetTopLevelContentDocumentInThisProcess(*aDocument);
MOZ_ASSERT(!aRoot || aRoot->IsDocument());
const Document* rootDocument =
aRoot ? aRoot->AsDocument()
: GetTopLevelContentDocumentInThisProcess(aDocument);
root = rootDocument;
if (rootDocument) {
@ -581,7 +584,7 @@ void DOMIntersectionObserver::Update(Document* aDocument,
}
if (Maybe<OopIframeMetrics> metrics =
GetOopIframeMetrics(*aDocument, rootDocument)) {
GetOopIframeMetrics(aDocument, rootDocument)) {
rootFrame = metrics->mInProcessRootFrame;
if (!rootDocument) {
rootRect = metrics->mInProcessRootRect;
@ -592,26 +595,28 @@ void DOMIntersectionObserver::Update(Document* aDocument,
nsMargin rootMargin; // This root margin is NOT applied in `implicit root`
// case, e.g. in out-of-process iframes.
if (aRootMargin) {
for (const auto side : mozilla::AllPhysicalSides()) {
nscoord basis = side == eSideTop || side == eSideBottom ? rootRect.Height()
nscoord basis = side == eSideTop || side == eSideBottom
? rootRect.Height()
: rootRect.Width();
rootMargin.Side(side) = mRootMargin.Get(side).Resolve(
rootMargin.Side(side) = aRootMargin->Get(side).Resolve(
basis, static_cast<nscoord (*)(float)>(NSToCoordRoundWithClamp));
}
}
return {isImplicitRoot, root, rootFrame,
rootRect, rootMargin, remoteDocumentVisibleRect};
}
// 2. For each target in observers internal [[ObservationTargets]] slot,
// processed in the same order that observe() was called on each target:
for (Element* target : mObservationTargets) {
nsIFrame* targetFrame = target->GetPrimaryFrame();
BrowsingContextOrigin origin = SimilarOrigin(*target, root);
Maybe<nsRect> intersectionRect;
nsRect targetRect;
nsRect rootBounds;
const bool canComputeIntersection = [&] {
if (!targetFrame || !rootFrame) {
return false;
// https://w3c.github.io/IntersectionObserver/#update-intersection-observations-algo
// (steps 2.1 - 2.5)
IntersectionOutput DOMIntersectionObserver::Intersect(
const IntersectionInput& aInput, Element& aTarget) {
const bool isSimilarOrigin = SimilarOrigin(aTarget, aInput.mRootNode) ==
BrowsingContextOrigin::Similar;
nsIFrame* targetFrame = aTarget.GetPrimaryFrame();
if (!targetFrame || !aInput.mRootFrame) {
return {isSimilarOrigin};
}
// "From the perspective of an IntersectionObserver, the skipped contents
@ -620,73 +625,74 @@ void DOMIntersectionObserver::Update(Document* aDocument,
// contents."
// https://drafts.csswg.org/css-contain/#cv-notes
if (targetFrame->AncestorHidesContent()) {
return false;
return {isSimilarOrigin};
}
// 2.1. If the intersection root is not the implicit root and target is
// not a descendant of the intersection root in the containing block
// chain, skip further processing for target.
//
// NOTE(emilio): We don't just "skip further processing" because that
// violates the invariant that there's at least one observation for a
// target (though that is also violated by 2.2), but it also causes
// different behavior when `target` is `display: none`, or not, which is
// really really odd, see:
// https://github.com/w3c/IntersectionObserver/issues/457
// 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 (rootFrame == targetFrame ||
!nsLayoutUtils::IsAncestorFrameCrossDocInProcess(rootFrame,
if (aInput.mRootFrame == targetFrame ||
!nsLayoutUtils::IsAncestorFrameCrossDocInProcess(aInput.mRootFrame,
targetFrame)) {
return false;
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 further
// processing for target.
//
// NOTE(emilio): We don't just "skip further processing", because that
// doesn't match reality and other browsers, see
// https://github.com/w3c/IntersectionObserver/issues/457.
if (mRoot && mRoot->OwnerDoc() != target->OwnerDoc()) {
return false;
nsRect rootBounds = aInput.mRootRect;
if (isSimilarOrigin) {
rootBounds.Inflate(aInput.mRootMargin);
}
return true;
}();
if (canComputeIntersection) {
rootBounds = rootRect;
if (origin == BrowsingContextOrigin::Similar) {
rootBounds.Inflate(rootMargin);
}
// 2.3. Let targetRect be a DOMRectReadOnly obtained by running the
// 2.4. Set targetRect to the DOMRectReadOnly obtained by running the
// getBoundingClientRect() algorithm on target.
targetRect = targetFrame->GetBoundingClientRect();
nsRect targetRect = targetFrame->GetBoundingClientRect();
// 2.4. Let intersectionRect be the result of running the compute the
// intersection algorithm on target.
intersectionRect = ComputeTheIntersection(
targetFrame, rootFrame, rootBounds, remoteDocumentVisibleRect);
}
// 2.5. Let intersectionRect be the result of running the compute the
// intersection algorithm on target and observers intersection root.
Maybe<nsRect> intersectionRect =
ComputeTheIntersection(targetFrame, aInput.mRootFrame, rootBounds,
aInput.mRemoteDocumentVisibleRect);
return {isSimilarOrigin, rootBounds, targetRect, intersectionRect};
}
// https://w3c.github.io/IntersectionObserver/#update-intersection-observations-algo
// (step 2)
void DOMIntersectionObserver::Update(Document* aDocument,
DOMHighResTimeStamp time) {
auto input = ComputeInput(*aDocument, mRoot, &mRootMargin);
// 2. For each target in observers 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 targetRects area.
int64_t targetArea =
(int64_t)targetRect.Width() * (int64_t)targetRect.Height();
int64_t targetArea = (int64_t)output.mTargetRect.Width() *
(int64_t)output.mTargetRect.Height();
// 2.6. Let intersectionArea be intersectionRects area.
int64_t intersectionArea = !intersectionRect
int64_t intersectionArea =
!output.mIntersectionRect
? 0
: (int64_t)intersectionRect->Width() *
(int64_t)intersectionRect->Height();
: (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.
const bool isIntersecting = intersectionRect.isSome();
const bool isIntersecting = output.Intersects();
// 2.8. If targetArea is non-zero, let intersectionRatio be intersectionArea
// divided by targetArea. Otherwise, let intersectionRatio be 1 if
@ -729,9 +735,9 @@ void DOMIntersectionObserver::Update(Document* aDocument,
// entry's isIntersecting value.
QueueIntersectionObserverEntry(
target, time,
origin == BrowsingContextOrigin::Similar ? Some(rootBounds)
: Nothing(),
targetRect, intersectionRect, thresholdIndex > 0, intersectionRatio);
output.mIsSimilarOrigin ? Some(output.mRootBounds) : Nothing(),
output.mTargetRect, output.mIntersectionRect, thresholdIndex > 0,
intersectionRatio);
}
}
}

Просмотреть файл

@ -80,6 +80,32 @@ class DOMIntersectionObserverEntry final : public nsISupports,
} \
}
// An input suitable to compute intersections with multiple targets.
struct IntersectionInput {
// Whether the root is implicit (null, originally).
const bool mIsImplicitRoot = false;
// The computed root node. For the implicit root, this will be the in-process
// root document we can compute coordinates against (along with the remote
// document visible rect if appropriate).
const nsINode* mRootNode = nullptr;
nsIFrame* mRootFrame = nullptr;
// The rect of mRootFrame in client coordinates.
nsRect mRootRect;
// The root margin computed against the root rect.
nsMargin mRootMargin;
// If this is in an OOP iframe, the visible rect of the OOP frame.
Maybe<nsRect> mRemoteDocumentVisibleRect;
};
struct IntersectionOutput {
const bool mIsSimilarOrigin;
const nsRect mRootBounds;
const nsRect mTargetRect;
const Maybe<nsRect> mIntersectionRect;
bool Intersects() const { return mIntersectionRect.isSome(); }
};
class DOMIntersectionObserver final : public nsISupports,
public nsWrapperCache {
virtual ~DOMIntersectionObserver() { Disconnect(); }
@ -122,6 +148,11 @@ class DOMIntersectionObserver final : public nsISupports,
void TakeRecords(nsTArray<RefPtr<DOMIntersectionObserverEntry>>& aRetVal);
static IntersectionInput ComputeInput(
const Document& aDocument, const nsINode* aRoot,
const StyleRect<LengthPercentage>* aRootMargin);
static IntersectionOutput Intersect(const IntersectionInput&, Element&);
void Update(Document* aDocument, DOMHighResTimeStamp time);
MOZ_CAN_RUN_SCRIPT void Notify();

Просмотреть файл

@ -6991,11 +6991,11 @@ void nsContentUtils::FireMutationEventsForDirectParsing(
}
/* static */
Document* nsContentUtils::GetInProcessSubtreeRootDocument(Document* aDoc) {
const Document* nsContentUtils::GetInProcessSubtreeRootDocument(const Document* aDoc) {
if (!aDoc) {
return nullptr;
}
Document* doc = aDoc;
const Document* doc = aDoc;
while (doc->GetInProcessParentDocument()) {
doc = doc->GetInProcessParentDocument();
}

Просмотреть файл

@ -2459,7 +2459,11 @@ class nsContentUtils {
* Returns the in-process subtree root document in a document hierarchy.
* This could be a chrome document.
*/
static Document* GetInProcessSubtreeRootDocument(Document* aDoc);
static Document* GetInProcessSubtreeRootDocument(Document* aDoc) {
return const_cast<Document*>(
GetInProcessSubtreeRootDocument(const_cast<const Document*>(aDoc)));
}
static const Document* GetInProcessSubtreeRootDocument(const Document* aDoc);
static void GetShiftText(nsAString& text);
static void GetControlText(nsAString& text);