tor-browser

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

MediaRecorder.cpp (76898B)


      1 /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
      2 /* vim:set ts=2 sw=2 sts=2 et cindent: */
      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 "MediaRecorder.h"
      8 
      9 #include "AudioNodeEngine.h"
     10 #include "AudioNodeTrack.h"
     11 #include "DOMMediaStream.h"
     12 #include "MediaDecoder.h"
     13 #include "MediaEncoder.h"
     14 #include "MediaTrackGraph.h"
     15 #include "VideoUtils.h"
     16 #include "mozilla/DOMEventTargetHelper.h"
     17 #include "mozilla/DefineEnum.h"
     18 #include "mozilla/MemoryReporting.h"
     19 #include "mozilla/Preferences.h"
     20 #include "mozilla/StaticPtr.h"
     21 #include "mozilla/TaskQueue.h"
     22 #include "mozilla/ToString.h"
     23 #include "mozilla/dom/AudioStreamTrack.h"
     24 #include "mozilla/dom/BlobEvent.h"
     25 #include "mozilla/dom/Document.h"
     26 #include "mozilla/dom/EmptyBlobImpl.h"
     27 #include "mozilla/dom/File.h"
     28 #include "mozilla/dom/MediaRecorderErrorEvent.h"
     29 #include "mozilla/dom/VideoStreamTrack.h"
     30 #include "mozilla/glean/DomMediaMetrics.h"
     31 #include "mozilla/media/MediaUtils.h"
     32 #include "nsContentTypeParser.h"
     33 #include "nsContentUtils.h"
     34 #include "nsDocShell.h"
     35 #include "nsError.h"
     36 #include "nsGlobalWindowInner.h"
     37 #include "nsIPrincipal.h"
     38 #include "nsIScriptError.h"
     39 #include "nsMimeTypes.h"
     40 #include "nsProxyRelease.h"
     41 #include "nsServiceManagerUtils.h"
     42 #include "nsTArray.h"
     43 
     44 mozilla::LazyLogModule gMediaRecorderLog("MediaRecorder");
     45 #define LOG(type, msg) MOZ_LOG(gMediaRecorderLog, type, msg)
     46 
     47 constexpr int MIN_VIDEO_BITRATE_BPS = 10e3;        // 10kbps
     48 constexpr int DEFAULT_VIDEO_BITRATE_BPS = 2500e3;  // 2.5Mbps
     49 constexpr int MAX_VIDEO_BITRATE_BPS = 100e6;       // 100Mbps
     50 
     51 constexpr int MIN_AUDIO_BITRATE_BPS = 500;        // 500bps
     52 constexpr int DEFAULT_AUDIO_BITRATE_BPS = 128e3;  // 128kbps
     53 constexpr int MAX_AUDIO_BITRATE_BPS = 512e3;      // 512kbps
     54 
     55 namespace mozilla::dom {
     56 
     57 using namespace mozilla::media;
     58 
     59 /**
     60 * MediaRecorderReporter measures memory being used by the Media Recorder.
     61 *
     62 * It is a singleton reporter and the single class object lives as long as at
     63 * least one Recorder is registered. In MediaRecorder, the reporter is
     64 * unregistered when it is destroyed.
     65 */
     66 class MediaRecorderReporter final : public nsIMemoryReporter {
     67 public:
     68  static void AddMediaRecorder(MediaRecorder* aRecorder) {
     69    if (!sUniqueInstance) {
     70      sUniqueInstance = MakeAndAddRef<MediaRecorderReporter>();
     71      RegisterWeakAsyncMemoryReporter(sUniqueInstance);
     72    }
     73    sUniqueInstance->mRecorders.AppendElement(aRecorder);
     74  }
     75 
     76  static void RemoveMediaRecorder(MediaRecorder* aRecorder) {
     77    if (!sUniqueInstance) {
     78      return;
     79    }
     80 
     81    sUniqueInstance->mRecorders.RemoveElement(aRecorder);
     82    if (sUniqueInstance->mRecorders.IsEmpty()) {
     83      UnregisterWeakMemoryReporter(sUniqueInstance);
     84      sUniqueInstance = nullptr;
     85    }
     86  }
     87 
     88  NS_DECL_THREADSAFE_ISUPPORTS
     89 
     90  MediaRecorderReporter() = default;
     91 
     92  NS_IMETHOD
     93  CollectReports(nsIHandleReportCallback* aHandleReport, nsISupports* aData,
     94                 bool aAnonymize) override {
     95    nsTArray<RefPtr<MediaRecorder::SizeOfPromise>> promises;
     96    for (const RefPtr<MediaRecorder>& recorder : mRecorders) {
     97      promises.AppendElement(recorder->SizeOfExcludingThis(MallocSizeOf));
     98    }
     99 
    100    nsCOMPtr<nsIHandleReportCallback> handleReport = aHandleReport;
    101    nsCOMPtr<nsISupports> data = aData;
    102    MediaRecorder::SizeOfPromise::All(GetCurrentSerialEventTarget(), promises)
    103        ->Then(
    104            GetCurrentSerialEventTarget(), __func__,
    105            [handleReport, data](const nsTArray<size_t>& sizes) {
    106              nsCOMPtr<nsIMemoryReporterManager> manager =
    107                  do_GetService("@mozilla.org/memory-reporter-manager;1");
    108              if (!manager) {
    109                return;
    110              }
    111 
    112              size_t sum = 0;
    113              for (const size_t& size : sizes) {
    114                sum += size;
    115              }
    116 
    117              handleReport->Callback(""_ns, "explicit/media/recorder"_ns,
    118                                     KIND_HEAP, UNITS_BYTES, sum,
    119                                     "Memory used by media recorder."_ns, data);
    120 
    121              manager->EndReport();
    122            },
    123            [](size_t) { MOZ_CRASH("Unexpected reject"); });
    124 
    125    return NS_OK;
    126  }
    127 
    128 private:
    129  MOZ_DEFINE_MALLOC_SIZE_OF(MallocSizeOf)
    130 
    131  virtual ~MediaRecorderReporter() {
    132    MOZ_ASSERT(mRecorders.IsEmpty(), "All recorders must have been removed");
    133  }
    134 
    135  static StaticRefPtr<MediaRecorderReporter> sUniqueInstance;
    136 
    137  nsTArray<RefPtr<MediaRecorder>> mRecorders;
    138 };
    139 NS_IMPL_ISUPPORTS(MediaRecorderReporter, nsIMemoryReporter);
    140 
    141 NS_IMPL_CYCLE_COLLECTION_CLASS(MediaRecorder)
    142 
    143 NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN_INHERITED(MediaRecorder,
    144                                                  DOMEventTargetHelper)
    145  NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mStream)
    146  NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mAudioNode)
    147  NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mOtherDomException)
    148  NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mSecurityDomException)
    149  NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mUnknownDomException)
    150  NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mDocument)
    151 NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END
    152 
    153 NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN_INHERITED(MediaRecorder,
    154                                                DOMEventTargetHelper)
    155  NS_IMPL_CYCLE_COLLECTION_UNLINK(mStream)
    156  NS_IMPL_CYCLE_COLLECTION_UNLINK(mAudioNode)
    157  NS_IMPL_CYCLE_COLLECTION_UNLINK(mOtherDomException)
    158  NS_IMPL_CYCLE_COLLECTION_UNLINK(mSecurityDomException)
    159  NS_IMPL_CYCLE_COLLECTION_UNLINK(mUnknownDomException)
    160  tmp->UnRegisterActivityObserver();
    161  NS_IMPL_CYCLE_COLLECTION_UNLINK(mDocument)
    162 NS_IMPL_CYCLE_COLLECTION_UNLINK_END
    163 
    164 NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(MediaRecorder)
    165  NS_INTERFACE_MAP_ENTRY(nsIDocumentActivity)
    166 NS_INTERFACE_MAP_END_INHERITING(DOMEventTargetHelper)
    167 
    168 NS_IMPL_ADDREF_INHERITED(MediaRecorder, DOMEventTargetHelper)
    169 NS_IMPL_RELEASE_INHERITED(MediaRecorder, DOMEventTargetHelper)
    170 
    171 namespace {
    172 bool PrincipalSubsumes(MediaRecorder* aRecorder, nsIPrincipal* aPrincipal) {
    173  if (!aRecorder->GetOwnerWindow()) {
    174    return false;
    175  }
    176  nsCOMPtr<Document> doc = aRecorder->GetOwnerWindow()->GetExtantDoc();
    177  if (!doc) {
    178    return false;
    179  }
    180  if (!aPrincipal) {
    181    return false;
    182  }
    183  bool subsumes;
    184  if (NS_FAILED(doc->NodePrincipal()->Subsumes(aPrincipal, &subsumes))) {
    185    return false;
    186  }
    187  return subsumes;
    188 }
    189 
    190 bool MediaStreamTracksPrincipalSubsumes(
    191    MediaRecorder* aRecorder,
    192    const nsTArray<RefPtr<MediaStreamTrack>>& aTracks) {
    193  nsCOMPtr<nsIPrincipal> principal = nullptr;
    194  for (const auto& track : aTracks) {
    195    nsContentUtils::CombineResourcePrincipals(&principal,
    196                                              track->GetPrincipal());
    197  }
    198  return PrincipalSubsumes(aRecorder, principal);
    199 }
    200 
    201 bool AudioNodePrincipalSubsumes(MediaRecorder* aRecorder,
    202                                AudioNode* aAudioNode) {
    203  MOZ_ASSERT(aAudioNode);
    204  Document* doc = aAudioNode->GetOwnerWindow()
    205                      ? aAudioNode->GetOwnerWindow()->GetExtantDoc()
    206                      : nullptr;
    207  nsCOMPtr<nsIPrincipal> principal = doc ? doc->NodePrincipal() : nullptr;
    208  return PrincipalSubsumes(aRecorder, principal);
    209 }
    210 
    211 // This list is sorted so that lesser failures are later, so that
    212 // IsTypeSupportedImpl() can report the error from audio or video types that
    213 // is closer to being supported.
    214 enum class TypeSupport {
    215  MediaTypeInvalid,
    216  NoVideoWithAudioType,
    217  ContainersDisabled,
    218  CodecsDisabled,
    219  ContainerUnsupported,
    220  CodecUnsupported,
    221  CodecDuplicated,
    222  Supported,
    223 };
    224 
    225 nsCString TypeSupportToCString(TypeSupport aSupport,
    226                               const nsAString& aMimeType) {
    227  nsAutoCString mime = NS_ConvertUTF16toUTF8(aMimeType);
    228  switch (aSupport) {
    229    case TypeSupport::Supported:
    230      return nsPrintfCString("%s is supported", mime.get());
    231    case TypeSupport::MediaTypeInvalid:
    232      return nsPrintfCString("%s is not a valid media type", mime.get());
    233    case TypeSupport::NoVideoWithAudioType:
    234      return nsPrintfCString(
    235          "Video cannot be recorded with %s as it is an audio type",
    236          mime.get());
    237    case TypeSupport::ContainersDisabled:
    238      return "All containers are disabled"_ns;
    239    case TypeSupport::CodecsDisabled:
    240      return "All codecs are disabled"_ns;
    241    case TypeSupport::ContainerUnsupported:
    242      return nsPrintfCString("%s indicates an unsupported container",
    243                             mime.get());
    244    case TypeSupport::CodecUnsupported:
    245      return nsPrintfCString("%s indicates an unsupported codec", mime.get());
    246    case TypeSupport::CodecDuplicated:
    247      return nsPrintfCString("%s contains the same codec multiple times",
    248                             mime.get());
    249    default:
    250      MOZ_ASSERT_UNREACHABLE("Unknown TypeSupport");
    251      return "Unknown error"_ns;
    252  }
    253 }
    254 
    255 TypeSupport CanRecordAudioTrackWith(const Maybe<MediaContainerType>& aMimeType,
    256                                    const nsAString& aMimeTypeString) {
    257  if (aMimeTypeString.IsEmpty()) {
    258    // For the empty string we just need to check whether we have support for an
    259    // audio container and an audio codec.
    260    if (!MediaEncoder::IsWebMEncoderEnabled() &&
    261        !MediaDecoder::IsOggEnabled()) {
    262      // No container support for audio.
    263      return TypeSupport::ContainersDisabled;
    264    }
    265 
    266    if (!MediaDecoder::IsOpusEnabled()) {
    267      // No codec support for audio.
    268      return TypeSupport::CodecsDisabled;
    269    }
    270 
    271    return TypeSupport::Supported;
    272  }
    273 
    274  if (!aMimeType) {
    275    // A mime type string was set, but it couldn't be parsed to a valid
    276    // MediaContainerType.
    277    return TypeSupport::MediaTypeInvalid;
    278  }
    279 
    280  if (aMimeType->Type() != MEDIAMIMETYPE(VIDEO_WEBM) &&
    281      aMimeType->Type() != MEDIAMIMETYPE(AUDIO_WEBM) &&
    282      aMimeType->Type() != MEDIAMIMETYPE(AUDIO_OGG)) {
    283    // Any currently supported container can record audio.
    284    return TypeSupport::ContainerUnsupported;
    285  }
    286 
    287  if (aMimeType->Type() == MEDIAMIMETYPE(VIDEO_WEBM) &&
    288      !MediaEncoder::IsWebMEncoderEnabled()) {
    289    return TypeSupport::ContainerUnsupported;
    290  }
    291 
    292  if (aMimeType->Type() == MEDIAMIMETYPE(AUDIO_WEBM) &&
    293      !MediaEncoder::IsWebMEncoderEnabled()) {
    294    return TypeSupport::ContainerUnsupported;
    295  }
    296 
    297  if (aMimeType->Type() == MEDIAMIMETYPE(AUDIO_OGG) &&
    298      !MediaDecoder::IsOggEnabled()) {
    299    return TypeSupport::ContainerUnsupported;
    300  }
    301 
    302  if (!MediaDecoder::IsOpusEnabled()) {
    303    return TypeSupport::CodecUnsupported;
    304  }
    305 
    306  if (!aMimeType->ExtendedType().HaveCodecs()) {
    307    // No codecs constrained, we can pick opus.
    308    return TypeSupport::Supported;
    309  }
    310 
    311  size_t opus = 0;
    312  size_t unknown = 0;
    313  for (const auto& codec : aMimeType->ExtendedType().Codecs().Range()) {
    314    // Ignore video codecs.
    315    if (codec.EqualsLiteral("vp8")) {
    316      continue;
    317    }
    318    if (codec.EqualsLiteral("vp8.0")) {
    319      continue;
    320    }
    321    if (codec.EqualsLiteral("opus")) {
    322      // All containers support opus
    323      opus++;
    324      continue;
    325    }
    326    unknown++;
    327  }
    328 
    329  if (unknown > 0) {
    330    // Unsupported codec.
    331    return TypeSupport::CodecUnsupported;
    332  }
    333 
    334  if (opus == 0) {
    335    // Codecs specified but not opus. Unsupported for audio.
    336    return TypeSupport::CodecUnsupported;
    337  }
    338 
    339  if (opus > 1) {
    340    // Opus specified more than once. Bad form.
    341    return TypeSupport::CodecDuplicated;
    342  }
    343 
    344  return TypeSupport::Supported;
    345 }
    346 
    347 TypeSupport CanRecordVideoTrackWith(const Maybe<MediaContainerType>& aMimeType,
    348                                    const nsAString& aMimeTypeString) {
    349  if (aMimeTypeString.IsEmpty()) {
    350    // For the empty string we just need to check whether we have support for a
    351    // video container and a video codec. The VP8 encoder is always available.
    352    if (!MediaEncoder::IsWebMEncoderEnabled()) {
    353      // No container support for video.
    354      return TypeSupport::ContainersDisabled;
    355    }
    356 
    357    return TypeSupport::Supported;
    358  }
    359 
    360  if (!aMimeType) {
    361    // A mime type string was set, but it couldn't be parsed to a valid
    362    // MediaContainerType.
    363    return TypeSupport::MediaTypeInvalid;
    364  }
    365 
    366  if (!aMimeType->Type().HasVideoMajorType()) {
    367    return TypeSupport::NoVideoWithAudioType;
    368  }
    369 
    370  if (aMimeType->Type() != MEDIAMIMETYPE(VIDEO_WEBM)) {
    371    return TypeSupport::ContainerUnsupported;
    372  }
    373 
    374  if (!MediaEncoder::IsWebMEncoderEnabled()) {
    375    return TypeSupport::ContainerUnsupported;
    376  }
    377 
    378  if (!aMimeType->ExtendedType().HaveCodecs()) {
    379    // No codecs constrained, we can pick vp8.
    380    return TypeSupport::Supported;
    381  }
    382 
    383  size_t vp8 = 0;
    384  size_t unknown = 0;
    385  for (const auto& codec : aMimeType->ExtendedType().Codecs().Range()) {
    386    if (codec.EqualsLiteral("opus")) {
    387      // Ignore audio codecs.
    388      continue;
    389    }
    390    if (codec.EqualsLiteral("vp8")) {
    391      vp8++;
    392      continue;
    393    }
    394    if (codec.EqualsLiteral("vp8.0")) {
    395      vp8++;
    396      continue;
    397    }
    398    unknown++;
    399  }
    400 
    401  if (unknown > 0) {
    402    // Unsupported codec.
    403    return TypeSupport::CodecUnsupported;
    404  }
    405 
    406  if (vp8 == 0) {
    407    // Codecs specified but not vp8. Unsupported for video.
    408    return TypeSupport::CodecUnsupported;
    409  }
    410 
    411  if (vp8 > 1) {
    412    // Vp8 specified more than once. Bad form.
    413    return TypeSupport::CodecDuplicated;
    414  }
    415 
    416  return TypeSupport::Supported;
    417 }
    418 
    419 TypeSupport CanRecordWith(MediaStreamTrack* aTrack,
    420                          const Maybe<MediaContainerType>& aMimeType,
    421                          const nsAString& aMimeTypeString) {
    422  if (aTrack->AsAudioStreamTrack()) {
    423    return CanRecordAudioTrackWith(aMimeType, aMimeTypeString);
    424  }
    425 
    426  if (aTrack->AsVideoStreamTrack()) {
    427    return CanRecordVideoTrackWith(aMimeType, aMimeTypeString);
    428  }
    429 
    430  MOZ_CRASH("Unexpected track type");
    431 }
    432 
    433 struct ParsedMIMEType {
    434  MOZ_DEFINE_ENUM_CLASS_WITH_TOSTRING_AT_CLASS_SCOPE(MediaType,
    435                                                     (Audio, Video, Unknown));
    436  MediaType mMediaType = MediaType::Unknown;
    437  MOZ_DEFINE_ENUM_CLASS_WITH_TOSTRING_AT_CLASS_SCOPE(Container, (MP4, MKV, WebM,
    438                                                                 Ogg, Unknown));
    439  Container mContainer = Container::Unknown;
    440  nsTArray<CodecType> mCodecs;
    441 };
    442 
    443 constexpr std::array<std::array<CodecType, 5>, 5> kValidAudioCodecs = {{
    444    // MP4
    445    {{CodecType::AAC, CodecType::Flac, CodecType::Opus}},
    446    // MKV
    447    {{CodecType::AAC, CodecType::Flac, CodecType::Opus, CodecType::PCM,
    448      CodecType::Vorbis}},
    449    // WebM
    450    {{CodecType::Opus, CodecType::Vorbis}},
    451    // Ogg
    452    {{CodecType::Flac, CodecType::Opus, CodecType::Vorbis}},
    453    // Unknown
    454    {{}},
    455 }};
    456 
    457 constexpr std::array<std::array<CodecType, 5>, 5> kValidVideoOnlyCodecs = {{
    458    // MP4
    459    {{CodecType::AV1, CodecType::H264, CodecType::H265, CodecType::VP9}},
    460    // MKV
    461    {{CodecType::AV1, CodecType::H264, CodecType::H265, CodecType::VP8,
    462      CodecType::VP9}},
    463    // WebM
    464    {{CodecType::AV1, CodecType::VP8, CodecType::VP9}},
    465    // Ogg
    466    {{CodecType::VP8, CodecType::VP9}},
    467    // Unknown
    468    {{}},
    469 }};
    470 
    471 constexpr auto kValidContainerCodecPairs = []() constexpr {
    472  std::array<
    473      std::array<std::array<CodecType, UnderlyingValue(kHighestCodecType) + 1>,
    474                 UnderlyingValue(ParsedMIMEType::sHighestContainer) + 1>,
    475      UnderlyingValue(ParsedMIMEType::sHighestMediaType) + 1>
    476      result{};
    477 
    478  // Generate valid audio container-codec pairs
    479  for (size_t c = 0; c < kValidAudioCodecs.size(); ++c) {
    480    for (size_t i = 0;
    481         i < kValidAudioCodecs[c].size() && IsAudio(kValidAudioCodecs[c][i]);
    482         ++i) {
    483      result[UnderlyingValue(ParsedMIMEType::MediaType::Audio)][c][i] =
    484          kValidAudioCodecs[c][i];
    485    }
    486  }
    487 
    488  // Generate valid video container-codec pairs
    489  for (size_t c = 0; c < kValidVideoOnlyCodecs.size(); ++c) {
    490    size_t k = 0;
    491    // Add video-only codecs
    492    for (size_t i = 0; i < kValidVideoOnlyCodecs[c].size() &&
    493                       IsVideo(kValidVideoOnlyCodecs[c][i]);
    494         ++i) {
    495      result[UnderlyingValue(ParsedMIMEType::MediaType::Video)][c][k++] =
    496          kValidVideoOnlyCodecs[c][i];
    497    }
    498    // Add audio-only codecs
    499    for (size_t i = 0;
    500         i < kValidAudioCodecs[c].size() && IsAudio(kValidAudioCodecs[c][i]);
    501         ++i) {
    502      result[UnderlyingValue(ParsedMIMEType::MediaType::Video)][c][k++] =
    503          kValidAudioCodecs[c][i];
    504    }
    505  }
    506 
    507  return result;
    508 }();
    509 
    510 static ParsedMIMEType::Container GetContainerFromMimeType(
    511    const MediaMIMEType& aType) {
    512  if (aType == MEDIAMIMETYPE(VIDEO_MP4) || aType == MEDIAMIMETYPE(AUDIO_MP4)) {
    513    return ParsedMIMEType::Container::MP4;
    514  }
    515  if (aType == MEDIAMIMETYPE(VIDEO_MATROSKA) ||
    516      aType == MEDIAMIMETYPE(VIDEO_MATROSKA_LEGACY) ||
    517      aType == MEDIAMIMETYPE(AUDIO_MATROSKA) ||
    518      aType == MEDIAMIMETYPE(AUDIO_MATROSKA_LEGACY)) {
    519    return ParsedMIMEType::Container::MKV;
    520  }
    521  if (aType == MEDIAMIMETYPE(VIDEO_WEBM) ||
    522      aType == MEDIAMIMETYPE(AUDIO_WEBM)) {
    523    return ParsedMIMEType::Container::WebM;
    524  }
    525  if (aType == MEDIAMIMETYPE(VIDEO_OGG) || aType == MEDIAMIMETYPE(AUDIO_OGG)) {
    526    return ParsedMIMEType::Container::Ogg;
    527  }
    528  return ParsedMIMEType::Container::Unknown;
    529 }
    530 
    531 static CodecType GetCodecTypeFromString(const nsAString& aCodec) {
    532  if (IsVP8CodecString(aCodec)) {
    533    return CodecType::VP8;
    534  }
    535  if (IsVP9CodecString(aCodec)) {
    536    return CodecType::VP9;
    537  }
    538  if (IsAV1CodecString(aCodec)) {
    539    return CodecType::AV1;
    540  }
    541  if (IsH264CodecString(aCodec)) {
    542    return CodecType::H264;
    543  }
    544  if (IsH265CodecString(aCodec)) {
    545    return CodecType::H265;
    546  }
    547  if (IsAACCodecString(aCodec)) {
    548    return CodecType::AAC;
    549  }
    550  if (aCodec.EqualsLiteral("flac")) {
    551    return CodecType::Flac;
    552  }
    553  if (aCodec.EqualsLiteral("pcm")) {
    554    return CodecType::PCM;
    555  }
    556  if (aCodec.EqualsLiteral("opus")) {
    557    return CodecType::Opus;
    558  }
    559  if (aCodec.EqualsLiteral("vorbis")) {
    560    return CodecType::Vorbis;
    561  }
    562  return CodecType::Unknown;
    563 }
    564 
    565 static ParsedMIMEType ParseMimeType(const Maybe<MediaContainerType>& aType) {
    566  ParsedMIMEType result;
    567 
    568  if (!aType) {
    569    return result;
    570  }
    571 
    572  result.mMediaType = [&] {
    573    if (aType->Type().HasAudioMajorType()) {
    574      return ParsedMIMEType::MediaType::Audio;
    575    }
    576    if (aType->Type().HasVideoMajorType()) {
    577      return ParsedMIMEType::MediaType::Video;
    578    }
    579    return ParsedMIMEType::MediaType::Unknown;
    580  }();
    581  result.mContainer = GetContainerFromMimeType(aType->Type());
    582  for (const auto& codec : aType->ExtendedType().Codecs().Range()) {
    583    result.mCodecs.AppendElement(GetCodecTypeFromString(codec));
    584  }
    585  return result;
    586 }
    587 
    588 static bool IsValidContainerCodecPair(ParsedMIMEType::MediaType aMediaType,
    589                                      ParsedMIMEType::Container aContainer,
    590                                      CodecType aCodec) {
    591  const auto& validCodecs =
    592      kValidContainerCodecPairs[UnderlyingValue(aMediaType)]
    593                               [UnderlyingValue(aContainer)];
    594  return std::find(validCodecs.begin(), validCodecs.end(), aCodec) !=
    595         validCodecs.end();
    596 }
    597 
    598 static nsTArray<nsCString> GetMIMELabelStrings(const ParsedMIMEType& aType) {
    599  nsTArray<nsCString> labels;
    600  if (aType.mContainer == ParsedMIMEType::Container::Unknown ||
    601      aType.mMediaType == ParsedMIMEType::MediaType::Unknown) {
    602    labels.AppendElement("others"_ns);
    603    return labels;
    604  }
    605  nsCString baseLabel(ParsedMIMEType::EnumValueToString(aType.mContainer));
    606  ToLowerCase(baseLabel);
    607  if (aType.mCodecs.IsEmpty()) {
    608    nsCString label = baseLabel;
    609    label.AppendLiteral("_unspecified");
    610    labels.AppendElement(label);
    611    return labels;
    612  }
    613  for (const auto& codec : aType.mCodecs) {
    614    nsCString label = baseLabel;
    615    if (IsValidContainerCodecPair(aType.mMediaType, aType.mContainer, codec)) {
    616      label.AppendLiteral("_");
    617      label.Append(EnumValueToString(codec));
    618      ToLowerCase(label);
    619    } else {
    620      label.AppendLiteral("_others");
    621    }
    622    LOG(LogLevel::Verbose,
    623        ("GetMIMELabelStrings: type: %s, container: %s, codec: %s => label: %s",
    624         ToString(aType.mMediaType).c_str(), ToString(aType.mContainer).c_str(),
    625         ToString(codec).c_str(), label.get()));
    626    labels.AppendElement(label);
    627  }
    628  return labels;
    629 }
    630 
    631 // The primary goal is to measure how frequently the MP4 container is requested,
    632 // while also collecting data on other container/codec combinations as a
    633 // secondary benefit.
    634 static void RecordQueriedMIMEType(const Maybe<MediaContainerType>& aMimeType,
    635                                  const nsAString& aMimeTypeString) {
    636  LOG(LogLevel::Verbose, ("RecordQueriedMIMEType: %s",
    637                          NS_ConvertUTF16toUTF8(aMimeTypeString).get()));
    638  if (aMimeTypeString.IsEmpty()) {
    639    LOG(LogLevel::Verbose, ("MIME queried is empty"));
    640    glean::media_recorder::mime_type_query.Get("empty"_ns).Add(1);
    641    return;
    642  }
    643  ParsedMIMEType aType = ParseMimeType(aMimeType);
    644  nsTArray<nsCString> labels = GetMIMELabelStrings(aType);
    645  for (const auto& label : labels) {
    646    LOG(LogLevel::Verbose, ("MIME queried: %s", label.get()));
    647    glean::media_recorder::mime_type_query.Get(label).Add(1);
    648  }
    649 }
    650 
    651 TypeSupport IsTypeSupportedImpl(const nsAString& aMIMEType) {
    652  if (aMIMEType.IsEmpty()) {
    653    RecordQueriedMIMEType(Nothing(), aMIMEType);
    654    // Lie and return true even if no container/codec support is enabled,
    655    // because the spec mandates it.
    656    return TypeSupport::Supported;
    657  }
    658  Maybe<MediaContainerType> mime = MakeMediaContainerType(aMIMEType);
    659  RecordQueriedMIMEType(mime, aMIMEType);
    660  TypeSupport audioSupport = CanRecordAudioTrackWith(mime, aMIMEType);
    661  TypeSupport videoSupport = CanRecordVideoTrackWith(mime, aMIMEType);
    662  return std::max(audioSupport, videoSupport);
    663 }
    664 
    665 nsString SelectMimeType(bool aHasVideo, bool aHasAudio,
    666                        const nsString& aConstrainedMimeType) {
    667  MOZ_ASSERT(aHasVideo || aHasAudio);
    668 
    669  Maybe<MediaContainerType> constrainedType =
    670      MakeMediaContainerType(aConstrainedMimeType);
    671 
    672  // If we are recording video, Start() should have rejected any non-video mime
    673  // types.
    674  MOZ_ASSERT_IF(constrainedType && aHasVideo,
    675                constrainedType->Type().HasVideoMajorType());
    676  // IsTypeSupported() rejects application mime types.
    677  MOZ_ASSERT_IF(constrainedType,
    678                !constrainedType->Type().HasApplicationMajorType());
    679 
    680  nsString result;
    681  if (constrainedType && constrainedType->ExtendedType().HaveCodecs()) {
    682    // The constrained mime type is fully defined (it has codecs!). No need to
    683    // select anything.
    684    CopyUTF8toUTF16(constrainedType->OriginalString(), result);
    685  } else {
    686    // There is no constrained mime type, or there is and it is not fully
    687    // defined but still valid. Select what's missing, so that we have major
    688    // type, container and codecs.
    689 
    690    // If there is a constrained mime type it should not have codecs defined,
    691    // because then it is fully defined and used unchanged (covered earlier).
    692    MOZ_ASSERT_IF(constrainedType,
    693                  !constrainedType->ExtendedType().HaveCodecs());
    694 
    695    nsCString majorType;
    696    {
    697      if (constrainedType) {
    698        // There is a constrained type. It has both major type and container in
    699        // order to be valid. Use them as is.
    700        majorType = constrainedType->Type().AsString();
    701      } else if (aHasVideo) {
    702        majorType = nsLiteralCString(VIDEO_WEBM);
    703      } else {
    704        majorType = nsLiteralCString(AUDIO_OGG);
    705      }
    706    }
    707 
    708    nsCString codecs;
    709    {
    710      if (aHasVideo && aHasAudio) {
    711        codecs = "\"vp8, opus\""_ns;
    712      } else if (aHasVideo) {
    713        codecs = "vp8"_ns;
    714      } else {
    715        codecs = "opus"_ns;
    716      }
    717    }
    718    result = NS_ConvertUTF8toUTF16(
    719        nsPrintfCString("%s; codecs=%s", majorType.get(), codecs.get()));
    720  }
    721 
    722  MOZ_ASSERT_IF(aHasAudio,
    723                CanRecordAudioTrackWith(MakeMediaContainerType(result),
    724                                        result) == TypeSupport::Supported);
    725  MOZ_ASSERT_IF(aHasVideo,
    726                CanRecordVideoTrackWith(MakeMediaContainerType(result),
    727                                        result) == TypeSupport::Supported);
    728  return result;
    729 }
    730 
    731 void SelectBitrates(uint32_t aBitsPerSecond, uint8_t aNumVideoTracks,
    732                    uint32_t* aOutVideoBps, uint8_t aNumAudioTracks,
    733                    uint32_t* aOutAudioBps) {
    734  uint32_t vbps = 0;
    735  uint32_t abps = 0;
    736 
    737  const uint32_t minVideoBps = MIN_VIDEO_BITRATE_BPS * aNumVideoTracks;
    738  const uint32_t maxVideoBps = MAX_VIDEO_BITRATE_BPS * aNumVideoTracks;
    739 
    740  const uint32_t minAudioBps = MIN_AUDIO_BITRATE_BPS * aNumAudioTracks;
    741  const uint32_t maxAudioBps = MAX_AUDIO_BITRATE_BPS * aNumAudioTracks;
    742 
    743  if (aNumVideoTracks == 0) {
    744    MOZ_DIAGNOSTIC_ASSERT(aNumAudioTracks > 0);
    745    abps = std::min(maxAudioBps, std::max(minAudioBps, aBitsPerSecond));
    746  } else if (aNumAudioTracks == 0) {
    747    vbps = std::min(maxVideoBps, std::max(minVideoBps, aBitsPerSecond));
    748  } else {
    749    // Scale the bits so that video gets 20 times the bits of audio.
    750    // Since we must account for varying number of tracks of each type we weight
    751    // them by type; video = weight 20, audio = weight 1.
    752    const uint32_t videoWeight = aNumVideoTracks * 20;
    753    const uint32_t audioWeight = aNumAudioTracks;
    754    const uint32_t totalWeights = audioWeight + videoWeight;
    755    const uint32_t videoBitrate =
    756        uint64_t(aBitsPerSecond) * videoWeight / totalWeights;
    757    const uint32_t audioBitrate =
    758        uint64_t(aBitsPerSecond) * audioWeight / totalWeights;
    759    vbps = std::min(maxVideoBps, std::max(minVideoBps, videoBitrate));
    760    abps = std::min(maxAudioBps, std::max(minAudioBps, audioBitrate));
    761  }
    762 
    763  *aOutVideoBps = vbps;
    764  *aOutAudioBps = abps;
    765 }
    766 }  // namespace
    767 
    768 /**
    769 * Session is an object to represent a single recording event.
    770 * In original design, all recording context is stored in MediaRecorder, which
    771 * causes a problem if someone calls MediaRecorder::Stop and
    772 * MediaRecorder::Start quickly. To prevent blocking main thread, media encoding
    773 * is executed in a second thread, named encoder thread. For the same reason, we
    774 * do not await encoder thread shutdown in MediaRecorder::Stop.
    775 * If someone calls MediaRecorder::Start before encoder thread shutdown, the
    776 * same recording context in MediaRecorder might be accessed by two distinct
    777 * encoder threads, which would be racy. With the recording context, including
    778 * the encoder thread, in a Session object the problem is solved.
    779 *
    780 * Lifetime of MediaRecorder and Session objects.
    781 * 1) MediaRecorder creates a Session in MediaRecorder::Start() and holds
    782 *    a reference to it. Then the Session registers itself to a ShutdownBlocker
    783 *    and also holds a reference to MediaRecorder.
    784 *    Therefore, the reference dependency in gecko is:
    785 *    ShutdownBlocker -> Session <-> MediaRecorder, note that there is a cycle
    786 *    reference between Session and MediaRecorder.
    787 * 2) A Session is destroyed after Session::DoSessionEndTask() has been called
    788 *    _and_ all encoded media data has been passed to OnDataAvailable handler.
    789 *    In some cases the encoded media can be discarded before being passed to
    790 *    the OnDataAvailable handler.
    791 * 3) Session::DoSessionEndTask is called by an application through
    792 *    MediaRecorder::Stop(), from a MediaEncoder Shutdown notification, from the
    793 *    document going inactive or invisible, or from the ShutdownBlocker.
    794 */
    795 class MediaRecorder::Session : public PrincipalChangeObserver<MediaStreamTrack>,
    796                               public DOMMediaStream::TrackListener {
    797  NS_DECL_ISUPPORTS_INHERITED
    798  NS_DECL_CYCLE_COLLECTION_CLASS_INHERITED(Session,
    799                                           DOMMediaStream::TrackListener)
    800 
    801  struct TrackTypeComparator {
    802    enum Type {
    803      AUDIO,
    804      VIDEO,
    805    };
    806    static bool Equals(const RefPtr<MediaStreamTrack>& aTrack, Type aType) {
    807      return (aType == AUDIO && aTrack->AsAudioStreamTrack()) ||
    808             (aType == VIDEO && aTrack->AsVideoStreamTrack());
    809    }
    810  };
    811 
    812 public:
    813  Session(MediaRecorder* aRecorder,
    814          nsTArray<RefPtr<MediaStreamTrack>> aMediaStreamTracks,
    815          uint32_t aVideoBitsPerSecond, uint32_t aAudioBitsPerSecond)
    816      : mRecorder(aRecorder),
    817        mMediaStreamTracks(std::move(aMediaStreamTracks)),
    818        mMimeType(SelectMimeType(
    819            mMediaStreamTracks.Contains(TrackTypeComparator::VIDEO,
    820                                        TrackTypeComparator()),
    821            mRecorder->mAudioNode ||
    822                mMediaStreamTracks.Contains(TrackTypeComparator::AUDIO,
    823                                            TrackTypeComparator()),
    824            mRecorder->mConstrainedMimeType)),
    825        mVideoBitsPerSecond(aVideoBitsPerSecond),
    826        mAudioBitsPerSecond(aAudioBitsPerSecond),
    827        mRunningState(RunningState::Idling) {
    828    MOZ_ASSERT(NS_IsMainThread());
    829  }
    830 
    831  void PrincipalChanged(MediaStreamTrack* aTrack) override {
    832    NS_ASSERTION(mMediaStreamTracks.Contains(aTrack),
    833                 "Principal changed for unrecorded track");
    834    if (!MediaStreamTracksPrincipalSubsumes(mRecorder, mMediaStreamTracks)) {
    835      DoSessionEndTask(NS_ERROR_DOM_SECURITY_ERR);
    836    }
    837  }
    838 
    839  void NotifyTrackAdded(const RefPtr<MediaStreamTrack>& aTrack) override {
    840    LOG(LogLevel::Warning,
    841        ("Session.NotifyTrackAdded %p Raising error due to track set change",
    842         this));
    843    // There's a chance we have a sensible JS stack here.
    844    if (!mRecorder->mOtherDomException) {
    845      mRecorder->mOtherDomException = DOMException::Create(
    846          NS_ERROR_DOM_INVALID_MODIFICATION_ERR,
    847          "An attempt was made to add a track to the recorded MediaStream "
    848          "during the recording"_ns);
    849    }
    850    DoSessionEndTask(NS_ERROR_DOM_INVALID_MODIFICATION_ERR);
    851  }
    852 
    853  void NotifyTrackRemoved(const RefPtr<MediaStreamTrack>& aTrack) override {
    854    if (aTrack->Ended()) {
    855      // TrackEncoder will pickup tracks that end itself.
    856      return;
    857    }
    858    LOG(LogLevel::Warning,
    859        ("Session.NotifyTrackRemoved %p Raising error due to track set change",
    860         this));
    861    // There's a chance we have a sensible JS stack here.
    862    if (!mRecorder->mOtherDomException) {
    863      mRecorder->mOtherDomException = DOMException::Create(
    864          NS_ERROR_DOM_INVALID_MODIFICATION_ERR,
    865          "An attempt was made to remove a track from the recorded MediaStream "
    866          "during the recording"_ns);
    867    }
    868    DoSessionEndTask(NS_ERROR_DOM_INVALID_MODIFICATION_ERR);
    869  }
    870 
    871  void Start(TimeDuration aTimeslice) {
    872    LOG(LogLevel::Debug, ("Session.Start %p", this));
    873    MOZ_ASSERT(NS_IsMainThread());
    874 
    875    if (mRecorder->mStream) {
    876      // The TrackListener reports back when tracks are added or removed from
    877      // the MediaStream.
    878      mMediaStream = mRecorder->mStream;
    879      mMediaStream->RegisterTrackListener(this);
    880 
    881      uint8_t trackTypes = 0;
    882      for (const auto& track : mMediaStreamTracks) {
    883        if (track->AsAudioStreamTrack()) {
    884          trackTypes |= ContainerWriter::CREATE_AUDIO_TRACK;
    885        } else if (track->AsVideoStreamTrack()) {
    886          trackTypes |= ContainerWriter::CREATE_VIDEO_TRACK;
    887        } else {
    888          MOZ_CRASH("Unexpected track type");
    889        }
    890      }
    891 
    892      for (const auto& t : mMediaStreamTracks) {
    893        t->AddPrincipalChangeObserver(this);
    894      }
    895 
    896      LOG(LogLevel::Debug, ("Session.Start track types = (%d)", trackTypes));
    897      InitEncoder(trackTypes, mMediaStreamTracks[0]->Graph()->GraphRate(),
    898                  aTimeslice);
    899      return;
    900    }
    901 
    902    if (mRecorder->mAudioNode) {
    903      TrackRate trackRate =
    904          mRecorder->mAudioNode->Context()->Graph()->GraphRate();
    905 
    906      // Web Audio node has only audio.
    907      InitEncoder(ContainerWriter::CREATE_AUDIO_TRACK, trackRate, aTimeslice);
    908      return;
    909    }
    910 
    911    MOZ_ASSERT(false, "Unknown source");
    912  }
    913 
    914  void Stop() {
    915    LOG(LogLevel::Debug, ("Session.Stop %p", this));
    916    MOZ_ASSERT(NS_IsMainThread());
    917 
    918    if (mEncoder) {
    919      mEncoder->DisconnectTracks();
    920    }
    921 
    922    // Remove main thread state added in Start().
    923    if (mMediaStream) {
    924      mMediaStream->UnregisterTrackListener(this);
    925      mMediaStream = nullptr;
    926    }
    927 
    928    {
    929      for (const auto& track : mMediaStreamTracks) {
    930        track->RemovePrincipalChangeObserver(this);
    931      }
    932    }
    933 
    934    if (mRunningState.isOk() &&
    935        mRunningState.inspect() == RunningState::Idling) {
    936      LOG(LogLevel::Debug, ("Session.Stop Explicit end task %p", this));
    937      // End the Session directly if there is no encoder.
    938      DoSessionEndTask(NS_OK);
    939    } else if (mRunningState.isOk() &&
    940               (mRunningState.inspect() == RunningState::Starting ||
    941                mRunningState.inspect() == RunningState::Running)) {
    942      if (mRunningState.inspect() == RunningState::Starting) {
    943        // The MediaEncoder might not report started, but by spec we must fire
    944        // "start".
    945        mStartedListener.DisconnectIfExists();
    946        NS_DispatchToMainThread(NewRunnableMethod(
    947            "MediaRecorder::Session::Stop", this, &Session::OnStarted));
    948      }
    949      mRunningState = RunningState::Stopping;
    950    }
    951  }
    952 
    953  void Pause() {
    954    LOG(LogLevel::Debug, ("Session.Pause"));
    955    MOZ_ASSERT(NS_IsMainThread());
    956    MOZ_ASSERT_IF(mRunningState.isOk(),
    957                  mRunningState.unwrap() != RunningState::Idling);
    958    if (mRunningState.isErr() ||
    959        mRunningState.unwrap() == RunningState::Stopping ||
    960        mRunningState.unwrap() == RunningState::Stopped) {
    961      return;
    962    }
    963    MOZ_ASSERT(mEncoder);
    964    mEncoder->Suspend();
    965  }
    966 
    967  void Resume() {
    968    LOG(LogLevel::Debug, ("Session.Resume"));
    969    MOZ_ASSERT(NS_IsMainThread());
    970    MOZ_ASSERT_IF(mRunningState.isOk(),
    971                  mRunningState.unwrap() != RunningState::Idling);
    972    if (mRunningState.isErr() ||
    973        mRunningState.unwrap() == RunningState::Stopping ||
    974        mRunningState.unwrap() == RunningState::Stopped) {
    975      return;
    976    }
    977    MOZ_ASSERT(mEncoder);
    978    mEncoder->Resume();
    979  }
    980 
    981  void RequestData() {
    982    LOG(LogLevel::Debug, ("Session.RequestData"));
    983    MOZ_ASSERT(NS_IsMainThread());
    984    MOZ_ASSERT(mEncoder);
    985 
    986    InvokeAsync(mEncoderThread, mEncoder.get(), __func__,
    987                &MediaEncoder::RequestData)
    988        ->Then(
    989            GetMainThreadSerialEventTarget(), __func__,
    990            [this, self = RefPtr<Session>(this)](
    991                const MediaEncoder::BlobPromise::ResolveOrRejectValue& aRrv) {
    992              if (aRrv.IsReject()) {
    993                LOG(LogLevel::Warning, ("RequestData failed"));
    994                DoSessionEndTask(aRrv.RejectValue());
    995                return;
    996              }
    997 
    998              nsresult rv =
    999                  mRecorder->CreateAndDispatchBlobEvent(aRrv.ResolveValue());
   1000              if (NS_FAILED(rv)) {
   1001                DoSessionEndTask(NS_OK);
   1002              }
   1003            });
   1004  }
   1005 
   1006 public:
   1007  RefPtr<SizeOfPromise> SizeOfExcludingThis(
   1008      mozilla::MallocSizeOf aMallocSizeOf) {
   1009    MOZ_ASSERT(NS_IsMainThread());
   1010    if (!mEncoder) {
   1011      return SizeOfPromise::CreateAndResolve(0, __func__);
   1012    }
   1013 
   1014    return mEncoder->SizeOfExcludingThis(aMallocSizeOf);
   1015  }
   1016 
   1017 private:
   1018  virtual ~Session() {
   1019    MOZ_ASSERT(NS_IsMainThread());
   1020    MOZ_ASSERT(mShutdownPromise);
   1021    MOZ_ASSERT(!mShutdownBlocker);
   1022    LOG(LogLevel::Debug, ("Session.~Session (%p)", this));
   1023  }
   1024 
   1025  void InitEncoder(uint8_t aTrackTypes, TrackRate aTrackRate,
   1026                   TimeDuration aTimeslice) {
   1027    LOG(LogLevel::Debug, ("Session.InitEncoder %p", this));
   1028    MOZ_ASSERT(NS_IsMainThread());
   1029 
   1030    if (!mRunningState.isOk() ||
   1031        mRunningState.inspect() != RunningState::Idling) {
   1032      MOZ_ASSERT_UNREACHABLE("Double-init");
   1033      return;
   1034    }
   1035 
   1036    // Create a TaskQueue to read encode media data from MediaEncoder.
   1037    MOZ_RELEASE_ASSERT(!mEncoderThread);
   1038    RefPtr<SharedThreadPool> pool =
   1039        GetMediaThreadPool(MediaThreadType::WEBRTC_WORKER);
   1040    if (!pool) {
   1041      LOG(LogLevel::Debug, ("Session.InitEncoder %p Failed to create "
   1042                            "MediaRecorderReadThread thread pool",
   1043                            this));
   1044      DoSessionEndTask(NS_ERROR_FAILURE);
   1045      return;
   1046    }
   1047 
   1048    mEncoderThread =
   1049        TaskQueue::Create(pool.forget(), "MediaRecorderReadThread");
   1050 
   1051    MOZ_DIAGNOSTIC_ASSERT(!mShutdownBlocker);
   1052    // Add a shutdown blocker so mEncoderThread can be shutdown async.
   1053    class Blocker : public ShutdownBlocker {
   1054      const RefPtr<Session> mSession;
   1055 
   1056     public:
   1057      Blocker(RefPtr<Session> aSession, const nsString& aName)
   1058          : ShutdownBlocker(aName), mSession(std::move(aSession)) {}
   1059 
   1060      NS_IMETHOD BlockShutdown(nsIAsyncShutdownClient*) override {
   1061        mSession->DoSessionEndTask(NS_ERROR_ABORT);
   1062        return NS_OK;
   1063      }
   1064    };
   1065 
   1066    nsCOMPtr<nsIAsyncShutdownClient> barrier = GetShutdownBarrier();
   1067    if (!barrier) {
   1068      LOG(LogLevel::Error,
   1069          ("Session.InitEncoder %p Failed to get shutdown barrier", this));
   1070      DoSessionEndTask(NS_ERROR_FAILURE);
   1071      return;
   1072    }
   1073 
   1074    nsString name;
   1075    name.AppendPrintf("MediaRecorder::Session %p shutdown", this);
   1076    mShutdownBlocker = MakeAndAddRef<Blocker>(this, name);
   1077    nsresult rv = barrier->AddBlocker(
   1078        mShutdownBlocker, NS_LITERAL_STRING_FROM_CSTRING(__FILE__), __LINE__,
   1079        u"MediaRecorder::Session: shutdown"_ns);
   1080    MOZ_RELEASE_ASSERT(NS_SUCCEEDED(rv));
   1081 
   1082    uint32_t maxMemory = Preferences::GetUint("media.recorder.max_memory",
   1083                                              MAX_ALLOW_MEMORY_BUFFER);
   1084 
   1085    mEncoder = MediaEncoder::CreateEncoder(
   1086        mEncoderThread, mMimeType, mAudioBitsPerSecond, mVideoBitsPerSecond,
   1087        aTrackTypes, aTrackRate, maxMemory, aTimeslice);
   1088 
   1089    if (!mEncoder) {
   1090      LOG(LogLevel::Error, ("Session.InitEncoder !mEncoder %p", this));
   1091      DoSessionEndTask(NS_ERROR_ABORT);
   1092      return;
   1093    }
   1094 
   1095    nsISerialEventTarget* mainThread = GetMainThreadSerialEventTarget();
   1096    mStartedListener =
   1097        mEncoder->StartedEvent().Connect(mainThread, this, &Session::OnStarted);
   1098    mDataAvailableListener = mEncoder->DataAvailableEvent().Connect(
   1099        mainThread, this, &Session::OnDataAvailable);
   1100    mErrorListener =
   1101        mEncoder->ErrorEvent().Connect(mainThread, this, &Session::OnError);
   1102    mShutdownListener = mEncoder->ShutdownEvent().Connect(mainThread, this,
   1103                                                          &Session::OnShutdown);
   1104 
   1105    if (mRecorder->mAudioNode) {
   1106      mEncoder->ConnectAudioNode(mRecorder->mAudioNode,
   1107                                 mRecorder->mAudioNodeOutput);
   1108    }
   1109 
   1110    for (const auto& track : mMediaStreamTracks) {
   1111      mEncoder->ConnectMediaStreamTrack(track);
   1112    }
   1113 
   1114    // Set mRunningState to Running so that DoSessionEndTask will
   1115    // take the responsibility to end the session.
   1116    mRunningState = RunningState::Starting;
   1117  }
   1118 
   1119  // This is the task that will stop recording per spec:
   1120  // - If rv is NS_ERROR_ABORT or NS_ERROR_DOM_SECURITY_ERR, cancel the encoders
   1121  // - Otherwise, stop the encoders gracefully, this still encodes buffered data
   1122  // - Set state to "inactive"
   1123  // - Fire an error event, if NS_FAILED(rv)
   1124  // - Discard blob data if rv is NS_ERROR_DOM_SECURITY_ERR
   1125  // - Fire a Blob event
   1126  // - Fire an event named stop
   1127  void DoSessionEndTask(nsresult rv) {
   1128    MOZ_ASSERT(NS_IsMainThread());
   1129    if (mRunningState.isErr()) {
   1130      // We have already ended with an error.
   1131      return;
   1132    }
   1133 
   1134    if (mRunningState.isOk() &&
   1135        mRunningState.inspect() == RunningState::Stopped) {
   1136      // We have already ended gracefully.
   1137      return;
   1138    }
   1139 
   1140    bool needsStartEvent = false;
   1141    if (mRunningState.isOk() &&
   1142        (mRunningState.inspect() == RunningState::Idling ||
   1143         mRunningState.inspect() == RunningState::Starting)) {
   1144      needsStartEvent = true;
   1145    }
   1146 
   1147    // Set a terminated running state. Future DoSessionEnd tasks will exit
   1148    // early.
   1149    if (rv == NS_OK) {
   1150      mRunningState = RunningState::Stopped;
   1151    } else {
   1152      mRunningState = Err(rv);
   1153    }
   1154 
   1155    RefPtr<MediaEncoder::BlobPromise> blobPromise;
   1156    if (!mEncoder) {
   1157      blobPromise = MediaEncoder::BlobPromise::CreateAndReject(NS_OK, __func__);
   1158    } else {
   1159      blobPromise =
   1160          (rv == NS_ERROR_ABORT || rv == NS_ERROR_DOM_SECURITY_ERR
   1161               ? mEncoder->Cancel()
   1162               : mEncoder->Stop())
   1163              ->Then(mEncoderThread, __func__,
   1164                     [encoder = mEncoder](
   1165                         const GenericNonExclusivePromise::ResolveOrRejectValue&
   1166                             aValue) {
   1167                       MOZ_DIAGNOSTIC_ASSERT(aValue.IsResolve());
   1168                       return encoder->RequestData();
   1169                     });
   1170    }
   1171 
   1172    blobPromise
   1173        ->Then(
   1174            GetMainThreadSerialEventTarget(), __func__,
   1175            [this, self = RefPtr<Session>(this), rv, needsStartEvent](
   1176                const MediaEncoder::BlobPromise::ResolveOrRejectValue& aRv) {
   1177              if (mRecorder->mSessions.LastElement() == this) {
   1178                // Set state to inactive, but only if the recorder is not
   1179                // controlled by another session already.
   1180                mRecorder->Inactivate();
   1181              }
   1182 
   1183              if (needsStartEvent) {
   1184                mRecorder->DispatchSimpleEvent(u"start"_ns);
   1185              }
   1186 
   1187              // If there was an error, Fire the appropriate one
   1188              if (NS_FAILED(rv)) {
   1189                mRecorder->NotifyError(rv);
   1190              }
   1191 
   1192              // Fire a blob event named dataavailable
   1193              RefPtr<BlobImpl> blobImpl;
   1194              if (rv == NS_ERROR_DOM_SECURITY_ERR || aRv.IsReject()) {
   1195                // In case of SecurityError, the blob data must be discarded.
   1196                // We create a new empty one and throw the blob with its data
   1197                // away.
   1198                // In case we failed to gather blob data, we create an empty
   1199                // memory blob instead.
   1200                blobImpl = new EmptyBlobImpl(mMimeType);
   1201              } else {
   1202                blobImpl = aRv.ResolveValue();
   1203              }
   1204              if (NS_FAILED(mRecorder->CreateAndDispatchBlobEvent(blobImpl))) {
   1205                // Failed to dispatch blob event. That's unexpected. It's
   1206                // probably all right to fire an error event if we haven't
   1207                // already.
   1208                if (NS_SUCCEEDED(rv)) {
   1209                  mRecorder->NotifyError(NS_ERROR_FAILURE);
   1210                }
   1211              }
   1212 
   1213              // Fire an event named stop
   1214              mRecorder->DispatchSimpleEvent(u"stop"_ns);
   1215 
   1216              // And finally, Shutdown and destroy the Session
   1217              return Shutdown();
   1218            })
   1219        ->Then(GetMainThreadSerialEventTarget(), __func__,
   1220               [this, self = RefPtr<Session>(this)] {
   1221                 // Guard against the case where we fail to add a blocker due to
   1222                 // being in XPCOM shutdown. If we're in this state we shouldn't
   1223                 // try and get a shutdown barrier as we'll fail.
   1224                 if (!mShutdownBlocker) {
   1225                   return;
   1226                 }
   1227                 MustGetShutdownBarrier()->RemoveBlocker(mShutdownBlocker);
   1228                 mShutdownBlocker = nullptr;
   1229               });
   1230  }
   1231 
   1232  void OnStarted() {
   1233    MOZ_ASSERT(NS_IsMainThread());
   1234    if (mRunningState.isErr()) {
   1235      return;
   1236    }
   1237    RunningState state = mRunningState.inspect();
   1238    if (state == RunningState::Starting || state == RunningState::Stopping) {
   1239      if (state == RunningState::Starting) {
   1240        // We set it to Running in the runnable since we can only assign
   1241        // mRunningState on main thread. We set it before running the start
   1242        // event runnable since that dispatches synchronously (and may cause
   1243        // js calls to methods depending on mRunningState).
   1244        mRunningState = RunningState::Running;
   1245 
   1246        mRecorder->mMimeType = mEncoder->mMimeType;
   1247      }
   1248      mRecorder->DispatchSimpleEvent(u"start"_ns);
   1249    }
   1250  }
   1251 
   1252  void OnDataAvailable(const RefPtr<BlobImpl>& aBlob) {
   1253    if (mRunningState.isErr() &&
   1254        mRunningState.unwrapErr() == NS_ERROR_DOM_SECURITY_ERR) {
   1255      return;
   1256    }
   1257    if (NS_WARN_IF(NS_FAILED(mRecorder->CreateAndDispatchBlobEvent(aBlob)))) {
   1258      LOG(LogLevel::Warning,
   1259          ("MediaRecorder %p Creating or dispatching BlobEvent failed", this));
   1260      DoSessionEndTask(NS_OK);
   1261    }
   1262  }
   1263 
   1264  void OnError() {
   1265    MOZ_ASSERT(NS_IsMainThread());
   1266    DoSessionEndTask(NS_ERROR_FAILURE);
   1267  }
   1268 
   1269  void OnShutdown() {
   1270    MOZ_ASSERT(NS_IsMainThread());
   1271    DoSessionEndTask(NS_OK);
   1272  }
   1273 
   1274  RefPtr<ShutdownPromise> Shutdown() {
   1275    MOZ_ASSERT(NS_IsMainThread());
   1276    LOG(LogLevel::Debug, ("Session Shutdown %p", this));
   1277 
   1278    if (mShutdownPromise) {
   1279      return mShutdownPromise;
   1280    }
   1281 
   1282    mShutdownPromise = ShutdownPromise::CreateAndResolve(true, __func__);
   1283 
   1284    if (mEncoder) {
   1285      mShutdownPromise =
   1286          mShutdownPromise
   1287              ->Then(GetMainThreadSerialEventTarget(), __func__,
   1288                     [this, self = RefPtr<Session>(this)] {
   1289                       mStartedListener.DisconnectIfExists();
   1290                       mDataAvailableListener.DisconnectIfExists();
   1291                       mErrorListener.DisconnectIfExists();
   1292                       mShutdownListener.DisconnectIfExists();
   1293                       return mEncoder->Cancel();
   1294                     })
   1295              ->Then(mEncoderThread, __func__, [] {
   1296                // Meh, this is just to convert the promise type to match
   1297                // mShutdownPromise.
   1298                return ShutdownPromise::CreateAndResolve(true, __func__);
   1299              });
   1300    }
   1301 
   1302    // Remove main thread state. This could be needed if Stop() wasn't called.
   1303    if (mMediaStream) {
   1304      mMediaStream->UnregisterTrackListener(this);
   1305      mMediaStream = nullptr;
   1306    }
   1307 
   1308    {
   1309      auto tracks(std::move(mMediaStreamTracks));
   1310      for (RefPtr<MediaStreamTrack>& track : tracks) {
   1311        track->RemovePrincipalChangeObserver(this);
   1312      }
   1313    }
   1314 
   1315    // Break the cycle reference between Session and MediaRecorder.
   1316    mShutdownPromise = mShutdownPromise->Then(
   1317        GetMainThreadSerialEventTarget(), __func__,
   1318        [self = RefPtr<Session>(this)]() {
   1319          self->mRecorder->RemoveSession(self);
   1320          return ShutdownPromise::CreateAndResolve(true, __func__);
   1321        },
   1322        []() {
   1323          MOZ_ASSERT_UNREACHABLE("Unexpected reject");
   1324          return ShutdownPromise::CreateAndReject(false, __func__);
   1325        });
   1326 
   1327    if (mEncoderThread) {
   1328      mShutdownPromise = mShutdownPromise->Then(
   1329          GetMainThreadSerialEventTarget(), __func__,
   1330          [encoderThread = mEncoderThread]() {
   1331            return encoderThread->BeginShutdown();
   1332          },
   1333          []() {
   1334            MOZ_ASSERT_UNREACHABLE("Unexpected reject");
   1335            return ShutdownPromise::CreateAndReject(false, __func__);
   1336          });
   1337    }
   1338 
   1339    return mShutdownPromise;
   1340  }
   1341 
   1342 private:
   1343  enum class RunningState {
   1344    Idling,    // Session has been created
   1345    Starting,  // MediaEncoder started, waiting for data
   1346    Running,   // MediaEncoder has received data
   1347    Stopping,  // Stop() has been called
   1348    Stopped,   // Session has stopped without any error
   1349  };
   1350 
   1351  // Our associated MediaRecorder.
   1352  const RefPtr<MediaRecorder> mRecorder;
   1353 
   1354  // Stream currently recorded.
   1355  RefPtr<DOMMediaStream> mMediaStream;
   1356 
   1357  // Tracks currently recorded. This should be a subset of mMediaStream's track
   1358  // set.
   1359  nsTArray<RefPtr<MediaStreamTrack>> mMediaStreamTracks;
   1360 
   1361  // Runnable thread for reading data from MediaEncoder.
   1362  RefPtr<TaskQueue> mEncoderThread;
   1363  // MediaEncoder pipeline.
   1364  RefPtr<MediaEncoder> mEncoder;
   1365  // Listener connected to mMediaEncoder::StartedEvent().
   1366  MediaEventListener mStartedListener;
   1367  // Listener connected to mMediaEncoder::DataAvailableEvent().
   1368  MediaEventListener mDataAvailableListener;
   1369  // Listener connected to mMediaEncoder::ErrorEvent().
   1370  MediaEventListener mErrorListener;
   1371  // Listener connected to mMediaEncoder::ShutdownEvent().
   1372  MediaEventListener mShutdownListener;
   1373  // Set in Shutdown() and resolved when shutdown is complete.
   1374  RefPtr<ShutdownPromise> mShutdownPromise;
   1375  // Session mimeType
   1376  const nsString mMimeType;
   1377  // The video bitrate the recorder was configured with.
   1378  const uint32_t mVideoBitsPerSecond;
   1379  // The audio bitrate the recorder was configured with.
   1380  const uint32_t mAudioBitsPerSecond;
   1381  // The session's current main thread state. The error type gets set when
   1382  // ending a recording with an error. An NS_OK error is invalid.
   1383  // Main thread only.
   1384  Result<RunningState, nsresult> mRunningState;
   1385  // Shutdown blocker unique for this Session. Main thread only.
   1386  RefPtr<ShutdownBlocker> mShutdownBlocker;
   1387 };
   1388 
   1389 NS_IMPL_CYCLE_COLLECTION_INHERITED(MediaRecorder::Session,
   1390                                   DOMMediaStream::TrackListener, mMediaStream,
   1391                                   mMediaStreamTracks)
   1392 NS_IMPL_ADDREF_INHERITED(MediaRecorder::Session, DOMMediaStream::TrackListener)
   1393 NS_IMPL_RELEASE_INHERITED(MediaRecorder::Session, DOMMediaStream::TrackListener)
   1394 NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(MediaRecorder::Session)
   1395 NS_INTERFACE_MAP_END_INHERITING(DOMMediaStream::TrackListener)
   1396 
   1397 MediaRecorder::~MediaRecorder() {
   1398  LOG(LogLevel::Debug, ("~MediaRecorder (%p)", this));
   1399  UnRegisterActivityObserver();
   1400 }
   1401 
   1402 MediaRecorder::MediaRecorder(nsPIDOMWindowInner* aOwnerWindow)
   1403    : DOMEventTargetHelper(aOwnerWindow) {
   1404  MOZ_ASSERT(aOwnerWindow);
   1405  RegisterActivityObserver();
   1406 }
   1407 
   1408 void MediaRecorder::RegisterActivityObserver() {
   1409  if (nsPIDOMWindowInner* window = GetOwnerWindow()) {
   1410    mDocument = window->GetExtantDoc();
   1411    if (mDocument) {
   1412      mDocument->RegisterActivityObserver(
   1413          NS_ISUPPORTS_CAST(nsIDocumentActivity*, this));
   1414    }
   1415  }
   1416 }
   1417 
   1418 void MediaRecorder::UnRegisterActivityObserver() {
   1419  if (mDocument) {
   1420    mDocument->UnregisterActivityObserver(
   1421        NS_ISUPPORTS_CAST(nsIDocumentActivity*, this));
   1422  }
   1423 }
   1424 
   1425 void MediaRecorder::GetMimeType(nsString& aMimeType) { aMimeType = mMimeType; }
   1426 
   1427 void MediaRecorder::Start(const Optional<uint32_t>& aTimeslice,
   1428                          ErrorResult& aResult) {
   1429  LOG(LogLevel::Debug, ("MediaRecorder.Start %p", this));
   1430 
   1431  InitializeDomExceptions();
   1432 
   1433  // When a MediaRecorder object’s start() method is invoked, the UA MUST run
   1434  // the following steps:
   1435 
   1436  // 1. Let recorder be the MediaRecorder object on which the method was
   1437  //    invoked.
   1438 
   1439  // 2. Let timeslice be the method’s first argument, if provided, or undefined.
   1440  TimeDuration timeslice =
   1441      aTimeslice.WasPassed()
   1442          ? TimeDuration::FromMilliseconds(aTimeslice.Value())
   1443          : TimeDuration::Forever();
   1444 
   1445  // 3. Let stream be the value of recorder’s stream attribute.
   1446 
   1447  // 4. Let tracks be the set of live tracks in stream’s track set.
   1448  nsTArray<RefPtr<MediaStreamTrack>> tracks;
   1449  if (mStream) {
   1450    mStream->GetTracks(tracks);
   1451  }
   1452  tracks.RemoveLastElements(
   1453      tracks.end() - std::remove_if(tracks.begin(), tracks.end(),
   1454                                    [](const auto& t) { return t->Ended(); }));
   1455 
   1456  // 5. If the value of recorder’s state attribute is not inactive, throw an
   1457  //    InvalidStateError DOMException and abort these steps.
   1458  if (mState != RecordingState::Inactive) {
   1459    aResult.ThrowInvalidStateError(
   1460        "The MediaRecorder has already been started");
   1461    return;
   1462  }
   1463 
   1464  // 6. If the isolation properties of stream disallow access from recorder,
   1465  //    throw a SecurityError DOMException and abort these steps.
   1466  if (mStream) {
   1467    RefPtr<nsIPrincipal> streamPrincipal = mStream->GetPrincipal();
   1468    if (!streamPrincipal) {
   1469      // This is more or less part of the step 7, see below.
   1470      aResult.ThrowNotSupportedError("The MediaStream is inactive");
   1471      return;
   1472    }
   1473 
   1474    if (!PrincipalSubsumes(this, streamPrincipal)) {
   1475      aResult.ThrowSecurityError(
   1476          "The MediaStream's isolation properties disallow access from "
   1477          "MediaRecorder");
   1478      return;
   1479    }
   1480  }
   1481  if (mAudioNode && !AudioNodePrincipalSubsumes(this, mAudioNode)) {
   1482    LOG(LogLevel::Warning,
   1483        ("MediaRecorder %p Start AudioNode principal check failed", this));
   1484    aResult.ThrowSecurityError(
   1485        "The AudioNode's isolation properties disallow access from "
   1486        "MediaRecorder");
   1487    return;
   1488  }
   1489 
   1490  // 7. If stream is inactive, throw a NotSupportedError DOMException and abort
   1491  //    these steps.
   1492  if (mStream && !mStream->Active()) {
   1493    aResult.ThrowNotSupportedError("The MediaStream is inactive");
   1494    return;
   1495  }
   1496 
   1497  // 8. If the [[ConstrainedMimeType]] slot specifies a media type, container,
   1498  //    or codec, then run the following sub steps:
   1499  //   1. Constrain the configuration of recorder to the media type, container,
   1500  //      and codec specified in the [[ConstrainedMimeType]] slot.
   1501  //   2. For each track in tracks, if the User Agent cannot record the track
   1502  //      using the current configuration, then throw a NotSupportedError
   1503  //      DOMException and abort all steps.
   1504  Maybe<MediaContainerType> mime;
   1505  if (mConstrainedMimeType.Length() > 0) {
   1506    mime = MakeMediaContainerType(mConstrainedMimeType);
   1507    MOZ_DIAGNOSTIC_ASSERT(
   1508        mime,
   1509        "Invalid media MIME type should have been caught by IsTypeSupported");
   1510  }
   1511  for (const auto& track : tracks) {
   1512    TypeSupport support = CanRecordWith(track, mime, mConstrainedMimeType);
   1513    if (support != TypeSupport::Supported) {
   1514      nsString id;
   1515      track->GetId(id);
   1516      aResult.ThrowNotSupportedError(nsPrintfCString(
   1517          "%s track cannot be recorded: %s",
   1518          track->AsAudioStreamTrack() ? "An audio" : "A video",
   1519          TypeSupportToCString(support, mConstrainedMimeType).get()));
   1520      return;
   1521    }
   1522  }
   1523  if (mAudioNode) {
   1524    TypeSupport support = CanRecordAudioTrackWith(mime, mConstrainedMimeType);
   1525    if (support != TypeSupport::Supported) {
   1526      aResult.ThrowNotSupportedError(nsPrintfCString(
   1527          "An AudioNode cannot be recorded: %s",
   1528          TypeSupportToCString(support, mConstrainedMimeType).get()));
   1529      return;
   1530    }
   1531  }
   1532 
   1533  // 9. If recorder’s [[ConstrainedBitsPerSecond]] slot is not undefined, set
   1534  //    recorder’s videoBitsPerSecond and audioBitsPerSecond attributes to
   1535  //    values the User Agent deems reasonable for the respective media types,
   1536  //    for recording all tracks in tracks, such that the sum of
   1537  //    videoBitsPerSecond and audioBitsPerSecond is close to the value of
   1538  //    recorder’s
   1539  //    [[ConstrainedBitsPerSecond]] slot.
   1540  uint8_t numVideoTracks = 0;
   1541  uint8_t numAudioTracks = 0;
   1542  for (const auto& t : tracks) {
   1543    if (t->AsVideoStreamTrack() && numVideoTracks < UINT8_MAX) {
   1544      ++numVideoTracks;
   1545    } else if (t->AsAudioStreamTrack() && numAudioTracks < UINT8_MAX) {
   1546      ++numAudioTracks;
   1547    }
   1548  }
   1549  if (mAudioNode) {
   1550    MOZ_DIAGNOSTIC_ASSERT(!mStream);
   1551    ++numAudioTracks;
   1552  }
   1553  if (mConstrainedBitsPerSecond) {
   1554    SelectBitrates(*mConstrainedBitsPerSecond, numVideoTracks,
   1555                   &mVideoBitsPerSecond, numAudioTracks, &mAudioBitsPerSecond);
   1556  }
   1557 
   1558  // 10. Let videoBitrate be the value of recorder’s videoBitsPerSecond
   1559  //     attribute, and constrain the configuration of recorder to target an
   1560  //     aggregate bitrate of videoBitrate bits per second for all video tracks
   1561  //     recorder will be recording. videoBitrate is a hint for the encoder and
   1562  //     the value might be surpassed, not achieved, or only be achieved over a
   1563  //     long period of time.
   1564  const uint32_t videoBitrate = mVideoBitsPerSecond;
   1565 
   1566  // 11. Let audioBitrate be the value of recorder’s audioBitsPerSecond
   1567  //     attribute, and constrain the configuration of recorder to target an
   1568  //     aggregate bitrate of audioBitrate bits per second for all audio tracks
   1569  //     recorder will be recording. audioBitrate is a hint for the encoder and
   1570  //     the value might be surpassed, not achieved, or only be achieved over a
   1571  //     long period of time.
   1572  const uint32_t audioBitrate = mAudioBitsPerSecond;
   1573 
   1574  // 12. Constrain the configuration of recorder to encode using the BitrateMode
   1575  //     specified by the value of recorder’s audioBitrateMode attribute for all
   1576  //     audio tracks recorder will be recording.
   1577  // -- NOT IMPLEMENTED
   1578 
   1579  // 13. For each track in tracks, if the User Agent cannot record the track
   1580  //     using the current configuration, then throw a NotSupportedError
   1581  //     DOMException and abort these steps.
   1582  if (numVideoTracks > 1) {
   1583    aResult.ThrowNotSupportedError(
   1584        "MediaRecorder does not support recording more than one video track"_ns);
   1585    return;
   1586  }
   1587  if (numAudioTracks > 1) {
   1588    aResult.ThrowNotSupportedError(
   1589        "MediaRecorder does not support recording more than one audio track"_ns);
   1590    return;
   1591  }
   1592 
   1593  // 14. Set recorder’s state to recording
   1594  mState = RecordingState::Recording;
   1595 
   1596  MediaRecorderReporter::AddMediaRecorder(this);
   1597  // Start a session.
   1598  mSessions.AppendElement();
   1599  mSessions.LastElement() =
   1600      new Session(this, std::move(tracks), videoBitrate, audioBitrate);
   1601  mSessions.LastElement()->Start(timeslice);
   1602 }
   1603 
   1604 void MediaRecorder::Stop(ErrorResult& aResult) {
   1605  LOG(LogLevel::Debug, ("MediaRecorder.Stop %p", this));
   1606  MediaRecorderReporter::RemoveMediaRecorder(this);
   1607 
   1608  // When a MediaRecorder object’s stop() method is invoked, the UA MUST run the
   1609  // following steps:
   1610 
   1611  // 1. Let recorder be the MediaRecorder object on which the method was
   1612  //    invoked.
   1613 
   1614  // 2. If recorder’s state attribute is inactive, abort these steps.
   1615  if (mState == RecordingState::Inactive) {
   1616    return;
   1617  }
   1618 
   1619  // 3. Inactivate the recorder with recorder.
   1620  Inactivate();
   1621 
   1622  // 4. Queue a task, using the DOM manipulation task source, that runs the
   1623  //    following steps:
   1624  //   1. Stop gathering data.
   1625  //   2. Let blob be the Blob of collected data so far, then fire a blob event
   1626  //      named dataavailable at recorder with blob.
   1627  //   3. Fire an event named stop at recorder.
   1628  MOZ_ASSERT(mSessions.Length() > 0);
   1629  mSessions.LastElement()->Stop();
   1630 
   1631  // 5. return undefined.
   1632 }
   1633 
   1634 void MediaRecorder::Pause(ErrorResult& aResult) {
   1635  LOG(LogLevel::Debug, ("MediaRecorder.Pause %p", this));
   1636 
   1637  // When a MediaRecorder object’s pause() method is invoked, the UA MUST run
   1638  // the following steps:
   1639 
   1640  // 1. If state is inactive, throw an InvalidStateError DOMException and abort
   1641  //    these steps.
   1642  if (mState == RecordingState::Inactive) {
   1643    aResult.ThrowInvalidStateError("The MediaRecorder is inactive");
   1644    return;
   1645  }
   1646 
   1647  // 2. If state is paused, abort these steps.
   1648  if (mState == RecordingState::Paused) {
   1649    return;
   1650  }
   1651 
   1652  // 3. Set state to paused, and queue a task, using the DOM manipulation task
   1653  //    source, that runs the following steps:
   1654  mState = RecordingState::Paused;
   1655 
   1656  // XXX - We pause synchronously pending spec issue
   1657  //       https://github.com/w3c/mediacapture-record/issues/131
   1658  //   1. Stop gathering data into blob (but keep it available so that
   1659  //      recording can be resumed in the future).
   1660  MOZ_ASSERT(!mSessions.IsEmpty());
   1661  mSessions.LastElement()->Pause();
   1662 
   1663  NS_DispatchToMainThread(NS_NewRunnableFunction(
   1664      "MediaRecorder::Pause", [recorder = RefPtr<MediaRecorder>(this)] {
   1665        // 2. Let target be the MediaRecorder context object. Fire an event
   1666        //    named pause at target.
   1667        recorder->DispatchSimpleEvent(u"pause"_ns);
   1668      }));
   1669 
   1670  // 4. return undefined.
   1671 }
   1672 
   1673 void MediaRecorder::Resume(ErrorResult& aResult) {
   1674  LOG(LogLevel::Debug, ("MediaRecorder.Resume %p", this));
   1675 
   1676  // When a MediaRecorder object’s resume() method is invoked, the UA MUST run
   1677  // the following steps:
   1678 
   1679  // 1. If state is inactive, throw an InvalidStateError DOMException and abort
   1680  //    these steps.
   1681  if (mState == RecordingState::Inactive) {
   1682    aResult.ThrowInvalidStateError("The MediaRecorder is inactive");
   1683    return;
   1684  }
   1685 
   1686  // 2. If state is recording, abort these steps.
   1687  if (mState == RecordingState::Recording) {
   1688    return;
   1689  }
   1690 
   1691  // 3. Set state to recording, and queue a task, using the DOM manipulation
   1692  //    task source, that runs the following steps:
   1693  mState = RecordingState::Recording;
   1694 
   1695  // XXX - We resume synchronously pending spec issue
   1696  //       https://github.com/w3c/mediacapture-record/issues/131
   1697  //   1. Resume (or continue) gathering data into the current blob.
   1698  MOZ_ASSERT(!mSessions.IsEmpty());
   1699  mSessions.LastElement()->Resume();
   1700 
   1701  NS_DispatchToMainThread(NS_NewRunnableFunction(
   1702      "MediaRecorder::Resume", [recorder = RefPtr<MediaRecorder>(this)] {
   1703        // 2. Let target be the MediaRecorder context object. Fire an event
   1704        //    named resume at target.
   1705        recorder->DispatchSimpleEvent(u"resume"_ns);
   1706      }));
   1707 
   1708  // 4. return undefined.
   1709 }
   1710 
   1711 void MediaRecorder::RequestData(ErrorResult& aResult) {
   1712  LOG(LogLevel::Debug, ("MediaRecorder.RequestData %p", this));
   1713 
   1714  // When a MediaRecorder object’s requestData() method is invoked, the UA MUST
   1715  // run the following steps:
   1716 
   1717  // 1. If state is inactive throw an InvalidStateError DOMException and
   1718  //    terminate these steps. Otherwise the UA MUST queue a task, using the DOM
   1719  //    manipulation task source, that runs the following steps:
   1720  //   1. Let blob be the Blob of collected data so far and let target be the
   1721  //      MediaRecorder context object, then fire a blob event named
   1722  //      dataavailable at target with blob. (Note that blob will be empty if no
   1723  //      data has been gathered yet.)
   1724  //   2. Create a new Blob and gather subsequent data into it.
   1725  if (mState == RecordingState::Inactive) {
   1726    aResult.ThrowInvalidStateError("The MediaRecorder is inactive");
   1727    return;
   1728  }
   1729  MOZ_ASSERT(mSessions.Length() > 0);
   1730  mSessions.LastElement()->RequestData();
   1731 
   1732  // 2. return undefined.
   1733 }
   1734 
   1735 JSObject* MediaRecorder::WrapObject(JSContext* aCx,
   1736                                    JS::Handle<JSObject*> aGivenProto) {
   1737  return MediaRecorder_Binding::Wrap(aCx, this, aGivenProto);
   1738 }
   1739 
   1740 /* static */
   1741 already_AddRefed<MediaRecorder> MediaRecorder::Constructor(
   1742    const GlobalObject& aGlobal, DOMMediaStream& aStream,
   1743    const MediaRecorderOptions& aOptions, ErrorResult& aRv) {
   1744  nsCOMPtr<nsPIDOMWindowInner> ownerWindow =
   1745      do_QueryInterface(aGlobal.GetAsSupports());
   1746  if (!ownerWindow) {
   1747    aRv.Throw(NS_ERROR_FAILURE);
   1748    return nullptr;
   1749  }
   1750 
   1751  // When the MediaRecorder() constructor is invoked, the User Agent MUST run
   1752  // the following steps:
   1753 
   1754  // 1. Let stream be the constructor’s first argument.
   1755 
   1756  // 2. Let options be the constructor’s second argument.
   1757 
   1758  // 3. If invoking is type supported with options’ mimeType member as its
   1759  //    argument returns false, throw a NotSupportedError DOMException and abort
   1760  //    these steps.
   1761  TypeSupport support = IsTypeSupportedImpl(aOptions.mMimeType);
   1762  if (support != TypeSupport::Supported) {
   1763    // This catches also the empty string mimeType when support for any encoders
   1764    // has been disabled.
   1765    aRv.ThrowNotSupportedError(
   1766        TypeSupportToCString(support, aOptions.mMimeType));
   1767    return nullptr;
   1768  }
   1769 
   1770  // 4. Let recorder be a newly constructed MediaRecorder object.
   1771  RefPtr<MediaRecorder> recorder = new MediaRecorder(ownerWindow);
   1772 
   1773  // 5. Let recorder have a [[ConstrainedMimeType]] internal slot, initialized
   1774  //    to the value of options' mimeType member.
   1775  recorder->mConstrainedMimeType = aOptions.mMimeType;
   1776 
   1777  // 6. Let recorder have a [[ConstrainedBitsPerSecond]] internal slot,
   1778  //    initialized to the value of options’ bitsPerSecond member, if it is
   1779  //    present, otherwise undefined.
   1780  recorder->mConstrainedBitsPerSecond =
   1781      aOptions.mBitsPerSecond.WasPassed()
   1782          ? Some(aOptions.mBitsPerSecond.Value())
   1783          : Nothing();
   1784 
   1785  // 7. Initialize recorder’s stream attribute to stream.
   1786  recorder->mStream = &aStream;
   1787 
   1788  // 8. Initialize recorder’s mimeType attribute to the value of recorder’s
   1789  //    [[ConstrainedMimeType]] slot.
   1790  recorder->mMimeType = recorder->mConstrainedMimeType;
   1791 
   1792  // 9. Initialize recorder’s state attribute to inactive.
   1793  recorder->mState = RecordingState::Inactive;
   1794 
   1795  // 10. Initialize recorder’s videoBitsPerSecond attribute to the value of
   1796  //     options’ videoBitsPerSecond member, if it is present. Otherwise, choose
   1797  //     a target value the User Agent deems reasonable for video.
   1798  recorder->mVideoBitsPerSecond = aOptions.mVideoBitsPerSecond.WasPassed()
   1799                                      ? aOptions.mVideoBitsPerSecond.Value()
   1800                                      : DEFAULT_VIDEO_BITRATE_BPS;
   1801 
   1802  // 11. Initialize recorder’s audioBitsPerSecond attribute to the value of
   1803  //     options’ audioBitsPerSecond member, if it is present. Otherwise, choose
   1804  //     a target value the User Agent deems reasonable for audio.
   1805  recorder->mAudioBitsPerSecond = aOptions.mAudioBitsPerSecond.WasPassed()
   1806                                      ? aOptions.mAudioBitsPerSecond.Value()
   1807                                      : DEFAULT_AUDIO_BITRATE_BPS;
   1808 
   1809  // 12. If recorder’s [[ConstrainedBitsPerSecond]] slot is not undefined, set
   1810  //     recorder’s videoBitsPerSecond and audioBitsPerSecond attributes to
   1811  //     values the User Agent deems reasonable for the respective media types,
   1812  //     such that the sum of videoBitsPerSecond and audioBitsPerSecond is close
   1813  //     to the value of recorder’s [[ConstrainedBitsPerSecond]] slot.
   1814  if (recorder->mConstrainedBitsPerSecond) {
   1815    SelectBitrates(*recorder->mConstrainedBitsPerSecond, 1,
   1816                   &recorder->mVideoBitsPerSecond, 1,
   1817                   &recorder->mAudioBitsPerSecond);
   1818  }
   1819 
   1820  // 13. Return recorder.
   1821  return recorder.forget();
   1822 }
   1823 
   1824 /* static */
   1825 already_AddRefed<MediaRecorder> MediaRecorder::Constructor(
   1826    const GlobalObject& aGlobal, AudioNode& aAudioNode,
   1827    uint32_t aAudioNodeOutput, const MediaRecorderOptions& aOptions,
   1828    ErrorResult& aRv) {
   1829  // Allow recording from audio node only when pref is on.
   1830  if (!Preferences::GetBool("media.recorder.audio_node.enabled", false)) {
   1831    // Pretending that this constructor is not defined.
   1832    aRv.ThrowTypeError<MSG_DOES_NOT_IMPLEMENT_INTERFACE>("Argument 1",
   1833                                                         "MediaStream");
   1834    return nullptr;
   1835  }
   1836 
   1837  nsCOMPtr<nsPIDOMWindowInner> ownerWindow =
   1838      do_QueryInterface(aGlobal.GetAsSupports());
   1839  if (!ownerWindow) {
   1840    aRv.Throw(NS_ERROR_FAILURE);
   1841    return nullptr;
   1842  }
   1843 
   1844  // aAudioNodeOutput doesn't matter to destination node because it has no
   1845  // output.
   1846  if (aAudioNode.NumberOfOutputs() > 0 &&
   1847      aAudioNodeOutput >= aAudioNode.NumberOfOutputs()) {
   1848    aRv.ThrowIndexSizeError("Invalid AudioNode output index");
   1849    return nullptr;
   1850  }
   1851 
   1852  // When the MediaRecorder() constructor is invoked, the User Agent MUST run
   1853  // the following steps:
   1854 
   1855  // 1. Let stream be the constructor’s first argument. (we'll let audioNode be
   1856  //    the first arg, and audioNodeOutput the second)
   1857 
   1858  // 2. Let options be the constructor’s second argument. (we'll let options be
   1859  //    the third arg)
   1860 
   1861  // 3. If invoking is type supported with options’ mimeType member as its
   1862  //    argument returns false, throw a NotSupportedError DOMException and abort
   1863  //    these steps.
   1864  TypeSupport support = IsTypeSupportedImpl(aOptions.mMimeType);
   1865  if (support != TypeSupport::Supported) {
   1866    // This catches also the empty string mimeType when support for any encoders
   1867    // has been disabled.
   1868    aRv.ThrowNotSupportedError(
   1869        TypeSupportToCString(support, aOptions.mMimeType));
   1870    return nullptr;
   1871  }
   1872 
   1873  // 4. Let recorder be a newly constructed MediaRecorder object.
   1874  RefPtr<MediaRecorder> recorder = new MediaRecorder(ownerWindow);
   1875 
   1876  // 5. Let recorder have a [[ConstrainedMimeType]] internal slot, initialized
   1877  //    to the value of options' mimeType member.
   1878  recorder->mConstrainedMimeType = aOptions.mMimeType;
   1879 
   1880  // 6. Let recorder have a [[ConstrainedBitsPerSecond]] internal slot,
   1881  //    initialized to the value of options’ bitsPerSecond member, if it is
   1882  //    present, otherwise undefined.
   1883  recorder->mConstrainedBitsPerSecond =
   1884      aOptions.mBitsPerSecond.WasPassed()
   1885          ? Some(aOptions.mBitsPerSecond.Value())
   1886          : Nothing();
   1887 
   1888  // 7. Initialize recorder’s stream attribute to stream. (make that the
   1889  //    audioNode and audioNodeOutput equivalents)
   1890  recorder->mAudioNode = &aAudioNode;
   1891  recorder->mAudioNodeOutput = aAudioNodeOutput;
   1892 
   1893  // 8. Initialize recorder’s mimeType attribute to the value of recorder’s
   1894  //    [[ConstrainedMimeType]] slot.
   1895  recorder->mMimeType = recorder->mConstrainedMimeType;
   1896 
   1897  // 9. Initialize recorder’s state attribute to inactive.
   1898  recorder->mState = RecordingState::Inactive;
   1899 
   1900  // 10. Initialize recorder’s videoBitsPerSecond attribute to the value of
   1901  //     options’ videoBitsPerSecond member, if it is present. Otherwise, choose
   1902  //     a target value the User Agent deems reasonable for video.
   1903  recorder->mVideoBitsPerSecond = aOptions.mVideoBitsPerSecond.WasPassed()
   1904                                      ? aOptions.mVideoBitsPerSecond.Value()
   1905                                      : DEFAULT_VIDEO_BITRATE_BPS;
   1906 
   1907  // 11. Initialize recorder’s audioBitsPerSecond attribute to the value of
   1908  //     options’ audioBitsPerSecond member, if it is present. Otherwise, choose
   1909  //     a target value the User Agent deems reasonable for audio.
   1910  recorder->mAudioBitsPerSecond = aOptions.mAudioBitsPerSecond.WasPassed()
   1911                                      ? aOptions.mAudioBitsPerSecond.Value()
   1912                                      : DEFAULT_AUDIO_BITRATE_BPS;
   1913 
   1914  // 12. If recorder’s [[ConstrainedBitsPerSecond]] slot is not undefined, set
   1915  //     recorder’s videoBitsPerSecond and audioBitsPerSecond attributes to
   1916  //     values the User Agent deems reasonable for the respective media types,
   1917  //     such that the sum of videoBitsPerSecond and audioBitsPerSecond is close
   1918  //     to the value of recorder’s [[ConstrainedBitsPerSecond]] slot.
   1919  if (recorder->mConstrainedBitsPerSecond) {
   1920    SelectBitrates(*recorder->mConstrainedBitsPerSecond, 1,
   1921                   &recorder->mVideoBitsPerSecond, 1,
   1922                   &recorder->mAudioBitsPerSecond);
   1923  }
   1924 
   1925  // 13. Return recorder.
   1926  return recorder.forget();
   1927 }
   1928 
   1929 /* static */
   1930 bool MediaRecorder::IsTypeSupported(GlobalObject& aGlobal,
   1931                                    const nsAString& aMIMEType) {
   1932  return MediaRecorder::IsTypeSupported(aMIMEType);
   1933 }
   1934 
   1935 /* static */
   1936 bool MediaRecorder::IsTypeSupported(const nsAString& aMIMEType) {
   1937  return IsTypeSupportedImpl(aMIMEType) == TypeSupport::Supported;
   1938 }
   1939 
   1940 nsresult MediaRecorder::CreateAndDispatchBlobEvent(BlobImpl* aBlobImpl) {
   1941  MOZ_ASSERT(NS_IsMainThread(), "Not running on main thread");
   1942 
   1943  if (!GetOwnerGlobal()) {
   1944    // This MediaRecorder has been disconnected in the meantime.
   1945    return NS_ERROR_FAILURE;
   1946  }
   1947 
   1948  RefPtr<Blob> blob = Blob::Create(GetOwnerGlobal(), aBlobImpl);
   1949  if (NS_WARN_IF(!blob)) {
   1950    return NS_ERROR_FAILURE;
   1951  }
   1952 
   1953  BlobEventInit init;
   1954  init.mBubbles = false;
   1955  init.mCancelable = false;
   1956  init.mData = blob;
   1957 
   1958  RefPtr<BlobEvent> event =
   1959      BlobEvent::Constructor(this, u"dataavailable"_ns, init);
   1960  event->SetTrusted(true);
   1961  ErrorResult rv;
   1962  DispatchEvent(*event, rv);
   1963  return rv.StealNSResult();
   1964 }
   1965 
   1966 void MediaRecorder::DispatchSimpleEvent(const nsAString& aStr) {
   1967  MOZ_ASSERT(NS_IsMainThread(), "Not running on main thread");
   1968  nsresult rv = CheckCurrentGlobalCorrectness();
   1969  if (NS_FAILED(rv)) {
   1970    return;
   1971  }
   1972 
   1973  rv = DOMEventTargetHelper::DispatchTrustedEvent(aStr);
   1974  if (NS_FAILED(rv)) {
   1975    LOG(LogLevel::Error,
   1976        ("MediaRecorder.DispatchSimpleEvent: DispatchTrustedEvent failed  %p",
   1977         this));
   1978    NS_ERROR("Failed to dispatch the event!!!");
   1979  }
   1980 }
   1981 
   1982 void MediaRecorder::NotifyError(nsresult aRv) {
   1983  MOZ_ASSERT(NS_IsMainThread(), "Not running on main thread");
   1984  nsresult rv = CheckCurrentGlobalCorrectness();
   1985  if (NS_FAILED(rv)) {
   1986    return;
   1987  }
   1988  MediaRecorderErrorEventInit init;
   1989  init.mBubbles = false;
   1990  init.mCancelable = false;
   1991  // These DOMExceptions have been created earlier so they can contain stack
   1992  // traces. We attach the appropriate one here to be fired. We should have
   1993  // exceptions here, but defensively check.
   1994  switch (aRv) {
   1995    case NS_ERROR_DOM_SECURITY_ERR:
   1996      if (!mSecurityDomException) {
   1997        LOG(LogLevel::Debug, ("MediaRecorder.NotifyError: "
   1998                              "mSecurityDomException was not initialized"));
   1999        mSecurityDomException = DOMException::Create(NS_ERROR_DOM_SECURITY_ERR);
   2000      }
   2001      init.mError = std::move(mSecurityDomException);
   2002      break;
   2003    default:
   2004      if (mOtherDomException && aRv == mOtherDomException->GetResult()) {
   2005        LOG(LogLevel::Debug, ("MediaRecorder.NotifyError: "
   2006                              "mOtherDomException being fired for aRv: %X",
   2007                              uint32_t(aRv)));
   2008        init.mError = std::move(mOtherDomException);
   2009        break;
   2010      }
   2011      if (!mUnknownDomException) {
   2012        LOG(LogLevel::Debug, ("MediaRecorder.NotifyError: "
   2013                              "mUnknownDomException was not initialized"));
   2014        mUnknownDomException = DOMException::Create(NS_ERROR_DOM_UNKNOWN_ERR);
   2015      }
   2016      LOG(LogLevel::Debug, ("MediaRecorder.NotifyError: "
   2017                            "mUnknownDomException being fired for aRv: %X",
   2018                            uint32_t(aRv)));
   2019      init.mError = std::move(mUnknownDomException);
   2020      break;
   2021  }
   2022 
   2023  RefPtr<MediaRecorderErrorEvent> event =
   2024      MediaRecorderErrorEvent::Constructor(this, u"error"_ns, init);
   2025  event->SetTrusted(true);
   2026 
   2027  IgnoredErrorResult res;
   2028  DispatchEvent(*event, res);
   2029  if (res.Failed()) {
   2030    NS_ERROR("Failed to dispatch the error event!!!");
   2031  }
   2032 }
   2033 
   2034 void MediaRecorder::RemoveSession(Session* aSession) {
   2035  LOG(LogLevel::Debug, ("MediaRecorder.RemoveSession (%p)", aSession));
   2036  mSessions.RemoveElement(aSession);
   2037 }
   2038 
   2039 void MediaRecorder::NotifyOwnerDocumentActivityChanged() {
   2040  nsPIDOMWindowInner* window = GetOwnerWindow();
   2041  NS_ENSURE_TRUE_VOID(window);
   2042  Document* doc = window->GetExtantDoc();
   2043  NS_ENSURE_TRUE_VOID(doc);
   2044 
   2045  LOG(LogLevel::Debug, ("MediaRecorder %p NotifyOwnerDocumentActivityChanged "
   2046                        "IsActive=%d, "
   2047                        "IsVisible=%d, ",
   2048                        this, doc->IsActive(), doc->IsVisible()));
   2049  if (!doc->IsActive() || !doc->IsVisible()) {
   2050    // Stop the session.
   2051    ErrorResult result;
   2052    Stop(result);
   2053    result.SuppressException();
   2054  }
   2055 }
   2056 
   2057 void MediaRecorder::Inactivate() {
   2058  LOG(LogLevel::Debug, ("MediaRecorder.Inactivate %p", this));
   2059  // The Inactivate the recorder algorithm given a recorder, is as follows:
   2060 
   2061  // 1. Set recorder’s mimeType attribute to the value of the
   2062  //    [[ConstrainedMimeType]] slot.
   2063  mMimeType = mConstrainedMimeType;
   2064 
   2065  // 2. Set recorder’s state attribute to inactive.
   2066  mState = RecordingState::Inactive;
   2067 
   2068  // 3. If recorder’s [[ConstrainedBitsPerSecond]] slot is not undefined, set
   2069  //    recorder’s videoBitsPerSecond and audioBitsPerSecond attributes to
   2070  //    values the User Agent deems reasonable for the respective media types,
   2071  //    such that the sum of videoBitsPerSecond and audioBitsPerSecond is close
   2072  //    to the value of recorder’s [[ConstrainedBitsPerSecond]] slot.
   2073  if (mConstrainedBitsPerSecond) {
   2074    SelectBitrates(*mConstrainedBitsPerSecond, 1, &mVideoBitsPerSecond, 1,
   2075                   &mAudioBitsPerSecond);
   2076  }
   2077 }
   2078 
   2079 void MediaRecorder::InitializeDomExceptions() {
   2080  mSecurityDomException = DOMException::Create(NS_ERROR_DOM_SECURITY_ERR);
   2081  mUnknownDomException = DOMException::Create(NS_ERROR_DOM_UNKNOWN_ERR);
   2082 }
   2083 
   2084 RefPtr<MediaRecorder::SizeOfPromise> MediaRecorder::SizeOfExcludingThis(
   2085    mozilla::MallocSizeOf aMallocSizeOf) {
   2086  MOZ_ASSERT(NS_IsMainThread());
   2087 
   2088  // The return type of a chained MozPromise cannot be changed, so we create a
   2089  // holder for our desired return type and resolve that from All()->Then().
   2090  auto holder = MakeRefPtr<Refcountable<MozPromiseHolder<SizeOfPromise>>>();
   2091  RefPtr<SizeOfPromise> promise = holder->Ensure(__func__);
   2092 
   2093  nsTArray<RefPtr<SizeOfPromise>> promises(mSessions.Length());
   2094  for (const RefPtr<Session>& session : mSessions) {
   2095    promises.AppendElement(session->SizeOfExcludingThis(aMallocSizeOf));
   2096  }
   2097 
   2098  SizeOfPromise::All(GetCurrentSerialEventTarget(), promises)
   2099      ->Then(
   2100          GetCurrentSerialEventTarget(), __func__,
   2101          [holder](const nsTArray<size_t>& sizes) {
   2102            size_t total = 0;
   2103            for (const size_t& size : sizes) {
   2104              total += size;
   2105            }
   2106            holder->Resolve(total, __func__);
   2107          },
   2108          []() { MOZ_CRASH("Unexpected reject"); });
   2109 
   2110  return promise;
   2111 }
   2112 
   2113 StaticRefPtr<MediaRecorderReporter> MediaRecorderReporter::sUniqueInstance;
   2114 
   2115 }  // namespace mozilla::dom
   2116 
   2117 #undef LOG