[Web Animations] Enqueue and dispatch animation events
authorgraouts@webkit.org <graouts@webkit.org@268f45cc-cd09-0410-ab3c-d52691b4dbfc>
Tue, 12 Dec 2017 19:01:18 +0000 (19:01 +0000)
committergraouts@webkit.org <graouts@webkit.org@268f45cc-cd09-0410-ab3c-d52691b4dbfc>
Tue, 12 Dec 2017 19:01:18 +0000 (19:01 +0000)
https://bugs.webkit.org/show_bug.cgi?id=180657
<rdar://problem/35970103>

Reviewed by Chris Dumez.

Source/WebCore:

Now that we have support for the AnimationPlaybackEvent interface, we need a way to enqueue
such events for dispatch at the opportune time. The Web Animations spec defines two ways
to queue and dispatch events.

If the animation has a "document for timing", it should enqueue events on this document.
In our implementation, if the animation timeline is set to a DocumentTimeline, that means
it has a document for timing, and we let the DocumentTimeline enqueue those events, which
will be dispatched through a dedicated GenericTaskQueue<Timer>. These events will be sorted
by their respective timeline time before being dispatched.

If there is no document for timing, events should be dispatched as a standalone task.

* animation/DocumentTimeline.cpp:
(WebCore::DocumentTimeline::~DocumentTimeline): Close the event dispatch task queue when the
document timeline is torn down.
(WebCore::DocumentTimeline::enqueueAnimationPlaybackEvent): Add the provided event to the
pending animation events queue and, if one hasn't been registered yet, enqueue a task to
dispatch events using a GenericTaskQueue<Timer>.
(WebCore::compareAnimationPlaybackEvents): Comparator used to sort events in performEventDispatchTask()
where events are sorted such that unresolved timeline times come first, and then from the
earlier resolved timeline times to the later resolved timeline times. Events with unresolved
timeline times and equal resolved timeline times are sorted in the order they were enqueued.
(WebCore::DocumentTimeline::performEventDispatchTask): Run a stable sort on a copy of the pending list
of events to dispatch and dispatch the events individually on their respective animations.
* animation/DocumentTimeline.h:
* animation/WebAnimation.cpp:
(WebCore::WebAnimation::create): Pass in the document to the constructor.
(WebCore::WebAnimation::WebAnimation): Use the provided document to initialize ActiveDOMObject.
(WebCore::WebAnimation::enqueueAnimationPlaybackEvent): Create an AnimationPlaybackEvent with
the provided type, timeline time and animation time and enqueue it on the document timeline,
if one is available, or dispatch on this animation as a standalone task.
(WebCore::WebAnimation::acceleratedRunningStateDidChange):
(WebCore::WebAnimation::activeDOMObjectName const):
(WebCore::WebAnimation::canSuspendForDocumentSuspension const):
(WebCore::WebAnimation::stop):
* animation/WebAnimation.h: Define WebAnimation to be an EventTarget and an ActiveDOMObject.
* animation/WebAnimation.idl: Define WebAnimation to be an EventTarget and an ActiveDOMObject.
* dom/EventTargetFactory.in:

LayoutTests:

Rebase Web Platform Tests with some progressions based on the IDL changes. Progressions due
to dispatching events will become apparent when the next patch, where we dispatch actual
animation playback events, lands.

* http/wpt/web-animations/interfaces/Animation/idlharness-expected.txt:

git-svn-id: https://svn.webkit.org/repository/webkit/trunk@225790 268f45cc-cd09-0410-ab3c-d52691b4dbfc

LayoutTests/ChangeLog
LayoutTests/http/wpt/web-animations/interfaces/Animation/idlharness-expected.txt
Source/WebCore/ChangeLog
Source/WebCore/animation/DocumentTimeline.cpp
Source/WebCore/animation/DocumentTimeline.h
Source/WebCore/animation/WebAnimation.cpp
Source/WebCore/animation/WebAnimation.h
Source/WebCore/animation/WebAnimation.idl
Source/WebCore/dom/EventTargetFactory.in

index 5fa4622..f0e6a9c 100644 (file)
@@ -1,3 +1,17 @@
+2017-12-11  Antoine Quint  <graouts@apple.com>
+
+        [Web Animations] Enqueue and dispatch animation events
+        https://bugs.webkit.org/show_bug.cgi?id=180657
+        <rdar://problem/35970103>
+
+        Reviewed by Chris Dumez.
+
+        Rebase Web Platform Tests with some progressions based on the IDL changes. Progressions due
+        to dispatching events will become apparent when the next patch, where we dispatch actual
+        animation playback events, lands.
+
+        * http/wpt/web-animations/interfaces/Animation/idlharness-expected.txt:
+
 2017-12-12  Youenn Fablet  <youenn@apple.com>
 
         Allow AudioContext to start when getUserMedia is on
index 577fe7f..0cee1a8 100644 (file)
@@ -1,13 +1,9 @@
 
 PASS Animation interface automated IDL tests 
-FAIL Animation interface: existence and properties of interface object assert_equals: prototype of Animation is not EventTarget expected function "function EventTarget() {
-    [native code]
-}" but got function "function () {
-    [native code]
-}"
+PASS Animation interface: existence and properties of interface object 
 PASS Animation interface object length 
 PASS Animation interface object name 
-FAIL Animation interface: existence and properties of interface prototype object assert_equals: prototype of Animation.prototype is not EventTarget.prototype expected object "[object EventTargetPrototype]" but got object "[object Object]"
+PASS Animation interface: existence and properties of interface prototype object 
 PASS Animation interface: existence and properties of interface prototype object's "constructor" property 
 FAIL Animation interface: attribute id assert_true: The prototype object must have a property "id" expected true got false
 PASS Animation interface: attribute effect 
index f00d161..38e8e9f 100644 (file)
@@ -1,3 +1,50 @@
+2017-12-11  Antoine Quint  <graouts@apple.com>
+
+        [Web Animations] Enqueue and dispatch animation events
+        https://bugs.webkit.org/show_bug.cgi?id=180657
+        <rdar://problem/35970103>
+
+        Reviewed by Chris Dumez.
+
+        Now that we have support for the AnimationPlaybackEvent interface, we need a way to enqueue
+        such events for dispatch at the opportune time. The Web Animations spec defines two ways
+        to queue and dispatch events.
+
+        If the animation has a "document for timing", it should enqueue events on this document.
+        In our implementation, if the animation timeline is set to a DocumentTimeline, that means
+        it has a document for timing, and we let the DocumentTimeline enqueue those events, which
+        will be dispatched through a dedicated GenericTaskQueue<Timer>. These events will be sorted
+        by their respective timeline time before being dispatched.
+
+        If there is no document for timing, events should be dispatched as a standalone task.
+
+        * animation/DocumentTimeline.cpp:
+        (WebCore::DocumentTimeline::~DocumentTimeline): Close the event dispatch task queue when the
+        document timeline is torn down.
+        (WebCore::DocumentTimeline::enqueueAnimationPlaybackEvent): Add the provided event to the
+        pending animation events queue and, if one hasn't been registered yet, enqueue a task to
+        dispatch events using a GenericTaskQueue<Timer>.
+        (WebCore::compareAnimationPlaybackEvents): Comparator used to sort events in performEventDispatchTask()
+        where events are sorted such that unresolved timeline times come first, and then from the
+        earlier resolved timeline times to the later resolved timeline times. Events with unresolved
+        timeline times and equal resolved timeline times are sorted in the order they were enqueued.
+        (WebCore::DocumentTimeline::performEventDispatchTask): Run a stable sort on a copy of the pending list
+        of events to dispatch and dispatch the events individually on their respective animations.
+        * animation/DocumentTimeline.h:
+        * animation/WebAnimation.cpp:
+        (WebCore::WebAnimation::create): Pass in the document to the constructor.
+        (WebCore::WebAnimation::WebAnimation): Use the provided document to initialize ActiveDOMObject.
+        (WebCore::WebAnimation::enqueueAnimationPlaybackEvent): Create an AnimationPlaybackEvent with
+        the provided type, timeline time and animation time and enqueue it on the document timeline,
+        if one is available, or dispatch on this animation as a standalone task.
+        (WebCore::WebAnimation::acceleratedRunningStateDidChange):
+        (WebCore::WebAnimation::activeDOMObjectName const):
+        (WebCore::WebAnimation::canSuspendForDocumentSuspension const):
+        (WebCore::WebAnimation::stop):
+        * animation/WebAnimation.h: Define WebAnimation to be an EventTarget and an ActiveDOMObject.
+        * animation/WebAnimation.idl: Define WebAnimation to be an EventTarget and an ActiveDOMObject.
+        * dom/EventTargetFactory.in:
+
 2017-12-12  Chris Dumez  <cdumez@apple.com>
 
         Simplify IPC code between WebProcess and StorageProcess for serviceWorker.postMessage()
index c4dba7b..ed9104c 100644 (file)
@@ -59,6 +59,7 @@ DocumentTimeline::DocumentTimeline(Document& document, PlatformDisplayID display
 DocumentTimeline::~DocumentTimeline()
 {
     m_invalidationTaskQueue.close();
+    m_eventDispatchTaskQueue.close();
 }
 
 void DocumentTimeline::detachFromDocument()
@@ -210,6 +211,39 @@ bool DocumentTimeline::runningAnimationsForElementAreAllAccelerated(Element& ele
     return !animations.isEmpty();
 }
 
+void DocumentTimeline::enqueueAnimationPlaybackEvent(AnimationPlaybackEvent& event)
+{
+    m_pendingAnimationEvents.append(event);
+
+    if (!m_eventDispatchTaskQueue.hasPendingTasks())
+        m_eventDispatchTaskQueue.enqueueTask(std::bind(&DocumentTimeline::performEventDispatchTask, this));
+}
+
+static inline bool compareAnimationPlaybackEvents(const Ref<WebCore::AnimationPlaybackEvent>& lhs, const Ref<WebCore::AnimationPlaybackEvent>& rhs)
+{
+    // Sort the events by their scheduled event time such that events that were scheduled to occur earlier, sort before events scheduled to occur later
+    // and events whose scheduled event time is unresolved sort before events with a resolved scheduled event time.
+    if (lhs->timelineTime() && !rhs->timelineTime())
+        return false;
+    if (!lhs->timelineTime() && rhs->timelineTime())
+        return true;
+    if (!lhs->timelineTime() && !rhs->timelineTime())
+        return true;
+    return lhs->timelineTime().value() < rhs->timelineTime().value();
+}
+
+void DocumentTimeline::performEventDispatchTask()
+{
+    if (m_pendingAnimationEvents.isEmpty())
+        return;
+
+    auto pendingAnimationEvents = WTFMove(m_pendingAnimationEvents);
+
+    std::stable_sort(pendingAnimationEvents.begin(), pendingAnimationEvents.end(), compareAnimationPlaybackEvents);
+    for (auto& pendingEvent : pendingAnimationEvents)
+        pendingEvent->target()->dispatchEvent(pendingEvent);
+}
+
 void DocumentTimeline::windowScreenDidChange(PlatformDisplayID displayID)
 {
 #if USE(REQUEST_ANIMATION_FRAME_DISPLAY_MONITOR)
index ff1e576..173c3de 100644 (file)
@@ -37,6 +37,7 @@
 
 namespace WebCore {
 
+class AnimationPlaybackEvent;
 class RenderElement;
 
 class DocumentTimeline final : public AnimationTimeline
@@ -59,6 +60,8 @@ public:
     bool runningAnimationsForElementAreAllAccelerated(Element&);
     void detachFromDocument();
 
+    void enqueueAnimationPlaybackEvent(AnimationPlaybackEvent&);
+
 private:
     DocumentTimeline(Document&, PlatformDisplayID);
 
@@ -68,14 +71,17 @@ private:
     void animationScheduleTimerFired();
     void scheduleAnimationResolution();
     void updateAnimations();
+    void performEventDispatchTask();
 
     RefPtr<Document> m_document;
     bool m_paused { false };
     std::optional<Seconds> m_cachedCurrentTime;
     GenericTaskQueue<Timer> m_invalidationTaskQueue;
+    GenericTaskQueue<Timer> m_eventDispatchTaskQueue;
     bool m_needsUpdateAnimationSchedule { false };
     Timer m_animationScheduleTimer;
     HashSet<RefPtr<WebAnimation>> m_acceleratedAnimationsPendingRunningStateChange;
+    Vector<Ref<AnimationPlaybackEvent>> m_pendingAnimationEvents;
 
 #if USE(REQUEST_ANIMATION_FRAME_DISPLAY_MONITOR)
     // Override for DisplayRefreshMonitorClient
index 5d670e1..2e3a2f3 100644 (file)
@@ -27,6 +27,7 @@
 #include "WebAnimation.h"
 
 #include "AnimationEffect.h"
+#include "AnimationPlaybackEvent.h"
 #include "AnimationTimeline.h"
 #include "Document.h"
 #include "KeyframeEffect.h"
@@ -36,7 +37,7 @@ namespace WebCore {
 
 Ref<WebAnimation> WebAnimation::create(Document& document, AnimationEffect* effect, AnimationTimeline* timeline)
 {
-    auto result = adoptRef(*new WebAnimation());
+    auto result = adoptRef(*new WebAnimation(document));
 
     result->setEffect(effect);
 
@@ -47,8 +48,10 @@ Ref<WebAnimation> WebAnimation::create(Document& document, AnimationEffect* effe
     return result;
 }
 
-WebAnimation::WebAnimation()
+WebAnimation::WebAnimation(Document& document)
+    : ActiveDOMObject(&document)
 {
+    suspendIfNeeded();
 }
 
 WebAnimation::~WebAnimation()
@@ -214,6 +217,26 @@ void WebAnimation::setPlaybackRate(double newPlaybackRate)
         setCurrentTime(previousTime);
 }
 
+void WebAnimation::enqueueAnimationPlaybackEvent(const AtomicString& type, std::optional<Seconds> currentTime, std::optional<Seconds> timelineTime)
+{
+    auto event = AnimationPlaybackEvent::create(type, currentTime, timelineTime);
+    event->setTarget(this);
+
+    if (is<DocumentTimeline>(m_timeline)) {
+        // If animation has a document for timing, then append event to its document for timing's pending animation event queue along
+        // with its target, animation. If animation is associated with an active timeline that defines a procedure to convert timeline times
+        // to origin-relative time, let the scheduled event time be the result of applying that procedure to timeline time. Otherwise, the
+        // scheduled event time is an unresolved time value.
+        downcast<DocumentTimeline>(*m_timeline).enqueueAnimationPlaybackEvent(WTFMove(event));
+    } else {
+        // Otherwise, queue a task to dispatch event at animation. The task source for this task is the DOM manipulation task source.
+        callOnMainThread([this, pendingActivity = makePendingActivity(*this), event = WTFMove(event)]() {
+            if (!m_isStopped)
+                this->dispatchEvent(event);
+        });
+    }
+}
+
 Seconds WebAnimation::timeToNextRequiredTick(Seconds timelineTime) const
 {
     if (!m_timeline || !m_startTime || !m_effect || !m_playbackRate)
@@ -245,7 +268,7 @@ void WebAnimation::resolve(RenderStyle& targetStyle)
 
 void WebAnimation::acceleratedRunningStateDidChange()
 {
-    if (m_timeline && m_timeline->isDocumentTimeline())
+    if (is<DocumentTimeline>(m_timeline))
         downcast<DocumentTimeline>(*m_timeline).animationAcceleratedRunningStateDidChange(*this);
 }
 
@@ -260,4 +283,20 @@ String WebAnimation::description()
     return "Animation";
 }
 
+const char* WebAnimation::activeDOMObjectName() const
+{
+    return "Animation";
+}
+
+bool WebAnimation::canSuspendForDocumentSuspension() const
+{
+    return !hasPendingActivity();
+}
+
+void WebAnimation::stop()
+{
+    m_isStopped = true;
+    removeAllEventListeners();
+}
+
 } // namespace WebCore
index ae9c28d..43632c6 100644 (file)
@@ -25,6 +25,8 @@
 
 #pragma once
 
+#include "ActiveDOMObject.h"
+#include "EventTarget.h"
 #include "ExceptionOr.h"
 #include <wtf/Forward.h>
 #include <wtf/Optional.h>
 namespace WebCore {
 
 class AnimationEffect;
+class AnimationPlaybackEvent;
 class AnimationTimeline;
 class Document;
 class RenderStyle;
 
-class WebAnimation final : public RefCounted<WebAnimation> {
+class WebAnimation final : public RefCounted<WebAnimation>, public EventTargetWithInlineData, public ActiveDOMObject {
 public:
     static Ref<WebAnimation> create(Document&, AnimationEffect*, AnimationTimeline*);
     ~WebAnimation();
@@ -70,13 +73,30 @@ public:
 
     String description();
 
+    using RefCounted::ref;
+    using RefCounted::deref;
+
 private:
-    WebAnimation();
+    explicit WebAnimation(Document&);
 
+    void enqueueAnimationPlaybackEvent(const AtomicString&, std::optional<Seconds>, std::optional<Seconds>);
+    
     RefPtr<AnimationEffect> m_effect;
     RefPtr<AnimationTimeline> m_timeline;
     std::optional<Seconds> m_startTime;
     double m_playbackRate { 1 };
+    bool m_isStopped { false };
+
+    // ActiveDOMObject.
+    const char* activeDOMObjectName() const final;
+    bool canSuspendForDocumentSuspension() const final;
+    void stop() final;
+
+    // EventTarget
+    EventTargetInterface eventTargetInterface() const final { return WebAnimationEventTargetInterfaceType; }
+    void refEventTarget() final { ref(); }
+    void derefEventTarget() final { deref(); }
+    ScriptExecutionContext* scriptExecutionContext() const final { return ActiveDOMObject::scriptExecutionContext(); }
 };
 
 } // namespace WebCore
index fa1c7f1..994b47e 100644 (file)
  */
 
 [
+    ActiveDOMObject,
     EnabledAtRuntime=WebAnimations,
     InterfaceName=Animation,
-    ImplementationLacksVTable,
     ConstructorCallWith=Document,
     Constructor(optional AnimationEffect? effect = null, optional AnimationTimeline? timeline)
-] interface WebAnimation {
+] interface WebAnimation : EventTarget {
     attribute AnimationEffect? effect;
     attribute AnimationTimeline? timeline;
     [ImplementedAs=bindingsStartTime] attribute double? startTime;
index 711689b..a8144d8 100644 (file)
@@ -44,6 +44,7 @@ TextTrackList conditional=VIDEO_TRACK
 VRDisplay
 VideoTrackList conditional=VIDEO_TRACK
 VisualViewport
+WebAnimation
 WebKitMediaKeySession conditional=LEGACY_ENCRYPTED_MEDIA
 WebSocket
 Worker