diff --git a/dom/html/HTMLMediaElement.cpp b/dom/html/HTMLMediaElement.cpp index f6b33c741e67..ad12f7ce7e1b 100644 --- a/dom/html/HTMLMediaElement.cpp +++ b/dom/html/HTMLMediaElement.cpp @@ -1485,6 +1485,7 @@ NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN_INHERITED(HTMLMediaElement, nsGenericHTM 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) @@ -1514,6 +1515,7 @@ NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN_INHERITED(HTMLMediaElement, nsGenericHTMLE 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) @@ -3985,6 +3987,7 @@ HTMLMediaElement::HTMLMediaElement(already_AddRefed& aNo mPlaybackRate(1.0), mPreservesPitch(true), mPlayed(new TimeRanges(ToSupports(OwnerDoc()))), + mAttachingMediaKey(false), mCurrentPlayRangeStart(-1.0), mLoadedDataFired(false), mAutoplaying(true), @@ -7015,6 +7018,142 @@ HTMLMediaElement::ContainsRestrictedContent() return GetMediaKeys() != nullptr; } +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) +{ + 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")); + 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) +{ + LOG(LogLevel::Debug, ("%s", __func__)); + // 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( + NS_ERROR_DOM_QUOTA_EXCEEDED_ERR, + NS_LITERAL_CSTRING( + "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 true; +} + +void +HTMLMediaElement::MakeAssociationWithCDMResolved(DetailedPromise* aPromise) +{ + LOG(LogLevel::Debug, ("%s", __func__)); + + // 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(); +} + +bool +HTMLMediaElement::TryMakeAssociationWithCDM(CDMProxy* aProxy) +{ + 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); + } + return true; +} + +bool +HTMLMediaElement::AttachNewMediaKeys(DetailedPromise* aPromise) +{ + LOG(LogLevel::Debug, + ("%s incoming MediaKeys(%p)", __func__, mIncomingMediaKeys.get())); + + // 5.3. If mediaKeys is not null, run the following steps: + if (mIncomingMediaKeys) { + auto cdmProxy = mIncomingMediaKeys->GetCDMProxy(); + if (!cdmProxy) { + aPromise->MaybeReject( + NS_ERROR_DOM_INVALID_STATE_ERR, + NS_LITERAL_CSTRING( + "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")); + return false; + } + return TryMakeAssociationWithCDM(cdmProxy); + } + return true; +} + +void +HTMLMediaElement::ResetSetMediaKeysTempVariables() +{ + mAttachingMediaKey = false; + mIncomingMediaKeys = nullptr; +} + already_AddRefed HTMLMediaElement::SetMediaKeys(mozilla::dom::MediaKeys* aMediaKeys, ErrorResult& aRv) @@ -7050,85 +7189,34 @@ HTMLMediaElement::SetMediaKeys(mozilla::dom::MediaKeys* aMediaKeys, // 2. If this object's attaching media keys value is true, return a // promise rejected with a new DOMException whose name is InvalidStateError. - // 3. Let this object's attaching media keys value be true. - // 4. Let promise be a new promise. - // 5. Run the following steps in parallel: - - // 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 (aMediaKeys && aMediaKeys->IsBoundToMediaElement()) { - promise->MaybeReject(NS_ERROR_DOM_QUOTA_EXCEEDED_ERR, - NS_LITERAL_CSTRING("MediaKeys object is already bound to another HTMLMediaElement")); + if (mAttachingMediaKey) { + promise->MaybeReject( + NS_ERROR_DOM_INVALID_STATE_ERR, + NS_LITERAL_CSTRING("A MediaKeys object is in attaching operation.")); return promise.forget(); } - // 5.2 If the mediaKeys attribute is not null, run the following steps: - if (mMediaKeys) { - // 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. + // 3. Let this object's attaching media keys value be true. + mAttachingMediaKey = true; + mIncomingMediaKeys = aMediaKeys; - // 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. - promise->MaybeReject(NS_ERROR_DOM_INVALID_STATE_ERR, - NS_LITERAL_CSTRING("Can't change MediaKeys on HTMLMediaElement after load has started")); - return promise.forget(); - } + // 4. Let promise be a new promise. + // 5. Run the following steps in parallel: - // 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; - - // 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. + // 5.1 & 5.2 + if (!DetachExistingMediaKeys(promise)) { + ResetSetMediaKeysTempVariables(); + return promise.forget(); } - // 5.3. If mediaKeys is not null, run the following steps: - if (aMediaKeys) { - if (!aMediaKeys->GetCDMProxy()) { - promise->MaybeReject(NS_ERROR_DOM_INVALID_STATE_ERR, - NS_LITERAL_CSTRING("CDM crashed before binding MediaKeys object to HTMLMediaElement")); - return promise.forget(); - } - - // 5.3.1 Associate the CDM instance represented by mediaKeys with the - // media element for decrypting media data. - if (NS_FAILED(aMediaKeys->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. - // 5.3.2.3 Reject promise with a new DOMException whose name is - // the appropriate error name. - promise->MaybeReject(NS_ERROR_DOM_INVALID_STATE_ERR, - NS_LITERAL_CSTRING("Failed to bind MediaKeys object to HTMLMediaElement")); - return promise.forget(); - } - // 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(aMediaKeys->GetCDMProxy()); - } + // 5.3 + if (!AttachNewMediaKeys(promise)) { + ResetSetMediaKeysTempVariables(); + return promise.forget(); } - // 5.4 Set the mediaKeys attribute to mediaKeys. - mMediaKeys = aMediaKeys; - - // 5.5 Let this object's attaching media keys value be false. - - // 5.6 Resolve promise. - promise->MaybeResolveWithUndefined(); + // 5.4, 5.5, 5.6 + MakeAssociationWithCDMResolved(promise); // 6. Return promise. return promise.forget(); diff --git a/dom/html/HTMLMediaElement.h b/dom/html/HTMLMediaElement.h index f0eee1e59b2a..37bd13118ce5 100644 --- a/dom/html/HTMLMediaElement.h +++ b/dom/html/HTMLMediaElement.h @@ -1332,6 +1332,14 @@ protected: const nsAttrValueOrString& aValue, bool aNotify) override; + bool DetachExistingMediaKeys(DetailedPromise* aPromise); + bool TryRemoveMediaKeysAssociation(DetailedPromise* aPromise); + void RemoveMediaKeys(); + bool AttachNewMediaKeys(DetailedPromise* aPromise); + bool TryMakeAssociationWithCDM(CDMProxy* aProxy); + void MakeAssociationWithCDMResolved(DetailedPromise* aPromise); + void ResetSetMediaKeysTempVariables(); + // The current decoder. Load() has been called on this decoder. // At most one of mDecoder and mSrcStream can be non-null. RefPtr mDecoder; @@ -1534,6 +1542,9 @@ protected: // Encrypted Media Extension media keys. RefPtr mMediaKeys; + RefPtr mIncomingMediaKeys; + // Used to indicate if the MediaKeys attaching operation is on-going or not. + bool mAttachingMediaKey; // Stores the time at the start of the current 'played' range. double mCurrentPlayRangeStart;