Skip to content

Commit

Permalink
Add 'shadowrealm-in-audioworklet' 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-audioworklet.html variant.

The wrapper here is similar to the one for ServiceWorkers, since dynamic
import() is also forbidden in worklet scopes. But additionally fetch() is
not exposed, so we add a utility function to set up the ability to call
the window realm's fetch() through the AudioWorklet's message port.

We also add /resources/testharness-shadowrealm-audioworkletprocessor.js to
contain most of the AudioWorklet setup boilerplate, so that it isn't
written inline in serve.py.

Note '.https.' needs to be added to the test path.
  • Loading branch information
ptomato committed Nov 18, 2024
1 parent 59367bb commit 60d6c48
Show file tree
Hide file tree
Showing 5 changed files with 145 additions and 0 deletions.
3 changes: 3 additions & 0 deletions docs/writing-tests/testharness.md
Original file line number Diff line number Diff line change
Expand Up @@ -182,6 +182,9 @@ are:
* `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-in-audioworklet`: runs the test code in a ShadowRealm context
hosted in an AudioWorklet processor; to be run at
<code><var>x</var>.https.any.shadowrealm-in-audioworklet.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
52 changes: 52 additions & 0 deletions resources/testharness-shadowrealm-audioworkletprocessor.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
/**
* AudioWorkletProcessor intended for hosting a ShadowRealm and running a test
* inside of that ShadowRealm.
*/
globalThis.TestRunner = class TestRunner extends AudioWorkletProcessor {
constructor() {
super();
this.createShadowRealmAndStartTests();
}

/**
* Fetch adaptor function intended as a drop-in replacement for fetchAdaptor()
* (see testharness-shadowrealm-outer.js), but it does not assume fetch() is
* present in the realm. Instead, it relies on setupFakeFetchOverMessagePort()
* having been called on the port on the other side of this.port's channel.
*/
fetchOverPortExecutor(resource) {
return (resolve, reject) => {
const listener = (event) => {
if (typeof event.data !== "string" || !event.data.startsWith("fetchResult::")) {
return;
}

const result = event.data.slice("fetchResult::".length);
if (result.startsWith("success::")) {
resolve(result.slice("success::".length));
} else {
reject(result.slice("fail::".length));
}

this.port.removeEventListener("message", listener);
}
this.port.addEventListener("message", listener);
this.port.start();
this.port.postMessage(`fetchRequest::${resource}`);
}
}

/**
* Async method, which is patched over in
* (test).any.audioworklet-shadowrealm.js; see serve.py
*/
async createShadowRealmAndStartTests() {
throw new Error("Forgot to overwrite this method!");
}

/** Overrides AudioWorkletProcessor.prototype.process() */
process() {
return false;
}
};
registerProcessor("test-runner", TestRunner);
25 changes: 25 additions & 0 deletions resources/testharness-shadowrealm-outer.js
Original file line number Diff line number Diff line change
Expand Up @@ -100,3 +100,28 @@ globalThis.setupFakeDynamicImportInShadowRealm = function(realm, adaptor) {
}
`)(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();
}
5 changes: 5 additions & 0 deletions tools/manifest/sourcefile.py
Original file line number Diff line number Diff line change
Expand Up @@ -93,12 +93,17 @@ class VariantData(TypedDict, total=False):
"force_https": True,
"suffix": ".https.any.shadowrealm-in-serviceworker.html",
},
"shadowrealm-in-audioworklet": {
"force_https": True,
"suffix": ".https.any.shadowrealm-in-audioworklet.html",
},
"shadowrealm": {"longhand": {
"shadowrealm-in-window",
"shadowrealm-in-shadowrealm",
"shadowrealm-in-dedicatedworker",
"shadowrealm-in-sharedworker",
"shadowrealm-in-serviceworker",
"shadowrealm-in-audioworklet",
}},
"jsshell": {"suffix": ".any.js"},
}
Expand Down
60 changes: 60 additions & 0 deletions tools/serve/serve.py
Original file line number Diff line number Diff line change
Expand Up @@ -535,6 +535,33 @@ class ShadowRealmInServiceWorkerHandler(ServiceWorkersHandler):
".any.serviceworker-shadowrealm.js")]


class ShadowRealmInAudioWorkletHandler(HtmlWrapperHandler):
global_type = "shadowrealm-in-audioworklet"
path_replace = [(".https.any.shadowrealm-in-audioworklet.html", ".any.js",
".any.audioworklet-shadowrealm.js")]

wrapper = """<!doctype html>
<meta charset=utf-8>
%(meta)s
<script src="/resources/testharness.js"></script>
<script src="/resources/testharnessreport.js"></script>
<script src="/resources/testharness-shadowrealm-outer.js"></script>
<script>
(async function() {
const context = new AudioContext();
await context.audioWorklet.addModule(
"/resources/testharness-shadowrealm-outer.js");
await context.audioWorklet.addModule(
"/resources/testharness-shadowrealm-audioworkletprocessor.js");
await context.audioWorklet.addModule("%(path)s%(query)s");
const node = new AudioWorkletNode(context, "test-runner");
setupFakeFetchOverMessagePort(node.port);
fetch_tests_from_worker(node.port);
})();
</script>
"""


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

Expand Down Expand Up @@ -654,6 +681,37 @@ def _create_script_import(self, attribute):
return 'await fakeDynamicImport("%s");' % attribute


class ShadowRealmAudioWorkletWrapperHandler(BaseWorkerHandler):
path_replace = [(".any.audioworklet-shadowrealm.js", ".any.js")]
wrapper = """%(meta)s
TestRunner.prototype.createShadowRealmAndStartTests = async function() {
const queryPart = import.meta.url.split('?')[1];
const locationSearch = queryPart ? '?' + queryPart : '';
const r = new ShadowRealm();
const adaptor = this.fetchOverPortExecutor.bind(this);
setupFakeDynamicImportInShadowRealm(r, adaptor);
await shadowRealmEvalAsync(r, `
await fakeDynamicImport("/resources/testharness-shadowrealm-inner.js");
await fakeDynamicImport("/resources/testharness.js");
`);
r.evaluate("setShadowRealmGlobalProperties")(locationSearch, adaptor);
await shadowRealmEvalAsync(r, `
%(script)s
await fakeDynamicImport("%(path)s");
`);
const forwardMessage = (msgJSON) =>
this.port.postMessage(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 @@ -715,11 +773,13 @@ def add_mount_point(self, url_base, path):
("GET", "*.any.shadowrealm-in-dedicatedworker.html", ShadowRealmInDedicatedWorkerHandler),
("GET", "*.any.shadowrealm-in-sharedworker.html", ShadowRealmInSharedWorkerHandler),
("GET", "*.any.shadowrealm-in-serviceworker.html", ShadowRealmInServiceWorkerHandler),
("GET", "*.any.shadowrealm-in-audioworklet.html", ShadowRealmInAudioWorkletHandler),
("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", "*.any.audioworklet-shadowrealm.js", ShadowRealmAudioWorkletWrapperHandler),
("GET", "*.asis", handlers.AsIsHandler),
("*", "/.well-known/attribution-reporting/report-event-attribution", handlers.PythonScriptHandler),
("*", "/.well-known/attribution-reporting/debug/report-event-attribution", handlers.PythonScriptHandler),
Expand Down

0 comments on commit 60d6c48

Please sign in to comment.