Bug 1247685 - WebIDL and DOM implementation changes for app server keys. r=mt r?baku draft
authorKit Cambridge <kcambridge@mozilla.com>
Tue, 22 Mar 2016 13:38:03 -0700
changeset 351023 87a448852abee1648f0ec29675c9acdb53acce2c
parent 351022 1ea9f47c600e196ac4c2ad7bd12e60e9a37e6e24
child 351024 224b366804f715d4c7829effa20a2561fb3de50d
push id15477
push userkcambridge@mozilla.com
push dateThu, 14 Apr 2016 17:33:22 +0000
reviewersmt, baku
bugs1247685
milestone48.0a1
Bug 1247685 - WebIDL and DOM implementation changes for app server keys. r=mt r?baku MozReview-Commit-ID: 1xYjSuLMnV4
dom/base/domerr.msg
dom/interfaces/push/nsIPushService.idl
dom/push/Push.js
dom/push/PushManager.cpp
dom/push/PushManager.h
dom/push/PushSubscription.cpp
dom/push/PushSubscription.h
dom/push/PushSubscriptionOptions.cpp
dom/push/PushSubscriptionOptions.h
dom/push/PushUtil.cpp
dom/push/PushUtil.h
dom/push/moz.build
dom/webidl/PushManager.webidl
dom/webidl/PushSubscription.webidl
dom/webidl/PushSubscriptionOptions.webidl
dom/webidl/moz.build
js/xpconnect/src/xpc.msg
xpcom/base/ErrorList.h
--- a/dom/base/domerr.msg
+++ b/dom/base/domerr.msg
@@ -151,11 +151,13 @@ DOM4_MSG_DEF(InvalidStateError, "A mutat
 DOM4_MSG_DEF(AbortError, "A request was aborted, for example through a call to FileHandle.abort.", NS_ERROR_DOM_FILEHANDLE_ABORT_ERR)
 DOM4_MSG_DEF(QuotaExceededError, "The current file handle exceeded its quota limitations.", NS_ERROR_DOM_FILEHANDLE_QUOTA_ERR)
 
 /* Push API errors. */
 DOM4_MSG_DEF(InvalidStateError, "Invalid service worker registration.", NS_ERROR_DOM_PUSH_INVALID_REGISTRATION_ERR)
 DOM4_MSG_DEF(PermissionDeniedError, "User denied permission to use the Push API.", NS_ERROR_DOM_PUSH_DENIED_ERR)
 DOM4_MSG_DEF(AbortError, "Error retrieving push subscription.", NS_ERROR_DOM_PUSH_ABORT_ERR)
 DOM4_MSG_DEF(NetworkError, "Push service unreachable.", NS_ERROR_DOM_PUSH_SERVICE_UNREACHABLE)
+DOM4_MSG_DEF(InvalidAccessError, "Invalid raw ECDSA P-256 public key.", NS_ERROR_DOM_PUSH_INVALID_KEY_ERR)
+DOM4_MSG_DEF(InvalidStateError, "A subscription with a different application server key already exists.", NS_ERROR_DOM_PUSH_MISMATCHED_KEY_ERR)
 
 DOM_MSG_DEF(NS_ERROR_DOM_JS_EXCEPTION, "A callback threw an exception")
 DOM_MSG_DEF(NS_ERROR_DOM_DOMEXCEPTION, "A DOMException was thrown")
--- a/dom/interfaces/push/nsIPushService.idl
+++ b/dom/interfaces/push/nsIPushService.idl
@@ -92,16 +92,26 @@ interface nsIPushService : nsISupports
    * will be fired, with the subject set to `null` and the data set to |scope|.
    * Servers may drop subscriptions at any time, so callers should recreate
    * subscriptions if desired.
    */
   void subscribe(in DOMString scope, in nsIPrincipal principal,
                  in nsIPushSubscriptionCallback callback);
 
   /**
+   * Creates a restricted push subscription with the given public |key|. The
+   * application server must use the corresponding private key to authenticate
+   * message delivery requests, as described in draft-thomson-webpush-vapid.
+   */
+  void subscribeWithKey(in DOMString scope, in nsIPrincipal principal,
+                        in uint32_t keyLength,
+                        [const, array, size_is(keyLength)] in uint8_t key,
+                        in nsIPushSubscriptionCallback callback);
+
+  /**
    * Removes a push subscription for the given |scope|.
    */
   void unsubscribe(in DOMString scope, in nsIPrincipal principal,
                    in nsIUnsubscribeResultCallback callback);
 
   /**
    * Retrieves the subscription record associated with the given
    * |(scope, principal)| pair. If the subscription does not exist, the
--- a/dom/push/Push.js
+++ b/dom/push/Push.js
@@ -41,37 +41,37 @@ Push.prototype = {
   contractID: "@mozilla.org/push/PushManager;1",
 
   classID : PUSH_CID,
 
   QueryInterface : XPCOMUtils.generateQI([Ci.nsIDOMGlobalPropertyInitializer,
                                           Ci.nsISupportsWeakReference,
                                           Ci.nsIObserver]),
 
-  init: function(aWindow) {
+  init: function(window) {
     console.debug("init()");
 
-    this._window = aWindow;
+    this._window = window;
 
-    this.initDOMRequestHelper(aWindow);
+    this.initDOMRequestHelper(window);
 
-    this._principal = aWindow.document.nodePrincipal;
+    this._principal = window.document.nodePrincipal;
   },
 
   __init: function(scope) {
     this._scope = scope;
   },
 
-  askPermission: function (aAllowCallback, aCancelCallback) {
+  askPermission: function () {
     console.debug("askPermission()");
 
     return this.createPromise((resolve, reject) => {
       let permissionDenied = () => {
         reject(new this._window.DOMException(
-          "User denied permission to use the Push API",
+          "User denied permission to use the Push API.",
           "PermissionDeniedError"
         ));
       };
 
       let permission = Ci.nsIPermissionManager.UNKNOWN_ACTION;
       try {
         permission = this._testPermission();
       } catch (e) {
@@ -84,25 +84,40 @@ Push.prototype = {
       } else if (permission == Ci.nsIPermissionManager.DENY_ACTION) {
         permissionDenied();
       } else {
         this._requestPermission(resolve, permissionDenied);
       }
     });
   },
 
-  subscribe: function() {
+  subscribe: function(options) {
     console.debug("subscribe()", this._scope);
 
     let histogram = Services.telemetry.getHistogramById("PUSH_API_USED");
     histogram.add(true);
     return this.askPermission().then(() =>
       this.createPromise((resolve, reject) => {
         let callback = new PushSubscriptionCallback(this, resolve, reject);
-        PushService.subscribe(this._scope, this._principal, callback);
+
+        if (!options || !options.applicationServerKey) {
+          PushService.subscribe(this._scope, this._principal, callback);
+          return;
+        }
+
+        let appServerKey = options.applicationServerKey;
+        let keyView = new Uint8Array(ArrayBuffer.isView(appServerKey) ?
+                                     appServerKey.buffer : appServerKey);
+        if (keyView.byteLength === 0) {
+          callback._rejectWithError(Cr.NS_ERROR_DOM_PUSH_INVALID_KEY_ERR);
+          return;
+        }
+        PushService.subscribeWithKey(this._scope, this._principal,
+                                     appServerKey.length, appServerKey,
+                                     callback);
       })
     );
   },
 
   getSubscription: function() {
     console.debug("getSubscription()", this._scope);
 
     return this.createPromise((resolve, reject) => {
@@ -185,43 +200,74 @@ function PushSubscriptionCallback(pushMa
 }
 
 PushSubscriptionCallback.prototype = {
   QueryInterface: XPCOMUtils.generateQI([Ci.nsIPushSubscriptionCallback]),
 
   onPushSubscription: function(ok, subscription) {
     let {pushManager} = this;
     if (!Components.isSuccessCode(ok)) {
-      this.reject(new pushManager._window.DOMException(
-        "Error retrieving push subscription",
-        "AbortError"
-      ));
+      this._rejectWithError(ok);
       return;
     }
 
     if (!subscription) {
       this.resolve(null);
       return;
     }
 
-    let publicKey = this._getKey(subscription, "p256dh");
+    let p256dhKey = this._getKey(subscription, "p256dh");
     let authSecret = this._getKey(subscription, "auth");
-    let sub = new pushManager._window.PushSubscription(subscription.endpoint,
-                                                       pushManager._scope,
-                                                       publicKey,
-                                                       authSecret);
+    let options = {
+      endpoint: subscription.endpoint,
+      scope: pushManager._scope,
+      p256dhKey: p256dhKey,
+      authSecret: authSecret,
+    };
+    let appServerKey = this._getKey(subscription, "appServer");
+    if (appServerKey) {
+      // Avoid passing null keys to work around bug 1256449.
+      options.appServerKey = appServerKey;
+    }
+    let sub = new pushManager._window.PushSubscription(options);
     this.resolve(sub);
   },
 
   _getKey: function(subscription, name) {
     let outKeyLen = {};
     let rawKey = subscription.getKey(name, outKeyLen);
     if (!outKeyLen.value) {
       return null;
     }
     let key = new ArrayBuffer(outKeyLen.value);
     let keyView = new Uint8Array(key);
     keyView.set(rawKey);
     return key;
   },
+
+  _rejectWithError: function(result) {
+    let error;
+    switch (result) {
+      case Cr.NS_ERROR_DOM_PUSH_INVALID_KEY_ERR:
+        error = new this.pushManager._window.DOMException(
+          "Invalid raw ECDSA P-256 public key.",
+          "InvalidAccessError"
+        );
+        break;
+
+      case Cr.NS_ERROR_DOM_PUSH_MISMATCHED_KEY_ERR:
+        error = new this.pushManager._window.DOMException(
+          "A subscription with a different application server key already exists.",
+          "InvalidStateError"
+        );
+        break;
+
+      default:
+        error = new this.pushManager._window.DOMException(
+          "Error retrieving push subscription.",
+          "AbortError"
+        );
+    }
+    this.reject(error);
+  },
 };
 
 this.NSGetFactory = XPCOMUtils.generateNSGetFactory([Push]);
--- a/dom/push/PushManager.cpp
+++ b/dom/push/PushManager.cpp
@@ -5,16 +5,18 @@
  * You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 #include "mozilla/dom/PushManager.h"
 
 #include "mozilla/Services.h"
 #include "mozilla/unused.h"
 #include "mozilla/dom/PushManagerBinding.h"
 #include "mozilla/dom/PushSubscription.h"
+#include "mozilla/dom/PushSubscriptionOptionsBinding.h"
+#include "mozilla/dom/PushUtil.h"
 
 #include "mozilla/dom/Promise.h"
 #include "mozilla/dom/PromiseWorkerProxy.h"
 
 #include "nsIGlobalObject.h"
 #include "nsIPermissionManager.h"
 #include "nsIPrincipal.h"
 #include "nsIPushService.h"
@@ -58,46 +60,124 @@ GetPermissionState(nsIPrincipal* aPrinci
     aState = PushPermissionState::Denied;
   } else {
     aState = PushPermissionState::Prompt;
   }
 
   return NS_OK;
 }
 
+// A helper class that frees an `nsIPushSubscription` key buffer when it
+// goes out of scope.
+class MOZ_RAII AutoFreeKeyBuffer final
+{
+  uint8_t** mKeyBuffer;
+
+public:
+  explicit AutoFreeKeyBuffer(uint8_t** aKeyBuffer)
+    : mKeyBuffer(aKeyBuffer)
+  {
+    MOZ_ASSERT(mKeyBuffer);
+  }
+
+  ~AutoFreeKeyBuffer()
+  {
+    NS_Free(*mKeyBuffer);
+  }
+};
+
+// Copies a subscription key buffer into an array.
+nsresult
+CopySubscriptionKeyToArray(nsIPushSubscription* aSubscription,
+                           const nsAString& aKeyName,
+                           nsTArray<uint8_t>& aKey)
+{
+  uint8_t* keyBuffer = nullptr;
+  AutoFreeKeyBuffer autoFree(&keyBuffer);
+
+  uint32_t keyLen;
+  nsresult rv = aSubscription->GetKey(aKeyName, &keyLen, &keyBuffer);
+  if (NS_FAILED(rv)) {
+    return rv;
+  }
+  if (!aKey.SetLength(keyLen, fallible) ||
+      !aKey.ReplaceElementsAt(0, keyLen, keyBuffer, keyLen, fallible)) {
+    return NS_ERROR_OUT_OF_MEMORY;
+  }
+  return NS_OK;
+}
+
+nsresult
+GetSubscriptionParams(nsIPushSubscription* aSubscription,
+                      nsAString& aEndpoint,
+                      nsTArray<uint8_t>& aRawP256dhKey,
+                      nsTArray<uint8_t>& aAuthSecret,
+                      nsTArray<uint8_t>& aAppServerKey)
+{
+  if (!aSubscription) {
+    return NS_OK;
+  }
+
+  nsresult rv = aSubscription->GetEndpoint(aEndpoint);
+  if (NS_WARN_IF(NS_FAILED(rv))) {
+    return rv;
+  }
+
+  rv = CopySubscriptionKeyToArray(aSubscription, NS_LITERAL_STRING("p256dh"),
+                                  aRawP256dhKey);
+  if (NS_WARN_IF(NS_FAILED(rv))) {
+    return rv;
+  }
+  rv = CopySubscriptionKeyToArray(aSubscription, NS_LITERAL_STRING("auth"),
+                                  aAuthSecret);
+  if (NS_WARN_IF(NS_FAILED(rv))) {
+    return rv;
+  }
+  rv = CopySubscriptionKeyToArray(aSubscription, NS_LITERAL_STRING("appServer"),
+                                  aAppServerKey);
+  if (NS_WARN_IF(NS_FAILED(rv))) {
+    return rv;
+  }
+
+  return NS_OK;
+}
+
 class GetSubscriptionResultRunnable final : public WorkerRunnable
 {
 public:
   GetSubscriptionResultRunnable(WorkerPrivate* aWorkerPrivate,
                                 already_AddRefed<PromiseWorkerProxy>&& aProxy,
                                 nsresult aStatus,
                                 const nsAString& aEndpoint,
                                 const nsAString& aScope,
                                 nsTArray<uint8_t>&& aRawP256dhKey,
-                                nsTArray<uint8_t>&& aAuthSecret)
+                                nsTArray<uint8_t>&& aAuthSecret,
+                                nsTArray<uint8_t>&& aAppServerKey)
     : WorkerRunnable(aWorkerPrivate, WorkerThreadModifyBusyCount)
     , mProxy(Move(aProxy))
     , mStatus(aStatus)
     , mEndpoint(aEndpoint)
     , mScope(aScope)
     , mRawP256dhKey(Move(aRawP256dhKey))
     , mAuthSecret(Move(aAuthSecret))
+    , mAppServerKey(Move(aAppServerKey))
   { }
 
   bool
   WorkerRun(JSContext* aCx, WorkerPrivate* aWorkerPrivate) override
   {
     RefPtr<Promise> promise = mProxy->WorkerPromise();
     if (NS_SUCCEEDED(mStatus)) {
       if (mEndpoint.IsEmpty()) {
         promise->MaybeResolve(JS::NullHandleValue);
       } else {
         RefPtr<PushSubscription> sub =
             new PushSubscription(nullptr, mEndpoint, mScope,
-                                 Move(mRawP256dhKey), Move(mAuthSecret));
+                                 Move(mRawP256dhKey), Move(mAuthSecret),
+                                 Move(mAppServerKey));
         promise->MaybeResolve(sub);
       }
     } else if (NS_ERROR_GET_MODULE(mStatus) == NS_ERROR_MODULE_DOM_PUSH ) {
       promise->MaybeReject(mStatus);
     } else {
       promise->MaybeReject(NS_ERROR_DOM_PUSH_ABORT_ERR);
     }
 
@@ -110,16 +190,17 @@ private:
   {}
 
   RefPtr<PromiseWorkerProxy> mProxy;
   nsresult mStatus;
   nsString mEndpoint;
   nsString mScope;
   nsTArray<uint8_t> mRawP256dhKey;
   nsTArray<uint8_t> mAuthSecret;
+  nsTArray<uint8_t> mAppServerKey;
 };
 
 class GetSubscriptionCallback final : public nsIPushSubscriptionCallback
 {
 public:
   NS_DECL_ISUPPORTS
 
   explicit GetSubscriptionCallback(PromiseWorkerProxy* aProxy,
@@ -136,31 +217,32 @@ public:
     MOZ_ASSERT(mProxy, "OnPushSubscription() called twice?");
 
     MutexAutoLock lock(mProxy->Lock());
     if (mProxy->CleanedUp()) {
       return NS_OK;
     }
 
     nsAutoString endpoint;
-    nsTArray<uint8_t> rawP256dhKey, authSecret;
+    nsTArray<uint8_t> rawP256dhKey, authSecret, appServerKey;
     if (NS_SUCCEEDED(aStatus)) {
       aStatus = GetSubscriptionParams(aSubscription, endpoint, rawP256dhKey,
-                                      authSecret);
+                                      authSecret, appServerKey);
     }
 
     WorkerPrivate* worker = mProxy->GetWorkerPrivate();
     RefPtr<GetSubscriptionResultRunnable> r =
       new GetSubscriptionResultRunnable(worker,
                                         mProxy.forget(),
                                         aStatus,
                                         endpoint,
                                         mScope,
                                         Move(rawP256dhKey),
-                                        Move(authSecret));
+                                        Move(authSecret),
+                                        Move(appServerKey));
     MOZ_ALWAYS_TRUE(r->Dispatch());
 
     return NS_OK;
   }
 
   // Convenience method for use in this file.
   void
   OnPushSubscriptionError(nsresult aStatus)
@@ -169,82 +251,33 @@ public:
         OnPushSubscription(aStatus, nullptr)));
   }
 
 protected:
   ~GetSubscriptionCallback()
   {}
 
 private:
-  inline nsresult
-  FreeKeys(nsresult aStatus, uint8_t* aKey, uint8_t* aAuthSecret)
-  {
-    NS_Free(aKey);
-    NS_Free(aAuthSecret);
-
-    return aStatus;
-  }
-
-  nsresult
-  GetSubscriptionParams(nsIPushSubscription* aSubscription,
-                        nsAString& aEndpoint,
-                        nsTArray<uint8_t>& aRawP256dhKey,
-                        nsTArray<uint8_t>& aAuthSecret)
-  {
-    if (!aSubscription) {
-      return NS_OK;
-    }
-
-    nsresult rv = aSubscription->GetEndpoint(aEndpoint);
-    if (NS_WARN_IF(NS_FAILED(rv))) {
-      return rv;
-    }
-
-    uint8_t* key = nullptr;
-    uint8_t* authSecret = nullptr;
-
-    uint32_t keyLen;
-    rv = aSubscription->GetKey(NS_LITERAL_STRING("p256dh"), &keyLen, &key);
-    if (NS_WARN_IF(NS_FAILED(rv))) {
-      return FreeKeys(rv, key, authSecret);
-    }
-
-    uint32_t authSecretLen;
-    rv = aSubscription->GetKey(NS_LITERAL_STRING("auth"), &authSecretLen,
-                               &authSecret);
-    if (NS_WARN_IF(NS_FAILED(rv))) {
-      return FreeKeys(rv, key, authSecret);
-    }
-
-    if (!aRawP256dhKey.SetLength(keyLen, fallible) ||
-        !aRawP256dhKey.ReplaceElementsAt(0, keyLen, key, keyLen, fallible) ||
-        !aAuthSecret.SetLength(authSecretLen, fallible) ||
-        !aAuthSecret.ReplaceElementsAt(0, authSecretLen, authSecret,
-                                       authSecretLen, fallible)) {
-
-      return FreeKeys(NS_ERROR_OUT_OF_MEMORY, key, authSecret);
-    }
-
-    return FreeKeys(NS_OK, key, authSecret);
-  }
-
   RefPtr<PromiseWorkerProxy> mProxy;
   nsString mScope;
 };
 
 NS_IMPL_ISUPPORTS(GetSubscriptionCallback, nsIPushSubscriptionCallback)
 
 class GetSubscriptionRunnable final : public nsRunnable
 {
 public:
   GetSubscriptionRunnable(PromiseWorkerProxy* aProxy,
                           const nsAString& aScope,
-                          PushManager::SubscriptionAction aAction)
+                          PushManager::SubscriptionAction aAction,
+                          nsTArray<uint8_t>&& aAppServerKey)
     : mProxy(aProxy)
-    , mScope(aScope), mAction(aAction)
+    , mScope(aScope)
+    , mAction(aAction)
+    , mAppServerKey(Move(aAppServerKey))
   {}
 
   NS_IMETHOD
   Run() override
   {
     AssertIsOnMainThread();
 
     nsCOMPtr<nsIPrincipal> principal;
@@ -284,17 +317,23 @@ public:
     nsCOMPtr<nsIPushService> service =
       do_GetService("@mozilla.org/push/Service;1");
     if (NS_WARN_IF(!service)) {
       callback->OnPushSubscriptionError(NS_ERROR_FAILURE);
       return NS_OK;
     }
 
     if (mAction == PushManager::SubscribeAction) {
-      rv = service->Subscribe(mScope, principal, callback);
+      if (mAppServerKey.IsEmpty()) {
+        rv = service->Subscribe(mScope, principal, callback);
+      } else {
+        rv = service->SubscribeWithKey(mScope, principal,
+                                       mAppServerKey.Length(),
+                                       mAppServerKey.Elements(), callback);
+      }
     } else {
       MOZ_ASSERT(mAction == PushManager::GetSubscriptionAction);
       rv = service->GetSubscription(mScope, principal, callback);
     }
 
     if (NS_WARN_IF(NS_FAILED(rv))) {
       callback->OnPushSubscriptionError(NS_ERROR_FAILURE);
       return NS_OK;
@@ -305,16 +344,17 @@ public:
 
 private:
   ~GetSubscriptionRunnable()
   {}
 
   RefPtr<PromiseWorkerProxy> mProxy;
   nsString mScope;
   PushManager::SubscriptionAction mAction;
+  nsTArray<uint8_t> mAppServerKey;
 };
 
 class PermissionResultRunnable final : public WorkerRunnable
 {
 public:
   PermissionResultRunnable(PromiseWorkerProxy *aProxy,
                            nsresult aStatus,
                            PushPermissionState aState)
@@ -448,43 +488,45 @@ PushManager::Constructor(GlobalObject& a
 
   nsCOMPtr<nsIGlobalObject> global = do_QueryInterface(aGlobal.GetAsSupports());
   RefPtr<PushManager> ret = new PushManager(global, impl);
 
   return ret.forget();
 }
 
 already_AddRefed<Promise>
-PushManager::Subscribe(ErrorResult& aRv)
+PushManager::Subscribe(const PushSubscriptionOptionsInit& aOptions,
+                       ErrorResult& aRv)
 {
   if (mImpl) {
     MOZ_ASSERT(NS_IsMainThread());
-    return mImpl->Subscribe(aRv);
+    return mImpl->Subscribe(aOptions, aRv);
   }
 
-  return PerformSubscriptionActionFromWorker(SubscribeAction, aRv);
+  return PerformSubscriptionActionFromWorker(SubscribeAction, aOptions, aRv);
 }
 
 already_AddRefed<Promise>
 PushManager::GetSubscription(ErrorResult& aRv)
 {
   if (mImpl) {
     MOZ_ASSERT(NS_IsMainThread());
     return mImpl->GetSubscription(aRv);
   }
 
   return PerformSubscriptionActionFromWorker(GetSubscriptionAction, aRv);
 }
 
 already_AddRefed<Promise>
-PushManager::PermissionState(ErrorResult& aRv)
+PushManager::PermissionState(const PushSubscriptionOptionsInit& aOptions,
+                             ErrorResult& aRv)
 {
   if (mImpl) {
     MOZ_ASSERT(NS_IsMainThread());
-    return mImpl->PermissionState(aRv);
+    return mImpl->PermissionState(aOptions, aRv);
   }
 
   WorkerPrivate* worker = GetCurrentThreadWorkerPrivate();
   MOZ_ASSERT(worker);
   worker->AssertIsOnWorkerThread();
 
   nsCOMPtr<nsIGlobalObject> global = worker->GlobalScope();
   RefPtr<Promise> p = Promise::Create(global, aRv);
@@ -501,18 +543,27 @@ PushManager::PermissionState(ErrorResult
   RefPtr<PermissionStateRunnable> r =
     new PermissionStateRunnable(proxy);
   NS_DispatchToMainThread(r);
 
   return p.forget();
 }
 
 already_AddRefed<Promise>
-PushManager::PerformSubscriptionActionFromWorker(
-  SubscriptionAction aAction, ErrorResult& aRv)
+PushManager::PerformSubscriptionActionFromWorker(SubscriptionAction aAction,
+                                                 ErrorResult& aRv)
+{
+  PushSubscriptionOptionsInit options;
+  return PerformSubscriptionActionFromWorker(aAction, options, aRv);
+}
+
+already_AddRefed<Promise>
+PushManager::PerformSubscriptionActionFromWorker(SubscriptionAction aAction,
+                                                 const PushSubscriptionOptionsInit& aOptions,
+                                                 ErrorResult& aRv)
 {
   WorkerPrivate* worker = GetCurrentThreadWorkerPrivate();
   MOZ_ASSERT(worker);
   worker->AssertIsOnWorkerThread();
 
   nsCOMPtr<nsIGlobalObject> global = worker->GlobalScope();
   RefPtr<Promise> p = Promise::Create(global, aRv);
   if (NS_WARN_IF(aRv.Failed())) {
@@ -520,17 +571,28 @@ PushManager::PerformSubscriptionActionFr
   }
 
   RefPtr<PromiseWorkerProxy> proxy = PromiseWorkerProxy::Create(worker, p);
   if (!proxy) {
     p->MaybeReject(NS_ERROR_DOM_PUSH_ABORT_ERR);
     return p.forget();
   }
 
+  nsTArray<uint8_t> appServerKey;
+  if (!aOptions.mApplicationServerKey.IsNull()) {
+    const OwningArrayBufferViewOrArrayBuffer& bufferSource =
+      aOptions.mApplicationServerKey.Value();
+    if (!PushUtil::CopyBufferSourceToArray(bufferSource, appServerKey) ||
+        appServerKey.IsEmpty()) {
+      p->MaybeReject(NS_ERROR_DOM_PUSH_INVALID_KEY_ERR);
+      return p.forget();
+    }
+  }
+
   RefPtr<GetSubscriptionRunnable> r =
-    new GetSubscriptionRunnable(proxy, mScope, aAction);
+    new GetSubscriptionRunnable(proxy, mScope, aAction, Move(appServerKey));
   MOZ_ALWAYS_SUCCEEDS(NS_DispatchToMainThread(r));
 
   return p.forget();
 }
 
 } // namespace dom
 } // namespace mozilla
--- a/dom/push/PushManager.h
+++ b/dom/push/PushManager.h
@@ -43,16 +43,17 @@ namespace mozilla {
 namespace dom {
 
 namespace workers {
 class WorkerPrivate;
 }
 
 class Promise;
 class PushManagerImpl;
+struct PushSubscriptionOptionsInit;
 
 class PushManager final : public nsISupports
                         , public nsWrapperCache
 {
 public:
   NS_DECL_CYCLE_COLLECTING_ISUPPORTS
   NS_DECL_CYCLE_COLLECTION_SCRIPT_HOLDER_CLASS(PushManager)
 
@@ -80,28 +81,33 @@ public:
   Constructor(GlobalObject& aGlobal, const nsAString& aScope,
               ErrorResult& aRv);
 
   already_AddRefed<Promise>
   PerformSubscriptionActionFromWorker(SubscriptionAction aAction,
                                       ErrorResult& aRv);
 
   already_AddRefed<Promise>
-  Subscribe(ErrorResult& aRv);
+  PerformSubscriptionActionFromWorker(SubscriptionAction aAction,
+                                      const PushSubscriptionOptionsInit& aOptions,
+                                      ErrorResult& aRv);
+
+  already_AddRefed<Promise>
+  Subscribe(const PushSubscriptionOptionsInit& aOptions, ErrorResult& aRv);
 
   already_AddRefed<Promise>
   GetSubscription(ErrorResult& aRv);
 
   already_AddRefed<Promise>
-  PermissionState(ErrorResult& aRv);
+  PermissionState(const PushSubscriptionOptionsInit& aOptions,
+                  ErrorResult& aRv);
 
-protected:
+private:
   ~PushManager();
 
-private:
   // The following are only set and accessed on the main thread.
   nsCOMPtr<nsIGlobalObject> mGlobal;
   RefPtr<PushManagerImpl> mImpl;
 
   // Only used on the worker thread.
   nsString mScope;
 };
 } // namespace dom
--- a/dom/push/PushSubscription.cpp
+++ b/dom/push/PushSubscription.cpp
@@ -7,16 +7,18 @@
 #include "nsIPushService.h"
 #include "nsIScriptObjectPrincipal.h"
 
 #include "mozilla/Base64.h"
 #include "mozilla/unused.h"
 
 #include "mozilla/dom/Promise.h"
 #include "mozilla/dom/PromiseWorkerProxy.h"
+#include "mozilla/dom/PushSubscriptionOptions.h"
+#include "mozilla/dom/PushUtil.h"
 #include "mozilla/dom/WorkerPrivate.h"
 #include "mozilla/dom/WorkerScope.h"
 #include "mozilla/dom/workers/Workers.h"
 
 namespace mozilla {
 namespace dom {
 
 using namespace workers;
@@ -186,97 +188,103 @@ public:
 private:
   ~UnsubscribeRunnable()
   {}
 
   RefPtr<PromiseWorkerProxy> mProxy;
   nsString mScope;
 };
 
-bool
-CopyArrayBufferToArray(const ArrayBuffer& aBuffer,
-                       nsTArray<uint8_t>& aArray)
-{
-  aBuffer.ComputeLengthAndData();
-  if (!aArray.SetLength(aBuffer.Length(), fallible) ||
-      !aArray.ReplaceElementsAt(0, aBuffer.Length(), aBuffer.Data(),
-                                aBuffer.Length(), fallible)) {
-    return false;
-  }
-  return true;
-}
-
 } // anonymous namespace
 
 PushSubscription::PushSubscription(nsIGlobalObject* aGlobal,
                                    const nsAString& aEndpoint,
                                    const nsAString& aScope,
                                    nsTArray<uint8_t>&& aRawP256dhKey,
-                                   nsTArray<uint8_t>&& aAuthSecret)
+                                   nsTArray<uint8_t>&& aAuthSecret,
+                                   nsTArray<uint8_t>&& aAppServerKey)
   : mEndpoint(aEndpoint)
   , mScope(aScope)
   , mRawP256dhKey(Move(aRawP256dhKey))
   , mAuthSecret(Move(aAuthSecret))
 {
   if (NS_IsMainThread()) {
     mGlobal = aGlobal;
   } else {
 #ifdef DEBUG
     // There's only one global on a worker, so we don't need to pass a global
     // object to the constructor.
     WorkerPrivate* worker = GetCurrentThreadWorkerPrivate();
     MOZ_ASSERT(worker);
     worker->AssertIsOnWorkerThread();
 #endif
   }
+  mOptions = new PushSubscriptionOptions(mGlobal, Move(aAppServerKey));
 }
 
 PushSubscription::~PushSubscription()
 {}
 
-NS_IMPL_CYCLE_COLLECTION_WRAPPERCACHE(PushSubscription, mGlobal)
-
+NS_IMPL_CYCLE_COLLECTION_WRAPPERCACHE(PushSubscription, mGlobal, mOptions)
 NS_IMPL_CYCLE_COLLECTING_ADDREF(PushSubscription)
 NS_IMPL_CYCLE_COLLECTING_RELEASE(PushSubscription)
 NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(PushSubscription)
   NS_WRAPPERCACHE_INTERFACE_MAP_ENTRY
   NS_INTERFACE_MAP_ENTRY(nsISupports)
 NS_INTERFACE_MAP_END
 
 JSObject*
 PushSubscription::WrapObject(JSContext* aCx, JS::Handle<JSObject*> aGivenProto)
 {
   return PushSubscriptionBinding::Wrap(aCx, this, aGivenProto);
 }
 
 // static
 already_AddRefed<PushSubscription>
 PushSubscription::Constructor(GlobalObject& aGlobal,
-                              const nsAString& aEndpoint,
-                              const nsAString& aScope,
-                              const Nullable<ArrayBuffer>& aP256dhKey,
-                              const Nullable<ArrayBuffer>& aAuthSecret,
+                              const PushSubscriptionInit& aInitDict,
                               ErrorResult& aRv)
 {
   nsCOMPtr<nsIGlobalObject> global = do_QueryInterface(aGlobal.GetAsSupports());
 
-  nsTArray<uint8_t> rawKey, authSecret;
-  if ((!aP256dhKey.IsNull() && !CopyArrayBufferToArray(aP256dhKey.Value(),
-                                                       rawKey)) ||
-      (!aAuthSecret.IsNull() && !CopyArrayBufferToArray(aAuthSecret.Value(),
-                                                        authSecret))) {
+  nsTArray<uint8_t> rawKey;
+  if (aInitDict.mP256dhKey.WasPassed() &&
+      !aInitDict.mP256dhKey.Value().IsNull() &&
+      !PushUtil::CopyArrayBufferToArray(aInitDict.mP256dhKey.Value().Value(),
+                                        rawKey)) {
+    aRv.Throw(NS_ERROR_OUT_OF_MEMORY);
+    return nullptr;
+  }
+
+  nsTArray<uint8_t> authSecret;
+  if (aInitDict.mAuthSecret.WasPassed() &&
+      !aInitDict.mAuthSecret.Value().IsNull() &&
+      !PushUtil::CopyArrayBufferToArray(aInitDict.mAuthSecret.Value().Value(),
+                                        authSecret)) {
     aRv.Throw(NS_ERROR_OUT_OF_MEMORY);
     return nullptr;
   }
 
+  nsTArray<uint8_t> appServerKey;
+  if (aInitDict.mAppServerKey.WasPassed() &&
+      !aInitDict.mAppServerKey.Value().IsNull()) {
+    const OwningArrayBufferViewOrArrayBuffer& bufferSource =
+      aInitDict.mAppServerKey.Value().Value();
+    if (!PushUtil::CopyBufferSourceToArray(bufferSource, appServerKey)) {
+      aRv.Throw(NS_ERROR_OUT_OF_MEMORY);
+      return nullptr;
+    }
+  }
+
   RefPtr<PushSubscription> sub = new PushSubscription(global,
-                                                      aEndpoint,
-                                                      aScope,
+                                                      aInitDict.mEndpoint,
+                                                      aInitDict.mScope,
                                                       Move(rawKey),
-                                                      Move(authSecret));
+                                                      Move(authSecret),
+                                                      Move(appServerKey));
 
   return sub.forget();
 }
 
 already_AddRefed<Promise>
 PushSubscription::Unsubscribe(ErrorResult& aRv)
 {
   if (!NS_IsMainThread()) {
@@ -310,26 +318,23 @@ PushSubscription::Unsubscribe(ErrorResul
     service->Unsubscribe(mScope, sop->GetPrincipal(), callback)));
 
   return p.forget();
 }
 
 void
 PushSubscription::GetKey(JSContext* aCx,
                          PushEncryptionKeyName aType,
-                         JS::MutableHandle<JSObject*> aKey)
+                         JS::MutableHandle<JSObject*> aKey,
+                         ErrorResult& aRv)
 {
-  if (aType == PushEncryptionKeyName::P256dh && !mRawP256dhKey.IsEmpty()) {
-    aKey.set(ArrayBuffer::Create(aCx,
-                                 mRawP256dhKey.Length(),
-                                 mRawP256dhKey.Elements()));
-  } else if (aType == PushEncryptionKeyName::Auth && !mAuthSecret.IsEmpty()) {
-    aKey.set(ArrayBuffer::Create(aCx,
-                                 mAuthSecret.Length(),
-                                 mAuthSecret.Elements()));
+  if (aType == PushEncryptionKeyName::P256dh) {
+    PushUtil::CopyArrayToArrayBuffer(aCx, mRawP256dhKey, aKey, aRv);
+  } else if (aType == PushEncryptionKeyName::Auth) {
+    PushUtil::CopyArrayToArrayBuffer(aCx, mAuthSecret, aKey, aRv);
   } else {
     aKey.set(nullptr);
   }
 }
 
 void
 PushSubscription::ToJSON(PushSubscriptionJSON& aJSON, ErrorResult& aRv)
 {
@@ -353,16 +358,23 @@ PushSubscription::ToJSON(PushSubscriptio
   rv = Base64URLEncode(mAuthSecret.Length(), mAuthSecret.Elements(),
                        encodeOptions, aJSON.mKeys.mAuth.Value());
   if (NS_WARN_IF(NS_FAILED(rv))) {
     aRv.Throw(rv);
     return;
   }
 }
 
+already_AddRefed<PushSubscriptionOptions>
+PushSubscription::Options()
+{
+  RefPtr<PushSubscriptionOptions> options = mOptions;
+  return options.forget();
+}
+
 already_AddRefed<Promise>
 PushSubscription::UnsubscribeFromWorker(ErrorResult& aRv)
 {
   WorkerPrivate* worker = GetCurrentThreadWorkerPrivate();
   MOZ_ASSERT(worker);
   worker->AssertIsOnWorkerThread();
 
   nsCOMPtr<nsIGlobalObject> global = worker->GlobalScope();
--- a/dom/push/PushSubscription.h
+++ b/dom/push/PushSubscription.h
@@ -10,16 +10,17 @@
 #include "nsWrapperCache.h"
 
 #include "mozilla/AlreadyAddRefed.h"
 #include "mozilla/ErrorResult.h"
 #include "mozilla/RefPtr.h"
 
 #include "mozilla/dom/BindingDeclarations.h"
 #include "mozilla/dom/PushSubscriptionBinding.h"
+#include "mozilla/dom/PushSubscriptionOptionsBinding.h"
 #include "mozilla/dom/TypedArray.h"
 
 class nsIGlobalObject;
 
 namespace mozilla {
 namespace dom {
 
 namespace workers {
@@ -34,17 +35,18 @@ class PushSubscription final : public ns
 public:
   NS_DECL_CYCLE_COLLECTING_ISUPPORTS
   NS_DECL_CYCLE_COLLECTION_SCRIPT_HOLDER_CLASS(PushSubscription)
 
   PushSubscription(nsIGlobalObject* aGlobal,
                    const nsAString& aEndpoint,
                    const nsAString& aScope,
                    nsTArray<uint8_t>&& aP256dhKey,
-                   nsTArray<uint8_t>&& aAuthSecret);
+                   nsTArray<uint8_t>&& aAuthSecret,
+                   nsTArray<uint8_t>&& aAppServerKey);
 
   JSObject*
   WrapObject(JSContext* aCx, JS::Handle<JSObject*> aGivenProto) override;
 
   nsIGlobalObject*
   GetParentObject() const
   {
     return mGlobal;
@@ -54,42 +56,43 @@ public:
   GetEndpoint(nsAString& aEndpoint) const
   {
     aEndpoint = mEndpoint;
   }
 
   void
   GetKey(JSContext* cx,
          PushEncryptionKeyName aType,
-         JS::MutableHandle<JSObject*> aKey);
+         JS::MutableHandle<JSObject*> aKey,
+         ErrorResult& aRv);
 
   static already_AddRefed<PushSubscription>
   Constructor(GlobalObject& aGlobal,
-              const nsAString& aEndpoint,
-              const nsAString& aScope,
-              const Nullable<ArrayBuffer>& aP256dhKey,
-              const Nullable<ArrayBuffer>& aAuthSecret,
+              const PushSubscriptionInit& aInitDict,
               ErrorResult& aRv);
 
   already_AddRefed<Promise>
   Unsubscribe(ErrorResult& aRv);
 
   void
   ToJSON(PushSubscriptionJSON& aJSON, ErrorResult& aRv);
 
-protected:
+  already_AddRefed<PushSubscriptionOptions>
+  Options();
+
+private:
   ~PushSubscription();
 
-private:
   already_AddRefed<Promise>
   UnsubscribeFromWorker(ErrorResult& aRv);
 
   nsString mEndpoint;
   nsString mScope;
   nsTArray<uint8_t> mRawP256dhKey;
   nsTArray<uint8_t> mAuthSecret;
   nsCOMPtr<nsIGlobalObject> mGlobal;
+  RefPtr<PushSubscriptionOptions> mOptions;
 };
 
 } // namespace dom
 } // namespace mozilla
 
 #endif // mozilla_dom_PushSubscription_h
new file mode 100644
--- /dev/null
+++ b/dom/push/PushSubscriptionOptions.cpp
@@ -0,0 +1,48 @@
+/* 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 "mozilla/dom/PushSubscriptionOptions.h"
+
+#include "mozilla/dom/PushSubscriptionOptionsBinding.h"
+
+namespace mozilla {
+namespace dom {
+
+PushSubscriptionOptions::PushSubscriptionOptions(nsIGlobalObject* aGlobal,
+                                                 nsTArray<uint8_t>&& aAppServerKey)
+  : mGlobal(aGlobal)
+  , mAppServerKey(Move(aAppServerKey))
+{
+  // There's only one global on a worker, so we don't need to pass a global
+  // object to the constructor.
+  MOZ_ASSERT_IF(NS_IsMainThread(), mGlobal);
+}
+
+PushSubscriptionOptions::~PushSubscriptionOptions() {}
+
+NS_IMPL_CYCLE_COLLECTION_WRAPPERCACHE(PushSubscriptionOptions, mGlobal)
+NS_IMPL_CYCLE_COLLECTING_ADDREF(PushSubscriptionOptions)
+NS_IMPL_CYCLE_COLLECTING_RELEASE(PushSubscriptionOptions)
+NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(PushSubscriptionOptions)
+  NS_WRAPPERCACHE_INTERFACE_MAP_ENTRY
+  NS_INTERFACE_MAP_ENTRY(nsISupports)
+NS_INTERFACE_MAP_END
+
+JSObject*
+PushSubscriptionOptions::WrapObject(JSContext* aCx,
+                                    JS::Handle<JSObject*> aGivenProto)
+{
+  return PushSubscriptionOptionsBinding::Wrap(aCx, this, aGivenProto);
+}
+
+void
+PushSubscriptionOptions::GetApplicationServerKey(JSContext* aCx,
+                                                 JS::MutableHandle<JSObject*> aKey,
+                                                 ErrorResult& aRv)
+{
+  PushUtil::CopyArrayToArrayBuffer(aCx, mAppServerKey, aKey, aRv);
+}
+
+} // namespace dom
+} // namespace mozilla
new file mode 100644
--- /dev/null
+++ b/dom/push/PushSubscriptionOptions.h
@@ -0,0 +1,45 @@
+/* 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 mozilla_dom_PushSubscriptionOptions_h
+#define mozilla_dom_PushSubscriptionOptions_h
+
+namespace mozilla {
+namespace dom {
+
+class PushSubscriptionOptions final : public nsISupports
+                                    , public nsWrapperCache
+{
+public:
+  NS_DECL_CYCLE_COLLECTING_ISUPPORTS
+  NS_DECL_CYCLE_COLLECTION_SCRIPT_HOLDER_CLASS(PushSubscriptionOptions)
+
+  PushSubscriptionOptions(nsIGlobalObject* aGlobal,
+                          nsTArray<uint8_t>&& aAppServerKey);
+
+  nsIGlobalObject*
+  GetParentObject() const
+  {
+    return mGlobal;
+  }
+
+  JSObject*
+  WrapObject(JSContext* aCx, JS::Handle<JSObject*> aGivenProto) override;
+
+  void
+  GetApplicationServerKey(JSContext* aCx,
+                          JS::MutableHandle<JSObject*> aKey,
+                          ErrorResult& aRv);
+
+private:
+  ~PushSubscriptionOptions();
+
+  nsCOMPtr<nsIGlobalObject> mGlobal;
+  nsTArray<uint8_t> mAppServerKey;
+};
+
+} // namespace dom
+} // namespace mozilla
+
+#endif // mozilla_dom_PushSubscriptionOptions_h
new file mode 100644
--- /dev/null
+++ b/dom/push/PushUtil.cpp
@@ -0,0 +1,58 @@
+/* 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 "mozilla/dom/PushUtil.h"
+
+namespace mozilla {
+namespace dom {
+
+/* static */ bool
+PushUtil::CopyArrayBufferToArray(const ArrayBuffer& aBuffer,
+                                 nsTArray<uint8_t>& aArray)
+{
+  aBuffer.ComputeLengthAndData();
+  return aArray.SetLength(aBuffer.Length(), fallible) &&
+         aArray.ReplaceElementsAt(0, aBuffer.Length(), aBuffer.Data(),
+                                  aBuffer.Length(), fallible);
+}
+
+/* static */ bool
+PushUtil::CopyBufferSourceToArray(
+  const OwningArrayBufferViewOrArrayBuffer& aSource, nsTArray<uint8_t>& aArray)
+{
+  if (aSource.IsArrayBuffer()) {
+    return CopyArrayBufferToArray(aSource.GetAsArrayBuffer(), aArray);
+  }
+  if (aSource.IsArrayBufferView()) {
+    const ArrayBufferView& view = aSource.GetAsArrayBufferView();
+    view.ComputeLengthAndData();
+    return aArray.SetLength(view.Length(), fallible) &&
+           aArray.ReplaceElementsAt(0, view.Length(), view.Data(),
+                                    view.Length(), fallible);
+  }
+  MOZ_CRASH("Uninitialized union: expected buffer or view");
+}
+
+/* static */ void
+PushUtil::CopyArrayToArrayBuffer(JSContext* aCx,
+                                 const nsTArray<uint8_t>& aArray,
+                                 JS::MutableHandle<JSObject*> aValue,
+                                 ErrorResult& aRv)
+{
+  if (aArray.IsEmpty()) {
+    aValue.set(nullptr);
+    return;
+  }
+  JS::Rooted<JSObject*> buffer(aCx, ArrayBuffer::Create(aCx,
+                                                        aArray.Length(),
+                                                        aArray.Elements()));
+  if (NS_WARN_IF(!buffer)) {
+    aRv.Throw(NS_ERROR_OUT_OF_MEMORY);
+    return;
+  }
+  aValue.set(buffer);
+}
+
+} // namespace dom
+} // namespace mozilla
new file mode 100644
--- /dev/null
+++ b/dom/push/PushUtil.h
@@ -0,0 +1,35 @@
+/* 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 mozilla_dom_PushUtil_h
+#define mozilla_dom_PushUtil_h
+
+namespace mozilla {
+namespace dom {
+
+class OwningArrayBufferViewOrArrayBuffer;
+
+class PushUtil final
+{
+private:
+  PushUtil() = delete;
+
+public:
+  static bool
+  CopyArrayBufferToArray(const ArrayBuffer& aBuffer,
+                         nsTArray<uint8_t>& aArray);
+
+  static bool
+  CopyBufferSourceToArray(const OwningArrayBufferViewOrArrayBuffer& aSource,
+                          nsTArray<uint8_t>& aArray);
+
+  static void
+  CopyArrayToArrayBuffer(JSContext* aCx, const nsTArray<uint8_t>& aArray,
+                         JS::MutableHandle<JSObject*> aValue, ErrorResult& aRv);
+};
+
+} // namespace dom
+} // namespace mozilla
+
+#endif // mozilla_dom_PushUtil_h
--- a/dom/push/moz.build
+++ b/dom/push/moz.build
@@ -35,22 +35,26 @@ MOCHITEST_MANIFESTS += [
 XPCSHELL_TESTS_MANIFESTS += [
     'test/xpcshell/xpcshell.ini',
 ]
 
 EXPORTS.mozilla.dom += [
     'PushManager.h',
     'PushNotifier.h',
     'PushSubscription.h',
+    'PushSubscriptionOptions.h',
+    'PushUtil.h',
 ]
 
 UNIFIED_SOURCES += [
     'PushManager.cpp',
     'PushNotifier.cpp',
     'PushSubscription.cpp',
+    'PushSubscriptionOptions.cpp',
+    'PushUtil.cpp',
 ]
 
 TEST_DIRS += ['test/xpcshell']
 
 include('/ipc/chromium/chromium-config.mozbuild')
 
 if CONFIG['GNU_CXX']:
     CXXFLAGS += ['-Wshadow']
--- a/dom/webidl/PushManager.webidl
+++ b/dom/webidl/PushManager.webidl
@@ -2,35 +2,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/.
 *
 * The origin of this IDL file is
 * https://w3c.github.io/push-api/
 */
 
+dictionary PushSubscriptionOptionsInit {
+  // boolean userVisibleOnly = false;
+  BufferSource? applicationServerKey = null;
+};
+
 // The main thread JS implementation. Please see comments in
 // dom/push/PushManager.h for the split between PushManagerImpl and PushManager.
 [JSImplementation="@mozilla.org/push/PushManager;1",
  ChromeOnly, Constructor(DOMString scope)]
 interface PushManagerImpl {
-  Promise<PushSubscription>    subscribe();
+  Promise<PushSubscription>    subscribe(optional PushSubscriptionOptionsInit options);
   Promise<PushSubscription?>   getSubscription();
-  Promise<PushPermissionState> permissionState();
+  Promise<PushPermissionState> permissionState(optional PushSubscriptionOptionsInit options);
 };
 
 [Exposed=(Window,Worker), Func="nsContentUtils::PushEnabled",
  ChromeConstructor(DOMString scope)]
 interface PushManager {
   [Throws, UseCounter]
-  Promise<PushSubscription>    subscribe();
+  Promise<PushSubscription>    subscribe(optional PushSubscriptionOptionsInit options);
   [Throws]
   Promise<PushSubscription?>   getSubscription();
   [Throws]
-  Promise<PushPermissionState> permissionState();
+  Promise<PushPermissionState> permissionState(optional PushSubscriptionOptionsInit options);
 };
 
 enum PushPermissionState
 {
     "granted",
     "denied",
     "prompt"
 };
--- a/dom/webidl/PushSubscription.webidl
+++ b/dom/webidl/PushSubscription.webidl
@@ -22,22 +22,32 @@ dictionary PushSubscriptionKeys
 };
 
 dictionary PushSubscriptionJSON
 {
   USVString endpoint;
   PushSubscriptionKeys keys;
 };
 
+dictionary PushSubscriptionInit
+{
+  required USVString endpoint;
+  required USVString scope;
+  ArrayBuffer? p256dhKey;
+  ArrayBuffer? authSecret;
+  BufferSource? appServerKey;
+};
+
 [Exposed=(Window,Worker), Func="nsContentUtils::PushEnabled",
- ChromeConstructor(DOMString pushEndpoint, DOMString scope,
-                   ArrayBuffer? key, ArrayBuffer? authSecret)]
+ ChromeConstructor(PushSubscriptionInit initDict)]
 interface PushSubscription
 {
-    readonly attribute USVString endpoint;
-    ArrayBuffer? getKey(PushEncryptionKeyName name);
-    [Throws, UseCounter]
-    Promise<boolean> unsubscribe();
+  readonly attribute USVString endpoint;
+  readonly attribute PushSubscriptionOptions options;
+  [Throws]
+  ArrayBuffer? getKey(PushEncryptionKeyName name);
+  [Throws, UseCounter]
+  Promise<boolean> unsubscribe();
 
-    // Implements the custom serializer specified in Push API, section 9.
-    [Throws]
-    PushSubscriptionJSON toJSON();
+  // Implements the custom serializer specified in Push API, section 9.
+  [Throws]
+  PushSubscriptionJSON toJSON();
 };
new file mode 100644
--- /dev/null
+++ b/dom/webidl/PushSubscriptionOptions.webidl
@@ -0,0 +1,15 @@
+/* -*- Mode: IDL; tab-width: 2; 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/.
+*
+* The origin of this IDL file is
+* https://w3c.github.io/push-api/
+*/
+
+[Exposed=(Window,Worker), Func="nsContentUtils::PushEnabled"]
+interface PushSubscriptionOptions
+{
+  [Throws]
+  readonly attribute ArrayBuffer? applicationServerKey;
+};
--- a/dom/webidl/moz.build
+++ b/dom/webidl/moz.build
@@ -721,16 +721,17 @@ if CONFIG['MOZ_SIMPLEPUSH']:
     ]
 else:
     WEBIDL_FILES += [
         'PushEvent.webidl',
         'PushManager.webidl',
         'PushManager.webidl',
         'PushMessageData.webidl',
         'PushSubscription.webidl',
+        'PushSubscriptionOptions.webidl',
     ]
 
 if CONFIG['MOZ_NFC']:
     WEBIDL_FILES += [
          'MozIsoDepTech.webidl',
          'MozNDEFRecord.webidl',
          'MozNFC.webidl',
          'MozNfcATech.webidl',
--- a/js/xpconnect/src/xpc.msg
+++ b/js/xpconnect/src/xpc.msg
@@ -213,8 +213,12 @@ XPC_MSG_DEF(NS_ERROR_GFX_PRINTER_NAME_NO
 XPC_MSG_DEF(NS_ERROR_GFX_PRINTER_COULD_NOT_OPEN_FILE  , "Failed to open output file for print to file.")
 XPC_MSG_DEF(NS_ERROR_GFX_PRINTER_STARTDOC             , "Printing failed while starting the print job.")
 XPC_MSG_DEF(NS_ERROR_GFX_PRINTER_ENDDOC               , "Printing failed while completing the print job.")
 XPC_MSG_DEF(NS_ERROR_GFX_PRINTER_STARTPAGE            , "Printing failed while starting a new page.")
 XPC_MSG_DEF(NS_ERROR_GFX_PRINTER_DOC_IS_BUSY          , "Cannot print this document yet, it is still being loaded.")
 
 /* Codes related to content */
 XPC_MSG_DEF(NS_ERROR_CONTENT_CRASHED                  , "The process that hosted this content has crashed.")
+
+/* Codes for the JS-implemented Push DOM API. These can be removed as part of bug 1252660. */
+XPC_MSG_DEF(NS_ERROR_DOM_PUSH_INVALID_KEY_ERR         , "Invalid raw ECDSA P-256 public key.")
+XPC_MSG_DEF(NS_ERROR_DOM_PUSH_MISMATCHED_KEY_ERR      , "A subscription with a different application server key already exists.")
--- a/xpcom/base/ErrorList.h
+++ b/xpcom/base/ErrorList.h
@@ -946,19 +946,21 @@
   ERROR(NS_ERROR_DOM_ANIM_NO_TARGET_ERR,                  FAILURE(2)),
 #undef MODULE
 
   /* ======================================================================= */
   /* 40: NS_ERROR_MODULE_DOM_PUSH */
   /* ======================================================================= */
 #define MODULE NS_ERROR_MODULE_DOM_PUSH
   ERROR(NS_ERROR_DOM_PUSH_INVALID_REGISTRATION_ERR, FAILURE(1)),
-  ERROR(NS_ERROR_DOM_PUSH_DENIED_ERR, FAILURE(2)),
-  ERROR(NS_ERROR_DOM_PUSH_ABORT_ERR, FAILURE(3)),
-  ERROR(NS_ERROR_DOM_PUSH_SERVICE_UNREACHABLE, FAILURE(4)),
+  ERROR(NS_ERROR_DOM_PUSH_DENIED_ERR,               FAILURE(2)),
+  ERROR(NS_ERROR_DOM_PUSH_ABORT_ERR,                FAILURE(3)),
+  ERROR(NS_ERROR_DOM_PUSH_SERVICE_UNREACHABLE,      FAILURE(4)),
+  ERROR(NS_ERROR_DOM_PUSH_INVALID_KEY_ERR,          FAILURE(5)),
+  ERROR(NS_ERROR_DOM_PUSH_MISMATCHED_KEY_ERR,       FAILURE(6)),
 #undef MODULE
 
   /* ======================================================================= */
   /* 51: NS_ERROR_MODULE_GENERAL */
   /* ======================================================================= */
 #define MODULE NS_ERROR_MODULE_GENERAL
   /* Error code used internally by the incremental downloader to cancel the
    * network channel when the download is already complete. */