summaryrefslogtreecommitdiffstats
path: root/dom/media/MediaPermissionGonk.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'dom/media/MediaPermissionGonk.cpp')
-rw-r--r--dom/media/MediaPermissionGonk.cpp522
1 files changed, 522 insertions, 0 deletions
diff --git a/dom/media/MediaPermissionGonk.cpp b/dom/media/MediaPermissionGonk.cpp
new file mode 100644
index 000000000..2a9cbf331
--- /dev/null
+++ b/dom/media/MediaPermissionGonk.cpp
@@ -0,0 +1,522 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ * You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "MediaManager.h"
+#include "MediaPermissionGonk.h"
+
+#include "nsArray.h"
+#include "nsCOMPtr.h"
+#include "nsIContentPermissionPrompt.h"
+#include "nsIDocument.h"
+#include "nsIDOMNavigatorUserMedia.h"
+#include "nsIStringEnumerator.h"
+#include "nsJSUtils.h"
+#include "nsQueryObject.h"
+#include "nsPIDOMWindow.h"
+#include "nsTArray.h"
+#include "GetUserMediaRequest.h"
+#include "mozilla/dom/PBrowserChild.h"
+#include "mozilla/dom/MediaStreamTrackBinding.h"
+#include "mozilla/dom/MediaStreamError.h"
+#include "nsISupportsPrimitives.h"
+#include "nsServiceManagerUtils.h"
+#include "nsArrayUtils.h"
+#include "nsContentPermissionHelper.h"
+#include "mozilla/dom/PermissionMessageUtils.h"
+
+#define AUDIO_PERMISSION_NAME "audio-capture"
+#define VIDEO_PERMISSION_NAME "video-capture"
+
+using namespace mozilla::dom;
+
+namespace mozilla {
+
+static MediaPermissionManager *gMediaPermMgr = nullptr;
+
+static void
+CreateDeviceNameList(nsTArray<nsCOMPtr<nsIMediaDevice> > &aDevices,
+ nsTArray<nsString> &aDeviceNameList)
+{
+ for (uint32_t i = 0; i < aDevices.Length(); ++i) {
+ nsString name;
+ nsresult rv = aDevices[i]->GetName(name);
+ NS_ENSURE_SUCCESS_VOID(rv);
+ aDeviceNameList.AppendElement(name);
+ }
+}
+
+static already_AddRefed<nsIMediaDevice>
+FindDeviceByName(nsTArray<nsCOMPtr<nsIMediaDevice> > &aDevices,
+ const nsAString &aDeviceName)
+{
+ for (uint32_t i = 0; i < aDevices.Length(); ++i) {
+ nsCOMPtr<nsIMediaDevice> device = aDevices[i];
+ nsString deviceName;
+ device->GetName(deviceName);
+ if (deviceName.Equals(aDeviceName)) {
+ return device.forget();
+ }
+ }
+
+ return nullptr;
+}
+
+// Helper function for notifying permission granted
+static nsresult
+NotifyPermissionAllow(const nsAString &aCallID, nsTArray<nsCOMPtr<nsIMediaDevice> > &aDevices)
+{
+ nsresult rv;
+ nsCOMPtr<nsIMutableArray> array = nsArray::Create();
+
+ for (uint32_t i = 0; i < aDevices.Length(); ++i) {
+ rv = array->AppendElement(aDevices.ElementAt(i), /*weak =*/ false);
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+
+ nsCOMPtr<nsIObserverService> obs = services::GetObserverService();
+ NS_ENSURE_TRUE(obs, NS_ERROR_FAILURE);
+
+ return obs->NotifyObservers(array, "getUserMedia:response:allow",
+ aCallID.BeginReading());
+}
+
+// Helper function for notifying permision denial or error
+static nsresult
+NotifyPermissionDeny(const nsAString &aCallID, const nsAString &aErrorMsg)
+{
+ nsresult rv;
+ nsCOMPtr<nsISupportsString> supportsString =
+ do_CreateInstance(NS_SUPPORTS_STRING_CONTRACTID, &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = supportsString->SetData(aErrorMsg);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCOMPtr<nsIObserverService> obs = services::GetObserverService();
+ NS_ENSURE_TRUE(obs, NS_ERROR_FAILURE);
+
+ return obs->NotifyObservers(supportsString, "getUserMedia:response:deny",
+ aCallID.BeginReading());
+}
+
+namespace {
+
+/**
+ * MediaPermissionRequest will send a prompt ipdl request to b2g process according
+ * to its owned type.
+ */
+class MediaPermissionRequest : public nsIContentPermissionRequest
+{
+public:
+ NS_DECL_ISUPPORTS
+ NS_DECL_NSICONTENTPERMISSIONREQUEST
+
+ MediaPermissionRequest(RefPtr<dom::GetUserMediaRequest> &aRequest,
+ nsTArray<nsCOMPtr<nsIMediaDevice> > &aDevices);
+
+ already_AddRefed<nsPIDOMWindowInner> GetOwner();
+
+protected:
+ virtual ~MediaPermissionRequest() {}
+
+private:
+ nsresult DoAllow(const nsString &audioDevice, const nsString &videoDevice);
+
+ bool mAudio; // Request for audio permission
+ bool mVideo; // Request for video permission
+ RefPtr<dom::GetUserMediaRequest> mRequest;
+ nsTArray<nsCOMPtr<nsIMediaDevice> > mAudioDevices; // candidate audio devices
+ nsTArray<nsCOMPtr<nsIMediaDevice> > mVideoDevices; // candidate video devices
+ nsCOMPtr<nsIContentPermissionRequester> mRequester;
+};
+
+// MediaPermissionRequest
+NS_IMPL_ISUPPORTS(MediaPermissionRequest, nsIContentPermissionRequest)
+
+MediaPermissionRequest::MediaPermissionRequest(RefPtr<dom::GetUserMediaRequest> &aRequest,
+ nsTArray<nsCOMPtr<nsIMediaDevice> > &aDevices)
+ : mRequest(aRequest)
+{
+ dom::MediaStreamConstraints constraints;
+ mRequest->GetConstraints(constraints);
+
+ mAudio = !constraints.mAudio.IsBoolean() || constraints.mAudio.GetAsBoolean();
+ mVideo = !constraints.mVideo.IsBoolean() || constraints.mVideo.GetAsBoolean();
+
+ for (uint32_t i = 0; i < aDevices.Length(); ++i) {
+ nsCOMPtr<nsIMediaDevice> device(aDevices[i]);
+ nsAutoString deviceType;
+ device->GetType(deviceType);
+ if (mAudio && deviceType.EqualsLiteral("audio")) {
+ mAudioDevices.AppendElement(device);
+ }
+ if (mVideo && deviceType.EqualsLiteral("video")) {
+ mVideoDevices.AppendElement(device);
+ }
+ }
+
+ nsCOMPtr<nsPIDOMWindowInner> window = GetOwner();
+ mRequester = new nsContentPermissionRequester(window);
+}
+
+// nsIContentPermissionRequest methods
+NS_IMETHODIMP
+MediaPermissionRequest::GetTypes(nsIArray** aTypes)
+{
+ nsCOMPtr<nsIMutableArray> types = do_CreateInstance(NS_ARRAY_CONTRACTID);
+ //XXX append device list
+ if (mAudio) {
+ nsTArray<nsString> audioDeviceNames;
+ CreateDeviceNameList(mAudioDevices, audioDeviceNames);
+ nsCOMPtr<nsISupports> AudioType =
+ new ContentPermissionType(NS_LITERAL_CSTRING(AUDIO_PERMISSION_NAME),
+ NS_LITERAL_CSTRING("unused"),
+ audioDeviceNames);
+ types->AppendElement(AudioType, false);
+ }
+ if (mVideo) {
+ nsTArray<nsString> videoDeviceNames;
+ CreateDeviceNameList(mVideoDevices, videoDeviceNames);
+ nsCOMPtr<nsISupports> VideoType =
+ new ContentPermissionType(NS_LITERAL_CSTRING(VIDEO_PERMISSION_NAME),
+ NS_LITERAL_CSTRING("unused"),
+ videoDeviceNames);
+ types->AppendElement(VideoType, false);
+ }
+ NS_IF_ADDREF(*aTypes = types);
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+MediaPermissionRequest::GetPrincipal(nsIPrincipal **aRequestingPrincipal)
+{
+ NS_ENSURE_ARG_POINTER(aRequestingPrincipal);
+
+ nsCOMPtr<nsPIDOMWindowInner> window =
+ nsGlobalWindow::GetInnerWindowWithId(mRequest->InnerWindowID())->AsInner();
+ NS_ENSURE_TRUE(window, NS_ERROR_FAILURE);
+
+ nsCOMPtr<nsIDocument> doc = window->GetExtantDoc();
+ NS_ENSURE_TRUE(doc, NS_ERROR_FAILURE);
+
+ NS_ADDREF(*aRequestingPrincipal = doc->NodePrincipal());
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+MediaPermissionRequest::GetWindow(mozIDOMWindow** aRequestingWindow)
+{
+ NS_ENSURE_ARG_POINTER(aRequestingWindow);
+ nsCOMPtr<nsPIDOMWindowInner> window =
+ nsGlobalWindow::GetInnerWindowWithId(mRequest->InnerWindowID())->AsInner();
+ window.forget(aRequestingWindow);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+MediaPermissionRequest::GetElement(nsIDOMElement** aRequestingElement)
+{
+ NS_ENSURE_ARG_POINTER(aRequestingElement);
+ *aRequestingElement = nullptr;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+MediaPermissionRequest::Cancel()
+{
+ nsString callID;
+ mRequest->GetCallID(callID);
+ NotifyPermissionDeny(callID, NS_LITERAL_STRING("SecurityError"));
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+MediaPermissionRequest::Allow(JS::HandleValue aChoices)
+{
+ // check if JS object
+ if (!aChoices.isObject()) {
+ MOZ_ASSERT(false, "Not a correct format of PermissionChoice");
+ return NS_ERROR_INVALID_ARG;
+ }
+ // iterate through audio-capture and video-capture
+ AutoJSAPI jsapi;
+ if (!jsapi.Init(&aChoices.toObject())) {
+ return NS_ERROR_UNEXPECTED;
+ }
+ JSContext* cx = jsapi.cx();
+ JS::Rooted<JSObject*> obj(cx, &aChoices.toObject());
+ JS::Rooted<JS::Value> v(cx);
+
+ // get selected audio device name
+ nsString audioDevice;
+ if (mAudio) {
+ if (!JS_GetProperty(cx, obj, AUDIO_PERMISSION_NAME, &v) || !v.isString()) {
+ return NS_ERROR_FAILURE;
+ }
+ nsAutoJSString deviceName;
+ if (!deviceName.init(cx, v)) {
+ MOZ_ASSERT(false, "Couldn't initialize string from aChoices");
+ return NS_ERROR_FAILURE;
+ }
+ audioDevice = deviceName;
+ }
+
+ // get selected video device name
+ nsString videoDevice;
+ if (mVideo) {
+ if (!JS_GetProperty(cx, obj, VIDEO_PERMISSION_NAME, &v) || !v.isString()) {
+ return NS_ERROR_FAILURE;
+ }
+ nsAutoJSString deviceName;
+ if (!deviceName.init(cx, v)) {
+ MOZ_ASSERT(false, "Couldn't initialize string from aChoices");
+ return NS_ERROR_FAILURE;
+ }
+ videoDevice = deviceName;
+ }
+
+ return DoAllow(audioDevice, videoDevice);
+}
+
+NS_IMETHODIMP
+MediaPermissionRequest::GetRequester(nsIContentPermissionRequester** aRequester)
+{
+ NS_ENSURE_ARG_POINTER(aRequester);
+
+ nsCOMPtr<nsIContentPermissionRequester> requester = mRequester;
+ requester.forget(aRequester);
+ return NS_OK;
+}
+
+nsresult
+MediaPermissionRequest::DoAllow(const nsString &audioDevice,
+ const nsString &videoDevice)
+{
+ nsTArray<nsCOMPtr<nsIMediaDevice> > selectedDevices;
+ if (mAudio) {
+ nsCOMPtr<nsIMediaDevice> device =
+ FindDeviceByName(mAudioDevices, audioDevice);
+ if (device) {
+ selectedDevices.AppendElement(device);
+ }
+ }
+
+ if (mVideo) {
+ nsCOMPtr<nsIMediaDevice> device =
+ FindDeviceByName(mVideoDevices, videoDevice);
+ if (device) {
+ selectedDevices.AppendElement(device);
+ }
+ }
+
+ nsString callID;
+ mRequest->GetCallID(callID);
+ return NotifyPermissionAllow(callID, selectedDevices);
+}
+
+already_AddRefed<nsPIDOMWindowInner>
+MediaPermissionRequest::GetOwner()
+{
+ nsCOMPtr<nsPIDOMWindowInner> window =
+ nsGlobalWindow::GetInnerWindowWithId(mRequest->InnerWindowID())->AsInner();
+ return window.forget();
+}
+
+// Success callback for MediaManager::GetUserMediaDevices().
+class MediaDeviceSuccessCallback: public nsIGetUserMediaDevicesSuccessCallback
+{
+public:
+ NS_DECL_ISUPPORTS
+ NS_DECL_NSIGETUSERMEDIADEVICESSUCCESSCALLBACK
+
+ explicit MediaDeviceSuccessCallback(RefPtr<dom::GetUserMediaRequest> &aRequest)
+ : mRequest(aRequest) {}
+
+protected:
+ virtual ~MediaDeviceSuccessCallback() {}
+
+private:
+ nsresult DoPrompt(RefPtr<MediaPermissionRequest> &req);
+ RefPtr<dom::GetUserMediaRequest> mRequest;
+};
+
+NS_IMPL_ISUPPORTS(MediaDeviceSuccessCallback, nsIGetUserMediaDevicesSuccessCallback)
+
+// nsIGetUserMediaDevicesSuccessCallback method
+NS_IMETHODIMP
+MediaDeviceSuccessCallback::OnSuccess(nsIVariant* aDevices)
+{
+ nsIID elementIID;
+ uint16_t elementType;
+ void* rawArray;
+ uint32_t arrayLen;
+
+ nsresult rv;
+ rv = aDevices->GetAsArray(&elementType, &elementIID, &arrayLen, &rawArray);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ if (elementType != nsIDataType::VTYPE_INTERFACE) {
+ free(rawArray);
+ return NS_ERROR_FAILURE;
+ }
+
+ // Create array for nsIMediaDevice
+ nsTArray<nsCOMPtr<nsIMediaDevice> > devices;
+
+ nsISupports **supportsArray = reinterpret_cast<nsISupports **>(rawArray);
+ for (uint32_t i = 0; i < arrayLen; ++i) {
+ nsCOMPtr<nsIMediaDevice> device(do_QueryInterface(supportsArray[i]));
+ devices.AppendElement(device);
+ NS_IF_RELEASE(supportsArray[i]); // explicitly decrease reference count for raw pointer
+ }
+ free(rawArray); // explicitly free for the memory from nsIVariant::GetAsArray
+
+ // Send MediaPermissionRequest
+ RefPtr<MediaPermissionRequest> req = new MediaPermissionRequest(mRequest, devices);
+ rv = DoPrompt(req);
+
+ NS_ENSURE_SUCCESS(rv, rv);
+ return NS_OK;
+}
+
+// Trigger permission prompt UI
+nsresult
+MediaDeviceSuccessCallback::DoPrompt(RefPtr<MediaPermissionRequest> &req)
+{
+ nsCOMPtr<nsPIDOMWindowInner> window(req->GetOwner());
+ return dom::nsContentPermissionUtils::AskPermission(req, window);
+}
+
+// Error callback for MediaManager::GetUserMediaDevices()
+class MediaDeviceErrorCallback: public nsIDOMGetUserMediaErrorCallback
+{
+public:
+ NS_DECL_ISUPPORTS
+ NS_DECL_NSIDOMGETUSERMEDIAERRORCALLBACK
+
+ explicit MediaDeviceErrorCallback(const nsAString &aCallID)
+ : mCallID(aCallID) {}
+
+protected:
+ virtual ~MediaDeviceErrorCallback() {}
+
+private:
+ const nsString mCallID;
+};
+
+NS_IMPL_ISUPPORTS(MediaDeviceErrorCallback, nsIDOMGetUserMediaErrorCallback)
+
+// nsIDOMGetUserMediaErrorCallback method
+NS_IMETHODIMP
+MediaDeviceErrorCallback::OnError(nsISupports* aError)
+{
+ RefPtr<MediaStreamError> error = do_QueryObject(aError);
+ if (!error) {
+ return NS_ERROR_NO_INTERFACE;
+ }
+
+ nsString name;
+ error->GetName(name);
+ return NotifyPermissionDeny(mCallID, name);
+}
+
+} // namespace anonymous
+
+// MediaPermissionManager
+NS_IMPL_ISUPPORTS(MediaPermissionManager, nsIObserver)
+
+MediaPermissionManager*
+MediaPermissionManager::GetInstance()
+{
+ if (!gMediaPermMgr) {
+ gMediaPermMgr = new MediaPermissionManager();
+ }
+
+ return gMediaPermMgr;
+}
+
+MediaPermissionManager::MediaPermissionManager()
+{
+ nsCOMPtr<nsIObserverService> obs = services::GetObserverService();
+ if (obs) {
+ obs->AddObserver(this, "getUserMedia:request", false);
+ obs->AddObserver(this, "xpcom-shutdown", false);
+ }
+}
+
+MediaPermissionManager::~MediaPermissionManager()
+{
+ this->Deinit();
+}
+
+nsresult
+MediaPermissionManager::Deinit()
+{
+ nsCOMPtr<nsIObserverService> obs = services::GetObserverService();
+ if (obs) {
+ obs->RemoveObserver(this, "getUserMedia:request");
+ obs->RemoveObserver(this, "xpcom-shutdown");
+ }
+ return NS_OK;
+}
+
+// nsIObserver method
+NS_IMETHODIMP
+MediaPermissionManager::Observe(nsISupports* aSubject, const char* aTopic,
+ const char16_t* aData)
+{
+ nsresult rv;
+ if (!strcmp(aTopic, "getUserMedia:request")) {
+ RefPtr<dom::GetUserMediaRequest> req =
+ static_cast<dom::GetUserMediaRequest*>(aSubject);
+ rv = HandleRequest(req);
+
+ if (NS_FAILED(rv)) {
+ nsString callID;
+ req->GetCallID(callID);
+ NotifyPermissionDeny(callID, NS_LITERAL_STRING("unable to enumerate media device"));
+ }
+ } else if (!strcmp(aTopic, "xpcom-shutdown")) {
+ rv = this->Deinit();
+ } else {
+ // not reachable
+ rv = NS_ERROR_FAILURE;
+ }
+ return rv;
+}
+
+// Handle GetUserMediaRequest, query available media device first.
+nsresult
+MediaPermissionManager::HandleRequest(RefPtr<dom::GetUserMediaRequest> &req)
+{
+ nsString callID;
+ req->GetCallID(callID);
+ uint64_t innerWindowID = req->InnerWindowID();
+
+ nsCOMPtr<nsPIDOMWindowInner> innerWindow =
+ nsGlobalWindow::GetInnerWindowWithId(innerWindowID)->AsInner();
+ if (!innerWindow) {
+ MOZ_ASSERT(false, "No inner window");
+ return NS_ERROR_FAILURE;
+ }
+
+ nsCOMPtr<nsIGetUserMediaDevicesSuccessCallback> onSuccess =
+ new MediaDeviceSuccessCallback(req);
+ nsCOMPtr<nsIDOMGetUserMediaErrorCallback> onError =
+ new MediaDeviceErrorCallback(callID);
+
+ dom::MediaStreamConstraints constraints;
+ req->GetConstraints(constraints);
+
+ RefPtr<MediaManager> MediaMgr = MediaManager::GetInstance();
+ nsresult rv = MediaMgr->GetUserMediaDevices(innerWindow, constraints,
+ onSuccess, onError,
+ innerWindowID, callID);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ return NS_OK;
+}
+
+} // namespace mozilla