mirror of
https://github.com/roytam1/palemoon27.git
synced 2026-05-26 14:30:27 +00:00
8a0a002cf2
- Bug 1148708: Add missing 'override' annotations in DocAccessibleChild.h. rs=ehsan (d606358545)
- Bug 1210408 - make nsMaiInterfaceAction work with proxies, r=tbsaunde (f7c819c6ae)
- Bug 1210407 - teach nsMaiInterfaceTable to use proxies, r=tbsaunde (4ca4f10b5f)
- bug 1185157 make sure we don't send an event to a destroyed ipc document r=billm (23acf53f75)
- bug 1214864 - make SetCarretOffset() async r=davidb (e3079e9b2d)
- missing of Bug 1139972 - IPC Proxy for charAt, r=tbsaunde (e9593ed752)
- bug 1191598 - Pass MOZ_CURRENT_PROJECT in environment when running post-build automation steps for universal mac builds. r=gps (fc342c6ced)
- Bug 1164596 - Add mach android-emulator command; r=ahal (afeb9b27d1)
- Bug 1223149 - Add basic usage documentation for mach build; r=glandium (bfb802d175)
- Bug 1182301 - Improve 'mach build' notifications. r=gps (2c65a122d1)
- Bug 1184696 - Add clobber targets to |mach clobber|; Ability to clobber compiled python files, r=gps (35d8be292e)
- Bug 1117958 - Allow any debugging options to the run or gtest mach subcommands to automatically enable debugging. r=gps (32f986af4b)
- Bug 1180081 - Properly rebuild gtest/libxul before running gtests. r=gps (80db9a3d49)
- Bug 1171647, part 1 - Define a new function to convert the mode to a string. r=njn (61ad16f5ba)
- Bug 1171647, part 2 - Remove redundant assertion for dark matter mode. r=njn (b5ac9519f3)
- Bug 1058178, part 1 - Implement DMD heap scanning mode. r=njn (60e1079536)
- Bug 1058178, part 2 - Implement address clamping analysis for DMD scan logs. r=njn (45c0326b93)
- Bug 1102388 - Fix DMD static constructor ordering dependency. r=mccr8 (59b87897a1)
- Bug 1128705 - Don't redefine PAGE_SIZE in DMD if it's already defined. r=erahm (49216348ee)
- Bug 1179042 - Add a script for analyzing memory blocks using a heap scan DMD log. r=njn DONTBUILD (1c08d2d66e)
- Bug 1207245 - part 6 - rename nsRefPtr<T> to RefPtr<T>; r=ehsan; a=Tomcat (1c999d139b)
- Bug 1158772 - fix non-idiomatic memset call in nsDeque.cpp; r=erahm (de6b555245)
- Bug 1199400 - Part 1: Use CheckedInt when growing nsDeque capacity. r=froydnj (dfdf6814a3)
- Bug 1199400 - Part 0: Remove unused nsDequeIterator. r=froydnj (38d69d7f47)
- Bug 1199400 - Part 2: Add tests for possible nsDeque corner cases. r=froydnj (931baff195)
- Bug 1201997 - Part 1 - Converted compiled test to gtest for nsDeque class. Added tests to test untested methods. r=froydn (e893916651)
- Bug 1201997 - Part 2 - Removing unused methods from the nsDeque class. r=froydn (41595a90ac)
- Bug 1201997 - Part 3 - Make internally used methods private. r=froydn (8cd3afd96f)
- Bug 1201997 - Part 4 - Change size and offset variables to size_t.r=froydn (73eabc8d60)
- Bug 1215140 P1 Add an nsIConsoleReportCollector interface to support navigation channel logging. r=bz (8a41535e2b)
- Bug 1215140 P2 Make HttpBaseChannel implement nsIConsoleReportCollector. r=bz (75fca301f2)
- Bug 1197679 - If nsUnknownDecoder is involved in e10s DivertToParent can break. r=jduell (5d94a12504)
- Bug 1178991 - smartptr for http converter r=hurley (8e7fbc8443)
- bug 366559 - patch 2, fix nsHTTPCompressConv indentation r=bagder (ba762da587)
- bug 366559 - patch 3, fix nsHTTPCompressConv bracing style r=bagder (54195ab451)
- bits of bug 366559 - patch 7, content-encoding brotli for http (f0b4051022)
- Bug 1205112 - Make PushEvent.data nullable. r=mt,smaug (775db32856)
- Bug 1193414 - SharedWorkers thread should be kept alive also when the SharedWorker object is CCed, r=khuey (b77ea8125c)
- Bug 1206520: Add about:config prefs to enable throwing on asm.js validation failures; r=bz (c42126665d)
- Bug 1193414 - Telemetry for SharedWorker spawning. r=bkelly (77984b7bcc)
- Bug 1205676 - Enable WPT service-worker/unregister-then-register-new-script.https.html in e10s, r=nsm (ec24939cf6)
- Bug 1193133 - Throw when calling postMessage from a Service Worker dom object with no global. r=bkelly (526dcacfab)
- Bug 1181871 P1 Only enforce Cache Context shared data destruction on target thread after init. r=ehsan (cdbf3ed3a8)
- Bug 1181871 P2 Fix ServiceWorkerManager usage of stack-based ErrorResult. r=ehsan (c449195d90)
- minor cleanup and missing bit of 1198230 (02f459db05)
- Bug 1143717 - Implement the ServiceWorkerMessageEvent interface. r=baku (027b3465f2)
- fix misspatch (708eee4e84)
- Bug 1188545 - Disentangle service workers from shared workers and refactor event dispatching code into a separate class. r=nsm,mrbkap (fb5b5341c9)
- Bug 1205228 - Change PackagedAppVerifier to notify the verification result asynchronously. r=valentin. (9edda0fa00)
- Bug 1178518 - Packaged App Utils. r=valentin (f60f3b7a93)
- Bug 1213150 - Part 1: Add a nsContentUtils::IsNonSubresourceRequest helper; r=jdm (b509cc3cc9)
- Bug 1213150 - Part 2: Rework ShouldPrepareForIntercept() in terms of subresource requests; r=jdm (2e92fe8780)
- Bug 1213150 - Part 3: Remove nsIInterceptedChannel.isNavigation; r=jdm (becf1cc12f)
- Bug 1213150 follow-up: fix build bustage (8d73d6ca73)
- Bug 1198394 - Part 1: Allow interception of HSTS upgraded connections in non-e10s mode; r=mcmanus (f504c5be08)
- Bug 1198394 - Part 2: Add a test for interception of HSTS upgraded connections; r=jdm (054e984eef)
- Bug 1187011 - Don't allow response body with null body status. r=bkelly (b1860741d1)
- missing bit of 1140788 (29d319712e)
- Bug 1213436 - Reject core dumps with node IDs that don't fit in an IEEE 754 double; r=sfink (3c1f6fdda0)
- Bug 1211006 - Add Debugger.Source.prototype.canonicalId; r=ejpbruel (eef7b79fce)
- Bug 1199218 - Implement JS::ubi::Node::size for js::LazyScript referents; r=sfink (098a48d240)
- Bug 1220031 - Add JS::ubi::Node::scriptFilename; r=sfink (6b824ae680)
- Bug 1143575. Remove unused MediaQueue::Empty. r=cpearce (de737f3433)
- Bug 1209933 - Make sure all parent runtime pointers are the topmost parent, r=billm. (fe824d967d)
- Bug 1197012 - Fix ThrowTypeError in Notification. r=mccr8 (0b1a097526)
- Bug 1197893 - Check the number of arguments for ThrowTypeError() and ThrowRangeError() at compile time. r=peterv (d98c7d78a0)
- Bug 1142083 - Add test for IDN Unicode domain redirect. r=mcmanus (0c8961fe17)
- Bug 1187159 - Add mochitest for loading packaged apps (iframe+fetch+mozapp) r=jduell (ce90ea561b)
- Bug 1186290 - Notify TabParent to switch process when loading a signed package. r=honzab, r=kanru. (c58a14554a)
- fix (15e2df75eb)
- Bug 1206124 P1 Fix "same-origin" CORS credentials in FetchDriver. r=ehsan (fae1bb6ab3)
- Bug 1206124 P2 Test fetch() with credentials and redirects. r=ehsan (ffc6254112)
- Bug 1211751: Remove nsIChannelEventSink-forwarding from EventSource and FetchDriver. It's never needed. r=smaug (adafe5737a)
- Bug 1212433 Fail fetch() calls that require preflight and also redirect. r=sicking a=abillings (c0d6742b9e)
- Bug 1193128 - Fix base64 decoding when fetching data URIs. r=baku (80bafa291a)
- Bug 1195167 part 1: Let necko handle all protocols. r=bkelly (bb932b0ada)
- Bug 1195167 part 2: Remove redundant aCORSFlag argument and instead use mCORSFlagEverSet. r=bkelly (beadafcad0)
- Bug 1195167 part 3: Remove more scheme-specific handling from FetchDriver. r=bkelly (d00b38db9e)
- Bug 1195167 part 4: Remove FetchDriver::BasicFetch since it is empty. r=bkelly (c5ed097267)
- Bug 1210413 P2 Test CORS credentials on cross-origin redirects. r=sicking a=dveditz (b4eeb8aac0)
- Bug 1210413 P1 Propagate new channel load flags from child to parent on redirect. r=jduell a=dveditz (8b329af4fa)
- Bug 1195167 part 5: Make FetchDriver use AsyncOpen2. r=bkelly (cc217c4cc1)
- Bug 1195167 part 6: Some code simplification since necko handles fetch recursion. r=bkelly (f3b6da2262)
- Bug 1195167: Followup to fix test which I forgot to change (81e7439a2e)
- Bug 1215746: Remove RequestMode::Cors_with_forced_preflight. r=bkelly (0336e812b6)
- Bug 1211000: Move CORS preflight logic from nsCORSListenerProxy to nsCORSPreflightListener. r=ehsan (bf2f71cf22)
- missing bit of Bug 1211443 - Drop scheduled update if decoder initialization isn't done yet. r=jya (f6bc074e33)
- Bug 1182571: Fix nsILoadInfo->GetContentPolicyType API to be less ambigious. Audit and fix all users of it. r=ckerschb (5af6fa7442)
- fix (e40c8e7625)
- Bug 1173811 - Part 1: Propagate the response URL to intercepted channels when necessary (non-e10s). r=mayhemer,bkelly (26f4f13c28)
- Bug 1173811 - Part 2: Propagate the response URL to intercepted channels when necessary (e10s). r=mayhemer,bkelly (a603fe1df2)
- Bug 1154309 - Add New Resource Timing Fields r=bz,hurley (1d14eb6bef)
- Bug 1175685 - add OriginAttribute to LoadInfo. r=jonas, r=ckerschb, r=michal (a5d18bb637)
- Bug 1175685 - add OriginAttribute to LoadInfo. r=jonas, r=ckerschb, r=michal (fb07d2c8aa)
- Bug 1212904 P1 Add a LoadTainting enumeration. r=jduell (a1db8a3e99)
- Bug 1212904 P2 Add LoadTainting information to nsILoadInfo. r=jduell (2482e5e334)
- Bug 1221151 - use [infallible] in nsILoadInfo.idl instead of manual %{C++ blocks; r=jduell (aae73129b6)
- Bug 1045891 - CSP 2 child-src implementation r=ckerschb (792920aeb9)
- Bug 1219931 - CSP: Don't allow removing a policy (r=sicking) (9daaab4186)
- Bug 1208661 - Dump client-side layer textures. r=BenWa (1f2d17d515)
1170 lines
35 KiB
C++
1170 lines
35 KiB
C++
/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
|
|
/* vim: set ts=8 sts=2 et sw=2 tw=80: */
|
|
/* 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/. */
|
|
|
|
#include "Animation.h"
|
|
#include "AnimationUtils.h"
|
|
#include "mozilla/dom/AnimationBinding.h"
|
|
#include "mozilla/dom/AnimationPlaybackEvent.h"
|
|
#include "mozilla/AutoRestore.h"
|
|
#include "mozilla/AsyncEventDispatcher.h" //For AsyncEventDispatcher
|
|
#include "AnimationCommon.h" // For AnimationCollection,
|
|
// CommonAnimationManager
|
|
#include "nsIDocument.h" // For nsIDocument
|
|
#include "nsIPresShell.h" // For nsIPresShell
|
|
#include "nsLayoutUtils.h" // For PostRestyleEvent (remove after bug 1073336)
|
|
#include "nsThreadUtils.h" // For nsRunnableMethod and nsRevocableEventPtr
|
|
#include "PendingAnimationTracker.h" // For PendingAnimationTracker
|
|
|
|
namespace mozilla {
|
|
namespace dom {
|
|
|
|
// Static members
|
|
uint64_t Animation::sNextAnimationIndex = 0;
|
|
|
|
NS_IMPL_CYCLE_COLLECTION_INHERITED(Animation, DOMEventTargetHelper,
|
|
mTimeline,
|
|
mEffect,
|
|
mReady,
|
|
mFinished)
|
|
|
|
NS_IMPL_ADDREF_INHERITED(Animation, DOMEventTargetHelper)
|
|
NS_IMPL_RELEASE_INHERITED(Animation, DOMEventTargetHelper)
|
|
|
|
NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION_INHERITED(Animation)
|
|
NS_INTERFACE_MAP_END_INHERITING(DOMEventTargetHelper)
|
|
|
|
JSObject*
|
|
Animation::WrapObject(JSContext* aCx, JS::Handle<JSObject*> aGivenProto)
|
|
{
|
|
return dom::AnimationBinding::Wrap(aCx, this, aGivenProto);
|
|
}
|
|
|
|
// ---------------------------------------------------------------------------
|
|
//
|
|
// Animation interface:
|
|
//
|
|
// ---------------------------------------------------------------------------
|
|
|
|
void
|
|
Animation::SetEffect(KeyframeEffectReadOnly* aEffect)
|
|
{
|
|
if (mEffect == aEffect) {
|
|
return;
|
|
}
|
|
if (mEffect) {
|
|
mEffect->SetParentTime(Nullable<TimeDuration>());
|
|
}
|
|
mEffect = aEffect;
|
|
if (mEffect) {
|
|
mEffect->SetParentTime(GetCurrentTime());
|
|
}
|
|
|
|
UpdateTiming(SeekFlag::NoSeek, SyncNotifyFlag::Async);
|
|
}
|
|
|
|
void
|
|
Animation::SetTimeline(AnimationTimeline* aTimeline)
|
|
{
|
|
if (mTimeline == aTimeline) {
|
|
return;
|
|
}
|
|
|
|
if (mTimeline) {
|
|
mTimeline->RemoveAnimation(*this);
|
|
}
|
|
|
|
mTimeline = aTimeline;
|
|
|
|
// FIXME(spec): Once we implement the seeking defined in the spec
|
|
// surely this should be SeekFlag::DidSeek but the spec says otherwise.
|
|
UpdateTiming(SeekFlag::NoSeek, SyncNotifyFlag::Async);
|
|
|
|
// FIXME: When we expose this method to script we'll need to call PostUpdate
|
|
// (but *not* when this method gets called from style).
|
|
}
|
|
|
|
// https://w3c.github.io/web-animations/#set-the-animation-start-time
|
|
void
|
|
Animation::SetStartTime(const Nullable<TimeDuration>& aNewStartTime)
|
|
{
|
|
Nullable<TimeDuration> timelineTime;
|
|
if (mTimeline) {
|
|
// The spec says to check if the timeline is active (has a resolved time)
|
|
// before using it here, but we don't need to since it's harmless to set
|
|
// the already null time to null.
|
|
timelineTime = mTimeline->GetCurrentTime();
|
|
}
|
|
if (timelineTime.IsNull() && !aNewStartTime.IsNull()) {
|
|
mHoldTime.SetNull();
|
|
}
|
|
|
|
Nullable<TimeDuration> previousCurrentTime = GetCurrentTime();
|
|
mStartTime = aNewStartTime;
|
|
if (!aNewStartTime.IsNull()) {
|
|
if (mPlaybackRate != 0.0) {
|
|
mHoldTime.SetNull();
|
|
}
|
|
} else {
|
|
mHoldTime = previousCurrentTime;
|
|
}
|
|
|
|
CancelPendingTasks();
|
|
if (mReady) {
|
|
// We may have already resolved mReady, but in that case calling
|
|
// MaybeResolve is a no-op, so that's okay.
|
|
mReady->MaybeResolve(this);
|
|
}
|
|
|
|
UpdateTiming(SeekFlag::NoSeek, SyncNotifyFlag::Async);
|
|
PostUpdate();
|
|
}
|
|
|
|
// https://w3c.github.io/web-animations/#current-time
|
|
Nullable<TimeDuration>
|
|
Animation::GetCurrentTime() const
|
|
{
|
|
Nullable<TimeDuration> result;
|
|
if (!mHoldTime.IsNull()) {
|
|
result = mHoldTime;
|
|
return result;
|
|
}
|
|
|
|
if (mTimeline && !mStartTime.IsNull()) {
|
|
Nullable<TimeDuration> timelineTime = mTimeline->GetCurrentTime();
|
|
if (!timelineTime.IsNull()) {
|
|
result.SetValue((timelineTime.Value() - mStartTime.Value())
|
|
.MultDouble(mPlaybackRate));
|
|
}
|
|
}
|
|
return result;
|
|
}
|
|
|
|
// https://w3c.github.io/web-animations/#set-the-current-time
|
|
void
|
|
Animation::SetCurrentTime(const TimeDuration& aSeekTime)
|
|
{
|
|
SilentlySetCurrentTime(aSeekTime);
|
|
|
|
if (mPendingState == PendingState::PausePending) {
|
|
// Finish the pause operation
|
|
mHoldTime.SetValue(aSeekTime);
|
|
mStartTime.SetNull();
|
|
|
|
if (mReady) {
|
|
mReady->MaybeResolve(this);
|
|
}
|
|
CancelPendingTasks();
|
|
}
|
|
|
|
UpdateTiming(SeekFlag::DidSeek, SyncNotifyFlag::Async);
|
|
PostUpdate();
|
|
}
|
|
|
|
// https://w3c.github.io/web-animations/#set-the-animation-playback-rate
|
|
void
|
|
Animation::SetPlaybackRate(double aPlaybackRate)
|
|
{
|
|
Nullable<TimeDuration> previousTime = GetCurrentTime();
|
|
mPlaybackRate = aPlaybackRate;
|
|
if (!previousTime.IsNull()) {
|
|
ErrorResult rv;
|
|
SetCurrentTime(previousTime.Value());
|
|
MOZ_ASSERT(!rv.Failed(), "Should not assert for non-null time");
|
|
}
|
|
}
|
|
|
|
// https://w3c.github.io/web-animations/#play-state
|
|
AnimationPlayState
|
|
Animation::PlayState() const
|
|
{
|
|
if (mPendingState != PendingState::NotPending) {
|
|
return AnimationPlayState::Pending;
|
|
}
|
|
|
|
Nullable<TimeDuration> currentTime = GetCurrentTime();
|
|
if (currentTime.IsNull()) {
|
|
return AnimationPlayState::Idle;
|
|
}
|
|
|
|
if (mStartTime.IsNull()) {
|
|
return AnimationPlayState::Paused;
|
|
}
|
|
|
|
if ((mPlaybackRate > 0.0 && currentTime.Value() >= EffectEnd()) ||
|
|
(mPlaybackRate < 0.0 && currentTime.Value().ToMilliseconds() <= 0.0)) {
|
|
return AnimationPlayState::Finished;
|
|
}
|
|
|
|
return AnimationPlayState::Running;
|
|
}
|
|
|
|
Promise*
|
|
Animation::GetReady(ErrorResult& aRv)
|
|
{
|
|
nsCOMPtr<nsIGlobalObject> global = GetOwnerGlobal();
|
|
if (!mReady && global) {
|
|
mReady = Promise::Create(global, aRv); // Lazily create on demand
|
|
}
|
|
if (!mReady) {
|
|
aRv.Throw(NS_ERROR_FAILURE);
|
|
} else if (PlayState() != AnimationPlayState::Pending) {
|
|
mReady->MaybeResolve(this);
|
|
}
|
|
return mReady;
|
|
}
|
|
|
|
Promise*
|
|
Animation::GetFinished(ErrorResult& aRv)
|
|
{
|
|
nsCOMPtr<nsIGlobalObject> global = GetOwnerGlobal();
|
|
if (!mFinished && global) {
|
|
mFinished = Promise::Create(global, aRv); // Lazily create on demand
|
|
}
|
|
if (!mFinished) {
|
|
aRv.Throw(NS_ERROR_FAILURE);
|
|
} else if (mFinishedIsResolved) {
|
|
MaybeResolveFinishedPromise();
|
|
}
|
|
return mFinished;
|
|
}
|
|
|
|
void
|
|
Animation::Cancel()
|
|
{
|
|
DoCancel();
|
|
PostUpdate();
|
|
}
|
|
|
|
// https://w3c.github.io/web-animations/#finish-an-animation
|
|
void
|
|
Animation::Finish(ErrorResult& aRv)
|
|
{
|
|
if (mPlaybackRate == 0 ||
|
|
(mPlaybackRate > 0 && EffectEnd() == TimeDuration::Forever())) {
|
|
aRv.Throw(NS_ERROR_DOM_INVALID_STATE_ERR);
|
|
return;
|
|
}
|
|
|
|
TimeDuration limit =
|
|
mPlaybackRate > 0 ? TimeDuration(EffectEnd()) : TimeDuration(0);
|
|
|
|
SilentlySetCurrentTime(limit);
|
|
|
|
// If we are paused or play-pending we need to fill in the start time in
|
|
// order to transition to the finished state.
|
|
//
|
|
// We only do this, however, if we have an active timeline. If we have an
|
|
// inactive timeline we can't transition into the finished state just like
|
|
// we can't transition to the running state (this finished state is really
|
|
// a substate of the running state).
|
|
if (mStartTime.IsNull() &&
|
|
mTimeline &&
|
|
!mTimeline->GetCurrentTime().IsNull()) {
|
|
mStartTime.SetValue(mTimeline->GetCurrentTime().Value() -
|
|
limit.MultDouble(1.0 / mPlaybackRate));
|
|
}
|
|
|
|
// If we just resolved the start time for a pause or play-pending
|
|
// animation, we need to clear the task. We don't do this as a branch of
|
|
// the above however since we can have a play-pending animation with a
|
|
// resolved start time if we aborted a pause operation.
|
|
if (!mStartTime.IsNull() &&
|
|
(mPendingState == PendingState::PlayPending ||
|
|
mPendingState == PendingState::PausePending)) {
|
|
if (mPendingState == PendingState::PausePending) {
|
|
mHoldTime.SetNull();
|
|
}
|
|
CancelPendingTasks();
|
|
if (mReady) {
|
|
mReady->MaybeResolve(this);
|
|
}
|
|
}
|
|
UpdateTiming(SeekFlag::DidSeek, SyncNotifyFlag::Sync);
|
|
PostUpdate();
|
|
}
|
|
|
|
void
|
|
Animation::Play(ErrorResult& aRv, LimitBehavior aLimitBehavior)
|
|
{
|
|
DoPlay(aRv, aLimitBehavior);
|
|
PostUpdate();
|
|
}
|
|
|
|
void
|
|
Animation::Pause(ErrorResult& aRv)
|
|
{
|
|
DoPause(aRv);
|
|
PostUpdate();
|
|
}
|
|
|
|
// https://w3c.github.io/web-animations/#reverse-an-animation
|
|
void
|
|
Animation::Reverse(ErrorResult& aRv)
|
|
{
|
|
if (!mTimeline || mTimeline->GetCurrentTime().IsNull()) {
|
|
aRv.Throw(NS_ERROR_DOM_INVALID_STATE_ERR);
|
|
return;
|
|
}
|
|
|
|
if (mPlaybackRate == 0.0) {
|
|
return;
|
|
}
|
|
|
|
SilentlySetPlaybackRate(-mPlaybackRate);
|
|
Play(aRv, LimitBehavior::AutoRewind);
|
|
}
|
|
|
|
// ---------------------------------------------------------------------------
|
|
//
|
|
// JS wrappers for Animation interface:
|
|
//
|
|
// ---------------------------------------------------------------------------
|
|
|
|
Nullable<double>
|
|
Animation::GetStartTimeAsDouble() const
|
|
{
|
|
return AnimationUtils::TimeDurationToDouble(mStartTime);
|
|
}
|
|
|
|
void
|
|
Animation::SetStartTimeAsDouble(const Nullable<double>& aStartTime)
|
|
{
|
|
return SetStartTime(AnimationUtils::DoubleToTimeDuration(aStartTime));
|
|
}
|
|
|
|
Nullable<double>
|
|
Animation::GetCurrentTimeAsDouble() const
|
|
{
|
|
return AnimationUtils::TimeDurationToDouble(GetCurrentTime());
|
|
}
|
|
|
|
void
|
|
Animation::SetCurrentTimeAsDouble(const Nullable<double>& aCurrentTime,
|
|
ErrorResult& aRv)
|
|
{
|
|
if (aCurrentTime.IsNull()) {
|
|
if (!GetCurrentTime().IsNull()) {
|
|
aRv.Throw(NS_ERROR_DOM_TYPE_ERR);
|
|
}
|
|
return;
|
|
}
|
|
|
|
return SetCurrentTime(TimeDuration::FromMilliseconds(aCurrentTime.Value()));
|
|
}
|
|
|
|
// ---------------------------------------------------------------------------
|
|
|
|
void
|
|
Animation::Tick()
|
|
{
|
|
// Since we are not guaranteed to get only one call per refresh driver tick,
|
|
// it's possible that mPendingReadyTime is set to a time in the future.
|
|
// In that case, we should wait until the next refresh driver tick before
|
|
// resuming.
|
|
if (mPendingState != PendingState::NotPending &&
|
|
!mPendingReadyTime.IsNull() &&
|
|
mTimeline &&
|
|
!mTimeline->GetCurrentTime().IsNull() &&
|
|
mPendingReadyTime.Value() <= mTimeline->GetCurrentTime().Value()) {
|
|
FinishPendingAt(mPendingReadyTime.Value());
|
|
mPendingReadyTime.SetNull();
|
|
}
|
|
|
|
if (IsPossiblyOrphanedPendingAnimation()) {
|
|
MOZ_ASSERT(mTimeline && !mTimeline->GetCurrentTime().IsNull(),
|
|
"Orphaned pending animtaions should have an active timeline");
|
|
FinishPendingAt(mTimeline->GetCurrentTime().Value());
|
|
}
|
|
|
|
UpdateTiming(SeekFlag::NoSeek, SyncNotifyFlag::Async);
|
|
|
|
// FIXME: Detect the no-change case and don't request a restyle at all
|
|
// FIXME: Detect changes to IsPlaying() state and request RestyleType::Layer
|
|
// so that layers get updated immediately
|
|
AnimationCollection* collection = GetCollection();
|
|
if (collection) {
|
|
collection->RequestRestyle(CanThrottle() ?
|
|
AnimationCollection::RestyleType::Throttled :
|
|
AnimationCollection::RestyleType::Standard);
|
|
}
|
|
}
|
|
|
|
void
|
|
Animation::TriggerOnNextTick(const Nullable<TimeDuration>& aReadyTime)
|
|
{
|
|
// Normally we expect the play state to be pending but it's possible that,
|
|
// due to the handling of possibly orphaned animations in Tick(), this
|
|
// animation got started whilst still being in another document's pending
|
|
// animation map.
|
|
if (PlayState() != AnimationPlayState::Pending) {
|
|
return;
|
|
}
|
|
|
|
// If aReadyTime.IsNull() we'll detect this in Tick() where we check for
|
|
// orphaned animations and trigger this animation anyway
|
|
mPendingReadyTime = aReadyTime;
|
|
}
|
|
|
|
void
|
|
Animation::TriggerNow()
|
|
{
|
|
// Normally we expect the play state to be pending but when an animation
|
|
// is cancelled and its rendered document can't be reached, we can end up
|
|
// with the animation still in a pending player tracker even after it is
|
|
// no longer pending.
|
|
if (PlayState() != AnimationPlayState::Pending) {
|
|
return;
|
|
}
|
|
|
|
// If we don't have an active timeline we can't trigger the animation.
|
|
// However, this is a test-only method that we don't expect to be used in
|
|
// conjunction with animations without an active timeline so generate
|
|
// a warning if we do find ourselves in that situation.
|
|
if (!mTimeline || mTimeline->GetCurrentTime().IsNull()) {
|
|
NS_WARNING("Failed to trigger an animation with an active timeline");
|
|
return;
|
|
}
|
|
|
|
FinishPendingAt(mTimeline->GetCurrentTime().Value());
|
|
}
|
|
|
|
Nullable<TimeDuration>
|
|
Animation::GetCurrentOrPendingStartTime() const
|
|
{
|
|
Nullable<TimeDuration> result;
|
|
|
|
if (!mStartTime.IsNull()) {
|
|
result = mStartTime;
|
|
return result;
|
|
}
|
|
|
|
if (mPendingReadyTime.IsNull() || mHoldTime.IsNull()) {
|
|
return result;
|
|
}
|
|
|
|
// Calculate the equivalent start time from the pending ready time.
|
|
// This is the same as the calculation performed in ResumeAt and will
|
|
// need to incorporate the playbackRate when implemented (bug 1127380).
|
|
result.SetValue(mPendingReadyTime.Value() - mHoldTime.Value());
|
|
return result;
|
|
}
|
|
|
|
// https://w3c.github.io/web-animations/#silently-set-the-current-time
|
|
void
|
|
Animation::SilentlySetCurrentTime(const TimeDuration& aSeekTime)
|
|
{
|
|
if (!mHoldTime.IsNull() ||
|
|
mStartTime.IsNull() ||
|
|
!mTimeline ||
|
|
mTimeline->GetCurrentTime().IsNull() ||
|
|
mPlaybackRate == 0.0) {
|
|
mHoldTime.SetValue(aSeekTime);
|
|
if (!mTimeline || mTimeline->GetCurrentTime().IsNull()) {
|
|
mStartTime.SetNull();
|
|
}
|
|
} else {
|
|
mStartTime.SetValue(mTimeline->GetCurrentTime().Value() -
|
|
(aSeekTime.MultDouble(1 / mPlaybackRate)));
|
|
}
|
|
|
|
mPreviousCurrentTime.SetNull();
|
|
}
|
|
|
|
void
|
|
Animation::SilentlySetPlaybackRate(double aPlaybackRate)
|
|
{
|
|
Nullable<TimeDuration> previousTime = GetCurrentTime();
|
|
mPlaybackRate = aPlaybackRate;
|
|
if (!previousTime.IsNull()) {
|
|
ErrorResult rv;
|
|
SilentlySetCurrentTime(previousTime.Value());
|
|
MOZ_ASSERT(!rv.Failed(), "Should not assert for non-null time");
|
|
}
|
|
}
|
|
|
|
// https://w3c.github.io/web-animations/#cancel-an-animation
|
|
void
|
|
Animation::DoCancel()
|
|
{
|
|
if (mPendingState != PendingState::NotPending) {
|
|
CancelPendingTasks();
|
|
if (mReady) {
|
|
mReady->MaybeReject(NS_ERROR_DOM_ABORT_ERR);
|
|
}
|
|
}
|
|
|
|
if (mFinished) {
|
|
mFinished->MaybeReject(NS_ERROR_DOM_ABORT_ERR);
|
|
}
|
|
ResetFinishedPromise();
|
|
|
|
DispatchPlaybackEvent(NS_LITERAL_STRING("cancel"));
|
|
|
|
mHoldTime.SetNull();
|
|
mStartTime.SetNull();
|
|
|
|
UpdateTiming(SeekFlag::NoSeek, SyncNotifyFlag::Async);
|
|
}
|
|
|
|
void
|
|
Animation::UpdateRelevance()
|
|
{
|
|
bool wasRelevant = mIsRelevant;
|
|
mIsRelevant = HasCurrentEffect() || IsInEffect();
|
|
|
|
// Notify animation observers.
|
|
if (wasRelevant && !mIsRelevant) {
|
|
nsNodeUtils::AnimationRemoved(this);
|
|
} else if (!wasRelevant && mIsRelevant) {
|
|
nsNodeUtils::AnimationAdded(this);
|
|
}
|
|
}
|
|
|
|
bool
|
|
Animation::HasLowerCompositeOrderThan(const Animation& aOther) const
|
|
{
|
|
// Due to the way subclasses of this repurpose the mAnimationIndex to
|
|
// implement their own brand of composite ordering it is possible for
|
|
// two animations to have an identical mAnimationIndex member.
|
|
// However, these subclasses override this method so we shouldn't see
|
|
// identical animation indices here.
|
|
MOZ_ASSERT(mAnimationIndex != aOther.mAnimationIndex || &aOther == this,
|
|
"Animation indices should be unique");
|
|
|
|
return mAnimationIndex < aOther.mAnimationIndex;
|
|
}
|
|
|
|
bool
|
|
Animation::CanThrottle() const
|
|
{
|
|
// This method answers the question, "Can we get away with NOT updating
|
|
// style on the main thread for this animation on this tick?"
|
|
|
|
// Ignore animations that were never going to have any effect anyway.
|
|
if (!mEffect || mEffect->Properties().IsEmpty()) {
|
|
return true;
|
|
}
|
|
|
|
// Finished animations can be throttled unless this is the first
|
|
// sample since finishing. In that case we need an unthrottled sample
|
|
// so we can apply the correct end-of-animation behavior on the main
|
|
// thread (either removing the animation style or applying the fill mode).
|
|
if (PlayState() == AnimationPlayState::Finished) {
|
|
return mFinishedAtLastComposeStyle;
|
|
}
|
|
|
|
// We should also ignore animations which are not "in effect"--i.e. not
|
|
// producing an output. This includes animations that are idle or in their
|
|
// delay phase but with no backwards fill.
|
|
//
|
|
// Note that unlike newly-finished animations, we don't need to worry about
|
|
// special handling for newly-idle animations or animations that are newly
|
|
// yet-to-start since any operation that would cause that change (e.g. a call
|
|
// to cancel() on the animation, or seeking its current time) will trigger an
|
|
// unthrottled sample.
|
|
if (!IsInEffect()) {
|
|
return true;
|
|
}
|
|
|
|
return IsRunningOnCompositor();
|
|
}
|
|
|
|
void
|
|
Animation::ComposeStyle(RefPtr<AnimValuesStyleRule>& aStyleRule,
|
|
nsCSSPropertySet& aSetProperties,
|
|
bool& aNeedsRefreshes)
|
|
{
|
|
if (!mEffect) {
|
|
return;
|
|
}
|
|
|
|
AnimationPlayState playState = PlayState();
|
|
if (playState == AnimationPlayState::Running ||
|
|
playState == AnimationPlayState::Pending ||
|
|
HasEndEventToQueue()) {
|
|
aNeedsRefreshes = true;
|
|
}
|
|
|
|
if (!IsInEffect()) {
|
|
return;
|
|
}
|
|
|
|
// In order to prevent flicker, there are a few cases where we want to use
|
|
// a different time for rendering that would otherwise be returned by
|
|
// GetCurrentTime. These are:
|
|
//
|
|
// (a) For animations that are pausing but which are still running on the
|
|
// compositor. In this case we send a layer transaction that removes the
|
|
// animation but which also contains the animation values calculated on
|
|
// the main thread. To prevent flicker when this occurs we want to ensure
|
|
// the timeline time used to calculate the main thread animation values
|
|
// does not lag far behind the time used on the compositor. Ideally we
|
|
// would like to use the "animation ready time" calculated at the end of
|
|
// the layer transaction as the timeline time but it will be too late to
|
|
// update the style rule at that point so instead we just use the current
|
|
// wallclock time.
|
|
//
|
|
// (b) For animations that are pausing that we have already taken off the
|
|
// compositor. In this case we record a pending ready time but we don't
|
|
// apply it until the next tick. However, while waiting for the next tick,
|
|
// we should still use the pending ready time as the timeline time. If we
|
|
// use the regular timeline time the animation may appear jump backwards
|
|
// if the main thread's timeline time lags behind the compositor.
|
|
//
|
|
// (c) For animations that are play-pending due to an aborted pause operation
|
|
// (i.e. a pause operation that was interrupted before we entered the
|
|
// paused state). When we cancel a pending pause we might momentarily take
|
|
// the animation off the compositor, only to re-add it moments later. In
|
|
// that case the compositor might have been ahead of the main thread so we
|
|
// should use the current wallclock time to ensure the animation doesn't
|
|
// temporarily jump backwards.
|
|
//
|
|
// To address each of these cases we temporarily tweak the hold time
|
|
// immediately before updating the style rule and then restore it immediately
|
|
// afterwards. This is purely to prevent visual flicker. Other behavior
|
|
// such as dispatching events continues to rely on the regular timeline time.
|
|
{
|
|
AutoRestore<Nullable<TimeDuration>> restoreHoldTime(mHoldTime);
|
|
bool updatedHoldTime = false;
|
|
|
|
AnimationPlayState playState = PlayState();
|
|
|
|
if (playState == AnimationPlayState::Pending &&
|
|
mHoldTime.IsNull() &&
|
|
!mStartTime.IsNull()) {
|
|
Nullable<TimeDuration> timeToUse = mPendingReadyTime;
|
|
if (timeToUse.IsNull() &&
|
|
mTimeline &&
|
|
mTimeline->TracksWallclockTime()) {
|
|
timeToUse = mTimeline->ToTimelineTime(TimeStamp::Now());
|
|
}
|
|
if (!timeToUse.IsNull()) {
|
|
mHoldTime.SetValue((timeToUse.Value() - mStartTime.Value())
|
|
.MultDouble(mPlaybackRate));
|
|
// Push the change down to the effect
|
|
UpdateEffect();
|
|
updatedHoldTime = true;
|
|
}
|
|
}
|
|
|
|
mEffect->ComposeStyle(aStyleRule, aSetProperties);
|
|
|
|
if (updatedHoldTime) {
|
|
UpdateTiming(SeekFlag::NoSeek, SyncNotifyFlag::Async);
|
|
}
|
|
|
|
mFinishedAtLastComposeStyle = (playState == AnimationPlayState::Finished);
|
|
}
|
|
}
|
|
|
|
void
|
|
Animation::NotifyEffectTimingUpdated()
|
|
{
|
|
MOZ_ASSERT(mEffect,
|
|
"We should only update timing effect when we have a target "
|
|
"effect");
|
|
UpdateTiming(Animation::SeekFlag::NoSeek,
|
|
Animation::SyncNotifyFlag::Async);
|
|
}
|
|
|
|
// https://w3c.github.io/web-animations/#play-an-animation
|
|
void
|
|
Animation::DoPlay(ErrorResult& aRv, LimitBehavior aLimitBehavior)
|
|
{
|
|
bool abortedPause = mPendingState == PendingState::PausePending;
|
|
|
|
Nullable<TimeDuration> currentTime = GetCurrentTime();
|
|
if (mPlaybackRate > 0.0 &&
|
|
(currentTime.IsNull() ||
|
|
(aLimitBehavior == LimitBehavior::AutoRewind &&
|
|
(currentTime.Value().ToMilliseconds() < 0.0 ||
|
|
currentTime.Value() >= EffectEnd())))) {
|
|
mHoldTime.SetValue(TimeDuration(0));
|
|
} else if (mPlaybackRate < 0.0 &&
|
|
(currentTime.IsNull() ||
|
|
(aLimitBehavior == LimitBehavior::AutoRewind &&
|
|
(currentTime.Value().ToMilliseconds() <= 0.0 ||
|
|
currentTime.Value() > EffectEnd())))) {
|
|
if (EffectEnd() == TimeDuration::Forever()) {
|
|
aRv.Throw(NS_ERROR_DOM_INVALID_STATE_ERR);
|
|
return;
|
|
}
|
|
mHoldTime.SetValue(TimeDuration(EffectEnd()));
|
|
} else if (mPlaybackRate == 0.0 && currentTime.IsNull()) {
|
|
mHoldTime.SetValue(TimeDuration(0));
|
|
}
|
|
|
|
bool reuseReadyPromise = false;
|
|
if (mPendingState != PendingState::NotPending) {
|
|
CancelPendingTasks();
|
|
reuseReadyPromise = true;
|
|
}
|
|
|
|
// If the hold time is null then we're either already playing normally (and
|
|
// we can ignore this call) or we aborted a pending pause operation (in which
|
|
// case, for consistency, we need to go through the motions of doing an
|
|
// asynchronous start even though we already have a resolved start time).
|
|
if (mHoldTime.IsNull() && !abortedPause) {
|
|
return;
|
|
}
|
|
|
|
// Clear the start time until we resolve a new one (unless we are aborting
|
|
// a pending pause operation, in which case we keep the old start time so
|
|
// that the animation continues moving uninterrupted by the aborted pause).
|
|
if (!abortedPause) {
|
|
mStartTime.SetNull();
|
|
}
|
|
|
|
if (!reuseReadyPromise) {
|
|
// Clear ready promise. We'll create a new one lazily.
|
|
mReady = nullptr;
|
|
}
|
|
|
|
mPendingState = PendingState::PlayPending;
|
|
|
|
nsIDocument* doc = GetRenderedDocument();
|
|
if (doc) {
|
|
PendingAnimationTracker* tracker =
|
|
doc->GetOrCreatePendingAnimationTracker();
|
|
tracker->AddPlayPending(*this);
|
|
} else {
|
|
TriggerOnNextTick(Nullable<TimeDuration>());
|
|
}
|
|
|
|
UpdateTiming(SeekFlag::NoSeek, SyncNotifyFlag::Async);
|
|
}
|
|
|
|
// https://w3c.github.io/web-animations/#pause-an-animation
|
|
void
|
|
Animation::DoPause(ErrorResult& aRv)
|
|
{
|
|
if (IsPausedOrPausing()) {
|
|
return;
|
|
}
|
|
|
|
// If we are transitioning from idle, fill in the current time
|
|
if (GetCurrentTime().IsNull()) {
|
|
if (mPlaybackRate >= 0.0) {
|
|
mHoldTime.SetValue(TimeDuration(0));
|
|
} else {
|
|
if (EffectEnd() == TimeDuration::Forever()) {
|
|
aRv.Throw(NS_ERROR_DOM_INVALID_STATE_ERR);
|
|
return;
|
|
}
|
|
mHoldTime.SetValue(TimeDuration(EffectEnd()));
|
|
}
|
|
}
|
|
|
|
bool reuseReadyPromise = false;
|
|
if (mPendingState == PendingState::PlayPending) {
|
|
CancelPendingTasks();
|
|
reuseReadyPromise = true;
|
|
}
|
|
|
|
if (!reuseReadyPromise) {
|
|
// Clear ready promise. We'll create a new one lazily.
|
|
mReady = nullptr;
|
|
}
|
|
|
|
mPendingState = PendingState::PausePending;
|
|
|
|
nsIDocument* doc = GetRenderedDocument();
|
|
if (doc) {
|
|
PendingAnimationTracker* tracker =
|
|
doc->GetOrCreatePendingAnimationTracker();
|
|
tracker->AddPausePending(*this);
|
|
} else {
|
|
TriggerOnNextTick(Nullable<TimeDuration>());
|
|
}
|
|
|
|
UpdateTiming(SeekFlag::NoSeek, SyncNotifyFlag::Async);
|
|
}
|
|
|
|
void
|
|
Animation::ResumeAt(const TimeDuration& aReadyTime)
|
|
{
|
|
// This method is only expected to be called for an animation that is
|
|
// waiting to play. We can easily adapt it to handle other states
|
|
// but it's currently not necessary.
|
|
MOZ_ASSERT(mPendingState == PendingState::PlayPending,
|
|
"Expected to resume a play-pending animation");
|
|
MOZ_ASSERT(mHoldTime.IsNull() != mStartTime.IsNull(),
|
|
"An animation in the play-pending state should have either a"
|
|
" resolved hold time or resolved start time (but not both)");
|
|
|
|
// If we aborted a pending pause operation we will already have a start time
|
|
// we should use. In all other cases, we resolve it from the ready time.
|
|
if (mStartTime.IsNull()) {
|
|
if (mPlaybackRate != 0) {
|
|
mStartTime.SetValue(aReadyTime -
|
|
(mHoldTime.Value().MultDouble(1 / mPlaybackRate)));
|
|
mHoldTime.SetNull();
|
|
} else {
|
|
mStartTime.SetValue(aReadyTime);
|
|
}
|
|
}
|
|
mPendingState = PendingState::NotPending;
|
|
|
|
UpdateTiming(SeekFlag::NoSeek, SyncNotifyFlag::Async);
|
|
|
|
if (mReady) {
|
|
mReady->MaybeResolve(this);
|
|
}
|
|
}
|
|
|
|
void
|
|
Animation::PauseAt(const TimeDuration& aReadyTime)
|
|
{
|
|
MOZ_ASSERT(mPendingState == PendingState::PausePending,
|
|
"Expected to pause a pause-pending animation");
|
|
|
|
if (!mStartTime.IsNull()) {
|
|
mHoldTime.SetValue((aReadyTime - mStartTime.Value())
|
|
.MultDouble(mPlaybackRate));
|
|
}
|
|
mStartTime.SetNull();
|
|
mPendingState = PendingState::NotPending;
|
|
|
|
UpdateTiming(SeekFlag::NoSeek, SyncNotifyFlag::Async);
|
|
|
|
if (mReady) {
|
|
mReady->MaybeResolve(this);
|
|
}
|
|
}
|
|
|
|
void
|
|
Animation::UpdateTiming(SeekFlag aSeekFlag, SyncNotifyFlag aSyncNotifyFlag)
|
|
{
|
|
// We call UpdateFinishedState before UpdateEffect because the former
|
|
// can change the current time, which is used by the latter.
|
|
UpdateFinishedState(aSeekFlag, aSyncNotifyFlag);
|
|
UpdateEffect();
|
|
|
|
// Unconditionally Add/Remove from the timeline. This is ok because if the
|
|
// animation has already been added/removed (which will be true more often
|
|
// than not) the work done by AnimationTimeline/DocumentTimeline is still
|
|
// negligible and its easier than trying to detect whenever we are switching
|
|
// to/from being relevant.
|
|
//
|
|
// We need to do this after calling UpdateEffect since it updates some
|
|
// cached state used by IsRelevant.
|
|
//
|
|
// Note that we only store relevant animations on the timeline since they
|
|
// are the only ones that need ticks and are the only ones returned from
|
|
// AnimationTimeline::GetAnimations. Storing any more than that would mean
|
|
// that we fail to garbage collect irrelevant animations since the timeline
|
|
// keeps a strong reference to each animation.
|
|
//
|
|
// Once we tick animations from the their timeline, and once we expect
|
|
// timelines to go in and out of being inactive, we will also need to store
|
|
// non-idle animations that are waiting for their timeline to become active
|
|
// on their timeline (as otherwise once the timeline becomes active it will
|
|
// have no way of notifying its animations). For now, however, we can
|
|
// simply store just the relevant animations.
|
|
if (mTimeline) {
|
|
// FIXME: Once we expect animations to go back and forth betweeen being
|
|
// inactive and active, we will need to store more than just relevant
|
|
// animations on the timeline. This is because an animation might be
|
|
// deemed irrelevant because its timeline is inactive. If it is removed
|
|
// from the timeline at that point the timeline will have no way of
|
|
// getting the animation to add itself again once it becomes active.
|
|
if (IsRelevant()) {
|
|
mTimeline->AddAnimation(*this);
|
|
} else {
|
|
mTimeline->RemoveAnimation(*this);
|
|
}
|
|
}
|
|
}
|
|
|
|
// https://w3c.github.io/web-animations/#update-an-animations-finished-state
|
|
void
|
|
Animation::UpdateFinishedState(SeekFlag aSeekFlag,
|
|
SyncNotifyFlag aSyncNotifyFlag)
|
|
{
|
|
Nullable<TimeDuration> currentTime = GetCurrentTime();
|
|
TimeDuration effectEnd = TimeDuration(EffectEnd());
|
|
|
|
if (!mStartTime.IsNull() &&
|
|
mPendingState == PendingState::NotPending) {
|
|
if (mPlaybackRate > 0.0 &&
|
|
!currentTime.IsNull() &&
|
|
currentTime.Value() >= effectEnd) {
|
|
if (aSeekFlag == SeekFlag::DidSeek) {
|
|
mHoldTime = currentTime;
|
|
} else if (!mPreviousCurrentTime.IsNull()) {
|
|
mHoldTime.SetValue(std::max(mPreviousCurrentTime.Value(), effectEnd));
|
|
} else {
|
|
mHoldTime.SetValue(effectEnd);
|
|
}
|
|
} else if (mPlaybackRate < 0.0 &&
|
|
!currentTime.IsNull() &&
|
|
currentTime.Value().ToMilliseconds() <= 0.0) {
|
|
if (aSeekFlag == SeekFlag::DidSeek) {
|
|
mHoldTime = currentTime;
|
|
} else {
|
|
mHoldTime.SetValue(0);
|
|
}
|
|
} else if (mPlaybackRate != 0.0 &&
|
|
!currentTime.IsNull() &&
|
|
mTimeline &&
|
|
!mTimeline->GetCurrentTime().IsNull()) {
|
|
if (aSeekFlag == SeekFlag::DidSeek && !mHoldTime.IsNull()) {
|
|
mStartTime.SetValue(mTimeline->GetCurrentTime().Value() -
|
|
(mHoldTime.Value().MultDouble(1 / mPlaybackRate)));
|
|
}
|
|
mHoldTime.SetNull();
|
|
}
|
|
}
|
|
|
|
bool currentFinishedState = PlayState() == AnimationPlayState::Finished;
|
|
if (currentFinishedState && !mFinishedIsResolved) {
|
|
DoFinishNotification(aSyncNotifyFlag);
|
|
} else if (!currentFinishedState && mFinishedIsResolved) {
|
|
ResetFinishedPromise();
|
|
}
|
|
// We must recalculate the current time to take account of any mHoldTime
|
|
// changes the code above made.
|
|
mPreviousCurrentTime = GetCurrentTime();
|
|
}
|
|
|
|
void
|
|
Animation::UpdateEffect()
|
|
{
|
|
if (mEffect) {
|
|
mEffect->SetParentTime(GetCurrentTime());
|
|
UpdateRelevance();
|
|
}
|
|
}
|
|
|
|
void
|
|
Animation::FlushStyle() const
|
|
{
|
|
nsIDocument* doc = GetRenderedDocument();
|
|
if (doc) {
|
|
doc->FlushPendingNotifications(Flush_Style);
|
|
}
|
|
}
|
|
|
|
void
|
|
Animation::PostUpdate()
|
|
{
|
|
AnimationCollection* collection = GetCollection();
|
|
if (collection) {
|
|
collection->RequestRestyle(AnimationCollection::RestyleType::Layer);
|
|
}
|
|
}
|
|
|
|
void
|
|
Animation::CancelPendingTasks()
|
|
{
|
|
if (mPendingState == PendingState::NotPending) {
|
|
return;
|
|
}
|
|
|
|
nsIDocument* doc = GetRenderedDocument();
|
|
if (doc) {
|
|
PendingAnimationTracker* tracker = doc->GetPendingAnimationTracker();
|
|
if (tracker) {
|
|
if (mPendingState == PendingState::PlayPending) {
|
|
tracker->RemovePlayPending(*this);
|
|
} else {
|
|
tracker->RemovePausePending(*this);
|
|
}
|
|
}
|
|
}
|
|
|
|
mPendingState = PendingState::NotPending;
|
|
mPendingReadyTime.SetNull();
|
|
}
|
|
|
|
bool
|
|
Animation::IsPossiblyOrphanedPendingAnimation() const
|
|
{
|
|
// Check if we are pending but might never start because we are not being
|
|
// tracked.
|
|
//
|
|
// This covers the following cases:
|
|
//
|
|
// * We started playing but our effect's target element was orphaned
|
|
// or bound to a different document.
|
|
// (note that for the case of our effect changing we should handle
|
|
// that in SetEffect)
|
|
// * We started playing but our timeline became inactive.
|
|
// In this case the pending animation tracker will drop us from its hashmap
|
|
// when we have been painted.
|
|
// * When we started playing we couldn't find a PendingAnimationTracker to
|
|
// register with (perhaps the effect had no document) so we simply
|
|
// set mPendingState in DoPlay and relied on this method to catch us on the
|
|
// next tick.
|
|
|
|
// If we're not pending we're ok.
|
|
if (mPendingState == PendingState::NotPending) {
|
|
return false;
|
|
}
|
|
|
|
// If we have a pending ready time then we will be started on the next
|
|
// tick.
|
|
if (!mPendingReadyTime.IsNull()) {
|
|
return false;
|
|
}
|
|
|
|
// If we don't have an active timeline then we shouldn't start until
|
|
// we do.
|
|
if (!mTimeline || mTimeline->GetCurrentTime().IsNull()) {
|
|
return false;
|
|
}
|
|
|
|
// If we have no rendered document, or we're not in our rendered document's
|
|
// PendingAnimationTracker then there's a good chance no one is tracking us.
|
|
//
|
|
// If we're wrong and another document is tracking us then, at worst, we'll
|
|
// simply start/pause the animation one tick too soon. That's better than
|
|
// never starting/pausing the animation and is unlikely.
|
|
nsIDocument* doc = GetRenderedDocument();
|
|
if (!doc) {
|
|
return true;
|
|
}
|
|
|
|
PendingAnimationTracker* tracker = doc->GetPendingAnimationTracker();
|
|
return !tracker ||
|
|
(!tracker->IsWaitingToPlay(*this) &&
|
|
!tracker->IsWaitingToPause(*this));
|
|
}
|
|
|
|
StickyTimeDuration
|
|
Animation::EffectEnd() const
|
|
{
|
|
if (!mEffect) {
|
|
return StickyTimeDuration(0);
|
|
}
|
|
|
|
return mEffect->Timing().mDelay
|
|
+ mEffect->GetComputedTiming().mActiveDuration;
|
|
}
|
|
|
|
nsIDocument*
|
|
Animation::GetRenderedDocument() const
|
|
{
|
|
if (!mEffect) {
|
|
return nullptr;
|
|
}
|
|
|
|
Element* targetElement;
|
|
nsCSSPseudoElements::Type pseudoType;
|
|
mEffect->GetTarget(targetElement, pseudoType);
|
|
if (!targetElement) {
|
|
return nullptr;
|
|
}
|
|
|
|
return targetElement->GetComposedDoc();
|
|
}
|
|
|
|
nsPresContext*
|
|
Animation::GetPresContext() const
|
|
{
|
|
nsIDocument* doc = GetRenderedDocument();
|
|
if (!doc) {
|
|
return nullptr;
|
|
}
|
|
nsIPresShell* shell = doc->GetShell();
|
|
if (!shell) {
|
|
return nullptr;
|
|
}
|
|
return shell->GetPresContext();
|
|
}
|
|
|
|
AnimationCollection*
|
|
Animation::GetCollection() const
|
|
{
|
|
CommonAnimationManager* manager = GetAnimationManager();
|
|
if (!manager) {
|
|
return nullptr;
|
|
}
|
|
MOZ_ASSERT(mEffect,
|
|
"An animation with an animation manager must have an effect");
|
|
|
|
Element* targetElement;
|
|
nsCSSPseudoElements::Type targetPseudoType;
|
|
mEffect->GetTarget(targetElement, targetPseudoType);
|
|
MOZ_ASSERT(targetElement,
|
|
"An animation with an animation manager must have a target");
|
|
|
|
return manager->GetAnimations(targetElement, targetPseudoType, false);
|
|
}
|
|
|
|
void
|
|
Animation::DoFinishNotification(SyncNotifyFlag aSyncNotifyFlag)
|
|
{
|
|
if (aSyncNotifyFlag == SyncNotifyFlag::Sync) {
|
|
DoFinishNotificationImmediately();
|
|
} else if (!mFinishNotificationTask.IsPending()) {
|
|
RefPtr<nsRunnableMethod<Animation>> runnable =
|
|
NS_NewRunnableMethod(this, &Animation::DoFinishNotificationImmediately);
|
|
Promise::DispatchToMicroTask(runnable);
|
|
mFinishNotificationTask = runnable;
|
|
}
|
|
}
|
|
|
|
void
|
|
Animation::ResetFinishedPromise()
|
|
{
|
|
mFinishedIsResolved = false;
|
|
mFinished = nullptr;
|
|
}
|
|
|
|
void
|
|
Animation::MaybeResolveFinishedPromise()
|
|
{
|
|
if (mFinished) {
|
|
mFinished->MaybeResolve(this);
|
|
}
|
|
mFinishedIsResolved = true;
|
|
}
|
|
|
|
void
|
|
Animation::DoFinishNotificationImmediately()
|
|
{
|
|
mFinishNotificationTask.Revoke();
|
|
|
|
if (PlayState() != AnimationPlayState::Finished) {
|
|
return;
|
|
}
|
|
|
|
MaybeResolveFinishedPromise();
|
|
|
|
DispatchPlaybackEvent(NS_LITERAL_STRING("finish"));
|
|
}
|
|
|
|
void
|
|
Animation::DispatchPlaybackEvent(const nsAString& aName)
|
|
{
|
|
AnimationPlaybackEventInit init;
|
|
|
|
if (aName.EqualsLiteral("finish")) {
|
|
init.mCurrentTime = GetCurrentTimeAsDouble();
|
|
}
|
|
if (mTimeline) {
|
|
init.mTimelineTime = mTimeline->GetCurrentTimeAsDouble();
|
|
}
|
|
|
|
RefPtr<AnimationPlaybackEvent> event =
|
|
AnimationPlaybackEvent::Constructor(this, aName, init);
|
|
event->SetTrusted(true);
|
|
|
|
RefPtr<AsyncEventDispatcher> asyncDispatcher =
|
|
new AsyncEventDispatcher(this, event);
|
|
asyncDispatcher->PostDOMEvent();
|
|
}
|
|
|
|
bool
|
|
Animation::IsRunningOnCompositor() const
|
|
{
|
|
return mEffect && mEffect->IsRunningOnCompositor();
|
|
}
|
|
|
|
} // namespace dom
|
|
} // namespace mozilla
|