305 lines
8.2 KiB
HTML
305 lines
8.2 KiB
HTML
<!DOCTYPE html>
|
|
<html>
|
|
<!--
|
|
https://bugzilla.mozilla.org/show_bug.cgi?id=1828264
|
|
-->
|
|
<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>
|
|
<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=1828264">Mozilla Bug 1828264</a>
|
|
<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>
|