Skip to content

Commit 2333bd0

Browse files
graveljpmoz-wptsync-bot
authored andcommitted
Bug 1859070 [wpt PR 42544] - Throw an exception in transferToImageBitmap if canvas layers are opened, a=testonly
Automatic update from web-platform-tests Throw an exception in transferToImageBitmap if canvas layers are opened This API is incompatible with how the 2D canvas is rasterized when it contains unclosed layers. Because layers can have filters that get applied on their final content, they can't be presented until they are closed. Instead, we normally keep the layer content alive after a flush, so that it can be presented in a later frame when the layer is finally closed. OffscreenCanvas.transferToImageBitmap however is supposed to release the canvas content, leaving the offscreen canvas empty. We cannot release the recording if layers are incomplete, and if we kept the layer content alive for later, we would not be leaving the canvas empty as the spec requires. This behavior is part of the current 2D Canvas Layer spec draft: Explainer: https://github.com/fserb/canvas2D/blob/master/spec/layers.md Spec draft: whatwg/html#9537 Bug: 1484741 Change-Id: Ic770b51a0343faf0b2c7477624d69f59187ce97f Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/4939633 Commit-Queue: Jean-Philippe Gravel <[email protected]> Reviewed-by: Fernando Serboncini <[email protected]> Cr-Commit-Position: refs/heads/main@{#1212692} -- wpt-commits: 3375400712353d2c9b011ed3dbb24c8d756b784f wpt-pr: 42544
1 parent d38f7f2 commit 2333bd0

8 files changed

+156
-181
lines changed

testing/web-platform/tests/html/canvas/offscreen/layers/2d.layer.render-opportunities.transferToImageBitmap-expected.html

Lines changed: 0 additions & 24 deletions
This file was deleted.

testing/web-platform/tests/html/canvas/offscreen/layers/2d.layer.render-opportunities.transferToImageBitmap.html

Lines changed: 0 additions & 36 deletions
This file was deleted.

testing/web-platform/tests/html/canvas/offscreen/layers/2d.layer.render-opportunities.transferToImageBitmap.w.html

Lines changed: 0 additions & 50 deletions
This file was deleted.
Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
<!DOCTYPE html>
2+
<!-- DO NOT EDIT! This test has been generated by /html/canvas/tools/gentest.py. -->
3+
<title>OffscreenCanvas test: 2d.layer.transferToImageBitmap</title>
4+
<script src="/resources/testharness.js"></script>
5+
<script src="/resources/testharnessreport.js"></script>
6+
<script src="/html/canvas/resources/canvas-tests.js"></script>
7+
8+
<h1>2d.layer.transferToImageBitmap</h1>
9+
<p class="desc">Check that calling transferToImageBitmap in a layer throws an exception.</p>
10+
11+
12+
<script>
13+
var t = async_test("Check that calling transferToImageBitmap in a layer throws an exception.");
14+
var t_pass = t.done.bind(t);
15+
var t_fail = t.step_func(function(reason) {
16+
throw reason;
17+
});
18+
t.step(function() {
19+
20+
var canvas = new OffscreenCanvas(100, 50);
21+
var ctx = canvas.getContext('2d');
22+
23+
// `transferToImageBitmap` shouldn't throw on it's own.
24+
canvas.transferToImageBitmap();
25+
// Make sure the exception isn't caused by calling the function twice.
26+
canvas.transferToImageBitmap();
27+
// Calling again inside a layer should throw.
28+
ctx.beginLayer();
29+
assert_throws_dom("InvalidStateError",
30+
() => canvas.transferToImageBitmap());
31+
t.done();
32+
33+
});
34+
</script>
Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
// DO NOT EDIT! This test has been generated by /html/canvas/tools/gentest.py.
2+
// OffscreenCanvas test in a worker:2d.layer.transferToImageBitmap
3+
// Description:Check that calling transferToImageBitmap in a layer throws an exception.
4+
// Note:
5+
6+
importScripts("/resources/testharness.js");
7+
importScripts("/html/canvas/resources/canvas-tests.js");
8+
9+
var t = async_test("Check that calling transferToImageBitmap in a layer throws an exception.");
10+
var t_pass = t.done.bind(t);
11+
var t_fail = t.step_func(function(reason) {
12+
throw reason;
13+
});
14+
t.step(function() {
15+
16+
var canvas = new OffscreenCanvas(100, 50);
17+
var ctx = canvas.getContext('2d');
18+
19+
// `transferToImageBitmap` shouldn't throw on it's own.
20+
canvas.transferToImageBitmap();
21+
// Make sure the exception isn't caused by calling the function twice.
22+
canvas.transferToImageBitmap();
23+
// Calling again inside a layer should throw.
24+
ctx.beginLayer();
25+
assert_throws_dom("InvalidStateError",
26+
() => canvas.transferToImageBitmap());
27+
t.done();
28+
});
29+
done();

testing/web-platform/tests/html/canvas/offscreen/layers/2d.layer.unclosed-nested.w.html

Lines changed: 20 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -13,20 +13,29 @@ <h1>2d.layer.unclosed-nested</h1>
1313
const canvas = new OffscreenCanvas(200, 200);
1414
const ctx = canvas.getContext('2d');
1515

16-
ctx.fillStyle = 'rgba(0, 0, 255, 1)';
17-
ctx.fillRect(60, 60, 75, 50);
18-
ctx.globalAlpha = 0.5;
16+
// `transferToImageBitmap` is used to transfer the test result to the
17+
// worker's parent, but `transferToImageBitmap` can't be called on canvas
18+
// with unclosed layers. We can however draw to a separate offscreen canvas
19+
// and write it to the main canvas using `drawImage`.
20+
const canvas2 = new OffscreenCanvas(200, 200);
21+
const ctx2 = canvas2.getContext('2d');
1922

20-
ctx.beginLayer();
21-
ctx.fillStyle = 'rgba(225, 0, 0, 1)';
22-
ctx.fillRect(50, 50, 75, 50);
23+
ctx2.fillStyle = 'rgba(0, 0, 255, 1)';
24+
ctx2.fillRect(60, 60, 75, 50);
25+
ctx2.globalAlpha = 0.5;
2326

24-
ctx.beginLayer();
25-
ctx.fillStyle = 'rgba(0, 255, 0, 1)';
26-
ctx.fillRect(70, 70, 75, 50);
27+
ctx2.beginLayer();
28+
ctx2.fillStyle = 'rgba(225, 0, 0, 1)';
29+
ctx2.fillRect(50, 50, 75, 50);
2730

28-
ctx.endLayer();
29-
// Missing ctx.endLayer() here.
31+
ctx2.beginLayer();
32+
ctx2.fillStyle = 'rgba(0, 255, 0, 1)';
33+
ctx2.fillRect(70, 70, 75, 50);
34+
35+
ctx2.endLayer();
36+
// Missing ctx2.endLayer() here.
37+
38+
ctx.drawImage(canvas2, 0, 0);
3039

3140
const bitmap = canvas.transferToImageBitmap();
3241
self.postMessage(bitmap, bitmap);

testing/web-platform/tests/html/canvas/offscreen/layers/2d.layer.unclosed.w.html

Lines changed: 16 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -13,14 +13,23 @@ <h1>2d.layer.unclosed</h1>
1313
const canvas = new OffscreenCanvas(200, 200);
1414
const ctx = canvas.getContext('2d');
1515

16-
ctx.fillStyle = 'purple';
17-
ctx.fillRect(60, 60, 75, 50);
18-
ctx.globalAlpha = 0.5;
16+
// `transferToImageBitmap` is used to transfer the test result to the
17+
// worker's parent, but `transferToImageBitmap` can't be called on canvas
18+
// with unclosed layers. We can however draw to a separate offscreen canvas
19+
// and write it to the main canvas using `drawImage`.
20+
const canvas2 = new OffscreenCanvas(200, 200);
21+
const ctx2 = canvas2.getContext('2d');
1922

20-
ctx.beginLayer({filter: {name: 'dropShadow', dx: -2, dy: 2}});
21-
ctx.fillRect(40, 40, 75, 50);
22-
ctx.fillStyle = 'grey';
23-
ctx.fillRect(50, 50, 75, 50);
23+
ctx2.fillStyle = 'purple';
24+
ctx2.fillRect(60, 60, 75, 50);
25+
ctx2.globalAlpha = 0.5;
26+
27+
ctx2.beginLayer({filter: {name: 'dropShadow', dx: -2, dy: 2}});
28+
ctx2.fillRect(40, 40, 75, 50);
29+
ctx2.fillStyle = 'grey';
30+
ctx2.fillRect(50, 50, 75, 50);
31+
32+
ctx.drawImage(canvas2, 0, 0);
2433

2534
const bitmap = canvas.transferToImageBitmap();
2635
self.postMessage(bitmap, bitmap);

testing/web-platform/tests/html/canvas/tools/yaml-new/layers.yaml

Lines changed: 57 additions & 53 deletions
Original file line numberDiff line numberDiff line change
@@ -312,14 +312,29 @@
312312
desc: Check that layers are rendered even if not closed.
313313
size: [200, 200]
314314
code: |
315-
ctx.fillStyle = 'purple';
316-
ctx.fillRect(60, 60, 75, 50);
317-
ctx.globalAlpha = 0.5;
315+
{% set ns = namespace(ctx='ctx') %}
316+
{% if canvas_type == 'worker' %}
317+
// `transferToImageBitmap` is used to transfer the test result to the
318+
// worker's parent, but `transferToImageBitmap` can't be called on canvas
319+
// with unclosed layers. We can however draw to a separate offscreen canvas
320+
// and write it to the main canvas using `drawImage`.
321+
const canvas2 = new OffscreenCanvas({{ size[0] }}, {{ size[1] }});
322+
const ctx2 = canvas2.getContext('2d');
323+
{% set ns.ctx = 'ctx2' %}
324+
{% endif %}
318325
319-
ctx.beginLayer({filter: {name: 'dropShadow', dx: -2, dy: 2}});
320-
ctx.fillRect(40, 40, 75, 50);
321-
ctx.fillStyle = 'grey';
322-
ctx.fillRect(50, 50, 75, 50);
326+
{{ ns.ctx }}.fillStyle = 'purple';
327+
{{ ns.ctx }}.fillRect(60, 60, 75, 50);
328+
{{ ns.ctx }}.globalAlpha = 0.5;
329+
330+
{{ ns.ctx }}.beginLayer({filter: {name: 'dropShadow', dx: -2, dy: 2}});
331+
{{ ns.ctx }}.fillRect(40, 40, 75, 50);
332+
{{ ns.ctx }}.fillStyle = 'grey';
333+
{{ ns.ctx }}.fillRect(50, 50, 75, 50);
334+
335+
{% if canvas_type == 'worker' %}
336+
ctx.drawImage(canvas2, 0, 0);
337+
{% endif %}
323338
reference: |
324339
ctx.fillStyle = 'purple';
325340
ctx.fillRect(60, 60, 75, 50);
@@ -336,20 +351,35 @@
336351
desc: Check that layers are rendered even if not closed.
337352
size: [200, 200]
338353
code: |
339-
ctx.fillStyle = 'rgba(0, 0, 255, 1)';
340-
ctx.fillRect(60, 60, 75, 50);
341-
ctx.globalAlpha = 0.5;
354+
{% set ns = namespace(ctx='ctx') %}
355+
{% if canvas_type == 'worker' %}
356+
// `transferToImageBitmap` is used to transfer the test result to the
357+
// worker's parent, but `transferToImageBitmap` can't be called on canvas
358+
// with unclosed layers. We can however draw to a separate offscreen canvas
359+
// and write it to the main canvas using `drawImage`.
360+
const canvas2 = new OffscreenCanvas({{ size[0] }}, {{ size[1] }});
361+
const ctx2 = canvas2.getContext('2d');
362+
{% set ns.ctx = 'ctx2' %}
363+
{% endif %}
342364
343-
ctx.beginLayer();
344-
ctx.fillStyle = 'rgba(225, 0, 0, 1)';
345-
ctx.fillRect(50, 50, 75, 50);
365+
{{ ns.ctx }}.fillStyle = 'rgba(0, 0, 255, 1)';
366+
{{ ns.ctx }}.fillRect(60, 60, 75, 50);
367+
{{ ns.ctx }}.globalAlpha = 0.5;
346368
347-
ctx.beginLayer();
348-
ctx.fillStyle = 'rgba(0, 255, 0, 1)';
349-
ctx.fillRect(70, 70, 75, 50);
369+
{{ ns.ctx }}.beginLayer();
370+
{{ ns.ctx }}.fillStyle = 'rgba(225, 0, 0, 1)';
371+
{{ ns.ctx }}.fillRect(50, 50, 75, 50);
350372
351-
ctx.endLayer();
352-
// Missing ctx.endLayer() here.
373+
{{ ns.ctx }}.beginLayer();
374+
{{ ns.ctx }}.fillStyle = 'rgba(0, 255, 0, 1)';
375+
{{ ns.ctx }}.fillRect(70, 70, 75, 50);
376+
377+
{{ ns.ctx }}.endLayer();
378+
// Missing {{ ns.ctx }}.endLayer() here.
379+
380+
{% if canvas_type == 'worker' %}
381+
ctx.drawImage(canvas2, 0, 0);
382+
{% endif %}
353383
reference: |
354384
const canvas1 = document.createElement('canvas');
355385
const ctx1 = canvas1.getContext('2d');
@@ -443,44 +473,18 @@
443473
canvasType: ['HTMLCanvas']
444474
flush_canvas: canvas.toDataURL();
445475

446-
447-
- name: 2d.layer.render-opportunities.transferToImageBitmap
448-
desc: Checks that transferToImageBitmap flushes and rebuilds the state stack.
449-
size: [200, 200]
476+
- name: 2d.layer.transferToImageBitmap
477+
desc: Check that calling transferToImageBitmap in a layer throws an exception.
450478
canvasType: ['OffscreenCanvas', 'Worker']
451479
code: |
452-
ctx.fillStyle = 'purple';
453-
ctx.fillRect(60, 60, 75, 50);
454-
ctx.globalAlpha = 0.5;
455-
456-
ctx.beginLayer({filter: {name: 'dropShadow', dx: -2, dy: 2}});
457-
ctx.fillRect(40, 40, 75, 50);
458-
ctx.fillStyle = 'grey';
459-
ctx.fillRect(50, 50, 75, 50);
460-
461-
// Force a flush and restoration of the state stack.
462-
// `transferToImageBitmap` clears the frame but preserves render states.
480+
// `transferToImageBitmap` shouldn't throw on it's own.
463481
canvas.transferToImageBitmap();
464-
465-
ctx.fillRect(70, 70, 75, 50);
466-
ctx.fillStyle = 'orange';
467-
ctx.fillRect(80, 80, 75, 50);
468-
ctx.endLayer();
469-
470-
ctx.fillRect(80, 40, 75, 50);
471-
reference: |
472-
ctx.fillStyle = 'purple';
473-
ctx.globalAlpha = 0.5;
474-
475-
ctx.beginLayer({filter: {name: 'dropShadow', dx: -2, dy: 2}});
476-
ctx.fillStyle = 'grey';
477-
ctx.fillRect(70, 70, 75, 50);
478-
ctx.fillStyle = 'orange';
479-
ctx.fillRect(80, 80, 75, 50);
480-
ctx.endLayer();
481-
482-
ctx.fillRect(80, 40, 75, 50);
483-
482+
// Make sure the exception isn't caused by calling the function twice.
483+
canvas.transferToImageBitmap();
484+
// Calling again inside a layer should throw.
485+
ctx.beginLayer();
486+
assert_throws_dom("InvalidStateError",
487+
() => canvas.transferToImageBitmap());
484488
485489
- name: 2d.layer.several-complex
486490
desc: >-

0 commit comments

Comments
 (0)