tor-browser

The Tor Browser
git clone https://git.dasho.dev/tor-browser.git
Log | Files | Refs | README | LICENSE

MediaKeys.cpp (31069B)


      1 /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
      2 /* vim: set ts=8 sts=2 et sw=2 tw=80: */
      3 /* This Source Code Form is subject to the terms of the Mozilla Public
      4 * License, v. 2.0. If a copy of the MPL was not distributed with this file,
      5 * You can obtain one at http://mozilla.org/MPL/2.0/. */
      6 
      7 #include "mozilla/dom/MediaKeys.h"
      8 
      9 #include "ChromiumCDMProxy.h"
     10 #include "GMPCrashHelper.h"
     11 #include "mozilla/EMEUtils.h"
     12 #include "mozilla/JSONStringWriteFuncs.h"
     13 #include "mozilla/dom/DOMException.h"
     14 #include "mozilla/dom/Document.h"
     15 #include "mozilla/dom/HTMLMediaElement.h"
     16 #include "mozilla/dom/MediaKeyError.h"
     17 #include "mozilla/dom/MediaKeyMessageEvent.h"
     18 #include "mozilla/dom/MediaKeySession.h"
     19 #include "mozilla/dom/MediaKeySessionBinding.h"
     20 #include "mozilla/dom/MediaKeyStatusMap.h"
     21 #include "mozilla/dom/MediaKeySystemAccess.h"
     22 #include "mozilla/dom/MediaKeysBinding.h"
     23 #include "mozilla/dom/UnionTypes.h"
     24 #include "mozilla/dom/WindowContext.h"
     25 #include "mozilla/dom/WindowGlobalChild.h"
     26 #include "nsContentTypeParser.h"
     27 #include "nsContentUtils.h"
     28 #include "nsIScriptObjectPrincipal.h"
     29 #include "nsPrintfCString.h"
     30 #include "nsServiceManagerUtils.h"
     31 
     32 #ifdef MOZ_WIDGET_ANDROID
     33 #  include "AndroidDecoderModule.h"
     34 #  include "mozilla/MediaDrmCDMProxy.h"
     35 #  include "mozilla/RemoteCDMChild.h"
     36 #  include "mozilla/RemoteMediaManagerChild.h"
     37 #  include "mozilla/StaticPrefs_media.h"
     38 #endif
     39 #ifdef MOZ_WMF_CDM
     40 #  include "mozilla/WMFCDMProxy.h"
     41 #endif
     42 
     43 namespace mozilla::dom {
     44 
     45 // We don't use NS_IMPL_CYCLE_COLLECTION_WRAPPERCACHE because we need to
     46 // disconnect our MediaKeys instances from the inner window (mparent) before
     47 // we unlink it.
     48 NS_IMPL_CYCLE_COLLECTION_WRAPPERCACHE_CLASS(MediaKeys)
     49 NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN(MediaKeys)
     50  NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mElement)
     51  NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mParent)
     52  NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mKeySessions)
     53  NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mPromises)
     54  NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mPendingSessions)
     55 NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END
     56 
     57 NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN(MediaKeys)
     58  NS_IMPL_CYCLE_COLLECTION_UNLINK(mElement)
     59  tmp->DisconnectInnerWindow();
     60  NS_IMPL_CYCLE_COLLECTION_UNLINK(mParent)
     61  NS_IMPL_CYCLE_COLLECTION_UNLINK(mKeySessions)
     62  NS_IMPL_CYCLE_COLLECTION_UNLINK(mPromises)
     63  NS_IMPL_CYCLE_COLLECTION_UNLINK(mPendingSessions)
     64  NS_IMPL_CYCLE_COLLECTION_UNLINK_PRESERVED_WRAPPER
     65  NS_IMPL_CYCLE_COLLECTION_UNLINK_WEAK_PTR
     66 NS_IMPL_CYCLE_COLLECTION_UNLINK_END
     67 
     68 NS_IMPL_CYCLE_COLLECTING_ADDREF(MediaKeys)
     69 NS_IMPL_CYCLE_COLLECTING_RELEASE(MediaKeys)
     70 NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(MediaKeys)
     71  NS_WRAPPERCACHE_INTERFACE_MAP_ENTRY
     72  NS_INTERFACE_MAP_ENTRY(nsISupports)
     73  NS_INTERFACE_MAP_ENTRY(nsIObserver)
     74 NS_INTERFACE_MAP_END
     75 
     76 MediaKeys::MediaKeys(nsPIDOMWindowInner* aParent, const nsAString& aKeySystem,
     77                     const MediaKeySystemConfiguration& aConfig)
     78    : mParent(aParent),
     79      mKeySystem(aKeySystem),
     80      mCreatePromiseId(0),
     81      mConfig(aConfig) {
     82  EME_LOG("MediaKeys[%p] constructed keySystem=%s", this,
     83          NS_ConvertUTF16toUTF8(mKeySystem).get());
     84 }
     85 
     86 MediaKeys::~MediaKeys() {
     87  MOZ_ASSERT(NS_IsMainThread());
     88 
     89  DisconnectInnerWindow();
     90  Shutdown();
     91  EME_LOG("MediaKeys[%p] destroyed", this);
     92 }
     93 
     94 NS_IMETHODIMP MediaKeys::Observe(nsISupports* aSubject, const char* aTopic,
     95                                 const char16_t* aData) {
     96  MOZ_ASSERT(NS_IsMainThread());
     97  MOZ_ASSERT(!strcmp(aTopic, kMediaKeysResponseTopic),
     98             "Should only listen for responses to MediaKey requests");
     99  EME_LOG("MediaKeys[%p] observing message with aTopic=%s aData=%s", this,
    100          aTopic, NS_ConvertUTF16toUTF8(aData).get());
    101  if (!strcmp(aTopic, kMediaKeysResponseTopic)) {
    102    if (!mProxy) {
    103      // This may happen if we're notified during shutdown or startup. If this
    104      // is happening outside of those scenarios there's a bug.
    105      EME_LOG(
    106          "MediaKeys[%p] can't notify CDM of observed message as mProxy is "
    107          "unset",
    108          this);
    109      return NS_OK;
    110    }
    111 
    112    if (u"capture-possible"_ns.Equals(aData)) {
    113      mProxy->NotifyOutputProtectionStatus(
    114          CDMProxy::OutputProtectionCheckStatus::CheckSuccessful,
    115          CDMProxy::OutputProtectionCaptureStatus::CapturePossilbe);
    116    } else if (u"capture-not-possible"_ns.Equals(aData)) {
    117      mProxy->NotifyOutputProtectionStatus(
    118          CDMProxy::OutputProtectionCheckStatus::CheckSuccessful,
    119          CDMProxy::OutputProtectionCaptureStatus::CaptureNotPossible);
    120    } else {
    121      MOZ_ASSERT_UNREACHABLE("No code paths should lead to the failure case");
    122      // This should be unreachable, but gracefully handle in case.
    123      mProxy->NotifyOutputProtectionStatus(
    124          CDMProxy::OutputProtectionCheckStatus::CheckFailed,
    125          CDMProxy::OutputProtectionCaptureStatus::Unused);
    126    }
    127  }
    128  return NS_OK;
    129 }
    130 
    131 void MediaKeys::ConnectInnerWindow() {
    132  MOZ_ASSERT(NS_IsMainThread());
    133 
    134  nsCOMPtr<nsPIDOMWindowInner> innerWindowParent = GetParentObject();
    135  MOZ_ASSERT(innerWindowParent,
    136             "We should only be connecting when we have an inner window!");
    137  innerWindowParent->AddMediaKeysInstance(this);
    138 }
    139 
    140 void MediaKeys::DisconnectInnerWindow() {
    141  MOZ_ASSERT(NS_IsMainThread());
    142 
    143  if (!GetParentObject()) {
    144    // We don't have a parent. We've been cycle collected, or the window
    145    // already notified us of its destruction and we cleared the ref.
    146    return;
    147  }
    148 
    149  GetParentObject()->RemoveMediaKeysInstance(this);
    150 }
    151 
    152 void MediaKeys::OnInnerWindowDestroy() {
    153  MOZ_ASSERT(NS_IsMainThread());
    154 
    155  EME_LOG("MediaKeys[%p] OnInnerWindowDestroy()", this);
    156 
    157  // The InnerWindow should clear its reference to this object after this call,
    158  // so we don't need to explicitly call DisconnectInnerWindow before nulling.
    159  mParent = nullptr;
    160 
    161  // Don't call shutdown directly because (at time of writing) mProxy can
    162  // spin the event loop when it's shutdown. This can change the world state
    163  // in the middle of window destruction, which we do not want.
    164  GetMainThreadSerialEventTarget()->Dispatch(
    165      NewRunnableMethod("MediaKeys::Shutdown", this, &MediaKeys::Shutdown));
    166 }
    167 
    168 void MediaKeys::Terminated() {
    169  EME_LOG("MediaKeys[%p] CDM crashed unexpectedly", this);
    170 
    171  KeySessionHashMap keySessions;
    172  // Remove entries during iteration will screw it. Make a copy first.
    173  for (const RefPtr<MediaKeySession>& session : mKeySessions.Values()) {
    174    // XXX Could the RefPtr still be moved here?
    175    keySessions.InsertOrUpdate(session->GetSessionId(), RefPtr{session});
    176  }
    177  for (const RefPtr<MediaKeySession>& session : keySessions.Values()) {
    178    session->OnClosed(MediaKeySessionClosedReason::Internal_error);
    179  }
    180  keySessions.Clear();
    181  MOZ_ASSERT(mKeySessions.Count() == 0);
    182 
    183  // Notify the element about that CDM has terminated.
    184  if (mElement) {
    185    mElement->DecodeError(NS_ERROR_DOM_MEDIA_CDM_ERR);
    186  }
    187 
    188  Shutdown();
    189 }
    190 
    191 void MediaKeys::Shutdown() {
    192  // Hold a self reference to keep us alive after we clear the self reference
    193  // for each promise. This ensures we stay alive until we're done shutting
    194  // down.
    195  RefPtr<MediaKeys> selfReference = this;
    196 
    197  EME_LOG("MediaKeys[%p]::Shutdown()", this);
    198  if (mProxy) {
    199    RefPtr<CDMProxy> proxy = mProxy;
    200    proxy->Shutdown();
    201    mProxy = nullptr;
    202  }
    203 
    204  nsCOMPtr<nsIObserverService> observerService =
    205      mozilla::services::GetObserverService();
    206  if (observerService && mObserverAdded) {
    207    observerService->RemoveObserver(this, kMediaKeysResponseTopic);
    208  }
    209 
    210  for (const RefPtr<dom::DetailedPromise>& promise : mPromises.Values()) {
    211    promise->MaybeRejectWithInvalidStateError(
    212        "Promise still outstanding at MediaKeys shutdown");
    213    Release();
    214  }
    215  mPromises.Clear();
    216 }
    217 
    218 nsPIDOMWindowInner* MediaKeys::GetParentObject() const { return mParent; }
    219 
    220 JSObject* MediaKeys::WrapObject(JSContext* aCx,
    221                                JS::Handle<JSObject*> aGivenProto) {
    222  return MediaKeys_Binding::Wrap(aCx, this, aGivenProto);
    223 }
    224 
    225 void MediaKeys::GetKeySystem(nsString& aOutKeySystem) const {
    226  aOutKeySystem.Assign(mKeySystem);
    227 }
    228 
    229 already_AddRefed<DetailedPromise> MediaKeys::SetServerCertificate(
    230    const ArrayBufferViewOrArrayBuffer& aCert, ErrorResult& aRv) {
    231  RefPtr<DetailedPromise> promise(
    232      MakePromise(aRv, "MediaKeys.setServerCertificate"_ns));
    233  if (aRv.Failed()) {
    234    return nullptr;
    235  }
    236 
    237  if (!mProxy) {
    238    NS_WARNING("Tried to use a MediaKeys without a CDM");
    239    promise->MaybeRejectWithInvalidStateError(
    240        "Null CDM in MediaKeys.setServerCertificate()");
    241    return promise.forget();
    242  }
    243 
    244  nsTArray<uint8_t> data;
    245  CopyArrayBufferViewOrArrayBufferData(aCert, data);
    246  if (data.IsEmpty()) {
    247    promise->MaybeRejectWithTypeError(
    248        "Empty certificate passed to MediaKeys.setServerCertificate()");
    249    return promise.forget();
    250  }
    251 
    252  mProxy->SetServerCertificate(StorePromise(promise), data);
    253  return promise.forget();
    254 }
    255 
    256 already_AddRefed<DetailedPromise> MediaKeys::MakePromise(
    257    ErrorResult& aRv, const nsACString& aName) {
    258  nsCOMPtr<nsIGlobalObject> global = do_QueryInterface(GetParentObject());
    259  if (!global) {
    260    NS_WARNING("Passed non-global to MediaKeys ctor!");
    261    aRv.Throw(NS_ERROR_UNEXPECTED);
    262    return nullptr;
    263  }
    264  return DetailedPromise::Create(global, aRv, aName);
    265 }
    266 
    267 PromiseId MediaKeys::StorePromise(DetailedPromise* aPromise) {
    268  static uint32_t sEMEPromiseCount = 1;
    269  MOZ_ASSERT(aPromise);
    270  uint32_t id = sEMEPromiseCount++;
    271 
    272  EME_LOG("MediaKeys[%p]::StorePromise() id=%" PRIu32, this, id);
    273 
    274  // Keep MediaKeys alive for the lifetime of its promises. Any still-pending
    275  // promises are rejected in Shutdown().
    276  EME_LOG("MediaKeys[%p]::StorePromise() calling AddRef()", this);
    277  AddRef();
    278 
    279 #ifdef DEBUG
    280  // We should not have already stored this promise!
    281  for (const RefPtr<dom::DetailedPromise>& promise : mPromises.Values()) {
    282    MOZ_ASSERT(promise != aPromise);
    283  }
    284 #endif
    285 
    286  mPromises.InsertOrUpdate(id, RefPtr{aPromise});
    287  return id;
    288 }
    289 
    290 void MediaKeys::ConnectPendingPromiseIdWithToken(PromiseId aId,
    291                                                 uint32_t aToken) {
    292  // Should only be called from MediaKeySession::GenerateRequest.
    293  mPromiseIdToken.InsertOrUpdate(aId, aToken);
    294  EME_LOG(
    295      "MediaKeys[%p]::ConnectPendingPromiseIdWithToken() id=%u => token(%u)",
    296      this, aId, aToken);
    297 }
    298 
    299 already_AddRefed<DetailedPromise> MediaKeys::RetrievePromise(PromiseId aId) {
    300  EME_LOG("MediaKeys[%p]::RetrievePromise(aId=%" PRIu32 ")", this, aId);
    301  if (!mPromises.Contains(aId)) {
    302    EME_LOG("MediaKeys[%p]::RetrievePromise(aId=%" PRIu32
    303            ") tried to retrieve non-existent promise!",
    304            this, aId);
    305    NS_WARNING(nsPrintfCString(
    306                   "Tried to retrieve a non-existent promise id=%" PRIu32, aId)
    307                   .get());
    308    return nullptr;
    309  }
    310  RefPtr<DetailedPromise> promise;
    311  mPromises.Remove(aId, getter_AddRefs(promise));
    312  EME_LOG("MediaKeys[%p]::RetrievePromise(aId=%" PRIu32 ") calling Release()",
    313          this, aId);
    314  Release();
    315  return promise.forget();
    316 }
    317 
    318 void MediaKeys::RejectPromise(PromiseId aId, ErrorResult&& aException,
    319                              const nsCString& aReason) {
    320  uint32_t errorCodeAsInt = aException.ErrorCodeAsInt();
    321  EME_LOG("MediaKeys[%p]::RejectPromise(%" PRIu32 ", 0x%" PRIx32 ")", this, aId,
    322          errorCodeAsInt);
    323 
    324  RefPtr<DetailedPromise> promise(RetrievePromise(aId));
    325  if (!promise) {
    326    EME_LOG("MediaKeys[%p]::RejectPromise(%" PRIu32 ", 0x%" PRIx32
    327            ") couldn't retrieve promise! Bailing!",
    328            this, aId, errorCodeAsInt);
    329    return;
    330  }
    331 
    332  // This promise could be a createSession or loadSession promise,
    333  // so we might have a pending session waiting to be resolved into
    334  // the promise on success. We've been directed to reject to promise,
    335  // so we can throw away the corresponding session object.
    336  uint32_t token = 0;
    337  if (mPromiseIdToken.Get(aId, &token)) {
    338    MOZ_ASSERT(mPendingSessions.Contains(token));
    339    mPendingSessions.Remove(token);
    340    mPromiseIdToken.Remove(aId);
    341  }
    342 
    343  MOZ_ASSERT(aException.Failed());
    344  promise->MaybeReject(std::move(aException), aReason);
    345 
    346  if (mCreatePromiseId == aId) {
    347    // Note: This will probably destroy the MediaKeys object!
    348    EME_LOG("MediaKeys[%p]::RejectPromise(%" PRIu32 ", 0x%" PRIx32
    349            ") calling Release()",
    350            this, aId, errorCodeAsInt);
    351    Release();
    352  }
    353 }
    354 
    355 void MediaKeys::OnSessionIdReady(MediaKeySession* aSession) {
    356  if (!aSession) {
    357    NS_WARNING("Invalid MediaKeySession passed to OnSessionIdReady()");
    358    return;
    359  }
    360  if (mKeySessions.Contains(aSession->GetSessionId())) {
    361    NS_WARNING("MediaKeySession's made ready multiple times!");
    362    return;
    363  }
    364  if (mPendingSessions.Contains(aSession->Token())) {
    365    NS_WARNING(
    366        "MediaKeySession made ready when it wasn't waiting to be ready!");
    367    return;
    368  }
    369  if (aSession->GetSessionId().IsEmpty()) {
    370    NS_WARNING(
    371        "MediaKeySession with invalid sessionId passed to OnSessionIdReady()");
    372    return;
    373  }
    374  mKeySessions.InsertOrUpdate(aSession->GetSessionId(), RefPtr{aSession});
    375 }
    376 
    377 void MediaKeys::ResolvePromise(PromiseId aId) {
    378  EME_LOG("MediaKeys[%p]::ResolvePromise(%" PRIu32 ")", this, aId);
    379 
    380  RefPtr<DetailedPromise> promise(RetrievePromise(aId));
    381  MOZ_ASSERT(!mPromises.Contains(aId));
    382  if (!promise) {
    383    return;
    384  }
    385 
    386  uint32_t token = 0;
    387  if (!mPromiseIdToken.Get(aId, &token)) {
    388    promise->MaybeResolveWithUndefined();
    389    return;
    390  } else if (!mPendingSessions.Contains(token)) {
    391    // Pending session for CreateSession() should be removed when sessionId
    392    // is ready.
    393    promise->MaybeResolveWithUndefined();
    394    mPromiseIdToken.Remove(aId);
    395    return;
    396  }
    397  mPromiseIdToken.Remove(aId);
    398 
    399  // We should only resolve LoadSession calls via this path,
    400  // not CreateSession() promises.
    401  RefPtr<MediaKeySession> session;
    402  mPendingSessions.Remove(token, getter_AddRefs(session));
    403  if (!session || session->GetSessionId().IsEmpty()) {
    404    NS_WARNING("Received activation for non-existent session!");
    405    promise->MaybeRejectWithInvalidAccessError(
    406        "CDM LoadSession() returned a different session ID than requested");
    407    return;
    408  }
    409  mKeySessions.InsertOrUpdate(session->GetSessionId(), RefPtr{session});
    410  promise->MaybeResolve(session);
    411 }
    412 
    413 class MediaKeysGMPCrashHelper : public GMPCrashHelper {
    414 public:
    415  explicit MediaKeysGMPCrashHelper(MediaKeys* aMediaKeys)
    416      : mMediaKeys(aMediaKeys) {
    417    MOZ_ASSERT(NS_IsMainThread());  // WeakPtr isn't thread safe.
    418  }
    419  already_AddRefed<nsPIDOMWindowInner> GetPluginCrashedEventTarget() override {
    420    MOZ_ASSERT(NS_IsMainThread());  // WeakPtr isn't thread safe.
    421    EME_LOG("MediaKeysGMPCrashHelper::GetPluginCrashedEventTarget()");
    422    return (mMediaKeys && mMediaKeys->GetParentObject())
    423               ? do_AddRef(mMediaKeys->GetParentObject())
    424               : nullptr;
    425  }
    426 
    427 private:
    428  WeakPtr<MediaKeys> mMediaKeys;
    429 };
    430 
    431 already_AddRefed<CDMProxy> MediaKeys::CreateCDMProxy() {
    432  const bool isHardwareDecryptionSupported =
    433      IsHardwareDecryptionSupported(mConfig) ||
    434      DoesKeySystemSupportHardwareDecryption(mKeySystem);
    435  EME_LOG("MediaKeys[%p]::CreateCDMProxy(), isHardwareDecryptionSupported=%d",
    436          this, isHardwareDecryptionSupported);
    437  RefPtr<CDMProxy> proxy;
    438 #ifdef MOZ_WIDGET_ANDROID
    439  if (IsWidevineKeySystem(mKeySystem)) {
    440    if (AndroidDecoderModule::IsJavaDecoderModuleAllowed()) {
    441      proxy = new MediaDrmCDMProxy(
    442          this, mKeySystem,
    443          mConfig.mDistinctiveIdentifier == MediaKeysRequirement::Required,
    444          mConfig.mPersistentState == MediaKeysRequirement::Required);
    445    } else {
    446      proxy = RemoteMediaManagerChild::CreateCDM(
    447          RemoteMediaIn::RddProcess, this, mKeySystem,
    448          mConfig.mDistinctiveIdentifier == MediaKeysRequirement::Required,
    449          mConfig.mPersistentState == MediaKeysRequirement::Required);
    450    }
    451  } else
    452 #endif
    453 #ifdef MOZ_WMF_CDM
    454      if (IsPlayReadyKeySystemAndSupported(mKeySystem) ||
    455          IsWidevineExperimentKeySystemAndSupported(mKeySystem) ||
    456          (IsWidevineKeySystem(mKeySystem) && isHardwareDecryptionSupported) ||
    457          IsWMFClearKeySystemAndSupported(mKeySystem)) {
    458    proxy = new WMFCDMProxy(this, mKeySystem, mConfig);
    459  } else
    460 #endif
    461  {
    462    proxy = new ChromiumCDMProxy(
    463        this, mKeySystem, new MediaKeysGMPCrashHelper(this),
    464        mConfig.mDistinctiveIdentifier == MediaKeysRequirement::Required,
    465        mConfig.mPersistentState == MediaKeysRequirement::Required);
    466  }
    467  return proxy.forget();
    468 }
    469 
    470 already_AddRefed<DetailedPromise> MediaKeys::Init(ErrorResult& aRv) {
    471  EME_LOG("MediaKeys[%p]::Init()", this);
    472  RefPtr<DetailedPromise> promise(MakePromise(aRv, "MediaKeys::Init()"_ns));
    473  if (aRv.Failed()) {
    474    return nullptr;
    475  }
    476 
    477  // Determine principal (at creation time) of the MediaKeys object.
    478  nsCOMPtr<nsIScriptObjectPrincipal> sop = do_QueryInterface(GetParentObject());
    479  if (!sop) {
    480    promise->MaybeRejectWithInvalidStateError(
    481        "Couldn't get script principal in MediaKeys::Init");
    482    return promise.forget();
    483  }
    484  mPrincipal = sop->GetPrincipal();
    485 
    486  // Begin figuring out the top level principal.
    487  nsCOMPtr<nsPIDOMWindowInner> window = GetParentObject();
    488 
    489  // If we're in a top level document, getting the top level principal is easy.
    490  // However, we're not in a top level doc this becomes more complicated. If
    491  // we're not top level we need to get the top level principal, this can be
    492  // done by reading the principal of the load info, which we can get of a
    493  // document's channel.
    494  //
    495  // There is an edge case we need to watch out for here where this code can be
    496  // run in an about:blank document before it has done its async load. In this
    497  // case the document will not yet have a load info. We address this below by
    498  // walking up a level in the window context chain. See
    499  // https://bugzilla.mozilla.org/show_bug.cgi?id=1675360
    500  // for more info.
    501  Document* document = window->GetExtantDoc();
    502  if (!document) {
    503    NS_WARNING("Failed to get document when creating MediaKeys");
    504    promise->MaybeRejectWithInvalidStateError(
    505        "Couldn't get document in MediaKeys::Init");
    506    return promise.forget();
    507  }
    508 
    509  WindowGlobalChild* windowGlobalChild = window->GetWindowGlobalChild();
    510  if (!windowGlobalChild) {
    511    NS_WARNING("Failed to get window global child when creating MediaKeys");
    512    promise->MaybeRejectWithInvalidStateError(
    513        "Couldn't get window global child in MediaKeys::Init");
    514    return promise.forget();
    515  }
    516 
    517  if (windowGlobalChild->SameOriginWithTop()) {
    518    // We're in the same origin as the top window context, so our principal
    519    // is also the top principal.
    520    mTopLevelPrincipal = mPrincipal;
    521  } else {
    522    // We have a different origin than the top doc, try and find the top level
    523    // principal by looking it up via load info, which we read off a channel.
    524    nsIChannel* channel = document->GetChannel();
    525 
    526    WindowContext* windowContext = document->GetWindowContext();
    527    if (!windowContext) {
    528      NS_WARNING("Failed to get window context when creating MediaKeys");
    529      promise->MaybeRejectWithInvalidStateError(
    530          "Couldn't get window context in MediaKeys::Init");
    531      return promise.forget();
    532    }
    533    while (!channel) {
    534      // We don't have a channel, this can happen if we're in an about:blank
    535      // page that hasn't yet had its async load performed. Try and get
    536      // the channel from our parent doc. We should be able to do this because
    537      // an about:blank is considered the same origin as its parent. We do this
    538      // recursively to cover pages do silly things like nesting blank iframes
    539      // and not waiting for loads.
    540 
    541      // Move our window context up a level.
    542      windowContext = windowContext->GetParentWindowContext();
    543      if (!windowContext || !windowContext->GetExtantDoc()) {
    544        NS_WARNING(
    545            "Failed to get parent window context's document when creating "
    546            "MediaKeys");
    547        promise->MaybeRejectWithInvalidStateError(
    548            "Couldn't get parent window context's document in "
    549            "MediaKeys::Init (likely due to an nested about about:blank frame "
    550            "that hasn't loaded yet)");
    551        return promise.forget();
    552      }
    553 
    554      Document* parentDoc = windowContext->GetExtantDoc();
    555      channel = parentDoc->GetChannel();
    556    }
    557 
    558    MOZ_RELEASE_ASSERT(
    559        channel, "Should either have a channel or should have returned by now");
    560 
    561    nsCOMPtr<nsILoadInfo> loadInfo = channel->LoadInfo();
    562    MOZ_RELEASE_ASSERT(loadInfo, "Channels should always have LoadInfo");
    563    mTopLevelPrincipal = loadInfo->GetTopLevelPrincipal();
    564    if (!mTopLevelPrincipal) {
    565      NS_WARNING("Failed to get top level principal when creating MediaKeys");
    566      promise->MaybeRejectWithInvalidStateError(
    567          "Couldn't get top level principal in MediaKeys::Init");
    568      return promise.forget();
    569    }
    570  }
    571 
    572  // We should have figured out our top level principal.
    573  if (!mPrincipal || !mTopLevelPrincipal) {
    574    NS_WARNING("Failed to get principals when creating MediaKeys");
    575    promise->MaybeRejectWithInvalidStateError(
    576        "Couldn't get principal(s) in MediaKeys::Init");
    577    return promise.forget();
    578  }
    579 
    580  nsAutoCString origin;
    581  nsresult rv = mPrincipal->GetOrigin(origin);
    582  if (NS_FAILED(rv)) {
    583    promise->MaybeRejectWithInvalidStateError(
    584        "Couldn't get principal origin string in MediaKeys::Init");
    585    return promise.forget();
    586  }
    587  nsAutoCString topLevelOrigin;
    588  rv = mTopLevelPrincipal->GetOrigin(topLevelOrigin);
    589  if (NS_FAILED(rv)) {
    590    promise->MaybeRejectWithInvalidStateError(
    591        "Couldn't get top-level principal origin string in MediaKeys::Init");
    592    return promise.forget();
    593  }
    594 
    595  EME_LOG("MediaKeys[%p]::Create() (%s, %s)", this, origin.get(),
    596          topLevelOrigin.get());
    597 
    598  mProxy = CreateCDMProxy();
    599 
    600  // The CDMProxy's initialization is asynchronous. The MediaKeys is
    601  // refcounted, and its instance is returned to JS by promise once
    602  // it's been initialized. No external refs exist to the MediaKeys while
    603  // we're waiting for the promise to be resolved, so we must hold a
    604  // reference to the new MediaKeys object until it's been created,
    605  // or its creation has failed. Store the id of the promise returned
    606  // here, and hold a self-reference until that promise is resolved or
    607  // rejected.
    608  MOZ_ASSERT(!mCreatePromiseId, "Should only be created once!");
    609  mCreatePromiseId = StorePromise(promise);
    610  EME_LOG("MediaKeys[%p]::Init() calling AddRef()", this);
    611  AddRef();
    612  mProxy->Init(mCreatePromiseId, NS_ConvertUTF8toUTF16(origin),
    613               NS_ConvertUTF8toUTF16(topLevelOrigin),
    614               KeySystemToProxyName(mKeySystem));
    615 
    616  ConnectInnerWindow();
    617 
    618  return promise.forget();
    619 }
    620 
    621 void MediaKeys::OnCDMCreated(PromiseId aId, const uint32_t aPluginId) {
    622  EME_LOG("MediaKeys[%p]::OnCDMCreated(aId=%" PRIu32 ", aPluginId=%" PRIu32 ")",
    623          this, aId, aPluginId);
    624  RefPtr<DetailedPromise> promise(RetrievePromise(aId));
    625  if (!promise) {
    626    return;
    627  }
    628  RefPtr<MediaKeys> keys(this);
    629 
    630  promise->MaybeResolve(keys);
    631  if (mCreatePromiseId == aId) {
    632    EME_LOG("MediaKeys[%p]::OnCDMCreated(aId=%" PRIu32 ", aPluginId=%" PRIu32
    633            ") calling Release()",
    634            this, aId, aPluginId);
    635    Release();
    636  }
    637 
    638  MediaKeySystemAccess::NotifyObservers(mParent, mKeySystem,
    639                                        MediaKeySystemStatus::Cdm_created);
    640 }
    641 
    642 static bool IsSessionTypeSupported(const MediaKeySessionType aSessionType,
    643                                   const MediaKeySystemConfiguration& aConfig) {
    644  if (aSessionType == MediaKeySessionType::Temporary) {
    645    // Temporary is always supported.
    646    return true;
    647  }
    648  if (!aConfig.mSessionTypes.WasPassed()) {
    649    // No other session types supported.
    650    return false;
    651  }
    652  return aConfig.mSessionTypes.Value().Contains(ToString(aSessionType));
    653 }
    654 
    655 already_AddRefed<MediaKeySession> MediaKeys::CreateSession(
    656    MediaKeySessionType aSessionType, ErrorResult& aRv) {
    657  EME_LOG("MediaKeys[%p]::CreateSession(aSessionType=%" PRIu8 ")", this,
    658          static_cast<uint8_t>(aSessionType));
    659  if (!IsSessionTypeSupported(aSessionType, mConfig)) {
    660    EME_LOG("MediaKeys[%p]::CreateSession() failed, unsupported session type",
    661            this);
    662    aRv.Throw(NS_ERROR_DOM_NOT_SUPPORTED_ERR);
    663    return nullptr;
    664  }
    665 
    666  if (!mProxy) {
    667    NS_WARNING("Tried to use a MediaKeys which lost its CDM");
    668    aRv.Throw(NS_ERROR_DOM_INVALID_STATE_ERR);
    669    return nullptr;
    670  }
    671 
    672  EME_LOG("MediaKeys[%p] Creating session", this);
    673 
    674  const bool isHardwareDecryption =
    675      IsHardwareDecryptionSupported(mConfig) ||
    676      DoesKeySystemSupportHardwareDecryption(mKeySystem);
    677  RefPtr<MediaKeySession> session =
    678      new MediaKeySession(GetParentObject(), this, mKeySystem, aSessionType,
    679                          isHardwareDecryption, aRv);
    680 
    681  if (aRv.Failed()) {
    682    return nullptr;
    683  }
    684  DDLINKCHILD("session", session.get());
    685 
    686  // Add session to the set of sessions awaiting their sessionId being ready.
    687  EME_LOG("MediaKeys[%p]::CreateSession(aSessionType=%" PRIu8
    688          ") putting session with token=%" PRIu32 " into mPendingSessions",
    689          this, static_cast<uint8_t>(aSessionType), session->Token());
    690  mPendingSessions.InsertOrUpdate(session->Token(), RefPtr{session});
    691 
    692  return session.forget();
    693 }
    694 
    695 void MediaKeys::OnSessionLoaded(PromiseId aId, bool aSuccess) {
    696  EME_LOG("MediaKeys[%p]::OnSessionLoaded() resolve promise id=%" PRIu32, this,
    697          aId);
    698 
    699  ResolvePromiseWithResult(aId, aSuccess);
    700 }
    701 
    702 void MediaKeys::OnSessionClosed(MediaKeySession* aSession) {
    703  nsAutoString id;
    704  aSession->GetSessionId(id);
    705  mKeySessions.Remove(id);
    706 }
    707 
    708 already_AddRefed<MediaKeySession> MediaKeys::GetSession(
    709    const nsAString& aSessionId) {
    710  RefPtr<MediaKeySession> session;
    711  mKeySessions.Get(aSessionId, getter_AddRefs(session));
    712  return session.forget();
    713 }
    714 
    715 already_AddRefed<MediaKeySession> MediaKeys::GetPendingSession(
    716    uint32_t aToken) {
    717  EME_LOG("MediaKeys[%p]::GetPendingSession(aToken=%" PRIu32 ")", this, aToken);
    718  RefPtr<MediaKeySession> session;
    719  mPendingSessions.Get(aToken, getter_AddRefs(session));
    720  mPendingSessions.Remove(aToken);
    721  return session.forget();
    722 }
    723 
    724 bool MediaKeys::IsBoundToMediaElement() const {
    725  MOZ_ASSERT(NS_IsMainThread());
    726  return mElement != nullptr;
    727 }
    728 
    729 nsresult MediaKeys::Bind(HTMLMediaElement* aElement) {
    730  MOZ_ASSERT(NS_IsMainThread());
    731  if (IsBoundToMediaElement()) {
    732    return NS_ERROR_FAILURE;
    733  }
    734 
    735  mElement = aElement;
    736 
    737  return NS_OK;
    738 }
    739 
    740 void MediaKeys::Unbind() {
    741  MOZ_ASSERT(NS_IsMainThread());
    742  mElement = nullptr;
    743 }
    744 
    745 void MediaKeys::CheckIsElementCapturePossible() {
    746  MOZ_ASSERT(NS_IsMainThread());
    747  EME_LOG("MediaKeys[%p]::IsElementCapturePossible()", this);
    748  // Note, HTMLMediaElement prevents capture of its content via Capture APIs
    749  // on the element if it has a media keys attached (see bug 1071482). So we
    750  // don't need to check those cases here (they are covered by tests).
    751 
    752  nsCOMPtr<nsIObserverService> observerService =
    753      mozilla::services::GetObserverService();
    754 
    755  if (!observerService) {
    756    // This can happen if we're in shutdown which means we may be going away
    757    // soon anyway, but respond saying capture is possible since we can't
    758    // forward the check further.
    759    if (mProxy) {
    760      mProxy->NotifyOutputProtectionStatus(
    761          CDMProxy::OutputProtectionCheckStatus::CheckFailed,
    762          CDMProxy::OutputProtectionCaptureStatus::Unused);
    763    }
    764    return;
    765  }
    766  if (!mObserverAdded) {
    767    nsresult rv =
    768        observerService->AddObserver(this, kMediaKeysResponseTopic, false);
    769    if (NS_FAILED(rv)) {
    770      if (mProxy) {
    771        mProxy->NotifyOutputProtectionStatus(
    772            CDMProxy::OutputProtectionCheckStatus::CheckFailed,
    773            CDMProxy::OutputProtectionCaptureStatus::Unused);
    774      }
    775      return;
    776    }
    777    mObserverAdded = true;
    778  }
    779 
    780  if (mCaptureCheckRequestJson.IsEmpty()) {
    781    // Lazily populate the JSON the first time we need it.
    782    JSONStringWriteFunc<nsAutoCString> json;
    783    JSONWriter jw{json};
    784    jw.Start();
    785    jw.StringProperty("status", "is-capture-possible");
    786    jw.StringProperty("keySystem", NS_ConvertUTF16toUTF8(mKeySystem));
    787    jw.End();
    788    mCaptureCheckRequestJson = NS_ConvertUTF8toUTF16(json.StringCRef());
    789  }
    790 
    791  MOZ_DIAGNOSTIC_ASSERT(!mCaptureCheckRequestJson.IsEmpty());
    792  observerService->NotifyObservers(mParent.get(), kMediaKeysRequestTopic,
    793                                   mCaptureCheckRequestJson.get());
    794 }
    795 
    796 void MediaKeys::GetSessionsInfo(nsString& sessionsInfo) {
    797  for (const auto& keySession : mKeySessions.Values()) {
    798    nsString sessionID;
    799    keySession->GetSessionId(sessionID);
    800    sessionsInfo.AppendLiteral("(sid=");
    801    sessionsInfo.Append(sessionID);
    802    MediaKeyStatusMap* keyStatusMap = keySession->KeyStatuses();
    803    for (uint32_t i = 0; i < keyStatusMap->GetIterableLength(); i++) {
    804      nsString keyID = keyStatusMap->GetKeyIDAsHexString(i);
    805      sessionsInfo.AppendLiteral("(kid=");
    806      sessionsInfo.Append(keyID);
    807      sessionsInfo.AppendLiteral(" status=");
    808      sessionsInfo.AppendASCII(GetEnumString(keyStatusMap->GetValueAtIndex(i)));
    809      sessionsInfo.AppendLiteral(")");
    810    }
    811    sessionsInfo.AppendLiteral(")");
    812  }
    813 }
    814 
    815 // https://w3c.github.io/encrypted-media/#dom-mediakeys-getstatusforpolicy
    816 already_AddRefed<Promise> MediaKeys::GetStatusForPolicy(
    817    const MediaKeysPolicy& aPolicy, ErrorResult& aRv) {
    818  RefPtr<DetailedPromise> promise(
    819      MakePromise(aRv, "MediaKeys::GetStatusForPolicy()"_ns));
    820  if (aRv.Failed()) {
    821    return nullptr;
    822  }
    823 
    824  // 1. If policy has no present members, return a promise rejected with a newly
    825  // created TypeError.
    826  if (!aPolicy.mMinHdcpVersion.WasPassed()) {
    827    promise->MaybeRejectWithTypeError("No minHdcpVersion in MediaKeysPolicy");
    828    return promise.forget();
    829  }
    830 
    831  if (!mProxy) {
    832    NS_WARNING("Tried to use a MediaKeys without a CDM");
    833    promise->MaybeRejectWithInvalidStateError(
    834        "Null CDM in MediaKeys.GetStatusForPolicy()");
    835    return promise.forget();
    836  }
    837 
    838  EME_LOG("GetStatusForPolicy minHdcpVersion = %s.",
    839          GetEnumString(aPolicy.mMinHdcpVersion.Value()).get());
    840  mProxy->GetStatusForPolicy(StorePromise(promise),
    841                             aPolicy.mMinHdcpVersion.Value());
    842  return promise.forget();
    843 }
    844 
    845 void MediaKeys::ResolvePromiseWithKeyStatus(PromiseId aId,
    846                                            MediaKeyStatus aMediaKeyStatus) {
    847  RefPtr<DetailedPromise> promise(RetrievePromise(aId));
    848  if (!promise) {
    849    return;
    850  }
    851  RefPtr<MediaKeys> keys(this);
    852  EME_LOG(
    853      "MediaKeys[%p]::ResolvePromiseWithKeyStatus() resolve promise id=%" PRIu32
    854      ", keystatus=%" PRIu8,
    855      this, aId, static_cast<uint8_t>(aMediaKeyStatus));
    856  promise->MaybeResolve(aMediaKeyStatus);
    857 }
    858 
    859 nsCString MediaKeys::GetMediaKeySystemConfigurationString() const {
    860  return MediaKeySystemAccess::ToCString(mConfig);
    861 }
    862 
    863 }  // namespace mozilla::dom