canvas-promise-test.js (6005B)
1 /** 2 * Framework for executing tests with HTMLCanvasElement, main thread 3 * OffscreenCanvas and worker OffscreenCanvas. Canvas tests are specified using 4 * calls to `canvasPromiseTest`, which runs the test on the main thread, using 5 * an HTML and an OffscreenCanvas. Calling `runCanvasTestsInWorker` at the 6 * script level then re-execute the whole script in a worker, this time using 7 * only OffscreenCanvas objects. Example usage: 8 * 9 * <script> 10 * runCanvasTestsInWorker(); 11 * 12 * canvasPromiseTest(async (canvas) => { 13 * // ... 14 * }, "Sample test") 15 * </script> 16 */ 17 18 /** 19 * Enum listing all test types emitted by `canvasPromiseTest()`. 20 */ 21 const CanvasTestType = Object.freeze({ 22 HTML: Symbol('html'), 23 DETACHED_HTML: Symbol('detached_html'), 24 OFFSCREEN: Symbol('offscreen'), 25 PLACEHOLDER: Symbol('placeholder'), 26 WORKER: Symbol('worker') 27 }); 28 29 ALL_CANVAS_TEST_TYPES = Object.values(CanvasTestType); 30 DEFAULT_CANVAS_TEST_TYPES = [ 31 CanvasTestType.HTML, 32 CanvasTestType.OFFSCREEN, 33 CanvasTestType.WORKER, 34 ]; 35 HTML_CANVAS_ELEMENT_TEST_TYPES = [ 36 CanvasTestType.HTML, 37 CanvasTestType.DETACHED_HTML, 38 ]; 39 OFFSCREEN_CANVAS_TEST_TYPES = [ 40 CanvasTestType.OFFSCREEN, 41 CanvasTestType.WORKER, 42 ]; 43 MAIN_THREAD_CANVAS_TEST_TYPES = [ 44 CanvasTestType.HTML, 45 CanvasTestType.DETACHED_HTML, 46 CanvasTestType.OFFSCREEN, 47 CanvasTestType.PLACEHOLDER, 48 ]; 49 WORKER_CANVAS_TEST_TYPES = [ 50 CanvasTestType.WORKER, 51 ]; 52 53 /** 54 * Run `testBody` in a `promise_test` against multiple types of canvases. By 55 * default, the test is executed against an HTMLCanvasElement, a main thread 56 * OffscreenCanvas and a worker OffscreenCanvas, though `testTypes` can be used 57 * only enable a subset of these. `testBody` must be a function accepting a 58 * canvas as parameter and returning a promise that resolves on test completion. 59 * 60 * This function has two implementations. The version below runs the test on the 61 * main thread and another version in `canvas-worker-test.js` runs it in a 62 * worker. The worker invocation is launched by calling `runCanvasTestsInWorker` 63 * at the script level. 64 */ 65 function canvasPromiseTest( 66 testBody, description, 67 {testTypes = DEFAULT_CANVAS_TEST_TYPES} = {}) { 68 if (testTypes.includes(CanvasTestType.WORKER)) { 69 setup(() => { 70 const currentScript = document.currentScript; 71 assert_true( 72 currentScript.classList.contains('runCanvasTestsInWorkerInvoked'), 73 'runCanvasTestsInWorker() must be called in the current script ' + 74 'before calling canvasPromiseTest with CanvasTestType.WORKER test ' + 75 'type, or else the test won\'t have worker coverage.'); 76 77 currentScript.classList.add('canvasWorkerTestAdded'); 78 }); 79 } 80 81 if (testTypes.includes(CanvasTestType.HTML)) { 82 promise_test(async () => { 83 if (!document.body) { 84 document.documentElement.appendChild(document.createElement("body")); 85 } 86 const canvas = document.createElement('canvas'); 87 document.body.appendChild(canvas); 88 await testBody(canvas, {canvasType: CanvasTestType.HTML}); 89 document.body.removeChild(canvas); 90 }, 'HTMLCanvasElement: ' + description); 91 } 92 93 if (testTypes.includes(CanvasTestType.DETACHED_HTML)) { 94 promise_test(() => testBody(document.createElement('canvas'), 95 {canvasType: CanvasTestType.DETACHED_HTML}), 96 'Detached HTMLCanvasElement: ' + description); 97 } 98 99 if (testTypes.includes(CanvasTestType.OFFSCREEN)) { 100 promise_test(() => testBody(new OffscreenCanvas(300, 150), 101 {canvasType: CanvasTestType.OFFSCREEN}), 102 'OffscreenCanvas: ' + description); 103 } 104 105 if (testTypes.includes(CanvasTestType.PLACEHOLDER)) { 106 promise_test(async () => { 107 if (!document.body) { 108 document.documentElement.appendChild(document.createElement("body")); 109 } 110 const placeholder = document.createElement('canvas'); 111 document.body.appendChild(placeholder); 112 await testBody(placeholder.transferControlToOffscreen(), 113 {canvasType: CanvasTestType.PLACEHOLDER}); 114 }, 'PlaceholderCanvas: ' + description); 115 } 116 } 117 118 /** 119 * Run all the canvasPromiseTest from the current script in a worker. 120 * If the tests depend on external scripts, these must be specified as a list 121 * via the `dependencies` parameter so that the worker could load them. 122 */ 123 function runCanvasTestsInWorker({dependencies = []} = {}) { 124 const currentScript = document.currentScript; 125 // Keep track of whether runCanvasTestsInWorker was invoked on the current 126 // script. `canvasPromiseTest` will fail if `runCanvasTestsInWorker` hasn't 127 // been called, to prevent accidentally omitting worker coverage. 128 setup(() => { 129 assert_false( 130 currentScript.classList.contains('runCanvasTestsInWorkerInvoked'), 131 'runCanvasTestsInWorker() can\'t be invoked twice on the same script.'); 132 currentScript.classList.add('runCanvasTestsInWorkerInvoked'); 133 }); 134 135 promise_setup(async () => { 136 const allDeps = [ 137 '/resources/testharness.js', 138 '/html/canvas/resources/canvas-promise-test.js', 139 // canvas-promise-test-worker.js overrides parts of canvas-test.js. 140 '/html/canvas/resources/canvas-promise-test-worker.js', 141 ].concat(dependencies); 142 143 const dependencyScripts = 144 await Promise.all(allDeps.map(dep => fetch(dep).then(r => r.text()))); 145 const canvasTests = currentScript.textContent; 146 const allScripts = dependencyScripts.concat([canvasTests, 'done();']); 147 148 const workerBlob = new Blob(allScripts); 149 const worker = new Worker(URL.createObjectURL(workerBlob)); 150 fetch_tests_from_worker(worker); 151 }); 152 153 promise_setup(async () => { 154 await new Promise(resolve => step_timeout(resolve, 0)); 155 assert_true( 156 currentScript.classList.contains('canvasWorkerTestAdded'), 157 'runCanvasTestsInWorker() should not be called if no ' + 158 'canvasPromiseTest uses the CanvasTestType.WORKER test type.'); 159 }); 160 }