Bug 1253438 - Expose Push observer notification topics. r?markh draft
authorKit Cambridge <kcambridge@mozilla.com>
Thu, 03 Mar 2016 14:37:10 -0800
changeset 336616 0ac4fd45ca34dafd931d26494510c8ba6fb0935a
parent 336615 00cdbf495c92ac1f7b1428f95690cff7d00d778a
child 336617 d376cd9154d07a00e3392493a9581691e0ad19f8
push id12148
push userkcambridge@mozilla.com
push dateThu, 03 Mar 2016 22:59:12 +0000
reviewersmarkh
bugs1253438
milestone47.0a1
Bug 1253438 - Expose Push observer notification topics. r?markh MozReview-Commit-ID: HublNSAD3NY
dom/interfaces/push/nsIPushService.idl
dom/push/PushComponents.js
dom/push/PushNotifier.cpp
dom/push/test/xpcshell/head.js
dom/push/test/xpcshell/test_drop_expired.js
dom/push/test/xpcshell/test_notification_ack.js
dom/push/test/xpcshell/test_notification_data.js
dom/push/test/xpcshell/test_notification_duplicate.js
dom/push/test/xpcshell/test_notification_error.js
dom/push/test/xpcshell/test_notification_http2.js
dom/push/test/xpcshell/test_notification_incomplete.js
dom/push/test/xpcshell/test_notification_version_string.js
dom/push/test/xpcshell/test_permissions.js
dom/push/test/xpcshell/test_quota_exceeded.js
dom/push/test/xpcshell/test_quota_observer.js
dom/push/test/xpcshell/test_quota_with_notification.js
dom/push/test/xpcshell/test_register_flush.js
dom/push/test/xpcshell/test_service_child.js
dom/push/test/xpcshell/test_service_parent.js
dom/push/test/xpcshell/test_updateRecordNoEncryptionKeys_http2.js
--- a/dom/interfaces/push/nsIPushService.idl
+++ b/dom/interfaces/push/nsIPushService.idl
@@ -68,16 +68,20 @@ interface nsIPushClearResultCallback : n
  * services. This functionality is exposed to content via the Push DOM API,
  * which uses service workers. This interface exists to support the DOM API,
  * and allows privileged code to receive messages without migrating to service
  * workers.
  */
 [scriptable, uuid(678ef584-bf25-47aa-ac84-03efc0865b68)]
 interface nsIPushService : nsISupports
 {
+  /** Observer topic names, exported for convenience. */
+  readonly attribute DOMString pushTopic;
+  readonly attribute DOMString subscriptionChangeTopic;
+
   /**
    * Creates a push subscription for the given |scope| URL and |principal|.
    * If a subscription already exists for this |(scope, principal)| pair,
    * the callback will receive the existing record as the second argument.
    *
    * The |endpoint| property of the subscription record is a URL string
    * that can be used to send push messages to subscribers.
    *
--- a/dom/push/PushComponents.js
+++ b/dom/push/PushComponents.js
@@ -11,16 +11,19 @@
 
 const { classes: Cc, interfaces: Ci, utils: Cu, results: Cr } = Components;
 
 Cu.import("resource://gre/modules/XPCOMUtils.jsm");
 Cu.import("resource://gre/modules/Services.jsm");
 
 var isParent = Services.appinfo.processType === Ci.nsIXULRuntime.PROCESS_TYPE_DEFAULT;
 
+const OBSERVER_TOPIC_PUSH = "push-message";
+const OBSERVER_TOPIC_SUBSCRIPTION_CHANGE = "push-subscription-change";
+
 /**
  * `PushServiceBase`, `PushServiceParent`, and `PushServiceContent` collectively
  * implement the `nsIPushService` interface. This interface provides calls
  * similar to the Push DOM API, but does not require service workers.
  *
  * Push service methods may be called from the parent or content process. The
  * parent process implementation loads `PushService.jsm` at app startup, and
  * calls its methods directly. The content implementation forwards calls to
@@ -38,16 +41,19 @@ PushServiceBase.prototype = {
   contractID: "@mozilla.org/push/Service;1",
   QueryInterface: XPCOMUtils.generateQI([
     Ci.nsIObserver,
     Ci.nsISupportsWeakReference,
     Ci.nsIPushService,
     Ci.nsIPushQuotaManager,
   ]),
 
+  pushTopic: OBSERVER_TOPIC_PUSH,
+  subscriptionChangeTopic: OBSERVER_TOPIC_SUBSCRIPTION_CHANGE,
+
   _handleReady() {},
 
   _addListeners() {
     for (let message of this._messages) {
       this._mm.addMessageListener(message, this);
     }
   },
 
--- a/dom/push/PushNotifier.cpp
+++ b/dom/push/PushNotifier.cpp
@@ -11,16 +11,19 @@
 #include "ServiceWorkerManager.h"
 
 #include "mozilla/Services.h"
 #include "mozilla/unused.h"
 
 #include "mozilla/dom/BodyUtil.h"
 #include "mozilla/dom/ContentParent.h"
 
+#define OBSERVER_TOPIC_PUSH "push-message"
+#define OBSERVER_TOPIC_SUBSCRIPTION_CHANGE "push-subscription-change"
+
 namespace mozilla {
 namespace dom {
 
 using workers::ServiceWorkerManager;
 
 PushNotifier::PushNotifier()
 {}
 
@@ -187,29 +190,30 @@ PushNotifier::NotifyPushObservers(const 
     mozilla::services::GetObserverService();
   if (!obsService) {
     return NS_ERROR_FAILURE;
   }
   nsCOMPtr<nsIPushMessage> message = nullptr;
   if (aData) {
     message = new PushMessage(aData.ref());
   }
-  return obsService->NotifyObservers(message, "push-message",
+  return obsService->NotifyObservers(message, OBSERVER_TOPIC_PUSH,
                                      NS_ConvertUTF8toUTF16(aScope).get());
 }
 
 nsresult
 PushNotifier::NotifySubscriptionChangeObservers(const nsACString& aScope)
 {
   nsCOMPtr<nsIObserverService> obsService =
     mozilla::services::GetObserverService();
   if (!obsService) {
     return NS_ERROR_FAILURE;
   }
-  return obsService->NotifyObservers(nullptr, "push-subscription-change",
+  return obsService->NotifyObservers(nullptr,
+                                     OBSERVER_TOPIC_SUBSCRIPTION_CHANGE,
                                      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.
--- a/dom/push/test/xpcshell/head.js
+++ b/dom/push/test/xpcshell/head.js
@@ -8,18 +8,21 @@ var {classes: Cc, interfaces: Ci, utils:
 Cu.import('resource://gre/modules/XPCOMUtils.jsm');
 Cu.import('resource://gre/modules/Services.jsm');
 Cu.import('resource://gre/modules/Task.jsm');
 Cu.import('resource://gre/modules/Timer.jsm');
 Cu.import('resource://gre/modules/Promise.jsm');
 Cu.import('resource://gre/modules/Preferences.jsm');
 Cu.import('resource://gre/modules/PlacesUtils.jsm');
 Cu.import('resource://gre/modules/ObjectUtils.jsm');
-XPCOMUtils.defineLazyModuleGetter(this, "PlacesTestUtils",
-                                  "resource://testing-common/PlacesTestUtils.jsm");
+
+XPCOMUtils.defineLazyModuleGetter(this, 'PlacesTestUtils',
+                                  'resource://testing-common/PlacesTestUtils.jsm');
+XPCOMUtils.defineLazyServiceGetter(this, 'PushServiceComponent',
+                                   '@mozilla.org/push/Service;1', 'nsIPushService');
 
 const serviceExports = Cu.import('resource://gre/modules/PushService.jsm', {});
 const servicePrefs = new Preferences('dom.push.');
 
 const DEFAULT_TIMEOUT = 5000;
 
 const WEBSOCKET_CLOSE_GOING_AWAY = 1001;
 
--- a/dom/push/test/xpcshell/test_drop_expired.js
+++ b/dom/push/test/xpcshell/test_drop_expired.js
@@ -94,17 +94,17 @@ add_task(function* setUp() {
     scope: 'https://example.ninja/active',
     perm: 'ALLOW_ACTION',
     quota: 16,
     lastPush: Date.now() - 10,
     lastVisit: Date.now() - 20,
   });
 
   let subChangePromise = promiseObserverNotification(
-    'push-subscription-change',
+    PushServiceComponent.subscriptionChangeTopic,
     (subject, data) => data == 'https://example.com/expired-quota-restored'
   );
 
   PushService.init({
     serverURI: 'wss://push.example.org/',
     networkInfo: new MockDesktopNetworkInfo(),
     db,
     makeWebSocket(uri) {
@@ -121,30 +121,30 @@ add_task(function* setUp() {
   });
 
   yield waitForPromise(subChangePromise, DEFAULT_TIMEOUT,
     'Timed out waiting for subscription change event on startup');
 });
 
 add_task(function* test_site_visited() {
   let subChangePromise = promiseObserverNotification(
-    'push-subscription-change',
+    PushServiceComponent.subscriptionChangeTopic,
     (subject, data) => data == 'https://example.xyz/expired-quota-exceeded'
   );
 
   yield visitURI(quotaURI, Date.now());
   PushService.observe(null, 'idle-daily', '');
 
   yield waitForPromise(subChangePromise, DEFAULT_TIMEOUT,
     'Timed out waiting for subscription change event after visit');
 });
 
 add_task(function* test_perm_restored() {
   let subChangePromise = promiseObserverNotification(
-    'push-subscription-change',
+    PushServiceComponent.subscriptionChangeTopic,
     (subject, data) => data == 'https://example.info/expired-perm-revoked'
   );
 
   Services.perms.add(permURI, 'desktop-notification',
     Ci.nsIPermissionManager.ALLOW_ACTION);
 
   yield waitForPromise(subChangePromise, DEFAULT_TIMEOUT,
     'Timed out waiting for subscription change event after permission');
--- a/dom/push/test/xpcshell/test_notification_ack.js
+++ b/dom/push/test/xpcshell/test_notification_ack.js
@@ -41,17 +41,17 @@ add_task(function* test_notification_ack
     quota: Infinity,
     systemRecord: true,
   }];
   for (let record of records) {
     yield db.put(record);
   }
 
   let notifyCount = 0;
-  let notifyPromise = promiseObserverNotification('push-message', () =>
+  let notifyPromise = promiseObserverNotification(PushServiceComponent.pushTopic, () =>
     ++notifyCount == 3);
 
   let acks = 0;
   let ackDone;
   let ackPromise = new Promise(resolve => ackDone = resolve);
   PushService.init({
     serverURI: "wss://push.example.org/",
     networkInfo: new MockDesktopNetworkInfo(),
--- a/dom/push/test/xpcshell/test_notification_data.js
+++ b/dom/push/test/xpcshell/test_notification_data.js
@@ -213,17 +213,17 @@ add_task(function* test_notification_ack
       receive: {
         scope: 'https://example.com/page/3',
         data: 'Some message'
       }
     },
   ];
 
   let sendAndReceive = testData => {
-    let messageReceived = promiseObserverNotification('push-message', (subject, data) => {
+    let messageReceived = promiseObserverNotification(PushServiceComponent.pushTopic, (subject, data) => {
       let notification = subject.QueryInterface(Ci.nsIPushMessage);
       equal(notification.text(), testData.receive.data,
             'Check data for notification ' + testData.version);
       equal(data, testData.receive.scope,
             'Check scope for notification ' + testData.version);
       return true;
     });
 
--- a/dom/push/test/xpcshell/test_notification_duplicate.js
+++ b/dom/push/test/xpcshell/test_notification_duplicate.js
@@ -35,17 +35,17 @@ add_task(function* test_notification_dup
     version: 2,
     quota: Infinity,
     systemRecord: true,
   }];
   for (let record of records) {
     yield db.put(record);
   }
 
-  let notifyPromise = promiseObserverNotification('push-message');
+  let notifyPromise = promiseObserverNotification(PushServiceComponent.pushTopic);
 
   let acks = 0;
   let ackDone;
   let ackPromise = new Promise(resolve => ackDone = after(2, resolve));
   PushService.init({
     serverURI: "wss://push.example.org/",
     networkInfo: new MockDesktopNetworkInfo(),
     db,
--- a/dom/push/test/xpcshell/test_notification_error.js
+++ b/dom/push/test/xpcshell/test_notification_error.js
@@ -45,17 +45,17 @@ add_task(function* test_notification_err
     quota: Infinity,
     systemRecord: true,
   }];
   for (let record of records) {
     yield db.put(record);
   }
 
   let scopes = [];
-  let notifyPromise = promiseObserverNotification('push-message', (subject, data) =>
+  let notifyPromise = promiseObserverNotification(PushServiceComponent.pushTopic, (subject, data) =>
     scopes.push(data) == 2);
 
   let ackDone;
   let ackPromise = new Promise(resolve => ackDone = after(records.length, resolve));
   PushService.init({
     serverURI: "wss://push.example.org/",
     networkInfo: new MockDesktopNetworkInfo(),
     db: makeStub(db, {
--- a/dom/push/test/xpcshell/test_notification_http2.js
+++ b/dom/push/test/xpcshell/test_notification_http2.js
@@ -123,31 +123,31 @@ add_task(function* test_pushNotification
     systemRecord: true,
   }];
 
   for (let record of records) {
     yield db.put(record);
   }
 
   let notifyPromise = Promise.all([
-    promiseObserverNotification('push-message', function(subject, data) {
+    promiseObserverNotification(PushServiceComponent.pushTopic, function(subject, data) {
       var message = subject.QueryInterface(Ci.nsIPushMessage);
       if (message && (data == "https://example.com/page/1")){
         equal(message.text(), "Some message", "decoded message is incorrect");
         return true;
       }
     }),
-    promiseObserverNotification('push-message', function(subject, data) {
+    promiseObserverNotification(PushServiceComponent.pushTopic, function(subject, data) {
       var message = subject.QueryInterface(Ci.nsIPushMessage);
       if (message && (data == "https://example.com/page/2")){
         equal(message.text(), "Some message", "decoded message is incorrect");
         return true;
       }
     }),
-    promiseObserverNotification('push-message', function(subject, data) {
+    promiseObserverNotification(PushServiceComponent.pushTopic, function(subject, data) {
       var message = subject.QueryInterface(Ci.nsIPushMessage);
       if (message && (data == "https://example.com/page/3")){
         equal(message.text(), "Some message", "decoded message is incorrect");
         return true;
       }
     })
   ]);
 
--- a/dom/push/test/xpcshell/test_notification_incomplete.js
+++ b/dom/push/test/xpcshell/test_notification_incomplete.js
@@ -50,18 +50,18 @@ add_task(function* test_notification_inc
   for (let record of records) {
     yield db.put(record);
   }
 
   function observeMessage(subject, topic, data) {
     ok(false, 'Should not deliver malformed updates');
   }
   do_register_cleanup(() =>
-    Services.obs.removeObserver(observeMessage, 'push-message'));
-  Services.obs.addObserver(observeMessage, 'push-message', false);
+    Services.obs.removeObserver(observeMessage, PushServiceComponent.pushTopic));
+  Services.obs.addObserver(observeMessage, PushServiceComponent.pushTopic, false);
 
   let notificationDone;
   let notificationPromise = new Promise(resolve => notificationDone = after(2, resolve));
   let prevHandler = PushServiceWebSocket._handleNotificationReply;
   PushServiceWebSocket._handleNotificationReply = function _handleNotificationReply() {
     notificationDone();
     return prevHandler.apply(this, arguments);
   };
--- a/dom/push/test/xpcshell/test_notification_version_string.js
+++ b/dom/push/test/xpcshell/test_notification_version_string.js
@@ -23,17 +23,17 @@ add_task(function* test_notification_ver
     pushEndpoint: 'https://example.org/updates/1',
     scope: 'https://example.com/page/1',
     originAttributes: '',
     version: 2,
     quota: Infinity,
     systemRecord: true,
   });
 
-  let notifyPromise = promiseObserverNotification('push-message');
+  let notifyPromise = promiseObserverNotification(PushServiceComponent.pushTopic);
 
   let ackDone;
   let ackPromise = new Promise(resolve => ackDone = resolve);
   PushService.init({
     serverURI: "wss://push.example.org/",
     networkInfo: new MockDesktopNetworkInfo(),
     db,
     makeWebSocket(uri) {
--- a/dom/push/test/xpcshell/test_permissions.js
+++ b/dom/push/test/xpcshell/test_permissions.js
@@ -47,17 +47,17 @@ function makePushPermission(url, capabil
       Services.io.newURI(url, null, null)
     ),
     type: 'desktop-notification',
   };
 }
 
 function promiseSubscriptionChanges(count) {
   let notifiedScopes = [];
-  let subChangePromise = promiseObserverNotification('push-subscription-change', (subject, data) => {
+  let subChangePromise = promiseObserverNotification(PushServiceComponent.subscriptionChangeTopic, (subject, data) => {
     notifiedScopes.push(data);
     return notifiedScopes.length == count;
   });
   return subChangePromise.then(_ => notifiedScopes.sort());
 }
 
 function allExpired(...keyIDs) {
   return Promise.all(keyIDs.map(
--- a/dom/push/test/xpcshell/test_quota_exceeded.js
+++ b/dom/push/test/xpcshell/test_quota_exceeded.js
@@ -74,17 +74,17 @@ add_task(function* test_expiration_origi
     }
   ]);
 
   // We expect to receive 6 notifications: 5 on the `auctions` channel,
   // and 1 on the `deals` channel. They're from the same origin, but
   // different scopes, so each can send 5 notifications before we remove
   // their subscription.
   let updates = 0;
-  let notifyPromise = promiseObserverNotification('push-message', (subject, data) => {
+  let notifyPromise = promiseObserverNotification(PushServiceComponent.pushTopic, (subject, data) => {
     updates++;
     return updates == 6;
   });
 
   let unregisterDone;
   let unregisterPromise = new Promise(resolve => unregisterDone = resolve);
 
   PushService.init({
--- a/dom/push/test/xpcshell/test_quota_observer.js
+++ b/dom/push/test/xpcshell/test_quota_observer.js
@@ -61,17 +61,17 @@ add_task(function* test_expiration_histo
     uri: 'https://example.com/infrequent',
     title: 'Infrequently-visited page',
     visitDate: (Date.now() - 14 * 24 * 60 * 60 * 1000) * 1000,
     transition: Ci.nsINavHistoryService.TRANSITION_LINK
   });
 
   let unregisterDone;
   let unregisterPromise = new Promise(resolve => unregisterDone = resolve);
-  let subChangePromise = promiseObserverNotification('push-subscription-change', (subject, data) =>
+  let subChangePromise = promiseObserverNotification(PushServiceComponent.subscriptionChangeTopic, (subject, data) =>
     data == 'https://example.com/stuff');
 
   PushService.init({
     serverURI: 'wss://push.example.org/',
     networkInfo: new MockDesktopNetworkInfo(),
     db,
     makeWebSocket(uri) {
       return new MockWebSocket(uri, {
@@ -102,17 +102,17 @@ add_task(function* test_expiration_histo
     'Timed out waiting for subscription change event on startup');
   yield waitForPromise(unregisterPromise, DEFAULT_TIMEOUT,
     'Timed out waiting for unregister request');
 
   let expiredRecord = yield db.getByKeyID('379c0668-8323-44d2-a315-4ee83f1a9ee9');
   strictEqual(expiredRecord.quota, 0, 'Expired record not updated');
 
   let notifiedScopes = [];
-  subChangePromise = promiseObserverNotification('push-subscription-change', (subject, data) => {
+  subChangePromise = promiseObserverNotification(PushServiceComponent.subscriptionChangeTopic, (subject, data) => {
     notifiedScopes.push(data);
     return notifiedScopes.length == 2;
   });
 
   // Add an expired registration that we'll revive later.
   yield putRecord('ALLOW_ACTION', {
     channelID: 'eb33fc90-c883-4267-b5cb-613969e8e349',
     pushEndpoint: 'https://example.org/push/2',
--- a/dom/push/test/xpcshell/test_quota_with_notification.js
+++ b/dom/push/test/xpcshell/test_quota_with_notification.js
@@ -48,17 +48,17 @@ add_task(function* test_expiration_origi
     title: 'Sign in to see your auctions',
     visitDate: (Date.now() - 1 * 24 * 60 * 60 * 1000) * 1000,
     transition: Ci.nsINavHistoryService.TRANSITION_LINK
   });
 
   let numMessages = 10;
 
   let updates = 0;
-  let notifyPromise = promiseObserverNotification('push-message', (subject, data) => {
+  let notifyPromise = promiseObserverNotification(PushServiceComponent.pushTopic, (subject, data) => {
     updates++;
     return updates == numMessages;
   });
 
   let updateQuotaPromise = new Promise((resolve, reject) => {
     let quotaUpdateCount = 0;
     PushService._updateQuotaTestCallback = function() {
       quotaUpdateCount++;
--- a/dom/push/test/xpcshell/test_register_flush.js
+++ b/dom/push/test/xpcshell/test_register_flush.js
@@ -27,17 +27,17 @@ add_task(function* test_register_flush()
     scope: 'https://example.com/page/1',
     originAttributes: '',
     version: 2,
     quota: Infinity,
     systemRecord: true,
   };
   yield db.put(record);
 
-  let notifyPromise = promiseObserverNotification('push-message');
+  let notifyPromise = promiseObserverNotification(PushServiceComponent.pushTopic);
 
   let ackDone;
   let ackPromise = new Promise(resolve => ackDone = after(2, resolve));
   PushService.init({
     serverURI: "wss://push.example.org/",
     networkInfo: new MockDesktopNetworkInfo(),
     db,
     makeWebSocket(uri) {
--- a/dom/push/test/xpcshell/test_service_child.js
+++ b/dom/push/test/xpcshell/test_service_child.js
@@ -1,37 +1,35 @@
 /* Any copyright is dedicated to the Public Domain.
    http://creativecommons.org/publicdomain/zero/1.0/ */
 
 'use strict';
 
 const {PushDB, PushService, PushServiceWebSocket} = serviceExports;
 
-var db, service;
+var db;
 
 function run_test() {
-  service = Cc['@mozilla.org/push/Service;1']
-              .getService(Ci.nsIPushService);
   if (isParent) {
     do_get_profile();
   }
   run_next_test();
 }
 
 if (isParent) {
   add_test(function setUp() {
     db = PushServiceWebSocket.newPushDB();
     do_register_cleanup(() => {return db.drop().then(_ => db.close());});
     setUpServiceInParent(PushService, db).then(run_next_test, run_next_test);
   });
 }
 
 add_test(function test_subscribe_success() {
   do_test_pending();
-  service.subscribe(
+  PushServiceComponent.subscribe(
     'https://example.com/sub/ok',
     Services.scriptSecurityManager.getSystemPrincipal(),
     (result, subscription) => {
       ok(Components.isSuccessCode(result), 'Error creating subscription');
       ok(subscription.endpoint.startsWith('https://example.org/push'), 'Wrong endpoint prefix');
       equal(subscription.pushCount, 0, 'Wrong push count');
       equal(subscription.lastPush, 0, 'Wrong last push time');
       equal(subscription.quota, -1, 'Wrong quota for system subscription');
@@ -39,32 +37,32 @@ add_test(function test_subscribe_success
       do_test_finished();
       run_next_test();
     }
   );
 });
 
 add_test(function test_subscribe_error() {
   do_test_pending();
-  service.subscribe(
+  PushServiceComponent.subscribe(
     'https://example.com/sub/fail',
     Services.scriptSecurityManager.getSystemPrincipal(),
     (result, subscription) => {
       ok(!Components.isSuccessCode(result), 'Expected error creating subscription');
       strictEqual(subscription, null, 'Unexpected subscription');
 
       do_test_finished();
       run_next_test();
     }
   );
 });
 
 add_test(function test_getSubscription_exists() {
   do_test_pending();
-  service.getSubscription(
+  PushServiceComponent.getSubscription(
     'https://example.com/get/ok',
     Services.scriptSecurityManager.getSystemPrincipal(),
     (result, subscription) => {
       ok(Components.isSuccessCode(result), 'Error getting subscription');
 
       equal(subscription.endpoint, 'https://example.org/push/get', 'Wrong endpoint');
       equal(subscription.pushCount, 10, 'Wrong push count');
       equal(subscription.lastPush, 1438360548322, 'Wrong last push');
@@ -73,77 +71,77 @@ add_test(function test_getSubscription_e
       do_test_finished();
       run_next_test();
     }
   );
 });
 
 add_test(function test_getSubscription_missing() {
   do_test_pending();
-  service.getSubscription(
+  PushServiceComponent.getSubscription(
     'https://example.com/get/missing',
     Services.scriptSecurityManager.getSystemPrincipal(),
     (result, subscription) => {
       ok(Components.isSuccessCode(result), 'Error getting nonexistent subscription');
       strictEqual(subscription, null, 'Nonexistent subscriptions should return null');
 
       do_test_finished();
       run_next_test();
     }
   );
 });
 
 add_test(function test_getSubscription_error() {
   do_test_pending();
-  service.getSubscription(
+  PushServiceComponent.getSubscription(
     'https://example.com/get/fail',
     Services.scriptSecurityManager.getSystemPrincipal(),
     (result, subscription) => {
       ok(!Components.isSuccessCode(result), 'Expected error getting subscription');
       strictEqual(subscription, null, 'Unexpected subscription');
 
       do_test_finished();
       run_next_test();
     }
   );
 });
 
 add_test(function test_unsubscribe_success() {
   do_test_pending();
-  service.unsubscribe(
+  PushServiceComponent.unsubscribe(
     'https://example.com/unsub/ok',
     Services.scriptSecurityManager.getSystemPrincipal(),
     (result, success) => {
       ok(Components.isSuccessCode(result), 'Error unsubscribing');
       strictEqual(success, true, 'Expected successful unsubscribe');
 
       do_test_finished();
       run_next_test();
     }
   );
 });
 
 add_test(function test_unsubscribe_nonexistent() {
   do_test_pending();
-  service.unsubscribe(
+  PushServiceComponent.unsubscribe(
     'https://example.com/unsub/ok',
     Services.scriptSecurityManager.getSystemPrincipal(),
     (result, success) => {
       ok(Components.isSuccessCode(result), 'Error removing nonexistent subscription');
       strictEqual(success, false, 'Nonexistent subscriptions should return false');
 
       do_test_finished();
       run_next_test();
     }
   );
 });
 
 add_test(function test_unsubscribe_error() {
   do_test_pending();
-  service.unsubscribe(
+  PushServiceComponent.unsubscribe(
     'https://example.com/unsub/fail',
     Services.scriptSecurityManager.getSystemPrincipal(),
     (result, success) => {
       ok(!Components.isSuccessCode(result), 'Expected error unsubscribing');
       strictEqual(success, false, 'Unexpected successful unsubscribe');
 
       do_test_finished();
       run_next_test();
@@ -154,63 +152,63 @@ add_test(function test_unsubscribe_error
 add_test(function test_subscribe_app_principal() {
   let principal = Services.scriptSecurityManager.getAppCodebasePrincipal(
     Services.io.newURI('https://example.net/app/1', null, null),
     1, /* appId */
     true /* browserOnly */
   );
 
   do_test_pending();
-  service.subscribe('https://example.net/scope/1', principal, (result, subscription) => {
+  PushServiceComponent.subscribe('https://example.net/scope/1', principal, (result, subscription) => {
     ok(Components.isSuccessCode(result), 'Error creating subscription');
     ok(subscription.endpoint.startsWith('https://example.org/push'),
       'Wrong push endpoint in app subscription');
     equal(subscription.quota, 16, 'Wrong quota for app subscription');
 
     do_test_finished();
     run_next_test();
   });
 });
 
 add_test(function test_subscribe_origin_principal() {
   let scope = 'https://example.net/origin-principal';
   let principal =
     Services.scriptSecurityManager.createCodebasePrincipalFromOrigin(scope);
 
   do_test_pending();
-  service.subscribe(scope, principal, (result, subscription) => {
+  PushServiceComponent.subscribe(scope, principal, (result, subscription) => {
     ok(Components.isSuccessCode(result),
       'Expected error creating subscription with origin principal');
     equal(subscription.quota, 16, 'Wrong quota for origin subscription');
 
     do_test_finished();
     run_next_test();
   });
 });
 
 add_test(function test_subscribe_null_principal() {
   do_test_pending();
-  service.subscribe(
+  PushServiceComponent.subscribe(
     'chrome://push/null-principal',
     Services.scriptSecurityManager.createNullPrincipal({}),
     (result, subscription) => {
       ok(!Components.isSuccessCode(result),
         'Expected error creating subscription with expanded principal');
       strictEqual(subscription, null,
         'Unexpected subscription with expanded principal');
 
       do_test_finished();
       run_next_test();
     }
   );
 });
 
 add_test(function test_subscribe_missing_principal() {
   do_test_pending();
-  service.subscribe('chrome://push/missing-principal', null,
+  PushServiceComponent.subscribe('chrome://push/missing-principal', null,
     (result, subscription) => {
       ok(!Components.isSuccessCode(result),
         'Expected error creating subscription without principal');
       strictEqual(subscription, null,
         'Unexpected subscription without principal');
 
       do_test_finished();
       run_next_test();
--- a/dom/push/test/xpcshell/test_service_parent.js
+++ b/dom/push/test/xpcshell/test_service_parent.js
@@ -10,15 +10,19 @@ function run_test() {
   run_next_test();
 }
 
 add_task(function* test_service_parent() {
   let db = PushServiceWebSocket.newPushDB();
   do_register_cleanup(() => {return db.drop().then(_ => db.close());});
   yield setUpServiceInParent(PushService, db);
 
-  // Start the service in the main process.
-  Cc['@mozilla.org/push/Service;1'].getService(Ci.nsIPushService);
+  // Accessing the lazy service getter will start the service in the main
+  // process.
+  equal(PushServiceComponent.pushTopic, "push-message",
+    "Wrong push message observer topic");
+  equal(PushServiceComponent.subscriptionChangeTopic,
+    "push-subscription-change", "Wrong subscription change observer topic");
 
   yield run_test_in_child('./test_service_child.js');
 
   yield tearDownServiceInParent(db);
 });
--- a/dom/push/test/xpcshell/test_updateRecordNoEncryptionKeys_http2.js
+++ b/dom/push/test/xpcshell/test_updateRecordNoEncryptionKeys_http2.js
@@ -54,17 +54,17 @@ add_task(function* test1() {
     scope: 'https://example.com/page',
     originAttributes: '',
     quota: Infinity,
     systemRecord: true,
   };
 
   yield db.put(record);
 
-  let notifyPromise = promiseObserverNotification('push-subscription-change',
+  let notifyPromise = promiseObserverNotification(PushServiceComponent.subscriptionChangeTopic,
                                                   _ => true);
 
   PushService.init({
     serverURI: serverURL + "/subscribe",
     db
   });
 
   yield waitForPromise(notifyPromise, DEFAULT_TIMEOUT,