Diff
Modified: trunk/LayoutTests/ChangeLog (200626 => 200627)
--- trunk/LayoutTests/ChangeLog 2016-05-10 17:23:09 UTC (rev 200626)
+++ trunk/LayoutTests/ChangeLog 2016-05-10 17:27:12 UTC (rev 200627)
@@ -1,3 +1,25 @@
+2016-05-05 Jer Noble <[email protected]>
+
+ 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 Zalan Bujtas <[email protected]>
REGRESSION (r193610): Drop down menu doesn’t expand at allofbach.com
Modified: trunk/LayoutTests/imported/w3c/ChangeLog (200626 => 200627)
--- trunk/LayoutTests/imported/w3c/ChangeLog 2016-05-10 17:23:09 UTC (rev 200626)
+++ trunk/LayoutTests/imported/w3c/ChangeLog 2016-05-10 17:27:12 UTC (rev 200627)
@@ -1,3 +1,14 @@
+2016-05-06 Jer Noble <[email protected]>
+
+ 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-04 Chris Dumez <[email protected]>
Media elements should not be paused right away when removed from the document
Modified: trunk/LayoutTests/imported/w3c/web-platform-tests/html/dom/interfaces-expected.txt (200626 => 200627)
--- trunk/LayoutTests/imported/w3c/web-platform-tests/html/dom/interfaces-expected.txt 2016-05-10 17:23:09 UTC (rev 200626)
+++ trunk/LayoutTests/imported/w3c/web-platform-tests/html/dom/interfaces-expected.txt 2016-05-10 17:27:12 UTC (rev 200627)
@@ -2447,7 +2447,9 @@
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
Added: trunk/LayoutTests/media/media-play-promise-reject-error-notsupported-expected.txt (0 => 200627)
--- trunk/LayoutTests/media/media-play-promise-reject-error-notsupported-expected.txt (rev 0)
+++ trunk/LayoutTests/media/media-play-promise-reject-error-notsupported-expected.txt 2016-05-10 17:27:12 UTC (rev 200627)
@@ -0,0 +1,7 @@
+
+RUN(mediaElement.src = "" "content/invalid"))
+RUN(mediaElement.play().then(failTest).catch(promiseRejected))
+Promise rejected. OK
+EXPECTED (error.name == 'NotSupportedError') OK
+END OF TEST
+
Added: trunk/LayoutTests/media/media-play-promise-reject-error-notsupported.html (0 => 200627)
--- trunk/LayoutTests/media/media-play-promise-reject-error-notsupported.html (rev 0)
+++ trunk/LayoutTests/media/media-play-promise-reject-error-notsupported.html 2016-05-10 17:27:12 UTC (rev 200627)
@@ -0,0 +1,31 @@
+<!DOCTYPE html>
+<html>
+ <head>
+ <script src=""
+ <script src=""
+
+ <script>
+ var promise;
+ var error;
+ function start()
+ {
+ findMediaElement();
+ run('mediaElement.src = "" "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>
Added: trunk/LayoutTests/media/media-play-promise-reject-load-abort-expected.txt (0 => 200627)
--- trunk/LayoutTests/media/media-play-promise-reject-load-abort-expected.txt (rev 0)
+++ trunk/LayoutTests/media/media-play-promise-reject-load-abort-expected.txt 2016-05-10 17:27:12 UTC (rev 200627)
@@ -0,0 +1,7 @@
+
+RUN(mediaElement.play().then(failTest).catch(promiseRejected))
+RUN(mediaElement.src = "" "content/test"))
+Promise rejected. OK
+EXPECTED (error.name == 'AbortError') OK
+END OF TEST
+
Added: trunk/LayoutTests/media/media-play-promise-reject-load-abort.html (0 => 200627)
--- trunk/LayoutTests/media/media-play-promise-reject-load-abort.html (rev 0)
+++ trunk/LayoutTests/media/media-play-promise-reject-load-abort.html 2016-05-10 17:27:12 UTC (rev 200627)
@@ -0,0 +1,31 @@
+<!DOCTYPE html>
+<html>
+ <head>
+ <script src=""
+ <script src=""
+
+ <script>
+ var promise;
+ var error;
+ function start()
+ {
+ findMediaElement();
+ run('mediaElement.play().then(failTest).catch(promiseRejected)');
+ run('mediaElement.src = "" "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>
Added: trunk/LayoutTests/media/media-play-promise-reject-pause-abort-expected.txt (0 => 200627)
--- trunk/LayoutTests/media/media-play-promise-reject-pause-abort-expected.txt (rev 0)
+++ trunk/LayoutTests/media/media-play-promise-reject-pause-abort-expected.txt 2016-05-10 17:27:12 UTC (rev 200627)
@@ -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
+
Added: trunk/LayoutTests/media/media-play-promise-reject-pause-abort.html (0 => 200627)
--- trunk/LayoutTests/media/media-play-promise-reject-pause-abort.html (rev 0)
+++ trunk/LayoutTests/media/media-play-promise-reject-pause-abort.html 2016-05-10 17:27:12 UTC (rev 200627)
@@ -0,0 +1,31 @@
+<!DOCTYPE html>
+<html>
+ <head>
+ <script src=""
+ <script src=""
+
+ <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>
Added: trunk/LayoutTests/media/media-play-promise-reject-play-notallowed-expected.txt (0 => 200627)
--- trunk/LayoutTests/media/media-play-promise-reject-play-notallowed-expected.txt (rev 0)
+++ trunk/LayoutTests/media/media-play-promise-reject-play-notallowed-expected.txt 2016-05-10 17:27:12 UTC (rev 200627)
@@ -0,0 +1,8 @@
+
+RUN(internals.setMediaElementRestrictions(mediaElement, "RequireUserGestureForVideoRateChange"))
+RUN(mediaElement.src = "" "content/test"))
+RUN(mediaElement.play().then(failTest).catch(promiseRejected))
+Promise rejected. OK
+EXPECTED (error.name == 'NotAllowedError') OK
+END OF TEST
+
Added: trunk/LayoutTests/media/media-play-promise-reject-play-notallowed.html (0 => 200627)
--- trunk/LayoutTests/media/media-play-promise-reject-play-notallowed.html (rev 0)
+++ trunk/LayoutTests/media/media-play-promise-reject-play-notallowed.html 2016-05-10 17:27:12 UTC (rev 200627)
@@ -0,0 +1,35 @@
+<!DOCTYPE html>
+<html>
+ <head>
+ <script src=""
+ <script src=""
+
+ <script>
+ var promise;
+ var error;
+ function start()
+ {
+ findMediaElement();
+ if (window.internals)
+ run('internals.setMediaElementRestrictions(mediaElement, "RequireUserGestureForVideoRateChange")');
+
+ run('mediaElement.src = "" "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>
Added: trunk/LayoutTests/media/media-play-promise-reject-play-notsupported-expected.txt (0 => 200627)
--- trunk/LayoutTests/media/media-play-promise-reject-play-notsupported-expected.txt (rev 0)
+++ trunk/LayoutTests/media/media-play-promise-reject-play-notsupported-expected.txt 2016-05-10 17:27:12 UTC (rev 200627)
@@ -0,0 +1,8 @@
+
+RUN(mediaElement.src = "" "content/invalid"))
+EVENT(error)
+RUN(mediaElement.play().then(failTest).catch(promiseRejected))
+Promise rejected. OK
+EXPECTED (error.name == 'NotSupportedError') OK
+END OF TEST
+
Added: trunk/LayoutTests/media/media-play-promise-reject-play-notsupported.html (0 => 200627)
--- trunk/LayoutTests/media/media-play-promise-reject-play-notsupported.html (rev 0)
+++ trunk/LayoutTests/media/media-play-promise-reject-play-notsupported.html 2016-05-10 17:27:12 UTC (rev 200627)
@@ -0,0 +1,37 @@
+<!DOCTYPE html>
+<html>
+ <head>
+ <script src=""
+ <script src=""
+
+ <script>
+ var promise;
+ var error;
+ function start()
+ {
+ findMediaElement();
+ run('mediaElement.src = "" "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>
Added: trunk/LayoutTests/media/media-play-promise-resolve-expected.txt (0 => 200627)
--- trunk/LayoutTests/media/media-play-promise-resolve-expected.txt (rev 0)
+++ trunk/LayoutTests/media/media-play-promise-resolve-expected.txt 2016-05-10 17:27:12 UTC (rev 200627)
@@ -0,0 +1,6 @@
+
+RUN(mediaElement.src = "" "content/test"))
+RUN(mediaElement.play().then(promiseResolved).catch(failTest))
+Promise resolved. OK
+END OF TEST
+
Added: trunk/LayoutTests/media/media-play-promise-resolve-when-playing-expected.txt (0 => 200627)
--- trunk/LayoutTests/media/media-play-promise-resolve-when-playing-expected.txt (rev 0)
+++ trunk/LayoutTests/media/media-play-promise-resolve-when-playing-expected.txt 2016-05-10 17:27:12 UTC (rev 200627)
@@ -0,0 +1,8 @@
+
+RUN(mediaElement.src = "" "content/test"))
+RUN(mediaElement.play())
+EVENT(playing)
+RUN(mediaElement.play().then(promiseResolved).catch(failTest))
+Promise resolved. OK
+END OF TEST
+
Added: trunk/LayoutTests/media/media-play-promise-resolve-when-playing.html (0 => 200627)
--- trunk/LayoutTests/media/media-play-promise-resolve-when-playing.html (rev 0)
+++ trunk/LayoutTests/media/media-play-promise-resolve-when-playing.html 2016-05-10 17:27:12 UTC (rev 200627)
@@ -0,0 +1,35 @@
+<!DOCTYPE html>
+<html>
+ <head>
+ <script src=""
+ <script src=""
+
+ <script>
+ var promise;
+ function start()
+ {
+ findMediaElement();
+ run('mediaElement.src = "" "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>
Added: trunk/LayoutTests/media/media-play-promise-resolve.html (0 => 200627)
--- trunk/LayoutTests/media/media-play-promise-resolve.html (rev 0)
+++ trunk/LayoutTests/media/media-play-promise-resolve.html 2016-05-10 17:27:12 UTC (rev 200627)
@@ -0,0 +1,29 @@
+<!DOCTYPE html>
+<html>
+ <head>
+ <script src=""
+ <script src=""
+
+ <script>
+ var promise;
+ function start()
+ {
+ findMediaElement();
+ run('mediaElement.src = "" "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>
Modified: trunk/Source/WebCore/CMakeLists.txt (200626 => 200627)
--- trunk/Source/WebCore/CMakeLists.txt 2016-05-10 17:23:09 UTC (rev 200626)
+++ trunk/Source/WebCore/CMakeLists.txt 2016-05-10 17:27:12 UTC (rev 200627)
@@ -2109,6 +2109,7 @@
platform/FileChooser.cpp
platform/FileStream.cpp
platform/FileSystem.cpp
+ platform/GenericTaskQueue.cpp
platform/Language.cpp
platform/Length.cpp
platform/LengthBox.cpp
Modified: trunk/Source/WebCore/ChangeLog (200626 => 200627)
--- trunk/Source/WebCore/ChangeLog 2016-05-10 17:23:09 UTC (rev 200626)
+++ trunk/Source/WebCore/ChangeLog 2016-05-10 17:27:12 UTC (rev 200627)
@@ -1,3 +1,85 @@
+2016-05-05 Jer Noble <[email protected]>
+
+ 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.
+
+ * 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 Chris Dumez <[email protected]>
Get rid of a lot of calls to RefPtr::release()
Modified: trunk/Source/WebCore/WebCore.xcodeproj/project.pbxproj (200626 => 200627)
--- trunk/Source/WebCore/WebCore.xcodeproj/project.pbxproj 2016-05-10 17:23:09 UTC (rev 200626)
+++ trunk/Source/WebCore/WebCore.xcodeproj/project.pbxproj 2016-05-10 17:27:12 UTC (rev 200627)
@@ -6042,6 +6042,7 @@
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 */; };
@@ -14032,6 +14033,7 @@
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>"; };
@@ -22949,6 +22951,7 @@
5179CE23195C81420019C198 /* GamepadProvider.h */,
5179CE29195C91860019C198 /* GamepadProviderClient.h */,
CD62FB941AF018E70012ED7D /* GenericTaskQueue.h */,
+ CD4BE5291CE13425009D87DA /* GenericTaskQueue.cpp */,
A8748BDF12CBF2DC001FBA41 /* HashTools.h */,
BC3BC29B0E91AB0F00835588 /* HostWindow.h */,
862F129F18C1DCE4005C54AF /* HysteresisActivity.h */,
@@ -31340,6 +31343,7 @@
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 */,
Modified: trunk/Source/WebCore/dom/GenericEventQueue.cpp (200626 => 200627)
--- trunk/Source/WebCore/dom/GenericEventQueue.cpp 2016-05-10 17:23:09 UTC (rev 200626)
+++ trunk/Source/WebCore/dom/GenericEventQueue.cpp 2016-05-10 17:27:12 UTC (rev 200627)
@@ -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 @@
GenericEventQueue::GenericEventQueue(EventTarget& owner)
: m_owner(owner)
- , m_weakPtrFactory(this)
, m_isClosed(false)
{
}
@@ -58,42 +58,9 @@
if (m_isSuspended)
return;
- pendingQueues().append(m_weakPtrFactory.createWeakPtr());
- if (!sharedTimer().isActive())
- sharedTimer().startOneShot(0);
+ m_taskQueue.enqueueTask(std::bind(&GenericEventQueue::dispatchOneEvent, this));
}
-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();
-}
-
void GenericEventQueue::dispatchOneEvent()
{
ASSERT(!m_pendingEvents.isEmpty());
@@ -108,13 +75,13 @@
{
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 @@
{
ASSERT(!m_isSuspended);
m_isSuspended = true;
- m_weakPtrFactory.revokeAll();
+ m_taskQueue.cancelAllTasks();
}
void GenericEventQueue::resume()
@@ -141,10 +108,7 @@
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));
}
}
Modified: trunk/Source/WebCore/dom/GenericEventQueue.h (200626 => 200627)
--- trunk/Source/WebCore/dom/GenericEventQueue.h 2016-05-10 17:23:09 UTC (rev 200626)
+++ trunk/Source/WebCore/dom/GenericEventQueue.h 2016-05-10 17:27:12 UTC (rev 200627)
@@ -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 @@
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 };
};
Modified: trunk/Source/WebCore/html/HTMLMediaElement.cpp (200626 => 200627)
--- trunk/Source/WebCore/html/HTMLMediaElement.cpp 2016-05-10 17:23:09 UTC (rev 200626)
+++ trunk/Source/WebCore/html/HTMLMediaElement.cpp 2016-05-10 17:27:12 UTC (rev 200627)
@@ -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 @@
, 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 @@
#endif
m_seekTaskQueue.close();
+ m_promiseTaskQueue.close();
+ m_pauseAfterDetachedTaskQueue.close();
m_completelyLoaded = true;
}
@@ -779,7 +778,7 @@
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 @@
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 @@
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 @@
// 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 @@
for (auto& source : childrenOfType<HTMLSourceElement>(*this))
source.cancelPendingErrorEvent();
+
+ rejectPendingPlayPromises(DOMError::create("AbortError", "The operation was aborted."));
}
void HTMLMediaElement::mediaPlayerNetworkStateChanged(MediaPlayer*)
@@ -2242,7 +2277,7 @@
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 @@
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 @@
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 @@
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 @@
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 @@
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 @@
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 @@
m_seekTaskQueue.close();
m_resizeTaskQueue.close();
m_shadowDOMTaskQueue.close();
+ m_promiseTaskQueue.close();
+ m_pauseAfterDetachedTaskQueue.close();
ActiveDOMObject::contextDestroyed();
}
@@ -5016,6 +5080,7 @@
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
Modified: trunk/Source/WebCore/html/HTMLMediaElement.h (200626 => 200627)
--- trunk/Source/WebCore/html/HTMLMediaElement.h 2016-05-10 17:23:09 UTC (rev 200626)
+++ trunk/Source/WebCore/html/HTMLMediaElement.h 2016-05-10 17:27:12 UTC (rev 200627)
@@ -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 @@
class AudioSourceProvider;
class MediaElementAudioSourceNode;
#endif
+class DOMError;
class DisplaySleepDisabler;
class Event;
class HTMLSourceElement;
@@ -141,6 +143,11 @@
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 @@
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 @@
// These "internal" functions do not check user gesture restrictions.
void loadInternal();
- void playInternal();
+ bool playInternal();
void pauseInternal();
void prepareForLoad();
@@ -772,19 +783,22 @@
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;
Modified: trunk/Source/WebCore/html/HTMLMediaElement.idl (200626 => 200627)
--- trunk/Source/WebCore/html/HTMLMediaElement.idl 2016-05-10 17:23:09 UTC (rev 200626)
+++ trunk/Source/WebCore/html/HTMLMediaElement.idl 2016-05-10 17:27:12 UTC (rev 200627)
@@ -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);
Added: trunk/Source/WebCore/platform/GenericTaskQueue.cpp (0 => 200627)
--- trunk/Source/WebCore/platform/GenericTaskQueue.cpp (rev 0)
+++ trunk/Source/WebCore/platform/GenericTaskQueue.cpp 2016-05-10 17:27:12 UTC (rev 200627)
@@ -0,0 +1,92 @@
+/*
+ * 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()
+{
+ Deque<TaskDispatcher<Timer>*> replacementQueue;
+ for (auto* dispatcher : pendingDispatchers()) {
+ if (dispatcher != this)
+ replacementQueue.append(dispatcher);
+ }
+ if (replacementQueue.isEmpty())
+ sharedTimer().stop();
+ pendingDispatchers() = WTFMove(replacementQueue);
+}
+
+void TaskDispatcher<Timer>::postTask(std::function<void()> function)
+{
+ m_pendingTasks.append(WTFMove(function));
+ pendingDispatchers().append(this);
+ 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<TaskDispatcher<Timer>*> queuedDispatchers = WTFMove(pendingDispatchers());
+ while (!queuedDispatchers.isEmpty()) {
+ TaskDispatcher<Timer>* dispatcher = queuedDispatchers.takeFirst();
+ if (!dispatcher)
+ continue;
+ dispatcher->dispatchOneTask();
+ }
+}
+
+Deque<TaskDispatcher<Timer>*>& TaskDispatcher<Timer>::pendingDispatchers()
+{
+ ASSERT(isMainThread());
+ static NeverDestroyed<Deque<TaskDispatcher<Timer>*>> dispatchers;
+ return dispatchers.get();
+}
+
+void TaskDispatcher<Timer>::dispatchOneTask()
+{
+ ASSERT(!m_pendingTasks.isEmpty());
+ std::function<void()> task = m_pendingTasks.takeFirst();
+ task();
+}
+
+}
+
Modified: trunk/Source/WebCore/platform/GenericTaskQueue.h (200626 => 200627)
--- trunk/Source/WebCore/platform/GenericTaskQueue.h 2016-05-10 17:23:09 UTC (rev 200626)
+++ trunk/Source/WebCore/platform/GenericTaskQueue.h 2016-05-10 17:27:12 UTC (rev 200627)
@@ -52,27 +52,17 @@
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<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;
+ Deque<std::function<void()>> m_pendingTasks;
};
template <typename T>