forked from mirrors/gecko-dev
***
Bug 1514594: Part 3a - Change ChromeUtils.import to return an exports object; not pollute global. r=mccr8
This changes the behavior of ChromeUtils.import() to return an exports object,
rather than a module global, in all cases except when `null` is passed as a
second argument, and changes the default behavior not to pollute the global
scope with the module's exports. Thus, the following code written for the old
model:
ChromeUtils.import("resource://gre/modules/Services.jsm");
is approximately the same as the following, in the new model:
var {Services} = ChromeUtils.import("resource://gre/modules/Services.jsm");
Since the two behaviors are mutually incompatible, this patch will land with a
scripted rewrite to update all existing callers to use the new model rather
than the old.
***
Bug 1514594: Part 3b - Mass rewrite all JS code to use the new ChromeUtils.import API. rs=Gijs
This was done using the followng script:
https://bitbucket.org/kmaglione/m-c-rewrites/src/tip/processors/cu-import-exports.jsm
***
Bug 1514594: Part 3c - Update ESLint plugin for ChromeUtils.import API changes. r=Standard8
Differential Revision: https://phabricator.services.mozilla.com/D16747
***
Bug 1514594: Part 3d - Remove/fix hundreds of duplicate imports from sync tests. r=Gijs
Differential Revision: https://phabricator.services.mozilla.com/D16748
***
Bug 1514594: Part 3e - Remove no-op ChromeUtils.import() calls. r=Gijs
Differential Revision: https://phabricator.services.mozilla.com/D16749
***
Bug 1514594: Part 3f.1 - Cleanup various test corner cases after mass rewrite. r=Gijs
***
Bug 1514594: Part 3f.2 - Cleanup various non-test corner cases after mass rewrite. r=Gijs
Differential Revision: https://phabricator.services.mozilla.com/D16750
--HG--
extra : rebase_source : 359574ee3064c90f33bf36c2ebe3159a24cc8895
extra : histedit_source : b93c8f42808b1599f9122d7842d2c0b3e656a594%2C64a3a4e3359dc889e2ab2b49461bab9e27fc10a7
353 lines
12 KiB
JavaScript
353 lines
12 KiB
JavaScript
"use strict";
|
|
|
|
PromiseTestUtils.whitelistRejectionsGlobally(/Message manager disconnected/);
|
|
|
|
const {ExtensionCommon} = ChromeUtils.import("resource://gre/modules/ExtensionCommon.jsm");
|
|
const {ExtensionAPI} = ExtensionCommon;
|
|
|
|
const SCHEMA = [
|
|
{
|
|
namespace: "eventtest",
|
|
events: [
|
|
{
|
|
name: "onEvent1",
|
|
type: "function",
|
|
extraParameters: [{type: "any"}],
|
|
},
|
|
{
|
|
name: "onEvent2",
|
|
type: "function",
|
|
extraParameters: [{type: "any"}],
|
|
},
|
|
],
|
|
},
|
|
];
|
|
|
|
// The code in this class does not actually run in this test scope, it is
|
|
// serialized into a string which is later loaded by the WebExtensions
|
|
// framework in the same context as other extension APIs. By writing it
|
|
// this way rather than as a big string constant we get lint coverage.
|
|
// But eslint doesn't understand that this code runs in a different context
|
|
// where the EventManager class is available so just tell it here:
|
|
/* global EventManager */
|
|
const API = class extends ExtensionAPI {
|
|
primeListener(extension, event, fire, params) {
|
|
let data = {wrappedJSObject: {event, params}};
|
|
Services.obs.notifyObservers(data, "prime-event-listener");
|
|
|
|
const FIRE_TOPIC = `fire-${event}`;
|
|
|
|
async function listener(subject, topic, _data) {
|
|
try {
|
|
await fire.async(subject.wrappedJSObject);
|
|
} catch (err) {
|
|
Services.obs.notifyObservers(data, "listener-callback-exception");
|
|
}
|
|
}
|
|
Services.obs.addObserver(listener, FIRE_TOPIC);
|
|
|
|
return {
|
|
unregister() {
|
|
Services.obs.notifyObservers(data, "unregister-primed-listener");
|
|
Services.obs.removeObserver(listener, FIRE_TOPIC);
|
|
},
|
|
convert(_fire) {
|
|
Services.obs.notifyObservers(data, "convert-event-listener");
|
|
fire = _fire;
|
|
},
|
|
};
|
|
}
|
|
|
|
getAPI(context) {
|
|
return {
|
|
eventtest: {
|
|
onEvent1: new EventManager({
|
|
context,
|
|
name: "test.event1",
|
|
persistent: {
|
|
module: "eventtest",
|
|
event: "onEvent1",
|
|
},
|
|
register: (fire, ...params) => {
|
|
let data = {wrappedJSObject: {event: "onEvent1", params}};
|
|
Services.obs.notifyObservers(data, "register-event-listener");
|
|
return () => {
|
|
Services.obs.notifyObservers(data, "unregister-event-listener");
|
|
};
|
|
},
|
|
}).api(),
|
|
|
|
onEvent2: new EventManager({
|
|
context,
|
|
name: "test.event1",
|
|
persistent: {
|
|
module: "eventtest",
|
|
event: "onEvent2",
|
|
},
|
|
register: (fire, ...params) => {
|
|
let data = {wrappedJSObject: {event: "onEvent2", params}};
|
|
Services.obs.notifyObservers(data, "register-event-listener");
|
|
return () => {
|
|
Services.obs.notifyObservers(data, "unregister-event-listener");
|
|
};
|
|
},
|
|
}).api(),
|
|
},
|
|
};
|
|
}
|
|
};
|
|
|
|
const API_SCRIPT = `this.eventtest = ${API.toString()}`;
|
|
|
|
const MODULE_INFO = {
|
|
eventtest: {
|
|
schema: `data:,${JSON.stringify(SCHEMA)}`,
|
|
scopes: ["addon_parent"],
|
|
paths: [["eventtest"]],
|
|
url: URL.createObjectURL(new Blob([API_SCRIPT])),
|
|
},
|
|
};
|
|
|
|
const global = this;
|
|
|
|
// Wait for the given event (topic) to occur a specific number of times
|
|
// (count). If fn is not supplied, the Promise returned from this function
|
|
// resolves as soon as that many instances of the event have been observed.
|
|
// If fn is supplied, this function also waits for the Promise that fn()
|
|
// returns to complete and ensures that the given event does not occur more
|
|
// than `count` times before then. On success, resolves with an array
|
|
// of the subjects from each of the observed events.
|
|
async function promiseObservable(topic, count, fn = null) {
|
|
let _countResolve;
|
|
let results = [];
|
|
function listener(subject, _topic, data) {
|
|
results.push(subject.wrappedJSObject);
|
|
if (results.length > count) {
|
|
ok(false, `Got unexpected ${topic} event`);
|
|
} else if (results.length == count) {
|
|
_countResolve();
|
|
}
|
|
}
|
|
Services.obs.addObserver(listener, topic);
|
|
|
|
try {
|
|
await Promise.all([
|
|
new Promise(resolve => { _countResolve = resolve; }),
|
|
fn && fn(),
|
|
]);
|
|
} finally {
|
|
Services.obs.removeObserver(listener, topic);
|
|
}
|
|
|
|
return results;
|
|
}
|
|
|
|
add_task(async function() {
|
|
Services.prefs.setBoolPref("extensions.webextensions.background-delayed-startup", true);
|
|
|
|
AddonTestUtils.init(global);
|
|
AddonTestUtils.overrideCertDB();
|
|
AddonTestUtils.createAppInfo("xpcshell@tests.mozilla.org", "XPCShell", "1", "43");
|
|
|
|
await AddonTestUtils.promiseStartupManager();
|
|
|
|
ExtensionParent.apiManager.registerModules(MODULE_INFO);
|
|
|
|
let extension = ExtensionTestUtils.loadExtension({
|
|
useAddonManager: "permanent",
|
|
background() {
|
|
let register1 = true, register2 = true;
|
|
if (localStorage.getItem("skip1")) {
|
|
register1 = false;
|
|
}
|
|
if (localStorage.getItem("skip2")) {
|
|
register2 = false;
|
|
}
|
|
|
|
let listener1 = arg => browser.test.sendMessage("listener1", arg);
|
|
let listener2 = arg => browser.test.sendMessage("listener2", arg);
|
|
let listener3 = arg => browser.test.sendMessage("listener3", arg);
|
|
|
|
if (register1) {
|
|
browser.eventtest.onEvent1.addListener(listener1, "listener1");
|
|
}
|
|
if (register2) {
|
|
browser.eventtest.onEvent1.addListener(listener2, "listener2");
|
|
browser.eventtest.onEvent2.addListener(listener3, "listener3");
|
|
}
|
|
|
|
browser.test.onMessage.addListener(msg => {
|
|
if (msg == "unregister2") {
|
|
browser.eventtest.onEvent2.removeListener(listener3);
|
|
localStorage.setItem("skip2", true);
|
|
} else if (msg == "unregister1") {
|
|
localStorage.setItem("skip1", true);
|
|
browser.test.sendMessage("unregistered");
|
|
}
|
|
});
|
|
|
|
browser.test.sendMessage("ready");
|
|
},
|
|
});
|
|
|
|
function check(info, what, {listener1 = true, listener2 = true, listener3 = true} = {}) {
|
|
let count = (listener1 ? 1 : 0) + (listener2 ? 1 : 0) + (listener3 ? 1 : 0);
|
|
equal(info.length, count, `Got ${count} ${what} events`);
|
|
|
|
let i = 0;
|
|
if (listener1) {
|
|
equal(info[i].event, "onEvent1", `Got ${what} on event1 for listener 1`);
|
|
deepEqual(info[i].params, ["listener1"], `Got event1 ${what} args for listener 1`);
|
|
++i;
|
|
}
|
|
|
|
if (listener2) {
|
|
equal(info[i].event, "onEvent1", `Got ${what} on event1 for listener 2`);
|
|
deepEqual(info[i].params, ["listener2"], `Got event1 ${what} args for listener 2`);
|
|
++i;
|
|
}
|
|
|
|
if (listener3) {
|
|
equal(info[i].event, "onEvent2", `Got ${what} on event2 for listener 3`);
|
|
deepEqual(info[i].params, ["listener3"], `Got event2 ${what} args for listener 3`);
|
|
++i;
|
|
}
|
|
}
|
|
|
|
// Check that the regular event registration process occurs when
|
|
// the extension is installed.
|
|
let [info] = await Promise.all([
|
|
promiseObservable("register-event-listener", 3),
|
|
extension.startup(),
|
|
]);
|
|
check(info, "register");
|
|
|
|
await extension.awaitMessage("ready");
|
|
|
|
// Check that the regular unregister process occurs when
|
|
// the browser shuts down.
|
|
[info] = await Promise.all([
|
|
promiseObservable("unregister-event-listener", 3),
|
|
new Promise(resolve => extension.extension.once("shutdown", resolve)),
|
|
AddonTestUtils.promiseShutdownManager(),
|
|
]);
|
|
check(info, "unregister");
|
|
|
|
// Check that listeners are primed at the next browser startup.
|
|
[info] = await Promise.all([
|
|
promiseObservable("prime-event-listener", 3),
|
|
AddonTestUtils.promiseStartupManager(),
|
|
]);
|
|
check(info, "prime");
|
|
|
|
// Check that primed listeners are converted to regular listeners
|
|
// when the background page is started after browser startup.
|
|
let p = promiseObservable("convert-event-listener", 3);
|
|
Services.obs.notifyObservers(null, "sessionstore-windows-restored");
|
|
info = await p;
|
|
|
|
check(info, "convert");
|
|
|
|
await extension.awaitMessage("ready");
|
|
|
|
// Check that when the event is triggered, all the plumbing worked
|
|
// correctly for the primed-then-converted listener.
|
|
let eventDetails = {test: "kaboom"};
|
|
let eventSubject = {wrappedJSObject: eventDetails};
|
|
Services.obs.notifyObservers(eventSubject, "fire-onEvent1");
|
|
|
|
let details = await extension.awaitMessage("listener1");
|
|
deepEqual(details, eventDetails, "Listener 1 fired");
|
|
details = await extension.awaitMessage("listener2");
|
|
deepEqual(details, eventDetails, "Listener 2 fired");
|
|
|
|
// Check that the converted listener is properly unregistered at
|
|
// browser shutdown.
|
|
[info] = await Promise.all([
|
|
promiseObservable("unregister-primed-listener", 3),
|
|
AddonTestUtils.promiseShutdownManager(),
|
|
]);
|
|
check(info, "unregister");
|
|
|
|
// Start up again, listener should be primed
|
|
[info] = await Promise.all([
|
|
promiseObservable("prime-event-listener", 3),
|
|
AddonTestUtils.promiseStartupManager(),
|
|
]);
|
|
check(info, "prime");
|
|
|
|
// Check that triggering the event before the listener has been converted
|
|
// causes the background page to be loaded and the listener to be converted,
|
|
// and the listener is invoked.
|
|
p = promiseObservable("convert-event-listener", 3);
|
|
eventDetails.test = "startup event";
|
|
Services.obs.notifyObservers(eventSubject, "fire-onEvent2");
|
|
info = await p;
|
|
|
|
check(info, "convert");
|
|
|
|
details = await extension.awaitMessage("listener3");
|
|
deepEqual(details, eventDetails, "Listener 3 fired for event during startup");
|
|
|
|
await extension.awaitMessage("ready");
|
|
|
|
// Check that the unregister process works when we manually remove
|
|
// a listener.
|
|
p = promiseObservable("unregister-primed-listener", 1);
|
|
extension.sendMessage("unregister2");
|
|
info = await p;
|
|
check(info, "unregister", {listener1: false, listener2: false});
|
|
|
|
// Check that we only get unregisters for the remaining events after
|
|
// one listener has been removed.
|
|
info = await promiseObservable("unregister-primed-listener", 2,
|
|
() => AddonTestUtils.promiseShutdownManager());
|
|
check(info, "unregister", {listener3: false});
|
|
|
|
// Check that after restart, only listeners that were present at
|
|
// the end of the last session are primed.
|
|
info = await promiseObservable("prime-event-listener", 2,
|
|
() => AddonTestUtils.promiseStartupManager());
|
|
check(info, "prime", {listener3: false});
|
|
|
|
// Check that if the background script does not re-register listeners,
|
|
// the primed listeners are unregistered after the background page
|
|
// starts up.
|
|
p = promiseObservable("unregister-primed-listener", 1,
|
|
() => extension.awaitMessage("ready"));
|
|
Services.obs.notifyObservers(null, "sessionstore-windows-restored");
|
|
info = await p;
|
|
check(info, "unregister", {listener1: false, listener3: false});
|
|
|
|
// Just listener1 should be registered now, fire event1 to confirm.
|
|
eventDetails.test = "third time";
|
|
Services.obs.notifyObservers(eventSubject, "fire-onEvent1");
|
|
details = await extension.awaitMessage("listener1");
|
|
deepEqual(details, eventDetails, "Listener 1 fired");
|
|
|
|
// Tell the extension not to re-register listener1 on the next startup
|
|
extension.sendMessage("unregister1");
|
|
await extension.awaitMessage("unregistered");
|
|
|
|
// Shut down, start up
|
|
info = await promiseObservable("unregister-primed-listener", 1,
|
|
() => AddonTestUtils.promiseShutdownManager());
|
|
check(info, "unregister", {listener2: false, listener3: false});
|
|
|
|
info = await promiseObservable("prime-event-listener", 1,
|
|
() => AddonTestUtils.promiseStartupManager());
|
|
check(info, "register", {listener2: false, listener3: false});
|
|
|
|
// Check that firing event1 causes the listener fire callback to
|
|
// reject.
|
|
p = promiseObservable("listener-callback-exception", 1);
|
|
Services.obs.notifyObservers(eventSubject, "fire-onEvent1");
|
|
await p;
|
|
ok(true, "Primed listener that was not re-registered received an error when event was triggered during startup");
|
|
|
|
await extension.awaitMessage("ready");
|
|
|
|
await extension.unload();
|
|
|
|
await AddonTestUtils.promiseShutdownManager();
|
|
});
|