Bug 1247685 - WebIDL and DOM implementation changes for app server keys. r?mt draft
authorKit Cambridge <kcambridge@mozilla.com>
Wed, 16 Mar 2016 02:47:11 -0700
changeset 340991 1ac84fdb0c13a18a28384b37f6cce5e4a48df48f
parent 340990 3405293d9380b6bbdbf077606b506efeb6ffbc4f
child 340992 7d5b1908a9ea40e22484e61aff885af24df46aff
push id13110
push userkcambridge@mozilla.com
push dateWed, 16 Mar 2016 10:01:24 +0000
reviewersmt
bugs1247685
milestone48.0a1
Bug 1247685 - WebIDL and DOM implementation changes for app server keys. r?mt MozReview-Commit-ID: 1xYjSuLMnV4
dom/base/domerr.msg
dom/bindings/Bindings.conf
dom/interfaces/push/nsIPushService.idl
dom/push/Push.js
dom/push/PushManager.cpp
dom/push/PushManager.h
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
@@ -152,11 +152,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/bindings/Bindings.conf
+++ b/dom/bindings/Bindings.conf
@@ -959,16 +959,21 @@ DOMInterfaces = {
     'workers': False,
     'headerFile': 'mozilla/dom/PushManager.h',
 }, {
     'workers': True,
     'headerFile': 'mozilla/dom/PushManager.h',
     'nativeType': 'mozilla::dom::WorkerPushSubscription',
 }],
 
+'PushSubscriptionOptions': {
+    'headerFile': 'mozilla/dom/PushManager.h',
+    'nativeType': 'mozilla::dom::PushSubscriptionOptions',
+},
+
 'Range': {
     'nativeType': 'nsRange',
     'binaryNames': {
         '__stringifier': 'ToString'
     }
 },
 
 'Rect': {
--- 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
@@ -62,17 +62,17 @@ Push.prototype = {
   },
 
   askPermission: function (aAllowCallback, aCancelCallback) {
     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) {
@@ -85,25 +85,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) => {
@@ -186,44 +201,75 @@ 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);
     sub.setPrincipal(pushManager._principal);
     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
@@ -7,16 +7,17 @@
 #include "mozilla/dom/PushManager.h"
 
 #include "mozilla/Base64.h"
 #include "mozilla/Preferences.h"
 #include "mozilla/Services.h"
 #include "mozilla/unused.h"
 #include "mozilla/dom/PushManagerBinding.h"
 #include "mozilla/dom/PushSubscriptionBinding.h"
+#include "mozilla/dom/PushSubscriptionOptionsBinding.h"
 #include "mozilla/dom/ServiceWorkerGlobalScopeBinding.h"
 
 #include "mozilla/dom/Promise.h"
 #include "mozilla/dom/PromiseWorkerProxy.h"
 
 #include "nsIGlobalObject.h"
 #include "nsIPermissionManager.h"
 #include "nsIPrincipal.h"
@@ -81,16 +82,110 @@ SubscriptionToJSON(PushSubscriptionJSON&
   Unused << NS_WARN_IF(NS_FAILED(rv));
 
   aJSON.mKeys.mAuth.Construct();
   rv = UnpaddedBase64URLEncode(aAuthSecret.Length(), aAuthSecret.Elements(),
                                aJSON.mKeys.mAuth.Value());
   Unused << NS_WARN_IF(NS_FAILED(rv));
 }
 
+void
+CopyBufferSourceToArray(const OwningArrayBufferViewOrArrayBuffer& aSource,
+                        nsTArray<uint8_t>& aArray)
+{
+  MOZ_ASSERT(aSource.IsArrayBuffer() || aSource.IsArrayBufferView());
+  size_t length = 0;
+  uint8_t* data = nullptr;
+  if (aSource.IsArrayBuffer()) {
+    const ArrayBuffer& buffer = aSource.GetAsArrayBuffer();
+    buffer.ComputeLengthAndData();
+    length = buffer.Length();
+    data = buffer.Data();
+  } else if (aSource.IsArrayBufferView()) {
+    const ArrayBufferView& view = aSource.GetAsArrayBufferView();
+    view.ComputeLengthAndData();
+    length = view.Length();
+    data = view.Data();
+  }
+  aArray.SetLength(length);
+  aArray.ReplaceElementsAt(0, length, data, length);
+}
+
+// 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)
+  {}
+
+  ~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;
+}
+
 } // anonymous namespace
 
 class UnsubscribeResultCallback final : public nsIUnsubscribeResultCallback
 {
 public:
   NS_DECL_ISUPPORTS
 
   explicit UnsubscribeResultCallback(Promise* aPromise)
@@ -149,35 +244,47 @@ PushSubscription::ToJSON(PushSubscriptio
 {
   SubscriptionToJSON(aJSON, mEndpoint, mRawP256dhKey, mAuthSecret);
 }
 
 PushSubscription::PushSubscription(nsIGlobalObject* aGlobal,
                                    const nsAString& aEndpoint,
                                    const nsAString& aScope,
                                    const nsTArray<uint8_t>& aRawP256dhKey,
-                                   const nsTArray<uint8_t>& aAuthSecret)
+                                   const nsTArray<uint8_t>& aAuthSecret,
+                                   const nsTArray<uint8_t>& aAppServerKey)
   : mGlobal(aGlobal)
   , mEndpoint(aEndpoint)
   , mScope(aScope)
   , mRawP256dhKey(aRawP256dhKey)
   , mAuthSecret(aAuthSecret)
+  , mAppServerKey(aAppServerKey)
 {
 }
 
 PushSubscription::~PushSubscription()
 {
 }
 
 JSObject*
 PushSubscription::WrapObject(JSContext* aCx, JS::Handle<JSObject*> aGivenProto)
 {
   return PushSubscriptionBinding::Wrap(aCx, this, aGivenProto);
 }
 
+already_AddRefed<PushSubscriptionOptions>
+PushSubscription::Options()
+{
+  if (!mOptions) {
+    mOptions = new PushSubscriptionOptions(mGlobal, mAppServerKey);
+  }
+  RefPtr<PushSubscriptionOptions> options = mOptions;
+  return options.forget();
+}
+
 void
 PushSubscription::GetKey(JSContext* aCx,
                          PushEncryptionKeyName aType,
                          JS::MutableHandle<JSObject*> aKey)
 {
   if (aType == PushEncryptionKeyName::P256dh && !mRawP256dhKey.IsEmpty()) {
     aKey.set(ArrayBuffer::Create(aCx,
                                  mRawP256dhKey.Length(),
@@ -196,45 +303,58 @@ PushSubscription::SetPrincipal(nsIPrinci
 {
   MOZ_ASSERT(!mPrincipal);
   mPrincipal = aPrincipal;
 }
 
 // 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)
 {
-  MOZ_ASSERT(!aEndpoint.IsEmpty());
-  MOZ_ASSERT(!aScope.IsEmpty());
+  const nsAString& endpoint = aInitDict.mEndpoint;
+  MOZ_ASSERT(!endpoint.IsEmpty());
+
+  const nsAString& scope = aInitDict.mScope;
+  MOZ_ASSERT(!scope.IsEmpty());
 
   nsCOMPtr<nsIGlobalObject> global = do_QueryInterface(aGlobal.GetAsSupports());
 
   nsTArray<uint8_t> rawKey;
-  if (!aP256dhKey.IsNull()) {
-    const ArrayBuffer& key = aP256dhKey.Value();
+  if (aInitDict.mP256dhKey.WasPassed() &&
+      !aInitDict.mP256dhKey.Value().IsNull()) {
+
+    const ArrayBuffer& key = aInitDict.mP256dhKey.Value().Value();
     key.ComputeLengthAndData();
     rawKey.InsertElementsAt(0, key.Data(), key.Length());
   }
 
   nsTArray<uint8_t> authSecret;
-  if (!aAuthSecret.IsNull()) {
-    const ArrayBuffer& sekrit = aAuthSecret.Value();
+  if (aInitDict.mAuthSecret.WasPassed() &&
+      !aInitDict.mAuthSecret.Value().IsNull()) {
+
+    const ArrayBuffer& sekrit = aInitDict.mAuthSecret.Value().Value();
     sekrit.ComputeLengthAndData();
     authSecret.InsertElementsAt(0, sekrit.Data(), sekrit.Length());
   }
+
+  nsTArray<uint8_t> appServerKey;
+  if (aInitDict.mAppServerKey.WasPassed() &&
+      !aInitDict.mAppServerKey.Value().IsNull()) {
+
+    CopyBufferSourceToArray(aInitDict.mAppServerKey.Value().Value(),
+                            appServerKey);
+  }
   RefPtr<PushSubscription> sub = new PushSubscription(global,
-                                                      aEndpoint,
-                                                      aScope,
+                                                      endpoint,
+                                                      scope,
                                                       rawKey,
-                                                      authSecret);
+                                                      authSecret,
+                                                      appServerKey);
 
   return sub.forget();
 }
 
 NS_IMPL_CYCLE_COLLECTION_WRAPPERCACHE(PushSubscription, mGlobal, mPrincipal)
 
 NS_IMPL_CYCLE_COLLECTING_ADDREF(PushSubscription)
 NS_IMPL_CYCLE_COLLECTING_RELEASE(PushSubscription)
@@ -265,20 +385,21 @@ void
 PushManager::SetPushManagerImpl(PushManagerImpl& foo, ErrorResult& aRv)
 {
   MOZ_ASSERT(NS_IsMainThread());
   MOZ_ASSERT(!mImpl);
   mImpl = &foo;
 }
 
 already_AddRefed<Promise>
-PushManager::Subscribe(ErrorResult& aRv)
+PushManager::Subscribe(const PushSubscriptionOptionsDict& aOptions,
+                       ErrorResult& aRv)
 {
   MOZ_ASSERT(mImpl);
-  return mImpl->Subscribe(aRv);
+  return mImpl->Subscribe(aOptions, aRv);
 }
 
 already_AddRefed<Promise>
 PushManager::GetSubscription(ErrorResult& aRv)
 {
   MOZ_ASSERT(mImpl);
   return mImpl->GetSubscription(aRv);
 }
@@ -298,21 +419,23 @@ NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(
   NS_INTERFACE_MAP_ENTRY(nsISupports)
 NS_INTERFACE_MAP_END
 
 // WorkerPushSubscription
 
 WorkerPushSubscription::WorkerPushSubscription(const nsAString& aEndpoint,
                                                const nsAString& aScope,
                                                const nsTArray<uint8_t>& aRawP256dhKey,
-                                               const nsTArray<uint8_t>& aAuthSecret)
+                                               const nsTArray<uint8_t>& aAuthSecret,
+                                               const nsTArray<uint8_t>& aAppServerKey)
   : mEndpoint(aEndpoint)
   , mScope(aScope)
   , mRawP256dhKey(aRawP256dhKey)
   , mAuthSecret(aAuthSecret)
+  , mAppServerKey(aAppServerKey)
 {
   MOZ_ASSERT(!aScope.IsEmpty());
   MOZ_ASSERT(!aEndpoint.IsEmpty());
 }
 
 WorkerPushSubscription::~WorkerPushSubscription()
 {}
 
@@ -320,50 +443,73 @@ JSObject*
 WorkerPushSubscription::WrapObject(JSContext* aCx, JS::Handle<JSObject*> aGivenProto)
 {
   return PushSubscriptionBinding_workers::Wrap(aCx, this, aGivenProto);
 }
 
 // static
 already_AddRefed<WorkerPushSubscription>
 WorkerPushSubscription::Constructor(GlobalObject& aGlobal,
-                                    const nsAString& aEndpoint,
-                                    const nsAString& aScope,
-                                    const Nullable<ArrayBuffer>& aP256dhKey,
-                                    const Nullable<ArrayBuffer>& aAuthSecret,
+                                    const PushSubscriptionInit& aInitDict,
                                     ErrorResult& aRv)
 {
   WorkerPrivate* worker = GetCurrentThreadWorkerPrivate();
   MOZ_ASSERT(worker);
   worker->AssertIsOnWorkerThread();
 
+  const nsAString& endpoint = aInitDict.mEndpoint;
+  const nsAString& scope = aInitDict.mScope;
+
   nsTArray<uint8_t> rawKey;
-  if (!aP256dhKey.IsNull()) {
-    const ArrayBuffer& key = aP256dhKey.Value();
+  if (aInitDict.mP256dhKey.WasPassed() &&
+      !aInitDict.mP256dhKey.Value().IsNull()) {
+
+    const ArrayBuffer& key = aInitDict.mP256dhKey.Value().Value();
     key.ComputeLengthAndData();
     rawKey.SetLength(key.Length());
     rawKey.ReplaceElementsAt(0, key.Length(), key.Data(), key.Length());
   }
 
   nsTArray<uint8_t> authSecret;
-  if (!aAuthSecret.IsNull()) {
-    const ArrayBuffer& sekrit = aAuthSecret.Value();
+  if (aInitDict.mAuthSecret.WasPassed() &&
+      !aInitDict.mAuthSecret.Value().IsNull()) {
+
+    const ArrayBuffer& sekrit = aInitDict.mAuthSecret.Value().Value();
     sekrit.ComputeLengthAndData();
     authSecret.SetLength(sekrit.Length());
     authSecret.ReplaceElementsAt(0, sekrit.Length(),
                                  sekrit.Data(), sekrit.Length());
   }
-  RefPtr<WorkerPushSubscription> sub = new WorkerPushSubscription(aEndpoint,
-                                                                  aScope,
+
+  nsTArray<uint8_t> appServerKey;
+  if (aInitDict.mAppServerKey.WasPassed() &&
+      !aInitDict.mAppServerKey.Value().IsNull()) {
+
+    CopyBufferSourceToArray(aInitDict.mAppServerKey.Value().Value(),
+                            appServerKey);
+  }
+  RefPtr<WorkerPushSubscription> sub = new WorkerPushSubscription(endpoint,
+                                                                  scope,
                                                                   rawKey,
-                                                                  authSecret);
+                                                                  authSecret,
+                                                                  appServerKey);
 
   return sub.forget();
 }
 
+already_AddRefed<PushSubscriptionOptions>
+WorkerPushSubscription::Options()
+{
+  if (!mOptions) {
+    mOptions = new PushSubscriptionOptions(nullptr, mAppServerKey);
+  }
+  RefPtr<PushSubscriptionOptions> options = mOptions;
+  return options.forget();
+}
+
 void
 WorkerPushSubscription::GetKey(JSContext* aCx,
                                PushEncryptionKeyName aType,
                                JS::MutableHandle<JSObject*> aKey)
 {
   if (aType == mozilla::dom::PushEncryptionKeyName::P256dh &&
       !mRawP256dhKey.IsEmpty()) {
     aKey.set(ArrayBuffer::Create(aCx,
@@ -567,37 +713,40 @@ WorkerPushManager::WrapObject(JSContext*
 class GetSubscriptionResultRunnable final : public WorkerRunnable
 {
 public:
   GetSubscriptionResultRunnable(PromiseWorkerProxy* aProxy,
                                 nsresult aStatus,
                                 const nsAString& aEndpoint,
                                 const nsAString& aScope,
                                 const nsTArray<uint8_t>& aRawP256dhKey,
-                                const nsTArray<uint8_t>& aAuthSecret)
+                                const nsTArray<uint8_t>& aAuthSecret,
+                                const nsTArray<uint8_t>& aAppServerKey)
     : WorkerRunnable(aProxy->GetWorkerPrivate(), WorkerThreadModifyBusyCount)
     , mProxy(aProxy)
     , mStatus(aStatus)
     , mEndpoint(aEndpoint)
     , mScope(aScope)
     , mRawP256dhKey(aRawP256dhKey)
     , mAuthSecret(aAuthSecret)
+    , mAppServerKey(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<WorkerPushSubscription> sub =
             new WorkerPushSubscription(mEndpoint, mScope,
-                                       mRawP256dhKey, mAuthSecret);
+                                       mRawP256dhKey, mAuthSecret,
+                                       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);
     }
 
@@ -609,16 +758,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,
@@ -637,29 +787,30 @@ public:
     RefPtr<PromiseWorkerProxy> proxy = mProxy.forget();
 
     MutexAutoLock lock(proxy->Lock());
     if (proxy->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);
     }
 
     RefPtr<GetSubscriptionResultRunnable> r =
       new GetSubscriptionResultRunnable(proxy,
                                         aStatus,
                                         endpoint,
                                         mScope,
                                         rawP256dhKey,
-                                        authSecret);
+                                        authSecret,
+                                        appServerKey);
     r->Dispatch();
     return NS_OK;
   }
 
   // Convenience method for use in this file.
   void
   OnPushSubscriptionError(nsresult aStatus)
   {
@@ -667,81 +818,31 @@ 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,
-                          WorkerPushManager::SubscriptionAction aAction)
+                          WorkerPushManager::SubscriptionAction aAction,
+                          const nsTArray<uint8_t>& aAppServerKey)
     : mProxy(aProxy)
-    , mScope(aScope), mAction(aAction)
+    , mScope(aScope), mAction(aAction), mAppServerKey(aAppServerKey)
   {}
 
   NS_IMETHOD
   Run() override
   {
     AssertIsOnMainThread();
 
     nsCOMPtr<nsIPrincipal> principal;
@@ -779,17 +880,23 @@ public:
     nsCOMPtr<nsIPushService> service =
       do_GetService("@mozilla.org/push/Service;1");
     if (!service) {
       callback->OnPushSubscriptionError(NS_ERROR_FAILURE);
       return NS_OK;
     }
 
     if (mAction == WorkerPushManager::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 == WorkerPushManager::GetSubscriptionAction);
       rv = service->GetSubscription(mScope, principal, callback);
     }
 
     if (NS_WARN_IF(NS_FAILED(rv))) {
       callback->OnPushSubscriptionError(NS_ERROR_FAILURE);
       return NS_OK;
@@ -800,20 +907,22 @@ public:
 
 private:
   ~GetSubscriptionRunnable()
   {}
 
   RefPtr<PromiseWorkerProxy> mProxy;
   nsString mScope;
   WorkerPushManager::SubscriptionAction mAction;
+  nsTArray<uint8_t> mAppServerKey;
 };
 
 already_AddRefed<Promise>
-WorkerPushManager::PerformSubscriptionAction(SubscriptionAction aAction, ErrorResult& aRv)
+WorkerPushManager::PerformSubscriptionAction(SubscriptionAction aAction,
+  const PushSubscriptionOptionsDict& 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())) {
@@ -821,27 +930,46 @@ WorkerPushManager::PerformSubscriptionAc
   }
 
   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()) {
+    CopyBufferSourceToArray(aOptions.mApplicationServerKey.Value(),
+                            appServerKey);
+    if (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, appServerKey);
   MOZ_ALWAYS_TRUE(NS_SUCCEEDED(NS_DispatchToMainThread(r)));
 
   return p.forget();
 }
 
 already_AddRefed<Promise>
-WorkerPushManager::Subscribe(ErrorResult& aRv)
+WorkerPushManager::PerformSubscriptionAction(SubscriptionAction aAction,
+                                             ErrorResult& aRv)
 {
-  return PerformSubscriptionAction(SubscribeAction, aRv);
+  PushSubscriptionOptionsDict options;
+  return PerformSubscriptionAction(aAction, options, aRv);
+}
+
+already_AddRefed<Promise>
+WorkerPushManager::Subscribe(const PushSubscriptionOptionsDict& aOptions,
+                             ErrorResult& aRv)
+{
+  return PerformSubscriptionAction(SubscribeAction, aOptions, aRv);
 }
 
 already_AddRefed<Promise>
 WorkerPushManager::GetSubscription(ErrorResult& aRv)
 {
   return PerformSubscriptionAction(GetSubscriptionAction, aRv);
 }
 
@@ -951,10 +1079,56 @@ WorkerPushManager::~WorkerPushManager()
 
 NS_IMPL_CYCLE_COLLECTION_WRAPPERCACHE_0(WorkerPushManager)
 NS_IMPL_CYCLE_COLLECTING_ADDREF(WorkerPushManager)
 NS_IMPL_CYCLE_COLLECTING_RELEASE(WorkerPushManager)
 NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(WorkerPushManager)
   NS_WRAPPERCACHE_INTERFACE_MAP_ENTRY
   NS_INTERFACE_MAP_ENTRY(nsISupports)
 NS_INTERFACE_MAP_END
+
+PushSubscriptionOptions::PushSubscriptionOptions(nsIGlobalObject* aGlobal,
+  const nsTArray<uint8_t>& aAppServerKey)
+  : mGlobal(aGlobal)
+  , mAppServerKey(aAppServerKey)
+{}
+
+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);
+}
+
+nsIGlobalObject*
+PushSubscriptionOptions::GetParentObject() const
+{
+  if (!NS_IsMainThread()) {
+    WorkerPrivate* worker = GetCurrentThreadWorkerPrivate();
+    MOZ_ASSERT(worker);
+    return worker->GlobalScope();
+  }
+  return mGlobal;
+}
+
+void
+PushSubscriptionOptions::GetApplicationServerKey(JSContext* aCx,
+  JS::MutableHandle<JSObject*> aKey)
+{
+  if (mAppServerKey.IsEmpty()) {
+    aKey.set(nullptr);
+  } else {
+    aKey.set(ArrayBuffer::Create(aCx,
+      mAppServerKey.Length(), mAppServerKey.Elements()));
+  }
+}
+
 } // namespace dom
 } // namespace mozilla
--- a/dom/push/PushManager.h
+++ b/dom/push/PushManager.h
@@ -49,31 +49,68 @@ class nsIPrincipal;
 
 namespace mozilla {
 namespace dom {
 
 namespace workers {
 class WorkerPrivate;
 }
 
+class OwningArrayBufferViewOrArrayBuffer;
 class Promise;
 class PushManagerImpl;
+struct PushSubscriptionOptionsDict;
+
+class PushSubscriptionOptions final : public nsISupports
+                                    , public nsWrapperCache
+{
+public:
+  NS_DECL_CYCLE_COLLECTING_ISUPPORTS
+  NS_DECL_CYCLE_COLLECTION_SCRIPT_HOLDER_CLASS(PushSubscriptionOptions)
+
+  PushSubscriptionOptions(nsIGlobalObject* aGlobal,
+                          const nsTArray<uint8_t>& aAppServerKey);
+
+  nsIGlobalObject*
+  GetParentObject() const;
+
+  JSObject*
+  WrapObject(JSContext* aCx, JS::Handle<JSObject*> aGivenProto) override;
+
+  bool
+  UserVisibleOnly() const
+  {
+    return false;
+  };
+
+  void
+  GetApplicationServerKey(JSContext* aCx,
+                          JS::MutableHandle<JSObject*> aKey);
+
+protected:
+  ~PushSubscriptionOptions();
+
+private:
+  nsCOMPtr<nsIGlobalObject> mGlobal;
+  nsTArray<uint8_t> mAppServerKey;
+};
 
 class PushSubscription final : public nsISupports
                              , public nsWrapperCache
 {
 public:
   NS_DECL_CYCLE_COLLECTING_ISUPPORTS
   NS_DECL_CYCLE_COLLECTION_SCRIPT_HOLDER_CLASS(PushSubscription)
 
   explicit PushSubscription(nsIGlobalObject* aGlobal,
                             const nsAString& aEndpoint,
                             const nsAString& aScope,
                             const nsTArray<uint8_t>& aP256dhKey,
-                            const nsTArray<uint8_t>& aAuthSecret);
+                            const nsTArray<uint8_t>& aAuthSecret,
+                            const nsTArray<uint8_t>& aAppServerKey);
 
   JSObject*
   WrapObject(JSContext* aCx, JS::Handle<JSObject*> aGivenProto) override;
 
   nsIGlobalObject*
   GetParentObject() const
   {
     return mGlobal;
@@ -87,41 +124,43 @@ public:
 
   void
   GetKey(JSContext* cx,
          PushEncryptionKeyName aType,
          JS::MutableHandle<JSObject*> aKey);
 
   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);
 
   void
   SetPrincipal(nsIPrincipal* aPrincipal);
 
   already_AddRefed<Promise>
   Unsubscribe(ErrorResult& aRv);
 
   void
   ToJSON(PushSubscriptionJSON& aJSON);
 
+  already_AddRefed<PushSubscriptionOptions>
+  Options();
+
 protected:
   ~PushSubscription();
 
 private:
   nsCOMPtr<nsIGlobalObject> mGlobal;
   nsCOMPtr<nsIPrincipal> mPrincipal;
   nsString mEndpoint;
   nsString mScope;
   nsTArray<uint8_t> mRawP256dhKey;
   nsTArray<uint8_t> mAuthSecret;
+  nsTArray<uint8_t> mAppServerKey;
+  RefPtr<PushSubscriptionOptions> mOptions;
 };
 
 class PushManager final : public nsISupports
                         , public nsWrapperCache
 {
 public:
   NS_DECL_CYCLE_COLLECTING_ISUPPORTS
   NS_DECL_CYCLE_COLLECTION_SCRIPT_HOLDER_CLASS(PushManager)
@@ -133,17 +172,18 @@ public:
   {
     return mGlobal;
   }
 
   JSObject*
   WrapObject(JSContext* aCx, JS::Handle<JSObject*> aGivenProto) override;
 
   already_AddRefed<Promise>
-  Subscribe(ErrorResult& aRv);
+  Subscribe(const PushSubscriptionOptionsDict& aOptions,
+            ErrorResult& aRv);
 
   already_AddRefed<Promise>
   GetSubscription(ErrorResult& aRv);
 
   already_AddRefed<Promise>
   PermissionState(ErrorResult& aRv);
 
   void
@@ -163,33 +203,31 @@ class WorkerPushSubscription final : pub
 {
 public:
   NS_DECL_CYCLE_COLLECTING_ISUPPORTS
   NS_DECL_CYCLE_COLLECTION_SCRIPT_HOLDER_CLASS(WorkerPushSubscription)
 
   explicit WorkerPushSubscription(const nsAString& aEndpoint,
                                   const nsAString& aScope,
                                   const nsTArray<uint8_t>& aRawP256dhKey,
-                                  const nsTArray<uint8_t>& aAuthSecret);
+                                  const nsTArray<uint8_t>& aAuthSecret,
+                                  const nsTArray<uint8_t>& aAppServerKey);
 
   nsIGlobalObject*
   GetParentObject() const
   {
     return nullptr;
   }
 
   JSObject*
   WrapObject(JSContext* aCx, JS::Handle<JSObject*> aGivenProto) override;
 
   static already_AddRefed<WorkerPushSubscription>
   Constructor(GlobalObject& aGlobal,
-              const nsAString& aEndpoint,
-              const nsAString& aScope,
-              const Nullable<ArrayBuffer>& aP256dhKey,
-              const Nullable<ArrayBuffer>& aAuthSecret,
+              const PushSubscriptionInit& aInitDict,
               ErrorResult& aRv);
 
   void
   GetEndpoint(nsAString& aEndpoint) const
   {
     aEndpoint = mEndpoint;
   }
 
@@ -198,24 +236,29 @@ public:
          JS::MutableHandle<JSObject*> aP256dhKey);
 
   already_AddRefed<Promise>
   Unsubscribe(ErrorResult& aRv);
 
   void
   ToJSON(PushSubscriptionJSON& aJSON);
 
+  already_AddRefed<PushSubscriptionOptions>
+  Options();
+
 protected:
   ~WorkerPushSubscription();
 
 private:
   nsString mEndpoint;
   nsString mScope;
   nsTArray<uint8_t> mRawP256dhKey;
   nsTArray<uint8_t> mAuthSecret;
+  nsTArray<uint8_t> mAppServerKey;
+  RefPtr<PushSubscriptionOptions> mOptions;
 };
 
 class WorkerPushManager final : public nsISupports
                               , public nsWrapperCache
 {
 public:
   NS_DECL_CYCLE_COLLECTING_ISUPPORTS
   NS_DECL_CYCLE_COLLECTION_SCRIPT_HOLDER_CLASS(WorkerPushManager)
@@ -235,17 +278,22 @@ public:
 
   JSObject*
   WrapObject(JSContext* aCx, JS::Handle<JSObject*> aGivenProto) override;
 
   already_AddRefed<Promise>
   PerformSubscriptionAction(SubscriptionAction aAction, ErrorResult& aRv);
 
   already_AddRefed<Promise>
-  Subscribe(ErrorResult& aRv);
+  PerformSubscriptionAction(SubscriptionAction aAction,
+    const PushSubscriptionOptionsDict& aOptions, ErrorResult& aRv);
+
+  already_AddRefed<Promise>
+  Subscribe(const PushSubscriptionOptionsDict& aOptions,
+            ErrorResult& aRv);
 
   already_AddRefed<Promise>
   GetSubscription(ErrorResult& aRv);
 
   already_AddRefed<Promise>
   PermissionState(ErrorResult& aRv);
 
 protected:
--- a/dom/webidl/PushManager.webidl
+++ b/dom/webidl/PushManager.webidl
@@ -2,38 +2,43 @@
 /* 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 PushSubscriptionOptionsDict {
+  // boolean userVisibleOnly = false;
+  BufferSource? applicationServerKey = null;
+};
+
 // Please see comments in dom/push/PushManager.h for the split between
 // PushManagerImpl and PushManager.
 [JSImplementation="@mozilla.org/push/PushManager;1",
  NoInterfaceObject]
 interface PushManagerImpl {
-    Promise<PushSubscription>     subscribe();
+    Promise<PushSubscription>     subscribe(optional PushSubscriptionOptionsDict options);
     Promise<PushSubscription?>    getSubscription();
     Promise<PushPermissionState> permissionState();
 
     // We need a setter in the bindings so that the C++ can use it,
     // but we don't want it exposed to client JS.  WebPushMethodHider
     // always returns false.
     [Func="ServiceWorkerRegistration::WebPushMethodHider"] void setScope(DOMString scope);
 };
 
 [Exposed=(Window,Worker), Func="nsContentUtils::PushEnabled"]
 interface PushManager {
   [ChromeOnly, Throws, Exposed=Window]
   void setPushManagerImpl(PushManagerImpl store);
 
   [Throws, UseCounter]
-  Promise<PushSubscription>     subscribe();
+  Promise<PushSubscription>     subscribe(optional PushSubscriptionOptionsDict options);
   [Throws]
   Promise<PushSubscription?>    getSubscription();
   [Throws]
   Promise<PushPermissionState> permissionState();
 };
 
 enum PushPermissionState
 {
--- a/dom/webidl/PushSubscription.webidl
+++ b/dom/webidl/PushSubscription.webidl
@@ -22,22 +22,31 @@ 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;
+    readonly attribute PushSubscriptionOptions options;
     ArrayBuffer? getKey(PushEncryptionKeyName name);
     [Throws, UseCounter]
     Promise<boolean> unsubscribe();
 
     // Implements the custom serializer specified in Push API, section 9.
     PushSubscriptionJSON toJSON();
 
     // Used to set the principal from the JS implemented PushManager.
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
+{
+  readonly attribute boolean userVisibleOnly;
+  readonly attribute ArrayBuffer? applicationServerKey;
+};
--- a/dom/webidl/moz.build
+++ b/dom/webidl/moz.build
@@ -723,16 +723,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
@@ -942,19 +942,21 @@
   ERROR(NS_ERROR_DOM_ANIM_TARGET_NOT_IN_DOC_ERR,          FAILURE(3)),
 #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. */