зеркало из https://github.com/mozilla/gecko-dev.git
Bug 1730284 - Factor out some IntersectionObserver code. r=smaug,sefeng
Differential Revision: https://phabricator.services.mozilla.com/D146573
This commit is contained in:
Родитель
e0b7dc04e6
Коммит
6edd8dc1c9
|
@ -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 observer’s 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 observer’s 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 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)targetRect.Width() * (int64_t)targetRect.Height();
|
||||
int64_t targetArea = (int64_t)output.mTargetRect.Width() *
|
||||
(int64_t)output.mTargetRect.Height();
|
||||
|
||||
// 2.6. Let intersectionArea be intersectionRect’s 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);
|
||||
|
|
Загрузка…
Ссылка в новой задаче