| <!-- |
| Iframes can be tricky because even after they're removed from the page, |
| script can continue to run. |
| So, WebGPU cannot assume that WebGPU resources can be immediately |
| destroyed with the page. |
| |
| This test tests a cases one after another by inserting an iframe into the page |
| with the test case script and waiting for the test to call |
| |removeIframe| which should remove itself from the DOM. After removal, we wait a bit |
| and then continue on to the next case. |
| --> |
| <html> |
| |
| <head> |
| <script> |
| // |runTestCase| creates an iframe containing the stringified script source of |testFn|. |
| // It returns a Promise and saves the |resolve| function as |iframeRemovedCallback|. |
| // This callback is called in removeIframe, so when the Promise resolves, we know |
| // the iframe containing the test case page has been removed. |
| let iframeRemovedCallback; |
| function runTestCase(testFn) { |
| return new Promise(resolve => { |
| iframeRemovedCallback = resolve; |
| |
| let iframe = document.createElement("iframe"); |
| iframe.setAttribute("id", "iframe"); |
| document.body.appendChild(iframe); |
| |
| const head = iframe.contentWindow.document.getElementsByTagName('head')[0]; |
| const script = iframe.contentWindow.document.createElement('script'); |
| script.innerText = `(${testFn.toString()})().catch(parent.fail);`; |
| script.type = 'text/javascript'; |
| head.appendChild(script); |
| }); |
| } |
| |
| // Called by the inner iframe to remove itself. |
| function removeIframe() { |
| const iframe = document.getElementById('iframe'); |
| iframe.parentNode.removeChild(iframe); |
| |
| // Wait a bit before signaling that the test case is finished. |
| setTimeout(iframeRemovedCallback, 100); |
| } |
| |
| function fail(err) { |
| if (window.domAutomationController) { |
| console.log(err); |
| } else { |
| alert(err); |
| } |
| window.domAutomationController.send('FAILED'); |
| } |
| |
| async function runTests() { |
| // Test running basic WebGPU commands after the iframe is removed. |
| await runTestCase(async () => { |
| let device; |
| try { |
| const adapter = await navigator.gpu.requestAdapter(); |
| device = await adapter.requestDevice(); |
| } catch { |
| console.warn('WebGPU not supported'); |
| parent.removeIframe(); |
| return; |
| } |
| |
| parent.removeIframe(); |
| device.queue.submit([device.createCommandEncoder().finish()]); |
| }); |
| |
| // Test getting a swapchain texture after the iframe is removed. |
| // This may interact with the shared image interfaces. |
| await runTestCase(async () => { |
| const adapter = navigator.gpu && await navigator.gpu.requestAdapter(); |
| if (!adapter) { |
| console.warn('WebGPU not supported'); |
| parent.removeIframe(); |
| return; |
| } |
| const device = await adapter.requestDevice(); |
| |
| const canvas = document.createElement('canvas'); |
| const context = canvas.getContext('webgpu'); |
| context.configure({ |
| device, |
| format: 'bgra8unorm' |
| }); |
| |
| parent.removeIframe(); |
| const texture = context.getCurrentTexture(); |
| }); |
| |
| // Test reconfiguring the swapchain after the iframe is removed. |
| // This may interact with the shared image interfaces. |
| // The expecation is that reconfiguring after removal does not crash, but |
| // does throw an exception. It is invalid to call |configure| after |
| // the canvas is destroyed. |
| await runTestCase(async () => { |
| const adapter = navigator.gpu && await navigator.gpu.requestAdapter(); |
| if (!adapter) { |
| console.warn('WebGPU not supported'); |
| parent.removeIframe(); |
| return; |
| } |
| const device = await adapter.requestDevice(); |
| |
| const canvas = document.createElement('canvas'); |
| const context = canvas.getContext('webgpu'); |
| context.configure({ |
| device, |
| format: 'bgra8unorm' |
| }); |
| const texture = context.getCurrentTexture(); |
| |
| parent.removeIframe(); |
| try { |
| /* Throws an exception since the canvas is destroyed */ |
| context.configure({ |
| device, |
| format: 'bgra8unorm' |
| }); |
| } catch (err) { |
| } |
| }); |
| |
| // Test creating a device asynchronously, but removing the iframe before |
| // the response is received. |
| await runTestCase(async () => { |
| const adapter = navigator.gpu && await navigator.gpu.requestAdapter(); |
| if (!adapter) { |
| console.warn('WebGPU not supported'); |
| parent.removeIframe(); |
| return; |
| } |
| |
| adapter.requestDevice(); |
| /* Wait till the next task so we're sure the command flushed */ |
| setTimeout(() => { |
| parent.removeIframe(); |
| }, 0); |
| }); |
| |
| // Test sending an async command and then removing the iframe |
| // before the response is received. |
| await runTestCase(async () => { |
| const adapter = navigator.gpu && await navigator.gpu.requestAdapter(); |
| if (!adapter) { |
| console.warn('WebGPU not supported'); |
| parent.removeIframe(); |
| return; |
| } |
| const device = await adapter.requestDevice(); |
| device.queue.onSubmittedWorkDone(); |
| /* Wait till the next microtask so we're sure the command flushed */ |
| setTimeout(() => { |
| parent.removeIframe(); |
| }, 0); |
| }); |
| |
| // Test creating a mappable buffer and then removing the iframe. |
| // Mappable buffers are tracked because mappings need to be detached |
| // when the context is destroyed. |
| await runTestCase(async () => { |
| const adapter = navigator.gpu && await navigator.gpu.requestAdapter(); |
| if (!adapter) { |
| console.warn('WebGPU not supported'); |
| parent.removeIframe(); |
| return; |
| } |
| const device = await adapter.requestDevice(); |
| |
| const buffer = device.createBuffer({ |
| size: 4, |
| usage: GPUBufferUsage.MAP_READ, |
| }); |
| |
| parent.removeIframe(); |
| }); |
| |
| // Test creating a mappable buffer and then in the next task, removing the |
| // iframe while the mapping is in flight. |
| // Mappable buffers are tracked because mappings need to be detached |
| // when the context is destroyed. |
| await runTestCase(async () => { |
| const adapter = navigator.gpu && await navigator.gpu.requestAdapter(); |
| if (!adapter) { |
| console.warn('WebGPU not supported'); |
| parent.removeIframe(); |
| return; |
| } |
| const device = await adapter.requestDevice(); |
| |
| const buffer = device.createBuffer({ |
| size: 4, |
| usage: GPUBufferUsage.MAP_READ, |
| }); |
| |
| buffer.mapAsync(GPUMapMode.READ); |
| /* Wait till the next microtask so we're sure the command flushed */ |
| setTimeout(() => { |
| parent.removeIframe(); |
| }, 0); |
| }); |
| |
| // Test creating a mappable buffer and then in the same task, removing the |
| // iframe while the mapping is in flight. |
| // Mappable buffers are tracked because mappings need to be detached |
| // when the context is destroyed. |
| await runTestCase(async () => { |
| const adapter = navigator.gpu && await navigator.gpu.requestAdapter(); |
| if (!adapter) { |
| console.warn('WebGPU not supported'); |
| parent.removeIframe(); |
| return; |
| } |
| const device = await adapter.requestDevice(); |
| |
| const buffer = device.createBuffer({ |
| size: 4, |
| usage: GPUBufferUsage.MAP_READ, |
| }); |
| |
| buffer.mapAsync(GPUMapMode.READ); |
| /* Empty submit to force a flush */ |
| device.queue.submit([]); |
| parent.removeIframe(); |
| }); |
| |
| // Test removing the iframe while the buffer is mapped. |
| // Mappable buffers are tracked because mappings need to be detached |
| // when the context is destroyed. |
| await runTestCase(async () => { |
| const adapter = navigator.gpu && await navigator.gpu.requestAdapter(); |
| if (!adapter) { |
| console.warn('WebGPU not supported'); |
| parent.removeIframe(); |
| return; |
| } |
| const device = await adapter.requestDevice(); |
| |
| const buffer = device.createBuffer({ |
| size: 4, |
| usage: GPUBufferUsage.MAP_WRITE, |
| }); |
| |
| const mapping = await buffer.mapAsync(GPUMapMode.WRITE); |
| parent.removeIframe(); |
| |
| if (mapping !== undefined && mapping.byteLength != 0) { |
| fail('GPUBuffer mapping should be detached') |
| } |
| }); |
| |
| // Test creating and mapping a buffer after the iframe is removed. |
| await runTestCase(async () => { |
| const adapter = navigator.gpu && await navigator.gpu.requestAdapter(); |
| if (!adapter) { |
| console.warn('WebGPU not supported'); |
| parent.removeIframe(); |
| return; |
| } |
| const device = await adapter.requestDevice(); |
| |
| /* Get these constants now. The receiver interface may become |
| undefined after the iframe is removed */ |
| const USAGE_MAP_WRITE = GPUBufferUsage.MAP_WRITE; |
| const MODE_WRITE = GPUMapMode.WRITE; |
| parent.removeIframe(); |
| |
| const buffer = device.createBuffer({ |
| size: 4, |
| usage: USAGE_MAP_WRITE, |
| }); |
| |
| const mapping = await buffer.mapAsync(MODE_WRITE); |
| if (mapping !== undefined && mapping.byteLength != 0) { |
| fail('GPUBuffer mapping should be detached') |
| } |
| }); |
| |
| window.domAutomationController.send('SUCCESS'); |
| } |
| </script> |
| </head> |
| |
| <body onload="runTests()"></body> |
| |
| </html> |