// testharness file with ShadowRealm utilities to be imported in the realm // hosting the ShadowRealm /** * Convenience function for evaluating some async code in the ShadowRealm and * waiting for the result. * * In case of error, this function intentionally exposes the stack trace (if it * is available) to the hosting realm, for debugging purposes. * * @param {ShadowRealm} realm - the ShadowRealm to evaluate the code in * @param {string} asyncBody - the code to evaluate; will be put in the body of * an async function, and must return a value explicitly if a value is to be * returned to the hosting realm. */ globalThis.shadowRealmEvalAsync = function (realm, asyncBody) { return new Promise(realm.evaluate(` (resolve, reject) => { (async () => { ${asyncBody} })().then(resolve, (e) => reject(e.toString() + "\\n" + (e.stack || ""))); } `)); }; /** * Convenience adaptor function for fetch() that can be passed to * setShadowRealmGlobalProperties() (see testharness-shadowrealm-inner.js). * Used to adapt the hosting realm's fetch(), if present, to fetch a resource * and pass its text through the callable boundary to the ShadowRealm. */ globalThis.fetchAdaptor = (resource) => (resolve, reject) => { fetch(resource) .then(res => res.text()) .then(resolve, (e) => reject(e.toString())); }; let workerMessagePortPromise; /** * Used when the hosting realm is a worker. This value is a Promise that * resolves to a function that posts a message to the worker's message port, * just like postMessage(). The message port is only available asynchronously in * SharedWorkers and ServiceWorkers. */ globalThis.getPostMessageFunc = async function () { if (typeof postMessage === "function") { return postMessage; // postMessage available directly in dedicated worker } if (workerMessagePortPromise) { return await workerMessagePortPromise; } throw new Error("getPostMessageFunc is intended for Worker scopes"); } // Port available asynchronously in shared worker, but not via an async func let savedResolver; if (globalThis.constructor.name === "SharedWorkerGlobalScope") { workerMessagePortPromise = new Promise((resolve) => { savedResolver = resolve; }); addEventListener("connect", function (event) { const port = event.ports[0]; savedResolver(port.postMessage.bind(port)); }); } else if (globalThis.constructor.name === "ServiceWorkerGlobalScope") { workerMessagePortPromise = new Promise((resolve) => { savedResolver = resolve; }); addEventListener("message", (e) => { if (typeof e.data === "object" && e.data !== null && e.data.type === "connect") { const client = e.source; savedResolver(client.postMessage.bind(client)); } }); } /** * Used when the hosting realm does not permit dynamic import, e.g. in * ServiceWorkers or AudioWorklets. Requires an adaptor function such as * fetchAdaptor() above, or an equivalent if fetch() is not present in the * hosting realm. * * @param {ShadowRealm} realm - the ShadowRealm in which to setup a * fakeDynamicImport() global function. * @param {function} adaptor - an adaptor function that does what fetchAdaptor() * does. */ globalThis.setupFakeDynamicImportInShadowRealm = function(realm, adaptor) { function fetchModuleTextExecutor(url) { return (resolve, reject) => { new Promise(adaptor(url)) .then(text => realm.evaluate(text + ";\nundefined")) .then(resolve, (e) => reject(e.toString())); } } realm.evaluate(` (fetchModuleTextExecutor) => { globalThis.fakeDynamicImport = function (url) { return new Promise(fetchModuleTextExecutor(url)); } } `)(fetchModuleTextExecutor); }; /** * Used when the hosting realm does not expose fetch(), i.e. in worklets. The * port on the other side of the channel needs to send messages starting with * 'fetchRequest::' and listen for messages starting with 'fetchResult::'. See * testharness-shadowrealm-audioworkletprocessor.js. * * @param {port} MessagePort - the message port on which to listen for fetch * requests */ globalThis.setupFakeFetchOverMessagePort = function (port) { port.addEventListener("message", (event) => { if (typeof event.data !== "string" || !event.data.startsWith("fetchRequest::")) { return; } fetch(event.data.slice("fetchRequest::".length)) .then(res => res.text()) .then( text => port.postMessage(`fetchResult::success::${text}`), error => port.postMessage(`fetchResult::fail::${error}`), ); }); port.start(); } /** * Returns a message suitable for posting with postMessage() that will signal to * the test harness that the tests are finished and there was an error in the * setup code. * * @param {message} any - error */ globalThis.createSetupErrorResult = function (message) { return { type: "complete", tests: [], asserts: [], status: { status: 1, // TestsStatus.ERROR, message: String(message), stack: typeof message === "object" && message !== null && "stack" in message ? message.stack : undefined, }, }; };