mirror of
https://github.com/mozilla/gecko-dev.git
synced 2024-11-26 22:32:46 +00:00
Bug 1866566 - If there is change in over-all relevancy, update HiddenByContentVisibility for animations, r=emilio,hiro
Differential Revision: https://phabricator.services.mozilla.com/D195009
This commit is contained in:
parent
1c04160996
commit
e88b28e248
@ -1953,6 +1953,22 @@ void Animation::SetHiddenByContentVisibility(bool hidden) {
|
||||
GetTimeline()->NotifyAnimationContentVisibilityChanged(this, !hidden);
|
||||
}
|
||||
|
||||
void Animation::UpdateHiddenByContentVisibility() {
|
||||
// To be consistent with nsIFrame::UpdateAnimationVisibility, here we only
|
||||
// deal with CSSAnimation and CSSTransition.
|
||||
if (!AsCSSAnimation() && !AsCSSTransition()) {
|
||||
return;
|
||||
}
|
||||
NonOwningAnimationTarget target = GetTargetForAnimation();
|
||||
if (!target) {
|
||||
return;
|
||||
}
|
||||
if (auto* frame = target.mElement->GetPrimaryFrame()) {
|
||||
SetHiddenByContentVisibility(
|
||||
frame->IsHiddenByContentVisibilityOnAnyAncestor());
|
||||
}
|
||||
}
|
||||
|
||||
StickyTimeDuration Animation::IntervalStartTime(
|
||||
const StickyTimeDuration& aActiveDuration) const {
|
||||
MOZ_ASSERT(AsCSSTransition() || AsCSSAnimation(),
|
||||
|
@ -399,6 +399,7 @@ class Animation : public DOMEventTargetHelper,
|
||||
bool IsHiddenByContentVisibility() const {
|
||||
return mHiddenByContentVisibility;
|
||||
}
|
||||
void UpdateHiddenByContentVisibility();
|
||||
|
||||
DocGroup* GetDocGroup();
|
||||
void SetSyncWithGeometricAnimations() { mSyncWithGeometricAnimations = true; }
|
||||
|
@ -112,4 +112,10 @@ void AnimationTimeline::NotifyAnimationContentVisibilityChanged(
|
||||
}
|
||||
}
|
||||
|
||||
void AnimationTimeline::UpdateHiddenByContentVisibility() {
|
||||
for (Animation* animation : mAnimations) {
|
||||
animation->UpdateHiddenByContentVisibility();
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace mozilla::dom
|
||||
|
@ -113,6 +113,7 @@ class AnimationTimeline : public nsISupports, public nsWrapperCache {
|
||||
virtual void RemoveAnimation(Animation* aAnimation);
|
||||
virtual void NotifyAnimationContentVisibilityChanged(Animation* aAnimation,
|
||||
bool aIsVisible);
|
||||
void UpdateHiddenByContentVisibility();
|
||||
|
||||
virtual Document* GetDocument() const = 0;
|
||||
|
||||
|
@ -263,6 +263,17 @@ const nsIScrollableFrame* ScrollTimeline::GetScrollFrame() const {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
void ScrollTimeline::NotifyAnimationContentVisibilityChanged(
|
||||
Animation* aAnimation, bool aIsVisible) {
|
||||
AnimationTimeline::NotifyAnimationContentVisibilityChanged(aAnimation,
|
||||
aIsVisible);
|
||||
if (mAnimationOrder.isEmpty()) {
|
||||
UnregisterFromScrollSource();
|
||||
} else {
|
||||
RegisterWithScrollSource();
|
||||
}
|
||||
}
|
||||
|
||||
// ------------------------------------
|
||||
// Methods of ProgressTimelineScheduler
|
||||
// ------------------------------------
|
||||
|
@ -197,6 +197,9 @@ class ScrollTimeline : public AnimationTimeline {
|
||||
PseudoStyleType aPseudoType,
|
||||
const StyleScrollTimeline& aNew);
|
||||
|
||||
void NotifyAnimationContentVisibilityChanged(Animation* aAnimation,
|
||||
bool aIsVisible) override;
|
||||
|
||||
protected:
|
||||
virtual ~ScrollTimeline() { Teardown(); }
|
||||
ScrollTimeline() = delete;
|
||||
|
@ -18983,4 +18983,9 @@ RadioGroupContainer& Document::OwnedRadioGroupContainer() {
|
||||
return *mRadioGroupContainer;
|
||||
}
|
||||
|
||||
void Document::UpdateHiddenByContentVisibilityForAnimations() {
|
||||
for (AnimationTimeline* timeline : Timelines()) {
|
||||
timeline->UpdateHiddenByContentVisibility();
|
||||
}
|
||||
}
|
||||
} // namespace mozilla::dom
|
||||
|
@ -3034,6 +3034,7 @@ class Document : public nsINode,
|
||||
|
||||
DocumentTimeline* Timeline();
|
||||
LinkedList<DocumentTimeline>& Timelines() { return mTimelines; }
|
||||
void UpdateHiddenByContentVisibilityForAnimations();
|
||||
|
||||
SVGSVGElement* GetSVGRootElement() const;
|
||||
|
||||
|
@ -11950,9 +11950,16 @@ void PresShell::UpdateRelevancyOfContentVisibilityAutoFrames() {
|
||||
return;
|
||||
}
|
||||
|
||||
bool isRelevantContentChanged = false;
|
||||
for (nsIFrame* frame : mContentVisibilityAutoFrames) {
|
||||
isRelevantContentChanged |=
|
||||
frame->UpdateIsRelevantContent(mContentVisibilityRelevancyToUpdate);
|
||||
}
|
||||
if (isRelevantContentChanged) {
|
||||
if (nsPresContext* presContext = GetPresContext()) {
|
||||
presContext->UpdateHiddenByContentVisibilityForAnimations();
|
||||
}
|
||||
}
|
||||
|
||||
mContentVisibilityRelevancyToUpdate.clear();
|
||||
}
|
||||
@ -11985,6 +11992,7 @@ PresShell::ProximityToViewportResult PresShell::DetermineProximityToViewport() {
|
||||
auto input = DOMIntersectionObserver::ComputeInput(
|
||||
*mDocument, /* aRoot = */ nullptr, &rootMargin);
|
||||
|
||||
bool isRelevantContentChanged = false;
|
||||
for (nsIFrame* frame : mContentVisibilityAutoFrames) {
|
||||
auto* element = frame->GetContent()->AsElement();
|
||||
result.mAnyScrollIntoViewFlag |=
|
||||
@ -12005,6 +12013,7 @@ PresShell::ProximityToViewportResult PresShell::DetermineProximityToViewport() {
|
||||
.Intersects();
|
||||
element->SetVisibleForContentVisibility(intersects);
|
||||
if (oldVisibility.isNothing() || *oldVisibility != intersects) {
|
||||
isRelevantContentChanged |=
|
||||
frame->UpdateIsRelevantContent(ContentRelevancyReason::Visible);
|
||||
}
|
||||
|
||||
@ -12013,6 +12022,11 @@ PresShell::ProximityToViewportResult PresShell::DetermineProximityToViewport() {
|
||||
result.mHadInitialDetermination = true;
|
||||
}
|
||||
}
|
||||
if (isRelevantContentChanged) {
|
||||
if (nsPresContext* presContext = GetPresContext()) {
|
||||
presContext->UpdateHiddenByContentVisibilityForAnimations();
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
@ -3077,6 +3077,11 @@ PerformanceMainThread* nsPresContext::GetPerformanceMainThread() const {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
void nsPresContext::UpdateHiddenByContentVisibilityForAnimations() {
|
||||
mDocument->UpdateHiddenByContentVisibilityForAnimations();
|
||||
TimelineManager()->UpdateHiddenByContentVisibilityForAnimations();
|
||||
}
|
||||
|
||||
#ifdef DEBUG
|
||||
|
||||
void nsPresContext::ValidatePresShellAndDocumentReleation() const {
|
||||
|
@ -1079,6 +1079,8 @@ class nsPresContext : public nsISupports, public mozilla::SupportsWeakPtr {
|
||||
return mFontPaletteValueSet;
|
||||
}
|
||||
|
||||
void UpdateHiddenByContentVisibilityForAnimations();
|
||||
|
||||
protected:
|
||||
friend class nsRunnableMethod<nsPresContext>;
|
||||
void ThemeChangedInternal();
|
||||
|
@ -7003,7 +7003,7 @@ bool nsIFrame::IsDescendantOfTopLayerElement() const {
|
||||
return false;
|
||||
}
|
||||
|
||||
void nsIFrame::UpdateIsRelevantContent(
|
||||
bool nsIFrame::UpdateIsRelevantContent(
|
||||
const ContentRelevancy& aRelevancyToUpdate) {
|
||||
MOZ_ASSERT(StyleDisplay()->ContentVisibility(*this) ==
|
||||
StyleContentVisibility::Auto);
|
||||
@ -7053,7 +7053,7 @@ void nsIFrame::UpdateIsRelevantContent(
|
||||
}
|
||||
|
||||
if (!overallRelevancyChanged) {
|
||||
return;
|
||||
return false;
|
||||
}
|
||||
|
||||
HandleLastRememberedSize();
|
||||
@ -7075,6 +7075,7 @@ void nsIFrame::UpdateIsRelevantContent(
|
||||
new AsyncEventDispatcher(element, event.forget());
|
||||
DebugOnly<nsresult> rv = asyncDispatcher->PostDOMEvent();
|
||||
NS_ASSERTION(NS_SUCCEEDED(rv), "AsyncEventDispatcher failed to dispatch");
|
||||
return true;
|
||||
}
|
||||
|
||||
nsresult nsIFrame::CharacterDataChanged(const CharacterDataChangeInfo&) {
|
||||
|
@ -3303,8 +3303,10 @@ class nsIFrame : public nsQueryFrame {
|
||||
* Update the whether or not this frame is considered relevant content for the
|
||||
* purposes of `content-visibility: auto` according to the rules specified in
|
||||
* https://drafts.csswg.org/css-contain-2/#relevant-to-the-user.
|
||||
* Returns true if the over-all relevancy changed.
|
||||
*/
|
||||
void UpdateIsRelevantContent(const ContentRelevancy& aRelevancyToUpdate);
|
||||
[[nodiscard]] bool UpdateIsRelevantContent(
|
||||
const ContentRelevancy& aRelevancyToUpdate);
|
||||
|
||||
/**
|
||||
* Get the "type" of the frame.
|
||||
|
@ -55,6 +55,7 @@ class TimelineCollection final
|
||||
// if it does not already exist.
|
||||
static TimelineCollection* Get(const dom::Element* aElement,
|
||||
PseudoStyleType aPseudoType);
|
||||
const TimelineMap& Timelines() const { return mTimelines; }
|
||||
|
||||
private:
|
||||
// The element. Weak reference is fine since it owns us.
|
||||
|
@ -168,4 +168,20 @@ void TimelineManager::DoUpdateTimelines(
|
||||
// siblings when mutating {scroll|view}-timeline-name.
|
||||
}
|
||||
|
||||
void TimelineManager::UpdateHiddenByContentVisibilityForAnimations() {
|
||||
for (auto* scrollTimelineCollection : mScrollTimelineCollections) {
|
||||
for (ScrollTimeline* timeline :
|
||||
scrollTimelineCollection->Timelines().Values()) {
|
||||
timeline->UpdateHiddenByContentVisibility();
|
||||
}
|
||||
}
|
||||
|
||||
for (auto* viewTimelineCollection : mViewTimelineCollections) {
|
||||
for (ViewTimeline* timeline :
|
||||
viewTimelineCollection->Timelines().Values()) {
|
||||
timeline->UpdateHiddenByContentVisibility();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace mozilla
|
||||
|
@ -51,6 +51,8 @@ class TimelineManager {
|
||||
const ComputedStyle* aComputedStyle,
|
||||
ProgressTimelineType aType);
|
||||
|
||||
void UpdateHiddenByContentVisibilityForAnimations();
|
||||
|
||||
private:
|
||||
template <typename StyleType, typename TimelineType>
|
||||
void DoUpdateTimelines(nsPresContext* aPresContext, dom::Element* aElement,
|
||||
|
@ -1,2 +0,0 @@
|
||||
[content-visibility-animation-and-scroll.html]
|
||||
expected: TIMEOUT
|
@ -1,13 +1,4 @@
|
||||
[content-visibility-animation-in-auto-subtree.html]
|
||||
expected: TIMEOUT
|
||||
[Animation events do not fire for a CSS animation running in a content visibility subtree]
|
||||
expected: TIMEOUT
|
||||
|
||||
[The finished promise does not resolve due to the normal passage of time for a CSS animation in a content visibility subtree]
|
||||
expected: NOTRUN
|
||||
|
||||
[The finished promise does not resolve due to the normal passage of time for a CSS transition in a content visibility subtree]
|
||||
expected: NOTRUN
|
||||
|
||||
[Events and promises are handled normally for animations without an owning element]
|
||||
expected: NOTRUN
|
||||
expected: TIMEOUT
|
||||
|
@ -0,0 +1,2 @@
|
||||
[content-visibility-animation-with-scroll-timeline-in-auto-subtree.html]
|
||||
prefs: [layout.css.scroll-driven-animations.enabled:true]
|
@ -0,0 +1,2 @@
|
||||
[content-visibility-animation-with-scroll-timeline-in-hidden-subtree.html]
|
||||
prefs: [layout.css.scroll-driven-animations.enabled:true]
|
@ -14,7 +14,7 @@
|
||||
to { opacity: 0; }
|
||||
}
|
||||
#target {
|
||||
background: 'green';
|
||||
background: green;
|
||||
height: 100px;
|
||||
width: 100px;
|
||||
}
|
||||
|
@ -0,0 +1,82 @@
|
||||
<!DOCTYPE html>
|
||||
<meta charset=utf8>
|
||||
<title>Test getComputedStyle on a CSS animation with scroll timeline in a content visibility subtree using content-visibility: auto</title>
|
||||
<link rel="help" href="https://drafts.csswg.org/css-contain-2/">
|
||||
<script src="/web-animations/testcommon.js"></script>
|
||||
<script src="/resources/testharness.js"></script>
|
||||
<script src="/resources/testharnessreport.js"></script>
|
||||
<style>
|
||||
#container {
|
||||
content-visibility: auto;
|
||||
}
|
||||
|
||||
#scrollContainer {
|
||||
height: 100vh;
|
||||
overflow-y: scroll;
|
||||
scroll-timeline-name: --targetTimeline;
|
||||
}
|
||||
#innerspacer {
|
||||
height: 300vh;
|
||||
}
|
||||
@keyframes fade {
|
||||
from { opacity: 1; }
|
||||
to { opacity: 0; }
|
||||
}
|
||||
#target {
|
||||
background: green;
|
||||
height: 100px;
|
||||
width: 100px;
|
||||
}
|
||||
.animate {
|
||||
animation-name: fade;
|
||||
animation-duration: 1ms;
|
||||
animation-direction: alternate;
|
||||
animation-timeline: --targetTimeline;
|
||||
}
|
||||
|
||||
</style>
|
||||
<body>
|
||||
<div id="log"></div>
|
||||
<div id="spacer"></div>
|
||||
<div id="scrollContainer">
|
||||
<div id="container"></div>
|
||||
<div id="innerspacer"></div>
|
||||
</div>
|
||||
|
||||
</body>
|
||||
<script>
|
||||
"use strict";
|
||||
|
||||
function createAnimatingElement(test, name) {
|
||||
const container = document.getElementById('container');
|
||||
const target = document.createElement('div');
|
||||
container.appendChild(target);
|
||||
target.id = 'target';
|
||||
target.className = name;
|
||||
return target;
|
||||
}
|
||||
|
||||
promise_test(async t => {
|
||||
const container = document.getElementById('container');
|
||||
const target = createAnimatingElement(t, 'animate');
|
||||
scrollContainer.scrollTop = 10000;
|
||||
const animation = target.getAnimations()[0];
|
||||
await animation.ready;
|
||||
await waitForAnimationFrames(1);
|
||||
let expectedOpacity = parseFloat(getComputedStyle(target).opacity);
|
||||
assert_approx_equals(expectedOpacity, 0, 0.1, 'scrollContainer scrolls to bottom, so the opacity should be 0');
|
||||
document.getElementById('spacer').style.height = '300vh';
|
||||
await waitForAnimationFrames(1);
|
||||
assert_equals(parseFloat(getComputedStyle(target).opacity), expectedOpacity, 'Opacity does not change when it is hidden by c-v');
|
||||
|
||||
scrollContainer.scrollTop = 0;
|
||||
assert_equals(parseFloat(getComputedStyle(target).opacity), expectedOpacity, 'The animation is hidden by c-v, so opacity does not change even if scrollTop changes');
|
||||
|
||||
await waitForAnimationFrames(2);
|
||||
|
||||
document.getElementById('spacer').style.height = '0vh';
|
||||
await waitForAnimationFrames(2);
|
||||
assert_equals(getComputedStyle(target).opacity, '1', 'Now that the animation is visible, opacity should be updated');
|
||||
}, 'Animation with scroll-timeline should be affected c-v');
|
||||
|
||||
</script>
|
@ -0,0 +1,81 @@
|
||||
<!DOCTYPE html>
|
||||
<meta charset=utf8>
|
||||
<title>Test getComputedStyle on a CSS animation with scroll-timeline in a content-visibility subtree</title>
|
||||
<link rel="help" href="https://drafts.csswg.org/css-contain-2/">
|
||||
<script src="/web-animations/testcommon.js"></script>
|
||||
<script src="/resources/testharness.js"></script>
|
||||
<script src="/resources/testharnessreport.js"></script>
|
||||
<style>
|
||||
#container {
|
||||
content-visibility: visible;
|
||||
}
|
||||
|
||||
#scrollContainer {
|
||||
height: 100vh;
|
||||
overflow-y: scroll;
|
||||
scroll-timeline-name: --targetTimeline;
|
||||
}
|
||||
#innerspacer {
|
||||
height: 300vh;
|
||||
}
|
||||
@keyframes fade {
|
||||
from { opacity: 1; }
|
||||
to { opacity: 0; }
|
||||
}
|
||||
#target {
|
||||
background: green;
|
||||
height: 100px;
|
||||
width: 100px;
|
||||
}
|
||||
.animate {
|
||||
animation-name: fade;
|
||||
animation-duration: 1ms;
|
||||
animation-direction: alternate;
|
||||
animation-timeline: --targetTimeline;
|
||||
}
|
||||
|
||||
</style>
|
||||
<body>
|
||||
<div id="log"></div>
|
||||
<div id="scrollContainer">
|
||||
<div id="container"></div>
|
||||
<div id="innerspacer"></div>
|
||||
</div>
|
||||
|
||||
</body>
|
||||
<script>
|
||||
"use strict";
|
||||
|
||||
function createAnimatingElement(test, name) {
|
||||
const container = document.getElementById('container');
|
||||
const target = document.createElement('div');
|
||||
container.appendChild(target);
|
||||
target.id = 'target';
|
||||
target.className = name;
|
||||
return target;
|
||||
}
|
||||
|
||||
promise_test(async t => {
|
||||
const container = document.getElementById('container');
|
||||
const target = createAnimatingElement(t, 'animate');
|
||||
scrollContainer.scrollTop = 10000;
|
||||
const animation = target.getAnimations()[0];
|
||||
await animation.ready;
|
||||
await waitForAnimationFrames(1);
|
||||
let expectedOpacity = parseFloat(getComputedStyle(target).opacity);
|
||||
assert_approx_equals(expectedOpacity, 0, 0.1, 'scrollContainer scrolls to bottom, so the opacity should be 0');
|
||||
document.getElementById('container').style.contentVisibility = 'hidden';
|
||||
await waitForAnimationFrames(1);
|
||||
assert_equals(parseFloat(getComputedStyle(target).opacity), expectedOpacity, 'Opacity does not change when it is hidden by c-v');
|
||||
|
||||
scrollContainer.scrollTop = 0;
|
||||
assert_equals(parseFloat(getComputedStyle(target).opacity), expectedOpacity, 'The animation is hidden by c-v, so opacity does not change even if scrollTop changes');
|
||||
|
||||
await waitForAnimationFrames(2);
|
||||
|
||||
document.getElementById('container').style.contentVisibility = 'visible';
|
||||
await waitForAnimationFrames(2);
|
||||
assert_approx_equals(parseFloat(getComputedStyle(target).opacity), 1, 0.1, 'Now that the animation is visible, opacity should be updated');
|
||||
}, 'Animation with scroll-timeline should be affected c-v');
|
||||
|
||||
</script>
|
@ -0,0 +1,74 @@
|
||||
<!DOCTYPE html>
|
||||
<meta charset=utf8>
|
||||
<title>Web Animation does not stop even if target is hidden by c-v</title>
|
||||
<link rel="help" href="https://drafts.csswg.org/css-contain-2/">
|
||||
<script src="/web-animations/testcommon.js"></script>
|
||||
<script src="/resources/testharness.js"></script>
|
||||
<script src="/resources/testharnessreport.js"></script>
|
||||
<style>
|
||||
#container {
|
||||
content-visibility: auto;
|
||||
}
|
||||
@keyframes fade {
|
||||
from { opacity: 1; }
|
||||
to { opacity: 0; }
|
||||
}
|
||||
#target {
|
||||
background: green;
|
||||
height: 100px;
|
||||
width: 100px;
|
||||
}
|
||||
.animate {
|
||||
animation: fade 1s linear 2 alternate;
|
||||
}
|
||||
.transition {
|
||||
transition: opacity 1s linear;
|
||||
}
|
||||
</style>
|
||||
<body>
|
||||
<div id="spacer"></div>
|
||||
<div id="container"></div>
|
||||
</body>
|
||||
<script>
|
||||
"use strict";
|
||||
|
||||
function createElementWithWebAnimation(test) {
|
||||
const container = document.getElementById('container');
|
||||
const target = document.createElement('div');
|
||||
container.appendChild(target);
|
||||
target.id = 'target';
|
||||
const keyframes = [
|
||||
{ opacity: 1 },
|
||||
{ opacity: 0 },
|
||||
];
|
||||
const options = {
|
||||
duration: 2000,
|
||||
iterations: 1,
|
||||
easing: 'linear',
|
||||
direction: 'alternate',
|
||||
};
|
||||
target.animate(keyframes, options);
|
||||
|
||||
return target;
|
||||
}
|
||||
|
||||
promise_test(async t => {
|
||||
// Make sure the target is hidden from the beginning.
|
||||
document.getElementById("spacer").style.height = "300vh";
|
||||
const target = createElementWithWebAnimation(t);
|
||||
const animation = target.getAnimations()[0];
|
||||
|
||||
let animationFinishEvent = false;
|
||||
animation.addEventListener('finish', () => {
|
||||
animationFinishEvent = true;
|
||||
});
|
||||
|
||||
animation.currentTime = 1999;
|
||||
await animation.ready;
|
||||
await waitForAnimationFrames(2);
|
||||
|
||||
assert_true(animationFinishEvent,
|
||||
'Web Animation event should keep going even if target is hidden by c-v');
|
||||
}, 'Web Animation does not stop even if target is hidden by c-v');
|
||||
|
||||
</script>
|
Loading…
Reference in New Issue
Block a user