- Revision
- 225862
- Author
- [email protected]
- Date
- 2017-12-13 12:01:48 -0800 (Wed, 13 Dec 2017)
Log Message
[Web Animations] Implement the "updating the finished state" procedure
https://bugs.webkit.org/show_bug.cgi?id=180743
<rdar://problem/36017232>
Reviewed by Simon Fraser.
Source/WebCore:
The Web Animations spec defines a procedure for "updating the finished state", which should run as the timeline time
changes and is responsible for ultimately triggering finish events and fulfil the "finished" promise. The procedure
allows for two flags to control its behavior: didSeek and synchronouslyNotify. When synchronouslyNotify is true, the
"finish notification steps" procedure is ran right away, otherwise it's queued as a microtask.
In this patch we introduce the notion of "hold time", which is the time held while an animation is paused. It will be
set by the pause() and play() method in future patches.
* animation/DocumentTimeline.cpp:
(WebCore::DocumentTimeline::updateAnimations): Update the finished state as the timeline time changes with both flags
set to false.
* animation/WebAnimation.cpp:
(WebCore::WebAnimation::currentTime const): Add a private currentTime(bool) variant which allows for the hold time to
be ignored in case updateFinishedState() was called with the didSeek flag set to false.
(WebCore::WebAnimation::updateFinishedState): Implement the procedure as specified with all spec-mandated steps inline.
(WebCore::WebAnimation::scheduleMicrotaskIfNeeded): Schedule a microtask to complete the "finish notification steps"
if we haven't scheduled a microtask before.
(WebCore::WebAnimation::performMicrotask): Perform the microtask if the "finish notification steps" procedure hasn't
been canceled after it was originally scheduled, as tracked by the m_finishNotificationStepsMicrotaskPending flag, since
microtasks are not presently cancelable.
(WebCore::WebAnimation::finishNotificationSteps): Implement the procedure as specified with all spec-mandated steps inline,
dispatching a "finish" events and fulfilling the "finished" promise.
* animation/WebAnimation.h:
* animation/WebAnimation.idl:
* dom/EventNames.h:
LayoutTests:
Rebase some WPT expectations with minor progressions due to exposing the "onfinish" property.
* http/wpt/web-animations/interfaces/Animation/idlharness-expected.txt:
Modified Paths
Diff
Modified: trunk/LayoutTests/ChangeLog (225861 => 225862)
--- trunk/LayoutTests/ChangeLog 2017-12-13 19:38:51 UTC (rev 225861)
+++ trunk/LayoutTests/ChangeLog 2017-12-13 20:01:48 UTC (rev 225862)
@@ -1,3 +1,15 @@
+2017-12-13 Antoine Quint <[email protected]>
+
+ [Web Animations] Implement the "updating the finished state" procedure
+ https://bugs.webkit.org/show_bug.cgi?id=180743
+ <rdar://problem/36017232>
+
+ Reviewed by Simon Fraser.
+
+ Rebase some WPT expectations with minor progressions due to exposing the "onfinish" property.
+
+ * http/wpt/web-animations/interfaces/Animation/idlharness-expected.txt:
+
2017-12-13 Matt Lewis <[email protected]>
Updated Expectations for http/tests/cache/disk-cache/disk-cache-204-status-code.html
Modified: trunk/LayoutTests/http/wpt/web-animations/interfaces/Animation/idlharness-expected.txt (225861 => 225862)
--- trunk/LayoutTests/http/wpt/web-animations/interfaces/Animation/idlharness-expected.txt 2017-12-13 19:38:51 UTC (rev 225861)
+++ trunk/LayoutTests/http/wpt/web-animations/interfaces/Animation/idlharness-expected.txt 2017-12-13 20:01:48 UTC (rev 225862)
@@ -14,7 +14,7 @@
PASS Animation interface: attribute playState
PASS Animation interface: attribute ready
PASS Animation interface: attribute finished
-FAIL Animation interface: attribute onfinish assert_true: The prototype object must have a property "onfinish" expected true got false
+PASS Animation interface: attribute onfinish
FAIL Animation interface: attribute oncancel assert_true: The prototype object must have a property "oncancel" expected true got false
FAIL Animation interface: operation cancel() assert_own_property: interface prototype object missing non-static operation expected property "cancel" missing
FAIL Animation interface: operation finish() assert_own_property: interface prototype object missing non-static operation expected property "finish" missing
@@ -32,7 +32,7 @@
PASS Animation interface: new Animation() must inherit property "playState" with the proper type
PASS Animation interface: new Animation() must inherit property "ready" with the proper type
PASS Animation interface: new Animation() must inherit property "finished" with the proper type
-FAIL Animation interface: new Animation() must inherit property "onfinish" with the proper type assert_inherits: property "onfinish" not found in prototype chain
+PASS Animation interface: new Animation() must inherit property "onfinish" with the proper type
FAIL Animation interface: new Animation() must inherit property "oncancel" with the proper type assert_inherits: property "oncancel" not found in prototype chain
FAIL Animation interface: new Animation() must inherit property "cancel()" with the proper type assert_inherits: property "cancel" not found in prototype chain
FAIL Animation interface: new Animation() must inherit property "finish()" with the proper type assert_inherits: property "finish" not found in prototype chain
Modified: trunk/Source/WebCore/ChangeLog (225861 => 225862)
--- trunk/Source/WebCore/ChangeLog 2017-12-13 19:38:51 UTC (rev 225861)
+++ trunk/Source/WebCore/ChangeLog 2017-12-13 20:01:48 UTC (rev 225862)
@@ -1,3 +1,37 @@
+2017-12-13 Antoine Quint <[email protected]>
+
+ [Web Animations] Implement the "updating the finished state" procedure
+ https://bugs.webkit.org/show_bug.cgi?id=180743
+ <rdar://problem/36017232>
+
+ Reviewed by Simon Fraser.
+
+ The Web Animations spec defines a procedure for "updating the finished state", which should run as the timeline time
+ changes and is responsible for ultimately triggering finish events and fulfil the "finished" promise. The procedure
+ allows for two flags to control its behavior: didSeek and synchronouslyNotify. When synchronouslyNotify is true, the
+ "finish notification steps" procedure is ran right away, otherwise it's queued as a microtask.
+
+ In this patch we introduce the notion of "hold time", which is the time held while an animation is paused. It will be
+ set by the pause() and play() method in future patches.
+
+ * animation/DocumentTimeline.cpp:
+ (WebCore::DocumentTimeline::updateAnimations): Update the finished state as the timeline time changes with both flags
+ set to false.
+ * animation/WebAnimation.cpp:
+ (WebCore::WebAnimation::currentTime const): Add a private currentTime(bool) variant which allows for the hold time to
+ be ignored in case updateFinishedState() was called with the didSeek flag set to false.
+ (WebCore::WebAnimation::updateFinishedState): Implement the procedure as specified with all spec-mandated steps inline.
+ (WebCore::WebAnimation::scheduleMicrotaskIfNeeded): Schedule a microtask to complete the "finish notification steps"
+ if we haven't scheduled a microtask before.
+ (WebCore::WebAnimation::performMicrotask): Perform the microtask if the "finish notification steps" procedure hasn't
+ been canceled after it was originally scheduled, as tracked by the m_finishNotificationStepsMicrotaskPending flag, since
+ microtasks are not presently cancelable.
+ (WebCore::WebAnimation::finishNotificationSteps): Implement the procedure as specified with all spec-mandated steps inline,
+ dispatching a "finish" events and fulfilling the "finished" promise.
+ * animation/WebAnimation.h:
+ * animation/WebAnimation.idl:
+ * dom/EventNames.h:
+
2017-12-13 Simon Fraser <[email protected]>
Fix crash under Document::visualUpdatesSuppressionTimerFired()
Modified: trunk/Source/WebCore/animation/DocumentTimeline.cpp (225861 => 225862)
--- trunk/Source/WebCore/animation/DocumentTimeline.cpp 2017-12-13 19:38:51 UTC (rev 225861)
+++ trunk/Source/WebCore/animation/DocumentTimeline.cpp 2017-12-13 20:01:48 UTC (rev 225862)
@@ -172,6 +172,9 @@
animation->startOrStopAccelerated();
m_acceleratedAnimationsPendingRunningStateChange.clear();
+ for (const auto& animation : animations())
+ animation->updateFinishedState(WebAnimation::DidSeek::No, WebAnimation::SynchronouslyNotify::No);
+
// Time has advanced, the timing model requires invalidation now.
animationTimingModelDidChange();
}
Modified: trunk/Source/WebCore/animation/WebAnimation.cpp (225861 => 225862)
--- trunk/Source/WebCore/animation/WebAnimation.cpp 2017-12-13 19:38:51 UTC (rev 225861)
+++ trunk/Source/WebCore/animation/WebAnimation.cpp 2017-12-13 20:01:48 UTC (rev 225862)
@@ -30,8 +30,10 @@
#include "AnimationPlaybackEvent.h"
#include "AnimationTimeline.h"
#include "Document.h"
+#include "EventNames.h"
#include "JSWebAnimation.h"
#include "KeyframeEffect.h"
+#include "Microtasks.h"
#include <wtf/text/WTFString.h>
namespace WebCore {
@@ -175,16 +177,30 @@
std::optional<Seconds> WebAnimation::currentTime() const
{
- // FIXME: return the hold time when we support pausing (webkit.org/b/178932).
+ return currentTime(RespectHoldTime::Yes);
+}
- if (!m_timeline || !m_startTime)
- return std::nullopt;
+std::optional<Seconds> WebAnimation::currentTime(RespectHoldTime respectHoldTime) const
+{
+ // 3.4.4. The current time of an animation
+ // https://drafts.csswg.org/web-animations-1/#the-current-time-of-an-animation
- auto timelineTime = m_timeline->currentTime();
- if (!timelineTime)
+ // The current time is calculated from the first matching condition from below:
+
+ // If the animation's hold time is resolved, the current time is the animation's hold time.
+ if (respectHoldTime == RespectHoldTime::Yes && m_holdTime)
+ return m_holdTime;
+
+ // If any of the following are true:
+ // 1. the animation has no associated timeline, or
+ // 2. the associated timeline is inactive, or
+ // 3. the animation's start time is unresolved.
+ // The current time is an unresolved time value.
+ if (!m_timeline || !m_timeline->currentTime() || !m_startTime)
return std::nullopt;
- return (timelineTime.value() - m_startTime.value()) * m_playbackRate;
+ // Otherwise, current time = (timeline time - start time) * playback rate
+ return (m_timeline->currentTime().value() - m_startTime.value()) * m_playbackRate;
}
void WebAnimation::setCurrentTime(std::optional<Seconds> seekTime)
@@ -273,6 +289,125 @@
}
}
+void WebAnimation::updateFinishedState(DidSeek didSeek, SynchronouslyNotify synchronouslyNotify)
+{
+ // 3.4.14. Updating the finished state
+ // https://drafts.csswg.org/web-animations-1/#updating-the-finished-state
+
+ // 1. Let the unconstrained current time be the result of calculating the current time substituting an unresolved time value
+ // for the hold time if did seek is false. If did seek is true, the unconstrained current time is equal to the current time.
+ auto unconstrainedCurrentTime = currentTime(didSeek == DidSeek::Yes ? RespectHoldTime::Yes : RespectHoldTime::No);
+ auto endTime = effectEndTime();
+
+ // 2. If all three of the following conditions are true,
+ // - the unconstrained current time is resolved, and
+ // - animation's start time is resolved, and
+ // - animation does not have a pending play task or a pending pause task,
+ if (unconstrainedCurrentTime && startTime() && !pending()) {
+ // then update animation's hold time based on the first matching condition for animation from below, if any:
+ if (m_playbackRate > 0 && unconstrainedCurrentTime >= endTime) {
+ // If animation playback rate > 0 and unconstrained current time is greater than or equal to target effect end,
+ // If did seek is true, let the hold time be the value of unconstrained current time.
+ if (didSeek == DidSeek::Yes)
+ m_holdTime = unconstrainedCurrentTime;
+ // If did seek is false, let the hold time be the maximum value of previous current time and target effect end. If the previous current time is unresolved, let the hold time be target effect end.
+ else if (!m_previousCurrentTime)
+ m_holdTime = endTime;
+ else
+ m_holdTime = std::max(m_previousCurrentTime.value(), endTime);
+ } else if (m_playbackRate < 0 && unconstrainedCurrentTime <= 0_s) {
+ // If animation playback rate < 0 and unconstrained current time is less than or equal to 0,
+ // If did seek is true, let the hold time be the value of unconstrained current time.
+ if (didSeek == DidSeek::Yes)
+ m_holdTime = unconstrainedCurrentTime;
+ // If did seek is false, let the hold time be the minimum value of previous current time and zero. If the previous current time is unresolved, let the hold time be zero.
+ else if (!m_previousCurrentTime)
+ m_holdTime = 0_s;
+ else
+ m_holdTime = std::min(m_previousCurrentTime.value(), 0_s);
+ } else if (m_playbackRate && m_timeline && m_timeline->currentTime()) {
+ // If animation playback rate ≠ 0, and animation is associated with an active timeline,
+ // Perform the following steps:
+ // 1. If did seek is true and the hold time is resolved, let animation's start time be equal to the result of evaluating timeline time - (hold time / playback rate)
+ // where timeline time is the current time value of timeline associated with animation.
+ if (didSeek == DidSeek::Yes && m_holdTime)
+ setStartTime(m_timeline->currentTime().value() - (m_holdTime.value() / m_playbackRate));
+ // 2. Let the hold time be unresolved.
+ m_holdTime = std::nullopt;
+ }
+ }
+
+ // 3. Set the previous current time of animation be the result of calculating its current time.
+ m_previousCurrentTime = currentTime();
+
+ // 4. Let current finished state be true if the play state of animation is finished. Otherwise, let it be false.
+ auto currentFinishedState = playState() == PlayState::Finished;
+
+ // 5. If current finished state is true and the current finished promise is not yet resolved, perform the following steps:
+ if (currentFinishedState && !m_finishedPromise.isFulfilled()) {
+ if (synchronouslyNotify == SynchronouslyNotify::Yes) {
+ // If synchronously notify is true, cancel any queued microtask to run the finish notification steps for this animation,
+ // and run the finish notification steps immediately.
+ m_finishNotificationStepsMicrotaskPending = false;
+ finishNotificationSteps();
+ } else if (!m_finishNotificationStepsMicrotaskPending) {
+ // Otherwise, if synchronously notify is false, queue a microtask to run finish notification steps for animation unless there
+ // is already a microtask queued to run those steps for animation.
+ m_finishNotificationStepsMicrotaskPending = true;
+ scheduleMicrotaskIfNeeded();
+ }
+ }
+
+ // 6. If current finished state is false and animation's current finished promise is already resolved, set animation's current
+ // finished promise to a new (pending) Promise object.
+ if (!currentFinishedState && m_finishedPromise.isFulfilled())
+ m_finishedPromise.clear();
+}
+
+void WebAnimation::scheduleMicrotaskIfNeeded()
+{
+ if (m_scheduledMicrotask)
+ return;
+
+ m_scheduledMicrotask = true;
+ MicrotaskQueue::mainThreadQueue().append(std::make_unique<VoidMicrotask>(std::bind(&WebAnimation::performMicrotask, this)));
+}
+
+void WebAnimation::performMicrotask()
+{
+ m_scheduledMicrotask = false;
+ if (!m_finishNotificationStepsMicrotaskPending)
+ return;
+
+ m_finishNotificationStepsMicrotaskPending = false;
+ finishNotificationSteps();
+}
+
+void WebAnimation::finishNotificationSteps()
+{
+ // 3.4.14. Updating the finished state
+ // https://drafts.csswg.org/web-animations-1/#finish-notification-steps
+
+ // Let finish notification steps refer to the following procedure:
+ // 1. If animation's play state is not equal to finished, abort these steps.
+ if (playState() != PlayState::Finished)
+ return;
+
+ // 2. Resolve animation's current finished promise object with animation.
+ m_finishedPromise.resolve(*this);
+
+ // 3. Create an AnimationPlaybackEvent, finishEvent.
+ // 4. Set finishEvent's type attribute to finish.
+ // 5. Set finishEvent's currentTime attribute to the current time of animation.
+ // 6. Set finishEvent's timelineTime attribute to the current time of the timeline with which animation is associated.
+ // If animation is not associated with a timeline, or the timeline is inactive, let timelineTime be null.
+ // 7. If animation has a document for timing, then append finishEvent to its document for timing's pending animation event
+ // queue along with its target, animation. For the scheduled event time, use the result of converting animation's target
+ // effect end to an origin-relative time.
+ // Otherwise, queue a task to dispatch finishEvent at animation. The task source for this task is the DOM manipulation task source.
+ enqueueAnimationPlaybackEvent(eventNames().finishEvent, currentTime(), m_timeline ? m_timeline->currentTime() : std::nullopt);
+}
+
Seconds WebAnimation::timeToNextRequiredTick(Seconds timelineTime) const
{
if (!m_timeline || !m_startTime || !m_effect || !m_playbackRate)
Modified: trunk/Source/WebCore/animation/WebAnimation.h (225861 => 225862)
--- trunk/Source/WebCore/animation/WebAnimation.h 2017-12-13 19:38:51 UTC (rev 225861)
+++ trunk/Source/WebCore/animation/WebAnimation.h 2017-12-13 20:01:48 UTC (rev 225862)
@@ -84,6 +84,10 @@
void acceleratedRunningStateDidChange();
void startOrStopAccelerated();
+ enum class DidSeek { Yes, No };
+ enum class SynchronouslyNotify { Yes, No };
+ void updateFinishedState(DidSeek, SynchronouslyNotify);
+
String description();
using RefCounted::ref;
@@ -92,16 +96,26 @@
private:
explicit WebAnimation(Document&);
+ enum class RespectHoldTime { Yes, No };
+
void enqueueAnimationPlaybackEvent(const AtomicString&, std::optional<Seconds>, std::optional<Seconds>);
Seconds effectEndTime() const;
WebAnimation& readyPromiseResolve();
WebAnimation& finishedPromiseResolve();
+ std::optional<Seconds> currentTime(RespectHoldTime) const;
+ void finishNotificationSteps();
+ void scheduleMicrotaskIfNeeded();
+ void performMicrotask();
RefPtr<AnimationEffect> m_effect;
RefPtr<AnimationTimeline> m_timeline;
+ std::optional<Seconds> m_previousCurrentTime;
std::optional<Seconds> m_startTime;
+ std::optional<Seconds> m_holdTime;
double m_playbackRate { 1 };
bool m_isStopped { false };
+ bool m_finishNotificationStepsMicrotaskPending;
+ bool m_scheduledMicrotask;
ReadyPromise m_readyPromise;
FinishedPromise m_finishedPromise;
Modified: trunk/Source/WebCore/animation/WebAnimation.idl (225861 => 225862)
--- trunk/Source/WebCore/animation/WebAnimation.idl 2017-12-13 19:38:51 UTC (rev 225861)
+++ trunk/Source/WebCore/animation/WebAnimation.idl 2017-12-13 20:01:48 UTC (rev 225862)
@@ -45,6 +45,7 @@
attribute double playbackRate;
readonly attribute AnimationPlayState playState;
readonly attribute boolean pending;
+ attribute EventHandler onfinish;
readonly attribute Promise<WebAnimation> ready;
readonly attribute Promise<WebAnimation> finished;
};
Modified: trunk/Source/WebCore/dom/EventNames.h (225861 => 225862)
--- trunk/Source/WebCore/dom/EventNames.h 2017-12-13 19:38:51 UTC (rev 225861)
+++ trunk/Source/WebCore/dom/EventNames.h 2017-12-13 20:01:48 UTC (rev 225862)
@@ -116,6 +116,7 @@
macro(error) \
macro(exit) \
macro(fetch) \
+ macro(finish) \
macro(focus) \
macro(focusin) \
macro(focusout) \