MediaManager.cpp (191515B)
1 /* -*- Mode: c++; c-basic-offset: 2; indent-tabs-mode: nil; tab-width: 40 -*- */ 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 file, 5 * You can obtain one at http://mozilla.org/MPL/2.0/. */ 6 7 #include "MediaManager.h" 8 9 #include "AudioCaptureTrack.h" 10 #include "AudioDeviceInfo.h" 11 #include "AudioStreamTrack.h" 12 #include "CubebDeviceEnumerator.h" 13 #include "CubebInputStream.h" 14 #include "MediaTimer.h" 15 #include "MediaTrackConstraints.h" 16 #include "MediaTrackGraph.h" 17 #include "MediaTrackListener.h" 18 #include "Tracing.h" 19 #include "VideoStreamTrack.h" 20 #include "VideoUtils.h" 21 #include "mozilla/Base64.h" 22 #include "mozilla/EventTargetCapability.h" 23 #include "mozilla/MozPromise.h" 24 #include "mozilla/NullPrincipal.h" 25 #include "mozilla/PeerIdentity.h" 26 #include "mozilla/PermissionDelegateHandler.h" 27 #include "mozilla/Sprintf.h" 28 #include "mozilla/StaticPrefs_media.h" 29 #include "mozilla/dom/BindingDeclarations.h" 30 #include "mozilla/dom/Document.h" 31 #include "mozilla/dom/Element.h" 32 #include "mozilla/dom/FeaturePolicyUtils.h" 33 #include "mozilla/dom/File.h" 34 #include "mozilla/dom/GetUserMediaRequestBinding.h" 35 #include "mozilla/dom/MediaDeviceInfo.h" 36 #include "mozilla/dom/MediaDevices.h" 37 #include "mozilla/dom/MediaDevicesBinding.h" 38 #include "mozilla/dom/MediaStreamBinding.h" 39 #include "mozilla/dom/MediaStreamTrackBinding.h" 40 #include "mozilla/dom/Promise.h" 41 #include "mozilla/dom/UserActivation.h" 42 #include "mozilla/dom/WindowContext.h" 43 #include "mozilla/dom/WindowGlobalChild.h" 44 #include "mozilla/glean/DomMediaWebrtcMetrics.h" 45 #include "mozilla/ipc/BackgroundChild.h" 46 #include "mozilla/ipc/PBackgroundChild.h" 47 #include "mozilla/media/CamerasTypes.h" 48 #include "mozilla/media/MediaChild.h" 49 #include "mozilla/media/MediaTaskUtils.h" 50 #include "nsAppDirectoryServiceDefs.h" 51 #include "nsArray.h" 52 #include "nsContentUtils.h" 53 #include "nsGlobalWindowInner.h" 54 #include "nsHashPropertyBag.h" 55 #include "nsIEventTarget.h" 56 #include "nsIPermissionManager.h" 57 #include "nsIUUIDGenerator.h" 58 #include "nsJSUtils.h" 59 #include "nsNetCID.h" 60 #include "nsNetUtil.h" 61 #include "nsProxyRelease.h" 62 #include "nspr.h" 63 #include "nss.h" 64 #include "pk11pub.h" 65 66 /* Using WebRTC backend on Desktops (Mac, Windows, Linux), otherwise default */ 67 #include "MediaEngineFake.h" 68 #include "MediaEngineSource.h" 69 #if defined(MOZ_WEBRTC) 70 # include "MediaEngineWebRTC.h" 71 # include "MediaEngineWebRTCAudio.h" 72 # include "browser_logging/WebRtcLog.h" 73 # include "libwebrtcglue/WebrtcTaskQueueWrapper.h" 74 # include "modules/audio_processing/include/audio_processing.h" 75 #endif 76 77 #if defined(XP_WIN) 78 # include <objbase.h> 79 #endif 80 81 // A specialization of nsMainThreadPtrHolder for 82 // mozilla::dom::CallbackObjectHolder. See documentation for 83 // nsMainThreadPtrHolder in nsProxyRelease.h. This specialization lets us avoid 84 // wrapping the CallbackObjectHolder into a separate refcounted object. 85 template <class WebIDLCallbackT, class XPCOMCallbackT> 86 class nsMainThreadPtrHolder< 87 mozilla::dom::CallbackObjectHolder<WebIDLCallbackT, XPCOMCallbackT>> 88 final { 89 typedef mozilla::dom::CallbackObjectHolder<WebIDLCallbackT, XPCOMCallbackT> 90 Holder; 91 92 public: 93 nsMainThreadPtrHolder(const char* aName, Holder&& aHolder) 94 : mHolder(std::move(aHolder)) 95 #ifndef RELEASE_OR_BETA 96 , 97 mName(aName) 98 #endif 99 { 100 MOZ_ASSERT(NS_IsMainThread()); 101 } 102 103 private: 104 // We can be released on any thread. 105 ~nsMainThreadPtrHolder() { 106 if (NS_IsMainThread()) { 107 mHolder.Reset(); 108 } else if (mHolder.GetISupports()) { 109 nsCOMPtr<nsIEventTarget> target = do_GetMainThread(); 110 MOZ_ASSERT(target); 111 NS_ProxyRelease( 112 #ifdef RELEASE_OR_BETA 113 nullptr, 114 #else 115 mName, 116 #endif 117 target, mHolder.Forget()); 118 } 119 } 120 121 public: 122 Holder* get() { 123 // Nobody should be touching the raw pointer off-main-thread. 124 if (MOZ_UNLIKELY(!NS_IsMainThread())) { 125 NS_ERROR("Can't dereference nsMainThreadPtrHolder off main thread"); 126 MOZ_CRASH(); 127 } 128 return &mHolder; 129 } 130 131 bool operator!() const { return !mHolder; } 132 133 NS_INLINE_DECL_THREADSAFE_REFCOUNTING(nsMainThreadPtrHolder<Holder>) 134 135 private: 136 // Our holder. 137 Holder mHolder; 138 139 #ifndef RELEASE_OR_BETA 140 const char* mName = nullptr; 141 #endif 142 143 // Copy constructor and operator= not implemented. Once constructed, the 144 // holder is immutable. 145 Holder& operator=(const nsMainThreadPtrHolder& aOther) = delete; 146 nsMainThreadPtrHolder(const nsMainThreadPtrHolder& aOther) = delete; 147 }; 148 149 namespace mozilla { 150 151 LazyLogModule gMediaManagerLog("MediaManager"); 152 #define LOG(...) MOZ_LOG(gMediaManagerLog, LogLevel::Debug, (__VA_ARGS__)) 153 154 class GetUserMediaStreamTask; 155 class LocalTrackSource; 156 class SelectAudioOutputTask; 157 158 using camera::CamerasAccessStatus; 159 using dom::BFCacheStatus; 160 using dom::CallerType; 161 using dom::ConstrainDOMStringParameters; 162 using dom::ConstrainDoubleRange; 163 using dom::ConstrainLongRange; 164 using dom::DisplayMediaStreamConstraints; 165 using dom::Document; 166 using dom::Element; 167 using dom::FeaturePolicyUtils; 168 using dom::File; 169 using dom::GetUserMediaRequest; 170 using dom::MediaDeviceKind; 171 using dom::MediaDevices; 172 using dom::MediaSourceEnum; 173 using dom::MediaStreamConstraints; 174 using dom::MediaStreamError; 175 using dom::MediaStreamTrack; 176 using dom::MediaStreamTrackSource; 177 using dom::MediaTrackCapabilities; 178 using dom::MediaTrackConstraints; 179 using dom::MediaTrackConstraintSet; 180 using dom::MediaTrackSettings; 181 using dom::OwningBooleanOrMediaTrackConstraints; 182 using dom::OwningStringOrStringSequence; 183 using dom::OwningStringOrStringSequenceOrConstrainDOMStringParameters; 184 using dom::Promise; 185 using dom::Sequence; 186 using dom::UserActivation; 187 using dom::VideoResizeModeEnum; 188 using dom::WindowGlobalChild; 189 using ConstDeviceSetPromise = MediaManager::ConstDeviceSetPromise; 190 using DeviceSetPromise = MediaManager::DeviceSetPromise; 191 using LocalDevicePromise = MediaManager::LocalDevicePromise; 192 using LocalDeviceSetPromise = MediaManager::LocalDeviceSetPromise; 193 using LocalMediaDeviceSetRefCnt = MediaManager::LocalMediaDeviceSetRefCnt; 194 using MediaDeviceSetRefCnt = MediaManager::MediaDeviceSetRefCnt; 195 using media::NewRunnableFrom; 196 using media::NewTaskFrom; 197 using media::Refcountable; 198 199 // Whether main thread actions of MediaManager shutdown (except for clearing 200 // of sSingleton) have completed. 201 static bool sHasMainThreadShutdown; 202 203 struct DeviceState { 204 DeviceState(RefPtr<LocalMediaDevice> aDevice, 205 RefPtr<LocalTrackSource> aTrackSource, bool aOffWhileDisabled) 206 : mOffWhileDisabled(aOffWhileDisabled), 207 mDevice(std::move(aDevice)), 208 mTrackSource(std::move(aTrackSource)) { 209 MOZ_ASSERT(mDevice); 210 MOZ_ASSERT(mTrackSource); 211 } 212 213 // true if we have allocated mDevice. When not allocated, we may not stop or 214 // deallocate. 215 // MainThread only. 216 bool mAllocated = false; 217 218 // true if we have stopped mDevice, this is a terminal state. 219 // MainThread only. 220 bool mStopped = false; 221 222 // true if mDevice is currently enabled. 223 // A device must be both enabled and unmuted to be turned on and capturing. 224 // MainThread only. 225 bool mDeviceEnabled = false; 226 227 // true if mDevice is currently muted. 228 // A device that is either muted or disabled is turned off and not capturing. 229 // MainThread only. 230 bool mDeviceMuted; 231 232 // true if the application has currently enabled mDevice. 233 // MainThread only. 234 bool mTrackEnabled = false; 235 236 // Time when the application last enabled mDevice. 237 // MainThread only. 238 TimeStamp mTrackEnabledTime; 239 240 // true if an operation to Start() or Stop() mDevice has been dispatched to 241 // the media thread and is not finished yet. 242 // MainThread only. 243 bool mOperationInProgress = false; 244 245 // true if we are allowed to turn off the underlying source while all tracks 246 // are disabled or muted. 247 // MainThread only. 248 bool mOffWhileDisabled = false; 249 250 // Timer triggered by a MediaStreamTrackSource signaling that all tracks got 251 // disabled. When the timer fires we initiate Stop()ing mDevice. 252 // If set we allow dynamically stopping and starting mDevice. 253 // Any thread. 254 const RefPtr<MediaTimer<TimeStamp>> mDisableTimer = 255 new MediaTimer<TimeStamp>(); 256 257 // The underlying device we keep state for. Always non-null. 258 // Threadsafe access, but see method declarations for individual constraints. 259 const RefPtr<LocalMediaDevice> mDevice; 260 261 // The MediaStreamTrackSource for any tracks (original and clones) originating 262 // from this device. Always non-null. Threadsafe access, but see method 263 // declarations for individual constraints. 264 const RefPtr<LocalTrackSource> mTrackSource; 265 }; 266 267 /** 268 * This mimics the capture state from nsIMediaManagerService. 269 */ 270 enum class CaptureState : uint16_t { 271 Off = nsIMediaManagerService::STATE_NOCAPTURE, 272 Enabled = nsIMediaManagerService::STATE_CAPTURE_ENABLED, 273 Disabled = nsIMediaManagerService::STATE_CAPTURE_DISABLED, 274 }; 275 276 static CaptureState CombineCaptureState(CaptureState aFirst, 277 CaptureState aSecond) { 278 if (aFirst == CaptureState::Enabled || aSecond == CaptureState::Enabled) { 279 return CaptureState::Enabled; 280 } 281 if (aFirst == CaptureState::Disabled || aSecond == CaptureState::Disabled) { 282 return CaptureState::Disabled; 283 } 284 MOZ_ASSERT(aFirst == CaptureState::Off); 285 MOZ_ASSERT(aSecond == CaptureState::Off); 286 return CaptureState::Off; 287 } 288 289 static uint16_t FromCaptureState(CaptureState aState) { 290 MOZ_ASSERT(aState == CaptureState::Off || aState == CaptureState::Enabled || 291 aState == CaptureState::Disabled); 292 return static_cast<uint16_t>(aState); 293 } 294 295 void MediaManager::CallOnError(GetUserMediaErrorCallback& aCallback, 296 MediaStreamError& aError) { 297 aCallback.Call(aError); 298 } 299 300 void MediaManager::CallOnSuccess(GetUserMediaSuccessCallback& aCallback, 301 DOMMediaStream& aStream) { 302 aCallback.Call(aStream); 303 } 304 305 enum class PersistentPermissionState : uint32_t { 306 Unknown = nsIPermissionManager::UNKNOWN_ACTION, 307 Allow = nsIPermissionManager::ALLOW_ACTION, 308 Deny = nsIPermissionManager::DENY_ACTION, 309 Prompt = nsIPermissionManager::PROMPT_ACTION, 310 }; 311 312 static PersistentPermissionState CheckPermission( 313 PersistentPermissionState aPermission) { 314 switch (aPermission) { 315 case PersistentPermissionState::Unknown: 316 case PersistentPermissionState::Allow: 317 case PersistentPermissionState::Deny: 318 case PersistentPermissionState::Prompt: 319 return aPermission; 320 } 321 MOZ_CRASH("Unexpected permission value"); 322 } 323 324 struct WindowPersistentPermissionState { 325 PersistentPermissionState mCameraPermission; 326 PersistentPermissionState mMicrophonePermission; 327 }; 328 329 static Result<WindowPersistentPermissionState, nsresult> 330 GetPersistentPermissions(uint64_t aWindowId) { 331 auto* window = nsGlobalWindowInner::GetInnerWindowWithId(aWindowId); 332 if (NS_WARN_IF(!window) || NS_WARN_IF(!window->GetPrincipal())) { 333 return Err(NS_ERROR_INVALID_ARG); 334 } 335 336 Document* doc = window->GetExtantDoc(); 337 if (NS_WARN_IF(!doc)) { 338 return Err(NS_ERROR_INVALID_ARG); 339 } 340 341 nsIPrincipal* principal = window->GetPrincipal(); 342 if (NS_WARN_IF(!principal)) { 343 return Err(NS_ERROR_INVALID_ARG); 344 } 345 346 nsresult rv; 347 RefPtr<PermissionDelegateHandler> permDelegate = 348 doc->GetPermissionDelegateHandler(); 349 if (NS_WARN_IF(!permDelegate)) { 350 return Err(NS_ERROR_INVALID_ARG); 351 } 352 353 uint32_t audio = nsIPermissionManager::UNKNOWN_ACTION; 354 uint32_t video = nsIPermissionManager::UNKNOWN_ACTION; 355 { 356 rv = permDelegate->GetPermission("microphone"_ns, &audio, true); 357 if (NS_WARN_IF(NS_FAILED(rv))) { 358 return Err(rv); 359 } 360 rv = permDelegate->GetPermission("camera"_ns, &video, true); 361 if (NS_WARN_IF(NS_FAILED(rv))) { 362 return Err(rv); 363 } 364 } 365 366 return WindowPersistentPermissionState{ 367 CheckPermission(static_cast<PersistentPermissionState>(video)), 368 CheckPermission(static_cast<PersistentPermissionState>(audio))}; 369 } 370 371 /** 372 * DeviceListener has threadsafe refcounting for use across the main, media and 373 * MTG threads. But it has a non-threadsafe SupportsWeakPtr for WeakPtr usage 374 * only from main thread, to ensure that garbage- and cycle-collected objects 375 * don't hold a reference to it during late shutdown. 376 */ 377 class DeviceListener : public SupportsWeakPtr { 378 public: 379 typedef MozPromise<bool /* aIgnored */, RefPtr<MediaMgrError>, false> 380 DeviceListenerPromise; 381 382 NS_INLINE_DECL_THREADSAFE_REFCOUNTING_WITH_DELETE_ON_MAIN_THREAD( 383 DeviceListener) 384 385 DeviceListener(); 386 387 /** 388 * Registers this device listener as belonging to the given window listener. 389 * Stop() must be called on registered DeviceListeners before destruction. 390 */ 391 void Register(GetUserMediaWindowListener* aListener); 392 393 /** 394 * Marks this listener as active and creates the internal device state. 395 */ 396 void Activate(RefPtr<LocalMediaDevice> aDevice, 397 RefPtr<LocalTrackSource> aTrackSource, bool aStartMuted, 398 bool aIsAllocated); 399 400 /** 401 * Posts a task to initialize and start the associated device. 402 */ 403 RefPtr<DeviceListenerPromise> InitializeAsync(); 404 405 private: 406 /** 407 * Initializes synchronously. Must be called on the media thread. 408 */ 409 nsresult Initialize(PrincipalHandle aPrincipal, LocalMediaDevice* aDevice, 410 MediaTrack* aTrack, bool aStartDevice); 411 412 public: 413 /** 414 * Synchronously clones this device listener, setting up the device to match 415 * our current device state asynchronously. Settings, constraints and other 416 * main thread state starts applying immediately. 417 */ 418 already_AddRefed<DeviceListener> Clone() const; 419 420 /** 421 * Posts a task to stop the device associated with this DeviceListener and 422 * notifies the associated window listener that a track was stopped. 423 * 424 * This will also clean up the weak reference to the associated window 425 * listener, and tell the window listener to remove its hard reference to this 426 * DeviceListener, so any caller will need to keep its own hard ref. 427 */ 428 void Stop(); 429 430 /** 431 * Gets the main thread MediaTrackSettings from the MediaEngineSource 432 * associated with aTrack. 433 */ 434 void GetSettings(MediaTrackSettings& aOutSettings) const; 435 436 /** 437 * Gets the main thread MediaTrackCapabilities from the MediaEngineSource 438 * associated with aTrack. 439 */ 440 void GetCapabilities(MediaTrackCapabilities& aOutCapabilities) const; 441 442 /** 443 * Posts a task to set the enabled state of the device associated with this 444 * DeviceListener to aEnabled and notifies the associated window listener that 445 * a track's state has changed. 446 * 447 * Turning the hardware off while the device is disabled is supported for: 448 * - Camera (enabled by default, controlled by pref 449 * "media.getusermedia.camera.off_while_disabled.enabled") 450 * - Microphone (disabled by default, controlled by pref 451 * "media.getusermedia.microphone.off_while_disabled.enabled") 452 * Screen-, app-, or windowsharing is not supported at this time. 453 * 454 * The behavior is also different between disabling and enabling a device. 455 * While enabling is immediate, disabling only happens after a delay. 456 * This is now defaulting to 3 seconds but can be overriden by prefs: 457 * - "media.getusermedia.camera.off_while_disabled.delay_ms" and 458 * - "media.getusermedia.microphone.off_while_disabled.delay_ms". 459 * 460 * The delay is in place to prevent misuse by malicious sites. If a track is 461 * re-enabled before the delay has passed, the device will not be touched 462 * until another disable followed by the full delay happens. 463 */ 464 void SetDeviceEnabled(bool aEnabled); 465 466 /** 467 * Posts a task to set the muted state of the device associated with this 468 * DeviceListener to aMuted and notifies the associated window listener that a 469 * track's state has changed. 470 * 471 * Turning the hardware off while the device is muted is supported for: 472 * - Camera (enabled by default, controlled by pref 473 * "media.getusermedia.camera.off_while_disabled.enabled") 474 * - Microphone (disabled by default, controlled by pref 475 * "media.getusermedia.microphone.off_while_disabled.enabled") 476 * Screen-, app-, or windowsharing is not supported at this time. 477 */ 478 void SetDeviceMuted(bool aMuted); 479 480 /** 481 * Mutes or unmutes the associated video device if it is a camera. 482 */ 483 void MuteOrUnmuteCamera(bool aMute); 484 void MuteOrUnmuteMicrophone(bool aMute); 485 486 LocalMediaDevice* GetDevice() const { 487 return mDeviceState ? mDeviceState->mDevice.get() : nullptr; 488 } 489 490 LocalTrackSource* GetTrackSource() const { 491 return mDeviceState ? mDeviceState->mTrackSource.get() : nullptr; 492 } 493 494 bool Activated() const { return static_cast<bool>(mDeviceState); } 495 496 bool Stopped() const { return mStopped; } 497 498 bool CapturingVideo() const; 499 500 bool CapturingAudio() const; 501 502 CaptureState CapturingSource(MediaSourceEnum aSource) const; 503 504 RefPtr<DeviceListenerPromise> ApplyConstraints( 505 const MediaTrackConstraints& aConstraints, CallerType aCallerType); 506 507 PrincipalHandle GetPrincipalHandle() const; 508 509 size_t SizeOfIncludingThis(MallocSizeOf aMallocSizeOf) const { 510 size_t amount = aMallocSizeOf(this); 511 // Assume mPrincipalHandle refers to a principal owned elsewhere. 512 // DeviceState does not have support for memory accounting. 513 return amount; 514 } 515 516 private: 517 virtual ~DeviceListener() { 518 MOZ_ASSERT(mStopped); 519 MOZ_ASSERT(!mWindowListener); 520 } 521 522 using DeviceOperationPromise = 523 MozPromise<nsresult, bool, /* IsExclusive = */ true>; 524 525 /** 526 * Posts a task to start or stop the device associated with aTrack, based on 527 * a passed-in boolean. Private method used by SetDeviceEnabled and 528 * SetDeviceMuted. 529 */ 530 RefPtr<DeviceOperationPromise> UpdateDevice(bool aOn); 531 532 // true after this listener has had all devices stopped. MainThread only. 533 bool mStopped; 534 535 // never ever indirect off this; just for assertions 536 PRThread* mMainThreadCheck; 537 538 // Set in Register() on main thread, then read from any thread. 539 PrincipalHandle mPrincipalHandle; 540 541 // Weak pointer to the window listener that owns us. MainThread only. 542 GetUserMediaWindowListener* mWindowListener; 543 544 // Accessed from MediaTrackGraph thread, MediaManager thread, and MainThread 545 // No locking needed as it's set on Activate() and never assigned to again. 546 UniquePtr<DeviceState> mDeviceState; 547 548 MediaEventListener mCaptureEndedListener; 549 }; 550 551 /** 552 * This class represents a WindowID and handles all MediaTrackListeners 553 * (here subclassed as DeviceListeners) used to feed GetUserMedia tracks. 554 * It proxies feedback from them into messages for browser chrome. 555 * The DeviceListeners are used to Start() and Stop() the underlying 556 * MediaEngineSource when MediaStreams are assigned and deassigned in content. 557 */ 558 class GetUserMediaWindowListener { 559 friend MediaManager; 560 561 public: 562 NS_INLINE_DECL_THREADSAFE_REFCOUNTING(GetUserMediaWindowListener) 563 564 // Create in an inactive state 565 GetUserMediaWindowListener(uint64_t aWindowID, 566 const PrincipalHandle& aPrincipalHandle) 567 : mWindowID(aWindowID), 568 mPrincipalHandle(aPrincipalHandle), 569 mChromeNotificationTaskPosted(false) {} 570 571 /** 572 * Registers an inactive gUM device listener for this WindowListener. 573 */ 574 void Register(RefPtr<DeviceListener> aListener) { 575 MOZ_ASSERT(NS_IsMainThread()); 576 MOZ_ASSERT(aListener); 577 MOZ_ASSERT(!aListener->Activated()); 578 MOZ_ASSERT(!mInactiveListeners.Contains(aListener), "Already registered"); 579 MOZ_ASSERT(!mActiveListeners.Contains(aListener), "Already activated"); 580 581 aListener->Register(this); 582 mInactiveListeners.AppendElement(std::move(aListener)); 583 } 584 585 /** 586 * Activates an already registered and inactive gUM device listener for this 587 * WindowListener. 588 */ 589 void Activate(RefPtr<DeviceListener> aListener, 590 RefPtr<LocalMediaDevice> aDevice, 591 RefPtr<LocalTrackSource> aTrackSource, bool aIsAllocated) { 592 MOZ_ASSERT(NS_IsMainThread()); 593 MOZ_ASSERT(aListener); 594 MOZ_ASSERT(!aListener->Activated()); 595 MOZ_ASSERT(mInactiveListeners.Contains(aListener), 596 "Must be registered to activate"); 597 MOZ_ASSERT(!mActiveListeners.Contains(aListener), "Already activated"); 598 599 bool muted = false; 600 if (aDevice->Kind() == MediaDeviceKind::Videoinput) { 601 muted = mCamerasAreMuted; 602 } else if (aDevice->Kind() == MediaDeviceKind::Audioinput) { 603 muted = mMicrophonesAreMuted; 604 } else { 605 MOZ_CRASH("Unexpected device kind"); 606 } 607 608 mInactiveListeners.RemoveElement(aListener); 609 aListener->Activate(std::move(aDevice), std::move(aTrackSource), muted, 610 aIsAllocated); 611 mActiveListeners.AppendElement(std::move(aListener)); 612 } 613 614 /** 615 * Removes all DeviceListeners from this window listener. 616 * Removes this window listener from the list of active windows, so callers 617 * need to make sure to hold a strong reference. 618 */ 619 void RemoveAll() { 620 MOZ_ASSERT(NS_IsMainThread()); 621 622 for (auto& l : mInactiveListeners.Clone()) { 623 Remove(l); 624 } 625 for (auto& l : mActiveListeners.Clone()) { 626 Remove(l); 627 } 628 MOZ_ASSERT(mInactiveListeners.Length() == 0); 629 MOZ_ASSERT(mActiveListeners.Length() == 0); 630 631 MediaManager* mgr = MediaManager::GetIfExists(); 632 if (!mgr) { 633 MOZ_ASSERT(false, "MediaManager should stay until everything is removed"); 634 return; 635 } 636 GetUserMediaWindowListener* windowListener = 637 mgr->GetWindowListener(mWindowID); 638 639 if (!windowListener) { 640 nsCOMPtr<nsIObserverService> obs = services::GetObserverService(); 641 auto* globalWindow = nsGlobalWindowInner::GetInnerWindowWithId(mWindowID); 642 if (globalWindow) { 643 auto req = MakeRefPtr<GetUserMediaRequest>( 644 globalWindow, VoidString(), VoidString(), 645 UserActivation::IsHandlingUserInput()); 646 obs->NotifyWhenScriptSafe(req, "recording-device-stopped", nullptr); 647 } 648 return; 649 } 650 651 MOZ_ASSERT(windowListener == this, 652 "There should only be one window listener per window ID"); 653 654 LOG("GUMWindowListener %p removing windowID %" PRIu64, this, mWindowID); 655 mgr->RemoveWindowID(mWindowID); 656 } 657 658 /** 659 * Removes a listener from our lists. Safe to call without holding a hard 660 * reference. That said, you'll still want to iterate on a copy of said lists, 661 * if you end up calling this method (or methods that may call this method) in 662 * the loop, to avoid inadvertently skipping members. 663 * 664 * For use only from GetUserMediaWindowListener and DeviceListener. 665 */ 666 bool Remove(RefPtr<DeviceListener> aListener) { 667 // We refcount aListener on entry since we're going to proxy-release it 668 // below to prevent the refcount going to zero on callers who might be 669 // inside the listener, but operating without a hard reference to self. 670 MOZ_ASSERT(NS_IsMainThread()); 671 672 if (!mInactiveListeners.RemoveElement(aListener) && 673 !mActiveListeners.RemoveElement(aListener)) { 674 return false; 675 } 676 MOZ_ASSERT(!mInactiveListeners.Contains(aListener), 677 "A DeviceListener should only be once in one of " 678 "mInactiveListeners and mActiveListeners"); 679 MOZ_ASSERT(!mActiveListeners.Contains(aListener), 680 "A DeviceListener should only be once in one of " 681 "mInactiveListeners and mActiveListeners"); 682 683 LOG("GUMWindowListener %p stopping DeviceListener %p.", this, 684 aListener.get()); 685 aListener->Stop(); 686 687 if (LocalMediaDevice* removedDevice = aListener->GetDevice()) { 688 bool revokePermission = true; 689 nsString removedRawId; 690 nsString removedSourceType; 691 removedDevice->GetRawId(removedRawId); 692 removedDevice->GetMediaSource(removedSourceType); 693 694 for (const auto& l : mActiveListeners) { 695 if (LocalMediaDevice* device = l->GetDevice()) { 696 nsString rawId; 697 device->GetRawId(rawId); 698 if (removedRawId.Equals(rawId)) { 699 revokePermission = false; 700 break; 701 } 702 } 703 } 704 705 if (revokePermission) { 706 nsCOMPtr<nsIObserverService> obs = services::GetObserverService(); 707 auto* window = nsGlobalWindowInner::GetInnerWindowWithId(mWindowID); 708 auto req = MakeRefPtr<GetUserMediaRequest>( 709 window, removedRawId, removedSourceType, 710 UserActivation::IsHandlingUserInput()); 711 obs->NotifyWhenScriptSafe(req, "recording-device-stopped", nullptr); 712 } 713 } 714 715 if (mInactiveListeners.Length() == 0 && mActiveListeners.Length() == 0) { 716 LOG("GUMWindowListener %p Removed last DeviceListener. Cleaning up.", 717 this); 718 RemoveAll(); 719 } 720 721 nsCOMPtr<nsIEventTarget> mainTarget = do_GetMainThread(); 722 // To allow being invoked by callers not holding a strong reference to self, 723 // hold the listener alive until the stack has unwound, by always 724 // dispatching a runnable (aAlwaysProxy = true) 725 NS_ProxyRelease(__func__, mainTarget, aListener.forget(), true); 726 return true; 727 } 728 729 /** 730 * Stops all screen/window/audioCapture sharing, but not camera or microphone. 731 */ 732 void StopSharing(); 733 734 void StopRawID(const nsString& removedDeviceID); 735 736 void MuteOrUnmuteCameras(bool aMute); 737 void MuteOrUnmuteMicrophones(bool aMute); 738 739 /** 740 * Called by one of our DeviceListeners when one of its tracks has changed so 741 * that chrome state is affected. 742 * Schedules an event for the next stable state to update chrome. 743 */ 744 void ChromeAffectingStateChanged(); 745 746 /** 747 * Called in stable state to send a notification to update chrome. 748 */ 749 void NotifyChrome(); 750 751 bool CapturingVideo() const { 752 MOZ_ASSERT(NS_IsMainThread()); 753 for (auto& l : mActiveListeners) { 754 if (l->CapturingVideo()) { 755 return true; 756 } 757 } 758 return false; 759 } 760 761 bool CapturingAudio() const { 762 MOZ_ASSERT(NS_IsMainThread()); 763 for (auto& l : mActiveListeners) { 764 if (l->CapturingAudio()) { 765 return true; 766 } 767 } 768 return false; 769 } 770 771 CaptureState CapturingSource(MediaSourceEnum aSource) const { 772 MOZ_ASSERT(NS_IsMainThread()); 773 CaptureState result = CaptureState::Off; 774 for (auto& l : mActiveListeners) { 775 result = CombineCaptureState(result, l->CapturingSource(aSource)); 776 } 777 return result; 778 } 779 780 RefPtr<LocalMediaDeviceSetRefCnt> GetDevices() { 781 RefPtr devices = new LocalMediaDeviceSetRefCnt(); 782 for (auto& l : mActiveListeners) { 783 devices->AppendElement(l->GetDevice()); 784 } 785 return devices; 786 } 787 788 uint64_t WindowID() const { return mWindowID; } 789 790 PrincipalHandle GetPrincipalHandle() const { return mPrincipalHandle; } 791 792 size_t SizeOfIncludingThis(MallocSizeOf aMallocSizeOf) const { 793 size_t amount = aMallocSizeOf(this); 794 // Assume mPrincipalHandle refers to a principal owned elsewhere. 795 amount += mInactiveListeners.ShallowSizeOfExcludingThis(aMallocSizeOf); 796 for (const RefPtr<DeviceListener>& listener : mInactiveListeners) { 797 amount += listener->SizeOfIncludingThis(aMallocSizeOf); 798 } 799 amount += mActiveListeners.ShallowSizeOfExcludingThis(aMallocSizeOf); 800 for (const RefPtr<DeviceListener>& listener : mActiveListeners) { 801 amount += listener->SizeOfIncludingThis(aMallocSizeOf); 802 } 803 return amount; 804 } 805 806 private: 807 ~GetUserMediaWindowListener() { 808 MOZ_ASSERT(mInactiveListeners.Length() == 0, 809 "Inactive listeners should already be removed"); 810 MOZ_ASSERT(mActiveListeners.Length() == 0, 811 "Active listeners should already be removed"); 812 } 813 814 uint64_t mWindowID; 815 const PrincipalHandle mPrincipalHandle; 816 817 // true if we have scheduled a task to notify chrome in the next stable state. 818 // The task will reset this to false. MainThread only. 819 bool mChromeNotificationTaskPosted; 820 821 nsTArray<RefPtr<DeviceListener>> mInactiveListeners; 822 nsTArray<RefPtr<DeviceListener>> mActiveListeners; 823 824 // Whether camera and microphone access in this window are currently 825 // User Agent (UA) muted. When true, new and cloned tracks must start 826 // out muted, to avoid JS circumventing UA mute. Per-camera and 827 // per-microphone UA muting is not supported. 828 bool mCamerasAreMuted = false; 829 bool mMicrophonesAreMuted = false; 830 }; 831 832 class LocalTrackSource : public MediaStreamTrackSource { 833 public: 834 LocalTrackSource(nsIPrincipal* aPrincipal, const nsString& aLabel, 835 const RefPtr<DeviceListener>& aListener, 836 MediaSourceEnum aSource, MediaTrack* aTrack, 837 RefPtr<const PeerIdentity> aPeerIdentity, 838 TrackingId aTrackingId = TrackingId()) 839 : MediaStreamTrackSource(aPrincipal, aLabel, std::move(aTrackingId)), 840 mSource(aSource), 841 mTrack(aTrack), 842 mPeerIdentity(std::move(aPeerIdentity)), 843 mListener(aListener.get()) {} 844 845 MediaSourceEnum GetMediaSource() const override { return mSource; } 846 847 const PeerIdentity* GetPeerIdentity() const override { return mPeerIdentity; } 848 849 RefPtr<MediaStreamTrackSource::ApplyConstraintsPromise> ApplyConstraints( 850 const MediaTrackConstraints& aConstraints, 851 CallerType aCallerType) override { 852 MOZ_ASSERT(NS_IsMainThread()); 853 if (sHasMainThreadShutdown || !mListener) { 854 // Track has been stopped, or we are in shutdown. In either case 855 // there's no observable outcome, so pretend we succeeded. 856 return MediaStreamTrackSource::ApplyConstraintsPromise::CreateAndResolve( 857 false, __func__); 858 } 859 auto p = mListener->ApplyConstraints(aConstraints, aCallerType); 860 p->Then( 861 GetCurrentSerialEventTarget(), __func__, 862 [aConstraints, this, self = RefPtr(this)] { 863 ConstraintsChanged(aConstraints); 864 }, 865 [] {}); 866 return p; 867 } 868 869 void GetSettings(MediaTrackSettings& aOutSettings) override { 870 if (mListener) { 871 mListener->GetSettings(aOutSettings); 872 } 873 } 874 875 void GetCapabilities(MediaTrackCapabilities& aOutCapabilities) override { 876 if (mListener) { 877 mListener->GetCapabilities(aOutCapabilities); 878 } 879 } 880 881 void Stop() override { 882 if (mListener) { 883 mListener->Stop(); 884 mListener = nullptr; 885 } 886 if (!mTrack->IsDestroyed()) { 887 mTrack->Destroy(); 888 } 889 } 890 891 CloneResult Clone() override { 892 if (!mListener) { 893 return {}; 894 } 895 RefPtr listener = mListener->Clone(); 896 MOZ_ASSERT(listener); 897 if (!listener) { 898 return {}; 899 } 900 901 return {.mSource = listener->GetTrackSource(), 902 .mInputTrack = listener->GetTrackSource()->mTrack}; 903 } 904 905 void Disable() override { 906 if (mListener) { 907 mListener->SetDeviceEnabled(false); 908 } 909 } 910 911 void Enable() override { 912 if (mListener) { 913 mListener->SetDeviceEnabled(true); 914 } 915 } 916 917 void Mute() { 918 MutedChanged(true); 919 mTrack->SetDisabledTrackMode(DisabledTrackMode::SILENCE_BLACK); 920 } 921 922 void Unmute() { 923 MutedChanged(false); 924 mTrack->SetDisabledTrackMode(DisabledTrackMode::ENABLED); 925 } 926 927 const MediaSourceEnum mSource; 928 const RefPtr<MediaTrack> mTrack; 929 const RefPtr<const PeerIdentity> mPeerIdentity; 930 931 protected: 932 ~LocalTrackSource() { 933 MOZ_ASSERT(NS_IsMainThread()); 934 MOZ_ASSERT(mTrack->IsDestroyed()); 935 } 936 937 // This is a weak pointer to avoid having the DeviceListener (which may 938 // have references to threads and threadpools) kept alive by DOM-objects 939 // that may have ref-cycles and thus are released very late during 940 // shutdown, even after xpcom-shutdown-threads. See bug 1351655 for what 941 // can happen. 942 WeakPtr<DeviceListener> mListener; 943 }; 944 945 class AudioCaptureTrackSource : public LocalTrackSource { 946 public: 947 AudioCaptureTrackSource(nsIPrincipal* aPrincipal, nsPIDOMWindowInner* aWindow, 948 const nsString& aLabel, 949 AudioCaptureTrack* aAudioCaptureTrack, 950 RefPtr<PeerIdentity> aPeerIdentity) 951 : LocalTrackSource(aPrincipal, aLabel, nullptr, 952 MediaSourceEnum::AudioCapture, aAudioCaptureTrack, 953 std::move(aPeerIdentity)), 954 mWindow(aWindow), 955 mAudioCaptureTrack(aAudioCaptureTrack) { 956 mAudioCaptureTrack->Start(); 957 mAudioCaptureTrack->Graph()->RegisterCaptureTrackForWindow( 958 mWindow->WindowID(), mAudioCaptureTrack); 959 mWindow->SetAudioCapture(true); 960 } 961 962 void Stop() override { 963 MOZ_ASSERT(NS_IsMainThread()); 964 if (!mAudioCaptureTrack->IsDestroyed()) { 965 MOZ_ASSERT(mWindow); 966 mWindow->SetAudioCapture(false); 967 mAudioCaptureTrack->Graph()->UnregisterCaptureTrackForWindow( 968 mWindow->WindowID()); 969 mWindow = nullptr; 970 } 971 // LocalTrackSource destroys the track. 972 LocalTrackSource::Stop(); 973 MOZ_ASSERT(mAudioCaptureTrack->IsDestroyed()); 974 } 975 976 ProcessedMediaTrack* InputTrack() const { return mAudioCaptureTrack.get(); } 977 978 protected: 979 ~AudioCaptureTrackSource() { 980 MOZ_ASSERT(NS_IsMainThread()); 981 MOZ_ASSERT(mAudioCaptureTrack->IsDestroyed()); 982 } 983 984 RefPtr<nsPIDOMWindowInner> mWindow; 985 const RefPtr<AudioCaptureTrack> mAudioCaptureTrack; 986 }; 987 988 /** 989 * nsIMediaDevice implementation. 990 */ 991 NS_IMPL_ISUPPORTS(LocalMediaDevice, nsIMediaDevice) 992 993 MediaDevice::MediaDevice(MediaEngine* aEngine, MediaSourceEnum aMediaSource, 994 const nsString& aRawName, const nsString& aRawID, 995 const nsString& aRawGroupID, IsScary aIsScary, 996 const OsPromptable canRequestOsLevelPrompt) 997 : mEngine(aEngine), 998 mAudioDeviceInfo(nullptr), 999 mMediaSource(aMediaSource), 1000 mKind(MediaEngineSource::IsVideo(aMediaSource) 1001 ? MediaDeviceKind::Videoinput 1002 : MediaDeviceKind::Audioinput), 1003 mScary(aIsScary == IsScary::Yes), 1004 mCanRequestOsLevelPrompt(canRequestOsLevelPrompt == OsPromptable::Yes), 1005 mIsFake(mEngine->IsFake()), 1006 mType(NS_ConvertASCIItoUTF16(dom::GetEnumString(mKind))), 1007 mRawID(aRawID), 1008 mRawGroupID(aRawGroupID), 1009 mRawName(aRawName) { 1010 MOZ_ASSERT(mEngine); 1011 } 1012 1013 MediaDevice::MediaDevice(MediaEngine* aEngine, 1014 const RefPtr<AudioDeviceInfo>& aAudioDeviceInfo, 1015 const nsString& aRawID) 1016 : mEngine(aEngine), 1017 mAudioDeviceInfo(aAudioDeviceInfo), 1018 mMediaSource(mAudioDeviceInfo->Type() == AudioDeviceInfo::TYPE_INPUT 1019 ? MediaSourceEnum::Microphone 1020 : MediaSourceEnum::Other), 1021 mKind(mMediaSource == MediaSourceEnum::Microphone 1022 ? MediaDeviceKind::Audioinput 1023 : MediaDeviceKind::Audiooutput), 1024 mScary(false), 1025 mCanRequestOsLevelPrompt(false), 1026 mIsFake(false), 1027 mType(NS_ConvertASCIItoUTF16(dom::GetEnumString(mKind))), 1028 mRawID(aRawID), 1029 mRawGroupID(mAudioDeviceInfo->GroupID()), 1030 mRawName(mAudioDeviceInfo->Name()) {} 1031 1032 /* static */ 1033 RefPtr<MediaDevice> MediaDevice::CopyWithNewRawGroupId( 1034 const RefPtr<MediaDevice>& aOther, const nsString& aRawGroupID) { 1035 MOZ_ASSERT(!aOther->mAudioDeviceInfo, "device not supported"); 1036 return new MediaDevice(aOther->mEngine, aOther->mMediaSource, 1037 aOther->mRawName, aOther->mRawID, aRawGroupID, 1038 IsScary(aOther->mScary), 1039 OsPromptable(aOther->mCanRequestOsLevelPrompt)); 1040 } 1041 1042 MediaDevice::~MediaDevice() = default; 1043 1044 LocalMediaDevice::LocalMediaDevice(RefPtr<const MediaDevice> aRawDevice, 1045 const nsString& aID, 1046 const nsString& aGroupID, 1047 const nsString& aName) 1048 : mRawDevice(std::move(aRawDevice)), 1049 mName(aName), 1050 mID(aID), 1051 mGroupID(aGroupID) { 1052 MOZ_ASSERT(mRawDevice); 1053 } 1054 1055 /** 1056 * Helper functions that implement the constraints algorithm from 1057 * http://dev.w3.org/2011/webrtc/editor/getusermedia.html#methods-5 1058 */ 1059 1060 /* static */ 1061 bool LocalMediaDevice::StringsContain( 1062 const OwningStringOrStringSequence& aStrings, nsString aN) { 1063 return aStrings.IsString() ? aStrings.GetAsString() == aN 1064 : aStrings.GetAsStringSequence().Contains(aN); 1065 } 1066 1067 /* static */ 1068 uint32_t LocalMediaDevice::FitnessDistance( 1069 nsString aN, const ConstrainDOMStringParameters& aParams) { 1070 if (aParams.mExact.WasPassed() && 1071 !StringsContain(aParams.mExact.Value(), aN)) { 1072 return UINT32_MAX; 1073 } 1074 if (aParams.mIdeal.WasPassed() && 1075 !StringsContain(aParams.mIdeal.Value(), aN)) { 1076 return 1; 1077 } 1078 return 0; 1079 } 1080 1081 // Binding code doesn't templatize well... 1082 1083 /* static */ 1084 uint32_t LocalMediaDevice::FitnessDistance( 1085 nsString aN, 1086 const OwningStringOrStringSequenceOrConstrainDOMStringParameters& 1087 aConstraint) { 1088 if (aConstraint.IsString()) { 1089 ConstrainDOMStringParameters params; 1090 params.mIdeal.Construct(); 1091 params.mIdeal.Value().SetAsString() = aConstraint.GetAsString(); 1092 return FitnessDistance(aN, params); 1093 } else if (aConstraint.IsStringSequence()) { 1094 ConstrainDOMStringParameters params; 1095 params.mIdeal.Construct(); 1096 params.mIdeal.Value().SetAsStringSequence() = 1097 aConstraint.GetAsStringSequence(); 1098 return FitnessDistance(aN, params); 1099 } else { 1100 return FitnessDistance(aN, aConstraint.GetAsConstrainDOMStringParameters()); 1101 } 1102 } 1103 1104 uint32_t LocalMediaDevice::GetBestFitnessDistance( 1105 const nsTArray<const NormalizedConstraintSet*>& aConstraintSets, 1106 const MediaEnginePrefs& aPrefs, CallerType aCallerType) { 1107 MOZ_ASSERT(MediaManager::IsInMediaThread()); 1108 MOZ_ASSERT(GetMediaSource() != MediaSourceEnum::Other); 1109 1110 bool isChrome = aCallerType == CallerType::System; 1111 const nsString& id = isChrome ? RawID() : mID; 1112 auto type = GetMediaSource(); 1113 uint64_t distance = 0; 1114 if (!aConstraintSets.IsEmpty()) { 1115 if (isChrome /* For the screen/window sharing preview */ || 1116 type == MediaSourceEnum::Camera || 1117 type == MediaSourceEnum::Microphone) { 1118 distance += uint64_t(MediaConstraintsHelper::FitnessDistance( 1119 Some(id), aConstraintSets[0]->mDeviceId)) + 1120 uint64_t(MediaConstraintsHelper::FitnessDistance( 1121 Some(mGroupID), aConstraintSets[0]->mGroupId)); 1122 } 1123 } 1124 if (distance < UINT32_MAX) { 1125 // Forward request to underlying object to interrogate per-mode 1126 // capabilities. 1127 distance += Source()->GetBestFitnessDistance(aConstraintSets, aPrefs); 1128 } 1129 return std::min<uint64_t>(distance, UINT32_MAX); 1130 } 1131 1132 NS_IMETHODIMP 1133 LocalMediaDevice::GetRawName(nsAString& aName) { 1134 MOZ_ASSERT(NS_IsMainThread()); 1135 aName.Assign(mRawDevice->mRawName); 1136 return NS_OK; 1137 } 1138 1139 NS_IMETHODIMP 1140 LocalMediaDevice::GetType(nsAString& aType) { 1141 MOZ_ASSERT(NS_IsMainThread()); 1142 aType.Assign(mRawDevice->mType); 1143 return NS_OK; 1144 } 1145 1146 NS_IMETHODIMP 1147 LocalMediaDevice::GetRawId(nsAString& aID) { 1148 MOZ_ASSERT(NS_IsMainThread()); 1149 aID.Assign(RawID()); 1150 return NS_OK; 1151 } 1152 1153 NS_IMETHODIMP 1154 LocalMediaDevice::GetId(nsAString& aID) { 1155 MOZ_ASSERT(NS_IsMainThread()); 1156 aID.Assign(mID); 1157 return NS_OK; 1158 } 1159 1160 NS_IMETHODIMP 1161 LocalMediaDevice::GetScary(bool* aScary) { 1162 *aScary = mRawDevice->mScary; 1163 return NS_OK; 1164 } 1165 1166 NS_IMETHODIMP 1167 LocalMediaDevice::GetCanRequestOsLevelPrompt(bool* aCanRequestOsLevelPrompt) { 1168 *aCanRequestOsLevelPrompt = mRawDevice->mCanRequestOsLevelPrompt; 1169 return NS_OK; 1170 } 1171 1172 void LocalMediaDevice::GetSettings(MediaTrackSettings& aOutSettings) { 1173 MOZ_ASSERT(NS_IsMainThread()); 1174 Source()->GetSettings(aOutSettings); 1175 } 1176 1177 void LocalMediaDevice::GetCapabilities( 1178 MediaTrackCapabilities& aOutCapabilities) { 1179 MOZ_ASSERT(NS_IsMainThread()); 1180 Source()->GetCapabilities(aOutCapabilities); 1181 } 1182 1183 MediaEngineSource* LocalMediaDevice::Source() { 1184 if (!mSource) { 1185 mSource = mRawDevice->mEngine->CreateSource(mRawDevice); 1186 } 1187 return mSource; 1188 } 1189 1190 const TrackingId& LocalMediaDevice::GetTrackingId() const { 1191 return mSource->GetTrackingId(); 1192 } 1193 1194 const dom::MediaTrackConstraints& LocalMediaDevice::Constraints() const { 1195 MOZ_ASSERT(MediaManager::IsInMediaThread()); 1196 return mConstraints; 1197 } 1198 1199 // Threadsafe since mKind and mSource are const. 1200 NS_IMETHODIMP 1201 LocalMediaDevice::GetMediaSource(nsAString& aMediaSource) { 1202 if (Kind() == MediaDeviceKind::Audiooutput) { 1203 aMediaSource.Truncate(); 1204 } else { 1205 aMediaSource.AssignASCII(dom::GetEnumString(GetMediaSource())); 1206 } 1207 return NS_OK; 1208 } 1209 1210 nsresult LocalMediaDevice::Allocate(const MediaTrackConstraints& aConstraints, 1211 const MediaEnginePrefs& aPrefs, 1212 uint64_t aWindowID, 1213 const char** aOutBadConstraint) { 1214 MOZ_ASSERT(MediaManager::IsInMediaThread()); 1215 1216 // Mock failure for automated tests. 1217 if (IsFake() && aConstraints.mDeviceId.WasPassed() && 1218 aConstraints.mDeviceId.Value().IsString() && 1219 aConstraints.mDeviceId.Value().GetAsString().EqualsASCII("bad device")) { 1220 return NS_ERROR_FAILURE; 1221 } 1222 1223 nsresult rv = 1224 Source()->Allocate(aConstraints, aPrefs, aWindowID, aOutBadConstraint); 1225 if (NS_SUCCEEDED(rv)) { 1226 mConstraints = aConstraints; 1227 } 1228 return rv; 1229 } 1230 1231 void LocalMediaDevice::SetTrack(const RefPtr<MediaTrack>& aTrack, 1232 const PrincipalHandle& aPrincipalHandle) { 1233 MOZ_ASSERT(MediaManager::IsInMediaThread()); 1234 Source()->SetTrack(aTrack, aPrincipalHandle); 1235 } 1236 1237 nsresult LocalMediaDevice::Start() { 1238 MOZ_ASSERT(MediaManager::IsInMediaThread()); 1239 MOZ_ASSERT(Source()); 1240 return Source()->Start(); 1241 } 1242 1243 nsresult LocalMediaDevice::Reconfigure( 1244 const MediaTrackConstraints& aConstraints, const MediaEnginePrefs& aPrefs, 1245 const char** aOutBadConstraint) { 1246 MOZ_ASSERT(MediaManager::IsInMediaThread()); 1247 using H = MediaConstraintsHelper; 1248 auto type = GetMediaSource(); 1249 if (type == MediaSourceEnum::Camera || type == MediaSourceEnum::Microphone) { 1250 NormalizedConstraints c(aConstraints); 1251 if (H::FitnessDistance(Some(mID), c.mDeviceId) == UINT32_MAX) { 1252 *aOutBadConstraint = "deviceId"; 1253 return NS_ERROR_INVALID_ARG; 1254 } 1255 if (H::FitnessDistance(Some(mGroupID), c.mGroupId) == UINT32_MAX) { 1256 *aOutBadConstraint = "groupId"; 1257 return NS_ERROR_INVALID_ARG; 1258 } 1259 if (aPrefs.mResizeModeEnabled && type == MediaSourceEnum::Camera) { 1260 // Check invalid exact resizeMode constraint (not a device property) 1261 nsString none = 1262 NS_ConvertASCIItoUTF16(dom::GetEnumString(VideoResizeModeEnum::None)); 1263 nsString crop = NS_ConvertASCIItoUTF16( 1264 dom::GetEnumString(VideoResizeModeEnum::Crop_and_scale)); 1265 if (H::FitnessDistance(Some(none), c.mResizeMode) == UINT32_MAX && 1266 H::FitnessDistance(Some(crop), c.mResizeMode) == UINT32_MAX) { 1267 *aOutBadConstraint = "resizeMode"; 1268 return NS_ERROR_INVALID_ARG; 1269 } 1270 } 1271 } 1272 nsresult rv = Source()->Reconfigure(aConstraints, aPrefs, aOutBadConstraint); 1273 if (NS_SUCCEEDED(rv)) { 1274 mConstraints = aConstraints; 1275 } 1276 return rv; 1277 } 1278 1279 nsresult LocalMediaDevice::FocusOnSelectedSource() { 1280 MOZ_ASSERT(MediaManager::IsInMediaThread()); 1281 return Source()->FocusOnSelectedSource(); 1282 } 1283 1284 nsresult LocalMediaDevice::Stop() { 1285 MOZ_ASSERT(MediaManager::IsInMediaThread()); 1286 MOZ_ASSERT(mSource); 1287 return mSource->Stop(); 1288 } 1289 1290 nsresult LocalMediaDevice::Deallocate() { 1291 MOZ_ASSERT(MediaManager::IsInMediaThread()); 1292 MOZ_ASSERT(mSource); 1293 return mSource->Deallocate(); 1294 } 1295 1296 already_AddRefed<LocalMediaDevice> LocalMediaDevice::Clone() const { 1297 MOZ_ASSERT(NS_IsMainThread()); 1298 auto device = MakeRefPtr<LocalMediaDevice>(mRawDevice, mID, mGroupID, mName); 1299 device->mSource = 1300 mRawDevice->mEngine->CreateSourceFrom(mSource, device->mRawDevice); 1301 #ifdef MOZ_THREAD_SAFETY_OWNERSHIP_CHECKS_SUPPORTED 1302 // The source is normally created on the MediaManager thread. But for cloning, 1303 // it ends up being created on main thread. Make sure its owning event target 1304 // is set properly. 1305 auto* src = device->Source(); 1306 src->_mOwningThread = mSource->_mOwningThread; 1307 #endif 1308 return device.forget(); 1309 } 1310 1311 MediaSourceEnum MediaDevice::GetMediaSource() const { return mMediaSource; } 1312 1313 static const MediaTrackConstraints& GetInvariant( 1314 const OwningBooleanOrMediaTrackConstraints& aUnion) { 1315 static const MediaTrackConstraints empty; 1316 return aUnion.IsMediaTrackConstraints() ? aUnion.GetAsMediaTrackConstraints() 1317 : empty; 1318 } 1319 1320 // Source getter returning full list 1321 1322 static void GetMediaDevices(MediaEngine* aEngine, MediaSourceEnum aSrcType, 1323 MediaManager::MediaDeviceSet& aResult, 1324 const char* aMediaDeviceName = nullptr) { 1325 MOZ_ASSERT(MediaManager::IsInMediaThread()); 1326 1327 LOG("%s: aEngine=%p, aSrcType=%" PRIu8 ", aMediaDeviceName=%s", __func__, 1328 aEngine, static_cast<uint8_t>(aSrcType), 1329 aMediaDeviceName ? aMediaDeviceName : "null"); 1330 nsTArray<RefPtr<MediaDevice>> devices; 1331 aEngine->EnumerateDevices(aSrcType, MediaSinkEnum::Other, &devices); 1332 1333 /* 1334 * We're allowing multiple tabs to access the same camera for parity 1335 * with Chrome. See bug 811757 for some of the issues surrounding 1336 * this decision. To disallow, we'd filter by IsAvailable() as we used 1337 * to. 1338 */ 1339 if (aMediaDeviceName && *aMediaDeviceName) { 1340 for (auto& device : devices) { 1341 if (device->mRawName.EqualsASCII(aMediaDeviceName)) { 1342 aResult.AppendElement(device); 1343 LOG("%s: found aMediaDeviceName=%s", __func__, aMediaDeviceName); 1344 break; 1345 } 1346 } 1347 } else { 1348 aResult = std::move(devices); 1349 if (MOZ_LOG_TEST(gMediaManagerLog, mozilla::LogLevel::Debug)) { 1350 for (auto& device : aResult) { 1351 LOG("%s: appending device=%s", __func__, 1352 NS_ConvertUTF16toUTF8(device->mRawName).get()); 1353 } 1354 } 1355 } 1356 } 1357 1358 RefPtr<LocalDeviceSetPromise> MediaManager::SelectSettings( 1359 const MediaStreamConstraints& aConstraints, CallerType aCallerType, 1360 RefPtr<LocalMediaDeviceSetRefCnt> aDevices) { 1361 MOZ_ASSERT(NS_IsMainThread()); 1362 1363 // Algorithm accesses device capabilities code and must run on media thread. 1364 // Modifies passed-in aDevices. 1365 1366 return MediaManager::Dispatch<LocalDeviceSetPromise>( 1367 __func__, [aConstraints, devices = std::move(aDevices), prefs = mPrefs, 1368 aCallerType](MozPromiseHolder<LocalDeviceSetPromise>& holder) { 1369 auto& devicesRef = *devices; 1370 1371 // Since the advanced part of the constraints algorithm needs to know 1372 // when a candidate set is overconstrained (zero members), we must split 1373 // up the list into videos and audios, and put it back together again at 1374 // the end. 1375 1376 nsTArray<RefPtr<LocalMediaDevice>> videos; 1377 nsTArray<RefPtr<LocalMediaDevice>> audios; 1378 1379 for (const auto& device : devicesRef) { 1380 MOZ_ASSERT(device->Kind() == MediaDeviceKind::Videoinput || 1381 device->Kind() == MediaDeviceKind::Audioinput); 1382 if (device->Kind() == MediaDeviceKind::Videoinput) { 1383 videos.AppendElement(device); 1384 } else if (device->Kind() == MediaDeviceKind::Audioinput) { 1385 audios.AppendElement(device); 1386 } 1387 } 1388 devicesRef.Clear(); 1389 const char* badConstraint = nullptr; 1390 bool needVideo = IsOn(aConstraints.mVideo); 1391 bool needAudio = IsOn(aConstraints.mAudio); 1392 1393 if (needVideo && videos.Length()) { 1394 badConstraint = MediaConstraintsHelper::SelectSettings( 1395 NormalizedConstraints(GetInvariant(aConstraints.mVideo)), prefs, 1396 videos, aCallerType); 1397 } 1398 if (!badConstraint && needAudio && audios.Length()) { 1399 badConstraint = MediaConstraintsHelper::SelectSettings( 1400 NormalizedConstraints(GetInvariant(aConstraints.mAudio)), prefs, 1401 audios, aCallerType); 1402 } 1403 if (badConstraint) { 1404 LOG("SelectSettings: bad constraint found! Calling error handler!"); 1405 nsString constraint; 1406 constraint.AssignASCII(badConstraint); 1407 holder.Reject( 1408 new MediaMgrError(MediaMgrError::Name::OverconstrainedError, "", 1409 constraint), 1410 __func__); 1411 return; 1412 } 1413 if (!needVideo == !videos.Length() && !needAudio == !audios.Length()) { 1414 for (auto& video : videos) { 1415 devicesRef.AppendElement(video); 1416 } 1417 for (auto& audio : audios) { 1418 devicesRef.AppendElement(audio); 1419 } 1420 } 1421 holder.Resolve(devices, __func__); 1422 }); 1423 } 1424 1425 /** 1426 * Describes a requested task that handles response from the UI and sends 1427 * results back to the DOM. 1428 */ 1429 class GetUserMediaTask { 1430 public: 1431 NS_INLINE_DECL_THREADSAFE_REFCOUNTING(GetUserMediaTask) 1432 GetUserMediaTask(uint64_t aWindowID, const ipc::PrincipalInfo& aPrincipalInfo, 1433 CallerType aCallerType) 1434 : mPrincipalInfo(aPrincipalInfo), 1435 mWindowID(aWindowID), 1436 mCallerType(aCallerType) {} 1437 1438 virtual void Denied(MediaMgrError::Name aName, 1439 const nsCString& aMessage = ""_ns) = 0; 1440 1441 virtual GetUserMediaStreamTask* AsGetUserMediaStreamTask() { return nullptr; } 1442 virtual SelectAudioOutputTask* AsSelectAudioOutputTask() { return nullptr; } 1443 1444 uint64_t GetWindowID() const { return mWindowID; } 1445 enum CallerType CallerType() const { return mCallerType; } 1446 1447 size_t SizeOfIncludingThis(MallocSizeOf aMallocSizeOf) const { 1448 size_t amount = aMallocSizeOf(this); 1449 // Assume mWindowListener is owned by MediaManager. 1450 // Assume mAudioDeviceListener and mVideoDeviceListener are owned by 1451 // mWindowListener. 1452 // Assume PrincipalInfo string buffers are shared. 1453 // Member types without support for accounting of pointees: 1454 // MozPromiseHolder, RefPtr<LocalMediaDevice>. 1455 // We don't have a good way to account for lambda captures for MozPromise 1456 // callbacks. 1457 return amount; 1458 } 1459 1460 protected: 1461 virtual ~GetUserMediaTask() = default; 1462 1463 // Call GetPrincipalKey again, if not private browing, this time with 1464 // persist = true, to promote deviceIds to persistent, in case they're not 1465 // already. Fire'n'forget. 1466 void PersistPrincipalKey() { 1467 if (IsPrincipalInfoPrivate(mPrincipalInfo)) { 1468 return; 1469 } 1470 media::GetPrincipalKey(mPrincipalInfo, true) 1471 ->Then( 1472 GetCurrentSerialEventTarget(), __func__, 1473 [](const media::PrincipalKeyPromise::ResolveOrRejectValue& aValue) { 1474 if (aValue.IsReject()) { 1475 LOG("Failed get Principal key. Persisting of deviceIds " 1476 "will be broken"); 1477 } 1478 }); 1479 } 1480 1481 private: 1482 // Thread-safe (object) principal of Window with ID mWindowID 1483 const ipc::PrincipalInfo mPrincipalInfo; 1484 1485 protected: 1486 // The ID of the not-necessarily-toplevel inner Window relevant global 1487 // object of the MediaDevices on which getUserMedia() was called 1488 const uint64_t mWindowID; 1489 // Whether the JS caller of getUserMedia() has system (subject) principal 1490 const enum CallerType mCallerType; 1491 }; 1492 1493 /** 1494 * Describes a requested task that handles response from the UI to a 1495 * getUserMedia() request and sends results back to content. If the request 1496 * is allowed and device initialization succeeds, then the MozPromise is 1497 * resolved with a DOMMediaStream having a track or tracks for the approved 1498 * device or devices. 1499 */ 1500 class GetUserMediaStreamTask final : public GetUserMediaTask { 1501 public: 1502 GetUserMediaStreamTask( 1503 const MediaStreamConstraints& aConstraints, 1504 MozPromiseHolder<MediaManager::StreamPromise>&& aHolder, 1505 uint64_t aWindowID, RefPtr<GetUserMediaWindowListener> aWindowListener, 1506 RefPtr<DeviceListener> aAudioDeviceListener, 1507 RefPtr<DeviceListener> aVideoDeviceListener, 1508 const MediaEnginePrefs& aPrefs, const ipc::PrincipalInfo& aPrincipalInfo, 1509 enum CallerType aCallerType, bool aShouldFocusSource) 1510 : GetUserMediaTask(aWindowID, aPrincipalInfo, aCallerType), 1511 mConstraints(aConstraints), 1512 mHolder(std::move(aHolder)), 1513 mWindowListener(std::move(aWindowListener)), 1514 mAudioDeviceListener(std::move(aAudioDeviceListener)), 1515 mVideoDeviceListener(std::move(aVideoDeviceListener)), 1516 mPrefs(aPrefs), 1517 mShouldFocusSource(aShouldFocusSource), 1518 mManager(MediaManager::GetInstance()) {} 1519 1520 void Allowed(RefPtr<LocalMediaDevice> aAudioDevice, 1521 RefPtr<LocalMediaDevice> aVideoDevice) { 1522 MOZ_ASSERT(aAudioDevice || aVideoDevice); 1523 mAudioDevice = std::move(aAudioDevice); 1524 mVideoDevice = std::move(aVideoDevice); 1525 // Reuse the same thread to save memory. 1526 MediaManager::Dispatch( 1527 NewRunnableMethod("GetUserMediaStreamTask::AllocateDevices", this, 1528 &GetUserMediaStreamTask::AllocateDevices)); 1529 } 1530 1531 GetUserMediaStreamTask* AsGetUserMediaStreamTask() override { return this; } 1532 1533 private: 1534 ~GetUserMediaStreamTask() override { 1535 if (!mHolder.IsEmpty()) { 1536 Fail(MediaMgrError::Name::NotAllowedError); 1537 } 1538 } 1539 1540 void Fail(MediaMgrError::Name aName, const nsCString& aMessage = ""_ns, 1541 const nsString& aConstraint = u""_ns) { 1542 mHolder.Reject(MakeRefPtr<MediaMgrError>(aName, aMessage, aConstraint), 1543 __func__); 1544 // We add a disabled listener to the StreamListeners array until accepted 1545 // If this was the only active MediaStream, remove the window from the list. 1546 NS_DispatchToMainThread(NS_NewRunnableFunction( 1547 "DeviceListener::Stop", 1548 [audio = mAudioDeviceListener, video = mVideoDeviceListener] { 1549 if (audio) { 1550 audio->Stop(); 1551 } 1552 if (video) { 1553 video->Stop(); 1554 } 1555 })); 1556 } 1557 1558 /** 1559 * Runs on a separate thread and is responsible for allocating devices. 1560 * 1561 * Do not run this on the main thread. 1562 */ 1563 void AllocateDevices() { 1564 MOZ_ASSERT(!NS_IsMainThread()); 1565 LOG("GetUserMediaStreamTask::AllocateDevices()"); 1566 1567 // Allocate a video or audio device and return a MediaStream via 1568 // PrepareDOMStream(). 1569 1570 nsresult rv; 1571 const char* errorMsg = nullptr; 1572 const char* badConstraint = nullptr; 1573 1574 if (mAudioDevice) { 1575 auto& constraints = GetInvariant(mConstraints.mAudio); 1576 rv = mAudioDevice->Allocate(constraints, mPrefs, mWindowID, 1577 &badConstraint); 1578 if (NS_FAILED(rv)) { 1579 errorMsg = "Failed to allocate audiosource"; 1580 if (rv == NS_ERROR_NOT_AVAILABLE && !badConstraint) { 1581 nsTArray<RefPtr<LocalMediaDevice>> devices; 1582 devices.AppendElement(mAudioDevice); 1583 badConstraint = MediaConstraintsHelper::SelectSettings( 1584 NormalizedConstraints(constraints), mPrefs, devices, mCallerType); 1585 } 1586 } 1587 } 1588 if (!errorMsg && mVideoDevice) { 1589 auto& constraints = GetInvariant(mConstraints.mVideo); 1590 rv = mVideoDevice->Allocate(constraints, mPrefs, mWindowID, 1591 &badConstraint); 1592 if (NS_FAILED(rv)) { 1593 errorMsg = "Failed to allocate videosource"; 1594 if (rv == NS_ERROR_NOT_AVAILABLE && !badConstraint) { 1595 nsTArray<RefPtr<LocalMediaDevice>> devices; 1596 devices.AppendElement(mVideoDevice); 1597 badConstraint = MediaConstraintsHelper::SelectSettings( 1598 NormalizedConstraints(constraints), mPrefs, devices, mCallerType); 1599 } 1600 if (mAudioDevice) { 1601 mAudioDevice->Deallocate(); 1602 } 1603 } else { 1604 mVideoTrackingId.emplace(mVideoDevice->GetTrackingId()); 1605 } 1606 } 1607 if (errorMsg) { 1608 LOG("%s %" PRIu32, errorMsg, static_cast<uint32_t>(rv)); 1609 if (badConstraint) { 1610 Fail(MediaMgrError::Name::OverconstrainedError, ""_ns, 1611 NS_ConvertUTF8toUTF16(badConstraint)); 1612 } else { 1613 Fail(MediaMgrError::Name::NotReadableError, nsCString(errorMsg)); 1614 } 1615 NS_DispatchToMainThread( 1616 NS_NewRunnableFunction("MediaManager::SendPendingGUMRequest", []() { 1617 if (MediaManager* manager = MediaManager::GetIfExists()) { 1618 manager->SendPendingGUMRequest(); 1619 } 1620 })); 1621 return; 1622 } 1623 NS_DispatchToMainThread( 1624 NewRunnableMethod("GetUserMediaStreamTask::PrepareDOMStream", this, 1625 &GetUserMediaStreamTask::PrepareDOMStream)); 1626 } 1627 1628 public: 1629 void Denied(MediaMgrError::Name aName, const nsCString& aMessage) override { 1630 MOZ_ASSERT(NS_IsMainThread()); 1631 Fail(aName, aMessage); 1632 } 1633 1634 const MediaStreamConstraints& GetConstraints() { return mConstraints; } 1635 1636 void PrimeVoiceProcessing() { 1637 mPrimingStream = MakeAndAddRef<PrimingCubebVoiceInputStream>(); 1638 mPrimingStream->Init(); 1639 } 1640 1641 private: 1642 void PrepareDOMStream(); 1643 1644 class PrimingCubebVoiceInputStream { 1645 class Listener final : public CubebInputStream::Listener { 1646 NS_INLINE_DECL_THREADSAFE_REFCOUNTING(Listener, override); 1647 1648 private: 1649 ~Listener() = default; 1650 1651 long DataCallback(const void*, long) override { 1652 MOZ_CRASH("Unexpected data callback"); 1653 } 1654 void StateCallback(cubeb_state) override {} 1655 void DeviceChangedCallback() override {} 1656 }; 1657 1658 NS_INLINE_DECL_THREADSAFE_REFCOUNTING_WITH_DELETE_ON_EVENT_TARGET( 1659 PrimingCubebVoiceInputStream, mCubebThread.GetEventTarget()) 1660 1661 public: 1662 void Init() { 1663 mCubebThread.GetEventTarget()->Dispatch( 1664 NS_NewRunnableFunction(__func__, [this, self = RefPtr(this)] { 1665 mCubebThread.AssertOnCurrentThread(); 1666 LOG("Priming voice processing with stream %p", this); 1667 TRACE("PrimingCubebVoiceInputStream::Init"); 1668 const cubeb_devid default_device = nullptr; 1669 const uint32_t mono = 1; 1670 const uint32_t rate = CubebUtils::PreferredSampleRate(false); 1671 const bool isVoice = true; 1672 mCubebStream = 1673 CubebInputStream::Create(default_device, mono, rate, isVoice, 1674 MakeRefPtr<Listener>().get()); 1675 })); 1676 } 1677 1678 private: 1679 ~PrimingCubebVoiceInputStream() { 1680 mCubebThread.AssertOnCurrentThread(); 1681 LOG("Releasing primed voice processing stream %p", this); 1682 mCubebStream = nullptr; 1683 } 1684 1685 const EventTargetCapability<nsISerialEventTarget> mCubebThread = 1686 EventTargetCapability<nsISerialEventTarget>( 1687 TaskQueue::Create(CubebUtils::GetCubebOperationThread(), 1688 "PrimingCubebInputStream::mCubebThread") 1689 .get()); 1690 UniquePtr<CubebInputStream> mCubebStream MOZ_GUARDED_BY(mCubebThread); 1691 }; 1692 1693 // Constraints derived from those passed to getUserMedia() but adjusted for 1694 // preferences, defaults, and security 1695 const MediaStreamConstraints mConstraints; 1696 1697 MozPromiseHolder<MediaManager::StreamPromise> mHolder; 1698 // GetUserMediaWindowListener with which DeviceListeners are registered 1699 const RefPtr<GetUserMediaWindowListener> mWindowListener; 1700 const RefPtr<DeviceListener> mAudioDeviceListener; 1701 const RefPtr<DeviceListener> mVideoDeviceListener; 1702 // MediaDevices are set when selected and Allowed() by the UI. 1703 RefPtr<LocalMediaDevice> mAudioDevice; 1704 RefPtr<LocalMediaDevice> mVideoDevice; 1705 RefPtr<PrimingCubebVoiceInputStream> mPrimingStream; 1706 // Tracking id unique for a video frame source. Set when the corresponding 1707 // device has been allocated. 1708 Maybe<TrackingId> mVideoTrackingId; 1709 // Copy of MediaManager::mPrefs 1710 const MediaEnginePrefs mPrefs; 1711 // media.getusermedia.window.focus_source.enabled 1712 const bool mShouldFocusSource; 1713 // The MediaManager is referenced at construction so that it won't be 1714 // created after its ShutdownBlocker would run. 1715 const RefPtr<MediaManager> mManager; 1716 }; 1717 1718 /** 1719 * Creates a MediaTrack, attaches a listener and resolves a MozPromise to 1720 * provide the stream to the DOM. 1721 * 1722 * All of this must be done on the main thread! 1723 */ 1724 void GetUserMediaStreamTask::PrepareDOMStream() { 1725 MOZ_ASSERT(NS_IsMainThread()); 1726 LOG("GetUserMediaStreamTask::PrepareDOMStream()"); 1727 nsGlobalWindowInner* window = 1728 nsGlobalWindowInner::GetInnerWindowWithId(mWindowID); 1729 1730 // We're on main-thread, and the windowlist can only 1731 // be invalidated from the main-thread (see OnNavigation) 1732 if (!mManager->IsWindowListenerStillActive(mWindowListener)) { 1733 // This window is no longer live. mListener has already been removed. 1734 return; 1735 } 1736 1737 MediaTrackGraph::GraphDriverType graphDriverType = 1738 mAudioDevice ? MediaTrackGraph::AUDIO_THREAD_DRIVER 1739 : MediaTrackGraph::SYSTEM_THREAD_DRIVER; 1740 MediaTrackGraph* mtg = MediaTrackGraph::GetInstance( 1741 graphDriverType, window, MediaTrackGraph::REQUEST_DEFAULT_SAMPLE_RATE, 1742 MediaTrackGraph::DEFAULT_OUTPUT_DEVICE); 1743 1744 auto domStream = MakeRefPtr<DOMMediaStream>(window); 1745 RefPtr<LocalTrackSource> audioTrackSource; 1746 RefPtr<LocalTrackSource> videoTrackSource; 1747 nsCOMPtr<nsIPrincipal> principal; 1748 RefPtr<PeerIdentity> peerIdentity = nullptr; 1749 if (!mConstraints.mPeerIdentity.IsEmpty()) { 1750 peerIdentity = new PeerIdentity(mConstraints.mPeerIdentity); 1751 principal = NullPrincipal::CreateWithInheritedAttributes( 1752 window->GetExtantDoc()->NodePrincipal()); 1753 } else { 1754 principal = window->GetExtantDoc()->NodePrincipal(); 1755 } 1756 RefPtr<GenericNonExclusivePromise> firstFramePromise; 1757 if (mAudioDevice) { 1758 if (mAudioDevice->GetMediaSource() == MediaSourceEnum::AudioCapture) { 1759 // AudioCapture is a special case, here, in the sense that we're not 1760 // really using the audio source and the SourceMediaTrack, which acts 1761 // as placeholders. We re-route a number of tracks internally in the 1762 // MTG and mix them down instead. 1763 NS_WARNING( 1764 "MediaCaptureWindowState doesn't handle " 1765 "MediaSourceEnum::AudioCapture. This must be fixed with UX " 1766 "before shipping."); 1767 auto audioCaptureSource = MakeRefPtr<AudioCaptureTrackSource>( 1768 principal, window, u"Window audio capture"_ns, 1769 mtg->CreateAudioCaptureTrack(), peerIdentity); 1770 audioTrackSource = audioCaptureSource; 1771 RefPtr<MediaStreamTrack> track = new dom::AudioStreamTrack( 1772 window, audioCaptureSource->InputTrack(), audioCaptureSource); 1773 domStream->AddTrackInternal(track); 1774 } else { 1775 const nsString& audioDeviceName = mAudioDevice->mName; 1776 RefPtr<MediaTrack> track; 1777 #ifdef MOZ_WEBRTC 1778 if (mAudioDevice->IsFake()) { 1779 track = mtg->CreateSourceTrack(MediaSegment::AUDIO); 1780 } else { 1781 track = AudioProcessingTrack::Create(mtg); 1782 track->Suspend(); // Microphone source resumes in SetTrack 1783 } 1784 #else 1785 track = mtg->CreateSourceTrack(MediaSegment::AUDIO); 1786 #endif 1787 audioTrackSource = new LocalTrackSource( 1788 principal, audioDeviceName, mAudioDeviceListener, 1789 mAudioDevice->GetMediaSource(), track, peerIdentity); 1790 MOZ_ASSERT(MediaManager::IsOn(mConstraints.mAudio)); 1791 RefPtr<MediaStreamTrack> domTrack = new dom::AudioStreamTrack( 1792 window, track, audioTrackSource, dom::MediaStreamTrackState::Live, 1793 false, GetInvariant(mConstraints.mAudio)); 1794 domStream->AddTrackInternal(domTrack); 1795 } 1796 } 1797 if (mVideoDevice) { 1798 const nsString& videoDeviceName = mVideoDevice->mName; 1799 RefPtr<MediaTrack> track = mtg->CreateSourceTrack(MediaSegment::VIDEO); 1800 videoTrackSource = new LocalTrackSource( 1801 principal, videoDeviceName, mVideoDeviceListener, 1802 mVideoDevice->GetMediaSource(), track, peerIdentity, *mVideoTrackingId); 1803 MOZ_ASSERT(MediaManager::IsOn(mConstraints.mVideo)); 1804 RefPtr<MediaStreamTrack> domTrack = new dom::VideoStreamTrack( 1805 window, track, videoTrackSource, dom::MediaStreamTrackState::Live, 1806 false, GetInvariant(mConstraints.mVideo)); 1807 domStream->AddTrackInternal(domTrack); 1808 switch (mVideoDevice->GetMediaSource()) { 1809 case MediaSourceEnum::Browser: 1810 case MediaSourceEnum::Screen: 1811 case MediaSourceEnum::Window: 1812 // Wait for first frame for screen-sharing devices, to ensure 1813 // with and height settings are available immediately, to pass wpt. 1814 firstFramePromise = mVideoDevice->Source()->GetFirstFramePromise(); 1815 break; 1816 default: 1817 break; 1818 } 1819 } 1820 1821 if (!domStream || (!audioTrackSource && !videoTrackSource) || 1822 sHasMainThreadShutdown) { 1823 LOG("Returning error for getUserMedia() - no stream"); 1824 1825 mHolder.Reject( 1826 MakeRefPtr<MediaMgrError>( 1827 MediaMgrError::Name::AbortError, 1828 sHasMainThreadShutdown ? "In shutdown"_ns : "No stream."_ns), 1829 __func__); 1830 return; 1831 } 1832 1833 // Activate our device listeners. We'll call Start() on the source when we 1834 // get a callback that the MediaStream has started consuming. The listener 1835 // is freed when the page is invalidated (on navigation or close). 1836 if (mAudioDeviceListener) { 1837 mWindowListener->Activate(mAudioDeviceListener, mAudioDevice, 1838 std::move(audioTrackSource), 1839 /*aIsAllocated=*/true); 1840 } 1841 if (mVideoDeviceListener) { 1842 mWindowListener->Activate(mVideoDeviceListener, mVideoDevice, 1843 std::move(videoTrackSource), 1844 /*aIsAllocated=*/true); 1845 } 1846 1847 // Dispatch to the media thread to ask it to start the sources, because that 1848 // can take a while. 1849 typedef DeviceListener::DeviceListenerPromise PromiseType; 1850 AutoTArray<RefPtr<PromiseType>, 2> promises; 1851 if (mAudioDeviceListener) { 1852 promises.AppendElement(mAudioDeviceListener->InitializeAsync()); 1853 } 1854 if (mVideoDeviceListener) { 1855 promises.AppendElement(mVideoDeviceListener->InitializeAsync()); 1856 } 1857 PromiseType::All(GetMainThreadSerialEventTarget(), promises) 1858 ->Then( 1859 GetMainThreadSerialEventTarget(), __func__, 1860 [manager = mManager, windowListener = mWindowListener, 1861 firstFramePromise] { 1862 LOG("GetUserMediaStreamTask::PrepareDOMStream: starting success " 1863 "callback following InitializeAsync()"); 1864 // Initiating and starting devices succeeded. 1865 windowListener->ChromeAffectingStateChanged(); 1866 manager->SendPendingGUMRequest(); 1867 if (!firstFramePromise) { 1868 return DeviceListener::DeviceListenerPromise::CreateAndResolve( 1869 true, __func__); 1870 } 1871 RefPtr<DeviceListener::DeviceListenerPromise> resolvePromise = 1872 firstFramePromise->Then( 1873 GetMainThreadSerialEventTarget(), __func__, 1874 [] { 1875 return DeviceListener::DeviceListenerPromise:: 1876 CreateAndResolve(true, __func__); 1877 }, 1878 [](nsresult aError) { 1879 MOZ_ASSERT(NS_FAILED(aError)); 1880 if (aError == NS_ERROR_UNEXPECTED) { 1881 return DeviceListener::DeviceListenerPromise:: 1882 CreateAndReject( 1883 MakeRefPtr<MediaMgrError>( 1884 MediaMgrError::Name::NotAllowedError), 1885 __func__); 1886 } 1887 MOZ_ASSERT(aError == NS_ERROR_ABORT); 1888 return DeviceListener::DeviceListenerPromise:: 1889 CreateAndReject(MakeRefPtr<MediaMgrError>( 1890 MediaMgrError::Name::AbortError, 1891 "In shutdown"), 1892 __func__); 1893 }); 1894 return resolvePromise; 1895 }, 1896 [audio = mAudioDeviceListener, 1897 video = mVideoDeviceListener](const RefPtr<MediaMgrError>& aError) { 1898 LOG("GetUserMediaStreamTask::PrepareDOMStream: starting failure " 1899 "callback following InitializeAsync()"); 1900 if (audio) { 1901 audio->Stop(); 1902 } 1903 if (video) { 1904 video->Stop(); 1905 } 1906 return DeviceListener::DeviceListenerPromise::CreateAndReject( 1907 aError, __func__); 1908 }) 1909 ->Then( 1910 GetMainThreadSerialEventTarget(), __func__, 1911 [holder = std::move(mHolder), domStream, callerType = mCallerType, 1912 shouldFocus = mShouldFocusSource, videoDevice = mVideoDevice]( 1913 const DeviceListener::DeviceListenerPromise::ResolveOrRejectValue& 1914 aValue) mutable { 1915 if (aValue.IsResolve()) { 1916 if (auto* mgr = MediaManager::GetIfExists(); 1917 mgr && !sHasMainThreadShutdown && videoDevice && 1918 callerType == CallerType::NonSystem && shouldFocus) { 1919 // Device was successfully started. Attempt to focus the 1920 // source. 1921 MOZ_ALWAYS_SUCCEEDS( 1922 mgr->mMediaThread->Dispatch(NS_NewRunnableFunction( 1923 "GetUserMediaStreamTask::FocusOnSelectedSource", 1924 [videoDevice = std::move(videoDevice)] { 1925 nsresult rv = videoDevice->FocusOnSelectedSource(); 1926 if (NS_FAILED(rv)) { 1927 LOG("FocusOnSelectedSource failed"); 1928 } 1929 }))); 1930 } 1931 1932 holder.Resolve(domStream, __func__); 1933 } else { 1934 holder.Reject(aValue.RejectValue(), __func__); 1935 } 1936 }); 1937 1938 PersistPrincipalKey(); 1939 } 1940 1941 /** 1942 * Describes a requested task that handles response from the UI to a 1943 * selectAudioOutput() request and sends results back to content. If the 1944 * request is allowed, then the MozPromise is resolved with a MediaDevice 1945 * for the approved device. 1946 */ 1947 class SelectAudioOutputTask final : public GetUserMediaTask { 1948 public: 1949 SelectAudioOutputTask(MozPromiseHolder<LocalDevicePromise>&& aHolder, 1950 uint64_t aWindowID, enum CallerType aCallerType, 1951 const ipc::PrincipalInfo& aPrincipalInfo) 1952 : GetUserMediaTask(aWindowID, aPrincipalInfo, aCallerType), 1953 mHolder(std::move(aHolder)) {} 1954 1955 void Allowed(RefPtr<LocalMediaDevice> aAudioOutput) { 1956 MOZ_ASSERT(aAudioOutput); 1957 mHolder.Resolve(std::move(aAudioOutput), __func__); 1958 PersistPrincipalKey(); 1959 } 1960 1961 void Denied(MediaMgrError::Name aName, const nsCString& aMessage) override { 1962 MOZ_ASSERT(NS_IsMainThread()); 1963 Fail(aName, aMessage); 1964 } 1965 1966 SelectAudioOutputTask* AsSelectAudioOutputTask() override { return this; } 1967 1968 private: 1969 ~SelectAudioOutputTask() override { 1970 if (!mHolder.IsEmpty()) { 1971 Fail(MediaMgrError::Name::NotAllowedError); 1972 } 1973 } 1974 1975 void Fail(MediaMgrError::Name aName, const nsCString& aMessage = ""_ns) { 1976 mHolder.Reject(MakeRefPtr<MediaMgrError>(aName, aMessage), __func__); 1977 } 1978 1979 private: 1980 MozPromiseHolder<LocalDevicePromise> mHolder; 1981 }; 1982 1983 /* static */ 1984 void MediaManager::GuessVideoDeviceGroupIDs(MediaDeviceSet& aDevices, 1985 const MediaDeviceSet& aAudios) { 1986 // Run the logic in a lambda to avoid duplication. 1987 auto updateGroupIdIfNeeded = [&](RefPtr<MediaDevice>& aVideo, 1988 const MediaDeviceKind aKind) -> bool { 1989 MOZ_ASSERT(aVideo->mKind == MediaDeviceKind::Videoinput); 1990 MOZ_ASSERT(aKind == MediaDeviceKind::Audioinput || 1991 aKind == MediaDeviceKind::Audiooutput); 1992 // This will store the new group id if a match is found. 1993 nsString newVideoGroupID; 1994 // If the group id needs to be updated this will become true. It is 1995 // necessary when the new group id is an empty string. Without this extra 1996 // variable to signal the update, we would resort to test if 1997 // `newVideoGroupId` is empty. However, 1998 // that check does not work when the new group id is an empty string. 1999 bool updateGroupId = false; 2000 for (const RefPtr<MediaDevice>& dev : aAudios) { 2001 if (dev->mKind != aKind) { 2002 continue; 2003 } 2004 if (!FindInReadable(aVideo->mRawName, dev->mRawName)) { 2005 continue; 2006 } 2007 if (newVideoGroupID.IsEmpty()) { 2008 // This is only expected on first match. If that's the only match group 2009 // id will be updated to this one at the end of the loop. 2010 updateGroupId = true; 2011 newVideoGroupID = dev->mRawGroupID; 2012 } else { 2013 // More than one device found, it is impossible to know which group id 2014 // is the correct one. 2015 updateGroupId = false; 2016 newVideoGroupID = u""_ns; 2017 break; 2018 } 2019 } 2020 if (updateGroupId) { 2021 aVideo = MediaDevice::CopyWithNewRawGroupId(aVideo, newVideoGroupID); 2022 return true; 2023 } 2024 return false; 2025 }; 2026 2027 for (RefPtr<MediaDevice>& video : aDevices) { 2028 if (video->mKind != MediaDeviceKind::Videoinput) { 2029 continue; 2030 } 2031 if (updateGroupIdIfNeeded(video, MediaDeviceKind::Audioinput)) { 2032 // GroupId has been updated, continue to the next video device 2033 continue; 2034 } 2035 // GroupId has not been updated, check among the outputs 2036 updateGroupIdIfNeeded(video, MediaDeviceKind::Audiooutput); 2037 } 2038 } 2039 2040 namespace { 2041 2042 // Class to hold the promise used to request device access and to resolve 2043 // even if |task| does not run, either because GeckoViewPermissionProcessChild 2044 // gets destroyed before ask-device-permission receives its 2045 // got-device-permission reply, or because the media thread is no longer 2046 // available. In either case, the process is shutting down so the result is 2047 // not important. Reject with a dummy error so the following Then-handler can 2048 // resolve with an empty set, so that callers do not need to handle rejection. 2049 class DeviceAccessRequestPromiseHolderWithFallback 2050 : public MozPromiseHolder<MozPromise< 2051 CamerasAccessStatus, mozilla::ipc::ResponseRejectReason, true>> { 2052 public: 2053 DeviceAccessRequestPromiseHolderWithFallback() = default; 2054 DeviceAccessRequestPromiseHolderWithFallback( 2055 DeviceAccessRequestPromiseHolderWithFallback&&) = default; 2056 ~DeviceAccessRequestPromiseHolderWithFallback() { 2057 if (!IsEmpty()) { 2058 Reject(ipc::ResponseRejectReason::ChannelClosed, __func__); 2059 } 2060 } 2061 }; 2062 2063 } // anonymous namespace 2064 2065 MediaManager::DeviceEnumerationParams::DeviceEnumerationParams( 2066 dom::MediaSourceEnum aInputType, DeviceType aType, 2067 nsAutoCString aForcedDeviceName) 2068 : mInputType(aInputType), 2069 mType(aType), 2070 mForcedDeviceName(std::move(aForcedDeviceName)) { 2071 MOZ_ASSERT(NS_IsMainThread()); 2072 MOZ_ASSERT(mInputType != dom::MediaSourceEnum::Other); 2073 MOZ_ASSERT_IF(!mForcedDeviceName.IsEmpty(), mType == DeviceType::Real); 2074 } 2075 2076 MediaManager::VideoDeviceEnumerationParams::VideoDeviceEnumerationParams( 2077 dom::MediaSourceEnum aInputType, DeviceType aType, 2078 nsAutoCString aForcedDeviceName, nsAutoCString aForcedMicrophoneName) 2079 : DeviceEnumerationParams(aInputType, aType, std::move(aForcedDeviceName)), 2080 mForcedMicrophoneName(std::move(aForcedMicrophoneName)) { 2081 MOZ_ASSERT(NS_IsMainThread()); 2082 MOZ_ASSERT_IF(!mForcedMicrophoneName.IsEmpty(), 2083 mInputType == dom::MediaSourceEnum::Camera); 2084 MOZ_ASSERT_IF(!mForcedMicrophoneName.IsEmpty(), mType == DeviceType::Real); 2085 } 2086 2087 MediaManager::EnumerationParams::EnumerationParams( 2088 EnumerationFlags aFlags, Maybe<VideoDeviceEnumerationParams> aVideo, 2089 Maybe<DeviceEnumerationParams> aAudio) 2090 : mFlags(aFlags), mVideo(std::move(aVideo)), mAudio(std::move(aAudio)) { 2091 MOZ_ASSERT(NS_IsMainThread()); 2092 MOZ_ASSERT_IF(mVideo, MediaEngineSource::IsVideo(mVideo->mInputType)); 2093 MOZ_ASSERT_IF(mVideo && !mVideo->mForcedDeviceName.IsEmpty(), 2094 mVideo->mInputType == dom::MediaSourceEnum::Camera); 2095 MOZ_ASSERT_IF(mVideo && mVideo->mType == DeviceType::Fake, 2096 mVideo->mInputType == dom::MediaSourceEnum::Camera); 2097 MOZ_ASSERT_IF(mAudio, MediaEngineSource::IsAudio(mAudio->mInputType)); 2098 MOZ_ASSERT_IF(mAudio && !mAudio->mForcedDeviceName.IsEmpty(), 2099 mAudio->mInputType == dom::MediaSourceEnum::Microphone); 2100 MOZ_ASSERT_IF(mAudio && mAudio->mType == DeviceType::Fake, 2101 mAudio->mInputType == dom::MediaSourceEnum::Microphone); 2102 } 2103 2104 bool MediaManager::EnumerationParams::HasFakeCams() const { 2105 return mVideo 2106 .map([](const auto& aDev) { return aDev.mType == DeviceType::Fake; }) 2107 .valueOr(false); 2108 } 2109 2110 bool MediaManager::EnumerationParams::HasFakeMics() const { 2111 return mAudio 2112 .map([](const auto& aDev) { return aDev.mType == DeviceType::Fake; }) 2113 .valueOr(false); 2114 } 2115 2116 bool MediaManager::EnumerationParams::RealDeviceRequested() const { 2117 auto isReal = [](const auto& aDev) { return aDev.mType == DeviceType::Real; }; 2118 return mVideo.map(isReal).valueOr(false) || 2119 mAudio.map(isReal).valueOr(false) || 2120 mFlags.contains(EnumerationFlag::EnumerateAudioOutputs); 2121 } 2122 2123 MediaSourceEnum MediaManager::EnumerationParams::VideoInputType() const { 2124 return mVideo.map([](const auto& aDev) { return aDev.mInputType; }) 2125 .valueOr(MediaSourceEnum::Other); 2126 } 2127 2128 MediaSourceEnum MediaManager::EnumerationParams::AudioInputType() const { 2129 return mAudio.map([](const auto& aDev) { return aDev.mInputType; }) 2130 .valueOr(MediaSourceEnum::Other); 2131 } 2132 2133 /* static */ MediaManager::EnumerationParams 2134 MediaManager::CreateEnumerationParams(dom::MediaSourceEnum aVideoInputType, 2135 dom::MediaSourceEnum aAudioInputType, 2136 EnumerationFlags aFlags) { 2137 MOZ_ASSERT(NS_IsMainThread()); 2138 MOZ_ASSERT_IF(!MediaEngineSource::IsVideo(aVideoInputType), 2139 aVideoInputType == dom::MediaSourceEnum::Other); 2140 MOZ_ASSERT_IF(!MediaEngineSource::IsAudio(aAudioInputType), 2141 aAudioInputType == dom::MediaSourceEnum::Other); 2142 const bool forceFakes = aFlags.contains(EnumerationFlag::ForceFakes); 2143 const bool fakeByPref = Preferences::GetBool("media.navigator.streams.fake"); 2144 Maybe<VideoDeviceEnumerationParams> videoParams; 2145 Maybe<DeviceEnumerationParams> audioParams; 2146 nsAutoCString audioDev; 2147 bool audioDevRead = false; 2148 constexpr const char* VIDEO_DEV_NAME = "media.video_loopback_dev"; 2149 constexpr const char* AUDIO_DEV_NAME = "media.audio_loopback_dev"; 2150 const auto ensureDev = [](const char* aPref, nsAutoCString* aLoopDev, 2151 bool* aPrefRead) { 2152 if (aPrefRead) { 2153 if (*aPrefRead) { 2154 return; 2155 } 2156 *aPrefRead = true; 2157 } 2158 2159 if (NS_FAILED(Preferences::GetCString(aPref, *aLoopDev))) { 2160 // Ensure we fall back to an empty string if reading the pref failed. 2161 aLoopDev->SetIsVoid(true); 2162 } 2163 }; 2164 if (MediaEngineSource::IsVideo(aVideoInputType)) { 2165 nsAutoCString videoDev; 2166 DeviceType type = DeviceType::Real; 2167 if (aVideoInputType == MediaSourceEnum::Camera) { 2168 // Fake and loopback devices are supported for only Camera. 2169 if (forceFakes) { 2170 type = DeviceType::Fake; 2171 } else { 2172 ensureDev(VIDEO_DEV_NAME, &videoDev, nullptr); 2173 // Loopback prefs take precedence over fake prefs 2174 if (fakeByPref && videoDev.IsEmpty()) { 2175 type = DeviceType::Fake; 2176 } else { 2177 // For groupId correlation we need the audio device name. 2178 ensureDev(AUDIO_DEV_NAME, &audioDev, &audioDevRead); 2179 } 2180 } 2181 } 2182 videoParams = Some(VideoDeviceEnumerationParams(aVideoInputType, type, 2183 videoDev, audioDev)); 2184 } 2185 if (MediaEngineSource::IsAudio(aAudioInputType)) { 2186 nsAutoCString realAudioDev; 2187 DeviceType type = DeviceType::Real; 2188 if (aAudioInputType == MediaSourceEnum::Microphone) { 2189 // Fake and loopback devices are supported for only Microphone. 2190 if (forceFakes) { 2191 type = DeviceType::Fake; 2192 } else { 2193 ensureDev(AUDIO_DEV_NAME, &audioDev, &audioDevRead); 2194 // Loopback prefs take precedence over fake prefs 2195 if (fakeByPref && audioDev.IsEmpty()) { 2196 type = DeviceType::Fake; 2197 } else { 2198 realAudioDev = audioDev; 2199 } 2200 } 2201 } 2202 audioParams = 2203 Some(DeviceEnumerationParams(aAudioInputType, type, realAudioDev)); 2204 } 2205 return EnumerationParams(aFlags, videoParams, audioParams); 2206 } 2207 2208 RefPtr<DeviceSetPromise> 2209 MediaManager::MaybeRequestPermissionAndEnumerateRawDevices( 2210 EnumerationParams aParams) { 2211 MOZ_ASSERT(NS_IsMainThread()); 2212 MOZ_ASSERT(aParams.mVideo.isSome() || aParams.mAudio.isSome() || 2213 aParams.mFlags.contains(EnumerationFlag::EnumerateAudioOutputs)); 2214 2215 LOG("%s: aVideoInputType=%" PRIu8 ", aAudioInputType=%" PRIu8, __func__, 2216 static_cast<uint8_t>(aParams.VideoInputType()), 2217 static_cast<uint8_t>(aParams.AudioInputType())); 2218 2219 if (sHasMainThreadShutdown) { 2220 // The media thread is no longer available but the result will not be 2221 // observable. 2222 return DeviceSetPromise::CreateAndResolve( 2223 new MediaDeviceSetRefCnt(), 2224 "MaybeRequestPermissionAndEnumerateRawDevices: sync shutdown"); 2225 } 2226 2227 const bool hasVideo = aParams.mVideo.isSome(); 2228 const bool hasAudio = aParams.mAudio.isSome(); 2229 const bool hasAudioOutput = 2230 aParams.mFlags.contains(EnumerationFlag::EnumerateAudioOutputs); 2231 const bool hasFakeCams = aParams.HasFakeCams(); 2232 const bool hasFakeMics = aParams.HasFakeMics(); 2233 // True if at least one of video input or audio input is a real device 2234 // or there is audio output. 2235 const bool realDeviceRequested = (!hasFakeCams && hasVideo) || 2236 (!hasFakeMics && hasAudio) || hasAudioOutput; 2237 2238 using NativePromise = 2239 MozPromise<CamerasAccessStatus, mozilla::ipc::ResponseRejectReason, 2240 /* IsExclusive = */ true>; 2241 RefPtr<NativePromise> deviceAccessPromise; 2242 if (realDeviceRequested && 2243 aParams.mFlags.contains(EnumerationFlag::AllowPermissionRequest) && 2244 Preferences::GetBool("media.navigator.permission.device", false)) { 2245 // Need to ask permission to retrieve list of all devices; 2246 // notify frontend observer and wait for callback notification to post 2247 // task. 2248 const char16_t* const type = 2249 (aParams.VideoInputType() != MediaSourceEnum::Camera) ? u"audio" 2250 : (aParams.AudioInputType() != MediaSourceEnum::Microphone) ? u"video" 2251 : u"all"; 2252 nsCOMPtr<nsIObserverService> obs = services::GetObserverService(); 2253 DeviceAccessRequestPromiseHolderWithFallback deviceAccessPromiseHolder; 2254 deviceAccessPromise = deviceAccessPromiseHolder.Ensure(__func__); 2255 RefPtr task = NS_NewRunnableFunction( 2256 __func__, [holder = std::move(deviceAccessPromiseHolder)]() mutable { 2257 holder.Resolve(CamerasAccessStatus::Granted, 2258 "getUserMedia:got-device-permission"); 2259 }); 2260 obs->NotifyObservers(static_cast<nsIRunnable*>(task), 2261 "getUserMedia:ask-device-permission", type); 2262 } else if (realDeviceRequested && hasVideo && 2263 aParams.VideoInputType() == MediaSourceEnum::Camera) { 2264 ipc::PBackgroundChild* backgroundChild = 2265 ipc::BackgroundChild::GetOrCreateForCurrentThread(); 2266 deviceAccessPromise = backgroundChild->SendRequestCameraAccess( 2267 aParams.mFlags.contains(EnumerationFlag::AllowPermissionRequest)); 2268 } 2269 2270 if (!deviceAccessPromise) { 2271 // No device access request needed. We can proceed directly, but we still 2272 // need to update camera availability, because the camera engine is always 2273 // created together with the WebRTC backend, which is done because 2274 // devicechange events must work before prompting in cases where persistent 2275 // permission has already been given. Making a request to camera access not 2276 // allowing a permission request does exactly what we need in this case. 2277 ipc::PBackgroundChild* backgroundChild = 2278 ipc::BackgroundChild::GetOrCreateForCurrentThread(); 2279 deviceAccessPromise = backgroundChild->SendRequestCameraAccess(false); 2280 } 2281 2282 return deviceAccessPromise->Then( 2283 GetCurrentSerialEventTarget(), __func__, 2284 [this, self = RefPtr(this), aParams = std::move(aParams)]( 2285 NativePromise::ResolveOrRejectValue&& aValue) mutable { 2286 if (sHasMainThreadShutdown) { 2287 return DeviceSetPromise::CreateAndResolve( 2288 new MediaDeviceSetRefCnt(), 2289 "MaybeRequestPermissionAndEnumerateRawDevices: async shutdown"); 2290 } 2291 2292 if (aValue.IsReject()) { 2293 // IPC failure probably means we're in shutdown. Resolve with 2294 // an empty set, so that callers do not need to handle rejection. 2295 return DeviceSetPromise::CreateAndResolve( 2296 new MediaDeviceSetRefCnt(), 2297 "MaybeRequestPermissionAndEnumerateRawDevices: ipc failure"); 2298 } 2299 2300 if (auto v = aValue.ResolveValue(); 2301 v == CamerasAccessStatus::Error || 2302 v == CamerasAccessStatus::Rejected) { 2303 LOG("Request to camera access %s", 2304 v == CamerasAccessStatus::Rejected ? "was rejected" : "failed"); 2305 if (v == CamerasAccessStatus::Error) { 2306 NS_WARNING("Failed to request camera access"); 2307 } 2308 return DeviceSetPromise::CreateAndReject( 2309 MakeRefPtr<MediaMgrError>(MediaMgrError::Name::NotAllowedError), 2310 "MaybeRequestPermissionAndEnumerateRawDevices: camera access " 2311 "rejected"); 2312 } 2313 2314 // We have to nest this, unfortunately, since we have no guarantees that 2315 // mMediaThread is alive. If we'd reject due to shutdown above, and have 2316 // the below async operation in a Then handler on the media thread the 2317 // Then handler would fail to dispatch and trip an assert on 2318 // destruction, for instance. 2319 return InvokeAsync( 2320 mMediaThread, __func__, [aParams = std::move(aParams)]() mutable { 2321 return DeviceSetPromise::CreateAndResolve( 2322 EnumerateRawDevices(std::move(aParams)), 2323 "MaybeRequestPermissionAndEnumerateRawDevices: success"); 2324 }); 2325 }); 2326 } 2327 2328 /** 2329 * EnumerateRawDevices - Enumerate a list of audio & video devices that 2330 * satisfy passed-in constraints. List contains raw id's. 2331 */ 2332 2333 /* static */ RefPtr<MediaManager::MediaDeviceSetRefCnt> 2334 MediaManager::EnumerateRawDevices(EnumerationParams aParams) { 2335 MOZ_ASSERT(IsInMediaThread()); 2336 // Only enumerate what's asked for, and only fake cams and mics. 2337 RefPtr<MediaEngine> fakeBackend, realBackend; 2338 if (aParams.HasFakeCams() || aParams.HasFakeMics()) { 2339 fakeBackend = new MediaEngineFake(); 2340 } 2341 if (aParams.RealDeviceRequested()) { 2342 MediaManager* manager = MediaManager::GetIfExists(); 2343 MOZ_RELEASE_ASSERT(manager, "Must exist while media thread is alive"); 2344 realBackend = manager->GetBackend(); 2345 } 2346 2347 RefPtr<MediaEngine> videoBackend; 2348 RefPtr<MediaEngine> audioBackend; 2349 Maybe<MediaDeviceSet> micsOfVideoBackend; 2350 Maybe<MediaDeviceSet> speakers; 2351 RefPtr devices = new MediaDeviceSetRefCnt(); 2352 2353 // Enumerate microphones first, then cameras, then speakers, since 2354 // the enumerateDevices() algorithm expects them listed in that order. 2355 if (const auto& audio = aParams.mAudio; audio.isSome()) { 2356 audioBackend = aParams.HasFakeMics() ? fakeBackend : realBackend; 2357 MediaDeviceSet audios; 2358 LOG("EnumerateRawDevices: Getting audio sources with %s backend", 2359 audioBackend == fakeBackend ? "fake" : "real"); 2360 GetMediaDevices(audioBackend, audio->mInputType, audios, 2361 audio->mForcedDeviceName.get()); 2362 if (audio->mInputType == MediaSourceEnum::Microphone && 2363 audioBackend == videoBackend) { 2364 micsOfVideoBackend.emplace(); 2365 micsOfVideoBackend->AppendElements(audios); 2366 } 2367 devices->AppendElements(std::move(audios)); 2368 } 2369 if (const auto& video = aParams.mVideo; video.isSome()) { 2370 videoBackend = aParams.HasFakeCams() ? fakeBackend : realBackend; 2371 MediaDeviceSet videos; 2372 LOG("EnumerateRawDevices: Getting video sources with %s backend", 2373 videoBackend == fakeBackend ? "fake" : "real"); 2374 GetMediaDevices(videoBackend, video->mInputType, videos, 2375 video->mForcedDeviceName.get()); 2376 devices->AppendElements(std::move(videos)); 2377 } 2378 if (aParams.mFlags.contains(EnumerationFlag::EnumerateAudioOutputs)) { 2379 MediaDeviceSet outputs; 2380 MOZ_ASSERT(realBackend); 2381 realBackend->EnumerateDevices(MediaSourceEnum::Other, 2382 MediaSinkEnum::Speaker, &outputs); 2383 speakers = Some(MediaDeviceSet()); 2384 speakers->AppendElements(outputs); 2385 devices->AppendElements(std::move(outputs)); 2386 } 2387 if (aParams.VideoInputType() == MediaSourceEnum::Camera) { 2388 MediaDeviceSet audios; 2389 LOG("EnumerateRawDevices: Getting audio sources with %s backend for " 2390 "groupId correlation", 2391 videoBackend == fakeBackend ? "fake" : "real"); 2392 // We need to correlate cameras with audio groupIds. We use the backend of 2393 // the camera to always do correlation on devices in the same scope. If we 2394 // don't do this, video-only getUserMedia will not apply groupId constraints 2395 // to the same set of groupIds as gets returned by enumerateDevices. 2396 if (micsOfVideoBackend.isSome()) { 2397 // Microphones from the same backend used for the cameras have 2398 // already been enumerated. Avoid doing it again. 2399 MOZ_ASSERT(aParams.mVideo->mForcedMicrophoneName == 2400 aParams.mAudio->mForcedDeviceName); 2401 audios.AppendElements(micsOfVideoBackend.extract()); 2402 } else { 2403 GetMediaDevices(videoBackend, MediaSourceEnum::Microphone, audios, 2404 aParams.mVideo->mForcedMicrophoneName.get()); 2405 } 2406 if (videoBackend == realBackend) { 2407 // When using the real backend for video, there could also be 2408 // speakers to correlate with. There are no fake speakers. 2409 if (speakers.isSome()) { 2410 // Speakers have already been enumerated. Avoid doing it again. 2411 audios.AppendElements(speakers.extract()); 2412 } else { 2413 realBackend->EnumerateDevices(MediaSourceEnum::Other, 2414 MediaSinkEnum::Speaker, &audios); 2415 } 2416 } 2417 GuessVideoDeviceGroupIDs(*devices, audios); 2418 } 2419 2420 return devices; 2421 } 2422 2423 RefPtr<ConstDeviceSetPromise> MediaManager::GetPhysicalDevices() { 2424 MOZ_ASSERT(NS_IsMainThread()); 2425 if (mPhysicalDevices) { 2426 return ConstDeviceSetPromise::CreateAndResolve(mPhysicalDevices, __func__); 2427 } 2428 if (mPendingDevicesPromises) { 2429 // Enumeration is already in progress. 2430 return mPendingDevicesPromises->AppendElement()->Ensure(__func__); 2431 } 2432 mPendingDevicesPromises = 2433 new Refcountable<nsTArray<MozPromiseHolder<ConstDeviceSetPromise>>>; 2434 MaybeRequestPermissionAndEnumerateRawDevices( 2435 CreateEnumerationParams(MediaSourceEnum::Camera, 2436 MediaSourceEnum::Microphone, 2437 EnumerationFlag::EnumerateAudioOutputs)) 2438 ->Then( 2439 GetCurrentSerialEventTarget(), __func__, 2440 [self = RefPtr(this), this, promises = mPendingDevicesPromises]( 2441 RefPtr<MediaDeviceSetRefCnt> aDevices) mutable { 2442 for (auto& promiseHolder : *promises) { 2443 promiseHolder.Resolve(aDevices, __func__); 2444 } 2445 // mPendingDevicesPromises may have changed if devices have changed. 2446 if (promises == mPendingDevicesPromises) { 2447 mPendingDevicesPromises = nullptr; 2448 mPhysicalDevices = std::move(aDevices); 2449 } 2450 }, 2451 [](RefPtr<MediaMgrError>&& reason) { 2452 MOZ_ASSERT_UNREACHABLE( 2453 "MaybeRequestPermissionAndEnumerateRawDevices does not reject"); 2454 }); 2455 2456 return mPendingDevicesPromises->AppendElement()->Ensure(__func__); 2457 } 2458 2459 MediaManager::MediaManager(already_AddRefed<TaskQueue> aMediaThread) 2460 : mMediaThread(aMediaThread), mBackend(nullptr) { 2461 mPrefs.mFreq = 1000; // 1KHz test tone 2462 mPrefs.mWidth = 0; // adaptive default 2463 mPrefs.mHeight = 0; // adaptive default 2464 mPrefs.mResizeModeEnabled = false; 2465 mPrefs.mResizeMode = VideoResizeModeEnum::None; 2466 mPrefs.mFPS = MediaEnginePrefs::DEFAULT_VIDEO_FPS; 2467 mPrefs.mUsePlatformProcessing = false; 2468 mPrefs.mAecOn = false; 2469 mPrefs.mUseAecMobile = false; 2470 mPrefs.mAgcOn = false; 2471 mPrefs.mHPFOn = false; 2472 mPrefs.mNoiseOn = false; 2473 mPrefs.mTransientOn = false; 2474 mPrefs.mAgc2Forced = false; 2475 mPrefs.mExpectDrift = -1; // auto 2476 #ifdef MOZ_WEBRTC 2477 mPrefs.mAgc = 2478 webrtc::AudioProcessing::Config::GainController1::Mode::kAdaptiveDigital; 2479 mPrefs.mNoise = 2480 webrtc::AudioProcessing::Config::NoiseSuppression::Level::kModerate; 2481 #else 2482 mPrefs.mAgc = 0; 2483 mPrefs.mNoise = 0; 2484 #endif 2485 mPrefs.mChannels = 0; // max channels default 2486 nsresult rv; 2487 nsCOMPtr<nsIPrefService> prefs = 2488 do_GetService("@mozilla.org/preferences-service;1", &rv); 2489 if (NS_SUCCEEDED(rv)) { 2490 nsCOMPtr<nsIPrefBranch> branch = do_QueryInterface(prefs); 2491 if (branch) { 2492 GetPrefs(branch, nullptr); 2493 } 2494 } 2495 } 2496 2497 NS_IMPL_ISUPPORTS(MediaManager, nsIMediaManagerService, nsIMemoryReporter, 2498 nsIObserver) 2499 2500 /* static */ 2501 StaticRefPtr<MediaManager> MediaManager::sSingleton; 2502 2503 #ifdef DEBUG 2504 /* static */ 2505 bool MediaManager::IsInMediaThread() { 2506 return sSingleton && sSingleton->mMediaThread->IsOnCurrentThread(); 2507 } 2508 #endif 2509 2510 template <typename Function> 2511 static void ForeachObservedPref(const Function& aFunction) { 2512 aFunction("media.navigator.video.default_width"_ns); 2513 aFunction("media.navigator.video.default_height"_ns); 2514 aFunction("media.navigator.video.default_fps"_ns); 2515 aFunction("media.navigator.audio.fake_frequency"_ns); 2516 aFunction("media.audio_loopback_dev"_ns); 2517 aFunction("media.video_loopback_dev"_ns); 2518 aFunction("media.getusermedia.fake-camera-name"_ns); 2519 #ifdef MOZ_WEBRTC 2520 aFunction("media.navigator.video.resize_mode.enabled"_ns); 2521 aFunction("media.navigator.video.default_resize_mode"_ns); 2522 aFunction("media.getusermedia.audio.processing.aec.enabled"_ns); 2523 aFunction("media.getusermedia.audio.processing.aec"_ns); 2524 aFunction("media.getusermedia.audio.processing.agc.enabled"_ns); 2525 aFunction("media.getusermedia.audio.processing.agc"_ns); 2526 aFunction("media.getusermedia.audio.processing.hpf.enabled"_ns); 2527 aFunction("media.getusermedia.audio.processing.noise.enabled"_ns); 2528 aFunction("media.getusermedia.audio.processing.noise"_ns); 2529 aFunction("media.getusermedia.audio.max_channels"_ns); 2530 aFunction("media.navigator.streams.fake"_ns); 2531 #endif 2532 } 2533 2534 // NOTE: never NS_DispatchAndSpinEventLoopUntilComplete to the MediaManager 2535 // thread from the MainThread, as we NS_DispatchAndSpinEventLoopUntilComplete to 2536 // MainThread from MediaManager thread. 2537 2538 // Guaranteed never to return nullptr. 2539 /* static */ 2540 MediaManager* MediaManager::Get() { 2541 MOZ_ASSERT(NS_IsMainThread()); 2542 2543 if (!sSingleton) { 2544 static int timesCreated = 0; 2545 timesCreated++; 2546 MOZ_RELEASE_ASSERT(timesCreated == 1); 2547 2548 constexpr bool kSupportsTailDispatch = false; 2549 RefPtr<TaskQueue> mediaThread = 2550 #ifdef MOZ_WEBRTC 2551 CreateWebrtcTaskQueueWrapper( 2552 GetMediaThreadPool(MediaThreadType::SUPERVISOR), "MediaManager"_ns, 2553 kSupportsTailDispatch); 2554 #else 2555 TaskQueue::Create(GetMediaThreadPool(MediaThreadType::SUPERVISOR), 2556 "MediaManager", kSupportsTailDispatch); 2557 #endif 2558 LOG("New Media thread for gum"); 2559 2560 sSingleton = new MediaManager(mediaThread.forget()); 2561 2562 nsCOMPtr<nsIObserverService> obs = services::GetObserverService(); 2563 if (obs) { 2564 obs->AddObserver(sSingleton, "last-pb-context-exited", false); 2565 obs->AddObserver(sSingleton, "getUserMedia:got-device-permission", false); 2566 obs->AddObserver(sSingleton, "getUserMedia:privileged:allow", false); 2567 obs->AddObserver(sSingleton, "getUserMedia:response:allow", false); 2568 obs->AddObserver(sSingleton, "getUserMedia:response:deny", false); 2569 obs->AddObserver(sSingleton, "getUserMedia:response:noOSPermission", 2570 false); 2571 obs->AddObserver(sSingleton, "getUserMedia:revoke", false); 2572 obs->AddObserver(sSingleton, "getUserMedia:muteVideo", false); 2573 obs->AddObserver(sSingleton, "getUserMedia:unmuteVideo", false); 2574 obs->AddObserver(sSingleton, "getUserMedia:muteAudio", false); 2575 obs->AddObserver(sSingleton, "getUserMedia:unmuteAudio", false); 2576 obs->AddObserver(sSingleton, "application-background", false); 2577 obs->AddObserver(sSingleton, "application-foreground", false); 2578 } 2579 // else MediaManager won't work properly and will leak (see bug 837874) 2580 nsCOMPtr<nsIPrefBranch> prefs = do_GetService(NS_PREFSERVICE_CONTRACTID); 2581 if (prefs) { 2582 ForeachObservedPref([&](const nsLiteralCString& aPrefName) { 2583 prefs->AddObserver(aPrefName, sSingleton, false); 2584 }); 2585 } 2586 RegisterStrongMemoryReporter(sSingleton); 2587 2588 // Prepare async shutdown 2589 2590 class Blocker : public media::ShutdownBlocker { 2591 public: 2592 Blocker() 2593 : media::ShutdownBlocker( 2594 u"Media shutdown: blocking on media thread"_ns) {} 2595 2596 NS_IMETHOD BlockShutdown(nsIAsyncShutdownClient*) override { 2597 MOZ_RELEASE_ASSERT(MediaManager::GetIfExists()); 2598 MediaManager::GetIfExists()->Shutdown(); 2599 return NS_OK; 2600 } 2601 }; 2602 2603 sSingleton->mShutdownBlocker = new Blocker(); 2604 nsresult rv = media::MustGetShutdownBarrier()->AddBlocker( 2605 sSingleton->mShutdownBlocker, NS_LITERAL_STRING_FROM_CSTRING(__FILE__), 2606 __LINE__, u""_ns); 2607 MOZ_RELEASE_ASSERT(NS_SUCCEEDED(rv)); 2608 } 2609 return sSingleton; 2610 } 2611 2612 /* static */ 2613 MediaManager* MediaManager::GetIfExists() { 2614 MOZ_ASSERT(NS_IsMainThread() || IsInMediaThread()); 2615 return sSingleton; 2616 } 2617 2618 /* static */ 2619 already_AddRefed<MediaManager> MediaManager::GetInstance() { 2620 // so we can have non-refcounted getters 2621 RefPtr<MediaManager> service = MediaManager::Get(); 2622 return service.forget(); 2623 } 2624 2625 media::Parent<media::NonE10s>* MediaManager::GetNonE10sParent() { 2626 if (!mNonE10sParent) { 2627 mNonE10sParent = new media::Parent<media::NonE10s>(); 2628 } 2629 return mNonE10sParent; 2630 } 2631 2632 /* static */ 2633 void MediaManager::Dispatch(already_AddRefed<Runnable> task) { 2634 MOZ_ASSERT(NS_IsMainThread()); 2635 if (sHasMainThreadShutdown) { 2636 // Can't safely delete task here since it may have items with specific 2637 // thread-release requirements. 2638 // XXXkhuey well then who is supposed to delete it?! We don't signal 2639 // that we failed ... 2640 MOZ_CRASH(); 2641 return; 2642 } 2643 NS_ASSERTION(Get(), "MediaManager singleton?"); 2644 NS_ASSERTION(Get()->mMediaThread, "No thread yet"); 2645 MOZ_ALWAYS_SUCCEEDS(Get()->mMediaThread->Dispatch(std::move(task))); 2646 } 2647 2648 template <typename MozPromiseType, typename FunctionType> 2649 /* static */ 2650 RefPtr<MozPromiseType> MediaManager::Dispatch(StaticString aName, 2651 FunctionType&& aFunction) { 2652 MozPromiseHolder<MozPromiseType> holder; 2653 RefPtr<MozPromiseType> promise = holder.Ensure(aName); 2654 MediaManager::Dispatch(NS_NewRunnableFunction( 2655 aName, [h = std::move(holder), func = std::forward<FunctionType>( 2656 aFunction)]() mutable { func(h); })); 2657 return promise; 2658 } 2659 2660 /* static */ 2661 nsresult MediaManager::NotifyRecordingStatusChange( 2662 nsPIDOMWindowInner* aWindow) { 2663 NS_ENSURE_ARG(aWindow); 2664 2665 nsCOMPtr<nsIObserverService> obs = services::GetObserverService(); 2666 if (!obs) { 2667 NS_WARNING( 2668 "Could not get the Observer service for GetUserMedia recording " 2669 "notification."); 2670 return NS_ERROR_FAILURE; 2671 } 2672 2673 auto props = MakeRefPtr<nsHashPropertyBag>(); 2674 2675 nsCString pageURL; 2676 nsCOMPtr<nsIURI> docURI = aWindow->GetDocumentURI(); 2677 NS_ENSURE_TRUE(docURI, NS_ERROR_FAILURE); 2678 2679 nsresult rv = docURI->GetSpec(pageURL); 2680 NS_ENSURE_SUCCESS(rv, rv); 2681 2682 NS_ConvertUTF8toUTF16 requestURL(pageURL); 2683 2684 props->SetPropertyAsAString(u"requestURL"_ns, requestURL); 2685 props->SetPropertyAsInterface(u"window"_ns, aWindow); 2686 2687 obs->NotifyObservers(static_cast<nsIPropertyBag2*>(props), 2688 "recording-device-events", nullptr); 2689 LOG("Sent recording-device-events for url '%s'", pageURL.get()); 2690 2691 return NS_OK; 2692 } 2693 2694 void MediaManager::DeviceListChanged() { 2695 MOZ_ASSERT(NS_IsMainThread()); 2696 if (sHasMainThreadShutdown) { 2697 return; 2698 } 2699 // Invalidate immediately to provide an up-to-date device list for future 2700 // enumerations on platforms with sane device-list-changed events. 2701 InvalidateDeviceCache(); 2702 2703 // Wait 200 ms, because 2704 // A) on some Windows machines, if we call EnumerateRawDevices immediately 2705 // after receiving devicechange event, we'd get an outdated devices list. 2706 // B) Waiting helps coalesce multiple calls on us into one, which can happen 2707 // if a device with both audio input and output is attached or removed. 2708 // We want to react & fire a devicechange event only once in that case. 2709 2710 // The wait is extended if another hardware device-list-changed notification 2711 // is received to provide the full 200ms for EnumerateRawDevices(). 2712 if (mDeviceChangeTimer) { 2713 mDeviceChangeTimer->Cancel(); 2714 } else { 2715 mDeviceChangeTimer = MakeRefPtr<MediaTimer<TimeStamp>>(); 2716 } 2717 // However, if this would cause a delay of over 1000ms in handling the 2718 // oldest unhandled event, then respond now and set the timer to run 2719 // EnumerateRawDevices() again in 200ms. 2720 auto now = TimeStamp::NowLoRes(); 2721 auto enumerateDelay = TimeDuration::FromMilliseconds(200); 2722 auto coalescenceLimit = TimeDuration::FromMilliseconds(1000) - enumerateDelay; 2723 if (!mUnhandledDeviceChangeTime) { 2724 mUnhandledDeviceChangeTime = now; 2725 } else if (now - mUnhandledDeviceChangeTime > coalescenceLimit) { 2726 HandleDeviceListChanged(); 2727 mUnhandledDeviceChangeTime = now; 2728 } 2729 RefPtr<MediaManager> self = this; 2730 mDeviceChangeTimer->WaitFor(enumerateDelay, __func__) 2731 ->Then( 2732 GetCurrentSerialEventTarget(), __func__, 2733 [self, this] { 2734 // Invalidate again for the sake of platforms with inconsistent 2735 // timing between device-list-changed notification and enumeration. 2736 InvalidateDeviceCache(); 2737 2738 mUnhandledDeviceChangeTime = TimeStamp(); 2739 HandleDeviceListChanged(); 2740 }, 2741 [] { /* Timer was canceled by us, or we're in shutdown. */ }); 2742 } 2743 2744 void MediaManager::InvalidateDeviceCache() { 2745 MOZ_ASSERT(NS_IsMainThread()); 2746 2747 mPhysicalDevices = nullptr; 2748 // Disconnect any in-progress enumeration, which may now be out of date, 2749 // from updating mPhysicalDevices or resolving future device request 2750 // promises. 2751 mPendingDevicesPromises = nullptr; 2752 } 2753 2754 void MediaManager::HandleDeviceListChanged() { 2755 mDeviceListChangeEvent.Notify(); 2756 2757 GetPhysicalDevices()->Then( 2758 GetCurrentSerialEventTarget(), __func__, 2759 [self = RefPtr(this), this](RefPtr<const MediaDeviceSetRefCnt> aDevices) { 2760 if (!MediaManager::GetIfExists()) { 2761 return; 2762 } 2763 2764 nsTHashSet<nsString> deviceIDs; 2765 for (const auto& device : *aDevices) { 2766 deviceIDs.Insert(device->mRawID); 2767 } 2768 // For any real removed cameras or microphones, notify their 2769 // listeners cleanly that the source has stopped, so JS knows and 2770 // usage indicators update. 2771 // First collect the listeners in an array to stop them after 2772 // iterating the hashtable. The StopRawID() method indirectly 2773 // modifies the mActiveWindows and would assert-crash if the 2774 // iterator were active while the table is being enumerated. 2775 const auto windowListeners = ToArray(mActiveWindows.Values()); 2776 for (const RefPtr<GetUserMediaWindowListener>& l : windowListeners) { 2777 const auto activeDevices = l->GetDevices(); 2778 for (const RefPtr<LocalMediaDevice>& device : *activeDevices) { 2779 if (device->IsFake()) { 2780 continue; 2781 } 2782 MediaSourceEnum mediaSource = device->GetMediaSource(); 2783 if (mediaSource != MediaSourceEnum::Microphone && 2784 mediaSource != MediaSourceEnum::Camera) { 2785 continue; 2786 } 2787 if (!deviceIDs.Contains(device->RawID())) { 2788 // Device has been removed 2789 l->StopRawID(device->RawID()); 2790 } 2791 } 2792 } 2793 }, 2794 [](RefPtr<MediaMgrError>&& reason) { 2795 MOZ_ASSERT_UNREACHABLE("EnumerateRawDevices does not reject"); 2796 }); 2797 } 2798 2799 size_t MediaManager::AddTaskAndGetCount(uint64_t aWindowID, 2800 const nsAString& aCallID, 2801 RefPtr<GetUserMediaTask> aTask) { 2802 // Store the task w/callbacks. 2803 mActiveCallbacks.InsertOrUpdate(aCallID, std::move(aTask)); 2804 2805 // Add a WindowID cross-reference so OnNavigation can tear things down 2806 nsTArray<nsString>* const array = mCallIds.GetOrInsertNew(aWindowID); 2807 array->AppendElement(aCallID); 2808 2809 return array->Length(); 2810 } 2811 2812 RefPtr<GetUserMediaTask> MediaManager::TakeGetUserMediaTask( 2813 const nsAString& aCallID) { 2814 RefPtr<GetUserMediaTask> task; 2815 mActiveCallbacks.Remove(aCallID, getter_AddRefs(task)); 2816 if (!task) { 2817 return nullptr; 2818 } 2819 nsTArray<nsString>* array; 2820 mCallIds.Get(task->GetWindowID(), &array); 2821 MOZ_ASSERT(array); 2822 array->RemoveElement(aCallID); 2823 return task; 2824 } 2825 2826 void MediaManager::NotifyAllowed(const nsString& aCallID, 2827 const LocalMediaDeviceSet& aDevices) { 2828 nsCOMPtr<nsIObserverService> obs = services::GetObserverService(); 2829 nsCOMPtr<nsIMutableArray> devicesCopy = nsArray::Create(); 2830 for (const auto& device : aDevices) { 2831 nsresult rv = devicesCopy->AppendElement(device); 2832 if (NS_WARN_IF(NS_FAILED(rv))) { 2833 obs->NotifyObservers(nullptr, "getUserMedia:response:deny", 2834 aCallID.get()); 2835 return; 2836 } 2837 } 2838 obs->NotifyObservers(devicesCopy, "getUserMedia:privileged:allow", 2839 aCallID.get()); 2840 } 2841 2842 nsresult MediaManager::GenerateUUID(nsAString& aResult) { 2843 nsresult rv; 2844 nsCOMPtr<nsIUUIDGenerator> uuidgen = 2845 do_GetService("@mozilla.org/uuid-generator;1", &rv); 2846 NS_ENSURE_SUCCESS(rv, rv); 2847 2848 // Generate a call ID. 2849 nsID id; 2850 rv = uuidgen->GenerateUUIDInPlace(&id); 2851 NS_ENSURE_SUCCESS(rv, rv); 2852 2853 char buffer[NSID_LENGTH]; 2854 id.ToProvidedString(buffer); 2855 aResult.Assign(NS_ConvertUTF8toUTF16(buffer)); 2856 return NS_OK; 2857 } 2858 2859 enum class GetUserMediaSecurityState { 2860 Other = 0, 2861 HTTPS = 1, 2862 File = 2, 2863 App = 3, 2864 Localhost = 4, 2865 Loop = 5, 2866 Privileged = 6 2867 }; 2868 2869 /** 2870 * This function is used in getUserMedia when privacy.resistFingerprinting is 2871 * true. Only mediaSource of audio/video constraint will be kept. On mobile 2872 * facing mode is also kept. 2873 */ 2874 static void ReduceConstraint( 2875 OwningBooleanOrMediaTrackConstraints& aConstraint) { 2876 // Not requesting stream. 2877 if (!MediaManager::IsOn(aConstraint)) { 2878 return; 2879 } 2880 2881 // It looks like {audio: true}, do nothing. 2882 if (!aConstraint.IsMediaTrackConstraints()) { 2883 return; 2884 } 2885 2886 // Keep mediaSource. 2887 Maybe<nsString> mediaSource; 2888 if (aConstraint.GetAsMediaTrackConstraints().mMediaSource.WasPassed()) { 2889 mediaSource = 2890 Some(aConstraint.GetAsMediaTrackConstraints().mMediaSource.Value()); 2891 } 2892 2893 Maybe<OwningStringOrStringSequenceOrConstrainDOMStringParameters> facingMode; 2894 if (aConstraint.GetAsMediaTrackConstraints().mFacingMode.WasPassed()) { 2895 facingMode = 2896 Some(aConstraint.GetAsMediaTrackConstraints().mFacingMode.Value()); 2897 } 2898 2899 aConstraint.Uninit(); 2900 if (mediaSource) { 2901 aConstraint.SetAsMediaTrackConstraints().mMediaSource.Construct( 2902 *mediaSource); 2903 } else { 2904 (void)aConstraint.SetAsMediaTrackConstraints(); 2905 } 2906 2907 #if defined(MOZ_WIDGET_ANDROID) || defined(MOZ_WIDGET_UIKIT) 2908 if (facingMode) { 2909 aConstraint.SetAsMediaTrackConstraints().mFacingMode.Construct(*facingMode); 2910 } else { 2911 (void)aConstraint.SetAsMediaTrackConstraints(); 2912 } 2913 #endif 2914 } 2915 2916 /** 2917 * The entry point for this file. A call from MediaDevices::GetUserMedia 2918 * will end up here. MediaManager is a singleton that is responsible 2919 * for handling all incoming getUserMedia calls from every window. 2920 */ 2921 RefPtr<MediaManager::StreamPromise> MediaManager::GetUserMedia( 2922 nsPIDOMWindowInner* aWindow, 2923 const MediaStreamConstraints& aConstraintsPassedIn, 2924 CallerType aCallerType) { 2925 MOZ_ASSERT(NS_IsMainThread()); 2926 MOZ_ASSERT(aWindow); 2927 uint64_t windowID = aWindow->WindowID(); 2928 2929 MediaStreamConstraints c(aConstraintsPassedIn); // use a modifiable copy 2930 2931 if (sHasMainThreadShutdown) { 2932 return StreamPromise::CreateAndReject( 2933 MakeRefPtr<MediaMgrError>(MediaMgrError::Name::AbortError, 2934 "In shutdown"), 2935 __func__); 2936 } 2937 2938 // Determine permissions early (while we still have a stack). 2939 2940 nsIURI* docURI = aWindow->GetDocumentURI(); 2941 if (!docURI) { 2942 return StreamPromise::CreateAndReject( 2943 MakeRefPtr<MediaMgrError>(MediaMgrError::Name::AbortError), __func__); 2944 } 2945 bool isChrome = (aCallerType == CallerType::System); 2946 bool privileged = 2947 isChrome || 2948 Preferences::GetBool("media.navigator.permission.disabled", false); 2949 bool isSecure = aWindow->IsSecureContext(); 2950 bool isHandlingUserInput = UserActivation::IsHandlingUserInput(); 2951 nsCString host; 2952 nsresult rv = docURI->GetHost(host); 2953 2954 nsCOMPtr<nsIPrincipal> principal = 2955 nsGlobalWindowInner::Cast(aWindow)->GetPrincipal(); 2956 if (NS_WARN_IF(!principal)) { 2957 return StreamPromise::CreateAndReject( 2958 MakeRefPtr<MediaMgrError>(MediaMgrError::Name::SecurityError), 2959 __func__); 2960 } 2961 2962 Document* doc = aWindow->GetExtantDoc(); 2963 if (NS_WARN_IF(!doc)) { 2964 return StreamPromise::CreateAndReject( 2965 MakeRefPtr<MediaMgrError>(MediaMgrError::Name::SecurityError), 2966 __func__); 2967 } 2968 2969 // Disallow access to null principal pages and http pages (unless pref) 2970 if (principal->GetIsNullPrincipal() || 2971 !(isSecure || StaticPrefs::media_getusermedia_insecure_enabled())) { 2972 return StreamPromise::CreateAndReject( 2973 MakeRefPtr<MediaMgrError>(MediaMgrError::Name::NotAllowedError), 2974 __func__); 2975 } 2976 2977 // This principal needs to be sent to different threads and so via IPC. 2978 // For this reason it's better to convert it to PrincipalInfo right now. 2979 ipc::PrincipalInfo principalInfo; 2980 rv = PrincipalToPrincipalInfo(principal, &principalInfo); 2981 if (NS_WARN_IF(NS_FAILED(rv))) { 2982 return StreamPromise::CreateAndReject( 2983 MakeRefPtr<MediaMgrError>(MediaMgrError::Name::SecurityError), 2984 __func__); 2985 } 2986 2987 const bool resistFingerprinting = 2988 !isChrome && doc->ShouldResistFingerprinting(RFPTarget::MediaDevices); 2989 if (resistFingerprinting) { 2990 ReduceConstraint(c.mVideo); 2991 ReduceConstraint(c.mAudio); 2992 } 2993 2994 if (!Preferences::GetBool("media.navigator.video.enabled", true)) { 2995 c.mVideo.SetAsBoolean() = false; 2996 } 2997 2998 MediaSourceEnum videoType = MediaSourceEnum::Other; // none 2999 MediaSourceEnum audioType = MediaSourceEnum::Other; // none 3000 3001 if (c.mVideo.IsMediaTrackConstraints()) { 3002 auto& vc = c.mVideo.GetAsMediaTrackConstraints(); 3003 if (!vc.mMediaSource.WasPassed()) { 3004 vc.mMediaSource.Construct().AssignASCII( 3005 dom::GetEnumString(MediaSourceEnum::Camera)); 3006 } 3007 videoType = dom::StringToEnum<MediaSourceEnum>(vc.mMediaSource.Value()) 3008 .valueOr(MediaSourceEnum::Other); 3009 glean::webrtc::get_user_media_type.AccumulateSingleSample( 3010 (uint32_t)videoType); 3011 switch (videoType) { 3012 case MediaSourceEnum::Camera: 3013 break; 3014 3015 case MediaSourceEnum::Browser: 3016 // If no window id is passed in then default to the caller's window. 3017 // Functional defaults are helpful in tests, but also a natural outcome 3018 // of the constraints API's limited semantics for requiring input. 3019 if (!vc.mBrowserWindow.WasPassed()) { 3020 nsPIDOMWindowOuter* outer = aWindow->GetOuterWindow(); 3021 vc.mBrowserWindow.Construct(outer->WindowID()); 3022 } 3023 [[fallthrough]]; 3024 case MediaSourceEnum::Screen: 3025 case MediaSourceEnum::Window: 3026 // Deny screensharing request if support is disabled, or 3027 // the requesting document is not from a host on the whitelist. 3028 if (!Preferences::GetBool( 3029 ((videoType == MediaSourceEnum::Browser) 3030 ? "media.getusermedia.browser.enabled" 3031 : "media.getusermedia.screensharing.enabled"), 3032 false) || 3033 (!privileged && !aWindow->IsSecureContext())) { 3034 return StreamPromise::CreateAndReject( 3035 MakeRefPtr<MediaMgrError>(MediaMgrError::Name::NotAllowedError), 3036 __func__); 3037 } 3038 break; 3039 3040 case MediaSourceEnum::Microphone: 3041 case MediaSourceEnum::Other: 3042 default: { 3043 return StreamPromise::CreateAndReject( 3044 MakeRefPtr<MediaMgrError>(MediaMgrError::Name::OverconstrainedError, 3045 "", u"mediaSource"_ns), 3046 __func__); 3047 } 3048 } 3049 3050 if (!privileged) { 3051 // Only allow privileged content to explicitly pick full-screen, 3052 // application or tabsharing, since these modes are still available for 3053 // testing. All others get "Window" (*) sharing. 3054 // 3055 // *) We overload "Window" with the new default getDisplayMedia spec- 3056 // mandated behavior of not influencing user-choice, which we currently 3057 // implement as a list containing BOTH windows AND screen(s). 3058 // 3059 // Notes on why we chose "Window" as the one to overload. Two reasons: 3060 // 3061 // 1. It's the closest logically & behaviorally (multi-choice, no default) 3062 // 2. Screen is still useful in tests (implicit default is entire screen) 3063 // 3064 // For UX reasons we don't want "Entire Screen" to be the first/default 3065 // choice (in our code first=default). It's a "scary" source that comes 3066 // with complicated warnings on-top that would be confusing as the first 3067 // thing people see, and also deserves to be listed as last resort for 3068 // privacy reasons. 3069 3070 if (videoType == MediaSourceEnum::Screen || 3071 videoType == MediaSourceEnum::Browser) { 3072 videoType = MediaSourceEnum::Window; 3073 vc.mMediaSource.Value().AssignASCII(dom::GetEnumString(videoType)); 3074 } 3075 // only allow privileged content to set the window id 3076 if (vc.mBrowserWindow.WasPassed()) { 3077 vc.mBrowserWindow.Value() = -1; 3078 } 3079 if (vc.mAdvanced.WasPassed()) { 3080 for (MediaTrackConstraintSet& cs : vc.mAdvanced.Value()) { 3081 if (cs.mBrowserWindow.WasPassed()) { 3082 cs.mBrowserWindow.Value() = -1; 3083 } 3084 } 3085 } 3086 } 3087 } else if (IsOn(c.mVideo)) { 3088 videoType = MediaSourceEnum::Camera; 3089 glean::webrtc::get_user_media_type.AccumulateSingleSample( 3090 (uint32_t)videoType); 3091 } 3092 3093 if (c.mAudio.IsMediaTrackConstraints()) { 3094 auto& ac = c.mAudio.GetAsMediaTrackConstraints(); 3095 if (!ac.mMediaSource.WasPassed()) { 3096 ac.mMediaSource.Construct(NS_ConvertASCIItoUTF16( 3097 dom::GetEnumString(MediaSourceEnum::Microphone))); 3098 } 3099 audioType = dom::StringToEnum<MediaSourceEnum>(ac.mMediaSource.Value()) 3100 .valueOr(MediaSourceEnum::Other); 3101 glean::webrtc::get_user_media_type.AccumulateSingleSample( 3102 (uint32_t)audioType); 3103 3104 switch (audioType) { 3105 case MediaSourceEnum::Microphone: 3106 break; 3107 3108 case MediaSourceEnum::AudioCapture: 3109 // Only enable AudioCapture if the pref is enabled. If it's not, we can 3110 // deny right away. 3111 if (!Preferences::GetBool("media.getusermedia.audio.capture.enabled")) { 3112 return StreamPromise::CreateAndReject( 3113 MakeRefPtr<MediaMgrError>(MediaMgrError::Name::NotAllowedError), 3114 __func__); 3115 } 3116 break; 3117 3118 case MediaSourceEnum::Other: 3119 default: { 3120 return StreamPromise::CreateAndReject( 3121 MakeRefPtr<MediaMgrError>(MediaMgrError::Name::OverconstrainedError, 3122 "", u"mediaSource"_ns), 3123 __func__); 3124 } 3125 } 3126 } else if (IsOn(c.mAudio)) { 3127 audioType = MediaSourceEnum::Microphone; 3128 glean::webrtc::get_user_media_type.AccumulateSingleSample( 3129 (uint32_t)audioType); 3130 } 3131 3132 // Create a window listener if it doesn't already exist. 3133 RefPtr<GetUserMediaWindowListener> windowListener = 3134 GetOrMakeWindowListener(aWindow); 3135 MOZ_ASSERT(windowListener); 3136 // Create an inactive DeviceListener to act as a placeholder, so the 3137 // window listener doesn't clean itself up until we're done. 3138 auto placeholderListener = MakeRefPtr<DeviceListener>(); 3139 windowListener->Register(placeholderListener); 3140 3141 { // Check Permissions Policy. Reject if a requested feature is disabled. 3142 bool disabled = !IsOn(c.mAudio) && !IsOn(c.mVideo); 3143 if (IsOn(c.mAudio)) { 3144 if (audioType == MediaSourceEnum::Microphone) { 3145 if (Preferences::GetBool("media.getusermedia.microphone.deny", false) || 3146 !FeaturePolicyUtils::IsFeatureAllowed(doc, u"microphone"_ns)) { 3147 disabled = true; 3148 } 3149 } else if (!FeaturePolicyUtils::IsFeatureAllowed(doc, 3150 u"display-capture"_ns)) { 3151 disabled = true; 3152 } 3153 } 3154 if (IsOn(c.mVideo)) { 3155 if (videoType == MediaSourceEnum::Camera) { 3156 if (Preferences::GetBool("media.getusermedia.camera.deny", false) || 3157 !FeaturePolicyUtils::IsFeatureAllowed(doc, u"camera"_ns)) { 3158 disabled = true; 3159 } 3160 } else if (!FeaturePolicyUtils::IsFeatureAllowed(doc, 3161 u"display-capture"_ns)) { 3162 disabled = true; 3163 } 3164 } 3165 3166 if (disabled) { 3167 placeholderListener->Stop(); 3168 return StreamPromise::CreateAndReject( 3169 MakeRefPtr<MediaMgrError>(MediaMgrError::Name::NotAllowedError), 3170 __func__); 3171 } 3172 } 3173 3174 // Get list of all devices, with origin-specific device ids. 3175 3176 MediaEnginePrefs prefs = mPrefs; 3177 3178 nsString callID; 3179 rv = GenerateUUID(callID); 3180 MOZ_RELEASE_ASSERT(NS_SUCCEEDED(rv)); 3181 3182 bool hasVideo = videoType != MediaSourceEnum::Other; 3183 bool hasAudio = audioType != MediaSourceEnum::Other; 3184 3185 // Handle fake requests from content. For gUM we don't consider resist 3186 // fingerprinting as users should be prompted anyway. 3187 bool forceFakes = c.mFake.WasPassed() && c.mFake.Value(); 3188 // fake:true is effective only for microphone and camera devices, so 3189 // permission must be requested for screen capture even if fake:true is set. 3190 bool hasOnlyForcedFakes = 3191 forceFakes && (!hasVideo || videoType == MediaSourceEnum::Camera) && 3192 (!hasAudio || audioType == MediaSourceEnum::Microphone); 3193 bool askPermission = 3194 (!privileged || 3195 Preferences::GetBool("media.navigator.permission.force")) && 3196 (!hasOnlyForcedFakes || 3197 Preferences::GetBool("media.navigator.permission.fake")); 3198 3199 LOG("%s: Preparing to enumerate devices. windowId=%" PRIu64 3200 ", videoType=%" PRIu8 ", audioType=%" PRIu8 3201 ", forceFakes=%s, askPermission=%s", 3202 __func__, windowID, static_cast<uint8_t>(videoType), 3203 static_cast<uint8_t>(audioType), forceFakes ? "true" : "false", 3204 askPermission ? "true" : "false"); 3205 3206 EnumerationFlags flags = EnumerationFlag::AllowPermissionRequest; 3207 if (forceFakes) { 3208 flags += EnumerationFlag::ForceFakes; 3209 } 3210 RefPtr<MediaManager> self = this; 3211 return EnumerateDevicesImpl( 3212 aWindow, CreateEnumerationParams(videoType, audioType, flags)) 3213 ->Then( 3214 GetCurrentSerialEventTarget(), __func__, 3215 [self, windowID, c, windowListener, 3216 aCallerType](RefPtr<LocalMediaDeviceSetRefCnt> aDevices) { 3217 LOG("GetUserMedia: post enumeration promise success callback " 3218 "starting"); 3219 // Ensure that our windowID is still good. 3220 RefPtr<nsPIDOMWindowInner> window = 3221 nsGlobalWindowInner::GetInnerWindowWithId(windowID); 3222 if (!window || !self->IsWindowListenerStillActive(windowListener)) { 3223 LOG("GetUserMedia: bad window (%" PRIu64 3224 ") in post enumeration success callback!", 3225 windowID); 3226 return LocalDeviceSetPromise::CreateAndReject( 3227 MakeRefPtr<MediaMgrError>(MediaMgrError::Name::AbortError), 3228 __func__); 3229 } 3230 // Apply any constraints. This modifies the passed-in list. 3231 return self->SelectSettings(c, aCallerType, std::move(aDevices)); 3232 }, 3233 [](RefPtr<MediaMgrError>&& aError) { 3234 LOG("GetUserMedia: post enumeration EnumerateDevicesImpl " 3235 "failure callback called!"); 3236 return LocalDeviceSetPromise::CreateAndReject(std::move(aError), 3237 __func__); 3238 }) 3239 ->Then( 3240 GetCurrentSerialEventTarget(), __func__, 3241 [self, windowID, c, windowListener, placeholderListener, hasAudio, 3242 hasVideo, askPermission, prefs, isSecure, isHandlingUserInput, 3243 callID, principalInfo, aCallerType, resistFingerprinting, audioType, 3244 forceFakes](RefPtr<LocalMediaDeviceSetRefCnt> aDevices) mutable { 3245 LOG("GetUserMedia: starting post enumeration promise2 success " 3246 "callback!"); 3247 3248 // Ensure that the window is still good. 3249 RefPtr<nsPIDOMWindowInner> window = 3250 nsGlobalWindowInner::GetInnerWindowWithId(windowID); 3251 if (!window || !self->IsWindowListenerStillActive(windowListener)) { 3252 LOG("GetUserMedia: bad window (%" PRIu64 3253 ") in post enumeration success callback 2!", 3254 windowID); 3255 placeholderListener->Stop(); 3256 return StreamPromise::CreateAndReject( 3257 MakeRefPtr<MediaMgrError>(MediaMgrError::Name::AbortError), 3258 __func__); 3259 } 3260 if (!aDevices->Length()) { 3261 LOG("GetUserMedia: no devices found in post enumeration promise2 " 3262 "success callback! Calling error handler!"); 3263 placeholderListener->Stop(); 3264 // When privacy.resistFingerprinting = true, no 3265 // available device implies content script is requesting 3266 // a fake device, so report NotAllowedError. 3267 auto error = resistFingerprinting 3268 ? MediaMgrError::Name::NotAllowedError 3269 : MediaMgrError::Name::NotFoundError; 3270 return StreamPromise::CreateAndReject( 3271 MakeRefPtr<MediaMgrError>(error), __func__); 3272 } 3273 3274 // Time to start devices. Create the necessary device listeners and 3275 // remove the placeholder. 3276 RefPtr<DeviceListener> audioListener; 3277 RefPtr<DeviceListener> videoListener; 3278 if (hasAudio) { 3279 audioListener = MakeRefPtr<DeviceListener>(); 3280 windowListener->Register(audioListener); 3281 } 3282 if (hasVideo) { 3283 videoListener = MakeRefPtr<DeviceListener>(); 3284 windowListener->Register(videoListener); 3285 } 3286 placeholderListener->Stop(); 3287 3288 bool focusSource = mozilla::Preferences::GetBool( 3289 "media.getusermedia.window.focus_source.enabled", true); 3290 3291 // Incremental hack to compile. To be replaced by deeper 3292 // refactoring. MediaManager allows 3293 // "neither-resolve-nor-reject" semantics, so we cannot 3294 // use MozPromiseHolder here. 3295 MozPromiseHolder<StreamPromise> holder; 3296 RefPtr<StreamPromise> p = holder.Ensure(__func__); 3297 3298 // Pass callbacks and listeners along to GetUserMediaStreamTask. 3299 auto task = MakeRefPtr<GetUserMediaStreamTask>( 3300 c, std::move(holder), windowID, std::move(windowListener), 3301 std::move(audioListener), std::move(videoListener), prefs, 3302 principalInfo, aCallerType, focusSource); 3303 3304 // It is time to ask for user permission, prime voice processing 3305 // now. Use a local lambda to enable a guard pattern. 3306 [&] { 3307 if (forceFakes) { 3308 return; 3309 } 3310 3311 if (audioType != MediaSourceEnum::Microphone) { 3312 return; 3313 } 3314 3315 if (!StaticPrefs:: 3316 media_getusermedia_microphone_voice_stream_priming_enabled() || 3317 !StaticPrefs:: 3318 media_getusermedia_microphone_prefer_voice_stream_with_processing_enabled()) { 3319 return; 3320 } 3321 3322 if (const auto fc = FlattenedConstraints( 3323 NormalizedConstraints(GetInvariant(c.mAudio))); 3324 !fc.mEchoCancellation.Get(prefs.mAecOn) && 3325 !fc.mAutoGainControl.Get(prefs.mAgcOn && prefs.mAecOn) && 3326 !fc.mNoiseSuppression.Get(prefs.mNoiseOn && prefs.mAecOn)) { 3327 return; 3328 } 3329 3330 if (GetPersistentPermissions(windowID) 3331 .map([](auto&& aState) { 3332 return aState.mMicrophonePermission == 3333 PersistentPermissionState::Deny; 3334 }) 3335 .unwrapOr(true)) { 3336 return; 3337 } 3338 3339 task->PrimeVoiceProcessing(); 3340 }(); 3341 3342 size_t taskCount = 3343 self->AddTaskAndGetCount(windowID, callID, std::move(task)); 3344 3345 if (!askPermission) { 3346 self->NotifyAllowed(callID, *aDevices); 3347 } else { 3348 auto req = MakeRefPtr<GetUserMediaRequest>( 3349 window, callID, std::move(aDevices), c, isSecure, 3350 isHandlingUserInput); 3351 if (!Preferences::GetBool("media.navigator.permission.force") && 3352 taskCount > 1) { 3353 // there is at least 1 pending gUM request 3354 // For the scarySources test case, always send the 3355 // request 3356 self->mPendingGUMRequest.AppendElement(req.forget()); 3357 } else { 3358 nsCOMPtr<nsIObserverService> obs = 3359 services::GetObserverService(); 3360 obs->NotifyObservers(req, "getUserMedia:request", nullptr); 3361 } 3362 } 3363 #ifdef MOZ_WEBRTC 3364 self->mLogHandle = EnsureWebrtcLogging(); 3365 #endif 3366 return p; 3367 }, 3368 [placeholderListener](RefPtr<MediaMgrError>&& aError) { 3369 LOG("GetUserMedia: post enumeration SelectSettings failure " 3370 "callback called!"); 3371 placeholderListener->Stop(); 3372 return StreamPromise::CreateAndReject(std::move(aError), __func__); 3373 }); 3374 }; 3375 3376 RefPtr<LocalDeviceSetPromise> MediaManager::AnonymizeDevices( 3377 nsPIDOMWindowInner* aWindow, RefPtr<const MediaDeviceSetRefCnt> aDevices) { 3378 // Get an origin-key (for either regular or private browsing). 3379 MOZ_ASSERT(NS_IsMainThread()); 3380 uint64_t windowId = aWindow->WindowID(); 3381 nsCOMPtr<nsIPrincipal> principal = 3382 nsGlobalWindowInner::Cast(aWindow)->GetPrincipal(); 3383 MOZ_ASSERT(principal); 3384 ipc::PrincipalInfo principalInfo; 3385 nsresult rv = PrincipalToPrincipalInfo(principal, &principalInfo); 3386 if (NS_WARN_IF(NS_FAILED(rv))) { 3387 return LocalDeviceSetPromise::CreateAndReject( 3388 MakeRefPtr<MediaMgrError>(MediaMgrError::Name::NotAllowedError), 3389 __func__); 3390 } 3391 bool resistFingerprinting = 3392 aWindow->AsGlobal()->ShouldResistFingerprinting(RFPTarget::MediaDevices); 3393 bool persist = 3394 IsActivelyCapturingOrHasAPermission(windowId) && !resistFingerprinting; 3395 return media::GetPrincipalKey(principalInfo, persist) 3396 ->Then( 3397 GetMainThreadSerialEventTarget(), __func__, 3398 [rawDevices = std::move(aDevices), windowId, 3399 resistFingerprinting](const nsCString& aOriginKey) { 3400 MOZ_ASSERT(!aOriginKey.IsEmpty()); 3401 RefPtr anonymized = new LocalMediaDeviceSetRefCnt(); 3402 for (const RefPtr<MediaDevice>& device : *rawDevices) { 3403 nsString name = device->mRawName; 3404 if (name.Find(u"AirPods"_ns) != -1) { 3405 name = u"AirPods"_ns; 3406 } 3407 3408 nsString id = device->mRawID; 3409 if (resistFingerprinting) { 3410 nsRFPService::GetMediaDeviceName(name, device->mKind); 3411 id = name; 3412 id.AppendInt(windowId); 3413 } 3414 // An empty id represents a virtual default device, for which 3415 // the exposed deviceId is the empty string. 3416 if (!id.IsEmpty()) { 3417 nsContentUtils::AnonymizeId(id, aOriginKey); 3418 } 3419 3420 nsString groupId = device->mRawGroupID; 3421 if (resistFingerprinting) { 3422 nsRFPService::GetMediaDeviceGroup(groupId, device->mKind); 3423 } 3424 // Use window id to salt group id in order to make it session 3425 // based as required by the spec. This does not provide unique 3426 // group ids through out a browser restart. However, this is not 3427 // against the spec. Furthermore, since device ids are the same 3428 // after a browser restart the fingerprint is not bigger. 3429 groupId.AppendInt(windowId); 3430 nsContentUtils::AnonymizeId(groupId, aOriginKey); 3431 anonymized->EmplaceBack( 3432 new LocalMediaDevice(device, id, groupId, name)); 3433 } 3434 return LocalDeviceSetPromise::CreateAndResolve(anonymized, 3435 __func__); 3436 }, 3437 [](nsresult rs) { 3438 NS_WARNING("AnonymizeDevices failed to get Principal Key"); 3439 return LocalDeviceSetPromise::CreateAndReject( 3440 MakeRefPtr<MediaMgrError>(MediaMgrError::Name::AbortError), 3441 __func__); 3442 }); 3443 } 3444 3445 RefPtr<LocalDeviceSetPromise> MediaManager::EnumerateDevicesImpl( 3446 nsPIDOMWindowInner* aWindow, EnumerationParams aParams) { 3447 MOZ_ASSERT(NS_IsMainThread()); 3448 3449 uint64_t windowId = aWindow->WindowID(); 3450 LOG("%s: windowId=%" PRIu64 ", aVideoInputType=%" PRIu8 3451 ", aAudioInputType=%" PRIu8, 3452 __func__, windowId, static_cast<uint8_t>(aParams.VideoInputType()), 3453 static_cast<uint8_t>(aParams.AudioInputType())); 3454 3455 // To get a device list anonymized for a particular origin, we must: 3456 // 1. Get the raw devices list 3457 // 2. Anonymize the raw list with an origin-key. 3458 3459 // Add the window id here to check for that and abort silently if no longer 3460 // exists. 3461 RefPtr<GetUserMediaWindowListener> windowListener = 3462 GetOrMakeWindowListener(aWindow); 3463 MOZ_ASSERT(windowListener); 3464 // Create an inactive DeviceListener to act as a placeholder, so the 3465 // window listener doesn't clean itself up until we're done. 3466 auto placeholderListener = MakeRefPtr<DeviceListener>(); 3467 windowListener->Register(placeholderListener); 3468 3469 return MaybeRequestPermissionAndEnumerateRawDevices(std::move(aParams)) 3470 ->Then( 3471 GetMainThreadSerialEventTarget(), __func__, 3472 [self = RefPtr(this), this, window = nsCOMPtr(aWindow), 3473 placeholderListener](RefPtr<MediaDeviceSetRefCnt> aDevices) mutable { 3474 // Only run if window is still on our active list. 3475 MediaManager* mgr = MediaManager::GetIfExists(); 3476 if (!mgr || placeholderListener->Stopped()) { 3477 // The listener has already been removed if the window is no 3478 // longer active. 3479 return LocalDeviceSetPromise::CreateAndReject( 3480 MakeRefPtr<MediaMgrError>(MediaMgrError::Name::AbortError), 3481 __func__); 3482 } 3483 MOZ_ASSERT(mgr->IsWindowStillActive(window->WindowID())); 3484 placeholderListener->Stop(); 3485 return AnonymizeDevices(window, aDevices); 3486 }, 3487 [placeholderListener](RefPtr<MediaMgrError>&& aError) { 3488 // EnumerateDevicesImpl may fail if a new doc has been set, in which 3489 // case the OnNavigation() method should have removed all previous 3490 // active listeners, or if a platform device access request was not 3491 // granted. 3492 placeholderListener->Stop(); 3493 return LocalDeviceSetPromise::CreateAndReject(std::move(aError), 3494 __func__); 3495 }); 3496 } 3497 3498 RefPtr<LocalDevicePromise> MediaManager::SelectAudioOutput( 3499 nsPIDOMWindowInner* aWindow, const dom::AudioOutputOptions& aOptions, 3500 CallerType aCallerType) { 3501 bool isHandlingUserInput = UserActivation::IsHandlingUserInput(); 3502 nsCOMPtr<nsIPrincipal> principal = 3503 nsGlobalWindowInner::Cast(aWindow)->GetPrincipal(); 3504 if (!FeaturePolicyUtils::IsFeatureAllowed(aWindow->GetExtantDoc(), 3505 u"speaker-selection"_ns)) { 3506 return LocalDevicePromise::CreateAndReject( 3507 MakeRefPtr<MediaMgrError>( 3508 MediaMgrError::Name::NotAllowedError, 3509 "Document's Permissions Policy does not allow selectAudioOutput()"), 3510 __func__); 3511 } 3512 if (NS_WARN_IF(!principal)) { 3513 return LocalDevicePromise::CreateAndReject( 3514 MakeRefPtr<MediaMgrError>(MediaMgrError::Name::SecurityError), 3515 __func__); 3516 } 3517 // Disallow access to null principal. 3518 if (principal->GetIsNullPrincipal()) { 3519 return LocalDevicePromise::CreateAndReject( 3520 MakeRefPtr<MediaMgrError>(MediaMgrError::Name::NotAllowedError), 3521 __func__); 3522 } 3523 ipc::PrincipalInfo principalInfo; 3524 nsresult rv = PrincipalToPrincipalInfo(principal, &principalInfo); 3525 if (NS_WARN_IF(NS_FAILED(rv))) { 3526 return LocalDevicePromise::CreateAndReject( 3527 MakeRefPtr<MediaMgrError>(MediaMgrError::Name::SecurityError), 3528 __func__); 3529 } 3530 uint64_t windowID = aWindow->WindowID(); 3531 const bool resistFingerprinting = 3532 aWindow->AsGlobal()->ShouldResistFingerprinting(aCallerType, 3533 RFPTarget::MediaDevices); 3534 return EnumerateDevicesImpl( 3535 aWindow, CreateEnumerationParams( 3536 MediaSourceEnum::Other, MediaSourceEnum::Other, 3537 {EnumerationFlag::EnumerateAudioOutputs, 3538 EnumerationFlag::AllowPermissionRequest})) 3539 ->Then( 3540 GetCurrentSerialEventTarget(), __func__, 3541 [self = RefPtr<MediaManager>(this), windowID, aOptions, aCallerType, 3542 resistFingerprinting, isHandlingUserInput, 3543 principalInfo](RefPtr<LocalMediaDeviceSetRefCnt> aDevices) mutable { 3544 // Ensure that the window is still good. 3545 RefPtr<nsPIDOMWindowInner> window = 3546 nsGlobalWindowInner::GetInnerWindowWithId(windowID); 3547 if (!window) { 3548 LOG("SelectAudioOutput: bad window (%" PRIu64 3549 ") in post enumeration success callback!", 3550 windowID); 3551 return LocalDevicePromise::CreateAndReject( 3552 MakeRefPtr<MediaMgrError>(MediaMgrError::Name::AbortError), 3553 __func__); 3554 } 3555 if (aDevices->IsEmpty()) { 3556 LOG("SelectAudioOutput: no devices found"); 3557 auto error = resistFingerprinting 3558 ? MediaMgrError::Name::NotAllowedError 3559 : MediaMgrError::Name::NotFoundError; 3560 return LocalDevicePromise::CreateAndReject( 3561 MakeRefPtr<MediaMgrError>(error), __func__); 3562 } 3563 MozPromiseHolder<LocalDevicePromise> holder; 3564 RefPtr<LocalDevicePromise> p = holder.Ensure(__func__); 3565 auto task = MakeRefPtr<SelectAudioOutputTask>( 3566 std::move(holder), windowID, aCallerType, principalInfo); 3567 nsString callID; 3568 nsresult rv = GenerateUUID(callID); 3569 MOZ_RELEASE_ASSERT(NS_SUCCEEDED(rv)); 3570 size_t taskCount = 3571 self->AddTaskAndGetCount(windowID, callID, std::move(task)); 3572 bool askPermission = 3573 !Preferences::GetBool("media.navigator.permission.disabled") || 3574 Preferences::GetBool("media.navigator.permission.force"); 3575 if (!askPermission) { 3576 self->NotifyAllowed(callID, *aDevices); 3577 } else { 3578 MOZ_ASSERT(window->IsSecureContext()); 3579 auto req = MakeRefPtr<GetUserMediaRequest>( 3580 window, callID, std::move(aDevices), aOptions, true, 3581 isHandlingUserInput); 3582 if (taskCount > 1) { 3583 // there is at least 1 pending gUM request 3584 self->mPendingGUMRequest.AppendElement(req.forget()); 3585 } else { 3586 nsCOMPtr<nsIObserverService> obs = 3587 services::GetObserverService(); 3588 obs->NotifyObservers(req, "getUserMedia:request", nullptr); 3589 } 3590 } 3591 return p; 3592 }, 3593 [](RefPtr<MediaMgrError> aError) { 3594 LOG("SelectAudioOutput: EnumerateDevicesImpl " 3595 "failure callback called!"); 3596 return LocalDevicePromise::CreateAndReject(std::move(aError), 3597 __func__); 3598 }); 3599 } 3600 3601 MediaEngine* MediaManager::GetBackend() { 3602 MOZ_ASSERT(MediaManager::IsInMediaThread()); 3603 // Plugin backends as appropriate. The default engine also currently 3604 // includes picture support for Android. 3605 // This IS called off main-thread. 3606 if (!mBackend) { 3607 #if defined(MOZ_WEBRTC) 3608 mBackend = new MediaEngineWebRTC(); 3609 #else 3610 mBackend = new MediaEngineFake(); 3611 #endif 3612 mDeviceListChangeListener = mBackend->DeviceListChangeEvent().Connect( 3613 AbstractThread::MainThread(), this, &MediaManager::DeviceListChanged); 3614 } 3615 return mBackend; 3616 } 3617 3618 void MediaManager::OnNavigation(uint64_t aWindowID) { 3619 MOZ_ASSERT(NS_IsMainThread()); 3620 LOG("OnNavigation for %" PRIu64, aWindowID); 3621 3622 // Stop the streams for this window. The runnables check this value before 3623 // making a call to content. 3624 3625 nsTArray<nsString>* callIDs; 3626 if (mCallIds.Get(aWindowID, &callIDs)) { 3627 for (auto& callID : *callIDs) { 3628 mActiveCallbacks.Remove(callID); 3629 for (auto& request : mPendingGUMRequest.Clone()) { 3630 nsString id; 3631 request->GetCallID(id); 3632 if (id == callID) { 3633 mPendingGUMRequest.RemoveElement(request); 3634 } 3635 } 3636 } 3637 mCallIds.Remove(aWindowID); 3638 } 3639 3640 if (RefPtr<GetUserMediaWindowListener> listener = 3641 GetWindowListener(aWindowID)) { 3642 listener->RemoveAll(); 3643 } 3644 MOZ_ASSERT(!GetWindowListener(aWindowID)); 3645 } 3646 3647 void MediaManager::OnCameraMute(bool aMute) { 3648 MOZ_ASSERT(NS_IsMainThread()); 3649 LOG("OnCameraMute for all windows"); 3650 mCamerasMuted = aMute; 3651 // This is safe since we're on main-thread, and the windowlist can only 3652 // be added to from the main-thread 3653 for (const auto& window : 3654 ToTArray<AutoTArray<RefPtr<GetUserMediaWindowListener>, 2>>( 3655 mActiveWindows.Values())) { 3656 window->MuteOrUnmuteCameras(aMute); 3657 } 3658 } 3659 3660 void MediaManager::OnMicrophoneMute(bool aMute) { 3661 MOZ_ASSERT(NS_IsMainThread()); 3662 LOG("OnMicrophoneMute for all windows"); 3663 mMicrophonesMuted = aMute; 3664 // This is safe since we're on main-thread, and the windowlist can only 3665 // be added to from the main-thread 3666 for (const auto& window : 3667 ToTArray<AutoTArray<RefPtr<GetUserMediaWindowListener>, 2>>( 3668 mActiveWindows.Values())) { 3669 window->MuteOrUnmuteMicrophones(aMute); 3670 } 3671 } 3672 3673 RefPtr<GetUserMediaWindowListener> MediaManager::GetOrMakeWindowListener( 3674 nsPIDOMWindowInner* aWindow) { 3675 Document* doc = aWindow->GetExtantDoc(); 3676 if (!doc) { 3677 // The window has been destroyed. Destroyed windows don't have listeners. 3678 return nullptr; 3679 } 3680 nsIPrincipal* principal = doc->NodePrincipal(); 3681 uint64_t windowId = aWindow->WindowID(); 3682 RefPtr<GetUserMediaWindowListener> windowListener = 3683 GetWindowListener(windowId); 3684 if (windowListener) { 3685 MOZ_ASSERT(PrincipalHandleMatches(windowListener->GetPrincipalHandle(), 3686 principal)); 3687 } else { 3688 windowListener = new GetUserMediaWindowListener( 3689 windowId, MakePrincipalHandle(principal)); 3690 AddWindowID(windowId, windowListener); 3691 } 3692 return windowListener; 3693 } 3694 3695 void MediaManager::AddWindowID(uint64_t aWindowId, 3696 RefPtr<GetUserMediaWindowListener> aListener) { 3697 MOZ_ASSERT(NS_IsMainThread()); 3698 // Store the WindowID in a hash table and mark as active. The entry is removed 3699 // when this window is closed or navigated away from. 3700 // This is safe since we're on main-thread, and the windowlist can only 3701 // be invalidated from the main-thread (see OnNavigation) 3702 if (IsWindowStillActive(aWindowId)) { 3703 MOZ_ASSERT(false, "Window already added"); 3704 return; 3705 } 3706 3707 aListener->MuteOrUnmuteCameras(mCamerasMuted); 3708 aListener->MuteOrUnmuteMicrophones(mMicrophonesMuted); 3709 GetActiveWindows()->InsertOrUpdate(aWindowId, std::move(aListener)); 3710 3711 RefPtr<WindowGlobalChild> wgc = 3712 WindowGlobalChild::GetByInnerWindowId(aWindowId); 3713 if (wgc) { 3714 wgc->BlockBFCacheFor(BFCacheStatus::ACTIVE_GET_USER_MEDIA); 3715 } 3716 } 3717 3718 void MediaManager::RemoveWindowID(uint64_t aWindowId) { 3719 RefPtr<WindowGlobalChild> wgc = 3720 WindowGlobalChild::GetByInnerWindowId(aWindowId); 3721 if (wgc) { 3722 wgc->UnblockBFCacheFor(BFCacheStatus::ACTIVE_GET_USER_MEDIA); 3723 } 3724 3725 mActiveWindows.Remove(aWindowId); 3726 3727 // get outer windowID 3728 auto* window = nsGlobalWindowInner::GetInnerWindowWithId(aWindowId); 3729 if (!window) { 3730 LOG("No inner window for %" PRIu64, aWindowId); 3731 return; 3732 } 3733 3734 auto* outer = window->GetOuterWindow(); 3735 if (!outer) { 3736 LOG("No outer window for inner %" PRIu64, aWindowId); 3737 return; 3738 } 3739 3740 uint64_t outerID = outer->WindowID(); 3741 3742 // Notify the UI that this window no longer has gUM active 3743 char windowBuffer[32]; 3744 SprintfLiteral(windowBuffer, "%" PRIu64, outerID); 3745 nsString data = NS_ConvertUTF8toUTF16(windowBuffer); 3746 3747 nsCOMPtr<nsIObserverService> obs = services::GetObserverService(); 3748 obs->NotifyWhenScriptSafe(nullptr, "recording-window-ended", data.get()); 3749 LOG("Sent recording-window-ended for window %" PRIu64 " (outer %" PRIu64 ")", 3750 aWindowId, outerID); 3751 } 3752 3753 bool MediaManager::IsWindowListenerStillActive( 3754 const RefPtr<GetUserMediaWindowListener>& aListener) { 3755 MOZ_DIAGNOSTIC_ASSERT(aListener); 3756 return aListener && aListener == GetWindowListener(aListener->WindowID()); 3757 } 3758 3759 nsresult MediaManager::GetPref(nsIPrefBranch* aBranch, const char* aPref, 3760 const char* aData, int32_t* aVal) { 3761 if (aData && strcmp(aPref, aData) != 0) { 3762 return NS_ERROR_INVALID_ARG; 3763 } 3764 3765 int32_t temp; 3766 nsresult rv = aBranch->GetIntPref(aPref, &temp); 3767 if (NS_SUCCEEDED(rv)) { 3768 *aVal = temp; 3769 } 3770 3771 return rv; 3772 } 3773 3774 nsresult MediaManager::GetPrefBool(nsIPrefBranch* aBranch, const char* aPref, 3775 const char* aData, bool* aVal) { 3776 if (aData && strcmp(aPref, aData) != 0) { 3777 return NS_ERROR_INVALID_ARG; 3778 } 3779 3780 bool temp; 3781 nsresult rv = aBranch->GetBoolPref(aPref, &temp); 3782 if (NS_SUCCEEDED(rv)) { 3783 *aVal = temp; 3784 } 3785 3786 return rv; 3787 } 3788 3789 #ifdef MOZ_WEBRTC 3790 template <class Enum, class Int> 3791 constexpr Enum ClampEnum(Int v) { 3792 return std::clamp( 3793 static_cast<Enum>(SaturatingCast<std::underlying_type_t<Enum>>(v)), 3794 ContiguousEnumValues<Enum>::min, ContiguousEnumValues<Enum>::max); 3795 } 3796 #endif 3797 3798 void MediaManager::GetPrefs(nsIPrefBranch* aBranch, const char* aData) { 3799 GetPref(aBranch, "media.navigator.video.default_width", aData, 3800 &mPrefs.mWidth); 3801 GetPref(aBranch, "media.navigator.video.default_height", aData, 3802 &mPrefs.mHeight); 3803 GetPref(aBranch, "media.navigator.video.default_fps", aData, &mPrefs.mFPS); 3804 GetPref(aBranch, "media.navigator.audio.fake_frequency", aData, 3805 &mPrefs.mFreq); 3806 #ifdef MOZ_WEBRTC 3807 GetPrefBool(aBranch, "media.navigator.video.resize_mode.enabled", aData, 3808 &mPrefs.mResizeModeEnabled); 3809 int32_t resizeMode{}; 3810 if (NS_SUCCEEDED(GetPref(aBranch, "media.navigator.video.default_resize_mode", 3811 aData, &resizeMode))) { 3812 mPrefs.mResizeMode = ClampEnum<VideoResizeModeEnum>(resizeMode); 3813 } 3814 GetPrefBool(aBranch, "media.getusermedia.audio.processing.platform.enabled", 3815 aData, &mPrefs.mUsePlatformProcessing); 3816 GetPrefBool(aBranch, "media.getusermedia.audio.processing.aec.enabled", aData, 3817 &mPrefs.mAecOn); 3818 GetPrefBool(aBranch, "media.getusermedia.audio.processing.agc.enabled", aData, 3819 &mPrefs.mAgcOn); 3820 GetPrefBool(aBranch, "media.getusermedia.audio.processing.hpf.enabled", aData, 3821 &mPrefs.mHPFOn); 3822 GetPrefBool(aBranch, "media.getusermedia.audio.processing.noise.enabled", 3823 aData, &mPrefs.mNoiseOn); 3824 GetPrefBool(aBranch, "media.getusermedia.audio.processing.transient.enabled", 3825 aData, &mPrefs.mTransientOn); 3826 GetPrefBool(aBranch, "media.getusermedia.audio.processing.agc2.forced", aData, 3827 &mPrefs.mAgc2Forced); 3828 // Use 0 or 1 to force to false or true 3829 // EchoCanceller3Config::echo_removal_control.has_clock_drift. 3830 // -1 is the default, which means automatically set has_clock_drift as 3831 // deemed appropriate. 3832 GetPref(aBranch, "media.getusermedia.audio.processing.aec.expect_drift", 3833 aData, &mPrefs.mExpectDrift); 3834 GetPref(aBranch, "media.getusermedia.audio.processing.agc", aData, 3835 &mPrefs.mAgc); 3836 GetPref(aBranch, "media.getusermedia.audio.processing.noise", aData, 3837 &mPrefs.mNoise); 3838 GetPref(aBranch, "media.getusermedia.audio.max_channels", aData, 3839 &mPrefs.mChannels); 3840 #endif 3841 LOG("%s: default prefs: %dx%d @%dfps, %dHz test tones, " 3842 "resize mode: %s, platform processing: %s, " 3843 "aec: %s, agc: %s, hpf: %s, noise: %s, drift: %s, agc level: %d, agc " 3844 "version: " 3845 "%s, noise level: %d, transient: %s, channels %d", 3846 __FUNCTION__, mPrefs.mWidth, mPrefs.mHeight, mPrefs.mFPS, mPrefs.mFreq, 3847 mPrefs.mResizeModeEnabled ? dom::GetEnumString(mPrefs.mResizeMode).get() 3848 : "disabled", 3849 mPrefs.mUsePlatformProcessing ? "on" : "off", 3850 mPrefs.mAecOn ? "on" : "off", mPrefs.mAgcOn ? "on" : "off", 3851 mPrefs.mHPFOn ? "on" : "off", mPrefs.mNoiseOn ? "on" : "off", 3852 mPrefs.mExpectDrift < 0 ? "auto" 3853 : mPrefs.mExpectDrift ? "on" 3854 : "off", 3855 mPrefs.mAgc, mPrefs.mAgc2Forced ? "2" : "1", mPrefs.mNoise, 3856 mPrefs.mTransientOn ? "on" : "off", mPrefs.mChannels); 3857 } 3858 3859 void MediaManager::Shutdown() { 3860 MOZ_ASSERT(NS_IsMainThread()); 3861 if (sHasMainThreadShutdown) { 3862 return; 3863 } 3864 3865 nsCOMPtr<nsIObserverService> obs = services::GetObserverService(); 3866 3867 obs->RemoveObserver(this, "last-pb-context-exited"); 3868 obs->RemoveObserver(this, "getUserMedia:privileged:allow"); 3869 obs->RemoveObserver(this, "getUserMedia:response:allow"); 3870 obs->RemoveObserver(this, "getUserMedia:response:deny"); 3871 obs->RemoveObserver(this, "getUserMedia:response:noOSPermission"); 3872 obs->RemoveObserver(this, "getUserMedia:revoke"); 3873 obs->RemoveObserver(this, "getUserMedia:muteVideo"); 3874 obs->RemoveObserver(this, "getUserMedia:unmuteVideo"); 3875 obs->RemoveObserver(this, "getUserMedia:muteAudio"); 3876 obs->RemoveObserver(this, "getUserMedia:unmuteAudio"); 3877 obs->RemoveObserver(this, "application-background"); 3878 obs->RemoveObserver(this, "application-foreground"); 3879 3880 nsCOMPtr<nsIPrefBranch> prefs = do_GetService(NS_PREFSERVICE_CONTRACTID); 3881 if (prefs) { 3882 ForeachObservedPref([&](const nsLiteralCString& aPrefName) { 3883 prefs->RemoveObserver(aPrefName, this); 3884 }); 3885 } 3886 3887 if (mDeviceChangeTimer) { 3888 mDeviceChangeTimer->Cancel(); 3889 // Drop ref to MediaTimer early to avoid blocking SharedThreadPool shutdown 3890 mDeviceChangeTimer = nullptr; 3891 } 3892 3893 { 3894 // Close off any remaining active windows. 3895 3896 // Live capture at this point is rare but can happen. Stopping it will make 3897 // the window listeners attempt to remove themselves from the active windows 3898 // table. We cannot touch the table at point so we grab a copy of the window 3899 // listeners first. 3900 const auto listeners = ToArray(GetActiveWindows()->Values()); 3901 for (const auto& listener : listeners) { 3902 listener->RemoveAll(); 3903 } 3904 } 3905 MOZ_ASSERT(GetActiveWindows()->Count() == 0); 3906 3907 GetActiveWindows()->Clear(); 3908 mActiveCallbacks.Clear(); 3909 mCallIds.Clear(); 3910 mPendingGUMRequest.Clear(); 3911 #ifdef MOZ_WEBRTC 3912 mLogHandle = nullptr; 3913 #endif 3914 3915 // From main thread's point of view, shutdown is now done. 3916 // All that remains is shutting down the media thread. 3917 sHasMainThreadShutdown = true; 3918 3919 // Release the backend (and call Shutdown()) from within mMediaThread. 3920 // Don't use MediaManager::Dispatch() because we're 3921 // sHasMainThreadShutdown == true here! 3922 MOZ_ALWAYS_SUCCEEDS(mMediaThread->Dispatch( 3923 NS_NewRunnableFunction(__func__, [self = RefPtr(this), this]() { 3924 LOG("MediaManager Thread Shutdown"); 3925 MOZ_ASSERT(IsInMediaThread()); 3926 // Must shutdown backend on MediaManager thread, since that's 3927 // where we started it from! 3928 if (mBackend) { 3929 mBackend->Shutdown(); // idempotent 3930 mDeviceListChangeListener.DisconnectIfExists(); 3931 } 3932 // last reference, will invoke Shutdown() again 3933 mBackend = nullptr; 3934 }))); 3935 3936 // note that this == sSingleton 3937 MOZ_ASSERT(this == sSingleton); 3938 3939 // Explicitly shut down the TaskQueue so that it releases its 3940 // SharedThreadPool when all tasks have completed. SharedThreadPool blocks 3941 // XPCOM shutdown from proceeding beyond "xpcom-shutdown-threads" until all 3942 // SharedThreadPools are released, but the nsComponentManager keeps a 3943 // reference to the MediaManager for the nsIMediaManagerService until much 3944 // later in shutdown. This also provides additional assurance that no 3945 // further tasks will be queued. 3946 mMediaThread->BeginShutdown()->Then( 3947 GetMainThreadSerialEventTarget(), __func__, [] { 3948 LOG("MediaManager shutdown lambda running, releasing MediaManager " 3949 "singleton"); 3950 // Remove async shutdown blocker 3951 media::MustGetShutdownBarrier()->RemoveBlocker( 3952 sSingleton->mShutdownBlocker); 3953 3954 sSingleton = nullptr; 3955 }); 3956 } 3957 3958 void MediaManager::SendPendingGUMRequest() { 3959 if (mPendingGUMRequest.Length() > 0) { 3960 nsCOMPtr<nsIObserverService> obs = services::GetObserverService(); 3961 obs->NotifyObservers(mPendingGUMRequest[0], "getUserMedia:request", 3962 nullptr); 3963 mPendingGUMRequest.RemoveElementAt(0); 3964 } 3965 } 3966 3967 bool IsGUMResponseNoAccess(const char* aTopic, 3968 MediaMgrError::Name& aErrorName) { 3969 if (!strcmp(aTopic, "getUserMedia:response:deny")) { 3970 aErrorName = MediaMgrError::Name::NotAllowedError; 3971 return true; 3972 } 3973 3974 if (!strcmp(aTopic, "getUserMedia:response:noOSPermission")) { 3975 aErrorName = MediaMgrError::Name::NotFoundError; 3976 return true; 3977 } 3978 3979 return false; 3980 } 3981 3982 static MediaSourceEnum ParseScreenColonWindowID(const char16_t* aData, 3983 uint64_t* aWindowIDOut) { 3984 MOZ_ASSERT(aWindowIDOut); 3985 // may be windowid or screen:windowid 3986 const nsDependentString data(aData); 3987 if (Substring(data, 0, strlen("screen:")).EqualsLiteral("screen:")) { 3988 nsresult rv; 3989 *aWindowIDOut = Substring(data, strlen("screen:")).ToInteger64(&rv); 3990 MOZ_RELEASE_ASSERT(NS_SUCCEEDED(rv)); 3991 return MediaSourceEnum::Screen; 3992 } 3993 nsresult rv; 3994 *aWindowIDOut = data.ToInteger64(&rv); 3995 MOZ_RELEASE_ASSERT(NS_SUCCEEDED(rv)); 3996 return MediaSourceEnum::Camera; 3997 } 3998 3999 nsresult MediaManager::Observe(nsISupports* aSubject, const char* aTopic, 4000 const char16_t* aData) { 4001 MOZ_ASSERT(NS_IsMainThread()); 4002 4003 MediaMgrError::Name gumNoAccessError = MediaMgrError::Name::NotAllowedError; 4004 4005 if (!strcmp(aTopic, NS_PREFBRANCH_PREFCHANGE_TOPIC_ID)) { 4006 nsCOMPtr<nsIPrefBranch> branch(do_QueryInterface(aSubject)); 4007 if (branch) { 4008 GetPrefs(branch, NS_ConvertUTF16toUTF8(aData).get()); 4009 DeviceListChanged(); 4010 } 4011 } else if (!strcmp(aTopic, "last-pb-context-exited")) { 4012 // Clear memory of private-browsing-specific deviceIds. Fire and forget. 4013 media::SanitizeOriginKeys(0, true); 4014 return NS_OK; 4015 } else if (!strcmp(aTopic, "getUserMedia:got-device-permission")) { 4016 MOZ_ASSERT(aSubject); 4017 nsCOMPtr<nsIRunnable> task = do_QueryInterface(aSubject); 4018 MediaManager::Dispatch(NewTaskFrom([task] { task->Run(); })); 4019 return NS_OK; 4020 } else if (!strcmp(aTopic, "getUserMedia:privileged:allow") || 4021 !strcmp(aTopic, "getUserMedia:response:allow")) { 4022 nsString key(aData); 4023 RefPtr<GetUserMediaTask> task = TakeGetUserMediaTask(key); 4024 if (!task) { 4025 return NS_OK; 4026 } 4027 4028 if (sHasMainThreadShutdown) { 4029 task->Denied(MediaMgrError::Name::AbortError, "In shutdown"_ns); 4030 return NS_OK; 4031 } 4032 if (NS_WARN_IF(!aSubject)) { 4033 return NS_ERROR_FAILURE; // ignored 4034 } 4035 // Permission has been granted. aSubject contains the particular device 4036 // or devices selected and approved by the user, if any. 4037 nsCOMPtr<nsIArray> array(do_QueryInterface(aSubject)); 4038 MOZ_ASSERT(array); 4039 uint32_t len = 0; 4040 array->GetLength(&len); 4041 RefPtr<LocalMediaDevice> audioInput; 4042 RefPtr<LocalMediaDevice> videoInput; 4043 RefPtr<LocalMediaDevice> audioOutput; 4044 for (uint32_t i = 0; i < len; i++) { 4045 nsCOMPtr<nsIMediaDevice> device; 4046 array->QueryElementAt(i, NS_GET_IID(nsIMediaDevice), 4047 getter_AddRefs(device)); 4048 MOZ_ASSERT(device); // shouldn't be returning anything else... 4049 if (!device) { 4050 continue; 4051 } 4052 4053 // Casting here is safe because a LocalMediaDevice is created 4054 // only in Gecko side, JS can only query for an instance. 4055 auto* dev = static_cast<LocalMediaDevice*>(device.get()); 4056 switch (dev->Kind()) { 4057 case MediaDeviceKind::Videoinput: 4058 if (!videoInput) { 4059 videoInput = dev; 4060 } 4061 break; 4062 case MediaDeviceKind::Audioinput: 4063 if (!audioInput) { 4064 audioInput = dev; 4065 } 4066 break; 4067 case MediaDeviceKind::Audiooutput: 4068 if (!audioOutput) { 4069 audioOutput = dev; 4070 } 4071 break; 4072 default: 4073 MOZ_CRASH("Unexpected device kind"); 4074 } 4075 } 4076 4077 if (GetUserMediaStreamTask* streamTask = task->AsGetUserMediaStreamTask()) { 4078 bool needVideo = IsOn(streamTask->GetConstraints().mVideo); 4079 bool needAudio = IsOn(streamTask->GetConstraints().mAudio); 4080 MOZ_ASSERT(needVideo || needAudio); 4081 4082 if ((needVideo && !videoInput) || (needAudio && !audioInput)) { 4083 task->Denied(MediaMgrError::Name::NotAllowedError); 4084 return NS_OK; 4085 } 4086 streamTask->Allowed(std::move(audioInput), std::move(videoInput)); 4087 return NS_OK; 4088 } 4089 if (SelectAudioOutputTask* outputTask = task->AsSelectAudioOutputTask()) { 4090 if (!audioOutput) { 4091 task->Denied(MediaMgrError::Name::NotAllowedError); 4092 return NS_OK; 4093 } 4094 outputTask->Allowed(std::move(audioOutput)); 4095 return NS_OK; 4096 } 4097 4098 NS_WARNING("Unknown task type in getUserMedia"); 4099 return NS_ERROR_FAILURE; 4100 4101 } else if (IsGUMResponseNoAccess(aTopic, gumNoAccessError)) { 4102 nsString key(aData); 4103 RefPtr<GetUserMediaTask> task = TakeGetUserMediaTask(key); 4104 if (task) { 4105 task->Denied(gumNoAccessError); 4106 SendPendingGUMRequest(); 4107 } 4108 return NS_OK; 4109 4110 } else if (!strcmp(aTopic, "getUserMedia:revoke")) { 4111 uint64_t windowID; 4112 if (ParseScreenColonWindowID(aData, &windowID) == MediaSourceEnum::Screen) { 4113 LOG("Revoking ScreenCapture access for window %" PRIu64, windowID); 4114 StopScreensharing(windowID); 4115 } else { 4116 LOG("Revoking MediaCapture access for window %" PRIu64, windowID); 4117 OnNavigation(windowID); 4118 } 4119 return NS_OK; 4120 } else if (!strcmp(aTopic, "getUserMedia:muteVideo") || 4121 !strcmp(aTopic, "getUserMedia:unmuteVideo")) { 4122 OnCameraMute(!strcmp(aTopic, "getUserMedia:muteVideo")); 4123 return NS_OK; 4124 } else if (!strcmp(aTopic, "getUserMedia:muteAudio") || 4125 !strcmp(aTopic, "getUserMedia:unmuteAudio")) { 4126 OnMicrophoneMute(!strcmp(aTopic, "getUserMedia:muteAudio")); 4127 return NS_OK; 4128 } else if ((!strcmp(aTopic, "application-background") || 4129 !strcmp(aTopic, "application-foreground")) && 4130 StaticPrefs::media_getusermedia_camera_background_mute_enabled()) { 4131 // On mobile we turn off any cameras (but not mics) while in the background. 4132 // Keeping things simple for now by duplicating test-covered code above. 4133 // 4134 // NOTE: If a mobile device ever wants to implement "getUserMedia:muteVideo" 4135 // as well, it'd need to update this code to handle & test the combinations. 4136 OnCameraMute(!strcmp(aTopic, "application-background")); 4137 } 4138 4139 return NS_OK; 4140 } 4141 4142 NS_IMETHODIMP 4143 MediaManager::CollectReports(nsIHandleReportCallback* aHandleReport, 4144 nsISupports* aData, bool aAnonymize) { 4145 size_t amount = 0; 4146 amount += mActiveWindows.ShallowSizeOfExcludingThis(MallocSizeOf); 4147 for (const GetUserMediaWindowListener* listener : mActiveWindows.Values()) { 4148 amount += listener->SizeOfIncludingThis(MallocSizeOf); 4149 } 4150 amount += mActiveCallbacks.ShallowSizeOfExcludingThis(MallocSizeOf); 4151 for (const GetUserMediaTask* task : mActiveCallbacks.Values()) { 4152 // Assume nsString buffers for keys are accounted in mCallIds. 4153 amount += task->SizeOfIncludingThis(MallocSizeOf); 4154 } 4155 amount += mCallIds.ShallowSizeOfExcludingThis(MallocSizeOf); 4156 for (const auto& array : mCallIds.Values()) { 4157 amount += array->ShallowSizeOfExcludingThis(MallocSizeOf); 4158 for (const nsString& callID : *array) { 4159 amount += callID.SizeOfExcludingThisEvenIfShared(MallocSizeOf); 4160 } 4161 } 4162 amount += mPendingGUMRequest.ShallowSizeOfExcludingThis(MallocSizeOf); 4163 // GetUserMediaRequest pointees of mPendingGUMRequest do not have support 4164 // for memory accounting. mPendingGUMRequest logic should probably be moved 4165 // to the front end (bug 1691625). 4166 MOZ_COLLECT_REPORT("explicit/media/media-manager-aggregates", KIND_HEAP, 4167 UNITS_BYTES, amount, 4168 "Memory used by MediaManager variable length members."); 4169 return NS_OK; 4170 } 4171 4172 nsresult MediaManager::GetActiveMediaCaptureWindows(nsIArray** aArray) { 4173 MOZ_ASSERT(aArray); 4174 4175 nsCOMPtr<nsIMutableArray> array = nsArray::Create(); 4176 4177 for (const auto& entry : mActiveWindows) { 4178 const uint64_t& id = entry.GetKey(); 4179 RefPtr<GetUserMediaWindowListener> winListener = entry.GetData(); 4180 if (!winListener) { 4181 continue; 4182 } 4183 4184 auto* window = nsGlobalWindowInner::GetInnerWindowWithId(id); 4185 MOZ_ASSERT(window); 4186 // XXXkhuey ... 4187 if (!window) { 4188 continue; 4189 } 4190 4191 if (winListener->CapturingVideo() || winListener->CapturingAudio()) { 4192 array->AppendElement(ToSupports(window)); 4193 } 4194 } 4195 4196 array.forget(aArray); 4197 return NS_OK; 4198 } 4199 4200 struct CaptureWindowStateData { 4201 uint16_t* mCamera; 4202 uint16_t* mMicrophone; 4203 uint16_t* mScreenShare; 4204 uint16_t* mWindowShare; 4205 uint16_t* mAppShare; 4206 uint16_t* mBrowserShare; 4207 }; 4208 4209 NS_IMETHODIMP 4210 MediaManager::MediaCaptureWindowState( 4211 nsIDOMWindow* aCapturedWindow, uint16_t* aCamera, uint16_t* aMicrophone, 4212 uint16_t* aScreen, uint16_t* aWindow, uint16_t* aBrowser, 4213 nsTArray<RefPtr<nsIMediaDevice>>& aDevices) { 4214 MOZ_ASSERT(NS_IsMainThread()); 4215 4216 CaptureState camera = CaptureState::Off; 4217 CaptureState microphone = CaptureState::Off; 4218 CaptureState screen = CaptureState::Off; 4219 CaptureState window = CaptureState::Off; 4220 CaptureState browser = CaptureState::Off; 4221 RefPtr<LocalMediaDeviceSetRefCnt> devices; 4222 4223 nsCOMPtr<nsPIDOMWindowInner> piWin = do_QueryInterface(aCapturedWindow); 4224 if (piWin) { 4225 if (RefPtr<GetUserMediaWindowListener> listener = 4226 GetWindowListener(piWin->WindowID())) { 4227 camera = listener->CapturingSource(MediaSourceEnum::Camera); 4228 microphone = listener->CapturingSource(MediaSourceEnum::Microphone); 4229 screen = listener->CapturingSource(MediaSourceEnum::Screen); 4230 window = listener->CapturingSource(MediaSourceEnum::Window); 4231 browser = listener->CapturingSource(MediaSourceEnum::Browser); 4232 devices = listener->GetDevices(); 4233 } 4234 } 4235 4236 *aCamera = FromCaptureState(camera); 4237 *aMicrophone = FromCaptureState(microphone); 4238 *aScreen = FromCaptureState(screen); 4239 *aWindow = FromCaptureState(window); 4240 *aBrowser = FromCaptureState(browser); 4241 if (devices) { 4242 for (auto& device : *devices) { 4243 aDevices.AppendElement(device); 4244 } 4245 } 4246 4247 LOG("%s: window %" PRIu64 " capturing %s %s %s %s %s", __FUNCTION__, 4248 piWin ? piWin->WindowID() : -1, 4249 *aCamera == nsIMediaManagerService::STATE_CAPTURE_ENABLED 4250 ? "camera (enabled)" 4251 : (*aCamera == nsIMediaManagerService::STATE_CAPTURE_DISABLED 4252 ? "camera (disabled)" 4253 : ""), 4254 *aMicrophone == nsIMediaManagerService::STATE_CAPTURE_ENABLED 4255 ? "microphone (enabled)" 4256 : (*aMicrophone == nsIMediaManagerService::STATE_CAPTURE_DISABLED 4257 ? "microphone (disabled)" 4258 : ""), 4259 *aScreen ? "screenshare" : "", *aWindow ? "windowshare" : "", 4260 *aBrowser ? "browsershare" : ""); 4261 4262 return NS_OK; 4263 } 4264 4265 NS_IMETHODIMP 4266 MediaManager::SanitizeDeviceIds(int64_t aSinceWhen) { 4267 MOZ_ASSERT(NS_IsMainThread()); 4268 LOG("%s: sinceWhen = %" PRId64, __FUNCTION__, aSinceWhen); 4269 4270 media::SanitizeOriginKeys(aSinceWhen, false); // we fire and forget 4271 return NS_OK; 4272 } 4273 4274 void MediaManager::StopScreensharing(uint64_t aWindowID) { 4275 // We need to stop window/screensharing for all streams in this innerwindow. 4276 4277 if (RefPtr<GetUserMediaWindowListener> listener = 4278 GetWindowListener(aWindowID)) { 4279 listener->StopSharing(); 4280 } 4281 } 4282 4283 bool MediaManager::IsActivelyCapturingOrHasAPermission(uint64_t aWindowId) { 4284 // Does page currently have a gUM stream active? 4285 4286 nsCOMPtr<nsIArray> array; 4287 GetActiveMediaCaptureWindows(getter_AddRefs(array)); 4288 uint32_t len; 4289 array->GetLength(&len); 4290 for (uint32_t i = 0; i < len; i++) { 4291 nsCOMPtr<nsPIDOMWindowInner> win; 4292 array->QueryElementAt(i, NS_GET_IID(nsPIDOMWindowInner), 4293 getter_AddRefs(win)); 4294 if (win && win->WindowID() == aWindowId) { 4295 return true; 4296 } 4297 } 4298 4299 // Or are persistent permissions (audio or video) granted? 4300 4301 return GetPersistentPermissions(aWindowId) 4302 .map([](auto&& aState) { 4303 return aState.mMicrophonePermission == 4304 PersistentPermissionState::Allow || 4305 aState.mCameraPermission == PersistentPermissionState::Allow; 4306 }) 4307 .unwrapOr(false); 4308 } 4309 4310 DeviceListener::DeviceListener() 4311 : mStopped(false), 4312 mMainThreadCheck(nullptr), 4313 mPrincipalHandle(PRINCIPAL_HANDLE_NONE), 4314 mWindowListener(nullptr) {} 4315 4316 void DeviceListener::Register(GetUserMediaWindowListener* aListener) { 4317 LOG("DeviceListener %p registering with window listener %p", this, aListener); 4318 4319 MOZ_ASSERT(aListener, "No listener"); 4320 MOZ_ASSERT(!mWindowListener, "Already registered"); 4321 MOZ_ASSERT(!Activated(), "Already activated"); 4322 4323 mPrincipalHandle = aListener->GetPrincipalHandle(); 4324 mWindowListener = aListener; 4325 } 4326 4327 void DeviceListener::Activate(RefPtr<LocalMediaDevice> aDevice, 4328 RefPtr<LocalTrackSource> aTrackSource, 4329 bool aStartMuted, bool aIsAllocated) { 4330 MOZ_ASSERT(NS_IsMainThread(), "Only call on main thread"); 4331 4332 LOG("DeviceListener %p activating %s device %p", this, 4333 dom::GetEnumString(aDevice->Kind()).get(), aDevice.get()); 4334 4335 MOZ_ASSERT(!mStopped, "Cannot activate stopped device listener"); 4336 MOZ_ASSERT(!Activated(), "Already activated"); 4337 4338 mMainThreadCheck = PR_GetCurrentThread(); 4339 bool offWhileDisabled = 4340 (aDevice->GetMediaSource() == MediaSourceEnum::Microphone && 4341 Preferences::GetBool( 4342 "media.getusermedia.microphone.off_while_disabled.enabled", true)) || 4343 (aDevice->GetMediaSource() == MediaSourceEnum::Camera && 4344 Preferences::GetBool( 4345 "media.getusermedia.camera.off_while_disabled.enabled", true)); 4346 4347 if (MediaEventSource<void>* event = aDevice->Source()->CaptureEndedEvent()) { 4348 mCaptureEndedListener = event->Connect(AbstractThread::MainThread(), this, 4349 &DeviceListener::Stop); 4350 } 4351 4352 mDeviceState = MakeUnique<DeviceState>( 4353 std::move(aDevice), std::move(aTrackSource), offWhileDisabled); 4354 mDeviceState->mDeviceMuted = aStartMuted; 4355 mDeviceState->mAllocated = aIsAllocated; 4356 if (aStartMuted) { 4357 mDeviceState->mTrackSource->Mute(); 4358 } 4359 } 4360 4361 RefPtr<DeviceListener::DeviceListenerPromise> 4362 DeviceListener::InitializeAsync() { 4363 MOZ_ASSERT(NS_IsMainThread(), "Only call on main thread"); 4364 MOZ_DIAGNOSTIC_ASSERT(!mStopped); 4365 4366 return InvokeAsync( 4367 MediaManager::Get()->mMediaThread, __func__, 4368 [this, self = RefPtr(this), principal = GetPrincipalHandle(), 4369 device = mDeviceState->mDevice, 4370 track = mDeviceState->mTrackSource->mTrack] { 4371 // Start the device if not muted, or if mOffWhileDisabled is 4372 // false (the device should remain on even when muted). 4373 bool startDevice = !mDeviceState->mDeviceMuted || 4374 !mDeviceState->mOffWhileDisabled; 4375 nsresult rv = Initialize(principal, device, track, startDevice); 4376 if (NS_SUCCEEDED(rv)) { 4377 return GenericPromise::CreateAndResolve( 4378 true, "DeviceListener::InitializeAsync success"); 4379 } 4380 return GenericPromise::CreateAndReject( 4381 rv, "DeviceListener::InitializeAsync failure"); 4382 }) 4383 ->Then( 4384 GetMainThreadSerialEventTarget(), __func__, 4385 [self = RefPtr<DeviceListener>(this), this](bool) { 4386 if (mStopped) { 4387 // We were shut down during the async init 4388 return DeviceListenerPromise::CreateAndResolve(true, __func__); 4389 } 4390 4391 MOZ_DIAGNOSTIC_ASSERT(!mDeviceState->mTrackEnabled); 4392 MOZ_DIAGNOSTIC_ASSERT(!mDeviceState->mDeviceEnabled); 4393 MOZ_DIAGNOSTIC_ASSERT(!mDeviceState->mStopped); 4394 4395 mDeviceState->mDeviceEnabled = true; 4396 mDeviceState->mTrackEnabled = true; 4397 mDeviceState->mTrackEnabledTime = TimeStamp::Now(); 4398 return DeviceListenerPromise::CreateAndResolve(true, __func__); 4399 }, 4400 [self = RefPtr<DeviceListener>(this), this](nsresult aRv) { 4401 auto kind = mDeviceState->mDevice->Kind(); 4402 RefPtr<MediaMgrError> err; 4403 if (aRv == NS_ERROR_NOT_AVAILABLE && 4404 kind == MediaDeviceKind::Audioinput) { 4405 nsCString log; 4406 log.AssignLiteral("Concurrent mic process limit."); 4407 err = MakeRefPtr<MediaMgrError>( 4408 MediaMgrError::Name::NotReadableError, std::move(log)); 4409 } else if (NS_FAILED(aRv)) { 4410 nsCString log; 4411 log.AppendPrintf("Starting %s failed", 4412 dom::GetEnumString(kind).get()); 4413 err = MakeRefPtr<MediaMgrError>(MediaMgrError::Name::AbortError, 4414 std::move(log)); 4415 } 4416 4417 if (mStopped) { 4418 return DeviceListenerPromise::CreateAndReject(err, __func__); 4419 } 4420 4421 MOZ_DIAGNOSTIC_ASSERT(!mDeviceState->mTrackEnabled); 4422 MOZ_DIAGNOSTIC_ASSERT(!mDeviceState->mDeviceEnabled); 4423 MOZ_DIAGNOSTIC_ASSERT(!mDeviceState->mStopped); 4424 4425 Stop(); 4426 4427 return DeviceListenerPromise::CreateAndReject(err, __func__); 4428 }); 4429 } 4430 4431 nsresult DeviceListener::Initialize(PrincipalHandle aPrincipal, 4432 LocalMediaDevice* aDevice, 4433 MediaTrack* aTrack, bool aStartDevice) { 4434 MOZ_ASSERT(MediaManager::IsInMediaThread()); 4435 4436 auto kind = aDevice->Kind(); 4437 aDevice->SetTrack(aTrack, aPrincipal); 4438 nsresult rv = aStartDevice ? aDevice->Start() : NS_OK; 4439 if (kind == MediaDeviceKind::Audioinput || 4440 kind == MediaDeviceKind::Videoinput) { 4441 if ((rv == NS_ERROR_NOT_AVAILABLE && kind == MediaDeviceKind::Audioinput) || 4442 (NS_FAILED(rv) && kind == MediaDeviceKind::Videoinput)) { 4443 PR_Sleep(200); 4444 rv = aDevice->Start(); 4445 } 4446 } 4447 LOG("started %s device %p", dom::GetEnumString(kind).get(), aDevice); 4448 return rv; 4449 } 4450 4451 already_AddRefed<DeviceListener> DeviceListener::Clone() const { 4452 MOZ_ASSERT(NS_IsMainThread()); 4453 MediaManager* mgr = MediaManager::GetIfExists(); 4454 if (!mgr) { 4455 return nullptr; 4456 } 4457 if (!mWindowListener) { 4458 return nullptr; 4459 } 4460 auto* thisDevice = GetDevice(); 4461 if (!thisDevice) { 4462 return nullptr; 4463 } 4464 4465 auto* thisTrackSource = GetTrackSource(); 4466 if (!thisTrackSource) { 4467 return nullptr; 4468 } 4469 4470 // See PrepareDOMStream for how a gUM/gDM track is created. 4471 RefPtr<MediaTrack> track; 4472 MediaTrackGraph* mtg = thisTrackSource->mTrack->Graph(); 4473 if (const auto source = thisDevice->GetMediaSource(); 4474 source == dom::MediaSourceEnum::Microphone) { 4475 #ifdef MOZ_WEBRTC 4476 if (thisDevice->IsFake()) { 4477 track = mtg->CreateSourceTrack(MediaSegment::AUDIO); 4478 } else { 4479 track = AudioProcessingTrack::Create(mtg); 4480 track->Suspend(); // Microphone source resumes in SetTrack 4481 } 4482 #else 4483 track = mtg->CreateSourceTrack(MediaSegment::AUDIO); 4484 #endif 4485 } else if (source == dom::MediaSourceEnum::Camera || 4486 source == dom::MediaSourceEnum::Screen || 4487 source == dom::MediaSourceEnum::Window || 4488 source == dom::MediaSourceEnum::Browser) { 4489 track = mtg->CreateSourceTrack(MediaSegment::VIDEO); 4490 } 4491 4492 if (!track) { 4493 return nullptr; 4494 } 4495 4496 RefPtr device = thisDevice->Clone(); 4497 auto listener = MakeRefPtr<DeviceListener>(); 4498 auto trackSource = MakeRefPtr<LocalTrackSource>( 4499 thisTrackSource->GetPrincipal(), thisTrackSource->mLabel, listener, 4500 thisTrackSource->mSource, track, thisTrackSource->mPeerIdentity, 4501 thisTrackSource->mTrackingId); 4502 4503 LOG("DeviceListener %p registering clone", this); 4504 mWindowListener->Register(listener); 4505 LOG("DeviceListener %p activating clone", this); 4506 mWindowListener->Activate(listener, device, trackSource, 4507 /*aIsAllocated=*/false); 4508 4509 listener->mDeviceState->mDeviceEnabled = mDeviceState->mDeviceEnabled; 4510 listener->mDeviceState->mDeviceMuted = mDeviceState->mDeviceMuted; 4511 listener->mDeviceState->mTrackEnabled = mDeviceState->mTrackEnabled; 4512 listener->mDeviceState->mTrackEnabledTime = TimeStamp::Now(); 4513 4514 // We have to do an async operation here, even though Clone() is sync. 4515 // This is fine because JS will not be able to trigger any operation to run 4516 // async on the media thread. 4517 LOG("DeviceListener %p allocating clone device %p async", this, device.get()); 4518 InvokeAsync( 4519 mgr->mMediaThread, __func__, 4520 [thisDevice = RefPtr(thisDevice), device, prefs = mgr->mPrefs, 4521 windowId = mWindowListener->WindowID(), listener, 4522 principal = GetPrincipalHandle(), track, 4523 startDevice = !listener->mDeviceState->mOffWhileDisabled || 4524 (!listener->mDeviceState->mDeviceMuted && 4525 listener->mDeviceState->mDeviceEnabled)] { 4526 const char* outBadConstraint{}; 4527 nsresult rv = device->Source()->Allocate( 4528 thisDevice->Constraints(), prefs, windowId, &outBadConstraint); 4529 LOG("Allocated clone device %p. rv=%s", device.get(), 4530 GetStaticErrorName(rv)); 4531 if (NS_FAILED(rv)) { 4532 return GenericPromise::CreateAndReject( 4533 rv, "DeviceListener::Clone failure #1"); 4534 } 4535 rv = listener->Initialize(principal, device, track, startDevice); 4536 if (NS_SUCCEEDED(rv)) { 4537 return GenericPromise::CreateAndResolve( 4538 true, "DeviceListener::Clone success"); 4539 } 4540 return GenericPromise::CreateAndReject( 4541 rv, "DeviceListener::Clone failure #2"); 4542 }) 4543 ->Then(GetMainThreadSerialEventTarget(), __func__, 4544 [listener, device, 4545 trackSource](GenericPromise::ResolveOrRejectValue&& aValue) { 4546 if (aValue.IsReject()) { 4547 // Allocating/initializing failed. Stopping the device listener 4548 // will destroy the MediaStreamTrackSource's MediaTrack, which 4549 // will make the MediaStreamTrack's mTrack MediaTrack auto-end 4550 // due to lack of inputs. This makes the MediaStreamTrack's 4551 // readyState transition to "ended" as expected. 4552 LOG("Allocating clone device %p failed. Stopping.", 4553 device.get()); 4554 listener->Stop(); 4555 return; 4556 } 4557 listener->mDeviceState->mAllocated = true; 4558 if (listener->mDeviceState->mStopped) { 4559 MediaManager::Dispatch(NS_NewRunnableFunction( 4560 "DeviceListener::Clone::Stop", 4561 [device = listener->mDeviceState->mDevice]() { 4562 device->Stop(); 4563 device->Deallocate(); 4564 })); 4565 } 4566 }); 4567 4568 return listener.forget(); 4569 } 4570 4571 void DeviceListener::Stop() { 4572 MOZ_ASSERT(NS_IsMainThread(), "Only call on main thread"); 4573 4574 if (mStopped) { 4575 return; 4576 } 4577 mStopped = true; 4578 4579 LOG("DeviceListener %p stopping", this); 4580 4581 if (mDeviceState) { 4582 mDeviceState->mDisableTimer->Cancel(); 4583 4584 if (mDeviceState->mStopped) { 4585 // device already stopped. 4586 return; 4587 } 4588 mDeviceState->mStopped = true; 4589 4590 mDeviceState->mTrackSource->Stop(); 4591 4592 if (mDeviceState->mAllocated) { 4593 MediaManager::Dispatch(NewTaskFrom([device = mDeviceState->mDevice]() { 4594 device->Stop(); 4595 device->Deallocate(); 4596 })); 4597 } 4598 4599 mWindowListener->ChromeAffectingStateChanged(); 4600 } 4601 4602 mCaptureEndedListener.DisconnectIfExists(); 4603 4604 // Keep a strong ref to the removed window listener. 4605 RefPtr<GetUserMediaWindowListener> windowListener = mWindowListener; 4606 mWindowListener = nullptr; 4607 windowListener->Remove(this); 4608 } 4609 4610 void DeviceListener::GetSettings(MediaTrackSettings& aOutSettings) const { 4611 MOZ_ASSERT(NS_IsMainThread(), "Only call on main thread"); 4612 LocalMediaDevice* device = GetDevice(); 4613 device->GetSettings(aOutSettings); 4614 4615 MediaSourceEnum mediaSource = device->GetMediaSource(); 4616 if (mediaSource == MediaSourceEnum::Camera || 4617 mediaSource == MediaSourceEnum::Microphone) { 4618 aOutSettings.mDeviceId.Construct(device->mID); 4619 aOutSettings.mGroupId.Construct(device->mGroupID); 4620 } 4621 } 4622 4623 void DeviceListener::GetCapabilities( 4624 MediaTrackCapabilities& aOutCapabilities) const { 4625 MOZ_ASSERT(NS_IsMainThread(), "Only call on main thread"); 4626 LocalMediaDevice* device = GetDevice(); 4627 device->GetCapabilities(aOutCapabilities); 4628 4629 MediaSourceEnum mediaSource = device->GetMediaSource(); 4630 if (mediaSource == MediaSourceEnum::Camera || 4631 mediaSource == MediaSourceEnum::Microphone) { 4632 aOutCapabilities.mDeviceId.Construct(device->mID); 4633 aOutCapabilities.mGroupId.Construct(device->mGroupID); 4634 } 4635 } 4636 4637 auto DeviceListener::UpdateDevice(bool aOn) -> RefPtr<DeviceOperationPromise> { 4638 MOZ_ASSERT(NS_IsMainThread()); 4639 RefPtr<DeviceListener> self = this; 4640 DeviceState& state = *mDeviceState; 4641 return MediaManager::Dispatch<DeviceOperationPromise>( 4642 __func__, 4643 [self, device = state.mDevice, 4644 aOn](MozPromiseHolder<DeviceOperationPromise>& h) { 4645 LOG("Turning %s device (%s)", aOn ? "on" : "off", 4646 NS_ConvertUTF16toUTF8(device->mName).get()); 4647 h.Resolve(aOn ? device->Start() : device->Stop(), __func__); 4648 }) 4649 ->Then( 4650 GetMainThreadSerialEventTarget(), __func__, 4651 [self, this, &state, aOn](nsresult aResult) { 4652 if (state.mStopped) { 4653 // Device was stopped on main thread during the operation. Done. 4654 return DeviceOperationPromise::CreateAndResolve(aResult, 4655 __func__); 4656 } 4657 LOG("DeviceListener %p turning %s %s input device %s", this, 4658 aOn ? "on" : "off", 4659 dom::GetEnumString(GetDevice()->Kind()).get(), 4660 NS_SUCCEEDED(aResult) ? "succeeded" : "failed"); 4661 4662 if (NS_FAILED(aResult) && aResult != NS_ERROR_ABORT) { 4663 // This path handles errors from starting or stopping the 4664 // device. NS_ERROR_ABORT are for cases where *we* aborted. They 4665 // need graceful handling. 4666 if (aOn) { 4667 // Starting the device failed. Stopping the track here will 4668 // make the MediaStreamTrack end after a pass through the 4669 // MediaTrackGraph. 4670 Stop(); 4671 } else { 4672 // Stopping the device failed. This is odd, but not fatal. 4673 MOZ_ASSERT_UNREACHABLE("The device should be stoppable"); 4674 } 4675 } 4676 return DeviceOperationPromise::CreateAndResolve(aResult, __func__); 4677 }, 4678 []() { 4679 MOZ_ASSERT_UNREACHABLE("Unexpected and unhandled reject"); 4680 return DeviceOperationPromise::CreateAndReject(false, __func__); 4681 }); 4682 } 4683 4684 void DeviceListener::SetDeviceEnabled(bool aEnable) { 4685 MOZ_ASSERT(NS_IsMainThread(), "Only call on main thread"); 4686 MOZ_ASSERT(Activated(), "No device to set enabled state for"); 4687 4688 DeviceState& state = *mDeviceState; 4689 4690 LOG("DeviceListener %p %s %s device", this, 4691 aEnable ? "enabling" : "disabling", 4692 dom::GetEnumString(GetDevice()->Kind()).get()); 4693 4694 state.mTrackEnabled = aEnable; 4695 4696 if (state.mStopped) { 4697 // Device terminally stopped. Updating device state is pointless. 4698 return; 4699 } 4700 4701 if (state.mOperationInProgress) { 4702 // If a timer is in progress, it needs to be canceled now so the next 4703 // DisableTrack() gets a fresh start. Canceling will trigger another 4704 // operation. 4705 state.mDisableTimer->Cancel(); 4706 return; 4707 } 4708 4709 if (state.mDeviceEnabled == aEnable) { 4710 // Device is already in the desired state. 4711 return; 4712 } 4713 4714 // All paths from here on must end in setting 4715 // `state.mOperationInProgress` to false. 4716 state.mOperationInProgress = true; 4717 4718 RefPtr<MediaTimerPromise> timerPromise; 4719 if (aEnable) { 4720 timerPromise = MediaTimerPromise::CreateAndResolve(true, __func__); 4721 state.mTrackEnabledTime = TimeStamp::Now(); 4722 } else { 4723 const TimeDuration maxDelay = 4724 TimeDuration::FromMilliseconds(Preferences::GetUint( 4725 GetDevice()->Kind() == MediaDeviceKind::Audioinput 4726 ? "media.getusermedia.microphone.off_while_disabled.delay_ms" 4727 : "media.getusermedia.camera.off_while_disabled.delay_ms", 4728 3000)); 4729 const TimeDuration durationEnabled = 4730 TimeStamp::Now() - state.mTrackEnabledTime; 4731 const TimeDuration delay = TimeDuration::Max( 4732 TimeDuration::FromMilliseconds(0), maxDelay - durationEnabled); 4733 timerPromise = state.mDisableTimer->WaitFor(delay, __func__); 4734 } 4735 4736 RefPtr<DeviceListener> self = this; 4737 timerPromise 4738 ->Then( 4739 GetMainThreadSerialEventTarget(), __func__, 4740 [self, this, &state, aEnable]() mutable { 4741 MOZ_ASSERT(state.mDeviceEnabled != aEnable, 4742 "Device operation hasn't started"); 4743 MOZ_ASSERT(state.mOperationInProgress, 4744 "It's our responsibility to reset the inProgress state"); 4745 4746 LOG("DeviceListener %p %s %s device - starting device operation", 4747 this, aEnable ? "enabling" : "disabling", 4748 dom::GetEnumString(GetDevice()->Kind()).get()); 4749 4750 if (state.mStopped) { 4751 // Source was stopped between timer resolving and this runnable. 4752 return DeviceOperationPromise::CreateAndResolve(NS_ERROR_ABORT, 4753 __func__); 4754 } 4755 4756 state.mDeviceEnabled = aEnable; 4757 4758 if (mWindowListener) { 4759 mWindowListener->ChromeAffectingStateChanged(); 4760 } 4761 if (!state.mOffWhileDisabled || state.mDeviceMuted) { 4762 // If the feature to turn a device off while disabled is itself 4763 // disabled, or the device is currently user agent muted, then 4764 // we shortcut the device operation and tell the 4765 // ux-updating code that everything went fine. 4766 return DeviceOperationPromise::CreateAndResolve(NS_OK, __func__); 4767 } 4768 return UpdateDevice(aEnable); 4769 }, 4770 []() { 4771 // Timer was canceled by us. We signal this with NS_ERROR_ABORT. 4772 return DeviceOperationPromise::CreateAndResolve(NS_ERROR_ABORT, 4773 __func__); 4774 }) 4775 ->Then( 4776 GetMainThreadSerialEventTarget(), __func__, 4777 [self, this, &state, aEnable](nsresult aResult) mutable { 4778 MOZ_ASSERT_IF(aResult != NS_ERROR_ABORT, 4779 state.mDeviceEnabled == aEnable); 4780 MOZ_ASSERT(state.mOperationInProgress); 4781 state.mOperationInProgress = false; 4782 4783 if (state.mStopped) { 4784 // Device was stopped on main thread during the operation. 4785 // Nothing to do. 4786 return; 4787 } 4788 4789 if (NS_FAILED(aResult) && aResult != NS_ERROR_ABORT && !aEnable) { 4790 // To keep our internal state sane in this case, we disallow 4791 // future stops due to disable. 4792 state.mOffWhileDisabled = false; 4793 return; 4794 } 4795 4796 // This path is for a device operation aResult that was success or 4797 // NS_ERROR_ABORT (*we* canceled the operation). 4798 // At this point we have to follow up on the intended state, i.e., 4799 // update the device state if the track state changed in the 4800 // meantime. 4801 4802 if (state.mTrackEnabled != state.mDeviceEnabled) { 4803 // Track state changed during this operation. We'll start over. 4804 SetDeviceEnabled(state.mTrackEnabled); 4805 } 4806 }, 4807 []() { MOZ_ASSERT_UNREACHABLE("Unexpected and unhandled reject"); }); 4808 } 4809 4810 void DeviceListener::SetDeviceMuted(bool aMute) { 4811 MOZ_ASSERT(NS_IsMainThread(), "Only call on main thread"); 4812 MOZ_ASSERT(Activated(), "No device to set muted state for"); 4813 4814 DeviceState& state = *mDeviceState; 4815 4816 LOG("DeviceListener %p %s %s device", this, aMute ? "muting" : "unmuting", 4817 dom::GetEnumString(GetDevice()->Kind()).get()); 4818 4819 if (state.mStopped) { 4820 // Device terminally stopped. Updating device state is pointless. 4821 return; 4822 } 4823 4824 if (state.mDeviceMuted == aMute) { 4825 // Device is already in the desired state. 4826 return; 4827 } 4828 4829 LOG("DeviceListener %p %s %s device - starting device operation", this, 4830 aMute ? "muting" : "unmuting", 4831 dom::GetEnumString(GetDevice()->Kind()).get()); 4832 4833 state.mDeviceMuted = aMute; 4834 4835 if (mWindowListener) { 4836 mWindowListener->ChromeAffectingStateChanged(); 4837 } 4838 // Update trackSource to fire mute/unmute events on all its tracks 4839 if (aMute) { 4840 state.mTrackSource->Mute(); 4841 } else { 4842 state.mTrackSource->Unmute(); 4843 } 4844 if (!state.mOffWhileDisabled || !state.mDeviceEnabled) { 4845 // If the pref to turn the underlying device is itself off, or the device 4846 // is already off, it's unecessary to do anything else. 4847 return; 4848 } 4849 UpdateDevice(!aMute); 4850 } 4851 4852 void DeviceListener::MuteOrUnmuteCamera(bool aMute) { 4853 MOZ_ASSERT(NS_IsMainThread()); 4854 4855 if (mStopped) { 4856 return; 4857 } 4858 4859 MOZ_RELEASE_ASSERT(mWindowListener); 4860 LOG("DeviceListener %p MuteOrUnmuteCamera: %s", this, 4861 aMute ? "mute" : "unmute"); 4862 4863 if (GetDevice() && 4864 (GetDevice()->GetMediaSource() == MediaSourceEnum::Camera)) { 4865 SetDeviceMuted(aMute); 4866 } 4867 } 4868 4869 void DeviceListener::MuteOrUnmuteMicrophone(bool aMute) { 4870 MOZ_ASSERT(NS_IsMainThread()); 4871 4872 if (mStopped) { 4873 return; 4874 } 4875 4876 MOZ_RELEASE_ASSERT(mWindowListener); 4877 LOG("DeviceListener %p MuteOrUnmuteMicrophone: %s", this, 4878 aMute ? "mute" : "unmute"); 4879 4880 if (GetDevice() && 4881 (GetDevice()->GetMediaSource() == MediaSourceEnum::Microphone)) { 4882 SetDeviceMuted(aMute); 4883 } 4884 } 4885 4886 bool DeviceListener::CapturingVideo() const { 4887 MOZ_ASSERT(NS_IsMainThread()); 4888 return Activated() && mDeviceState && !mDeviceState->mStopped && 4889 MediaEngineSource::IsVideo(GetDevice()->GetMediaSource()) && 4890 (!GetDevice()->IsFake() || 4891 Preferences::GetBool("media.navigator.permission.fake")); 4892 } 4893 4894 bool DeviceListener::CapturingAudio() const { 4895 MOZ_ASSERT(NS_IsMainThread()); 4896 return Activated() && mDeviceState && !mDeviceState->mStopped && 4897 MediaEngineSource::IsAudio(GetDevice()->GetMediaSource()) && 4898 (!GetDevice()->IsFake() || 4899 Preferences::GetBool("media.navigator.permission.fake")); 4900 } 4901 4902 CaptureState DeviceListener::CapturingSource(MediaSourceEnum aSource) const { 4903 MOZ_ASSERT(NS_IsMainThread()); 4904 if (GetDevice()->GetMediaSource() != aSource) { 4905 // This DeviceListener doesn't capture a matching source 4906 return CaptureState::Off; 4907 } 4908 4909 if (mDeviceState->mStopped) { 4910 // The source is a match but has been permanently stopped 4911 return CaptureState::Off; 4912 } 4913 4914 if ((aSource == MediaSourceEnum::Camera || 4915 aSource == MediaSourceEnum::Microphone) && 4916 GetDevice()->IsFake() && 4917 !Preferences::GetBool("media.navigator.permission.fake")) { 4918 // Fake Camera and Microphone only count if there is no fake permission 4919 return CaptureState::Off; 4920 } 4921 4922 // Source is a match and is active and unmuted 4923 4924 if (mDeviceState->mDeviceEnabled && !mDeviceState->mDeviceMuted) { 4925 return CaptureState::Enabled; 4926 } 4927 4928 return CaptureState::Disabled; 4929 } 4930 4931 RefPtr<DeviceListener::DeviceListenerPromise> DeviceListener::ApplyConstraints( 4932 const MediaTrackConstraints& aConstraints, CallerType aCallerType) { 4933 MOZ_ASSERT(NS_IsMainThread()); 4934 4935 if (mStopped || mDeviceState->mStopped) { 4936 LOG("DeviceListener %p %s device applyConstraints, but device is stopped", 4937 this, dom::GetEnumString(GetDevice()->Kind()).get()); 4938 return DeviceListenerPromise::CreateAndResolve(false, __func__); 4939 } 4940 4941 MediaManager* mgr = MediaManager::GetIfExists(); 4942 if (!mgr) { 4943 return DeviceListenerPromise::CreateAndResolve(false, __func__); 4944 } 4945 4946 return InvokeAsync( 4947 mgr->mMediaThread, __func__, 4948 [device = mDeviceState->mDevice, aConstraints, prefs = mgr->mPrefs, 4949 aCallerType]() mutable -> RefPtr<DeviceListenerPromise> { 4950 MOZ_ASSERT(MediaManager::IsInMediaThread()); 4951 MediaManager* mgr = MediaManager::GetIfExists(); 4952 MOZ_RELEASE_ASSERT(mgr); // Must exist while media thread is alive 4953 const char* badConstraint = nullptr; 4954 nsresult rv = 4955 device->Reconfigure(aConstraints, mgr->mPrefs, &badConstraint); 4956 if (NS_FAILED(rv)) { 4957 if (rv == NS_ERROR_INVALID_ARG) { 4958 // Reconfigure failed due to constraints 4959 if (!badConstraint) { 4960 nsTArray<RefPtr<LocalMediaDevice>> devices; 4961 devices.AppendElement(device); 4962 badConstraint = MediaConstraintsHelper::SelectSettings( 4963 NormalizedConstraints(aConstraints), prefs, devices, 4964 aCallerType); 4965 } 4966 } else { 4967 // Unexpected. ApplyConstraints* cannot fail with any other error. 4968 badConstraint = ""; 4969 LOG("ApplyConstraints-Task: Unexpected fail %" PRIx32, 4970 static_cast<uint32_t>(rv)); 4971 } 4972 4973 return DeviceListenerPromise::CreateAndReject( 4974 MakeRefPtr<MediaMgrError>( 4975 MediaMgrError::Name::OverconstrainedError, "", 4976 NS_ConvertASCIItoUTF16(badConstraint)), 4977 __func__); 4978 } 4979 // Reconfigure was successful 4980 return DeviceListenerPromise::CreateAndResolve(false, __func__); 4981 }); 4982 } 4983 4984 PrincipalHandle DeviceListener::GetPrincipalHandle() const { 4985 return mPrincipalHandle; 4986 } 4987 4988 void GetUserMediaWindowListener::StopSharing() { 4989 MOZ_ASSERT(NS_IsMainThread(), "Only call on main thread"); 4990 4991 for (auto& l : mActiveListeners.Clone()) { 4992 MediaSourceEnum source = l->GetDevice()->GetMediaSource(); 4993 if (source == MediaSourceEnum::Screen || 4994 source == MediaSourceEnum::Window || 4995 source == MediaSourceEnum::AudioCapture || 4996 source == MediaSourceEnum::Browser) { 4997 l->Stop(); 4998 } 4999 } 5000 } 5001 5002 void GetUserMediaWindowListener::StopRawID(const nsString& removedDeviceID) { 5003 MOZ_ASSERT(NS_IsMainThread(), "Only call on main thread"); 5004 5005 for (auto& l : mActiveListeners.Clone()) { 5006 if (removedDeviceID.Equals(l->GetDevice()->RawID())) { 5007 l->Stop(); 5008 } 5009 } 5010 } 5011 5012 void GetUserMediaWindowListener::MuteOrUnmuteCameras(bool aMute) { 5013 MOZ_ASSERT(NS_IsMainThread(), "Only call on main thread"); 5014 5015 if (mCamerasAreMuted == aMute) { 5016 return; 5017 } 5018 mCamerasAreMuted = aMute; 5019 5020 for (auto& l : mActiveListeners.Clone()) { 5021 if (l->GetDevice()->Kind() == MediaDeviceKind::Videoinput) { 5022 l->MuteOrUnmuteCamera(aMute); 5023 } 5024 } 5025 } 5026 5027 void GetUserMediaWindowListener::MuteOrUnmuteMicrophones(bool aMute) { 5028 MOZ_ASSERT(NS_IsMainThread(), "Only call on main thread"); 5029 5030 if (mMicrophonesAreMuted == aMute) { 5031 return; 5032 } 5033 mMicrophonesAreMuted = aMute; 5034 5035 for (auto& l : mActiveListeners.Clone()) { 5036 if (l->GetDevice()->Kind() == MediaDeviceKind::Audioinput) { 5037 l->MuteOrUnmuteMicrophone(aMute); 5038 } 5039 } 5040 } 5041 5042 void GetUserMediaWindowListener::ChromeAffectingStateChanged() { 5043 MOZ_ASSERT(NS_IsMainThread()); 5044 5045 // We wait until stable state before notifying chrome so chrome only does 5046 // one update if more updates happen in this event loop. 5047 5048 if (mChromeNotificationTaskPosted) { 5049 return; 5050 } 5051 5052 nsCOMPtr<nsIRunnable> runnable = 5053 NewRunnableMethod("GetUserMediaWindowListener::NotifyChrome", this, 5054 &GetUserMediaWindowListener::NotifyChrome); 5055 nsContentUtils::RunInStableState(runnable.forget()); 5056 mChromeNotificationTaskPosted = true; 5057 } 5058 5059 void GetUserMediaWindowListener::NotifyChrome() { 5060 MOZ_ASSERT(mChromeNotificationTaskPosted); 5061 mChromeNotificationTaskPosted = false; 5062 5063 NS_DispatchToMainThread(NS_NewRunnableFunction( 5064 "MediaManager::NotifyChrome", [windowID = mWindowID]() { 5065 auto* window = nsGlobalWindowInner::GetInnerWindowWithId(windowID); 5066 if (!window) { 5067 MOZ_ASSERT_UNREACHABLE("Should have window"); 5068 return; 5069 } 5070 5071 nsresult rv = MediaManager::NotifyRecordingStatusChange(window); 5072 if (NS_FAILED(rv)) { 5073 MOZ_ASSERT_UNREACHABLE("Should be able to notify chrome"); 5074 return; 5075 } 5076 })); 5077 } 5078 5079 #undef LOG 5080 5081 } // namespace mozilla