зеркало из https://github.com/mozilla/gecko-dev.git
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:
Родитель
b0e0c758e9
Коммит
e7e16e453b
|
@ -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 observer’s 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 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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>
|
Загрузка…
Ссылка в новой задаче