tor-browser

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

MediaCapabilities.cpp (33038B)


      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
      5 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
      6 
      7 #include "MediaCapabilities.h"
      8 
      9 #include <inttypes.h>
     10 
     11 #include <utility>
     12 
     13 #include "AllocationPolicy.h"
     14 #include "DecoderTraits.h"
     15 #include "MP4Decoder.h"
     16 #include "MediaCapabilitiesValidation.h"
     17 #include "MediaInfo.h"
     18 #include "MediaRecorder.h"
     19 #include "PDMFactory.h"
     20 #include "VPXDecoder.h"
     21 #include "WindowRenderer.h"
     22 #include "mozilla/ClearOnShutdown.h"
     23 #include "mozilla/EMEUtils.h"
     24 #include "mozilla/SchedulerGroup.h"
     25 #include "mozilla/StaticPrefs_media.h"
     26 #include "mozilla/TaskQueue.h"
     27 #include "mozilla/dom/DOMMozPromiseRequestHolder.h"
     28 #include "mozilla/dom/Document.h"
     29 #include "mozilla/dom/MediaCapabilitiesBinding.h"
     30 #include "mozilla/dom/MediaKeySystemAccess.h"
     31 #include "mozilla/dom/MediaSource.h"
     32 #include "mozilla/dom/Navigator.h"
     33 #include "mozilla/dom/Promise.h"
     34 #include "mozilla/dom/WorkerCommon.h"
     35 #include "mozilla/dom/WorkerPrivate.h"
     36 #include "mozilla/dom/WorkerRef.h"
     37 #include "mozilla/layers/KnowsCompositor.h"
     38 #include "nsContentUtils.h"
     39 
     40 mozilla::LazyLogModule sMediaCapabilitiesLog("MediaCapabilities");
     41 
     42 #define LOG(msg, ...) \
     43  DDMOZ_LOG(sMediaCapabilitiesLog, LogLevel::Debug, msg, ##__VA_ARGS__)
     44 
     45 namespace mozilla::dom {
     46 using mediacaps::IsValidMediaDecodingConfiguration;
     47 using mediacaps::IsValidMediaEncodingConfiguration;
     48 
     49 static bool
     50 MediaCapabilitiesKeySystemConfigurationToMediaKeySystemConfiguration(
     51    const MediaDecodingConfiguration& aInConfig,
     52    MediaKeySystemConfiguration& aOutConfig) {
     53  if (!aInConfig.mKeySystemConfiguration.WasPassed()) {
     54    return false;
     55  }
     56 
     57  const auto& keySystemConfig = aInConfig.mKeySystemConfiguration.Value();
     58  if (!keySystemConfig.mInitDataType.IsEmpty()) {
     59    if (NS_WARN_IF(!aOutConfig.mInitDataTypes.AppendElement(
     60            keySystemConfig.mInitDataType, fallible))) {
     61      return false;
     62    }
     63  }
     64  if (keySystemConfig.mSessionTypes.WasPassed() &&
     65      !keySystemConfig.mSessionTypes.Value().IsEmpty()) {
     66    aOutConfig.mSessionTypes.Construct();
     67    for (const auto& type : keySystemConfig.mSessionTypes.Value()) {
     68      if (NS_WARN_IF(!aOutConfig.mSessionTypes.Value().AppendElement(
     69              type, fallible))) {
     70        return false;
     71      }
     72    }
     73  }
     74  aOutConfig.mDistinctiveIdentifier = keySystemConfig.mDistinctiveIdentifier;
     75  aOutConfig.mPersistentState = keySystemConfig.mPersistentState;
     76 
     77  if (aInConfig.mAudio.WasPassed()) {
     78    auto* capabilitiy = aOutConfig.mAudioCapabilities.AppendElement(fallible);
     79    if (NS_WARN_IF(!capabilitiy)) {
     80      return false;
     81    }
     82    capabilitiy->mContentType = aInConfig.mAudio.Value().mContentType;
     83    if (keySystemConfig.mAudio.WasPassed()) {
     84      const auto& config = keySystemConfig.mAudio.Value();
     85      capabilitiy->mRobustness = config.mRobustness;
     86      capabilitiy->mEncryptionScheme = config.mEncryptionScheme;
     87    }
     88  }
     89  if (aInConfig.mVideo.WasPassed()) {
     90    auto* capabilitiy = aOutConfig.mVideoCapabilities.AppendElement(fallible);
     91    if (NS_WARN_IF(!capabilitiy)) {
     92      return false;
     93    }
     94    capabilitiy->mContentType = aInConfig.mVideo.Value().mContentType;
     95    if (keySystemConfig.mVideo.WasPassed()) {
     96      const auto& config = keySystemConfig.mVideo.Value();
     97      capabilitiy->mRobustness = config.mRobustness;
     98      capabilitiy->mEncryptionScheme = config.mEncryptionScheme;
     99    }
    100  }
    101  return true;
    102 }
    103 
    104 static nsCString VideoConfigurationToStr(const VideoConfiguration* aConfig) {
    105  if (!aConfig) {
    106    return nsCString();
    107  }
    108 
    109  auto str = nsPrintfCString(
    110      "[contentType:%s width:%d height:%d bitrate:%" PRIu64
    111      " framerate:%lf hasAlphaChannel:%s hdrMetadataType:%s colorGamut:%s "
    112      "transferFunction:%s scalabilityMode:%s]",
    113      NS_ConvertUTF16toUTF8(aConfig->mContentType).get(), aConfig->mWidth,
    114      aConfig->mHeight, aConfig->mBitrate, aConfig->mFramerate,
    115      aConfig->mHasAlphaChannel.WasPassed()
    116          ? aConfig->mHasAlphaChannel.Value() ? "true" : "false"
    117          : "?",
    118      aConfig->mHdrMetadataType.WasPassed()
    119          ? GetEnumString(aConfig->mHdrMetadataType.Value()).get()
    120          : "?",
    121      aConfig->mColorGamut.WasPassed()
    122          ? GetEnumString(aConfig->mColorGamut.Value()).get()
    123          : "?",
    124      aConfig->mTransferFunction.WasPassed()
    125          ? GetEnumString(aConfig->mTransferFunction.Value()).get()
    126          : "?",
    127      aConfig->mScalabilityMode.WasPassed()
    128          ? NS_ConvertUTF16toUTF8(aConfig->mScalabilityMode.Value()).get()
    129          : "?");
    130  return std::move(str);
    131 }
    132 
    133 static nsCString AudioConfigurationToStr(const AudioConfiguration* aConfig) {
    134  if (!aConfig) {
    135    return nsCString();
    136  }
    137  auto str = nsPrintfCString(
    138      "[contentType:%s channels:%s bitrate:%" PRIu64 " samplerate:%d]",
    139      NS_ConvertUTF16toUTF8(aConfig->mContentType).get(),
    140      aConfig->mChannels.WasPassed()
    141          ? NS_ConvertUTF16toUTF8(aConfig->mChannels.Value()).get()
    142          : "?",
    143      aConfig->mBitrate.WasPassed() ? aConfig->mBitrate.Value() : 0,
    144      aConfig->mSamplerate.WasPassed() ? aConfig->mSamplerate.Value() : 0);
    145  return std::move(str);
    146 }
    147 
    148 static nsCString MediaCapabilitiesInfoToStr(
    149    const MediaCapabilitiesInfo& aInfo) {
    150  auto str = nsPrintfCString("[supported:%s smooth:%s powerEfficient:%s]",
    151                             aInfo.mSupported ? "true" : "false",
    152                             aInfo.mSmooth ? "true" : "false",
    153                             aInfo.mPowerEfficient ? "true" : "false");
    154  return std::move(str);
    155 }
    156 
    157 static nsCString MediaDecodingConfigurationToStr(
    158    const MediaDecodingConfiguration& aConfig) {
    159  nsCString str;
    160  str += "["_ns;
    161  if (aConfig.mVideo.WasPassed()) {
    162    str += "video:"_ns + VideoConfigurationToStr(&aConfig.mVideo.Value());
    163    if (aConfig.mAudio.WasPassed()) {
    164      str += " "_ns;
    165    }
    166  }
    167  if (aConfig.mAudio.WasPassed()) {
    168    str += "audio:"_ns + AudioConfigurationToStr(&aConfig.mAudio.Value());
    169  }
    170  if (aConfig.mKeySystemConfiguration.WasPassed()) {
    171    str += "[keySystem:"_ns +
    172           NS_ConvertUTF16toUTF8(
    173               aConfig.mKeySystemConfiguration.Value().mKeySystem) +
    174           ", "_ns;
    175    MediaKeySystemConfiguration emeConfig;
    176    if (MediaCapabilitiesKeySystemConfigurationToMediaKeySystemConfiguration(
    177            aConfig, emeConfig)) {
    178      str += MediaKeySystemAccess::ToCString(emeConfig);
    179    }
    180    str += "]"_ns;
    181  }
    182  str += "]"_ns;
    183  return str;
    184 }
    185 
    186 MediaCapabilities::MediaCapabilities(nsIGlobalObject* aParent)
    187    : mParent(aParent) {}
    188 
    189 // https://w3c.github.io/media-capabilities/#dom-mediacapabilities-decodinginfo
    190 // Section 2.5.2 DecodingInfo() Method
    191 already_AddRefed<Promise> MediaCapabilities::DecodingInfo(
    192    const MediaDecodingConfiguration& aConfiguration, ErrorResult& aRv) {
    193  RefPtr<Promise> promise = Promise::Create(mParent, aRv);
    194  if (aRv.Failed()) {
    195    return nullptr;
    196  }
    197 
    198  // Step 1: If configuration is not a valid MediaDecodingConfiguration,
    199  // return a Promise rejected with a newly created TypeError.
    200  if (auto configCheck = IsValidMediaDecodingConfiguration(aConfiguration);
    201      configCheck.isErr()) {
    202    RejectWithValidationResult(promise, configCheck.unwrapErr());
    203    return promise.forget();
    204  }
    205 
    206  // Step 2: If configuration.keySystemConfiguration exists,
    207  // run the following substeps:
    208  if (aConfiguration.mKeySystemConfiguration.WasPassed()) {
    209    // Step 2.1: If the global object is of type WorkerGlobalScope,
    210    //           return a Promise rejected with a newly created DOMException
    211    //           whose name is InvalidStateError.
    212    if (IsWorkerGlobal(mParent->GetGlobalJSObject())) {
    213      promise->MaybeRejectWithInvalidStateError(
    214          "key system configuration is not allowed in the worker scope");
    215      return promise.forget();
    216    }
    217    // Step 2.2 If the global object’s relevant settings object is a
    218    //          non-secure context, return a Promise rejected with a newly
    219    //          created DOMException whose name is SecurityError.
    220    if (auto* window = mParent->GetAsInnerWindow();
    221        window && !window->IsSecureContext()) {
    222      promise->MaybeRejectWithSecurityError(
    223          "key system configuration is not allowed in a non-secure context");
    224      return promise.forget();
    225    }
    226  }
    227 
    228  // Step 3: Let p be a new Promise (already have it!)
    229  // Step 4: In parallel, run the Create a MediaCapabilitiesDecodingInfo
    230  //         algorithm with configuration and resolve p with its result.
    231  CreateMediaCapabilitiesDecodingInfo(aConfiguration, aRv, promise);
    232  return promise.forget();
    233 }
    234 
    235 // https://w3c.github.io/media-capabilities/#create-media-capabilities-decoding-info
    236 void MediaCapabilities::CreateMediaCapabilitiesDecodingInfo(
    237    const MediaDecodingConfiguration& aConfiguration, ErrorResult& aRv,
    238    Promise* aPromise) {
    239  LOG("Processing %s", MediaDecodingConfigurationToStr(aConfiguration).get());
    240 
    241  bool supported = true;
    242  Maybe<MediaContainerType> videoContainer;
    243  Maybe<MediaContainerType> audioContainer;
    244 
    245  // If configuration.video is present and is not a valid video configuration,
    246  // return a Promise rejected with a TypeError.
    247  if (aConfiguration.mVideo.WasPassed()) {
    248    videoContainer = CheckVideoConfiguration(aConfiguration.mVideo.Value());
    249    if (!videoContainer) {
    250      aPromise->MaybeRejectWithTypeError("Invalid VideoConfiguration");
    251      return;
    252    }
    253 
    254    // We have a video configuration and it is valid. Check if it is supported.
    255    supported &=
    256        aConfiguration.mType == MediaDecodingType::File
    257            ? CheckTypeForFile(aConfiguration.mVideo.Value().mContentType)
    258            : CheckTypeForMediaSource(
    259                  aConfiguration.mVideo.Value().mContentType);
    260  }
    261  if (aConfiguration.mAudio.WasPassed()) {
    262    audioContainer = CheckAudioConfiguration(aConfiguration.mAudio.Value());
    263    if (!audioContainer) {
    264      aPromise->MaybeRejectWithTypeError("Invalid AudioConfiguration");
    265      return;
    266    }
    267    // We have an audio configuration and it is valid. Check if it is supported.
    268    supported &=
    269        aConfiguration.mType == MediaDecodingType::File
    270            ? CheckTypeForFile(aConfiguration.mAudio.Value().mContentType)
    271            : CheckTypeForMediaSource(
    272                  aConfiguration.mAudio.Value().mContentType);
    273  }
    274 
    275  if (!supported) {
    276    MediaCapabilitiesDecodingInfo info;
    277    info.mSupported = false;
    278    info.mSmooth = false;
    279    info.mPowerEfficient = false;
    280    LOG("%s -> %s", MediaDecodingConfigurationToStr(aConfiguration).get(),
    281        MediaCapabilitiesInfoToStr(info).get());
    282    aPromise->MaybeResolve(std::move(info));
    283    return;
    284  }
    285 
    286  nsTArray<UniquePtr<TrackInfo>> tracks;
    287  if (aConfiguration.mVideo.WasPassed()) {
    288    MOZ_ASSERT(videoContainer.isSome(), "configuration is valid and supported");
    289    auto videoTracks = DecoderTraits::GetTracksInfo(*videoContainer);
    290    // If the MIME type does not imply a codec, the string MUST
    291    // also have one and only one parameter that is named codecs with a value
    292    // describing a single media codec. Otherwise, it MUST contain no
    293    // parameters.
    294    if (videoTracks.Length() != 1) {
    295      aPromise->MaybeRejectWithTypeError(nsPrintfCString(
    296          "The provided type '%s' does not have a 'codecs' parameter.",
    297          videoContainer->OriginalString().get()));
    298      return;
    299    }
    300    MOZ_DIAGNOSTIC_ASSERT(videoTracks.ElementAt(0),
    301                          "must contain a valid trackinfo");
    302    // If the type refers to an audio codec, reject now.
    303    if (videoTracks[0]->GetType() != TrackInfo::kVideoTrack) {
    304      aPromise->MaybeRejectWithTypeError("Invalid VideoConfiguration");
    305      return;
    306    }
    307    tracks.AppendElements(std::move(videoTracks));
    308  }
    309  if (aConfiguration.mAudio.WasPassed()) {
    310    MOZ_ASSERT(audioContainer.isSome(), "configuration is valid and supported");
    311    auto audioTracks = DecoderTraits::GetTracksInfo(*audioContainer);
    312    // If the MIME type does not imply a codec, the string MUST
    313    // also have one and only one parameter that is named codecs with a value
    314    // describing a single media codec. Otherwise, it MUST contain no
    315    // parameters.
    316    if (audioTracks.Length() != 1) {
    317      aPromise->MaybeRejectWithTypeError(nsPrintfCString(
    318          "The provided type '%s' does not have a 'codecs' parameter.",
    319          audioContainer->OriginalString().get()));
    320      return;
    321    }
    322    MOZ_DIAGNOSTIC_ASSERT(audioTracks.ElementAt(0),
    323                          "must contain a valid trackinfo");
    324    // If the type refers to a video codec, reject now.
    325    if (audioTracks[0]->GetType() != TrackInfo::kAudioTrack) {
    326      aPromise->MaybeRejectWithTypeError("Invalid AudioConfiguration");
    327      return;
    328    }
    329    tracks.AppendElements(std::move(audioTracks));
    330  }
    331 
    332  // If configuration.keySystemConfiguration exists:
    333  if (aConfiguration.mKeySystemConfiguration.WasPassed()) {
    334    MOZ_ASSERT(
    335        NS_IsMainThread(),
    336        "Key system configuration qurey can not run on the worker thread!");
    337 
    338    auto* mainThread = GetMainThreadSerialEventTarget();
    339    if (!mainThread) {
    340      aPromise->MaybeRejectWithInvalidStateError(
    341          "The main thread is shutted down");
    342      return;
    343    }
    344 
    345    // This check isn't defined in the spec but exists in web platform tests, so
    346    // we perform the check as well in order to reduce the web compatibility
    347    // issues. https://github.com/w3c/media-capabilities/issues/220
    348    const auto& keySystemConfig =
    349        aConfiguration.mKeySystemConfiguration.Value();
    350    if ((keySystemConfig.mVideo.WasPassed() &&
    351         !aConfiguration.mVideo.WasPassed()) ||
    352        (keySystemConfig.mAudio.WasPassed() &&
    353         !aConfiguration.mAudio.WasPassed())) {
    354      aPromise->MaybeRejectWithTypeError(
    355          "The type of decoding config doesn't match the type of key system "
    356          "config");
    357      return;
    358    }
    359    CheckEncryptedDecodingSupport(aConfiguration)
    360        ->Then(mainThread, __func__,
    361               [promise = RefPtr<Promise>{aPromise},
    362                self = RefPtr<MediaCapabilities>{this}, aConfiguration,
    363                this](MediaKeySystemAccessManager::MediaKeySystemAccessPromise::
    364                          ResolveOrRejectValue&& aValue) {
    365                 if (aValue.IsReject()) {
    366                   MediaCapabilitiesDecodingInfo info;
    367                   info.mSupported = false;
    368                   info.mSmooth = false;
    369                   info.mPowerEfficient = false;
    370                   LOG("%s -> %s",
    371                       MediaDecodingConfigurationToStr(aConfiguration).get(),
    372                       MediaCapabilitiesInfoToStr(info).get());
    373                   promise->MaybeResolve(std::move(info));
    374                   return;
    375                 }
    376                 MediaCapabilitiesDecodingInfo info;
    377                 info.mSupported = true;
    378                 info.mSmooth = true;
    379                 info.mKeySystemAccess = aValue.ResolveValue();
    380                 MOZ_ASSERT(info.mKeySystemAccess);
    381                 MediaKeySystemConfiguration config;
    382                 info.mKeySystemAccess->GetConfiguration(config);
    383                 info.mPowerEfficient = IsHardwareDecryptionSupported(config);
    384                 LOG("%s -> %s",
    385                     MediaDecodingConfigurationToStr(aConfiguration).get(),
    386                     MediaCapabilitiesInfoToStr(info).get());
    387                 promise->MaybeResolve(std::move(info));
    388               });
    389    return;
    390  }
    391 
    392  // Otherwise, run the following steps:
    393  using CapabilitiesPromise = MozPromise<MediaCapabilitiesInfo, MediaResult,
    394                                         /* IsExclusive = */ true>;
    395  nsTArray<RefPtr<CapabilitiesPromise>> promises;
    396 
    397  RefPtr<TaskQueue> taskQueue =
    398      TaskQueue::Create(GetMediaThreadPool(MediaThreadType::PLATFORM_DECODER),
    399                        "MediaCapabilities::TaskQueue");
    400  for (auto&& config : tracks) {
    401    TrackInfo::TrackType type =
    402        config->IsVideo() ? TrackInfo::kVideoTrack : TrackInfo::kAudioTrack;
    403 
    404    MOZ_ASSERT(type == TrackInfo::kAudioTrack ||
    405                   videoContainer->ExtendedType().GetFramerate().isSome(),
    406               "framerate is a required member of VideoConfiguration");
    407 
    408    if (type == TrackInfo::kAudioTrack) {
    409      // There's no need to create an audio decoder has we only want to know if
    410      // such codec is supported. We do need to call the PDMFactory::Supports
    411      // API outside the main thread to get accurate results.
    412      promises.AppendElement(
    413          InvokeAsync(taskQueue, __func__, [config = std::move(config)]() {
    414            RefPtr<PDMFactory> pdm = new PDMFactory();
    415            SupportDecoderParams params{*config};
    416            if (pdm->Supports(params, nullptr /* decoder doctor */).isEmpty()) {
    417              return CapabilitiesPromise::CreateAndReject(NS_ERROR_FAILURE,
    418                                                          __func__);
    419            }
    420            MediaCapabilitiesDecodingInfo info;
    421            info.mSupported = true;
    422            info.mSmooth = true;
    423            info.mPowerEfficient = true;
    424            return CapabilitiesPromise::CreateAndResolve(std::move(info),
    425                                                         __func__);
    426          }));
    427      continue;
    428    }
    429 
    430    // On Windows, the MediaDataDecoder expects to be created on a thread
    431    // supporting MTA, which the main thread doesn't. So we use our task queue
    432    // to create such decoder and perform initialization.
    433 
    434    RefPtr<layers::KnowsCompositor> compositor = GetCompositor();
    435    float frameRate =
    436        static_cast<float>(videoContainer->ExtendedType().GetFramerate().ref());
    437    const bool shouldResistFingerprinting =
    438        mParent->ShouldResistFingerprinting(RFPTarget::MediaCapabilities);
    439 
    440    // clang-format off
    441    promises.AppendElement(InvokeAsync(
    442        taskQueue, __func__,
    443        [taskQueue, frameRate, shouldResistFingerprinting, compositor,
    444         config = std::move(config)]() mutable -> RefPtr<CapabilitiesPromise> {
    445          // MediaDataDecoder keeps a reference to the config object, so we must
    446          // keep it alive until the decoder has been shutdown.
    447          static Atomic<uint32_t> sTrackingIdCounter(0);
    448          TrackingId trackingId(TrackingId::Source::MediaCapabilities,
    449                                sTrackingIdCounter++,
    450                                TrackingId::TrackAcrossProcesses::Yes);
    451          CreateDecoderParams params{
    452              *config, compositor,
    453              CreateDecoderParams::VideoFrameRate(frameRate),
    454              TrackInfo::kVideoTrack, Some(std::move(trackingId))};
    455          // We want to ensure that all decoder's queries are occurring only
    456          // once at a time as it can quickly exhaust the system resources
    457          // otherwise.
    458          static RefPtr<AllocPolicy> sVideoAllocPolicy = [&taskQueue]() {
    459            SchedulerGroup::Dispatch(
    460                NS_NewRunnableFunction(
    461                    "MediaCapabilities::AllocPolicy:Video", []() {
    462                      ClearOnShutdown(&sVideoAllocPolicy,
    463                                      ShutdownPhase::XPCOMShutdownThreads);
    464                    }));
    465            return new SingleAllocPolicy(TrackInfo::TrackType::kVideoTrack,
    466                                         taskQueue);
    467          }();
    468          return AllocationWrapper::CreateDecoder(params, sVideoAllocPolicy)
    469              ->Then(
    470                  taskQueue, __func__,
    471                  [taskQueue, shouldResistFingerprinting,
    472                   config = std::move(config)](
    473                      AllocationWrapper::AllocateDecoderPromise::
    474                          ResolveOrRejectValue&& aValue) mutable {
    475                    if (aValue.IsReject()) {
    476                      return CapabilitiesPromise::CreateAndReject(
    477                          std::move(aValue.RejectValue()), __func__);
    478                    }
    479                    RefPtr<MediaDataDecoder> decoder =
    480                        std::move(aValue.ResolveValue());
    481                    // We now query the decoder to determine if it's power
    482                    // efficient.
    483                    RefPtr<CapabilitiesPromise> p = decoder->Init()->Then(
    484                        taskQueue, __func__,
    485                        [taskQueue, decoder,
    486                         shouldResistFingerprinting,
    487                         config = std::move(config)](
    488                            MediaDataDecoder::InitPromise::
    489                                ResolveOrRejectValue&& aValue) mutable {
    490                          RefPtr<CapabilitiesPromise> p;
    491                          if (aValue.IsReject()) {
    492                            p = CapabilitiesPromise::CreateAndReject(
    493                                std::move(aValue.RejectValue()), __func__);
    494                          } else if (shouldResistFingerprinting) {
    495                            MediaCapabilitiesDecodingInfo info;
    496                            info.mSupported = true;
    497                            info.mSmooth = true;
    498                            info.mPowerEfficient = false;
    499                            p = CapabilitiesPromise::CreateAndResolve(std::move(info), __func__);
    500                          } else {
    501                            MOZ_ASSERT(config->IsVideo());
    502                            if (config->GetAsVideoInfo()->mImage.height < 480) {
    503                              // Assume that we can do stuff at 480p or less in
    504                              // a power efficient manner and smoothly. If
    505                              // greater than 480p we assume that if the video
    506                              // decoding is hardware accelerated it will be
    507                              // smooth and power efficient, otherwise we use
    508                              // the benchmark to estimate
    509                              MediaCapabilitiesDecodingInfo info;
    510                              info.mSupported = true;
    511                              info.mSmooth = true;
    512                              info.mPowerEfficient = true;
    513                              p = CapabilitiesPromise::CreateAndResolve(std::move(info), __func__);
    514                            } else {
    515                              nsAutoCString reason;
    516                              bool smooth = true;
    517                              bool powerEfficient =
    518                                  decoder->IsHardwareAccelerated(reason);
    519                              MediaCapabilitiesDecodingInfo info;
    520                              info.mSupported = true;
    521                              info.mSmooth = smooth;
    522                              info.mPowerEfficient = powerEfficient;
    523                              p = CapabilitiesPromise::CreateAndResolve(std::move(info), __func__);
    524                            }
    525                          }
    526                          MOZ_ASSERT(p.get(), "the promise has been created");
    527                          // Let's keep alive the decoder and the config object
    528                          // until the decoder has shutdown.
    529                          decoder->Shutdown()->Then(
    530                              taskQueue, __func__,
    531                              [taskQueue, decoder, config = std::move(config)](
    532                                  const ShutdownPromise::ResolveOrRejectValue&
    533                                      aValue) {});
    534                          return p;
    535                        });
    536                    return p;
    537                  });
    538        }));
    539    // clang-format on
    540  }
    541 
    542  auto holder = MakeRefPtr<
    543      DOMMozPromiseRequestHolder<CapabilitiesPromise::AllPromiseType>>(mParent);
    544  RefPtr<nsISerialEventTarget> targetThread;
    545  RefPtr<StrongWorkerRef> workerRef;
    546 
    547  if (NS_IsMainThread()) {
    548    targetThread = GetMainThreadSerialEventTarget();
    549  } else {
    550    WorkerPrivate* wp = GetCurrentThreadWorkerPrivate();
    551    MOZ_ASSERT(wp, "Must be called from a worker thread");
    552    targetThread = wp->HybridEventTarget();
    553    workerRef = StrongWorkerRef::Create(
    554        wp, "MediaCapabilities", [holder, targetThread]() {
    555          MOZ_ASSERT(targetThread->IsOnCurrentThread());
    556          holder->DisconnectIfExists();
    557        });
    558    if (NS_WARN_IF(!workerRef)) {
    559      aPromise->MaybeRejectWithInvalidStateError("The worker is shutting down");
    560      return;
    561    }
    562  }
    563 
    564  MOZ_ASSERT(targetThread);
    565 
    566  // this is only captured for use with the LOG macro.
    567  RefPtr<MediaCapabilities> self = this;
    568 
    569  CapabilitiesPromise::All(targetThread, promises)
    570      ->Then(targetThread, __func__,
    571             [promise = RefPtr<Promise>{aPromise}, tracks = std::move(tracks),
    572              workerRef, holder, aConfiguration, self,
    573              this](CapabilitiesPromise::AllPromiseType::ResolveOrRejectValue&&
    574                        aValue) {
    575               holder->Complete();
    576               if (aValue.IsReject()) {
    577                 MediaCapabilitiesDecodingInfo info;
    578                 info.mSupported = false;
    579                 info.mSmooth = false;
    580                 info.mPowerEfficient = false;
    581                 LOG("%s -> %s",
    582                     MediaDecodingConfigurationToStr(aConfiguration).get(),
    583                     MediaCapabilitiesInfoToStr(info).get());
    584                 promise->MaybeResolve(std::move(info));
    585                 return;
    586               }
    587               bool powerEfficient = true;
    588               bool smooth = true;
    589               for (auto&& capability : aValue.ResolveValue()) {
    590                 smooth &= capability.mSmooth;
    591                 powerEfficient &= capability.mPowerEfficient;
    592               }
    593               MediaCapabilitiesDecodingInfo info;
    594               info.mSupported = true;
    595               info.mSmooth = smooth;
    596               info.mPowerEfficient = powerEfficient;
    597               LOG("%s -> %s",
    598                   MediaDecodingConfigurationToStr(aConfiguration).get(),
    599                   MediaCapabilitiesInfoToStr(info).get());
    600               promise->MaybeResolve(std::move(info));
    601             })
    602      ->Track(*holder);
    603 }
    604 
    605 // https://www.w3.org/TR/media-capabilities/#is-encrypted-decode-supported
    606 RefPtr<MediaKeySystemAccessManager::MediaKeySystemAccessPromise>
    607 MediaCapabilities::CheckEncryptedDecodingSupport(
    608    const MediaDecodingConfiguration& aConfiguration) {
    609  using MediaKeySystemAccessPromise =
    610      MediaKeySystemAccessManager::MediaKeySystemAccessPromise;
    611  auto* window = mParent->GetAsInnerWindow();
    612  if (NS_WARN_IF(!window)) {
    613    return MediaKeySystemAccessPromise::CreateAndReject(NS_ERROR_FAILURE,
    614                                                        __func__);
    615  }
    616 
    617  auto* manager = window->Navigator()->GetOrCreateMediaKeySystemAccessManager();
    618  if (NS_WARN_IF(!manager)) {
    619    return MediaKeySystemAccessPromise::CreateAndReject(NS_ERROR_FAILURE,
    620                                                        __func__);
    621  }
    622 
    623  // Let emeConfiguration be a new MediaKeySystemConfiguration, and initialize
    624  // it as follows
    625  Sequence<MediaKeySystemConfiguration> configs;
    626  auto* emeConfig = configs.AppendElement(fallible);
    627  if (NS_WARN_IF(!emeConfig)) {
    628    return MediaKeySystemAccessPromise::CreateAndReject(NS_ERROR_FAILURE,
    629                                                        __func__);
    630  }
    631 
    632  if (!MediaCapabilitiesKeySystemConfigurationToMediaKeySystemConfiguration(
    633          aConfiguration, *emeConfig)) {
    634    return MediaKeySystemAccessPromise::CreateAndReject(NS_ERROR_FAILURE,
    635                                                        __func__);
    636  }
    637  return manager->Request(
    638      aConfiguration.mKeySystemConfiguration.Value().mKeySystem, configs);
    639 }
    640 
    641 already_AddRefed<Promise> MediaCapabilities::EncodingInfo(
    642    const MediaEncodingConfiguration& aConfiguration, ErrorResult& aRv) {
    643  RefPtr<Promise> promise = Promise::Create(mParent, aRv);
    644  if (aRv.Failed()) {
    645    return nullptr;
    646  }
    647 
    648  // If configuration is not a valid MediaConfiguration, return a Promise
    649  // rejected with a TypeError.
    650  if (auto configCheck = IsValidMediaEncodingConfiguration(aConfiguration);
    651      configCheck.isErr()) {
    652    ThrowWithValidationResult(aRv, configCheck.unwrapErr());
    653    return nullptr;
    654  }
    655 
    656  bool supported = true;
    657 
    658  // If configuration.video is present and is not a valid video configuration,
    659  // return a Promise rejected with a TypeError.
    660  if (aConfiguration.mVideo.WasPassed()) {
    661    if (!CheckVideoConfiguration(aConfiguration.mVideo.Value())) {
    662      aRv.ThrowTypeError<MSG_INVALID_MEDIA_VIDEO_CONFIGURATION>();
    663      return nullptr;
    664    }
    665    // We have a video configuration and it is valid. Check if it is supported.
    666    supported &=
    667        CheckTypeForEncoder(aConfiguration.mVideo.Value().mContentType);
    668  }
    669  if (aConfiguration.mAudio.WasPassed()) {
    670    if (!CheckAudioConfiguration(aConfiguration.mAudio.Value())) {
    671      aRv.ThrowTypeError<MSG_INVALID_MEDIA_AUDIO_CONFIGURATION>();
    672      return nullptr;
    673    }
    674    // We have an audio configuration and it is valid. Check if it is supported.
    675    supported &=
    676        CheckTypeForEncoder(aConfiguration.mAudio.Value().mContentType);
    677  }
    678 
    679  MediaCapabilitiesInfo info;
    680  info.mSupported = supported;
    681  info.mSmooth = supported;
    682  info.mPowerEfficient = false;
    683  promise->MaybeResolve(std::move(info));
    684 
    685  return promise.forget();
    686 }
    687 
    688 Maybe<MediaContainerType> MediaCapabilities::CheckVideoConfiguration(
    689    const VideoConfiguration& aConfig) const {
    690  Maybe<MediaExtendedMIMEType> container = MakeMediaExtendedMIMEType(aConfig);
    691  if (!container) {
    692    return Nothing();
    693  }
    694  // A valid video MIME type is a string that is a valid media MIME type and for
    695  // which the type per [RFC7231] is either video or application.
    696  if (!container->Type().HasVideoMajorType() &&
    697      !container->Type().HasApplicationMajorType()) {
    698    return Nothing();
    699  }
    700 
    701  // If the MIME type does not imply a codec, the string MUST also have one and
    702  // only one parameter that is named codecs with a value describing a single
    703  // media codec. Otherwise, it MUST contain no parameters.
    704  // TODO (nsIMOMEHeaderParam doesn't provide backend to count number of
    705  // parameters)
    706 
    707  return Some(MediaContainerType(std::move(*container)));
    708 }
    709 
    710 Maybe<MediaContainerType> MediaCapabilities::CheckAudioConfiguration(
    711    const AudioConfiguration& aConfig) const {
    712  Maybe<MediaExtendedMIMEType> container = MakeMediaExtendedMIMEType(aConfig);
    713  if (!container) {
    714    return Nothing();
    715  }
    716  // A valid audio MIME type is a string that is valid media MIME type and for
    717  // which the type per [RFC7231] is either audio or application.
    718  if (!container->Type().HasAudioMajorType() &&
    719      !container->Type().HasApplicationMajorType()) {
    720    return Nothing();
    721  }
    722 
    723  // If the MIME type does not imply a codec, the string MUST also have one and
    724  // only one parameter that is named codecs with a value describing a single
    725  // media codec. Otherwise, it MUST contain no parameters.
    726  // TODO (nsIMOMEHeaderParam doesn't provide backend to count number of
    727  // parameters)
    728 
    729  return Some(MediaContainerType(std::move(*container)));
    730 }
    731 
    732 bool MediaCapabilities::CheckTypeForMediaSource(const nsAString& aType) {
    733  IgnoredErrorResult rv;
    734  MediaSource::IsTypeSupported(
    735      aType, nullptr /* DecoderDoctorDiagnostics */, rv,
    736      Some(mParent->ShouldResistFingerprinting(RFPTarget::MediaCapabilities)));
    737 
    738  return !rv.Failed();
    739 }
    740 
    741 bool MediaCapabilities::CheckTypeForFile(const nsAString& aType) {
    742  Maybe<MediaContainerType> containerType = MakeMediaContainerType(aType);
    743  if (!containerType) {
    744    return false;
    745  }
    746 
    747  return DecoderTraits::CanHandleContainerType(
    748             *containerType, nullptr /* DecoderDoctorDiagnostics */) !=
    749         CANPLAY_NO;
    750 }
    751 
    752 bool MediaCapabilities::CheckTypeForEncoder(const nsAString& aType) {
    753  return MediaRecorder::IsTypeSupported(aType);
    754 }
    755 
    756 already_AddRefed<layers::KnowsCompositor> MediaCapabilities::GetCompositor() {
    757  nsCOMPtr<nsPIDOMWindowInner> window = do_QueryInterface(GetParentObject());
    758  if (NS_WARN_IF(!window)) {
    759    return nullptr;
    760  }
    761 
    762  nsCOMPtr<Document> doc = window->GetExtantDoc();
    763  if (NS_WARN_IF(!doc)) {
    764    return nullptr;
    765  }
    766  WindowRenderer* renderer = nsContentUtils::WindowRendererForDocument(doc);
    767  if (NS_WARN_IF(!renderer)) {
    768    return nullptr;
    769  }
    770  RefPtr<layers::KnowsCompositor> knows = renderer->AsKnowsCompositor();
    771  if (NS_WARN_IF(!knows)) {
    772    return nullptr;
    773  }
    774  return knows->GetForMedia().forget();
    775 }
    776 
    777 JSObject* MediaCapabilities::WrapObject(JSContext* aCx,
    778                                        JS::Handle<JSObject*> aGivenProto) {
    779  return MediaCapabilities_Binding::Wrap(aCx, this, aGivenProto);
    780 }
    781 
    782 NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(MediaCapabilities)
    783  NS_WRAPPERCACHE_INTERFACE_MAP_ENTRY
    784  NS_INTERFACE_MAP_ENTRY(nsISupports)
    785 NS_INTERFACE_MAP_END
    786 
    787 NS_IMPL_CYCLE_COLLECTING_ADDREF(MediaCapabilities)
    788 NS_IMPL_CYCLE_COLLECTING_RELEASE(MediaCapabilities)
    789 
    790 NS_IMPL_CYCLE_COLLECTION_WRAPPERCACHE(MediaCapabilities, mParent)
    791 
    792 }  // namespace mozilla::dom
    793 #undef LOG