forked from mirrors/gecko-dev
Depends on D168396 Differential Revision: https://phabricator.services.mozilla.com/D168397
1268 lines
42 KiB
JavaScript
1268 lines
42 KiB
JavaScript
"use strict";
|
|
|
|
// Delay loading until createAppInfo is called and setup.
|
|
ChromeUtils.defineModuleGetter(
|
|
this,
|
|
"AddonManager",
|
|
"resource://gre/modules/AddonManager.jsm"
|
|
);
|
|
|
|
const { ExtensionAPI } = ExtensionCommon;
|
|
|
|
// 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 {
|
|
static namespace = undefined;
|
|
primeListener(event, fire, params, isInStartup) {
|
|
if (isInStartup && event == "nonBlockingEvent") {
|
|
return;
|
|
}
|
|
// eslint-disable-next-line no-undef
|
|
let { eventName, throwError, ignoreListener } =
|
|
this.constructor.testOptions || {};
|
|
let { namespace } = this.constructor;
|
|
|
|
if (eventName == event) {
|
|
if (throwError) {
|
|
throw new Error(throwError);
|
|
}
|
|
if (ignoreListener) {
|
|
return;
|
|
}
|
|
}
|
|
|
|
Services.obs.notifyObservers(
|
|
{ namespace, event, fire, params },
|
|
"prime-event-listener"
|
|
);
|
|
|
|
const FIRE_TOPIC = `fire-${namespace}.${event}`;
|
|
|
|
async function listener(subject, topic, data) {
|
|
try {
|
|
if (subject.wrappedJSObject.waitForBackground) {
|
|
await fire.wakeup();
|
|
}
|
|
await fire.async(subject.wrappedJSObject.listenerArgs);
|
|
} catch (err) {
|
|
let errSubject = { namespace, event, errorMessage: err.toString() };
|
|
Services.obs.notifyObservers(errSubject, "listener-callback-exception");
|
|
}
|
|
}
|
|
Services.obs.addObserver(listener, FIRE_TOPIC);
|
|
|
|
return {
|
|
unregister() {
|
|
Services.obs.notifyObservers(
|
|
{ namespace, event, params },
|
|
"unregister-primed-listener"
|
|
);
|
|
Services.obs.removeObserver(listener, FIRE_TOPIC);
|
|
},
|
|
convert(_fire) {
|
|
Services.obs.notifyObservers(
|
|
{ namespace, event, params },
|
|
"convert-event-listener"
|
|
);
|
|
fire = _fire;
|
|
},
|
|
};
|
|
}
|
|
|
|
getAPI(context) {
|
|
let self = this;
|
|
let { namespace } = this.constructor;
|
|
return {
|
|
[namespace]: {
|
|
testOptions(options) {
|
|
// We want to be able to test errors on startup.
|
|
// We use a global here because we test restarting AOM,
|
|
// which causes the instance of this class to be destroyed.
|
|
// eslint-disable-next-line no-undef
|
|
self.constructor.testOptions = options;
|
|
},
|
|
onEvent1: new EventManager({
|
|
context,
|
|
module: namespace,
|
|
event: "onEvent1",
|
|
register: (fire, ...params) => {
|
|
let data = { namespace, event: "onEvent1", params };
|
|
Services.obs.notifyObservers(data, "register-event-listener");
|
|
return () => {
|
|
Services.obs.notifyObservers(data, "unregister-event-listener");
|
|
};
|
|
},
|
|
}).api(),
|
|
|
|
onEvent2: new EventManager({
|
|
context,
|
|
module: namespace,
|
|
event: "onEvent2",
|
|
register: (fire, ...params) => {
|
|
let data = { namespace, event: "onEvent2", params };
|
|
Services.obs.notifyObservers(data, "register-event-listener");
|
|
return () => {
|
|
Services.obs.notifyObservers(data, "unregister-event-listener");
|
|
};
|
|
},
|
|
}).api(),
|
|
|
|
nonBlockingEvent: new EventManager({
|
|
context,
|
|
module: namespace,
|
|
event: "nonBlockingEvent",
|
|
register: (fire, ...params) => {
|
|
let data = { namespace, event: "nonBlockingEvent", params };
|
|
Services.obs.notifyObservers(data, "register-event-listener");
|
|
return () => {
|
|
Services.obs.notifyObservers(data, "unregister-event-listener");
|
|
};
|
|
},
|
|
}).api(),
|
|
},
|
|
};
|
|
}
|
|
};
|
|
|
|
function makeModule(namespace, options = {}) {
|
|
const SCHEMA = [
|
|
{
|
|
namespace,
|
|
functions: [
|
|
{
|
|
name: "testOptions",
|
|
type: "function",
|
|
async: true,
|
|
parameters: [
|
|
{
|
|
name: "options",
|
|
type: "object",
|
|
additionalProperties: {
|
|
type: "any",
|
|
},
|
|
},
|
|
],
|
|
},
|
|
],
|
|
events: [
|
|
{
|
|
name: "onEvent1",
|
|
type: "function",
|
|
extraParameters: [{ type: "any", optional: true }],
|
|
},
|
|
{
|
|
name: "onEvent2",
|
|
type: "function",
|
|
extraParameters: [{ type: "any", optional: true }],
|
|
},
|
|
{
|
|
name: "nonBlockingEvent",
|
|
type: "function",
|
|
extraParameters: [{ type: "any", optional: true }],
|
|
},
|
|
],
|
|
},
|
|
];
|
|
|
|
const API_SCRIPT = `
|
|
this.${namespace} = ${API.toString()};
|
|
this.${namespace}.namespace = "${namespace}";
|
|
`;
|
|
|
|
// MODULE_INFO for registerModules
|
|
let { startupBlocking } = options;
|
|
return {
|
|
schema: `data:,${JSON.stringify(SCHEMA)}`,
|
|
scopes: ["addon_parent"],
|
|
paths: [[namespace]],
|
|
startupBlocking,
|
|
url: URL.createObjectURL(new Blob([API_SCRIPT])),
|
|
};
|
|
}
|
|
|
|
// Two modules, primary test module is startupBlocking
|
|
const MODULE_INFO = {
|
|
startupBlocking: makeModule("startupBlocking", { startupBlocking: true }),
|
|
nonStartupBlocking: makeModule("nonStartupBlocking"),
|
|
};
|
|
|
|
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) {
|
|
const eventDetails = subject.wrappedJSObject;
|
|
results.push(eventDetails);
|
|
if (results.length > count) {
|
|
ok(
|
|
false,
|
|
`Got unexpected ${topic} event with ${JSON.stringify(eventDetails)}`
|
|
);
|
|
} 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;
|
|
}
|
|
|
|
function trackEvents(wrapper) {
|
|
let events = new Map();
|
|
for (let event of ["background-script-event", "start-background-script"]) {
|
|
events.set(event, false);
|
|
wrapper.extension.once(event, () => events.set(event, true));
|
|
}
|
|
return events;
|
|
}
|
|
|
|
const server = createHttpServer({ hosts: ["example.com"] });
|
|
server.registerDirectory("/data/", do_get_file("data"));
|
|
|
|
add_task(async function setup() {
|
|
// The blob:-URL registered above in MODULE_INFO gets loaded at
|
|
// https://searchfox.org/mozilla-central/rev/0fec57c05d3996cc00c55a66f20dd5793a9bfb5d/toolkit/components/extensions/ExtensionCommon.jsm#1649
|
|
Services.prefs.setBoolPref(
|
|
"security.allow_parent_unrestricted_js_loads",
|
|
true
|
|
);
|
|
registerCleanupFunction(() => {
|
|
Services.prefs.clearUserPref("security.allow_parent_unrestricted_js_loads");
|
|
});
|
|
|
|
AddonTestUtils.init(global);
|
|
AddonTestUtils.overrideCertDB();
|
|
AddonTestUtils.createAppInfo(
|
|
"xpcshell@tests.mozilla.org",
|
|
"XPCShell",
|
|
"42",
|
|
"42"
|
|
);
|
|
|
|
ExtensionParent.apiManager.registerModules(MODULE_INFO);
|
|
});
|
|
|
|
add_task(async function test_persistent_events() {
|
|
await AddonTestUtils.promiseStartupManager();
|
|
|
|
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.startupBlocking.onEvent1.addListener(listener1, "listener1");
|
|
}
|
|
if (register2) {
|
|
browser.startupBlocking.onEvent1.addListener(listener2, "listener2");
|
|
browser.startupBlocking.onEvent2.addListener(listener3, "listener3");
|
|
}
|
|
|
|
browser.test.onMessage.addListener(msg => {
|
|
if (msg == "unregister2") {
|
|
browser.startupBlocking.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 [observed] = await Promise.all([
|
|
promiseObservable("register-event-listener", 3),
|
|
extension.startup(),
|
|
]);
|
|
check(observed, "register");
|
|
|
|
await extension.awaitMessage("ready");
|
|
|
|
// Check that the regular unregister process occurs when
|
|
// the browser shuts down.
|
|
[observed] = await Promise.all([
|
|
promiseObservable("unregister-event-listener", 3),
|
|
new Promise(resolve => extension.extension.once("shutdown", resolve)),
|
|
AddonTestUtils.promiseShutdownManager(),
|
|
]);
|
|
check(observed, "unregister");
|
|
|
|
// Check that listeners are primed at the next browser startup.
|
|
[observed] = await Promise.all([
|
|
promiseObservable("prime-event-listener", 3),
|
|
AddonTestUtils.promiseStartupManager(),
|
|
]);
|
|
check(observed, "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);
|
|
AddonTestUtils.notifyLateStartup();
|
|
observed = await p;
|
|
|
|
check(observed, "convert");
|
|
|
|
await extension.awaitMessage("ready");
|
|
|
|
// Check that when the event is triggered, all the plumbing worked
|
|
// correctly for the primed-then-converted listener.
|
|
let listenerArgs = { test: "kaboom" };
|
|
Services.obs.notifyObservers(
|
|
{ listenerArgs },
|
|
"fire-startupBlocking.onEvent1"
|
|
);
|
|
|
|
let details = await extension.awaitMessage("listener1");
|
|
deepEqual(details, listenerArgs, "Listener 1 fired");
|
|
details = await extension.awaitMessage("listener2");
|
|
deepEqual(details, listenerArgs, "Listener 2 fired");
|
|
|
|
// Check that the converted listener is properly unregistered at
|
|
// browser shutdown.
|
|
[observed] = await Promise.all([
|
|
promiseObservable("unregister-primed-listener", 3),
|
|
AddonTestUtils.promiseShutdownManager(),
|
|
]);
|
|
check(observed, "unregister");
|
|
|
|
// Start up again, listener should be primed
|
|
[observed] = await Promise.all([
|
|
promiseObservable("prime-event-listener", 3),
|
|
AddonTestUtils.promiseStartupManager(),
|
|
]);
|
|
check(observed, "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);
|
|
listenerArgs.test = "startup event";
|
|
Services.obs.notifyObservers(
|
|
{ listenerArgs },
|
|
"fire-startupBlocking.onEvent2"
|
|
);
|
|
observed = await p;
|
|
|
|
check(observed, "convert");
|
|
|
|
details = await extension.awaitMessage("listener3");
|
|
deepEqual(details, listenerArgs, "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");
|
|
observed = await p;
|
|
check(observed, "unregister", { listener1: false, listener2: false });
|
|
|
|
// Check that we only get unregisters for the remaining events after
|
|
// one listener has been removed.
|
|
observed = await promiseObservable("unregister-primed-listener", 2, () =>
|
|
AddonTestUtils.promiseShutdownManager()
|
|
);
|
|
check(observed, "unregister", { listener3: false });
|
|
|
|
// Check that after restart, only listeners that were present at
|
|
// the end of the last session are primed.
|
|
observed = await promiseObservable("prime-event-listener", 2, () =>
|
|
AddonTestUtils.promiseStartupManager()
|
|
);
|
|
check(observed, "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")
|
|
);
|
|
|
|
AddonTestUtils.notifyLateStartup();
|
|
observed = await p;
|
|
check(observed, "unregister", { listener1: false, listener3: false });
|
|
|
|
// Just listener1 should be registered now, fire event1 to confirm.
|
|
listenerArgs.test = "third time";
|
|
Services.obs.notifyObservers(
|
|
{ listenerArgs },
|
|
"fire-startupBlocking.onEvent1"
|
|
);
|
|
details = await extension.awaitMessage("listener1");
|
|
deepEqual(details, listenerArgs, "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
|
|
observed = await promiseObservable("unregister-primed-listener", 1, () =>
|
|
AddonTestUtils.promiseShutdownManager()
|
|
);
|
|
check(observed, "unregister", { listener2: false, listener3: false });
|
|
|
|
observed = await promiseObservable("prime-event-listener", 1, () =>
|
|
AddonTestUtils.promiseStartupManager()
|
|
);
|
|
check(observed, "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(
|
|
{ listenerArgs, waitForBackground: true },
|
|
"fire-startupBlocking.onEvent1"
|
|
);
|
|
equal(
|
|
(await p)[0].errorMessage,
|
|
"Error: primed listener startupBlocking.onEvent1 not re-registered",
|
|
"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();
|
|
});
|
|
|
|
// This test checks whether primed listeners are correctly unregistered when
|
|
// a background page load is interrupted. In particular, it verifies that the
|
|
// fire.wakeup() and fire.async() promises settle eventually.
|
|
add_task(async function test_shutdown_before_background_loaded() {
|
|
await AddonTestUtils.promiseStartupManager();
|
|
|
|
let extension = ExtensionTestUtils.loadExtension({
|
|
useAddonManager: "permanent",
|
|
background() {
|
|
let listener = arg => browser.test.sendMessage("triggered", arg);
|
|
browser.startupBlocking.onEvent1.addListener(listener, "triggered");
|
|
browser.test.sendMessage("bg_started");
|
|
},
|
|
});
|
|
await Promise.all([
|
|
promiseObservable("register-event-listener", 1),
|
|
extension.startup(),
|
|
]);
|
|
await extension.awaitMessage("bg_started");
|
|
|
|
await Promise.all([
|
|
promiseObservable("unregister-event-listener", 1),
|
|
new Promise(resolve => extension.extension.once("shutdown", resolve)),
|
|
AddonTestUtils.promiseShutdownManager(),
|
|
]);
|
|
|
|
let primeListenerPromise = promiseObservable("prime-event-listener", 1);
|
|
let fire;
|
|
let fireWakeupBeforeBgFail;
|
|
let fireAsyncBeforeBgFail;
|
|
|
|
let bgAbortedPromise = new Promise(resolve => {
|
|
let Management = ExtensionParent.apiManager;
|
|
Management.once("extension-browser-inserted", (eventName, browser) => {
|
|
browser.fixupAndLoadURIString = async () => {
|
|
// The fire.wakeup/fire.async promises created while loading the
|
|
// background page should settle when the page fails to load.
|
|
fire = (await primeListenerPromise)[0].fire;
|
|
fireWakeupBeforeBgFail = fire.wakeup();
|
|
fireAsyncBeforeBgFail = fire.async();
|
|
|
|
extension.extension.once("background-script-aborted", resolve);
|
|
info("Forcing the background load to fail");
|
|
browser.remove();
|
|
};
|
|
});
|
|
});
|
|
|
|
let unregisterPromise = promiseObservable("unregister-primed-listener", 1);
|
|
|
|
await Promise.all([
|
|
primeListenerPromise,
|
|
AddonTestUtils.promiseStartupManager(),
|
|
]);
|
|
await bgAbortedPromise;
|
|
info("Loaded extension and aborted load of background page");
|
|
|
|
await unregisterPromise;
|
|
info("Primed listener has been unregistered");
|
|
|
|
await fireWakeupBeforeBgFail;
|
|
info("fire.wakeup() before background load failure should settle");
|
|
|
|
await Assert.rejects(
|
|
fireAsyncBeforeBgFail,
|
|
/Error: listener not re-registered/,
|
|
"fire.async before background load failure should be rejected"
|
|
);
|
|
|
|
await fire.wakeup();
|
|
info("fire.wakeup() after background load failure should settle");
|
|
|
|
await Assert.rejects(
|
|
fire.async(),
|
|
/Error: primed listener startupBlocking.onEvent1 not re-registered/,
|
|
"fire.async after background load failure should be rejected"
|
|
);
|
|
|
|
await AddonTestUtils.promiseShutdownManager();
|
|
|
|
// End of the abnormal shutdown test. Now restart the extension to verify
|
|
// that the persistent listeners have not been unregistered.
|
|
|
|
// Suppress background page start until an explicit notification.
|
|
await Promise.all([
|
|
promiseObservable("prime-event-listener", 1),
|
|
AddonTestUtils.promiseStartupManager({ earlyStartup: false }),
|
|
]);
|
|
info("Triggering persistent event to force the background page to start");
|
|
Services.obs.notifyObservers(
|
|
{ listenerArgs: 123 },
|
|
"fire-startupBlocking.onEvent1"
|
|
);
|
|
AddonTestUtils.notifyEarlyStartup();
|
|
await extension.awaitMessage("bg_started");
|
|
equal(await extension.awaitMessage("triggered"), 123, "triggered event");
|
|
|
|
await Promise.all([
|
|
promiseObservable("unregister-primed-listener", 1),
|
|
AddonTestUtils.promiseShutdownManager(),
|
|
]);
|
|
|
|
// And lastly, verify that a primed listener is correctly removed when the
|
|
// extension unloads normally before the delayed background page can load.
|
|
await Promise.all([
|
|
promiseObservable("prime-event-listener", 1),
|
|
AddonTestUtils.promiseStartupManager({ earlyStartup: false }),
|
|
]);
|
|
|
|
info("Unloading extension before background page has loaded");
|
|
await Promise.all([
|
|
promiseObservable("unregister-primed-listener", 1),
|
|
extension.unload(),
|
|
]);
|
|
|
|
await AddonTestUtils.promiseShutdownManager();
|
|
});
|
|
|
|
// This test checks whether primed listeners are correctly primed to
|
|
// restart the background once the background has been shutdown or
|
|
// put to sleep.
|
|
add_task(async function test_background_restarted() {
|
|
await AddonTestUtils.promiseStartupManager();
|
|
|
|
let extension = ExtensionTestUtils.loadExtension({
|
|
useAddonManager: "permanent",
|
|
background() {
|
|
let listener = arg => browser.test.sendMessage("triggered", arg);
|
|
browser.startupBlocking.onEvent1.addListener(listener, "triggered");
|
|
browser.test.sendMessage("bg_started");
|
|
},
|
|
});
|
|
await Promise.all([
|
|
promiseObservable("register-event-listener", 1),
|
|
extension.startup(),
|
|
]);
|
|
await extension.awaitMessage("bg_started");
|
|
assertPersistentListeners(extension, "startupBlocking", "onEvent1", {
|
|
primed: false,
|
|
});
|
|
|
|
// Shutdown the background page
|
|
await Promise.all([
|
|
promiseObservable("unregister-event-listener", 1),
|
|
extension.terminateBackground(),
|
|
]);
|
|
// When sleeping the background, its events should become persisted
|
|
assertPersistentListeners(extension, "startupBlocking", "onEvent1", {
|
|
primed: true,
|
|
});
|
|
|
|
info("Triggering persistent event to force the background page to start");
|
|
Services.obs.notifyObservers(
|
|
{ listenerArgs: 123 },
|
|
"fire-startupBlocking.onEvent1"
|
|
);
|
|
await extension.awaitMessage("bg_started");
|
|
equal(await extension.awaitMessage("triggered"), 123, "triggered event");
|
|
|
|
await extension.unload();
|
|
await AddonTestUtils.promiseShutdownManager();
|
|
});
|
|
|
|
// This test checks whether primed listeners are correctly primed to
|
|
// restart the background once the background has been shutdown or
|
|
// put to sleep.
|
|
add_task(
|
|
{ pref_set: [["extensions.eventPages.enabled", true]] },
|
|
async function test_eventpage_startup() {
|
|
await AddonTestUtils.promiseStartupManager();
|
|
|
|
let extension = ExtensionTestUtils.loadExtension({
|
|
useAddonManager: "permanent",
|
|
manifest: {
|
|
browser_specific_settings: { gecko: { id: "eventpage@test" } },
|
|
background: { persistent: false },
|
|
},
|
|
background() {
|
|
let listener = arg => browser.test.sendMessage("triggered", arg);
|
|
browser.startupBlocking.onEvent1.addListener(listener, "triggered");
|
|
let listenerNs = arg => browser.test.sendMessage("triggered-et2", arg);
|
|
browser.nonStartupBlocking.onEvent1.addListener(
|
|
listenerNs,
|
|
"triggered-et2"
|
|
);
|
|
browser.test.onMessage.addListener(() => {
|
|
let listener = arg => browser.test.sendMessage("triggered2", arg);
|
|
browser.startupBlocking.onEvent2.addListener(listener, "triggered2");
|
|
browser.test.sendMessage("async-registered-listener");
|
|
});
|
|
browser.test.sendMessage("bg_started");
|
|
},
|
|
});
|
|
await Promise.all([
|
|
promiseObservable("register-event-listener", 2),
|
|
extension.startup(),
|
|
]);
|
|
await extension.awaitMessage("bg_started");
|
|
extension.sendMessage("async-register-listener");
|
|
await extension.awaitMessage("async-registered-listener");
|
|
|
|
async function testAfterRestart() {
|
|
assertPersistentListeners(extension, "startupBlocking", "onEvent1", {
|
|
primed: true,
|
|
});
|
|
// async registration should not be primed or persisted
|
|
assertPersistentListeners(extension, "startupBlocking", "onEvent2", {
|
|
primed: false,
|
|
persisted: false,
|
|
});
|
|
|
|
let events = trackEvents(extension);
|
|
ok(
|
|
!events.get("background-script-event"),
|
|
"Should not have received a background script event"
|
|
);
|
|
ok(
|
|
!events.get("start-background-script"),
|
|
"Background script should not be started"
|
|
);
|
|
|
|
info("Triggering persistent event to force the background page to start");
|
|
let converted = promiseObservable("convert-event-listener", 1);
|
|
Services.obs.notifyObservers(
|
|
{ listenerArgs: 123 },
|
|
"fire-startupBlocking.onEvent1"
|
|
);
|
|
await extension.awaitMessage("bg_started");
|
|
await converted;
|
|
equal(await extension.awaitMessage("triggered"), 123, "triggered event");
|
|
ok(
|
|
events.get("background-script-event"),
|
|
"Should have received a background script event"
|
|
);
|
|
ok(
|
|
events.get("start-background-script"),
|
|
"Background script should be started"
|
|
);
|
|
}
|
|
|
|
// Shutdown the background page
|
|
await Promise.all([
|
|
promiseObservable("unregister-event-listener", 3),
|
|
new Promise(resolve => extension.extension.once("shutdown", resolve)),
|
|
AddonTestUtils.promiseShutdownManager(),
|
|
]);
|
|
await AddonTestUtils.promiseStartupManager({ lateStartup: false });
|
|
await extension.awaitStartup();
|
|
assertPersistentListeners(extension, "nonStartupBlocking", "onEvent1", {
|
|
primed: false,
|
|
persisted: true,
|
|
});
|
|
await testAfterRestart();
|
|
|
|
extension.sendMessage("async-register-listener");
|
|
await extension.awaitMessage("async-registered-listener");
|
|
|
|
// We sleep twice to ensure startup and shutdown work correctly
|
|
info("test event listener registration during termination");
|
|
let registrationEvents = Promise.all([
|
|
promiseObservable("unregister-event-listener", 2),
|
|
promiseObservable("unregister-primed-listener", 1),
|
|
promiseObservable("prime-event-listener", 2),
|
|
]);
|
|
await extension.terminateBackground();
|
|
await registrationEvents;
|
|
|
|
assertPersistentListeners(extension, "nonStartupBlocking", "onEvent1", {
|
|
primed: true,
|
|
persisted: true,
|
|
});
|
|
|
|
// Ensure onEvent2 does not fire, testAfterRestart will fail otherwise.
|
|
Services.obs.notifyObservers(
|
|
{ listenerArgs: 123 },
|
|
"fire-startupBlocking.onEvent2"
|
|
);
|
|
await testAfterRestart();
|
|
|
|
registrationEvents = Promise.all([
|
|
promiseObservable("unregister-primed-listener", 2),
|
|
promiseObservable("prime-event-listener", 2),
|
|
]);
|
|
await extension.terminateBackground();
|
|
await registrationEvents;
|
|
await testAfterRestart();
|
|
|
|
await extension.unload();
|
|
await AddonTestUtils.promiseShutdownManager();
|
|
}
|
|
);
|
|
|
|
// This test verifies primeListener behavior for errors or ignored listeners.
|
|
add_task(async function test_background_primeListener_errors() {
|
|
await AddonTestUtils.promiseStartupManager();
|
|
|
|
// The internal APIs to shutdown the background work with any
|
|
// background, and in the shutdown case, events will be persisted
|
|
// and primed for a restart.
|
|
let extension = ExtensionTestUtils.loadExtension({
|
|
useAddonManager: "permanent",
|
|
background() {
|
|
// Listen for options being set so a restart will have them.
|
|
browser.test.onMessage.addListener(async (message, options) => {
|
|
if (message == "set-options") {
|
|
await browser.startupBlocking.testOptions(options);
|
|
browser.test.sendMessage("set-options:done");
|
|
}
|
|
});
|
|
let listener = arg => browser.test.sendMessage("triggered", arg);
|
|
browser.startupBlocking.onEvent1.addListener(listener, "triggered");
|
|
let listener2 = arg => browser.test.sendMessage("triggered", arg);
|
|
browser.startupBlocking.onEvent2.addListener(listener2, "triggered");
|
|
browser.test.sendMessage("bg_started");
|
|
},
|
|
});
|
|
await Promise.all([
|
|
promiseObservable("register-event-listener", 1),
|
|
extension.startup(),
|
|
]);
|
|
await extension.awaitMessage("bg_started");
|
|
assertPersistentListeners(extension, "startupBlocking", "onEvent1", {
|
|
primed: false,
|
|
});
|
|
|
|
// If an event is removed from an api, a permission is removed,
|
|
// or some other option prevents priming, ensure that
|
|
// primelistener works correctly.
|
|
// In this scenario we are testing that an event is not renewed
|
|
// on startup because the API does not re-prime it. The result
|
|
// is that the event is also not persisted. However the other
|
|
// events that are renewed should still be primed and persisted.
|
|
extension.sendMessage("set-options", {
|
|
eventName: "onEvent1",
|
|
ignoreListener: true,
|
|
});
|
|
await extension.awaitMessage("set-options:done");
|
|
|
|
// Shutdown the background page
|
|
await Promise.all([
|
|
promiseObservable("unregister-event-listener", 2),
|
|
extension.terminateBackground(),
|
|
]);
|
|
// startupBlocking.onEvent1 was not re-primed and should not be persisted, but
|
|
// onEvent2 should still be primed and persisted.
|
|
assertPersistentListeners(extension, "startupBlocking", "onEvent1", {
|
|
primed: false,
|
|
persisted: false,
|
|
});
|
|
assertPersistentListeners(extension, "startupBlocking", "onEvent2", {
|
|
primed: true,
|
|
});
|
|
|
|
info("Triggering persistent event to force the background page to start");
|
|
Services.obs.notifyObservers(
|
|
{ listenerArgs: 123 },
|
|
"fire-startupBlocking.onEvent2"
|
|
);
|
|
await extension.awaitMessage("bg_started");
|
|
equal(await extension.awaitMessage("triggered"), 123, "triggered event");
|
|
|
|
// On restart, test an exception, it should not be re-primed.
|
|
extension.sendMessage("set-options", {
|
|
eventName: "onEvent1",
|
|
throwError: "error",
|
|
});
|
|
await extension.awaitMessage("set-options:done");
|
|
|
|
// Shutdown the background page
|
|
await Promise.all([
|
|
promiseObservable("unregister-event-listener", 1),
|
|
extension.terminateBackground(),
|
|
]);
|
|
// startupBlocking.onEvent1 failed and should not be persisted
|
|
assertPersistentListeners(extension, "startupBlocking", "onEvent1", {
|
|
primed: false,
|
|
persisted: false,
|
|
});
|
|
|
|
info("Triggering event to verify background starts after prior error");
|
|
Services.obs.notifyObservers(
|
|
{ listenerArgs: 123 },
|
|
"fire-startupBlocking.onEvent2"
|
|
);
|
|
await extension.awaitMessage("bg_started");
|
|
equal(await extension.awaitMessage("triggered"), 123, "triggered event");
|
|
|
|
info("reset options for next test");
|
|
extension.sendMessage("set-options", {});
|
|
await extension.awaitMessage("set-options:done");
|
|
|
|
// Test errors on app restart
|
|
info("Test errors during app startup");
|
|
await AddonTestUtils.promiseRestartManager();
|
|
await extension.awaitStartup();
|
|
await extension.awaitMessage("bg_started");
|
|
|
|
info("restart AOM and verify primed listener");
|
|
await AddonTestUtils.promiseRestartManager({ earlyStartup: false });
|
|
await extension.awaitStartup();
|
|
assertPersistentListeners(extension, "startupBlocking", "onEvent1", {
|
|
primed: true,
|
|
persisted: true,
|
|
});
|
|
AddonTestUtils.notifyEarlyStartup();
|
|
|
|
Services.obs.notifyObservers(
|
|
{ listenerArgs: 123 },
|
|
"fire-startupBlocking.onEvent1"
|
|
);
|
|
await extension.awaitMessage("bg_started");
|
|
equal(await extension.awaitMessage("triggered"), 123, "triggered event");
|
|
|
|
// Test that an exception happening during priming clears the
|
|
// event from being persisted when restarting the browser, and that
|
|
// the background correctly starts.
|
|
info("test exception during primeListener on startup");
|
|
extension.sendMessage("set-options", {
|
|
eventName: "onEvent1",
|
|
throwError: "error",
|
|
});
|
|
await extension.awaitMessage("set-options:done");
|
|
|
|
await AddonTestUtils.promiseRestartManager({ earlyStartup: false });
|
|
await extension.awaitStartup();
|
|
AddonTestUtils.notifyEarlyStartup();
|
|
|
|
// At this point, the exception results in the persisted entry
|
|
// being cleared.
|
|
assertPersistentListeners(extension, "startupBlocking", "onEvent1", {
|
|
primed: false,
|
|
persisted: false,
|
|
});
|
|
|
|
AddonTestUtils.notifyLateStartup();
|
|
|
|
await extension.awaitMessage("bg_started");
|
|
|
|
// The background added the listener back during top level execution,
|
|
// verify it is in the persisted list.
|
|
assertPersistentListeners(extension, "startupBlocking", "onEvent1", {
|
|
primed: false,
|
|
persisted: true,
|
|
});
|
|
|
|
// reset options
|
|
extension.sendMessage("set-options", {});
|
|
await extension.awaitMessage("set-options:done");
|
|
await extension.unload();
|
|
await AddonTestUtils.promiseShutdownManager();
|
|
});
|
|
|
|
add_task(async function test_non_background_context_listener_not_persisted() {
|
|
await AddonTestUtils.promiseStartupManager();
|
|
|
|
let extension = ExtensionTestUtils.loadExtension({
|
|
useAddonManager: "permanent",
|
|
background() {
|
|
let listener = arg => browser.test.sendMessage("triggered", arg);
|
|
browser.startupBlocking.onEvent1.addListener(listener, "triggered");
|
|
browser.test.sendMessage(
|
|
"bg_started",
|
|
browser.runtime.getURL("extpage.html")
|
|
);
|
|
},
|
|
files: {
|
|
"extpage.html": `<script src="extpage.js"></script>`,
|
|
"extpage.js": function() {
|
|
let listener = arg =>
|
|
browser.test.sendMessage("extpage-triggered", arg);
|
|
browser.startupBlocking.onEvent2.addListener(
|
|
listener,
|
|
"extpage-triggered"
|
|
);
|
|
// Send a message to signal the extpage has registered the listener,
|
|
// after calling an async method and wait it to be resolved to make sure
|
|
// the addListener call to have been handled in the parent process by
|
|
// the time we will assert the persisted listeners.
|
|
browser.runtime.getPlatformInfo().then(() => {
|
|
browser.test.sendMessage("extpage_started");
|
|
});
|
|
},
|
|
},
|
|
});
|
|
|
|
await extension.startup();
|
|
const extpage_url = await extension.awaitMessage("bg_started");
|
|
|
|
assertPersistentListeners(extension, "startupBlocking", "onEvent1", {
|
|
persisted: true,
|
|
primed: false,
|
|
});
|
|
|
|
assertPersistentListeners(extension, "startupBlocking", "onEvent2", {
|
|
persisted: false,
|
|
});
|
|
|
|
const page = await ExtensionTestUtils.loadContentPage(extpage_url);
|
|
await extension.awaitMessage("extpage_started");
|
|
|
|
// Expect the onEvent2 listener subscribed by the extpage to not be persisted.
|
|
assertPersistentListeners(extension, "startupBlocking", "onEvent2", {
|
|
persisted: false,
|
|
});
|
|
|
|
await page.close();
|
|
await extension.unload();
|
|
await AddonTestUtils.promiseShutdownManager();
|
|
});
|
|
|
|
// Test support for event page tests
|
|
const background = async function() {
|
|
let listener2 = () =>
|
|
browser.test.sendMessage("triggered:non-startupblocking");
|
|
browser.startupBlocking.onEvent1.addListener(() => {});
|
|
browser.startupBlocking.nonBlockingEvent.addListener(() => {});
|
|
browser.nonStartupBlocking.onEvent2.addListener(listener2);
|
|
browser.test.sendMessage("bg_started");
|
|
};
|
|
|
|
const background_update = async function() {
|
|
browser.startupBlocking.onEvent1.addListener(() => {});
|
|
browser.nonStartupBlocking.onEvent2.addListener(() => {});
|
|
browser.test.sendMessage("updated_bg_started");
|
|
};
|
|
|
|
function testPersistentListeners(extension, expect) {
|
|
for (let [ns, event, persisted, primed] of expect) {
|
|
assertPersistentListeners(extension, ns, event, {
|
|
persisted,
|
|
primed,
|
|
});
|
|
}
|
|
}
|
|
|
|
add_task(
|
|
{ pref_set: [["extensions.eventPages.enabled", true]] },
|
|
async function test_startupblocking_behavior() {
|
|
await AddonTestUtils.promiseStartupManager();
|
|
|
|
let extension = ExtensionTestUtils.loadExtension({
|
|
useAddonManager: "permanent",
|
|
manifest: {
|
|
background: { persistent: false },
|
|
},
|
|
background,
|
|
});
|
|
|
|
await extension.startup();
|
|
await extension.awaitMessage("bg_started");
|
|
|
|
// All are persisted on startup
|
|
testPersistentListeners(extension, [
|
|
["startupBlocking", "onEvent1", true, false],
|
|
["startupBlocking", "nonBlockingEvent", true, false],
|
|
["nonStartupBlocking", "onEvent2", true, false],
|
|
]);
|
|
|
|
info("Test after mocked browser restart");
|
|
await Promise.all([
|
|
new Promise(resolve => extension.extension.once("shutdown", resolve)),
|
|
AddonTestUtils.promiseShutdownManager(),
|
|
]);
|
|
await AddonTestUtils.promiseStartupManager({ lateStartup: false });
|
|
await extension.awaitStartup();
|
|
|
|
testPersistentListeners(extension, [
|
|
// Startup blocking event is expected to be persisted and primed.
|
|
["startupBlocking", "onEvent1", true, true],
|
|
// A non-startup-blocking event shouldn't be primed yet.
|
|
["startupBlocking", "nonBlockingEvent", true, false],
|
|
// Non "Startup blocking" event is expected to be persisted but not primed yet.
|
|
["nonStartupBlocking", "onEvent2", true, false],
|
|
]);
|
|
|
|
// Complete the browser startup and fire the startup blocking event
|
|
// to let the backgrund script to run.
|
|
AddonTestUtils.notifyLateStartup();
|
|
Services.obs.notifyObservers({}, "fire-startupBlocking.onEvent1");
|
|
await extension.awaitMessage("bg_started");
|
|
|
|
info("Test after terminate background script");
|
|
await extension.terminateBackground();
|
|
|
|
// After the background is terminated, all are persisted and primed.
|
|
testPersistentListeners(extension, [
|
|
["startupBlocking", "onEvent1", true, true],
|
|
["startupBlocking", "nonBlockingEvent", true, true],
|
|
["nonStartupBlocking", "onEvent2", true, true],
|
|
]);
|
|
|
|
info("Notify event for the non-startupBlocking API event");
|
|
Services.obs.notifyObservers({}, "fire-nonStartupBlocking.onEvent2");
|
|
await extension.awaitMessage("bg_started");
|
|
await extension.awaitMessage("triggered:non-startupblocking");
|
|
|
|
await extension.unload();
|
|
await AddonTestUtils.promiseShutdownManager();
|
|
}
|
|
);
|
|
|
|
add_task(
|
|
{ pref_set: [["extensions.eventPages.enabled", true]] },
|
|
async function test_startupblocking_behavior_upgrade() {
|
|
let id = "persistent-upgrade@test";
|
|
await AddonTestUtils.promiseStartupManager();
|
|
|
|
let extensionData = {
|
|
useAddonManager: "permanent",
|
|
manifest: {
|
|
version: "1.0",
|
|
browser_specific_settings: {
|
|
gecko: { id },
|
|
},
|
|
background: { persistent: false },
|
|
},
|
|
background,
|
|
};
|
|
let extension = ExtensionTestUtils.loadExtension(extensionData);
|
|
await extension.startup();
|
|
await extension.awaitMessage("bg_started");
|
|
|
|
// All are persisted on startup
|
|
testPersistentListeners(extension, [
|
|
["startupBlocking", "onEvent1", true, false],
|
|
["startupBlocking", "nonBlockingEvent", true, false],
|
|
["nonStartupBlocking", "onEvent2", true, false],
|
|
]);
|
|
|
|
// Prepare the extension that will be updated.
|
|
extensionData.manifest.version = "2.0";
|
|
extensionData.background = background_update;
|
|
|
|
info("Test after a upgrade");
|
|
await extension.upgrade(extensionData);
|
|
// upgrade should start the background
|
|
await extension.awaitMessage("updated_bg_started");
|
|
|
|
// Nothing should be primed at this point after the background
|
|
// has started. We look specifically for nonBlockingEvent to
|
|
// no longer be a part of the persisted listeners.
|
|
testPersistentListeners(extension, [
|
|
["startupBlocking", "onEvent1", true, false],
|
|
["startupBlocking", "nonBlockingEvent", false, false],
|
|
["nonStartupBlocking", "onEvent2", true, false],
|
|
]);
|
|
|
|
await extension.unload();
|
|
await AddonTestUtils.promiseShutdownManager();
|
|
}
|
|
);
|
|
|
|
add_task(
|
|
{ pref_set: [["extensions.eventPages.enabled", true]] },
|
|
async function test_startupblocking_behavior_staged_upgrade() {
|
|
AddonManager.checkUpdateSecurity = false;
|
|
let id = "persistent-staged-upgrade@test";
|
|
|
|
// register an update file.
|
|
AddonTestUtils.registerJSON(server, "/test_update.json", {
|
|
addons: {
|
|
[id]: {
|
|
updates: [
|
|
{
|
|
version: "2.0",
|
|
update_link:
|
|
"http://example.com/addons/test_settings_staged_restart.xpi",
|
|
},
|
|
],
|
|
},
|
|
},
|
|
});
|
|
|
|
let extensionData = {
|
|
useAddonManager: "permanent",
|
|
manifest: {
|
|
version: "2.0",
|
|
browser_specific_settings: {
|
|
gecko: { id, update_url: `http://example.com/test_update.json` },
|
|
},
|
|
background: { persistent: false },
|
|
},
|
|
background: background_update,
|
|
};
|
|
|
|
// Prepare the update first.
|
|
server.registerFile(
|
|
`/addons/test_settings_staged_restart.xpi`,
|
|
AddonTestUtils.createTempWebExtensionFile(extensionData)
|
|
);
|
|
|
|
// Prepare the extension that will be updated.
|
|
extensionData.manifest.version = "1.0";
|
|
extensionData.background = async function() {
|
|
// we're testing persistence, not operation, so no action in listeners.
|
|
browser.startupBlocking.onEvent1.addListener(() => {});
|
|
// nonBlockingEvent will be removed on upgrade
|
|
browser.startupBlocking.nonBlockingEvent.addListener(() => {});
|
|
browser.nonStartupBlocking.onEvent2.addListener(() => {});
|
|
|
|
// Force a staged updated.
|
|
browser.runtime.onUpdateAvailable.addListener(async details => {
|
|
// This should be the version of the pending update.
|
|
browser.test.assertEq("2.0", details.version, "correct version");
|
|
browser.test.sendMessage("delay");
|
|
});
|
|
|
|
browser.test.sendMessage("bg_started");
|
|
};
|
|
|
|
await AddonTestUtils.promiseStartupManager();
|
|
let extension = ExtensionTestUtils.loadExtension(extensionData);
|
|
|
|
await extension.startup();
|
|
await extension.awaitMessage("bg_started");
|
|
|
|
// All are persisted but not primed on startup
|
|
testPersistentListeners(extension, [
|
|
["startupBlocking", "onEvent1", true, false],
|
|
["startupBlocking", "nonBlockingEvent", true, false],
|
|
["nonStartupBlocking", "onEvent2", true, false],
|
|
]);
|
|
|
|
info("Test after a staged update");
|
|
// first, deal with getting and staging an upgrade
|
|
let addon = await AddonManager.getAddonByID(id);
|
|
Assert.equal(addon.version, "1.0", "1.0 is loaded");
|
|
|
|
let update = await AddonTestUtils.promiseFindAddonUpdates(addon);
|
|
let install = update.updateAvailable;
|
|
Assert.ok(install, `install is available ${update.error}`);
|
|
|
|
await AddonTestUtils.promiseCompleteAllInstalls([install]);
|
|
|
|
Assert.equal(
|
|
install.state,
|
|
AddonManager.STATE_POSTPONED,
|
|
"update is staged for install"
|
|
);
|
|
await extension.awaitMessage("delay");
|
|
|
|
await AddonTestUtils.promiseShutdownManager();
|
|
|
|
// restarting allows upgrade to proceed
|
|
await AddonTestUtils.promiseStartupManager();
|
|
// upgrade should always start the background
|
|
await extension.awaitMessage("updated_bg_started");
|
|
|
|
// Since this is an upgraded addon, the background will have started
|
|
// and we no longer have primed listeners. Check only the persisted
|
|
// values, and that nonBlockingEvent is not persisted.
|
|
testPersistentListeners(extension, [
|
|
["startupBlocking", "onEvent1", true, false],
|
|
["startupBlocking", "nonBlockingEvent", false, false],
|
|
["nonStartupBlocking", "onEvent2", true, false],
|
|
]);
|
|
|
|
await extension.unload();
|
|
await AddonTestUtils.promiseShutdownManager();
|
|
}
|
|
);
|