michael@0: /* This Source Code Form is subject to the terms of the Mozilla Public michael@0: * License, v. 2.0. If a copy of the MPL was not distributed with this michael@0: * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ michael@0: michael@0: const events = require("sdk/system/events"); michael@0: const self = require("sdk/self"); michael@0: const { Cc, Ci, Cu } = require("chrome"); michael@0: const { setTimeout } = require("sdk/timers"); michael@0: const { Loader, LoaderWithHookedConsole2 } = require("sdk/test/loader"); michael@0: const nsIObserverService = Cc["@mozilla.org/observer-service;1"]. michael@0: getService(Ci.nsIObserverService); michael@0: michael@0: let isConsoleEvent = (topic) => michael@0: !!~["console-api-log-event", "console-storage-cache-event"].indexOf(topic) michael@0: michael@0: exports["test basic"] = function(assert) { michael@0: let type = Date.now().toString(32); michael@0: michael@0: let timesCalled = 0; michael@0: function handler({subject, data}) { timesCalled++; }; michael@0: michael@0: events.on(type, handler); michael@0: events.emit(type, { data: "yo yo" }); michael@0: michael@0: assert.equal(timesCalled, 1, "event handler was called"); michael@0: michael@0: events.off(type, handler); michael@0: events.emit(type, { data: "no way" }); michael@0: michael@0: assert.equal(timesCalled, 1, "removed handler is no longer called"); michael@0: michael@0: events.once(type, handler); michael@0: events.emit(type, { data: "and we meet again" }); michael@0: events.emit(type, { data: "it's always hard to say bye" }); michael@0: michael@0: assert.equal(timesCalled, 2, "handlers added via once are triggered once"); michael@0: } michael@0: michael@0: exports["test simple argument passing"] = function (assert) { michael@0: let type = Date.now().toString(32); michael@0: michael@0: let lastArg; michael@0: function handler({data}) { lastArg = data; } michael@0: events.on(type, handler); michael@0: michael@0: [true, false, 100, 0, 'a string', ''].forEach(arg => { michael@0: events.emit(type, arg); michael@0: assert.strictEqual(lastArg, arg + '', michael@0: 'event emitted for ' + arg + ' has correct data value'); michael@0: michael@0: events.emit(type, { data: arg }); michael@0: assert.strictEqual(lastArg, arg + '', michael@0: 'event emitted for ' + arg + ' has correct data value when a property on an object'); michael@0: }); michael@0: michael@0: [null, undefined, {}].forEach(arg => { michael@0: events.emit(type, arg); michael@0: assert.strictEqual(lastArg, null, michael@0: 'emitting ' + arg + ' gets null data'); michael@0: }); michael@0: michael@0: events.off(type, handler); michael@0: }; michael@0: michael@0: exports["test error reporting"] = function(assert) { michael@0: let { loader, messages } = LoaderWithHookedConsole2(module); michael@0: michael@0: let events = loader.require("sdk/system/events"); michael@0: function brokenHandler(subject, data) { throw new Error("foo"); }; michael@0: michael@0: let lineNumber; michael@0: try { brokenHandler() } catch (error) { lineNumber = error.lineNumber } michael@0: michael@0: let errorType = Date.now().toString(32); michael@0: michael@0: events.on(errorType, brokenHandler); michael@0: events.emit(errorType, { data: "yo yo" }); michael@0: michael@0: assert.equal(messages.length, 2, "Got an exception"); michael@0: assert.equal(messages[0], "console.error: " + self.name + ": \n", michael@0: "error is logged"); michael@0: let text = messages[1]; michael@0: assert.ok(text.indexOf("Error: foo") >= 0, "error message is logged"); michael@0: assert.ok(text.indexOf(module.uri) >= 0, "module uri is logged"); michael@0: assert.ok(text.indexOf(lineNumber) >= 0, "error line is logged"); michael@0: michael@0: events.off(errorType, brokenHandler); michael@0: michael@0: loader.unload(); michael@0: }; michael@0: michael@0: exports["test listeners are GC-ed"] = function(assert, done) { michael@0: let receivedFromWeak = []; michael@0: let receivedFromStrong = []; michael@0: let loader = Loader(module); michael@0: let events = loader.require('sdk/system/events'); michael@0: michael@0: let type = 'test-listeners-are-garbage-collected'; michael@0: function handler(event) { receivedFromStrong.push(event); } michael@0: function weakHandler(event) { receivedFromWeak.push(event); } michael@0: michael@0: events.on(type, handler, true); michael@0: events.on(type, weakHandler); michael@0: michael@0: events.emit(type, { data: 1 }); michael@0: assert.equal(receivedFromStrong.length, 1, "strong listener invoked"); michael@0: assert.equal(receivedFromWeak.length, 1, "weak listener invoked"); michael@0: michael@0: handler = weakHandler = null; michael@0: michael@0: Cu.schedulePreciseGC(function() { michael@0: events.emit(type, { data: 2 }); michael@0: michael@0: assert.equal(receivedFromWeak.length, 1, "weak listener was GC-ed"); michael@0: assert.equal(receivedFromStrong.length, 2, "strong listener was invoked"); michael@0: michael@0: loader.unload(); michael@0: done(); michael@0: }); michael@0: }; michael@0: michael@0: exports["test alive listeners are removed on unload"] = function(assert) { michael@0: let receivedFromWeak = []; michael@0: let receivedFromStrong = []; michael@0: let loader = Loader(module); michael@0: let events = loader.require('sdk/system/events'); michael@0: michael@0: let type = 'test-alive-listeners-are-removed'; michael@0: const handler = (event) => receivedFromStrong.push(event); michael@0: const weakHandler = (event) => receivedFromWeak.push(event); michael@0: michael@0: events.on(type, handler, true); michael@0: events.on(type, weakHandler); michael@0: michael@0: events.emit(type, { data: 1 }); michael@0: assert.equal(receivedFromStrong.length, 1, "strong listener invoked"); michael@0: assert.equal(receivedFromWeak.length, 1, "weak listener invoked"); michael@0: michael@0: loader.unload(); michael@0: events.emit(type, { data: 2 }); michael@0: michael@0: assert.equal(receivedFromWeak.length, 1, "weak listener was removed"); michael@0: assert.equal(receivedFromStrong.length, 1, "strong listener was removed"); michael@0: }; michael@0: michael@0: exports["test handle nsIObserverService notifications"] = function(assert) { michael@0: let ios = Cc['@mozilla.org/network/io-service;1'] michael@0: .getService(Ci.nsIIOService); michael@0: michael@0: let uri = ios.newURI("http://www.foo.com", null, null); michael@0: michael@0: let type = Date.now().toString(32); michael@0: let timesCalled = 0; michael@0: let lastSubject = null; michael@0: let lastData = null; michael@0: let lastType = null; michael@0: michael@0: function handler({ subject, data, type }) { michael@0: // Ignores internal console events michael@0: if (isConsoleEvent(type)) michael@0: return; michael@0: timesCalled++; michael@0: lastSubject = subject; michael@0: lastData = data; michael@0: lastType = type; michael@0: }; michael@0: michael@0: events.on(type, handler); michael@0: nsIObserverService.notifyObservers(uri, type, "some data"); michael@0: michael@0: assert.equal(timesCalled, 1, "notification invokes handler"); michael@0: assert.equal(lastType, type, "event.type is notification topic"); michael@0: assert.equal(lastSubject, uri, "event.subject is notification subject"); michael@0: assert.equal(lastData, "some data", "event.data is notification data"); michael@0: michael@0: function customSubject() {} michael@0: function customData() {} michael@0: michael@0: events.emit(type, { data: customData, subject: customSubject }); michael@0: michael@0: assert.equal(timesCalled, 2, "notification invokes handler"); michael@0: assert.equal(lastType, type, "event.type is notification topic"); michael@0: assert.equal(lastSubject, customSubject, michael@0: "event.subject is wrapped & unwrapped"); michael@0: assert.equal(lastData, customData, "event.data is wrapped & unwrapped"); michael@0: michael@0: events.off(type, handler); michael@0: michael@0: nsIObserverService.notifyObservers(null, type, "some data"); michael@0: michael@0: assert.equal(timesCalled, 2, "event handler is removed"); michael@0: michael@0: events.on("*", handler); michael@0: michael@0: nsIObserverService.notifyObservers(null, type, "more data"); michael@0: michael@0: assert.equal(timesCalled, 3, "notification invokes * handler"); michael@0: assert.equal(lastType, type, "event.type is notification topic"); michael@0: assert.equal(lastSubject, null, michael@0: "event.subject is notification subject"); michael@0: assert.equal(lastData, "more data", "event.data is notification data"); michael@0: michael@0: events.off("*", handler); michael@0: michael@0: nsIObserverService.notifyObservers(null, type, "last data"); michael@0: michael@0: assert.equal(timesCalled, 3, "* event handler is removed"); michael@0: }; michael@0: michael@0: exports["test emit to nsIObserverService observers"] = function(assert) { michael@0: let ios = Cc['@mozilla.org/network/io-service;1'] michael@0: .getService(Ci.nsIIOService); michael@0: michael@0: let uri = ios.newURI("http://www.foo.com", null, null); michael@0: let timesCalled = 0; michael@0: let lastSubject = null; michael@0: let lastData = null; michael@0: let lastTopic = null; michael@0: michael@0: var topic = Date.now().toString(32) michael@0: let nsIObserver = { michael@0: QueryInterface: function() { michael@0: return nsIObserver; michael@0: }, michael@0: observe: function(subject, topic, data) { michael@0: // Ignores internal console events michael@0: if (isConsoleEvent(topic)) michael@0: return; michael@0: timesCalled = timesCalled + 1; michael@0: lastSubject = subject; michael@0: lastData = data; michael@0: lastTopic = topic; michael@0: } michael@0: }; michael@0: michael@0: nsIObserverService.addObserver(nsIObserver, topic, false); michael@0: michael@0: events.emit(topic, { subject: uri, data: "some data" }); michael@0: michael@0: assert.equal(timesCalled, 1, "emit notifies observers"); michael@0: assert.equal(lastTopic, topic, "event type is notification topic"); michael@0: assert.equal(lastSubject.wrappedJSObject.object, uri, michael@0: "event.subject is notification subject"); michael@0: assert.equal(lastData, "some data", "event.data is notification data"); michael@0: function customSubject() {} michael@0: function customData() {} michael@0: events.emit(topic, { subject: customSubject, data: customData }); michael@0: michael@0: assert.equal(timesCalled, 2, "emit notifies observers"); michael@0: assert.equal(lastTopic, topic, "event.type is notification"); michael@0: assert.equal(lastSubject.wrappedJSObject.object, customSubject, michael@0: "event.subject is notification subject"); michael@0: assert.equal(lastData, customData, "event.data is notification data"); michael@0: michael@0: nsIObserverService.removeObserver(nsIObserver, topic); michael@0: michael@0: events.emit(topic, { data: "more data" }); michael@0: michael@0: assert.equal(timesCalled, 2, "removed observers no longer invoked"); michael@0: michael@0: nsIObserverService.addObserver(nsIObserver, "*", false); michael@0: michael@0: events.emit(topic, { data: "data again" }); michael@0: michael@0: assert.equal(timesCalled, 3, "emit notifies * observers"); michael@0: michael@0: assert.equal(lastTopic, topic, "event.type is notification"); michael@0: assert.equal(lastSubject, null, michael@0: "event.subject is notification subject"); michael@0: assert.equal(lastData, "data again", "event.data is notification data"); michael@0: michael@0: nsIObserverService.removeObserver(nsIObserver, "*"); michael@0: michael@0: events.emit(topic, { data: "last data" }); michael@0: assert.equal(timesCalled, 3, "removed observers no longer invoked"); michael@0: } michael@0: michael@0: require("test").run(exports);