tor-browser

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

MediaDevices.cpp (34152B)


      1 /* This Source Code Form is subject to the terms of the Mozilla Public
      2 * License, v. 2.0. If a copy of the MPL was not distributed with this file,
      3 * You can obtain one at http://mozilla.org/MPL/2.0/. */
      4 
      5 #include "mozilla/dom/MediaDevices.h"
      6 
      7 #include "AudioDeviceInfo.h"
      8 #include "MediaEngine.h"
      9 #include "MediaEngineFake.h"
     10 #include "MediaTrackConstraints.h"
     11 #include "mozilla/MediaManager.h"
     12 #include "mozilla/StaticPrefs_media.h"
     13 #include "mozilla/dom/BrowsingContext.h"
     14 #include "mozilla/dom/Document.h"
     15 #include "mozilla/dom/FeaturePolicyUtils.h"
     16 #include "mozilla/dom/MediaDeviceInfo.h"
     17 #include "mozilla/dom/MediaDevicesBinding.h"
     18 #include "mozilla/dom/MediaStreamBinding.h"
     19 #include "mozilla/dom/MediaTrackSupportedConstraintsBinding.h"
     20 #include "mozilla/dom/NavigatorBinding.h"
     21 #include "mozilla/dom/Promise.h"
     22 #include "mozilla/dom/WindowContext.h"
     23 #include "mozilla/intl/Localization.h"
     24 #include "nsContentUtils.h"
     25 #include "nsGlobalWindowInner.h"
     26 #include "nsINamed.h"
     27 #include "nsIScriptGlobalObject.h"
     28 #include "nsPIDOMWindow.h"
     29 #include "nsQueryObject.h"
     30 
     31 namespace mozilla::dom {
     32 
     33 using ConstDeviceSetPromise = MediaManager::ConstDeviceSetPromise;
     34 using LocalDeviceSetPromise = MediaManager::LocalDeviceSetPromise;
     35 using LocalMediaDeviceSetRefCnt = MediaManager::LocalMediaDeviceSetRefCnt;
     36 using MediaDeviceSetRefCnt = MediaManager::MediaDeviceSetRefCnt;
     37 using mozilla::intl::Localization;
     38 
     39 MediaDevices::MediaDevices(nsPIDOMWindowInner* aWindow)
     40    : DOMEventTargetHelper(aWindow), mDefaultOutputLabel(VoidString()) {}
     41 
     42 MediaDevices::~MediaDevices() {
     43  MOZ_ASSERT(NS_IsMainThread());
     44  mDeviceChangeListener.DisconnectIfExists();
     45 }
     46 
     47 // No code needed, unless controlled by prefs, as
     48 // MediaTrackSupportedConstraints members default to true.
     49 void MediaDevices::GetSupportedConstraints(
     50    MediaTrackSupportedConstraints& aResult) {
     51  if (Preferences::GetBool("media.navigator.video.resize_mode.enabled")) {
     52    aResult.mResizeMode.Construct(true);
     53  }
     54 }
     55 
     56 already_AddRefed<Promise> MediaDevices::GetUserMedia(
     57    const MediaStreamConstraints& aConstraints, CallerType aCallerType,
     58    ErrorResult& aRv) {
     59  MOZ_ASSERT(NS_IsMainThread());
     60  // Get the relevant global for the promise from the wrapper cache because
     61  // DOMEventTargetHelper::GetOwnerWindow() returns null if the document is
     62  // unloaded.
     63  // We know the wrapper exists because it is being used for |this| from JS.
     64  // See https://github.com/heycam/webidl/issues/932 for why the relevant
     65  // global is used instead of the current global.
     66  nsCOMPtr<nsIGlobalObject> global = xpc::NativeGlobal(GetWrapper());
     67  // global is a window because MediaDevices is exposed only to Window.
     68  nsCOMPtr<nsPIDOMWindowInner> owner = do_QueryInterface(global);
     69  if (Document* doc = owner->GetExtantDoc()) {
     70    if (!owner->IsSecureContext()) {
     71      doc->SetUseCounter(eUseCounter_custom_GetUserMediaInsec);
     72    }
     73    Document* topDoc = doc->GetTopLevelContentDocumentIfSameProcess();
     74    IgnoredErrorResult ignored;
     75    if (topDoc && !topDoc->HasFocus(ignored)) {
     76      doc->SetUseCounter(eUseCounter_custom_GetUserMediaUnfocused);
     77    }
     78  }
     79  RefPtr<Promise> p = Promise::Create(global, aRv);
     80  if (NS_WARN_IF(aRv.Failed())) {
     81    return nullptr;
     82  }
     83  /* If requestedMediaTypes is the empty set, return a promise rejected with a
     84   * TypeError. */
     85  if (!MediaManager::IsOn(aConstraints.mVideo) &&
     86      !MediaManager::IsOn(aConstraints.mAudio)) {
     87    p->MaybeRejectWithTypeError("audio and/or video is required");
     88    return p.forget();
     89  }
     90  /* If the relevant settings object's responsible document is NOT fully
     91   * active, return a promise rejected with a DOMException object whose name
     92   * attribute has the value "InvalidStateError". */
     93  if (!owner->IsFullyActive()) {
     94    p->MaybeRejectWithInvalidStateError("The document is not fully active.");
     95    return p.forget();
     96  }
     97  const OwningBooleanOrMediaTrackConstraints& video = aConstraints.mVideo;
     98  if (aCallerType != CallerType::System && video.IsMediaTrackConstraints()) {
     99    const Optional<nsString>& mediaSource =
    100        video.GetAsMediaTrackConstraints().mMediaSource;
    101    if (mediaSource.WasPassed() &&
    102        !mediaSource.Value().EqualsLiteral("camera")) {
    103      WindowContext* wc = owner->GetWindowContext();
    104      if (!wc || !wc->HasValidTransientUserGestureActivation()) {
    105        p->MaybeRejectWithInvalidStateError(
    106            "Display capture requires transient activation "
    107            "from a user gesture.");
    108        return p.forget();
    109      }
    110    }
    111  }
    112  RefPtr<MediaDevices> self(this);
    113  GetUserMedia(owner, aConstraints, aCallerType)
    114      ->Then(
    115          GetCurrentSerialEventTarget(), __func__,
    116          [this, self, p](RefPtr<DOMMediaStream>&& aStream) {
    117            if (!GetWindowIfCurrent()) {
    118              return;  // Leave Promise pending after navigation by design.
    119            }
    120            p->MaybeResolve(std::move(aStream));
    121          },
    122          [this, self, p](const RefPtr<MediaMgrError>& error) {
    123            nsPIDOMWindowInner* window = GetWindowIfCurrent();
    124            if (!window) {
    125              return;  // Leave Promise pending after navigation by design.
    126            }
    127            error->Reject(p);
    128          });
    129  return p.forget();
    130 }
    131 
    132 RefPtr<MediaDevices::StreamPromise> MediaDevices::GetUserMedia(
    133    nsPIDOMWindowInner* aWindow, const MediaStreamConstraints& aConstraints,
    134    CallerType aCallerType) {
    135  MOZ_ASSERT(NS_IsMainThread());
    136  bool haveFake = aConstraints.mFake.WasPassed() && aConstraints.mFake.Value();
    137  const OwningBooleanOrMediaTrackConstraints& video = aConstraints.mVideo;
    138  const OwningBooleanOrMediaTrackConstraints& audio = aConstraints.mAudio;
    139  bool isMicrophone =
    140      !haveFake &&
    141      (audio.IsBoolean()
    142           ? audio.GetAsBoolean()
    143           : !audio.GetAsMediaTrackConstraints().mMediaSource.WasPassed());
    144  bool isCamera =
    145      !haveFake &&
    146      (video.IsBoolean()
    147           ? video.GetAsBoolean()
    148           : !video.GetAsMediaTrackConstraints().mMediaSource.WasPassed());
    149 
    150  RefPtr<MediaDevices> self(this);
    151  return MediaManager::Get()
    152      ->GetUserMedia(aWindow, aConstraints, aCallerType)
    153      ->Then(
    154          GetCurrentSerialEventTarget(), __func__,
    155          [this, self, isMicrophone,
    156           isCamera](RefPtr<DOMMediaStream>&& aStream) {
    157            if (isMicrophone) {
    158              mCanExposeMicrophoneInfo = true;
    159            }
    160            if (isCamera) {
    161              mCanExposeCameraInfo = true;
    162            }
    163            return StreamPromise::CreateAndResolve(std::move(aStream),
    164                                                   __func__);
    165          },
    166          [](RefPtr<MediaMgrError>&& aError) {
    167            return StreamPromise::CreateAndReject(std::move(aError), __func__);
    168          });
    169 }
    170 
    171 already_AddRefed<Promise> MediaDevices::EnumerateDevices(ErrorResult& aRv) {
    172  MOZ_ASSERT(NS_IsMainThread());
    173  nsCOMPtr<nsIGlobalObject> global = xpc::NativeGlobal(GetWrapper());
    174  nsCOMPtr<nsPIDOMWindowInner> owner = do_QueryInterface(global);
    175  if (Document* doc = owner->GetExtantDoc()) {
    176    if (!owner->IsSecureContext()) {
    177      doc->SetUseCounter(eUseCounter_custom_EnumerateDevicesInsec);
    178    }
    179    Document* topDoc = doc->GetTopLevelContentDocumentIfSameProcess();
    180    IgnoredErrorResult ignored;
    181    if (topDoc && !topDoc->HasFocus(ignored)) {
    182      doc->SetUseCounter(eUseCounter_custom_EnumerateDevicesUnfocused);
    183    }
    184  }
    185  RefPtr<Promise> p = Promise::Create(global, aRv);
    186  if (NS_WARN_IF(aRv.Failed())) {
    187    return nullptr;
    188  }
    189  mPendingEnumerateDevicesPromises.AppendElement(p);
    190  MaybeResumeDeviceExposure();
    191  return p.forget();
    192 }
    193 
    194 void MediaDevices::MaybeResumeDeviceExposure() {
    195  if (mPendingEnumerateDevicesPromises.IsEmpty() &&
    196      !mHaveUnprocessedDeviceListChange) {
    197    return;
    198  }
    199  nsPIDOMWindowInner* window = GetOwnerWindow();
    200  if (!window || !window->IsFullyActive()) {
    201    return;
    202  }
    203  if (!StaticPrefs::media_devices_unfocused_enabled()) {
    204    // Device list changes are not exposed to unfocused contexts because the
    205    // timing information would allow fingerprinting for content to identify
    206    // concurrent browsing, even when pages are in different containers.
    207    BrowsingContext* bc = window->GetBrowsingContext();
    208    if (!bc->IsActive() ||  // background tab or browser window fully obscured
    209        !bc->GetIsActiveBrowserWindow()) {  // browser window without focus
    210      return;
    211    }
    212  }
    213  bool shouldResistFingerprinting =
    214      window->AsGlobal()->ShouldResistFingerprinting(RFPTarget::MediaDevices);
    215  MediaManager::Get()->GetPhysicalDevices()->Then(
    216      GetCurrentSerialEventTarget(), __func__,
    217      [self = RefPtr(this), this,
    218       haveDeviceListChange = mHaveUnprocessedDeviceListChange,
    219       enumerateDevicesPromises = std::move(mPendingEnumerateDevicesPromises),
    220       shouldResistFingerprinting](
    221          RefPtr<const MediaDeviceSetRefCnt> aAllDevices) mutable {
    222        RefPtr<MediaDeviceSetRefCnt> exposedDevices =
    223            FilterExposedDevices(*aAllDevices);
    224        if (haveDeviceListChange && !shouldResistFingerprinting) {
    225          if (ShouldQueueDeviceChange(*exposedDevices)) {
    226            NS_DispatchToCurrentThread(NS_NewRunnableFunction(
    227                "devicechange", [self = RefPtr(this), this] {
    228                  DispatchTrustedEvent(u"devicechange"_ns);
    229                }));
    230          }
    231          mLastPhysicalDevices = std::move(aAllDevices);
    232        }
    233        if (!enumerateDevicesPromises.IsEmpty()) {
    234          ResumeEnumerateDevices(std::move(enumerateDevicesPromises),
    235                                 std::move(exposedDevices));
    236        }
    237      },
    238      [](RefPtr<MediaMgrError>&&) {
    239        MOZ_ASSERT_UNREACHABLE("GetPhysicalDevices does not reject");
    240      });
    241  mHaveUnprocessedDeviceListChange = false;
    242 }
    243 
    244 static bool IsLegacyMode(nsPIDOMWindowInner* window) {
    245  if (StaticPrefs::media_devices_enumerate_legacy_enabled()) {
    246    return true;
    247  }
    248  if (window->GetDocumentURI()) {
    249    nsAutoCString host;
    250    window->GetDocumentURI()->GetAsciiHost(host);
    251    if (media::HostnameInPref("media.devices.enumerate.legacy.allowlist",
    252                              host)) {
    253      return true;
    254    }
    255  }
    256  return false;
    257 }
    258 
    259 RefPtr<MediaDeviceSetRefCnt> MediaDevices::FilterExposedDevices(
    260    const MediaDeviceSet& aDevices) const {
    261  nsPIDOMWindowInner* window = GetOwnerWindow();
    262  RefPtr exposed = new MediaDeviceSetRefCnt();
    263  if (!window) {
    264    return exposed;  // Promises will be left pending
    265  }
    266  Document* doc = window->GetExtantDoc();
    267  if (!doc) {
    268    return exposed;
    269  }
    270  // Only expose devices which are allowed to use:
    271  // https://w3c.github.io/mediacapture-main/#dom-mediadevices-enumeratedevices
    272  bool dropMics = !FeaturePolicyUtils::IsFeatureAllowed(doc, u"microphone"_ns);
    273  bool dropCams = !FeaturePolicyUtils::IsFeatureAllowed(doc, u"camera"_ns);
    274  bool dropSpeakers =
    275      !Preferences::GetBool("media.setsinkid.enabled") ||
    276      !FeaturePolicyUtils::IsFeatureAllowed(doc, u"speaker-selection"_ns);
    277  bool shouldResistFingerprinting =
    278      window->AsGlobal()->ShouldResistFingerprinting(RFPTarget::MediaDevices);
    279  bool legacy = IsLegacyMode(window);
    280  bool outputIsDefault = true;  // First output is the default.
    281  bool haveDefaultOutput = false;
    282  nsTHashSet<nsString> exposedMicrophoneGroupIds;
    283  for (const auto& device : aDevices) {
    284    switch (device->mKind) {
    285      case MediaDeviceKind::Audioinput:
    286        if (dropMics) {
    287          continue;
    288        }
    289        if (mCanExposeMicrophoneInfo) {
    290          exposedMicrophoneGroupIds.Insert(device->mRawGroupID);
    291        }
    292        if (!mCanExposeMicrophoneInfo && !legacy) {
    293          dropMics = true;
    294        }
    295        break;
    296      case MediaDeviceKind::Videoinput:
    297        if (dropCams) {
    298          continue;
    299        }
    300        if (!mCanExposeCameraInfo && !legacy) {
    301          dropCams = true;
    302        }
    303        break;
    304      case MediaDeviceKind::Audiooutput:
    305        if (dropSpeakers ||
    306            (!mExplicitlyGrantedAudioOutputRawIds.Contains(device->mRawID) &&
    307             (!mCanExposeMicrophoneInfo ||
    308              (shouldResistFingerprinting &&
    309               // Assumes aDevices order has microphones before speakers.
    310               !exposedMicrophoneGroupIds.Contains(device->mRawGroupID))))) {
    311          outputIsDefault = false;
    312          continue;
    313        }
    314        if (!haveDefaultOutput && !outputIsDefault) {
    315          // Insert a virtual default device so that the first enumerated
    316          // device is the default output.
    317          if (mDefaultOutputLabel.IsVoid()) {
    318            mDefaultOutputLabel.SetIsVoid(false);
    319            AutoTArray<nsCString, 1> resourceIds{"dom/media.ftl"_ns};
    320            RefPtr l10n = Localization::Create(resourceIds, /*sync*/ true);
    321            nsAutoCString translation;
    322            IgnoredErrorResult rv;
    323            l10n->FormatValueSync("default-audio-output-device-label"_ns, {},
    324                                  translation, rv);
    325            if (!rv.Failed()) {
    326              AppendUTF8toUTF16(translation, mDefaultOutputLabel);
    327            }
    328          }
    329          RefPtr info = new AudioDeviceInfo(
    330              nullptr, mDefaultOutputLabel, u""_ns, u""_ns,
    331              CUBEB_DEVICE_TYPE_OUTPUT, CUBEB_DEVICE_STATE_ENABLED,
    332              CUBEB_DEVICE_PREF_ALL, CUBEB_DEVICE_FMT_ALL,
    333              CUBEB_DEVICE_FMT_S16NE, 2, 44100, 44100, 44100, 128, 128);
    334          exposed->AppendElement(
    335              new MediaDevice(new MediaEngineFake(), info, u""_ns));
    336        }
    337        haveDefaultOutput = true;
    338        break;
    339        // Avoid `default:` so that `-Wswitch` catches missing
    340        // enumerators at compile time.
    341    }
    342    exposed->AppendElement(device);
    343  }
    344 
    345  if (doc->ShouldResistFingerprinting(RFPTarget::MediaDevices)) {
    346    // We expose a single device of each kind.
    347    // Legacy mode also achieves the same thing, except for speakers.
    348    nsTHashSet<MediaDeviceKind> seenKinds;
    349 
    350    for (uint32_t i = 0; i < exposed->Length(); i++) {
    351      RefPtr<mozilla::MediaDevice> device = exposed->ElementAt(i);
    352      if (seenKinds.Contains(device->mKind)) {
    353        exposed->RemoveElementAt(i);
    354        i--;
    355        continue;
    356      }
    357      seenKinds.Insert(device->mKind);
    358    }
    359 
    360    // We haven't seen at least one of each kind of device.
    361    // Audioinput, Videoinput, Audiooutput.
    362    // Insert fake devices.
    363    if (seenKinds.Count() != 3) {
    364      RefPtr fakeEngine = new MediaEngineFake();
    365      RefPtr fakeDevices = new MediaDeviceSetRefCnt();
    366      // The order in which we insert the fake devices is important.
    367      // Microphone is inserted first, then camera, then speaker.
    368      // If we haven't seen a microphone, insert a fake one.
    369      if (!seenKinds.Contains(MediaDeviceKind::Audioinput)) {
    370        fakeEngine->EnumerateDevices(MediaSourceEnum::Microphone,
    371                                     MediaSinkEnum::Other, fakeDevices);
    372        exposed->InsertElementAt(0, fakeDevices->LastElement());
    373      }
    374      // If we haven't seen a camera, insert a fake one.
    375      if (!seenKinds.Contains(MediaDeviceKind::Videoinput)) {
    376        fakeEngine->EnumerateDevices(MediaSourceEnum::Camera,
    377                                     MediaSinkEnum::Other, fakeDevices);
    378        exposed->InsertElementAt(1, fakeDevices->LastElement());
    379      }
    380      // If we haven't seen a speaker, insert a fake one.
    381      if (!seenKinds.Contains(MediaDeviceKind::Audiooutput) &&
    382          mCanExposeMicrophoneInfo) {
    383        RefPtr info = new AudioDeviceInfo(
    384            nullptr, u""_ns, u""_ns, u""_ns, CUBEB_DEVICE_TYPE_OUTPUT,
    385            CUBEB_DEVICE_STATE_ENABLED, CUBEB_DEVICE_PREF_ALL,
    386            CUBEB_DEVICE_FMT_ALL, CUBEB_DEVICE_FMT_S16NE, 2, 44100, 44100,
    387            44100, 128, 128);
    388        exposed->AppendElement(
    389            new MediaDevice(new MediaEngineFake(), info, u""_ns));
    390      }
    391    }
    392  }
    393 
    394  return exposed;
    395 }
    396 
    397 bool MediaDevices::CanExposeInfo(MediaDeviceKind aKind) const {
    398  switch (aKind) {
    399    case MediaDeviceKind::Audioinput:
    400      return mCanExposeMicrophoneInfo;
    401    case MediaDeviceKind::Videoinput:
    402      return mCanExposeCameraInfo;
    403    case MediaDeviceKind::Audiooutput:
    404      // Assumes caller has used FilterExposedDevices()
    405      return true;
    406      // Avoid `default:` so that `-Wswitch` catches missing enumerators at
    407      // compile time.
    408  }
    409  MOZ_ASSERT_UNREACHABLE("unexpected MediaDeviceKind");
    410  return false;
    411 }
    412 
    413 bool MediaDevices::ShouldQueueDeviceChange(
    414    const MediaDeviceSet& aExposedDevices) const {
    415  if (!mLastPhysicalDevices) {  // SetupDeviceChangeListener not complete
    416    return false;
    417  }
    418  RefPtr<MediaDeviceSetRefCnt> lastExposedDevices =
    419      FilterExposedDevices(*mLastPhysicalDevices);
    420  auto exposed = aExposedDevices.begin();
    421  auto exposedEnd = aExposedDevices.end();
    422  auto last = lastExposedDevices->begin();
    423  auto lastEnd = lastExposedDevices->end();
    424  // Lists from FilterExposedDevices may have multiple devices of the same
    425  // kind even when only a single anonymous device of that kind should be
    426  // exposed by enumerateDevices() (but multiple devices are currently exposed
    427  // - bug 1528042).  "devicechange" events are not queued when the number
    428  // of such devices changes but remains non-zero.
    429  while (exposed < exposedEnd && last < lastEnd) {
    430    // First determine whether there is at least one device of the same kind
    431    // in both `aExposedDevices` and `lastExposedDevices`.
    432    // A change between zero and non-zero numbers of microphone or camera
    433    // devices triggers a devicechange event even if that kind of device is
    434    // not yet exposed.
    435    MediaDeviceKind kind = (*exposed)->mKind;
    436    if (kind != (*last)->mKind) {
    437      return true;
    438    }
    439    // `exposed` and `last` have matching kind.
    440    if (CanExposeInfo(kind)) {
    441      // Queue "devicechange" if there has been any change in devices of this
    442      // exposed kind.  ID and kind uniquely identify a device.
    443      if ((*exposed)->mRawID != (*last)->mRawID) {
    444        return true;
    445      }
    446      ++exposed;
    447      ++last;
    448      continue;
    449    }
    450    // `aExposedDevices` and `lastExposedDevices` both have non-zero numbers
    451    // of devices of this unexposed kind.
    452    // Skip remaining devices of this kind because all devices of this kind
    453    // should be exposed as a single anonymous device.
    454    do {
    455      ++exposed;
    456    } while (exposed != exposedEnd && (*exposed)->mKind == kind);
    457    do {
    458      ++last;
    459    } while (last != lastEnd && (*last)->mKind == kind);
    460  }
    461  // Queue "devicechange" if the number of exposed devices differs.
    462  return exposed < exposedEnd || last < lastEnd;
    463 }
    464 
    465 void MediaDevices::ResumeEnumerateDevices(
    466    nsTArray<RefPtr<Promise>>&& aPromises,
    467    RefPtr<const MediaDeviceSetRefCnt> aExposedDevices) const {
    468  nsCOMPtr<nsPIDOMWindowInner> window = GetOwnerWindow();
    469  if (!window) {
    470    return;  // Leave Promise pending after navigation by design.
    471  }
    472  MediaManager::Get()
    473      ->AnonymizeDevices(window, std::move(aExposedDevices))
    474      ->Then(GetCurrentSerialEventTarget(), __func__,
    475             [self = RefPtr(this), this, promises = std::move(aPromises)](
    476                 const LocalDeviceSetPromise::ResolveOrRejectValue&
    477                     aLocalDevices) {
    478               nsPIDOMWindowInner* window = GetWindowIfCurrent();
    479               if (!window) {
    480                 return;  // Leave Promises pending after navigation by design.
    481               }
    482               for (const RefPtr<Promise>& promise : promises) {
    483                 if (aLocalDevices.IsReject()) {
    484                   aLocalDevices.RejectValue()->Reject(promise);
    485                 } else {
    486                   ResolveEnumerateDevicesPromise(
    487                       promise, *aLocalDevices.ResolveValue());
    488                 }
    489               }
    490             });
    491 }
    492 
    493 void MediaDevices::ResolveEnumerateDevicesPromise(
    494    Promise* aPromise, const LocalMediaDeviceSet& aDevices) const {
    495  nsCOMPtr<nsPIDOMWindowInner> window = GetOwnerWindow();
    496  auto windowId = window->WindowID();
    497  nsTArray<RefPtr<MediaDeviceInfo>> infos;
    498  bool legacy = IsLegacyMode(window);
    499  bool capturePermitted =
    500      legacy &&
    501      MediaManager::Get()->IsActivelyCapturingOrHasAPermission(windowId);
    502 
    503  for (const RefPtr<LocalMediaDevice>& device : aDevices) {
    504    bool exposeInfo = CanExposeInfo(device->Kind()) || legacy;
    505    bool exposeLabel = legacy ? capturePermitted : exposeInfo;
    506    infos.AppendElement(MakeRefPtr<MediaDeviceInfo>(
    507        exposeInfo ? device->mID : u""_ns, device->Kind(),
    508        exposeLabel ? device->mName : u""_ns,
    509        exposeInfo ? device->mGroupID : u""_ns));
    510  }
    511  aPromise->MaybeResolve(std::move(infos));
    512 }
    513 
    514 already_AddRefed<Promise> MediaDevices::GetDisplayMedia(
    515    const DisplayMediaStreamConstraints& aConstraints, CallerType aCallerType,
    516    ErrorResult& aRv) {
    517  nsCOMPtr<nsIGlobalObject> global = xpc::NativeGlobal(GetWrapper());
    518  RefPtr<Promise> p = Promise::Create(global, aRv);
    519  if (NS_WARN_IF(aRv.Failed())) {
    520    return nullptr;
    521  }
    522  nsCOMPtr<nsPIDOMWindowInner> owner = do_QueryInterface(global);
    523  /* If the relevant global object of this does not have transient activation,
    524   * return a promise rejected with a DOMException object whose name attribute
    525   * has the value InvalidStateError. */
    526  WindowContext* wc = owner->GetWindowContext();
    527  if (!wc || !wc->HasValidTransientUserGestureActivation()) {
    528    p->MaybeRejectWithInvalidStateError(
    529        "getDisplayMedia requires transient activation from a user gesture.");
    530    return p.forget();
    531  }
    532  /* If constraints.video is false, return a promise rejected with a newly
    533   * created TypeError. */
    534  if (!MediaManager::IsOn(aConstraints.mVideo)) {
    535    p->MaybeRejectWithTypeError("video is required");
    536    return p.forget();
    537  }
    538  MediaStreamConstraints c;
    539  auto& vc = c.mVideo.SetAsMediaTrackConstraints();
    540 
    541  if (aConstraints.mVideo.IsMediaTrackConstraints()) {
    542    vc = aConstraints.mVideo.GetAsMediaTrackConstraints();
    543    /* If CS contains a member named advanced, return a promise rejected with
    544     * a newly created TypeError. */
    545    if (vc.mAdvanced.WasPassed()) {
    546      p->MaybeRejectWithTypeError("advanced not allowed");
    547      return p.forget();
    548    }
    549    auto getCLR = [](const auto& aCon) -> const ConstrainLongRange& {
    550      static ConstrainLongRange empty;
    551      return (aCon.WasPassed() && !aCon.Value().IsLong())
    552                 ? aCon.Value().GetAsConstrainLongRange()
    553                 : empty;
    554    };
    555    auto getCDR = [](auto&& aCon) -> const ConstrainDoubleRange& {
    556      static ConstrainDoubleRange empty;
    557      return (aCon.WasPassed() && !aCon.Value().IsDouble())
    558                 ? aCon.Value().GetAsConstrainDoubleRange()
    559                 : empty;
    560    };
    561    const auto& w = getCLR(vc.mWidth);
    562    const auto& h = getCLR(vc.mHeight);
    563    const auto& f = getCDR(vc.mFrameRate);
    564    /* If CS contains a member whose name specifies a constrainable property
    565     * applicable to display surfaces, and whose value in turn is a dictionary
    566     * containing a member named either min or exact, return a promise
    567     * rejected with a newly created TypeError. */
    568    if (w.mMin.WasPassed() || h.mMin.WasPassed() || f.mMin.WasPassed()) {
    569      p->MaybeRejectWithTypeError("min not allowed");
    570      return p.forget();
    571    }
    572    if (w.mExact.WasPassed() || h.mExact.WasPassed() || f.mExact.WasPassed()) {
    573      p->MaybeRejectWithTypeError("exact not allowed");
    574      return p.forget();
    575    }
    576    /* If CS contains a member whose name, failedConstraint specifies a
    577     * constrainable property, constraint, applicable to display surfaces, and
    578     * whose value in turn is a dictionary containing a member named max, and
    579     * that member's value in turn is less than the constrainable property's
    580     * floor value, then let failedConstraint be the name of the constraint,
    581     * let message be either undefined or an informative human-readable
    582     * message, and return a promise rejected with a new OverconstrainedError
    583     * created by calling OverconstrainedError(failedConstraint, message). */
    584    // We fail early without incurring a prompt, on known-to-fail constraint
    585    // values that don't reveal anything about the user's system.
    586    const char* badConstraint = nullptr;
    587    if (w.mMax.WasPassed() && w.mMax.Value() < 1) {
    588      badConstraint = "width";
    589    }
    590    if (h.mMax.WasPassed() && h.mMax.Value() < 1) {
    591      badConstraint = "height";
    592    }
    593    if (f.mMax.WasPassed() && f.mMax.Value() < 1) {
    594      badConstraint = "frameRate";
    595    }
    596    if (badConstraint) {
    597      p->MaybeReject(MakeRefPtr<dom::MediaStreamError>(
    598          owner, *MakeRefPtr<MediaMgrError>(
    599                     MediaMgrError::Name::OverconstrainedError, "",
    600                     NS_ConvertASCIItoUTF16(badConstraint))));
    601      return p.forget();
    602    }
    603  }
    604  /* If the relevant settings object's responsible document is NOT fully
    605   * active, return a promise rejected with a DOMException object whose name
    606   * attribute has the value "InvalidStateError". */
    607  if (!owner->IsFullyActive()) {
    608    p->MaybeRejectWithInvalidStateError("The document is not fully active.");
    609    return p.forget();
    610  }
    611  // We ask for "screen" sharing.
    612  //
    613  // If this is a privileged call or permission is disabled, this gives us full
    614  // screen sharing by default, which is useful for internal testing.
    615  //
    616  // If this is a non-priviliged call, GetUserMedia() will change it to "window"
    617  // for us.
    618  vc.mMediaSource.Reset();
    619  vc.mMediaSource.Construct().AssignASCII(
    620      dom::GetEnumString(MediaSourceEnum::Screen));
    621 
    622  RefPtr<MediaDevices> self(this);
    623  MediaManager::Get()
    624      ->GetUserMedia(owner, c, aCallerType)
    625      ->Then(
    626          GetCurrentSerialEventTarget(), __func__,
    627          [this, self, p](RefPtr<DOMMediaStream>&& aStream) {
    628            if (!GetWindowIfCurrent()) {
    629              return;  // leave promise pending after navigation.
    630            }
    631            p->MaybeResolve(std::move(aStream));
    632          },
    633          [this, self, p](RefPtr<MediaMgrError>&& error) {
    634            nsPIDOMWindowInner* window = GetWindowIfCurrent();
    635            if (!window) {
    636              return;  // leave promise pending after navigation.
    637            }
    638            error->Reject(p);
    639          });
    640  return p.forget();
    641 }
    642 
    643 already_AddRefed<Promise> MediaDevices::SelectAudioOutput(
    644    const AudioOutputOptions& aOptions, CallerType aCallerType,
    645    ErrorResult& aRv) {
    646  nsCOMPtr<nsIGlobalObject> global = xpc::NativeGlobal(GetWrapper());
    647  RefPtr<Promise> p = Promise::Create(global, aRv);
    648  if (NS_WARN_IF(aRv.Failed())) {
    649    return nullptr;
    650  }
    651  /* (This includes the expected user activation update of
    652   * https://github.com/w3c/mediacapture-output/issues/107)
    653   * If the relevant global object of this does not have transient activation,
    654   * return a promise rejected with a DOMException object whose name attribute
    655   * has the value InvalidStateError. */
    656  nsCOMPtr<nsPIDOMWindowInner> owner = do_QueryInterface(global);
    657  WindowContext* wc = owner->GetWindowContext();
    658  if (!wc || !wc->HasValidTransientUserGestureActivation()) {
    659    p->MaybeRejectWithInvalidStateError(
    660        "selectAudioOutput requires transient user activation.");
    661    return p.forget();
    662  }
    663  RefPtr<MediaDevices> self(this);
    664  MediaManager::Get()
    665      ->SelectAudioOutput(owner, aOptions, aCallerType)
    666      ->Then(
    667          GetCurrentSerialEventTarget(), __func__,
    668          [this, self, p](RefPtr<LocalMediaDevice> aDevice) {
    669            nsPIDOMWindowInner* window = GetWindowIfCurrent();
    670            if (!window) {
    671              return;  // Leave Promise pending after navigation by design.
    672            }
    673            MOZ_ASSERT(aDevice->Kind() == dom::MediaDeviceKind::Audiooutput);
    674            mExplicitlyGrantedAudioOutputRawIds.Insert(aDevice->RawID());
    675            p->MaybeResolve(
    676                MakeRefPtr<MediaDeviceInfo>(aDevice->mID, aDevice->Kind(),
    677                                            aDevice->mName, aDevice->mGroupID));
    678          },
    679          [this, self, p](const RefPtr<MediaMgrError>& error) {
    680            nsPIDOMWindowInner* window = GetWindowIfCurrent();
    681            if (!window) {
    682              return;  // Leave Promise pending after navigation by design.
    683            }
    684            error->Reject(p);
    685          });
    686  return p.forget();
    687 }
    688 
    689 static RefPtr<AudioDeviceInfo> CopyWithNullDeviceId(
    690    AudioDeviceInfo* aDeviceInfo) {
    691  MOZ_ASSERT(aDeviceInfo->Preferred());
    692 
    693  nsString vendor;
    694  aDeviceInfo->GetVendor(vendor);
    695  uint16_t type;
    696  aDeviceInfo->GetType(&type);
    697  uint16_t state;
    698  aDeviceInfo->GetState(&state);
    699  uint16_t pref;
    700  aDeviceInfo->GetPreferred(&pref);
    701  uint16_t supportedFormat;
    702  aDeviceInfo->GetSupportedFormat(&supportedFormat);
    703  uint16_t defaultFormat;
    704  aDeviceInfo->GetDefaultFormat(&defaultFormat);
    705  uint32_t maxChannels;
    706  aDeviceInfo->GetMaxChannels(&maxChannels);
    707  uint32_t defaultRate;
    708  aDeviceInfo->GetDefaultRate(&defaultRate);
    709  uint32_t maxRate;
    710  aDeviceInfo->GetMaxRate(&maxRate);
    711  uint32_t minRate;
    712  aDeviceInfo->GetMinRate(&minRate);
    713  uint32_t maxLatency;
    714  aDeviceInfo->GetMaxLatency(&maxLatency);
    715  uint32_t minLatency;
    716  aDeviceInfo->GetMinLatency(&minLatency);
    717 
    718  return MakeRefPtr<AudioDeviceInfo>(
    719      nullptr, aDeviceInfo->Name(), aDeviceInfo->GroupID(), vendor, type, state,
    720      pref, supportedFormat, defaultFormat, maxChannels, defaultRate, maxRate,
    721      minRate, maxLatency, minLatency);
    722 }
    723 
    724 RefPtr<MediaDevices::SinkInfoPromise> MediaDevices::GetSinkDevice(
    725    const nsString& aDeviceId) {
    726  MOZ_ASSERT(NS_IsMainThread());
    727  return MediaManager::Get()
    728      ->GetPhysicalDevices()
    729      ->Then(
    730          GetCurrentSerialEventTarget(), __func__,
    731          [self = RefPtr(this), this,
    732           aDeviceId](RefPtr<const MediaDeviceSetRefCnt> aRawDevices) {
    733            nsCOMPtr<nsPIDOMWindowInner> window = GetOwnerWindow();
    734            if (!window) {
    735              return LocalDeviceSetPromise::CreateAndReject(
    736                  new MediaMgrError(MediaMgrError::Name::AbortError), __func__);
    737            }
    738            // Don't filter if matching the preferred device, because that may
    739            // not be exposed.
    740            RefPtr devices = aDeviceId.IsEmpty()
    741                                 ? std::move(aRawDevices)
    742                                 : FilterExposedDevices(*aRawDevices);
    743            return MediaManager::Get()->AnonymizeDevices(window,
    744                                                         std::move(devices));
    745          },
    746          [](RefPtr<MediaMgrError>&& reason) {
    747            MOZ_ASSERT_UNREACHABLE("GetPhysicalDevices does not reject");
    748            return RefPtr<LocalDeviceSetPromise>();
    749          })
    750      ->Then(
    751          GetCurrentSerialEventTarget(), __func__,
    752          [aDeviceId](RefPtr<LocalMediaDeviceSetRefCnt> aDevices) {
    753            RefPtr<AudioDeviceInfo> outputInfo;
    754            // Check for a matching device.
    755            for (const RefPtr<LocalMediaDevice>& device : *aDevices) {
    756              if (device->Kind() != dom::MediaDeviceKind::Audiooutput) {
    757                continue;
    758              }
    759              if (aDeviceId.IsEmpty()) {
    760                MOZ_ASSERT(device->GetAudioDeviceInfo()->Preferred(),
    761                           "First Audiooutput should be preferred");
    762                return SinkInfoPromise::CreateAndResolve(
    763                    CopyWithNullDeviceId(device->GetAudioDeviceInfo()),
    764                    __func__);
    765              } else if (aDeviceId.Equals(device->mID)) {
    766                return SinkInfoPromise::CreateAndResolve(
    767                    device->GetAudioDeviceInfo(), __func__);
    768              }
    769            }
    770            /* If sinkId is not the empty string and does not match any audio
    771             * output device identified by the result that would be provided
    772             * by enumerateDevices(), reject p with a new DOMException whose
    773             * name is NotFoundError and abort these substeps. */
    774            return SinkInfoPromise::CreateAndReject(NS_ERROR_NOT_AVAILABLE,
    775                                                    __func__);
    776          },
    777          // aRejectMethod =
    778          [](RefPtr<MediaMgrError>&& aError) {
    779            return SinkInfoPromise::CreateAndReject(NS_ERROR_NOT_AVAILABLE,
    780                                                    __func__);
    781          });
    782 }
    783 
    784 NS_IMPL_ISUPPORTS_CYCLE_COLLECTION_INHERITED_0(MediaDevices,
    785                                               DOMEventTargetHelper)
    786 NS_IMPL_CYCLE_COLLECTION_INHERITED(MediaDevices, DOMEventTargetHelper,
    787                                   mPendingEnumerateDevicesPromises)
    788 
    789 void MediaDevices::OnDeviceChange() {
    790  MOZ_ASSERT(NS_IsMainThread());
    791  if (NS_FAILED(CheckCurrentGlobalCorrectness())) {
    792    // This is a ghost window, don't do anything.
    793    return;
    794  }
    795 
    796  mHaveUnprocessedDeviceListChange = true;
    797  MaybeResumeDeviceExposure();
    798 }
    799 
    800 mozilla::dom::EventHandlerNonNull* MediaDevices::GetOndevicechange() {
    801  return GetEventHandler(nsGkAtoms::ondevicechange);
    802 }
    803 
    804 void MediaDevices::SetupDeviceChangeListener() {
    805  if (mIsDeviceChangeListenerSetUp) {
    806    return;
    807  }
    808 
    809  nsPIDOMWindowInner* window = GetOwnerWindow();
    810  if (!window) {
    811    return;
    812  }
    813 
    814  mDeviceChangeListener = MediaManager::Get()->DeviceListChangeEvent().Connect(
    815      GetMainThreadSerialEventTarget(), this, &MediaDevices::OnDeviceChange);
    816  mIsDeviceChangeListenerSetUp = true;
    817 
    818  MediaManager::Get()->GetPhysicalDevices()->Then(
    819      GetCurrentSerialEventTarget(), __func__,
    820      [self = RefPtr(this), this](RefPtr<const MediaDeviceSetRefCnt> aDevices) {
    821        mLastPhysicalDevices = std::move(aDevices);
    822      },
    823      [](RefPtr<MediaMgrError>&& reason) {
    824        MOZ_ASSERT_UNREACHABLE("GetPhysicalDevices does not reject");
    825      });
    826 }
    827 
    828 void MediaDevices::SetOndevicechange(
    829    mozilla::dom::EventHandlerNonNull* aCallback) {
    830  SetEventHandler(nsGkAtoms::ondevicechange, aCallback);
    831 }
    832 
    833 void MediaDevices::EventListenerAdded(nsAtom* aType) {
    834  DOMEventTargetHelper::EventListenerAdded(aType);
    835  SetupDeviceChangeListener();
    836 }
    837 
    838 JSObject* MediaDevices::WrapObject(JSContext* aCx,
    839                                   JS::Handle<JSObject*> aGivenProto) {
    840  return MediaDevices_Binding::Wrap(aCx, this, aGivenProto);
    841 }
    842 
    843 }  // namespace mozilla::dom