Coverage Report

Created: 2018-09-25 14:53

/src/mozilla-central/dom/media/MediaManager.cpp
Line
Count
Source (jump to first uncovered line)
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 "AllocationHandle.h"
10
#include "MediaStreamGraph.h"
11
#include "MediaTimer.h"
12
#include "mozilla/dom/MediaStreamTrack.h"
13
#include "mozilla/dom/MediaDeviceInfo.h"
14
#include "MediaStreamListener.h"
15
#include "nsArray.h"
16
#include "nsContentUtils.h"
17
#include "nsGlobalWindow.h"
18
#include "nsHashPropertyBag.h"
19
#include "nsIEventTarget.h"
20
#include "nsIUUIDGenerator.h"
21
#include "nsIScriptGlobalObject.h"
22
#include "nsIPermissionManager.h"
23
#include "nsIDocShell.h"
24
#include "nsIDocument.h"
25
#include "nsISupportsPrimitives.h"
26
#include "nsIInterfaceRequestorUtils.h"
27
#include "nsIIDNService.h"
28
#include "nsNetCID.h"
29
#include "nsNetUtil.h"
30
#include "nsICryptoHash.h"
31
#include "nsICryptoHMAC.h"
32
#include "nsIKeyModule.h"
33
#include "nsAppDirectoryServiceDefs.h"
34
#include "nsIInputStream.h"
35
#include "nsILineInputStream.h"
36
#include "nsIWeakReferenceUtils.h"
37
#include "nsPIDOMWindow.h"
38
#include "mozilla/EventStateManager.h"
39
#include "mozilla/MozPromise.h"
40
#include "mozilla/NullPrincipal.h"
41
#include "mozilla/Telemetry.h"
42
#include "mozilla/Types.h"
43
#include "mozilla/PeerIdentity.h"
44
#include "mozilla/dom/BindingDeclarations.h"
45
#include "mozilla/dom/ContentChild.h"
46
#include "mozilla/dom/Element.h"
47
#include "mozilla/dom/File.h"
48
#include "mozilla/dom/MediaStreamBinding.h"
49
#include "mozilla/dom/MediaStreamTrackBinding.h"
50
#include "mozilla/dom/GetUserMediaRequestBinding.h"
51
#include "mozilla/dom/Promise.h"
52
#include "mozilla/dom/MediaDevices.h"
53
#include "mozilla/Base64.h"
54
#include "mozilla/ipc/BackgroundChild.h"
55
#include "mozilla/media/MediaChild.h"
56
#include "mozilla/media/MediaTaskUtils.h"
57
#include "MediaTrackConstraints.h"
58
#include "VideoUtils.h"
59
#include "ThreadSafeRefcountingWithMainThreadDestruction.h"
60
#include "Latency.h"
61
#include "nsProxyRelease.h"
62
#include "nsVariant.h"
63
64
// For snprintf
65
#include "mozilla/Sprintf.h"
66
67
#include "nsJSUtils.h"
68
#include "nsGlobalWindow.h"
69
#include "nsIUUIDGenerator.h"
70
#include "nspr.h"
71
#include "nss.h"
72
#include "pk11pub.h"
73
74
/* Using WebRTC backend on Desktops (Mac, Windows, Linux), otherwise default */
75
#include "MediaEngineDefault.h"
76
#if defined(MOZ_WEBRTC)
77
#include "MediaEngineWebRTC.h"
78
#include "browser_logging/WebRtcLog.h"
79
#endif
80
81
#if defined (XP_WIN)
82
#include "mozilla/WindowsVersion.h"
83
#include <objbase.h>
84
#include <winsock2.h>
85
#include <iphlpapi.h>
86
#include <tchar.h>
87
#endif
88
89
// GetCurrentTime is defined in winbase.h as zero argument macro forwarding to
90
// GetTickCount() and conflicts with MediaStream::GetCurrentTime.
91
#ifdef GetCurrentTime
92
#undef GetCurrentTime
93
#endif
94
95
// XXX Workaround for bug 986974 to maintain the existing broken semantics
96
template<>
97
struct nsIMediaDevice::COMTypeInfo<mozilla::MediaDevice, void> {
98
  static const nsIID kIID;
99
};
100
const nsIID nsIMediaDevice::COMTypeInfo<mozilla::MediaDevice, void>::kIID = NS_IMEDIADEVICE_IID;
101
102
// A specialization of nsMainThreadPtrHolder for
103
// mozilla::dom::CallbackObjectHolder.  See documentation for
104
// nsMainThreadPtrHolder in nsProxyRelease.h.  This specialization lets us avoid
105
// wrapping the CallbackObjectHolder into a separate refcounted object.
106
template<class WebIDLCallbackT, class XPCOMCallbackT>
107
class nsMainThreadPtrHolder<mozilla::dom::CallbackObjectHolder<WebIDLCallbackT,
108
                                                               XPCOMCallbackT>> final
109
{
110
  typedef mozilla::dom::CallbackObjectHolder<WebIDLCallbackT,
111
                                             XPCOMCallbackT> Holder;
112
public:
113
  nsMainThreadPtrHolder(const char* aName, Holder&& aHolder)
114
    : mHolder(std::move(aHolder))
115
#ifndef RELEASE_OR_BETA
116
    , mName(aName)
117
#endif
118
0
  {
119
0
    MOZ_ASSERT(NS_IsMainThread());
120
0
  }
Unexecuted instantiation: nsMainThreadPtrHolder<mozilla::dom::CallbackObjectHolder<mozilla::dom::NavigatorUserMediaSuccessCallback, nsIDOMGetUserMediaSuccessCallback> >::nsMainThreadPtrHolder(char const*, mozilla::dom::CallbackObjectHolder<mozilla::dom::NavigatorUserMediaSuccessCallback, nsIDOMGetUserMediaSuccessCallback>&&)
Unexecuted instantiation: nsMainThreadPtrHolder<mozilla::dom::CallbackObjectHolder<mozilla::dom::NavigatorUserMediaErrorCallback, nsIDOMGetUserMediaErrorCallback> >::nsMainThreadPtrHolder(char const*, mozilla::dom::CallbackObjectHolder<mozilla::dom::NavigatorUserMediaErrorCallback, nsIDOMGetUserMediaErrorCallback>&&)
121
122
private:
123
  // We can be released on any thread.
124
  ~nsMainThreadPtrHolder()
125
0
  {
126
0
    if (NS_IsMainThread()) {
127
0
      mHolder.Reset();
128
0
    } else if (mHolder.GetISupports()) {
129
0
      nsCOMPtr<nsIEventTarget> target = do_GetMainThread();
130
0
      MOZ_ASSERT(target);
131
0
      NS_ProxyRelease(
132
#ifdef RELEASE_OR_BETA
133
        nullptr,
134
#else
135
        mName,
136
0
#endif
137
0
        target, mHolder.Forget());
138
0
    }
139
0
  }
Unexecuted instantiation: nsMainThreadPtrHolder<mozilla::dom::CallbackObjectHolder<mozilla::dom::NavigatorUserMediaErrorCallback, nsIDOMGetUserMediaErrorCallback> >::~nsMainThreadPtrHolder()
Unexecuted instantiation: nsMainThreadPtrHolder<mozilla::dom::CallbackObjectHolder<mozilla::dom::NavigatorUserMediaSuccessCallback, nsIDOMGetUserMediaSuccessCallback> >::~nsMainThreadPtrHolder()
140
141
public:
142
  Holder* get()
143
0
  {
144
0
    // Nobody should be touching the raw pointer off-main-thread.
145
0
    if (MOZ_UNLIKELY(!NS_IsMainThread())) {
146
0
      NS_ERROR("Can't dereference nsMainThreadPtrHolder off main thread");
147
0
      MOZ_CRASH();
148
0
    }
149
0
    return &mHolder;
150
0
  }
Unexecuted instantiation: nsMainThreadPtrHolder<mozilla::dom::CallbackObjectHolder<mozilla::dom::NavigatorUserMediaErrorCallback, nsIDOMGetUserMediaErrorCallback> >::get()
Unexecuted instantiation: nsMainThreadPtrHolder<mozilla::dom::CallbackObjectHolder<mozilla::dom::NavigatorUserMediaSuccessCallback, nsIDOMGetUserMediaSuccessCallback> >::get()
151
152
  bool operator!() const
153
  {
154
    return !mHolder;
155
  }
156
157
  NS_INLINE_DECL_THREADSAFE_REFCOUNTING(nsMainThreadPtrHolder<Holder>)
158
159
private:
160
  // Our holder.
161
  Holder mHolder;
162
163
#ifndef RELEASE_OR_BETA
164
  const char* mName = nullptr;
165
#endif
166
167
  // Copy constructor and operator= not implemented. Once constructed, the
168
  // holder is immutable.
169
  Holder& operator=(const nsMainThreadPtrHolder& aOther) = delete;
170
  nsMainThreadPtrHolder(const nsMainThreadPtrHolder& aOther) = delete;
171
};
172
173
namespace {
174
0
already_AddRefed<nsIAsyncShutdownClient> GetShutdownPhase() {
175
0
  nsCOMPtr<nsIAsyncShutdownService> svc = mozilla::services::GetAsyncShutdown();
176
0
  MOZ_RELEASE_ASSERT(svc);
177
0
178
0
  nsCOMPtr<nsIAsyncShutdownClient> shutdownPhase;
179
0
  nsresult rv = svc->GetProfileBeforeChange(getter_AddRefs(shutdownPhase));
180
0
  if (!shutdownPhase) {
181
0
    // We are probably in a content process. We need to do cleanup at
182
0
    // XPCOM shutdown in leakchecking builds.
183
0
    rv = svc->GetXpcomWillShutdown(getter_AddRefs(shutdownPhase));
184
0
  }
185
0
  MOZ_RELEASE_ASSERT(shutdownPhase);
186
0
  MOZ_RELEASE_ASSERT(NS_SUCCEEDED(rv));
187
0
  return shutdownPhase.forget();
188
0
}
189
}
190
191
namespace mozilla {
192
193
#ifdef LOG
194
#undef LOG
195
#endif
196
197
LogModule*
198
GetMediaManagerLog()
199
0
{
200
0
  static LazyLogModule sLog("MediaManager");
201
0
  return sLog;
202
0
}
203
0
#define LOG(msg) MOZ_LOG(GetMediaManagerLog(), mozilla::LogLevel::Debug, msg)
204
205
using dom::BasicTrackSource;
206
using dom::ConstrainDOMStringParameters;
207
using dom::File;
208
using dom::GetUserMediaRequest;
209
using dom::MediaSourceEnum;
210
using dom::MediaStreamConstraints;
211
using dom::MediaStreamError;
212
using dom::MediaStreamTrack;
213
using dom::MediaStreamTrackSource;
214
using dom::MediaTrackConstraints;
215
using dom::MediaTrackConstraintSet;
216
using dom::OwningBooleanOrMediaTrackConstraints;
217
using dom::OwningStringOrStringSequence;
218
using dom::OwningStringOrStringSequenceOrConstrainDOMStringParameters;
219
using dom::Promise;
220
using dom::Sequence;
221
using media::NewRunnableFrom;
222
using media::NewTaskFrom;
223
using media::Pledge;
224
using media::Refcountable;
225
226
static Atomic<bool> sHasShutdown;
227
228
struct DeviceState {
229
  DeviceState(const RefPtr<MediaDevice>& aDevice, bool aOffWhileDisabled)
230
    : mOffWhileDisabled(aOffWhileDisabled)
231
    , mDevice(aDevice)
232
0
  {
233
0
    MOZ_ASSERT(mDevice);
234
0
  }
235
236
  // true if we have stopped mDevice, this is a terminal state.
237
  // MainThread only.
238
  bool mStopped = false;
239
240
  // true if mDevice is currently enabled, i.e., turned on and capturing.
241
  // MainThread only.
242
  bool mDeviceEnabled = false;
243
244
  // true if the application has currently enabled mDevice.
245
  // MainThread only.
246
  bool mTrackEnabled = false;
247
248
  // Time when the application last enabled mDevice.
249
  // MainThread only.
250
  TimeStamp mTrackEnabledTime;
251
252
  // true if an operation to Start() or Stop() mDevice has been dispatched to
253
  // the media thread and is not finished yet.
254
  // MainThread only.
255
  bool mOperationInProgress = false;
256
257
  // true if we are allowed to turn off the underlying source while all tracks
258
  // are disabled.
259
  // MainThread only.
260
  bool mOffWhileDisabled = false;
261
262
  // Timer triggered by a MediaStreamTrackSource signaling that all tracks got
263
  // disabled. When the timer fires we initiate Stop()ing mDevice.
264
  // If set we allow dynamically stopping and starting mDevice.
265
  // Any thread.
266
  const RefPtr<MediaTimer> mDisableTimer = new MediaTimer();
267
268
  // The underlying device we keep state for. Always non-null.
269
  // Threadsafe access, but see method declarations for individual constraints.
270
  const RefPtr<MediaDevice> mDevice;
271
};
272
273
/**
274
 * This mimics the capture state from nsIMediaManagerService.
275
 */
276
enum class CaptureState : uint16_t {
277
  Off = nsIMediaManagerService::STATE_NOCAPTURE,
278
  Enabled = nsIMediaManagerService::STATE_CAPTURE_ENABLED,
279
  Disabled = nsIMediaManagerService::STATE_CAPTURE_DISABLED,
280
};
281
282
static CaptureState
283
CombineCaptureState(CaptureState aFirst, CaptureState aSecond)
284
0
{
285
0
  if (aFirst == CaptureState::Enabled || aSecond == CaptureState::Enabled) {
286
0
    return CaptureState::Enabled;
287
0
  }
288
0
  if (aFirst == CaptureState::Disabled || aSecond == CaptureState::Disabled) {
289
0
    return CaptureState::Disabled;
290
0
  }
291
0
  MOZ_ASSERT(aFirst == CaptureState::Off);
292
0
  MOZ_ASSERT(aSecond == CaptureState::Off);
293
0
  return CaptureState::Off;
294
0
}
295
296
static uint16_t
297
FromCaptureState(CaptureState aState)
298
0
{
299
0
  MOZ_ASSERT(aState == CaptureState::Off ||
300
0
             aState == CaptureState::Enabled ||
301
0
             aState == CaptureState::Disabled);
302
0
  return static_cast<uint16_t>(aState);
303
0
}
304
305
static void
306
CallOnError(MediaManager::GetUserMediaErrorCallback* aCallback,
307
            MediaStreamError& aError)
308
0
{
309
0
  MOZ_ASSERT(aCallback);
310
0
  if (aCallback->HasWebIDLCallback()) {
311
0
    aCallback->GetWebIDLCallback()->Call(aError);
312
0
  } else {
313
0
    aCallback->GetXPCOMCallback()->OnError(&aError);
314
0
  }
315
0
}
316
317
static void
318
CallOnSuccess(MediaManager::GetUserMediaSuccessCallback* aCallback,
319
              DOMMediaStream& aStream)
320
0
{
321
0
  MOZ_ASSERT(aCallback);
322
0
  if (aCallback->HasWebIDLCallback()) {
323
0
    aCallback->GetWebIDLCallback()->Call(aStream);
324
0
  } else {
325
0
    aCallback->GetXPCOMCallback()->OnSuccess(&aStream);
326
0
  }
327
0
}
328
329
/**
330
 * SourceListener has threadsafe refcounting for use across the main, media and
331
 * MSG threads. But it has a non-threadsafe SupportsWeakPtr for WeakPtr usage
332
 * only from main thread, to ensure that garbage- and cycle-collected objects
333
 * don't hold a reference to it during late shutdown.
334
 *
335
 * There's also a hard reference to the SourceListener through its
336
 * SourceStreamListener and the MediaStreamGraph. MediaStreamGraph
337
 * clears this on XPCOM_WILL_SHUTDOWN, before MediaManager enters shutdown.
338
 */
339
class SourceListener : public SupportsWeakPtr<SourceListener> {
340
public:
341
  typedef MozPromise<bool /* aIgnored */, Maybe<nsString>, true> ApplyConstraintsPromise;
342
  typedef MozPromise<bool /* aIgnored */, RefPtr<MediaMgrError>, true> InitPromise;
343
344
  MOZ_DECLARE_WEAKREFERENCE_TYPENAME(SourceListener)
345
  NS_INLINE_DECL_THREADSAFE_REFCOUNTING_WITH_MAIN_THREAD_DESTRUCTION_AND_RECORDING(SourceListener,
346
                                                                                   recordreplay::Behavior::Preserve)
347
348
  SourceListener();
349
350
  /**
351
   * Registers this source listener as belonging to the given window listener.
352
   */
353
  void Register(GetUserMediaWindowListener* aListener);
354
355
  /**
356
   * Marks this listener as active and adds itself as a listener to aStream.
357
   */
358
  void Activate(SourceMediaStream* aStream,
359
                MediaDevice* aAudioDevice,
360
                MediaDevice* aVideoDevice);
361
362
  /**
363
   * Posts a task to initialize and start all associated devices.
364
   */
365
  RefPtr<InitPromise> InitializeAsync();
366
367
  /**
368
   * Stops all live tracks, finishes the associated MediaStream and cleans up.
369
   */
370
  void Stop();
371
372
  /**
373
   * Removes this SourceListener from its associated MediaStream and marks it
374
   * removed. Also removes the weak reference to the associated window listener.
375
   */
376
  void Remove();
377
378
  /**
379
   * Posts a task to stop the device associated with aTrackID and notifies the
380
   * associated window listener that a track was stopped.
381
   * Should this track be the last live one to be stopped, we'll also clean up.
382
   */
383
  void StopTrack(TrackID aTrackID);
384
385
  /**
386
   * Gets the main thread MediaTrackSettings from the MediaEngineSource
387
   * associated with aTrackID.
388
   */
389
  void GetSettingsFor(TrackID aTrackID, dom::MediaTrackSettings& aOutSettings) const;
390
391
  /**
392
   * Posts a task to set the enabled state of the device associated with
393
   * aTrackID to aEnabled and notifies the associated window listener that a
394
   * track's state has changed.
395
   *
396
   * Turning the hardware off while the device is disabled is supported for:
397
   * - Camera (enabled by default, controlled by pref
398
   *   "media.getusermedia.camera.off_while_disabled.enabled")
399
   * - Microphone (disabled by default, controlled by pref
400
   *   "media.getusermedia.microphone.off_while_disabled.enabled")
401
   * Screen-, app-, or windowsharing is not supported at this time.
402
   *
403
   * The behavior is also different between disabling and enabling a device.
404
   * While enabling is immediate, disabling only happens after a delay.
405
   * This is now defaulting to 3 seconds but can be overriden by prefs:
406
   * - "media.getusermedia.camera.off_while_disabled.delay_ms" and
407
   * - "media.getusermedia.microphone.off_while_disabled.delay_ms".
408
   *
409
   * The delay is in place to prevent misuse by malicious sites. If a track is
410
   * re-enabled before the delay has passed, the device will not be touched
411
   * until another disable followed by the full delay happens.
412
   */
413
  void SetEnabledFor(TrackID aTrackID, bool aEnabled);
414
415
  /**
416
   * Stops all screen/app/window/audioCapture sharing, but not camera or
417
   * microphone.
418
   */
419
  void StopSharing();
420
421
  MediaStream* Stream() const
422
0
  {
423
0
    return mStream;
424
0
  }
425
426
  SourceMediaStream* GetSourceStream();
427
428
  MediaDevice* GetAudioDevice() const
429
0
  {
430
0
    return mAudioDeviceState ? mAudioDeviceState->mDevice.get() : nullptr;
431
0
  }
432
433
  MediaDevice* GetVideoDevice() const
434
0
  {
435
0
    return mVideoDeviceState ? mVideoDeviceState->mDevice.get() : nullptr;
436
0
  }
437
438
  /**
439
   * Called on MediaStreamGraph thread when MSG asks us for more data from
440
   * input devices.
441
   */
442
  void NotifyPull(MediaStreamGraph* aGraph,
443
                  StreamTime aDesiredTime);
444
445
  /**
446
   * Called on main thread after MediaStreamGraph notifies us that our
447
   * MediaStream was marked finish in the graph.
448
   */
449
  void NotifyFinished();
450
451
  /**
452
   * Called on main thread after MediaStreamGraph notifies us that we
453
   * were removed as listener from the MediaStream in the graph.
454
   */
455
  void NotifyRemoved();
456
457
  bool Activated() const
458
0
  {
459
0
    return mStream;
460
0
  }
461
462
  bool Stopped() const
463
0
  {
464
0
    return mStopped;
465
0
  }
466
467
  bool CapturingVideo() const;
468
469
  bool CapturingAudio() const;
470
471
  CaptureState CapturingSource(MediaSourceEnum aSource) const;
472
473
  RefPtr<ApplyConstraintsPromise>
474
  ApplyConstraintsToTrack(nsPIDOMWindowInner* aWindow,
475
                          TrackID aTrackID,
476
                          const dom::MediaTrackConstraints& aConstraints,
477
                          dom::CallerType aCallerType);
478
479
  PrincipalHandle GetPrincipalHandle() const;
480
481
private:
482
  /**
483
   * Wrapper class for the MediaStreamListener part of SourceListener.
484
   *
485
   * This is required since MediaStreamListener and SupportsWeakPtr
486
   * both implement refcounting.
487
   */
488
  class SourceStreamListener : public MediaStreamListener {
489
  public:
490
    explicit SourceStreamListener(SourceListener* aSourceListener)
491
      : mSourceListener(aSourceListener)
492
0
    {
493
0
    }
494
495
    void NotifyPull(MediaStreamGraph* aGraph,
496
                    StreamTime aDesiredTime) override
497
0
    {
498
0
      mSourceListener->NotifyPull(aGraph, aDesiredTime);
499
0
    }
500
501
    void NotifyEvent(MediaStreamGraph* aGraph,
502
                     MediaStreamGraphEvent aEvent) override
503
0
    {
504
0
      nsCOMPtr<nsIEventTarget> target;
505
0
506
0
      switch (aEvent) {
507
0
        case MediaStreamGraphEvent::EVENT_FINISHED:
508
0
          target = GetMainThreadEventTarget();
509
0
          if (NS_WARN_IF(!target)) {
510
0
            NS_ASSERTION(false, "Mainthread not available; running on current thread");
511
0
            // Ensure this really *was* MainThread (NS_GetCurrentThread won't work)
512
0
            MOZ_RELEASE_ASSERT(mSourceListener->mMainThreadCheck == GetCurrentVirtualThread());
513
0
            mSourceListener->NotifyFinished();
514
0
            return;
515
0
          }
516
0
          target->Dispatch(NewRunnableMethod("SourceListener::NotifyFinished",
517
0
                                             mSourceListener,
518
0
                                             &SourceListener::NotifyFinished),
519
0
                           NS_DISPATCH_NORMAL);
520
0
          break;
521
0
        case MediaStreamGraphEvent::EVENT_REMOVED:
522
0
          target = GetMainThreadEventTarget();
523
0
          if (NS_WARN_IF(!target)) {
524
0
            NS_ASSERTION(false, "Mainthread not available; running on current thread");
525
0
            // Ensure this really *was* MainThread (NS_GetCurrentThread won't work)
526
0
            MOZ_RELEASE_ASSERT(mSourceListener->mMainThreadCheck == GetCurrentVirtualThread());
527
0
            mSourceListener->NotifyRemoved();
528
0
            return;
529
0
          }
530
0
          target->Dispatch(NewRunnableMethod("SourceListener::NotifyRemoved",
531
0
                                             mSourceListener,
532
0
                                             &SourceListener::NotifyRemoved),
533
0
                           NS_DISPATCH_NORMAL);
534
0
          break;
535
0
        default:
536
0
          break;
537
0
      }
538
0
    }
539
  private:
540
    RefPtr<SourceListener> mSourceListener;
541
  };
542
543
0
  virtual ~SourceListener() = default;
544
545
  /**
546
   * Returns a pointer to the device state for aTrackID.
547
   *
548
   * This is intended for internal use where we need to figure out which state
549
   * corresponds to aTrackID, not for availability checks. As such, we assert
550
   * that the device does indeed exist.
551
   *
552
   * Since this is a raw pointer and the state lifetime depends on the
553
   * SourceListener's lifetime, it's internal use only.
554
   */
555
  DeviceState& GetDeviceStateFor(TrackID aTrackID) const;
556
557
  // true after this listener has had all devices stopped. MainThread only.
558
  bool mStopped;
559
560
  // true after the stream this listener is listening to has finished in the
561
  // MediaStreamGraph. MainThread only.
562
  bool mFinished;
563
564
  // true after this listener has been removed from its MediaStream.
565
  // MainThread only.
566
  bool mRemoved;
567
568
  // never ever indirect off this; just for assertions
569
  PRThread* mMainThreadCheck;
570
571
  // Set in Register() on main thread, then read from any thread.
572
  PrincipalHandle mPrincipalHandle;
573
574
  // Weak pointer to the window listener that owns us. MainThread only.
575
  GetUserMediaWindowListener* mWindowListener;
576
577
  // Accessed from MediaStreamGraph thread, MediaManager thread, and MainThread
578
  // No locking needed as they're set on Activate() and never assigned to again.
579
  UniquePtr<DeviceState> mAudioDeviceState;
580
  UniquePtr<DeviceState> mVideoDeviceState;
581
  RefPtr<SourceMediaStream> mStream; // threadsafe refcnt
582
  RefPtr<SourceStreamListener> mStreamListener; // threadsafe refcnt
583
};
584
585
/**
586
 * This class represents a WindowID and handles all MediaStreamListeners
587
 * (here subclassed as SourceListeners) used to feed GetUserMedia source
588
 * streams. It proxies feedback from them into messages for browser chrome.
589
 * The SourceListeners are used to Start() and Stop() the underlying
590
 * MediaEngineSource when MediaStreams are assigned and deassigned in content.
591
 */
592
class GetUserMediaWindowListener
593
{
594
  friend MediaManager;
595
public:
596
  NS_INLINE_DECL_THREADSAFE_REFCOUNTING(GetUserMediaWindowListener)
597
598
  // Create in an inactive state
599
  GetUserMediaWindowListener(base::Thread *aThread,
600
    uint64_t aWindowID,
601
    const PrincipalHandle& aPrincipalHandle)
602
    : mMediaThread(aThread)
603
    , mWindowID(aWindowID)
604
    , mPrincipalHandle(aPrincipalHandle)
605
    , mChromeNotificationTaskPosted(false)
606
0
  {}
607
608
  /**
609
   * Registers an inactive gUM source listener for this WindowListener.
610
   */
611
  void Register(SourceListener* aListener)
612
0
  {
613
0
    MOZ_ASSERT(NS_IsMainThread());
614
0
    MOZ_ASSERT(aListener);
615
0
    MOZ_ASSERT(!aListener->Activated());
616
0
    MOZ_ASSERT(!mInactiveListeners.Contains(aListener), "Already registered");
617
0
    MOZ_ASSERT(!mActiveListeners.Contains(aListener), "Already activated");
618
0
619
0
    aListener->Register(this);
620
0
    mInactiveListeners.AppendElement(aListener);
621
0
  }
622
623
  /**
624
   * Activates an already registered and inactive gUM source listener for this
625
   * WindowListener.
626
   */
627
  void Activate(SourceListener* aListener,
628
                SourceMediaStream* aStream,
629
                MediaDevice* aAudioDevice,
630
                MediaDevice* aVideoDevice)
631
0
  {
632
0
    MOZ_ASSERT(NS_IsMainThread());
633
0
    MOZ_ASSERT(aListener);
634
0
    MOZ_ASSERT(!aListener->Activated());
635
0
    MOZ_ASSERT(mInactiveListeners.Contains(aListener), "Must be registered to activate");
636
0
    MOZ_ASSERT(!mActiveListeners.Contains(aListener), "Already activated");
637
0
638
0
    mInactiveListeners.RemoveElement(aListener);
639
0
    aListener->Activate(aStream, aAudioDevice, aVideoDevice);
640
0
    mActiveListeners.AppendElement(do_AddRef(aListener));
641
0
  }
642
643
  // Can be invoked from EITHER MainThread or MSG thread
644
  void Stop()
645
0
  {
646
0
    MOZ_ASSERT(NS_IsMainThread(), "Only call on main thread");
647
0
648
0
    for (auto& source : mActiveListeners) {
649
0
      source->Stop();
650
0
    }
651
0
652
0
    // Once all tracks have stopped, that will trigger the chrome notification
653
0
  }
654
655
  /**
656
   * Removes all SourceListeners from this window listener.
657
   * Removes this window listener from the list of active windows, so callers
658
   * need to make sure to hold a strong reference.
659
   */
660
  void RemoveAll()
661
0
  {
662
0
    MOZ_ASSERT(NS_IsMainThread());
663
0
664
0
    // Shallow copy since SourceListener::Remove() will modify the arrays.
665
0
    nsTArray<RefPtr<SourceListener>> listeners(mInactiveListeners.Length()
666
0
                                               + mActiveListeners.Length());
667
0
    listeners.AppendElements(mInactiveListeners);
668
0
    listeners.AppendElements(mActiveListeners);
669
0
    for (auto& l : listeners) {
670
0
      Remove(l);
671
0
    }
672
0
673
0
    MOZ_ASSERT(mInactiveListeners.Length() == 0);
674
0
    MOZ_ASSERT(mActiveListeners.Length() == 0);
675
0
676
0
    MediaManager* mgr = MediaManager::GetIfExists();
677
0
    if (!mgr) {
678
0
      MOZ_ASSERT(false, "MediaManager should stay until everything is removed");
679
0
      return;
680
0
    }
681
0
    GetUserMediaWindowListener* windowListener =
682
0
      mgr->GetWindowListener(mWindowID);
683
0
684
0
    if (!windowListener) {
685
0
      nsCOMPtr<nsIObserverService> obs = services::GetObserverService();
686
0
      auto* globalWindow = nsGlobalWindowInner::GetInnerWindowWithId(mWindowID);
687
0
      if (globalWindow) {
688
0
        RefPtr<GetUserMediaRequest> req =
689
0
          new GetUserMediaRequest(globalWindow->AsInner(),
690
0
                                  VoidString(), VoidString());
691
0
        obs->NotifyObservers(req, "recording-device-stopped", nullptr);
692
0
      }
693
0
      return;
694
0
    }
695
0
696
0
    MOZ_ASSERT(windowListener == this,
697
0
               "There should only be one window listener per window ID");
698
0
699
0
    LOG(("GUMWindowListener %p removing windowID %" PRIu64, this, mWindowID));
700
0
    mgr->RemoveWindowID(mWindowID);
701
0
  }
702
703
  bool Remove(SourceListener* aListener)
704
0
  {
705
0
    MOZ_ASSERT(NS_IsMainThread());
706
0
707
0
    if (!mInactiveListeners.RemoveElement(aListener) &&
708
0
        !mActiveListeners.RemoveElement(aListener)) {
709
0
      return false;
710
0
    }
711
0
712
0
    MOZ_ASSERT(!mInactiveListeners.Contains(aListener),
713
0
               "A SourceListener should only be once in one of "
714
0
               "mInactiveListeners and mActiveListeners");
715
0
    MOZ_ASSERT(!mActiveListeners.Contains(aListener),
716
0
               "A SourceListener should only be once in one of "
717
0
               "mInactiveListeners and mActiveListeners");
718
0
719
0
    LOG(("GUMWindowListener %p removing SourceListener %p.", this, aListener));
720
0
    aListener->Remove();
721
0
722
0
    if (MediaDevice* removedDevice = aListener->GetVideoDevice()) {
723
0
      bool revokeVideoPermission = true;
724
0
      nsString removedRawId;
725
0
      nsString removedSourceType;
726
0
      removedDevice->GetRawId(removedRawId);
727
0
      removedDevice->GetMediaSource(removedSourceType);
728
0
      for (const auto& l : mActiveListeners) {
729
0
        if (MediaDevice* device = l->GetVideoDevice()) {
730
0
          nsString rawId;
731
0
          device->GetRawId(rawId);
732
0
          if (removedRawId.Equals(rawId)) {
733
0
            revokeVideoPermission = false;
734
0
            break;
735
0
          }
736
0
        }
737
0
      }
738
0
739
0
      if (revokeVideoPermission) {
740
0
        nsCOMPtr<nsIObserverService> obs = services::GetObserverService();
741
0
        auto* globalWindow = nsGlobalWindowInner::GetInnerWindowWithId(mWindowID);
742
0
        nsPIDOMWindowInner* window = globalWindow ? globalWindow->AsInner()
743
0
                                                  : nullptr;
744
0
        RefPtr<GetUserMediaRequest> req =
745
0
          new GetUserMediaRequest(window, removedRawId, removedSourceType);
746
0
        obs->NotifyObservers(req, "recording-device-stopped", nullptr);
747
0
      }
748
0
    }
749
0
750
0
    if (MediaDevice* removedDevice = aListener->GetAudioDevice()) {
751
0
      bool revokeAudioPermission = true;
752
0
      nsString removedRawId;
753
0
      nsString removedSourceType;
754
0
      removedDevice->GetRawId(removedRawId);
755
0
      removedDevice->GetMediaSource(removedSourceType);
756
0
      for (const auto& l : mActiveListeners) {
757
0
        if (MediaDevice* device = l->GetAudioDevice()) {
758
0
          nsString rawId;
759
0
          device->GetRawId(rawId);
760
0
          if (removedRawId.Equals(rawId)) {
761
0
            revokeAudioPermission = false;
762
0
            break;
763
0
          }
764
0
        }
765
0
      }
766
0
767
0
      if (revokeAudioPermission) {
768
0
        nsCOMPtr<nsIObserverService> obs = services::GetObserverService();
769
0
        auto* globalWindow = nsGlobalWindowInner::GetInnerWindowWithId(mWindowID);
770
0
        nsPIDOMWindowInner* window = globalWindow ? globalWindow->AsInner()
771
0
                                                  : nullptr;
772
0
        RefPtr<GetUserMediaRequest> req =
773
0
          new GetUserMediaRequest(window, removedRawId, removedSourceType);
774
0
        obs->NotifyObservers(req, "recording-device-stopped", nullptr);
775
0
      }
776
0
    }
777
0
778
0
    if (mInactiveListeners.Length() == 0 &&
779
0
        mActiveListeners.Length() == 0) {
780
0
      LOG(("GUMWindowListener %p Removed the last SourceListener. "
781
0
           "Cleaning up.", this));
782
0
      RemoveAll();
783
0
    }
784
0
785
0
    return true;
786
0
  }
787
788
  void StopSharing();
789
790
  void StopRawID(const nsString& removedDeviceID);
791
792
  /**
793
   * Called by one of our SourceListeners when one of its tracks has changed so
794
   * that chrome state is affected.
795
   * Schedules an event for the next stable state to update chrome.
796
   */
797
  void ChromeAffectingStateChanged();
798
799
  /**
800
   * Called in stable state to send a notification to update chrome.
801
   */
802
  void NotifyChrome();
803
804
  bool CapturingVideo() const
805
0
  {
806
0
    MOZ_ASSERT(NS_IsMainThread());
807
0
    for (auto& l : mActiveListeners) {
808
0
      if (l->CapturingVideo()) {
809
0
        return true;
810
0
      }
811
0
    }
812
0
    return false;
813
0
  }
814
815
  bool CapturingAudio() const
816
0
  {
817
0
    MOZ_ASSERT(NS_IsMainThread());
818
0
    for (auto& l : mActiveListeners) {
819
0
      if (l->CapturingAudio()) {
820
0
        return true;
821
0
      }
822
0
    }
823
0
    return false;
824
0
  }
825
826
  CaptureState CapturingSource(MediaSourceEnum aSource) const
827
0
  {
828
0
    MOZ_ASSERT(NS_IsMainThread());
829
0
    CaptureState result = CaptureState::Off;
830
0
    for (auto& l : mActiveListeners) {
831
0
      result = CombineCaptureState(result, l->CapturingSource(aSource));
832
0
    }
833
0
    return result;
834
0
  }
835
836
  uint64_t WindowID() const
837
0
  {
838
0
    return mWindowID;
839
0
  }
840
841
0
  PrincipalHandle GetPrincipalHandle() const { return mPrincipalHandle; }
842
843
private:
844
  ~GetUserMediaWindowListener()
845
0
  {
846
0
    MOZ_ASSERT(mInactiveListeners.Length() == 0,
847
0
               "Inactive listeners should already be removed");
848
0
    MOZ_ASSERT(mActiveListeners.Length() == 0,
849
0
               "Active listeners should already be removed");
850
0
    Unused << mMediaThread;
851
0
    // It's OK to release mStream on any thread; they have thread-safe
852
0
    // refcounts.
853
0
  }
854
855
  // Set at construction
856
  base::Thread* mMediaThread;
857
858
  uint64_t mWindowID;
859
  const PrincipalHandle mPrincipalHandle;
860
861
  // true if we have scheduled a task to notify chrome in the next stable state.
862
  // The task will reset this to false. MainThread only.
863
  bool mChromeNotificationTaskPosted;
864
865
  nsTArray<RefPtr<SourceListener>> mInactiveListeners;
866
  nsTArray<RefPtr<SourceListener>> mActiveListeners;
867
};
868
869
/**
870
 * Send an error back to content. Do this only on the main thread.
871
 */
872
class ErrorCallbackRunnable : public Runnable
873
{
874
public:
875
  ErrorCallbackRunnable(const nsMainThreadPtrHandle<MediaManager::GetUserMediaErrorCallback>& aOnFailure,
876
                        MediaMgrError& aError,
877
                        uint64_t aWindowID)
878
    : Runnable("ErrorCallbackRunnable")
879
    , mOnFailure(aOnFailure)
880
    , mError(&aError)
881
    , mWindowID(aWindowID)
882
    , mManager(MediaManager::GetInstance())
883
0
  {
884
0
  }
885
886
  NS_IMETHOD
887
  Run() override
888
0
  {
889
0
    MOZ_ASSERT(NS_IsMainThread());
890
0
891
0
    // Only run if the window is still active.
892
0
    if (!(mManager->IsWindowStillActive(mWindowID))) {
893
0
      return NS_OK;
894
0
    }
895
0
    // This is safe since we're on main-thread, and the windowlist can only
896
0
    // be invalidated from the main-thread (see OnNavigation)
897
0
    if (auto* window = nsGlobalWindowInner::GetInnerWindowWithId(mWindowID)) {
898
0
      RefPtr<MediaStreamError> error =
899
0
        new MediaStreamError(window->AsInner(), *mError);
900
0
      CallOnError(mOnFailure, *error);
901
0
    }
902
0
    return NS_OK;
903
0
  }
904
private:
905
0
  ~ErrorCallbackRunnable() override = default;
906
907
  nsMainThreadPtrHandle<MediaManager::GetUserMediaErrorCallback> mOnFailure;
908
  RefPtr<MediaMgrError> mError;
909
  uint64_t mWindowID;
910
  RefPtr<MediaManager> mManager; // get ref to this when creating the runnable
911
};
912
913
/**
914
 * nsIMediaDevice implementation.
915
 */
916
NS_IMPL_ISUPPORTS(MediaDevice, nsIMediaDevice)
917
918
MediaDevice::MediaDevice(MediaEngineSource* aSource,
919
                         const nsString& aName,
920
                         const nsString& aID,
921
                         const nsString& aRawID)
922
  : mSource(aSource)
923
  , mKind((mSource && MediaEngineSource::IsVideo(mSource->GetMediaSource())) ?
924
          dom::MediaDeviceKind::Videoinput : dom::MediaDeviceKind::Audioinput)
925
  , mScary(mSource->GetScary())
926
  , mType(NS_ConvertUTF8toUTF16(dom::MediaDeviceKindValues::strings[uint32_t(mKind)].value))
927
  , mName(aName)
928
  , mID(aID)
929
  , mRawID(aRawID)
930
0
{
931
0
  MOZ_ASSERT(mSource);
932
0
}
933
934
MediaDevice::MediaDevice(const nsString& aName,
935
                         const dom::MediaDeviceKind aKind,
936
                         const nsString& aID,
937
                         const nsString& aRawID)
938
  : mSource(nullptr)
939
  , mKind(aKind)
940
  , mScary(false)
941
  , mType(NS_ConvertUTF8toUTF16(dom::MediaDeviceKindValues::strings[uint32_t(mKind)].value))
942
  , mName(aName)
943
  , mID(aID)
944
  , mRawID(aRawID)
945
0
{
946
0
  // For now this ctor is used only for Audiooutput.
947
0
  // It could be used for Audioinput and Videoinput
948
0
  // when we do not instantiate a MediaEngineSource
949
0
  // during EnumerateDevices.
950
0
  MOZ_ASSERT(mKind == dom::MediaDeviceKind::Audiooutput);
951
0
}
952
953
MediaDevice::MediaDevice(const MediaDevice* aOther,
954
                         const nsString& aID,
955
                         const nsString& aRawID)
956
  : mSource(aOther->mSource)
957
  , mKind(aOther->mKind)
958
  , mScary(aOther->mScary)
959
  , mType(aOther->mType)
960
  , mName(aOther->mName)
961
  , mID(aID)
962
  , mRawID(aRawID)
963
0
{
964
0
  MOZ_ASSERT(aOther);
965
0
}
966
967
/**
968
 * Helper functions that implement the constraints algorithm from
969
 * http://dev.w3.org/2011/webrtc/editor/getusermedia.html#methods-5
970
 */
971
972
/* static */ bool
973
MediaDevice::StringsContain(const OwningStringOrStringSequence& aStrings,
974
                            nsString aN)
975
0
{
976
0
  return aStrings.IsString() ? aStrings.GetAsString() == aN
977
0
                             : aStrings.GetAsStringSequence().Contains(aN);
978
0
}
979
980
/* static */ uint32_t
981
MediaDevice::FitnessDistance(nsString aN,
982
                             const ConstrainDOMStringParameters& aParams)
983
0
{
984
0
  if (aParams.mExact.WasPassed() && !StringsContain(aParams.mExact.Value(), aN)) {
985
0
    return UINT32_MAX;
986
0
  }
987
0
  if (aParams.mIdeal.WasPassed() && !StringsContain(aParams.mIdeal.Value(), aN)) {
988
0
    return 1;
989
0
  }
990
0
  return 0;
991
0
}
992
993
// Binding code doesn't templatize well...
994
995
/* static */ uint32_t
996
MediaDevice::FitnessDistance(nsString aN,
997
    const OwningStringOrStringSequenceOrConstrainDOMStringParameters& aConstraint)
998
0
{
999
0
  if (aConstraint.IsString()) {
1000
0
    ConstrainDOMStringParameters params;
1001
0
    params.mIdeal.Construct();
1002
0
    params.mIdeal.Value().SetAsString() = aConstraint.GetAsString();
1003
0
    return FitnessDistance(aN, params);
1004
0
  } else if (aConstraint.IsStringSequence()) {
1005
0
    ConstrainDOMStringParameters params;
1006
0
    params.mIdeal.Construct();
1007
0
    params.mIdeal.Value().SetAsStringSequence() = aConstraint.GetAsStringSequence();
1008
0
    return FitnessDistance(aN, params);
1009
0
  } else {
1010
0
    return FitnessDistance(aN, aConstraint.GetAsConstrainDOMStringParameters());
1011
0
  }
1012
0
}
1013
1014
uint32_t
1015
MediaDevice::GetBestFitnessDistance(
1016
    const nsTArray<const NormalizedConstraintSet*>& aConstraintSets,
1017
    bool aIsChrome)
1018
0
{
1019
0
  MOZ_ASSERT(MediaManager::IsInMediaThread());
1020
0
  MOZ_ASSERT(mSource);
1021
0
1022
0
  nsString mediaSource;
1023
0
  GetMediaSource(mediaSource);
1024
0
1025
0
  // This code is reused for audio, where the mediaSource constraint does
1026
0
  // not currently have a function, but because it defaults to "camera" in
1027
0
  // webidl, we ignore it for audio here.
1028
0
  if (!mediaSource.EqualsASCII("microphone")) {
1029
0
    for (const auto& constraint : aConstraintSets) {
1030
0
      if (constraint->mMediaSource.mIdeal.find(mediaSource) ==
1031
0
          constraint->mMediaSource.mIdeal.end()) {
1032
0
        return UINT32_MAX;
1033
0
      }
1034
0
    }
1035
0
  }
1036
0
  // Forward request to underlying object to interrogate per-mode capabilities.
1037
0
  // Pass in device's origin-specific id for deviceId constraint comparison.
1038
0
  const nsString& id = aIsChrome ? mRawID : mID;
1039
0
  return mSource->GetBestFitnessDistance(aConstraintSets, id);
1040
0
}
1041
1042
NS_IMETHODIMP
1043
MediaDevice::GetName(nsAString& aName)
1044
0
{
1045
0
  MOZ_ASSERT(NS_IsMainThread());
1046
0
  aName.Assign(mName);
1047
0
  return NS_OK;
1048
0
}
1049
1050
NS_IMETHODIMP
1051
MediaDevice::GetType(nsAString& aType)
1052
0
{
1053
0
  MOZ_ASSERT(NS_IsMainThread());
1054
0
  aType.Assign(mType);
1055
0
  return NS_OK;
1056
0
}
1057
1058
NS_IMETHODIMP
1059
MediaDevice::GetId(nsAString& aID)
1060
0
{
1061
0
  MOZ_ASSERT(NS_IsMainThread());
1062
0
  aID.Assign(mID);
1063
0
  return NS_OK;
1064
0
}
1065
1066
NS_IMETHODIMP
1067
MediaDevice::GetRawId(nsAString& aID)
1068
0
{
1069
0
  MOZ_ASSERT(NS_IsMainThread());
1070
0
  aID.Assign(mRawID);
1071
0
  return NS_OK;
1072
0
}
1073
1074
NS_IMETHODIMP
1075
MediaDevice::GetScary(bool* aScary)
1076
0
{
1077
0
  *aScary = mScary;
1078
0
  return NS_OK;
1079
0
}
1080
1081
void
1082
MediaDevice::GetSettings(dom::MediaTrackSettings& aOutSettings) const
1083
0
{
1084
0
  MOZ_ASSERT(NS_IsMainThread());
1085
0
  MOZ_ASSERT(mSource);
1086
0
  mSource->GetSettings(aOutSettings);
1087
0
}
1088
1089
  // Threadsafe since mSource is const.
1090
NS_IMETHODIMP
1091
MediaDevice::GetMediaSource(nsAString& aMediaSource)
1092
0
{
1093
0
  aMediaSource.Assign(NS_ConvertUTF8toUTF16(
1094
0
    dom::MediaSourceEnumValues::strings[uint32_t(GetMediaSource())].value));
1095
0
  return NS_OK;
1096
0
}
1097
1098
nsresult
1099
MediaDevice::Allocate(const dom::MediaTrackConstraints &aConstraints,
1100
                      const MediaEnginePrefs &aPrefs,
1101
                      const ipc::PrincipalInfo& aPrincipalInfo,
1102
                      const char** aOutBadConstraint)
1103
0
{
1104
0
  MOZ_ASSERT(MediaManager::IsInMediaThread());
1105
0
  MOZ_ASSERT(mSource);
1106
0
  return mSource->Allocate(aConstraints,
1107
0
                           aPrefs,
1108
0
                           mID,
1109
0
                           aPrincipalInfo,
1110
0
                           getter_AddRefs(mAllocationHandle),
1111
0
                           aOutBadConstraint);
1112
0
}
1113
1114
nsresult
1115
MediaDevice::SetTrack(const RefPtr<SourceMediaStream>& aStream,
1116
                      TrackID aTrackID,
1117
                      const PrincipalHandle& aPrincipalHandle)
1118
0
{
1119
0
  MOZ_ASSERT(MediaManager::IsInMediaThread());
1120
0
  MOZ_ASSERT(mSource);
1121
0
  return mSource->SetTrack(mAllocationHandle, aStream, aTrackID, aPrincipalHandle);
1122
0
}
1123
1124
nsresult
1125
MediaDevice::Start()
1126
0
{
1127
0
  MOZ_ASSERT(MediaManager::IsInMediaThread());
1128
0
  MOZ_ASSERT(mSource);
1129
0
  return mSource->Start(mAllocationHandle);
1130
0
}
1131
1132
nsresult
1133
MediaDevice::Reconfigure(const dom::MediaTrackConstraints &aConstraints,
1134
                         const MediaEnginePrefs &aPrefs,
1135
                         const char** aOutBadConstraint)
1136
0
{
1137
0
  MOZ_ASSERT(MediaManager::IsInMediaThread());
1138
0
  MOZ_ASSERT(mSource);
1139
0
  return mSource->Reconfigure(mAllocationHandle,
1140
0
                              aConstraints,
1141
0
                              aPrefs,
1142
0
                              mID,
1143
0
                              aOutBadConstraint);
1144
0
}
1145
1146
nsresult
1147
MediaDevice::FocusOnSelectedSource()
1148
0
{
1149
0
  MOZ_ASSERT(MediaManager::IsInMediaThread());
1150
0
  MOZ_ASSERT(mSource);
1151
0
  return mSource->FocusOnSelectedSource(mAllocationHandle);
1152
0
}
1153
1154
nsresult
1155
MediaDevice::Stop()
1156
0
{
1157
0
  MOZ_ASSERT(MediaManager::IsInMediaThread());
1158
0
  MOZ_ASSERT(mSource);
1159
0
  return mSource->Stop(mAllocationHandle);
1160
0
}
1161
1162
nsresult
1163
MediaDevice::Deallocate()
1164
0
{
1165
0
  MOZ_ASSERT(MediaManager::IsInMediaThread());
1166
0
  MOZ_ASSERT(mSource);
1167
0
  return mSource->Deallocate(mAllocationHandle);
1168
0
}
1169
1170
void
1171
MediaDevice::Pull(const RefPtr<SourceMediaStream>& aStream,
1172
                  TrackID aTrackID,
1173
                  StreamTime aDesiredTime,
1174
                  const PrincipalHandle& aPrincipal)
1175
0
{
1176
0
  // This is on the graph thread, but mAllocationHandle is safe since we never
1177
0
  // change it after it's been set, which is guaranteed to happen before
1178
0
  // registering the listener for pulls.
1179
0
  MOZ_ASSERT(mSource);
1180
0
  mSource->Pull(mAllocationHandle, aStream, aTrackID, aDesiredTime, aPrincipal);
1181
0
}
1182
1183
dom::MediaSourceEnum
1184
MediaDevice::GetMediaSource() const
1185
0
{
1186
0
  // Threadsafe because mSource is const. GetMediaSource() might have other
1187
0
  // requirements.
1188
0
  MOZ_ASSERT(mSource);
1189
0
  return mSource->GetMediaSource();
1190
0
}
1191
1192
static bool
1193
0
IsOn(const OwningBooleanOrMediaTrackConstraints &aUnion) {
1194
0
  return !aUnion.IsBoolean() || aUnion.GetAsBoolean();
1195
0
}
1196
1197
static const MediaTrackConstraints&
1198
0
GetInvariant(const OwningBooleanOrMediaTrackConstraints &aUnion) {
1199
0
  static const MediaTrackConstraints empty;
1200
0
  return aUnion.IsMediaTrackConstraints() ?
1201
0
      aUnion.GetAsMediaTrackConstraints() : empty;
1202
0
}
1203
1204
/**
1205
 * This class is only needed since fake tracks are added dynamically.
1206
 * Instead of refactoring to add them explicitly we let the DOMMediaStream
1207
 * query us for the source as they become available.
1208
 * Since they are used only for testing the API surface, we make them very
1209
 * simple.
1210
 */
1211
class FakeTrackSourceGetter : public MediaStreamTrackSourceGetter
1212
{
1213
public:
1214
  NS_DECL_ISUPPORTS_INHERITED
1215
  NS_DECL_CYCLE_COLLECTION_CLASS_INHERITED(FakeTrackSourceGetter,
1216
                                           MediaStreamTrackSourceGetter)
1217
1218
  explicit FakeTrackSourceGetter(nsIPrincipal* aPrincipal)
1219
0
    : mPrincipal(aPrincipal) {}
1220
1221
  already_AddRefed<dom::MediaStreamTrackSource>
1222
  GetMediaStreamTrackSource(TrackID aInputTrackID) override
1223
0
  {
1224
0
    NS_ASSERTION(kAudioTrack != aInputTrackID,
1225
0
                 "Only fake tracks should appear dynamically");
1226
0
    NS_ASSERTION(kVideoTrack != aInputTrackID,
1227
0
                 "Only fake tracks should appear dynamically");
1228
0
    return do_AddRef(new BasicTrackSource(mPrincipal));
1229
0
  }
1230
1231
protected:
1232
0
  virtual ~FakeTrackSourceGetter() {}
1233
1234
  nsCOMPtr<nsIPrincipal> mPrincipal;
1235
};
1236
1237
NS_IMPL_ADDREF_INHERITED(FakeTrackSourceGetter, MediaStreamTrackSourceGetter)
1238
NS_IMPL_RELEASE_INHERITED(FakeTrackSourceGetter, MediaStreamTrackSourceGetter)
1239
0
NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(FakeTrackSourceGetter)
1240
0
NS_INTERFACE_MAP_END_INHERITING(MediaStreamTrackSourceGetter)
1241
NS_IMPL_CYCLE_COLLECTION_INHERITED(FakeTrackSourceGetter,
1242
                                   MediaStreamTrackSourceGetter,
1243
                                   mPrincipal)
1244
1245
/**
1246
 * Creates a MediaStream, attaches a listener and fires off a success callback
1247
 * to the DOM with the stream. We also pass in the error callback so it can
1248
 * be released correctly.
1249
 *
1250
 * All of this must be done on the main thread!
1251
 *
1252
 * Note that the various GetUserMedia Runnable classes currently allow for
1253
 * two streams.  If we ever need to support getting more than two streams
1254
 * at once, we could convert everything to nsTArray<RefPtr<blah> >'s,
1255
 * though that would complicate the constructors some.  Currently the
1256
 * GetUserMedia spec does not allow for more than 2 streams to be obtained in
1257
 * one call, to simplify handling of constraints.
1258
 */
1259
class GetUserMediaStreamRunnable : public Runnable
1260
{
1261
public:
1262
  GetUserMediaStreamRunnable(
1263
    const nsMainThreadPtrHandle<MediaManager::GetUserMediaSuccessCallback>& aOnSuccess,
1264
    const nsMainThreadPtrHandle<MediaManager::GetUserMediaErrorCallback>& aOnFailure,
1265
    uint64_t aWindowID,
1266
    GetUserMediaWindowListener* aWindowListener,
1267
    SourceListener* aSourceListener,
1268
    const ipc::PrincipalInfo& aPrincipalInfo,
1269
    const MediaStreamConstraints& aConstraints,
1270
    MediaDevice* aAudioDevice,
1271
    MediaDevice* aVideoDevice,
1272
    PeerIdentity* aPeerIdentity,
1273
    bool aIsChrome)
1274
    : Runnable("GetUserMediaStreamRunnable")
1275
    , mOnSuccess(aOnSuccess)
1276
    , mOnFailure(aOnFailure)
1277
    , mConstraints(aConstraints)
1278
    , mAudioDevice(aAudioDevice)
1279
    , mVideoDevice(aVideoDevice)
1280
    , mWindowID(aWindowID)
1281
    , mWindowListener(aWindowListener)
1282
    , mSourceListener(aSourceListener)
1283
    , mPrincipalInfo(aPrincipalInfo)
1284
    , mPeerIdentity(aPeerIdentity)
1285
    , mManager(MediaManager::GetInstance())
1286
0
  {
1287
0
  }
1288
1289
0
  ~GetUserMediaStreamRunnable() {}
1290
1291
  class TracksAvailableCallback : public OnTracksAvailableCallback
1292
  {
1293
  public:
1294
    TracksAvailableCallback(MediaManager* aManager,
1295
                            const nsMainThreadPtrHandle<MediaManager::GetUserMediaSuccessCallback>& aSuccess,
1296
                            const RefPtr<GetUserMediaWindowListener>& aWindowListener,
1297
                            DOMMediaStream* aStream)
1298
      : mWindowListener(aWindowListener),
1299
        mOnSuccess(aSuccess),
1300
        mManager(aManager),
1301
        mStream(aStream)
1302
0
    {}
1303
    void NotifyTracksAvailable(DOMMediaStream* aStream) override
1304
0
    {
1305
0
      // We're on the main thread, so no worries here.
1306
0
      if (!mManager->IsWindowListenerStillActive(mWindowListener)) {
1307
0
        return;
1308
0
      }
1309
0
1310
0
      // Start currentTime from the point where this stream was successfully
1311
0
      // returned.
1312
0
      aStream->SetLogicalStreamStartTime(aStream->GetPlaybackStream()->GetCurrentTime());
1313
0
1314
0
      // This is safe since we're on main-thread, and the windowlist can only
1315
0
      // be invalidated from the main-thread (see OnNavigation)
1316
0
      LOG(("Returning success for getUserMedia()"));
1317
0
      CallOnSuccess(mOnSuccess, *aStream);
1318
0
    }
1319
    RefPtr<GetUserMediaWindowListener> mWindowListener;
1320
    nsMainThreadPtrHandle<MediaManager::GetUserMediaSuccessCallback> mOnSuccess;
1321
    RefPtr<MediaManager> mManager;
1322
    // Keep the DOMMediaStream alive until the NotifyTracksAvailable callback
1323
    // has fired, otherwise we might immediately destroy the DOMMediaStream and
1324
    // shut down the underlying MediaStream prematurely.
1325
    // This creates a cycle which is broken when NotifyTracksAvailable
1326
    // is fired (which will happen unless the browser shuts down,
1327
    // since we only add this callback when we've successfully appended
1328
    // the desired tracks in the MediaStreamGraph) or when
1329
    // DOMMediaStream::NotifyMediaStreamGraphShutdown is called.
1330
    RefPtr<DOMMediaStream> mStream;
1331
  };
1332
1333
  NS_IMETHOD
1334
  Run() override
1335
0
  {
1336
0
    MOZ_ASSERT(NS_IsMainThread());
1337
0
    LOG(("GetUserMediaStreamRunnable::Run()"));
1338
0
    nsGlobalWindowInner* globalWindow = nsGlobalWindowInner::GetInnerWindowWithId(mWindowID);
1339
0
    nsPIDOMWindowInner* window = globalWindow ? globalWindow->AsInner() : nullptr;
1340
0
1341
0
    // We're on main-thread, and the windowlist can only
1342
0
    // be invalidated from the main-thread (see OnNavigation)
1343
0
    if (!mManager->IsWindowListenerStillActive(mWindowListener)) {
1344
0
      // This window is no longer live. mListener has already been removed.
1345
0
      return NS_OK;
1346
0
    }
1347
0
1348
0
    MediaStreamGraph::GraphDriverType graphDriverType =
1349
0
      mAudioDevice ? MediaStreamGraph::AUDIO_THREAD_DRIVER
1350
0
                   : MediaStreamGraph::SYSTEM_THREAD_DRIVER;
1351
0
    MediaStreamGraph* msg =
1352
0
      MediaStreamGraph::GetInstance(graphDriverType, window,
1353
0
                                    MediaStreamGraph::REQUEST_DEFAULT_SAMPLE_RATE);
1354
0
1355
0
    nsMainThreadPtrHandle<DOMMediaStream> domStream;
1356
0
    RefPtr<SourceMediaStream> stream;
1357
0
    // AudioCapture is a special case, here, in the sense that we're not really
1358
0
    // using the audio source and the SourceMediaStream, which acts as
1359
0
    // placeholders. We re-route a number of stream internaly in the MSG and mix
1360
0
    // them down instead.
1361
0
    if (mAudioDevice &&
1362
0
        mAudioDevice->GetMediaSource() == MediaSourceEnum::AudioCapture) {
1363
0
      NS_WARNING("MediaCaptureWindowState doesn't handle "
1364
0
                 "MediaSourceEnum::AudioCapture. This must be fixed with UX "
1365
0
                 "before shipping.");
1366
0
      // It should be possible to pipe the capture stream to anything. CORS is
1367
0
      // not a problem here, we got explicit user content.
1368
0
      nsCOMPtr<nsIPrincipal> principal = window->GetExtantDoc()->NodePrincipal();
1369
0
      domStream = new nsMainThreadPtrHolder<DOMMediaStream>(
1370
0
        "GetUserMediaStreamRunnable::AudioCaptureDOMStreamMainThreadHolder",
1371
0
        DOMMediaStream::CreateAudioCaptureStreamAsInput(window, principal, msg));
1372
0
1373
0
      stream = msg->CreateSourceStream(); // Placeholder
1374
0
      msg->RegisterCaptureStreamForWindow(
1375
0
            mWindowID, domStream->GetInputStream()->AsProcessedStream());
1376
0
      window->SetAudioCapture(true);
1377
0
    } else {
1378
0
      class LocalTrackSource : public MediaStreamTrackSource
1379
0
      {
1380
0
      public:
1381
0
        LocalTrackSource(nsIPrincipal* aPrincipal,
1382
0
                         const nsString& aLabel,
1383
0
                         const RefPtr<SourceListener>& aListener,
1384
0
                         const MediaSourceEnum aSource,
1385
0
                         const TrackID aTrackID,
1386
0
                         const PeerIdentity* aPeerIdentity)
1387
0
          : MediaStreamTrackSource(aPrincipal, aLabel),
1388
0
            mListener(aListener.get()),
1389
0
            mSource(aSource),
1390
0
            mTrackID(aTrackID),
1391
0
            mPeerIdentity(aPeerIdentity)
1392
0
        {}
1393
0
1394
0
        MediaSourceEnum GetMediaSource() const override
1395
0
        {
1396
0
          return mSource;
1397
0
        }
1398
0
1399
0
        const PeerIdentity* GetPeerIdentity() const override
1400
0
        {
1401
0
          return mPeerIdentity;
1402
0
        }
1403
0
1404
0
        already_AddRefed<PledgeVoid>
1405
0
        ApplyConstraints(nsPIDOMWindowInner* aWindow,
1406
0
                         const MediaTrackConstraints& aConstraints,
1407
0
                         dom::CallerType aCallerType) override
1408
0
        {
1409
0
          RefPtr<PledgeVoid> p = new PledgeVoid();
1410
0
          if (sHasShutdown || !mListener) {
1411
0
            // Track has been stopped, or we are in shutdown. In either case
1412
0
            // there's no observable outcome, so pretend we succeeded.
1413
0
            p->Resolve(false);
1414
0
            return p.forget();
1415
0
          }
1416
0
1417
0
          mListener->ApplyConstraintsToTrack(aWindow, mTrackID,
1418
0
                                             aConstraints, aCallerType)
1419
0
            ->Then(GetMainThreadSerialEventTarget(), __func__,
1420
0
                [p]()
1421
0
                {
1422
0
                  if (!MediaManager::Exists()) {
1423
0
                    return;
1424
0
                  }
1425
0
1426
0
                  p->Resolve(false);
1427
0
                },
1428
0
                [p, weakWindow = nsWeakPtr(do_GetWeakReference(aWindow)),
1429
0
                 listener = mListener, trackID = mTrackID]
1430
0
                (Maybe<nsString>&& aBadConstraint)
1431
0
                {
1432
0
                  if (!MediaManager::Exists()) {
1433
0
                    return;
1434
0
                  }
1435
0
1436
0
                  if (!weakWindow->IsAlive()) {
1437
0
                    return;
1438
0
                  }
1439
0
1440
0
                  if (aBadConstraint.isNothing()) {
1441
0
                    // Unexpected error during reconfig that left the source
1442
0
                    // stopped. We resolve the promise and end the track.
1443
0
                    if (listener) {
1444
0
                      listener->StopTrack(trackID);
1445
0
                    }
1446
0
                    p->Resolve(false);
1447
0
                    return;
1448
0
                  }
1449
0
1450
0
                  nsCOMPtr<nsPIDOMWindowInner> window = do_QueryReferent(weakWindow);
1451
0
                  auto error = MakeRefPtr<MediaStreamError>(
1452
0
                      window,
1453
0
                      MediaMgrError::Name::OverconstrainedError,
1454
0
                      NS_LITERAL_STRING(""),
1455
0
                      aBadConstraint.valueOr(nsString()));
1456
0
                  p->Reject(error);
1457
0
                });
1458
0
1459
0
          return p.forget();
1460
0
        }
1461
0
1462
0
        void
1463
0
        GetSettings(dom::MediaTrackSettings& aOutSettings) override
1464
0
        {
1465
0
          if (mListener) {
1466
0
            mListener->GetSettingsFor(mTrackID, aOutSettings);
1467
0
          }
1468
0
        }
1469
0
1470
0
        void Stop() override
1471
0
        {
1472
0
          if (mListener) {
1473
0
            mListener->StopTrack(mTrackID);
1474
0
            mListener = nullptr;
1475
0
          }
1476
0
        }
1477
0
1478
0
        void Disable() override
1479
0
        {
1480
0
          if (mListener) {
1481
0
            mListener->SetEnabledFor(mTrackID, false);
1482
0
          }
1483
0
        }
1484
0
1485
0
        void Enable() override
1486
0
        {
1487
0
          if (mListener) {
1488
0
            mListener->SetEnabledFor(mTrackID, true);
1489
0
          }
1490
0
        }
1491
0
1492
0
      protected:
1493
0
        ~LocalTrackSource() {}
1494
0
1495
0
        // This is a weak pointer to avoid having the SourceListener (which may
1496
0
        // have references to threads and threadpools) kept alive by DOM-objects
1497
0
        // that may have ref-cycles and thus are released very late during
1498
0
        // shutdown, even after xpcom-shutdown-threads. See bug 1351655 for what
1499
0
        // can happen.
1500
0
        WeakPtr<SourceListener> mListener;
1501
0
        const MediaSourceEnum mSource;
1502
0
        const TrackID mTrackID;
1503
0
        const RefPtr<const PeerIdentity> mPeerIdentity;
1504
0
      };
1505
0
1506
0
      nsCOMPtr<nsIPrincipal> principal;
1507
0
      if (mPeerIdentity) {
1508
0
        principal = NullPrincipal::CreateWithInheritedAttributes(window->GetExtantDoc()->NodePrincipal());
1509
0
      } else {
1510
0
        principal = window->GetExtantDoc()->NodePrincipal();
1511
0
      }
1512
0
1513
0
      // Normal case, connect the source stream to the track union stream to
1514
0
      // avoid us blocking. Pass a simple TrackSourceGetter for potential
1515
0
      // fake tracks. Apart from them gUM never adds tracks dynamically.
1516
0
      domStream = new nsMainThreadPtrHolder<DOMMediaStream>(
1517
0
        "GetUserMediaStreamRunnable::DOMMediaStreamMainThreadHolder",
1518
0
        DOMLocalMediaStream::CreateSourceStreamAsInput(window, msg,
1519
0
                                                       new FakeTrackSourceGetter(principal)));
1520
0
      stream = domStream->GetInputStream()->AsSourceStream();
1521
0
1522
0
      if (mAudioDevice) {
1523
0
        nsString audioDeviceName;
1524
0
        mAudioDevice->GetName(audioDeviceName);
1525
0
        const MediaSourceEnum source = mAudioDevice->GetMediaSource();
1526
0
        RefPtr<MediaStreamTrackSource> audioSource =
1527
0
          new LocalTrackSource(principal, audioDeviceName, mSourceListener,
1528
0
                               source, kAudioTrack, mPeerIdentity);
1529
0
        MOZ_ASSERT(IsOn(mConstraints.mAudio));
1530
0
        RefPtr<MediaStreamTrack> track =
1531
0
          domStream->CreateDOMTrack(kAudioTrack, MediaSegment::AUDIO, audioSource,
1532
0
                                    GetInvariant(mConstraints.mAudio));
1533
0
        domStream->AddTrackInternal(track);
1534
0
      }
1535
0
      if (mVideoDevice) {
1536
0
        nsString videoDeviceName;
1537
0
        mVideoDevice->GetName(videoDeviceName);
1538
0
        const MediaSourceEnum source = mVideoDevice->GetMediaSource();
1539
0
        RefPtr<MediaStreamTrackSource> videoSource =
1540
0
          new LocalTrackSource(principal, videoDeviceName, mSourceListener,
1541
0
                               source, kVideoTrack, mPeerIdentity);
1542
0
        MOZ_ASSERT(IsOn(mConstraints.mVideo));
1543
0
        RefPtr<MediaStreamTrack> track =
1544
0
          domStream->CreateDOMTrack(kVideoTrack, MediaSegment::VIDEO, videoSource,
1545
0
                                    GetInvariant(mConstraints.mVideo));
1546
0
        domStream->AddTrackInternal(track);
1547
0
      }
1548
0
    }
1549
0
1550
0
    if (!domStream || !stream || sHasShutdown) {
1551
0
      LOG(("Returning error for getUserMedia() - no stream"));
1552
0
1553
0
      if (auto* window = nsGlobalWindowInner::GetInnerWindowWithId(mWindowID)) {
1554
0
        RefPtr<MediaStreamError> error = new MediaStreamError(window->AsInner(),
1555
0
            MediaStreamError::Name::AbortError,
1556
0
            sHasShutdown ? NS_LITERAL_STRING("In shutdown") :
1557
0
                           NS_LITERAL_STRING("No stream."));
1558
0
        CallOnError(mOnFailure, *error);
1559
0
      }
1560
0
      return NS_OK;
1561
0
    }
1562
0
1563
0
    // Activate our source listener. We'll call Start() on the source when we
1564
0
    // get a callback that the MediaStream has started consuming. The listener
1565
0
    // is freed when the page is invalidated (on navigation or close).
1566
0
    mWindowListener->Activate(mSourceListener, stream, mAudioDevice, mVideoDevice);
1567
0
1568
0
    // Note: includes JS callbacks; must be released on MainThread
1569
0
    typedef Refcountable<UniquePtr<TracksAvailableCallback>> Callback;
1570
0
    nsMainThreadPtrHandle<Callback> callback(
1571
0
      new nsMainThreadPtrHolder<Callback>(
1572
0
        "GetUserMediaStreamRunnable::TracksAvailableCallbackMainThreadHolder",
1573
0
        MakeAndAddRef<Callback>(
1574
0
          new TracksAvailableCallback(mManager,
1575
0
                                      mOnSuccess,
1576
0
                                      mWindowListener,
1577
0
                                      domStream))));
1578
0
1579
0
    // Dispatch to the media thread to ask it to start the sources,
1580
0
    // because that can take a while.
1581
0
    // Pass ownership of domStream through the lambda to the nested chrome
1582
0
    // notification lambda to ensure it's kept alive until that lambda runs or is discarded.
1583
0
    mSourceListener->InitializeAsync()->Then(
1584
0
      GetMainThreadSerialEventTarget(), __func__,
1585
0
      [manager = mManager, domStream, callback,
1586
0
       windowListener = mWindowListener]()
1587
0
      {
1588
0
        LOG(("GetUserMediaStreamRunnable::Run: starting success callback "
1589
0
             "following InitializeAsync()"));
1590
0
        // Initiating and starting devices succeeded.
1591
0
        // onTracksAvailableCallback must be added to domStream on main thread.
1592
0
        domStream->OnTracksAvailable(callback->release());
1593
0
        windowListener->ChromeAffectingStateChanged();
1594
0
        manager->SendPendingGUMRequest();
1595
0
      },[manager = mManager, windowID = mWindowID,
1596
0
         onFailure = std::move(mOnFailure)](const RefPtr<MediaMgrError>& error)
1597
0
      {
1598
0
        LOG(("GetUserMediaStreamRunnable::Run: starting failure callback "
1599
0
             "following InitializeAsync()"));
1600
0
        // Initiating and starting devices failed.
1601
0
1602
0
        // Only run if the window is still active for our window listener.
1603
0
        if (!(manager->IsWindowStillActive(windowID))) {
1604
0
          return;
1605
0
        }
1606
0
        // This is safe since we're on main-thread, and the windowlist can only
1607
0
        // be invalidated from the main-thread (see OnNavigation)
1608
0
        if (auto* window = nsGlobalWindowInner::GetInnerWindowWithId(windowID)) {
1609
0
          auto streamError = MakeRefPtr<MediaStreamError>(window->AsInner(), *error);
1610
0
          CallOnError(onFailure, *streamError);
1611
0
        }
1612
0
      });
1613
0
1614
0
    if (!IsPincipalInfoPrivate(mPrincipalInfo)) {
1615
0
      // Call GetPrincipalKey again, this time w/persist = true, to promote
1616
0
      // deviceIds to persistent, in case they're not already. Fire'n'forget.
1617
0
      RefPtr<Pledge<nsCString>> p =
1618
0
        media::GetPrincipalKey(mPrincipalInfo, true);
1619
0
    }
1620
0
    return NS_OK;
1621
0
  }
1622
1623
private:
1624
  nsMainThreadPtrHandle<MediaManager::GetUserMediaSuccessCallback> mOnSuccess;
1625
  nsMainThreadPtrHandle<MediaManager::GetUserMediaErrorCallback> mOnFailure;
1626
  MediaStreamConstraints mConstraints;
1627
  RefPtr<MediaDevice> mAudioDevice;
1628
  RefPtr<MediaDevice> mVideoDevice;
1629
  uint64_t mWindowID;
1630
  RefPtr<GetUserMediaWindowListener> mWindowListener;
1631
  RefPtr<SourceListener> mSourceListener;
1632
  ipc::PrincipalInfo mPrincipalInfo;
1633
  RefPtr<PeerIdentity> mPeerIdentity;
1634
  RefPtr<MediaManager> mManager; // get ref to this when creating the runnable
1635
};
1636
1637
// Source getter returning full list
1638
1639
static void
1640
GetMediaDevices(MediaEngine *aEngine,
1641
                uint64_t aWindowId,
1642
                MediaSourceEnum aSrcType,
1643
                nsTArray<RefPtr<MediaDevice>>& aResult,
1644
                const char* aMediaDeviceName = nullptr)
1645
0
{
1646
0
  MOZ_ASSERT(MediaManager::IsInMediaThread());
1647
0
1648
0
  LOG(("%s: aEngine=%p, aWindowId=%" PRIu64 ", aSrcType=%" PRIu8 ", aMediaDeviceName=%s",
1649
0
       __func__, aEngine, aWindowId, static_cast<uint8_t>(aSrcType),
1650
0
       aMediaDeviceName ? aMediaDeviceName : "null"));
1651
0
  nsTArray<RefPtr<MediaDevice>> devices;
1652
0
  aEngine->EnumerateDevices(aWindowId, aSrcType, MediaSinkEnum::Other, &devices);
1653
0
1654
0
  /*
1655
0
   * We're allowing multiple tabs to access the same camera for parity
1656
0
   * with Chrome.  See bug 811757 for some of the issues surrounding
1657
0
   * this decision.  To disallow, we'd filter by IsAvailable() as we used
1658
0
   * to.
1659
0
   */
1660
0
  if (aMediaDeviceName && *aMediaDeviceName)  {
1661
0
    for (auto& device : devices) {
1662
0
      if (device->mName.EqualsASCII(aMediaDeviceName)) {
1663
0
        aResult.AppendElement(device);
1664
0
        LOG(("%s: found aMediaDeviceName=%s", __func__, aMediaDeviceName));
1665
0
        break;
1666
0
      }
1667
0
    }
1668
0
  } else {
1669
0
    aResult = devices;
1670
0
    if (MOZ_LOG_TEST(GetMediaManagerLog(), mozilla::LogLevel::Debug)) {
1671
0
      for (auto& device : devices) {
1672
0
        LOG(("%s: appending device=%s", __func__,
1673
0
             NS_ConvertUTF16toUTF8(device->mName).get()));
1674
0
      }
1675
0
    }
1676
0
  }
1677
0
}
1678
1679
// TODO: Remove once upgraded to GCC 4.8+ on linux. Bogus error on static func:
1680
// error: 'this' was not captured for this lambda function
1681
1682
static auto& MediaManager_ToJSArray = MediaManager::ToJSArray;
1683
static auto& MediaManager_AnonymizeDevices = MediaManager::AnonymizeDevices;
1684
1685
already_AddRefed<MediaManager::PledgeChar>
1686
MediaManager::SelectSettings(
1687
    MediaStreamConstraints& aConstraints,
1688
    bool aIsChrome,
1689
    RefPtr<Refcountable<UniquePtr<MediaDeviceSet>>>& aSources)
1690
0
{
1691
0
  MOZ_ASSERT(NS_IsMainThread());
1692
0
  RefPtr<PledgeChar> p = new PledgeChar();
1693
0
  uint32_t id = mOutstandingCharPledges.Append(*p);
1694
0
1695
0
  // Algorithm accesses device capabilities code and must run on media thread.
1696
0
  // Modifies passed-in aSources.
1697
0
1698
0
  MediaManager::PostTask(NewTaskFrom([id, aConstraints,
1699
0
                                      aSources, aIsChrome]() mutable {
1700
0
    auto& sources = **aSources;
1701
0
1702
0
    // Since the advanced part of the constraints algorithm needs to know when
1703
0
    // a candidate set is overconstrained (zero members), we must split up the
1704
0
    // list into videos and audios, and put it back together again at the end.
1705
0
1706
0
    nsTArray<RefPtr<MediaDevice>> videos;
1707
0
    nsTArray<RefPtr<MediaDevice>> audios;
1708
0
1709
0
    for (auto& source : sources) {
1710
0
      MOZ_ASSERT(source->mKind == dom::MediaDeviceKind::Videoinput ||
1711
0
                 source->mKind == dom::MediaDeviceKind::Audioinput);
1712
0
      if (source->mKind == dom::MediaDeviceKind::Videoinput) {
1713
0
        videos.AppendElement(source);
1714
0
      } else if (source->mKind == dom::MediaDeviceKind::Audioinput) {
1715
0
        audios.AppendElement(source);
1716
0
      }
1717
0
    }
1718
0
    sources.Clear();
1719
0
    const char* badConstraint = nullptr;
1720
0
    bool needVideo = IsOn(aConstraints.mVideo);
1721
0
    bool needAudio = IsOn(aConstraints.mAudio);
1722
0
1723
0
    if (needVideo && videos.Length()) {
1724
0
      badConstraint = MediaConstraintsHelper::SelectSettings(
1725
0
          NormalizedConstraints(GetInvariant(aConstraints.mVideo)), videos,
1726
0
          aIsChrome);
1727
0
    }
1728
0
    if (!badConstraint && needAudio && audios.Length()) {
1729
0
      badConstraint = MediaConstraintsHelper::SelectSettings(
1730
0
          NormalizedConstraints(GetInvariant(aConstraints.mAudio)), audios,
1731
0
          aIsChrome);
1732
0
    }
1733
0
    if (!badConstraint &&
1734
0
        !needVideo == !videos.Length() &&
1735
0
        !needAudio == !audios.Length()) {
1736
0
      for (auto& video : videos) {
1737
0
        sources.AppendElement(video);
1738
0
      }
1739
0
      for (auto& audio : audios) {
1740
0
        sources.AppendElement(audio);
1741
0
      }
1742
0
    }
1743
0
    NS_DispatchToMainThread(NewRunnableFrom([id, badConstraint]() mutable {
1744
0
      MediaManager* mgr = MediaManager::GetIfExists();
1745
0
      if (!mgr) {
1746
0
        return NS_OK;
1747
0
      }
1748
0
      RefPtr<PledgeChar> p = mgr->mOutstandingCharPledges.Remove(id);
1749
0
      if (p) {
1750
0
        p->Resolve(badConstraint);
1751
0
      }
1752
0
      return NS_OK;
1753
0
    }));
1754
0
  }));
1755
0
  return p.forget();
1756
0
}
1757
1758
/**
1759
 * Runs on a seperate thread and is responsible for enumerating devices.
1760
 * Depending on whether a picture or stream was asked for, either
1761
 * ProcessGetUserMedia is called, and the results are sent back to the DOM.
1762
 *
1763
 * Do not run this on the main thread. The success and error callbacks *MUST*
1764
 * be dispatched on the main thread!
1765
 */
1766
class GetUserMediaTask : public Runnable
1767
{
1768
public:
1769
  GetUserMediaTask(
1770
    const MediaStreamConstraints& aConstraints,
1771
    const nsMainThreadPtrHandle<MediaManager::GetUserMediaSuccessCallback>& aOnSuccess,
1772
    const nsMainThreadPtrHandle<MediaManager::GetUserMediaErrorCallback>& aOnFailure,
1773
    uint64_t aWindowID,
1774
    GetUserMediaWindowListener* aWindowListener,
1775
    SourceListener* aSourceListener,
1776
    MediaEnginePrefs& aPrefs,
1777
    const ipc::PrincipalInfo& aPrincipalInfo,
1778
    bool aIsChrome,
1779
    MediaManager::MediaDeviceSet* aMediaDeviceSet,
1780
    bool aShouldFocusSource)
1781
    : Runnable("GetUserMediaTask")
1782
    , mConstraints(aConstraints)
1783
    , mOnSuccess(aOnSuccess)
1784
    , mOnFailure(aOnFailure)
1785
    , mWindowID(aWindowID)
1786
    , mWindowListener(aWindowListener)
1787
    , mSourceListener(aSourceListener)
1788
    , mPrefs(aPrefs)
1789
    , mPrincipalInfo(aPrincipalInfo)
1790
    , mIsChrome(aIsChrome)
1791
    , mShouldFocusSource(aShouldFocusSource)
1792
    , mDeviceChosen(false)
1793
    , mMediaDeviceSet(aMediaDeviceSet)
1794
    , mManager(MediaManager::GetInstance())
1795
0
  {}
1796
1797
0
  ~GetUserMediaTask() {
1798
0
  }
1799
1800
  void
1801
  Fail(MediaMgrError::Name aName,
1802
       const nsAString& aMessage = EmptyString(),
1803
0
       const nsAString& aConstraint = EmptyString()) {
1804
0
    RefPtr<MediaMgrError> error = new MediaMgrError(aName, aMessage, aConstraint);
1805
0
    auto errorRunnable = MakeRefPtr<ErrorCallbackRunnable>(
1806
0
        mOnFailure, *error, mWindowID);
1807
0
1808
0
    NS_DispatchToMainThread(errorRunnable.forget());
1809
0
    // Do after ErrorCallbackRunnable Run()s, as it checks active window list
1810
0
    NS_DispatchToMainThread(NewRunnableMethod<RefPtr<SourceListener>>(
1811
0
      "GetUserMediaWindowListener::Remove",
1812
0
      mWindowListener,
1813
0
      &GetUserMediaWindowListener::Remove,
1814
0
      mSourceListener));
1815
0
  }
1816
1817
  NS_IMETHOD
1818
  Run() override
1819
0
  {
1820
0
    MOZ_ASSERT(!NS_IsMainThread());
1821
0
    MOZ_ASSERT(mOnSuccess);
1822
0
    MOZ_ASSERT(mOnFailure);
1823
0
    MOZ_ASSERT(mDeviceChosen);
1824
0
    LOG(("GetUserMediaTask::Run()"));
1825
0
1826
0
    // Allocate a video or audio device and return a MediaStream via
1827
0
    // a GetUserMediaStreamRunnable.
1828
0
1829
0
    nsresult rv;
1830
0
    const char* errorMsg = nullptr;
1831
0
    const char* badConstraint = nullptr;
1832
0
1833
0
    if (mAudioDevice) {
1834
0
      auto& constraints = GetInvariant(mConstraints.mAudio);
1835
0
      rv = mAudioDevice->Allocate(constraints, mPrefs, mPrincipalInfo,
1836
0
                                  &badConstraint);
1837
0
      if (NS_FAILED(rv)) {
1838
0
        errorMsg = "Failed to allocate audiosource";
1839
0
        if (rv == NS_ERROR_NOT_AVAILABLE && !badConstraint) {
1840
0
          nsTArray<RefPtr<MediaDevice>> devices;
1841
0
          devices.AppendElement(mAudioDevice);
1842
0
          badConstraint = MediaConstraintsHelper::SelectSettings(
1843
0
              NormalizedConstraints(constraints), devices, mIsChrome);
1844
0
        }
1845
0
      }
1846
0
    }
1847
0
    if (!errorMsg && mVideoDevice) {
1848
0
      auto& constraints = GetInvariant(mConstraints.mVideo);
1849
0
      rv = mVideoDevice->Allocate(constraints, mPrefs, mPrincipalInfo,
1850
0
                                  &badConstraint);
1851
0
      if (NS_FAILED(rv)) {
1852
0
        errorMsg = "Failed to allocate videosource";
1853
0
        if (rv == NS_ERROR_NOT_AVAILABLE && !badConstraint) {
1854
0
          nsTArray<RefPtr<MediaDevice>> devices;
1855
0
          devices.AppendElement(mVideoDevice);
1856
0
          badConstraint = MediaConstraintsHelper::SelectSettings(
1857
0
              NormalizedConstraints(constraints), devices, mIsChrome);
1858
0
        }
1859
0
        if (mAudioDevice) {
1860
0
          mAudioDevice->Deallocate();
1861
0
        }
1862
0
      } else {
1863
0
        if (!mIsChrome) {
1864
0
          if (mShouldFocusSource) {
1865
0
            rv = mVideoDevice->FocusOnSelectedSource();
1866
0
1867
0
            if (NS_FAILED(rv)) {
1868
0
              LOG(("FocusOnSelectedSource failed"));
1869
0
            }
1870
0
          }
1871
0
        }
1872
0
      }
1873
0
    }
1874
0
    if (errorMsg) {
1875
0
      LOG(("%s %" PRIu32, errorMsg, static_cast<uint32_t>(rv)));
1876
0
      if (badConstraint) {
1877
0
        Fail(MediaMgrError::Name::OverconstrainedError,
1878
0
             NS_LITERAL_STRING(""),
1879
0
             NS_ConvertUTF8toUTF16(badConstraint));
1880
0
      } else {
1881
0
        Fail(MediaMgrError::Name::NotReadableError,
1882
0
             NS_ConvertUTF8toUTF16(errorMsg));
1883
0
      }
1884
0
      NS_DispatchToMainThread(NS_NewRunnableFunction("MediaManager::SendPendingGUMRequest",
1885
0
                                                     []() -> void {
1886
0
        MediaManager* manager = MediaManager::GetIfExists();
1887
0
        if (!manager) {
1888
0
          return;
1889
0
        }
1890
0
        manager->SendPendingGUMRequest();
1891
0
      }));
1892
0
      return NS_OK;
1893
0
    }
1894
0
    PeerIdentity* peerIdentity = nullptr;
1895
0
    if (!mConstraints.mPeerIdentity.IsEmpty()) {
1896
0
      peerIdentity = new PeerIdentity(mConstraints.mPeerIdentity);
1897
0
    }
1898
0
1899
0
    NS_DispatchToMainThread(do_AddRef(
1900
0
        new GetUserMediaStreamRunnable(mOnSuccess, mOnFailure, mWindowID,
1901
0
                                       mWindowListener, mSourceListener,
1902
0
                                       mPrincipalInfo, mConstraints,
1903
0
                                       mAudioDevice, mVideoDevice,
1904
0
                                       peerIdentity, mIsChrome)));
1905
0
    return NS_OK;
1906
0
  }
1907
1908
  nsresult
1909
  Denied(MediaMgrError::Name aName,
1910
         const nsAString& aMessage = EmptyString())
1911
0
  {
1912
0
    MOZ_ASSERT(mOnSuccess);
1913
0
    MOZ_ASSERT(mOnFailure);
1914
0
1915
0
    // We add a disabled listener to the StreamListeners array until accepted
1916
0
    // If this was the only active MediaStream, remove the window from the list.
1917
0
    if (NS_IsMainThread()) {
1918
0
      if (auto* window = nsGlobalWindowInner::GetInnerWindowWithId(mWindowID)) {
1919
0
        RefPtr<MediaStreamError> error = new MediaStreamError(window->AsInner(),
1920
0
                                                              aName, aMessage);
1921
0
        CallOnError(mOnFailure, *error);
1922
0
      }
1923
0
      // Should happen *after* error runs for consistency, but may not matter
1924
0
      mWindowListener->Remove(mSourceListener);
1925
0
    } else {
1926
0
      // This will re-check the window being alive on main-thread
1927
0
      Fail(aName, aMessage);
1928
0
    }
1929
0
1930
0
    return NS_OK;
1931
0
  }
1932
1933
  nsresult
1934
  SetContraints(const MediaStreamConstraints& aConstraints)
1935
0
  {
1936
0
    mConstraints = aConstraints;
1937
0
    return NS_OK;
1938
0
  }
1939
1940
  const MediaStreamConstraints&
1941
  GetConstraints()
1942
0
  {
1943
0
    return mConstraints;
1944
0
  }
1945
1946
  nsresult
1947
  SetAudioDevice(MediaDevice* aAudioDevice)
1948
0
  {
1949
0
    mAudioDevice = aAudioDevice;
1950
0
    mDeviceChosen = true;
1951
0
    return NS_OK;
1952
0
  }
1953
1954
  nsresult
1955
  SetVideoDevice(MediaDevice* aVideoDevice)
1956
0
  {
1957
0
    mVideoDevice = aVideoDevice;
1958
0
    mDeviceChosen = true;
1959
0
    return NS_OK;
1960
0
  }
1961
1962
  uint64_t
1963
  GetWindowID()
1964
0
  {
1965
0
    return mWindowID;
1966
0
  }
1967
1968
private:
1969
  MediaStreamConstraints mConstraints;
1970
1971
  nsMainThreadPtrHandle<MediaManager::GetUserMediaSuccessCallback> mOnSuccess;
1972
  nsMainThreadPtrHandle<MediaManager::GetUserMediaErrorCallback> mOnFailure;
1973
  uint64_t mWindowID;
1974
  RefPtr<GetUserMediaWindowListener> mWindowListener;
1975
  RefPtr<SourceListener> mSourceListener;
1976
  RefPtr<MediaDevice> mAudioDevice;
1977
  RefPtr<MediaDevice> mVideoDevice;
1978
  MediaEnginePrefs mPrefs;
1979
  ipc::PrincipalInfo mPrincipalInfo;
1980
  bool mIsChrome;
1981
  bool mShouldFocusSource;
1982
1983
  bool mDeviceChosen;
1984
public:
1985
  nsAutoPtr<MediaManager::MediaDeviceSet> mMediaDeviceSet;
1986
private:
1987
  RefPtr<MediaManager> mManager; // get ref to this when creating the runnable
1988
};
1989
1990
#if defined(ANDROID)
1991
class GetUserMediaRunnableWrapper : public Runnable
1992
{
1993
public:
1994
  // This object must take ownership of task
1995
  explicit GetUserMediaRunnableWrapper(GetUserMediaTask* task)
1996
    : Runnable("GetUserMediaRunnableWrapper")
1997
    , mTask(task) {
1998
  }
1999
2000
  ~GetUserMediaRunnableWrapper() {
2001
  }
2002
2003
  NS_IMETHOD Run() override {
2004
    mTask->Run();
2005
    return NS_OK;
2006
  }
2007
2008
private:
2009
  nsAutoPtr<GetUserMediaTask> mTask;
2010
};
2011
#endif
2012
2013
/**
2014
 * EnumerateRawDevices - Enumerate a list of audio & video devices that
2015
 * satisfy passed-in constraints. List contains raw id's.
2016
 */
2017
2018
already_AddRefed<MediaManager::PledgeMediaDeviceSet>
2019
MediaManager::EnumerateRawDevices(uint64_t aWindowId,
2020
                                  MediaSourceEnum aVideoInputType,
2021
                                  MediaSourceEnum aAudioInputType,
2022
                                  MediaSinkEnum aAudioOutputType,
2023
                                  DeviceEnumerationType aVideoInputEnumType /* = DeviceEnumerationType::Normal */,
2024
                                  DeviceEnumerationType aAudioInputEnumType /* = DeviceEnumerationType::Normal */)
2025
0
{
2026
0
  MOZ_ASSERT(NS_IsMainThread());
2027
0
  MOZ_ASSERT(aVideoInputType != MediaSourceEnum::Other ||
2028
0
             aAudioInputType != MediaSourceEnum::Other ||
2029
0
             aAudioOutputType != MediaSinkEnum::Other);
2030
0
  // Since the enums can take one of several values, the following asserts rely
2031
0
  // on short circuting behavior. E.g. aVideoInputEnumType != Fake will be true if
2032
0
  // the requested device is not fake and thus the assert will pass. However,
2033
0
  // if the device is fake, aVideoInputType == MediaSourceEnum::Camera will be
2034
0
  // checked as well, ensuring that fake devices are of the camera type.
2035
0
  MOZ_ASSERT(aVideoInputEnumType != DeviceEnumerationType::Fake ||
2036
0
             aVideoInputType == MediaSourceEnum::Camera,
2037
0
             "If fake cams are requested video type should be camera!");
2038
0
  MOZ_ASSERT(aVideoInputEnumType != DeviceEnumerationType::Loopback ||
2039
0
             aVideoInputType == MediaSourceEnum::Camera,
2040
0
             "If loopback video is requested video type should be camera!");
2041
0
  MOZ_ASSERT(aAudioInputEnumType != DeviceEnumerationType::Fake ||
2042
0
             aAudioInputType == MediaSourceEnum::Microphone,
2043
0
             "If fake mics are requested audio type should be microphone!");
2044
0
  MOZ_ASSERT(aAudioInputEnumType != DeviceEnumerationType::Loopback ||
2045
0
             aAudioInputType == MediaSourceEnum::Microphone,
2046
0
             "If loopback audio is requested audio type should be microphone!");
2047
0
2048
0
  LOG(("%s: aWindowId=%" PRIu64 ", aVideoInputType=%" PRIu8 ", aAudioInputType=%" PRIu8
2049
0
       ", aVideoInputEnumType=%" PRIu8 ", aAudioInputEnumType=%" PRIu8,
2050
0
       __func__, aWindowId,
2051
0
       static_cast<uint8_t>(aVideoInputType), static_cast<uint8_t>(aAudioInputType),
2052
0
       static_cast<uint8_t>(aVideoInputEnumType), static_cast<uint8_t>(aAudioInputEnumType)));
2053
0
  RefPtr<PledgeMediaDeviceSet> p = new PledgeMediaDeviceSet();
2054
0
  uint32_t id = mOutstandingPledges.Append(*p);
2055
0
2056
0
  bool hasVideo = aVideoInputType != MediaSourceEnum::Other;
2057
0
  bool hasAudio = aAudioInputType != MediaSourceEnum::Other;
2058
0
  bool hasAudioOutput = aAudioOutputType == MediaSinkEnum::Speaker;
2059
0
2060
0
  // True of at least one of video input or audio input is a fake device
2061
0
  bool fakeDeviceRequested = (aVideoInputEnumType == DeviceEnumerationType::Fake && hasVideo) ||
2062
0
                             (aAudioInputEnumType == DeviceEnumerationType::Fake && hasAudio);
2063
0
  // True if at least one of video input or audio input is a real device
2064
0
  // or there is audio output.
2065
0
  bool realDeviceRequested = (aVideoInputEnumType != DeviceEnumerationType::Fake && hasVideo) ||
2066
0
                             (aAudioInputEnumType != DeviceEnumerationType::Fake && hasAudio) ||
2067
0
                             hasAudioOutput;
2068
0
2069
0
  nsAutoCString videoLoopDev, audioLoopDev;
2070
0
  if (hasVideo && aVideoInputEnumType == DeviceEnumerationType::Loopback) {
2071
0
    Preferences::GetCString("media.video_loopback_dev", videoLoopDev);
2072
0
  }
2073
0
  if (hasAudio && aAudioInputEnumType == DeviceEnumerationType::Loopback) {
2074
0
    Preferences::GetCString("media.audio_loopback_dev", audioLoopDev);
2075
0
  }
2076
0
2077
0
  RefPtr<Runnable> task = NewTaskFrom([id, aWindowId, aVideoInputType, aAudioInputType,
2078
0
                                       aVideoInputEnumType, aAudioInputEnumType,
2079
0
                                       videoLoopDev, audioLoopDev,
2080
0
                                       hasVideo, hasAudio, hasAudioOutput,
2081
0
                                       fakeDeviceRequested, realDeviceRequested]() {
2082
0
    // Only enumerate what's asked for, and only fake cams and mics.
2083
0
    RefPtr<MediaEngine> fakeBackend, realBackend;
2084
0
    if (fakeDeviceRequested) {
2085
0
      fakeBackend = new MediaEngineDefault();
2086
0
    }
2087
0
    if (realDeviceRequested) {
2088
0
      MediaManager* manager = MediaManager::GetIfExists();
2089
0
      MOZ_RELEASE_ASSERT(manager); // Must exist while media thread is alive
2090
0
      realBackend = manager->GetBackend(aWindowId);
2091
0
    }
2092
0
2093
0
    auto result = MakeUnique<MediaDeviceSet>();
2094
0
2095
0
    if (hasVideo) {
2096
0
      MediaDeviceSet videos;
2097
0
      LOG(("EnumerateRawDevices Task: Getting video sources with %s backend",
2098
0
           aVideoInputEnumType == DeviceEnumerationType::Fake ? "fake" : "real"));
2099
0
      GetMediaDevices(aVideoInputEnumType == DeviceEnumerationType::Fake ? fakeBackend : realBackend,
2100
0
                      aWindowId, aVideoInputType, videos, videoLoopDev.get());
2101
0
      result->AppendElements(videos);
2102
0
    }
2103
0
    if (hasAudio) {
2104
0
      MediaDeviceSet audios;
2105
0
      LOG(("EnumerateRawDevices Task: Getting audio sources with %s backend",
2106
0
           aAudioInputEnumType == DeviceEnumerationType::Fake ? "fake" : "real"));
2107
0
      GetMediaDevices(aAudioInputEnumType == DeviceEnumerationType::Fake ? fakeBackend : realBackend,
2108
0
                      aWindowId, aAudioInputType, audios, audioLoopDev.get());
2109
0
      result->AppendElements(audios);
2110
0
    }
2111
0
    if (hasAudioOutput) {
2112
0
      MediaDeviceSet outputs;
2113
0
      MOZ_ASSERT(realBackend);
2114
0
      realBackend->EnumerateDevices(aWindowId,
2115
0
                                    MediaSourceEnum::Other,
2116
0
                                    MediaSinkEnum::Speaker,
2117
0
                                    &outputs);
2118
0
      result->AppendElements(outputs);
2119
0
    }
2120
0
2121
0
    NS_DispatchToMainThread(NewRunnableFrom([id, result = std::move(result)]() mutable {
2122
0
      MediaManager* mgr = MediaManager::GetIfExists();
2123
0
      if (!mgr) {
2124
0
        return NS_OK;
2125
0
      }
2126
0
      RefPtr<PledgeMediaDeviceSet> p = mgr->mOutstandingPledges.Remove(id);
2127
0
      if (p) {
2128
0
        p->Resolve(result.release());
2129
0
      }
2130
0
      return NS_OK;
2131
0
    }));
2132
0
  });
2133
0
2134
0
  if (realDeviceRequested &&
2135
0
      Preferences::GetBool("media.navigator.permission.device", false)) {
2136
0
    // Need to ask permission to retrieve list of all devices;
2137
0
    // notify frontend observer and wait for callback notification to post task.
2138
0
    const char16_t* const type =
2139
0
      (aVideoInputType != MediaSourceEnum::Camera)     ? u"audio" :
2140
0
      (aAudioInputType != MediaSourceEnum::Microphone) ? u"video" :
2141
0
                                                         u"all";
2142
0
    nsCOMPtr<nsIObserverService> obs = services::GetObserverService();
2143
0
    obs->NotifyObservers(static_cast<nsIRunnable*>(task),
2144
0
                         "getUserMedia:ask-device-permission",
2145
0
                         type);
2146
0
  } else {
2147
0
    // Don't need to ask permission to retrieve list of all devices;
2148
0
    // post the retrieval task immediately.
2149
0
    MediaManager::PostTask(task.forget());
2150
0
  }
2151
0
2152
0
  return p.forget();
2153
0
}
2154
2155
MediaManager::MediaManager()
2156
  : mMediaThread(nullptr)
2157
0
  , mBackend(nullptr) {
2158
0
  mPrefs.mFreq         = 1000; // 1KHz test tone
2159
0
  mPrefs.mWidth        = 0; // adaptive default
2160
0
  mPrefs.mHeight       = 0; // adaptive default
2161
0
  mPrefs.mFPS          = MediaEnginePrefs::DEFAULT_VIDEO_FPS;
2162
0
  mPrefs.mAecOn        = false;
2163
0
  mPrefs.mAgcOn        = false;
2164
0
  mPrefs.mNoiseOn      = false;
2165
0
  mPrefs.mExtendedFilter = true;
2166
0
  mPrefs.mDelayAgnostic = true;
2167
0
  mPrefs.mFakeDeviceChangeEventOn = false;
2168
0
#ifdef MOZ_WEBRTC
2169
0
  mPrefs.mAec          = webrtc::kEcUnchanged;
2170
0
  mPrefs.mAgc          = webrtc::kAgcUnchanged;
2171
0
  mPrefs.mNoise        = webrtc::kNsUnchanged;
2172
#else
2173
  mPrefs.mAec          = 0;
2174
  mPrefs.mAgc          = 0;
2175
  mPrefs.mNoise        = 0;
2176
#endif
2177
  mPrefs.mFullDuplex = false;
2178
0
  mPrefs.mChannels     = 0; // max channels default
2179
0
  nsresult rv;
2180
0
  nsCOMPtr<nsIPrefService> prefs = do_GetService("@mozilla.org/preferences-service;1", &rv);
2181
0
  if (NS_SUCCEEDED(rv)) {
2182
0
    nsCOMPtr<nsIPrefBranch> branch = do_QueryInterface(prefs);
2183
0
    if (branch) {
2184
0
      GetPrefs(branch, nullptr);
2185
0
    }
2186
0
  }
2187
0
  LOG(("%s: default prefs: %dx%d @%dfps, %dHz test tones, aec: %s,"
2188
0
       "agc: %s, noise: %s, aec level: %d, agc level: %d, noise level: %d,"
2189
0
       "%sfull_duplex, extended aec %s, delay_agnostic %s "
2190
0
       "channels %d",
2191
0
       __FUNCTION__, mPrefs.mWidth, mPrefs.mHeight,
2192
0
       mPrefs.mFPS, mPrefs.mFreq, mPrefs.mAecOn ? "on" : "off",
2193
0
       mPrefs.mAgcOn ? "on": "off", mPrefs.mNoiseOn ? "on": "off", mPrefs.mAec,
2194
0
       mPrefs.mAgc, mPrefs.mNoise, mPrefs.mFullDuplex ? "" : "not ",
2195
0
       mPrefs.mExtendedFilter ? "on" : "off", mPrefs.mDelayAgnostic ? "on" : "off",
2196
0
       mPrefs.mChannels));
2197
0
}
2198
2199
NS_IMPL_ISUPPORTS(MediaManager, nsIMediaManagerService, nsIObserver)
2200
2201
/* static */ StaticRefPtr<MediaManager> MediaManager::sSingleton;
2202
2203
#ifdef DEBUG
2204
/* static */ bool
2205
MediaManager::IsInMediaThread()
2206
{
2207
  return sSingleton?
2208
      (sSingleton->mMediaThread->thread_id() == PlatformThread::CurrentId()) :
2209
      false;
2210
}
2211
#endif
2212
2213
#ifdef XP_WIN
2214
class MTAThread : public base::Thread {
2215
public:
2216
  explicit MTAThread(const char* aName)
2217
    : base::Thread(aName)
2218
    , mResult(E_FAIL)
2219
  {
2220
  }
2221
2222
protected:
2223
  virtual void Init() override {
2224
    mResult = CoInitializeEx(nullptr, COINIT_MULTITHREADED);
2225
  }
2226
2227
  virtual void CleanUp() override {
2228
    if (SUCCEEDED(mResult)) {
2229
      CoUninitialize();
2230
    }
2231
  }
2232
2233
private:
2234
  HRESULT mResult;
2235
};
2236
#endif
2237
2238
// NOTE: never Dispatch(....,NS_DISPATCH_SYNC) to the MediaManager
2239
// thread from the MainThread, as we NS_DISPATCH_SYNC to MainThread
2240
// from MediaManager thread.
2241
2242
// Guaranteed never to return nullptr.
2243
/* static */  MediaManager*
2244
0
MediaManager::Get() {
2245
0
  if (!sSingleton) {
2246
0
    MOZ_ASSERT(NS_IsMainThread());
2247
0
2248
0
    static int timesCreated = 0;
2249
0
    timesCreated++;
2250
0
    MOZ_RELEASE_ASSERT(timesCreated == 1);
2251
0
2252
0
    sSingleton = new MediaManager();
2253
0
2254
#ifdef XP_WIN
2255
    sSingleton->mMediaThread = new MTAThread("MediaManager");
2256
#else
2257
    sSingleton->mMediaThread = new base::Thread("MediaManager");
2258
0
#endif
2259
0
    base::Thread::Options options;
2260
#if defined(_WIN32)
2261
    options.message_loop_type = MessageLoop::TYPE_MOZILLA_NONMAINUITHREAD;
2262
#else
2263
    options.message_loop_type = MessageLoop::TYPE_MOZILLA_NONMAINTHREAD;
2264
0
#endif
2265
0
    if (!sSingleton->mMediaThread->StartWithOptions(options)) {
2266
0
      MOZ_CRASH();
2267
0
    }
2268
0
2269
0
    LOG(("New Media thread for gum"));
2270
0
2271
0
    nsCOMPtr<nsIObserverService> obs = services::GetObserverService();
2272
0
    if (obs) {
2273
0
      obs->AddObserver(sSingleton, "last-pb-context-exited", false);
2274
0
      obs->AddObserver(sSingleton, "getUserMedia:got-device-permission", false);
2275
0
      obs->AddObserver(sSingleton, "getUserMedia:privileged:allow", false);
2276
0
      obs->AddObserver(sSingleton, "getUserMedia:response:allow", false);
2277
0
      obs->AddObserver(sSingleton, "getUserMedia:response:deny", false);
2278
0
      obs->AddObserver(sSingleton, "getUserMedia:revoke", false);
2279
0
    }
2280
0
    // else MediaManager won't work properly and will leak (see bug 837874)
2281
0
    nsCOMPtr<nsIPrefBranch> prefs = do_GetService(NS_PREFSERVICE_CONTRACTID);
2282
0
    if (prefs) {
2283
0
      prefs->AddObserver("media.navigator.video.default_width", sSingleton, false);
2284
0
      prefs->AddObserver("media.navigator.video.default_height", sSingleton, false);
2285
0
      prefs->AddObserver("media.navigator.video.default_fps", sSingleton, false);
2286
0
      prefs->AddObserver("media.navigator.audio.fake_frequency", sSingleton, false);
2287
0
      prefs->AddObserver("media.navigator.audio.full_duplex", sSingleton, false);
2288
0
#ifdef MOZ_WEBRTC
2289
0
      prefs->AddObserver("media.getusermedia.aec_enabled", sSingleton, false);
2290
0
      prefs->AddObserver("media.getusermedia.aec", sSingleton, false);
2291
0
      prefs->AddObserver("media.getusermedia.agc_enabled", sSingleton, false);
2292
0
      prefs->AddObserver("media.getusermedia.agc", sSingleton, false);
2293
0
      prefs->AddObserver("media.getusermedia.noise_enabled", sSingleton, false);
2294
0
      prefs->AddObserver("media.getusermedia.noise", sSingleton, false);
2295
0
      prefs->AddObserver("media.ondevicechange.fakeDeviceChangeEvent.enabled", sSingleton, false);
2296
0
      prefs->AddObserver("media.getusermedia.channels", sSingleton, false);
2297
0
#endif
2298
0
    }
2299
0
2300
0
    // Prepare async shutdown
2301
0
2302
0
    nsCOMPtr<nsIAsyncShutdownClient> shutdownPhase = GetShutdownPhase();
2303
0
2304
0
    class Blocker : public media::ShutdownBlocker
2305
0
    {
2306
0
    public:
2307
0
      Blocker()
2308
0
      : media::ShutdownBlocker(NS_LITERAL_STRING(
2309
0
          "Media shutdown: blocking on media thread")) {}
2310
0
2311
0
      NS_IMETHOD BlockShutdown(nsIAsyncShutdownClient*) override
2312
0
      {
2313
0
        MOZ_RELEASE_ASSERT(MediaManager::GetIfExists());
2314
0
        MediaManager::GetIfExists()->Shutdown();
2315
0
        return NS_OK;
2316
0
      }
2317
0
    };
2318
0
2319
0
    sSingleton->mShutdownBlocker = new Blocker();
2320
0
    nsresult rv = shutdownPhase->AddBlocker(sSingleton->mShutdownBlocker,
2321
0
                                            NS_LITERAL_STRING(__FILE__),
2322
0
                                            __LINE__,
2323
0
                                            NS_LITERAL_STRING("Media shutdown"));
2324
0
    MOZ_RELEASE_ASSERT(NS_SUCCEEDED(rv));
2325
0
  }
2326
0
  return sSingleton;
2327
0
}
2328
2329
/* static */  MediaManager*
2330
0
MediaManager::GetIfExists() {
2331
0
  return sSingleton;
2332
0
}
2333
2334
/* static */ already_AddRefed<MediaManager>
2335
MediaManager::GetInstance()
2336
0
{
2337
0
  // so we can have non-refcounted getters
2338
0
  RefPtr<MediaManager> service = MediaManager::Get();
2339
0
  return service.forget();
2340
0
}
2341
2342
media::Parent<media::NonE10s>*
2343
MediaManager::GetNonE10sParent()
2344
0
{
2345
0
  if (!mNonE10sParent) {
2346
0
    mNonE10sParent = new media::Parent<media::NonE10s>();
2347
0
  }
2348
0
  return mNonE10sParent;
2349
0
}
2350
2351
/* static */ void
2352
MediaManager::StartupInit()
2353
3
{
2354
#ifdef WIN32
2355
  if (!IsWin8OrLater()) {
2356
    // Bug 1107702 - Older Windows fail in GetAdaptersInfo (and others) if the
2357
    // first(?) call occurs after the process size is over 2GB (kb/2588507).
2358
    // Attempt to 'prime' the pump by making a call at startup.
2359
    unsigned long out_buf_len = sizeof(IP_ADAPTER_INFO);
2360
    PIP_ADAPTER_INFO pAdapterInfo = (IP_ADAPTER_INFO *) moz_xmalloc(out_buf_len);
2361
    if (GetAdaptersInfo(pAdapterInfo, &out_buf_len) == ERROR_BUFFER_OVERFLOW) {
2362
      free(pAdapterInfo);
2363
      pAdapterInfo = (IP_ADAPTER_INFO *) moz_xmalloc(out_buf_len);
2364
      GetAdaptersInfo(pAdapterInfo, &out_buf_len);
2365
    }
2366
    if (pAdapterInfo) {
2367
      free(pAdapterInfo);
2368
    }
2369
  }
2370
#endif
2371
}
2372
2373
/* static */
2374
void
2375
MediaManager::PostTask(already_AddRefed<Runnable> task)
2376
0
{
2377
0
  if (sHasShutdown) {
2378
0
    // Can't safely delete task here since it may have items with specific
2379
0
    // thread-release requirements.
2380
0
    // XXXkhuey well then who is supposed to delete it?! We don't signal
2381
0
    // that we failed ...
2382
0
    MOZ_CRASH();
2383
0
    return;
2384
0
  }
2385
0
  NS_ASSERTION(Get(), "MediaManager singleton?");
2386
0
  NS_ASSERTION(Get()->mMediaThread, "No thread yet");
2387
0
  Get()->mMediaThread->message_loop()->PostTask(std::move(task));
2388
0
}
2389
2390
template<typename MozPromiseType, typename FunctionType>
2391
/* static */ RefPtr<MozPromiseType>
2392
MediaManager::PostTask(const char* aName, FunctionType&& aFunction)
2393
0
{
2394
0
  MozPromiseHolder<MozPromiseType> holder;
2395
0
  RefPtr<MozPromiseType> promise = holder.Ensure(aName);
2396
0
  MediaManager::PostTask(NS_NewRunnableFunction(aName,
2397
0
        [h = std::move(holder), func = std::forward<FunctionType>(aFunction)]() mutable
2398
0
        {
2399
0
          func(h);
2400
0
        }));
Unexecuted instantiation: Unified_cpp_dom_media6.cpp:RefPtr<mozilla::MozPromise<bool, RefPtr<mozilla::MediaMgrError>, true> > mozilla::MediaManager::PostTask<mozilla::MozPromise<bool, RefPtr<mozilla::MediaMgrError>, true>, mozilla::SourceListener::InitializeAsync()::$_40>(char const*, mozilla::SourceListener::InitializeAsync()::$_40&&)::{lambda()#1}::operator()()
Unexecuted instantiation: Unified_cpp_dom_media6.cpp:RefPtr<mozilla::MozPromise<nsresult, bool, true> > mozilla::MediaManager::PostTask<mozilla::MozPromise<nsresult, bool, true>, mozilla::SourceListener::SetEnabledFor(int, bool)::$_45::operator()()::{lambda(mozilla::MozPromiseHolder<mozilla::MozPromise<nsresult, bool, true> >&)#1}>(char const*, mozilla::SourceListener::SetEnabledFor(int, bool)::$_45::operator()()::{lambda(mozilla::MozPromiseHolder<mozilla::MozPromise<nsresult, bool, true> >&)#1}&&)::{lambda()#1}::operator()()
Unexecuted instantiation: Unified_cpp_dom_media6.cpp:RefPtr<mozilla::MozPromise<bool, mozilla::Maybe<nsTString<char16_t> >, true> > mozilla::MediaManager::PostTask<mozilla::MozPromise<bool, mozilla::Maybe<nsTString<char16_t> >, true>, mozilla::SourceListener::ApplyConstraintsToTrack(nsPIDOMWindowInner*, int, mozilla::dom::MediaTrackConstraints const&, mozilla::dom::CallerType)::$_49>(char const*, mozilla::SourceListener::ApplyConstraintsToTrack(nsPIDOMWindowInner*, int, mozilla::dom::MediaTrackConstraints const&, mozilla::dom::CallerType)::$_49&&)::{lambda()#1}::operator()()
2401
0
  return promise;
2402
0
}
Unexecuted instantiation: Unified_cpp_dom_media6.cpp:RefPtr<mozilla::MozPromise<bool, RefPtr<mozilla::MediaMgrError>, true> > mozilla::MediaManager::PostTask<mozilla::MozPromise<bool, RefPtr<mozilla::MediaMgrError>, true>, mozilla::SourceListener::InitializeAsync()::$_40>(char const*, mozilla::SourceListener::InitializeAsync()::$_40&&)
Unexecuted instantiation: Unified_cpp_dom_media6.cpp:RefPtr<mozilla::MozPromise<nsresult, bool, true> > mozilla::MediaManager::PostTask<mozilla::MozPromise<nsresult, bool, true>, mozilla::SourceListener::SetEnabledFor(int, bool)::$_45::operator()()::{lambda(mozilla::MozPromiseHolder<mozilla::MozPromise<nsresult, bool, true> >&)#1}>(char const*, mozilla::SourceListener::SetEnabledFor(int, bool)::$_45::operator()()::{lambda(mozilla::MozPromiseHolder<mozilla::MozPromise<nsresult, bool, true> >&)#1}&&)
Unexecuted instantiation: Unified_cpp_dom_media6.cpp:RefPtr<mozilla::MozPromise<bool, mozilla::Maybe<nsTString<char16_t> >, true> > mozilla::MediaManager::PostTask<mozilla::MozPromise<bool, mozilla::Maybe<nsTString<char16_t> >, true>, mozilla::SourceListener::ApplyConstraintsToTrack(nsPIDOMWindowInner*, int, mozilla::dom::MediaTrackConstraints const&, mozilla::dom::CallerType)::$_49>(char const*, mozilla::SourceListener::ApplyConstraintsToTrack(nsPIDOMWindowInner*, int, mozilla::dom::MediaTrackConstraints const&, mozilla::dom::CallerType)::$_49&&)
2403
2404
/* static */ nsresult
2405
MediaManager::NotifyRecordingStatusChange(nsPIDOMWindowInner* aWindow)
2406
0
{
2407
0
  NS_ENSURE_ARG(aWindow);
2408
0
2409
0
  nsCOMPtr<nsIObserverService> obs = services::GetObserverService();
2410
0
  if (!obs) {
2411
0
    NS_WARNING("Could not get the Observer service for GetUserMedia recording notification.");
2412
0
    return NS_ERROR_FAILURE;
2413
0
  }
2414
0
2415
0
  RefPtr<nsHashPropertyBag> props = new nsHashPropertyBag();
2416
0
2417
0
  nsCString pageURL;
2418
0
  nsCOMPtr<nsIURI> docURI = aWindow->GetDocumentURI();
2419
0
  NS_ENSURE_TRUE(docURI, NS_ERROR_FAILURE);
2420
0
2421
0
  nsresult rv = docURI->GetSpec(pageURL);
2422
0
  NS_ENSURE_SUCCESS(rv, rv);
2423
0
2424
0
  NS_ConvertUTF8toUTF16 requestURL(pageURL);
2425
0
2426
0
  props->SetPropertyAsAString(NS_LITERAL_STRING("requestURL"), requestURL);
2427
0
2428
0
  obs->NotifyObservers(static_cast<nsIPropertyBag2*>(props),
2429
0
                       "recording-device-events",
2430
0
                       nullptr);
2431
0
2432
0
  return NS_OK;
2433
0
}
2434
2435
int MediaManager::AddDeviceChangeCallback(DeviceChangeCallback* aCallback)
2436
0
{
2437
0
  bool fakeDeviceChangeEventOn = mPrefs.mFakeDeviceChangeEventOn;
2438
0
  MediaManager::PostTask(NewTaskFrom([fakeDeviceChangeEventOn]() {
2439
0
    MediaManager* manager = MediaManager::GetIfExists();
2440
0
    MOZ_RELEASE_ASSERT(manager); // Must exist while media thread is alive
2441
0
    // this is needed in case persistent permission is given but no gUM()
2442
0
    // or enumeration() has created the real backend yet
2443
0
    manager->GetBackend(0);
2444
0
    if (fakeDeviceChangeEventOn)
2445
0
      manager->GetBackend(0)->SetFakeDeviceChangeEvents();
2446
0
  }));
2447
0
2448
0
  return DeviceChangeCallback::AddDeviceChangeCallback(aCallback);
2449
0
}
2450
2451
0
void MediaManager::OnDeviceChange() {
2452
0
  RefPtr<MediaManager> self(this);
2453
0
  NS_DispatchToMainThread(media::NewRunnableFrom([self]() mutable {
2454
0
    MOZ_ASSERT(NS_IsMainThread());
2455
0
    if (sHasShutdown) {
2456
0
      return NS_OK;
2457
0
    }
2458
0
    self->DeviceChangeCallback::OnDeviceChange();
2459
0
2460
0
    // On some Windows machine, if we call EnumerateRawDevices immediately after receiving
2461
0
    // devicechange event, sometimes we would get outdated devices list.
2462
0
    PR_Sleep(PR_MillisecondsToInterval(100));
2463
0
    RefPtr<PledgeMediaDeviceSet> p = self->EnumerateRawDevices(0,
2464
0
                                                               MediaSourceEnum::Camera,
2465
0
                                                               MediaSourceEnum::Microphone,
2466
0
                                                               MediaSinkEnum::Speaker);
2467
0
    p->Then([self](MediaDeviceSet*& aDevices) mutable {
2468
0
      UniquePtr<MediaDeviceSet> devices(aDevices);
2469
0
      nsTArray<nsString> deviceIDs;
2470
0
2471
0
      for (auto& device : *devices) {
2472
0
        nsString id;
2473
0
        device->GetId(id);
2474
0
        id.ReplaceSubstring(NS_LITERAL_STRING("default: "), NS_LITERAL_STRING(""));
2475
0
        if (!deviceIDs.Contains(id)) {
2476
0
          deviceIDs.AppendElement(id);
2477
0
        }
2478
0
      }
2479
0
2480
0
      for (auto& id : self->mDeviceIDs) {
2481
0
        if (deviceIDs.Contains(id)) {
2482
0
          continue;
2483
0
        }
2484
0
2485
0
        // Stop the coresponding SourceListener
2486
0
        nsGlobalWindowInner::InnerWindowByIdTable* windowsById =
2487
0
          nsGlobalWindowInner::GetWindowsTable();
2488
0
        if (!windowsById) {
2489
0
          continue;
2490
0
        }
2491
0
2492
0
        for (auto iter = windowsById->Iter(); !iter.Done(); iter.Next()) {
2493
0
          nsGlobalWindowInner* window = iter.Data();
2494
0
          self->IterateWindowListeners(window->AsInner(),
2495
0
              [&id](GetUserMediaWindowListener* aListener)
2496
0
              {
2497
0
                aListener->StopRawID(id);
2498
0
              });
2499
0
        }
2500
0
      }
2501
0
2502
0
      self->mDeviceIDs = deviceIDs;
2503
0
    }, [](MediaStreamError*& reason) {});
2504
0
    return NS_OK;
2505
0
  }));
2506
0
}
2507
2508
nsresult MediaManager::GenerateUUID(nsAString& aResult)
2509
0
{
2510
0
  nsresult rv;
2511
0
  nsCOMPtr<nsIUUIDGenerator> uuidgen =
2512
0
      do_GetService("@mozilla.org/uuid-generator;1", &rv);
2513
0
  NS_ENSURE_SUCCESS(rv, rv);
2514
0
2515
0
  // Generate a call ID.
2516
0
  nsID id;
2517
0
  rv = uuidgen->GenerateUUIDInPlace(&id);
2518
0
  NS_ENSURE_SUCCESS(rv, rv);
2519
0
2520
0
  char buffer[NSID_LENGTH];
2521
0
  id.ToProvidedString(buffer);
2522
0
  aResult.Assign(NS_ConvertUTF8toUTF16(buffer));
2523
0
  return NS_OK;
2524
0
}
2525
2526
static bool IsFullyActive(nsPIDOMWindowInner* aWindow)
2527
0
{
2528
0
  while (true) {
2529
0
    if (!aWindow) {
2530
0
      return false;
2531
0
    }
2532
0
    nsIDocument* document = aWindow->GetExtantDoc();
2533
0
    if (!document) {
2534
0
      return false;
2535
0
    }
2536
0
    if (!document->IsCurrentActiveDocument()) {
2537
0
      return false;
2538
0
    }
2539
0
    nsPIDOMWindowOuter* context = aWindow->GetOuterWindow();
2540
0
    if (!context) {
2541
0
      return false;
2542
0
    }
2543
0
    if (context->IsTopLevelWindow()) {
2544
0
      return true;
2545
0
    }
2546
0
    nsCOMPtr<Element> frameElement =
2547
0
      nsGlobalWindowOuter::Cast(context)->GetRealFrameElementOuter();
2548
0
    if (!frameElement) {
2549
0
      return false;
2550
0
    }
2551
0
    aWindow = frameElement->OwnerDoc()->GetInnerWindow();
2552
0
  }
2553
0
}
2554
2555
enum class GetUserMediaSecurityState {
2556
  Other = 0,
2557
  HTTPS = 1,
2558
  File = 2,
2559
  App = 3,
2560
  Localhost = 4,
2561
  Loop = 5,
2562
  Privileged = 6
2563
};
2564
2565
/**
2566
 * This function is used in getUserMedia when privacy.resistFingerprinting is true.
2567
 * Only mediaSource of audio/video constraint will be kept.
2568
 */
2569
static void
2570
ReduceConstraint(
2571
0
    mozilla::dom::OwningBooleanOrMediaTrackConstraints& aConstraint) {
2572
0
  // Not requesting stream.
2573
0
  if (!IsOn(aConstraint)) {
2574
0
    return;
2575
0
  }
2576
0
2577
0
  // It looks like {audio: true}, do nothing.
2578
0
  if (!aConstraint.IsMediaTrackConstraints()) {
2579
0
    return;
2580
0
  }
2581
0
2582
0
  // Keep mediaSource, ignore all other constraints.
2583
0
  auto& c = aConstraint.GetAsMediaTrackConstraints();
2584
0
  nsString mediaSource = c.mMediaSource;
2585
0
  aConstraint.SetAsMediaTrackConstraints().mMediaSource = mediaSource;
2586
0
}
2587
2588
/**
2589
 * The entry point for this file. A call from Navigator::mozGetUserMedia
2590
 * will end up here. MediaManager is a singleton that is responsible
2591
 * for handling all incoming getUserMedia calls from every window.
2592
 */
2593
nsresult
2594
MediaManager::GetUserMedia(nsPIDOMWindowInner* aWindow,
2595
                           const MediaStreamConstraints& aConstraintsPassedIn,
2596
                           GetUserMediaSuccessCallback&& aOnSuccess,
2597
                           GetUserMediaErrorCallback&& aOnFailure,
2598
                           dom::CallerType aCallerType)
2599
0
{
2600
0
  MOZ_ASSERT(NS_IsMainThread());
2601
0
  MOZ_ASSERT(aWindow);
2602
0
  MOZ_ASSERT(aOnFailure.GetISupports());
2603
0
  MOZ_ASSERT(aOnSuccess.GetISupports());
2604
0
  nsMainThreadPtrHandle<GetUserMediaSuccessCallback> onSuccess(
2605
0
      new nsMainThreadPtrHolder<GetUserMediaSuccessCallback>(
2606
0
          "GetUserMedia::SuccessCallback", std::move(aOnSuccess)));
2607
0
  nsMainThreadPtrHandle<GetUserMediaErrorCallback> onFailure(
2608
0
      new nsMainThreadPtrHolder<GetUserMediaErrorCallback>(
2609
0
          "GetUserMedia::FailureCallback", std::move(aOnFailure)));
2610
0
  uint64_t windowID = aWindow->WindowID();
2611
0
2612
0
  MediaStreamConstraints c(aConstraintsPassedIn); // use a modifiable copy
2613
0
2614
0
  // Do all the validation we can while we're sync (to return an
2615
0
  // already-rejected promise on failure).
2616
0
2617
0
  if (!IsOn(c.mVideo) && !IsOn(c.mAudio)) {
2618
0
    RefPtr<MediaStreamError> error =
2619
0
        new MediaStreamError(aWindow,
2620
0
                             MediaStreamError::Name::TypeError,
2621
0
                             NS_LITERAL_STRING("audio and/or video is required"));
2622
0
    CallOnError(onFailure, *error);
2623
0
    return NS_OK;
2624
0
  }
2625
0
2626
0
  if (!IsFullyActive(aWindow)) {
2627
0
    RefPtr<MediaStreamError> error =
2628
0
        new MediaStreamError(aWindow, MediaStreamError::Name::InvalidStateError);
2629
0
    CallOnError(onFailure, *error);
2630
0
    return NS_OK;
2631
0
  }
2632
0
2633
0
  if (sHasShutdown) {
2634
0
    RefPtr<MediaStreamError> error =
2635
0
        new MediaStreamError(aWindow,
2636
0
                             MediaStreamError::Name::AbortError,
2637
0
                             NS_LITERAL_STRING("In shutdown"));
2638
0
    CallOnError(onFailure, *error);
2639
0
    return NS_OK;
2640
0
  }
2641
0
2642
0
  // Determine permissions early (while we still have a stack).
2643
0
2644
0
  nsIURI* docURI = aWindow->GetDocumentURI();
2645
0
  if (!docURI) {
2646
0
    return NS_ERROR_UNEXPECTED;
2647
0
  }
2648
0
  bool isChrome = (aCallerType == dom::CallerType::System);
2649
0
  bool privileged = isChrome ||
2650
0
      Preferences::GetBool("media.navigator.permission.disabled", false);
2651
0
  bool isHTTPS = false;
2652
0
  bool isHandlingUserInput = EventStateManager::IsHandlingUserInput();;
2653
0
  docURI->SchemeIs("https", &isHTTPS);
2654
0
  nsCString host;
2655
0
  nsresult rv = docURI->GetHost(host);
2656
0
  // Test for some other schemes that ServiceWorker recognizes
2657
0
  bool isFile;
2658
0
  docURI->SchemeIs("file", &isFile);
2659
0
  bool isApp;
2660
0
  docURI->SchemeIs("app", &isApp);
2661
0
  // Same localhost check as ServiceWorkers uses
2662
0
  // (see IsOriginPotentiallyTrustworthy())
2663
0
  bool isLocalhost = NS_SUCCEEDED(rv) &&
2664
0
                     (host.LowerCaseEqualsLiteral("localhost") ||
2665
0
                      host.LowerCaseEqualsLiteral("127.0.0.1") ||
2666
0
                      host.LowerCaseEqualsLiteral("::1"));
2667
0
2668
0
  // Record telemetry about whether the source of the call was secure, i.e.,
2669
0
  // privileged or HTTPS.  We may handle other cases
2670
0
  if (privileged) {
2671
0
    Telemetry::Accumulate(Telemetry::WEBRTC_GET_USER_MEDIA_SECURE_ORIGIN,
2672
0
                          (uint32_t) GetUserMediaSecurityState::Privileged);
2673
0
  } else if (isHTTPS) {
2674
0
    Telemetry::Accumulate(Telemetry::WEBRTC_GET_USER_MEDIA_SECURE_ORIGIN,
2675
0
                          (uint32_t) GetUserMediaSecurityState::HTTPS);
2676
0
  } else if (isFile) {
2677
0
    Telemetry::Accumulate(Telemetry::WEBRTC_GET_USER_MEDIA_SECURE_ORIGIN,
2678
0
                          (uint32_t) GetUserMediaSecurityState::File);
2679
0
  } else if (isApp) {
2680
0
    Telemetry::Accumulate(Telemetry::WEBRTC_GET_USER_MEDIA_SECURE_ORIGIN,
2681
0
                          (uint32_t) GetUserMediaSecurityState::App);
2682
0
  } else if (isLocalhost) {
2683
0
    Telemetry::Accumulate(Telemetry::WEBRTC_GET_USER_MEDIA_SECURE_ORIGIN,
2684
0
                          (uint32_t) GetUserMediaSecurityState::Localhost);
2685
0
  } else {
2686
0
    Telemetry::Accumulate(Telemetry::WEBRTC_GET_USER_MEDIA_SECURE_ORIGIN,
2687
0
                          (uint32_t) GetUserMediaSecurityState::Other);
2688
0
  }
2689
0
2690
0
  nsCOMPtr<nsIPrincipal> principal =
2691
0
    nsGlobalWindowInner::Cast(aWindow)->GetPrincipal();
2692
0
  if (NS_WARN_IF(!principal)) {
2693
0
    return NS_ERROR_FAILURE;
2694
0
  }
2695
0
2696
0
  // This principal needs to be sent to different threads and so via IPC.
2697
0
  // For this reason it's better to convert it to PrincipalInfo right now.
2698
0
  ipc::PrincipalInfo principalInfo;
2699
0
  rv = PrincipalToPrincipalInfo(principal, &principalInfo);
2700
0
  if (NS_WARN_IF(NS_FAILED(rv))) {
2701
0
    return rv;
2702
0
  }
2703
0
2704
0
  const bool resistFingerprinting = nsContentUtils::ResistFingerprinting(aCallerType);
2705
0
2706
0
  if (resistFingerprinting) {
2707
0
    ReduceConstraint(c.mVideo);
2708
0
    ReduceConstraint(c.mAudio);
2709
0
  }
2710
0
2711
0
  if (!Preferences::GetBool("media.navigator.video.enabled", true)) {
2712
0
    c.mVideo.SetAsBoolean() = false;
2713
0
  }
2714
0
2715
0
  MediaSourceEnum videoType = MediaSourceEnum::Other; // none
2716
0
  MediaSourceEnum audioType = MediaSourceEnum::Other; // none
2717
0
2718
0
  if (c.mVideo.IsMediaTrackConstraints()) {
2719
0
    auto& vc = c.mVideo.GetAsMediaTrackConstraints();
2720
0
    videoType = StringToEnum(dom::MediaSourceEnumValues::strings,
2721
0
                             vc.mMediaSource,
2722
0
                             MediaSourceEnum::Other);
2723
0
    Telemetry::Accumulate(Telemetry::WEBRTC_GET_USER_MEDIA_TYPE,
2724
0
                          (uint32_t) videoType);
2725
0
    switch (videoType) {
2726
0
      case MediaSourceEnum::Camera:
2727
0
        break;
2728
0
2729
0
      case MediaSourceEnum::Browser:
2730
0
        // If no window id is passed in then default to the caller's window.
2731
0
        // Functional defaults are helpful in tests, but also a natural outcome
2732
0
        // of the constraints API's limited semantics for requiring input.
2733
0
        if (!vc.mBrowserWindow.WasPassed()) {
2734
0
          nsPIDOMWindowOuter* outer = aWindow->GetOuterWindow();
2735
0
          vc.mBrowserWindow.Construct(outer->WindowID());
2736
0
        }
2737
0
        MOZ_FALLTHROUGH;
2738
0
      case MediaSourceEnum::Screen:
2739
0
      case MediaSourceEnum::Application:
2740
0
      case MediaSourceEnum::Window:
2741
0
        // Deny screensharing request if support is disabled, or
2742
0
        // the requesting document is not from a host on the whitelist.
2743
0
        if (!Preferences::GetBool(((videoType == MediaSourceEnum::Browser)?
2744
0
                                   "media.getusermedia.browser.enabled" :
2745
0
                                   "media.getusermedia.screensharing.enabled"),
2746
0
                                  false) ||
2747
0
            (!privileged && !aWindow->IsSecureContext())) {
2748
0
          RefPtr<MediaStreamError> error =
2749
0
              new MediaStreamError(aWindow,
2750
0
                                   MediaStreamError::Name::NotAllowedError);
2751
0
          CallOnError(onFailure, *error);
2752
0
          return NS_OK;
2753
0
        }
2754
0
        break;
2755
0
2756
0
      case MediaSourceEnum::Microphone:
2757
0
      case MediaSourceEnum::Other:
2758
0
      default: {
2759
0
        RefPtr<MediaStreamError> error =
2760
0
            new MediaStreamError(aWindow,
2761
0
                                 MediaStreamError::Name::OverconstrainedError,
2762
0
                                 NS_LITERAL_STRING(""),
2763
0
                                 NS_LITERAL_STRING("mediaSource"));
2764
0
        CallOnError(onFailure, *error);
2765
0
        return NS_OK;
2766
0
      }
2767
0
    }
2768
0
2769
0
    if (vc.mAdvanced.WasPassed() && videoType != MediaSourceEnum::Camera) {
2770
0
      // iterate through advanced, forcing all unset mediaSources to match "root"
2771
0
      const char *unset = EnumToASCII(dom::MediaSourceEnumValues::strings,
2772
0
                                      MediaSourceEnum::Camera);
2773
0
      for (MediaTrackConstraintSet& cs : vc.mAdvanced.Value()) {
2774
0
        if (cs.mMediaSource.EqualsASCII(unset)) {
2775
0
          cs.mMediaSource = vc.mMediaSource;
2776
0
        }
2777
0
      }
2778
0
    }
2779
0
    if (!privileged) {
2780
0
      // only allow privileged content to set the window id
2781
0
      if (vc.mBrowserWindow.WasPassed()) {
2782
0
        vc.mBrowserWindow.Value() = -1;
2783
0
      }
2784
0
      if (vc.mAdvanced.WasPassed()) {
2785
0
        for (MediaTrackConstraintSet& cs : vc.mAdvanced.Value()) {
2786
0
          if (cs.mBrowserWindow.WasPassed()) {
2787
0
            cs.mBrowserWindow.Value() = -1;
2788
0
          }
2789
0
        }
2790
0
      }
2791
0
    }
2792
0
  } else if (IsOn(c.mVideo)) {
2793
0
    videoType = MediaSourceEnum::Camera;
2794
0
    Telemetry::Accumulate(Telemetry::WEBRTC_GET_USER_MEDIA_TYPE,
2795
0
                          (uint32_t) videoType);
2796
0
  }
2797
0
2798
0
  if (c.mAudio.IsMediaTrackConstraints()) {
2799
0
    auto& ac = c.mAudio.GetAsMediaTrackConstraints();
2800
0
    MediaConstraintsHelper::ConvertOldWithWarning(ac.mMozAutoGainControl,
2801
0
                                                  ac.mAutoGainControl,
2802
0
                                                  "MozAutoGainControlWarning",
2803
0
                                                  aWindow);
2804
0
    MediaConstraintsHelper::ConvertOldWithWarning(ac.mMozNoiseSuppression,
2805
0
                                                  ac.mNoiseSuppression,
2806
0
                                                  "MozNoiseSuppressionWarning",
2807
0
                                                  aWindow);
2808
0
    audioType = StringToEnum(dom::MediaSourceEnumValues::strings,
2809
0
                             ac.mMediaSource,
2810
0
                             MediaSourceEnum::Other);
2811
0
    // Work around WebIDL default since spec uses same dictionary w/audio & video.
2812
0
    if (audioType == MediaSourceEnum::Camera) {
2813
0
      audioType = MediaSourceEnum::Microphone;
2814
0
      ac.mMediaSource.AssignASCII(EnumToASCII(dom::MediaSourceEnumValues::strings,
2815
0
                                              audioType));
2816
0
    }
2817
0
    Telemetry::Accumulate(Telemetry::WEBRTC_GET_USER_MEDIA_TYPE,
2818
0
                          (uint32_t) audioType);
2819
0
2820
0
    switch (audioType) {
2821
0
      case MediaSourceEnum::Microphone:
2822
0
        break;
2823
0
2824
0
      case MediaSourceEnum::AudioCapture:
2825
0
        // Only enable AudioCapture if the pref is enabled. If it's not, we can
2826
0
        // deny right away.
2827
0
        if (!Preferences::GetBool("media.getusermedia.audiocapture.enabled")) {
2828
0
          RefPtr<MediaStreamError> error =
2829
0
            new MediaStreamError(aWindow,
2830
0
                                 MediaStreamError::Name::NotAllowedError);
2831
0
          CallOnError(onFailure, *error);
2832
0
          return NS_OK;
2833
0
        }
2834
0
        break;
2835
0
2836
0
      case MediaSourceEnum::Other:
2837
0
      default: {
2838
0
        RefPtr<MediaStreamError> error =
2839
0
            new MediaStreamError(aWindow,
2840
0
                                 MediaStreamError::Name::OverconstrainedError,
2841
0
                                 NS_LITERAL_STRING(""),
2842
0
                                 NS_LITERAL_STRING("mediaSource"));
2843
0
        CallOnError(onFailure, *error);
2844
0
        return NS_OK;
2845
0
      }
2846
0
    }
2847
0
    if (ac.mAdvanced.WasPassed()) {
2848
0
      // iterate through advanced, forcing all unset mediaSources to match "root"
2849
0
      const char *unset = EnumToASCII(dom::MediaSourceEnumValues::strings,
2850
0
                                      MediaSourceEnum::Camera);
2851
0
      for (MediaTrackConstraintSet& cs : ac.mAdvanced.Value()) {
2852
0
        if (cs.mMediaSource.EqualsASCII(unset)) {
2853
0
          cs.mMediaSource = ac.mMediaSource;
2854
0
        }
2855
0
      }
2856
0
    }
2857
0
  } else if (IsOn(c.mAudio)) {
2858
0
    audioType = MediaSourceEnum::Microphone;
2859
0
    Telemetry::Accumulate(Telemetry::WEBRTC_GET_USER_MEDIA_TYPE,
2860
0
                          (uint32_t) audioType);
2861
0
  }
2862
0
2863
0
  // Create a window listener if it doesn't already exist.
2864
0
  RefPtr<GetUserMediaWindowListener> windowListener =
2865
0
    GetWindowListener(windowID);
2866
0
  if (windowListener) {
2867
0
    PrincipalHandle existingPrincipalHandle = windowListener->GetPrincipalHandle();
2868
0
    MOZ_ASSERT(PrincipalHandleMatches(existingPrincipalHandle, principal));
2869
0
  } else {
2870
0
    windowListener = new GetUserMediaWindowListener(mMediaThread, windowID,
2871
0
                                                    MakePrincipalHandle(principal));
2872
0
    AddWindowID(windowID, windowListener);
2873
0
  }
2874
0
2875
0
  RefPtr<SourceListener> sourceListener = new SourceListener();
2876
0
  windowListener->Register(sourceListener);
2877
0
2878
0
  if (!privileged) {
2879
0
    // Check if this site has had persistent permissions denied.
2880
0
    nsCOMPtr<nsIPermissionManager> permManager =
2881
0
      do_GetService(NS_PERMISSIONMANAGER_CONTRACTID, &rv);
2882
0
    NS_ENSURE_SUCCESS(rv, rv);
2883
0
2884
0
    uint32_t audioPerm = nsIPermissionManager::UNKNOWN_ACTION;
2885
0
    if (IsOn(c.mAudio)) {
2886
0
      if (audioType == MediaSourceEnum::Microphone &&
2887
0
          Preferences::GetBool("media.getusermedia.microphone.deny", false)) {
2888
0
        audioPerm = nsIPermissionManager::DENY_ACTION;
2889
0
      } else {
2890
0
        rv = permManager->TestExactPermissionFromPrincipal(
2891
0
          principal, "microphone", &audioPerm);
2892
0
        NS_ENSURE_SUCCESS(rv, rv);
2893
0
      }
2894
0
    }
2895
0
2896
0
    uint32_t videoPerm = nsIPermissionManager::UNKNOWN_ACTION;
2897
0
    if (IsOn(c.mVideo)) {
2898
0
      if (videoType == MediaSourceEnum::Camera &&
2899
0
          Preferences::GetBool("media.getusermedia.camera.deny", false)) {
2900
0
        videoPerm = nsIPermissionManager::DENY_ACTION;
2901
0
      } else {
2902
0
        rv = permManager->TestExactPermissionFromPrincipal(
2903
0
          principal, videoType == MediaSourceEnum::Camera ? "camera" : "screen",
2904
0
          &videoPerm);
2905
0
        NS_ENSURE_SUCCESS(rv, rv);
2906
0
      }
2907
0
    }
2908
0
2909
0
    if ((!IsOn(c.mAudio) && !IsOn(c.mVideo)) ||
2910
0
        (IsOn(c.mAudio) && audioPerm == nsIPermissionManager::DENY_ACTION) ||
2911
0
        (IsOn(c.mVideo) && videoPerm == nsIPermissionManager::DENY_ACTION)) {
2912
0
      RefPtr<MediaStreamError> error =
2913
0
          new MediaStreamError(aWindow, MediaStreamError::Name::NotAllowedError);
2914
0
      CallOnError(onFailure, *error);
2915
0
      windowListener->Remove(sourceListener);
2916
0
      return NS_OK;
2917
0
    }
2918
0
  }
2919
0
2920
0
  // Get list of all devices, with origin-specific device ids.
2921
0
2922
0
  MediaEnginePrefs prefs = mPrefs;
2923
0
2924
0
  nsString callID;
2925
0
  rv = GenerateUUID(callID);
2926
0
  NS_ENSURE_SUCCESS(rv, rv);
2927
0
2928
0
  bool hasVideo = videoType != MediaSourceEnum::Other;
2929
0
  bool hasAudio = audioType != MediaSourceEnum::Other;
2930
0
  DeviceEnumerationType videoEnumerationType = DeviceEnumerationType::Normal;
2931
0
  DeviceEnumerationType audioEnumerationType = DeviceEnumerationType::Normal;
2932
0
2933
0
  // Handle loopback and fake requests. For gUM we don't consider resist
2934
0
  // fingerprinting as users should be prompted anyway.
2935
0
  bool wantFakes = c.mFake.WasPassed() ? c.mFake.Value() :
2936
0
    Preferences::GetBool("media.navigator.streams.fake");
2937
0
  nsAutoCString videoLoopDev, audioLoopDev;
2938
0
  // Video
2939
0
  if (videoType == MediaSourceEnum::Camera) {
2940
0
    Preferences::GetCString("media.video_loopback_dev", videoLoopDev);
2941
0
    // Loopback prefs take precedence over fake prefs
2942
0
    if (!videoLoopDev.IsEmpty()) {
2943
0
      videoEnumerationType = DeviceEnumerationType::Loopback;
2944
0
    } else if (wantFakes) {
2945
0
      videoEnumerationType = DeviceEnumerationType::Fake;
2946
0
    }
2947
0
  }
2948
0
  // Audio
2949
0
  if (audioType == MediaSourceEnum::Microphone) {
2950
0
    Preferences::GetCString("media.audio_loopback_dev", audioLoopDev);
2951
0
    // Loopback prefs take precedence over fake prefs
2952
0
    if (!audioLoopDev.IsEmpty()) {
2953
0
      audioEnumerationType = DeviceEnumerationType::Loopback;
2954
0
    } else if (wantFakes) {
2955
0
      audioEnumerationType = DeviceEnumerationType::Fake;
2956
0
    }
2957
0
  }
2958
0
2959
0
  bool realDevicesRequested = (videoEnumerationType != DeviceEnumerationType::Fake && hasVideo) ||
2960
0
                              (audioEnumerationType != DeviceEnumerationType::Fake && hasAudio);
2961
0
  bool askPermission =
2962
0
    (!privileged || Preferences::GetBool("media.navigator.permission.force")) &&
2963
0
    (realDevicesRequested || Preferences::GetBool("media.navigator.permission.fake"));
2964
0
2965
0
  LOG(("%s: Preparing to enumerate devices. windowId=%" PRIu64
2966
0
       ", videoType=%" PRIu8 ", audioType=%" PRIu8
2967
0
       ", videoEnumerationType=%" PRIu8 ", audioEnumerationType=%" PRIu8
2968
0
       ", askPermission=%s",
2969
0
       __func__, windowID,
2970
0
       static_cast<uint8_t>(videoType), static_cast<uint8_t>(audioType),
2971
0
       static_cast<uint8_t>(videoEnumerationType), static_cast<uint8_t>(audioEnumerationType),
2972
0
       askPermission ? "true" : "false"));
2973
0
2974
0
  RefPtr<PledgeMediaDeviceSet> p = EnumerateDevicesImpl(windowID,
2975
0
                                                        videoType,
2976
0
                                                        audioType,
2977
0
                                                        MediaSinkEnum::Other,
2978
0
                                                        videoEnumerationType,
2979
0
                                                        audioEnumerationType);
2980
0
  RefPtr<MediaManager> self = this;
2981
0
  p->Then([self, onSuccess, onFailure, windowID, c, windowListener,
2982
0
           sourceListener, askPermission, prefs, isHTTPS, isHandlingUserInput,
2983
0
           callID, principalInfo, isChrome, resistFingerprinting](MediaDeviceSet*& aDevices) mutable {
2984
0
    LOG(("GetUserMedia: post enumeration pledge success callback starting"));
2985
0
    // grab result
2986
0
    auto devices = MakeRefPtr<Refcountable<UniquePtr<MediaDeviceSet>>>(aDevices);
2987
0
2988
0
    // Ensure that our windowID is still good.
2989
0
    if (!nsGlobalWindowInner::GetInnerWindowWithId(windowID) ||
2990
0
        !self->IsWindowListenerStillActive(windowListener)) {
2991
0
      LOG(("GetUserMedia: bad window (%" PRIu64 ") in post enumeration "
2992
0
           "success callback!", windowID));
2993
0
      return;
2994
0
    }
2995
0
2996
0
    // Apply any constraints. This modifies the passed-in list.
2997
0
    RefPtr<PledgeChar> p2 = self->SelectSettings(c, isChrome, devices);
2998
0
2999
0
    p2->Then([self, onSuccess, onFailure, windowID, c,
3000
0
              windowListener, sourceListener, askPermission, prefs, isHTTPS,
3001
0
              isHandlingUserInput, callID, principalInfo, isChrome, devices,
3002
0
              resistFingerprinting
3003
0
             ](const char*& badConstraint) mutable {
3004
0
      LOG(("GetUserMedia: starting post enumeration pledge2 success "
3005
0
           "callback!"));
3006
0
3007
0
      // Ensure that the window is still good.
3008
0
      auto* globalWindow = nsGlobalWindowInner::GetInnerWindowWithId(windowID);
3009
0
      RefPtr<nsPIDOMWindowInner> window = globalWindow ? globalWindow->AsInner()
3010
0
                                                       : nullptr;
3011
0
      if (!window || !self->IsWindowListenerStillActive(windowListener)) {
3012
0
        LOG(("GetUserMedia: bad window (%" PRIu64 ") in post enumeration "
3013
0
             "success callback 2!", windowID));
3014
0
        return;
3015
0
      }
3016
0
3017
0
      if (badConstraint) {
3018
0
        LOG(("GetUserMedia: bad constraint found in post enumeration pledge2 "
3019
0
             "success callback! Calling error handler!"));
3020
0
        nsString constraint;
3021
0
        constraint.AssignASCII(badConstraint);
3022
0
        RefPtr<MediaStreamError> error =
3023
0
            new MediaStreamError(window,
3024
0
                                 MediaStreamError::Name::OverconstrainedError,
3025
0
                                 NS_LITERAL_STRING(""),
3026
0
                                 constraint);
3027
0
        CallOnError(onFailure, *error);
3028
0
        return;
3029
0
      }
3030
0
      if (!(*devices)->Length()) {
3031
0
        LOG(("GetUserMedia: no devices found in post enumeration pledge2 "
3032
0
             "success callback! Calling error handler!"));
3033
0
        RefPtr<MediaStreamError> error =
3034
0
            new MediaStreamError(
3035
0
                window,
3036
0
                // When privacy.resistFingerprinting = true, no available
3037
0
                // device implies content script is requesting a fake
3038
0
                // device, so report NotAllowedError.
3039
0
                resistFingerprinting ? MediaStreamError::Name::NotAllowedError
3040
0
                                     : MediaStreamError::Name::NotFoundError);
3041
0
        CallOnError(onFailure, *error);
3042
0
        return;
3043
0
      }
3044
0
3045
0
      nsCOMPtr<nsIMutableArray> devicesCopy = nsArray::Create(); // before we give up devices below
3046
0
      if (!askPermission) {
3047
0
        for (auto& device : **devices) {
3048
0
          nsresult rv = devicesCopy->AppendElement(device);
3049
0
          if (NS_WARN_IF(NS_FAILED(rv))) {
3050
0
            return;
3051
0
          }
3052
0
        }
3053
0
      }
3054
0
3055
0
      bool focusSource;
3056
0
      focusSource = mozilla::Preferences::GetBool("media.getusermedia.window.focus_source.enabled", true);
3057
0
3058
0
      // Pass callbacks and listeners along to GetUserMediaTask.
3059
0
      RefPtr<GetUserMediaTask> task (new GetUserMediaTask(c,
3060
0
                                                          onSuccess,
3061
0
                                                          onFailure,
3062
0
                                                          windowID,
3063
0
                                                          windowListener,
3064
0
                                                          sourceListener,
3065
0
                                                          prefs,
3066
0
                                                          principalInfo,
3067
0
                                                          isChrome,
3068
0
                                                          devices->release(),
3069
0
                                                          focusSource));
3070
0
      // Store the task w/callbacks.
3071
0
      self->mActiveCallbacks.Put(callID, task.forget());
3072
0
3073
0
      // Add a WindowID cross-reference so OnNavigation can tear things down
3074
0
      nsTArray<nsString>* array;
3075
0
      if (!self->mCallIds.Get(windowID, &array)) {
3076
0
        array = new nsTArray<nsString>();
3077
0
        self->mCallIds.Put(windowID, array);
3078
0
      }
3079
0
      array->AppendElement(callID);
3080
0
3081
0
      nsCOMPtr<nsIObserverService> obs = services::GetObserverService();
3082
0
      if (!askPermission) {
3083
0
        obs->NotifyObservers(devicesCopy, "getUserMedia:privileged:allow",
3084
0
                             callID.BeginReading());
3085
0
      } else {
3086
0
        RefPtr<GetUserMediaRequest> req =
3087
0
            new GetUserMediaRequest(window, callID, c, isHTTPS, isHandlingUserInput);
3088
0
        if (!Preferences::GetBool("media.navigator.permission.force") && array->Length() > 1) {
3089
0
          // there is at least 1 pending gUM request
3090
0
          // For the scarySources test case, always send the request
3091
0
          self->mPendingGUMRequest.AppendElement(req.forget());
3092
0
        } else {
3093
0
          obs->NotifyObservers(req, "getUserMedia:request", nullptr);
3094
0
        }
3095
0
      }
3096
0
3097
0
#ifdef MOZ_WEBRTC
3098
0
      EnableWebRtcLog();
3099
0
#endif
3100
0
    }, [onFailure](MediaStreamError*& reason) mutable {
3101
0
      LOG(("GetUserMedia: post enumeration pledge2 failure callback called!"));
3102
0
      CallOnError(onFailure, *reason);
3103
0
    });
3104
0
  }, [onFailure](MediaStreamError*& reason) mutable {
3105
0
    LOG(("GetUserMedia: post enumeration pledge failure callback called!"));
3106
0
    CallOnError(onFailure, *reason);
3107
0
  });
3108
0
  return NS_OK;
3109
0
}
3110
3111
/* static */ void
3112
MediaManager::AnonymizeDevices(MediaDeviceSet& aDevices, const nsACString& aOriginKey)
3113
0
{
3114
0
  if (!aOriginKey.IsEmpty()) {
3115
0
    for (RefPtr<MediaDevice>& device : aDevices) {
3116
0
      nsString id;
3117
0
      device->GetId(id);
3118
0
      nsString rawId(id);
3119
0
      AnonymizeId(id, aOriginKey);
3120
0
      device = new MediaDevice(device, id, rawId);
3121
0
    }
3122
0
  }
3123
0
}
3124
3125
/* static */ nsresult
3126
MediaManager::AnonymizeId(nsAString& aId, const nsACString& aOriginKey)
3127
0
{
3128
0
  MOZ_ASSERT(NS_IsMainThread());
3129
0
3130
0
  nsresult rv;
3131
0
  nsCOMPtr<nsIKeyObjectFactory> factory =
3132
0
    do_GetService("@mozilla.org/security/keyobjectfactory;1", &rv);
3133
0
  if (NS_FAILED(rv)) {
3134
0
    return rv;
3135
0
  }
3136
0
  nsCString rawKey;
3137
0
  rv = Base64Decode(aOriginKey, rawKey);
3138
0
  if (NS_FAILED(rv)) {
3139
0
    return rv;
3140
0
  }
3141
0
  nsCOMPtr<nsIKeyObject> key;
3142
0
  rv = factory->KeyFromString(nsIKeyObject::HMAC, rawKey, getter_AddRefs(key));
3143
0
  if (NS_FAILED(rv)) {
3144
0
    return rv;
3145
0
  }
3146
0
3147
0
  nsCOMPtr<nsICryptoHMAC> hasher =
3148
0
    do_CreateInstance(NS_CRYPTO_HMAC_CONTRACTID, &rv);
3149
0
  if (NS_FAILED(rv)) {
3150
0
    return rv;
3151
0
  }
3152
0
  rv = hasher->Init(nsICryptoHMAC::SHA256, key);
3153
0
  if (NS_FAILED(rv)) {
3154
0
    return rv;
3155
0
  }
3156
0
  NS_ConvertUTF16toUTF8 id(aId);
3157
0
  rv = hasher->Update(reinterpret_cast<const uint8_t*> (id.get()), id.Length());
3158
0
  if (NS_FAILED(rv)) {
3159
0
    return rv;
3160
0
  }
3161
0
  nsCString mac;
3162
0
  rv = hasher->Finish(true, mac);
3163
0
  if (NS_FAILED(rv)) {
3164
0
    return rv;
3165
0
  }
3166
0
3167
0
  aId = NS_ConvertUTF8toUTF16(mac);
3168
0
  return NS_OK;
3169
0
}
3170
3171
/* static */
3172
already_AddRefed<nsIWritableVariant>
3173
MediaManager::ToJSArray(MediaDeviceSet& aDevices)
3174
0
{
3175
0
  MOZ_ASSERT(NS_IsMainThread());
3176
0
  RefPtr<nsVariantCC> var = new nsVariantCC();
3177
0
  size_t len = aDevices.Length();
3178
0
  if (len) {
3179
0
    nsTArray<nsIMediaDevice*> tmp(len);
3180
0
    for (auto& device : aDevices) {
3181
0
      tmp.AppendElement(device);
3182
0
    }
3183
0
    auto* elements = static_cast<const void*>(tmp.Elements());
3184
0
    nsresult rv = var->SetAsArray(nsIDataType::VTYPE_INTERFACE,
3185
0
                                  &NS_GET_IID(nsIMediaDevice), len,
3186
0
                                  const_cast<void*>(elements));
3187
0
    if (NS_WARN_IF(NS_FAILED(rv))) {
3188
0
      return nullptr;
3189
0
    }
3190
0
  } else {
3191
0
    var->SetAsEmptyArray(); // because SetAsArray() fails on zero length arrays.
3192
0
  }
3193
0
  return var.forget();
3194
0
}
3195
3196
already_AddRefed<MediaManager::PledgeMediaDeviceSet>
3197
MediaManager::EnumerateDevicesImpl(uint64_t aWindowId,
3198
                                   MediaSourceEnum       aVideoInputType,
3199
                                   MediaSourceEnum       aAudioInputType,
3200
                                   MediaSinkEnum  aAudioOutputType,
3201
                                   DeviceEnumerationType aVideoInputEnumType,
3202
                                   DeviceEnumerationType aAudioInputEnumType)
3203
0
{
3204
0
  MOZ_ASSERT(NS_IsMainThread());
3205
0
3206
0
  LOG(("%s: aWindowId=%" PRIu64 ", aVideoInputType=%" PRIu8 ", aAudioInputType=%" PRIu8
3207
0
       ", aVideoInputEnumType=%" PRIu8 ", aAudioInputEnumType=%" PRIu8,
3208
0
       __func__, aWindowId,
3209
0
       static_cast<uint8_t>(aVideoInputType), static_cast<uint8_t>(aAudioInputType),
3210
0
       static_cast<uint8_t>(aVideoInputEnumType), static_cast<uint8_t>(aAudioInputEnumType)));
3211
0
  nsPIDOMWindowInner* window =
3212
0
    nsGlobalWindowInner::GetInnerWindowWithId(aWindowId)->AsInner();
3213
0
3214
0
  // This function returns a pledge, a promise-like object with the future result
3215
0
  RefPtr<PledgeMediaDeviceSet> pledge = new PledgeMediaDeviceSet();
3216
0
  uint32_t id = mOutstandingPledges.Append(*pledge);
3217
0
3218
0
  // To get a device list anonymized for a particular origin, we must:
3219
0
  // 1. Get an origin-key (for either regular or private browsing)
3220
0
  // 2. Get the raw devices list
3221
0
  // 3. Anonymize the raw list with the origin-key.
3222
0
3223
0
  nsCOMPtr<nsIPrincipal> principal =
3224
0
    nsGlobalWindowInner::Cast(window)->GetPrincipal();
3225
0
  MOZ_ASSERT(principal);
3226
0
3227
0
  ipc::PrincipalInfo principalInfo;
3228
0
  nsresult rv = PrincipalToPrincipalInfo(principal, &principalInfo);
3229
0
  if (NS_WARN_IF(NS_FAILED(rv))) {
3230
0
    RefPtr<PledgeMediaDeviceSet> p = new PledgeMediaDeviceSet();
3231
0
    RefPtr<MediaStreamError> error =
3232
0
      new MediaStreamError(window, MediaStreamError::Name::NotAllowedError);
3233
0
    p->Reject(error);
3234
0
    return p.forget();
3235
0
  }
3236
0
3237
0
  bool persist = IsActivelyCapturingOrHasAPermission(aWindowId);
3238
0
3239
0
  // GetPrincipalKey is an async API that returns a pledge (a promise-like
3240
0
  // pattern). We use .Then() to pass in a lambda to run back on this same
3241
0
  // thread later once GetPrincipalKey resolves. Needed variables are "captured"
3242
0
  // (passed by value) safely into the lambda.
3243
0
3244
0
  RefPtr<Pledge<nsCString>> p = media::GetPrincipalKey(principalInfo, persist);
3245
0
  p->Then([id, aWindowId, aVideoInputType, aAudioInputType,
3246
0
          aVideoInputEnumType, aAudioInputEnumType, aAudioOutputType](const nsCString& aOriginKey) mutable {
3247
0
    MOZ_ASSERT(NS_IsMainThread());
3248
0
    MediaManager* mgr = MediaManager::GetIfExists();
3249
0
    if (!mgr) {
3250
0
      return;
3251
0
    }
3252
0
3253
0
    RefPtr<PledgeMediaDeviceSet> p = mgr->EnumerateRawDevices(aWindowId,
3254
0
                                                              aVideoInputType,
3255
0
                                                              aAudioInputType,
3256
0
                                                              aAudioOutputType,
3257
0
                                                              aVideoInputEnumType,
3258
0
                                                              aAudioInputEnumType);
3259
0
    p->Then([id,
3260
0
             aWindowId,
3261
0
             aOriginKey,
3262
0
             aVideoInputEnumType,
3263
0
             aAudioInputEnumType,
3264
0
             aVideoInputType,
3265
0
             aAudioInputType](MediaDeviceSet*& aDevices) mutable {
3266
0
      UniquePtr<MediaDeviceSet> devices(aDevices); // secondary result
3267
0
3268
0
      // Only run if window is still on our active list.
3269
0
      MediaManager* mgr = MediaManager::GetIfExists();
3270
0
      if (!mgr) {
3271
0
        return NS_OK;
3272
0
      }
3273
0
3274
0
      // If we fetched any real cameras or mics, remove the "default" part of
3275
0
      // their IDs.
3276
0
      if (aVideoInputType == MediaSourceEnum::Camera &&
3277
0
          aAudioInputType == MediaSourceEnum::Microphone &&
3278
0
          (aVideoInputEnumType != DeviceEnumerationType::Fake ||
3279
0
           aAudioInputEnumType != DeviceEnumerationType::Fake)) {
3280
0
        mgr->mDeviceIDs.Clear();
3281
0
        for (auto& device : *devices) {
3282
0
          nsString id;
3283
0
          device->GetId(id);
3284
0
          id.ReplaceSubstring(NS_LITERAL_STRING("default: "), NS_LITERAL_STRING(""));
3285
0
          if (!mgr->mDeviceIDs.Contains(id)) {
3286
0
            mgr->mDeviceIDs.AppendElement(id);
3287
0
          }
3288
0
        }
3289
0
      }
3290
0
3291
0
      RefPtr<PledgeMediaDeviceSet> p = mgr->mOutstandingPledges.Remove(id);
3292
0
      if (!p || !mgr->IsWindowStillActive(aWindowId)) {
3293
0
        return NS_OK;
3294
0
      }
3295
0
      MediaManager_AnonymizeDevices(*devices, aOriginKey);
3296
0
      p->Resolve(devices.release());
3297
0
      return NS_OK;
3298
0
    });
3299
0
  });
3300
0
  return pledge.forget();
3301
0
}
3302
3303
nsresult
3304
MediaManager::EnumerateDevices(nsPIDOMWindowInner* aWindow,
3305
                               nsIGetUserMediaDevicesSuccessCallback* aOnSuccess,
3306
                               nsIDOMGetUserMediaErrorCallback* aOnFailure,
3307
                               dom::CallerType aCallerType)
3308
0
{
3309
0
  MOZ_ASSERT(NS_IsMainThread());
3310
0
  NS_ENSURE_TRUE(!sHasShutdown, NS_ERROR_FAILURE);
3311
0
  nsCOMPtr<nsIGetUserMediaDevicesSuccessCallback> onSuccess(aOnSuccess);
3312
0
  nsCOMPtr<nsIDOMGetUserMediaErrorCallback> onFailure(aOnFailure);
3313
0
  uint64_t windowId = aWindow->WindowID();
3314
0
3315
0
  nsIPrincipal* principal = aWindow->GetExtantDoc()->NodePrincipal();
3316
0
3317
0
  RefPtr<GetUserMediaWindowListener> windowListener =
3318
0
    GetWindowListener(windowId);
3319
0
  if (windowListener) {
3320
0
    PrincipalHandle existingPrincipalHandle =
3321
0
      windowListener->GetPrincipalHandle();
3322
0
    MOZ_ASSERT(PrincipalHandleMatches(existingPrincipalHandle, principal));
3323
0
  } else {
3324
0
    windowListener = new GetUserMediaWindowListener(mMediaThread, windowId,
3325
0
                                                    MakePrincipalHandle(principal));
3326
0
    AddWindowID(windowId, windowListener);
3327
0
  }
3328
0
3329
0
  // Create an inactive SourceListener to act as a placeholder, so the
3330
0
  // window listener doesn't clean itself up until we're done.
3331
0
  RefPtr<SourceListener> sourceListener = new SourceListener();
3332
0
  windowListener->Register(sourceListener);
3333
0
3334
0
  DeviceEnumerationType videoEnumerationType = DeviceEnumerationType::Normal;
3335
0
  DeviceEnumerationType audioEnumerationType = DeviceEnumerationType::Normal;
3336
0
  bool resistFingerprinting = nsContentUtils::ResistFingerprinting(aCallerType);
3337
0
3338
0
  // In order of precedence: resist fingerprinting > loopback > fake pref
3339
0
  if (resistFingerprinting) {
3340
0
    videoEnumerationType = DeviceEnumerationType::Fake;
3341
0
    audioEnumerationType = DeviceEnumerationType::Fake;
3342
0
  } else {
3343
0
    // Handle loopback and fake requests
3344
0
    nsAutoCString videoLoopDev, audioLoopDev;
3345
0
    bool wantFakes = Preferences::GetBool("media.navigator.streams.fake");
3346
0
    // Video
3347
0
    Preferences::GetCString("media.video_loopback_dev", videoLoopDev);
3348
0
    // Loopback prefs take precedence over fake prefs
3349
0
    if (!videoLoopDev.IsEmpty()) {
3350
0
      videoEnumerationType = DeviceEnumerationType::Loopback;
3351
0
    } else if (wantFakes) {
3352
0
      videoEnumerationType = DeviceEnumerationType::Fake;
3353
0
    }
3354
0
    // Audio
3355
0
    Preferences::GetCString("media.audio_loopback_dev", audioLoopDev);
3356
0
    // Loopback prefs take precedence over fake prefs
3357
0
    if (!audioLoopDev.IsEmpty()) {
3358
0
      audioEnumerationType = DeviceEnumerationType::Loopback;
3359
0
    } else if (wantFakes) {
3360
0
      audioEnumerationType = DeviceEnumerationType::Fake;
3361
0
    }
3362
0
  }
3363
0
3364
0
  MediaSinkEnum audioOutputType = MediaSinkEnum::Other;
3365
0
  if (Preferences::GetBool("media.setsinkid.enabled")) {
3366
0
    audioOutputType = MediaSinkEnum::Speaker;
3367
0
  }
3368
0
  RefPtr<PledgeMediaDeviceSet> p = EnumerateDevicesImpl(windowId,
3369
0
                                                        MediaSourceEnum::Camera,
3370
0
                                                        MediaSourceEnum::Microphone,
3371
0
                                                        audioOutputType,
3372
0
                                                        videoEnumerationType,
3373
0
                                                        audioEnumerationType);
3374
0
  p->Then([onSuccess, windowListener, sourceListener](MediaDeviceSet*& aDevices) mutable {
3375
0
    UniquePtr<MediaDeviceSet> devices(aDevices); // grab result
3376
0
    DebugOnly<bool> rv = windowListener->Remove(sourceListener);
3377
0
    MOZ_ASSERT(rv);
3378
0
    nsCOMPtr<nsIWritableVariant> array = MediaManager_ToJSArray(*devices);
3379
0
    onSuccess->OnSuccess(array);
3380
0
  }, [onFailure, windowListener, sourceListener](MediaStreamError*& reason) mutable {
3381
0
    DebugOnly<bool> rv = windowListener->Remove(sourceListener);
3382
0
    MOZ_ASSERT(rv);
3383
0
    onFailure->OnError(reason);
3384
0
  });
3385
0
  return NS_OK;
3386
0
}
3387
3388
/*
3389
 * GetUserMediaDevices - called by the UI-part of getUserMedia from chrome JS.
3390
 */
3391
3392
nsresult
3393
MediaManager::GetUserMediaDevices(nsPIDOMWindowInner* aWindow,
3394
                                  const MediaStreamConstraints& aConstraints,
3395
                                  dom::MozGetUserMediaDevicesSuccessCallback& aOnSuccess,
3396
                                  uint64_t aWindowId,
3397
                                  const nsAString& aCallID)
3398
0
{
3399
0
  MOZ_ASSERT(NS_IsMainThread());
3400
0
  if (!aWindowId) {
3401
0
    aWindowId = aWindow->WindowID();
3402
0
  }
3403
0
3404
0
  // Ignore passed-in constraints, instead locate + return already-constrained list.
3405
0
3406
0
  nsTArray<nsString>* callIDs;
3407
0
  if (!mCallIds.Get(aWindowId, &callIDs)) {
3408
0
    return NS_ERROR_UNEXPECTED;
3409
0
  }
3410
0
3411
0
  for (auto& callID : *callIDs) {
3412
0
    RefPtr<GetUserMediaTask> task;
3413
0
    if (!aCallID.Length() || aCallID == callID) {
3414
0
      if (mActiveCallbacks.Get(callID, getter_AddRefs(task))) {
3415
0
        nsCOMPtr<nsIWritableVariant> array = MediaManager_ToJSArray(*task->mMediaDeviceSet);
3416
0
        aOnSuccess.Call(array);
3417
0
        return NS_OK;
3418
0
      }
3419
0
    }
3420
0
  }
3421
0
  return NS_ERROR_UNEXPECTED;
3422
0
}
3423
3424
MediaEngine*
3425
MediaManager::GetBackend(uint64_t aWindowId)
3426
0
{
3427
0
  MOZ_ASSERT(MediaManager::IsInMediaThread());
3428
0
  // Plugin backends as appropriate. The default engine also currently
3429
0
  // includes picture support for Android.
3430
0
  // This IS called off main-thread.
3431
0
  if (!mBackend) {
3432
0
    MOZ_RELEASE_ASSERT(!sHasShutdown);  // we should never create a new backend in shutdown
3433
0
#if defined(MOZ_WEBRTC)
3434
0
    mBackend = new MediaEngineWebRTC(mPrefs);
3435
#else
3436
    mBackend = new MediaEngineDefault();
3437
#endif
3438
    mBackend->AddDeviceChangeCallback(this);
3439
0
  }
3440
0
  return mBackend;
3441
0
}
3442
3443
void
3444
MediaManager::OnNavigation(uint64_t aWindowID)
3445
0
{
3446
0
  MOZ_ASSERT(NS_IsMainThread());
3447
0
  LOG(("OnNavigation for %" PRIu64, aWindowID));
3448
0
3449
0
  // Stop the streams for this window. The runnables check this value before
3450
0
  // making a call to content.
3451
0
3452
0
  nsTArray<nsString>* callIDs;
3453
0
  if (mCallIds.Get(aWindowID, &callIDs)) {
3454
0
    for (auto& callID : *callIDs) {
3455
0
      mActiveCallbacks.Remove(callID);
3456
0
    }
3457
0
    mCallIds.Remove(aWindowID);
3458
0
  }
3459
0
3460
0
  // This is safe since we're on main-thread, and the windowlist can only
3461
0
  // be added to from the main-thread
3462
0
  auto* window = nsGlobalWindowInner::GetInnerWindowWithId(aWindowID);
3463
0
  if (window) {
3464
0
    IterateWindowListeners(window->AsInner(),
3465
0
        [self = RefPtr<MediaManager>(this),
3466
0
         windowID = DebugOnly<decltype(aWindowID)>(aWindowID)]
3467
0
        (GetUserMediaWindowListener* aListener)
3468
0
        {
3469
0
          // Grab a strong ref since RemoveAll() might destroy the listener
3470
0
          // mid-way when clearing the mActiveWindows reference.
3471
0
          RefPtr<GetUserMediaWindowListener> listener(aListener);
3472
0
3473
0
          listener->Stop();
3474
0
          listener->RemoveAll();
3475
0
          MOZ_ASSERT(!self->GetWindowListener(windowID));
3476
0
        });
3477
0
  } else {
3478
0
    RemoveWindowID(aWindowID);
3479
0
  }
3480
0
  MOZ_ASSERT(!GetWindowListener(aWindowID));
3481
0
3482
0
  RemoveMediaDevicesCallback(aWindowID);
3483
0
3484
0
  RefPtr<MediaManager> self = this;
3485
0
  MediaManager::PostTask(NewTaskFrom([self, aWindowID]() {
3486
0
    self->GetBackend()->ReleaseResourcesForWindow(aWindowID);
3487
0
  }));
3488
0
}
3489
3490
void
3491
MediaManager::RemoveMediaDevicesCallback(uint64_t aWindowID)
3492
0
{
3493
0
  MutexAutoLock lock(mCallbackMutex);
3494
0
  for (DeviceChangeCallback* observer : mDeviceChangeCallbackList)
3495
0
  {
3496
0
    dom::MediaDevices* mediadevices = static_cast<dom::MediaDevices *>(observer);
3497
0
    MOZ_ASSERT(mediadevices);
3498
0
    if (mediadevices) {
3499
0
      nsPIDOMWindowInner* window = mediadevices->GetOwner();
3500
0
      MOZ_ASSERT(window);
3501
0
      if (window && window->WindowID() == aWindowID) {
3502
0
        DeviceChangeCallback::RemoveDeviceChangeCallbackLocked(observer);
3503
0
        return;
3504
0
      }
3505
0
    }
3506
0
  }
3507
0
}
3508
3509
void
3510
MediaManager::AddWindowID(uint64_t aWindowId,
3511
                          GetUserMediaWindowListener* aListener)
3512
0
{
3513
0
  MOZ_ASSERT(NS_IsMainThread());
3514
0
  // Store the WindowID in a hash table and mark as active. The entry is removed
3515
0
  // when this window is closed or navigated away from.
3516
0
  // This is safe since we're on main-thread, and the windowlist can only
3517
0
  // be invalidated from the main-thread (see OnNavigation)
3518
0
  if (IsWindowStillActive(aWindowId)) {
3519
0
    MOZ_ASSERT(false, "Window already added");
3520
0
    return;
3521
0
  }
3522
0
3523
0
  GetActiveWindows()->Put(aWindowId, aListener);
3524
0
}
3525
3526
void
3527
MediaManager::RemoveWindowID(uint64_t aWindowId)
3528
0
{
3529
0
  mActiveWindows.Remove(aWindowId);
3530
0
3531
0
  // get outer windowID
3532
0
  auto* window = nsGlobalWindowInner::GetInnerWindowWithId(aWindowId);
3533
0
  if (!window) {
3534
0
    LOG(("No inner window for %" PRIu64, aWindowId));
3535
0
    return;
3536
0
  }
3537
0
3538
0
  nsPIDOMWindowOuter* outer = window->AsInner()->GetOuterWindow();
3539
0
  if (!outer) {
3540
0
    LOG(("No outer window for inner %" PRIu64, aWindowId));
3541
0
    return;
3542
0
  }
3543
0
3544
0
  uint64_t outerID = outer->WindowID();
3545
0
3546
0
  // Notify the UI that this window no longer has gUM active
3547
0
  char windowBuffer[32];
3548
0
  SprintfLiteral(windowBuffer, "%" PRIu64, outerID);
3549
0
  nsString data = NS_ConvertUTF8toUTF16(windowBuffer);
3550
0
3551
0
  nsCOMPtr<nsIObserverService> obs = services::GetObserverService();
3552
0
  obs->NotifyObservers(nullptr, "recording-window-ended", data.get());
3553
0
  LOG(("Sent recording-window-ended for window %" PRIu64 " (outer %" PRIu64 ")",
3554
0
       aWindowId, outerID));
3555
0
}
3556
3557
bool
3558
MediaManager::IsWindowListenerStillActive(GetUserMediaWindowListener* aListener)
3559
0
{
3560
0
  MOZ_DIAGNOSTIC_ASSERT(aListener);
3561
0
  return aListener && aListener == GetWindowListener(aListener->WindowID());
3562
0
}
3563
3564
void
3565
MediaManager::GetPref(nsIPrefBranch *aBranch, const char *aPref,
3566
                      const char *aData, int32_t *aVal)
3567
0
{
3568
0
  int32_t temp;
3569
0
  if (aData == nullptr || strcmp(aPref,aData) == 0) {
3570
0
    if (NS_SUCCEEDED(aBranch->GetIntPref(aPref, &temp))) {
3571
0
      *aVal = temp;
3572
0
    }
3573
0
  }
3574
0
}
3575
3576
void
3577
MediaManager::GetPrefBool(nsIPrefBranch *aBranch, const char *aPref,
3578
                          const char *aData, bool *aVal)
3579
0
{
3580
0
  bool temp;
3581
0
  if (aData == nullptr || strcmp(aPref,aData) == 0) {
3582
0
    if (NS_SUCCEEDED(aBranch->GetBoolPref(aPref, &temp))) {
3583
0
      *aVal = temp;
3584
0
    }
3585
0
  }
3586
0
}
3587
3588
void
3589
MediaManager::GetPrefs(nsIPrefBranch *aBranch, const char *aData)
3590
0
{
3591
0
  GetPref(aBranch, "media.navigator.video.default_width", aData, &mPrefs.mWidth);
3592
0
  GetPref(aBranch, "media.navigator.video.default_height", aData, &mPrefs.mHeight);
3593
0
  GetPref(aBranch, "media.navigator.video.default_fps", aData, &mPrefs.mFPS);
3594
0
  GetPref(aBranch, "media.navigator.audio.fake_frequency", aData, &mPrefs.mFreq);
3595
0
#ifdef MOZ_WEBRTC
3596
0
  GetPrefBool(aBranch, "media.getusermedia.aec_enabled", aData, &mPrefs.mAecOn);
3597
0
  GetPrefBool(aBranch, "media.getusermedia.agc_enabled", aData, &mPrefs.mAgcOn);
3598
0
  GetPrefBool(aBranch, "media.getusermedia.noise_enabled", aData, &mPrefs.mNoiseOn);
3599
0
  GetPref(aBranch, "media.getusermedia.aec", aData, &mPrefs.mAec);
3600
0
  GetPref(aBranch, "media.getusermedia.agc", aData, &mPrefs.mAgc);
3601
0
  GetPref(aBranch, "media.getusermedia.noise", aData, &mPrefs.mNoise);
3602
0
  GetPrefBool(aBranch, "media.getusermedia.aec_extended_filter", aData, &mPrefs.mExtendedFilter);
3603
0
  GetPrefBool(aBranch, "media.getusermedia.aec_aec_delay_agnostic", aData, &mPrefs.mDelayAgnostic);
3604
0
  GetPref(aBranch, "media.getusermedia.channels", aData, &mPrefs.mChannels);
3605
0
  GetPrefBool(aBranch, "media.ondevicechange.fakeDeviceChangeEvent.enabled", aData, &mPrefs.mFakeDeviceChangeEventOn);
3606
0
#endif
3607
0
  GetPrefBool(aBranch, "media.navigator.audio.full_duplex", aData, &mPrefs.mFullDuplex);
3608
0
}
3609
3610
void
3611
MediaManager::Shutdown()
3612
0
{
3613
0
  MOZ_ASSERT(NS_IsMainThread());
3614
0
  if (sHasShutdown) {
3615
0
    return;
3616
0
  }
3617
0
3618
0
  nsCOMPtr<nsIObserverService> obs = services::GetObserverService();
3619
0
3620
0
  obs->RemoveObserver(this, "last-pb-context-exited");
3621
0
  obs->RemoveObserver(this, "getUserMedia:privileged:allow");
3622
0
  obs->RemoveObserver(this, "getUserMedia:response:allow");
3623
0
  obs->RemoveObserver(this, "getUserMedia:response:deny");
3624
0
  obs->RemoveObserver(this, "getUserMedia:revoke");
3625
0
3626
0
  nsCOMPtr<nsIPrefBranch> prefs = do_GetService(NS_PREFSERVICE_CONTRACTID);
3627
0
  if (prefs) {
3628
0
    prefs->RemoveObserver("media.navigator.video.default_width", this);
3629
0
    prefs->RemoveObserver("media.navigator.video.default_height", this);
3630
0
    prefs->RemoveObserver("media.navigator.video.default_fps", this);
3631
0
    prefs->RemoveObserver("media.navigator.audio.fake_frequency", this);
3632
0
#ifdef MOZ_WEBRTC
3633
0
    prefs->RemoveObserver("media.getusermedia.aec_enabled", this);
3634
0
    prefs->RemoveObserver("media.getusermedia.aec", this);
3635
0
    prefs->RemoveObserver("media.getusermedia.agc_enabled", this);
3636
0
    prefs->RemoveObserver("media.getusermedia.agc", this);
3637
0
    prefs->RemoveObserver("media.getusermedia.noise_enabled", this);
3638
0
    prefs->RemoveObserver("media.getusermedia.noise", this);
3639
0
    prefs->RemoveObserver("media.ondevicechange.fakeDeviceChangeEvent.enabled", this);
3640
0
    prefs->RemoveObserver("media.getusermedia.channels", this);
3641
0
#endif
3642
0
    prefs->RemoveObserver("media.navigator.audio.full_duplex", this);
3643
0
  }
3644
0
3645
0
  {
3646
0
    // Close off any remaining active windows.
3647
0
3648
0
    // Live capture at this point is rare but can happen. Stopping it will make
3649
0
    // the window listeners attempt to remove themselves from the active windows
3650
0
    // table. We cannot touch the table at point so we grab a copy of the window
3651
0
    // listeners first.
3652
0
    nsTArray<RefPtr<GetUserMediaWindowListener>> listeners(GetActiveWindows()->Count());
3653
0
    for (auto iter = GetActiveWindows()->Iter(); !iter.Done(); iter.Next()) {
3654
0
      listeners.AppendElement(iter.UserData());
3655
0
    }
3656
0
    for (auto& listener : listeners) {
3657
0
      listener->Stop();
3658
0
      listener->RemoveAll();
3659
0
    }
3660
0
  }
3661
0
  MOZ_ASSERT(GetActiveWindows()->Count() == 0);
3662
0
3663
0
  GetActiveWindows()->Clear();
3664
0
  mActiveCallbacks.Clear();
3665
0
  mCallIds.Clear();
3666
0
  mPendingGUMRequest.Clear();
3667
0
  mDeviceIDs.Clear();
3668
0
#ifdef MOZ_WEBRTC
3669
0
  StopWebRtcLog();
3670
0
#endif
3671
0
3672
0
  // From main thread's point of view, shutdown is now done.
3673
0
  // All that remains is shutting down the media thread.
3674
0
  sHasShutdown = true;
3675
0
3676
0
  // Because mMediaThread is not an nsThread, we must dispatch to it so it can
3677
0
  // clean up BackgroundChild. Continue stopping thread once this is done.
3678
0
3679
0
  class ShutdownTask : public Runnable
3680
0
  {
3681
0
  public:
3682
0
    ShutdownTask(MediaManager* aManager, already_AddRefed<Runnable> aReply)
3683
0
      : mozilla::Runnable("ShutdownTask")
3684
0
      , mManager(aManager)
3685
0
      , mReply(aReply)
3686
0
    {
3687
0
    }
3688
0
3689
0
  private:
3690
0
    NS_IMETHOD
3691
0
    Run() override
3692
0
    {
3693
0
      LOG(("MediaManager Thread Shutdown"));
3694
0
      MOZ_ASSERT(MediaManager::IsInMediaThread());
3695
0
      // Must shutdown backend on MediaManager thread, since that's where we started it from!
3696
0
      {
3697
0
        if (mManager->mBackend) {
3698
0
          mManager->mBackend->Shutdown(); // ok to invoke multiple times
3699
0
          mManager->mBackend->RemoveDeviceChangeCallback(mManager);
3700
0
        }
3701
0
      }
3702
0
      mozilla::ipc::BackgroundChild::CloseForCurrentThread();
3703
0
      // must explicitly do this before dispatching the reply, since the reply may kill us with Stop()
3704
0
      mManager->mBackend = nullptr; // last reference, will invoke Shutdown() again
3705
0
3706
0
      if (NS_FAILED(NS_DispatchToMainThread(mReply.forget()))) {
3707
0
        LOG(("Will leak thread: DispatchToMainthread of reply runnable failed in MediaManager shutdown"));
3708
0
      }
3709
0
3710
0
      return NS_OK;
3711
0
    }
3712
0
    RefPtr<MediaManager> mManager;
3713
0
    RefPtr<Runnable> mReply;
3714
0
  };
3715
0
3716
0
  // Post ShutdownTask to execute on mMediaThread and pass in a lambda
3717
0
  // callback to be executed back on this thread once it is done.
3718
0
  //
3719
0
  // The lambda callback "captures" the 'this' pointer for member access.
3720
0
  // This is safe since this is guaranteed to be here since sSingleton isn't
3721
0
  // cleared until the lambda function clears it.
3722
0
3723
0
  // note that this == sSingleton
3724
0
  MOZ_ASSERT(this == sSingleton);
3725
0
  RefPtr<MediaManager> that = this;
3726
0
3727
0
  // Release the backend (and call Shutdown()) from within the MediaManager thread
3728
0
  // Don't use MediaManager::PostTask() because we're sHasShutdown=true here!
3729
0
  RefPtr<ShutdownTask> shutdown = new ShutdownTask(this,
3730
0
      media::NewRunnableFrom([this, that]() mutable {
3731
0
    LOG(("MediaManager shutdown lambda running, releasing MediaManager singleton and thread"));
3732
0
    if (mMediaThread) {
3733
0
      mMediaThread->Stop();
3734
0
    }
3735
0
3736
0
    // Remove async shutdown blocker
3737
0
3738
0
    nsCOMPtr<nsIAsyncShutdownClient> shutdownPhase = GetShutdownPhase();
3739
0
    shutdownPhase->RemoveBlocker(sSingleton->mShutdownBlocker);
3740
0
3741
0
    // we hold a ref to 'that' which is the same as sSingleton
3742
0
    sSingleton = nullptr;
3743
0
3744
0
    return NS_OK;
3745
0
  }));
3746
0
  mMediaThread->message_loop()->PostTask(shutdown.forget());
3747
0
}
3748
3749
void
3750
MediaManager::SendPendingGUMRequest()
3751
0
{
3752
0
  if (mPendingGUMRequest.Length() > 0) {
3753
0
    nsCOMPtr<nsIObserverService> obs = services::GetObserverService();
3754
0
    obs->NotifyObservers(mPendingGUMRequest[0], "getUserMedia:request", nullptr);
3755
0
    mPendingGUMRequest.RemoveElementAt(0);
3756
0
  }
3757
0
}
3758
3759
nsresult
3760
MediaManager::Observe(nsISupports* aSubject, const char* aTopic,
3761
  const char16_t* aData)
3762
0
{
3763
0
  MOZ_ASSERT(NS_IsMainThread());
3764
0
3765
0
  if (!strcmp(aTopic, NS_PREFBRANCH_PREFCHANGE_TOPIC_ID)) {
3766
0
    nsCOMPtr<nsIPrefBranch> branch( do_QueryInterface(aSubject) );
3767
0
    if (branch) {
3768
0
      GetPrefs(branch,NS_ConvertUTF16toUTF8(aData).get());
3769
0
      LOG(("%s: %dx%d @%dfps", __FUNCTION__,
3770
0
           mPrefs.mWidth, mPrefs.mHeight, mPrefs.mFPS));
3771
0
    }
3772
0
  } else if (!strcmp(aTopic, "last-pb-context-exited")) {
3773
0
    // Clear memory of private-browsing-specific deviceIds. Fire and forget.
3774
0
    media::SanitizeOriginKeys(0, true);
3775
0
    return NS_OK;
3776
0
  } else if (!strcmp(aTopic, "getUserMedia:got-device-permission")) {
3777
0
    MOZ_ASSERT(aSubject);
3778
0
    nsCOMPtr<nsIRunnable> task = do_QueryInterface(aSubject);
3779
0
    MediaManager::PostTask(NewTaskFrom([task] {
3780
0
      task->Run();
3781
0
    }));
3782
0
    return NS_OK;
3783
0
  } else if (!strcmp(aTopic, "getUserMedia:privileged:allow") ||
3784
0
             !strcmp(aTopic, "getUserMedia:response:allow")) {
3785
0
    nsString key(aData);
3786
0
    RefPtr<GetUserMediaTask> task;
3787
0
    mActiveCallbacks.Remove(key, getter_AddRefs(task));
3788
0
    if (!task) {
3789
0
      return NS_OK;
3790
0
    }
3791
0
3792
0
    nsTArray<nsString>* array;
3793
0
    if (!mCallIds.Get(task->GetWindowID(), &array)) {
3794
0
      return NS_OK;
3795
0
    }
3796
0
    array->RemoveElement(key);
3797
0
3798
0
    if (aSubject) {
3799
0
      // A particular device or devices were chosen by the user.
3800
0
      // NOTE: does not allow setting a device to null; assumes nullptr
3801
0
      nsCOMPtr<nsIArray> array(do_QueryInterface(aSubject));
3802
0
      MOZ_ASSERT(array);
3803
0
      uint32_t len = 0;
3804
0
      array->GetLength(&len);
3805
0
      bool videoFound = false, audioFound = false;
3806
0
      for (uint32_t i = 0; i < len; i++) {
3807
0
        nsCOMPtr<nsIMediaDevice> device;
3808
0
        array->QueryElementAt(i, NS_GET_IID(nsIMediaDevice),
3809
0
                              getter_AddRefs(device));
3810
0
        MOZ_ASSERT(device); // shouldn't be returning anything else...
3811
0
        if (!device) {
3812
0
          continue;
3813
0
        }
3814
0
3815
0
        // Casting here is safe because a MediaDevice is created
3816
0
        // only in Gecko side, JS can only query for an instance.
3817
0
        MediaDevice* dev = static_cast<MediaDevice*>(device.get());
3818
0
        if (dev->mKind == dom::MediaDeviceKind::Videoinput) {
3819
0
          if (!videoFound) {
3820
0
            task->SetVideoDevice(dev);
3821
0
            videoFound = true;
3822
0
          }
3823
0
        } else if (dev->mKind == dom::MediaDeviceKind::Audioinput) {
3824
0
          if (!audioFound) {
3825
0
            task->SetAudioDevice(dev);
3826
0
            audioFound = true;
3827
0
          }
3828
0
        } else {
3829
0
          NS_WARNING("Unknown device type in getUserMedia");
3830
0
        }
3831
0
      }
3832
0
      bool needVideo = IsOn(task->GetConstraints().mVideo);
3833
0
      bool needAudio = IsOn(task->GetConstraints().mAudio);
3834
0
      MOZ_ASSERT(needVideo || needAudio);
3835
0
3836
0
      if ((needVideo && !videoFound) || (needAudio && !audioFound)) {
3837
0
        task->Denied(MediaMgrError::Name::NotAllowedError);
3838
0
        return NS_OK;
3839
0
      }
3840
0
    }
3841
0
3842
0
    if (sHasShutdown) {
3843
0
      return task->Denied(MediaMgrError::Name::AbortError,
3844
0
                          NS_LITERAL_STRING("In shutdown"));
3845
0
    }
3846
0
    // Reuse the same thread to save memory.
3847
0
    MediaManager::PostTask(task.forget());
3848
0
    return NS_OK;
3849
0
3850
0
  } else if (!strcmp(aTopic, "getUserMedia:response:deny")) {
3851
0
    nsString key(aData);
3852
0
    RefPtr<GetUserMediaTask> task;
3853
0
    mActiveCallbacks.Remove(key, getter_AddRefs(task));
3854
0
    if (task) {
3855
0
      task->Denied(MediaMgrError::Name::NotAllowedError);
3856
0
      nsTArray<nsString>* array;
3857
0
      if (!mCallIds.Get(task->GetWindowID(), &array)) {
3858
0
        return NS_OK;
3859
0
      }
3860
0
      array->RemoveElement(key);
3861
0
      SendPendingGUMRequest();
3862
0
    }
3863
0
    return NS_OK;
3864
0
3865
0
  } else if (!strcmp(aTopic, "getUserMedia:revoke")) {
3866
0
    nsresult rv;
3867
0
    // may be windowid or screen:windowid
3868
0
    nsDependentString data(aData);
3869
0
    if (Substring(data, 0, strlen("screen:")).EqualsLiteral("screen:")) {
3870
0
      uint64_t windowID = PromiseFlatString(Substring(data, strlen("screen:"))).ToInteger64(&rv);
3871
0
      MOZ_ASSERT(NS_SUCCEEDED(rv));
3872
0
      if (NS_SUCCEEDED(rv)) {
3873
0
        LOG(("Revoking Screen/windowCapture access for window %" PRIu64, windowID));
3874
0
        StopScreensharing(windowID);
3875
0
      }
3876
0
    } else {
3877
0
      uint64_t windowID = nsString(aData).ToInteger64(&rv);
3878
0
      MOZ_ASSERT(NS_SUCCEEDED(rv));
3879
0
      if (NS_SUCCEEDED(rv)) {
3880
0
        LOG(("Revoking MediaCapture access for window %" PRIu64, windowID));
3881
0
        OnNavigation(windowID);
3882
0
      }
3883
0
    }
3884
0
    return NS_OK;
3885
0
  }
3886
0
3887
0
  return NS_OK;
3888
0
}
3889
3890
nsresult
3891
MediaManager::GetActiveMediaCaptureWindows(nsIArray** aArray)
3892
0
{
3893
0
  MOZ_ASSERT(aArray);
3894
0
3895
0
  nsCOMPtr<nsIMutableArray> array = nsArray::Create();
3896
0
3897
0
  for (auto iter = mActiveWindows.Iter(); !iter.Done(); iter.Next()) {
3898
0
    const uint64_t& id = iter.Key();
3899
0
    RefPtr<GetUserMediaWindowListener> winListener = iter.UserData();
3900
0
    if (!winListener) {
3901
0
      continue;
3902
0
    }
3903
0
3904
0
    nsPIDOMWindowInner* window =
3905
0
      nsGlobalWindowInner::GetInnerWindowWithId(id)->AsInner();
3906
0
    MOZ_ASSERT(window);
3907
0
    // XXXkhuey ...
3908
0
    if (!window) {
3909
0
      continue;
3910
0
    }
3911
0
3912
0
    if (winListener->CapturingVideo() || winListener->CapturingAudio()) {
3913
0
      array->AppendElement(window);
3914
0
    }
3915
0
  }
3916
0
3917
0
  array.forget(aArray);
3918
0
  return NS_OK;
3919
0
}
3920
3921
struct CaptureWindowStateData {
3922
  uint16_t* mCamera;
3923
  uint16_t* mMicrophone;
3924
  uint16_t* mScreenShare;
3925
  uint16_t* mWindowShare;
3926
  uint16_t* mAppShare;
3927
  uint16_t* mBrowserShare;
3928
};
3929
3930
NS_IMETHODIMP
3931
MediaManager::MediaCaptureWindowState(nsIDOMWindow* aCapturedWindow,
3932
                                      uint16_t* aCamera,
3933
                                      uint16_t* aMicrophone,
3934
                                      uint16_t* aScreen,
3935
                                      uint16_t* aWindow,
3936
                                      uint16_t* aApplication,
3937
                                      uint16_t* aBrowser)
3938
0
{
3939
0
  MOZ_ASSERT(NS_IsMainThread());
3940
0
3941
0
  CaptureState camera = CaptureState::Off;
3942
0
  CaptureState microphone = CaptureState::Off;
3943
0
  CaptureState screen = CaptureState::Off;
3944
0
  CaptureState window = CaptureState::Off;
3945
0
  CaptureState application = CaptureState::Off;
3946
0
  CaptureState browser = CaptureState::Off;
3947
0
3948
0
  nsCOMPtr<nsPIDOMWindowInner> piWin = do_QueryInterface(aCapturedWindow);
3949
0
  if (piWin) {
3950
0
    IterateWindowListeners(piWin,
3951
0
      [&camera, &microphone, &screen, &window, &application, &browser]
3952
0
      (GetUserMediaWindowListener* aListener)
3953
0
      {
3954
0
        camera = CombineCaptureState(
3955
0
            camera, aListener->CapturingSource(MediaSourceEnum::Camera));
3956
0
        microphone = CombineCaptureState(
3957
0
            microphone, aListener->CapturingSource(MediaSourceEnum::Microphone));
3958
0
        screen = CombineCaptureState(
3959
0
            screen, aListener->CapturingSource(MediaSourceEnum::Screen));
3960
0
        window = CombineCaptureState(
3961
0
            window, aListener->CapturingSource(MediaSourceEnum::Window));
3962
0
        application = CombineCaptureState(
3963
0
            application, aListener->CapturingSource(MediaSourceEnum::Application));
3964
0
        browser = CombineCaptureState(
3965
0
            browser, aListener->CapturingSource(MediaSourceEnum::Browser));
3966
0
      });
3967
0
  }
3968
0
3969
0
  *aCamera = FromCaptureState(camera);
3970
0
  *aMicrophone= FromCaptureState(microphone);
3971
0
  *aScreen = FromCaptureState(screen);
3972
0
  *aWindow = FromCaptureState(window);
3973
0
  *aApplication = FromCaptureState(application);
3974
0
  *aBrowser = FromCaptureState(browser);
3975
0
3976
#ifdef DEBUG
3977
  LOG(("%s: window %" PRIu64 " capturing %s %s %s %s %s %s", __FUNCTION__, piWin ? piWin->WindowID() : -1,
3978
       *aCamera == nsIMediaManagerService::STATE_CAPTURE_ENABLED
3979
         ? "camera (enabled)"
3980
         : (*aCamera == nsIMediaManagerService::STATE_CAPTURE_DISABLED
3981
            ? "camera (disabled)" : ""),
3982
       *aMicrophone == nsIMediaManagerService::STATE_CAPTURE_ENABLED
3983
         ? "microphone (enabled)"
3984
         : (*aMicrophone == nsIMediaManagerService::STATE_CAPTURE_DISABLED
3985
            ? "microphone (disabled)" : ""),
3986
       *aScreen ? "screenshare" : "",
3987
       *aWindow ? "windowshare" : "",
3988
       *aApplication ? "appshare" : "",
3989
       *aBrowser ? "browsershare" : ""));
3990
#endif
3991
  return NS_OK;
3992
0
}
3993
3994
NS_IMETHODIMP
3995
MediaManager::SanitizeDeviceIds(int64_t aSinceWhen)
3996
0
{
3997
0
  MOZ_ASSERT(NS_IsMainThread());
3998
0
  LOG(("%s: sinceWhen = %" PRId64, __FUNCTION__, aSinceWhen));
3999
0
4000
0
  media::SanitizeOriginKeys(aSinceWhen, false); // we fire and forget
4001
0
  return NS_OK;
4002
0
}
4003
4004
void
4005
MediaManager::StopScreensharing(uint64_t aWindowID)
4006
0
{
4007
0
  // We need to stop window/screensharing for all streams in all innerwindows that
4008
0
  // correspond to that outerwindow.
4009
0
4010
0
  auto* window = nsGlobalWindowInner::GetInnerWindowWithId(aWindowID);
4011
0
  if (!window) {
4012
0
    return;
4013
0
  }
4014
0
  IterateWindowListeners(window->AsInner(),
4015
0
    [](GetUserMediaWindowListener* aListener)
4016
0
    {
4017
0
      aListener->StopSharing();
4018
0
    });
4019
0
}
4020
4021
template<typename FunctionType>
4022
void
4023
MediaManager::IterateWindowListeners(nsPIDOMWindowInner* aWindow,
4024
                                     const FunctionType& aCallback)
4025
0
{
4026
0
  // Iterate the docshell tree to find all the child windows, and for each
4027
0
  // invoke the callback
4028
0
  if (aWindow) {
4029
0
    {
4030
0
      uint64_t windowID = aWindow->WindowID();
4031
0
      GetUserMediaWindowListener* listener = GetWindowListener(windowID);
4032
0
      if (listener) {
4033
0
        aCallback(listener);
4034
0
      }
4035
0
      // NB: `listener` might have been destroyed.
4036
0
    }
4037
0
4038
0
    // iterate any children of *this* window (iframes, etc)
4039
0
    nsCOMPtr<nsIDocShell> docShell = aWindow->GetDocShell();
4040
0
    if (docShell) {
4041
0
      int32_t i, count;
4042
0
      docShell->GetChildCount(&count);
4043
0
      for (i = 0; i < count; ++i) {
4044
0
        nsCOMPtr<nsIDocShellTreeItem> item;
4045
0
        docShell->GetChildAt(i, getter_AddRefs(item));
4046
0
        nsCOMPtr<nsPIDOMWindowOuter> winOuter = item ? item->GetWindow() : nullptr;
4047
0
4048
0
        if (winOuter) {
4049
0
          IterateWindowListeners(winOuter->GetCurrentInnerWindow(), aCallback);
4050
0
        }
4051
0
      }
4052
0
    }
4053
0
  }
4054
0
}
Unexecuted instantiation: Unified_cpp_dom_media6.cpp:void mozilla::MediaManager::IterateWindowListeners<mozilla::MediaManager::OnDeviceChange()::$_28::operator()()::{lambda(nsTArray<RefPtr<mozilla::MediaDevice> >*&)#1}::operator()(nsTArray<RefPtr<mozilla::MediaDevice> >*&)::{lambda(mozilla::GetUserMediaWindowListener*)#1}>(nsPIDOMWindowInner*, mozilla::MediaManager::OnDeviceChange()::$_28::operator()()::{lambda(nsTArray<RefPtr<mozilla::MediaDevice> >*&)#1}::operator()(nsTArray<RefPtr<mozilla::MediaDevice> >*&)::{lambda(mozilla::GetUserMediaWindowListener*)#1} const&)
Unexecuted instantiation: Unified_cpp_dom_media6.cpp:void mozilla::MediaManager::IterateWindowListeners<mozilla::MediaManager::OnNavigation(unsigned long)::$_34>(nsPIDOMWindowInner*, mozilla::MediaManager::OnNavigation(unsigned long)::$_34 const&)
Unexecuted instantiation: Unified_cpp_dom_media6.cpp:void mozilla::MediaManager::IterateWindowListeners<mozilla::MediaManager::MediaCaptureWindowState(nsIDOMWindow*, unsigned short*, unsigned short*, unsigned short*, unsigned short*, unsigned short*, unsigned short*)::$_38>(nsPIDOMWindowInner*, mozilla::MediaManager::MediaCaptureWindowState(nsIDOMWindow*, unsigned short*, unsigned short*, unsigned short*, unsigned short*, unsigned short*, unsigned short*)::$_38 const&)
Unexecuted instantiation: Unified_cpp_dom_media6.cpp:void mozilla::MediaManager::IterateWindowListeners<mozilla::MediaManager::StopScreensharing(unsigned long)::$_39>(nsPIDOMWindowInner*, mozilla::MediaManager::StopScreensharing(unsigned long)::$_39 const&)
4055
4056
4057
void
4058
MediaManager::StopMediaStreams()
4059
0
{
4060
0
  nsCOMPtr<nsIArray> array;
4061
0
  GetActiveMediaCaptureWindows(getter_AddRefs(array));
4062
0
  uint32_t len;
4063
0
  array->GetLength(&len);
4064
0
  for (uint32_t i = 0; i < len; i++) {
4065
0
    nsCOMPtr<nsPIDOMWindowInner> win;
4066
0
    array->QueryElementAt(i, NS_GET_IID(nsPIDOMWindowInner),
4067
0
                          getter_AddRefs(win));
4068
0
    if (win) {
4069
0
      OnNavigation(win->WindowID());
4070
0
    }
4071
0
  }
4072
0
}
4073
4074
bool
4075
MediaManager::IsActivelyCapturingOrHasAPermission(uint64_t aWindowId)
4076
0
{
4077
0
  // Does page currently have a gUM stream active?
4078
0
4079
0
  nsCOMPtr<nsIArray> array;
4080
0
  GetActiveMediaCaptureWindows(getter_AddRefs(array));
4081
0
  uint32_t len;
4082
0
  array->GetLength(&len);
4083
0
  for (uint32_t i = 0; i < len; i++) {
4084
0
    nsCOMPtr<nsPIDOMWindowInner> win;
4085
0
    array->QueryElementAt(i, NS_GET_IID(nsPIDOMWindowInner),
4086
0
                          getter_AddRefs(win));
4087
0
    if (win && win->WindowID() == aWindowId) {
4088
0
      return true;
4089
0
    }
4090
0
  }
4091
0
4092
0
  // Or are persistent permissions (audio or video) granted?
4093
0
4094
0
  auto* window = nsGlobalWindowInner::GetInnerWindowWithId(aWindowId);
4095
0
  if (NS_WARN_IF(!window) || NS_WARN_IF(!window->GetPrincipal())) {
4096
0
    return false;
4097
0
  }
4098
0
  // Check if this site has persistent permissions.
4099
0
  nsresult rv;
4100
0
  nsCOMPtr<nsIPermissionManager> mgr =
4101
0
    do_GetService(NS_PERMISSIONMANAGER_CONTRACTID, &rv);
4102
0
  if (NS_WARN_IF(NS_FAILED(rv))) {
4103
0
    return false; // no permission manager no permissions!
4104
0
  }
4105
0
4106
0
  uint32_t audio = nsIPermissionManager::UNKNOWN_ACTION;
4107
0
  uint32_t video = nsIPermissionManager::UNKNOWN_ACTION;
4108
0
  {
4109
0
    auto* principal = window->GetPrincipal();
4110
0
    rv = mgr->TestExactPermissionFromPrincipal(principal, "microphone", &audio);
4111
0
    if (NS_WARN_IF(NS_FAILED(rv))) {
4112
0
      return false;
4113
0
    }
4114
0
    rv = mgr->TestExactPermissionFromPrincipal(principal, "camera", &video);
4115
0
    if (NS_WARN_IF(NS_FAILED(rv))) {
4116
0
      return false;
4117
0
    }
4118
0
  }
4119
0
  return audio == nsIPermissionManager::ALLOW_ACTION ||
4120
0
         video == nsIPermissionManager::ALLOW_ACTION;
4121
0
}
4122
4123
SourceListener::SourceListener()
4124
  : mStopped(false)
4125
  , mFinished(false)
4126
  , mRemoved(false)
4127
  , mMainThreadCheck(nullptr)
4128
  , mPrincipalHandle(PRINCIPAL_HANDLE_NONE)
4129
  , mWindowListener(nullptr)
4130
0
{}
4131
4132
void
4133
SourceListener::Register(GetUserMediaWindowListener* aListener)
4134
0
{
4135
0
  LOG(("SourceListener %p registering with window listener %p", this, aListener));
4136
0
4137
0
  MOZ_ASSERT(aListener, "No listener");
4138
0
  MOZ_ASSERT(!mWindowListener, "Already registered");
4139
0
  MOZ_ASSERT(!Activated(), "Already activated");
4140
0
4141
0
  mPrincipalHandle = aListener->GetPrincipalHandle();
4142
0
  mWindowListener = aListener;
4143
0
}
4144
4145
void
4146
SourceListener::Activate(SourceMediaStream* aStream,
4147
                         MediaDevice* aAudioDevice,
4148
                         MediaDevice* aVideoDevice)
4149
0
{
4150
0
  MOZ_ASSERT(NS_IsMainThread(), "Only call on main thread");
4151
0
4152
0
  LOG(("SourceListener %p activating audio=%p video=%p", this, aAudioDevice, aVideoDevice));
4153
0
4154
0
  MOZ_ASSERT(!mStopped, "Cannot activate stopped source listener");
4155
0
  MOZ_ASSERT(!Activated(), "Already activated");
4156
0
4157
0
  mMainThreadCheck = GetCurrentVirtualThread();
4158
0
  mStream = aStream;
4159
0
  mStreamListener = new SourceStreamListener(this);
4160
0
  if (aAudioDevice) {
4161
0
    mAudioDeviceState =
4162
0
      MakeUnique<DeviceState>(
4163
0
          aAudioDevice,
4164
0
          aAudioDevice->GetMediaSource() == dom::MediaSourceEnum::Microphone &&
4165
0
          Preferences::GetBool("media.getusermedia.microphone.off_while_disabled.enabled", true));
4166
0
  }
4167
0
4168
0
  if (aVideoDevice) {
4169
0
    mVideoDeviceState =
4170
0
      MakeUnique<DeviceState>(
4171
0
          aVideoDevice,
4172
0
          aVideoDevice->GetMediaSource() == dom::MediaSourceEnum::Camera &&
4173
0
          Preferences::GetBool("media.getusermedia.camera.off_while_disabled.enabled", true));
4174
0
  }
4175
0
4176
0
  mStream->AddListener(mStreamListener);
4177
0
}
4178
4179
RefPtr<SourceListener::InitPromise>
4180
SourceListener::InitializeAsync()
4181
0
{
4182
0
  MOZ_ASSERT(NS_IsMainThread(), "Only call on main thread");
4183
0
  MOZ_DIAGNOSTIC_ASSERT(!mStopped);
4184
0
4185
0
  RefPtr<InitPromise> init = MediaManager::PostTask<InitPromise>(__func__,
4186
0
    [ stream = mStream
4187
0
    , principal = GetPrincipalHandle()
4188
0
    , audioDevice = mAudioDeviceState ? mAudioDeviceState->mDevice : nullptr
4189
0
    , videoDevice = mVideoDeviceState ? mVideoDeviceState->mDevice : nullptr
4190
0
    ](MozPromiseHolder<InitPromise>& aHolder)
4191
0
    {
4192
0
      if (audioDevice) {
4193
0
        nsresult rv = audioDevice->SetTrack(stream, kAudioTrack, principal);
4194
0
        if (NS_SUCCEEDED(rv)) {
4195
0
          rv = audioDevice->Start();
4196
0
        }
4197
0
        if (NS_FAILED(rv)) {
4198
0
          nsString log;
4199
0
          if (rv == NS_ERROR_NOT_AVAILABLE) {
4200
0
            log.AssignLiteral("Concurrent mic process limit.");
4201
0
            aHolder.Reject(MakeRefPtr<MediaMgrError>(
4202
0
                  MediaMgrError::Name::NotReadableError, log), __func__);
4203
0
            return;
4204
0
          }
4205
0
          log.AssignLiteral("Starting audio failed");
4206
0
          aHolder.Reject(MakeRefPtr<MediaMgrError>(
4207
0
                MediaMgrError::Name::AbortError, log), __func__);
4208
0
          return;
4209
0
        }
4210
0
      }
4211
0
4212
0
      if (videoDevice) {
4213
0
        nsresult rv = videoDevice->SetTrack(stream, kVideoTrack, principal);
4214
0
        if (NS_SUCCEEDED(rv)) {
4215
0
          rv = videoDevice->Start();
4216
0
        }
4217
0
        if (NS_FAILED(rv)) {
4218
0
          if (audioDevice) {
4219
0
            if (NS_WARN_IF(NS_FAILED(audioDevice->Stop()))) {
4220
0
              MOZ_ASSERT_UNREACHABLE("Stopping audio failed");
4221
0
            }
4222
0
          }
4223
0
          nsString log;
4224
0
          log.AssignLiteral("Starting video failed");
4225
0
          aHolder.Reject(MakeRefPtr<MediaMgrError>(MediaMgrError::Name::AbortError, log), __func__);
4226
0
          return;
4227
0
        }
4228
0
      }
4229
0
4230
0
      // Start() queued the tracks to be added synchronously to avoid races
4231
0
      stream->FinishAddTracks();
4232
0
      stream->AdvanceKnownTracksTime(STREAM_TIME_MAX);
4233
0
      LOG(("started all sources"));
4234
0
4235
0
      aHolder.Resolve(true, __func__);
4236
0
    });
4237
0
4238
0
  return init->Then(GetMainThreadSerialEventTarget(), __func__,
4239
0
    [self = RefPtr<SourceListener>(this), this]()
4240
0
    {
4241
0
      if (mStopped) {
4242
0
        // We were shut down during the async init
4243
0
        return InitPromise::CreateAndResolve(true, __func__);
4244
0
      }
4245
0
4246
0
      mStream->SetPullEnabled(true);
4247
0
4248
0
      for (DeviceState* state : {mAudioDeviceState.get(),
4249
0
                                 mVideoDeviceState.get()}) {
4250
0
        if (!state) {
4251
0
          continue;
4252
0
        }
4253
0
        MOZ_DIAGNOSTIC_ASSERT(!state->mTrackEnabled);
4254
0
        MOZ_DIAGNOSTIC_ASSERT(!state->mDeviceEnabled);
4255
0
        MOZ_DIAGNOSTIC_ASSERT(!state->mStopped);
4256
0
4257
0
        state->mDeviceEnabled = true;
4258
0
        state->mTrackEnabled = true;
4259
0
        state->mTrackEnabledTime = TimeStamp::Now();
4260
0
      }
4261
0
      return InitPromise::CreateAndResolve(true, __func__);
4262
0
    }, [self = RefPtr<SourceListener>(this), this](RefPtr<MediaMgrError>&& aResult)
4263
0
    {
4264
0
      if (mStopped) {
4265
0
        return InitPromise::CreateAndReject(std::move(aResult), __func__);
4266
0
      }
4267
0
4268
0
      for (DeviceState* state : {mAudioDeviceState.get(),
4269
0
                                 mVideoDeviceState.get()}) {
4270
0
        if (!state) {
4271
0
          continue;
4272
0
        }
4273
0
        MOZ_DIAGNOSTIC_ASSERT(!state->mTrackEnabled);
4274
0
        MOZ_DIAGNOSTIC_ASSERT(!state->mDeviceEnabled);
4275
0
        MOZ_DIAGNOSTIC_ASSERT(!state->mStopped);
4276
0
4277
0
        state->mStopped = true;
4278
0
      }
4279
0
      return InitPromise::CreateAndReject(std::move(aResult), __func__);
4280
0
    });
4281
0
}
4282
4283
void
4284
SourceListener::Stop()
4285
0
{
4286
0
  MOZ_ASSERT(NS_IsMainThread(), "Only call on main thread");
4287
0
4288
0
  if (mStopped) {
4289
0
    return;
4290
0
  }
4291
0
4292
0
  LOG(("SourceListener %p stopping", this));
4293
0
4294
0
  // StopSharing() has some special logic, at least for audio capture.
4295
0
  // It must be called when all tracks have stopped, before setting mStopped.
4296
0
  StopSharing();
4297
0
4298
0
  mStopped = true;
4299
0
4300
0
  MOZ_ASSERT(Activated(), "There are no devices or any source stream to stop");
4301
0
  MOZ_ASSERT(mStream, "Can't end tracks. No source stream.");
4302
0
4303
0
  if (mAudioDeviceState && !mAudioDeviceState->mStopped) {
4304
0
    StopTrack(kAudioTrack);
4305
0
  }
4306
0
  if (mVideoDeviceState && !mVideoDeviceState->mStopped) {
4307
0
    StopTrack(kVideoTrack);
4308
0
  }
4309
0
4310
0
  MediaManager::PostTask(NewTaskFrom([source = mStream]() {
4311
0
    MOZ_ASSERT(MediaManager::IsInMediaThread());
4312
0
    source->EndAllTrackAndFinish();
4313
0
  }));
4314
0
}
4315
4316
void
4317
SourceListener::Remove()
4318
0
{
4319
0
  MOZ_ASSERT(NS_IsMainThread());
4320
0
4321
0
  if (mAudioDeviceState) {
4322
0
    mAudioDeviceState->mDisableTimer->Cancel();
4323
0
  }
4324
0
  if (mVideoDeviceState) {
4325
0
    mVideoDeviceState->mDisableTimer->Cancel();
4326
0
  }
4327
0
4328
0
  if (!mStream || mRemoved) {
4329
0
    return;
4330
0
  }
4331
0
4332
0
  LOG(("SourceListener %p removed on purpose, mFinished = %d", this, (int) mFinished));
4333
0
  mRemoved = true; // RemoveListener is async, avoid races
4334
0
  mWindowListener = nullptr;
4335
0
4336
0
  // If it's destroyed, don't call - listener will be removed and we'll be notified!
4337
0
  if (!mStream->IsDestroyed()) {
4338
0
    // We disable pulling before removing so we don't risk having live tracks
4339
0
    // without a listener attached - that wouldn't produce data and would be
4340
0
    // illegal to the graph.
4341
0
    mStream->SetPullEnabled(false);
4342
0
    mStream->RemoveListener(mStreamListener);
4343
0
  }
4344
0
  mStreamListener = nullptr;
4345
0
}
4346
4347
void
4348
SourceListener::StopTrack(TrackID aTrackID)
4349
0
{
4350
0
  MOZ_ASSERT(NS_IsMainThread(), "Only call on main thread");
4351
0
  MOZ_ASSERT(Activated(), "No device to stop");
4352
0
  MOZ_ASSERT(aTrackID == kAudioTrack || aTrackID == kVideoTrack,
4353
0
             "Unknown track id");
4354
0
  DeviceState& state = GetDeviceStateFor(aTrackID);
4355
0
4356
0
  LOG(("SourceListener %p stopping %s track %d",
4357
0
       this, aTrackID == kAudioTrack ? "audio" : "video", aTrackID));
4358
0
4359
0
  if (state.mStopped) {
4360
0
    // device already stopped.
4361
0
    return;
4362
0
  }
4363
0
  state.mStopped = true;
4364
0
4365
0
  state.mDisableTimer->Cancel();
4366
0
4367
0
  MediaManager::PostTask(NewTaskFrom([device = state.mDevice]() {
4368
0
    device->Stop();
4369
0
    device->Deallocate();
4370
0
  }));
4371
0
4372
0
  if ((!mAudioDeviceState || mAudioDeviceState->mStopped) &&
4373
0
      (!mVideoDeviceState || mVideoDeviceState->mStopped)) {
4374
0
    LOG(("SourceListener %p this was the last track stopped", this));
4375
0
    Stop();
4376
0
  }
4377
0
4378
0
  MOZ_ASSERT(mWindowListener, "Should still have window listener");
4379
0
  mWindowListener->ChromeAffectingStateChanged();
4380
0
}
4381
4382
void
4383
SourceListener::GetSettingsFor(TrackID aTrackID,
4384
                               dom::MediaTrackSettings& aOutSettings) const
4385
0
{
4386
0
  MOZ_ASSERT(NS_IsMainThread(), "Only call on main thread");
4387
0
  GetDeviceStateFor(aTrackID).mDevice->GetSettings(aOutSettings);
4388
0
}
4389
4390
void
4391
SourceListener::SetEnabledFor(TrackID aTrackID, bool aEnable)
4392
0
{
4393
0
  MOZ_ASSERT(NS_IsMainThread(), "Only call on main thread");
4394
0
  MOZ_ASSERT(Activated(), "No device to set enabled state for");
4395
0
  MOZ_ASSERT(aTrackID == kAudioTrack || aTrackID == kVideoTrack,
4396
0
             "Unknown track id");
4397
0
4398
0
  if (mRemoved) {
4399
0
    return;
4400
0
  }
4401
0
4402
0
  LOG(("SourceListener %p %s %s track %d",
4403
0
       this, aEnable ? "enabling" : "disabling",
4404
0
       aTrackID == kAudioTrack ? "audio" : "video", aTrackID));
4405
0
4406
0
  DeviceState& state = GetDeviceStateFor(aTrackID);
4407
0
4408
0
  state.mTrackEnabled = aEnable;
4409
0
4410
0
  if (state.mStopped) {
4411
0
    // Device terminally stopped. Updating device state is pointless.
4412
0
    return;
4413
0
  }
4414
0
4415
0
  if (state.mOperationInProgress) {
4416
0
    // If a timer is in progress, it needs to be canceled now so the next
4417
0
    // DisableTrack() gets a fresh start. Canceling will trigger another
4418
0
    // operation.
4419
0
    state.mDisableTimer->Cancel();
4420
0
    return;
4421
0
  }
4422
0
4423
0
  if (state.mDeviceEnabled == aEnable) {
4424
0
    // Device is already in the desired state.
4425
0
    return;
4426
0
  }
4427
0
4428
0
  // All paths from here on must end in setting `state.mOperationInProgress`
4429
0
  // to false.
4430
0
  state.mOperationInProgress = true;
4431
0
4432
0
  RefPtr<MediaTimerPromise> timerPromise;
4433
0
  if (aEnable) {
4434
0
    timerPromise = MediaTimerPromise::CreateAndResolve(true, __func__);
4435
0
    state.mTrackEnabledTime = TimeStamp::Now();
4436
0
  } else {
4437
0
    const TimeDuration maxDelay = TimeDuration::FromMilliseconds(
4438
0
      Preferences::GetUint(
4439
0
        aTrackID == kAudioTrack
4440
0
          ? "media.getusermedia.microphone.off_while_disabled.delay_ms"
4441
0
          : "media.getusermedia.camera.off_while_disabled.delay_ms",
4442
0
        3000));
4443
0
    const TimeDuration durationEnabled =
4444
0
      TimeStamp::Now() - state.mTrackEnabledTime;
4445
0
    const TimeDuration delay =
4446
0
      TimeDuration::Max(TimeDuration::FromMilliseconds(0),
4447
0
                        maxDelay - durationEnabled);
4448
0
    timerPromise = state.mDisableTimer->WaitFor(delay, __func__);
4449
0
  }
4450
0
4451
0
  typedef MozPromise<nsresult, bool, /* IsExclusive = */ true> DeviceOperationPromise;
4452
0
  RefPtr<SourceListener> self = this;
4453
0
  timerPromise->Then(GetMainThreadSerialEventTarget(), __func__,
4454
0
    [self, this, &state, aTrackID, aEnable]() mutable {
4455
0
      MOZ_ASSERT(state.mDeviceEnabled != aEnable,
4456
0
                 "Device operation hasn't started");
4457
0
      MOZ_ASSERT(state.mOperationInProgress,
4458
0
                 "It's our responsibility to reset the inProgress state");
4459
0
4460
0
      LOG(("SourceListener %p %s %s track %d - starting device operation",
4461
0
           this, aEnable ? "enabling" : "disabling",
4462
0
           aTrackID == kAudioTrack ? "audio" : "video",
4463
0
           aTrackID));
4464
0
4465
0
      if (mRemoved) {
4466
0
        // Listener was removed between timer resolving and this runnable.
4467
0
        return DeviceOperationPromise::CreateAndResolve(NS_ERROR_ABORT, __func__);
4468
0
      }
4469
0
4470
0
      if (state.mStopped) {
4471
0
        // Source was stopped between timer resolving and this runnable.
4472
0
        return DeviceOperationPromise::CreateAndResolve(NS_ERROR_ABORT, __func__);
4473
0
      }
4474
0
4475
0
      state.mDeviceEnabled = aEnable;
4476
0
4477
0
      if (mWindowListener) {
4478
0
        mWindowListener->ChromeAffectingStateChanged();
4479
0
      }
4480
0
4481
0
      if (!state.mOffWhileDisabled) {
4482
0
        // If the feature to turn a device off while disabled is itself disabled
4483
0
        // we shortcut the device operation and tell the ux-updating code
4484
0
        // that everything went fine.
4485
0
        return DeviceOperationPromise::CreateAndResolve(NS_OK, __func__);
4486
0
      }
4487
0
4488
0
      return MediaManager::PostTask<DeviceOperationPromise>(__func__,
4489
0
          [self, device = state.mDevice, aEnable]
4490
0
          (MozPromiseHolder<DeviceOperationPromise>& h) {
4491
0
            h.Resolve(aEnable ? device->Start() : device->Stop(), __func__);
4492
0
          });
4493
0
    }, []() {
4494
0
      // Timer was canceled by us. We signal this with NS_ERROR_ABORT.
4495
0
      return DeviceOperationPromise::CreateAndResolve(NS_ERROR_ABORT, __func__);
4496
0
    })->Then(GetMainThreadSerialEventTarget(), __func__,
4497
0
    [self, this, &state, aTrackID, aEnable](nsresult aResult) mutable {
4498
0
      MOZ_ASSERT_IF(aResult != NS_ERROR_ABORT,
4499
0
                    state.mDeviceEnabled == aEnable);
4500
0
      MOZ_ASSERT(state.mOperationInProgress);
4501
0
      state.mOperationInProgress = false;
4502
0
4503
0
      if (state.mStopped) {
4504
0
        // Device was stopped on main thread during the operation. Nothing to do.
4505
0
        return;
4506
0
      }
4507
0
4508
0
      LOG(("SourceListener %p %s %s track %d %s",
4509
0
           this,
4510
0
           aEnable ? "enabling" : "disabling",
4511
0
           aTrackID == kAudioTrack ? "audio" : "video",
4512
0
           aTrackID,
4513
0
           NS_SUCCEEDED(aResult) ? "succeeded" : "failed"));
4514
0
4515
0
      if (NS_FAILED(aResult) && aResult != NS_ERROR_ABORT) {
4516
0
        // This path handles errors from starting or stopping the device.
4517
0
        // NS_ERROR_ABORT are for cases where *we* aborted. They need graceful
4518
0
        // handling.
4519
0
        if (aEnable) {
4520
0
          // Starting the device failed. Stopping the track here will make the
4521
0
          // MediaStreamTrack end after a pass through the MediaStreamGraph.
4522
0
          StopTrack(aTrackID);
4523
0
        } else {
4524
0
          // Stopping the device failed. This is odd, but not fatal.
4525
0
          MOZ_ASSERT_UNREACHABLE("The device should be stoppable");
4526
0
4527
0
          // To keep our internal state sane in this case, we disallow future
4528
0
          // stops due to disable.
4529
0
          state.mOffWhileDisabled = false;
4530
0
        }
4531
0
        return;
4532
0
      }
4533
0
4534
0
      // This path is for a device operation aResult that was success or
4535
0
      // NS_ERROR_ABORT (*we* canceled the operation).
4536
0
      // At this point we have to follow up on the intended state, i.e., update
4537
0
      // the device state if the track state changed in the meantime.
4538
0
4539
0
      if (state.mTrackEnabled == state.mDeviceEnabled) {
4540
0
        // Intended state is same as device's current state.
4541
0
        // Nothing more to do.
4542
0
        return;
4543
0
      }
4544
0
4545
0
      // Track state changed during this operation. We'll start over.
4546
0
      if (state.mTrackEnabled) {
4547
0
        SetEnabledFor(aTrackID, true);
4548
0
      } else {
4549
0
        SetEnabledFor(aTrackID, false);
4550
0
      }
4551
0
    }, []() {
4552
0
      MOZ_ASSERT_UNREACHABLE("Unexpected and unhandled reject");
4553
0
    });
4554
0
}
4555
4556
void
4557
SourceListener::StopSharing()
4558
0
{
4559
0
  MOZ_ASSERT(NS_IsMainThread());
4560
0
  MOZ_RELEASE_ASSERT(mWindowListener);
4561
0
4562
0
  if (mStopped) {
4563
0
    return;
4564
0
  }
4565
0
4566
0
  LOG(("SourceListener %p StopSharing", this));
4567
0
4568
0
  if (mVideoDeviceState &&
4569
0
      (mVideoDeviceState->mDevice->GetMediaSource() == MediaSourceEnum::Screen ||
4570
0
       mVideoDeviceState->mDevice->GetMediaSource() == MediaSourceEnum::Application ||
4571
0
       mVideoDeviceState->mDevice->GetMediaSource() == MediaSourceEnum::Window)) {
4572
0
    // We want to stop the whole stream if there's no audio;
4573
0
    // just the video track if we have both.
4574
0
    // StopTrack figures this out for us.
4575
0
    StopTrack(kVideoTrack);
4576
0
  }
4577
0
  if (mAudioDeviceState &&
4578
0
      mAudioDeviceState->mDevice->GetMediaSource() == MediaSourceEnum::AudioCapture) {
4579
0
    uint64_t windowID = mWindowListener->WindowID();
4580
0
    nsCOMPtr<nsPIDOMWindowInner> window = nsGlobalWindowInner::GetInnerWindowWithId(windowID)->AsInner();
4581
0
    MOZ_RELEASE_ASSERT(window);
4582
0
    window->SetAudioCapture(false);
4583
0
    MediaStreamGraph* graph = mStream->Graph();
4584
0
    graph->UnregisterCaptureStreamForWindow(windowID);
4585
0
    mStream->Destroy();
4586
0
  }
4587
0
}
4588
4589
SourceMediaStream*
4590
SourceListener::GetSourceStream()
4591
0
{
4592
0
  NS_ASSERTION(mStream,"Getting stream from never-activated SourceListener");
4593
0
  return mStream;
4594
0
}
4595
4596
4597
// Proxy NotifyPull() to sources
4598
void
4599
SourceListener::NotifyPull(MediaStreamGraph* aGraph,
4600
                           StreamTime aDesiredTime)
4601
0
{
4602
0
  if (mAudioDeviceState) {
4603
0
    mAudioDeviceState->mDevice->Pull(mStream, kAudioTrack,
4604
0
                                     aDesiredTime, mPrincipalHandle);
4605
0
  }
4606
0
  if (mVideoDeviceState) {
4607
0
    mVideoDeviceState->mDevice->Pull(mStream, kVideoTrack,
4608
0
                                     aDesiredTime, mPrincipalHandle);
4609
0
  }
4610
0
}
4611
4612
void
4613
SourceListener::NotifyFinished()
4614
0
{
4615
0
  MOZ_ASSERT(NS_IsMainThread());
4616
0
  mFinished = true;
4617
0
  if (!mWindowListener) {
4618
0
    // Removed explicitly before finished.
4619
0
    return;
4620
0
  }
4621
0
4622
0
  LOG(("SourceListener %p NotifyFinished", this));
4623
0
4624
0
  Stop(); // we know it's been activated
4625
0
  mWindowListener->Remove(this);
4626
0
}
4627
4628
void
4629
SourceListener::NotifyRemoved()
4630
0
{
4631
0
  MOZ_ASSERT(NS_IsMainThread());
4632
0
  LOG(("SourceListener removed, mFinished = %d", (int) mFinished));
4633
0
  mRemoved = true;
4634
0
4635
0
  if (Activated() && !mFinished) {
4636
0
    NotifyFinished();
4637
0
  }
4638
0
4639
0
  mWindowListener = nullptr;
4640
0
  mStreamListener = nullptr;
4641
0
}
4642
4643
bool
4644
SourceListener::CapturingVideo() const
4645
0
{
4646
0
  MOZ_ASSERT(NS_IsMainThread());
4647
0
  return Activated() && mVideoDeviceState && !mVideoDeviceState->mStopped &&
4648
0
         (!mVideoDeviceState->mDevice->mSource->IsFake() ||
4649
0
          Preferences::GetBool("media.navigator.permission.fake"));
4650
0
}
4651
4652
bool
4653
SourceListener::CapturingAudio() const
4654
0
{
4655
0
  MOZ_ASSERT(NS_IsMainThread());
4656
0
  return Activated() && mAudioDeviceState && !mAudioDeviceState->mStopped &&
4657
0
         (!mAudioDeviceState->mDevice->mSource->IsFake() ||
4658
0
          Preferences::GetBool("media.navigator.permission.fake"));
4659
0
}
4660
4661
CaptureState
4662
SourceListener::CapturingSource(MediaSourceEnum aSource) const
4663
0
{
4664
0
  MOZ_ASSERT(NS_IsMainThread());
4665
0
  if ((!GetVideoDevice() || GetVideoDevice()->GetMediaSource() != aSource) &&
4666
0
      (!GetAudioDevice() || GetAudioDevice()->GetMediaSource() != aSource)) {
4667
0
    // This SourceListener doesn't capture a matching source
4668
0
    return CaptureState::Off;
4669
0
  }
4670
0
4671
0
  DeviceState& state =
4672
0
    (GetAudioDevice() && GetAudioDevice()->GetMediaSource() == aSource)
4673
0
    ? *mAudioDeviceState : *mVideoDeviceState;
4674
0
  MOZ_ASSERT(state.mDevice->GetMediaSource() == aSource);
4675
0
4676
0
  if (state.mStopped) {
4677
0
    // The source is a match but has been permanently stopped
4678
0
    return CaptureState::Off;
4679
0
  }
4680
0
4681
0
  if ((aSource == MediaSourceEnum::Camera ||
4682
0
       aSource == MediaSourceEnum::Microphone) &&
4683
0
      state.mDevice->mSource->IsFake() &&
4684
0
      !Preferences::GetBool("media.navigator.permission.fake")) {
4685
0
    // Fake Camera and Microphone only count if there is no fake permission
4686
0
    return CaptureState::Off;
4687
0
  }
4688
0
4689
0
  // Source is a match and is active
4690
0
4691
0
  if (state.mDeviceEnabled) {
4692
0
    return CaptureState::Enabled;
4693
0
  }
4694
0
4695
0
  return CaptureState::Disabled;
4696
0
}
4697
4698
RefPtr<SourceListener::ApplyConstraintsPromise>
4699
SourceListener::ApplyConstraintsToTrack(
4700
    nsPIDOMWindowInner* aWindow,
4701
    TrackID aTrackID,
4702
    const MediaTrackConstraints& aConstraintsPassedIn,
4703
    dom::CallerType aCallerType)
4704
0
{
4705
0
  MOZ_ASSERT(NS_IsMainThread());
4706
0
  DeviceState& state = GetDeviceStateFor(aTrackID);
4707
0
4708
0
  if (mStopped || state.mStopped) {
4709
0
    LOG(("gUM %s track %d applyConstraints, but source is stopped",
4710
0
         aTrackID == kAudioTrack ? "audio" : "video", aTrackID));
4711
0
    return ApplyConstraintsPromise::CreateAndResolve(false, __func__);
4712
0
  }
4713
0
4714
0
  MediaTrackConstraints c(aConstraintsPassedIn); // use a modifiable copy
4715
0
  MediaConstraintsHelper::ConvertOldWithWarning(c.mMozAutoGainControl,
4716
0
                                                c.mAutoGainControl,
4717
0
                                                "MozAutoGainControlWarning",
4718
0
                                                aWindow);
4719
0
  MediaConstraintsHelper::ConvertOldWithWarning(c.mMozNoiseSuppression,
4720
0
                                                c.mNoiseSuppression,
4721
0
                                                "MozNoiseSuppressionWarning",
4722
0
                                                aWindow);
4723
0
4724
0
  MediaManager* mgr = MediaManager::GetIfExists();
4725
0
  if (!mgr) {
4726
0
    return ApplyConstraintsPromise::CreateAndResolve(false, __func__);
4727
0
  }
4728
0
4729
0
  return MediaManager::PostTask<ApplyConstraintsPromise>(__func__,
4730
0
      [device = state.mDevice, c,
4731
0
       isChrome = aCallerType == dom::CallerType::System]
4732
0
      (MozPromiseHolder<ApplyConstraintsPromise>& aHolder) mutable {
4733
0
    MOZ_ASSERT(MediaManager::IsInMediaThread());
4734
0
    MediaManager* mgr = MediaManager::GetIfExists();
4735
0
    MOZ_RELEASE_ASSERT(mgr); // Must exist while media thread is alive
4736
0
    const char* badConstraint = nullptr;
4737
0
    nsresult rv = device->Reconfigure(c, mgr->mPrefs, &badConstraint);
4738
0
    if (rv == NS_ERROR_INVALID_ARG) {
4739
0
      // Reconfigure failed due to constraints
4740
0
      if (!badConstraint) {
4741
0
        nsTArray<RefPtr<MediaDevice>> devices;
4742
0
        devices.AppendElement(device);
4743
0
        badConstraint = MediaConstraintsHelper::SelectSettings(
4744
0
            NormalizedConstraints(c), devices, isChrome);
4745
0
      }
4746
0
4747
0
      aHolder.Reject(Some(NS_ConvertASCIItoUTF16(badConstraint)), __func__);
4748
0
      return;
4749
0
    }
4750
0
4751
0
    if (NS_FAILED(rv)) {
4752
0
      // Reconfigure failed unexpectedly
4753
0
      aHolder.Reject(Nothing(), __func__);
4754
0
      return;
4755
0
    }
4756
0
4757
0
    // Reconfigure was successful
4758
0
    aHolder.Resolve(false, __func__);
4759
0
  });
4760
0
}
4761
4762
PrincipalHandle
4763
SourceListener::GetPrincipalHandle() const
4764
0
{
4765
0
  return mPrincipalHandle;
4766
0
}
4767
4768
DeviceState&
4769
SourceListener::GetDeviceStateFor(TrackID aTrackID) const
4770
0
{
4771
0
  // XXX to support multiple tracks of a type in a stream, this should key off
4772
0
  // the TrackID and not just the type
4773
0
  switch (aTrackID) {
4774
0
    case kAudioTrack:
4775
0
      MOZ_ASSERT(mAudioDeviceState, "No audio device");
4776
0
      return *mAudioDeviceState;
4777
0
    case kVideoTrack:
4778
0
      MOZ_ASSERT(mVideoDeviceState, "No video device");
4779
0
      return *mVideoDeviceState;
4780
0
    default:
4781
0
      MOZ_CRASH("Unknown track id");
4782
0
  }
4783
0
}
4784
4785
// Doesn't kill audio
4786
void
4787
GetUserMediaWindowListener::StopSharing()
4788
0
{
4789
0
  MOZ_ASSERT(NS_IsMainThread(), "Only call on main thread");
4790
0
4791
0
  for (auto& source : mActiveListeners) {
4792
0
    source->StopSharing();
4793
0
  }
4794
0
}
4795
4796
void
4797
GetUserMediaWindowListener::StopRawID(const nsString& removedDeviceID)
4798
0
{
4799
0
  MOZ_ASSERT(NS_IsMainThread(), "Only call on main thread");
4800
0
4801
0
  for (auto& source : mActiveListeners) {
4802
0
    if (source->GetAudioDevice()) {
4803
0
      nsString id;
4804
0
      source->GetAudioDevice()->GetRawId(id);
4805
0
      if (removedDeviceID.Equals(id)) {
4806
0
        source->StopTrack(kAudioTrack);
4807
0
      }
4808
0
    }
4809
0
    if (source->GetVideoDevice()) {
4810
0
      nsString id;
4811
0
      source->GetVideoDevice()->GetRawId(id);
4812
0
      if (removedDeviceID.Equals(id)) {
4813
0
        source->StopTrack(kVideoTrack);
4814
0
      }
4815
0
    }
4816
0
  }
4817
0
}
4818
4819
void
4820
GetUserMediaWindowListener::ChromeAffectingStateChanged()
4821
0
{
4822
0
  MOZ_ASSERT(NS_IsMainThread());
4823
0
4824
0
  // We wait until stable state before notifying chrome so chrome only does one
4825
0
  // update if more updates happen in this event loop.
4826
0
4827
0
  if (mChromeNotificationTaskPosted) {
4828
0
    return;
4829
0
  }
4830
0
4831
0
  nsCOMPtr<nsIRunnable> runnable =
4832
0
    NewRunnableMethod("GetUserMediaWindowListener::NotifyChrome",
4833
0
                      this,
4834
0
                      &GetUserMediaWindowListener::NotifyChrome);
4835
0
  nsContentUtils::RunInStableState(runnable.forget());
4836
0
  mChromeNotificationTaskPosted = true;
4837
0
}
4838
4839
void
4840
GetUserMediaWindowListener::NotifyChrome()
4841
0
{
4842
0
  MOZ_ASSERT(mChromeNotificationTaskPosted);
4843
0
  mChromeNotificationTaskPosted = false;
4844
0
4845
0
  NS_DispatchToMainThread(NS_NewRunnableFunction("MediaManager::NotifyChrome",
4846
0
                                                 [windowID = mWindowID]() {
4847
0
    nsGlobalWindowInner* window =
4848
0
      nsGlobalWindowInner::GetInnerWindowWithId(windowID);
4849
0
    if (!window) {
4850
0
      MOZ_ASSERT_UNREACHABLE("Should have window");
4851
0
      return;
4852
0
    }
4853
0
4854
0
    nsresult rv = MediaManager::NotifyRecordingStatusChange(window->AsInner());
4855
0
    if (NS_FAILED(rv)) {
4856
0
      MOZ_ASSERT_UNREACHABLE("Should be able to notify chrome");
4857
0
      return;
4858
0
    }
4859
0
  }));
4860
0
}
4861
4862
} // namespace mozilla