tor-browser

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

MediaKeySystemAccessManager.cpp (29417B)


      1 /* This Source Code Form is subject to the terms of the Mozilla Public
      2 * License, v. 2.0. If a copy of the MPL was not distributed with this
      3 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
      4 
      5 #include "MediaKeySystemAccessManager.h"
      6 
      7 #include "DecoderDoctorDiagnostics.h"
      8 #include "MediaKeySystemAccessPermissionRequest.h"
      9 #include "VideoUtils.h"
     10 #include "mozilla/DetailedPromise.h"
     11 #include "mozilla/EMEUtils.h"
     12 #include "mozilla/Preferences.h"
     13 #include "mozilla/Services.h"
     14 #include "mozilla/StaticPrefs_media.h"
     15 #include "mozilla/dom/BrowserChild.h"
     16 #include "mozilla/dom/Document.h"
     17 #include "mozilla/dom/KeySystemNames.h"
     18 #include "nsComponentManagerUtils.h"
     19 #include "nsContentUtils.h"
     20 #include "nsIObserverService.h"
     21 #include "nsIScriptError.h"
     22 #include "nsPrintfCString.h"
     23 #include "nsServiceManagerUtils.h"
     24 #include "nsTHashMap.h"
     25 
     26 namespace mozilla::dom {
     27 
     28 MediaKeySystemAccessManager::PendingRequest::PendingRequest(
     29    DetailedPromise* aPromise, const nsAString& aKeySystem,
     30    const Sequence<MediaKeySystemConfiguration>& aConfigs)
     31    : MediaKeySystemAccessRequest(aKeySystem, aConfigs), mPromise(aPromise) {
     32  MOZ_COUNT_CTOR(MediaKeySystemAccessManager::PendingRequest);
     33 }
     34 
     35 MediaKeySystemAccessManager::PendingRequest::~PendingRequest() {
     36  MOZ_COUNT_DTOR(MediaKeySystemAccessManager::PendingRequest);
     37 }
     38 
     39 void MediaKeySystemAccessManager::PendingRequest::CancelTimer() {
     40  if (mTimer) {
     41    mTimer->Cancel();
     42    mTimer = nullptr;
     43  }
     44 }
     45 
     46 void MediaKeySystemAccessManager::PendingRequest::
     47    RejectPromiseWithInvalidAccessError(const nsACString& aReason) {
     48  if (mPromise) {
     49    mPromise->MaybeRejectWithInvalidAccessError(aReason);
     50  }
     51 }
     52 
     53 void MediaKeySystemAccessManager::PendingRequest::
     54    RejectPromiseWithNotSupportedError(const nsACString& aReason) {
     55  if (mPromise) {
     56    mPromise->MaybeRejectWithNotSupportedError(aReason);
     57  }
     58 }
     59 
     60 void MediaKeySystemAccessManager::PendingRequest::RejectPromiseWithTypeError(
     61    const nsACString& aReason) {
     62  if (mPromise) {
     63    mPromise->MaybeRejectWithTypeError(aReason);
     64  }
     65 }
     66 
     67 void MediaKeySystemAccessManager::PendingRequest::ResolvePromise(
     68    MediaKeySystemAccess* aAccess) {
     69  if (mPromise) {
     70    mPromise->MaybeResolve(aAccess);
     71  }
     72 }
     73 
     74 void MediaKeySystemAccessManager::PendingRequestWithMozPromise::
     75    RejectPromiseWithInvalidAccessError(const nsACString& aReason) {
     76  mAccessPromise.RejectIfExists(NS_ERROR_FAILURE, __func__);
     77 }
     78 
     79 void MediaKeySystemAccessManager::PendingRequestWithMozPromise::
     80    RejectPromiseWithNotSupportedError(const nsACString& aReason) {
     81  mAccessPromise.RejectIfExists(NS_ERROR_FAILURE, __func__);
     82 }
     83 
     84 void MediaKeySystemAccessManager::PendingRequestWithMozPromise::
     85    RejectPromiseWithTypeError(const nsACString& aReason) {
     86  mAccessPromise.RejectIfExists(NS_ERROR_FAILURE, __func__);
     87 }
     88 
     89 void MediaKeySystemAccessManager::PendingRequestWithMozPromise::ResolvePromise(
     90    MediaKeySystemAccess* aAccess) {
     91  mAccessPromise.ResolveIfExists(aAccess, __func__);
     92 }
     93 
     94 NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(MediaKeySystemAccessManager)
     95  NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports, nsIObserver)
     96  NS_INTERFACE_MAP_ENTRY(nsIObserver)
     97  NS_INTERFACE_MAP_ENTRY(nsINamed)
     98 NS_INTERFACE_MAP_END
     99 
    100 NS_IMPL_CYCLE_COLLECTING_ADDREF(MediaKeySystemAccessManager)
    101 NS_IMPL_CYCLE_COLLECTING_RELEASE(MediaKeySystemAccessManager)
    102 
    103 NS_IMPL_CYCLE_COLLECTION_CLASS(MediaKeySystemAccessManager)
    104 
    105 NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN(MediaKeySystemAccessManager)
    106  NS_IMPL_CYCLE_COLLECTION_UNLINK(mWindow)
    107  for (size_t i = 0; i < tmp->mPendingInstallRequests.Length(); i++) {
    108    tmp->mPendingInstallRequests[i]->CancelTimer();
    109    tmp->mPendingInstallRequests[i]->RejectPromiseWithInvalidAccessError(
    110        nsLiteralCString(
    111            "Promise still outstanding at MediaKeySystemAccessManager GC"));
    112    NS_IMPL_CYCLE_COLLECTION_UNLINK(mPendingInstallRequests[i]->mPromise)
    113  }
    114  tmp->mPendingInstallRequests.Clear();
    115  for (size_t i = 0; i < tmp->mPendingAppApprovalRequests.Length(); i++) {
    116    tmp->mPendingAppApprovalRequests[i]->RejectPromiseWithInvalidAccessError(
    117        nsLiteralCString(
    118            "Promise still outstanding at MediaKeySystemAccessManager GC"));
    119    NS_IMPL_CYCLE_COLLECTION_UNLINK(mPendingAppApprovalRequests[i]->mPromise)
    120  }
    121  tmp->mPendingAppApprovalRequests.Clear();
    122 NS_IMPL_CYCLE_COLLECTION_UNLINK_END
    123 
    124 NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN(MediaKeySystemAccessManager)
    125  NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mWindow)
    126  for (size_t i = 0; i < tmp->mPendingInstallRequests.Length(); i++) {
    127    NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mPendingInstallRequests[i]->mPromise)
    128  }
    129  for (size_t i = 0; i < tmp->mPendingAppApprovalRequests.Length(); i++) {
    130    NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mPendingAppApprovalRequests[i]->mPromise)
    131  }
    132 NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END
    133 
    134 #define MKSAM_LOG_DEBUG(msg, ...) \
    135  EME_LOG("MediaKeySystemAccessManager::%s " msg, __func__, ##__VA_ARGS__)
    136 
    137 MediaKeySystemAccessManager::MediaKeySystemAccessManager(
    138    nsPIDOMWindowInner* aWindow)
    139    : mWindow(aWindow) {
    140  MOZ_ASSERT(NS_IsMainThread());
    141 }
    142 
    143 MediaKeySystemAccessManager::~MediaKeySystemAccessManager() {
    144  MOZ_ASSERT(NS_IsMainThread());
    145  Shutdown();
    146 }
    147 
    148 void MediaKeySystemAccessManager::Request(
    149    DetailedPromise* aPromise, const nsAString& aKeySystem,
    150    const Sequence<MediaKeySystemConfiguration>& aConfigs) {
    151  MOZ_ASSERT(NS_IsMainThread());
    152  CheckDoesWindowSupportProtectedMedia(
    153      MakeUnique<PendingRequest>(aPromise, aKeySystem, aConfigs));
    154 }
    155 
    156 RefPtr<MediaKeySystemAccessManager::MediaKeySystemAccessPromise>
    157 MediaKeySystemAccessManager::Request(
    158    const nsAString& aKeySystem,
    159    const Sequence<MediaKeySystemConfiguration>& aConfigs) {
    160  MOZ_ASSERT(NS_IsMainThread());
    161  auto request = MakeUnique<PendingRequestWithMozPromise>(aKeySystem, aConfigs);
    162  RefPtr<MediaKeySystemAccessPromise> promise =
    163      request->mAccessPromise.Ensure(__func__);
    164  CheckDoesWindowSupportProtectedMedia(std::move(request));
    165  return promise;
    166 }
    167 
    168 void MediaKeySystemAccessManager::CheckDoesWindowSupportProtectedMedia(
    169    UniquePtr<PendingRequest> aRequest) {
    170  MOZ_ASSERT(NS_IsMainThread());
    171  MOZ_ASSERT(aRequest);
    172  MKSAM_LOG_DEBUG("aRequest->mKeySystem=%s",
    173                  NS_ConvertUTF16toUTF8(aRequest->mKeySystem).get());
    174 
    175  // In Windows OS, some Firefox windows that host content cannot support
    176  // protected content, so check the status of support for this window.
    177  // On other platforms windows should always support protected media.
    178 #ifdef XP_WIN
    179  RefPtr<BrowserChild> browser(BrowserChild::GetFrom(mWindow));
    180  if (!browser) {
    181    if (!XRE_IsParentProcess() || XRE_IsE10sParentProcess()) {
    182      // In this case, there is no browser because the Navigator object has
    183      // been disconnected from its window. Thus, reject the promise.
    184      aRequest->RejectPromiseWithTypeError(
    185          "Browsing context is no longer available"_ns);
    186    } else {
    187      // In this case, there is no browser because e10s is off. Proceed with
    188      // the request with support since this scenario is always supported.
    189      MKSAM_LOG_DEBUG("Allowing protected media on Windows with e10s off.");
    190 
    191      OnDoesWindowSupportProtectedMedia(true, std::move(aRequest));
    192    }
    193 
    194    return;
    195  }
    196 
    197  RefPtr<MediaKeySystemAccessManager> self(this);
    198 
    199  MKSAM_LOG_DEBUG(
    200      "Checking with browser if this window supports protected media.");
    201  browser->DoesWindowSupportProtectedMedia()->Then(
    202      GetCurrentSerialEventTarget(), __func__,
    203      [self, request = std::move(aRequest)](
    204          const BrowserChild::IsWindowSupportingProtectedMediaPromise::
    205              ResolveOrRejectValue& value) mutable {
    206        if (value.IsResolve()) {
    207          self->OnDoesWindowSupportProtectedMedia(value.ResolveValue(),
    208                                                  std::move(request));
    209        } else {
    210          EME_LOG(
    211              "MediaKeySystemAccessManager::DoesWindowSupportProtectedMedia-"
    212              "ResolveOrRejectLambda Failed to make IPC call to "
    213              "IsWindowSupportingProtectedMedia: "
    214              "reason=%d",
    215              static_cast<int>(value.RejectValue()));
    216          // Treat as failure.
    217          self->OnDoesWindowSupportProtectedMedia(false, std::move(request));
    218        }
    219      });
    220 
    221 #else
    222  // Non-Windows OS windows always support protected media.
    223  MKSAM_LOG_DEBUG(
    224      "Allowing protected media because all non-Windows OS windows support "
    225      "protected media.");
    226  OnDoesWindowSupportProtectedMedia(true, std::move(aRequest));
    227 #endif
    228 }
    229 
    230 void MediaKeySystemAccessManager::OnDoesWindowSupportProtectedMedia(
    231    bool aIsSupportedInWindow, UniquePtr<PendingRequest> aRequest) {
    232  MOZ_ASSERT(NS_IsMainThread());
    233  MOZ_ASSERT(aRequest);
    234  MKSAM_LOG_DEBUG("aIsSupportedInWindow=%s aRequest->mKeySystem=%s",
    235                  aIsSupportedInWindow ? "true" : "false",
    236                  NS_ConvertUTF16toUTF8(aRequest->mKeySystem).get());
    237 
    238  if (!aIsSupportedInWindow) {
    239    aRequest->RejectPromiseWithNotSupportedError(
    240        "EME is not supported in this window"_ns);
    241    return;
    242  }
    243 
    244  RequestMediaKeySystemAccess(std::move(aRequest));
    245 }
    246 
    247 void MediaKeySystemAccessManager::CheckDoesAppAllowProtectedMedia(
    248    UniquePtr<PendingRequest> aRequest) {
    249  // At time of writing, only GeckoView is expected to leverage the need to
    250  // approve EME requests from the application. However, this functionality
    251  // can be tested on all platforms by manipulating the
    252  // media.eme.require-app-approval + test prefs associated with
    253  // MediaKeySystemPermissionRequest.
    254  MOZ_ASSERT(NS_IsMainThread());
    255  MOZ_ASSERT(aRequest);
    256  MKSAM_LOG_DEBUG("aRequest->mKeySystem=%s",
    257                  NS_ConvertUTF16toUTF8(aRequest->mKeySystem).get());
    258 
    259  if (!StaticPrefs::media_eme_require_app_approval()) {
    260    MKSAM_LOG_DEBUG(
    261        "media.eme.require-app-approval is false, allowing request.");
    262    // We don't require app approval as the pref is not set. Treat as app
    263    // approving by passing true to the handler.
    264    OnDoesAppAllowProtectedMedia(true, std::move(aRequest));
    265    return;
    266  }
    267 
    268  if (mAppAllowsProtectedMediaPromiseRequest.Exists()) {
    269    // We already have a pending permission request, we don't need to fire
    270    // another. Just wait for the existing permission request to be handled
    271    // and the result from that will be used to handle the current request.
    272    MKSAM_LOG_DEBUG(
    273        "mAppAllowsProtectedMediaPromiseRequest already exists. aRequest "
    274        "addded to queue and will be handled when exising permission request "
    275        "is serviced.");
    276    mPendingAppApprovalRequests.AppendElement(std::move(aRequest));
    277    return;
    278  }
    279 
    280  RefPtr<MediaKeySystemAccessPermissionRequest> appApprovalRequest =
    281      MediaKeySystemAccessPermissionRequest::Create(mWindow);
    282  if (!appApprovalRequest) {
    283    MKSAM_LOG_DEBUG(
    284        "Failed to create app approval request! Blocking eme request as "
    285        "fallback.");
    286    aRequest->RejectPromiseWithInvalidAccessError(nsLiteralCString(
    287        "Failed to create approval request to send to app embedding Gecko."));
    288    return;
    289  }
    290 
    291  // If we're not using testing prefs (which take precedence over cached
    292  // values) and have a cached value, handle based on the cached value.
    293  if (appApprovalRequest->CheckPromptPrefs() ==
    294          MediaKeySystemAccessPermissionRequest::PromptResult::Pending &&
    295      mAppAllowsProtectedMedia) {
    296    MKSAM_LOG_DEBUG(
    297        "Short circuiting based on mAppAllowsProtectedMedia cached value");
    298    OnDoesAppAllowProtectedMedia(*mAppAllowsProtectedMedia,
    299                                 std::move(aRequest));
    300    return;
    301  }
    302 
    303  // Store the approval request, it will be handled when we get a response
    304  // from the app.
    305  mPendingAppApprovalRequests.AppendElement(std::move(aRequest));
    306 
    307  RefPtr<MediaKeySystemAccessPermissionRequest::RequestPromise> p =
    308      appApprovalRequest->GetPromise();
    309  p->Then(
    310       GetCurrentSerialEventTarget(), __func__,
    311       // Allow callback
    312       [this,
    313        self = RefPtr<MediaKeySystemAccessManager>(this)](bool aRequestResult) {
    314         MOZ_ASSERT(NS_IsMainThread());
    315         MOZ_ASSERT(aRequestResult, "Result should be true on allow callback!");
    316         mAppAllowsProtectedMediaPromiseRequest.Complete();
    317         // Cache result.
    318         mAppAllowsProtectedMedia = Some(aRequestResult);
    319         // For each pending request, handle it based on the app's response.
    320         for (UniquePtr<PendingRequest>& approvalRequest :
    321              mPendingAppApprovalRequests) {
    322           OnDoesAppAllowProtectedMedia(*mAppAllowsProtectedMedia,
    323                                        std::move(approvalRequest));
    324         }
    325         self->mPendingAppApprovalRequests.Clear();
    326       },
    327       // Cancel callback
    328       [this,
    329        self = RefPtr<MediaKeySystemAccessManager>(this)](bool aRequestResult) {
    330         MOZ_ASSERT(NS_IsMainThread());
    331         MOZ_ASSERT(!aRequestResult,
    332                    "Result should be false on cancel callback!");
    333         mAppAllowsProtectedMediaPromiseRequest.Complete();
    334         // Cache result.
    335         mAppAllowsProtectedMedia = Some(aRequestResult);
    336         // For each pending request, handle it based on the app's response.
    337         for (UniquePtr<PendingRequest>& approvalRequest :
    338              mPendingAppApprovalRequests) {
    339           OnDoesAppAllowProtectedMedia(*mAppAllowsProtectedMedia,
    340                                        std::move(approvalRequest));
    341         }
    342         self->mPendingAppApprovalRequests.Clear();
    343       })
    344      ->Track(mAppAllowsProtectedMediaPromiseRequest);
    345 
    346  // Prefs not causing short circuit, no cached value, go ahead and request
    347  // permission.
    348  MKSAM_LOG_DEBUG("Dispatching async request for app approval");
    349  if (NS_FAILED(appApprovalRequest->Start())) {
    350    // This shouldn't happen unless we're shutting down or similar edge cases.
    351    // If this is regularly being hit then something is wrong and should be
    352    // investigated.
    353    MKSAM_LOG_DEBUG(
    354        "Failed to start app approval request! Eme approval will be left in "
    355        "limbo!");
    356  }
    357 }
    358 
    359 void MediaKeySystemAccessManager::OnDoesAppAllowProtectedMedia(
    360    bool aIsAllowed, UniquePtr<PendingRequest> aRequest) {
    361  MOZ_ASSERT(NS_IsMainThread());
    362  MOZ_ASSERT(aRequest);
    363  MKSAM_LOG_DEBUG("aIsAllowed=%s aRequest->mKeySystem=%s",
    364                  aIsAllowed ? "true" : "false",
    365                  NS_ConvertUTF16toUTF8(aRequest->mKeySystem).get());
    366  if (!aIsAllowed) {
    367    aRequest->RejectPromiseWithNotSupportedError(
    368        nsLiteralCString("The application embedding this user agent has "
    369                         "blocked MediaKeySystemAccess"));
    370    return;
    371  }
    372 
    373  ProvideAccess(std::move(aRequest));
    374 }
    375 
    376 void MediaKeySystemAccessManager::RequestMediaKeySystemAccess(
    377    UniquePtr<PendingRequest> aRequest) {
    378  MOZ_ASSERT(NS_IsMainThread());
    379  MOZ_ASSERT(aRequest);
    380  MKSAM_LOG_DEBUG("aIsSupportedInWindow=%s",
    381                  NS_ConvertUTF16toUTF8(aRequest->mKeySystem).get());
    382 
    383  // 1. If keySystem is the empty string, return a promise rejected with a newly
    384  // created TypeError.
    385  if (aRequest->mKeySystem.IsEmpty()) {
    386    aRequest->RejectPromiseWithTypeError("Key system string is empty"_ns);
    387    // Don't notify DecoderDoctor, as there's nothing we or the user can
    388    // do to fix this situation; the site is using the API wrong.
    389    return;
    390  }
    391  // 2. If supportedConfigurations is empty, return a promise rejected with a
    392  // newly created TypeError.
    393  if (aRequest->mConfigs.IsEmpty()) {
    394    aRequest->RejectPromiseWithTypeError(
    395        "Candidate MediaKeySystemConfigs is empty"_ns);
    396    // Don't notify DecoderDoctor, as there's nothing we or the user can
    397    // do to fix this situation; the site is using the API wrong.
    398    return;
    399  }
    400 
    401  // 3. Let document be the calling context's Document.
    402  // 4. Let origin be the origin of document.
    403  // 5. Let promise be a new promise.
    404  // 6. Run the following steps in parallel:
    405 
    406  //   1. If keySystem is not one of the Key Systems supported by the user
    407  //   agent, reject promise with a NotSupportedError. String comparison is
    408  //   case-sensitive.
    409  if (!IsWidevineKeySystem(aRequest->mKeySystem) &&
    410 #ifdef MOZ_WMF_CDM
    411      !IsPlayReadyKeySystemAndSupported(aRequest->mKeySystem) &&
    412      !IsWidevineExperimentKeySystemAndSupported(aRequest->mKeySystem) &&
    413 #endif
    414      !IsClearkeyKeySystem(aRequest->mKeySystem)) {
    415    // Not to inform user, because nothing to do if the keySystem is not
    416    // supported.
    417    aRequest->RejectPromiseWithNotSupportedError(
    418        "Key system is unsupported"_ns);
    419    aRequest->mDiagnostics.StoreMediaKeySystemAccess(
    420        mWindow->GetExtantDoc(), aRequest->mKeySystem, false, __func__);
    421    return;
    422  }
    423 
    424  if (!StaticPrefs::media_eme_enabled() &&
    425      !IsClearkeyKeySystem(aRequest->mKeySystem)) {
    426    // EME disabled by user, send notification to chrome so UI can inform user.
    427    // Clearkey is allowed even when EME is disabled because we want the pref
    428    // "media.eme.enabled" only taking effect on proprietary DRMs.
    429    // We don't show the notification if the pref is locked.
    430    if (!Preferences::IsLocked("media.eme.enabled")) {
    431      MediaKeySystemAccess::NotifyObservers(mWindow, aRequest->mKeySystem,
    432                                            MediaKeySystemStatus::Api_disabled);
    433    }
    434    aRequest->RejectPromiseWithNotSupportedError("EME has been preffed off"_ns);
    435    aRequest->mDiagnostics.StoreMediaKeySystemAccess(
    436        mWindow->GetExtantDoc(), aRequest->mKeySystem, false, __func__);
    437    return;
    438  }
    439 
    440  nsAutoCString message;
    441  MediaKeySystemStatus status =
    442      MediaKeySystemAccess::GetKeySystemStatus(*aRequest, message);
    443 
    444  nsPrintfCString msg(
    445      "MediaKeySystemAccess::GetKeySystemStatus(%s) "
    446      "result=%s msg='%s'",
    447      NS_ConvertUTF16toUTF8(aRequest->mKeySystem).get(),
    448      GetEnumString(status).get(), message.get());
    449  LogToBrowserConsole(NS_ConvertUTF8toUTF16(msg));
    450  EME_LOG("%s", msg.get());
    451 
    452  // We may need to install Widevine CDM to continue.
    453  if (status == MediaKeySystemStatus::Cdm_not_installed &&
    454      (IsWidevineKeySystem(aRequest->mKeySystem)
    455 #ifdef MOZ_WMF_CDM
    456       || IsWidevineExperimentKeySystemAndSupported(aRequest->mKeySystem)
    457 #endif
    458           )) {
    459    // These are cases which could be resolved by downloading a new(er) CDM.
    460    // When we send the status to chrome, chrome's GMPProvider will attempt to
    461    // download or update the CDM. In AwaitInstall() we add listeners to wait
    462    // for the update to complete, and we'll call this function again with
    463    // aType==Subsequent once the download has completed and the GMPService
    464    // has had a new plugin added. AwaitInstall() sets a timer to fail if the
    465    // update/download takes too long or fails.
    466 
    467    if (aRequest->mRequestType != PendingRequest::RequestType::Initial) {
    468      MOZ_ASSERT(aRequest->mRequestType ==
    469                 PendingRequest::RequestType::Subsequent);
    470      // CDM is not installed, but this is a subsequent request. We've waited,
    471      // but can't service this request! Give up. Chrome will still be showing a
    472      // "I can't play, updating" notification.
    473      aRequest->RejectPromiseWithNotSupportedError(
    474          "Timed out while waiting for a CDM update"_ns);
    475      aRequest->mDiagnostics.StoreMediaKeySystemAccess(
    476          mWindow->GetExtantDoc(), aRequest->mKeySystem, false, __func__);
    477      return;
    478    }
    479 
    480    nsString keySystem = aRequest->mKeySystem;
    481 #ifdef MOZ_WMF_CDM
    482    // If cdm-not-install is for HWDRM, that means we want to install Widevine
    483    // L1, which requires using hardware key system name for GMP to look up the
    484    // plugin.
    485    if (CheckIfHarewareDRMConfigExists(aRequest->mConfigs)) {
    486      keySystem = NS_ConvertUTF8toUTF16(kWidevineExperimentKeySystemName);
    487    }
    488 #endif
    489    auto& diagnostics = aRequest->mDiagnostics;
    490    if (AwaitInstall(std::move(aRequest))) {
    491      // Notify chrome that we're going to wait for the CDM to download/update.
    492      EME_LOG("Await %s for installation",
    493              NS_ConvertUTF16toUTF8(keySystem).get());
    494      MediaKeySystemAccess::NotifyObservers(mWindow, keySystem, status);
    495    } else {
    496      // Failed to await the install. Log failure and give up trying to service
    497      // this request.
    498      EME_LOG("Failed to await %s for installation",
    499              NS_ConvertUTF16toUTF8(keySystem).get());
    500      diagnostics.StoreMediaKeySystemAccess(mWindow->GetExtantDoc(), keySystem,
    501                                            false, __func__);
    502    }
    503    return;
    504  }
    505  if (status != MediaKeySystemStatus::Available) {
    506    // Failed due to user disabling something, send a notification to
    507    // chrome, so we can show some UI to explain how the user can rectify
    508    // the situation.
    509    EME_LOG("Notify CDM failure for %s and reject the promise",
    510            NS_ConvertUTF16toUTF8(aRequest->mKeySystem).get());
    511    MediaKeySystemAccess::NotifyObservers(mWindow, aRequest->mKeySystem,
    512                                          status);
    513    aRequest->RejectPromiseWithNotSupportedError(message);
    514    return;
    515  }
    516 
    517  bool isPrivateBrowsing =
    518      mWindow->GetExtantDoc() &&
    519      mWindow->GetExtantDoc()->NodePrincipal()->GetIsInPrivateBrowsing();
    520  //   2. Let implementation be the implementation of keySystem.
    521  //   3. For each value in supportedConfigurations:
    522  //     1. Let candidate configuration be the value.
    523  //     2. Let supported configuration be the result of executing the Get
    524  //     Supported Configuration algorithm on implementation, candidate
    525  //     configuration, and origin.
    526  //     3. If supported configuration is not NotSupported, run the following
    527  //     steps:
    528  //       1. Let access be a new MediaKeySystemAccess object, and initialize it
    529  //       as follows:
    530  //         1. Set the keySystem attribute to keySystem.
    531  //         2. Let the configuration value be supported configuration.
    532  //         3. Let the cdm implementation value be implementation.
    533  //      2. Resolve promise with access and abort the parallel steps of this
    534  //      algorithm.
    535  MediaKeySystemAccess::GetSupportedConfig(aRequest.get(), isPrivateBrowsing,
    536                                           mWindow->GetExtantDoc())
    537      ->Then(GetMainThreadSerialEventTarget(), __func__,
    538             [self = RefPtr<MediaKeySystemAccessManager>{this}, this,
    539              request = UniquePtr<PendingRequest>{std::move(aRequest)}](
    540                 const KeySystemConfig::KeySystemConfigPromise::
    541                     ResolveOrRejectValue& aResult) mutable {
    542               if (aResult.IsResolve()) {
    543                 request->mSupportedConfig = Some(aResult.ResolveValue());
    544                 // The app gets the final say on if we provide access or not.
    545                 CheckDoesAppAllowProtectedMedia(std::move(request));
    546               } else {
    547                 // 4. Reject promise with a NotSupportedError.
    548                 // Not to inform user, because nothing to do if the
    549                 // corresponding keySystem configuration is not supported.
    550                 request->RejectPromiseWithNotSupportedError(
    551                     "Key system configuration is not supported"_ns);
    552                 request->mDiagnostics.StoreMediaKeySystemAccess(
    553                     mWindow->GetExtantDoc(), request->mKeySystem, false,
    554                     __func__);
    555               }
    556             });
    557 }
    558 
    559 void MediaKeySystemAccessManager::ProvideAccess(
    560    UniquePtr<PendingRequest> aRequest) {
    561  MOZ_ASSERT(aRequest);
    562  MOZ_ASSERT(
    563      aRequest->mSupportedConfig,
    564      "The request needs a supported config if we're going to provide access!");
    565  MKSAM_LOG_DEBUG("aRequest->mKeySystem=%s",
    566                  NS_ConvertUTF16toUTF8(aRequest->mKeySystem).get());
    567 
    568  DecoderDoctorDiagnostics diagnostics;
    569 
    570  RefPtr<MediaKeySystemAccess> access(new MediaKeySystemAccess(
    571      mWindow, aRequest->mKeySystem, aRequest->mSupportedConfig.ref()));
    572  aRequest->ResolvePromise(access);
    573  diagnostics.StoreMediaKeySystemAccess(mWindow->GetExtantDoc(),
    574                                        aRequest->mKeySystem, true, __func__);
    575 }
    576 
    577 bool MediaKeySystemAccessManager::AwaitInstall(
    578    UniquePtr<PendingRequest> aRequest) {
    579  MOZ_ASSERT(NS_IsMainThread());
    580  MOZ_ASSERT(aRequest);
    581  MKSAM_LOG_DEBUG("aRequest->mKeySystem=%s",
    582                  NS_ConvertUTF16toUTF8(aRequest->mKeySystem).get());
    583 
    584  if (!EnsureObserversAdded()) {
    585    NS_WARNING("Failed to add pref observer");
    586    aRequest->RejectPromiseWithNotSupportedError(nsLiteralCString(
    587        "Failed trying to setup CDM update: failed adding observers"));
    588    return false;
    589  }
    590 
    591  nsCOMPtr<nsITimer> timer;
    592  NS_NewTimerWithObserver(getter_AddRefs(timer), this, 60 * 1000,
    593                          nsITimer::TYPE_ONE_SHOT);
    594  if (!timer) {
    595    NS_WARNING("Failed to create timer to await CDM install.");
    596    aRequest->RejectPromiseWithNotSupportedError(nsLiteralCString(
    597        "Failed trying to setup CDM update: failed timer creation"));
    598    return false;
    599  }
    600 
    601  MOZ_DIAGNOSTIC_ASSERT(
    602      aRequest->mTimer == nullptr,
    603      "Timer should not already be set on a request we're about to await");
    604  aRequest->mTimer = timer;
    605 
    606  mPendingInstallRequests.AppendElement(std::move(aRequest));
    607  return true;
    608 }
    609 
    610 void MediaKeySystemAccessManager::RetryRequest(
    611    UniquePtr<PendingRequest> aRequest) {
    612  MOZ_ASSERT(NS_IsMainThread());
    613  MOZ_ASSERT(aRequest);
    614  MKSAM_LOG_DEBUG("aRequest->mKeySystem=%s",
    615                  NS_ConvertUTF16toUTF8(aRequest->mKeySystem).get());
    616  // Cancel and null timer if it exists.
    617  aRequest->CancelTimer();
    618  // Indicate that this is a request that's being retried.
    619  aRequest->mRequestType = PendingRequest::RequestType::Subsequent;
    620  RequestMediaKeySystemAccess(std::move(aRequest));
    621 }
    622 
    623 nsresult MediaKeySystemAccessManager::Observe(nsISupports* aSubject,
    624                                              const char* aTopic,
    625                                              const char16_t* aData) {
    626  MOZ_ASSERT(NS_IsMainThread());
    627  MKSAM_LOG_DEBUG("%s", aTopic);
    628 
    629  if (!strcmp(aTopic, "gmp-changed")) {
    630    // Filter out the requests where the CDM's install-status is no longer
    631    // "unavailable". This will be the CDMs which have downloaded since the
    632    // initial request.
    633    // Note: We don't have a way to communicate from chrome that the CDM has
    634    // failed to download, so we'll just let the timeout fail us in that case.
    635    nsTArray<UniquePtr<PendingRequest>> requests;
    636    for (size_t i = mPendingInstallRequests.Length(); i-- > 0;) {
    637      nsAutoCString message;
    638      MediaKeySystemStatus status = MediaKeySystemAccess::GetKeySystemStatus(
    639          *mPendingInstallRequests[i], message);
    640      if (status == MediaKeySystemStatus::Cdm_not_installed) {
    641        // Not yet installed, don't retry. Keep waiting until timeout.
    642        continue;
    643      }
    644      // Status has changed, retry request.
    645      requests.AppendElement(std::move(mPendingInstallRequests[i]));
    646      mPendingInstallRequests.RemoveElementAt(i);
    647    }
    648    // Retry all pending requests, but this time fail if the CDM is not
    649    // installed.
    650    for (size_t i = requests.Length(); i-- > 0;) {
    651      RetryRequest(std::move(requests[i]));
    652    }
    653  } else if (!strcmp(aTopic, "timer-callback")) {
    654    // Find the timer that expired and re-run the request for it.
    655    nsCOMPtr<nsITimer> timer(do_QueryInterface(aSubject));
    656    for (size_t i = 0; i < mPendingInstallRequests.Length(); i++) {
    657      if (mPendingInstallRequests[i]->mTimer == timer) {
    658        EME_LOG("MediaKeySystemAccessManager::AwaitInstall resuming request");
    659        UniquePtr<PendingRequest> request =
    660            std::move(mPendingInstallRequests[i]);
    661        mPendingInstallRequests.RemoveElementAt(i);
    662        RetryRequest(std::move(request));
    663        break;
    664      }
    665    }
    666  }
    667  return NS_OK;
    668 }
    669 
    670 nsresult MediaKeySystemAccessManager::GetName(nsACString& aName) {
    671  aName.AssignLiteral("MediaKeySystemAccessManager");
    672  return NS_OK;
    673 }
    674 
    675 bool MediaKeySystemAccessManager::EnsureObserversAdded() {
    676  MOZ_ASSERT(NS_IsMainThread());
    677  if (mAddedObservers) {
    678    return true;
    679  }
    680 
    681  nsCOMPtr<nsIObserverService> obsService =
    682      mozilla::services::GetObserverService();
    683  if (NS_WARN_IF(!obsService)) {
    684    return false;
    685  }
    686  mAddedObservers =
    687      NS_SUCCEEDED(obsService->AddObserver(this, "gmp-changed", false));
    688  return mAddedObservers;
    689 }
    690 
    691 void MediaKeySystemAccessManager::Shutdown() {
    692  MOZ_ASSERT(NS_IsMainThread());
    693  MKSAM_LOG_DEBUG("");
    694  for (const UniquePtr<PendingRequest>& installRequest :
    695       mPendingInstallRequests) {
    696    // Cancel all requests; we're shutting down.
    697    installRequest->CancelTimer();
    698    installRequest->RejectPromiseWithInvalidAccessError(nsLiteralCString(
    699        "Promise still outstanding at MediaKeySystemAccessManager shutdown"));
    700  }
    701  mPendingInstallRequests.Clear();
    702  for (const UniquePtr<PendingRequest>& approvalRequest :
    703       mPendingAppApprovalRequests) {
    704    approvalRequest->RejectPromiseWithInvalidAccessError(nsLiteralCString(
    705        "Promise still outstanding at MediaKeySystemAccessManager shutdown"));
    706  }
    707  mPendingAppApprovalRequests.Clear();
    708  mAppAllowsProtectedMediaPromiseRequest.DisconnectIfExists();
    709  if (mAddedObservers) {
    710    nsCOMPtr<nsIObserverService> obsService =
    711        mozilla::services::GetObserverService();
    712    if (obsService) {
    713      obsService->RemoveObserver(this, "gmp-changed");
    714      mAddedObservers = false;
    715    }
    716  }
    717 }
    718 
    719 }  // namespace mozilla::dom
    720 
    721 #undef MKSAM_LOG_DEBUG