зеркало из https://github.com/mozilla/gecko-dev.git
Bug 1777478 - Animations in content hidden by `content-visibility` should not fire events or resolve promises r=hiro
Keep a separate list of animations in the timeline that are hidden by content visibility. This allows the DocumentTimeline to disconnect from the refresh driver when all animations are hidden and prevents ticking hidden animations in general. Differential Revision: https://phabricator.services.mozilla.com/D150764
This commit is contained in:
Родитель
e08e5475c4
Коммит
2fbf04543f
|
@ -1965,6 +1965,20 @@ bool Animation::IsInEffect() const {
|
|||
return GetEffect() && GetEffect()->IsInEffect();
|
||||
}
|
||||
|
||||
void Animation::SetHiddenByContentVisibility(bool hidden) {
|
||||
if (mHiddenByContentVisibility == hidden) {
|
||||
return;
|
||||
}
|
||||
|
||||
mHiddenByContentVisibility = hidden;
|
||||
|
||||
if (!GetTimeline()) {
|
||||
return;
|
||||
}
|
||||
|
||||
GetTimeline()->NotifyAnimationContentVisibilityChanged(this, !hidden);
|
||||
}
|
||||
|
||||
StickyTimeDuration Animation::IntervalStartTime(
|
||||
const StickyTimeDuration& aActiveDuration) const {
|
||||
MOZ_ASSERT(AsCSSTransition() || AsCSSAnimation(),
|
||||
|
|
|
@ -474,6 +474,11 @@ class Animation : public DOMEventTargetHelper,
|
|||
mPlaybackRate);
|
||||
}
|
||||
|
||||
void SetHiddenByContentVisibility(bool hidden);
|
||||
bool IsHiddenByContentVisibility() const {
|
||||
return mHiddenByContentVisibility;
|
||||
}
|
||||
|
||||
protected:
|
||||
void SilentlySetCurrentTime(const TimeDuration& aNewCurrentTime);
|
||||
void CancelNoUpdate();
|
||||
|
@ -641,6 +646,8 @@ class Animation : public DOMEventTargetHelper,
|
|||
bool mFinishedAtLastComposeStyle = false;
|
||||
bool mWasReplaceableAtLastTick = false;
|
||||
|
||||
bool mHiddenByContentVisibility = false;
|
||||
|
||||
// Indicates that the animation should be exposed in an element's
|
||||
// getAnimations() list.
|
||||
bool mIsRelevant = false;
|
||||
|
|
|
@ -42,6 +42,8 @@ bool AnimationTimeline::Tick() {
|
|||
for (Animation* animation = mAnimationOrder.getFirst(); animation;
|
||||
animation =
|
||||
static_cast<LinkedListElement<Animation>*>(animation)->getNext()) {
|
||||
MOZ_ASSERT(!animation->IsHiddenByContentVisibility());
|
||||
|
||||
// Skip any animations that are longer need associated with this timeline.
|
||||
if (animation->GetTimeline() != this) {
|
||||
// If animation has some other timeline, it better not be also in the
|
||||
|
@ -74,7 +76,9 @@ void AnimationTimeline::NotifyAnimationUpdated(Animation& aAnimation) {
|
|||
if (aAnimation.GetTimeline() && aAnimation.GetTimeline() != this) {
|
||||
aAnimation.GetTimeline()->RemoveAnimation(&aAnimation);
|
||||
}
|
||||
mAnimationOrder.insertBack(&aAnimation);
|
||||
if (!aAnimation.IsHiddenByContentVisibility()) {
|
||||
mAnimationOrder.insertBack(&aAnimation);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -86,4 +90,15 @@ void AnimationTimeline::RemoveAnimation(Animation* aAnimation) {
|
|||
mAnimations.Remove(aAnimation);
|
||||
}
|
||||
|
||||
void AnimationTimeline::NotifyAnimationContentVisibilityChanged(
|
||||
Animation* aAnimation, bool visible) {
|
||||
bool inList =
|
||||
static_cast<LinkedListElement<Animation>*>(aAnimation)->isInList();
|
||||
if (visible && !inList) {
|
||||
mAnimationOrder.insertBack(aAnimation);
|
||||
} else if (!visible && inList) {
|
||||
static_cast<LinkedListElement<Animation>*>(aAnimation)->remove();
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace mozilla::dom
|
||||
|
|
|
@ -98,6 +98,8 @@ class AnimationTimeline : public nsISupports, public nsWrapperCache {
|
|||
bool HasAnimations() const { return !mAnimations.IsEmpty(); }
|
||||
|
||||
virtual void RemoveAnimation(Animation* aAnimation);
|
||||
virtual void NotifyAnimationContentVisibilityChanged(Animation* aAnimation,
|
||||
bool visible);
|
||||
|
||||
virtual Document* GetDocument() const = 0;
|
||||
|
||||
|
@ -119,7 +121,8 @@ class AnimationTimeline : public nsISupports, public nsWrapperCache {
|
|||
// Animations observing this timeline
|
||||
//
|
||||
// We store them in (a) a hashset for quick lookup, and (b) an array
|
||||
// to maintain a fixed sampling order.
|
||||
// to maintain a fixed sampling order. Animations that are hidden by
|
||||
// `content-visibility` are not sampled and will only be in the hashset.
|
||||
//
|
||||
// The hashset keeps a strong reference to each animation since
|
||||
// dealing with addref/release with LinkedList is difficult.
|
||||
|
|
|
@ -155,7 +155,7 @@ Nullable<TimeDuration> DocumentTimeline::ToTimelineTime(
|
|||
void DocumentTimeline::NotifyAnimationUpdated(Animation& aAnimation) {
|
||||
AnimationTimeline::NotifyAnimationUpdated(aAnimation);
|
||||
|
||||
if (!mIsObservingRefreshDriver) {
|
||||
if (!mIsObservingRefreshDriver && !mAnimationOrder.isEmpty()) {
|
||||
nsRefreshDriver* refreshDriver = GetRefreshDriver();
|
||||
if (refreshDriver) {
|
||||
MOZ_ASSERT(isInList(),
|
||||
|
@ -245,9 +245,32 @@ void DocumentTimeline::NotifyRefreshDriverDestroying(nsRefreshDriver* aDriver) {
|
|||
void DocumentTimeline::RemoveAnimation(Animation* aAnimation) {
|
||||
AnimationTimeline::RemoveAnimation(aAnimation);
|
||||
|
||||
if (mIsObservingRefreshDriver && mAnimations.IsEmpty()) {
|
||||
if (!mIsObservingRefreshDriver || !mAnimationOrder.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
|
||||
UnregisterFromRefreshDriver();
|
||||
}
|
||||
|
||||
void DocumentTimeline::NotifyAnimationContentVisibilityChanged(
|
||||
Animation* aAnimation, bool visible) {
|
||||
AnimationTimeline::NotifyAnimationContentVisibilityChanged(aAnimation,
|
||||
visible);
|
||||
|
||||
if (mIsObservingRefreshDriver && mAnimationOrder.isEmpty()) {
|
||||
UnregisterFromRefreshDriver();
|
||||
}
|
||||
|
||||
if (!mIsObservingRefreshDriver && !mAnimationOrder.isEmpty()) {
|
||||
nsRefreshDriver* refreshDriver = GetRefreshDriver();
|
||||
if (refreshDriver) {
|
||||
MOZ_ASSERT(isInList(),
|
||||
"We should not register with the refresh driver if we are not"
|
||||
" in the document's list of timelines");
|
||||
|
||||
ObserveRefreshDriver(refreshDriver);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
TimeStamp DocumentTimeline::ToTimeStamp(
|
||||
|
|
|
@ -56,6 +56,8 @@ class DocumentTimeline final : public AnimationTimeline,
|
|||
void NotifyAnimationUpdated(Animation& aAnimation) override;
|
||||
|
||||
void RemoveAnimation(Animation* aAnimation) override;
|
||||
void NotifyAnimationContentVisibilityChanged(Animation* aAnimation,
|
||||
bool visible) override;
|
||||
|
||||
// nsARefreshObserver methods
|
||||
void WillRefresh(TimeStamp aTime) override;
|
||||
|
|
|
@ -17,6 +17,8 @@
|
|||
#include "mozilla/ComputedStyle.h"
|
||||
#include "mozilla/DebugOnly.h"
|
||||
#include "mozilla/DisplayPortUtils.h"
|
||||
#include "mozilla/dom/CSSAnimation.h"
|
||||
#include "mozilla/dom/CSSTransition.h"
|
||||
#include "mozilla/dom/DocumentInlines.h"
|
||||
#include "mozilla/dom/AncestorIterator.h"
|
||||
#include "mozilla/dom/ElementInlines.h"
|
||||
|
@ -808,6 +810,12 @@ void nsIFrame::Init(nsIContent* aContent, nsContainerFrame* aParent,
|
|||
if (!IsPlaceholderFrame() && !aPrevInFlow) {
|
||||
UpdateVisibleDescendantsState();
|
||||
}
|
||||
|
||||
// TODO(mrobinson): Once bug 1765615 is fixed, this should be called on
|
||||
// layout changes. In addition, when `content-visibility: auto` is implemented
|
||||
// this should also be called when scrolling or focus causes content to be
|
||||
// skipped or unskipped.
|
||||
UpdateAnimationVisibility();
|
||||
}
|
||||
|
||||
void nsIFrame::DestroyFrom(nsIFrame* aDestructRoot,
|
||||
|
@ -11642,6 +11650,31 @@ void nsIFrame::UpdateVisibleDescendantsState() {
|
|||
}
|
||||
}
|
||||
|
||||
void nsIFrame::UpdateAnimationVisibility() {
|
||||
auto* animationCollection =
|
||||
AnimationCollection<CSSAnimation>::GetAnimationCollection(this);
|
||||
auto* transitionCollection =
|
||||
AnimationCollection<CSSTransition>::GetAnimationCollection(this);
|
||||
|
||||
if ((!animationCollection || animationCollection->mAnimations.IsEmpty()) &&
|
||||
(!transitionCollection || transitionCollection->mAnimations.IsEmpty())) {
|
||||
return;
|
||||
}
|
||||
|
||||
bool hidden = AncestorHidesContent();
|
||||
if (animationCollection) {
|
||||
for (auto& animation : animationCollection->mAnimations) {
|
||||
animation->SetHiddenByContentVisibility(hidden);
|
||||
}
|
||||
}
|
||||
|
||||
if (transitionCollection) {
|
||||
for (auto& transition : transitionCollection->mAnimations) {
|
||||
transition->SetHiddenByContentVisibility(hidden);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
nsIFrame::PhysicalAxes nsIFrame::ShouldApplyOverflowClipping(
|
||||
const nsStyleDisplay* aDisp) const {
|
||||
MOZ_ASSERT(aDisp == StyleDisplay(), "Wrong display struct");
|
||||
|
|
|
@ -4705,6 +4705,8 @@ class nsIFrame : public nsQueryFrame {
|
|||
// Update mAllDescendantsAreInvisible flag for this frame and ancestors.
|
||||
void UpdateVisibleDescendantsState();
|
||||
|
||||
void UpdateAnimationVisibility();
|
||||
|
||||
/**
|
||||
* If this returns true, the frame it's called on should get the
|
||||
* NS_FRAME_HAS_DIRTY_CHILDREN bit set on it by the caller; either directly
|
||||
|
|
|
@ -1,12 +1,3 @@
|
|||
[animation-display-lock.html]
|
||||
[Animation events do not fire for a CSS animation running in a display locked subtree]
|
||||
expected: FAIL
|
||||
|
||||
[Events and promises are handled normally for animations without an owning element]
|
||||
expected: FAIL
|
||||
|
||||
[The finished promise does not resolve due to the normal passage of time for a CSS animation in a display locked subtree]
|
||||
expected: FAIL
|
||||
|
||||
[The finished promise does not resolve due to the normal passage of time for a CSS transition in a display locked subtree]
|
||||
expected: FAIL
|
||||
|
|
Загрузка…
Ссылка в новой задаче