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