Changeset 225862 in webkit


Ignore:
Timestamp:
Dec 13, 2017 12:01:48 PM (6 years ago)
Author:
graouts@webkit.org
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:
Location:
trunk
Files:
8 edited

Legend:

Unmodified
Added
Removed
  • trunk/LayoutTests/ChangeLog

    r225852 r225862  
     12017-12-13  Antoine Quint  <graouts@apple.com>
     2
     3        [Web Animations] Implement the "updating the finished state" procedure
     4        https://bugs.webkit.org/show_bug.cgi?id=180743
     5        <rdar://problem/36017232>
     6
     7        Reviewed by Simon Fraser.
     8
     9        Rebase some WPT expectations with minor progressions due to exposing the "onfinish" property.
     10
     11        * http/wpt/web-animations/interfaces/Animation/idlharness-expected.txt:
     12
    1132017-12-13  Matt Lewis  <jlewis3@apple.com>
    214
  • trunk/LayoutTests/http/wpt/web-animations/interfaces/Animation/idlharness-expected.txt

    r225812 r225862  
    1515PASS Animation interface: attribute ready
    1616PASS Animation interface: attribute finished
    17 FAIL Animation interface: attribute onfinish assert_true: The prototype object must have a property "onfinish" expected true got false
     17PASS Animation interface: attribute onfinish
    1818FAIL Animation interface: attribute oncancel assert_true: The prototype object must have a property "oncancel" expected true got false
    1919FAIL Animation interface: operation cancel() assert_own_property: interface prototype object missing non-static operation expected property "cancel" missing
     
    3333PASS Animation interface: new Animation() must inherit property "ready" with the proper type
    3434PASS Animation interface: new Animation() must inherit property "finished" with the proper type
    35 FAIL Animation interface: new Animation() must inherit property "onfinish" with the proper type assert_inherits: property "onfinish" not found in prototype chain
     35PASS Animation interface: new Animation() must inherit property "onfinish" with the proper type
    3636FAIL Animation interface: new Animation() must inherit property "oncancel" with the proper type assert_inherits: property "oncancel" not found in prototype chain
    3737FAIL Animation interface: new Animation() must inherit property "cancel()" with the proper type assert_inherits: property "cancel" not found in prototype chain
  • trunk/Source/WebCore/ChangeLog

    r225859 r225862  
     12017-12-13  Antoine Quint  <graouts@apple.com>
     2
     3        [Web Animations] Implement the "updating the finished state" procedure
     4        https://bugs.webkit.org/show_bug.cgi?id=180743
     5        <rdar://problem/36017232>
     6
     7        Reviewed by Simon Fraser.
     8
     9        The Web Animations spec defines a procedure for "updating the finished state", which should run as the timeline time
     10        changes and is responsible for ultimately triggering finish events and fulfil the "finished" promise. The procedure
     11        allows for two flags to control its behavior: didSeek and synchronouslyNotify. When synchronouslyNotify is true, the
     12        "finish notification steps" procedure is ran right away, otherwise it's queued as a microtask.
     13
     14        In this patch we introduce the notion of "hold time", which is the time held while an animation is paused. It will be
     15        set by the pause() and play() method in future patches.
     16
     17        * animation/DocumentTimeline.cpp:
     18        (WebCore::DocumentTimeline::updateAnimations): Update the finished state as the timeline time changes with both flags
     19        set to false.
     20        * animation/WebAnimation.cpp:
     21        (WebCore::WebAnimation::currentTime const): Add a private currentTime(bool) variant which allows for the hold time to
     22        be ignored in case updateFinishedState() was called with the didSeek flag set to false.
     23        (WebCore::WebAnimation::updateFinishedState): Implement the procedure as specified with all spec-mandated steps inline.
     24        (WebCore::WebAnimation::scheduleMicrotaskIfNeeded): Schedule a microtask to complete the "finish notification steps"
     25        if we haven't scheduled a microtask before.
     26        (WebCore::WebAnimation::performMicrotask): Perform the microtask if the "finish notification steps" procedure hasn't
     27        been canceled after it was originally scheduled, as tracked by the m_finishNotificationStepsMicrotaskPending flag, since
     28        microtasks are not presently cancelable.
     29        (WebCore::WebAnimation::finishNotificationSteps): Implement the procedure as specified with all spec-mandated steps inline,
     30        dispatching a "finish" events and fulfilling the "finished" promise.
     31        * animation/WebAnimation.h:
     32        * animation/WebAnimation.idl:
     33        * dom/EventNames.h:
     34
    1352017-12-13  Simon Fraser  <simon.fraser@apple.com>
    236
  • trunk/Source/WebCore/animation/DocumentTimeline.cpp

    r225790 r225862  
    173173    m_acceleratedAnimationsPendingRunningStateChange.clear();
    174174
     175    for (const auto& animation : animations())
     176        animation->updateFinishedState(WebAnimation::DidSeek::No, WebAnimation::SynchronouslyNotify::No);
     177
    175178    // Time has advanced, the timing model requires invalidation now.
    176179    animationTimingModelDidChange();
  • trunk/Source/WebCore/animation/WebAnimation.cpp

    r225812 r225862  
    3131#include "AnimationTimeline.h"
    3232#include "Document.h"
     33#include "EventNames.h"
    3334#include "JSWebAnimation.h"
    3435#include "KeyframeEffect.h"
     36#include "Microtasks.h"
    3537#include <wtf/text/WTFString.h>
    3638
     
    176178std::optional<Seconds> WebAnimation::currentTime() const
    177179{
    178     // FIXME: return the hold time when we support pausing (webkit.org/b/178932).
    179 
    180     if (!m_timeline || !m_startTime)
     180    return currentTime(RespectHoldTime::Yes);
     181}
     182
     183std::optional<Seconds> WebAnimation::currentTime(RespectHoldTime respectHoldTime) const
     184{
     185    // 3.4.4. The current time of an animation
     186    // https://drafts.csswg.org/web-animations-1/#the-current-time-of-an-animation
     187
     188    // The current time is calculated from the first matching condition from below:
     189
     190    // If the animation's hold time is resolved, the current time is the animation's hold time.
     191    if (respectHoldTime == RespectHoldTime::Yes && m_holdTime)
     192        return m_holdTime;
     193
     194    // If any of the following are true:
     195    //     1. the animation has no associated timeline, or
     196    //     2. the associated timeline is inactive, or
     197    //     3. the animation's start time is unresolved.
     198    // The current time is an unresolved time value.
     199    if (!m_timeline || !m_timeline->currentTime() || !m_startTime)
    181200        return std::nullopt;
    182201
    183     auto timelineTime = m_timeline->currentTime();
    184     if (!timelineTime)
    185         return std::nullopt;
    186 
    187     return (timelineTime.value() - m_startTime.value()) * m_playbackRate;
     202    // Otherwise, current time = (timeline time - start time) * playback rate
     203    return (m_timeline->currentTime().value() - m_startTime.value()) * m_playbackRate;
    188204}
    189205
     
    274290}
    275291
     292void WebAnimation::updateFinishedState(DidSeek didSeek, SynchronouslyNotify synchronouslyNotify)
     293{
     294    // 3.4.14. Updating the finished state
     295    // https://drafts.csswg.org/web-animations-1/#updating-the-finished-state
     296
     297    // 1. Let the unconstrained current time be the result of calculating the current time substituting an unresolved time value
     298    // for the hold time if did seek is false. If did seek is true, the unconstrained current time is equal to the current time.
     299    auto unconstrainedCurrentTime = currentTime(didSeek == DidSeek::Yes ? RespectHoldTime::Yes : RespectHoldTime::No);
     300    auto endTime = effectEndTime();
     301
     302    // 2. If all three of the following conditions are true,
     303    //    - the unconstrained current time is resolved, and
     304    //    - animation's start time is resolved, and
     305    //    - animation does not have a pending play task or a pending pause task,
     306    if (unconstrainedCurrentTime && startTime() && !pending()) {
     307        // then update animation's hold time based on the first matching condition for animation from below, if any:
     308        if (m_playbackRate > 0 && unconstrainedCurrentTime >= endTime) {
     309            // If animation playback rate > 0 and unconstrained current time is greater than or equal to target effect end,
     310            // If did seek is true, let the hold time be the value of unconstrained current time.
     311            if (didSeek == DidSeek::Yes)
     312                m_holdTime = unconstrainedCurrentTime;
     313            // 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.
     314            else if (!m_previousCurrentTime)
     315                m_holdTime = endTime;
     316            else
     317                m_holdTime = std::max(m_previousCurrentTime.value(), endTime);
     318        } else if (m_playbackRate < 0 && unconstrainedCurrentTime <= 0_s) {
     319            // If animation playback rate < 0 and unconstrained current time is less than or equal to 0,
     320            // If did seek is true, let the hold time be the value of unconstrained current time.
     321            if (didSeek == DidSeek::Yes)
     322                m_holdTime = unconstrainedCurrentTime;
     323            // 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.
     324            else if (!m_previousCurrentTime)
     325                m_holdTime = 0_s;
     326            else
     327                m_holdTime = std::min(m_previousCurrentTime.value(), 0_s);
     328        } else if (m_playbackRate && m_timeline && m_timeline->currentTime()) {
     329            // If animation playback rate ≠ 0, and animation is associated with an active timeline,
     330            // Perform the following steps:
     331            // 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)
     332            //    where timeline time is the current time value of timeline associated with animation.
     333            if (didSeek == DidSeek::Yes && m_holdTime)
     334                setStartTime(m_timeline->currentTime().value() - (m_holdTime.value() / m_playbackRate));
     335            // 2. Let the hold time be unresolved.
     336            m_holdTime = std::nullopt;
     337        }
     338    }
     339
     340    // 3. Set the previous current time of animation be the result of calculating its current time.
     341    m_previousCurrentTime = currentTime();
     342
     343    // 4. Let current finished state be true if the play state of animation is finished. Otherwise, let it be false.
     344    auto currentFinishedState = playState() == PlayState::Finished;
     345
     346    // 5. If current finished state is true and the current finished promise is not yet resolved, perform the following steps:
     347    if (currentFinishedState && !m_finishedPromise.isFulfilled()) {
     348        if (synchronouslyNotify == SynchronouslyNotify::Yes) {
     349            // If synchronously notify is true, cancel any queued microtask to run the finish notification steps for this animation,
     350            // and run the finish notification steps immediately.
     351            m_finishNotificationStepsMicrotaskPending = false;
     352            finishNotificationSteps();
     353        } else if (!m_finishNotificationStepsMicrotaskPending) {
     354            // Otherwise, if synchronously notify is false, queue a microtask to run finish notification steps for animation unless there
     355            // is already a microtask queued to run those steps for animation.
     356            m_finishNotificationStepsMicrotaskPending = true;
     357            scheduleMicrotaskIfNeeded();
     358        }
     359    }
     360
     361    // 6. If current finished state is false and animation's current finished promise is already resolved, set animation's current
     362    // finished promise to a new (pending) Promise object.
     363    if (!currentFinishedState && m_finishedPromise.isFulfilled())
     364        m_finishedPromise.clear();
     365}
     366
     367void WebAnimation::scheduleMicrotaskIfNeeded()
     368{
     369    if (m_scheduledMicrotask)
     370        return;
     371
     372    m_scheduledMicrotask = true;
     373    MicrotaskQueue::mainThreadQueue().append(std::make_unique<VoidMicrotask>(std::bind(&WebAnimation::performMicrotask, this)));
     374}
     375
     376void WebAnimation::performMicrotask()
     377{
     378    m_scheduledMicrotask = false;
     379    if (!m_finishNotificationStepsMicrotaskPending)
     380        return;
     381
     382    m_finishNotificationStepsMicrotaskPending = false;
     383    finishNotificationSteps();
     384}
     385
     386void WebAnimation::finishNotificationSteps()
     387{
     388    // 3.4.14. Updating the finished state
     389    // https://drafts.csswg.org/web-animations-1/#finish-notification-steps
     390
     391    // Let finish notification steps refer to the following procedure:
     392    // 1. If animation's play state is not equal to finished, abort these steps.
     393    if (playState() != PlayState::Finished)
     394        return;
     395
     396    // 2. Resolve animation's current finished promise object with animation.
     397    m_finishedPromise.resolve(*this);
     398
     399    // 3. Create an AnimationPlaybackEvent, finishEvent.
     400    // 4. Set finishEvent's type attribute to finish.
     401    // 5. Set finishEvent's currentTime attribute to the current time of animation.
     402    // 6. Set finishEvent's timelineTime attribute to the current time of the timeline with which animation is associated.
     403    //    If animation is not associated with a timeline, or the timeline is inactive, let timelineTime be null.
     404    // 7. If animation has a document for timing, then append finishEvent to its document for timing's pending animation event
     405    //    queue along with its target, animation. For the scheduled event time, use the result of converting animation's target
     406    //    effect end to an origin-relative time.
     407    //    Otherwise, queue a task to dispatch finishEvent at animation. The task source for this task is the DOM manipulation task source.
     408    enqueueAnimationPlaybackEvent(eventNames().finishEvent, currentTime(), m_timeline ? m_timeline->currentTime() : std::nullopt);
     409}
     410
    276411Seconds WebAnimation::timeToNextRequiredTick(Seconds timelineTime) const
    277412{
  • trunk/Source/WebCore/animation/WebAnimation.h

    r225812 r225862  
    8585    void startOrStopAccelerated();
    8686
     87    enum class DidSeek { Yes, No };
     88    enum class SynchronouslyNotify { Yes, No };
     89    void updateFinishedState(DidSeek, SynchronouslyNotify);
     90
    8791    String description();
    8892
     
    9397    explicit WebAnimation(Document&);
    9498
     99    enum class RespectHoldTime { Yes, No };
     100
    95101    void enqueueAnimationPlaybackEvent(const AtomicString&, std::optional<Seconds>, std::optional<Seconds>);
    96102    Seconds effectEndTime() const;
    97103    WebAnimation& readyPromiseResolve();
    98104    WebAnimation& finishedPromiseResolve();
     105    std::optional<Seconds> currentTime(RespectHoldTime) const;
     106    void finishNotificationSteps();
     107    void scheduleMicrotaskIfNeeded();
     108    void performMicrotask();
    99109   
    100110    RefPtr<AnimationEffect> m_effect;
    101111    RefPtr<AnimationTimeline> m_timeline;
     112    std::optional<Seconds> m_previousCurrentTime;
    102113    std::optional<Seconds> m_startTime;
     114    std::optional<Seconds> m_holdTime;
    103115    double m_playbackRate { 1 };
    104116    bool m_isStopped { false };
     117    bool m_finishNotificationStepsMicrotaskPending;
     118    bool m_scheduledMicrotask;
    105119    ReadyPromise m_readyPromise;
    106120    FinishedPromise m_finishedPromise;
  • trunk/Source/WebCore/animation/WebAnimation.idl

    r225812 r225862  
    4646    readonly attribute AnimationPlayState playState;
    4747    readonly attribute boolean pending;
     48    attribute EventHandler onfinish;
    4849    readonly attribute Promise<WebAnimation> ready;
    4950    readonly attribute Promise<WebAnimation> finished;
  • trunk/Source/WebCore/dom/EventNames.h

    r225849 r225862  
    117117    macro(exit) \
    118118    macro(fetch) \
     119    macro(finish) \
    119120    macro(focus) \
    120121    macro(focusin) \
Note: See TracChangeset for help on using the changeset viewer.