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