tor-browser

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

MediaCapabilitiesValidation.cpp (20748B)


      1 /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
      2 /* vim: set ts=2 et sw=2 tw=80: */
      3 /* This Source Code Form is subject to the terms of the Mozilla Public
      4 * License, v. 2.0. If a copy of the MPL was not distributed with this
      5 * file, You can obtain one at https://mozilla.org/MPL/2.0/. */
      6 #include "MediaCapabilitiesValidation.h"
      7 
      8 #include <algorithm>
      9 #include <array>
     10 #include <cmath>
     11 
     12 #include "MediaMIMETypes.h"
     13 #include "mozilla/Assertions.h"
     14 #include "mozilla/ErrorResult.h"
     15 #include "mozilla/Logging.h"
     16 #include "mozilla/Result.h"
     17 #include "mozilla/Variant.h"
     18 #include "mozilla/dom/MediaCapabilitiesBinding.h"
     19 #include "mozilla/dom/Promise.h"
     20 #include "nsReadableUtils.h"
     21 
     22 extern mozilla::LazyLogModule sMediaCapabilitiesLog;
     23 #define LOG(args) MOZ_LOG(sMediaCapabilitiesLog, LogLevel::Debug, args)
     24 
     25 namespace mozilla::mediacaps {
     26 using dom::AudioConfiguration;
     27 using dom::MediaConfiguration;
     28 using dom::MediaDecodingConfiguration;
     29 using dom::MediaDecodingType;
     30 using dom::MediaEncodingConfiguration;
     31 using dom::MediaEncodingType;
     32 using dom::MSG_INVALID_MEDIA_AUDIO_CONFIGURATION;
     33 using dom::MSG_INVALID_MEDIA_VIDEO_CONFIGURATION;
     34 using dom::MSG_MISSING_REQUIRED_DICTIONARY_MEMBER;
     35 using dom::Promise;
     36 using dom::VideoConfiguration;
     37 
     38 static nsAutoCString GetMIMEDebugString(const MediaConfiguration& aConfig);
     39 static bool IsContainerType(const MediaExtendedMIMEType& aMime);
     40 static bool IsSingleCodecType(const MediaExtendedMIMEType& aMime);
     41 
     42 // If encodingOrDecodingType is webrtc (MediaEncodingType) or webrtc
     43 // (MediaDecodingType) and mimeType is not one that is used with RTP
     44 // (as defined in the specifications of the corresponding RTP payload formats
     45 // [IANA-MEDIA-TYPES] [RFC6838]), return unsupported.
     46 //
     47 // Unsupported: iLBC, iSAC (Chrome, Safari)
     48 // https://developer.mozilla.org/en-US/docs/Web/Media/Guides/Formats/WebRTC_codecs
     49 static const std::array kSingleWebRTCCodecTypes = {
     50    // "audio/ilbc"_ns, "audio/isac"_ns,
     51    "audio/g711-alaw"_ns, "audio/g711-mlaw"_ns, "audio/g722"_ns,
     52    "audio/opus"_ns,      "audio/pcma"_ns,      "audio/pcmu"_ns,
     53    "video/av1"_ns,       "video/h264"_ns,      "video/vp8"_ns,
     54    "video/vp9"_ns,
     55 };
     56 
     57 static const std::array kContainerTypes = {"video/mkv"_ns,  "video/mp4"_ns,
     58                                           "video/webm"_ns, "audio/ogg"_ns,
     59                                           "audio/mp4"_ns,  "audio/webm"_ns};
     60 
     61 // https://w3c.github.io/media-capabilities/#check-mime-type-support
     62 ValidationResult CheckMIMETypeSupport(const MediaExtendedMIMEType& aMime,
     63                                      const AVType& aAVType,
     64                                      const MediaType& aMediaType) {
     65  // Step 1: If encodingOrDecodingType is webrtc (MediaEncodingType) or
     66  // webrtc (MediaDecodingType) and mimeType is not one that is used with
     67  // RTP (as defined in the specifications of the corresponding RTP payload
     68  // formats [IANA-MEDIA-TYPES] [RFC6838]), return unsupported.
     69  // TODO bug 1825286
     70 
     71  // Step 2: If colorGamut is present and is not valid for mimeType, return
     72  // unsupported.
     73  // TODO bug 1825286
     74  return Ok();
     75 }
     76 
     77 // Checks MIME type validity as per:
     78 // https://w3c.github.io/media-capabilities/#check-mime-type-validity
     79 // NOTE: Open issue, https://github.com/w3c/media-capabilities/issues/238
     80 // "Do WebRTC encoding/decoding types have the single-codec restrictions?"
     81 static ValidationResult CheckMIMETypeValidity(
     82    const MediaExtendedMIMEType& aMime, const AVType& aAVType,
     83    const MediaType& aMediaType) {
     84  // Step 1: If the type of mimeType per [RFC9110] is neither
     85  //         media nor application, return false.
     86  const MediaMIMEType& mimetype = aMime.Type();
     87  if (!mimetype.HasAudioMajorType() && !mimetype.HasVideoMajorType() &&
     88      !mimetype.HasApplicationMajorType()) {
     89    ValidationResult err =
     90        Err(aAVType == AVType::AUDIO ? ValidationError::InvalidAudioType
     91                                     : ValidationError::InvalidVideoType);
     92    LOG(
     93        ("[Invalid MIME Validity #1, %s] Rejecting - not media, not "
     94         "application %s",
     95         EnumValueToString(err.unwrapErr()), aMime.OriginalString().get()));
     96    return err;
     97  }
     98 
     99  // The following two steps don't appear to be explicitly defined in the spec
    100  // but are required for some WPT passes and seem like they'd make the most
    101  // sense to have here. The tests in question can be found here:
    102  // https://searchfox.org/firefox-main/rev/cd639e07f74b203d72b0f4a2bea757ae9e10401a/testing/web-platform/tests/media-capabilities/decodingInfo.any.js#140-161
    103 
    104  // Step 1a?: Test that decodingInfo rejects if the audio configuration
    105  // contentType is of type video
    106  if (aAVType == AVType::AUDIO && !aMime.Type().HasAudioMajorType()) {
    107    ValidationResult err = Err(ValidationError::InvalidAudioType);
    108    LOG(("[Invalid MIME Validity #1a?, %s] Rejecting '%s'",
    109         EnumValueToString(err.unwrapErr()), aMime.OriginalString().get()));
    110    return err;
    111  }
    112 
    113  // Step 1b?: Test that decodingInfo rejects if the video configuration
    114  // contentType is of type audio
    115  if (aAVType == AVType::VIDEO && !aMime.Type().HasVideoMajorType()) {
    116    ValidationResult err = Err(ValidationError::InvalidVideoType);
    117    LOG(("[Invalid MIME Validity #1b?, %s] Rejecting '%s'",
    118         EnumValueToString(err.unwrapErr()), aMime.OriginalString().get()));
    119    return err;
    120  }
    121 
    122  // Step 2: If the combined type and subtype members of mimeType allow a
    123  //         single media codec and the parameters member of mimeType is not
    124  //         empty, return false.
    125  //
    126  // (NOTE: WEBRTC EXCEPTION, SEE ISSUE)
    127  // https://github.com/w3c/media-capabilities/issues/238
    128  // TODO bug 1825286 (WebRTC)
    129  const size_t numParams = aMime.GetParameterCount();
    130  if (IsSingleCodecType(aMime) && numParams != 0) {
    131    ValidationResult err = Err(ValidationError::SingleCodecHasParams);
    132    LOG(("[Invalid MIME Validity #2, %s] Rejecting '%s'",
    133         EnumValueToString(err.unwrapErr()), aMime.OriginalString().get()));
    134    return err;
    135  }
    136 
    137  // Step 3: If the combined type and subtype members of mimeType allow
    138  // multiple media codecs, run the following steps:
    139  if (IsContainerType(aMime)) {
    140    // Step 3.1: If the parameters member of mimeType does not contain a single
    141    //            key named "codecs", return false.
    142    if ((numParams != 1) || !aMime.HaveCodecs()) {
    143      ValidationResult err = Err(ValidationError::ContainerMissingCodecsParam);
    144      LOG(("[Invalid MIME Validity #3.1, %s] Rejecting '%s'",
    145           EnumValueToString(err.unwrapErr()), aMime.OriginalString().get()));
    146      return err;
    147    }
    148 
    149    // Step 3.2: If the value of mimeType.parameters["codecs"] does not
    150    // describe a single media codec, return false.
    151    const auto& codecs = aMime.Codecs();
    152    if (!aMime.HaveCodecs() || codecs.IsEmpty() ||
    153        codecs.AsString().FindChar(',') != kNotFound) {
    154      ValidationResult err = Err(ValidationError::ContainerCodecsNotSingle);
    155      LOG(("[Invalid MIME #3.2, %s] Rejecting '%s'",
    156           EnumValueToString(err.unwrapErr()), aMime.OriginalString().get()));
    157      return err;
    158    }
    159  }
    160 
    161  // Step 4: Return true
    162  return Ok();
    163 }
    164 
    165 // https://w3c.github.io/media-capabilities/#audioconfiguration
    166 ValidationResult IsValidAudioConfiguration(const AudioConfiguration& aConfig,
    167                                           const MediaType& aType) {
    168  // Step 1: Let mimeType be the result of running parse a MIME type with
    169  // configuration’s contentType.
    170  const Maybe<MediaExtendedMIMEType> mime =
    171      MakeMediaExtendedMIMEType(aConfig.mContentType);
    172 
    173  // Step 2: If mimeType is failure, return false.
    174  if (!mime) {
    175    ValidationResult err = Err(ValidationError::InvalidAudioType);
    176    LOG(("[Invalid AudioConfiguration #2, %s] Rejecting '%s'\n",
    177         EnumValueToString(err.unwrapErr()),
    178         NS_ConvertUTF16toUTF8(aConfig.mContentType).get()));
    179    return err;
    180  }
    181 
    182  // Return the result of running check MIME type validity with mimeType and
    183  // audio. The channels member represents the audio channels used by the audio
    184  // track. channels is only applicable to the decoding types media-source,
    185  // file, and webrtc and the encoding type webrtc.
    186  return CheckMIMETypeValidity(mime.ref(), AVType::AUDIO, aType);
    187 }
    188 
    189 // https://w3c.github.io/media-capabilities/#audioconfiguration
    190 // To check if a VideoConfiguration configuration is a valid video
    191 // configuration, the following steps MUST be run...
    192 template <typename CodingType>
    193 ValidationResult IsValidVideoConfiguration(const VideoConfiguration& aConfig,
    194                                           const CodingType& aType) {
    195  static_assert(std::is_same_v<std::decay_t<CodingType>, MediaEncodingType> ||
    196                    std::is_same_v<CodingType, MediaDecodingType>,
    197                "tType must be MediaEncodingType or MediaDecodingType");
    198 
    199  // Step 1: If framerate is not finite or is not greater than 0,
    200  // return false and abort these steps.
    201  if (!isfinite(aConfig.mFramerate) || !(aConfig.mFramerate > 0)) {
    202    ValidationResult err = Err(ValidationError::FramerateInvalid);
    203    LOG(("[Invalid VideoConfiguration (Framerate, %s) #1] Rejecting '%s'\n",
    204         EnumValueToString(err.unwrapErr()),
    205         NS_ConvertUTF16toUTF8(aConfig.mContentType).get()));
    206    return err;
    207  }
    208 
    209  // Step 2: If an optional member is specified for a MediaDecodingType or
    210  // MediaEncodingType to which it’s not applicable, return false and abort
    211  // these steps. See applicability rules in the member definitions below.
    212  if constexpr (std::is_same_v<CodingType, MediaDecodingType>) {
    213    // hdrMetadataType is only applicable to MediaDecodingConfiguration
    214    // for types media-source and file.
    215    if (aConfig.mHdrMetadataType.WasPassed() &&
    216        aType != MediaDecodingType::File &&
    217        aType != MediaDecodingType::Media_source) {
    218      ValidationResult err = Err(ValidationError::InapplicableMember);
    219      LOG(("[Invalid VideoConfiguration (HDR, %s) #2] Rejecting '%s'\n",
    220           EnumValueToString(err.unwrapErr()),
    221           NS_ConvertUTF16toUTF8(aConfig.mContentType).get()));
    222      return err;
    223    }
    224    // colorGamut is only applicable to
    225    // MediaDecodingConfiguration for types media-source and file.
    226    if (aConfig.mColorGamut.WasPassed() && aType != MediaDecodingType::File &&
    227        aType != MediaDecodingType::Media_source) {
    228      ValidationResult err = Err(ValidationError::InapplicableMember);
    229      LOG(("[Invalid VideoConfiguration (Color Gamut, %s) #2] Rejecting '%s'\n",
    230           EnumValueToString(err.unwrapErr()),
    231           NS_ConvertUTF16toUTF8(aConfig.mContentType).get()));
    232      return err;
    233    }
    234 
    235    // transferFunction is only
    236    // applicable to MediaDecodingConfiguration for types media-source and file.
    237    if (aConfig.mTransferFunction.WasPassed() &&
    238        aType != MediaDecodingType::File &&
    239        aType != MediaDecodingType::Media_source) {
    240      ValidationResult err = Err(ValidationError::InapplicableMember);
    241      LOG(
    242          ("[Invalid VideoConfiguration (Transfer Function, %s) #2] Rejecting "
    243           "'%s'\n",
    244           EnumValueToString(err.unwrapErr()),
    245           NS_ConvertUTF16toUTF8(aConfig.mContentType).get()));
    246      return err;
    247    }
    248  }
    249 
    250  // ScalabilityMode is only applicable to MediaEncodingConfiguration
    251  // for type webrtc.
    252  // TODO bug 1825286
    253 
    254  // Step 3: Let mimeType be the result of running parse a MIME type with
    255  // configuration’s contentType.
    256  const Maybe<MediaExtendedMIMEType> mime =
    257      MakeMediaExtendedMIMEType(aConfig.mContentType);
    258 
    259  // Step 4: If mimeType is failure, return false.
    260  if (!mime) {
    261    ValidationResult err = Err(ValidationError::InvalidVideoType);
    262    LOG(("[Invalid VideoConfiguration (MIME failure, %s) #4] Rejecting '%s'\n",
    263         EnumValueToString(err.unwrapErr()),
    264         NS_ConvertUTF16toUTF8(aConfig.mContentType).get()));
    265    return err;
    266  }
    267 
    268  // Step 5: Return the result of running check MIME type validity
    269  // with mimeType and video.
    270  return CheckMIMETypeValidity(mime.ref(), AVType::VIDEO, AsVariant(aType));
    271 }
    272 
    273 template ValidationResult IsValidVideoConfiguration<MediaEncodingType>(
    274    const VideoConfiguration&, const MediaEncodingType&);
    275 template ValidationResult IsValidVideoConfiguration<MediaDecodingType>(
    276    const VideoConfiguration&, const MediaDecodingType&);
    277 
    278 ValidationResult IsValidVideoConfiguration(const VideoConfiguration& aConfig,
    279                                           const MediaType& aType) {
    280  return aType.match(
    281      [&](const MediaEncodingType& t) {
    282        return IsValidVideoConfiguration(aConfig, t);
    283      },
    284      [&](const MediaDecodingType& t) {
    285        return IsValidVideoConfiguration(aConfig, t);
    286      });
    287 }
    288 
    289 // https://w3c.github.io/media-capabilities/#mediaconfiguration
    290 ValidationResult IsValidMediaConfiguration(const MediaConfiguration& aConfig,
    291                                           const MediaType& aType) {
    292  // Step 1: audio and/or video MUST exist.
    293  if (!aConfig.mVideo.WasPassed() && !aConfig.mAudio.WasPassed()) {
    294    ValidationResult err = Err(ValidationError::MissingType);
    295    LOG(("[Invalid Media Configuration (No A/V, %s) #1] '%s'",
    296         EnumValueToString(err.unwrapErr()),
    297         GetMIMEDebugString(aConfig).get()));
    298    return err;
    299  }
    300 
    301  // Step 2: audio MUST be a valid audio configuration if it exists.
    302  if (aConfig.mAudio.WasPassed()) {
    303    auto rv = IsValidAudioConfiguration(aConfig.mAudio.Value(), aType);
    304    if (rv.isErr()) {
    305      LOG(("[Invalid Media Configuration (Invalid Audio, %s) #2] '%s'",
    306           EnumValueToString(rv.unwrapErr()),
    307           GetMIMEDebugString(aConfig).get()));
    308      return rv;
    309    }
    310  }
    311 
    312  // Step 3: video MUST be a valid video configuration if it exists.
    313  if (aConfig.mVideo.WasPassed()) {
    314    auto rv = IsValidVideoConfiguration(aConfig.mVideo.Value(), aType);
    315    if (rv.isErr()) {
    316      LOG(("[Invalid Media Configuration (Invalid Video, %s) #3] '%s'",
    317           EnumValueToString(rv.unwrapErr()),
    318           GetMIMEDebugString(aConfig).get()));
    319      return rv;
    320    }
    321  }
    322  return Ok();
    323 }
    324 
    325 // No specific validation steps in the spec...
    326 ValidationResult IsValidMediaEncodingConfiguration(
    327    const MediaEncodingConfiguration& aConfig) {
    328  return IsValidMediaConfiguration(aConfig, AsVariant(aConfig.mType));
    329 }
    330 
    331 // https://w3c.github.io/media-capabilities/#mediaconfiguration
    332 ValidationResult IsValidMediaDecodingConfiguration(
    333    const MediaDecodingConfiguration& aConfig) {
    334  // For a MediaDecodingConfiguration to be a valid MediaDecodingConfiguration,
    335  // all of the following conditions MUST be true:
    336 
    337  // Step 1: It MUST be a valid MediaConfiguration.
    338  auto base = IsValidMediaConfiguration(aConfig, AsVariant(aConfig.mType));
    339  if (base.isErr()) {
    340    LOG(
    341        ("[Invalid MediaDecodingConfiguration (Invalid MediaConfiguration, %s) "
    342         "#1]",
    343         EnumValueToString(base.unwrapErr())));
    344    return base;
    345  }
    346  // Step 2: If keySystemConfiguration exists...
    347  if (aConfig.mKeySystemConfiguration.WasPassed()) {
    348    const auto& keySystemConfig = aConfig.mKeySystemConfiguration.Value();
    349 
    350    // Step 2.1: The type MUST be media-source or file.
    351    if (aConfig.mType != MediaDecodingType::File &&
    352        aConfig.mType != MediaDecodingType::Media_source) {
    353      ValidationResult err = Err(ValidationError::KeySystemWrongType);
    354      LOG(("[Invalid MediaDecodingConfiguration (keysystem, %s) #2.1]",
    355           EnumValueToString(err.unwrapErr())));
    356      return err;
    357    }
    358 
    359    // Step 2.2: If keySystemConfiguration.audio exists, audio MUST also exist.
    360    if (keySystemConfig.mAudio.WasPassed() && !aConfig.mAudio.WasPassed()) {
    361      ValidationResult err = Err(ValidationError::KeySystemAudioMissing);
    362      LOG(("[Invalid MediaDecodingConfiguration (keysystem, %s) #2.2]",
    363           EnumValueToString(err.unwrapErr())));
    364      return err;
    365    }
    366 
    367    // Step 2.3: If keySystemConfiguration.video exists, video MUST also exist.
    368    if (keySystemConfig.mVideo.WasPassed() && !aConfig.mVideo.WasPassed()) {
    369      ValidationResult err = Err(ValidationError::KeySystemVideoMissing);
    370      LOG(("[Invalid MediaDecodingConfiguration (keysystem, %s) #2.3]",
    371           EnumValueToString(err.unwrapErr())));
    372      return err;
    373    }
    374  }
    375  return Ok();
    376 }
    377 
    378 /////////////////////////////////
    379 // Helper functions begin here //
    380 /////////////////////////////////
    381 
    382 void RejectWithValidationResult(Promise* aPromise, const ValidationError aErr) {
    383  switch (aErr) {
    384    case ValidationError::MissingType:
    385      aPromise->MaybeRejectWithTypeError(
    386          "'audio' or 'video' member of argument of MediaCapabilities");
    387      return;
    388    case ValidationError::InvalidAudioConfiguration:
    389      aPromise->MaybeRejectWithTypeError("Invalid AudioConfiguration!");
    390      return;
    391    case ValidationError::InvalidAudioType:
    392      aPromise->MaybeRejectWithTypeError(
    393          "Invalid AudioConfiguration MIME type");
    394      return;
    395    case ValidationError::InvalidVideoConfiguration:
    396      aPromise->MaybeRejectWithTypeError("Invalid VideoConfiguration!");
    397      return;
    398    case ValidationError::InvalidVideoType:
    399      aPromise->MaybeRejectWithTypeError("Invalid Video MIME type");
    400      return;
    401    case ValidationError::SingleCodecHasParams:
    402      aPromise->MaybeRejectWithTypeError("Single codec has parameters");
    403      return;
    404    case ValidationError::ContainerMissingCodecsParam:
    405      aPromise->MaybeRejectWithTypeError("Container missing codec parameters");
    406      return;
    407    case ValidationError::ContainerCodecsNotSingle:
    408      aPromise->MaybeRejectWithTypeError("Container has more than one codec");
    409      return;
    410    case ValidationError::FramerateInvalid:
    411      aPromise->MaybeRejectWithTypeError("Invalid frame rate");
    412      return;
    413    case ValidationError::InapplicableMember:
    414      aPromise->MaybeRejectWithTypeError("Inapplicable member");
    415      return;
    416    case ValidationError::KeySystemWrongType:
    417    case ValidationError::KeySystemAudioMissing:
    418    case ValidationError::KeySystemVideoMissing:
    419      aPromise->MaybeRejectWithTypeError("Invalid keysystem configuration");
    420      return;
    421    default:
    422      MOZ_ASSERT_UNREACHABLE("Unhandled MediaCapabilities validation error!");
    423      return;
    424  }
    425 }
    426 
    427 void ThrowWithValidationResult(ErrorResult& aRv, const ValidationError aErr) {
    428  switch (aErr) {
    429    case ValidationError::MissingType:
    430      aRv.ThrowTypeError<MSG_MISSING_REQUIRED_DICTIONARY_MEMBER>(
    431          "'audio' or 'video' member of argument of MediaCapabilities");
    432      return;
    433    case ValidationError::InvalidAudioConfiguration:
    434      aRv.ThrowTypeError<MSG_INVALID_MEDIA_AUDIO_CONFIGURATION>();
    435      return;
    436    case ValidationError::InvalidAudioType:
    437    case ValidationError::KeySystemAudioMissing:
    438      aRv.ThrowTypeError<MSG_INVALID_MEDIA_AUDIO_CONFIGURATION>();
    439      return;
    440    case ValidationError::InvalidVideoConfiguration:
    441    case ValidationError::InvalidVideoType:
    442    case ValidationError::SingleCodecHasParams:
    443    case ValidationError::ContainerMissingCodecsParam:
    444    case ValidationError::ContainerCodecsNotSingle:
    445    case ValidationError::FramerateInvalid:
    446    case ValidationError::InapplicableMember:
    447      aRv.ThrowTypeError<MSG_INVALID_MEDIA_VIDEO_CONFIGURATION>();
    448      return;
    449    case ValidationError::KeySystemWrongType:
    450    case ValidationError::KeySystemVideoMissing:
    451      aRv.ThrowTypeError<MSG_INVALID_MEDIA_VIDEO_CONFIGURATION>();
    452      return;
    453    default:
    454      MOZ_ASSERT_UNREACHABLE("Unhandled MediaCapabilities validation error!");
    455      return;
    456  }
    457 }
    458 
    459 template <size_t N>
    460 static bool MimePrefixStartsWith(
    461    const MediaExtendedMIMEType& aMime,
    462    const std::array<nsLiteralCString, N>& aPrefixes) {
    463  const nsACString& s = aMime.OriginalString();
    464  return std::any_of(aPrefixes.begin(), aPrefixes.end(), [&](const auto& p) {
    465    return StringBeginsWith(s, p, nsCaseInsensitiveCStringComparator);
    466  });
    467 }
    468 static bool IsContainerType(const MediaExtendedMIMEType& aMime) {
    469  return MimePrefixStartsWith(aMime, kContainerTypes);
    470 }
    471 static bool IsSingleCodecType(const MediaExtendedMIMEType& aMime) {
    472  return MimePrefixStartsWith(aMime, kSingleWebRTCCodecTypes);
    473 }
    474 
    475 static nsAutoCString GetMIMEDebugString(const MediaConfiguration& aConfig) {
    476  nsAutoCString result;
    477  result.SetCapacity(64);
    478  result.AssignLiteral("Audio MIME: ");
    479  if (aConfig.mAudio.WasPassed()) {
    480    result.Append(NS_ConvertUTF16toUTF8(aConfig.mAudio.Value().mContentType));
    481  } else {
    482    result.AppendLiteral("(none)");
    483  }
    484  result.AppendLiteral(" Video MIME: ");
    485  if (aConfig.mVideo.WasPassed()) {
    486    result.Append(NS_ConvertUTF16toUTF8(aConfig.mVideo.Value().mContentType));
    487  } else {
    488    result.AppendLiteral("(none)");
    489  }
    490  return result;
    491 }
    492 
    493 }  // namespace mozilla::mediacaps
    494 #undef LOG