Bug 1487204 - Add platform support for calling authorizationStatusForMediaType and requestAccessForMediaType from JS r=spohl
authorHaik Aftandilian <haftandilian@mozilla.com>
Thu, 06 Sep 2018 16:06:15 +0000
changeset 435091 cc550315583a58f968b438ec7e892a333e9c906a
parent 435090 7d0e529c980343e3f93d22949e221dddcbfbd303
child 435092 50313b936f90394696e1d771fb993eb51077ad2a
push id34591
push userbtara@mozilla.com
push dateThu, 06 Sep 2018 21:53:32 +0000
treeherdermozilla-central@e9d83a2e788a [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersspohl
bugs1487204
milestone64.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
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
docshell/build/nsDocShellModule.cpp
dom/system/mac/moz.build
dom/system/mac/nsOSPermissionRequest.h
dom/system/mac/nsOSPermissionRequest.mm
dom/system/moz.build
dom/system/nsIOSPermissionRequest.idl
dom/system/nsOSPermissionRequest.h
dom/system/nsOSPermissionRequestBase.cpp
dom/system/nsOSPermissionRequestBase.h
widget/cocoa/nsCocoaUtils.h
widget/cocoa/nsCocoaUtils.mm
--- a/docshell/build/nsDocShellModule.cpp
+++ b/docshell/build/nsDocShellModule.cpp
@@ -14,16 +14,17 @@
 #include "nsWebNavigationInfo.h"
 #include "nsAboutRedirector.h"
 #include "nsCDefaultURIFixup.h"
 
 // uriloader
 #include "nsURILoader.h"
 #include "nsDocLoader.h"
 #include "nsOSHelperAppService.h"
+#include "nsOSPermissionRequest.h"
 #include "nsExternalProtocolHandler.h"
 #include "nsPrefetchService.h"
 #include "nsOfflineCacheUpdate.h"
 #include "nsLocalHandlerApp.h"
 #include "ContentHandlerService.h"
 #ifdef MOZ_ENABLE_DBUS
 #include "nsDBusHandlerApp.h"
 #endif
@@ -86,31 +87,35 @@ NS_GENERIC_FACTORY_CONSTRUCTOR(PlatformL
 #ifdef MOZ_ENABLE_DBUS
 NS_GENERIC_FACTORY_CONSTRUCTOR(nsDBusHandlerApp)
 #endif
 #if defined(MOZ_WIDGET_ANDROID)
 NS_GENERIC_FACTORY_CONSTRUCTOR(nsExternalURLHandlerService)
 #endif
 NS_GENERIC_FACTORY_CONSTRUCTOR_INIT(ContentHandlerService, Init)
 
+// OS access permissions
+NS_GENERIC_FACTORY_CONSTRUCTOR(nsOSPermissionRequest)
+
 // session history
 NS_GENERIC_FACTORY_CONSTRUCTOR(nsSHEntry)
 
 NS_DEFINE_NAMED_CID(NS_DOCSHELL_CID);
 NS_DEFINE_NAMED_CID(NS_DEFAULTURIFIXUP_CID);
 NS_DEFINE_NAMED_CID(NS_WEBNAVIGATION_INFO_CID);
 NS_DEFINE_NAMED_CID(NS_ABOUT_REDIRECTOR_MODULE_CID);
 NS_DEFINE_NAMED_CID(NS_URI_LOADER_CID);
 NS_DEFINE_NAMED_CID(NS_DOCUMENTLOADER_SERVICE_CID);
 NS_DEFINE_NAMED_CID(NS_EXTERNALHELPERAPPSERVICE_CID);
 NS_DEFINE_NAMED_CID(NS_EXTERNALPROTOCOLHANDLER_CID);
 NS_DEFINE_NAMED_CID(NS_PREFETCHSERVICE_CID);
 NS_DEFINE_NAMED_CID(NS_OFFLINECACHEUPDATESERVICE_CID);
 NS_DEFINE_NAMED_CID(NS_OFFLINECACHEUPDATE_CID);
 NS_DEFINE_NAMED_CID(NS_LOCALHANDLERAPP_CID);
+NS_DEFINE_NAMED_CID(NS_OSPERMISSIONREQUEST_CID);
 #ifdef MOZ_ENABLE_DBUS
 NS_DEFINE_NAMED_CID(NS_DBUSHANDLERAPP_CID);
 #endif
 #if defined(MOZ_WIDGET_ANDROID)
 NS_DEFINE_NAMED_CID(NS_EXTERNALURLHANDLERSERVICE_CID);
 #endif
 NS_DEFINE_NAMED_CID(NS_SHENTRY_CID);
 NS_DEFINE_NAMED_CID(NS_CONTENTHANDLERSERVICE_CID);
@@ -120,16 +125,17 @@ NS_DEFINE_NAMED_CID(NS_PRIVATELOADCONTEX
 const mozilla::Module::CIDEntry kDocShellCIDs[] = {
   { &kNS_DOCSHELL_CID, false, nullptr, nsDocShellConstructor },
   { &kNS_DEFAULTURIFIXUP_CID, false, nullptr, nsDefaultURIFixupConstructor },
   { &kNS_WEBNAVIGATION_INFO_CID, false, nullptr, nsWebNavigationInfoConstructor },
   { &kNS_ABOUT_REDIRECTOR_MODULE_CID, false, nullptr, nsAboutRedirector::Create },
   { &kNS_URI_LOADER_CID, false, nullptr, nsURILoaderConstructor },
   { &kNS_DOCUMENTLOADER_SERVICE_CID, false, nullptr, nsDocLoaderConstructor },
   { &kNS_EXTERNALHELPERAPPSERVICE_CID, false, nullptr, nsOSHelperAppServiceConstructor },
+  { &kNS_OSPERMISSIONREQUEST_CID, false, nullptr, nsOSPermissionRequestConstructor },
   { &kNS_CONTENTHANDLERSERVICE_CID, false, nullptr, ContentHandlerServiceConstructor,
     mozilla::Module::CONTENT_PROCESS_ONLY },
   { &kNS_EXTERNALPROTOCOLHANDLER_CID, false, nullptr, nsExternalProtocolHandlerConstructor },
   { &kNS_PREFETCHSERVICE_CID, false, nullptr, nsPrefetchServiceConstructor },
   { &kNS_OFFLINECACHEUPDATESERVICE_CID, false, nullptr, nsOfflineCacheUpdateServiceConstructor },
   { &kNS_OFFLINECACHEUPDATE_CID, false, nullptr, nsOfflineCacheUpdateConstructor },
   { &kNS_LOCALHANDLERAPP_CID, false, nullptr, PlatformLocalHandlerApp_tConstructor },
 #ifdef MOZ_ENABLE_DBUS
@@ -192,16 +198,17 @@ const mozilla::Module::ContractIDEntry k
   { NS_DBUSHANDLERAPP_CONTRACTID, &kNS_DBUSHANDLERAPP_CID },
 #endif
 #if defined(MOZ_WIDGET_ANDROID)
   { NS_EXTERNALURLHANDLERSERVICE_CONTRACTID, &kNS_EXTERNALURLHANDLERSERVICE_CID },
 #endif
   { NS_SHENTRY_CONTRACTID, &kNS_SHENTRY_CID },
   { NS_LOADCONTEXT_CONTRACTID, &kNS_LOADCONTEXT_CID },
   { NS_PRIVATELOADCONTEXT_CONTRACTID, &kNS_PRIVATELOADCONTEXT_CID },
+  { NS_OSPERMISSIONREQUEST_CONTRACTID, &kNS_OSPERMISSIONREQUEST_CID, mozilla::Module::MAIN_PROCESS_ONLY },
   { nullptr }
 };
 
 static const mozilla::Module kDocShellModule = {
   mozilla::Module::kVersion,
   kDocShellCIDs,
   kDocShellContracts,
   nullptr,
--- a/dom/system/mac/moz.build
+++ b/dom/system/mac/moz.build
@@ -1,15 +1,22 @@
 # -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*-
 # vim: set filetype=python:
 # 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/.
 
-SOURCES += ['CoreLocationLocationProvider.mm']
+SOURCES += [
+    'CoreLocationLocationProvider.mm',
+    'nsOSPermissionRequest.mm',
+]
+
+EXPORTS += [
+    'nsOSPermissionRequest.h',
+]
 
 include('/ipc/chromium/chromium-config.mozbuild')
 
 FINAL_LIBRARY = 'xul'
 LOCAL_INCLUDES += [
     '/dom/geolocation',
 ]
 
new file mode 100644
--- /dev/null
+++ b/dom/system/mac/nsOSPermissionRequest.h
@@ -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
new file mode 100644
--- /dev/null
+++ b/dom/system/mac/nsOSPermissionRequest.mm
@@ -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;
+}
--- a/dom/system/moz.build
+++ b/dom/system/moz.build
@@ -15,16 +15,19 @@ with Files("*ocationProvider*"):
     BUG_COMPONENT = ("Core", "Geolocation")
 
 with Files("windows/*LocationProvider*"):
     BUG_COMPONENT = ("Core", "Geolocation")
 
 with Files("mac/*LocationProvider*"):
     BUG_COMPONENT = ("Core", "Geolocation")
 
+with Files("mac/*OSPermissionRequest*"):
+    BUG_COMPONENT = ("Firefox", "Device Permissions")
+
 with Files("linux/*LocationProvider*"):
     BUG_COMPONENT = ("Core", "Geolocation")
 
 with Files("android/*LocationProvider*"):
     BUG_COMPONENT = ("Core", "Geolocation")
 
 with Files("tests/chrome.ini"):
     BUG_COMPONENT = ("Toolkit", "OS.File")
@@ -44,32 +47,40 @@ if toolkit == 'windows':
     DIRS += ['windows']
 elif toolkit == 'cocoa':
     DIRS += ['mac']
 elif toolkit == 'android':
     DIRS += ['android']
 elif toolkit == 'gtk3':
     DIRS += ['linux']
 
+if toolkit != 'cocoa':
+    EXPORTS += [
+        'nsOSPermissionRequest.h',
+    ]
+
 XPIDL_SOURCES += [
     'nsIOSFileConstantsService.idl',
+    'nsIOSPermissionRequest.idl',
 ]
 
 XPIDL_MODULE = 'dom_system'
 
 EXPORTS += [
     'nsDeviceSensors.h',
+    'nsOSPermissionRequestBase.h',
 ]
 
 EXPORTS.mozilla += [
     'OSFileConstants.h',
 ]
 
 UNIFIED_SOURCES += [
     'nsDeviceSensors.cpp',
+    'nsOSPermissionRequestBase.cpp',
     'OSFileConstants.cpp',
 ]
 
 EXTRA_COMPONENTS += [
     'NetworkGeolocationProvider.js',
     'NetworkGeolocationProvider.manifest',
 ]
 
new file mode 100644
--- /dev/null
+++ b/dom/system/nsIOSPermissionRequest.idl
@@ -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();
+};
new file mode 100644
--- /dev/null
+++ b/dom/system/nsOSPermissionRequest.h
@@ -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__ */
new file mode 100644
--- /dev/null
+++ b/dom/system/nsOSPermissionRequestBase.cpp
@@ -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;
+}
new file mode 100644
--- /dev/null
+++ b/dom/system/nsOSPermissionRequestBase.h
@@ -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
--- a/widget/cocoa/nsCocoaUtils.h
+++ b/widget/cocoa/nsCocoaUtils.h
@@ -13,16 +13,17 @@
 #include "npapi.h"
 #include "nsTArray.h"
 #include "Units.h"
 
 // This must be the last include:
 #include "nsObjCExceptions.h"
 
 #include "mozilla/EventForwards.h"
+#include "mozilla/StaticPtr.h"
 
 // Declare the backingScaleFactor method that we want to call
 // on NSView/Window/Screen objects, if they recognize it.
 @interface NSObject (BackingScaleFactorCategory)
 - (CGFloat)backingScaleFactor;
 @end
 
 #if !defined(MAC_OS_X_VERSION_10_8) || MAC_OS_X_VERSION_MAX_ALLOWED < MAC_OS_X_VERSION_10_8
@@ -33,18 +34,24 @@ enum {
 
 class nsIWidget;
 
 namespace mozilla {
 class TimeStamp;
 namespace gfx {
 class SourceSurface;
 } // namespace gfx
+namespace dom {
+class Promise;
+} // namespace dom
 } // namespace mozilla
 
+using mozilla::StaticAutoPtr;
+using mozilla::StaticMutex;
+
 // Used to retain a Cocoa object for the remainder of a method's execution.
 class nsAutoRetainCocoaObject {
 public:
 explicit nsAutoRetainCocoaObject(id anObject)
 {
   mObject = NS_OBJC_TRY_EXPR_ABORT([anObject retain]);
 }
 ~nsAutoRetainCocoaObject()
@@ -97,21 +104,28 @@ struct KeyBindingsCommand
 - (void)startRecording:(nsTArray<KeyBindingsCommand>&)aCommands;
 
 - (void)doCommandBySelector:(SEL)aSelector;
 
 - (void)insertText:(id)aString;
 
 @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
 {
   typedef mozilla::gfx::SourceSurface SourceSurface;
   typedef mozilla::LayoutDeviceIntPoint LayoutDeviceIntPoint;
   typedef mozilla::LayoutDeviceIntRect LayoutDeviceIntRect;
+  typedef mozilla::dom::Promise Promise;
+  typedef StaticAutoPtr<nsTArray<RefPtr<Promise>>> PromiseArray;
 
 public:
 
   // Get the backing scale factor from an object that supports this selector
   // (NSView/Window/Screen, on 10.7 or later), returning 1.0 if not supported
   static CGFloat
   GetBackingScaleFactor(id aObject)
   {
@@ -391,11 +405,89 @@ public:
            const bool aIsVertical,
            const CGFloat aBackingScaleFactor);
 
   /**
    * Compute TimeStamp from an event's timestamp.
    * If aEventTime is 0, this returns current timestamp.
    */
   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_
--- a/widget/cocoa/nsCocoaUtils.mm
+++ b/widget/cocoa/nsCocoaUtils.mm
@@ -1,54 +1,78 @@
 /* -*- Mode: C++; tab-width: 20; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
 /* 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/. */
 
+#import <AVFoundation/AVFoundation.h>
+
 #include <cmath>
 
 #include "gfx2DGlue.h"
 #include "gfxPlatform.h"
 #include "gfxUtils.h"
 #include "ImageRegion.h"
 #include "nsCocoaUtils.h"
 #include "nsChildView.h"
 #include "nsMenuBarX.h"
 #include "nsCocoaWindow.h"
 #include "nsCOMPtr.h"
 #include "nsIInterfaceRequestorUtils.h"
 #include "nsIAppShellService.h"
+#include "nsIOSPermissionRequest.h"
+#include "nsIRunnable.h"
 #include "nsIXULWindow.h"
 #include "nsIBaseWindow.h"
 #include "nsIServiceManager.h"
 #include "nsMenuUtilsX.h"
 #include "nsToolkit.h"
 #include "nsCRT.h"
 #include "SVGImageContext.h"
+#include "mozilla/ClearOnShutdown.h"
+#include "mozilla/dom/Promise.h"
 #include "mozilla/gfx/2D.h"
+#include "mozilla/Logging.h"
 #include "mozilla/MiscEvents.h"
 #include "mozilla/Preferences.h"
 #include "mozilla/TextEvents.h"
+#include "mozilla/StaticMutex.h"
 
 using namespace mozilla;
 using namespace mozilla::widget;
 
+using mozilla::dom::Promise;
 using mozilla::gfx::BackendType;
 using mozilla::gfx::DataSourceSurface;
 using mozilla::gfx::DrawTarget;
 using mozilla::gfx::Factory;
 using mozilla::gfx::SamplingFilter;
 using mozilla::gfx::IntPoint;
 using mozilla::gfx::IntRect;
 using mozilla::gfx::IntSize;
 using mozilla::gfx::SurfaceFormat;
 using mozilla::gfx::SourceSurface;
 using mozilla::image::ImageRegion;
 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
 MenuBarScreenHeight()
 {
   NS_OBJC_BEGIN_TRY_ABORT_BLOCK_RETURN;
 
   NSArray* allScreens = [NSScreen screens];
   if ([allScreens count]) {
     return [[allScreens objectAtIndex:0] frame].size.height;
@@ -1118,8 +1142,264 @@ nsCocoaUtils::GetEventTimeStamp(NSTimeIn
   // mach_absolute_time(), which measures "ticks" since boot.
   // Event timestamps are NSTimeIntervals (seconds) since boot. So the two time
   // representations already have the same base; we only need to convert
   // seconds into ticks.
   int64_t tick =
     BaseTimeDurationPlatformUtils::TicksFromMilliseconds(aEventTime * 1000.0);
   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());
+}