Index: third_party/WebKit/Source/core/html/HTMLMediaElement.cpp |
diff --git a/third_party/WebKit/Source/core/html/HTMLMediaElement.cpp b/third_party/WebKit/Source/core/html/HTMLMediaElement.cpp |
index 7beecb9dd4be9a28665ac847fe9e6f45f67b085f..c938c39946b299731088bc3646a3cd1c29398fb9 100644 |
--- a/third_party/WebKit/Source/core/html/HTMLMediaElement.cpp |
+++ b/third_party/WebKit/Source/core/html/HTMLMediaElement.cpp |
@@ -434,8 +434,8 @@ HTMLMediaElement::HTMLMediaElement(const QualifiedName& tagName, Document& docum |
, m_audioTracks(AudioTrackList::create(*this)) |
, m_videoTracks(VideoTrackList::create(*this)) |
, m_textTracks(nullptr) |
- , m_playPromiseResolveTask(CancellableTaskFactory::create(this, &HTMLMediaElement::resolvePlayPromises)) |
- , m_playPromiseRejectTask(CancellableTaskFactory::create(this, &HTMLMediaElement::rejectPlayPromises)) |
+ , m_playPromiseResolveTask(CancellableTaskFactory::create(this, &HTMLMediaElement::resolveScheduledPlayPromises)) |
+ , m_playPromiseRejectTask(CancellableTaskFactory::create(this, &HTMLMediaElement::rejectScheduledPlayPromises)) |
, m_audioSourceNode(nullptr) |
, m_autoplayHelperClient(AutoplayHelperClientImpl::create(this)) |
, m_autoplayHelper(AutoplayExperimentHelper::create(m_autoplayHelperClient.get())) |
@@ -1394,7 +1394,9 @@ void HTMLMediaElement::cancelPendingEventsAndCallbacks() |
source->cancelPendingErrorEvent(); |
m_playPromiseResolveTask->cancel(); |
+ m_playPromiseResolveList.clear(); |
m_playPromiseRejectTask->cancel(); |
+ m_playPromiseRejectList.clear(); |
} |
void HTMLMediaElement::networkStateChanged() |
@@ -2021,8 +2023,20 @@ WebMediaPlayer::Preload HTMLMediaElement::effectivePreloadType() const |
ScriptPromise HTMLMediaElement::playForBindings(ScriptState* scriptState) |
{ |
+ // We have to share the same logic for internal and external callers. The |
+ // internal callers do not want to receive a Promise back but when ::play() |
+ // is called, |m_playPromiseResolvers| needs to be populated. What this code |
+ // does is to populate |m_playPromiseResolvers| before calling ::play() and |
+ // remove the Promise if ::play() failed. |
+ ScriptPromiseResolver* resolver = ScriptPromiseResolver::create(scriptState); |
+ ScriptPromise promise = resolver->promise(); |
+ m_playPromiseResolvers.append(resolver); |
+ |
Nullable<ExceptionCode> code = play(); |
if (!code.isNull()) { |
+ DCHECK(!m_playPromiseResolvers.isEmpty()); |
+ m_playPromiseResolvers.removeLast(); |
+ |
String message; |
switch (code.get()) { |
case NotAllowedError: |
@@ -2037,10 +2051,6 @@ ScriptPromise HTMLMediaElement::playForBindings(ScriptState* scriptState) |
return ScriptPromise::rejectWithDOMException(scriptState, DOMException::create(code.get(), message)); |
} |
- ScriptPromiseResolver* resolver = ScriptPromiseResolver::create(scriptState); |
- ScriptPromise promise = resolver->promise(); |
- |
- m_playResolvers.append(resolver); |
return promise; |
} |
@@ -3602,7 +3612,9 @@ DEFINE_TRACE(HTMLMediaElement) |
visitor->trace(m_cueTimeline); |
visitor->trace(m_textTracks); |
visitor->trace(m_textTracksWhenResourceSelectionBegan); |
- visitor->trace(m_playResolvers); |
+ visitor->trace(m_playPromiseResolvers); |
+ visitor->trace(m_playPromiseResolveList); |
+ visitor->trace(m_playPromiseRejectList); |
visitor->trace(m_audioSourceProvider); |
visitor->trace(m_autoplayHelperClient); |
visitor->trace(m_autoplayHelper); |
@@ -3699,10 +3711,19 @@ void HTMLMediaElement::triggerAutoplayViewportCheckForTesting() |
void HTMLMediaElement::scheduleResolvePlayPromises() |
{ |
- // Per spec, if there are two tasks in the queue, the first task will remove |
- // all the pending promises making the second task useless unless a promise |
- // can be added between the first and second task being run which is not |
- // possible at the moment. |
+ // TODO(mlamouri): per spec, we should create a new task but we can't create |
+ // a new cancellable task without cancelling the previous one. There are two |
+ // approaches then: cancel the previous task and create a new one with the |
+ // appended promise list or append the new promise to the current list. The |
+ // latter approach is preferred because it might be the less observable |
+ // change. |
+ DCHECK(m_playPromiseResolveList.isEmpty() || m_playPromiseResolveTask->isPending()); |
+ if (m_playPromiseResolvers.isEmpty()) |
+ return; |
+ |
+ m_playPromiseResolveList.appendVector(m_playPromiseResolvers); |
+ m_playPromiseResolvers.clear(); |
+ |
if (m_playPromiseResolveTask->isPending()) |
return; |
@@ -3711,10 +3732,19 @@ void HTMLMediaElement::scheduleResolvePlayPromises() |
void HTMLMediaElement::scheduleRejectPlayPromises(ExceptionCode code) |
{ |
- // Per spec, if there are two tasks in the queue, the first task will remove |
- // all the pending promises making the second task useless unless a promise |
- // can be added between the first and second task being run which is not |
- // possible at the moment. |
+ // TODO(mlamouri): per spec, we should create a new task but we can't create |
+ // a new cancellable task without cancelling the previous one. There are two |
+ // approaches then: cancel the previous task and create a new one with the |
+ // appended promise list or append the new promise to the current list. The |
+ // latter approach is preferred because it might be the less observable |
+ // change. |
+ DCHECK(m_playPromiseRejectList.isEmpty() || m_playPromiseRejectTask->isPending()); |
+ if (m_playPromiseResolvers.isEmpty()) |
+ return; |
+ |
+ m_playPromiseRejectList.appendVector(m_playPromiseResolvers); |
+ m_playPromiseResolvers.clear(); |
+ |
if (m_playPromiseRejectTask->isPending()) |
return; |
@@ -3730,34 +3760,41 @@ void HTMLMediaElement::scheduleNotifyPlaying() |
scheduleResolvePlayPromises(); |
} |
-void HTMLMediaElement::resolvePlayPromises() |
+void HTMLMediaElement::resolveScheduledPlayPromises() |
{ |
- for (auto& resolver: m_playResolvers) |
+ for (auto& resolver: m_playPromiseResolveList) |
resolver->resolve(); |
- m_playResolvers.clear(); |
+ m_playPromiseResolveList.clear(); |
} |
-void HTMLMediaElement::rejectPlayPromises() |
+void HTMLMediaElement::rejectScheduledPlayPromises() |
{ |
// TODO(mlamouri): the message is generated based on the code because |
// arguments can't be passed to a cancellable task. In order to save space |
// used by the object, the string isn't saved. |
DCHECK(m_playPromiseErrorCode == AbortError || m_playPromiseErrorCode == NotSupportedError); |
if (m_playPromiseErrorCode == AbortError) |
- rejectPlayPromises(AbortError, "The play() request was interrupted by a call to pause()."); |
+ rejectPlayPromisesInternal(AbortError, "The play() request was interrupted by a call to pause()."); |
else |
- rejectPlayPromises(NotSupportedError, "Failed to load because no supported source was found."); |
+ rejectPlayPromisesInternal(NotSupportedError, "Failed to load because no supported source was found."); |
} |
void HTMLMediaElement::rejectPlayPromises(ExceptionCode code, const String& message) |
{ |
+ m_playPromiseRejectList.appendVector(m_playPromiseResolvers); |
+ m_playPromiseResolvers.clear(); |
+ rejectPlayPromisesInternal(code, message); |
+} |
+ |
+void HTMLMediaElement::rejectPlayPromisesInternal(ExceptionCode code, const String& message) |
+{ |
DCHECK(code == AbortError || code == NotSupportedError); |
- for (auto& resolver: m_playResolvers) |
+ for (auto& resolver: m_playPromiseRejectList) |
resolver->reject(DOMException::create(code, message)); |
- m_playResolvers.clear(); |
+ m_playPromiseRejectList.clear(); |
} |
EnumerationHistogram& HTMLMediaElement::showControlsHistogram() const |