Bug 1247685 - WebIDL and DOM implementation changes for app server keys. r=mt,baku
authorKit Cambridge <kcambridge@mozilla.com>
Tue, 22 Mar 2016 13:38:03 -0700
changeset 332252 6ce993bd6aec52d4cf9e0c9783418453c423fb27
parent 332251 f0131a8cfd627a4beeece0c890f20c0c8123788c
child 332253 8067eb75c506a397da008c198a55d53611cc165c
push id6048
push userkmoir@mozilla.com
push dateMon, 06 Jun 2016 19:02:08 +0000
treeherdermozilla-beta@46d72a56c57d [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersmt, baku
bugs1247685
milestone48.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 1247685 - WebIDL and DOM implementation changes for app server keys. r=mt,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(win) {
     console.debug("init()");
 
-    this._window = aWindow;
+    this._window = win;
 
-    this.initDOMRequestHelper(aWindow);
+    this.initDOMRequestHelper(win);
 
-    this._principal = aWindow.document.nodePrincipal;
+    this._principal = win.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
@@ -722,16 +722,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. */