Skip to content

Commit

Permalink
Add 'shadowrealm-in-serviceworker' global
Browse files Browse the repository at this point in the history
This will add to any test with global=shadowrealm in its metadata, an
.any.shadowrealm-in-serviceworker.html variant.

We have to use a slightly different .any.serviceworker-shadowrealm.js
wrapper from the wrapper used for the other types of workers, because
dynamic import() is forbidden in ServiceWorker scopes. Instead, add a
utility function to set up a fakeDynamicImport() function inside the
ShadowRealm which uses the fetch adaptor to get the module's source text
and evaluate it in the shadowRealm.

Also add a case for ServiceWorkers to getPostMessageFunc(), which returns
a postMessage() drop-in replacement that broadcasts the message to all
clients, since test result messages from the ShadowRealm are not in
response to any particular message received by the ServiceWorker.

Note '.https.' needs to be added to the test path.
  • Loading branch information
ptomato committed Nov 18, 2024
1 parent 42160ae commit 59367bb
Show file tree
Hide file tree
Showing 4 changed files with 88 additions and 1 deletion.
3 changes: 3 additions & 0 deletions docs/writing-tests/testharness.md
Original file line number Diff line number Diff line change
Expand Up @@ -179,6 +179,9 @@ are:
* `shadowrealm-in-sharedworker`: runs the test code in a ShadowRealm context
hosted in a shared worker; to be run at
<code><var>x</var>.any.shadowrealm-in-sharedworker.html</code>
* `shadowrealm-in-serviceworker`: runs the test code in a ShadowRealm context
hosted in a service worker; to be run at
<code><var>x</var>.https.any.shadowrealm-in-serviceworker.html</code>
* `shadowrealm`: shorthand for all of the ShadowRealm scopes

To check what scope your test is run from, you can use the following methods that will
Expand Down
40 changes: 39 additions & 1 deletion resources/testharness-shadowrealm-outer.js
Original file line number Diff line number Diff line change
Expand Up @@ -37,13 +37,22 @@ let sharedWorkerMessagePortPromise;
* 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.
* SharedWorkers and ServiceWorkers.
*/
globalThis.getPostMessageFunc = async function () {
if (typeof postMessage === "function") {
return postMessage; // postMessage available directly in dedicated worker
}

if (typeof clients === "object") {
// Messages from the ShadowRealm are not in response to any message received
// from the ServiceWorker's client, so broadcast them to all clients
const allClients = await clients.matchAll({ includeUncontrolled: true });
return function broadcast(msg) {
allClients.map(client => client.postMessage(msg));
}
}

if (sharedWorkerMessagePortPromise) {
return await sharedWorkerMessagePortPromise;
}
Expand All @@ -62,3 +71,32 @@ if (globalThis.constructor.name === "SharedWorkerGlobalScope") {
savedResolver(port.postMessage.bind(port));
});
}

/**
* 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);
};
5 changes: 5 additions & 0 deletions tools/manifest/sourcefile.py
Original file line number Diff line number Diff line change
Expand Up @@ -89,11 +89,16 @@ class VariantData(TypedDict, total=False):
"shadowrealm-in-shadowrealm": {},
"shadowrealm-in-dedicatedworker": {},
"shadowrealm-in-sharedworker": {},
"shadowrealm-in-serviceworker": {
"force_https": True,
"suffix": ".https.any.shadowrealm-in-serviceworker.html",
},
"shadowrealm": {"longhand": {
"shadowrealm-in-window",
"shadowrealm-in-shadowrealm",
"shadowrealm-in-dedicatedworker",
"shadowrealm-in-sharedworker",
"shadowrealm-in-serviceworker",
}},
"jsshell": {"suffix": ".any.js"},
}
Expand Down
41 changes: 41 additions & 0 deletions tools/serve/serve.py
Original file line number Diff line number Diff line change
Expand Up @@ -528,6 +528,13 @@ class ShadowRealmInSharedWorkerHandler(SharedWorkersHandler):
".any.worker-shadowrealm.js")]


class ShadowRealmInServiceWorkerHandler(ServiceWorkersHandler):
global_type = "shadowrealm-in-serviceworker"
path_replace = [(".https.any.shadowrealm-in-serviceworker.html",
".any.js",
".any.serviceworker-shadowrealm.js")]


class BaseWorkerHandler(WrapperHandler):
headers = [('Content-Type', 'text/javascript')]

Expand Down Expand Up @@ -615,6 +622,38 @@ def _create_script_import(self, attribute):
return 'await import("%s");' % attribute


class ShadowRealmServiceWorkerWrapperHandler(BaseWorkerHandler):
path_replace = [(".any.serviceworker-shadowrealm.js", ".any.js")]
wrapper = """%(meta)s
importScripts("/resources/testharness-shadowrealm-outer.js");
(async function () {
const r = new ShadowRealm();
setupFakeDynamicImportInShadowRealm(r, fetchAdaptor);
await shadowRealmEvalAsync(r, `
await fakeDynamicImport("/resources/testharness-shadowrealm-inner.js");
await fakeDynamicImport("/resources/testharness.js");
`);
r.evaluate("setShadowRealmGlobalProperties")("%(query)s", fetchAdaptor);
await shadowRealmEvalAsync(r, `
%(script)s
await fakeDynamicImport("%(path)s");
`);
const postMessageFunc = await getPostMessageFunc();
function forwardMessage(msgJSON) {
postMessageFunc(JSON.parse(msgJSON));
}
r.evaluate("begin_shadow_realm_tests")(forwardMessage);
})();
"""

def _create_script_import(self, attribute):
return 'await fakeDynamicImport("%s");' % attribute


rewrites = [("GET", "/resources/WebIDLParser.js", "/resources/webidl2/lib/webidl2.js")]


Expand Down Expand Up @@ -675,9 +714,11 @@ def add_mount_point(self, url_base, path):
("GET", "*.any.shadowrealm-in-shadowrealm.html", ShadowRealmInShadowRealmHandler),
("GET", "*.any.shadowrealm-in-dedicatedworker.html", ShadowRealmInDedicatedWorkerHandler),
("GET", "*.any.shadowrealm-in-sharedworker.html", ShadowRealmInSharedWorkerHandler),
("GET", "*.any.shadowrealm-in-serviceworker.html", ShadowRealmInServiceWorkerHandler),
("GET", "*.any.window-module.html", WindowModulesHandler),
("GET", "*.any.worker.js", ClassicWorkerHandler),
("GET", "*.any.worker-module.js", ModuleWorkerHandler),
("GET", "*.any.serviceworker-shadowrealm.js", ShadowRealmServiceWorkerWrapperHandler),
("GET", "*.any.worker-shadowrealm.js", ShadowRealmWorkerWrapperHandler),
("GET", "*.asis", handlers.AsIsHandler),
("*", "/.well-known/attribution-reporting/report-event-attribution", handlers.PythonScriptHandler),
Expand Down

0 comments on commit 59367bb

Please sign in to comment.