forked from mirrors/gecko-dev
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
24 changed files with 335 additions and 18 deletions
|
|
@ -1953,6 +1953,22 @@ void Animation::SetHiddenByContentVisibility(bool hidden) {
|
||||||
GetTimeline()->NotifyAnimationContentVisibilityChanged(this, !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(
|
StickyTimeDuration Animation::IntervalStartTime(
|
||||||
const StickyTimeDuration& aActiveDuration) const {
|
const StickyTimeDuration& aActiveDuration) const {
|
||||||
MOZ_ASSERT(AsCSSTransition() || AsCSSAnimation(),
|
MOZ_ASSERT(AsCSSTransition() || AsCSSAnimation(),
|
||||||
|
|
|
||||||
|
|
@ -399,6 +399,7 @@ class Animation : public DOMEventTargetHelper,
|
||||||
bool IsHiddenByContentVisibility() const {
|
bool IsHiddenByContentVisibility() const {
|
||||||
return mHiddenByContentVisibility;
|
return mHiddenByContentVisibility;
|
||||||
}
|
}
|
||||||
|
void UpdateHiddenByContentVisibility();
|
||||||
|
|
||||||
DocGroup* GetDocGroup();
|
DocGroup* GetDocGroup();
|
||||||
void SetSyncWithGeometricAnimations() { mSyncWithGeometricAnimations = true; }
|
void SetSyncWithGeometricAnimations() { mSyncWithGeometricAnimations = true; }
|
||||||
|
|
|
||||||
|
|
@ -112,4 +112,10 @@ void AnimationTimeline::NotifyAnimationContentVisibilityChanged(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void AnimationTimeline::UpdateHiddenByContentVisibility() {
|
||||||
|
for (Animation* animation : mAnimations) {
|
||||||
|
animation->UpdateHiddenByContentVisibility();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
} // namespace mozilla::dom
|
} // namespace mozilla::dom
|
||||||
|
|
|
||||||
|
|
@ -113,6 +113,7 @@ class AnimationTimeline : public nsISupports, public nsWrapperCache {
|
||||||
virtual void RemoveAnimation(Animation* aAnimation);
|
virtual void RemoveAnimation(Animation* aAnimation);
|
||||||
virtual void NotifyAnimationContentVisibilityChanged(Animation* aAnimation,
|
virtual void NotifyAnimationContentVisibilityChanged(Animation* aAnimation,
|
||||||
bool aIsVisible);
|
bool aIsVisible);
|
||||||
|
void UpdateHiddenByContentVisibility();
|
||||||
|
|
||||||
virtual Document* GetDocument() const = 0;
|
virtual Document* GetDocument() const = 0;
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -263,6 +263,17 @@ const nsIScrollableFrame* ScrollTimeline::GetScrollFrame() const {
|
||||||
return nullptr;
|
return nullptr;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void ScrollTimeline::NotifyAnimationContentVisibilityChanged(
|
||||||
|
Animation* aAnimation, bool aIsVisible) {
|
||||||
|
AnimationTimeline::NotifyAnimationContentVisibilityChanged(aAnimation,
|
||||||
|
aIsVisible);
|
||||||
|
if (mAnimationOrder.isEmpty()) {
|
||||||
|
UnregisterFromScrollSource();
|
||||||
|
} else {
|
||||||
|
RegisterWithScrollSource();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// ------------------------------------
|
// ------------------------------------
|
||||||
// Methods of ProgressTimelineScheduler
|
// Methods of ProgressTimelineScheduler
|
||||||
// ------------------------------------
|
// ------------------------------------
|
||||||
|
|
|
||||||
|
|
@ -197,6 +197,9 @@ class ScrollTimeline : public AnimationTimeline {
|
||||||
PseudoStyleType aPseudoType,
|
PseudoStyleType aPseudoType,
|
||||||
const StyleScrollTimeline& aNew);
|
const StyleScrollTimeline& aNew);
|
||||||
|
|
||||||
|
void NotifyAnimationContentVisibilityChanged(Animation* aAnimation,
|
||||||
|
bool aIsVisible) override;
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
virtual ~ScrollTimeline() { Teardown(); }
|
virtual ~ScrollTimeline() { Teardown(); }
|
||||||
ScrollTimeline() = delete;
|
ScrollTimeline() = delete;
|
||||||
|
|
|
||||||
|
|
@ -18983,4 +18983,9 @@ RadioGroupContainer& Document::OwnedRadioGroupContainer() {
|
||||||
return *mRadioGroupContainer;
|
return *mRadioGroupContainer;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void Document::UpdateHiddenByContentVisibilityForAnimations() {
|
||||||
|
for (AnimationTimeline* timeline : Timelines()) {
|
||||||
|
timeline->UpdateHiddenByContentVisibility();
|
||||||
|
}
|
||||||
|
}
|
||||||
} // namespace mozilla::dom
|
} // namespace mozilla::dom
|
||||||
|
|
|
||||||
|
|
@ -3034,6 +3034,7 @@ class Document : public nsINode,
|
||||||
|
|
||||||
DocumentTimeline* Timeline();
|
DocumentTimeline* Timeline();
|
||||||
LinkedList<DocumentTimeline>& Timelines() { return mTimelines; }
|
LinkedList<DocumentTimeline>& Timelines() { return mTimelines; }
|
||||||
|
void UpdateHiddenByContentVisibilityForAnimations();
|
||||||
|
|
||||||
SVGSVGElement* GetSVGRootElement() const;
|
SVGSVGElement* GetSVGRootElement() const;
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -11950,8 +11950,15 @@ void PresShell::UpdateRelevancyOfContentVisibilityAutoFrames() {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool isRelevantContentChanged = false;
|
||||||
for (nsIFrame* frame : mContentVisibilityAutoFrames) {
|
for (nsIFrame* frame : mContentVisibilityAutoFrames) {
|
||||||
frame->UpdateIsRelevantContent(mContentVisibilityRelevancyToUpdate);
|
isRelevantContentChanged |=
|
||||||
|
frame->UpdateIsRelevantContent(mContentVisibilityRelevancyToUpdate);
|
||||||
|
}
|
||||||
|
if (isRelevantContentChanged) {
|
||||||
|
if (nsPresContext* presContext = GetPresContext()) {
|
||||||
|
presContext->UpdateHiddenByContentVisibilityForAnimations();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
mContentVisibilityRelevancyToUpdate.clear();
|
mContentVisibilityRelevancyToUpdate.clear();
|
||||||
|
|
@ -11985,6 +11992,7 @@ PresShell::ProximityToViewportResult PresShell::DetermineProximityToViewport() {
|
||||||
auto input = DOMIntersectionObserver::ComputeInput(
|
auto input = DOMIntersectionObserver::ComputeInput(
|
||||||
*mDocument, /* aRoot = */ nullptr, &rootMargin);
|
*mDocument, /* aRoot = */ nullptr, &rootMargin);
|
||||||
|
|
||||||
|
bool isRelevantContentChanged = false;
|
||||||
for (nsIFrame* frame : mContentVisibilityAutoFrames) {
|
for (nsIFrame* frame : mContentVisibilityAutoFrames) {
|
||||||
auto* element = frame->GetContent()->AsElement();
|
auto* element = frame->GetContent()->AsElement();
|
||||||
result.mAnyScrollIntoViewFlag |=
|
result.mAnyScrollIntoViewFlag |=
|
||||||
|
|
@ -12005,7 +12013,8 @@ PresShell::ProximityToViewportResult PresShell::DetermineProximityToViewport() {
|
||||||
.Intersects();
|
.Intersects();
|
||||||
element->SetVisibleForContentVisibility(intersects);
|
element->SetVisibleForContentVisibility(intersects);
|
||||||
if (oldVisibility.isNothing() || *oldVisibility != intersects) {
|
if (oldVisibility.isNothing() || *oldVisibility != intersects) {
|
||||||
frame->UpdateIsRelevantContent(ContentRelevancyReason::Visible);
|
isRelevantContentChanged |=
|
||||||
|
frame->UpdateIsRelevantContent(ContentRelevancyReason::Visible);
|
||||||
}
|
}
|
||||||
|
|
||||||
// 14.2.3.3
|
// 14.2.3.3
|
||||||
|
|
@ -12013,6 +12022,11 @@ PresShell::ProximityToViewportResult PresShell::DetermineProximityToViewport() {
|
||||||
result.mHadInitialDetermination = true;
|
result.mHadInitialDetermination = true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if (isRelevantContentChanged) {
|
||||||
|
if (nsPresContext* presContext = GetPresContext()) {
|
||||||
|
presContext->UpdateHiddenByContentVisibilityForAnimations();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -3077,6 +3077,11 @@ PerformanceMainThread* nsPresContext::GetPerformanceMainThread() const {
|
||||||
return nullptr;
|
return nullptr;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void nsPresContext::UpdateHiddenByContentVisibilityForAnimations() {
|
||||||
|
mDocument->UpdateHiddenByContentVisibilityForAnimations();
|
||||||
|
TimelineManager()->UpdateHiddenByContentVisibilityForAnimations();
|
||||||
|
}
|
||||||
|
|
||||||
#ifdef DEBUG
|
#ifdef DEBUG
|
||||||
|
|
||||||
void nsPresContext::ValidatePresShellAndDocumentReleation() const {
|
void nsPresContext::ValidatePresShellAndDocumentReleation() const {
|
||||||
|
|
|
||||||
|
|
@ -1079,6 +1079,8 @@ class nsPresContext : public nsISupports, public mozilla::SupportsWeakPtr {
|
||||||
return mFontPaletteValueSet;
|
return mFontPaletteValueSet;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void UpdateHiddenByContentVisibilityForAnimations();
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
friend class nsRunnableMethod<nsPresContext>;
|
friend class nsRunnableMethod<nsPresContext>;
|
||||||
void ThemeChangedInternal();
|
void ThemeChangedInternal();
|
||||||
|
|
|
||||||
|
|
@ -7003,7 +7003,7 @@ bool nsIFrame::IsDescendantOfTopLayerElement() const {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
void nsIFrame::UpdateIsRelevantContent(
|
bool nsIFrame::UpdateIsRelevantContent(
|
||||||
const ContentRelevancy& aRelevancyToUpdate) {
|
const ContentRelevancy& aRelevancyToUpdate) {
|
||||||
MOZ_ASSERT(StyleDisplay()->ContentVisibility(*this) ==
|
MOZ_ASSERT(StyleDisplay()->ContentVisibility(*this) ==
|
||||||
StyleContentVisibility::Auto);
|
StyleContentVisibility::Auto);
|
||||||
|
|
@ -7053,7 +7053,7 @@ void nsIFrame::UpdateIsRelevantContent(
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!overallRelevancyChanged) {
|
if (!overallRelevancyChanged) {
|
||||||
return;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
HandleLastRememberedSize();
|
HandleLastRememberedSize();
|
||||||
|
|
@ -7075,6 +7075,7 @@ void nsIFrame::UpdateIsRelevantContent(
|
||||||
new AsyncEventDispatcher(element, event.forget());
|
new AsyncEventDispatcher(element, event.forget());
|
||||||
DebugOnly<nsresult> rv = asyncDispatcher->PostDOMEvent();
|
DebugOnly<nsresult> rv = asyncDispatcher->PostDOMEvent();
|
||||||
NS_ASSERTION(NS_SUCCEEDED(rv), "AsyncEventDispatcher failed to dispatch");
|
NS_ASSERTION(NS_SUCCEEDED(rv), "AsyncEventDispatcher failed to dispatch");
|
||||||
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
nsresult nsIFrame::CharacterDataChanged(const CharacterDataChangeInfo&) {
|
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
|
* Update the whether or not this frame is considered relevant content for the
|
||||||
* purposes of `content-visibility: auto` according to the rules specified in
|
* purposes of `content-visibility: auto` according to the rules specified in
|
||||||
* https://drafts.csswg.org/css-contain-2/#relevant-to-the-user.
|
* 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.
|
* Get the "type" of the frame.
|
||||||
|
|
|
||||||
|
|
@ -55,6 +55,7 @@ class TimelineCollection final
|
||||||
// if it does not already exist.
|
// if it does not already exist.
|
||||||
static TimelineCollection* Get(const dom::Element* aElement,
|
static TimelineCollection* Get(const dom::Element* aElement,
|
||||||
PseudoStyleType aPseudoType);
|
PseudoStyleType aPseudoType);
|
||||||
|
const TimelineMap& Timelines() const { return mTimelines; }
|
||||||
|
|
||||||
private:
|
private:
|
||||||
// The element. Weak reference is fine since it owns us.
|
// The element. Weak reference is fine since it owns us.
|
||||||
|
|
|
||||||
|
|
@ -168,4 +168,20 @@ void TimelineManager::DoUpdateTimelines(
|
||||||
// siblings when mutating {scroll|view}-timeline-name.
|
// 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
|
} // namespace mozilla
|
||||||
|
|
|
||||||
|
|
@ -51,6 +51,8 @@ class TimelineManager {
|
||||||
const ComputedStyle* aComputedStyle,
|
const ComputedStyle* aComputedStyle,
|
||||||
ProgressTimelineType aType);
|
ProgressTimelineType aType);
|
||||||
|
|
||||||
|
void UpdateHiddenByContentVisibilityForAnimations();
|
||||||
|
|
||||||
private:
|
private:
|
||||||
template <typename StyleType, typename TimelineType>
|
template <typename StyleType, typename TimelineType>
|
||||||
void DoUpdateTimelines(nsPresContext* aPresContext, dom::Element* aElement,
|
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]
|
[content-visibility-animation-in-auto-subtree.html]
|
||||||
expected: TIMEOUT
|
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]
|
[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; }
|
to { opacity: 0; }
|
||||||
}
|
}
|
||||||
#target {
|
#target {
|
||||||
background: 'green';
|
background: green;
|
||||||
height: 100px;
|
height: 100px;
|
||||||
width: 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 a new issue