diff --git a/b2g/components/ContentPermissionPrompt.js b/b2g/components/ContentPermissionPrompt.js index 86cf0c5e086..070450babbd 100644 --- a/b2g/components/ContentPermissionPrompt.js +++ b/b2g/components/ContentPermissionPrompt.js @@ -13,7 +13,7 @@ const Cr = Components.results; const Cu = Components.utils; const Cc = Components.classes; -const PROMPT_FOR_UNKNOWN = ["geolocation", "desktop-notification"]; +const PROMPT_FOR_UNKNOWN = ["geolocation", "desktop-notification", "audio-capture"]; Cu.import("resource://gre/modules/XPCOMUtils.jsm"); Cu.import("resource://gre/modules/Services.jsm"); diff --git a/dom/apps/src/PermissionsTable.jsm b/dom/apps/src/PermissionsTable.jsm index 1fa7f2f35a9..f3f7f3d399e 100644 --- a/dom/apps/src/PermissionsTable.jsm +++ b/dom/apps/src/PermissionsTable.jsm @@ -292,6 +292,11 @@ this.PermissionsTable = { geolocation: { privileged: DENY_ACTION, certified: ALLOW_ACTION }, + "audio-capture": { + app: PROMPT_ACTION, + privileged: PROMPT_ACTION, + certified: PROMPT_ACTION + }, }; /** diff --git a/dom/media/MediaManager.cpp b/dom/media/MediaManager.cpp index ae3706b1059..1816664aae7 100644 --- a/dom/media/MediaManager.cpp +++ b/dom/media/MediaManager.cpp @@ -38,6 +38,10 @@ #include "MediaEngineWebRTC.h" #endif +#ifdef MOZ_WIDGET_GONK +#include "MediaPermissionGonk.h" +#endif + // GetCurrentTime is defined in winbase.h as zero argument macro forwarding to // GetTickCount() and conflicts with MediaStream::GetCurrentTime. #ifdef GetCurrentTime @@ -1192,6 +1196,10 @@ MediaManager::GetUserMedia(JSContext* aCx, bool aPrivileged, // Force MediaManager to startup before we try to access it from other threads // Hack: should init singleton earlier unless it's expensive (mem or CPU) (void) MediaManager::Get(); +#ifdef MOZ_WIDGET_GONK + // Initialize MediaPermissionManager before send out any permission request. + (void) MediaPermissionManager::GetInstance(); +#endif //MOZ_WIDGET_GONK } // Store the WindowID in a hash table and mark as active. The entry is removed @@ -1252,10 +1260,7 @@ MediaManager::GetUserMedia(JSContext* aCx, bool aPrivileged, #ifdef MOZ_B2G_CAMERA if (mCameraManager == nullptr) { - aPrivileged = nsDOMCameraManager::CheckPermission(aWindow); - if (aPrivileged) { - mCameraManager = nsDOMCameraManager::CreateInstance(aWindow); - } + mCameraManager = nsDOMCameraManager::CreateInstance(aWindow); } #endif diff --git a/dom/media/MediaPermissionGonk.cpp b/dom/media/MediaPermissionGonk.cpp new file mode 100644 index 00000000000..8ce436ba3be --- /dev/null +++ b/dom/media/MediaPermissionGonk.cpp @@ -0,0 +1,427 @@ +/* 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 "nsCOMPtr.h" +#include "nsCxPusher.h" +#include "nsIContentPermissionPrompt.h" +#include "nsIDocument.h" +#include "nsIDOMNavigatorUserMedia.h" +#include "nsISupportsArray.h" +#include "nsPIDOMWindow.h" +#include "nsTArray.h" +#include "GetUserMediaRequest.h" +#include "PCOMContentPermissionRequestChild.h" +#include "mozilla/dom/PBrowserChild.h" +#include "mozilla/dom/TabChild.h" +#include "mozilla/dom/MediaStreamTrackBinding.h" + +#define AUDIO_PERMISSION_NAME "audio-capture" + +namespace mozilla { + +static MediaPermissionManager *gMediaPermMgr = nullptr; + +// Helper function for notifying permission granted +static nsresult +NotifyPermissionAllow(const nsAString &aCallID, nsTArray > &aDevices) +{ + nsresult rv; + nsCOMPtr array; + rv = NS_NewISupportsArray(getter_AddRefs(array)); + NS_ENSURE_SUCCESS(rv, rv); + + for (uint32_t i = 0; i < aDevices.Length(); ++i) { + rv = array->AppendElement(aDevices.ElementAt(i)); + NS_ENSURE_SUCCESS(rv, rv); + } + + nsCOMPtr 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 supportsString = + do_CreateInstance(NS_SUPPORTS_STRING_CONTRACTID, &rv); + NS_ENSURE_SUCCESS(rv, rv); + + rv = supportsString->SetData(aErrorMsg); + NS_ENSURE_SUCCESS(rv, rv); + + nsCOMPtr 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 PCOMContentPermissionRequestChild +{ +public: + NS_DECL_ISUPPORTS + NS_DECL_NSICONTENTPERMISSIONREQUEST + + MediaPermissionRequest(nsRefPtr &aRequest, + nsTArray > &aDevices); + virtual ~MediaPermissionRequest() {} + + // It will be called when prompt dismissed. + virtual bool Recv__delete__(const bool &allow) MOZ_OVERRIDE; + virtual void IPDLRelease() MOZ_OVERRIDE { Release(); } + + already_AddRefed GetOwner(); + +private: + bool mAudio; // Request for audio permission + nsRefPtr mRequest; + nsTArray > mDevices; // candiate device list +}; + +// MediaPermissionRequest +NS_IMPL_ISUPPORTS1(MediaPermissionRequest, nsIContentPermissionRequest) + +MediaPermissionRequest::MediaPermissionRequest(nsRefPtr &aRequest, + nsTArray > &aDevices) + : mRequest(aRequest) +{ + dom::MediaStreamConstraintsInternal constraints; + mRequest->GetConstraints(constraints); + + mAudio = constraints.mAudio; + + for (uint32_t i = 0; i < aDevices.Length(); ++i) { + nsCOMPtr device(aDevices[i]); + nsAutoString deviceType; + device->GetType(deviceType); + if (mAudio && deviceType.EqualsLiteral("audio")) { + mDevices.AppendElement(device); + } + } +} + +// nsIContentPermissionRequest methods +NS_IMETHODIMP +MediaPermissionRequest::GetPrincipal(nsIPrincipal **aRequestingPrincipal) +{ + NS_ENSURE_ARG_POINTER(aRequestingPrincipal); + + nsCOMPtr window = do_QueryInterface(mRequest->GetParentObject()); + NS_ENSURE_TRUE(window, NS_ERROR_FAILURE); + + nsCOMPtr doc = window->GetExtantDoc(); + NS_ENSURE_TRUE(doc, NS_ERROR_FAILURE); + + NS_ADDREF(*aRequestingPrincipal = doc->NodePrincipal()); + return NS_OK; +} + +NS_IMETHODIMP +MediaPermissionRequest::GetType(nsACString &aType) +{ + if (mAudio) { + aType = AUDIO_PERMISSION_NAME; + return NS_OK; + } + + return NS_OK; +} + +NS_IMETHODIMP +MediaPermissionRequest::GetAccess(nsACString &aAccess) +{ + aAccess = "unused"; + return NS_OK; +} + +NS_IMETHODIMP +MediaPermissionRequest::GetWindow(nsIDOMWindow** aRequestingWindow) +{ + NS_ENSURE_ARG_POINTER(aRequestingWindow); + nsCOMPtr window = do_QueryInterface(mRequest->GetParentObject()); + 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("Permission Denied")); + return NS_OK; +} + +NS_IMETHODIMP +MediaPermissionRequest::Allow() +{ + nsString callID; + mRequest->GetCallID(callID); + NotifyPermissionAllow(callID, mDevices); + return NS_OK; +} + +already_AddRefed +MediaPermissionRequest::GetOwner() +{ + nsCOMPtr window = do_QueryInterface(mRequest->GetParentObject()); + return window.forget(); +} + +//PCOMContentPermissionRequestChild +bool +MediaPermissionRequest::Recv__delete__(const bool& allow) +{ + if (allow) { + (void) Allow(); + } else { + (void) Cancel(); + } + return true; +} + +// Success callback for MediaManager::GetUserMediaDevices(). +class MediaDeviceSuccessCallback: public nsIGetUserMediaDevicesSuccessCallback +{ +public: + NS_DECL_ISUPPORTS + NS_DECL_NSIGETUSERMEDIADEVICESSUCCESSCALLBACK + + MediaDeviceSuccessCallback(nsRefPtr &aRequest) + : mRequest(aRequest) {} + virtual ~MediaDeviceSuccessCallback() {} + +private: + nsresult DoPrompt(nsRefPtr &req); + nsRefPtr mRequest; +}; + +NS_IMPL_ISUPPORTS1(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) { + NS_Free(rawArray); + return NS_ERROR_FAILURE; + } + + // Create array for nsIMediaDevice + nsTArray > devices; + + nsISupports **supportsArray = reinterpret_cast(rawArray); + for (uint32_t i = 0; i < arrayLen; ++i) { + nsCOMPtr device(do_QueryInterface(supportsArray[i])); + devices.AppendElement(device); + NS_IF_RELEASE(supportsArray[i]); // explicitly decrease reference count for raw pointer + } + NS_Free(rawArray); // explicitly free for the memory from nsIVariant::GetAsArray + + // Send MediaPermissionRequest + nsRefPtr req = new MediaPermissionRequest(mRequest, devices); + rv = DoPrompt(req); + + NS_ENSURE_SUCCESS(rv, rv); + return NS_OK; +} + +// Trigger permission prompt UI +nsresult +MediaDeviceSuccessCallback::DoPrompt(nsRefPtr &req) +{ + // for content process + if (XRE_GetProcessType() == GeckoProcessType_Content) { + MOZ_ASSERT(NS_IsMainThread()); // IPC can only be execute on main thread. + + nsresult rv; + + nsCOMPtr window(req->GetOwner()); + NS_ENSURE_TRUE(window, NS_ERROR_FAILURE); + + dom::TabChild* child = dom::TabChild::GetFrom(window->GetDocShell()); + NS_ENSURE_TRUE(child, NS_ERROR_FAILURE); + + nsAutoCString type; + rv = req->GetType(type); + NS_ENSURE_SUCCESS(rv, rv); + + nsAutoCString access; + rv = req->GetAccess(access); + NS_ENSURE_SUCCESS(rv, rv); + + nsCOMPtr principal; + rv = req->GetPrincipal(getter_AddRefs(principal)); + NS_ENSURE_SUCCESS(rv, rv); + + req->AddRef(); + child->SendPContentPermissionRequestConstructor(req, + type, + access, + IPC::Principal(principal)); + + req->Sendprompt(); + return NS_OK; + } + + // for chrome process + nsCOMPtr prompt = + do_GetService(NS_CONTENT_PERMISSION_PROMPT_CONTRACTID); + if (prompt) { + prompt->Prompt(req); + } + return NS_OK; +} + +// Error callback for MediaManager::GetUserMediaDevices() +class MediaDeviceErrorCallback: public nsIDOMGetUserMediaErrorCallback +{ +public: + NS_DECL_ISUPPORTS + NS_DECL_NSIDOMGETUSERMEDIAERRORCALLBACK + + MediaDeviceErrorCallback(const nsAString &aCallID) + : mCallID(aCallID) {} + + virtual ~MediaDeviceErrorCallback() {} + +private: + const nsString mCallID; +}; + +NS_IMPL_ISUPPORTS1(MediaDeviceErrorCallback, nsIDOMGetUserMediaErrorCallback) + +// nsIDOMGetUserMediaErrorCallback method +NS_IMETHODIMP +MediaDeviceErrorCallback::OnError(const nsAString &aError) +{ + return NotifyPermissionDeny(mCallID, aError); +} + +} // namespace anonymous + +// MediaPermissionManager +NS_IMPL_ISUPPORTS1(MediaPermissionManager, nsIObserver) + +MediaPermissionManager* +MediaPermissionManager::GetInstance() +{ + if (!gMediaPermMgr) { + gMediaPermMgr = new MediaPermissionManager(); + } + + return gMediaPermMgr; +} + +MediaPermissionManager::MediaPermissionManager() +{ + nsCOMPtr 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 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 PRUnichar* aData) +{ + nsresult rv; + if (!strcmp(aTopic, "getUserMedia:request")) { + nsRefPtr req = + static_cast(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(nsRefPtr &req) +{ + nsString callID; + req->GetCallID(callID); + + nsCOMPtr innerWindow = do_QueryInterface(req->GetParentObject()); + if (!innerWindow) { + MOZ_ASSERT(false, "No inner window"); + return NS_ERROR_FAILURE; + } + + nsCOMPtr onSuccess = + new MediaDeviceSuccessCallback(req); + nsCOMPtr onError = + new MediaDeviceErrorCallback(callID); + + dom::MediaStreamConstraintsInternal constraints; + req->GetConstraints(constraints); + + nsRefPtr MediaMgr = MediaManager::GetInstance(); + nsresult rv = MediaMgr->GetUserMediaDevices(innerWindow, constraints, onSuccess, onError); + NS_ENSURE_SUCCESS(rv, rv); + + return NS_OK; +} + +} // namespace mozilla diff --git a/dom/media/MediaPermissionGonk.h b/dom/media/MediaPermissionGonk.h new file mode 100644 index 00000000000..947e4f3fd2f --- /dev/null +++ b/dom/media/MediaPermissionGonk.h @@ -0,0 +1,36 @@ +/* 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/. */ + +#ifndef DOM_MEDIA_MEDIAPERMISSIONGONK_H +#define DOM_MEDIA_MEDIAPERMISSIONGONK_H + +#include "nsError.h" +#include "nsIObserver.h" +#include "nsISupportsImpl.h" +#include "GetUserMediaRequest.h" + +namespace mozilla { + +/** + * The observer to create the MediaPermissionMgr. This is the entry point of + * permission request on b2g. + */ +class MediaPermissionManager : public nsIObserver +{ +public: + NS_DECL_ISUPPORTS + NS_DECL_NSIOBSERVER + + static MediaPermissionManager* GetInstance(); + virtual ~MediaPermissionManager(); + +private: + MediaPermissionManager(); + nsresult Deinit(); + nsresult HandleRequest(nsRefPtr &req); +}; + +} // namespace mozilla +#endif // DOM_MEDIA_MEDIAPERMISSIONGONK_H + diff --git a/dom/media/moz.build b/dom/media/moz.build index 66e0bf361c5..83636aba9a4 100644 --- a/dom/media/moz.build +++ b/dom/media/moz.build @@ -37,6 +37,14 @@ EXTRA_COMPONENTS += [ 'PeerConnection.manifest', ] +if CONFIG['MOZ_B2G']: + EXPORTS.mozilla += [ + 'MediaPermissionGonk.h', + ] + CPP_SOURCES += [ + 'MediaPermissionGonk.cpp', + ] + FAIL_ON_WARNINGS = True LIBXUL_LIBRARY = True