From e7e16e453b0dd57c23e998eb98934cc359f97963 Mon Sep 17 00:00:00 2001 From: Frederic Wang Date: Mon, 27 Nov 2023 08:26:46 +0000 Subject: [PATCH] Bug 1807253 - unreliable timing for content-visibility:auto, r=emilio Rewrite implementation of content-visibility: auto as defined in https://github.com/w3c/csswg-drafts/issues/8542 Differential Revision: https://phabricator.services.mozilla.com/D170394 --- dom/base/DOMIntersectionObserver.cpp | 76 ++-------- dom/base/DOMIntersectionObserver.h | 7 +- dom/base/Document.cpp | 56 ++++--- dom/base/Document.h | 22 ++- dom/base/FragmentOrElement.h | 4 + dom/base/ResizeObserver.cpp | 4 +- layout/base/PresShell.cpp | 57 +++++++ layout/base/PresShell.h | 14 ++ layout/base/nsRefreshDriver.cpp | 34 ++--- layout/base/nsRefreshDriver.h | 3 +- layout/generic/nsIFrame.cpp | 10 -- .../content-visibility-086.html.ini | 3 - ...-auto-first-observation-immediate.html.ini | 3 - ...y-auto-relevancy-updates-stop-ticking.html | 139 ++++++++++++++++++ 14 files changed, 290 insertions(+), 142 deletions(-) delete mode 100644 testing/web-platform/meta/css/css-contain/content-visibility/content-visibility-086.html.ini delete mode 100644 testing/web-platform/meta/css/css-contain/content-visibility/content-visibility-auto-first-observation-immediate.html.ini create mode 100644 testing/web-platform/mozilla/tests/css/css-contain/content-visibility/content-visibility-auto-relevancy-updates-stop-ticking.html diff --git a/dom/base/DOMIntersectionObserver.cpp b/dom/base/DOMIntersectionObserver.cpp index f1d1fabbd1ee..12f7ee302901 100644 --- a/dom/base/DOMIntersectionObserver.cpp +++ b/dom/base/DOMIntersectionObserver.cpp @@ -160,20 +160,6 @@ static void LazyLoadCallback( } } -static void ContentVisibilityCallback( - const Sequence>& aEntries) { - for (const auto& entry : aEntries) { - entry->Target()->SetVisibleForContentVisibility(entry->IsIntersecting()); - - if (RefPtr doc = entry->Target()->GetComposedDoc()) { - if (RefPtr presShell = doc->GetPresShell()) { - presShell->ScheduleContentRelevancyUpdate( - ContentRelevancyReason::Visible); - } - } - } -} - static LengthPercentage PrefMargin(float aValue, bool aIsPercentage) { return aIsPercentage ? LengthPercentage::FromPercentage(aValue / 100.0f) : LengthPercentage::FromPixels(aValue); @@ -205,25 +191,6 @@ DOMIntersectionObserver::CreateLazyLoadObserver(Document& aDocument) { return observer.forget(); } -already_AddRefed -DOMIntersectionObserver::CreateContentVisibilityObserver(Document& aDocument) { - RefPtr observer = - new DOMIntersectionObserver(aDocument, ContentVisibilityCallback); - - observer->mThresholds.AppendElement(0.0f); - - auto margin = LengthPercentage::FromPercentage( - StaticPrefs::layout_css_content_visibility_relevant_content_margin() / - 100.0f); - - observer->mRootMargin.Get(eSideTop) = margin; - observer->mRootMargin.Get(eSideRight) = margin; - observer->mRootMargin.Get(eSideBottom) = margin; - observer->mRootMargin.Get(eSideLeft) = margin; - - return observer.forget(); -} - bool DOMIntersectionObserver::SetRootMargin(const nsACString& aString) { return Servo_IntersectionObserverRootMargin_Parse(&aString, &mRootMargin); } @@ -348,8 +315,8 @@ static const Document* GetTopLevelContentDocumentInThisProcess( static Maybe ComputeTheIntersection( nsIFrame* aTarget, nsIFrame* aRoot, const nsRect& aRootBounds, const Maybe& aRemoteDocumentVisibleRect, - DOMIntersectionObserver::IsContentVisibilityObserver - aIsContentVisibilityObserver) { + DOMIntersectionObserver::IsForProximityToViewport + aIsForProximityToViewport) { nsIFrame* target = aTarget; // 1. Let intersectionRect be the result of running the // getBoundingClientRect() algorithm on the target. @@ -359,8 +326,8 @@ static Maybe ComputeTheIntersection( target, target, nsLayoutUtils::RECTS_ACCOUNT_FOR_TRANSFORMS); // For content-visibility, we need to observe the overflow clip edge, // https://drafts.csswg.org/css-contain-2/#close-to-the-viewport - if (aIsContentVisibilityObserver == - DOMIntersectionObserver::IsContentVisibilityObserver::Yes) { + if (aIsForProximityToViewport == + DOMIntersectionObserver::IsForProximityToViewport::Yes) { const auto& disp = *target->StyleDisplay(); auto clipAxes = target->ShouldApplyOverflowClipping(&disp); if (clipAxes != PhysicalAxes::None) { @@ -648,7 +615,7 @@ IntersectionInput DOMIntersectionObserver::ComputeInput( // (steps 2.1 - 2.5) IntersectionOutput DOMIntersectionObserver::Intersect( const IntersectionInput& aInput, const Element& aTarget, - IsContentVisibilityObserver aIsContentVisibilityObserver) { + IsForProximityToViewport aIsForProximityToViewport) { const bool isSimilarOrigin = SimilarOrigin(aTarget, aInput.mRootNode) == BrowsingContextOrigin::Similar; nsIFrame* targetFrame = aTarget.GetPrimaryFrame(); @@ -663,9 +630,9 @@ IntersectionOutput DOMIntersectionObserver::Intersect( // https://drafts.csswg.org/css-contain/#cv-notes // // Skip the intersection if the element is hidden, unless this is the - // DOMIntersectionObserver used specifically to track the visibility of + // specifically to determine the proximity to the viewport for // `content-visibility: auto` elements. - if (aIsContentVisibilityObserver == IsContentVisibilityObserver::No && + if (aIsForProximityToViewport == IsForProximityToViewport::No && targetFrame->IsHiddenByContentVisibilityOnAnyAncestor()) { return {isSimilarOrigin}; } @@ -699,7 +666,7 @@ IntersectionOutput DOMIntersectionObserver::Intersect( nsRect targetRect = targetFrame->GetBoundingClientRect(); // For content-visibility, we need to observe the overflow clip edge, // https://drafts.csswg.org/css-contain-2/#close-to-the-viewport - if (aIsContentVisibilityObserver == IsContentVisibilityObserver::Yes) { + if (aIsForProximityToViewport == IsForProximityToViewport::Yes) { const auto& disp = *targetFrame->StyleDisplay(); auto clipAxes = targetFrame->ShouldApplyOverflowClipping(&disp); if (clipAxes != PhysicalAxes::None) { @@ -713,7 +680,7 @@ IntersectionOutput DOMIntersectionObserver::Intersect( // intersection algorithm on target and observer’s intersection root. Maybe intersectionRect = ComputeTheIntersection( targetFrame, aInput.mRootFrame, rootBounds, - aInput.mRemoteDocumentVisibleRect, aIsContentVisibilityObserver); + aInput.mRemoteDocumentVisibleRect, aIsForProximityToViewport); return {isSimilarOrigin, rootBounds, targetRect, intersectionRect}; } @@ -737,20 +704,11 @@ void DOMIntersectionObserver::Update(Document& aDocument, DOMHighResTimeStamp time) { auto input = ComputeInput(aDocument, mRoot, &mRootMargin); - // If this observer is used to determine content relevancy for - // `content-visiblity: auto` content, then do not skip intersection - // for content that is hidden by `content-visibility: auto`. - IsContentVisibilityObserver isContentVisibilityObserver = - aDocument.GetContentVisibilityObserver() == this - ? IsContentVisibilityObserver::Yes - : IsContentVisibilityObserver::No; - // 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, isContentVisibilityObserver); + IntersectionOutput output = Intersect(input, *target); // 2.5. Let targetArea be targetRect’s area. int64_t targetArea = (int64_t)output.mTargetRect.Width() * @@ -803,16 +761,8 @@ void DOMIntersectionObserver::Update(Document& aDocument, } } - // If descendantScrolledIntoView, it means the target is with c-v: auto, and - // the content relevancy value has been set to visible before - // scrollIntoView. Here, we need to generate entries for them, so that the - // content relevancy value could be checked in the callback. - const bool temporarilyVisibleForScrolledIntoView = - isContentVisibilityObserver == IsContentVisibilityObserver::Yes && - target->TemporarilyVisibleForScrolledIntoViewDescendant(); // Steps 2.10 - 2.15. - if (target->UpdateIntersectionObservation(this, thresholdIndex) || - temporarilyVisibleForScrolledIntoView) { + if (target->UpdateIntersectionObservation(this, thresholdIndex)) { // See https://github.com/w3c/IntersectionObserver/issues/432 about // why we use thresholdIndex > 0 rather than isIntersecting for the // entry's isIntersecting value. @@ -821,10 +771,6 @@ void DOMIntersectionObserver::Update(Document& aDocument, output.mIsSimilarOrigin ? Some(output.mRootBounds) : Nothing(), output.mTargetRect, output.mIntersectionRect, thresholdIndex > 0, intersectionRatio); - - if (temporarilyVisibleForScrolledIntoView) { - target->SetTemporarilyVisibleForScrolledIntoViewDescendant(false); - } } } } diff --git a/dom/base/DOMIntersectionObserver.h b/dom/base/DOMIntersectionObserver.h index bdb371766386..b9d2da77f7ab 100644 --- a/dom/base/DOMIntersectionObserver.h +++ b/dom/base/DOMIntersectionObserver.h @@ -152,10 +152,10 @@ class DOMIntersectionObserver final : public nsISupports, const Document& aDocument, const nsINode* aRoot, const StyleRect* aRootMargin); - enum class IsContentVisibilityObserver : bool { No, Yes }; + enum class IsForProximityToViewport : bool { No, Yes }; static IntersectionOutput Intersect( const IntersectionInput&, const Element&, - IsContentVisibilityObserver = IsContentVisibilityObserver::No); + IsForProximityToViewport = IsForProximityToViewport::No); // Intersects with a given rect, already relative to the root frame. static IntersectionOutput Intersect(const IntersectionInput&, const nsRect&); @@ -165,9 +165,6 @@ class DOMIntersectionObserver final : public nsISupports, static already_AddRefed CreateLazyLoadObserver( Document&); - static already_AddRefed - CreateContentVisibilityObserver(Document&); - static Maybe EdgeInclusiveIntersection(const nsRect& aRect, const nsRect& aOtherRect); diff --git a/dom/base/Document.cpp b/dom/base/Document.cpp index 429dd23d834c..0023ac18d4fe 100644 --- a/dom/base/Document.cpp +++ b/dom/base/Document.cpp @@ -2520,7 +2520,6 @@ NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN_INTERNAL(Document) NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mOnloadBlocker) NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mLazyLoadObserver) NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mLastRememberedSizeObserver) - NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mContentVisibilityObserver) NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mDOMImplementation) NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mImageMaps) NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mOrientationPendingPromise) @@ -2637,7 +2636,6 @@ NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN(Document) NS_IMPL_CYCLE_COLLECTION_UNLINK(mSecurityInfo) NS_IMPL_CYCLE_COLLECTION_UNLINK(mDisplayDocument) NS_IMPL_CYCLE_COLLECTION_UNLINK(mLazyLoadObserver) - NS_IMPL_CYCLE_COLLECTION_UNLINK(mContentVisibilityObserver) NS_IMPL_CYCLE_COLLECTION_UNLINK(mLastRememberedSizeObserver) NS_IMPL_CYCLE_COLLECTION_UNLINK(mFontFaceSet) NS_IMPL_CYCLE_COLLECTION_UNLINK(mReadyForIdle) @@ -16451,24 +16449,6 @@ DOMIntersectionObserver& Document::EnsureLazyLoadObserver() { return *mLazyLoadObserver; } -DOMIntersectionObserver& Document::EnsureContentVisibilityObserver() { - if (!mContentVisibilityObserver) { - mContentVisibilityObserver = - DOMIntersectionObserver::CreateContentVisibilityObserver(*this); - } - return *mContentVisibilityObserver; -} - -void Document::ObserveForContentVisibility(Element& aElement) { - EnsureContentVisibilityObserver().Observe(aElement); -} - -void Document::UnobserveForContentVisibility(Element& aElement) { - if (mContentVisibilityObserver) { - mContentVisibilityObserver->Unobserve(aElement); - } -} - ResizeObserver& Document::EnsureLastRememberedSizeObserver() { if (!mLastRememberedSizeObserver) { mLastRememberedSizeObserver = @@ -17181,18 +17161,42 @@ static void FlushLayoutForWholeBrowsingContextTree(Document& aDoc) { } } -void Document::NotifyResizeObservers() { - if (mResizeObservers.IsEmpty()) { - return; +bool Document::HasContentVisibilityAutoElements() const { + if (PresShell* presShell = GetPresShell()) { + return presShell->HasContentVisibilityAutoFrames(); } + return false; +} +void Document::DetermineProximityToViewportAndNotifyResizeObservers() { uint32_t shallowestTargetDepth = 0; + bool initialResetOfScrolledIntoViewFlagsDone = false; while (true) { // Flush layout, so that any callback functions' style changes / resizes // get a chance to take effect. The callback functions may do changes in its // sub-documents or ancestors, so flushing layout for the whole browsing // context tree makes sure we don't miss anyone. FlushLayoutForWholeBrowsingContextTree(*this); + if (PresShell* presShell = GetPresShell()) { + auto result = presShell->DetermineProximityToViewport(); + if (result.mHadInitialDetermination) { + continue; + } + if (result.mAnyScrollIntoViewFlag) { + // Not defined in the spec: It's possible that some elements with + // content-visibility: auto were forced to be visible in order to + // perform scrollIntoView() so clear their flags now and restart the + // loop. + // See https://github.com/w3c/csswg-drafts/issues/9337 + presShell->ClearTemporarilyVisibleForScrolledIntoViewDescendantFlags(); + presShell->ScheduleContentRelevancyUpdate( + ContentRelevancyReason::Visible); + if (!initialResetOfScrolledIntoViewFlagsDone) { + initialResetOfScrolledIntoViewFlagsDone = true; + continue; + } + } + } // To avoid infinite resize loop, we only gather all active observations // that have the depth of observed target element more than current @@ -17203,6 +17207,12 @@ void Document::NotifyResizeObservers() { break; } + // nsIFrame::UpdateIsRelevantContent may call ObserveForLastRememberedSize() + // so update the relevancy after the observations are gathered, otherwise + // the last remembered size observation could be a skipped one. + if (PresShell* presShell = GetPresShell()) { + presShell->UpdateRelevancyOfContentVisibilityAutoFrames(); + } DebugOnly oldShallowestTargetDepth = shallowestTargetDepth; shallowestTargetDepth = BroadcastAllActiveResizeObservations(); NS_ASSERTION(oldShallowestTargetDepth < shallowestTargetDepth, diff --git a/dom/base/Document.h b/dom/base/Document.h index 37ef1de145ad..8464ef693e3a 100644 --- a/dom/base/Document.h +++ b/dom/base/Document.h @@ -3709,13 +3709,6 @@ class Document : public nsINode, DOMIntersectionObserver* GetLazyLoadObserver() { return mLazyLoadObserver; } DOMIntersectionObserver& EnsureLazyLoadObserver(); - DOMIntersectionObserver* GetContentVisibilityObserver() const { - return mContentVisibilityObserver; - } - DOMIntersectionObserver& EnsureContentVisibilityObserver(); - void ObserveForContentVisibility(Element&); - void UnobserveForContentVisibility(Element&); - ResizeObserver* GetLastRememberedSizeObserver() { return mLastRememberedSizeObserver; } @@ -3762,7 +3755,16 @@ class Document : public nsINode, * Returns whether there is any ResizeObserver that has skipped observations. */ bool HasAnySkippedResizeObservations() const; - MOZ_CAN_RUN_SCRIPT void NotifyResizeObservers(); + /** + * Returns whether the document contains any content-visibility: auto element. + */ + bool HasContentVisibilityAutoElements() const; + /** + * Determine proximity to viewport for content-visibility: auto elements and + * notify resize observers. + */ + MOZ_CAN_RUN_SCRIPT void + DetermineProximityToViewportAndNotifyResizeObservers(); // Getter for PermissionDelegateHandler. Performs lazy initialization. PermissionDelegateHandler* GetPermissionDelegateHandler(); @@ -5118,10 +5120,6 @@ class Document : public nsINode, RefPtr mLazyLoadObserver; - // Used for detecting when `content-visibility: auto` elements are near - // or far from the viewport. - RefPtr mContentVisibilityObserver; - // ResizeObserver for storing and removing the last remembered size. // @see {@link https://drafts.csswg.org/css-sizing-4/#last-remembered} RefPtr mLastRememberedSizeObserver; diff --git a/dom/base/FragmentOrElement.h b/dom/base/FragmentOrElement.h index c1f80a4b9b2f..81b2acf099e5 100644 --- a/dom/base/FragmentOrElement.h +++ b/dom/base/FragmentOrElement.h @@ -238,12 +238,16 @@ class FragmentOrElement : public nsIContent { /** * Whether the content of this element is relevant for the purposes * of `content-visibility: auto. + * Reflects 'relevant to the user' concept, see + * https://drafts.csswg.org/css-contain/#relevant-to-the-user. */ Maybe mContentRelevancy; /** * Whether the content of this element is considered visible for * the purposes of `content-visibility: auto. + * Reflects 'proximity to the viewport' concept, see + * https://drafts.csswg.org/css-contain/#proximity-to-the-viewport. */ Maybe mVisibleForContentVisibility; diff --git a/dom/base/ResizeObserver.cpp b/dom/base/ResizeObserver.cpp index edad2a1b2059..676064c7ac02 100644 --- a/dom/base/ResizeObserver.cpp +++ b/dom/base/ResizeObserver.cpp @@ -531,14 +531,12 @@ static void LastRememberedSizeCallback( continue; } nsIFrame* frame = target->GetPrimaryFrame(); - if (!frame) { + if (!frame || frame->HidesContent()) { aObserver.Unobserve(*target); continue; } MOZ_ASSERT(!frame->IsLineParticipant() || frame->IsReplaced(), "Should have unobserved non-replaced inline."); - MOZ_ASSERT(!frame->HidesContent(), - "Should have unobserved element skipping its contents."); const nsStylePosition* stylePos = frame->StylePosition(); const WritingMode wm = frame->GetWritingMode(); bool canUpdateBSize = stylePos->ContainIntrinsicBSize(wm).HasAuto(); diff --git a/layout/base/PresShell.cpp b/layout/base/PresShell.cpp index 244d089ba97b..41495b1b54dd 100644 --- a/layout/base/PresShell.cpp +++ b/layout/base/PresShell.cpp @@ -68,6 +68,7 @@ #include "mozilla/dom/Element.h" #include "mozilla/dom/PointerEventHandler.h" #include "mozilla/dom/PopupBlocker.h" +#include "mozilla/dom/DOMIntersectionObserver.h" #include "mozilla/dom/Document.h" #include "mozilla/dom/DocumentInlines.h" #include "mozilla/dom/UserActivation.h" @@ -11960,6 +11961,62 @@ void PresShell::ScheduleContentRelevancyUpdate(ContentRelevancyReason aReason) { } } +PresShell::ProximityToViewportResult PresShell::DetermineProximityToViewport() { + ProximityToViewportResult result; + if (mContentVisibilityAutoFrames.IsEmpty()) { + return result; + } + + auto margin = LengthPercentage::FromPercentage( + StaticPrefs::layout_css_content_visibility_relevant_content_margin() / + 100.0f); + + auto rootMargin = StyleRect::WithAllSides(margin); + + auto input = DOMIntersectionObserver::ComputeInput( + *mDocument, /* aRoot = */ nullptr, &rootMargin); + + for (nsIFrame* frame : mContentVisibilityAutoFrames) { + auto* element = frame->GetContent()->AsElement(); + result.mAnyScrollIntoViewFlag |= + element->TemporarilyVisibleForScrolledIntoViewDescendant(); + + // 14.2.3.1 + Maybe oldVisibility = element->GetVisibleForContentVisibility(); + bool checkForInitialDetermination = + oldVisibility.isNothing() && + (element->GetContentRelevancy().isNothing() || + element->GetContentRelevancy()->isEmpty()); + + // 14.2.3.2 + bool intersects = + DOMIntersectionObserver::Intersect( + input, *element, + DOMIntersectionObserver::IsForProximityToViewport::Yes) + .Intersects(); + element->SetVisibleForContentVisibility(intersects); + if (oldVisibility.isNothing() || *oldVisibility != intersects) { + ScheduleContentRelevancyUpdate(ContentRelevancyReason::Visible); + } + + // 14.2.3.3 + if (checkForInitialDetermination && intersects) { + result.mHadInitialDetermination = true; + } + } + + return result; +} + +void PresShell::ClearTemporarilyVisibleForScrolledIntoViewDescendantFlags() + const { + for (nsIFrame* frame : mContentVisibilityAutoFrames) { + frame->GetContent() + ->AsElement() + ->SetTemporarilyVisibleForScrolledIntoViewDescendant(false); + } +} + void PresShell::UpdateContentRelevancyImmediately( ContentRelevancyReason aReason) { if (MOZ_UNLIKELY(mIsDestroying)) { diff --git a/layout/base/PresShell.h b/layout/base/PresShell.h index 9bb01674fe1c..82158700648b 100644 --- a/layout/base/PresShell.h +++ b/layout/base/PresShell.h @@ -1748,11 +1748,25 @@ class PresShell final : public nsStubDocumentObserver, void RegisterContentVisibilityAutoFrame(nsIFrame* aFrame) { mContentVisibilityAutoFrames.Insert(aFrame); } + bool HasContentVisibilityAutoFrames() const { + return !mContentVisibilityAutoFrames.IsEmpty(); + } void UpdateRelevancyOfContentVisibilityAutoFrames(); void ScheduleContentRelevancyUpdate(ContentRelevancyReason aReason); void UpdateContentRelevancyImmediately(ContentRelevancyReason aReason); + // Determination of proximity to the viewport. + // Refer to "update the rendering: step 14", see + // https://html.spec.whatwg.org/#update-the-rendering + struct ProximityToViewportResult { + bool mHadInitialDetermination = false; + bool mAnyScrollIntoViewFlag = false; + }; + ProximityToViewportResult DetermineProximityToViewport(); + + void ClearTemporarilyVisibleForScrolledIntoViewDescendantFlags() const; + private: ~PresShell(); diff --git a/layout/base/nsRefreshDriver.cpp b/layout/base/nsRefreshDriver.cpp index 58417e3b8684..5ffe637a2b2d 100644 --- a/layout/base/nsRefreshDriver.cpp +++ b/layout/base/nsRefreshDriver.cpp @@ -2242,11 +2242,8 @@ void nsRefreshDriver::UpdateRelevancyOfContentVisibilityAutoFrames() { mNeedToUpdateContentRelevancy = false; } -void nsRefreshDriver::NotifyResizeObservers() { - AUTO_PROFILER_LABEL_RELEVANT_FOR_JS("Notify ResizeObserver", LAYOUT); - if (!mNeedToUpdateResizeObservers) { - return; - } +void nsRefreshDriver::DetermineProximityToViewportAndNotifyResizeObservers() { + AUTO_PROFILER_LABEL_RELEVANT_FOR_JS("Update the rendering: step 14", LAYOUT); // NotifyResizeObservers might re-schedule us for next tick. mNeedToUpdateResizeObservers = false; @@ -2255,17 +2252,19 @@ void nsRefreshDriver::NotifyResizeObservers() { } AutoTArray, 32> documents; - if (mPresContext->Document()->HasResizeObservers()) { + if (mPresContext->Document()->HasResizeObservers() || + mPresContext->Document()->HasContentVisibilityAutoElements()) { documents.AppendElement(mPresContext->Document()); } mPresContext->Document()->CollectDescendantDocuments( documents, [](const Document* document) -> bool { - return document->HasResizeObservers(); + return document->HasResizeObservers() || + document->HasContentVisibilityAutoElements(); }); for (const RefPtr& doc : documents) { - MOZ_KnownLive(doc)->NotifyResizeObservers(); + MOZ_KnownLive(doc)->DetermineProximityToViewportAndNotifyResizeObservers(); } } @@ -2748,15 +2747,6 @@ void nsRefreshDriver::Tick(VsyncId aId, TimeStamp aNowTime, pm->UpdatePopupPositions(this); } - // Notify resize observers if any, see - // https://html.spec.whatwg.org/#update-the-rendering step 14. - NotifyResizeObservers(); - if (MOZ_UNLIKELY(!mPresContext || !mPresContext->GetPresShell())) { - // A resize observer callback apparently destroyed our PresContext. - StopTimer(); - return; - } - // Update the relevancy of the content of any `content-visibility: auto` // elements. The specification says: "Specifically, such changes will // take effect between steps 13 and 14 of Update the Rendering step of @@ -2765,6 +2755,16 @@ void nsRefreshDriver::Tick(VsyncId aId, TimeStamp aNowTime, // https://drafts.csswg.org/css-contain/#cv-notes UpdateRelevancyOfContentVisibilityAutoFrames(); + // Step 14 (https://html.spec.whatwg.org/#update-the-rendering). + // 1) Initial proximity to the viewport determination for + // content-visibility:auto elements and 2) Resize observers notifications. + DetermineProximityToViewportAndNotifyResizeObservers(); + if (MOZ_UNLIKELY(!mPresContext || !mPresContext->GetPresShell())) { + // A resize observer callback apparently destroyed our PresContext. + StopTimer(); + return; + } + UpdateIntersectionObservations(aNowTime); UpdateAnimatedImages(previousRefresh, aNowTime); diff --git a/layout/base/nsRefreshDriver.h b/layout/base/nsRefreshDriver.h index 295f1c8b94ce..43edf6d85d61 100644 --- a/layout/base/nsRefreshDriver.h +++ b/layout/base/nsRefreshDriver.h @@ -499,7 +499,8 @@ class nsRefreshDriver final : public mozilla::layers::TransactionIdAllocator, void RunFrameRequestCallbacks(mozilla::TimeStamp aNowTime); void UpdateIntersectionObservations(mozilla::TimeStamp aNowTime); void UpdateRelevancyOfContentVisibilityAutoFrames(); - MOZ_CAN_RUN_SCRIPT void NotifyResizeObservers(); + MOZ_CAN_RUN_SCRIPT void + DetermineProximityToViewportAndNotifyResizeObservers(); void MaybeIncreaseMeasuredTicksSinceLoading(); void EvaluateMediaQueriesAndReportChanges(); diff --git a/layout/generic/nsIFrame.cpp b/layout/generic/nsIFrame.cpp index 1242f8932a4a..ab3b38206113 100644 --- a/layout/generic/nsIFrame.cpp +++ b/layout/generic/nsIFrame.cpp @@ -745,9 +745,6 @@ void nsIFrame::InitPrimaryFrame() { if (StyleDisplay()->ContentVisibility(*this) == StyleContentVisibility::Auto) { PresShell()->RegisterContentVisibilityAutoFrame(this); - auto* element = Element::FromNodeOrNull(GetContent()); - MOZ_ASSERT(element); - PresContext()->Document()->ObserveForContentVisibility(*element); } else if (auto* element = Element::FromNodeOrNull(GetContent())) { element->ClearContentRelevancy(); } @@ -819,13 +816,6 @@ void nsIFrame::Destroy(DestroyContext& aContext) { } } - if (StyleDisplay()->ContentVisibility(*this) == - StyleContentVisibility::Auto) { - if (auto* element = Element::FromNodeOrNull(GetContent())) { - PresContext()->Document()->UnobserveForContentVisibility(*element); - } - } - // Disable visibility tracking. Note that we have to do this before we clear // frame properties and lose track of whether we were previously visible. // XXX(seth): It'd be ideal to assert that we're already marked nonvisible diff --git a/testing/web-platform/meta/css/css-contain/content-visibility/content-visibility-086.html.ini b/testing/web-platform/meta/css/css-contain/content-visibility/content-visibility-086.html.ini deleted file mode 100644 index 5a7680c86e56..000000000000 --- a/testing/web-platform/meta/css/css-contain/content-visibility/content-visibility-086.html.ini +++ /dev/null @@ -1,3 +0,0 @@ -[content-visibility-086.html] - [Content Visibility: innerText] - expected: [FAIL, PASS] diff --git a/testing/web-platform/meta/css/css-contain/content-visibility/content-visibility-auto-first-observation-immediate.html.ini b/testing/web-platform/meta/css/css-contain/content-visibility/content-visibility-auto-first-observation-immediate.html.ini deleted file mode 100644 index 77a107cccbb9..000000000000 --- a/testing/web-platform/meta/css/css-contain/content-visibility/content-visibility-auto-first-observation-immediate.html.ini +++ /dev/null @@ -1,3 +0,0 @@ -[content-visibility-auto-first-observation-immediate.html] - [Target is sized and laid out before resize observer] - expected: FAIL diff --git a/testing/web-platform/mozilla/tests/css/css-contain/content-visibility/content-visibility-auto-relevancy-updates-stop-ticking.html b/testing/web-platform/mozilla/tests/css/css-contain/content-visibility/content-visibility-auto-relevancy-updates-stop-ticking.html new file mode 100644 index 000000000000..57715be8a327 --- /dev/null +++ b/testing/web-platform/mozilla/tests/css/css-contain/content-visibility/content-visibility-auto-relevancy-updates-stop-ticking.html @@ -0,0 +1,139 @@ + + +Content Visibility: stop ticking after relevancy updates + + + + + + + + + + + +
+
+
+
+ Hello, World! +
+
+ +