diff options
Diffstat (limited to 'dom/media/MediaPermissionGonk.cpp')
-rw-r--r-- | dom/media/MediaPermissionGonk.cpp | 522 |
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 |