author | Kit Cambridge <kcambridge@mozilla.com> |
Tue, 22 Mar 2016 17:34:41 -0700 | |
changeset 290409 | 2c90f268c62bcd3c55776ec9ffa8eead9ddf27d7 |
parent 290408 | 34b0a503329ad8383f7ef499410fa7457f2be968 |
child 290410 | 7e9d1b43cf767da76f1223427bbd4da980a3a37d |
push id | 74245 |
push user | kcambridge@mozilla.com |
push date | Fri, 25 Mar 2016 21:57:53 +0000 |
treeherder | mozilla-inbound@7e9d1b43cf76 [default view] [failures only] |
perfherder | [talos] [build metrics] [platform microbench] (compared to previous push) |
reviewers | wchen |
bugs | 1258883 |
milestone | 48.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
|
--- a/dom/push/PushComponents.js +++ b/dom/push/PushComponents.js @@ -11,16 +11,24 @@ 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; +// The default Push service implementation. +XPCOMUtils.defineLazyGetter(this, "PushService", function() { + const {PushService} = Cu.import("resource://gre/modules/PushService.jsm", + {}); + PushService.init(); + return PushService; +}); + // 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 @@ -94,24 +102,16 @@ function PushServiceParent() { PushServiceBase.call(this); } PushServiceParent.prototype = Object.create(PushServiceBase.prototype); XPCOMUtils.defineLazyServiceGetter(PushServiceParent.prototype, "_mm", "@mozilla.org/parentprocessmessagemanager;1", "nsIMessageBroadcaster"); -XPCOMUtils.defineLazyGetter(PushServiceParent.prototype, "_service", - function() { - const {PushService} = Cu.import("resource://gre/modules/PushService.jsm", - {}); - PushService.init(); - return PushService; -}); - Object.assign(PushServiceParent.prototype, { _xpcom_factory: XPCOMUtils.generateSingletonFactory(PushServiceParent), _messages: [ "Push:Register", "Push:Registration", "Push:Unregister", "Push:Clear", @@ -159,21 +159,21 @@ Object.assign(PushServiceParent.prototyp }, error => { callback.onClear(Cr.NS_ERROR_FAILURE); }).catch(Cu.reportError); }, // nsIPushQuotaManager methods notificationForOriginShown(origin) { - this._service.notificationForOriginShown(origin); + this.service.notificationForOriginShown(origin); }, notificationForOriginClosed(origin) { - this._service.notificationForOriginClosed(origin); + this.service.notificationForOriginClosed(origin); }, receiveMessage(message) { if (!this._isValidMessage(message)) { return; } let {name, principal, target, data} = message; if (name === "Push:NotificationForOriginShown") { @@ -196,17 +196,17 @@ Object.assign(PushServiceParent.prototyp }, error => { sender.sendAsyncMessage(this._getResponseName(name, "KO"), { requestID: data.requestID, }); }).catch(Cu.reportError); }, _handleReady() { - this._service.init(); + this.service.init(); }, _toPageRecord(principal, data) { if (!data.scope) { throw new Error("Invalid page record: missing scope"); } if (!principal) { throw new Error("Invalid page record: missing principal"); @@ -223,53 +223,63 @@ Object.assign(PushServiceParent.prototyp data.originAttributes = ChromeUtils.originAttributesToSuffix(principal.originAttributes); return data; }, _handleRequest(name, principal, data) { if (name == "Push:Clear") { - return this._service.clear(data); + return this.service.clear(data); } let pageRecord; try { pageRecord = this._toPageRecord(principal, data); } catch (e) { return Promise.reject(e); } if (name === "Push:Register") { - return this._service.register(pageRecord); + return this.service.register(pageRecord); } if (name === "Push:Registration") { - return this._service.registration(pageRecord); + return this.service.registration(pageRecord); } if (name === "Push:Unregister") { - return this._service.unregister(pageRecord); + return this.service.unregister(pageRecord); } return Promise.reject(new Error("Invalid request: unknown name")); }, _getResponseName(requestName, suffix) { let name = requestName.slice("Push:".length); return "PushService:" + name + ":" + suffix; }, // Methods used for mocking in tests. replaceServiceBackend(options) { - this._service.changeTestServer(options.serverURI, options); + this.service.changeTestServer(options.serverURI, options); }, restoreServiceBackend() { var defaultServerURL = Services.prefs.getCharPref("dom.push.serverURL"); - this._service.changeTestServer(defaultServerURL); + this.service.changeTestServer(defaultServerURL); + }, +}); + +// Used to replace the implementation with a mock. +Object.defineProperty(PushServiceParent.prototype, "service", { + get() { + return this._service || PushService; + }, + set(impl) { + this._service = impl; }, }); /** * The content process implementation of `nsIPushService`. This version * uses the child message manager to forward calls to the parent process. * The parent Push service instance handles the request, and responds with a * message containing the result.
--- a/dom/push/test/mockpushserviceparent.js +++ b/dom/push/test/mockpushserviceparent.js @@ -43,17 +43,17 @@ MockWebSocketParent.prototype = { asyncOpen(uri, origin, windowId, listener, context) { this._listener = listener; this._context = context; waterfall(() => this._listener.onStart(this._context)); }, sendMsg(msg) { - sendAsyncMessage("client-msg", msg); + sendAsyncMessage("socket-client-msg", msg); }, close() { waterfall(() => this._listener.onStop(this._context, Cr.NS_OK)); }, serverSendMsg(msg) { waterfall(() => this._listener.onMessageAvailable(this._context, msg), @@ -78,17 +78,17 @@ MockNetworkInfo.prototype = { }; var pushService = Cc["@mozilla.org/push/Service;1"]. getService(Ci.nsIPushService). wrappedJSObject; var mockWebSocket; -addMessageListener("setup", function () { +addMessageListener("socket-setup", function () { mockWebSocket = new Promise((resolve, reject) => { var mockSocket = null; pushService.replaceServiceBackend({ serverURI: "wss://push.example.org/", networkInfo: new MockNetworkInfo(), makeWebSocket(uri) { if (!mockSocket) { mockSocket = new MockWebSocketParent(uri); @@ -96,20 +96,77 @@ addMessageListener("setup", function () } return mockSocket; } }); }); }); -addMessageListener("teardown", function () { +addMessageListener("socket-teardown", function () { mockWebSocket.then(socket => { socket.close(); pushService.restoreServiceBackend(); }); }); -addMessageListener("server-msg", function (msg) { +addMessageListener("socket-server-msg", function (msg) { mockWebSocket.then(socket => { socket.serverSendMsg(msg); }); }); + +var MockService = { + requestID: 1, + resolvers: new Map(), + + sendRequest(name, params) { + return new Promise((resolve, reject) => { + let id = this.requestID++; + this.resolvers.set(id, { resolve, reject }); + sendAsyncMessage("service-request", { + name: name, + id: id, + params: params, + }); + }); + }, + + handleResponse(response) { + if (!this.resolvers.has(response.id)) { + Cu.reportError(`Unexpected response for request ${response.id}`); + return; + } + let resolver = this.resolvers.get(response.id); + this.resolvers.delete(response.id); + if (response.error) { + resolver.reject(response.error); + } else { + resolver.resolve(response.result); + } + }, + + init() {}, + + register(pageRecord) { + return this.sendRequest("register", pageRecord); + }, + + registration(pageRecord) { + return this.sendRequest("registration", pageRecord); + }, + + unregister(pageRecord) { + return this.sendRequest("unregister", pageRecord); + }, +}; + +addMessageListener("service-replace", function () { + pushService.service = MockService; +}); + +addMessageListener("service-restore", function () { + pushService.service = null; +}); + +addMessageListener("service-response", function (response) { + MockService.handleResponse(response); +});
--- a/dom/push/test/test_data.html +++ b/dom/push/test/test_data.html @@ -37,17 +37,17 @@ http://creativecommons.org/licenses/publ channelID, status: 200, pushEndpoint: "https://example.com/endpoint/1" })); }; var registration; add_task(function* start() { - yield setupPrefsAndMock(mockSocket); + yield setupPrefsAndMockSocket(mockSocket); yield setPushPermission(true); var url = "worker.js" + "?" + (Math.random()); registration = yield navigator.serviceWorker.register(url, {scope: "."}); }); var controlledFrame; add_task(function* createControlledIFrame() {
--- a/dom/push/test/test_multiple_register.html +++ b/dom/push/test/test_multiple_register.html @@ -114,14 +114,14 @@ http://creativecommons.org/licenses/publ .then(getEndpoint) .then(unregisterPushNotification) .then(unregister) .catch(function(e) { ok(false, "Some test failed with error " + e); }).then(SimpleTest.finish); } - setupPrefsAndMock(new MockWebSocket()).then(_ => runTest()); + setupPrefsAndMockSocket(new MockWebSocket()).then(_ => runTest()); SpecialPowers.addPermission("desktop-notification", true, document); SimpleTest.waitForExplicitFinish(); </script> </body> </html>
--- a/dom/push/test/test_multiple_register_different_scope.html +++ b/dom/push/test/test_multiple_register_different_scope.html @@ -109,14 +109,14 @@ http://creativecommons.org/licenses/publ .then(_ => unregister(swrB)) ) ) .catch(err => { ok(false, "Some test failed with error " + err); }).then(SimpleTest.finish); } - setupPrefsAndMock(new MockWebSocket()).then(_ => runTest()); + setupPrefsAndMockSocket(new MockWebSocket()).then(_ => runTest()); SpecialPowers.addPermission("desktop-notification", true, document); SimpleTest.waitForExplicitFinish(); </script> </body> </html>
--- a/dom/push/test/test_multiple_register_during_service_activation.html +++ b/dom/push/test/test_multiple_register_during_service_activation.html @@ -52,18 +52,18 @@ http://creativecommons.org/licenses/publ }, err => { ok(false, "could not register for push notification"); throw err; }); } function setupMultipleSubscriptions(swr) { // We need to do this to restart service so that a queue will be formed. - teardownMockPushService(); - setupMockPushService(new MockWebSocket()); + teardownMockPushSocket(); + setupMockPushSocket(new MockWebSocket()); return Promise.all([ subscribe(swr), subscribe(swr) ]).then(a => { ok(a[0].endpoint == a[1].endpoint, "setupMultipleSubscriptions - Got the same endpoint back."); return a[0]; }, err => { @@ -94,14 +94,14 @@ http://creativecommons.org/licenses/publ .then(sub => unsubscribe(sub)) .then(_ => unregister(swr)) ) .catch(err => { ok(false, "Some test failed with error " + err); }).then(SimpleTest.finish); } - setupPrefsAndMock(new MockWebSocket()).then(_ => runTest()); + setupPrefsAndMockSocket(new MockWebSocket()).then(_ => runTest()); SpecialPowers.addPermission("desktop-notification", true, document); SimpleTest.waitForExplicitFinish(); </script> </body> </html>
--- a/dom/push/test/test_permissions.html +++ b/dom/push/test/test_permissions.html @@ -26,17 +26,17 @@ http://creativecommons.org/licenses/publ <script class="testbody" type="text/javascript"> function debug(str) { // console.log(str + "\n"); } var registration; add_task(function* start() { - yield setupPrefsAndMock(new MockWebSocket()); + yield setupPrefsAndMockSocket(new MockWebSocket()); yield setPushPermission(false); var url = "worker.js" + "?" + Math.random(); registration = yield navigator.serviceWorker.register(url, {scope: "."}); }); add_task(function* denySubscribe() { try {
--- a/dom/push/test/test_register.html +++ b/dom/push/test/test_register.html @@ -41,17 +41,17 @@ http://creativecommons.org/licenses/publ channelID, status: 200, pushEndpoint: "https://example.com/endpoint/1" })); }; var registration; add_task(function* start() { - yield setupPrefsAndMock(mockSocket); + yield setupPrefsAndMockSocket(mockSocket); yield setPushPermission(true); var url = "worker.js" + "?" + (Math.random()); registration = yield navigator.serviceWorker.register(url, {scope: "."}); }); var controlledFrame; add_task(function* createControlledIFrame() {
--- a/dom/push/test/test_serviceworker_lifetime.html +++ b/dom/push/test/test_serviceworker_lifetime.html @@ -330,14 +330,14 @@ .then(subTest(test3)) .then(unregisterPushNotification) .then(unregister) .catch(function(e) { ok(false, "Some test failed with error " + e) }).then(SimpleTest.finish); } - setupPrefsAndMock(mockSocket).then(_ => runTest()); + setupPrefsAndMockSocket(mockSocket).then(_ => runTest()); SpecialPowers.addPermission('desktop-notification', true, document); SimpleTest.waitForExplicitFinish(); </script> </body> </html>
--- a/dom/push/test/test_subscription_change.html +++ b/dom/push/test/test_subscription_change.html @@ -22,17 +22,17 @@ http://creativecommons.org/licenses/publ </div> <pre id="test"> </pre> <script class="testbody" type="text/javascript"> var registration; add_task(function* start() { - yield setupPrefsAndMock(new MockWebSocket()); + yield setupPrefsAndMockSocket(new MockWebSocket()); yield setPushPermission(true); var url = "worker.js" + "?" + (Math.random()); registration = yield navigator.serviceWorker.register(url, {scope: "."}); }); var controlledFrame; add_task(function* createControlledIFrame() {
--- a/dom/push/test/test_try_registering_offline_disabled.html +++ b/dom/push/test/test_try_registering_offline_disabled.html @@ -291,14 +291,14 @@ http://creativecommons.org/licenses/publ .then(_ => runTest2()) .then(_ => runTest3()) .then(_ => runTest4()) .then(_ => runTest5()) .then(_ => runTest6()) .then(SimpleTest.finish); } - setupPrefsAndMock(new MockWebSocket()).then(_ => runTest()); + setupPrefsAndMockSocket(new MockWebSocket()).then(_ => runTest()); SpecialPowers.addPermission("desktop-notification", true, document); SimpleTest.waitForExplicitFinish(); </script> </body> </html>
--- a/dom/push/test/test_unregister.html +++ b/dom/push/test/test_unregister.html @@ -75,15 +75,15 @@ http://creativecommons.org/licenses/publ .then(unregisterPushNotification) .then(unregisterAgain) .then(unregisterSW) .catch(function(e) { ok(false, "Some test failed with error " + e); }).then(SimpleTest.finish); } - setupPrefsAndMock(new MockWebSocket()).then(_ => runTest()); + setupPrefsAndMockSocket(new MockWebSocket()).then(_ => runTest()); SpecialPowers.addPermission("desktop-notification", true, document); SimpleTest.waitForExplicitFinish(); </script> </body> </html>
--- a/dom/push/test/test_utils.js +++ b/dom/push/test/test_utils.js @@ -1,31 +1,70 @@ (function (g) { "use strict"; let url = SimpleTest.getTestFileURL("mockpushserviceparent.js"); let chromeScript = SpecialPowers.loadChromeScript(url); + /** + * Replaces `PushService.jsm` with a mock implementation that handles requests + * from the DOM API. This allows tests to simulate local errors and error + * reporting, bypassing the `PushService.jsm` machinery. + */ + function replacePushService(mockService) { + chromeScript.sendSyncMessage("service-replace"); + chromeScript.addMessageListener("service-request", function(msg) { + let promise; + try { + let handler = mockService[msg.name]; + promise = Promise.resolve(handler(msg.params)); + } catch (error) { + promise = Promise.reject(error); + } + promise.then(result => { + chromeScript.sendAsyncMessage("service-response", { + id: msg.id, + result: result, + }); + }, error => { + chromeScript.sendAsyncMessage("service-response", { + id: msg.id, + error: error, + }); + }); + }); + } + + function restorePushService() { + chromeScript.sendSyncMessage("service-restore"); + } + let userAgentID = "8e1c93a9-139b-419c-b200-e715bb1e8ce8"; let currentMockSocket = null; - function setupMockPushService(mockWebSocket) { + /** + * Sets up a mock connection for the WebSocket backend. This only replaces + * the transport layer; `PushService.jsm` still handles DOM API requests, + * observes permission changes, writes to IndexedDB, and notifies service + * workers of incoming push messages. + */ + function setupMockPushSocket(mockWebSocket) { currentMockSocket = mockWebSocket; currentMockSocket._isActive = true; - chromeScript.sendSyncMessage("setup"); - chromeScript.addMessageListener("client-msg", function(msg) { + chromeScript.sendSyncMessage("socket-setup"); + chromeScript.addMessageListener("socket-client-msg", function(msg) { mockWebSocket.handleMessage(msg); }); } - function teardownMockPushService() { + function teardownMockPushSocket() { if (currentMockSocket) { currentMockSocket._isActive = false; - chromeScript.sendSyncMessage("teardown"); + chromeScript.sendSyncMessage("socket-teardown"); } } /** * Minimal implementation of web sockets for use in testing. Forwards * messages to a mock web socket in the parent process that is used * by the push service. */ @@ -85,58 +124,70 @@ break; default: throw new Error("Unexpected message: " + messageType); } }, serverSendMsg(msg) { if (this._isActive) { - chromeScript.sendAsyncMessage("server-msg", msg); + chromeScript.sendAsyncMessage("socket-server-msg", msg); } }, }; g.MockWebSocket = MockWebSocket; - g.setupMockPushService = setupMockPushService; - g.teardownMockPushService = teardownMockPushService; + g.setupMockPushSocket = setupMockPushSocket; + g.teardownMockPushSocket = teardownMockPushSocket; + g.replacePushService = replacePushService; + g.restorePushService = restorePushService; }(this)); // Remove permissions and prefs when the test finishes. SimpleTest.registerCleanupFunction(() => { new Promise(resolve => { SpecialPowers.flushPermissions(_ => { SpecialPowers.flushPrefEnv(resolve); }); }).then(_ => { - teardownMockPushService(); + teardownMockPushSocket(); + restorePushService(); }); }); function setPushPermission(allow) { return new Promise(resolve => { SpecialPowers.pushPermissions([ { type: "desktop-notification", allow, context: document }, ], resolve); }); } -function setupPrefsAndMock(mockSocket) { +function setupPrefs() { return new Promise(resolve => { - setupMockPushService(mockSocket); SpecialPowers.pushPrefEnv({"set": [ ["dom.push.enabled", true], ["dom.push.connection.enabled", true], ["dom.serviceWorkers.exemptFromPerDomainMax", true], ["dom.serviceWorkers.enabled", true], ["dom.serviceWorkers.testing.enabled", true] ]}, resolve); }); } +function setupPrefsAndReplaceService(mockService) { + replacePushService(mockService); + return setupPrefs(); +} + +function setupPrefsAndMockSocket(mockSocket) { + setupMockPushSocket(mockSocket); + return setupPrefs(); +} + function injectControlledFrame(target = document.body) { return new Promise(function(res, rej) { var iframe = document.createElement("iframe"); iframe.src = "/tests/dom/push/test/frame.html"; var controlledFrame = { remove() { target.removeChild(iframe);