Source code
Revision control
Copy as Markdown
Other Tools
/* Any copyright is dedicated to the Public Domain.
"use strict";
var { AsyncShutdown } = ChromeUtils.importESModule(
"resource://gre/modules/AsyncShutdown.sys.mjs"
);
var asyncShutdownService = Cc[
"@mozilla.org/async-shutdown-service;1"
].getService(Ci.nsIAsyncShutdownService);
Services.prefs.setBoolPref("toolkit.asyncshutdown.testing", true);
/**
* Utility function used to provide the same API for various sources
* of async shutdown barriers.
*
* @param {string} kind One of
* - "phase" to test an AsyncShutdown phase;
* - "barrier" to test an instance of AsyncShutdown.Barrier;
* - "xpcom-barrier" to test an instance of nsIAsyncShutdownBarrier;
* - "xpcom-barrier-unwrapped" to test the field `jsclient` of a nsIAsyncShutdownClient.
*
* @return An object with the following methods:
* - addBlocker() - the same method as AsyncShutdown phases and barrier clients
* - wait() - trigger the resolution of the lock
*/
function makeLock(kind) {
if (kind == "phase") {
let topic = "test-Phase-" + ++makeLock.counter;
let phase = AsyncShutdown._getPhase(topic);
return {
addBlocker(...args) {
return phase.addBlocker(...args);
},
removeBlocker(blocker) {
return phase.removeBlocker(blocker);
},
wait() {
Services.obs.notifyObservers(null, topic);
return Promise.resolve();
},
get isClosed() {
return phase.isClosed;
},
};
} else if (kind == "barrier") {
let name = "test-Barrier-" + ++makeLock.counter;
let barrier = new AsyncShutdown.Barrier(name);
return {
addBlocker: barrier.client.addBlocker,
removeBlocker: barrier.client.removeBlocker,
wait() {
return barrier.wait();
},
get isClosed() {
return barrier.client.isClosed;
},
};
} else if (kind == "xpcom-barrier") {
let name = "test-xpcom-Barrier-" + ++makeLock.counter;
let barrier = asyncShutdownService.makeBarrier(name);
return {
addBlocker(blockerName, condition, state) {
if (condition == null) {
// Slight trick as `null` or `undefined` cannot be used as keys
// for `xpcomMap`. Note that this has no incidence on the result
// of the test as the XPCOM interface imposes that the condition
// is a method, so it cannot be `null`/`undefined`.
condition = "<this case can't happen with the xpcom interface>";
}
let blocker = makeLock.xpcomMap.get(condition);
if (!blocker) {
blocker = {
name: blockerName,
state,
blockShutdown(aBarrierClient) {
return (async function () {
try {
if (typeof condition == "function") {
await Promise.resolve(condition());
} else {
await Promise.resolve(condition);
}
} finally {
aBarrierClient.removeBlocker(blocker);
}
})();
},
};
makeLock.xpcomMap.set(condition, blocker);
}
let { fileName, lineNumber, stack } = new Error();
return barrier.client.addBlocker(blocker, fileName, lineNumber, stack);
},
removeBlocker(condition) {
let blocker = makeLock.xpcomMap.get(condition);
if (!blocker) {
return;
}
barrier.client.removeBlocker(blocker);
},
wait() {
return new Promise(resolve => {
barrier.wait(resolve);
});
},
get isClosed() {
return barrier.client.isClosed;
},
};
} else if ("unwrapped-xpcom-barrier") {
let name = "unwrapped-xpcom-barrier-" + ++makeLock.counter;
let barrier = asyncShutdownService.makeBarrier(name);
let client = barrier.client.jsclient;
return {
addBlocker: client.addBlocker,
removeBlocker: client.removeBlocker,
wait() {
return new Promise(resolve => {
barrier.wait(resolve);
});
},
get isClosed() {
return client.isClosed;
},
};
}
throw new TypeError("Unknown kind " + kind);
}
makeLock.counter = 0;
makeLock.xpcomMap = new Map(); // Note: Not a WeakMap as we wish to handle non-gc-able keys (e.g. strings)
/**
* An asynchronous task that takes several ticks to complete.
*
* @param {*=} resolution The value with which the resulting promise will be
* resolved once the task is complete. This may be a rejected promise,
* in which case the resulting promise will itself be rejected.
* @param {object=} outResult An object modified by side-effect during the task.
* Initially, its field |isFinished| is set to |false|. Once the task is
* complete, its field |isFinished| is set to |true|.
*
* @return {promise} A promise fulfilled once the task is complete
*/
function longRunningAsyncTask(resolution = undefined, outResult = {}) {
outResult.isFinished = false;
if (!("countFinished" in outResult)) {
outResult.countFinished = 0;
}
return new Promise(resolve => {
do_timeout(100, function () {
++outResult.countFinished;
outResult.isFinished = true;
resolve(resolution);
});
});
}
function get_exn(f) {
try {
f();
return null;
} catch (ex) {
return ex;
}
}
function do_check_exn(exn, constructor) {
Assert.notEqual(exn, null);
if (exn.name == constructor) {
Assert.equal(exn.constructor.name, constructor);
return;
}
info("Wrong error constructor");
info(exn.constructor.name);
info(exn.stack);
Assert.ok(false);
}