Bug 1266433 - Send Push observer notifications to parent and content processes. f=janx r=dragana
MozReview-Commit-ID: 1aUS8HcQApo
--- a/dom/interfaces/push/nsIPushNotifier.idl
+++ b/dom/interfaces/push/nsIPushNotifier.idl
@@ -4,18 +4,17 @@
* 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.
+ * and XPCOM observer notifications for system subscriptions.
*/
[scriptable, builtinclass, uuid(b00dfdeb-14e5-425b-adc7-b531442e3216)]
interface nsIPushNotifier : nsISupports
{
void notifyPush(in ACString scope, in nsIPrincipal principal,
in DOMString messageId);
void notifyPushWithData(in ACString scope, in nsIPrincipal principal,
--- a/dom/ipc/ContentChild.cpp
+++ b/dom/ipc/ContentChild.cpp
@@ -3269,18 +3269,22 @@ ContentChild::RecvPush(const nsCString&
#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,
- aMessageId, Nothing());
+
+ nsresult rv = pushNotifier->NotifyPushObservers(aScope, Nothing());
+ Unused << NS_WARN_IF(NS_FAILED(rv));
+
+ 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,
@@ -3290,18 +3294,22 @@ ContentChild::RecvPushWithData(const nsC
#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,
- aMessageId, Some(aData));
+
+ nsresult rv = pushNotifier->NotifyPushObservers(aScope, Some(aData));
+ Unused << NS_WARN_IF(NS_FAILED(rv));
+
+ rv = pushNotifier->NotifyPushWorkers(aScope, aPrincipal,
+ aMessageId, Some(aData));
Unused << NS_WARN_IF(NS_FAILED(rv));
#endif
return true;
}
bool
ContentChild::RecvPushSubscriptionChange(const nsCString& aScope,
const IPC::Principal& aPrincipal)
@@ -3309,18 +3317,21 @@ ContentChild::RecvPushSubscriptionChange
#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->NotifySubscriptionChangeWorkers(aScope,
- aPrincipal);
+
+ nsresult rv = pushNotifier->NotifySubscriptionChangeObservers(aScope);
+ Unused << NS_WARN_IF(NS_FAILED(rv));
+
+ 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)
--- a/dom/ipc/ContentParent.cpp
+++ b/dom/ipc/ContentParent.cpp
@@ -259,16 +259,20 @@ using namespace mozilla::system;
#include "nsIProfiler.h"
#include "nsIProfileSaveEvent.h"
#endif
#ifdef MOZ_GAMEPAD
#include "mozilla/dom/GamepadMonitoring.h"
#endif
+#ifndef MOZ_SIMPLEPUSH
+#include "mozilla/dom/PushNotifier.h"
+#endif
+
#ifdef XP_WIN
#include "mozilla/widget/AudioSession.h"
#endif
#include "VRManagerParent.h" // for VRManagerParent
// For VP9Benchmark::sBenchmarkFpsPref
#include "Benchmark.h"
@@ -5828,16 +5832,73 @@ ContentParent::StartProfiler(nsIProfiler
return;
}
nsCOMPtr<nsISupports> gatherer;
profiler->GetProfileGatherer(getter_AddRefs(gatherer));
mGatherer = static_cast<ProfileGatherer*>(gatherer.get());
#endif
}
+bool
+ContentParent::RecvNotifyPushObservers(const nsCString& aScope,
+ 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->NotifyPushObservers(aScope, Nothing());
+ Unused << NS_WARN_IF(NS_FAILED(rv));
+#endif
+ return true;
+}
+
+bool
+ContentParent::RecvNotifyPushObserversWithData(const nsCString& aScope,
+ 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->NotifyPushObservers(aScope, Some(aData));
+ Unused << NS_WARN_IF(NS_FAILED(rv));
+#endif
+ return true;
+}
+
+bool
+ContentParent::RecvNotifyPushSubscriptionChangeObservers(const nsCString& aScope)
+{
+#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->NotifySubscriptionChangeObservers(aScope);
+ Unused << NS_WARN_IF(NS_FAILED(rv));
+#endif
+ return true;
+}
+
} // namespace dom
} // namespace mozilla
NS_IMPL_ISUPPORTS(ParentIdleListener, nsIObserver)
NS_IMETHODIMP
ParentIdleListener::Observe(nsISupports*, const char* aTopic, const char16_t* aData)
{
--- a/dom/ipc/ContentParent.h
+++ b/dom/ipc/ContentParent.h
@@ -1106,16 +1106,25 @@ private:
virtual bool RecvGetDeviceStorageLocations(DeviceStorageLocationInfo* info) override;
virtual bool RecvGetAndroidSystemInfo(AndroidSystemInfo* aInfo) override;
virtual bool RecvNotifyBenchmarkResult(const nsString& aCodecName,
const uint32_t& aDecodeFPS) override;
+ virtual bool RecvNotifyPushObservers(const nsCString& aScope,
+ const nsString& aMessageId) override;
+
+ virtual bool RecvNotifyPushObserversWithData(const nsCString& aScope,
+ const nsString& aMessageId,
+ InfallibleTArray<uint8_t>&& aData) override;
+
+ virtual bool RecvNotifyPushSubscriptionChangeObservers(const nsCString& aScope) override;
+
// If you add strong pointers to cycle collected objects here, be sure to
// release these objects in ShutDownProcess. See the comment there for more
// details.
GeckoChildProcessHost* mSubprocess;
ContentParent* mOpener;
ContentParentId mChildID;
--- a/dom/ipc/PContent.ipdl
+++ b/dom/ipc/PContent.ipdl
@@ -1184,15 +1184,31 @@ parent:
sync RemovePermission(Principal principal, nsCString permissionType) returns (nsresult rv);
/**
* Tell the parent that a decoder's' benchmark has been completed.
* The result can then be stored in permanent storage.
*/
async NotifyBenchmarkResult(nsString aCodecName, uint32_t aDecodeFPS);
+ /**
+ * Notify `push-message` observers without data in the parent.
+ */
+ async NotifyPushObservers(nsCString scope, nsString messageId);
+
+ /**
+ * Notify `push-message` observers with data in the parent.
+ */
+ async NotifyPushObserversWithData(nsCString scope, nsString messageId,
+ uint8_t[] data);
+
+ /**
+ * Notify `push-subscription-change` observers in the parent.
+ */
+ async NotifyPushSubscriptionChangeObservers(nsCString scope);
+
both:
async AsyncMessage(nsString aMessage, CpowEntry[] aCpows,
Principal aPrincipal, ClonedMessageData aData);
};
}
}
--- a/dom/push/PushNotifier.cpp
+++ b/dom/push/PushNotifier.cpp
@@ -11,16 +11,17 @@
#include "nsNetUtil.h"
#include "nsXPCOM.h"
#include "ServiceWorkerManager.h"
#include "mozilla/Services.h"
#include "mozilla/unused.h"
#include "mozilla/dom/BodyUtil.h"
+#include "mozilla/dom/ContentChild.h"
#include "mozilla/dom/ContentParent.h"
namespace mozilla {
namespace dom {
using workers::AssertIsOnMainThread;
using workers::ServiceWorkerManager;
@@ -62,28 +63,32 @@ PushNotifier::NotifyPush(const nsACStrin
{
return NotifyPush(aScope, aPrincipal, aMessageId, Nothing());
}
NS_IMETHODIMP
PushNotifier::NotifySubscriptionChange(const nsACString& aScope,
nsIPrincipal* aPrincipal)
{
- nsresult rv;
- if (ShouldNotifyObservers(aPrincipal)) {
- rv = NotifySubscriptionChangeObservers(aScope);
- if (NS_WARN_IF(NS_FAILED(rv))) {
- return rv;
+ nsresult rv = NotifySubscriptionChangeObservers(aScope);
+ Unused << NS_WARN_IF(NS_FAILED(rv));
+
+ if (XRE_IsContentProcess()) {
+ // Forward XPCOM observer notifications to the parent.
+ ContentChild* parentActor = ContentChild::GetSingleton();
+ if (!NS_WARN_IF(!parentActor)) {
+ Unused << NS_WARN_IF(
+ !parentActor->SendNotifyPushSubscriptionChangeObservers(
+ PromiseFlatCString(aScope)));
}
}
- if (ShouldNotifyWorkers(aPrincipal)) {
- rv = NotifySubscriptionChangeWorkers(aScope, aPrincipal);
- if (NS_WARN_IF(NS_FAILED(rv))) {
- return rv;
- }
+
+ 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)
{
@@ -104,28 +109,42 @@ PushNotifier::NotifyError(const nsACStri
nsContentUtils::eOMIT_LOCATION);
}
nsresult
PushNotifier::NotifyPush(const nsACString& aScope, nsIPrincipal* aPrincipal,
const nsAString& aMessageId,
const Maybe<nsTArray<uint8_t>>& aData)
{
- nsresult rv;
- if (ShouldNotifyObservers(aPrincipal)) {
- rv = NotifyPushObservers(aScope, aData);
- if (NS_WARN_IF(NS_FAILED(rv))) {
- return rv;
+ // Notify XPCOM observers in the current process.
+ nsresult rv = NotifyPushObservers(aScope, aData);
+ Unused << NS_WARN_IF(NS_FAILED(rv));
+
+ if (XRE_IsContentProcess()) {
+ // If we're in the content process, forward the notification to the parent.
+ // We don't need to do anything if we're already in the parent;
+ // `ContentChild::RecvPush` will notify content process observers.
+ ContentChild* parentActor = ContentChild::GetSingleton();
+ if (!NS_WARN_IF(!parentActor)) {
+ if (aData) {
+ Unused << NS_WARN_IF(
+ !parentActor->SendNotifyPushObserversWithData(
+ PromiseFlatCString(aScope), PromiseFlatString(aMessageId),
+ aData.ref()));
+ } else {
+ Unused << NS_WARN_IF(
+ !parentActor->SendNotifyPushObservers(
+ PromiseFlatCString(aScope), PromiseFlatString(aMessageId)));
+ }
}
}
- if (ShouldNotifyWorkers(aPrincipal)) {
- rv = NotifyPushWorkers(aScope, aPrincipal, aMessageId, aData);
- if (NS_WARN_IF(NS_FAILED(rv))) {
- return rv;
- }
+
+ 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,
const nsAString& aMessageId,
@@ -135,16 +154,19 @@ PushNotifier::NotifyPushWorkers(const ns
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.
+ if (!ShouldNotifyWorkers(aPrincipal)) {
+ return NS_OK;
+ }
RefPtr<ServiceWorkerManager> swm = ServiceWorkerManager::GetInstance();
if (!swm) {
return NS_ERROR_FAILURE;
}
nsAutoCString originSuffix;
nsresult rv = aPrincipal->GetOriginSuffix(originSuffix);
if (NS_WARN_IF(NS_FAILED(rv))) {
return rv;
@@ -175,16 +197,19 @@ PushNotifier::NotifySubscriptionChangeWo
{
AssertIsOnMainThread();
if (!aPrincipal) {
return NS_ERROR_INVALID_ARG;
}
if (XRE_IsContentProcess() || !BrowserTabsRemoteAutostart()) {
// Content process or e10s disabled.
+ if (!ShouldNotifyWorkers(aPrincipal)) {
+ return NS_OK;
+ }
RefPtr<ServiceWorkerManager> swm = ServiceWorkerManager::GetInstance();
if (!swm) {
return NS_ERROR_FAILURE;
}
nsAutoCString originSuffix;
nsresult rv = aPrincipal->GetOriginSuffix(originSuffix);
if (NS_WARN_IF(NS_FAILED(rv))) {
return rv;
@@ -295,25 +320,16 @@ PushNotifier::DoNotifyObservers(nsISuppo
nsCOMPtr<nsISupports> service = do_GetService(contractId);
}
}
return obsService->NotifyObservers(aSubject, aTopic,
NS_ConvertUTF8toUTF16(aScope).get());
}
bool
-PushNotifier::ShouldNotifyObservers(nsIPrincipal* aPrincipal)
-{
- // Notify XPCOM observers for system subscriptions, or all subscriptions
- // if the `testing.notifyAllObservers` pref is set.
- return nsContentUtils::IsSystemPrincipal(aPrincipal) ||
- Preferences::GetBool("dom.push.testing.notifyAllObservers");
-}
-
-bool
PushNotifier::ShouldNotifyWorkers(nsIPrincipal* aPrincipal)
{
// System subscriptions use XPCOM observer notifications instead of service
// worker events. The `testing.notifyWorkers` pref disables worker events for
// non-system subscriptions.
return !nsContentUtils::IsSystemPrincipal(aPrincipal) &&
Preferences::GetBool("dom.push.testing.notifyWorkers", true);
}
--- a/dom/push/PushNotifier.h
+++ b/dom/push/PushNotifier.h
@@ -19,55 +19,58 @@
// These constants are duplicated in `PushComponents.js`.
#define OBSERVER_TOPIC_PUSH "push-message"
#define OBSERVER_TOPIC_SUBSCRIPTION_CHANGE "push-subscription-change"
namespace mozilla {
namespace dom {
+class ContentParent;
+class ContentChild;
+
/**
* `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.
*
* This service exists solely to support `PushService.jsm`. Other callers
* should use `ServiceWorkerManager` directly.
*/
class PushNotifier final : public nsIPushNotifier
{
+ friend class ContentParent;
+ friend class ContentChild;
+
public:
PushNotifier();
NS_DECL_CYCLE_COLLECTING_ISUPPORTS
NS_DECL_CYCLE_COLLECTION_CLASS_AMBIGUOUS(PushNotifier, nsIPushNotifier)
NS_DECL_NSIPUSHNOTIFIER
+private:
+ virtual ~PushNotifier();
+
nsresult NotifyPush(const nsACString& aScope, nsIPrincipal* aPrincipal,
const nsAString& aMessageId,
const Maybe<nsTArray<uint8_t>>& aData);
nsresult NotifyPushWorkers(const nsACString& aScope,
nsIPrincipal* aPrincipal,
const nsAString& aMessageId,
const 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,
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);
};
/**
* `PushMessage` implements the `nsIPushMessage` interface, similar to
* the `PushMessageData` WebIDL interface. Instances of this class are
* passed as the subject of `push-message` observer notifications for
* system subscriptions.
--- a/dom/push/test/xpcshell/PushServiceHandler.js
+++ b/dom/push/test/xpcshell/PushServiceHandler.js
@@ -1,10 +1,10 @@
-// An XPCOM service that's registered with the category manager for handling
-// push notifications with scope "chrome://test-scope"
+// An XPCOM service that's registered with the category manager in the parent
+// process for handling push notifications with scope "chrome://test-scope"
"use strict";
const {classes: Cc, interfaces: Ci, utils: Cu, results: Cr} = Components;
Cu.import("resource://gre/modules/XPCOMUtils.jsm");
Cu.import("resource://gre/modules/Services.jsm");
let pushService = Cc["@mozilla.org/push/Service;1"].getService(Ci.nsIPushService);
--- a/dom/push/test/xpcshell/head.js
+++ b/dom/push/test/xpcshell/head.js
@@ -159,17 +159,16 @@ function setPrefs(prefs = {}) {
// Misc. defaults.
'adaptive.mobile': '',
'http2.maxRetries': 2,
'http2.retryInterval': 500,
'http2.reset_retry_count_after_ms': 60000,
maxQuotaPerSubscription: 16,
quotaUpdateDelay: 3000,
'testing.notifyWorkers': false,
- 'testing.notifyAllObservers': true,
}, prefs);
for (let pref in defaultPrefs) {
servicePrefs.set(pref, defaultPrefs[pref]);
}
}
function compareAscending(a, b) {
return a > b ? 1 : a < b ? -1 : 0;
new file mode 100644
--- /dev/null
+++ b/dom/push/test/xpcshell/test_handler_service_parent.js
@@ -0,0 +1,25 @@
+'use strict';
+
+function run_test() {
+ do_get_profile();
+ run_next_test();
+}
+
+add_task(function* test_observer_notifications() {
+ // Push observer notifications dispatched in the child should be forwarded to
+ // the parent.
+ let notifyPromise = promiseObserverNotification(
+ PushServiceComponent.pushTopic);
+ let subChangePromise = promiseObserverNotification(
+ PushServiceComponent.subscriptionChangeTopic);
+
+ yield run_test_in_child('./test_handler_service.js');
+
+ let {data: notifyScope} = yield notifyPromise;
+ equal(notifyScope, 'chrome://test-scope',
+ 'Should forward push notifications with the correct scope');
+
+ let {data: subChangeScope} = yield subChangePromise;
+ equal(subChangeScope, 'chrome://test-scope',
+ 'Should forward subscription change notifications with the correct scope');
+});
--- a/dom/push/test/xpcshell/test_notification_http2.js
+++ b/dom/push/test/xpcshell/test_notification_http2.js
@@ -38,17 +38,16 @@ function run_test() {
addCertOverride("localhost", serverPort,
Ci.nsICertOverrideService.ERROR_UNTRUSTED |
Ci.nsICertOverrideService.ERROR_MISMATCH |
Ci.nsICertOverrideService.ERROR_TIME);
prefs.setIntPref("network.http.speculative-parallel-limit", oldPref);
servicePrefs.set('testing.notifyWorkers', false);
- servicePrefs.set('testing.notifyAllObservers', true);
run_next_test();
}
add_task(function* test_pushNotifications() {
// /pushNotifications/subscription1 will send a message with no rs and padding
// length 1.
--- a/dom/push/test/xpcshell/test_resubscribe_4xxCode_http2.js
+++ b/dom/push/test/xpcshell/test_resubscribe_4xxCode_http2.js
@@ -47,17 +47,16 @@ httpServer.registerPathHandler("/subscri
httpServer.registerPathHandler("/newSubscription", listenSuccessHandler);
httpServer.start(-1);
function run_test() {
do_get_profile();
servicePrefs.set('testing.notifyWorkers', false);
- servicePrefs.set('testing.notifyAllObservers', true);
setPrefs();
run_next_test();
}
add_task(function* test1() {
let db = PushServiceHttp2.newPushDB();
--- a/dom/push/test/xpcshell/xpcshell.ini
+++ b/dom/push/test/xpcshell/xpcshell.ini
@@ -2,16 +2,17 @@
head = head.js head-http2.js
tail =
# Push notifications and alarms are currently disabled on Android.
skip-if = toolkit == 'android'
[test_clear_origin_data.js]
[test_crypto.js]
[test_drop_expired.js]
+[test_handler_service_parent.js]
[test_handler_service.js]
support-files = PushServiceHandler.js PushServiceHandler.manifest
[test_notification_ack.js]
[test_notification_data.js]
[test_notification_duplicate.js]
[test_notification_error.js]
[test_notification_incomplete.js]
[test_notification_version_string.js]