Bug 1487204 - Add platform support for calling authorizationStatusForMediaType and requestAccessForMediaType from JS r=spohl

Add a new interface nsIOSPermissionRequest for querying the
staus of access permissions for audio/video media capture and
requesting access to audio/video capture devices. Provides an
implementation for macOS 10.14 and a default implementation
(nsOSPermissionRequestBase) for earlier macOS versions and other
platforms. The default implementation always returns status
indicating access is allowed.

Differential Revision: https://phabricator.services.mozilla.com/D4601
This commit is contained in:
Haik Aftandilian
2018-09-06 16:06:15 +00:00
parent 8a7e3540a2
commit 0d33aaf955
11 changed files with 721 additions and 1 deletions

View File

@@ -19,6 +19,7 @@
#include "nsURILoader.h" #include "nsURILoader.h"
#include "nsDocLoader.h" #include "nsDocLoader.h"
#include "nsOSHelperAppService.h" #include "nsOSHelperAppService.h"
#include "nsOSPermissionRequest.h"
#include "nsExternalProtocolHandler.h" #include "nsExternalProtocolHandler.h"
#include "nsPrefetchService.h" #include "nsPrefetchService.h"
#include "nsOfflineCacheUpdate.h" #include "nsOfflineCacheUpdate.h"
@@ -91,6 +92,9 @@ NS_GENERIC_FACTORY_CONSTRUCTOR(nsExternalURLHandlerService)
#endif #endif
NS_GENERIC_FACTORY_CONSTRUCTOR_INIT(ContentHandlerService, Init) NS_GENERIC_FACTORY_CONSTRUCTOR_INIT(ContentHandlerService, Init)
// OS access permissions
NS_GENERIC_FACTORY_CONSTRUCTOR(nsOSPermissionRequest)
// session history // session history
NS_GENERIC_FACTORY_CONSTRUCTOR(nsSHEntry) NS_GENERIC_FACTORY_CONSTRUCTOR(nsSHEntry)
@@ -106,6 +110,7 @@ NS_DEFINE_NAMED_CID(NS_PREFETCHSERVICE_CID);
NS_DEFINE_NAMED_CID(NS_OFFLINECACHEUPDATESERVICE_CID); NS_DEFINE_NAMED_CID(NS_OFFLINECACHEUPDATESERVICE_CID);
NS_DEFINE_NAMED_CID(NS_OFFLINECACHEUPDATE_CID); NS_DEFINE_NAMED_CID(NS_OFFLINECACHEUPDATE_CID);
NS_DEFINE_NAMED_CID(NS_LOCALHANDLERAPP_CID); NS_DEFINE_NAMED_CID(NS_LOCALHANDLERAPP_CID);
NS_DEFINE_NAMED_CID(NS_OSPERMISSIONREQUEST_CID);
#ifdef MOZ_ENABLE_DBUS #ifdef MOZ_ENABLE_DBUS
NS_DEFINE_NAMED_CID(NS_DBUSHANDLERAPP_CID); NS_DEFINE_NAMED_CID(NS_DBUSHANDLERAPP_CID);
#endif #endif
@@ -125,6 +130,7 @@ const mozilla::Module::CIDEntry kDocShellCIDs[] = {
{ &kNS_URI_LOADER_CID, false, nullptr, nsURILoaderConstructor }, { &kNS_URI_LOADER_CID, false, nullptr, nsURILoaderConstructor },
{ &kNS_DOCUMENTLOADER_SERVICE_CID, false, nullptr, nsDocLoaderConstructor }, { &kNS_DOCUMENTLOADER_SERVICE_CID, false, nullptr, nsDocLoaderConstructor },
{ &kNS_EXTERNALHELPERAPPSERVICE_CID, false, nullptr, nsOSHelperAppServiceConstructor }, { &kNS_EXTERNALHELPERAPPSERVICE_CID, false, nullptr, nsOSHelperAppServiceConstructor },
{ &kNS_OSPERMISSIONREQUEST_CID, false, nullptr, nsOSPermissionRequestConstructor },
{ &kNS_CONTENTHANDLERSERVICE_CID, false, nullptr, ContentHandlerServiceConstructor, { &kNS_CONTENTHANDLERSERVICE_CID, false, nullptr, ContentHandlerServiceConstructor,
mozilla::Module::CONTENT_PROCESS_ONLY }, mozilla::Module::CONTENT_PROCESS_ONLY },
{ &kNS_EXTERNALPROTOCOLHANDLER_CID, false, nullptr, nsExternalProtocolHandlerConstructor }, { &kNS_EXTERNALPROTOCOLHANDLER_CID, false, nullptr, nsExternalProtocolHandlerConstructor },
@@ -197,6 +203,7 @@ const mozilla::Module::ContractIDEntry kDocShellContracts[] = {
{ NS_SHENTRY_CONTRACTID, &kNS_SHENTRY_CID }, { NS_SHENTRY_CONTRACTID, &kNS_SHENTRY_CID },
{ NS_LOADCONTEXT_CONTRACTID, &kNS_LOADCONTEXT_CID }, { NS_LOADCONTEXT_CONTRACTID, &kNS_LOADCONTEXT_CID },
{ NS_PRIVATELOADCONTEXT_CONTRACTID, &kNS_PRIVATELOADCONTEXT_CID }, { NS_PRIVATELOADCONTEXT_CONTRACTID, &kNS_PRIVATELOADCONTEXT_CID },
{ NS_OSPERMISSIONREQUEST_CONTRACTID, &kNS_OSPERMISSIONREQUEST_CID, mozilla::Module::MAIN_PROCESS_ONLY },
{ nullptr } { nullptr }
}; };

View File

@@ -4,7 +4,14 @@
# License, v. 2.0. If a copy of the MPL was not distributed with this # 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/. # file, You can obtain one at http://mozilla.org/MPL/2.0/.
SOURCES += ['CoreLocationLocationProvider.mm'] SOURCES += [
'CoreLocationLocationProvider.mm',
'nsOSPermissionRequest.mm',
]
EXPORTS += [
'nsOSPermissionRequest.h',
]
include('/ipc/chromium/chromium-config.mozbuild') include('/ipc/chromium/chromium-config.mozbuild')

View File

@@ -0,0 +1,30 @@
/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* vim: set ts=8 sts=2 et sw=2 tw=80: */
/* 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 nsOSPermissionRequest_h__
#define nsOSPermissionRequest_h__
#include "nsOSPermissionRequestBase.h"
class nsOSPermissionRequest : public nsOSPermissionRequestBase
{
public:
nsOSPermissionRequest() {};
NS_IMETHOD GetAudioCapturePermissionState(uint16_t* aAudio) override;
NS_IMETHOD GetVideoCapturePermissionState(uint16_t* aVideo) override;
NS_IMETHOD RequestVideoCapturePermission(JSContext* aCx,
mozilla::dom::Promise** aPromiseOut)
override;
NS_IMETHOD RequestAudioCapturePermission(JSContext* aCx,
mozilla::dom::Promise** aPromiseOut)
override;
};
#endif

View File

@@ -0,0 +1,77 @@
/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* vim:set ts=2 sw=2 sts=2 et cindent: */
/* 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 "nsOSPermissionRequest.h"
#include "mozilla/dom/Promise.h"
#include "nsCocoaFeatures.h"
#include "nsCocoaUtils.h"
using namespace mozilla;
using mozilla::dom::Promise;
NS_IMETHODIMP
nsOSPermissionRequest::GetAudioCapturePermissionState(uint16_t* aAudio)
{
MOZ_ASSERT(aAudio);
if (!nsCocoaFeatures::OnMojaveOrLater()) {
return nsOSPermissionRequestBase::GetAudioCapturePermissionState(aAudio);
}
return nsCocoaUtils::GetAudioCapturePermissionState(*aAudio);
}
NS_IMETHODIMP
nsOSPermissionRequest::GetVideoCapturePermissionState(uint16_t* aVideo)
{
MOZ_ASSERT(aVideo);
if (!nsCocoaFeatures::OnMojaveOrLater()) {
return nsOSPermissionRequestBase::GetVideoCapturePermissionState(aVideo);
}
return nsCocoaUtils::GetVideoCapturePermissionState(*aVideo);
}
NS_IMETHODIMP
nsOSPermissionRequest::RequestVideoCapturePermission(JSContext* aCx,
Promise** aPromiseOut)
{
if (!nsCocoaFeatures::OnMojaveOrLater()) {
return nsOSPermissionRequestBase::RequestVideoCapturePermission(aCx, aPromiseOut);
}
RefPtr<Promise> promiseHandle;
nsresult rv = GetPromise(aCx, promiseHandle);
if (NS_FAILED(rv)) {
return rv;
}
rv = nsCocoaUtils::RequestVideoCapturePermission(promiseHandle);
promiseHandle.forget(aPromiseOut);
return rv;
}
NS_IMETHODIMP
nsOSPermissionRequest::RequestAudioCapturePermission(JSContext* aCx,
Promise** aPromiseOut)
{
if (!nsCocoaFeatures::OnMojaveOrLater()) {
return nsOSPermissionRequestBase::RequestAudioCapturePermission(aCx, aPromiseOut);
}
RefPtr<Promise> promiseHandle;
nsresult rv = GetPromise(aCx, promiseHandle);
if (NS_FAILED(rv)) {
return rv;
}
rv = nsCocoaUtils::RequestAudioCapturePermission(promiseHandle);
promiseHandle.forget(aPromiseOut);
return rv;
}

View File

@@ -20,6 +20,9 @@ with Files("windows/*LocationProvider*"):
with Files("mac/*LocationProvider*"): with Files("mac/*LocationProvider*"):
BUG_COMPONENT = ("Core", "Geolocation") BUG_COMPONENT = ("Core", "Geolocation")
with Files("mac/*OSPermissionRequest*"):
BUG_COMPONENT = ("Firefox", "Device Permissions")
with Files("linux/*LocationProvider*"): with Files("linux/*LocationProvider*"):
BUG_COMPONENT = ("Core", "Geolocation") BUG_COMPONENT = ("Core", "Geolocation")
@@ -49,14 +52,21 @@ elif toolkit == 'android':
elif toolkit == 'gtk3': elif toolkit == 'gtk3':
DIRS += ['linux'] DIRS += ['linux']
if toolkit != 'cocoa':
EXPORTS += [
'nsOSPermissionRequest.h',
]
XPIDL_SOURCES += [ XPIDL_SOURCES += [
'nsIOSFileConstantsService.idl', 'nsIOSFileConstantsService.idl',
'nsIOSPermissionRequest.idl',
] ]
XPIDL_MODULE = 'dom_system' XPIDL_MODULE = 'dom_system'
EXPORTS += [ EXPORTS += [
'nsDeviceSensors.h', 'nsDeviceSensors.h',
'nsOSPermissionRequestBase.h',
] ]
EXPORTS.mozilla += [ EXPORTS.mozilla += [
@@ -65,6 +75,7 @@ EXPORTS.mozilla += [
UNIFIED_SOURCES += [ UNIFIED_SOURCES += [
'nsDeviceSensors.cpp', 'nsDeviceSensors.cpp',
'nsOSPermissionRequestBase.cpp',
'OSFileConstants.cpp', 'OSFileConstants.cpp',
] ]

View File

@@ -0,0 +1,55 @@
/* -*- Mode: c++; c-basic-offset: 2; indent-tabs-mode: nil; tab-width: 40 -*- */
/* vim: set ts=2 et sw=2 tw=40: */
/* 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 "nsISupports.idl"
[scriptable, uuid(95790842-75a0-430d-98bf-f5ce3788ea6d)]
interface nsIOSPermissionRequest: nsISupports
{
/*
* The permission state is not known. As an example, on macOS
* this is used to indicate the user has not been prompted to
* authorize or deny access and there is no policy in place to
* deny access.
*/
const uint16_t PERMISSION_STATE_NOTDETERMINED = 0;
/* A policy prevents the application from accessing the resource */
const uint16_t PERMISSION_STATE_RESTRICTED = 1;
/* Access to the resource is denied */
const uint16_t PERMISSION_STATE_DENIED = 2;
/* Access to the resource is allowed */
const uint16_t PERMISSION_STATE_AUTHORIZED = 3;
/* Get the permission state for both audio and video capture */
void getMediaCapturePermissionState(out uint16_t aVideo,
out uint16_t aAudio);
/* Get the permission state for audio capture */
void getAudioCapturePermissionState(out uint16_t aAudio);
/* Get the permission state for video capture */
void getVideoCapturePermissionState(out uint16_t aVideo);
/*
* Request permission to access video capture devices. Returns a
* promise that resolves with |true| after the browser has been
* granted permission to capture video. If capture access is denied,
* the promise is resolved with |false|. The promise is rejected if
* an error occurs.
*/
[implicit_jscontext, must_use]
Promise requestVideoCapturePermission();
/*
* Request permission to access audio capture devices. Returns a
* promise with the same semantics as |requestVideoCapturePermission|.
*/
[implicit_jscontext, must_use]
Promise requestAudioCapturePermission();
};

View File

@@ -0,0 +1,20 @@
/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* vim: set ts=8 sts=2 et sw=2 tw=80: */
/* 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 nsOSPermissionRequest_h__
#define nsOSPermissionRequest_h__
#include "nsOSPermissionRequestBase.h"
/*
* The default implementation of nsOSPermissionRequestBase used on platforms
* that don't have a platform-specific version.
*/
class nsOSPermissionRequest : public nsOSPermissionRequestBase
{
};
#endif /* nsOSPermissionRequest_h__ */

View File

@@ -0,0 +1,93 @@
/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* vim:set ts=2 sw=2 sts=2 et cindent: */
/* 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 "nsOSPermissionRequestBase.h"
#include "mozilla/dom/Promise.h"
using namespace mozilla;
using mozilla::dom::Promise;
NS_IMPL_ISUPPORTS(
nsOSPermissionRequestBase,
nsIOSPermissionRequest,
nsISupportsWeakReference)
NS_IMETHODIMP
nsOSPermissionRequestBase::GetMediaCapturePermissionState(uint16_t* aCamera,
uint16_t* aMicrophone)
{
nsresult rv = GetVideoCapturePermissionState(aCamera);
if (NS_FAILED(rv)) {
return rv;
}
return GetAudioCapturePermissionState(aMicrophone);
}
NS_IMETHODIMP
nsOSPermissionRequestBase::GetAudioCapturePermissionState(uint16_t* aAudio)
{
MOZ_ASSERT(aAudio);
*aAudio = PERMISSION_STATE_AUTHORIZED;
return NS_OK;
}
NS_IMETHODIMP
nsOSPermissionRequestBase::GetVideoCapturePermissionState(uint16_t* aVideo)
{
MOZ_ASSERT(aVideo);
*aVideo = PERMISSION_STATE_AUTHORIZED;
return NS_OK;
}
nsresult
nsOSPermissionRequestBase::GetPromise(JSContext* aCx,
RefPtr<Promise>& aPromiseOut)
{
nsIGlobalObject* globalObject = xpc::CurrentNativeGlobal(aCx);
if (NS_WARN_IF(!globalObject)) {
return NS_ERROR_UNEXPECTED;
}
ErrorResult result;
aPromiseOut = Promise::Create(globalObject, result);
if (NS_WARN_IF(result.Failed())) {
return result.StealNSResult();
}
return NS_OK;
}
NS_IMETHODIMP
nsOSPermissionRequestBase::RequestVideoCapturePermission(JSContext* aCx,
Promise** aPromiseOut)
{
RefPtr<Promise> promiseHandle;
nsresult rv = GetPromise(aCx, promiseHandle);
if (NS_FAILED(rv)) {
return rv;
}
promiseHandle->MaybeResolve(true /* access authorized */);
promiseHandle.forget(aPromiseOut);
return NS_OK;
}
NS_IMETHODIMP
nsOSPermissionRequestBase::RequestAudioCapturePermission(JSContext* aCx,
Promise** aPromiseOut)
{
RefPtr<Promise> promiseHandle;
nsresult rv = GetPromise(aCx, promiseHandle);
if (NS_FAILED(rv)) {
return rv;
}
promiseHandle->MaybeResolve(true /* access authorized */);
promiseHandle.forget(aPromiseOut);
return NS_OK;
}

View File

@@ -0,0 +1,48 @@
/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* vim: set ts=8 sts=2 et sw=2 tw=80: */
/* 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 nsOSPermissionRequestBase_h__
#define nsOSPermissionRequestBase_h__
#include "nsIOSPermissionRequest.h"
#include "nsWeakReference.h"
#define NS_OSPERMISSIONREQUEST_CID \
{ 0x95790842, 0x75a0, 0x430d, \
{ 0x98, 0xbf, 0xf5, 0xce, 0x37, 0x88, 0xea, 0x6d } }
#define NS_OSPERMISSIONREQUEST_CONTRACTID \
"@mozilla.org/ospermissionrequest;1"
namespace mozilla {
namespace dom {
class Promise;
} // namespace dom
} // namespace mozilla
using mozilla::dom::Promise;
/*
* The base implementation of nsIOSPermissionRequest to be subclassed on
* platforms that require permission requests for access to resources such
* as media captures devices. This implementation always returns results
* indicating access is permitted.
*/
class nsOSPermissionRequestBase
: public nsIOSPermissionRequest,
public nsSupportsWeakReference
{
public:
NS_DECL_ISUPPORTS
NS_DECL_NSIOSPERMISSIONREQUEST
nsOSPermissionRequestBase() {};
protected:
nsresult GetPromise(JSContext* aCx, RefPtr<Promise>& aPromiseOut);
virtual ~nsOSPermissionRequestBase() = default;
};
#endif

View File

@@ -18,6 +18,7 @@
#include "nsObjCExceptions.h" #include "nsObjCExceptions.h"
#include "mozilla/EventForwards.h" #include "mozilla/EventForwards.h"
#include "mozilla/StaticPtr.h"
// Declare the backingScaleFactor method that we want to call // Declare the backingScaleFactor method that we want to call
// on NSView/Window/Screen objects, if they recognize it. // on NSView/Window/Screen objects, if they recognize it.
@@ -38,8 +39,14 @@ class TimeStamp;
namespace gfx { namespace gfx {
class SourceSurface; class SourceSurface;
} // namespace gfx } // namespace gfx
namespace dom {
class Promise;
} // namespace dom
} // namespace mozilla } // namespace mozilla
using mozilla::StaticAutoPtr;
using mozilla::StaticMutex;
// Used to retain a Cocoa object for the remainder of a method's execution. // Used to retain a Cocoa object for the remainder of a method's execution.
class nsAutoRetainCocoaObject { class nsAutoRetainCocoaObject {
public: public:
@@ -102,11 +109,18 @@ struct KeyBindingsCommand
@end // NativeKeyBindingsRecorder @end // NativeKeyBindingsRecorder
#if !defined(MAC_OS_X_VERSION_10_14) || \
MAC_OS_X_VERSION_MAX_ALLOWED < MAC_OS_X_VERSION_10_14
typedef NSString* AVMediaType;
#endif
class nsCocoaUtils class nsCocoaUtils
{ {
typedef mozilla::gfx::SourceSurface SourceSurface; typedef mozilla::gfx::SourceSurface SourceSurface;
typedef mozilla::LayoutDeviceIntPoint LayoutDeviceIntPoint; typedef mozilla::LayoutDeviceIntPoint LayoutDeviceIntPoint;
typedef mozilla::LayoutDeviceIntRect LayoutDeviceIntRect; typedef mozilla::LayoutDeviceIntRect LayoutDeviceIntRect;
typedef mozilla::dom::Promise Promise;
typedef StaticAutoPtr<nsTArray<RefPtr<Promise>>> PromiseArray;
public: public:
@@ -396,6 +410,84 @@ public:
* If aEventTime is 0, this returns current timestamp. * If aEventTime is 0, this returns current timestamp.
*/ */
static mozilla::TimeStamp GetEventTimeStamp(NSTimeInterval aEventTime); static mozilla::TimeStamp GetEventTimeStamp(NSTimeInterval aEventTime);
/**
* Get the current video capture permission status.
* Returns NS_ERROR_NOT_IMPLEMENTED on 10.13 and earlier macOS versions.
*/
static nsresult GetVideoCapturePermissionState(uint16_t& aPermissionState);
/**
* Get the current audio capture permission status.
* Returns NS_ERROR_NOT_IMPLEMENTED on 10.13 and earlier macOS versions.
*/
static nsresult GetAudioCapturePermissionState(uint16_t& aPermissionState);
/**
* Request video capture permission from the OS. Caller must be running
* on the main thread and the promise will be resolved on the main thread.
* Returns NS_ERROR_NOT_IMPLEMENTED on 10.13 and earlier macOS versions.
*/
static nsresult RequestVideoCapturePermission(RefPtr<Promise>& aPromise);
/**
* Request audio capture permission from the OS. Caller must be running
* on the main thread and the promise will be resolved on the main thread.
* Returns NS_ERROR_NOT_IMPLEMENTED on 10.13 and earlier macOS versions.
*/
static nsresult RequestAudioCapturePermission(RefPtr<Promise>& aPromise);
private:
/**
* Completion handlers used as an argument to the macOS API to
* request media capture permission. These are called asynchronously
* on an arbitrary dispatch queue.
*/
static void (^AudioCompletionHandler)(BOOL);
static void (^VideoCompletionHandler)(BOOL);
/**
* Called from the audio and video completion handlers in order to
* dispatch the handling back to the main thread.
*/
static void ResolveAudioCapturePromises(bool aGranted);
static void ResolveVideoCapturePromises(bool aGranted);
/**
* Main implementation for Request{Audio,Video}CapturePermission.
* @param aType the AVMediaType to request capture permission for
* @param aPromise the Promise to resolve when capture permission
* is either allowed or denied
* @param aPromiseList the array of promises to save |aPromise| in
* @param aHandler the block function (either ResolveAudioCapturePromises
* or ResolveVideoCapturePromises) to be used as
* the requestAccessForMediaType callback.
*/
static nsresult RequestCapturePermission(NSString* aType,
RefPtr<Promise>& aPromise,
PromiseArray& aPromiseList,
void (^aHandler)(BOOL granted));
/**
* Resolves the pending promises that are waiting for a response
* to a request video or audio capture permission.
*/
static void ResolveMediaCapturePromises(bool aGranted,
PromiseArray& aPromiseList);
/**
* Array of promises waiting to be resolved due to a video capture request.
*/
static PromiseArray sVideoCapturePromises;
/**
* Array of promises waiting to be resolved due to an audio capture request.
*/
static PromiseArray sAudioCapturePromises;
/**
* Lock protecting |sVideoCapturePromises| and |sAudioCapturePromises|.
*/
static StaticMutex sMediaCaptureMutex;
}; };
#endif // nsCocoaUtils_h_ #endif // nsCocoaUtils_h_

View File

@@ -3,6 +3,8 @@
* License, v. 2.0. If a copy of the MPL was not distributed with this * 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/. */ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
#import <AVFoundation/AVFoundation.h>
#include <cmath> #include <cmath>
#include "gfx2DGlue.h" #include "gfx2DGlue.h"
@@ -16,6 +18,8 @@
#include "nsCOMPtr.h" #include "nsCOMPtr.h"
#include "nsIInterfaceRequestorUtils.h" #include "nsIInterfaceRequestorUtils.h"
#include "nsIAppShellService.h" #include "nsIAppShellService.h"
#include "nsIOSPermissionRequest.h"
#include "nsIRunnable.h"
#include "nsIXULWindow.h" #include "nsIXULWindow.h"
#include "nsIBaseWindow.h" #include "nsIBaseWindow.h"
#include "nsIServiceManager.h" #include "nsIServiceManager.h"
@@ -23,14 +27,19 @@
#include "nsToolkit.h" #include "nsToolkit.h"
#include "nsCRT.h" #include "nsCRT.h"
#include "SVGImageContext.h" #include "SVGImageContext.h"
#include "mozilla/ClearOnShutdown.h"
#include "mozilla/dom/Promise.h"
#include "mozilla/gfx/2D.h" #include "mozilla/gfx/2D.h"
#include "mozilla/Logging.h"
#include "mozilla/MiscEvents.h" #include "mozilla/MiscEvents.h"
#include "mozilla/Preferences.h" #include "mozilla/Preferences.h"
#include "mozilla/TextEvents.h" #include "mozilla/TextEvents.h"
#include "mozilla/StaticMutex.h"
using namespace mozilla; using namespace mozilla;
using namespace mozilla::widget; using namespace mozilla::widget;
using mozilla::dom::Promise;
using mozilla::gfx::BackendType; using mozilla::gfx::BackendType;
using mozilla::gfx::DataSourceSurface; using mozilla::gfx::DataSourceSurface;
using mozilla::gfx::DrawTarget; using mozilla::gfx::DrawTarget;
@@ -44,6 +53,21 @@ using mozilla::gfx::SourceSurface;
using mozilla::image::ImageRegion; using mozilla::image::ImageRegion;
using std::ceil; using std::ceil;
LazyLogModule gCocoaUtilsLog("nsCocoaUtils");
#undef LOG
#define LOG(...) MOZ_LOG(gCocoaUtilsLog, LogLevel::Debug, (__VA_ARGS__))
/*
* For each audio and video capture request, we hold an owning reference
* to a promise to be resolved when the request's async callback is invoked.
* sVideoCapturePromises and sAudioCapturePromises are arrays of video and
* audio promises waiting for to be resolved. Each array is protected by a
* mutex.
*/
nsCocoaUtils::PromiseArray nsCocoaUtils::sVideoCapturePromises;
nsCocoaUtils::PromiseArray nsCocoaUtils::sAudioCapturePromises;
StaticMutex nsCocoaUtils::sMediaCaptureMutex;
static float static float
MenuBarScreenHeight() MenuBarScreenHeight()
{ {
@@ -1123,3 +1147,259 @@ nsCocoaUtils::GetEventTimeStamp(NSTimeInterval aEventTime)
BaseTimeDurationPlatformUtils::TicksFromMilliseconds(aEventTime * 1000.0); BaseTimeDurationPlatformUtils::TicksFromMilliseconds(aEventTime * 1000.0);
return TimeStamp::FromSystemTime(tick); return TimeStamp::FromSystemTime(tick);
} }
// AVAuthorizationStatus is not needed unless we are running on 10.14.
// However, on pre-10.14 SDK's, AVAuthorizationStatus and its enum values
// are both defined and prohibited from use by compile-time checks. We
// define a copy of AVAuthorizationStatus to allow compilation on pre-10.14
// SDK's. The enum values must match what is defined in the 10.14 SDK.
// We use ASSERTS for 10.14 SDK builds to check the enum values match.
enum GeckoAVAuthorizationStatus {
GeckoAVAuthorizationStatusNotDetermined = 0,
GeckoAVAuthorizationStatusRestricted = 1,
GeckoAVAuthorizationStatusDenied = 2,
GeckoAVAuthorizationStatusAuthorized = 3
};
#if !defined(MAC_OS_X_VERSION_10_14) || \
MAC_OS_X_VERSION_MAX_ALLOWED < MAC_OS_X_VERSION_10_14
// Define authorizationStatusForMediaType: as returning
// GeckoAVAuthorizationStatus instead of AVAuthorizationStatus to allow
// compilation on pre-10.14 SDK's.
@interface AVCaptureDevice(GeckoAVAuthorizationStatus)
+ (GeckoAVAuthorizationStatus)authorizationStatusForMediaType:(AVMediaType)mediaType;
@end
@interface AVCaptureDevice(WithCompletionHandler)
+ (void)requestAccessForMediaType:(AVMediaType)mediaType completionHandler:(void (^)(BOOL granted))handler;
@end
#endif
static const char*
AVMediaTypeToString(AVMediaType aType)
{
if (aType == AVMediaTypeVideo) {
return "video";
}
if (aType == AVMediaTypeAudio) {
return "audio";
}
return "unexpected type";
}
static void
LogAuthorizationStatus(AVMediaType aType, int aState)
{
const char* stateString;
switch (aState) {
case GeckoAVAuthorizationStatusAuthorized:
stateString = "AVAuthorizationStatusAuthorized";
break;
case GeckoAVAuthorizationStatusDenied:
stateString = "AVAuthorizationStatusDenied";
break;
case GeckoAVAuthorizationStatusNotDetermined:
stateString = "AVAuthorizationStatusNotDetermined";
break;
case GeckoAVAuthorizationStatusRestricted:
stateString = "AVAuthorizationStatusRestricted";
break;
default:
stateString = "Invalid state";
}
LOG("%s authorization status: %s\n", AVMediaTypeToString(aType), stateString);
}
static nsresult
GetPermissionState(AVMediaType aMediaType, uint16_t& aState)
{
MOZ_ASSERT(aMediaType == AVMediaTypeVideo || aMediaType == AVMediaTypeAudio);
// Only attempt to check authorization status on 10.14+.
if (!nsCocoaFeatures::OnMojaveOrLater()) {
return NS_ERROR_NOT_IMPLEMENTED;
}
GeckoAVAuthorizationStatus authStatus =
[AVCaptureDevice authorizationStatusForMediaType:aMediaType];
LogAuthorizationStatus(aMediaType, authStatus);
// Convert GeckoAVAuthorizationStatus to nsIOSPermissionRequest const
switch (authStatus) {
case GeckoAVAuthorizationStatusAuthorized:
aState = nsIOSPermissionRequest::PERMISSION_STATE_AUTHORIZED;
return NS_OK;
case GeckoAVAuthorizationStatusDenied:
aState = nsIOSPermissionRequest::PERMISSION_STATE_DENIED;
return NS_OK;
case GeckoAVAuthorizationStatusNotDetermined:
aState = nsIOSPermissionRequest::PERMISSION_STATE_NOTDETERMINED;
return NS_OK;
case GeckoAVAuthorizationStatusRestricted:
aState = nsIOSPermissionRequest::PERMISSION_STATE_RESTRICTED;
return NS_OK;
default:
MOZ_ASSERT(false, "Invalid authorization status");
return NS_ERROR_UNEXPECTED;
}
}
nsresult
nsCocoaUtils::GetVideoCapturePermissionState(uint16_t& aPermissionState)
{
return GetPermissionState(AVMediaTypeVideo, aPermissionState);
}
nsresult
nsCocoaUtils::GetAudioCapturePermissionState(uint16_t& aPermissionState)
{
return GetPermissionState(AVMediaTypeAudio, aPermissionState);
}
nsresult
nsCocoaUtils::RequestVideoCapturePermission(RefPtr<Promise>& aPromise)
{
MOZ_ASSERT(NS_IsMainThread());
return nsCocoaUtils::RequestCapturePermission(AVMediaTypeVideo,
aPromise,
sVideoCapturePromises,
VideoCompletionHandler);
}
nsresult
nsCocoaUtils::RequestAudioCapturePermission(RefPtr<Promise>& aPromise)
{
MOZ_ASSERT(NS_IsMainThread());
return nsCocoaUtils::RequestCapturePermission(AVMediaTypeAudio,
aPromise,
sAudioCapturePromises,
AudioCompletionHandler);
}
//
// Stores |aPromise| on |aPromiseList| and starts an asynchronous media
// capture request for the given media type |aType|. If we are already
// waiting for a capture request for this media type, don't start a new
// request. |aHandler| is invoked on an arbitrary dispatch queue when the
// request completes and must resolve any waiting Promises on the main
// thread.
//
nsresult
nsCocoaUtils::RequestCapturePermission(AVMediaType aType,
RefPtr<Promise>& aPromise,
PromiseArray& aPromiseList,
void (^aHandler)(BOOL granted))
{
MOZ_ASSERT(aType == AVMediaTypeVideo || aType == AVMediaTypeAudio);
#if defined(MAC_OS_X_VERSION_10_14)
// Ensure our enum constants match. We can only do this when
// compiling on 10.14+ because AVAuthorizationStatus is
// prohibited by preprocessor checks on earlier OS versions.
MOZ_ASSERT((int)GeckoAVAuthorizationStatusNotDetermined ==
(int)AVAuthorizationStatusNotDetermined);
MOZ_ASSERT((int)GeckoAVAuthorizationStatusRestricted ==
(int)AVAuthorizationStatusRestricted);
MOZ_ASSERT((int)GeckoAVAuthorizationStatusDenied ==
(int)AVAuthorizationStatusDenied);
MOZ_ASSERT((int)GeckoAVAuthorizationStatusAuthorized ==
(int)AVAuthorizationStatusAuthorized);
#endif
LOG("RequestCapturePermission(%s)", AVMediaTypeToString(aType));
// Only attempt to request authorization on 10.14+.
if (!nsCocoaFeatures::OnMojaveOrLater()) {
return NS_ERROR_NOT_IMPLEMENTED;
}
sMediaCaptureMutex.Lock();
// Initialize our list of promises on first invocation
if (aPromiseList == nullptr) {
aPromiseList = new nsTArray<RefPtr<Promise>>;
ClearOnShutdown(&aPromiseList);
}
aPromiseList->AppendElement(aPromise);
size_t nPromises = aPromiseList->Length();
sMediaCaptureMutex.Unlock();
LOG("RequestCapturePermission(%s): %ld promise(s) unresolved",
AVMediaTypeToString(aType), nPromises);
// If we had one or more more existing promises waiting to be resolved
// by the completion handler, we don't need to start another request.
if (nPromises > 1) {
return NS_OK;
}
// Start the request
[AVCaptureDevice requestAccessForMediaType:aType completionHandler:aHandler];
return NS_OK;
}
//
// Audio capture request completion handler. Called from an arbitrary
// dispatch queue.
//
void (^nsCocoaUtils::AudioCompletionHandler)(BOOL) = ^void (BOOL granted)
{
nsCocoaUtils::ResolveAudioCapturePromises(granted);
};
//
// Video capture request completion handler. Called from an arbitrary
// dispatch queue.
//
void (^nsCocoaUtils::VideoCompletionHandler)(BOOL) = ^void (BOOL granted)
{
nsCocoaUtils::ResolveVideoCapturePromises(granted);
};
void
nsCocoaUtils::ResolveMediaCapturePromises(bool aGranted,
PromiseArray& aPromiseList)
{
StaticMutexAutoLock lock(sMediaCaptureMutex);
// Remove each promise from the list and resolve it.
while (aPromiseList->Length() > 0) {
RefPtr<Promise> promise = aPromiseList->LastElement();
aPromiseList->RemoveLastElement();
// Resolve on main thread
nsCOMPtr<nsIRunnable> runnable(NS_NewRunnableFunction(
"ResolveMediaAccessPromise",
[aGranted, aPromise = std::move(promise)]() {
aPromise->MaybeResolve(aGranted);
}));
NS_DispatchToMainThread(runnable.forget());
}
}
void
nsCocoaUtils::ResolveAudioCapturePromises(bool aGranted)
{
// Resolve on main thread
nsCOMPtr<nsIRunnable> runnable(NS_NewRunnableFunction(
"ResolveAudioCapturePromise", [aGranted]() {
ResolveMediaCapturePromises(aGranted, sAudioCapturePromises);
}));
NS_DispatchToMainThread(runnable.forget());
}
void
nsCocoaUtils::ResolveVideoCapturePromises(bool aGranted)
{
// Resolve on main thread
nsCOMPtr<nsIRunnable> runnable(NS_NewRunnableFunction(
"ResolveVideoCapturePromise", [aGranted]() {
ResolveMediaCapturePromises(aGranted, sVideoCapturePromises);
}));
NS_DispatchToMainThread(runnable.forget());
}