Bug 1253438 - Expose Push observer notification topics. r=markh
authorKit Cambridge <kcambridge@mozilla.com>
Thu, 03 Mar 2016 14:37:10 -0800
changeset 322956 65955cf5a546876c516aa38ac2486015a41b291f
parent 322955 0d112a6d618c9730bddd665f65d75eb60873e9b6
child 322957 6b7cac27bb8397f223af0ea20950f657328fbb4b
push id5913
push userjlund@mozilla.com
push dateMon, 25 Apr 2016 16:57:49 +0000
treeherdermozilla-beta@dcaf0a6fa115 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersmarkh
bugs1253438
milestone47.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
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/PushNotifier.h
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,22 @@
 
 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;
 
+// Observer notification topics for system subscriptions. These are duplicated
+// and used in `PushNotifier.cpp`. They're exposed on `nsIPushService` instead
+// of `nsIPushNotifier` so that JS callers only need to import this service.
+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 +44,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
@@ -187,29 +187,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/PushNotifier.h
+++ b/dom/push/PushNotifier.h
@@ -12,16 +12,20 @@
 #include "nsString.h"
 #include "nsTArray.h"
 
 #include "mozilla/Maybe.h"
 
 #define PUSHNOTIFIER_CONTRACTID \
   "@mozilla.org/push/Notifier;1"
 
+// 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 {
 
 /**
  * `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.
  *
--- 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,