Bug 1896762 - Make animation timing match the spec. r=smaug,firefox-animation-reviewers,boris
Differential Revision: https://phabricator.services.mozilla.com/D210658
This commit is contained in:
@@ -19,7 +19,6 @@ namespace mozilla::dom {
|
|||||||
NS_IMPL_CYCLE_COLLECTION_CLASS(DocumentTimeline)
|
NS_IMPL_CYCLE_COLLECTION_CLASS(DocumentTimeline)
|
||||||
NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN_INHERITED(DocumentTimeline,
|
NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN_INHERITED(DocumentTimeline,
|
||||||
AnimationTimeline)
|
AnimationTimeline)
|
||||||
tmp->UnregisterFromRefreshDriver();
|
|
||||||
if (tmp->isInList()) {
|
if (tmp->isInList()) {
|
||||||
tmp->remove();
|
tmp->remove();
|
||||||
}
|
}
|
||||||
@@ -45,7 +44,6 @@ DocumentTimeline::DocumentTimeline(Document* aDocument,
|
|||||||
: AnimationTimeline(aDocument->GetParentObject(),
|
: AnimationTimeline(aDocument->GetParentObject(),
|
||||||
aDocument->GetScopeObject()->GetRTPCallerType()),
|
aDocument->GetScopeObject()->GetRTPCallerType()),
|
||||||
mDocument(aDocument),
|
mDocument(aDocument),
|
||||||
mIsObservingRefreshDriver(false),
|
|
||||||
mOriginTime(aOriginTime) {
|
mOriginTime(aOriginTime) {
|
||||||
if (mDocument) {
|
if (mDocument) {
|
||||||
mDocument->Timelines().insertBack(this);
|
mDocument->Timelines().insertBack(this);
|
||||||
@@ -55,9 +53,6 @@ DocumentTimeline::DocumentTimeline(Document* aDocument,
|
|||||||
}
|
}
|
||||||
|
|
||||||
DocumentTimeline::~DocumentTimeline() {
|
DocumentTimeline::~DocumentTimeline() {
|
||||||
MOZ_RELEASE_ASSERT(!mIsObservingRefreshDriver,
|
|
||||||
"Timeline should have disassociated"
|
|
||||||
" from the refresh driver before being destroyed");
|
|
||||||
if (isInList()) {
|
if (isInList()) {
|
||||||
remove();
|
remove();
|
||||||
}
|
}
|
||||||
@@ -105,11 +100,8 @@ TimeStamp DocumentTimeline::GetCurrentTimeStamp() const {
|
|||||||
: mLastRefreshDriverTime;
|
: mLastRefreshDriverTime;
|
||||||
}
|
}
|
||||||
|
|
||||||
void DocumentTimeline::UpdateLastRefreshDriverTime(TimeStamp aKnownTime) {
|
void DocumentTimeline::UpdateLastRefreshDriverTime() {
|
||||||
TimeStamp result = [&] {
|
TimeStamp result = [&] {
|
||||||
if (!aKnownTime.IsNull()) {
|
|
||||||
return aKnownTime;
|
|
||||||
}
|
|
||||||
if (auto* rd = GetRefreshDriver()) {
|
if (auto* rd = GetRefreshDriver()) {
|
||||||
return rd->MostRecentRefresh();
|
return rd->MostRecentRefresh();
|
||||||
};
|
};
|
||||||
@@ -157,110 +149,53 @@ Nullable<TimeDuration> DocumentTimeline::ToTimelineTime(
|
|||||||
void DocumentTimeline::NotifyAnimationUpdated(Animation& aAnimation) {
|
void DocumentTimeline::NotifyAnimationUpdated(Animation& aAnimation) {
|
||||||
AnimationTimeline::NotifyAnimationUpdated(aAnimation);
|
AnimationTimeline::NotifyAnimationUpdated(aAnimation);
|
||||||
|
|
||||||
if (!mIsObservingRefreshDriver && !mAnimationOrder.isEmpty()) {
|
if (!mAnimationOrder.isEmpty()) {
|
||||||
nsRefreshDriver* refreshDriver = GetRefreshDriver();
|
if (nsRefreshDriver* refreshDriver = GetRefreshDriver()) {
|
||||||
if (refreshDriver) {
|
|
||||||
MOZ_ASSERT(isInList(),
|
MOZ_ASSERT(isInList(),
|
||||||
"We should not register with the refresh driver if we are not"
|
"We should not register with the refresh driver if we are not"
|
||||||
" in the document's list of timelines");
|
" in the document's list of timelines");
|
||||||
|
refreshDriver->EnsureAnimationUpdate();
|
||||||
ObserveRefreshDriver(refreshDriver);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void DocumentTimeline::MostRecentRefreshTimeUpdated() {
|
|
||||||
MOZ_ASSERT(mIsObservingRefreshDriver);
|
|
||||||
MOZ_ASSERT(GetRefreshDriver(),
|
|
||||||
"Should be able to reach refresh driver from within WillRefresh");
|
|
||||||
|
|
||||||
nsAutoAnimationMutationBatch mb(mDocument);
|
|
||||||
|
|
||||||
TickState state;
|
|
||||||
bool ticked = Tick(state);
|
|
||||||
if (!ticked) {
|
|
||||||
// We already assert that GetRefreshDriver() is non-null at the beginning
|
|
||||||
// of this function but we check it again here to be sure that ticking
|
|
||||||
// animations does not have any side effects that cause us to lose the
|
|
||||||
// connection with the refresh driver, such as triggering the destruction
|
|
||||||
// of mDocument's PresShell.
|
|
||||||
MOZ_ASSERT(GetRefreshDriver(),
|
|
||||||
"Refresh driver should still be valid at end of WillRefresh");
|
|
||||||
UnregisterFromRefreshDriver();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void DocumentTimeline::TriggerAllPendingAnimationsNow() {
|
void DocumentTimeline::TriggerAllPendingAnimationsNow() {
|
||||||
for (Animation* animation : mAnimationOrder) {
|
for (Animation* animation : mAnimationOrder) {
|
||||||
animation->TryTriggerNow();
|
animation->TryTriggerNow();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void DocumentTimeline::WillRefresh(TimeStamp aTime) {
|
void DocumentTimeline::WillRefresh() {
|
||||||
UpdateLastRefreshDriverTime();
|
if (!mDocument->GetPresShell()) {
|
||||||
MostRecentRefreshTimeUpdated();
|
// If we're not displayed, don't tick animations.
|
||||||
}
|
|
||||||
|
|
||||||
void DocumentTimeline::NotifyTimerAdjusted(TimeStamp aTime) {
|
|
||||||
MostRecentRefreshTimeUpdated();
|
|
||||||
}
|
|
||||||
|
|
||||||
void DocumentTimeline::ObserveRefreshDriver(nsRefreshDriver* aDriver) {
|
|
||||||
MOZ_RELEASE_ASSERT(!mIsObservingRefreshDriver,
|
|
||||||
"shouldn't register as an observer more than once");
|
|
||||||
// Set the mIsObservingRefreshDriver flag before calling AddRefreshObserver
|
|
||||||
// since it might end up calling NotifyTimerAdjusted which calls
|
|
||||||
// MostRecentRefreshTimeUpdated which has an assertion for
|
|
||||||
// mIsObserveingRefreshDriver check.
|
|
||||||
mIsObservingRefreshDriver = true;
|
|
||||||
aDriver->AddRefreshObserver(this, FlushType::Style,
|
|
||||||
"DocumentTimeline animations");
|
|
||||||
aDriver->AddTimerAdjustmentObserver(this);
|
|
||||||
}
|
|
||||||
|
|
||||||
void DocumentTimeline::NotifyRefreshDriverCreated(nsRefreshDriver* aDriver) {
|
|
||||||
MOZ_RELEASE_ASSERT(
|
|
||||||
!mIsObservingRefreshDriver,
|
|
||||||
"Timeline should not be observing the refresh driver before"
|
|
||||||
" it is created");
|
|
||||||
|
|
||||||
if (!mAnimationOrder.isEmpty()) {
|
|
||||||
MOZ_ASSERT(isInList(),
|
|
||||||
"We should not register with the refresh driver if we are not"
|
|
||||||
" in the document's list of timelines");
|
|
||||||
ObserveRefreshDriver(aDriver);
|
|
||||||
// Although we have started observing the refresh driver, it's possible we
|
|
||||||
// could perform a paint before the first refresh driver tick happens. To
|
|
||||||
// ensure we're in a consistent state in that case we run the first tick
|
|
||||||
// manually.
|
|
||||||
MostRecentRefreshTimeUpdated();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void DocumentTimeline::DisconnectRefreshDriver(nsRefreshDriver* aDriver) {
|
|
||||||
MOZ_ASSERT(mIsObservingRefreshDriver);
|
|
||||||
|
|
||||||
aDriver->RemoveRefreshObserver(this, FlushType::Style);
|
|
||||||
aDriver->RemoveTimerAdjustmentObserver(this);
|
|
||||||
mIsObservingRefreshDriver = false;
|
|
||||||
}
|
|
||||||
|
|
||||||
void DocumentTimeline::NotifyRefreshDriverDestroying(nsRefreshDriver* aDriver) {
|
|
||||||
if (!mIsObservingRefreshDriver) {
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
UpdateLastRefreshDriverTime();
|
||||||
|
if (mAnimationOrder.isEmpty()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
nsAutoAnimationMutationBatch mb(mDocument);
|
||||||
|
|
||||||
DisconnectRefreshDriver(aDriver);
|
TickState state;
|
||||||
|
bool ticked = Tick(state);
|
||||||
|
if (!ticked) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
// We already assert that GetRefreshDriver() is non-null at the beginning
|
||||||
|
// of this function but we check it again here to be sure that ticking
|
||||||
|
// animations does not have any side effects that cause us to lose the
|
||||||
|
// connection with the refresh driver, such as triggering the destruction
|
||||||
|
// of mDocument's PresShell.
|
||||||
|
if (nsRefreshDriver* refreshDriver = GetRefreshDriver()) {
|
||||||
|
refreshDriver->EnsureAnimationUpdate();
|
||||||
|
} else {
|
||||||
|
MOZ_ASSERT_UNREACHABLE(
|
||||||
|
"Refresh driver should still be valid at end of WillRefresh");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void DocumentTimeline::RemoveAnimation(Animation* aAnimation) {
|
void DocumentTimeline::RemoveAnimation(Animation* aAnimation) {
|
||||||
AnimationTimeline::RemoveAnimation(aAnimation);
|
AnimationTimeline::RemoveAnimation(aAnimation);
|
||||||
|
|
||||||
if (!mIsObservingRefreshDriver || !mAnimationOrder.isEmpty()) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
UnregisterFromRefreshDriver();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void DocumentTimeline::NotifyAnimationContentVisibilityChanged(
|
void DocumentTimeline::NotifyAnimationContentVisibilityChanged(
|
||||||
@@ -268,19 +203,11 @@ void DocumentTimeline::NotifyAnimationContentVisibilityChanged(
|
|||||||
AnimationTimeline::NotifyAnimationContentVisibilityChanged(aAnimation,
|
AnimationTimeline::NotifyAnimationContentVisibilityChanged(aAnimation,
|
||||||
aIsVisible);
|
aIsVisible);
|
||||||
|
|
||||||
if (mIsObservingRefreshDriver && mAnimationOrder.isEmpty()) {
|
if (nsRefreshDriver* refreshDriver = GetRefreshDriver()) {
|
||||||
UnregisterFromRefreshDriver();
|
MOZ_ASSERT(isInList(),
|
||||||
}
|
"We should not register with the refresh driver if we are not"
|
||||||
|
" in the document's list of timelines");
|
||||||
if (!mIsObservingRefreshDriver && !mAnimationOrder.isEmpty()) {
|
refreshDriver->EnsureAnimationUpdate();
|
||||||
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);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -302,20 +229,7 @@ nsRefreshDriver* DocumentTimeline::GetRefreshDriver() const {
|
|||||||
if (MOZ_UNLIKELY(!presContext)) {
|
if (MOZ_UNLIKELY(!presContext)) {
|
||||||
return nullptr;
|
return nullptr;
|
||||||
}
|
}
|
||||||
|
|
||||||
return presContext->RefreshDriver();
|
return presContext->RefreshDriver();
|
||||||
}
|
}
|
||||||
|
|
||||||
void DocumentTimeline::UnregisterFromRefreshDriver() {
|
|
||||||
if (!mIsObservingRefreshDriver) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
nsRefreshDriver* refreshDriver = GetRefreshDriver();
|
|
||||||
if (!refreshDriver) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
DisconnectRefreshDriver(refreshDriver);
|
|
||||||
}
|
|
||||||
|
|
||||||
} // namespace mozilla::dom
|
} // namespace mozilla::dom
|
||||||
|
|||||||
@@ -14,15 +14,12 @@
|
|||||||
#include "AnimationTimeline.h"
|
#include "AnimationTimeline.h"
|
||||||
#include "nsDOMNavigationTiming.h" // for DOMHighResTimeStamp
|
#include "nsDOMNavigationTiming.h" // for DOMHighResTimeStamp
|
||||||
#include "nsRefreshDriver.h"
|
#include "nsRefreshDriver.h"
|
||||||
#include "nsRefreshObservers.h"
|
|
||||||
|
|
||||||
struct JSContext;
|
struct JSContext;
|
||||||
|
|
||||||
namespace mozilla::dom {
|
namespace mozilla::dom {
|
||||||
|
|
||||||
class DocumentTimeline final : public AnimationTimeline,
|
class DocumentTimeline final : public AnimationTimeline,
|
||||||
public nsARefreshObserver,
|
|
||||||
public nsATimerAdjustmentObserver,
|
|
||||||
public LinkedListElement<DocumentTimeline> {
|
public LinkedListElement<DocumentTimeline> {
|
||||||
public:
|
public:
|
||||||
DocumentTimeline(Document* aDocument, const TimeDuration& aOriginTime);
|
DocumentTimeline(Document* aDocument, const TimeDuration& aOriginTime);
|
||||||
@@ -35,8 +32,7 @@ class DocumentTimeline final : public AnimationTimeline,
|
|||||||
NS_DECL_CYCLE_COLLECTION_SCRIPT_HOLDER_CLASS_INHERITED(DocumentTimeline,
|
NS_DECL_CYCLE_COLLECTION_SCRIPT_HOLDER_CLASS_INHERITED(DocumentTimeline,
|
||||||
AnimationTimeline)
|
AnimationTimeline)
|
||||||
|
|
||||||
virtual JSObject* WrapObject(JSContext* aCx,
|
JSObject* WrapObject(JSContext*, JS::Handle<JSObject*> aGivenProto) override;
|
||||||
JS::Handle<JSObject*> aGivenProto) override;
|
|
||||||
|
|
||||||
static already_AddRefed<DocumentTimeline> Constructor(
|
static already_AddRefed<DocumentTimeline> Constructor(
|
||||||
const GlobalObject& aGlobal, const DocumentTimelineOptions& aOptions,
|
const GlobalObject& aGlobal, const DocumentTimelineOptions& aOptions,
|
||||||
@@ -46,7 +42,7 @@ class DocumentTimeline final : public AnimationTimeline,
|
|||||||
|
|
||||||
// This is deliberately _not_ called GetCurrentTime since that would clash
|
// This is deliberately _not_ called GetCurrentTime since that would clash
|
||||||
// with a macro defined in winbase.h
|
// with a macro defined in winbase.h
|
||||||
virtual Nullable<TimeDuration> GetCurrentTimeAsDuration() const override;
|
Nullable<TimeDuration> GetCurrentTimeAsDuration() const override;
|
||||||
|
|
||||||
bool TracksWallclockTime() const override;
|
bool TracksWallclockTime() const override;
|
||||||
Nullable<TimeDuration> ToTimelineTime(
|
Nullable<TimeDuration> ToTimelineTime(
|
||||||
@@ -61,27 +57,17 @@ class DocumentTimeline final : public AnimationTimeline,
|
|||||||
|
|
||||||
void TriggerAllPendingAnimationsNow();
|
void TriggerAllPendingAnimationsNow();
|
||||||
|
|
||||||
// nsARefreshObserver methods
|
void WillRefresh();
|
||||||
void WillRefresh(TimeStamp aTime) override;
|
|
||||||
// nsATimerAdjustmentObserver methods
|
|
||||||
void NotifyTimerAdjusted(TimeStamp aTime) override;
|
|
||||||
|
|
||||||
void NotifyRefreshDriverCreated(nsRefreshDriver* aDriver);
|
|
||||||
void NotifyRefreshDriverDestroying(nsRefreshDriver* aDriver);
|
|
||||||
|
|
||||||
Document* GetDocument() const override { return mDocument; }
|
Document* GetDocument() const override { return mDocument; }
|
||||||
|
|
||||||
void UpdateLastRefreshDriverTime(TimeStamp aKnownTime = {});
|
void UpdateLastRefreshDriverTime();
|
||||||
|
|
||||||
bool IsMonotonicallyIncreasing() const override { return true; }
|
bool IsMonotonicallyIncreasing() const override { return true; }
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
TimeStamp GetCurrentTimeStamp() const;
|
TimeStamp GetCurrentTimeStamp() const;
|
||||||
nsRefreshDriver* GetRefreshDriver() const;
|
nsRefreshDriver* GetRefreshDriver() const;
|
||||||
void UnregisterFromRefreshDriver();
|
|
||||||
void MostRecentRefreshTimeUpdated();
|
|
||||||
void ObserveRefreshDriver(nsRefreshDriver* aDriver);
|
|
||||||
void DisconnectRefreshDriver(nsRefreshDriver* aDriver);
|
|
||||||
|
|
||||||
RefPtr<Document> mDocument;
|
RefPtr<Document> mDocument;
|
||||||
|
|
||||||
@@ -89,8 +75,6 @@ class DocumentTimeline final : public AnimationTimeline,
|
|||||||
// we don't have a refresh driver (e.g. because we are in a display:none
|
// we don't have a refresh driver (e.g. because we are in a display:none
|
||||||
// iframe).
|
// iframe).
|
||||||
TimeStamp mLastRefreshDriverTime;
|
TimeStamp mLastRefreshDriverTime;
|
||||||
bool mIsObservingRefreshDriver;
|
|
||||||
|
|
||||||
TimeDuration mOriginTime;
|
TimeDuration mOriginTime;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -88,11 +88,12 @@ promise_test(async t => {
|
|||||||
div.style.animationPlayState = 'paused';
|
div.style.animationPlayState = 'paused';
|
||||||
|
|
||||||
await animation.ready;
|
await animation.ready;
|
||||||
|
await waitForPaints();
|
||||||
|
|
||||||
assert_animation_is_not_running_on_compositor(animation,
|
assert_animation_is_not_running_on_compositor(animation,
|
||||||
'Animation reports that it is NOT running on the compositor'
|
'Animation reports that it is NOT running on the compositor'
|
||||||
+ ' when paused');
|
+ ' when paused');
|
||||||
}, '');
|
}, 'Basic test');
|
||||||
|
|
||||||
promise_test(async t => {
|
promise_test(async t => {
|
||||||
var div = addDiv(t, { style: 'animation: z-index 100s' });
|
var div = addDiv(t, { style: 'animation: z-index 100s' });
|
||||||
@@ -127,6 +128,8 @@ promise_test(async t => {
|
|||||||
animation.pause();
|
animation.pause();
|
||||||
await animation.ready;
|
await animation.ready;
|
||||||
|
|
||||||
|
await waitForPaints();
|
||||||
|
|
||||||
assert_animation_is_not_running_on_compositor(animation,
|
assert_animation_is_not_running_on_compositor(animation,
|
||||||
'Animation reports that it is NOT running on the compositor'
|
'Animation reports that it is NOT running on the compositor'
|
||||||
+ ' when animation.pause() is called');
|
+ ' when animation.pause() is called');
|
||||||
|
|||||||
@@ -994,8 +994,8 @@ void PresShell::Init(nsPresContext* aPresContext, nsViewManager* aViewManager) {
|
|||||||
animCtrl->NotifyRefreshDriverCreated(GetPresContext()->RefreshDriver());
|
animCtrl->NotifyRefreshDriverCreated(GetPresContext()->RefreshDriver());
|
||||||
}
|
}
|
||||||
|
|
||||||
for (DocumentTimeline* timeline : mDocument->Timelines()) {
|
for (DocumentTimeline* timelines : mDocument->Timelines()) {
|
||||||
timeline->NotifyRefreshDriverCreated(GetPresContext()->RefreshDriver());
|
timelines->UpdateLastRefreshDriverTime();
|
||||||
}
|
}
|
||||||
|
|
||||||
// Get our activeness from the docShell.
|
// Get our activeness from the docShell.
|
||||||
@@ -1338,9 +1338,6 @@ void PresShell::Destroy() {
|
|||||||
if (mDocument->HasAnimationController()) {
|
if (mDocument->HasAnimationController()) {
|
||||||
mDocument->GetAnimationController()->NotifyRefreshDriverDestroying(rd);
|
mDocument->GetAnimationController()->NotifyRefreshDriverDestroying(rd);
|
||||||
}
|
}
|
||||||
for (DocumentTimeline* timeline : mDocument->Timelines()) {
|
|
||||||
timeline->NotifyRefreshDriverDestroying(rd);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (mPresContext) {
|
if (mPresContext) {
|
||||||
|
|||||||
@@ -50,6 +50,7 @@
|
|||||||
#include "nsComponentManagerUtils.h"
|
#include "nsComponentManagerUtils.h"
|
||||||
#include "mozilla/Logging.h"
|
#include "mozilla/Logging.h"
|
||||||
#include "mozilla/dom/Document.h"
|
#include "mozilla/dom/Document.h"
|
||||||
|
#include "mozilla/dom/DocumentTimeline.h"
|
||||||
#include "mozilla/dom/DocumentInlines.h"
|
#include "mozilla/dom/DocumentInlines.h"
|
||||||
#include "nsIXULRuntime.h"
|
#include "nsIXULRuntime.h"
|
||||||
#include "jsapi.h"
|
#include "jsapi.h"
|
||||||
@@ -1364,6 +1365,7 @@ nsRefreshDriver::nsRefreshDriver(nsPresContext* aPresContext)
|
|||||||
mNotifyDOMContentFlushed(false),
|
mNotifyDOMContentFlushed(false),
|
||||||
mNeedToUpdateIntersectionObservations(false),
|
mNeedToUpdateIntersectionObservations(false),
|
||||||
mNeedToUpdateResizeObservers(false),
|
mNeedToUpdateResizeObservers(false),
|
||||||
|
mNeedToUpdateAnimations(false),
|
||||||
mMightNeedMediaQueryListenerUpdate(false),
|
mMightNeedMediaQueryListenerUpdate(false),
|
||||||
mNeedToUpdateContentRelevancy(false),
|
mNeedToUpdateContentRelevancy(false),
|
||||||
mInNormalTick(false),
|
mInNormalTick(false),
|
||||||
@@ -1489,18 +1491,6 @@ bool nsRefreshDriver::RemoveRefreshObserver(nsARefreshObserver* aObserver,
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
void nsRefreshDriver::AddTimerAdjustmentObserver(
|
|
||||||
nsATimerAdjustmentObserver* aObserver) {
|
|
||||||
MOZ_ASSERT(!mTimerAdjustmentObservers.Contains(aObserver));
|
|
||||||
mTimerAdjustmentObservers.AppendElement(aObserver);
|
|
||||||
}
|
|
||||||
|
|
||||||
void nsRefreshDriver::RemoveTimerAdjustmentObserver(
|
|
||||||
nsATimerAdjustmentObserver* aObserver) {
|
|
||||||
MOZ_ASSERT(mTimerAdjustmentObservers.Contains(aObserver));
|
|
||||||
mTimerAdjustmentObservers.RemoveElement(aObserver);
|
|
||||||
}
|
|
||||||
|
|
||||||
void nsRefreshDriver::PostVisualViewportResizeEvent(
|
void nsRefreshDriver::PostVisualViewportResizeEvent(
|
||||||
VVPResizeEvent* aResizeEvent) {
|
VVPResizeEvent* aResizeEvent) {
|
||||||
mVisualViewportResizeEvents.AppendElement(aResizeEvent);
|
mVisualViewportResizeEvents.AppendElement(aResizeEvent);
|
||||||
@@ -1885,11 +1875,6 @@ void nsRefreshDriver::EnsureTimerStarted(EnsureTimerStartedFlags aFlags) {
|
|||||||
|
|
||||||
if (mMostRecentRefresh != mActiveTimer->MostRecentRefresh()) {
|
if (mMostRecentRefresh != mActiveTimer->MostRecentRefresh()) {
|
||||||
mMostRecentRefresh = mActiveTimer->MostRecentRefresh();
|
mMostRecentRefresh = mActiveTimer->MostRecentRefresh();
|
||||||
|
|
||||||
for (nsATimerAdjustmentObserver* obs :
|
|
||||||
mTimerAdjustmentObservers.EndLimitedRange()) {
|
|
||||||
obs->NotifyTimerAdjusted(mMostRecentRefresh);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1919,7 +1904,6 @@ uint32_t nsRefreshDriver::ObserverCount() const {
|
|||||||
sum += mThrottledFrameRequestCallbackDocs.Length();
|
sum += mThrottledFrameRequestCallbackDocs.Length();
|
||||||
sum += mViewManagerFlushIsPending;
|
sum += mViewManagerFlushIsPending;
|
||||||
sum += mEarlyRunners.Length();
|
sum += mEarlyRunners.Length();
|
||||||
sum += mTimerAdjustmentObservers.Length();
|
|
||||||
sum += mAutoFocusFlushDocuments.Length();
|
sum += mAutoFocusFlushDocuments.Length();
|
||||||
return sum;
|
return sum;
|
||||||
}
|
}
|
||||||
@@ -2011,6 +1995,9 @@ auto nsRefreshDriver::GetReasonsToTick() const -> TickReasons {
|
|||||||
if (mNeedToUpdateResizeObservers) {
|
if (mNeedToUpdateResizeObservers) {
|
||||||
reasons |= TickReasons::eNeedsToNotifyResizeObservers;
|
reasons |= TickReasons::eNeedsToNotifyResizeObservers;
|
||||||
}
|
}
|
||||||
|
if (mNeedToUpdateAnimations) {
|
||||||
|
reasons |= TickReasons::eNeedsToUpdateAnimations;
|
||||||
|
}
|
||||||
if (mNeedToUpdateIntersectionObservations) {
|
if (mNeedToUpdateIntersectionObservations) {
|
||||||
reasons |= TickReasons::eNeedsToUpdateIntersectionObservations;
|
reasons |= TickReasons::eNeedsToUpdateIntersectionObservations;
|
||||||
}
|
}
|
||||||
@@ -2054,6 +2041,9 @@ void nsRefreshDriver::AppendTickReasonsToString(TickReasons aReasons,
|
|||||||
if (aReasons & TickReasons::eNeedsToNotifyResizeObservers) {
|
if (aReasons & TickReasons::eNeedsToNotifyResizeObservers) {
|
||||||
aStr.AppendLiteral(" NeedsToNotifyResizeObservers");
|
aStr.AppendLiteral(" NeedsToNotifyResizeObservers");
|
||||||
}
|
}
|
||||||
|
if (aReasons & TickReasons::eNeedsToUpdateAnimations) {
|
||||||
|
aStr.AppendLiteral(" NeedsToUpdateAnimations");
|
||||||
|
}
|
||||||
if (aReasons & TickReasons::eNeedsToUpdateIntersectionObservations) {
|
if (aReasons & TickReasons::eNeedsToUpdateIntersectionObservations) {
|
||||||
aStr.AppendLiteral(" NeedsToUpdateIntersectionObservations");
|
aStr.AppendLiteral(" NeedsToUpdateIntersectionObservations");
|
||||||
}
|
}
|
||||||
@@ -2344,11 +2334,41 @@ void nsRefreshDriver::DetermineProximityToViewportAndNotifyResizeObservers() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void nsRefreshDriver::DispatchAnimationEvents() {
|
static CallState UpdateAndReduceAnimations(Document& aDocument) {
|
||||||
|
for (DocumentTimeline* timeline : aDocument.Timelines()) {
|
||||||
|
timeline->WillRefresh();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (nsPresContext* pc = aDocument.GetPresContext()) {
|
||||||
|
if (pc->EffectCompositor()->NeedsReducing()) {
|
||||||
|
pc->EffectCompositor()->ReduceAnimations();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
aDocument.EnumerateSubDocuments(UpdateAndReduceAnimations);
|
||||||
|
return CallState::Continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
void nsRefreshDriver::UpdateAnimationsAndSendEvents() {
|
||||||
|
// TODO(emilio): Can we early-return here if mNeedToUpdateAnimations is
|
||||||
|
// already false?
|
||||||
|
mNeedToUpdateAnimations = false;
|
||||||
if (!mPresContext) {
|
if (!mPresContext) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
// Animation updates may queue Promise resolution microtasks. We shouldn't
|
||||||
|
// run these, however, until we have fully updated the animation state. As
|
||||||
|
// per the "update animations and send events" procedure[1], we should
|
||||||
|
// remove replaced animations and then run these microtasks before
|
||||||
|
// dispatching the corresponding animation events.
|
||||||
|
//
|
||||||
|
// [1]:
|
||||||
|
// https://drafts.csswg.org/web-animations-1/#update-animations-and-send-events
|
||||||
|
nsAutoMicroTask mt;
|
||||||
|
UpdateAndReduceAnimations(*mPresContext->Document());
|
||||||
|
}
|
||||||
|
|
||||||
// Hold all AnimationEventDispatcher in mAnimationEventFlushObservers as
|
// Hold all AnimationEventDispatcher in mAnimationEventFlushObservers as
|
||||||
// a RefPtr<> array since each AnimationEventDispatcher might be destroyed
|
// a RefPtr<> array since each AnimationEventDispatcher might be destroyed
|
||||||
// during processing the previous dispatcher.
|
// during processing the previous dispatcher.
|
||||||
@@ -2495,16 +2515,6 @@ void nsRefreshDriver::CancelIdleTask(Task* aTask) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
static CallState ReduceAnimations(Document& aDocument) {
|
|
||||||
if (nsPresContext* pc = aDocument.GetPresContext()) {
|
|
||||||
if (pc->EffectCompositor()->NeedsReducing()) {
|
|
||||||
pc->EffectCompositor()->ReduceAnimations();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
aDocument.EnumerateSubDocuments(ReduceAnimations);
|
|
||||||
return CallState::Continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool nsRefreshDriver::TickObserverArray(uint32_t aIdx, TimeStamp aNowTime) {
|
bool nsRefreshDriver::TickObserverArray(uint32_t aIdx, TimeStamp aNowTime) {
|
||||||
MOZ_ASSERT(aIdx < ArrayLength(mObservers));
|
MOZ_ASSERT(aIdx < ArrayLength(mObservers));
|
||||||
for (RefPtr<nsARefreshObserver> obs : mObservers[aIdx].EndLimitedRange()) {
|
for (RefPtr<nsARefreshObserver> obs : mObservers[aIdx].EndLimitedRange()) {
|
||||||
@@ -2667,28 +2677,6 @@ void nsRefreshDriver::Tick(VsyncId aId, TimeStamp aNowTime,
|
|||||||
return StopTimer();
|
return StopTimer();
|
||||||
}
|
}
|
||||||
|
|
||||||
// Any animation timelines updated above (animation timelines are style flush
|
|
||||||
// observers) may cause animations to queue Promise resolution microtasks. We
|
|
||||||
// shouldn't run these, however, until we have fully updated the animation
|
|
||||||
// state.
|
|
||||||
//
|
|
||||||
// As per the "update animations and send events" procedure[1], we should
|
|
||||||
// remove replaced animations and then run these microtasks before
|
|
||||||
// dispatching the corresponding animation events.
|
|
||||||
//
|
|
||||||
// FIXME(emilio, bug 1896762): This comment doesn't make much sense to me.
|
|
||||||
// We're running micro-tasks in the block below, but we don't "Update
|
|
||||||
// animations and send events" until we hit DispatchAnimationEvents() below.
|
|
||||||
// We should probably refactor the setup so that animation timelines are
|
|
||||||
// ticked as part of step 11 below or so, and do this only then.
|
|
||||||
//
|
|
||||||
// [1]:
|
|
||||||
// https://drafts.csswg.org/web-animations-1/#update-animations-and-send-events
|
|
||||||
{
|
|
||||||
nsAutoMicroTask mt;
|
|
||||||
ReduceAnimations(*mPresContext->Document());
|
|
||||||
}
|
|
||||||
|
|
||||||
// Check if running the microtask checkpoint above caused the pres context to
|
// Check if running the microtask checkpoint above caused the pres context to
|
||||||
// be destroyed.
|
// be destroyed.
|
||||||
if (!mPresContext || !mPresContext->GetPresShell()) {
|
if (!mPresContext || !mPresContext->GetPresShell()) {
|
||||||
@@ -2713,7 +2701,7 @@ void nsRefreshDriver::Tick(VsyncId aId, TimeStamp aNowTime,
|
|||||||
EvaluateMediaQueriesAndReportChanges();
|
EvaluateMediaQueriesAndReportChanges();
|
||||||
|
|
||||||
// Step 11. For each doc of docs, update animations and send events for doc.
|
// Step 11. For each doc of docs, update animations and send events for doc.
|
||||||
DispatchAnimationEvents();
|
UpdateAnimationsAndSendEvents();
|
||||||
|
|
||||||
// Step 12. For each doc of docs, run the fullscreen steps for doc.
|
// Step 12. For each doc of docs, run the fullscreen steps for doc.
|
||||||
RunFullscreenSteps();
|
RunFullscreenSteps();
|
||||||
|
|||||||
@@ -103,12 +103,6 @@ class nsRefreshDriver final : public mozilla::layers::TransactionIdAllocator,
|
|||||||
const char* aObserverDescription);
|
const char* aObserverDescription);
|
||||||
bool RemoveRefreshObserver(nsARefreshObserver* aObserver,
|
bool RemoveRefreshObserver(nsARefreshObserver* aObserver,
|
||||||
mozilla::FlushType aFlushType);
|
mozilla::FlushType aFlushType);
|
||||||
/**
|
|
||||||
* Add / remove an observer wants to know the time when the refresh driver
|
|
||||||
* updated the most recent refresh time due to its active timer changes.
|
|
||||||
*/
|
|
||||||
void AddTimerAdjustmentObserver(nsATimerAdjustmentObserver* aObserver);
|
|
||||||
void RemoveTimerAdjustmentObserver(nsATimerAdjustmentObserver* aObserver);
|
|
||||||
|
|
||||||
void PostVisualViewportResizeEvent(VVPResizeEvent* aResizeEvent);
|
void PostVisualViewportResizeEvent(VVPResizeEvent* aResizeEvent);
|
||||||
void DispatchVisualViewportResizeEvents();
|
void DispatchVisualViewportResizeEvents();
|
||||||
@@ -417,6 +411,11 @@ class nsRefreshDriver final : public mozilla::layers::TransactionIdAllocator,
|
|||||||
mNeedToUpdateResizeObservers = true;
|
mNeedToUpdateResizeObservers = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void EnsureAnimationUpdate() {
|
||||||
|
EnsureTimerStarted();
|
||||||
|
mNeedToUpdateAnimations = true;
|
||||||
|
}
|
||||||
|
|
||||||
void ScheduleMediaQueryListenerUpdate() {
|
void ScheduleMediaQueryListenerUpdate() {
|
||||||
EnsureTimerStarted();
|
EnsureTimerStarted();
|
||||||
mMightNeedMediaQueryListenerUpdate = true;
|
mMightNeedMediaQueryListenerUpdate = true;
|
||||||
@@ -446,6 +445,7 @@ class nsRefreshDriver final : public mozilla::layers::TransactionIdAllocator,
|
|||||||
eHasPendingMediaQueryListeners = 1 << 7,
|
eHasPendingMediaQueryListeners = 1 << 7,
|
||||||
eNeedsToNotifyResizeObservers = 1 << 8,
|
eNeedsToNotifyResizeObservers = 1 << 8,
|
||||||
eRootNeedsMoreTicksForUserInput = 1 << 9,
|
eRootNeedsMoreTicksForUserInput = 1 << 9,
|
||||||
|
eNeedsToUpdateAnimations = 1 << 10,
|
||||||
};
|
};
|
||||||
|
|
||||||
void AddForceNotifyContentfulPaintPresContext(nsPresContext* aPresContext);
|
void AddForceNotifyContentfulPaintPresContext(nsPresContext* aPresContext);
|
||||||
@@ -487,7 +487,7 @@ class nsRefreshDriver final : public mozilla::layers::TransactionIdAllocator,
|
|||||||
MOZ_CAN_RUN_SCRIPT
|
MOZ_CAN_RUN_SCRIPT
|
||||||
void FlushAutoFocusDocuments();
|
void FlushAutoFocusDocuments();
|
||||||
void RunFullscreenSteps();
|
void RunFullscreenSteps();
|
||||||
void DispatchAnimationEvents();
|
void UpdateAnimationsAndSendEvents();
|
||||||
MOZ_CAN_RUN_SCRIPT
|
MOZ_CAN_RUN_SCRIPT
|
||||||
void RunFrameRequestCallbacks(mozilla::TimeStamp aNowTime);
|
void RunFrameRequestCallbacks(mozilla::TimeStamp aNowTime);
|
||||||
void UpdateIntersectionObservations(mozilla::TimeStamp aNowTime);
|
void UpdateIntersectionObservations(mozilla::TimeStamp aNowTime);
|
||||||
@@ -643,6 +643,9 @@ class nsRefreshDriver final : public mozilla::layers::TransactionIdAllocator,
|
|||||||
// all our documents.
|
// all our documents.
|
||||||
bool mNeedToUpdateResizeObservers : 1;
|
bool mNeedToUpdateResizeObservers : 1;
|
||||||
|
|
||||||
|
// True if we need to update animations.
|
||||||
|
bool mNeedToUpdateAnimations : 1;
|
||||||
|
|
||||||
// True if we might need to report media query changes in any of our
|
// True if we might need to report media query changes in any of our
|
||||||
// documents.
|
// documents.
|
||||||
bool mMightNeedMediaQueryListenerUpdate : 1;
|
bool mMightNeedMediaQueryListenerUpdate : 1;
|
||||||
@@ -673,12 +676,6 @@ class nsRefreshDriver final : public mozilla::layers::TransactionIdAllocator,
|
|||||||
|
|
||||||
// separate arrays for each flush type we support
|
// separate arrays for each flush type we support
|
||||||
ObserverArray mObservers[3];
|
ObserverArray mObservers[3];
|
||||||
// These observers should NOT be included in HasObservers() since that method
|
|
||||||
// is used to determine whether or not to stop the timer, or restore it when
|
|
||||||
// thawing the refresh driver. On the other hand these observers are intended
|
|
||||||
// to be called when the timer is re-started and should not influence its
|
|
||||||
// starting or stopping.
|
|
||||||
nsTObserverArray<nsATimerAdjustmentObserver*> mTimerAdjustmentObservers;
|
|
||||||
nsTArray<mozilla::layers::CompositionPayload> mCompositionPayloads;
|
nsTArray<mozilla::layers::CompositionPayload> mCompositionPayloads;
|
||||||
RequestTable mRequests;
|
RequestTable mRequests;
|
||||||
ImageStartTable mStartTable;
|
ImageStartTable mStartTable;
|
||||||
|
|||||||
@@ -60,17 +60,6 @@ class nsARefreshObserver {
|
|||||||
#endif // DEBUG
|
#endif // DEBUG
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
|
||||||
* An abstract base class to be implemented by callers wanting to be notified
|
|
||||||
* when the observing refresh driver updated mMostRecentRefresh due to active
|
|
||||||
* timer changes. Callers must ensure an observer is removed before it is
|
|
||||||
* destroyed.
|
|
||||||
*/
|
|
||||||
class nsATimerAdjustmentObserver {
|
|
||||||
public:
|
|
||||||
virtual void NotifyTimerAdjusted(mozilla::TimeStamp aTime) = 0;
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* An abstract base class to be implemented by callers wanting to be notified
|
* An abstract base class to be implemented by callers wanting to be notified
|
||||||
* that a refresh has occurred. Callers must ensure an observer is removed
|
* that a refresh has occurred. Callers must ensure an observer is removed
|
||||||
|
|||||||
@@ -20,6 +20,7 @@
|
|||||||
#include "mozilla/dom/ShadowRoot.h"
|
#include "mozilla/dom/ShadowRoot.h"
|
||||||
#include "mozilla/dom/CustomEvent.h"
|
#include "mozilla/dom/CustomEvent.h"
|
||||||
#include "mozilla/dom/ContentChild.h"
|
#include "mozilla/dom/ContentChild.h"
|
||||||
|
#include "mozilla/dom/DocumentTimeline.h"
|
||||||
#include "mozilla/dom/HTMLCanvasElement.h"
|
#include "mozilla/dom/HTMLCanvasElement.h"
|
||||||
#include "mozilla/dom/ScriptSettings.h"
|
#include "mozilla/dom/ScriptSettings.h"
|
||||||
#include "mozilla/IntegerRange.h"
|
#include "mozilla/IntegerRange.h"
|
||||||
@@ -1414,6 +1415,10 @@ nsresult nsPrintJob::ReflowPrintObject(const UniquePtr<nsPrintObject>& aPO) {
|
|||||||
aPO->mPresContext->SetPageSize(pageSize);
|
aPO->mPresContext->SetPageSize(pageSize);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
// Make sure animations are active.
|
||||||
|
for (DocumentTimeline* tl : aPO->mDocument->Timelines()) {
|
||||||
|
tl->TriggerAllPendingAnimationsNow();
|
||||||
|
}
|
||||||
// Process the reflow event Initialize posted
|
// Process the reflow event Initialize posted
|
||||||
presShell->FlushPendingNotifications(FlushType::Layout);
|
presShell->FlushPendingNotifications(FlushType::Layout);
|
||||||
|
|
||||||
|
|||||||
@@ -1,16 +1,4 @@
|
|||||||
[css-transition-cross-document.html]
|
[css-transition-cross-document.html]
|
||||||
expected:
|
expected: [TIMEOUT, OK]
|
||||||
if (os == "linux") and debug and fission: [OK, TIMEOUT]
|
|
||||||
if (os == "linux") and debug and not fission: [OK, TIMEOUT]
|
|
||||||
if (os == "win") and not debug: TIMEOUT
|
|
||||||
if (os == "linux") and not debug: TIMEOUT
|
|
||||||
if os == "android": OK
|
|
||||||
[TIMEOUT, OK]
|
|
||||||
[Moving a transition across documents should reset its state]
|
[Moving a transition across documents should reset its state]
|
||||||
expected:
|
expected: [TIMEOUT, FAIL]
|
||||||
if (os == "linux") and debug and fission: [FAIL, TIMEOUT]
|
|
||||||
if (os == "linux") and debug and not fission: [FAIL, TIMEOUT]
|
|
||||||
if (os == "win") and not debug: TIMEOUT
|
|
||||||
if (os == "linux") and not debug: TIMEOUT
|
|
||||||
if os == "android": FAIL
|
|
||||||
[TIMEOUT, FAIL]
|
|
||||||
|
|||||||
Reference in New Issue
Block a user