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
This commit is contained in:
Frederic Wang 2023-11-27 08:26:46 +00:00
Родитель b0e0c758e9
Коммит e7e16e453b
14 изменённых файлов: 290 добавлений и 142 удалений

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

@ -160,20 +160,6 @@ static void LazyLoadCallback(
}
}
static void ContentVisibilityCallback(
const Sequence<OwningNonNull<DOMIntersectionObserverEntry>>& aEntries) {
for (const auto& entry : aEntries) {
entry->Target()->SetVisibleForContentVisibility(entry->IsIntersecting());
if (RefPtr<Document> doc = entry->Target()->GetComposedDoc()) {
if (RefPtr<PresShell> 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>
DOMIntersectionObserver::CreateContentVisibilityObserver(Document& aDocument) {
RefPtr<DOMIntersectionObserver> 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<nsRect> ComputeTheIntersection(
nsIFrame* aTarget, nsIFrame* aRoot, const nsRect& aRootBounds,
const Maybe<nsRect>& 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<nsRect> 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 observers intersection root.
Maybe<nsRect> 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 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, isContentVisibilityObserver);
IntersectionOutput output = Intersect(input, *target);
// 2.5. Let targetArea be targetRects 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);
}
}
}
}

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

@ -152,10 +152,10 @@ class DOMIntersectionObserver final : public nsISupports,
const Document& aDocument, const nsINode* aRoot,
const StyleRect<LengthPercentage>* 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<DOMIntersectionObserver> CreateLazyLoadObserver(
Document&);
static already_AddRefed<DOMIntersectionObserver>
CreateContentVisibilityObserver(Document&);
static Maybe<nsRect> EdgeInclusiveIntersection(const nsRect& aRect,
const nsRect& aOtherRect);

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

@ -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<uint32_t> oldShallowestTargetDepth = shallowestTargetDepth;
shallowestTargetDepth = BroadcastAllActiveResizeObservations();
NS_ASSERTION(oldShallowestTargetDepth < shallowestTargetDepth,

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

@ -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<DOMIntersectionObserver> mLazyLoadObserver;
// Used for detecting when `content-visibility: auto` elements are near
// or far from the viewport.
RefPtr<DOMIntersectionObserver> mContentVisibilityObserver;
// ResizeObserver for storing and removing the last remembered size.
// @see {@link https://drafts.csswg.org/css-sizing-4/#last-remembered}
RefPtr<ResizeObserver> mLastRememberedSizeObserver;

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

@ -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<ContentRelevancy> 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<bool> mVisibleForContentVisibility;

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

@ -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();

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

@ -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<LengthPercentage>::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<bool> 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)) {

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

@ -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();

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

@ -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<RefPtr<Document>, 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<Document>& 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);

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

@ -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();

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

@ -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

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

@ -1,3 +0,0 @@
[content-visibility-086.html]
[Content Visibility: innerText]
expected: [FAIL, PASS]

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

@ -1,3 +0,0 @@
[content-visibility-auto-first-observation-immediate.html]
[Target is sized and laid out before resize observer]
expected: FAIL

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

@ -0,0 +1,139 @@
<!doctype html>
<meta charset="utf-8">
<title>Content Visibility: stop ticking after relevancy updates</title>
<!--
Copied from testing/web-platform/tests/css/css-contain/content-visibility/content-visibility-auto-relevancy-updates.html
-->
<link rel="author" title="Frédéric Wang" href="mailto:fwang@igalia.com">
<link rel="help" href="https://drafts.csswg.org/css-contain/#content-visibility">
<link rel="help" href="https://drafts.csswg.org/css-contain/#relevant-to-the-user">
<link rel="help" href="https://drafts.csswg.org/css-contain/#cv-notes">
<meta name="assert" content="Verify relevancy is properly updated for content-visibility: auto elements and refresh driver stops ticking after such update.">
<script src="/resources/testharness.js"></script>
<script src="/resources/testharnessreport.js"></script>
<style>
#spacer {
height: 300vh;
}
#contentVisibilityAuto {
content-visibility: auto;
border: solid;
}
</style>
<div>
<div id="log"></div>
<div tabindex="1" id="spacer"></div>
<div tabindex="2" id="contentVisibilityAuto">
<span>Hello, World!</span>
</div>
</div>
<script>
function hasPendingTick() {
return SpecialPowers.wrap(window).windowUtils.refreshDriverHasPendingTick;
}
// See comment in layout/base/tests/test_bug1756118.html about why the timeouts
// etc.
async function expectTicksToStop() {
for (let i = 0; i < 100; i++) {
await new Promise(r => setTimeout(r, 8));
if(!hasPendingTick()) {
break;
}
}
assert_false(hasPendingTick(), "refresh driver should have eventually stopped ticking");
}
function tick() {
return new Promise(r => {
requestAnimationFrame(() => requestAnimationFrame(r));
});
}
function contentVisibilityAutoElementIsRelevant() {
// A content-visibility: auto element that is not relevant skips its contents,
// which do not contribute to the result of innerText.
return contentVisibilityAuto.innerText.length > 0;
}
function clearRelevancyReasons() {
window.scrollTo(0, 0);
spacer.focus({preventScroll: true});
window.getSelection().empty();
}
promise_test(async function(t) {
// Wait for page load.
await new Promise(resolve => { window.addEventListener("load", resolve); });
// Register cleanup method to reset relevancy.
t.add_cleanup(clearRelevancyReasons);
// Element should initially not be relevant and ticking should have stopped.
await SpecialPowers.pushPrefEnv({'set':
[['layout.keep_ticking_after_load_ms', 0]]});
await tick();
await expectTicksToStop();
assert_false(contentVisibilityAutoElementIsRelevant(), "initial relevancy");
// Make element close to the viewport.
contentVisibilityAuto.firstElementChild.scrollIntoView();
await tick();
await expectTicksToStop();
assert_true(contentVisibilityAutoElementIsRelevant(), "close to viewport");
// Scroll away from the element again.
window.scrollTo(0, 0);
await tick();
await expectTicksToStop();
assert_false(contentVisibilityAutoElementIsRelevant(), "far from viewport");
}, "Relevancy updated after changing proximity to the viewport.");
promise_test(async function(t) {
// Register cleanup method to reset relevancy.
t.add_cleanup(clearRelevancyReasons);
// Element should initially not be relevant and no ticking be in progress.
await tick();
await expectTicksToStop();
assert_false(contentVisibilityAutoElementIsRelevant(), "initial relevancy");
// Focus the element.
contentVisibilityAuto.focus({preventScroll: true});
await tick();
await expectTicksToStop();
assert_true(contentVisibilityAutoElementIsRelevant(), "focused");
// Unfocus the element again.
spacer.focus({preventScroll: true});
await tick();
await expectTicksToStop();
assert_false(contentVisibilityAutoElementIsRelevant(), "unfocused");
}, "Relevancy updated after being focused/unfocused.");
promise_test(async function(t) {
// Register cleanup method to reset relevancy.
t.add_cleanup(clearRelevancyReasons);
// Element should initially not be relevant and no ticking be in progress.
await tick();
await expectTicksToStop();
assert_false(contentVisibilityAutoElementIsRelevant(), "initial relevancy");
// Select the contents of the element.
window.getSelection().selectAllChildren(contentVisibilityAuto);
await tick();
await expectTicksToStop();
assert_true(contentVisibilityAutoElementIsRelevant(), "selected");
// Unselect the contents of the element.
window.getSelection().empty();
await tick();
await expectTicksToStop();
assert_false(contentVisibilityAutoElementIsRelevant(), "unselected");
}, "Relevancy updated after being selected/unselected.");
</script>