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
+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.
+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.
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
--- /dev/null
+
+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
+
--- /dev/null
+<!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>
--- /dev/null
+
+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
+
--- /dev/null
+<!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>
--- /dev/null
+
+RUN(mediaElement.play().then(failTest).catch(promiseRejected))
+RUN(mediaElement.pause())
+Promise rejected. OK
+EXPECTED (error.name == 'AbortError') OK
+END OF TEST
+
--- /dev/null
+<!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>
--- /dev/null
+
+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
+
--- /dev/null
+<!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>
--- /dev/null
+
+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
+
--- /dev/null
+<!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>
--- /dev/null
+
+RUN(mediaElement.src = findMediaFile("video", "content/test"))
+RUN(mediaElement.play().then(promiseResolved).catch(failTest))
+Promise resolved. OK
+END OF TEST
+
--- /dev/null
+
+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
+
--- /dev/null
+<!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>
--- /dev/null
+<!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>
platform/FileChooser.cpp
platform/FileStream.cpp
platform/FileSystem.cpp
+ platform/GenericTaskQueue.cpp
platform/Language.cpp
platform/Length.cpp
platform/LengthBox.cpp
+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.
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 */,
#include "Event.h"
#include "EventTarget.h"
+#include "ScriptExecutionContext.h"
#include "Timer.h"
#include <wtf/MainThread.h>
#include <wtf/NeverDestroyed.h>
GenericEventQueue::GenericEventQueue(EventTarget& owner)
: m_owner(owner)
- , m_weakPtrFactory(this)
, m_isClosed(false)
{
}
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()
{
m_isClosed = true;
- m_weakPtrFactory.revokeAll();
+ m_taskQueue.close();
m_pendingEvents.clear();
}
void GenericEventQueue::cancelAllEvents()
{
- m_weakPtrFactory.revokeAll();
+ m_taskQueue.cancelAllTasks();
m_pendingEvents.clear();
}
{
ASSERT(!m_isSuspended);
m_isSuspended = true;
- m_weakPtrFactory.revokeAll();
+ m_taskQueue.cancelAllTasks();
}
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));
}
}
#ifndef GenericEventQueue_h
#define GenericEventQueue_h
+#include "GenericTaskQueue.h"
#include <wtf/Deque.h>
#include <wtf/Forward.h>
#include <wtf/RefPtr.h>
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 };
};
#include "FrameView.h"
#include "HTMLSourceElement.h"
#include "HTMLVideoElement.h"
+#include "JSDOMError.h"
#include "JSHTMLMediaElement.h"
#include "Language.h"
#include "Logging.h"
, 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)
#endif
m_seekTaskQueue.close();
+ m_promiseTaskQueue.close();
+ m_pauseAfterDetachedTaskQueue.close();
m_completelyLoaded = true;
}
return InsertionDone;
}
-void HTMLMediaElement::pauseAfterDetachedTimerFired()
+void HTMLMediaElement::pauseAfterDetachedTask()
{
// If we were re-inserted into an active document, no need to pause.
if (m_inActiveDocument)
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);
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.
// 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
for (auto& source : childrenOfType<HTMLSourceElement>(*this))
source.cancelPendingErrorEvent();
+
+ rejectPendingPlayPromises(DOMError::create("AbortError", "The operation was aborted."));
}
void HTMLMediaElement::mediaPlayerNetworkStateChanged(MediaPlayer*)
if (m_readyState == HAVE_FUTURE_DATA && oldState <= HAVE_CURRENT_DATA && tracksAreReady) {
scheduleEvent(eventNames().canplayEvent);
if (isPotentiallyPlaying)
- scheduleEvent(eventNames().playingEvent);
+ scheduleNotifyAboutPlaying();
shouldUpdateDisplayState = true;
}
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;
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);
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
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
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()
m_paused = true;
scheduleTimeupdateEvent(false);
scheduleEvent(eventNames().pauseEvent);
+ rejectPendingPlayPromises(DOMError::create("AbortError", "The operation was aborted."));
if (MemoryPressureHandler::singleton().isUnderMemoryPressure())
purgeBufferedDataIfPossible();
m_seekTaskQueue.close();
m_resizeTaskQueue.close();
m_shadowDOMTaskQueue.close();
+ m_promiseTaskQueue.close();
+ m_pauseAfterDetachedTaskQueue.close();
ActiveDOMObject::contextDestroyed();
}
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
#include "GenericEventQueue.h"
#include "GenericTaskQueue.h"
#include "HTMLMediaElementEnums.h"
+#include "JSDOMPromise.h"
#include "MediaCanStartListener.h"
#include "MediaControllerInterface.h"
#include "MediaElementSession.h"
class AudioSourceProvider;
class MediaElementAudioSourceNode;
#endif
+class DOMError;
class DisplaySleepDisabler;
class Event;
class HTMLSourceElement;
using HTMLMediaElementEnums::DelayedActionType;
void scheduleDelayedAction(DelayedActionType);
+ void scheduleResolvePendingPlayPromises();
+ void rejectPendingPlayPromises(DOMError&);
+ void resolvePendingPlayPromises();
+ void scheduleNotifyAboutPlaying();
+ void notifyAboutPlaying();
MediaPlayerEnums::MovieLoadType movieLoadType() const;
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;
// These "internal" functions do not check user gesture restrictions.
void loadInternal();
- void playInternal();
+ bool playInternal();
void pauseInternal();
void prepareForLoad();
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;
readonly attribute boolean ended;
[Reflect] attribute boolean autoplay;
[Reflect] attribute boolean loop;
- void play();
+ Promise play();
void pause();
void fastSeek(unrestricted double time);
--- /dev/null
+/*
+ * 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();
+}
+
+}
+
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>