Line data Source code
1 : /* This Source Code Form is subject to the terms of the Mozilla Public
2 : * License, v. 2.0. If a copy of the MPL was not distributed with this file,
3 : * You can obtain one at http://mozilla.org/MPL/2.0/. */
4 :
5 : #include "mozilla/dom/MediaDevices.h"
6 : #include "mozilla/dom/MediaStreamBinding.h"
7 : #include "mozilla/dom/MediaDeviceInfo.h"
8 : #include "mozilla/dom/MediaDevicesBinding.h"
9 : #include "mozilla/dom/Promise.h"
10 : #include "mozilla/MediaManager.h"
11 : #include "MediaTrackConstraints.h"
12 : #include "nsIEventTarget.h"
13 : #include "nsIScriptGlobalObject.h"
14 : #include "nsIPermissionManager.h"
15 : #include "nsPIDOMWindow.h"
16 : #include "nsQueryObject.h"
17 :
18 : #define DEVICECHANGE_HOLD_TIME_IN_MS 1000
19 :
20 : namespace mozilla {
21 : namespace dom {
22 :
23 : class FuzzTimerCallBack final : public nsITimerCallback
24 : {
25 0 : ~FuzzTimerCallBack() {}
26 :
27 : public:
28 0 : explicit FuzzTimerCallBack(MediaDevices* aMediaDevices) : mMediaDevices(aMediaDevices) {}
29 :
30 : NS_DECL_ISUPPORTS
31 :
32 0 : NS_IMETHOD Notify(nsITimer* aTimer) final
33 : {
34 0 : mMediaDevices->DispatchTrustedEvent(NS_LITERAL_STRING("devicechange"));
35 0 : return NS_OK;
36 : }
37 :
38 : private:
39 : nsCOMPtr<MediaDevices> mMediaDevices;
40 : };
41 :
42 0 : NS_IMPL_ISUPPORTS(FuzzTimerCallBack, nsITimerCallback)
43 :
44 : class MediaDevices::GumResolver : public nsIDOMGetUserMediaSuccessCallback
45 : {
46 : public:
47 : NS_DECL_ISUPPORTS
48 :
49 0 : explicit GumResolver(Promise* aPromise) : mPromise(aPromise) {}
50 :
51 : NS_IMETHOD
52 0 : OnSuccess(nsISupports* aStream) override
53 : {
54 0 : RefPtr<DOMMediaStream> stream = do_QueryObject(aStream);
55 0 : if (!stream) {
56 0 : return NS_ERROR_FAILURE;
57 : }
58 0 : mPromise->MaybeResolve(stream);
59 0 : return NS_OK;
60 : }
61 :
62 : private:
63 0 : virtual ~GumResolver() {}
64 : RefPtr<Promise> mPromise;
65 : };
66 :
67 : class MediaDevices::EnumDevResolver : public nsIGetUserMediaDevicesSuccessCallback
68 : {
69 : public:
70 : NS_DECL_ISUPPORTS
71 :
72 0 : EnumDevResolver(Promise* aPromise, uint64_t aWindowId)
73 0 : : mPromise(aPromise), mWindowId(aWindowId) {}
74 :
75 : NS_IMETHOD
76 0 : OnSuccess(nsIVariant* aDevices) override
77 : {
78 : // Cribbed from MediaPermissionGonk.cpp
79 :
80 : // Create array for nsIMediaDevice
81 0 : nsTArray<nsCOMPtr<nsIMediaDevice>> devices;
82 : // Contain the fumes
83 : {
84 : uint16_t vtype;
85 0 : nsresult rv = aDevices->GetDataType(&vtype);
86 0 : NS_ENSURE_SUCCESS(rv, rv);
87 0 : if (vtype != nsIDataType::VTYPE_EMPTY_ARRAY) {
88 : nsIID elementIID;
89 : uint16_t elementType;
90 : void* rawArray;
91 : uint32_t arrayLen;
92 0 : rv = aDevices->GetAsArray(&elementType, &elementIID, &arrayLen, &rawArray);
93 0 : NS_ENSURE_SUCCESS(rv, rv);
94 0 : if (elementType != nsIDataType::VTYPE_INTERFACE) {
95 0 : free(rawArray);
96 0 : return NS_ERROR_FAILURE;
97 : }
98 :
99 0 : nsISupports **supportsArray = reinterpret_cast<nsISupports **>(rawArray);
100 0 : for (uint32_t i = 0; i < arrayLen; ++i) {
101 0 : nsCOMPtr<nsIMediaDevice> device(do_QueryInterface(supportsArray[i]));
102 0 : devices.AppendElement(device);
103 0 : NS_IF_RELEASE(supportsArray[i]); // explicitly decrease refcount for rawptr
104 : }
105 0 : free(rawArray); // explicitly free memory from nsIVariant::GetAsArray
106 : }
107 : }
108 0 : nsTArray<RefPtr<MediaDeviceInfo>> infos;
109 0 : for (auto& device : devices) {
110 0 : nsString type;
111 0 : device->GetType(type);
112 0 : bool isVideo = type.EqualsLiteral("video");
113 0 : bool isAudio = type.EqualsLiteral("audio");
114 0 : if (isVideo || isAudio) {
115 0 : MediaDeviceKind kind = isVideo ?
116 0 : MediaDeviceKind::Videoinput : MediaDeviceKind::Audioinput;
117 0 : nsString id;
118 0 : nsString name;
119 0 : device->GetId(id);
120 : // Include name only if page currently has a gUM stream active or
121 : // persistent permissions (audio or video) have been granted
122 0 : if (MediaManager::Get()->IsActivelyCapturingOrHasAPermission(mWindowId) ||
123 0 : Preferences::GetBool("media.navigator.permission.disabled", false)) {
124 0 : device->GetName(name);
125 : }
126 0 : RefPtr<MediaDeviceInfo> info = new MediaDeviceInfo(id, kind, name);
127 0 : infos.AppendElement(info);
128 : }
129 : }
130 0 : mPromise->MaybeResolve(infos);
131 0 : return NS_OK;
132 : }
133 :
134 : private:
135 0 : virtual ~EnumDevResolver() {}
136 : RefPtr<Promise> mPromise;
137 : uint64_t mWindowId;
138 : };
139 :
140 : class MediaDevices::GumRejecter : public nsIDOMGetUserMediaErrorCallback
141 : {
142 : public:
143 : NS_DECL_ISUPPORTS
144 :
145 0 : explicit GumRejecter(Promise* aPromise) : mPromise(aPromise) {}
146 :
147 : NS_IMETHOD
148 0 : OnError(nsISupports* aError) override
149 : {
150 0 : RefPtr<MediaStreamError> error = do_QueryObject(aError);
151 0 : if (!error) {
152 0 : return NS_ERROR_FAILURE;
153 : }
154 0 : mPromise->MaybeReject(error);
155 0 : return NS_OK;
156 : }
157 :
158 : private:
159 0 : virtual ~GumRejecter() {}
160 : RefPtr<Promise> mPromise;
161 : };
162 :
163 0 : MediaDevices::~MediaDevices()
164 : {
165 0 : MediaManager* mediamanager = MediaManager::GetIfExists();
166 0 : if (mediamanager) {
167 0 : mediamanager->RemoveDeviceChangeCallback(this);
168 : }
169 0 : }
170 :
171 0 : NS_IMPL_ISUPPORTS(MediaDevices::GumResolver, nsIDOMGetUserMediaSuccessCallback)
172 0 : NS_IMPL_ISUPPORTS(MediaDevices::EnumDevResolver, nsIGetUserMediaDevicesSuccessCallback)
173 0 : NS_IMPL_ISUPPORTS(MediaDevices::GumRejecter, nsIDOMGetUserMediaErrorCallback)
174 :
175 : already_AddRefed<Promise>
176 0 : MediaDevices::GetUserMedia(const MediaStreamConstraints& aConstraints,
177 : CallerType aCallerType,
178 : ErrorResult &aRv)
179 : {
180 0 : nsPIDOMWindowInner* window = GetOwner();
181 0 : nsCOMPtr<nsIGlobalObject> go = do_QueryInterface(window);
182 0 : RefPtr<Promise> p = Promise::Create(go, aRv);
183 0 : NS_ENSURE_TRUE(!aRv.Failed(), nullptr);
184 :
185 0 : RefPtr<GumResolver> resolver = new GumResolver(p);
186 0 : RefPtr<GumRejecter> rejecter = new GumRejecter(p);
187 :
188 0 : aRv = MediaManager::Get()->GetUserMedia(window, aConstraints,
189 : resolver, rejecter,
190 0 : aCallerType);
191 0 : return p.forget();
192 : }
193 :
194 : already_AddRefed<Promise>
195 0 : MediaDevices::EnumerateDevices(ErrorResult &aRv)
196 : {
197 0 : nsPIDOMWindowInner* window = GetOwner();
198 0 : nsCOMPtr<nsIGlobalObject> go = do_QueryInterface(window);
199 0 : RefPtr<Promise> p = Promise::Create(go, aRv);
200 0 : NS_ENSURE_TRUE(!aRv.Failed(), nullptr);
201 :
202 0 : RefPtr<EnumDevResolver> resolver = new EnumDevResolver(p, window->WindowID());
203 0 : RefPtr<GumRejecter> rejecter = new GumRejecter(p);
204 :
205 0 : aRv = MediaManager::Get()->EnumerateDevices(window, resolver, rejecter);
206 0 : return p.forget();
207 : }
208 :
209 0 : NS_IMPL_ADDREF_INHERITED(MediaDevices, DOMEventTargetHelper)
210 0 : NS_IMPL_RELEASE_INHERITED(MediaDevices, DOMEventTargetHelper)
211 0 : NS_INTERFACE_MAP_BEGIN(MediaDevices)
212 0 : NS_INTERFACE_MAP_ENTRY(MediaDevices)
213 0 : NS_INTERFACE_MAP_END_INHERITING(DOMEventTargetHelper)
214 :
215 : void
216 0 : MediaDevices::OnDeviceChange()
217 : {
218 0 : MOZ_ASSERT(NS_IsMainThread());
219 0 : nsresult rv = CheckInnerWindowCorrectness();
220 0 : if (NS_FAILED(rv)) {
221 0 : MOZ_ASSERT(false);
222 : return;
223 : }
224 :
225 0 : if (!(MediaManager::Get()->IsActivelyCapturingOrHasAPermission(GetOwner()->WindowID()) ||
226 0 : Preferences::GetBool("media.navigator.permission.disabled", false))) {
227 0 : return;
228 : }
229 :
230 0 : if (!mFuzzTimer)
231 : {
232 0 : mFuzzTimer = do_CreateInstance(NS_TIMER_CONTRACTID);
233 : }
234 :
235 0 : if (!mFuzzTimer) {
236 0 : MOZ_ASSERT(false);
237 : return;
238 : }
239 :
240 0 : mFuzzTimer->Cancel();
241 0 : RefPtr<FuzzTimerCallBack> cb = new FuzzTimerCallBack(this);
242 0 : mFuzzTimer->InitWithCallback(cb, DEVICECHANGE_HOLD_TIME_IN_MS, nsITimer::TYPE_ONE_SHOT);
243 : }
244 :
245 : mozilla::dom::EventHandlerNonNull*
246 0 : MediaDevices::GetOndevicechange()
247 : {
248 0 : if (NS_IsMainThread()) {
249 0 : return GetEventHandler(nsGkAtoms::ondevicechange, EmptyString());
250 : }
251 0 : return GetEventHandler(nullptr, NS_LITERAL_STRING("devicechange"));
252 : }
253 :
254 : void
255 0 : MediaDevices::SetOndevicechange(mozilla::dom::EventHandlerNonNull* aCallback)
256 : {
257 0 : if (NS_IsMainThread()) {
258 0 : SetEventHandler(nsGkAtoms::ondevicechange, EmptyString(), aCallback);
259 : } else {
260 0 : SetEventHandler(nullptr, NS_LITERAL_STRING("devicechange"), aCallback);
261 : }
262 :
263 0 : MediaManager::Get()->AddDeviceChangeCallback(this);
264 0 : }
265 :
266 : nsresult
267 0 : MediaDevices::AddEventListener(const nsAString& aType,
268 : nsIDOMEventListener* aListener,
269 : bool aUseCapture, bool aWantsUntrusted,
270 : uint8_t optional_argc)
271 : {
272 0 : MediaManager::Get()->AddDeviceChangeCallback(this);
273 :
274 0 : return mozilla::DOMEventTargetHelper::AddEventListener(aType, aListener,
275 : aUseCapture,
276 : aWantsUntrusted,
277 0 : optional_argc);
278 : }
279 :
280 : void
281 0 : MediaDevices::AddEventListener(const nsAString& aType,
282 : dom::EventListener* aListener,
283 : const dom::AddEventListenerOptionsOrBoolean& aOptions,
284 : const dom::Nullable<bool>& aWantsUntrusted,
285 : ErrorResult& aRv)
286 : {
287 0 : MediaManager::Get()->AddDeviceChangeCallback(this);
288 :
289 0 : return mozilla::DOMEventTargetHelper::AddEventListener(aType, aListener,
290 : aOptions,
291 : aWantsUntrusted,
292 0 : aRv);
293 : }
294 :
295 : JSObject*
296 0 : MediaDevices::WrapObject(JSContext* aCx, JS::Handle<JSObject*> aGivenProto)
297 : {
298 0 : return MediaDevicesBinding::Wrap(aCx, this, aGivenProto);
299 : }
300 :
301 : } // namespace dom
302 : } // namespace mozilla
|