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