Bug 1037321 - Implement playState on AnimationPlayer; r=birtles, r=bz
authorDavid Zbarsky <dzbarsky@gmail.com>
Mon, 20 Oct 2014 13:55:45 +0900
changeset 211173 3e5016be92f475efa67a98e67c6e42ccc52bec97
parent 211172 8e104207af9d99dab949103534239bd8b2d34d92
child 211174 f6866bdaa73dcf0c26604b8c00bb9f8bc8af84c4
push id1
push userroot
push dateMon, 20 Oct 2014 17:29:22 +0000
reviewersbirtles, bz
bugs1037321
milestone36.0a1
Bug 1037321 - Implement playState on AnimationPlayer; r=birtles, r=bz
dom/animation/AnimationPlayer.cpp
dom/animation/AnimationPlayer.h
dom/animation/test/css-integration/test_animation-player-playstate.html
dom/animation/test/css-integration/test_element-get-animation-players.html
dom/animation/test/mochitest.ini
dom/webidl/AnimationPlayer.webidl
--- a/dom/animation/AnimationPlayer.cpp
+++ b/dom/animation/AnimationPlayer.cpp
@@ -29,16 +29,35 @@ AnimationPlayer::GetStartTime() const
 }
 
 Nullable<double>
 AnimationPlayer::GetCurrentTime() const
 {
   return AnimationUtils::TimeDurationToDouble(GetCurrentTimeDuration());
 }
 
+AnimationPlayState
+AnimationPlayer::PlayState() const
+{
+  Nullable<TimeDuration> currentTime = GetCurrentTimeDuration();
+  if (currentTime.IsNull()) {
+    return AnimationPlayState::Idle;
+  }
+
+  if (mIsPaused) {
+    return AnimationPlayState::Paused;
+  }
+
+  if (currentTime.Value() >= SourceContentEnd()) {
+    return AnimationPlayState::Finished;
+  }
+
+  return AnimationPlayState::Running;
+}
+
 void
 AnimationPlayer::Play(UpdateFlags aFlags)
 {
   // FIXME: When we implement finishing behavior (bug 1074630) we should
   // not return early if mIsPaused is false since we may still need to seek.
   // (However, we will need to pass a flag so that when we start playing due to
   //  a change in animation-play-state we *don't* trigger finishing behavior.)
   if (!mIsPaused) {
@@ -76,16 +95,27 @@ AnimationPlayer::Pause(UpdateFlags aFlag
   mHoldTime = GetCurrentTimeDuration();
   mStartTime.SetNull();
 
   if (aFlags == eUpdateStyle) {
     MaybePostRestyle();
   }
 }
 
+AnimationPlayState
+AnimationPlayer::PlayStateFromJS() const
+{
+  // FIXME: Once we introduce CSSTransitionPlayer, this should move to an
+  // override of PlayStateFromJS in CSSAnimationPlayer and CSSTransitionPlayer
+  // and we should skip it in the general case.
+  FlushStyle();
+
+  return PlayState();
+}
+
 void
 AnimationPlayer::PlayFromJS()
 {
   // Flush style to ensure that any properties controlling animation state
   // (e.g. animation-play-state) are fully updated before we proceed.
   //
   // Note that this might trigger PlayFromStyle()/PauseFromStyle() on this
   // object.
@@ -169,16 +199,27 @@ AnimationPlayer::MaybePostRestyle() cons
 
   // FIXME: This is a bit heavy-handed but in bug 1073336 we hope to
   // introduce a better means for players to update style.
   nsLayoutUtils::PostRestyleEvent(mSource->GetTarget(),
                                   eRestyle_Self,
                                   nsChangeHint_AllReflowHints);
 }
 
+StickyTimeDuration
+AnimationPlayer::SourceContentEnd() const
+{
+  if (!mSource) {
+    return StickyTimeDuration(0);
+  }
+
+  return mSource->Timing().mDelay
+         + mSource->GetComputedTiming().mActiveDuration;
+}
+
 } // namespace dom
 
 void
 CSSAnimationPlayer::Play(UpdateFlags aUpdateFlags)
 {
   mPauseShouldStick = false;
   AnimationPlayer::Play(aUpdateFlags);
 }
--- a/dom/animation/AnimationPlayer.h
+++ b/dom/animation/AnimationPlayer.h
@@ -6,16 +6,17 @@
 #ifndef mozilla_dom_AnimationPlayer_h
 #define mozilla_dom_AnimationPlayer_h
 
 #include "nsWrapperCache.h"
 #include "nsCycleCollectionParticipant.h"
 #include "mozilla/Attributes.h"
 #include "mozilla/TimeStamp.h" // for TimeStamp, TimeDuration
 #include "mozilla/dom/Animation.h" // for Animation
+#include "mozilla/dom/AnimationPlayerBinding.h" // for AnimationPlayState
 #include "mozilla/dom/AnimationTimeline.h" // for AnimationTimeline
 #include "nsCSSProperty.h" // for nsCSSProperty
 
 // X11 has a #define for CurrentTime.
 #ifdef CurrentTime
 #undef CurrentTime
 #endif
 
@@ -55,22 +56,24 @@ public:
     eUpdateStyle
   };
 
   // AnimationPlayer methods
   Animation* GetSource() const { return mSource; }
   AnimationTimeline* Timeline() const { return mTimeline; }
   Nullable<double> GetStartTime() const;
   Nullable<double> GetCurrentTime() const;
+  AnimationPlayState PlayState() const;
   virtual void Play(UpdateFlags aUpdateFlags);
   virtual void Pause(UpdateFlags aUpdateFlags);
   bool IsRunningOnCompositor() const { return mIsRunningOnCompositor; }
 
   // Wrapper functions for performing extra steps such as flushing
   // style when calling from JS.
+  AnimationPlayState PlayStateFromJS() const;
   void PlayFromJS();
   void PauseFromJS();
 
   void SetSource(Animation* aSource);
   void Tick();
 
   const nsString& Name() const {
     return mSource ? mSource->Name() : EmptyString();
@@ -96,16 +99,17 @@ public:
   bool mIsRunningOnCompositor;
 
   nsRefPtr<AnimationTimeline> mTimeline;
   nsRefPtr<Animation> mSource;
 
 protected:
   void FlushStyle() const;
   void MaybePostRestyle() const;
+  StickyTimeDuration SourceContentEnd() const;
 
   Nullable<TimeDuration> mHoldTime;  // Player timescale
   bool mIsPaused;
 };
 
 } // namespace dom
 
 class CSSAnimationPlayer MOZ_FINAL : public dom::AnimationPlayer
new file mode 100644
--- /dev/null
+++ b/dom/animation/test/css-integration/test_animation-player-playstate.html
@@ -0,0 +1,57 @@
+<!doctype html>
+<meta charset=utf-8>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<div id="log"></div>
+<style>
+@keyframes anim { }
+</style>
+<script>
+'use strict';
+
+function addDiv() {
+  var div = document.createElement('div');
+  document.body.appendChild(div);
+  return div;
+}
+
+test(function() {
+  var div = addDiv();
+  var cs = window.getComputedStyle(div);
+  div.style.animation = 'anim 1000s';
+
+  var player = div.getAnimationPlayers()[0];
+  assert_equals(player.playState, 'running');
+}, 'Player returns correct playState when running');
+
+test(function() {
+  var div = addDiv();
+  var cs = window.getComputedStyle(div);
+  div.style.animation = 'anim 1000s paused';
+
+  var player = div.getAnimationPlayers()[0];
+  assert_equals(player.playState, 'paused');
+}, 'Player returns correct playState when paused');
+
+test(function() {
+  var div = addDiv();
+  var cs = window.getComputedStyle(div);
+  div.style.animation = 'anim 1000s';
+
+  var player = div.getAnimationPlayers()[0];
+  player.pause();
+  assert_equals(player.playState, 'paused');
+}, 'Player.playState updates when paused by script');
+
+test(function() {
+  var div = addDiv();
+  var cs = window.getComputedStyle(div);
+  div.style.animation = 'anim 1000s paused';
+
+  var player = div.getAnimationPlayers()[0];
+  div.style.animationPlayState = 'running';
+  // This test also checks that calling playState flushes style
+  assert_equals(player.playState, 'running');
+}, 'Player.playState updates when resumed by setting style');
+
+</script>
--- a/dom/animation/test/css-integration/test_element-get-animation-players.html
+++ b/dom/animation/test/css-integration/test_element-get-animation-players.html
@@ -318,18 +318,18 @@ test(function() {
 test(function() {
   var div = addDiv();
   div.style.animation = 'anim1 100s';
   var originalPlayer = div.getAnimationPlayers()[0];
 
   // Update pause state (an AnimationPlayer change)
   div.style.animationPlayState = 'paused';
   var pausedPlayer = div.getAnimationPlayers()[0];
-  // FIXME: Check pausedPlayer.playState has changed once the API is available
-  // (bug 1037321)
+  assert_equals(pausedPlayer.playState, 'paused',
+                'player\'s paused state is updated');
   assert_equals(originalPlayer, pausedPlayer,
                 'getAnimationPlayers returns the same objects even when their'
                 + ' play state changes');
 
   // Update duration (an Animation change)
   div.style.animationDuration = '200s';
   var extendedPlayer = div.getAnimationPlayers()[0];
   // FIXME: Check extendedPlayer.source.timing.duration has changed once the
--- a/dom/animation/test/mochitest.ini
+++ b/dom/animation/test/mochitest.ini
@@ -1,8 +1,9 @@
 [animation-timeline/test_animation-timeline.html]
 skip-if = buildapp == 'mulet'
 [css-integration/test_element-get-animation-players.html]
 skip-if = buildapp == 'mulet'
 [css-integration/test_animations-dynamic-changes.html]
 [css-integration/test_animation-effect-name.html]
 [css-integration/test_animation-pausing.html]
+[css-integration/test_animation-player-playstate.html]
 [css-integration/test_animation-target.html]
--- a/dom/webidl/AnimationPlayer.webidl
+++ b/dom/webidl/AnimationPlayer.webidl
@@ -5,30 +5,34 @@
  *
  * The origin of this IDL file is
  * http://dev.w3.org/fxtf/web-animations/#idl-def-AnimationPlayer
  *
  * Copyright © 2014 W3C® (MIT, ERCIM, Keio), All Rights Reserved. W3C
  * liability, trademark and document use rules apply.
  */
 
+enum AnimationPlayState { "idle", "pending", "running", "paused", "finished" };
+
 [Pref="dom.animations-api.core.enabled"]
 interface AnimationPlayer {
   // Bug 1049975
   //           attribute AnimationNode?     source;
   [Pure]
   readonly attribute Animation? source;
   readonly attribute AnimationTimeline timeline;
   [Pure]
   readonly attribute double? startTime;
   readonly attribute double? currentTime;
 
   /* Not yet implemented
-           attribute double             playbackRate;
+           attribute double             playbackRate; */
+  [BinaryName="playStateFromJS"]
   readonly attribute AnimationPlayState playState;
+  /*
   readonly attribute Promise            ready;
   readonly attribute Promise            finished;
   void cancel ();
   void finish ();
   */
   [BinaryName="playFromJS"]
   void play ();
   [BinaryName="pauseFromJS"]