| /** |
| * Framework for executing tests with HTMLCanvasElement, main thread |
| * OffscreenCanvas and worker OffscreenCanvas. Canvas tests are specified using |
| * calls to `canvasPromiseTest`, which runs the test on the main thread, using |
| * an HTML and an OffscreenCanvas. Calling `runCanvasTestsInWorker` at the |
| * script level then re-execute the whole script in a worker, this time using |
| * only OffscreenCanvas objects. Example usage: |
| * |
| * <script> |
| * runCanvasTestsInWorker(); |
| * |
| * canvasPromiseTest(async (canvas) => { |
| * // ... |
| * }, "Sample test") |
| * </script> |
| */ |
| |
| /** |
| * Enum listing all test types emitted by `canvasPromiseTest()`. |
| */ |
| const CanvasTestType = Object.freeze({ |
| HTML: Symbol('html'), |
| DETACHED_HTML: Symbol('detached_html'), |
| OFFSCREEN: Symbol('offscreen'), |
| PLACEHOLDER: Symbol('placeholder'), |
| WORKER: Symbol('worker') |
| }); |
| |
| ALL_CANVAS_TEST_TYPES = Object.values(CanvasTestType); |
| DEFAULT_CANVAS_TEST_TYPES = [ |
| CanvasTestType.HTML, |
| CanvasTestType.OFFSCREEN, |
| CanvasTestType.WORKER, |
| ]; |
| HTML_CANVAS_ELEMENT_TEST_TYPES = [ |
| CanvasTestType.HTML, |
| CanvasTestType.DETACHED_HTML, |
| ]; |
| OFFSCREEN_CANVAS_TEST_TYPES = [ |
| CanvasTestType.OFFSCREEN, |
| CanvasTestType.WORKER, |
| ]; |
| MAIN_THREAD_CANVAS_TEST_TYPES = [ |
| CanvasTestType.HTML, |
| CanvasTestType.DETACHED_HTML, |
| CanvasTestType.OFFSCREEN, |
| CanvasTestType.PLACEHOLDER, |
| ]; |
| WORKER_CANVAS_TEST_TYPES = [ |
| CanvasTestType.WORKER, |
| ]; |
| |
| /** |
| * Run `testBody` in a `promise_test` against multiple types of canvases. By |
| * default, the test is executed against an HTMLCanvasElement, a main thread |
| * OffscreenCanvas and a worker OffscreenCanvas, though `testTypes` can be used |
| * only enable a subset of these. `testBody` must be a function accepting a |
| * canvas as parameter and returning a promise that resolves on test completion. |
| * |
| * This function has two implementations. The version below runs the test on the |
| * main thread and another version in `canvas-worker-test.js` runs it in a |
| * worker. The worker invocation is launched by calling `runCanvasTestsInWorker` |
| * at the script level. |
| */ |
| function canvasPromiseTest( |
| testBody, description, |
| {testTypes = DEFAULT_CANVAS_TEST_TYPES} = {}) { |
| if (testTypes.includes(CanvasTestType.WORKER)) { |
| setup(() => { |
| const currentScript = document.currentScript; |
| assert_true( |
| currentScript.classList.contains('runCanvasTestsInWorkerInvoked'), |
| 'runCanvasTestsInWorker() must be called in the current script ' + |
| 'before calling canvasPromiseTest with CanvasTestType.WORKER test ' + |
| 'type, or else the test won\'t have worker coverage.'); |
| |
| currentScript.classList.add('canvasWorkerTestAdded'); |
| }); |
| } |
| |
| if (testTypes.includes(CanvasTestType.HTML)) { |
| promise_test(async () => { |
| if (!document.body) { |
| document.documentElement.appendChild(document.createElement("body")); |
| } |
| const canvas = document.createElement('canvas'); |
| document.body.appendChild(canvas); |
| await testBody(canvas, {canvasType: CanvasTestType.HTML}); |
| document.body.removeChild(canvas); |
| }, 'HTMLCanvasElement: ' + description); |
| } |
| |
| if (testTypes.includes(CanvasTestType.DETACHED_HTML)) { |
| promise_test(() => testBody(document.createElement('canvas'), |
| {canvasType: CanvasTestType.DETACHED_HTML}), |
| 'Detached HTMLCanvasElement: ' + description); |
| } |
| |
| if (testTypes.includes(CanvasTestType.OFFSCREEN)) { |
| promise_test(() => testBody(new OffscreenCanvas(300, 150), |
| {canvasType: CanvasTestType.OFFSCREEN}), |
| 'OffscreenCanvas: ' + description); |
| } |
| |
| if (testTypes.includes(CanvasTestType.PLACEHOLDER)) { |
| promise_test(async () => { |
| if (!document.body) { |
| document.documentElement.appendChild(document.createElement("body")); |
| } |
| const placeholder = document.createElement('canvas'); |
| document.body.appendChild(placeholder); |
| await testBody(placeholder.transferControlToOffscreen(), |
| {canvasType: CanvasTestType.PLACEHOLDER}); |
| }, 'PlaceholderCanvas: ' + description); |
| } |
| } |
| |
| /** |
| * Run all the canvasPromiseTest from the current script in a worker. |
| * If the tests depend on external scripts, these must be specified as a list |
| * via the `dependencies` parameter so that the worker could load them. |
| */ |
| function runCanvasTestsInWorker({dependencies = []} = {}) { |
| const currentScript = document.currentScript; |
| // Keep track of whether runCanvasTestsInWorker was invoked on the current |
| // script. `canvasPromiseTest` will fail if `runCanvasTestsInWorker` hasn't |
| // been called, to prevent accidentally omitting worker coverage. |
| setup(() => { |
| assert_false( |
| currentScript.classList.contains('runCanvasTestsInWorkerInvoked'), |
| 'runCanvasTestsInWorker() can\'t be invoked twice on the same script.'); |
| currentScript.classList.add('runCanvasTestsInWorkerInvoked'); |
| }); |
| |
| promise_setup(async () => { |
| const allDeps = [ |
| '/resources/testharness.js', |
| '/html/canvas/resources/canvas-promise-test.js', |
| // canvas-promise-test-worker.js overrides parts of canvas-test.js. |
| '/html/canvas/resources/canvas-promise-test-worker.js', |
| ].concat(dependencies); |
| |
| const dependencyScripts = |
| await Promise.all(allDeps.map(dep => fetch(dep).then(r => r.text()))); |
| const canvasTests = currentScript.textContent; |
| const allScripts = dependencyScripts.concat([canvasTests, 'done();']); |
| |
| const workerBlob = new Blob(allScripts); |
| const worker = new Worker(URL.createObjectURL(workerBlob)); |
| fetch_tests_from_worker(worker); |
| }); |
| |
| promise_setup(async () => { |
| await new Promise(resolve => step_timeout(resolve, 0)); |
| assert_true( |
| currentScript.classList.contains('canvasWorkerTestAdded'), |
| 'runCanvasTestsInWorker() should not be called if no ' + |
| 'canvasPromiseTest uses the CanvasTestType.WORKER test type.'); |
| }); |
| } |