Source code

Revision control

Copy as Markdown

Other Tools

Test Info:

<!DOCTYPE html>
<html>
<!--
-->
<head>
<title>Basic structured clone tests</title>
<script src="/tests/SimpleTest/SimpleTest.js"></script>
<link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
</head>
<body>
<p id="display">
<iframe id="frame"></iframe>
<image href="four-colors.png" height="320" width="240"/>
</p>
<div id="content" style="display: none"></div>
<pre id="test">
<script class="testbody" type="application/javascript">
SimpleTest.waitForExplicitFinish();
class WebIDLGlobal {
globalNames;
resolve;
#otherSide;
constructor(globalNames, initSides) {
this.globalNames = globalNames;
let global = this;
this.#otherSide = initSides().then(([ourSide, otherSide, cleanup]) => {
if (cleanup) {
SimpleTest.registerCleanupFunction(cleanup);
}
ourSide.addEventListener("message", ({ data }) => {
global.resultReceived(data);
});
return otherSide;
});
}
get otherSide() {
return this.#otherSide;
}
waitForResult() {
ok(!this.resolve, "Still waiting on previous message?");
return new Promise(resolve => {
this.resolve = resolve;
});
}
resultReceived(value) {
this.resolve(value);
this.resolve = undefined;
}
}
// Init functions return an array containing in order the object to use for
// sending a messages to the other global, the object to use to listen for
// messages from the other global and optioanlly a cleanup function.
function waitForInitMessage(target) {
return new Promise(resolve => {
target.addEventListener("message", resolve, { once: true });
});
}
function getModuleURL(additionalScript = "") {
return new URL(
`file_structuredCloneAndExposed.sjs?additionalScript=${encodeURIComponent(
additionalScript
)}`,
location.href
);
}
function initFrame() {
let callInstallListeners = `
installListeners(globalThis, parent);
`;
frame.srcdoc =
`<script src="${getModuleURL(callInstallListeners)}" type="module"></` +
`script>`;
return waitForInitMessage(self).then(() => [self, frame.contentWindow]);
}
function initDedicatedWorker() {
let callInstallListeners = `
installListeners(globalThis, globalThis);
`;
const worker = new Worker(getModuleURL(callInstallListeners), {
type: "module",
});
return waitForInitMessage(worker).then(() => [worker, worker]);
}
function initSharedWorker() {
let callInstallListeners = `
self.addEventListener("connect", (e) => {
const port = e.ports[0];
port.start();
installListeners(port, port);
});
`;
const worker = new SharedWorker(getModuleURL(callInstallListeners), {
type: "module",
});
worker.port.start();
return waitForInitMessage(worker.port).then(() => [worker.port, worker.port]);
}
function initServiceWorker() {
let callInstallListeners = `
addEventListener("message", ({ source: client }) => {
installListeners(globalThis, client);
}, { once: true });
`;
return navigator.serviceWorker
.register(getModuleURL(callInstallListeners), { type: "module" })
.then(registration => {
let worker =
registration.installing || registration.waiting || registration.active;
worker.postMessage("installListeners");
return waitForInitMessage(navigator.serviceWorker).then(() => [
navigator.serviceWorker,
worker,
() => registration.unregister(),
]);
});
}
function initAudioWorklet() {
const exporter = `
export { installListeners };
`;
const workletSource = `
import { installListeners } from "${getModuleURL(exporter)}";
class CustomAudioWorkletProcessor extends AudioWorkletProcessor {
constructor() {
super();
this.port.start();
installListeners(this.port, this.port, "AudioWorklet");
}
process(inputs, outputs, params) {
// Do nothing, output silence
}
}
registerProcessor("custom-audio-worklet-processor", CustomAudioWorkletProcessor);
`;
let workletURL = URL.createObjectURL(
new Blob([workletSource], { type: "text/javascript" })
);
// We need to keep the context alive while we're using the message ports.
globalThis.context = new OfflineAudioContext(1, 64, 8000);
return context.audioWorklet.addModule(workletURL).then(() => {
let node = new AudioWorkletNode(context, "custom-audio-worklet-processor");
node.port.start();
return waitForInitMessage(node.port).then(() => [
node.port,
node.port,
() => {
node.port.close();
delete globalThis.context;
},
]);
});
}
const globals = [
["Window", ["Window"], initFrame],
[
"DedicatedWorkerGlobalScope",
["Worker", "DedicatedWorker"],
initDedicatedWorker,
],
["SharedWorkerGlobalScope", ["Worker", "SharedWorker"], initSharedWorker],
["ServiceWorkerGlobalScope", ["Worker", "ServiceWorker"], initServiceWorker],
// WorkerDebuggerGlobalScope can only receive string messages.
["AudioWorkletGlobalScope", ["Worklet", "AudioWorklet"], initAudioWorklet],
// PaintWorkletGlobalScope doesn't have a messaging mechanism
].map(([global, globalNames, init]) => [
global,
new WebIDLGlobal(globalNames, init),
]);
function generateCertificate() {
return RTCPeerConnection.generateCertificate({
name: "ECDSA",
namedCurve: "P-256",
});
}
function makeCanvas() {
const width = 20;
const height = 20;
let canvas = new OffscreenCanvas(width, height);
let ctx = canvas.getContext("2d");
ctx.fillStyle = "rgba(50, 100, 150, 255)";
ctx.fillRect(0, 0, width, height);
return canvas;
}
function makeVideoFrame() {
return new VideoFrame(makeCanvas(), { timestamp: 1, alpha: "discard" });
}
function makeImageBitmap() {
return makeCanvas().transferToImageBitmap();
}
const serializable = [
["DOMException", ["Window", "Worker"], () => new DOMException()],
["DOMMatrixReadOnly", ["Window", "Worker"], () => new DOMMatrixReadOnly()],
["ImageBitmap", ["Window", "Worker"], makeImageBitmap],
["RTCCertificate", ["Window"], generateCertificate],
["VideoFrame", ["Window", "DedicatedWorker"], makeVideoFrame],
];
const transferable = [
[
"MessagePort",
["Window", "Worker", "AudioWorklet"],
() => new MessageChannel().port1,
],
["ImageBitmap", ["Window", "Worker"], makeImageBitmap],
["ReadableStream", ["*"], () => new ReadableStream()],
["VideoFrame", ["Window", "DedicatedWorker"], makeVideoFrame],
];
function isExposed(exposure, global) {
if (exposure.length === 1 && exposure[0] === "*") {
return true;
}
return !!global.globalNames.filter(v => exposure.includes(v)).length;
}
async function runTest() {
async function testDOMClass(domClass, exposure, createObject, transferable) {
for ([globalName, webidlGlobal] of globals) {
info(
`Testing ${
transferable ? "transfer" : "serialization"
} of ${domClass} with ${globalName}`
);
let object = await createObject();
let otherSide = await webidlGlobal.otherSide;
let options = { targetOrigin: "*" };
let message;
if (transferable) {
options.transfer = [object];
} else {
message = object;
}
otherSide.postMessage(message, options);
let result = await webidlGlobal.waitForResult();
let expected = isExposed(exposure, webidlGlobal);
if (
domClass === "ImageBitmap" &&
globalName === "ServiceWorkerGlobalScope"
) {
// Service workers don't support transferring shared memory objects
// (see ServiceWorker::PostMessage).
expected = false;
}
is(
result,
expected,
`Deserialization for ${domClass} in ${globalName} ${
expected ? "should" : "shouldn't"
} succeed`
);
}
}
for ([domClass, exposure, createObject] of serializable) {
await testDOMClass(domClass, exposure, createObject, false);
}
for ([domClass, exposure, createObject] of transferable) {
await testDOMClass(domClass, exposure, createObject, true);
}
SimpleTest.finish();
}
runTest();
</script>
</pre>
</body>
</html>