Bug 1395922 - [P3] Make HTMLMediaElement::SetMediaKeys asynchronously. draft
authorKilik Kuo <kikuo@mozilla.com>
Fri, 03 Nov 2017 20:14:17 +0800
changeset 694022 d91340274257e8ce662468a065a92eed75669636
parent 694021 4724534e8454eaba9152959c8b31502e21cee326
child 694023 6b8e97af4b54eb4e2b3c3fbc1835276f9a054e1e
push id88026
push userkikuo@mozilla.com
push dateTue, 07 Nov 2017 09:43:50 +0000
bugs1395922
milestone58.0a1
Bug 1395922 - [P3] Make HTMLMediaElement::SetMediaKeys asynchronously. 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
@@ -1485,16 +1485,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();
@@ -1515,16 +1516,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)
@@ -1720,16 +1722,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;
 }
 
@@ -7015,137 +7018,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
@@ -7181,48 +7222,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();