/* Any copyright is dedicated to the Public Domain. * http://creativecommons.org/publicdomain/zero/1.0/ */ "use strict"; const { PromiseUtils } = ChromeUtils.importESModule( "resource://gre/modules/PromiseUtils.sys.mjs" ); 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 = ""; } 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); }