Bug 1798485 - Add support for the contentvisibilityautostatechange event r=emilio

Add support for the contentvisibilityautostatechange and fire it when
the relevancy of `content-visibility: auto` elements change.

This commit also makes some changes to the
content-visibility-auto-state-changed.html test. Two more subtests are
added which verifies that an event is sent after `content-visibility:
auto` is applied to an element. Finally the `top` element is renamed to
`upper` as `top` can also refer to the top-level Window and it seems
that Gecko has a different precedence when accessing variables in
script.

Differential Revision: https://phabricator.services.mozilla.com/D161140
This commit is contained in:
Martin Robinson 2022-12-19 11:01:24 +00:00
parent 812f4325ff
commit 97f351c1d4
5 changed files with 85 additions and 20 deletions

View File

@ -0,0 +1,18 @@
/* -*- Mode: IDL; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this file,
* You can obtain one at http://mozilla.org/MPL/2.0/.
*
* https://drafts.csswg.org/css-contain-2/#content-visibility-auto-state-changed
*/
[Exposed=Window, Pref="layout.css.content-visibility.enabled"]
interface ContentVisibilityAutoStateChangeEvent : Event {
constructor(DOMString type,
optional ContentVisibilityAutoStateChangeEventInit eventInitDict = {});
readonly attribute boolean skipped;
};
dictionary ContentVisibilityAutoStateChangeEventInit : EventInit {
boolean skipped = false;
};

View File

@ -1147,6 +1147,7 @@ GENERATED_EVENTS_WEBIDL_FILES = [
"BlobEvent.webidl",
"CaretStateChangedEvent.webidl",
"CloseEvent.webidl",
"ContentVisibilityAutoStateChangeEvent.webidl",
"DeviceLightEvent.webidl",
"DeviceOrientationEvent.webidl",
"ErrorEvent.webidl",

View File

@ -19,6 +19,7 @@
#include "mozilla/DisplayPortUtils.h"
#include "mozilla/dom/CSSAnimation.h"
#include "mozilla/dom/CSSTransition.h"
#include "mozilla/dom/ContentVisibilityAutoStateChangeEvent.h"
#include "mozilla/dom/DocumentInlines.h"
#include "mozilla/dom/AncestorIterator.h"
#include "mozilla/dom/ElementInlines.h"
@ -7053,12 +7054,29 @@ void nsIFrame::UpdateIsRelevantContent(
element->SetContentRelevancy(newRelevancy);
}
if (overallRelevancyChanged) {
HandleLastRememberedSize();
PresShell()->FrameNeedsReflow(
this, IntrinsicDirty::FrameAncestorsAndDescendants, NS_FRAME_IS_DIRTY);
InvalidateFrame();
if (!overallRelevancyChanged) {
return;
}
HandleLastRememberedSize();
PresShell()->FrameNeedsReflow(
this, IntrinsicDirty::FrameAncestorsAndDescendants, NS_FRAME_IS_DIRTY);
InvalidateFrame();
ContentVisibilityAutoStateChangeEventInit init;
init.mSkipped = newRelevancy.isEmpty();
RefPtr<ContentVisibilityAutoStateChangeEvent> event =
ContentVisibilityAutoStateChangeEvent::Constructor(
element, u"contentvisibilityautostatechange"_ns, init);
// Per
// https://drafts.csswg.org/css-contain/#content-visibility-auto-state-changed
// "This event is dispatched by posting a task at the time when the state
// change occurs."
RefPtr<AsyncEventDispatcher> asyncDispatcher =
new AsyncEventDispatcher(element, event.get());
DebugOnly<nsresult> rv = asyncDispatcher->PostDOMEvent();
NS_ASSERTION(NS_SUCCEEDED(rv), "AsyncEventDispatcher failed to dispatch");
}
nsresult nsIFrame::CharacterDataChanged(const CharacterDataChangeInfo&) {

View File

@ -1,7 +0,0 @@
[content-visibility-auto-state-changed.html]
expected: TIMEOUT
[ContentVisibilityAutoStateChange fires when skipped]
expected: TIMEOUT
[ContentVisibilityAutoStateChange fires when not skipped]
expected: NOTRUN

View File

@ -21,24 +21,52 @@
.auto { content-visibility: auto; }
</style>
<div id=top class=auto></div>
<div id=upper></div>
<div class=spacer></div>
<div id=middle></div>
<div class=spacer></div>
<div id=bottom class=auto></div>
<div id=lower></div>
<script>
promise_test(t => new Promise(async (resolve, reject) => {
let shouldSkip = false;
const listener = (e) => {
if (!e.skipped)
resolve();
};
upper.addEventListener("contentvisibilityautostatechange", listener);
t.add_cleanup(() => upper.removeEventListener("contentvisibilityautostatechange", listener));
upper.classList.add("auto");
}), "ContentVisibilityAutoStateChange fires when relevant element gains `content-visibility:auto`");
promise_test(t => new Promise(async (resolve, reject) => {
let shouldSkip = false;
const listener = (e) => {
if (e.skipped)
resolve();
else
reject();
};
lower.addEventListener("contentvisibilityautostatechange", listener);
t.add_cleanup(() => lower.removeEventListener("contentvisibilityautostatechange", listener));
lower.classList.add("auto");
}), "ContentVisibilityAutoStateChange fires when not relevant element gains `content-visibility:auto`");
promise_test(t => new Promise(async (resolve, reject) => {
await new Promise((waited, _) => {
requestAnimationFrame(() => requestAnimationFrame(waited));
});
top.addEventListener("contentvisibilityautostatechange", (e) => {
const listener = (e) => {
if (e.skipped)
resolve();
else
reject();
});
};
upper.addEventListener("contentvisibilityautostatechange", listener);
t.add_cleanup(() => upper.removeEventListener("contentvisibilityautostatechange", listener));
requestAnimationFrame(() => requestAnimationFrame(() => {
middle.scrollIntoView();
}));
@ -49,15 +77,22 @@ promise_test(t => new Promise(async (resolve, reject) => {
requestAnimationFrame(() => requestAnimationFrame(waited));
});
bottom.addEventListener("contentvisibilityautostatechange", (e) => {
const listener = (e) => {
if (!e.skipped)
resolve();
else
reject();
}
lower.addEventListener("contentvisibilityautostatechange", listener);
t.add_cleanup(() => {
lower.removeEventListener("contentvisibilityautostatechange", listener);
});
requestAnimationFrame(() => requestAnimationFrame(() => {
bottom.scrollIntoView();
lower.scrollIntoView();
}));
}), "ContentVisibilityAutoStateChange fires when not skipped");
</script>