Bug 1247089 - Log Web Push decryption errors. r?bkelly
MozReview-Commit-ID: 8IlzZKpLxoP
--- 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()");