test_structuredCloneAndExposed.html (8362B)
1 <!DOCTYPE html> 2 <html> 3 <!-- 4 https://bugzilla.mozilla.org/show_bug.cgi?id=1828264 5 --> 6 <head> 7 <title>Basic structured clone tests</title> 8 <script src="/tests/SimpleTest/SimpleTest.js"></script> 9 <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" /> 10 </head> 11 <body> 12 <a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=1828264">Mozilla Bug 1828264</a> 13 <p id="display"> 14 <iframe id="frame"></iframe> 15 <image href="four-colors.png" height="320" width="240"/> 16 </p> 17 <div id="content" style="display: none"></div> 18 19 20 <pre id="test"> 21 <script class="testbody" type="application/javascript"> 22 23 SimpleTest.waitForExplicitFinish(); 24 25 class WebIDLGlobal { 26 globalNames; 27 resolve; 28 #otherSide; 29 30 constructor(globalNames, initSides) { 31 this.globalNames = globalNames; 32 let global = this; 33 this.#otherSide = initSides().then(([ourSide, otherSide, cleanup]) => { 34 if (cleanup) { 35 SimpleTest.registerCleanupFunction(cleanup); 36 } 37 ourSide.addEventListener("message", ({ data }) => { 38 global.resultReceived(data); 39 }); 40 return otherSide; 41 }); 42 } 43 44 get otherSide() { 45 return this.#otherSide; 46 } 47 48 waitForResult() { 49 ok(!this.resolve, "Still waiting on previous message?"); 50 return new Promise(resolve => { 51 this.resolve = resolve; 52 }); 53 } 54 resultReceived(value) { 55 this.resolve(value); 56 this.resolve = undefined; 57 } 58 } 59 60 // Init functions return an array containing in order the object to use for 61 // sending a messages to the other global, the object to use to listen for 62 // messages from the other global and optioanlly a cleanup function. 63 64 function waitForInitMessage(target) { 65 return new Promise(resolve => { 66 target.addEventListener("message", resolve, { once: true }); 67 }); 68 } 69 70 function getModuleURL(additionalScript = "") { 71 return new URL( 72 `file_structuredCloneAndExposed.sjs?additionalScript=${encodeURIComponent( 73 additionalScript 74 )}`, 75 location.href 76 ); 77 } 78 79 function initFrame() { 80 let callInstallListeners = ` 81 installListeners(globalThis, parent); 82 `; 83 frame.srcdoc = 84 `<script src="${getModuleURL(callInstallListeners)}" type="module"></` + 85 `script>`; 86 return waitForInitMessage(self).then(() => [self, frame.contentWindow]); 87 } 88 89 function initDedicatedWorker() { 90 let callInstallListeners = ` 91 installListeners(globalThis, globalThis); 92 `; 93 const worker = new Worker(getModuleURL(callInstallListeners), { 94 type: "module", 95 }); 96 return waitForInitMessage(worker).then(() => [worker, worker]); 97 } 98 99 function initSharedWorker() { 100 let callInstallListeners = ` 101 self.addEventListener("connect", (e) => { 102 const port = e.ports[0]; 103 port.start(); 104 installListeners(port, port); 105 }); 106 `; 107 const worker = new SharedWorker(getModuleURL(callInstallListeners), { 108 type: "module", 109 }); 110 worker.port.start(); 111 return waitForInitMessage(worker.port).then(() => [worker.port, worker.port]); 112 } 113 114 function initServiceWorker() { 115 let callInstallListeners = ` 116 addEventListener("message", ({ source: client }) => { 117 installListeners(globalThis, client); 118 }, { once: true }); 119 `; 120 121 return navigator.serviceWorker 122 .register(getModuleURL(callInstallListeners), { type: "module" }) 123 .then(registration => { 124 let worker = 125 registration.installing || registration.waiting || registration.active; 126 worker.postMessage("installListeners"); 127 return waitForInitMessage(navigator.serviceWorker).then(() => [ 128 navigator.serviceWorker, 129 worker, 130 () => registration.unregister(), 131 ]); 132 }); 133 } 134 135 function initAudioWorklet() { 136 const exporter = ` 137 export { installListeners }; 138 `; 139 140 const workletSource = ` 141 import { installListeners } from "${getModuleURL(exporter)}"; 142 143 class CustomAudioWorkletProcessor extends AudioWorkletProcessor { 144 constructor() { 145 super(); 146 this.port.start(); 147 installListeners(this.port, this.port, "AudioWorklet"); 148 } 149 150 process(inputs, outputs, params) { 151 // Do nothing, output silence 152 } 153 } 154 155 registerProcessor("custom-audio-worklet-processor", CustomAudioWorkletProcessor); 156 `; 157 let workletURL = URL.createObjectURL( 158 new Blob([workletSource], { type: "text/javascript" }) 159 ); 160 161 // We need to keep the context alive while we're using the message ports. 162 globalThis.context = new OfflineAudioContext(1, 64, 8000); 163 return context.audioWorklet.addModule(workletURL).then(() => { 164 let node = new AudioWorkletNode(context, "custom-audio-worklet-processor"); 165 node.port.start(); 166 return waitForInitMessage(node.port).then(() => [ 167 node.port, 168 node.port, 169 () => { 170 node.port.close(); 171 delete globalThis.context; 172 }, 173 ]); 174 }); 175 } 176 177 const globals = [ 178 ["Window", ["Window"], initFrame], 179 [ 180 "DedicatedWorkerGlobalScope", 181 ["Worker", "DedicatedWorker"], 182 initDedicatedWorker, 183 ], 184 ["SharedWorkerGlobalScope", ["Worker", "SharedWorker"], initSharedWorker], 185 ["ServiceWorkerGlobalScope", ["Worker", "ServiceWorker"], initServiceWorker], 186 // WorkerDebuggerGlobalScope can only receive string messages. 187 ["AudioWorkletGlobalScope", ["Worklet", "AudioWorklet"], initAudioWorklet], 188 // PaintWorkletGlobalScope doesn't have a messaging mechanism 189 ].map(([global, globalNames, init]) => [ 190 global, 191 new WebIDLGlobal(globalNames, init), 192 ]); 193 194 195 function generateCertificate() { 196 return RTCPeerConnection.generateCertificate({ 197 name: "ECDSA", 198 namedCurve: "P-256", 199 }); 200 } 201 202 function makeCanvas() { 203 const width = 20; 204 const height = 20; 205 let canvas = new OffscreenCanvas(width, height); 206 let ctx = canvas.getContext("2d"); 207 ctx.fillStyle = "rgba(50, 100, 150, 255)"; 208 ctx.fillRect(0, 0, width, height); 209 return canvas; 210 } 211 212 function makeVideoFrame() { 213 return new VideoFrame(makeCanvas(), { timestamp: 1, alpha: "discard" }); 214 } 215 216 function makeImageBitmap() { 217 return makeCanvas().transferToImageBitmap(); 218 } 219 220 const serializable = [ 221 ["DOMException", ["Window", "Worker"], () => new DOMException()], 222 ["DOMMatrixReadOnly", ["Window", "Worker"], () => new DOMMatrixReadOnly()], 223 ["ImageBitmap", ["Window", "Worker"], makeImageBitmap], 224 ["RTCCertificate", ["Window"], generateCertificate], 225 ["VideoFrame", ["Window", "DedicatedWorker"], makeVideoFrame], 226 ]; 227 228 const transferable = [ 229 [ 230 "MessagePort", 231 ["Window", "Worker", "AudioWorklet"], 232 () => new MessageChannel().port1, 233 ], 234 ["ImageBitmap", ["Window", "Worker"], makeImageBitmap], 235 ["ReadableStream", ["*"], () => new ReadableStream()], 236 ["VideoFrame", ["Window", "DedicatedWorker"], makeVideoFrame], 237 ]; 238 239 240 function isExposed(exposure, global) { 241 if (exposure.length === 1 && exposure[0] === "*") { 242 return true; 243 } 244 return !!global.globalNames.filter(v => exposure.includes(v)).length; 245 } 246 247 248 async function runTest() { 249 async function testDOMClass(domClass, exposure, createObject, transferable) { 250 for ([globalName, webidlGlobal] of globals) { 251 info( 252 `Testing ${ 253 transferable ? "transfer" : "serialization" 254 } of ${domClass} with ${globalName}` 255 ); 256 257 let object = await createObject(); 258 let otherSide = await webidlGlobal.otherSide; 259 let options = { targetOrigin: "*" }; 260 let message; 261 if (transferable) { 262 options.transfer = [object]; 263 } else { 264 message = object; 265 } 266 otherSide.postMessage(message, options); 267 268 let result = await webidlGlobal.waitForResult(); 269 let expected = isExposed(exposure, webidlGlobal); 270 271 if ( 272 domClass === "ImageBitmap" && 273 globalName === "ServiceWorkerGlobalScope" 274 ) { 275 // Service workers don't support transferring shared memory objects 276 // (see ServiceWorker::PostMessage). 277 expected = false; 278 } 279 280 is( 281 result, 282 expected, 283 `Deserialization for ${domClass} in ${globalName} ${ 284 expected ? "should" : "shouldn't" 285 } succeed` 286 ); 287 } 288 } 289 290 for ([domClass, exposure, createObject] of serializable) { 291 await testDOMClass(domClass, exposure, createObject, false); 292 } 293 for ([domClass, exposure, createObject] of transferable) { 294 await testDOMClass(domClass, exposure, createObject, true); 295 } 296 297 SimpleTest.finish(); 298 } 299 300 runTest(); 301 302 </script> 303 </pre> 304 </body> 305 </html>