mirror of
https://github.com/mozilla/gecko-dev.git
synced 2024-10-17 07:15:46 +00:00
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:
parent
491d7fb118
commit
5ddfedd1cc
@ -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);
|
||||
}
|
||||
|
129
toolkit/components/extensions/test/xpcshell/test_ext_contexts.js
Normal file
129
toolkit/components/extensions/test/xpcshell/test_ext_contexts.js
Normal 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));
|
||||
});
|
@ -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]
|
Loading…
Reference in New Issue
Block a user