Bug 1247089 - Log Web Push decryption errors. r?bkelly draft
authorKit Cambridge <kcambridge@mozilla.com>
Thu, 18 Feb 2016 14:23:58 -0800
changeset 331970 e5b560d396eedeabbccc27fb12311aa58a081d42
parent 331969 dcdd07cfe92236a462f3ce473e2de6de56cca547
child 514514 b35ce040114ba8fbc0084461349f3a53b7d82cc6
push id11131
push userkcambridge@mozilla.com
push dateThu, 18 Feb 2016 23:21:21 +0000
reviewersbkelly
bugs1247089
milestone47.0a1
Bug 1247089 - Log Web Push decryption errors. r?bkelly MozReview-Commit-ID: 8IlzZKpLxoP
dom/interfaces/push/nsIPushNotifier.idl
dom/ipc/ContentChild.cpp
dom/ipc/ContentChild.h
dom/ipc/PContent.ipdl
dom/locales/en-US/chrome/dom/dom.properties
dom/push/PushNotifier.cpp
dom/push/PushNotifier.h
dom/push/PushService.jsm
--- a/dom/interfaces/push/nsIPushNotifier.idl
+++ b/dom/interfaces/push/nsIPushNotifier.idl
@@ -12,16 +12,19 @@ interface nsIPushNotifier : nsISupports
 {
   void notifyPush(in ACString scope, in nsIPrincipal principal);
 
   void notifyPushWithData(in ACString scope, in nsIPrincipal principal,
                           [optional] in uint32_t dataLen,
                           [array, size_is(dataLen)] in uint8_t data);
 
   void notifySubscriptionChange(in ACString scope, in nsIPrincipal principal);
+
+  void notifyError(in ACString scope, in nsIPrincipal principal,
+                   in DOMString message, in uint32_t flags);
 };
 
 /**
  * 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.
--- a/dom/ipc/ContentChild.cpp
+++ b/dom/ipc/ContentChild.cpp
@@ -3269,10 +3269,27 @@ ContentChild::RecvPushSubscriptionChange
     static_cast<PushNotifier*>(pushNotifierIface.get());
   nsresult rv = pushNotifier->NotifySubscriptionChangeWorkers(aScope,
                                                               aPrincipal);
   Unused << NS_WARN_IF(NS_FAILED(rv));
 #endif
   return true;
 }
 
+bool
+ContentChild::RecvPushError(const nsCString& aScope, const nsString& aMessage,
+                            const uint32_t& aFlags)
+{
+#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());
+  pushNotifier->NotifyErrorWorkers(aScope, aMessage, aFlags);
+#endif
+  return true;
+}
+
 } // namespace dom
 } // namespace mozilla
--- a/dom/ipc/ContentChild.h
+++ b/dom/ipc/ContentChild.h
@@ -534,16 +534,20 @@ public:
   RecvPushWithData(const nsCString& aScope,
                    const IPC::Principal& aPrincipal,
                    InfallibleTArray<uint8_t>&& aData) override;
 
   virtual bool
   RecvPushSubscriptionChange(const nsCString& aScope,
                              const IPC::Principal& aPrincipal) override;
 
+  virtual bool
+  RecvPushError(const nsCString& aScope, const nsString& aMessage,
+                const uint32_t& aFlags) override;
+
 #ifdef ANDROID
   gfx::IntSize GetScreenSize() { return mScreenSize; }
 #endif
 
   // Get the directory for IndexedDB files. We query the parent for this and
   // cache the value
   nsString &GetIndexedDBPath();
 
--- a/dom/ipc/PContent.ipdl
+++ b/dom/ipc/PContent.ipdl
@@ -738,16 +738,21 @@ child:
      */
     async PushWithData(nsCString scope, Principal principal, uint8_t[] data);
 
     /**
      * Send a `pushsubscriptionchange` event to a service worker in the child.
      */
     async PushSubscriptionChange(nsCString scope, Principal principal);
 
+    /**
+     * Send a Push error message to all service worker clients in the child.
+     */
+    async PushError(nsCString scope, nsString message, uint32_t flags);
+
 parent:
     /**
      * Tell the content process some attributes of itself.  This is
      * among the first information queried by content processes after
      * startup.  (The message is sync to allow the content process to
      * control when it receives the information.)
      *
      * |id| is a unique ID among all subprocesses.  When |isForApp &&
--- a/dom/locales/en-US/chrome/dom/dom.properties
+++ b/dom/locales/en-US/chrome/dom/dom.properties
@@ -197,8 +197,10 @@ ManifestStartURLInvalid=The start URL is
 ManifestStartURLShouldBeSameOrigin=The start URL must be same origin as document.
 # LOCALIZATION NOTE: %1$S is the name of the object whose property is invalid. %2$S is the name of the invalid property. %3$S is the expected type of the property value. E.g. "Expected the manifest's start_url member to be a string."
 ManifestInvalidType=Expected the %1$S's %2$S member to be a %3$S.
 # LOCALIZATION NOTE: %1$S is the name of the property whose value is invalid. %2$S is the (invalid) value of the property. E.g. "theme_color: 42 is not a valid CSS color."
 ManifestInvalidCSSColor=%1$S: %2$S is not a valid CSS color.
 PatternAttributeCompileFailure=Unable to check <input pattern='%S'> because the pattern is not a valid regexp: %S
 # LOCALIZATION NOTE: Do not translate "postMessage" or DOMWindow. %S values are origins, like https://domain.com:port
 TargetPrincipalDoesNotMatch=Failed to execute 'postMessage' on 'DOMWindow': The target origin provided ('%S') does not match the recipient window's origin ('%S').
+# LOCALIZATION NOTE: Do not translate "ServiceWorker". %1$S is the ServiceWorker scope URL. %2$S is an error string.
+PushMessageDecryptionFailure=The ServiceWorker for scope '%1$S' encountered an error decrypting a push message: '%2$S'. For help with encryption, please see https://developer.mozilla.org/en-US/docs/Web/API/Push_API/Using_the_Push_API#Encryption
--- a/dom/push/PushNotifier.cpp
+++ b/dom/push/PushNotifier.cpp
@@ -14,16 +14,17 @@
 #include "mozilla/unused.h"
 
 #include "mozilla/dom/ContentParent.h"
 #include "mozilla/dom/FetchUtil.h"
 
 namespace mozilla {
 namespace dom {
 
+using workers::AssertIsOnMainThread;
 using workers::ServiceWorkerManager;
 
 PushNotifier::PushNotifier()
 {}
 
 PushNotifier::~PushNotifier()
 {}
 
@@ -76,16 +77,36 @@ PushNotifier::NotifySubscriptionChange(c
     rv = NotifySubscriptionChangeWorkers(aScope, aPrincipal);
     if (NS_WARN_IF(NS_FAILED(rv))) {
       return rv;
     }
   }
   return NS_OK;
 }
 
+NS_IMETHODIMP
+PushNotifier::NotifyError(const nsACString& aScope, nsIPrincipal* aPrincipal,
+                          const nsAString& aMessage, uint32_t aFlags)
+{
+  if (XRE_IsContentProcess()) {
+    return NS_ERROR_NOT_IMPLEMENTED;
+  }
+  if (ShouldNotifyWorkers(aPrincipal)) {
+    // For service worker subscriptions, report the error to all clients.
+    NotifyErrorWorkers(aScope, aMessage, aFlags);
+    return NS_OK;
+  }
+  // For system subscriptions, log the error directly to the browser console.
+  return nsContentUtils::ReportToConsoleNonLocalized(aMessage,
+                                                     aFlags,
+                                                     NS_LITERAL_CSTRING("Push"),
+                                                     nullptr, /* aDocument */
+                                                     EmptyString() /* aSpec */);
+}
+
 nsresult
 PushNotifier::NotifyPush(const nsACString& aScope, nsIPrincipal* aPrincipal,
                          Maybe<nsTArray<uint8_t>> aData)
 {
   if (XRE_IsContentProcess()) {
     return NS_ERROR_NOT_IMPLEMENTED;
   }
   nsresult rv;
@@ -104,16 +125,17 @@ PushNotifier::NotifyPush(const nsACStrin
   return NS_OK;
 }
 
 nsresult
 PushNotifier::NotifyPushWorkers(const nsACString& aScope,
                                 nsIPrincipal* aPrincipal,
                                 Maybe<nsTArray<uint8_t>> aData)
 {
+  AssertIsOnMainThread();
   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
     // is disabled.
@@ -145,16 +167,17 @@ PushNotifier::NotifyPushWorkers(const ns
   }
   return ok ? NS_OK : NS_ERROR_FAILURE;
 }
 
 nsresult
 PushNotifier::NotifySubscriptionChangeWorkers(const nsACString& aScope,
                                               nsIPrincipal* aPrincipal)
 {
+  AssertIsOnMainThread();
   if (!aPrincipal) {
     return NS_ERROR_INVALID_ARG;
   }
 
   if (XRE_IsContentProcess() || !BrowserTabsRemoteAutostart()) {
     // Content process or e10s disabled.
     RefPtr<ServiceWorkerManager> swm = ServiceWorkerManager::GetInstance();
     if (!swm) {
@@ -174,16 +197,50 @@ PushNotifier::NotifySubscriptionChangeWo
   ContentParent::GetAll(contentActors);
   for (uint32_t i = 0; i < contentActors.Length(); ++i) {
     ok &= contentActors[i]->SendPushSubscriptionChange(
       PromiseFlatCString(aScope), IPC::Principal(aPrincipal));
   }
   return ok ? NS_OK : NS_ERROR_FAILURE;
 }
 
+void
+PushNotifier::NotifyErrorWorkers(const nsACString& aScope,
+                                 const nsAString& aMessage,
+                                 uint32_t aFlags)
+{
+  AssertIsOnMainThread();
+
+  const nsPromiseFlatCString& scope = PromiseFlatCString(aScope);
+  const nsPromiseFlatString& message = PromiseFlatString(aMessage);
+
+  if (XRE_IsContentProcess() || !BrowserTabsRemoteAutostart()) {
+    // Content process or e10s disabled.
+    RefPtr<ServiceWorkerManager> swm = ServiceWorkerManager::GetInstance();
+    if (swm) {
+      swm->ReportToAllClients(scope,
+                              message,
+                              EmptyString(), /* aFilename */
+                              EmptyString(), /* aLine */
+                              0, /* aLineNumber */
+                              0, /* aColumnNumber */
+                              aFlags);
+    }
+    return;
+  }
+
+  // Parent process, e10s enabled.
+  nsTArray<ContentParent*> contentActors;
+  ContentParent::GetAll(contentActors);
+  for (uint32_t i = 0; i < contentActors.Length(); ++i) {
+    Unused << NS_WARN_IF(
+      !contentActors[i]->SendPushError(scope, message, aFlags));
+  }
+}
+
 nsresult
 PushNotifier::NotifyPushObservers(const nsACString& aScope,
                                   Maybe<nsTArray<uint8_t>> aData)
 {
   nsCOMPtr<nsIObserverService> obsService =
     mozilla::services::GetObserverService();
   if (!obsService) {
     return NS_ERROR_FAILURE;
--- a/dom/push/PushNotifier.h
+++ b/dom/push/PushNotifier.h
@@ -20,19 +20,19 @@
 namespace mozilla {
 namespace dom {
 
 /**
  * `PushNotifier` implements the `nsIPushNotifier` interface. This service
  * forwards incoming push messages to service workers running in the content
  * process, and emits XPCOM observer notifications for system subscriptions.
  *
- * The XPCOM service can only be used from the main process. Callers running
- * in the content process should use
- * `ServiceWorkerManager::SendPush{SubscriptionChange}Event` directly.
+ * The XPCOM service can only be used from the main process, and exists solely
+ * to support `PushService.jsm`. Other callers should use `ServiceWorkerManager`
+ * directly.
  */
 class PushNotifier final : public nsIPushNotifier
 {
 public:
   PushNotifier();
 
   NS_DECL_CYCLE_COLLECTING_ISUPPORTS
   NS_DECL_CYCLE_COLLECTION_CLASS_AMBIGUOUS(PushNotifier, nsIPushNotifier)
@@ -40,16 +40,18 @@ public:
 
   nsresult NotifyPush(const nsACString& aScope, nsIPrincipal* aPrincipal,
                       Maybe<nsTArray<uint8_t>> aData);
   nsresult NotifyPushWorkers(const nsACString& aScope,
                              nsIPrincipal* aPrincipal,
                              Maybe<nsTArray<uint8_t>> aData);
   nsresult NotifySubscriptionChangeWorkers(const nsACString& aScope,
                                            nsIPrincipal* aPrincipal);
+  void NotifyErrorWorkers(const nsACString& aScope, const nsAString& aMessage,
+                          uint32_t aFlags);
 
 protected:
   virtual ~PushNotifier();
 
 private:
   nsresult NotifyPushObservers(const nsACString& aScope,
                                Maybe<nsTArray<uint8_t>> aData);
   nsresult NotifySubscriptionChangeObservers(const nsACString& aScope);
--- a/dom/push/PushService.jsm
+++ b/dom/push/PushService.jsm
@@ -30,16 +30,19 @@ XPCOMUtils.defineLazyModuleGetter(this, 
 XPCOMUtils.defineLazyServiceGetter(this, "gContentSecurityManager",
                                    "@mozilla.org/contentsecuritymanager;1",
                                    "nsIContentSecurityManager");
 
 XPCOMUtils.defineLazyServiceGetter(this, "gPushNotifier",
                                    "@mozilla.org/push/Notifier;1",
                                    "nsIPushNotifier");
 
+XPCOMUtils.defineLazyGetter(this, "gDOMBundle", () =>
+  Services.strings.createBundle("chrome://global/locale/dom/dom.properties"));
+
 this.EXPORTED_SYMBOLS = ["PushService"];
 
 XPCOMUtils.defineLazyGetter(this, "console", () => {
   let {ConsoleAPI} = Cu.import("resource://gre/modules/Console.jsm", {});
   return new ConsoleAPI({
     maxLogLevelPref: "dom.push.loglevel",
     prefix: "PushService",
   });
@@ -849,17 +852,20 @@ this.PushService = {
           notified = this._notifyApp(record, message);
         }
         // Update quota after the delay, at which point
         // we check for visible notifications.
         setTimeout(() => this._updateQuota(keyID),
           prefs.get("quotaUpdateDelay"));
         return notified;
       }, error => {
-        console.error("receivedPushMessage: Error decrypting message", error);
+        let message = gDOMBundle.formatStringFromName(
+          "PushMessageDecryptionFailure", [record.scope, String(error)], 2);
+        gPushNotifier.notifyError(record.scope, record.principal, message,
+                                  Ci.nsIScriptError.errorFlag);
       });
     }).catch(error => {
       console.error("receivedPushMessage: Error notifying app", error);
     });
   },
 
   _updateQuota: function(keyID) {
     console.debug("updateQuota()");