Bug 1244768 part 1 - implement utilities which are the foundation of promise-based play operation; r=jwwang

In this patch, the following utilities are implemented:
(1) A list to keep pending promises of a HTMLMediaElement.
(2) A method to take pending promises out from the HTMLMediaElement.
(3) A global function to resolve the passed promises.
(4) A global function to reject the passed promises with an error code.
(5) A method to asynchronously resolve all pending promises of a HTMLMediaElement.
(6) A method to asynchronously reject all pending promises of a HTMLMediaElement.
(7) A method to dispatch a 'playing' event and resolve all the pending play promises.

All the above functionalities are defined at WHATWG 4.8.12.8:
https://html.spec.whatwg.org/multipage/embedded-content.html#list-of-pending-play-promises

This patch also implements two MediaEvent classes, nsResolveOrRejectPendingPlayPromisesRunner and nsNotifyAboutPlayingRunner, which help (5), (6) and (7).

This patch also implements a list of already-dispatched nsResolveOrRejectPendingPlayPromisesRunner; we keep tracing these tasks because the load algorithm resolves/rejects all already-dispatched pending play promises (in patch 2).

MozReview-Commit-ID: EUirNqDfttk
This commit is contained in:
Kaku Kuo
2016-12-08 14:34:39 -10:00
parent b9f30ab4f0
commit 77834d2806
2 changed files with 173 additions and 2 deletions

View File

@@ -8,6 +8,7 @@
#include "mozilla/dom/HTMLMediaElementBinding.h"
#include "mozilla/dom/HTMLSourceElement.h"
#include "mozilla/dom/ElementInlines.h"
#include "mozilla/dom/Promise.h"
#include "mozilla/ArrayUtils.h"
#include "mozilla/MathAlgorithms.h"
#include "mozilla/AsyncEventDispatcher.h"
@@ -153,6 +154,22 @@ static const unsigned short MEDIA_ERR_NETWORK = 2;
static const unsigned short MEDIA_ERR_DECODE = 3;
static const unsigned short MEDIA_ERR_SRC_NOT_SUPPORTED = 4;
static void
ResolvePromisesWithUndefined(const nsTArray<RefPtr<Promise>>& aPromises)
{
for (auto& promise : aPromises) {
promise->MaybeResolveWithUndefined();
}
}
static void
RejectPromises(const nsTArray<RefPtr<Promise>>& aPromises, nsresult aError)
{
for (auto& promise : aPromises) {
promise->MaybeReject(aError);
}
}
// Under certain conditions there may be no-one holding references to
// a media element from script, DOM parent, etc, but the element may still
// fire meaningful events in the future so we can't destroy it yet:
@@ -243,6 +260,74 @@ public:
}
};
/*
* If no error is passed while constructing an instance, the instance will
* resolve the passed promises with undefined; otherwise, the instance will
* reject the passed promises with the passed error.
*
* The constructor appends the constructed instance into the passed media
* element's mPendingPlayPromisesRunners member and once the the runner is run
* (whether fulfilled or canceled), it removes itself from
* mPendingPlayPromisesRunners.
*/
class HTMLMediaElement::nsResolveOrRejectPendingPlayPromisesRunner : public nsMediaEvent
{
nsTArray<RefPtr<Promise>> mPromises;
nsresult mError;
public:
nsResolveOrRejectPendingPlayPromisesRunner(HTMLMediaElement* aElement,
nsTArray<RefPtr<Promise>>&& aPromises,
nsresult aError = NS_OK)
: nsMediaEvent(aElement)
, mPromises(Move(aPromises))
, mError(aError)
{
mElement->mPendingPlayPromisesRunners.AppendElement(this);
}
void ResolveOrReject()
{
if (NS_SUCCEEDED(mError)) {
ResolvePromisesWithUndefined(mPromises);
} else {
RejectPromises(mPromises, mError);
}
}
NS_IMETHOD Run() override
{
if (!IsCancelled()) {
ResolveOrReject();
}
mElement->mPendingPlayPromisesRunners.RemoveElement(this);
return NS_OK;
}
};
class HTMLMediaElement::nsNotifyAboutPlayingRunner : public nsResolveOrRejectPendingPlayPromisesRunner
{
public:
nsNotifyAboutPlayingRunner(HTMLMediaElement* aElement,
nsTArray<RefPtr<Promise>>&& aPendingPlayPromises)
: nsResolveOrRejectPendingPlayPromisesRunner(aElement,
Move(aPendingPlayPromises))
{
}
NS_IMETHOD Run() override
{
if (IsCancelled()) {
mElement->mPendingPlayPromisesRunners.RemoveElement(this);
return NS_OK;
}
mElement->DispatchEvent(NS_LITERAL_STRING("playing"));
return nsResolveOrRejectPendingPlayPromisesRunner::Run();
}
};
class nsSourceErrorEventRunner : public nsMediaEvent
{
private:
@@ -1281,6 +1366,7 @@ NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN_INHERITED(HTMLMediaElement, nsGenericHTM
NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mVideoTrackList)
NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mMediaKeys)
NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mSelectedVideoStreamTrack)
NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mPendingPlayPromises)
NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END
NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN_INHERITED(HTMLMediaElement, nsGenericHTMLElement)
@@ -1309,6 +1395,7 @@ NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN_INHERITED(HTMLMediaElement, nsGenericHTMLE
NS_IMPL_CYCLE_COLLECTION_UNLINK(mVideoTrackList)
NS_IMPL_CYCLE_COLLECTION_UNLINK(mMediaKeys)
NS_IMPL_CYCLE_COLLECTION_UNLINK(mSelectedVideoStreamTrack)
NS_IMPL_CYCLE_COLLECTION_UNLINK(mPendingPlayPromises)
NS_IMPL_CYCLE_COLLECTION_UNLINK_END
NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION_INHERITED(HTMLMediaElement)
@@ -5698,7 +5785,14 @@ nsresult HTMLMediaElement::DispatchAsyncEvent(const nsAString& aName)
return NS_OK;
}
nsCOMPtr<nsIRunnable> event = new nsAsyncEventRunner(aName, this);
nsCOMPtr<nsIRunnable> event;
if (aName.EqualsLiteral("playing")) {
event = new nsNotifyAboutPlayingRunner(this, TakePendingPlayPromises());
} else {
event = new nsAsyncEventRunner(aName, this);
}
OwnerDoc()->Dispatch("HTMLMediaElement::DispatchAsyncEvent",
TaskCategory::Other,
event.forget());
@@ -6925,5 +7019,53 @@ HTMLMediaElement::UpdateCustomPolicyAfterPlayed()
}
}
nsTArray<RefPtr<Promise>>
HTMLMediaElement::TakePendingPlayPromises()
{
return Move(mPendingPlayPromises);
}
void
HTMLMediaElement::NotifyAboutPlaying()
{
// Stick to the DispatchAsyncEvent() call path for now because we want to
// trigger some telemetry-related codes in the DispatchAsyncEvent() method.
DispatchAsyncEvent(NS_LITERAL_STRING("playing"));
}
void
HTMLMediaElement::AsyncResolvePendingPlayPromises()
{
if (mShuttingDown) {
return;
}
nsCOMPtr<nsIRunnable> event
= new nsResolveOrRejectPendingPlayPromisesRunner(this,
TakePendingPlayPromises());
OwnerDoc()->Dispatch("HTMLMediaElement::AsyncResolvePendingPlayPromises",
TaskCategory::Other,
event.forget());
}
void
HTMLMediaElement::AsyncRejectPendingPlayPromises(nsresult aError)
{
if (mShuttingDown) {
return;
}
nsCOMPtr<nsIRunnable> event
= new nsResolveOrRejectPendingPlayPromisesRunner(this,
TakePendingPlayPromises(),
aError);
OwnerDoc()->Dispatch("HTMLMediaElement::AsyncRejectPendingPlayPromises",
TaskCategory::Other,
event.forget());
}
} // namespace dom
} // namespace mozilla