Bug 1248846: [webext] Test that event callbacks and promises do not fire later than expected. r=aswan

MozReview-Commit-ID: 4fpHc22txy

--HG--
extra : rebase_source : 24ddf22e1f273f753adcd7962f9262919e30b69c
This commit is contained in:
Kris Maglione 2016-04-05 08:59:47 -07:00
parent 491d7fb118
commit 5ddfedd1cc
3 changed files with 133 additions and 3 deletions

View File

@ -616,7 +616,7 @@ EventManager.prototype = {
for (let callback of this.callbacks) {
Promise.resolve(callback).then(callback => {
if (this.context.unloaded) {
dump(`${this.name} event fired after context unloaded.`);
dump(`${this.name} event fired after context unloaded.\n`);
} else if (this.callbacks.has(callback)) {
this.context.runSafe(callback, ...args);
}
@ -634,7 +634,7 @@ EventManager.prototype = {
if (this.callbacks.size) {
this.unregister();
}
this.callbacks = null;
this.callbacks = Object.freeze([]);
},
api() {
@ -661,7 +661,7 @@ SingletonEventManager.prototype = {
addListener(callback, ...args) {
let wrappedCallback = (...args) => {
if (this.context.unloaded) {
dump(`${this.name} event fired after context unloaded.`);
dump(`${this.name} event fired after context unloaded.\n`);
} else if (this.unregister.has(callback)) {
return callback(...args);
}

View File

@ -0,0 +1,129 @@
"use strict";
const global = this;
Cu.import("resource://gre/modules/Timer.jsm");
Cu.import("resource://gre/modules/ExtensionUtils.jsm");
var {
BaseContext,
EventManager,
SingletonEventManager,
} = ExtensionUtils;
class StubContext extends BaseContext {
constructor() {
super();
this.sandbox = new Cu.Sandbox(global);
}
get cloneScope() {
return this. sandbox;
}
get extension() {
return {id: "test@web.extension"};
}
}
add_task(function* test_post_unload_promises() {
let context = new StubContext();
let fail = result => {
ok(false, `Unexpected callback: ${result}`);
};
// Make sure promises resolve normally prior to unload.
let promises = [
context.wrapPromise(Promise.resolve()),
context.wrapPromise(Promise.reject({message: ""})).catch(() => {}),
];
yield Promise.all(promises);
// Make sure promises that resolve after unload do not trigger
// resolution handlers.
context.wrapPromise(Promise.resolve("resolved"))
.then(fail);
context.wrapPromise(Promise.reject({message: "rejected"}))
.then(fail, fail);
context.unload();
// The `setTimeout` ensures that we return to the event loop after
// promise resolution, which means we're guaranteed to return after
// any micro-tasks that get enqueued by the resolution handlers above.
yield new Promise(resolve => setTimeout(resolve, 0));
});
add_task(function* test_post_unload_listeners() {
let context = new StubContext();
let fireEvent;
let onEvent = new EventManager(context, "onEvent", fire => {
fireEvent = fire;
return () => {};
});
let fireSingleton;
let onSingleton = new SingletonEventManager(context, "onSingleton", callback => {
fireSingleton = () => {
Promise.resolve().then(callback);
};
return () => {};
});
let fail = event => {
ok(false, `Unexpected event: ${event}`);
};
// Check that event listeners aren't called after they've been removed.
onEvent.addListener(fail);
onSingleton.addListener(fail);
let promises = [
new Promise(resolve => onEvent.addListener(resolve)),
new Promise(resolve => onSingleton.addListener(resolve)),
];
fireEvent("onEvent");
fireSingleton("onSingleton");
// Both `fireEvent` calls are dispatched asynchronously, so they won't
// have fired by this point. The `fail` listeners that we remove now
// should not be called, even though the events have already been
// enqueued.
onEvent.removeListener(fail);
onSingleton.removeListener(fail);
// Wait for the remaining listeners to be called, which should always
// happen after the `fail` listeners would normally be called.
yield Promise.all(promises);
// Check that event listeners aren't called after the context has
// unloaded.
onEvent.addListener(fail);
onSingleton.addListener(fail);
// The EventManager `fire` callback always dispatches events
// asynchronously, so we need to test that any pending event callbacks
// aren't fired after the context unloads. We also need to test that
// any `fire` calls that happen *after* the context is unloaded also
// do not trigger callbacks.
fireEvent("onEvent");
Promise.resolve("onEvent").then(fireEvent);
fireSingleton("onSingleton");
Promise.resolve("onSingleton").then(fireSingleton);
context.unload();
// The `setTimeout` ensures that we return to the event loop after
// promise resolution, which means we're guaranteed to return after
// any micro-tasks that get enqueued by the resolution handlers above.
yield new Promise(resolve => setTimeout(resolve, 0));
});

View File

@ -6,5 +6,6 @@ skip-if = toolkit == 'gonk'
[test_locale_data.js]
[test_locale_converter.js]
[test_ext_contexts.js]
[test_ext_schemas.js]
[test_getAPILevelForWindow.js]