Bug 1246341 - Report push event errors and rejections to the Push service. r=baku
authorKit Cambridge <kcambridge@mozilla.com>
Mon, 28 Mar 2016 11:50:39 -0700
changeset 290950 69fe21c66f7f9edb46c6269fa95d0f0079dab5fa
parent 290949 77b2b963de2c9e8172cb5896d938416d30867e8b
child 290951 7aefa268e50cecfece06621123e5f6f2bd5e57ff
push id19656
push usergwagner@mozilla.com
push dateMon, 04 Apr 2016 13:43:23 +0000
treeherderb2g-inbound@e99061fde28a [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersbaku
bugs1246341
milestone48.0a1
Bug 1246341 - Report push event errors and rejections to the Push service. r=baku MozReview-Commit-ID: D6fRPqojOEh
dom/interfaces/push/moz.build
dom/interfaces/push/nsIPushErrorReporter.idl
dom/interfaces/push/nsIPushNotifier.idl
dom/ipc/ContentChild.cpp
dom/ipc/ContentChild.h
dom/ipc/PContent.ipdl
dom/push/PushNotifier.cpp
dom/push/PushNotifier.h
dom/workers/ServiceWorkerManager.cpp
dom/workers/ServiceWorkerManager.h
dom/workers/ServiceWorkerPrivate.cpp
dom/workers/ServiceWorkerPrivate.h
--- a/dom/interfaces/push/moz.build
+++ b/dom/interfaces/push/moz.build
@@ -1,12 +1,13 @@
 # -*- Mode: python; c-basic-offset: 4; indent-tabs-mode: nil; tab-width: 40 -*-
 # vim: set filetype=python:
 # This Source Code Form is subject to the terms of the Mozilla Public
 # License, v. 2.0. If a copy of the MPL was not distributed with this
 # file, You can obtain one at http://mozilla.org/MPL/2.0/.
 
 XPIDL_SOURCES += [
+    'nsIPushErrorReporter.idl',
     'nsIPushNotifier.idl',
     'nsIPushService.idl',
 ]
 
 XPIDL_MODULE = 'dom_push'
new file mode 100644
--- /dev/null
+++ b/dom/interfaces/push/nsIPushErrorReporter.idl
@@ -0,0 +1,46 @@
+/* -*- 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/. */
+
+#include "nsISupports.idl"
+
+[scriptable, uuid(b58249f9-1a04-48cc-bc20-2c992d64c73e)]
+interface nsIPushErrorReporter : nsISupports
+{
+  /**
+   * Ack status codes, reported when the Push service acknowledges an incoming
+   * message.
+   *
+   * Acks are sent before the message is dispatched to the service worker,
+   * since the server delays new messages until all outstanding ones have been
+   * acked. |reportDeliveryError| will be called if an error occurs in the
+   * worker's `push` event handler after acking the message.
+  */
+  const uint16_t ACK_DELIVERED = 0;
+  const uint16_t ACK_DECRYPTION_ERROR = 1;
+  const uint16_t ACK_NOT_DELIVERED = 2;
+
+  /**
+   * Unsubscribe reasons, reported when the service drops a subscription.
+   */
+  const uint16_t UNSUBSCRIBE_MANUAL = 3;
+  const uint16_t UNSUBSCRIBE_QUOTA_EXCEEDED = 4;
+  const uint16_t UNSUBSCRIBE_PERMISSION_REVOKED = 5;
+
+  /**
+   * Delivery error reasons, reported when a service worker fails to handle
+   * an incoming push message in its `push` event handler.
+   */
+  const uint16_t DELIVERY_UNCAUGHT_EXCEPTION = 6;
+  const uint16_t DELIVERY_UNHANDLED_REJECTION = 7;
+  const uint16_t DELIVERY_INTERNAL_ERROR = 8;
+
+  /**
+   * Reports a `push` event handler error to the Push service. |messageId| is
+   * an opaque string passed to `nsIPushNotifier.notifyPush{WithData}`.
+   * |status| is a delivery error reason.
+   */
+  void reportDeliveryError(in DOMString messageId,
+                           [optional] in uint16_t reason);
+};
--- a/dom/interfaces/push/nsIPushNotifier.idl
+++ b/dom/interfaces/push/nsIPushNotifier.idl
@@ -1,37 +1,44 @@
+/* -*- 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/. */
+
 #include "nsISupports.idl"
 
 interface nsIPrincipal;
 
 /**
  * Fires service worker events for push messages sent to content subscriptions,
  * and XPCOM observer notifications for system subscriptions. This service
  * can only be used from the parent process.
  */
-[scriptable, uuid(b00dfdeb-14e5-425b-adc7-b531442e3216)]
+[scriptable, builtinclass, uuid(b00dfdeb-14e5-425b-adc7-b531442e3216)]
 interface nsIPushNotifier : nsISupports
 {
-  void notifyPush(in ACString scope, in nsIPrincipal principal);
+  void notifyPush(in ACString scope, in nsIPrincipal principal,
+                  in DOMString messageId);
 
   void notifyPushWithData(in ACString scope, in nsIPrincipal principal,
+                          in DOMString messageId,
                           [optional] in uint32_t dataLen,
                           [array, size_is(dataLen)] in uint8_t data);
 
   void notifySubscriptionChange(in ACString scope, in nsIPrincipal principal);
 };
 
 /**
  * A push message sent to a system subscription, used as the subject of a
  * `push-message` observer notification. System subscriptions are created by
  * the system principal, and do not use worker events.
  *
  * This interface resembles the `PushMessageData` WebIDL interface.
  */
-[scriptable, uuid(136dc8fd-8c56-4176-9170-eaa86b6ba99e)]
+[scriptable, builtinclass, uuid(136dc8fd-8c56-4176-9170-eaa86b6ba99e)]
 interface nsIPushMessage : nsISupports
 {
   /** Extracts the data as a UTF-8 text string. */
   DOMString text();
 
   /** Extracts the data as a JSON value. */
   [implicit_jscontext] jsval json();
 
--- a/dom/ipc/ContentChild.cpp
+++ b/dom/ipc/ContentChild.cpp
@@ -3268,47 +3268,50 @@ ContentChild::RecvEndDragSession(const b
     }
     dragService->EndDragSession(aDoneDrag);
   }
   return true;
 }
 
 bool
 ContentChild::RecvPush(const nsCString& aScope,
-                       const IPC::Principal& aPrincipal)
+                       const IPC::Principal& aPrincipal,
+                       const nsString& aMessageId)
 {
 #ifndef MOZ_SIMPLEPUSH
   nsCOMPtr<nsIPushNotifier> pushNotifierIface =
       do_GetService("@mozilla.org/push/Notifier;1");
   if (NS_WARN_IF(!pushNotifierIface)) {
       return true;
   }
   PushNotifier* pushNotifier =
     static_cast<PushNotifier*>(pushNotifierIface.get());
-  nsresult rv = pushNotifier->NotifyPushWorkers(aScope, aPrincipal, Nothing());
+  nsresult rv = pushNotifier->NotifyPushWorkers(aScope, aPrincipal,
+                                                aMessageId, Nothing());
   Unused << NS_WARN_IF(NS_FAILED(rv));
 #endif
   return true;
 }
 
 bool
 ContentChild::RecvPushWithData(const nsCString& aScope,
                                const IPC::Principal& aPrincipal,
+                               const nsString& aMessageId,
                                InfallibleTArray<uint8_t>&& aData)
 {
 #ifndef MOZ_SIMPLEPUSH
   nsCOMPtr<nsIPushNotifier> pushNotifierIface =
       do_GetService("@mozilla.org/push/Notifier;1");
   if (NS_WARN_IF(!pushNotifierIface)) {
       return true;
   }
   PushNotifier* pushNotifier =
     static_cast<PushNotifier*>(pushNotifierIface.get());
   nsresult rv = pushNotifier->NotifyPushWorkers(aScope, aPrincipal,
-                                                Some(aData));
+                                                aMessageId, Some(aData));
   Unused << NS_WARN_IF(NS_FAILED(rv));
 #endif
   return true;
 }
 
 bool
 ContentChild::RecvPushSubscriptionChange(const nsCString& aScope,
                                          const IPC::Principal& aPrincipal)
--- a/dom/ipc/ContentChild.h
+++ b/dom/ipc/ContentChild.h
@@ -526,21 +526,23 @@ public:
   RecvInvokeDragSession(nsTArray<IPCDataTransfer>&& aTransfers,
                         const uint32_t& aAction) override;
 
   virtual bool RecvEndDragSession(const bool& aDoneDrag,
                                   const bool& aUserCancelled) override;
 
   virtual bool
   RecvPush(const nsCString& aScope,
-           const IPC::Principal& aPrincipal) override;
+           const IPC::Principal& aPrincipal,
+           const nsString& aMessageId) override;
 
   virtual bool
   RecvPushWithData(const nsCString& aScope,
                    const IPC::Principal& aPrincipal,
+                   const nsString& aMessageId,
                    InfallibleTArray<uint8_t>&& aData) override;
 
   virtual bool
   RecvPushSubscriptionChange(const nsCString& aScope,
                              const IPC::Principal& aPrincipal) override;
 
   // Get the directory for IndexedDB files. We query the parent for this and
   // cache the value
--- a/dom/ipc/PContent.ipdl
+++ b/dom/ipc/PContent.ipdl
@@ -725,22 +725,23 @@ child:
     /**
      * Notify the child that the Gecko Media Plugins installed changed.
      */
     async NotifyGMPsChanged();
 
     /**
      * Send a `push` event without data to a service worker in the child.
      */
-    async Push(nsCString scope, Principal principal);
+    async Push(nsCString scope, Principal principal, nsString messageId);
 
     /**
      * Send a `push` event with data to a service worker in the child.
      */
-    async PushWithData(nsCString scope, Principal principal, uint8_t[] data);
+    async PushWithData(nsCString scope, Principal principal,
+                       nsString messageId, uint8_t[] data);
 
     /**
      * Send a `pushsubscriptionchange` event to a service worker in the child.
      */
     async PushSubscriptionChange(nsCString scope, Principal principal);
 
     /**
      * Windows specific: associate this content process with the browsers
--- a/dom/push/PushNotifier.cpp
+++ b/dom/push/PushNotifier.cpp
@@ -36,32 +36,34 @@ NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(
 NS_INTERFACE_MAP_END
 
 NS_IMPL_CYCLE_COLLECTING_ADDREF(PushNotifier)
 NS_IMPL_CYCLE_COLLECTING_RELEASE(PushNotifier)
 
 NS_IMETHODIMP
 PushNotifier::NotifyPushWithData(const nsACString& aScope,
                                  nsIPrincipal* aPrincipal,
+                                 const nsAString& aMessageId,
                                  uint32_t aDataLen, uint8_t* aData)
 {
   nsTArray<uint8_t> data;
   if (!data.SetCapacity(aDataLen, fallible)) {
     return NS_ERROR_OUT_OF_MEMORY;
   }
   if (!data.InsertElementsAt(0, aData, aDataLen, fallible)) {
     return NS_ERROR_OUT_OF_MEMORY;
   }
-  return NotifyPush(aScope, aPrincipal, Some(data));
+  return NotifyPush(aScope, aPrincipal, aMessageId, Some(data));
 }
 
 NS_IMETHODIMP
-PushNotifier::NotifyPush(const nsACString& aScope, nsIPrincipal* aPrincipal)
+PushNotifier::NotifyPush(const nsACString& aScope, nsIPrincipal* aPrincipal,
+                         const nsAString& aMessageId)
 {
-  return NotifyPush(aScope, aPrincipal, Nothing());
+  return NotifyPush(aScope, aPrincipal, aMessageId, Nothing());
 }
 
 NS_IMETHODIMP
 PushNotifier::NotifySubscriptionChange(const nsACString& aScope,
                                        nsIPrincipal* aPrincipal)
 {
   if (XRE_IsContentProcess()) {
     return NS_ERROR_NOT_IMPLEMENTED;
@@ -79,41 +81,43 @@ PushNotifier::NotifySubscriptionChange(c
       return rv;
     }
   }
   return NS_OK;
 }
 
 nsresult
 PushNotifier::NotifyPush(const nsACString& aScope, nsIPrincipal* aPrincipal,
-                         Maybe<nsTArray<uint8_t>> aData)
+                         const nsAString& aMessageId,
+                         const Maybe<nsTArray<uint8_t>>& aData)
 {
   if (XRE_IsContentProcess()) {
     return NS_ERROR_NOT_IMPLEMENTED;
   }
   nsresult rv;
   if (ShouldNotifyObservers(aPrincipal)) {
     rv = NotifyPushObservers(aScope, aData);
     if (NS_WARN_IF(NS_FAILED(rv))) {
       return rv;
     }
   }
   if (ShouldNotifyWorkers(aPrincipal)) {
-    rv = NotifyPushWorkers(aScope, aPrincipal, aData);
+    rv = NotifyPushWorkers(aScope, aPrincipal, aMessageId, aData);
     if (NS_WARN_IF(NS_FAILED(rv))) {
       return rv;
     }
   }
   return NS_OK;
 }
 
 nsresult
 PushNotifier::NotifyPushWorkers(const nsACString& aScope,
                                 nsIPrincipal* aPrincipal,
-                                Maybe<nsTArray<uint8_t>> aData)
+                                const nsAString& aMessageId,
+                                const Maybe<nsTArray<uint8_t>>& aData)
 {
   if (!aPrincipal) {
     return NS_ERROR_INVALID_ARG;
   }
 
   if (XRE_IsContentProcess() || !BrowserTabsRemoteAutostart()) {
     // Notify the worker from the current process. Either we're running in
     // the content process and received a message from the parent, or e10s
@@ -122,31 +126,31 @@ PushNotifier::NotifyPushWorkers(const ns
     if (!swm) {
       return NS_ERROR_FAILURE;
     }
     nsAutoCString originSuffix;
     nsresult rv = aPrincipal->GetOriginSuffix(originSuffix);
     if (NS_WARN_IF(NS_FAILED(rv))) {
       return rv;
     }
-    return swm->SendPushEvent(originSuffix, aScope, aData);
+    return swm->SendPushEvent(originSuffix, aScope, aMessageId, aData);
   }
 
   // Otherwise, we're in the parent and e10s is enabled. Broadcast the event
   // to all content processes.
   bool ok = true;
   nsTArray<ContentParent*> contentActors;
   ContentParent::GetAll(contentActors);
   for (uint32_t i = 0; i < contentActors.Length(); ++i) {
     if (aData) {
       ok &= contentActors[i]->SendPushWithData(PromiseFlatCString(aScope),
-        IPC::Principal(aPrincipal), aData.ref());
+        IPC::Principal(aPrincipal), PromiseFlatString(aMessageId), aData.ref());
     } else {
       ok &= contentActors[i]->SendPush(PromiseFlatCString(aScope),
-        IPC::Principal(aPrincipal));
+        IPC::Principal(aPrincipal), PromiseFlatString(aMessageId));
     }
   }
   return ok ? NS_OK : NS_ERROR_FAILURE;
 }
 
 nsresult
 PushNotifier::NotifySubscriptionChangeWorkers(const nsACString& aScope,
                                               nsIPrincipal* aPrincipal)
@@ -177,17 +181,17 @@ PushNotifier::NotifySubscriptionChangeWo
     ok &= contentActors[i]->SendPushSubscriptionChange(
       PromiseFlatCString(aScope), IPC::Principal(aPrincipal));
   }
   return ok ? NS_OK : NS_ERROR_FAILURE;
 }
 
 nsresult
 PushNotifier::NotifyPushObservers(const nsACString& aScope,
-                                  Maybe<nsTArray<uint8_t>> aData)
+                                  const Maybe<nsTArray<uint8_t>>& aData)
 {
   nsCOMPtr<nsIPushMessage> message = nullptr;
   if (aData) {
     message = new PushMessage(aData.ref());
   }
   return DoNotifyObservers(message, OBSERVER_TOPIC_PUSH, aScope);
 }
 
--- a/dom/push/PushNotifier.h
+++ b/dom/push/PushNotifier.h
@@ -38,29 +38,31 @@ class PushNotifier final : public nsIPus
 public:
   PushNotifier();
 
   NS_DECL_CYCLE_COLLECTING_ISUPPORTS
   NS_DECL_CYCLE_COLLECTION_CLASS_AMBIGUOUS(PushNotifier, nsIPushNotifier)
   NS_DECL_NSIPUSHNOTIFIER
 
   nsresult NotifyPush(const nsACString& aScope, nsIPrincipal* aPrincipal,
-                      Maybe<nsTArray<uint8_t>> aData);
+                      const nsAString& aMessageId,
+                      const Maybe<nsTArray<uint8_t>>& aData);
   nsresult NotifyPushWorkers(const nsACString& aScope,
                              nsIPrincipal* aPrincipal,
-                             Maybe<nsTArray<uint8_t>> aData);
+                             const nsAString& aMessageId,
+                             const Maybe<nsTArray<uint8_t>>& aData);
   nsresult NotifySubscriptionChangeWorkers(const nsACString& aScope,
                                            nsIPrincipal* aPrincipal);
 
 protected:
   virtual ~PushNotifier();
 
 private:
   nsresult NotifyPushObservers(const nsACString& aScope,
-                               Maybe<nsTArray<uint8_t>> aData);
+                               const Maybe<nsTArray<uint8_t>>& aData);
   nsresult NotifySubscriptionChangeObservers(const nsACString& aScope);
   nsresult DoNotifyObservers(nsISupports *aSubject, const char *aTopic,
                              const nsACString& aScope);
   bool ShouldNotifyObservers(nsIPrincipal* aPrincipal);
   bool ShouldNotifyWorkers(nsIPrincipal* aPrincipal);
 };
 
 /**
--- a/dom/workers/ServiceWorkerManager.cpp
+++ b/dom/workers/ServiceWorkerManager.cpp
@@ -2221,26 +2221,27 @@ ServiceWorkerManager::SendPushEvent(cons
                                     uint8_t* aDataBytes,
                                     uint8_t optional_argc)
 {
   if (optional_argc == 2) {
     nsTArray<uint8_t> data;
     if (!data.InsertElementsAt(0, aDataBytes, aDataLength, fallible)) {
       return NS_ERROR_OUT_OF_MEMORY;
     }
-    return SendPushEvent(aOriginAttributes, aScope, Some(data));
+    return SendPushEvent(aOriginAttributes, aScope, EmptyString(), Some(data));
   }
   MOZ_ASSERT(optional_argc == 0);
-  return SendPushEvent(aOriginAttributes, aScope, Nothing());
+  return SendPushEvent(aOriginAttributes, aScope, EmptyString(), Nothing());
 }
 
 nsresult
 ServiceWorkerManager::SendPushEvent(const nsACString& aOriginAttributes,
                                     const nsACString& aScope,
-                                    Maybe<nsTArray<uint8_t>> aData)
+                                    const nsAString& aMessageId,
+                                    const Maybe<nsTArray<uint8_t>>& aData)
 {
 #ifdef MOZ_SIMPLEPUSH
   return NS_ERROR_NOT_AVAILABLE;
 #else
   PrincipalOriginAttributes attrs;
   if (!attrs.PopulateFromSuffix(aOriginAttributes)) {
     return NS_ERROR_INVALID_ARG;
   }
@@ -2248,17 +2249,18 @@ ServiceWorkerManager::SendPushEvent(cons
   ServiceWorkerInfo* serviceWorker = GetActiveWorkerInfoForScope(attrs, aScope);
   if (NS_WARN_IF(!serviceWorker)) {
     return NS_ERROR_FAILURE;
   }
 
   RefPtr<ServiceWorkerRegistrationInfo> registration =
     GetRegistration(serviceWorker->GetPrincipal(), aScope);
 
-  return serviceWorker->WorkerPrivate()->SendPushEvent(aData, registration);
+  return serviceWorker->WorkerPrivate()->SendPushEvent(aMessageId, aData,
+                                                       registration);
 #endif // MOZ_SIMPLEPUSH
 }
 
 NS_IMETHODIMP
 ServiceWorkerManager::SendPushSubscriptionChangeEvent(const nsACString& aOriginAttributes,
                                                       const nsACString& aScope)
 {
 #ifdef MOZ_SIMPLEPUSH
--- a/dom/workers/ServiceWorkerManager.h
+++ b/dom/workers/ServiceWorkerManager.h
@@ -494,17 +494,18 @@ public:
                                   ServiceWorkerRegistrationListener* aListener);
 
   void
   MaybeCheckNavigationUpdate(nsIDocument* aDoc);
 
   nsresult
   SendPushEvent(const nsACString& aOriginAttributes,
                 const nsACString& aScope,
-                Maybe<nsTArray<uint8_t>> aData);
+                const nsAString& aMessageId,
+                const Maybe<nsTArray<uint8_t>>& aData);
 
   nsresult
   NotifyUnregister(nsIPrincipal* aPrincipal, const nsAString& aScope);
 
 private:
   ServiceWorkerManager();
   ~ServiceWorkerManager();
 
--- a/dom/workers/ServiceWorkerPrivate.cpp
+++ b/dom/workers/ServiceWorkerPrivate.cpp
@@ -21,20 +21,23 @@
 #include "WorkerRunnable.h"
 #include "WorkerScope.h"
 #include "mozilla/Assertions.h"
 #include "mozilla/dom/FetchUtil.h"
 #include "mozilla/dom/IndexedDatabaseManager.h"
 #include "mozilla/dom/InternalHeaders.h"
 #include "mozilla/dom/NotificationEvent.h"
 #include "mozilla/dom/PromiseNativeHandler.h"
+#include "mozilla/dom/RequestBinding.h"
+#include "mozilla/unused.h"
+
 #ifndef MOZ_SIMPLEPUSH
+#include "nsIPushErrorReporter.h"
 #include "mozilla/dom/PushEventBinding.h"
 #endif
-#include "mozilla/dom/RequestBinding.h"
 
 using namespace mozilla;
 using namespace mozilla::dom;
 
 BEGIN_WORKERS_NAMESPACE
 
 NS_IMPL_CYCLE_COLLECTING_ADDREF(ServiceWorkerPrivate)
 NS_IMPL_CYCLE_COLLECTING_RELEASE(ServiceWorkerPrivate)
@@ -572,63 +575,139 @@ ServiceWorkerPrivate::SendLifeCycleEvent
   }
 
   return NS_OK;
 }
 
 #ifndef MOZ_SIMPLEPUSH
 namespace {
 
+class PushErrorReporter final : public PromiseNativeHandler
+{
+  WorkerPrivate* mWorkerPrivate;
+  nsString mMessageId;
+
+  ~PushErrorReporter()
+  {
+  }
+
+public:
+  NS_DECL_THREADSAFE_ISUPPORTS
+
+  PushErrorReporter(WorkerPrivate* aWorkerPrivate,
+                    const nsAString& aMessageId)
+    : mWorkerPrivate(aWorkerPrivate)
+    , mMessageId(aMessageId)
+  {
+    mWorkerPrivate->AssertIsOnWorkerThread();
+  }
+
+  void ResolvedCallback(JSContext* aCx, JS::Handle<JS::Value> aValue) override
+  {
+    mWorkerPrivate->AssertIsOnWorkerThread();
+    mWorkerPrivate = nullptr;
+    // Do nothing; we only use this to report errors to the Push service.
+  }
+
+  void RejectedCallback(JSContext* aCx, JS::Handle<JS::Value> aValue) override
+  {
+    Report(nsIPushErrorReporter::DELIVERY_UNHANDLED_REJECTION);
+  }
+
+  void Report(uint16_t aReason = nsIPushErrorReporter::DELIVERY_INTERNAL_ERROR)
+  {
+    mWorkerPrivate->AssertIsOnWorkerThread();
+    mWorkerPrivate = nullptr;
+
+    if (NS_WARN_IF(aReason > nsIPushErrorReporter::DELIVERY_INTERNAL_ERROR) ||
+        mMessageId.IsEmpty()) {
+      return;
+    }
+    nsCOMPtr<nsIRunnable> runnable =
+      NS_NewRunnableMethodWithArg<uint16_t>(this,
+        &PushErrorReporter::ReportOnMainThread, aReason);
+    MOZ_ALWAYS_TRUE(NS_SUCCEEDED(
+      NS_DispatchToMainThread(runnable.forget())));
+  }
+
+  void ReportOnMainThread(uint16_t aReason)
+  {
+    AssertIsOnMainThread();
+    nsCOMPtr<nsIPushErrorReporter> reporter =
+      do_GetService("@mozilla.org/push/Service;1");
+    if (reporter) {
+      nsresult rv = reporter->ReportDeliveryError(mMessageId, aReason);
+      Unused << NS_WARN_IF(NS_FAILED(rv));
+    }
+  }
+};
+
+NS_IMPL_ISUPPORTS0(PushErrorReporter)
+
 class SendPushEventRunnable final : public ExtendableFunctionalEventWorkerRunnable
 {
+  nsString mMessageId;
   Maybe<nsTArray<uint8_t>> mData;
 
 public:
   SendPushEventRunnable(WorkerPrivate* aWorkerPrivate,
                         KeepAliveToken* aKeepAliveToken,
+                        const nsAString& aMessageId,
                         const Maybe<nsTArray<uint8_t>>& aData,
                         nsMainThreadPtrHandle<ServiceWorkerRegistrationInfo> aRegistration)
       : ExtendableFunctionalEventWorkerRunnable(
           aWorkerPrivate, aKeepAliveToken, aRegistration)
+      , mMessageId(aMessageId)
       , mData(aData)
   {
     AssertIsOnMainThread();
     MOZ_ASSERT(aWorkerPrivate);
     MOZ_ASSERT(aWorkerPrivate->IsServiceWorker());
   }
 
   bool
   WorkerRun(JSContext* aCx, WorkerPrivate* aWorkerPrivate) override
   {
     MOZ_ASSERT(aWorkerPrivate);
     GlobalObject globalObj(aCx, aWorkerPrivate->GlobalScope()->GetWrapper());
 
+    RefPtr<PushErrorReporter> errorReporter =
+      new PushErrorReporter(aWorkerPrivate, mMessageId);
+
     PushEventInit pei;
     if (mData) {
       const nsTArray<uint8_t>& bytes = mData.ref();
       JSObject* data = Uint8Array::Create(aCx, bytes.Length(), bytes.Elements());
       if (!data) {
+        errorReporter->Report();
         return false;
       }
       pei.mData.Construct().SetAsArrayBufferView().Init(data);
     }
     pei.mBubbles = false;
     pei.mCancelable = false;
 
     ErrorResult result;
     RefPtr<PushEvent> event =
       PushEvent::Constructor(globalObj, NS_LITERAL_STRING("push"), pei, result);
     if (NS_WARN_IF(result.Failed())) {
       result.SuppressException();
+      errorReporter->Report();
       return false;
     }
     event->SetTrusted(true);
 
+    RefPtr<Promise> waitUntil;
     DispatchExtendableEventOnWorkerScope(aCx, aWorkerPrivate->GlobalScope(),
-                                         event, nullptr);
+                                         event, getter_AddRefs(waitUntil));
+    if (waitUntil) {
+      waitUntil->AppendNativeHandler(errorReporter);
+    } else {
+      errorReporter->Report(nsIPushErrorReporter::DELIVERY_UNCAUGHT_EXCEPTION);
+    }
 
     return true;
   }
 };
 
 class SendPushSubscriptionChangeEventRunnable final : public ExtendableEventWorkerRunnable
 {
 
@@ -666,34 +745,36 @@ public:
     return true;
   }
 };
 
 } // anonymous namespace
 #endif // !MOZ_SIMPLEPUSH
 
 nsresult
-ServiceWorkerPrivate::SendPushEvent(const Maybe<nsTArray<uint8_t>>& aData,
+ServiceWorkerPrivate::SendPushEvent(const nsAString& aMessageId,
+                                    const Maybe<nsTArray<uint8_t>>& aData,
                                     ServiceWorkerRegistrationInfo* aRegistration)
 {
 #ifdef MOZ_SIMPLEPUSH
   return NS_ERROR_NOT_AVAILABLE;
 #else
   nsresult rv = SpawnWorkerIfNeeded(PushEvent, nullptr);
   NS_ENSURE_SUCCESS(rv, rv);
 
   MOZ_ASSERT(mKeepAliveToken);
 
   nsMainThreadPtrHandle<ServiceWorkerRegistrationInfo> regInfo(
     new nsMainThreadPtrHolder<ServiceWorkerRegistrationInfo>(aRegistration, false));
 
   RefPtr<WorkerRunnable> r = new SendPushEventRunnable(mWorkerPrivate,
-                                                         mKeepAliveToken,
-                                                         aData,
-                                                         regInfo);
+                                                       mKeepAliveToken,
+                                                       aMessageId,
+                                                       aData,
+                                                       regInfo);
 
   if (mInfo->State() == ServiceWorkerState::Activating) {
     mPendingFunctionalEvents.AppendElement(r.forget());
     return NS_OK;
   }
 
   MOZ_ASSERT(mInfo->State() == ServiceWorkerState::Activated);
 
--- a/dom/workers/ServiceWorkerPrivate.h
+++ b/dom/workers/ServiceWorkerPrivate.h
@@ -80,17 +80,18 @@ public:
   CheckScriptEvaluation(LifeCycleEventCallback* aCallback);
 
   nsresult
   SendLifeCycleEvent(const nsAString& aEventType,
                      LifeCycleEventCallback* aCallback,
                      nsIRunnable* aLoadFailure);
 
   nsresult
-  SendPushEvent(const Maybe<nsTArray<uint8_t>>& aData,
+  SendPushEvent(const nsAString& aMessageId,
+                const Maybe<nsTArray<uint8_t>>& aData,
                 ServiceWorkerRegistrationInfo* aRegistration);
 
   nsresult
   SendPushSubscriptionChangeEvent();
 
   nsresult
   SendNotificationClickEvent(const nsAString& aID,
                              const nsAString& aTitle,