Bug 1395922 - [P3] Make HTMLMediaElement::SetMediaKeys asynchronously. r=cpearce
authorKilik Kuo <kikuo@mozilla.com>
Fri, 03 Nov 2017 20:14:17 +0800
changeset 441473 d013a75c11c802e11feade059f611167f46c7273
parent 441472 2849269a64be100c949b4917fab599979d224f2e
child 441474 cf1f507a4dcc47d64f775eca197b670f3dd24c3a
push id8130
push userryanvm@gmail.com
push dateThu, 09 Nov 2017 00:28:20 +0000
treeherdermozilla-beta@a49a6fa54363 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewerscpearce
bugs1395922
milestone58.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Bug 1395922 - [P3] Make HTMLMediaElement::SetMediaKeys asynchronously. r=cpearce MozReview-Commit-ID: 5M8CTHMsmIh
dom/html/HTMLMediaElement.cpp
dom/html/HTMLMediaElement.h
dom/media/MediaDecoder.cpp
dom/media/MediaDecoder.h
--- a/dom/html/HTMLMediaElement.cpp
+++ b/dom/html/HTMLMediaElement.cpp
@@ -1484,16 +1484,17 @@ NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN_
   NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mTextTrackManager)
   NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mAudioTrackList)
   NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mVideoTrackList)
   NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mMediaKeys)
   NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mIncomingMediaKeys)
   NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mSelectedVideoStreamTrack)
   NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mPendingPlayPromises)
   NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mSeekDOMPromise)
+  NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mSetMediaKeysDOMPromise)
 NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END
 
 NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN_INHERITED(HTMLMediaElement, nsGenericHTMLElement)
   tmp->RemoveMutationObserver(tmp);
   if (tmp->mSrcStream) {
     // Need to EndMediaStreamPlayback to clear mSrcStream and make sure everything
     // gets unhooked correctly.
     tmp->EndSrcMediaStreamPlayback();
@@ -1514,16 +1515,17 @@ NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN_IN
   NS_IMPL_CYCLE_COLLECTION_UNLINK(mTextTrackManager)
   NS_IMPL_CYCLE_COLLECTION_UNLINK(mAudioTrackList)
   NS_IMPL_CYCLE_COLLECTION_UNLINK(mVideoTrackList)
   NS_IMPL_CYCLE_COLLECTION_UNLINK(mMediaKeys)
   NS_IMPL_CYCLE_COLLECTION_UNLINK(mIncomingMediaKeys)
   NS_IMPL_CYCLE_COLLECTION_UNLINK(mSelectedVideoStreamTrack)
   NS_IMPL_CYCLE_COLLECTION_UNLINK(mPendingPlayPromises)
   NS_IMPL_CYCLE_COLLECTION_UNLINK(mSeekDOMPromise)
+  NS_IMPL_CYCLE_COLLECTION_UNLINK(mSetMediaKeysDOMPromise)
 NS_IMPL_CYCLE_COLLECTION_UNLINK_END
 
 NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(HTMLMediaElement)
   NS_INTERFACE_MAP_ENTRY(nsIDOMHTMLMediaElement)
 NS_INTERFACE_MAP_END_INHERITING(nsGenericHTMLElement)
 
 // nsIDOMHTMLMediaElement
 NS_IMPL_URI_ATTR(HTMLMediaElement, Src, src)
@@ -1719,16 +1721,17 @@ HTMLMediaElement::OnChannelRedirect(nsIC
   MOZ_ASSERT(mChannelLoader);
   return mChannelLoader->Redirect(aChannel, aNewChannel, aFlags);
 }
 
 void HTMLMediaElement::ShutdownDecoder()
 {
   RemoveMediaElementFromURITable();
   NS_ASSERTION(mDecoder, "Must have decoder to shut down");
+  mSetCDMRequest.DisconnectIfExists();
   mWaitingForKeyListener.DisconnectIfExists();
   if (mMediaSource) {
     mMediaSource->CompletePendingTransactions();
   }
   mDecoder->Shutdown();
   mDecoder = nullptr;
 }
 
@@ -7014,137 +7017,175 @@ HTMLMediaElement::GetMediaKeys() const
 
 bool
 HTMLMediaElement::ContainsRestrictedContent()
 {
   return GetMediaKeys() != nullptr;
 }
 
 void
+HTMLMediaElement::SetCDMProxyFailure(const MediaResult& aResult)
+{
+  LOG(LogLevel::Debug, ("%s", __func__));
+  MOZ_ASSERT(mSetMediaKeysDOMPromise);
+
+  ResetSetMediaKeysTempVariables();
+
+  mSetMediaKeysDOMPromise->MaybeReject(aResult.Code(), aResult.Message());
+}
+
+void
 HTMLMediaElement::RemoveMediaKeys()
 {
   LOG(LogLevel::Debug, ("%s", __func__));
   // 5.2.3 Stop using the CDM instance represented by the mediaKeys attribute
   // to decrypt media data and remove the association with the media element.
   mMediaKeys->Unbind();
   mMediaKeys = nullptr;
 }
 
 bool
-HTMLMediaElement::TryRemoveMediaKeysAssociation(DetailedPromise* aPromise)
+HTMLMediaElement::TryRemoveMediaKeysAssociation()
 {
   MOZ_ASSERT(mMediaKeys);
   LOG(LogLevel::Debug, ("%s", __func__));
   // 5.2.1 If the user agent or CDM do not support removing the association,
   // let this object's attaching media keys value be false and reject promise
   // with a new DOMException whose name is NotSupportedError.
   // 5.2.2 If the association cannot currently be removed, let this object's
   // attaching media keys value be false and reject promise with a new
   // DOMException whose name is InvalidStateError.
   if (mDecoder) {
-    // We don't support swapping out the MediaKeys once we've started to
-    // setup the playback pipeline. Note this also means we don't need to worry
-    // about handling disassociating the MediaKeys from the MediaDecoder.
-    aPromise->MaybeReject(
-      NS_ERROR_DOM_INVALID_STATE_ERR,
-      NS_LITERAL_CSTRING(
-        "Can't change MediaKeys on HTMLMediaElement after load has started"));
+    RefPtr<HTMLMediaElement> self = this;
+    mDecoder->SetCDMProxy(nullptr)
+      ->Then(mAbstractMainThread,
+             __func__,
+             [self]() {
+               self->mSetCDMRequest.Complete();
+
+               self->RemoveMediaKeys();
+               if (self->AttachNewMediaKeys()) {
+                 // No incoming MediaKeys object or MediaDecoder is not created yet.
+                 self->MakeAssociationWithCDMResolved();
+               }
+             },
+             [self](const MediaResult& aResult) {
+               self->mSetCDMRequest.Complete();
+               // 5.2.4 If the preceding step failed, let this object's attaching media
+               // keys value be false and reject promise with a new DOMException whose
+               // name is the appropriate error name.
+               self->SetCDMProxyFailure(aResult);
+             })
+      ->Track(mSetCDMRequest);
     return false;
   }
+
   RemoveMediaKeys();
-
-  // 5.2.4 If the preceding step failed, let this object's attaching media
-  // keys value be false and reject promise with a new DOMException whose
-  // name is the appropriate error name.
   return true;
 }
 
 bool
-HTMLMediaElement::DetachExistingMediaKeys(DetailedPromise* aPromise)
+HTMLMediaElement::DetachExistingMediaKeys()
 {
   LOG(LogLevel::Debug, ("%s", __func__));
+  MOZ_ASSERT(mSetMediaKeysDOMPromise);
   // 5.1 If mediaKeys is not null, CDM instance represented by mediaKeys is
   // already in use by another media element, and the user agent is unable
   // to use it with this element, let this object's attaching media keys
   // value be false and reject promise with a new DOMException whose name
   // is QuotaExceededError.
   if (mIncomingMediaKeys && mIncomingMediaKeys->IsBoundToMediaElement()) {
-    aPromise->MaybeReject(
+    SetCDMProxyFailure(MediaResult(
       NS_ERROR_DOM_QUOTA_EXCEEDED_ERR,
-      NS_LITERAL_CSTRING(
-        "MediaKeys object is already bound to another HTMLMediaElement"));
+      "MediaKeys object is already bound to another HTMLMediaElement"));
     return false;
   }
 
   // 5.2 If the mediaKeys attribute is not null, run the following steps:
   if (mMediaKeys) {
-    return TryRemoveMediaKeysAssociation(aPromise);
+    return TryRemoveMediaKeysAssociation();
   }
   return true;
 }
 
 void
-HTMLMediaElement::MakeAssociationWithCDMResolved(DetailedPromise* aPromise)
+HTMLMediaElement::MakeAssociationWithCDMResolved()
 {
   LOG(LogLevel::Debug, ("%s", __func__));
+  MOZ_ASSERT(mSetMediaKeysDOMPromise);
 
   // 5.4 Set the mediaKeys attribute to mediaKeys.
   mMediaKeys = mIncomingMediaKeys;
   // 5.5 Let this object's attaching media keys value be false.
   ResetSetMediaKeysTempVariables();
   // 5.6 Resolve promise.
-  aPromise->MaybeResolveWithUndefined();
+  mSetMediaKeysDOMPromise->MaybeResolveWithUndefined();
+  mSetMediaKeysDOMPromise = nullptr;
 }
 
 bool
 HTMLMediaElement::TryMakeAssociationWithCDM(CDMProxy* aProxy)
 {
+  LOG(LogLevel::Debug, ("%s", __func__));
   MOZ_ASSERT(aProxy);
-  LOG(LogLevel::Debug, ("%s", __func__));
+
   // 5.3.3 Queue a task to run the "Attempt to Resume Playback If Necessary"
   // algorithm on the media element.
   // Note: Setting the CDMProxy on the MediaDecoder will unblock playback.
   if (mDecoder) {
-    mDecoder->SetCDMProxy(aProxy);
+    // CDMProxy is set asynchronously in MediaFormatReader, once it's done,
+    // HTMLMediaElement should resolve or reject the DOM promise.
+    RefPtr<HTMLMediaElement> self = this;
+    mDecoder->SetCDMProxy(aProxy)
+      ->Then(mAbstractMainThread,
+             __func__,
+             [self]() {
+               self->mSetCDMRequest.Complete();
+               self->MakeAssociationWithCDMResolved();
+             },
+             [self](const MediaResult& aResult) {
+               self->mSetCDMRequest.Complete();
+               self->SetCDMProxyFailure(aResult);
+             })
+      ->Track(mSetCDMRequest);
+    return false;
   }
   return true;
 }
 
 bool
-HTMLMediaElement::AttachNewMediaKeys(DetailedPromise* aPromise)
+HTMLMediaElement::AttachNewMediaKeys()
 {
   LOG(LogLevel::Debug,
       ("%s incoming MediaKeys(%p)", __func__, mIncomingMediaKeys.get()));
+  MOZ_ASSERT(mSetMediaKeysDOMPromise);
 
   // 5.3. If mediaKeys is not null, run the following steps:
   if (mIncomingMediaKeys) {
     auto cdmProxy = mIncomingMediaKeys->GetCDMProxy();
     if (!cdmProxy) {
-      aPromise->MaybeReject(
+      SetCDMProxyFailure(MediaResult(
         NS_ERROR_DOM_INVALID_STATE_ERR,
-        NS_LITERAL_CSTRING(
-          "CDM crashed before binding MediaKeys object to HTMLMediaElement"));
+        "CDM crashed before binding MediaKeys object to HTMLMediaElement"));
       return false;
     }
 
     // 5.3.1 Associate the CDM instance represented by mediaKeys with the
     // media element for decrypting media data.
     if (NS_FAILED(mIncomingMediaKeys->Bind(this))) {
       // 5.3.2 If the preceding step failed, run the following steps:
 
       // 5.3.2.1 Set the mediaKeys attribute to null.
       mMediaKeys = nullptr;
       // 5.3.2.2 Let this object's attaching media keys value be false.
-      ResetSetMediaKeysTempVariables();
       // 5.3.2.3 Reject promise with a new DOMException whose name is
       // the appropriate error name.
-      aPromise->MaybeReject(
-        NS_ERROR_DOM_INVALID_STATE_ERR,
-        NS_LITERAL_CSTRING(
-          "Failed to bind MediaKeys object to HTMLMediaElement"));
+      SetCDMProxyFailure(
+        MediaResult(NS_ERROR_DOM_INVALID_STATE_ERR,
+                    "Failed to bind MediaKeys object to HTMLMediaElement"));
       return false;
     }
     return TryMakeAssociationWithCDM(cdmProxy);
   }
   return true;
 }
 
 void
@@ -7180,48 +7221,41 @@ HTMLMediaElement::SetMediaKeys(mozilla::
 
   // 1. If mediaKeys and the mediaKeys attribute are the same object,
   // return a resolved promise.
   if (mMediaKeys == aMediaKeys) {
     promise->MaybeResolveWithUndefined();
     return promise.forget();
   }
 
-  // Note: Our attaching code is synchronous, so we can skip the following steps.
-
   // 2. If this object's attaching media keys value is true, return a
   // promise rejected with a new DOMException whose name is InvalidStateError.
   if (mAttachingMediaKey) {
     promise->MaybeReject(
       NS_ERROR_DOM_INVALID_STATE_ERR,
       NS_LITERAL_CSTRING("A MediaKeys object is in attaching operation."));
     return promise.forget();
   }
 
   // 3. Let this object's attaching media keys value be true.
   mAttachingMediaKey = true;
   mIncomingMediaKeys = aMediaKeys;
 
   // 4. Let promise be a new promise.
+  mSetMediaKeysDOMPromise = promise;
+
   // 5. Run the following steps in parallel:
 
-  // 5.1 & 5.2
-  if (!DetachExistingMediaKeys(promise)) {
-    ResetSetMediaKeysTempVariables();
-    return promise.forget();
-  }
-
-  // 5.3
-  if (!AttachNewMediaKeys(promise)) {
-    ResetSetMediaKeysTempVariables();
+  // 5.1 & 5.2 & 5.3
+  if (!DetachExistingMediaKeys() || !AttachNewMediaKeys()) {
     return promise.forget();
   }
 
   // 5.4, 5.5, 5.6
-  MakeAssociationWithCDMResolved(promise);
+  MakeAssociationWithCDMResolved();
 
   // 6. Return promise.
   return promise.forget();
 }
 
 EventHandlerNonNull*
 HTMLMediaElement::GetOnencrypted()
 {
--- a/dom/html/HTMLMediaElement.h
+++ b/dom/html/HTMLMediaElement.h
@@ -7,16 +7,17 @@
 #define mozilla_dom_HTMLMediaElement_h
 
 #include "nsAutoPtr.h"
 #include "nsIDOMHTMLMediaElement.h"
 #include "nsGenericHTMLElement.h"
 #include "MediaEventSource.h"
 #include "SeekTarget.h"
 #include "MediaDecoderOwner.h"
+#include "MediaPromiseDefs.h"
 #include "nsCycleCollectionParticipant.h"
 #include "nsIObserver.h"
 #include "mozilla/CORSMode.h"
 #include "DecoderTraits.h"
 #include "nsIAudioChannelAgent.h"
 #include "mozilla/Attributes.h"
 #include "mozilla/dom/TextTrackManager.h"
 #include "mozilla/WeakPtr.h"
@@ -1327,22 +1328,23 @@ protected:
                                 const nsAttrValue* aValue,
                                 const nsAttrValue* aOldValue,
                                 nsIPrincipal* aMaybeScriptedPrincipal,
                                 bool aNotify) override;
   virtual nsresult OnAttrSetButNotChanged(int32_t aNamespaceID, nsAtom* aName,
                                           const nsAttrValueOrString& aValue,
                                           bool aNotify) override;
 
-  bool DetachExistingMediaKeys(DetailedPromise* aPromise);
-  bool TryRemoveMediaKeysAssociation(DetailedPromise* aPromise);
+  bool DetachExistingMediaKeys();
+  bool TryRemoveMediaKeysAssociation();
   void RemoveMediaKeys();
-  bool AttachNewMediaKeys(DetailedPromise* aPromise);
+  bool AttachNewMediaKeys();
   bool TryMakeAssociationWithCDM(CDMProxy* aProxy);
-  void MakeAssociationWithCDMResolved(DetailedPromise* aPromise);
+  void MakeAssociationWithCDMResolved();
+  void SetCDMProxyFailure(const MediaResult& aResult);
   void ResetSetMediaKeysTempVariables();
 
   // The current decoder. Load() has been called on this decoder.
   // At most one of mDecoder and mSrcStream can be non-null.
   RefPtr<MediaDecoder> mDecoder;
 
   // The DocGroup-specific nsISerialEventTarget of this HTML element on the main
   // thread.
@@ -1538,18 +1540,21 @@ protected:
   nsCOMPtr<nsITimer> mProgressTimer;
 
   // Timer used to simulate video-suspend.
   nsCOMPtr<nsITimer> mVideoDecodeSuspendTimer;
 
   // Encrypted Media Extension media keys.
   RefPtr<MediaKeys> mMediaKeys;
   RefPtr<MediaKeys> mIncomingMediaKeys;
+  // The dom promise is used for HTMLMediaElement::SetMediaKeys.
+  RefPtr<DetailedPromise> mSetMediaKeysDOMPromise;
   // Used to indicate if the MediaKeys attaching operation is on-going or not.
   bool mAttachingMediaKey;
+  MozPromiseRequestHolder<SetCDMPromise> mSetCDMRequest;
 
   // Stores the time at the start of the current 'played' range.
   double mCurrentPlayRangeStart;
 
   // True if loadeddata has been fired.
   bool mLoadedDataFired;
 
   // Indicates whether current playback is a result of user action
--- a/dom/media/MediaDecoder.cpp
+++ b/dom/media/MediaDecoder.cpp
@@ -1418,28 +1418,25 @@ MediaDecoder::CanPlayThrough()
   bool val = CanPlayThroughImpl();
   if (val != mCanPlayThrough) {
     mCanPlayThrough = val;
     mDecoderStateMachine->DispatchCanPlayThrough(val);
   }
   return val;
 }
 
-void
+RefPtr<SetCDMPromise>
 MediaDecoder::SetCDMProxy(CDMProxy* aProxy)
 {
   MOZ_ASSERT(NS_IsMainThread());
-  RefPtr<CDMProxy> proxy = aProxy;
-  RefPtr<MediaFormatReader> reader = mReader;
-  nsCOMPtr<nsIRunnable> r = NS_NewRunnableFunction(
-    "MediaFormatReader::SetCDMProxy",
-    [reader, proxy]() {
-    reader->SetCDMProxy(proxy);
-    });
-  mReader->OwnerThread()->Dispatch(r.forget());
+  return InvokeAsync<RefPtr<CDMProxy>>(mReader->OwnerThread(),
+                                       mReader.get(),
+                                       __func__,
+                                       &MediaFormatReader::SetCDMProxy,
+                                       aProxy);
 }
 
 bool
 MediaDecoder::IsOpusEnabled()
 {
   return Preferences::GetBool("media.opus.enabled");
 }
 
--- a/dom/media/MediaDecoder.h
+++ b/dom/media/MediaDecoder.h
@@ -7,16 +7,17 @@
 #if !defined(MediaDecoder_h_)
 #define MediaDecoder_h_
 
 #include "DecoderDoctorDiagnostics.h"
 #include "MediaContainerType.h"
 #include "MediaDecoderOwner.h"
 #include "MediaEventSource.h"
 #include "MediaMetadataManager.h"
+#include "MediaPromiseDefs.h"
 #include "MediaResource.h"
 #include "MediaStatistics.h"
 #include "MediaStreamGraph.h"
 #include "SeekTarget.h"
 #include "TimeUnits.h"
 #include "mozilla/Atomics.h"
 #include "mozilla/CDMProxy.h"
 #include "mozilla/MozPromise.h"
@@ -350,17 +351,17 @@ private:
 
   MediaDecoderOwner* GetOwner() const;
 
   AbstractThread* AbstractMainThread() const
   {
     return mAbstractMainThread;
   }
 
-  void SetCDMProxy(CDMProxy* aProxy);
+  RefPtr<SetCDMPromise> SetCDMProxy(CDMProxy* aProxy);
 
   void EnsureTelemetryReported();
 
   static bool IsOggEnabled();
   static bool IsOpusEnabled();
   static bool IsWaveEnabled();
   static bool IsWebMEnabled();