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 "MediaManager.h"
6 #include "MediaPermissionGonk.h"
7
8 #include "nsArray.h"
9 #include "nsCOMPtr.h"
10 #include "nsIContentPermissionPrompt.h"
11 #include "nsIDocument.h"
12 #include "nsIDOMNavigatorUserMedia.h"
13 #include "nsIStringEnumerator.h"
14 #include "nsJSUtils.h"
15 #include "nsQueryObject.h"
16 #include "nsPIDOMWindow.h"
17 #include "nsTArray.h"
18 #include "GetUserMediaRequest.h"
19 #include "mozilla/dom/PBrowserChild.h"
20 #include "mozilla/dom/MediaStreamTrackBinding.h"
21 #include "mozilla/dom/MediaStreamError.h"
22 #include "nsISupportsPrimitives.h"
23 #include "nsServiceManagerUtils.h"
24 #include "nsArrayUtils.h"
25 #include "nsContentPermissionHelper.h"
26 #include "mozilla/dom/PermissionMessageUtils.h"
27
28 #define AUDIO_PERMISSION_NAME "audio-capture"
29 #define VIDEO_PERMISSION_NAME "video-capture"
30
31 using namespace mozilla::dom;
32
33 namespace mozilla {
34
35 static MediaPermissionManager *gMediaPermMgr = nullptr;
36
37 static void
CreateDeviceNameList(nsTArray<nsCOMPtr<nsIMediaDevice>> & aDevices,nsTArray<nsString> & aDeviceNameList)38 CreateDeviceNameList(nsTArray<nsCOMPtr<nsIMediaDevice> > &aDevices,
39 nsTArray<nsString> &aDeviceNameList)
40 {
41 for (uint32_t i = 0; i < aDevices.Length(); ++i) {
42 nsString name;
43 nsresult rv = aDevices[i]->GetName(name);
44 NS_ENSURE_SUCCESS_VOID(rv);
45 aDeviceNameList.AppendElement(name);
46 }
47 }
48
49 static already_AddRefed<nsIMediaDevice>
FindDeviceByName(nsTArray<nsCOMPtr<nsIMediaDevice>> & aDevices,const nsAString & aDeviceName)50 FindDeviceByName(nsTArray<nsCOMPtr<nsIMediaDevice> > &aDevices,
51 const nsAString &aDeviceName)
52 {
53 for (uint32_t i = 0; i < aDevices.Length(); ++i) {
54 nsCOMPtr<nsIMediaDevice> device = aDevices[i];
55 nsString deviceName;
56 device->GetName(deviceName);
57 if (deviceName.Equals(aDeviceName)) {
58 return device.forget();
59 }
60 }
61
62 return nullptr;
63 }
64
65 // Helper function for notifying permission granted
66 static nsresult
NotifyPermissionAllow(const nsAString & aCallID,nsTArray<nsCOMPtr<nsIMediaDevice>> & aDevices)67 NotifyPermissionAllow(const nsAString &aCallID, nsTArray<nsCOMPtr<nsIMediaDevice> > &aDevices)
68 {
69 nsresult rv;
70 nsCOMPtr<nsIMutableArray> array = nsArray::Create();
71
72 for (uint32_t i = 0; i < aDevices.Length(); ++i) {
73 rv = array->AppendElement(aDevices.ElementAt(i), /*weak =*/ false);
74 NS_ENSURE_SUCCESS(rv, rv);
75 }
76
77 nsCOMPtr<nsIObserverService> obs = services::GetObserverService();
78 NS_ENSURE_TRUE(obs, NS_ERROR_FAILURE);
79
80 return obs->NotifyObservers(array, "getUserMedia:response:allow",
81 aCallID.BeginReading());
82 }
83
84 // Helper function for notifying permision denial or error
85 static nsresult
NotifyPermissionDeny(const nsAString & aCallID,const nsAString & aErrorMsg)86 NotifyPermissionDeny(const nsAString &aCallID, const nsAString &aErrorMsg)
87 {
88 nsresult rv;
89 nsCOMPtr<nsISupportsString> supportsString =
90 do_CreateInstance(NS_SUPPORTS_STRING_CONTRACTID, &rv);
91 NS_ENSURE_SUCCESS(rv, rv);
92
93 rv = supportsString->SetData(aErrorMsg);
94 NS_ENSURE_SUCCESS(rv, rv);
95
96 nsCOMPtr<nsIObserverService> obs = services::GetObserverService();
97 NS_ENSURE_TRUE(obs, NS_ERROR_FAILURE);
98
99 return obs->NotifyObservers(supportsString, "getUserMedia:response:deny",
100 aCallID.BeginReading());
101 }
102
103 namespace {
104
105 /**
106 * MediaPermissionRequest will send a prompt ipdl request to b2g process according
107 * to its owned type.
108 */
109 class MediaPermissionRequest : public nsIContentPermissionRequest
110 {
111 public:
112 NS_DECL_ISUPPORTS
113 NS_DECL_NSICONTENTPERMISSIONREQUEST
114
115 MediaPermissionRequest(RefPtr<dom::GetUserMediaRequest> &aRequest,
116 nsTArray<nsCOMPtr<nsIMediaDevice> > &aDevices);
117
118 already_AddRefed<nsPIDOMWindowInner> GetOwner();
119
120 protected:
~MediaPermissionRequest()121 virtual ~MediaPermissionRequest() {}
122
123 private:
124 nsresult DoAllow(const nsString &audioDevice, const nsString &videoDevice);
125
126 bool mAudio; // Request for audio permission
127 bool mVideo; // Request for video permission
128 RefPtr<dom::GetUserMediaRequest> mRequest;
129 nsTArray<nsCOMPtr<nsIMediaDevice> > mAudioDevices; // candidate audio devices
130 nsTArray<nsCOMPtr<nsIMediaDevice> > mVideoDevices; // candidate video devices
131 nsCOMPtr<nsIContentPermissionRequester> mRequester;
132 };
133
134 // MediaPermissionRequest
NS_IMPL_ISUPPORTS(MediaPermissionRequest,nsIContentPermissionRequest)135 NS_IMPL_ISUPPORTS(MediaPermissionRequest, nsIContentPermissionRequest)
136
137 MediaPermissionRequest::MediaPermissionRequest(RefPtr<dom::GetUserMediaRequest> &aRequest,
138 nsTArray<nsCOMPtr<nsIMediaDevice> > &aDevices)
139 : mRequest(aRequest)
140 {
141 dom::MediaStreamConstraints constraints;
142 mRequest->GetConstraints(constraints);
143
144 mAudio = !constraints.mAudio.IsBoolean() || constraints.mAudio.GetAsBoolean();
145 mVideo = !constraints.mVideo.IsBoolean() || constraints.mVideo.GetAsBoolean();
146
147 for (uint32_t i = 0; i < aDevices.Length(); ++i) {
148 nsCOMPtr<nsIMediaDevice> device(aDevices[i]);
149 nsAutoString deviceType;
150 device->GetType(deviceType);
151 if (mAudio && deviceType.EqualsLiteral("audio")) {
152 mAudioDevices.AppendElement(device);
153 }
154 if (mVideo && deviceType.EqualsLiteral("video")) {
155 mVideoDevices.AppendElement(device);
156 }
157 }
158
159 nsCOMPtr<nsPIDOMWindowInner> window = GetOwner();
160 mRequester = new nsContentPermissionRequester(window);
161 }
162
163 // nsIContentPermissionRequest methods
164 NS_IMETHODIMP
GetTypes(nsIArray ** aTypes)165 MediaPermissionRequest::GetTypes(nsIArray** aTypes)
166 {
167 nsCOMPtr<nsIMutableArray> types = do_CreateInstance(NS_ARRAY_CONTRACTID);
168 //XXX append device list
169 if (mAudio) {
170 nsTArray<nsString> audioDeviceNames;
171 CreateDeviceNameList(mAudioDevices, audioDeviceNames);
172 nsCOMPtr<nsISupports> AudioType =
173 new ContentPermissionType(NS_LITERAL_CSTRING(AUDIO_PERMISSION_NAME),
174 NS_LITERAL_CSTRING("unused"),
175 audioDeviceNames);
176 types->AppendElement(AudioType, false);
177 }
178 if (mVideo) {
179 nsTArray<nsString> videoDeviceNames;
180 CreateDeviceNameList(mVideoDevices, videoDeviceNames);
181 nsCOMPtr<nsISupports> VideoType =
182 new ContentPermissionType(NS_LITERAL_CSTRING(VIDEO_PERMISSION_NAME),
183 NS_LITERAL_CSTRING("unused"),
184 videoDeviceNames);
185 types->AppendElement(VideoType, false);
186 }
187 NS_IF_ADDREF(*aTypes = types);
188
189 return NS_OK;
190 }
191
192 NS_IMETHODIMP
GetPrincipal(nsIPrincipal ** aRequestingPrincipal)193 MediaPermissionRequest::GetPrincipal(nsIPrincipal **aRequestingPrincipal)
194 {
195 NS_ENSURE_ARG_POINTER(aRequestingPrincipal);
196
197 nsCOMPtr<nsPIDOMWindowInner> window =
198 nsGlobalWindow::GetInnerWindowWithId(mRequest->InnerWindowID())->AsInner();
199 NS_ENSURE_TRUE(window, NS_ERROR_FAILURE);
200
201 nsCOMPtr<nsIDocument> doc = window->GetExtantDoc();
202 NS_ENSURE_TRUE(doc, NS_ERROR_FAILURE);
203
204 NS_ADDREF(*aRequestingPrincipal = doc->NodePrincipal());
205 return NS_OK;
206 }
207
208 NS_IMETHODIMP
GetWindow(mozIDOMWindow ** aRequestingWindow)209 MediaPermissionRequest::GetWindow(mozIDOMWindow** aRequestingWindow)
210 {
211 NS_ENSURE_ARG_POINTER(aRequestingWindow);
212 nsCOMPtr<nsPIDOMWindowInner> window =
213 nsGlobalWindow::GetInnerWindowWithId(mRequest->InnerWindowID())->AsInner();
214 window.forget(aRequestingWindow);
215 return NS_OK;
216 }
217
218 NS_IMETHODIMP
GetElement(nsIDOMElement ** aRequestingElement)219 MediaPermissionRequest::GetElement(nsIDOMElement** aRequestingElement)
220 {
221 NS_ENSURE_ARG_POINTER(aRequestingElement);
222 *aRequestingElement = nullptr;
223 return NS_OK;
224 }
225
226 NS_IMETHODIMP
Cancel()227 MediaPermissionRequest::Cancel()
228 {
229 nsString callID;
230 mRequest->GetCallID(callID);
231 NotifyPermissionDeny(callID, NS_LITERAL_STRING("SecurityError"));
232 return NS_OK;
233 }
234
235 NS_IMETHODIMP
Allow(JS::HandleValue aChoices)236 MediaPermissionRequest::Allow(JS::HandleValue aChoices)
237 {
238 // check if JS object
239 if (!aChoices.isObject()) {
240 MOZ_ASSERT(false, "Not a correct format of PermissionChoice");
241 return NS_ERROR_INVALID_ARG;
242 }
243 // iterate through audio-capture and video-capture
244 AutoJSAPI jsapi;
245 if (!jsapi.Init(&aChoices.toObject())) {
246 return NS_ERROR_UNEXPECTED;
247 }
248 JSContext* cx = jsapi.cx();
249 JS::Rooted<JSObject*> obj(cx, &aChoices.toObject());
250 JS::Rooted<JS::Value> v(cx);
251
252 // get selected audio device name
253 nsString audioDevice;
254 if (mAudio) {
255 if (!JS_GetProperty(cx, obj, AUDIO_PERMISSION_NAME, &v) || !v.isString()) {
256 return NS_ERROR_FAILURE;
257 }
258 nsAutoJSString deviceName;
259 if (!deviceName.init(cx, v)) {
260 MOZ_ASSERT(false, "Couldn't initialize string from aChoices");
261 return NS_ERROR_FAILURE;
262 }
263 audioDevice = deviceName;
264 }
265
266 // get selected video device name
267 nsString videoDevice;
268 if (mVideo) {
269 if (!JS_GetProperty(cx, obj, VIDEO_PERMISSION_NAME, &v) || !v.isString()) {
270 return NS_ERROR_FAILURE;
271 }
272 nsAutoJSString deviceName;
273 if (!deviceName.init(cx, v)) {
274 MOZ_ASSERT(false, "Couldn't initialize string from aChoices");
275 return NS_ERROR_FAILURE;
276 }
277 videoDevice = deviceName;
278 }
279
280 return DoAllow(audioDevice, videoDevice);
281 }
282
283 NS_IMETHODIMP
GetRequester(nsIContentPermissionRequester ** aRequester)284 MediaPermissionRequest::GetRequester(nsIContentPermissionRequester** aRequester)
285 {
286 NS_ENSURE_ARG_POINTER(aRequester);
287
288 nsCOMPtr<nsIContentPermissionRequester> requester = mRequester;
289 requester.forget(aRequester);
290 return NS_OK;
291 }
292
293 nsresult
DoAllow(const nsString & audioDevice,const nsString & videoDevice)294 MediaPermissionRequest::DoAllow(const nsString &audioDevice,
295 const nsString &videoDevice)
296 {
297 nsTArray<nsCOMPtr<nsIMediaDevice> > selectedDevices;
298 if (mAudio) {
299 nsCOMPtr<nsIMediaDevice> device =
300 FindDeviceByName(mAudioDevices, audioDevice);
301 if (device) {
302 selectedDevices.AppendElement(device);
303 }
304 }
305
306 if (mVideo) {
307 nsCOMPtr<nsIMediaDevice> device =
308 FindDeviceByName(mVideoDevices, videoDevice);
309 if (device) {
310 selectedDevices.AppendElement(device);
311 }
312 }
313
314 nsString callID;
315 mRequest->GetCallID(callID);
316 return NotifyPermissionAllow(callID, selectedDevices);
317 }
318
319 already_AddRefed<nsPIDOMWindowInner>
GetOwner()320 MediaPermissionRequest::GetOwner()
321 {
322 nsCOMPtr<nsPIDOMWindowInner> window =
323 nsGlobalWindow::GetInnerWindowWithId(mRequest->InnerWindowID())->AsInner();
324 return window.forget();
325 }
326
327 // Success callback for MediaManager::GetUserMediaDevices().
328 class MediaDeviceSuccessCallback: public nsIGetUserMediaDevicesSuccessCallback
329 {
330 public:
331 NS_DECL_ISUPPORTS
332 NS_DECL_NSIGETUSERMEDIADEVICESSUCCESSCALLBACK
333
MediaDeviceSuccessCallback(RefPtr<dom::GetUserMediaRequest> & aRequest)334 explicit MediaDeviceSuccessCallback(RefPtr<dom::GetUserMediaRequest> &aRequest)
335 : mRequest(aRequest) {}
336
337 protected:
~MediaDeviceSuccessCallback()338 virtual ~MediaDeviceSuccessCallback() {}
339
340 private:
341 nsresult DoPrompt(RefPtr<MediaPermissionRequest> &req);
342 RefPtr<dom::GetUserMediaRequest> mRequest;
343 };
344
NS_IMPL_ISUPPORTS(MediaDeviceSuccessCallback,nsIGetUserMediaDevicesSuccessCallback)345 NS_IMPL_ISUPPORTS(MediaDeviceSuccessCallback, nsIGetUserMediaDevicesSuccessCallback)
346
347 // nsIGetUserMediaDevicesSuccessCallback method
348 NS_IMETHODIMP
349 MediaDeviceSuccessCallback::OnSuccess(nsIVariant* aDevices)
350 {
351 nsIID elementIID;
352 uint16_t elementType;
353 void* rawArray;
354 uint32_t arrayLen;
355
356 nsresult rv;
357 rv = aDevices->GetAsArray(&elementType, &elementIID, &arrayLen, &rawArray);
358 NS_ENSURE_SUCCESS(rv, rv);
359
360 if (elementType != nsIDataType::VTYPE_INTERFACE) {
361 free(rawArray);
362 return NS_ERROR_FAILURE;
363 }
364
365 // Create array for nsIMediaDevice
366 nsTArray<nsCOMPtr<nsIMediaDevice> > devices;
367
368 nsISupports **supportsArray = reinterpret_cast<nsISupports **>(rawArray);
369 for (uint32_t i = 0; i < arrayLen; ++i) {
370 nsCOMPtr<nsIMediaDevice> device(do_QueryInterface(supportsArray[i]));
371 devices.AppendElement(device);
372 NS_IF_RELEASE(supportsArray[i]); // explicitly decrease reference count for raw pointer
373 }
374 free(rawArray); // explicitly free for the memory from nsIVariant::GetAsArray
375
376 // Send MediaPermissionRequest
377 RefPtr<MediaPermissionRequest> req = new MediaPermissionRequest(mRequest, devices);
378 rv = DoPrompt(req);
379
380 NS_ENSURE_SUCCESS(rv, rv);
381 return NS_OK;
382 }
383
384 // Trigger permission prompt UI
385 nsresult
DoPrompt(RefPtr<MediaPermissionRequest> & req)386 MediaDeviceSuccessCallback::DoPrompt(RefPtr<MediaPermissionRequest> &req)
387 {
388 nsCOMPtr<nsPIDOMWindowInner> window(req->GetOwner());
389 return dom::nsContentPermissionUtils::AskPermission(req, window);
390 }
391
392 // Error callback for MediaManager::GetUserMediaDevices()
393 class MediaDeviceErrorCallback: public nsIDOMGetUserMediaErrorCallback
394 {
395 public:
396 NS_DECL_ISUPPORTS
397 NS_DECL_NSIDOMGETUSERMEDIAERRORCALLBACK
398
MediaDeviceErrorCallback(const nsAString & aCallID)399 explicit MediaDeviceErrorCallback(const nsAString &aCallID)
400 : mCallID(aCallID) {}
401
402 protected:
~MediaDeviceErrorCallback()403 virtual ~MediaDeviceErrorCallback() {}
404
405 private:
406 const nsString mCallID;
407 };
408
NS_IMPL_ISUPPORTS(MediaDeviceErrorCallback,nsIDOMGetUserMediaErrorCallback)409 NS_IMPL_ISUPPORTS(MediaDeviceErrorCallback, nsIDOMGetUserMediaErrorCallback)
410
411 // nsIDOMGetUserMediaErrorCallback method
412 NS_IMETHODIMP
413 MediaDeviceErrorCallback::OnError(nsISupports* aError)
414 {
415 RefPtr<MediaStreamError> error = do_QueryObject(aError);
416 if (!error) {
417 return NS_ERROR_NO_INTERFACE;
418 }
419
420 nsString name;
421 error->GetName(name);
422 return NotifyPermissionDeny(mCallID, name);
423 }
424
425 } // namespace anonymous
426
427 // MediaPermissionManager
NS_IMPL_ISUPPORTS(MediaPermissionManager,nsIObserver)428 NS_IMPL_ISUPPORTS(MediaPermissionManager, nsIObserver)
429
430 MediaPermissionManager*
431 MediaPermissionManager::GetInstance()
432 {
433 if (!gMediaPermMgr) {
434 gMediaPermMgr = new MediaPermissionManager();
435 }
436
437 return gMediaPermMgr;
438 }
439
MediaPermissionManager()440 MediaPermissionManager::MediaPermissionManager()
441 {
442 nsCOMPtr<nsIObserverService> obs = services::GetObserverService();
443 if (obs) {
444 obs->AddObserver(this, "getUserMedia:request", false);
445 obs->AddObserver(this, "xpcom-shutdown", false);
446 }
447 }
448
~MediaPermissionManager()449 MediaPermissionManager::~MediaPermissionManager()
450 {
451 this->Deinit();
452 }
453
454 nsresult
Deinit()455 MediaPermissionManager::Deinit()
456 {
457 nsCOMPtr<nsIObserverService> obs = services::GetObserverService();
458 if (obs) {
459 obs->RemoveObserver(this, "getUserMedia:request");
460 obs->RemoveObserver(this, "xpcom-shutdown");
461 }
462 return NS_OK;
463 }
464
465 // nsIObserver method
466 NS_IMETHODIMP
Observe(nsISupports * aSubject,const char * aTopic,const char16_t * aData)467 MediaPermissionManager::Observe(nsISupports* aSubject, const char* aTopic,
468 const char16_t* aData)
469 {
470 nsresult rv;
471 if (!strcmp(aTopic, "getUserMedia:request")) {
472 RefPtr<dom::GetUserMediaRequest> req =
473 static_cast<dom::GetUserMediaRequest*>(aSubject);
474 rv = HandleRequest(req);
475
476 if (NS_FAILED(rv)) {
477 nsString callID;
478 req->GetCallID(callID);
479 NotifyPermissionDeny(callID, NS_LITERAL_STRING("unable to enumerate media device"));
480 }
481 } else if (!strcmp(aTopic, "xpcom-shutdown")) {
482 rv = this->Deinit();
483 } else {
484 // not reachable
485 rv = NS_ERROR_FAILURE;
486 }
487 return rv;
488 }
489
490 // Handle GetUserMediaRequest, query available media device first.
491 nsresult
HandleRequest(RefPtr<dom::GetUserMediaRequest> & req)492 MediaPermissionManager::HandleRequest(RefPtr<dom::GetUserMediaRequest> &req)
493 {
494 nsString callID;
495 req->GetCallID(callID);
496 uint64_t innerWindowID = req->InnerWindowID();
497
498 nsCOMPtr<nsPIDOMWindowInner> innerWindow =
499 nsGlobalWindow::GetInnerWindowWithId(innerWindowID)->AsInner();
500 if (!innerWindow) {
501 MOZ_ASSERT(false, "No inner window");
502 return NS_ERROR_FAILURE;
503 }
504
505 nsCOMPtr<nsIGetUserMediaDevicesSuccessCallback> onSuccess =
506 new MediaDeviceSuccessCallback(req);
507 nsCOMPtr<nsIDOMGetUserMediaErrorCallback> onError =
508 new MediaDeviceErrorCallback(callID);
509
510 dom::MediaStreamConstraints constraints;
511 req->GetConstraints(constraints);
512
513 RefPtr<MediaManager> MediaMgr = MediaManager::GetInstance();
514 nsresult rv = MediaMgr->GetUserMediaDevices(innerWindow, constraints,
515 onSuccess, onError,
516 innerWindowID, callID);
517 NS_ENSURE_SUCCESS(rv, rv);
518
519 return NS_OK;
520 }
521
522 } // namespace mozilla
523