Return a Promise from HTMLMediaElement.play()
authorjer.noble@apple.com <jer.noble@apple.com@268f45cc-cd09-0410-ab3c-d52691b4dbfc>
Tue, 10 May 2016 20:32:02 +0000 (20:32 +0000)
committerjer.noble@apple.com <jer.noble@apple.com@268f45cc-cd09-0410-ab3c-d52691b4dbfc>
Tue, 10 May 2016 20:32:02 +0000 (20:32 +0000)
https://bugs.webkit.org/show_bug.cgi?id=157400

Reviewed by Eric Carlson.

LayoutTests/imported/w3c:

Rebaseline web-platform-tests/html/dom/interfaces-expected.txt with new (failing) result.

* web-platform-tests/html/dom/interfaces-expected.txt:

Source/WebCore:

Tests: media/media-play-promise-reject-error-notsupported.html
       media/media-play-promise-reject-load-abort.html
       media/media-play-promise-reject-pause-abort.html
       media/media-play-promise-reject-play-notallowed.html
       media/media-play-promise-reject-play-notsupported.html
       media/media-play-promise-resolve-when-playing.html
       media/media-play-promise-resolve.html

The HTML Living Standard Spec <https://html.spec.whatwg.org/multipage/embedded-content.html>
(5 May 2016) adds support for a Promise to be returned by the play() method, to be resolved
or rejected at defined points during loading and playback.

Add utility methods which encapsulate the definitions of the equivalent algorithms from the
HTML Spec.  Add a new, overloaded play() method on HTMLMediaElement which takes a DeferredWrapper
reference.

After the change to use scheduleNotifyAboutPlaying() instead of enqueueing the "playing" event
directly, we must ensure that the notifyAboutPlaying() task does not get fired before the
"play" event preceeding it does. So re-implement GenericEventQueue (which previously used
a timer to dispatch events) to use a GenericTaskQueue instead. This ensures that all tasks and
events are interleaved in the order in which they were enqueued.

Additionally, the new pauseAfterDetachedTimerFired() event was firing out of microtask order, which
broke some W3C tests after the changes to GenericEventQueue. Move GenericEventQueue and
GenericTaskQueue to the same timing source (namely, a WebCore Timer) and interleave Events
and Tasks by having GenericEventQueue use GenericTaskQueue to issue its Events. Because
Document::postTask() cannot ensure ordering with Timer-based events, switch HTMLMediaElement
over to Timer-backed GenericTaskQueues.

Use a WeakPtr to track the destruction of TaskDispatcher<Timer> objects in pendingDispatchers().

* dom/GenericEventQueue.cpp:
(WebCore::GenericEventQueue::GenericEventQueue):
(WebCore::GenericEventQueue::enqueueEvent):
(WebCore::GenericEventQueue::close):
(WebCore::GenericEventQueue::cancelAllEvents):
(WebCore::GenericEventQueue::suspend):
(WebCore::GenericEventQueue::resume):
(WebCore::GenericEventQueue::sharedTimer): Deleted.
(WebCore::GenericEventQueue::sharedTimerFired): Deleted.
(WebCore::GenericEventQueue::pendingQueues): Deleted.
* dom/GenericEventQueue.h:
* platform/GenericTaskQueue.cpp: Added.
(WebCore::TaskDispatcher<Timer>::~TaskDispatcher):
(WebCore::TaskDispatcher<Timer>::postTask):
(WebCore::TaskDispatcher<Timer>::sharedTimer):
(WebCore::TaskDispatcher<Timer>::sharedTimerFired):
(WebCore::TaskDispatcher<Timer>::pendingDispatchers):
(WebCore::TaskDispatcher<Timer>::dispatchOneTask):
* platform/GenericTaskQueue.h:
(WebCore::TaskDispatcher<Timer>::TaskDispatcher): Deleted.
(WebCore::TaskDispatcher<Timer>::postTask): Deleted.
(WebCore::TaskDispatcher<Timer>::timerFired): Deleted.
* html/HTMLMediaElement.cpp:
(WebCore::HTMLMediaElement::HTMLMediaElement):
(WebCore::HTMLMediaElement::~HTMLMediaElement):
(WebCore::HTMLMediaElement::scheduleResolvePendingPlayPromises):
(WebCore::HTMLMediaElement::rejectPendingPlayPromises):
(WebCore::HTMLMediaElement::resolvePendingPlayPromises):
(WebCore::HTMLMediaElement::scheduleNotifyAboutPlaying):
(WebCore::HTMLMediaElement::notifyAboutPlaying):
(WebCore::HTMLMediaElement::noneSupported):
(WebCore::HTMLMediaElement::cancelPendingEventsAndCallbacks):
(WebCore::HTMLMediaElement::setReadyState):
(WebCore::HTMLMediaElement::play):
(WebCore::HTMLMediaElement::playInternal):
(WebCore::HTMLMediaElement::pauseInternal):
(WebCore::HTMLMediaElement::contextDestroyed):
(WebCore::HTMLMediaElement::stop):
(WebCore::HTMLMediaElement::pauseAfterDetachedTask): Renamed from pauseAfterDetachedTimerFired.
(WebCore::HTMLMediaElement::removedFrom):
(WebCore::HTMLMediaElement::contextDestroyed):
* html/HTMLMediaElement.h:
* html/HTMLMediaElement.idl:
* CMakeLists.txt:
* WebCore.xcodeproj/project.pbxproj:

LayoutTests:

* media/media-play-promise-reject-error-notsupported-expected.txt: Added.
* media/media-play-promise-reject-error-notsupported.html: Added.
* media/media-play-promise-reject-load-abort-expected.txt: Added.
* media/media-play-promise-reject-load-abort.html: Added.
* media/media-play-promise-reject-pause-abort-expected.txt: Added.
* media/media-play-promise-reject-pause-abort.html: Added.
* media/media-play-promise-reject-play-notallowed-expected.txt: Added.
* media/media-play-promise-reject-play-notallowed.html: Added.
* media/media-play-promise-reject-play-notsupported-expected.txt: Added.
* media/media-play-promise-reject-play-notsupported.html: Added.
* media/media-play-promise-resolve-expected.txt: Added.
* media/media-play-promise-resolve-when-playing-expected.txt: Added.
* media/media-play-promise-resolve-when-playing.html: Added.
* media/media-play-promise-resolve.html: Added.

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

27 files changed:
LayoutTests/ChangeLog
LayoutTests/imported/w3c/ChangeLog
LayoutTests/imported/w3c/web-platform-tests/html/dom/interfaces-expected.txt
LayoutTests/media/media-play-promise-reject-error-notsupported-expected.txt [new file with mode: 0644]
LayoutTests/media/media-play-promise-reject-error-notsupported.html [new file with mode: 0644]
LayoutTests/media/media-play-promise-reject-load-abort-expected.txt [new file with mode: 0644]
LayoutTests/media/media-play-promise-reject-load-abort.html [new file with mode: 0644]
LayoutTests/media/media-play-promise-reject-pause-abort-expected.txt [new file with mode: 0644]
LayoutTests/media/media-play-promise-reject-pause-abort.html [new file with mode: 0644]
LayoutTests/media/media-play-promise-reject-play-notallowed-expected.txt [new file with mode: 0644]
LayoutTests/media/media-play-promise-reject-play-notallowed.html [new file with mode: 0644]
LayoutTests/media/media-play-promise-reject-play-notsupported-expected.txt [new file with mode: 0644]
LayoutTests/media/media-play-promise-reject-play-notsupported.html [new file with mode: 0644]
LayoutTests/media/media-play-promise-resolve-expected.txt [new file with mode: 0644]
LayoutTests/media/media-play-promise-resolve-when-playing-expected.txt [new file with mode: 0644]
LayoutTests/media/media-play-promise-resolve-when-playing.html [new file with mode: 0644]
LayoutTests/media/media-play-promise-resolve.html [new file with mode: 0644]
Source/WebCore/CMakeLists.txt
Source/WebCore/ChangeLog
Source/WebCore/WebCore.xcodeproj/project.pbxproj
Source/WebCore/dom/GenericEventQueue.cpp
Source/WebCore/dom/GenericEventQueue.h
Source/WebCore/html/HTMLMediaElement.cpp
Source/WebCore/html/HTMLMediaElement.h
Source/WebCore/html/HTMLMediaElement.idl
Source/WebCore/platform/GenericTaskQueue.cpp [new file with mode: 0644]
Source/WebCore/platform/GenericTaskQueue.h

index 8dfe7af..118d1ff 100644 (file)
@@ -1,3 +1,25 @@
+2016-05-05  Jer Noble  <jer.noble@apple.com>
+
+        Return a Promise from HTMLMediaElement.play()
+        https://bugs.webkit.org/show_bug.cgi?id=157400
+
+        Reviewed by Eric Carlson.
+
+        * media/media-play-promise-reject-error-notsupported-expected.txt: Added.
+        * media/media-play-promise-reject-error-notsupported.html: Added.
+        * media/media-play-promise-reject-load-abort-expected.txt: Added.
+        * media/media-play-promise-reject-load-abort.html: Added.
+        * media/media-play-promise-reject-pause-abort-expected.txt: Added.
+        * media/media-play-promise-reject-pause-abort.html: Added.
+        * media/media-play-promise-reject-play-notallowed-expected.txt: Added.
+        * media/media-play-promise-reject-play-notallowed.html: Added.
+        * media/media-play-promise-reject-play-notsupported-expected.txt: Added.
+        * media/media-play-promise-reject-play-notsupported.html: Added.
+        * media/media-play-promise-resolve-expected.txt: Added.
+        * media/media-play-promise-resolve-when-playing-expected.txt: Added.
+        * media/media-play-promise-resolve-when-playing.html: Added.
+        * media/media-play-promise-resolve.html: Added.
+
 2016-05-10  Commit Queue  <commit-queue@webkit.org>
 
         Unreviewed, rolling out r200627.
index a0b71aa..a763e77 100644 (file)
@@ -1,3 +1,14 @@
+2016-05-06  Jer Noble  <jer.noble@apple.com>
+
+        Return a Promise from HTMLMediaElement.play()
+        https://bugs.webkit.org/show_bug.cgi?id=157400
+
+        Reviewed by Eric Carlson.
+
+        Rebaseline web-platform-tests/html/dom/interfaces-expected.txt with new (failing) result.
+
+        * web-platform-tests/html/dom/interfaces-expected.txt:
+
 2016-05-10  Commit Queue  <commit-queue@webkit.org>
 
         Unreviewed, rolling out r200627.
index 88d8485..f9e64eb 100644 (file)
@@ -2447,7 +2447,9 @@ PASS HTMLMediaElement interface: attribute seekable
 PASS HTMLMediaElement interface: attribute ended 
 PASS HTMLMediaElement interface: attribute autoplay 
 PASS HTMLMediaElement interface: attribute loop 
-PASS HTMLMediaElement interface: operation play() 
+FAIL HTMLMediaElement interface: operation play() assert_throws: calling operation with this = null didn't throw TypeError function "function () {
+            fn.apply(obj, args);
+        }" did not throw
 PASS HTMLMediaElement interface: operation pause() 
 PASS HTMLMediaElement interface: attribute mediaGroup 
 PASS HTMLMediaElement interface: attribute controller 
diff --git a/LayoutTests/media/media-play-promise-reject-error-notsupported-expected.txt b/LayoutTests/media/media-play-promise-reject-error-notsupported-expected.txt
new file mode 100644 (file)
index 0000000..def321c
--- /dev/null
@@ -0,0 +1,7 @@
+
+RUN(mediaElement.src = findMediaFile("video", "content/invalid"))
+RUN(mediaElement.play().then(failTest).catch(promiseRejected))
+Promise rejected. OK
+EXPECTED (error.name == 'NotSupportedError') OK
+END OF TEST
+
diff --git a/LayoutTests/media/media-play-promise-reject-error-notsupported.html b/LayoutTests/media/media-play-promise-reject-error-notsupported.html
new file mode 100644 (file)
index 0000000..42d77aa
--- /dev/null
@@ -0,0 +1,31 @@
+<!DOCTYPE html>
+<html>
+    <head>
+        <script src=media-file.js></script>
+        <script src=video-test.js></script>
+
+        <script>
+        var promise;
+        var error;
+        function start()
+        {
+            findMediaElement();
+            run('mediaElement.src = findMediaFile("video", "content/invalid")');
+            run('mediaElement.play().then(failTest).catch(promiseRejected)');    
+        }
+        function promiseRejected(e)
+        {
+            error = e;
+            logResult(true, "Promise rejected.");
+            testExpected('error.name', 'NotSupportedError');
+            endTest();
+        }
+        </script>
+    </head>
+
+    <body onload="start()">
+
+        <video></video>
+    
+    </body>
+</html>
diff --git a/LayoutTests/media/media-play-promise-reject-load-abort-expected.txt b/LayoutTests/media/media-play-promise-reject-load-abort-expected.txt
new file mode 100644 (file)
index 0000000..f618897
--- /dev/null
@@ -0,0 +1,7 @@
+
+RUN(mediaElement.play().then(failTest).catch(promiseRejected))
+RUN(mediaElement.src = findMediaFile("video", "content/test"))
+Promise rejected. OK
+EXPECTED (error.name == 'AbortError') OK
+END OF TEST
+
diff --git a/LayoutTests/media/media-play-promise-reject-load-abort.html b/LayoutTests/media/media-play-promise-reject-load-abort.html
new file mode 100644 (file)
index 0000000..5cb2faf
--- /dev/null
@@ -0,0 +1,31 @@
+<!DOCTYPE html>
+<html>
+    <head>
+        <script src=media-file.js></script>
+        <script src=video-test.js></script>
+
+        <script>
+        var promise;
+        var error;
+        function start()
+        {
+            findMediaElement();
+            run('mediaElement.play().then(failTest).catch(promiseRejected)');    
+            run('mediaElement.src = findMediaFile("video", "content/test")');
+        }
+        function promiseRejected(e)
+        {
+            error = e;
+            logResult(true, "Promise rejected.");
+            testExpected('error.name', 'AbortError');
+            endTest();
+        }
+        </script>
+    </head>
+
+    <body onload="start()">
+
+        <video></video>
+    
+    </body>
+</html>
diff --git a/LayoutTests/media/media-play-promise-reject-pause-abort-expected.txt b/LayoutTests/media/media-play-promise-reject-pause-abort-expected.txt
new file mode 100644 (file)
index 0000000..6a8ef34
--- /dev/null
@@ -0,0 +1,7 @@
+
+RUN(mediaElement.play().then(failTest).catch(promiseRejected))
+RUN(mediaElement.pause())
+Promise rejected. OK
+EXPECTED (error.name == 'AbortError') OK
+END OF TEST
+
diff --git a/LayoutTests/media/media-play-promise-reject-pause-abort.html b/LayoutTests/media/media-play-promise-reject-pause-abort.html
new file mode 100644 (file)
index 0000000..44431ed
--- /dev/null
@@ -0,0 +1,31 @@
+<!DOCTYPE html>
+<html>
+    <head>
+        <script src=media-file.js></script>
+        <script src=video-test.js></script>
+
+        <script>
+        var promise;
+        var error;
+        function start()
+        {
+            findMediaElement();
+            run('mediaElement.play().then(failTest).catch(promiseRejected)');    
+            run('mediaElement.pause()');
+        }
+        function promiseRejected(e)
+        {
+            error = e;
+            logResult(true, "Promise rejected.");
+            testExpected('error.name', 'AbortError');
+            endTest();
+        }
+        </script>
+    </head>
+
+    <body onload="start()">
+
+        <video></video>
+    
+    </body>
+</html>
diff --git a/LayoutTests/media/media-play-promise-reject-play-notallowed-expected.txt b/LayoutTests/media/media-play-promise-reject-play-notallowed-expected.txt
new file mode 100644 (file)
index 0000000..14d89cc
--- /dev/null
@@ -0,0 +1,8 @@
+
+RUN(internals.setMediaElementRestrictions(mediaElement, "RequireUserGestureForVideoRateChange"))
+RUN(mediaElement.src = findMediaFile("video", "content/test"))
+RUN(mediaElement.play().then(failTest).catch(promiseRejected))
+Promise rejected. OK
+EXPECTED (error.name == 'NotAllowedError') OK
+END OF TEST
+
diff --git a/LayoutTests/media/media-play-promise-reject-play-notallowed.html b/LayoutTests/media/media-play-promise-reject-play-notallowed.html
new file mode 100644 (file)
index 0000000..f789e92
--- /dev/null
@@ -0,0 +1,35 @@
+<!DOCTYPE html>
+<html>
+    <head>
+        <script src=media-file.js></script>
+        <script src=video-test.js></script>
+
+        <script>
+        var promise;
+        var error;
+        function start()
+        {
+            findMediaElement();
+               if (window.internals)
+                   run('internals.setMediaElementRestrictions(mediaElement, "RequireUserGestureForVideoRateChange")');
+
+            run('mediaElement.src = findMediaFile("video", "content/test")');
+            run('mediaElement.play().then(failTest).catch(promiseRejected)');    
+        }
+
+        function promiseRejected(e)
+        {
+            error = e;
+            logResult(true, "Promise rejected.");
+            testExpected('error.name', 'NotAllowedError');
+            endTest();
+        }
+        </script>
+    </head>
+
+    <body onload="start()">
+
+        <video></video>
+    
+    </body>
+</html>
diff --git a/LayoutTests/media/media-play-promise-reject-play-notsupported-expected.txt b/LayoutTests/media/media-play-promise-reject-play-notsupported-expected.txt
new file mode 100644 (file)
index 0000000..a6f8199
--- /dev/null
@@ -0,0 +1,8 @@
+
+RUN(mediaElement.src = findMediaFile("video", "content/invalid"))
+EVENT(error)
+RUN(mediaElement.play().then(failTest).catch(promiseRejected))
+Promise rejected. OK
+EXPECTED (error.name == 'NotSupportedError') OK
+END OF TEST
+
diff --git a/LayoutTests/media/media-play-promise-reject-play-notsupported.html b/LayoutTests/media/media-play-promise-reject-play-notsupported.html
new file mode 100644 (file)
index 0000000..413f303
--- /dev/null
@@ -0,0 +1,37 @@
+<!DOCTYPE html>
+<html>
+    <head>
+        <script src=media-file.js></script>
+        <script src=video-test.js></script>
+
+        <script>
+        var promise;
+        var error;
+        function start()
+        {
+            findMediaElement();
+            run('mediaElement.src = findMediaFile("video", "content/invalid")');
+            waitForEventOnce('error', error);
+        }
+
+        function error()
+        {
+            run('mediaElement.play().then(failTest).catch(promiseRejected)');    
+        }
+
+        function promiseRejected(e)
+        {
+            error = e;
+            logResult(true, "Promise rejected.");
+            testExpected('error.name', 'NotSupportedError');
+            endTest();
+        }
+        </script>
+    </head>
+
+    <body onload="start()">
+
+        <video></video>
+    
+    </body>
+</html>
diff --git a/LayoutTests/media/media-play-promise-resolve-expected.txt b/LayoutTests/media/media-play-promise-resolve-expected.txt
new file mode 100644 (file)
index 0000000..0063eed
--- /dev/null
@@ -0,0 +1,6 @@
+
+RUN(mediaElement.src = findMediaFile("video", "content/test"))
+RUN(mediaElement.play().then(promiseResolved).catch(failTest))
+Promise resolved. OK
+END OF TEST
+
diff --git a/LayoutTests/media/media-play-promise-resolve-when-playing-expected.txt b/LayoutTests/media/media-play-promise-resolve-when-playing-expected.txt
new file mode 100644 (file)
index 0000000..1659a1f
--- /dev/null
@@ -0,0 +1,8 @@
+
+RUN(mediaElement.src = findMediaFile("video", "content/test"))
+RUN(mediaElement.play())
+EVENT(playing)
+RUN(mediaElement.play().then(promiseResolved).catch(failTest))
+Promise resolved. OK
+END OF TEST
+
diff --git a/LayoutTests/media/media-play-promise-resolve-when-playing.html b/LayoutTests/media/media-play-promise-resolve-when-playing.html
new file mode 100644 (file)
index 0000000..d1d4efa
--- /dev/null
@@ -0,0 +1,35 @@
+<!DOCTYPE html>
+<html>
+    <head>
+        <script src=media-file.js></script>
+        <script src=video-test.js></script>
+
+        <script>
+        var promise;
+        function start()
+        {
+            findMediaElement();
+            run('mediaElement.src = findMediaFile("video", "content/test")');
+            run('mediaElement.play()');
+            waitForEventOnce('playing', playing);
+        }
+
+        function playing()
+        {
+            run('mediaElement.play().then(promiseResolved).catch(failTest)');    
+        }
+
+        function promiseResolved()
+        {
+            logResult(true, "Promise resolved.");
+            endTest();
+        }
+        </script>
+    </head>
+
+    <body onload="start()">
+
+        <video></video>
+    
+    </body>
+</html>
diff --git a/LayoutTests/media/media-play-promise-resolve.html b/LayoutTests/media/media-play-promise-resolve.html
new file mode 100644 (file)
index 0000000..58a3278
--- /dev/null
@@ -0,0 +1,29 @@
+<!DOCTYPE html>
+<html>
+    <head>
+        <script src=media-file.js></script>
+        <script src=video-test.js></script>
+
+        <script>
+        var promise;
+        function start()
+        {
+            findMediaElement();
+            run('mediaElement.src = findMediaFile("video", "content/test")');
+            run('mediaElement.play().then(promiseResolved).catch(failTest)');
+        }
+
+        function promiseResolved()
+        {
+            logResult(true, "Promise resolved.");
+            endTest();
+        }
+        </script>
+    </head>
+
+    <body onload="start()">
+
+        <video></video>
+    
+    </body>
+</html>
index a984ae0..7550b00 100644 (file)
@@ -2109,6 +2109,7 @@ set(WebCore_SOURCES
     platform/FileChooser.cpp
     platform/FileStream.cpp
     platform/FileSystem.cpp
+    platform/GenericTaskQueue.cpp
     platform/Language.cpp
     platform/Length.cpp
     platform/LengthBox.cpp
index 46d5c99..85e7124 100644 (file)
@@ -1,3 +1,87 @@
+2016-05-05  Jer Noble  <jer.noble@apple.com>
+
+        Return a Promise from HTMLMediaElement.play()
+        https://bugs.webkit.org/show_bug.cgi?id=157400
+
+        Reviewed by Eric Carlson.
+
+        Tests: media/media-play-promise-reject-error-notsupported.html
+               media/media-play-promise-reject-load-abort.html
+               media/media-play-promise-reject-pause-abort.html
+               media/media-play-promise-reject-play-notallowed.html
+               media/media-play-promise-reject-play-notsupported.html
+               media/media-play-promise-resolve-when-playing.html
+               media/media-play-promise-resolve.html
+
+        The HTML Living Standard Spec <https://html.spec.whatwg.org/multipage/embedded-content.html>
+        (5 May 2016) adds support for a Promise to be returned by the play() method, to be resolved
+        or rejected at defined points during loading and playback.
+
+        Add utility methods which encapsulate the definitions of the equivalent algorithms from the
+        HTML Spec.  Add a new, overloaded play() method on HTMLMediaElement which takes a DeferredWrapper
+        reference.
+
+        After the change to use scheduleNotifyAboutPlaying() instead of enqueueing the "playing" event
+        directly, we must ensure that the notifyAboutPlaying() task does not get fired before the
+        "play" event preceeding it does. So re-implement GenericEventQueue (which previously used
+        a timer to dispatch events) to use a GenericTaskQueue instead. This ensures that all tasks and
+        events are interleaved in the order in which they were enqueued.
+
+        Additionally, the new pauseAfterDetachedTimerFired() event was firing out of microtask order, which
+        broke some W3C tests after the changes to GenericEventQueue. Move GenericEventQueue and
+        GenericTaskQueue to the same timing source (namely, a WebCore Timer) and interleave Events
+        and Tasks by having GenericEventQueue use GenericTaskQueue to issue its Events. Because
+        Document::postTask() cannot ensure ordering with Timer-based events, switch HTMLMediaElement
+        over to Timer-backed GenericTaskQueues.
+
+        Use a WeakPtr to track the destruction of TaskDispatcher<Timer> objects in pendingDispatchers().
+
+        * dom/GenericEventQueue.cpp:
+        (WebCore::GenericEventQueue::GenericEventQueue):
+        (WebCore::GenericEventQueue::enqueueEvent):
+        (WebCore::GenericEventQueue::close):
+        (WebCore::GenericEventQueue::cancelAllEvents):
+        (WebCore::GenericEventQueue::suspend):
+        (WebCore::GenericEventQueue::resume):
+        (WebCore::GenericEventQueue::sharedTimer): Deleted.
+        (WebCore::GenericEventQueue::sharedTimerFired): Deleted.
+        (WebCore::GenericEventQueue::pendingQueues): Deleted.
+        * dom/GenericEventQueue.h:
+        * platform/GenericTaskQueue.cpp: Added.
+        (WebCore::TaskDispatcher<Timer>::~TaskDispatcher):
+        (WebCore::TaskDispatcher<Timer>::postTask):
+        (WebCore::TaskDispatcher<Timer>::sharedTimer):
+        (WebCore::TaskDispatcher<Timer>::sharedTimerFired):
+        (WebCore::TaskDispatcher<Timer>::pendingDispatchers):
+        (WebCore::TaskDispatcher<Timer>::dispatchOneTask):
+        * platform/GenericTaskQueue.h:
+        (WebCore::TaskDispatcher<Timer>::TaskDispatcher): Deleted.
+        (WebCore::TaskDispatcher<Timer>::postTask): Deleted.
+        (WebCore::TaskDispatcher<Timer>::timerFired): Deleted.
+        * html/HTMLMediaElement.cpp:
+        (WebCore::HTMLMediaElement::HTMLMediaElement):
+        (WebCore::HTMLMediaElement::~HTMLMediaElement):
+        (WebCore::HTMLMediaElement::scheduleResolvePendingPlayPromises):
+        (WebCore::HTMLMediaElement::rejectPendingPlayPromises):
+        (WebCore::HTMLMediaElement::resolvePendingPlayPromises):
+        (WebCore::HTMLMediaElement::scheduleNotifyAboutPlaying):
+        (WebCore::HTMLMediaElement::notifyAboutPlaying):
+        (WebCore::HTMLMediaElement::noneSupported):
+        (WebCore::HTMLMediaElement::cancelPendingEventsAndCallbacks):
+        (WebCore::HTMLMediaElement::setReadyState):
+        (WebCore::HTMLMediaElement::play):
+        (WebCore::HTMLMediaElement::playInternal):
+        (WebCore::HTMLMediaElement::pauseInternal):
+        (WebCore::HTMLMediaElement::contextDestroyed):
+        (WebCore::HTMLMediaElement::stop):
+        (WebCore::HTMLMediaElement::pauseAfterDetachedTask): Renamed from pauseAfterDetachedTimerFired.
+        (WebCore::HTMLMediaElement::removedFrom):
+        (WebCore::HTMLMediaElement::contextDestroyed):
+        * html/HTMLMediaElement.h:
+        * html/HTMLMediaElement.idl:
+        * CMakeLists.txt:
+        * WebCore.xcodeproj/project.pbxproj:
+
 2016-05-10  Commit Queue  <commit-queue@webkit.org>
 
         Unreviewed, rolling out r200627.
index 7ac102e..f526727 100644 (file)
                CD3E252318046BCD00E27F56 /* CSSGridTemplateAreasValue.cpp in Sources */ = {isa = PBXBuildFile; fileRef = CD3E252118046BCD00E27F56 /* CSSGridTemplateAreasValue.cpp */; };
                CD3E252418046BCD00E27F56 /* CSSGridTemplateAreasValue.h in Headers */ = {isa = PBXBuildFile; fileRef = CD3E252218046BCD00E27F56 /* CSSGridTemplateAreasValue.h */; };
                CD4AC52A1496AE9A0087C4EF /* Composite.wav in Copy Audio Resources */ = {isa = PBXBuildFile; fileRef = CD4AC5281496AE2F0087C4EF /* Composite.wav */; };
+               CD4BE52A1CE136EF009D87DA /* GenericTaskQueue.cpp in Sources */ = {isa = PBXBuildFile; fileRef = CD4BE5291CE13425009D87DA /* GenericTaskQueue.cpp */; };
                CD5209E41B0BD8380077184E /* MediaPlayerEnums.h in Headers */ = {isa = PBXBuildFile; fileRef = CD5209E31B0BD8380077184E /* MediaPlayerEnums.h */; settings = {ATTRIBUTES = (Private, ); }; };
                CD5209E61B0BD9E10077184E /* HTMLMediaElementEnums.h in Headers */ = {isa = PBXBuildFile; fileRef = CD5209E51B0BD9E10077184E /* HTMLMediaElementEnums.h */; settings = {ATTRIBUTES = (Private, ); }; };
                CD52481A18E200ED0008A07D /* DisplaySleepDisabler.cpp in Sources */ = {isa = PBXBuildFile; fileRef = CD52481818E200ED0008A07D /* DisplaySleepDisabler.cpp */; };
                CD3E252218046BCD00E27F56 /* CSSGridTemplateAreasValue.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = CSSGridTemplateAreasValue.h; sourceTree = "<group>"; };
                CD4097FF1A8C855F004C65E9 /* CFNSURLConnectionSPI.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = CFNSURLConnectionSPI.h; sourceTree = "<group>"; };
                CD4AC5281496AE2F0087C4EF /* Composite.wav */ = {isa = PBXFileReference; lastKnownFileType = audio.wav; name = Composite.wav; path = platform/audio/resources/Composite.wav; sourceTree = SOURCE_ROOT; };
+               CD4BE5291CE13425009D87DA /* GenericTaskQueue.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = GenericTaskQueue.cpp; sourceTree = "<group>"; };
                CD4E0AFA11F7BC27009D3811 /* fullscreen.css */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.css; path = fullscreen.css; sourceTree = "<group>"; };
                CD5209E31B0BD8380077184E /* MediaPlayerEnums.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MediaPlayerEnums.h; sourceTree = "<group>"; };
                CD5209E51B0BD9E10077184E /* HTMLMediaElementEnums.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = HTMLMediaElementEnums.h; sourceTree = "<group>"; };
                                5179CE23195C81420019C198 /* GamepadProvider.h */,
                                5179CE29195C91860019C198 /* GamepadProviderClient.h */,
                                CD62FB941AF018E70012ED7D /* GenericTaskQueue.h */,
+                               CD4BE5291CE13425009D87DA /* GenericTaskQueue.cpp */,
                                A8748BDF12CBF2DC001FBA41 /* HashTools.h */,
                                BC3BC29B0E91AB0F00835588 /* HostWindow.h */,
                                862F129F18C1DCE4005C54AF /* HysteresisActivity.h */,
                                D70AD65713E1342B005B50B4 /* RenderRegion.cpp in Sources */,
                                BCE93F471517C6D5008CCF74 /* RenderRegionSet.cpp in Sources */,
                                A871DFE20A15376B00B12A68 /* RenderReplaced.cpp in Sources */,
+                               CD4BE52A1CE136EF009D87DA /* GenericTaskQueue.cpp in Sources */,
                                BCA846D60DC67A350026C309 /* RenderReplica.cpp in Sources */,
                                1479FAED109AE37500DED655 /* RenderRuby.cpp in Sources */,
                                1479FAEF109AE37500DED655 /* RenderRubyBase.cpp in Sources */,
index 694c493..feb4c21 100644 (file)
@@ -28,6 +28,7 @@
 
 #include "Event.h"
 #include "EventTarget.h"
+#include "ScriptExecutionContext.h"
 #include "Timer.h"
 #include <wtf/MainThread.h>
 #include <wtf/NeverDestroyed.h>
@@ -36,7 +37,6 @@ namespace WebCore {
 
 GenericEventQueue::GenericEventQueue(EventTarget& owner)
     : m_owner(owner)
-    , m_weakPtrFactory(this)
     , m_isClosed(false)
 {
 }
@@ -58,40 +58,7 @@ void GenericEventQueue::enqueueEvent(RefPtr<Event>&& event)
     if (m_isSuspended)
         return;
 
-    pendingQueues().append(m_weakPtrFactory.createWeakPtr());
-    if (!sharedTimer().isActive())
-        sharedTimer().startOneShot(0);
-}
-
-Timer& GenericEventQueue::sharedTimer()
-{
-    ASSERT(isMainThread());
-    static NeverDestroyed<Timer> timer(GenericEventQueue::sharedTimerFired);
-    return timer.get();
-}
-
-void GenericEventQueue::sharedTimerFired()
-{
-    ASSERT(!sharedTimer().isActive());
-    ASSERT(!pendingQueues().isEmpty());
-
-    // Copy the pending events first because we don't want to process synchronously the new events
-    // queued by the JS events handlers that are executed in the loop below.
-    Deque<WeakPtr<GenericEventQueue>> queuedEvents;
-    std::swap(queuedEvents, pendingQueues());
-    while (!queuedEvents.isEmpty()) {
-        WeakPtr<GenericEventQueue> queue = queuedEvents.takeFirst();
-        if (!queue)
-            continue;
-        queue->dispatchOneEvent();
-    }
-}
-
-Deque<WeakPtr<GenericEventQueue>>& GenericEventQueue::pendingQueues()
-{
-    ASSERT(isMainThread());
-    static NeverDestroyed<Deque<WeakPtr<GenericEventQueue>>> queues;
-    return queues.get();
+    m_taskQueue.enqueueTask(std::bind(&GenericEventQueue::dispatchOneEvent, this));
 }
 
 void GenericEventQueue::dispatchOneEvent()
@@ -108,13 +75,13 @@ void GenericEventQueue::close()
 {
     m_isClosed = true;
 
-    m_weakPtrFactory.revokeAll();
+    m_taskQueue.close();
     m_pendingEvents.clear();
 }
 
 void GenericEventQueue::cancelAllEvents()
 {
-    m_weakPtrFactory.revokeAll();
+    m_taskQueue.cancelAllTasks();
     m_pendingEvents.clear();
 }
 
@@ -127,7 +94,7 @@ void GenericEventQueue::suspend()
 {
     ASSERT(!m_isSuspended);
     m_isSuspended = true;
-    m_weakPtrFactory.revokeAll();
+    m_taskQueue.cancelAllTasks();
 }
 
 void GenericEventQueue::resume()
@@ -141,10 +108,7 @@ void GenericEventQueue::resume()
         return;
 
     for (unsigned i = 0; i < m_pendingEvents.size(); ++i)
-        pendingQueues().append(m_weakPtrFactory.createWeakPtr());
-
-    if (!sharedTimer().isActive())
-        sharedTimer().startOneShot(0);
+        m_taskQueue.enqueueTask(std::bind(&GenericEventQueue::dispatchOneEvent, this));
 }
 
 }
index d3a038a..b111b01 100644 (file)
@@ -26,6 +26,7 @@
 #ifndef GenericEventQueue_h
 #define GenericEventQueue_h
 
+#include "GenericTaskQueue.h"
 #include <wtf/Deque.h>
 #include <wtf/Forward.h>
 #include <wtf/RefPtr.h>
@@ -52,15 +53,11 @@ public:
     void resume();
 
 private:
-    static Timer& sharedTimer();
-    static void sharedTimerFired();
-    static Deque<WeakPtr<GenericEventQueue>>& pendingQueues();
-
     void dispatchOneEvent();
 
     EventTarget& m_owner;
+    GenericTaskQueue<Timer> m_taskQueue;
     Deque<RefPtr<Event>> m_pendingEvents;
-    WeakPtrFactory<GenericEventQueue> m_weakPtrFactory;
     bool m_isClosed;
     bool m_isSuspended { false };
 };
index 300cf6a..b883f67 100644 (file)
@@ -51,6 +51,7 @@
 #include "FrameView.h"
 #include "HTMLSourceElement.h"
 #include "HTMLVideoElement.h"
+#include "JSDOMError.h"
 #include "JSHTMLMediaElement.h"
 #include "Language.h"
 #include "Logging.h"
@@ -348,10 +349,6 @@ HTMLMediaElement::HTMLMediaElement(const QualifiedName& tagName, Document& docum
     , m_progressEventTimer(*this, &HTMLMediaElement::progressEventTimerFired)
     , m_playbackProgressTimer(*this, &HTMLMediaElement::playbackProgressTimerFired)
     , m_scanTimer(*this, &HTMLMediaElement::scanTimerFired)
-    , m_pauseAfterDetachedTimer(*this, &HTMLMediaElement::pauseAfterDetachedTimerFired)
-    , m_seekTaskQueue(document)
-    , m_resizeTaskQueue(document)
-    , m_shadowDOMTaskQueue(document)
     , m_playedTimeRanges()
     , m_asyncEventQueue(*this)
     , m_requestedPlaybackRate(1)
@@ -560,6 +557,8 @@ HTMLMediaElement::~HTMLMediaElement()
 #endif
 
     m_seekTaskQueue.close();
+    m_promiseTaskQueue.close();
+    m_pauseAfterDetachedTaskQueue.close();
 
     m_completelyLoaded = true;
 }
@@ -779,7 +778,7 @@ Node::InsertionNotificationRequest HTMLMediaElement::insertedInto(ContainerNode&
     return InsertionDone;
 }
 
-void HTMLMediaElement::pauseAfterDetachedTimerFired()
+void HTMLMediaElement::pauseAfterDetachedTask()
 {
     // If we were re-inserted into an active document, no need to pause.
     if (m_inActiveDocument)
@@ -815,7 +814,7 @@ void HTMLMediaElement::removedFrom(ContainerNode& insertionPoint)
     m_inActiveDocument = false;
     if (insertionPoint.inDocument()) {
         // Pause asynchronously to let the operation that removed us finish, in case we get inserted back into a document.
-        m_pauseAfterDetachedTimer.startOneShot(0);
+        m_pauseAfterDetachedTaskQueue.enqueueTask(std::bind(&HTMLMediaElement::pauseAfterDetachedTask, this));
     }
 
     HTMLElement::removedFrom(insertionPoint);
@@ -903,6 +902,38 @@ void HTMLMediaElement::scheduleEvent(const AtomicString& eventName)
     m_asyncEventQueue.enqueueEvent(WTFMove(event));
 }
 
+void HTMLMediaElement::scheduleResolvePendingPlayPromises()
+{
+    m_promiseTaskQueue.enqueueTask(std::bind(&HTMLMediaElement::resolvePendingPlayPromises, this));
+}
+
+void HTMLMediaElement::rejectPendingPlayPromises(DOMError& error)
+{
+    Vector<PlayPromise> pendingPlayPromises = WTFMove(m_pendingPlayPromises);
+
+    for (auto& promise : pendingPlayPromises)
+        promise.reject(error);
+}
+
+void HTMLMediaElement::resolvePendingPlayPromises()
+{
+    Vector<PlayPromise> pendingPlayPromises = WTFMove(m_pendingPlayPromises);
+
+    for (auto& promise : pendingPlayPromises)
+        promise.resolve(nullptr);
+}
+
+void HTMLMediaElement::scheduleNotifyAboutPlaying()
+{
+    m_promiseTaskQueue.enqueueTask(std::bind(&HTMLMediaElement::notifyAboutPlaying, this));
+}
+
+void HTMLMediaElement::notifyAboutPlaying()
+{
+    dispatchEvent(Event::create(eventNames().playingEvent, false, true));
+    resolvePendingPlayPromises();
+}
+
 void HTMLMediaElement::pendingActionTimerFired()
 {
     Ref<HTMLMediaElement> protect(*this); // loadNextSourceChild may fire 'beforeload', which can make arbitrary DOM mutations.
@@ -1915,6 +1946,8 @@ void HTMLMediaElement::noneSupported()
     // 7 - Queue a task to fire a simple event named error at the media element.
     scheduleEvent(eventNames().errorEvent);
 
+    rejectPendingPlayPromises(DOMError::create("NotSupportedError", "The operation is not supported."));
+
 #if ENABLE(MEDIA_SOURCE)
     closeMediaSource();
 #endif
@@ -1979,6 +2012,8 @@ void HTMLMediaElement::cancelPendingEventsAndCallbacks()
 
     for (auto& source : childrenOfType<HTMLSourceElement>(*this))
         source.cancelPendingErrorEvent();
+
+    rejectPendingPlayPromises(DOMError::create("AbortError", "The operation was aborted."));
 }
 
 void HTMLMediaElement::mediaPlayerNetworkStateChanged(MediaPlayer*)
@@ -2242,7 +2277,7 @@ void HTMLMediaElement::setReadyState(MediaPlayer::ReadyState state)
     if (m_readyState == HAVE_FUTURE_DATA && oldState <= HAVE_CURRENT_DATA && tracksAreReady) {
         scheduleEvent(eventNames().canplayEvent);
         if (isPotentiallyPlaying)
-            scheduleEvent(eventNames().playingEvent);
+            scheduleNotifyAboutPlaying();
         shouldUpdateDisplayState = true;
     }
 
@@ -2253,13 +2288,13 @@ void HTMLMediaElement::setReadyState(MediaPlayer::ReadyState state)
         scheduleEvent(eventNames().canplaythroughEvent);
 
         if (isPotentiallyPlaying && oldState <= HAVE_CURRENT_DATA)
-            scheduleEvent(eventNames().playingEvent);
+            scheduleNotifyAboutPlaying();
 
         if (canTransitionFromAutoplayToPlay()) {
             m_paused = false;
             invalidateCachedTime();
             scheduleEvent(eventNames().playEvent);
-            scheduleEvent(eventNames().playingEvent);
+            scheduleNotifyAboutPlaying();
         }
 
         shouldUpdateDisplayState = true;
@@ -2986,6 +3021,29 @@ void HTMLMediaElement::setPreload(const String& preload)
     setAttribute(preloadAttr, preload);
 }
 
+void HTMLMediaElement::play(PlayPromise&& promise)
+{
+    LOG(Media, "HTMLMediaElement::play(%p)", this);
+
+    if (!m_mediaSession->playbackPermitted(*this)) {
+        promise.reject(DOMError::create("NotAllowedError", "The request is not allowed by the user agent or the platform in the current context."));
+        return;
+    }
+
+    if (m_error && m_error->code() == MediaError::MEDIA_ERR_SRC_NOT_SUPPORTED) {
+        promise.reject(DOMError::create("NotSupportedError", "The operation is not supported.."));
+        return;
+    }
+
+    if (ScriptController::processingUserGestureForMedia())
+        removeBehaviorsRestrictionsAfterFirstUserGesture();
+
+    if (!playInternal())
+        promise.reject(DOMError::create("NotAllowedError", "The request is not allowed by the user agent or the platform in the current context."));
+
+    m_pendingPlayPromises.append(WTFMove(promise));
+}
+
 void HTMLMediaElement::play()
 {
     LOG(Media, "HTMLMediaElement::play(%p)", this);
@@ -2998,13 +3056,13 @@ void HTMLMediaElement::play()
     playInternal();
 }
 
-void HTMLMediaElement::playInternal()
+bool HTMLMediaElement::playInternal()
 {
     LOG(Media, "HTMLMediaElement::playInternal(%p)", this);
     
     if (!m_mediaSession->clientWillBeginPlayback()) {
         LOG(Media, "  returning because of interruption");
-        return;
+        return false;
     }
 
     // 4.8.10.9. Playing the media resource
@@ -3025,7 +3083,7 @@ void HTMLMediaElement::playInternal()
         if (m_readyState <= HAVE_CURRENT_DATA)
             scheduleEvent(eventNames().waitingEvent);
         else if (m_readyState >= HAVE_FUTURE_DATA)
-            scheduleEvent(eventNames().playingEvent);
+            scheduleNotifyAboutPlaying();
 
 #if ENABLE(MEDIA_SESSION)
         // 6.3 Activating a media session from a media element
@@ -3047,15 +3105,18 @@ void HTMLMediaElement::playInternal()
 
                 if (!m_session->invoke()) {
                     pause();
-                    return;
+                    return false;
                 }
             }
         }
 #endif
-    }
+    } else if (m_readyState >= HAVE_FUTURE_DATA)
+        scheduleResolvePendingPlayPromises();
+
     m_autoplaying = false;
     updatePlayState();
     updateMediaController();
+    return true;
 }
 
 void HTMLMediaElement::pause()
@@ -3093,6 +3154,7 @@ void HTMLMediaElement::pauseInternal()
         m_paused = true;
         scheduleTimeupdateEvent(false);
         scheduleEvent(eventNames().pauseEvent);
+        rejectPendingPlayPromises(DOMError::create("AbortError", "The operation was aborted."));
 
         if (MemoryPressureHandler::singleton().isUnderMemoryPressure())
             purgeBufferedDataIfPossible();
@@ -5005,6 +5067,8 @@ void HTMLMediaElement::contextDestroyed()
     m_seekTaskQueue.close();
     m_resizeTaskQueue.close();
     m_shadowDOMTaskQueue.close();
+    m_promiseTaskQueue.close();
+    m_pauseAfterDetachedTaskQueue.close();
 
     ActiveDOMObject::contextDestroyed();
 }
@@ -5016,6 +5080,7 @@ void HTMLMediaElement::stop()
     stopWithoutDestroyingMediaPlayer();
 
     m_asyncEventQueue.close();
+    m_promiseTaskQueue.close();
 
     // Once an active DOM object has been stopped it can not be restarted, so we can deallocate
     // the media player now. Note that userCancelledLoad will already called clearMediaPlayer
index 76a793d..d8bc155 100644 (file)
@@ -32,6 +32,7 @@
 #include "GenericEventQueue.h"
 #include "GenericTaskQueue.h"
 #include "HTMLMediaElementEnums.h"
+#include "JSDOMPromise.h"
 #include "MediaCanStartListener.h"
 #include "MediaControllerInterface.h"
 #include "MediaElementSession.h"
@@ -58,6 +59,7 @@ namespace WebCore {
 class AudioSourceProvider;
 class MediaElementAudioSourceNode;
 #endif
+class DOMError;
 class DisplaySleepDisabler;
 class Event;
 class HTMLSourceElement;
@@ -141,6 +143,11 @@ public:
 
     using HTMLMediaElementEnums::DelayedActionType;
     void scheduleDelayedAction(DelayedActionType);
+    void scheduleResolvePendingPlayPromises();
+    void rejectPendingPlayPromises(DOMError&);
+    void resolvePendingPlayPromises();
+    void scheduleNotifyAboutPlaying();
+    void notifyAboutPlaying();
     
     MediaPlayerEnums::MovieLoadType movieLoadType() const;
     
@@ -204,6 +211,10 @@ public:
     bool isAutoplaying() const { return m_autoplaying; }
     bool loop() const;
     void setLoop(bool b);
+
+    typedef DOMPromise<std::nullptr_t, DOMError&> PlayPromise;
+    void play(PlayPromise&&);
+
     WEBCORE_EXPORT void play() override;
     WEBCORE_EXPORT void pause() override;
     void setShouldBufferData(bool) override;
@@ -671,7 +682,7 @@ private:
 
     // These "internal" functions do not check user gesture restrictions.
     void loadInternal();
-    void playInternal();
+    bool playInternal();
     void pauseInternal();
 
     void prepareForLoad();
@@ -772,19 +783,22 @@ private:
     void isVisibleInViewportChanged() final;
     void updateShouldAutoplay();
 
-    void pauseAfterDetachedTimerFired();
+    void pauseAfterDetachedTask();
 
     Timer m_pendingActionTimer;
     Timer m_progressEventTimer;
     Timer m_playbackProgressTimer;
     Timer m_scanTimer;
-    Timer m_pauseAfterDetachedTimer;
-    GenericTaskQueue<ScriptExecutionContext> m_seekTaskQueue;
-    GenericTaskQueue<ScriptExecutionContext> m_resizeTaskQueue;
-    GenericTaskQueue<ScriptExecutionContext> m_shadowDOMTaskQueue;
+    GenericTaskQueue<Timer> m_seekTaskQueue;
+    GenericTaskQueue<Timer> m_resizeTaskQueue;
+    GenericTaskQueue<Timer> m_shadowDOMTaskQueue;
+    GenericTaskQueue<Timer> m_promiseTaskQueue;
+    GenericTaskQueue<Timer> m_pauseAfterDetachedTaskQueue;
     RefPtr<TimeRanges> m_playedTimeRanges;
     GenericEventQueue m_asyncEventQueue;
 
+    Vector<PlayPromise> m_pendingPlayPromises;
+
     double m_requestedPlaybackRate;
     double m_reportedPlaybackRate;
     double m_defaultPlaybackRate;
index 9766e7f..d04ad11 100644 (file)
@@ -75,7 +75,7 @@
     readonly attribute boolean ended;
     [Reflect] attribute boolean autoplay;
     [Reflect] attribute boolean loop;
-    void play();
+    Promise play();
     void pause();
     void fastSeek(unrestricted double time);
 
diff --git a/Source/WebCore/platform/GenericTaskQueue.cpp b/Source/WebCore/platform/GenericTaskQueue.cpp
new file mode 100644 (file)
index 0000000..45a81de
--- /dev/null
@@ -0,0 +1,85 @@
+/*
+ * Copyright (C) 2016 Apple Inc. All rights reserved.
+ * 
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY APPLE INC. AND ITS CONTRIBUTORS ``AS IS''
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+ * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR ITS CONTRIBUTORS
+ * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
+ * THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include "config.h"
+#include "GenericTaskQueue.h"
+
+#include <wtf/MainThread.h>
+#include <wtf/NeverDestroyed.h>
+
+namespace WebCore {
+
+TaskDispatcher<Timer>::TaskDispatcher()
+    : m_weakPtrFactory(this)
+{
+}
+
+void TaskDispatcher<Timer>::postTask(std::function<void()> function)
+{
+    m_pendingTasks.append(WTFMove(function));
+    pendingDispatchers().append(m_weakPtrFactory.createWeakPtr());
+    if (!sharedTimer().isActive())
+        sharedTimer().startOneShot(0);
+}
+
+Timer& TaskDispatcher<Timer>::sharedTimer()
+{
+    ASSERT(isMainThread());
+    static NeverDestroyed<Timer> timer(TaskDispatcher<Timer>::sharedTimerFired);
+    return timer.get();
+}
+
+void TaskDispatcher<Timer>::sharedTimerFired()
+{
+    ASSERT(!sharedTimer().isActive());
+    ASSERT(!pendingDispatchers().isEmpty());
+
+    // Copy the pending events first because we don't want to process synchronously the new events
+    // queued by the JS events handlers that are executed in the loop below.
+    Deque<WeakPtr<TaskDispatcher<Timer>>> queuedDispatchers = WTFMove(pendingDispatchers());
+    while (!queuedDispatchers.isEmpty()) {
+        WeakPtr<TaskDispatcher<Timer>> dispatcher = queuedDispatchers.takeFirst();
+        if (!dispatcher)
+            continue;
+        dispatcher->dispatchOneTask();
+    }
+}
+
+Deque<WeakPtr<TaskDispatcher<Timer>>>& TaskDispatcher<Timer>::pendingDispatchers()
+{
+    ASSERT(isMainThread());
+    static NeverDestroyed<Deque<WeakPtr<TaskDispatcher<Timer>>>> dispatchers;
+    return dispatchers.get();
+}
+
+void TaskDispatcher<Timer>::dispatchOneTask()
+{
+    ASSERT(!m_pendingTasks.isEmpty());
+    std::function<void()> task = m_pendingTasks.takeFirst();
+    task();
+}
+
+}
+
index d06d0c1..e677a4c 100644 (file)
@@ -52,27 +52,18 @@ private:
 template<>
 class TaskDispatcher<Timer> {
 public:
-    TaskDispatcher()
-        : m_timer(*this, &TaskDispatcher<Timer>::timerFired)
-    {
-    }
+    TaskDispatcher();
+    void postTask(std::function<void()>);
 
-    void postTask(std::function<void()> function)
-    {
-        m_queue.append(function);
-        m_timer.startOneShot(0);
-    }
+private:
+    static Timer& sharedTimer();
+    static void sharedTimerFired();
+    static Deque<WeakPtr<TaskDispatcher<Timer>>>& pendingDispatchers();
 
-    void timerFired()
-    {
-        Deque<std::function<void()>> queue;
-        queue.swap(m_queue);
-        for (std::function<void()>& function : queue)
-            function();
-    }
+    void dispatchOneTask();
 
-    Timer m_timer;
-    Deque<std::function<void()>> m_queue;
+    WeakPtrFactory<TaskDispatcher> m_weakPtrFactory;
+    Deque<std::function<void()>> m_pendingTasks;
 };
 
 template <typename T>