Skip to content

Commit

Permalink
Bug 1859070 [wpt PR 42544] - Throw an exception in transferToImageBit…
Browse files Browse the repository at this point in the history
…map 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
  • Loading branch information
graveljp authored and moz-wptsync-bot committed Oct 31, 2023
1 parent e71efec commit 5031a60
Show file tree
Hide file tree
Showing 8 changed files with 156 additions and 181 deletions.

This file was deleted.

This file was deleted.

This file was deleted.

Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
<!DOCTYPE html>
<!-- DO NOT EDIT! This test has been generated by /html/canvas/tools/gentest.py. -->
<title>OffscreenCanvas test: 2d.layer.transferToImageBitmap</title>
<script src="/resources/testharness.js"></script>
<script src="/resources/testharnessreport.js"></script>
<script src="/html/canvas/resources/canvas-tests.js"></script>

<h1>2d.layer.transferToImageBitmap</h1>
<p class="desc">Check that calling transferToImageBitmap in a layer throws an exception.</p>


<script>
var t = async_test("Check that calling transferToImageBitmap in a layer throws an exception.");
var t_pass = t.done.bind(t);
var t_fail = t.step_func(function(reason) {
throw reason;
});
t.step(function() {

var canvas = new OffscreenCanvas(100, 50);
var ctx = canvas.getContext('2d');

// `transferToImageBitmap` shouldn't throw on it's own.
canvas.transferToImageBitmap();
// Make sure the exception isn't caused by calling the function twice.
canvas.transferToImageBitmap();
// Calling again inside a layer should throw.
ctx.beginLayer();
assert_throws_dom("InvalidStateError",
() => canvas.transferToImageBitmap());
t.done();

});
</script>
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
// DO NOT EDIT! This test has been generated by /html/canvas/tools/gentest.py.
// OffscreenCanvas test in a worker:2d.layer.transferToImageBitmap
// Description:Check that calling transferToImageBitmap in a layer throws an exception.
// Note:

importScripts("/resources/testharness.js");
importScripts("/html/canvas/resources/canvas-tests.js");

var t = async_test("Check that calling transferToImageBitmap in a layer throws an exception.");
var t_pass = t.done.bind(t);
var t_fail = t.step_func(function(reason) {
throw reason;
});
t.step(function() {

var canvas = new OffscreenCanvas(100, 50);
var ctx = canvas.getContext('2d');

// `transferToImageBitmap` shouldn't throw on it's own.
canvas.transferToImageBitmap();
// Make sure the exception isn't caused by calling the function twice.
canvas.transferToImageBitmap();
// Calling again inside a layer should throw.
ctx.beginLayer();
assert_throws_dom("InvalidStateError",
() => canvas.transferToImageBitmap());
t.done();
});
done();
Original file line number Diff line number Diff line change
Expand Up @@ -13,20 +13,29 @@ <h1>2d.layer.unclosed-nested</h1>
const canvas = new OffscreenCanvas(200, 200);
const ctx = canvas.getContext('2d');

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

ctx.beginLayer();
ctx.fillStyle = 'rgba(225, 0, 0, 1)';
ctx.fillRect(50, 50, 75, 50);
ctx2.fillStyle = 'rgba(0, 0, 255, 1)';
ctx2.fillRect(60, 60, 75, 50);
ctx2.globalAlpha = 0.5;

ctx.beginLayer();
ctx.fillStyle = 'rgba(0, 255, 0, 1)';
ctx.fillRect(70, 70, 75, 50);
ctx2.beginLayer();
ctx2.fillStyle = 'rgba(225, 0, 0, 1)';
ctx2.fillRect(50, 50, 75, 50);

ctx.endLayer();
// Missing ctx.endLayer() here.
ctx2.beginLayer();
ctx2.fillStyle = 'rgba(0, 255, 0, 1)';
ctx2.fillRect(70, 70, 75, 50);

ctx2.endLayer();
// Missing ctx2.endLayer() here.

ctx.drawImage(canvas2, 0, 0);

const bitmap = canvas.transferToImageBitmap();
self.postMessage(bitmap, bitmap);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,14 +13,23 @@ <h1>2d.layer.unclosed</h1>
const canvas = new OffscreenCanvas(200, 200);
const ctx = canvas.getContext('2d');

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

ctx.beginLayer({filter: {name: 'dropShadow', dx: -2, dy: 2}});
ctx.fillRect(40, 40, 75, 50);
ctx.fillStyle = 'grey';
ctx.fillRect(50, 50, 75, 50);
ctx2.fillStyle = 'purple';
ctx2.fillRect(60, 60, 75, 50);
ctx2.globalAlpha = 0.5;

ctx2.beginLayer({filter: {name: 'dropShadow', dx: -2, dy: 2}});
ctx2.fillRect(40, 40, 75, 50);
ctx2.fillStyle = 'grey';
ctx2.fillRect(50, 50, 75, 50);

ctx.drawImage(canvas2, 0, 0);

const bitmap = canvas.transferToImageBitmap();
self.postMessage(bitmap, bitmap);
Expand Down
110 changes: 57 additions & 53 deletions testing/web-platform/tests/html/canvas/tools/yaml-new/layers.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -312,14 +312,29 @@
desc: Check that layers are rendered even if not closed.
size: [200, 200]
code: |
ctx.fillStyle = 'purple';
ctx.fillRect(60, 60, 75, 50);
ctx.globalAlpha = 0.5;
{% set ns = namespace(ctx='ctx') %}
{% if canvas_type == 'worker' %}
// `transferToImageBitmap` is used to transfer the test result to the
// worker's parent, but `transferToImageBitmap` can't be called on canvas
// with unclosed layers. We can however draw to a separate offscreen canvas
// and write it to the main canvas using `drawImage`.
const canvas2 = new OffscreenCanvas({{ size[0] }}, {{ size[1] }});
const ctx2 = canvas2.getContext('2d');
{% set ns.ctx = 'ctx2' %}
{% endif %}
ctx.beginLayer({filter: {name: 'dropShadow', dx: -2, dy: 2}});
ctx.fillRect(40, 40, 75, 50);
ctx.fillStyle = 'grey';
ctx.fillRect(50, 50, 75, 50);
{{ ns.ctx }}.fillStyle = 'purple';
{{ ns.ctx }}.fillRect(60, 60, 75, 50);
{{ ns.ctx }}.globalAlpha = 0.5;
{{ ns.ctx }}.beginLayer({filter: {name: 'dropShadow', dx: -2, dy: 2}});
{{ ns.ctx }}.fillRect(40, 40, 75, 50);
{{ ns.ctx }}.fillStyle = 'grey';
{{ ns.ctx }}.fillRect(50, 50, 75, 50);
{% if canvas_type == 'worker' %}
ctx.drawImage(canvas2, 0, 0);
{% endif %}
reference: |
ctx.fillStyle = 'purple';
ctx.fillRect(60, 60, 75, 50);
Expand All @@ -336,20 +351,35 @@
desc: Check that layers are rendered even if not closed.
size: [200, 200]
code: |
ctx.fillStyle = 'rgba(0, 0, 255, 1)';
ctx.fillRect(60, 60, 75, 50);
ctx.globalAlpha = 0.5;
{% set ns = namespace(ctx='ctx') %}
{% if canvas_type == 'worker' %}
// `transferToImageBitmap` is used to transfer the test result to the
// worker's parent, but `transferToImageBitmap` can't be called on canvas
// with unclosed layers. We can however draw to a separate offscreen canvas
// and write it to the main canvas using `drawImage`.
const canvas2 = new OffscreenCanvas({{ size[0] }}, {{ size[1] }});
const ctx2 = canvas2.getContext('2d');
{% set ns.ctx = 'ctx2' %}
{% endif %}
ctx.beginLayer();
ctx.fillStyle = 'rgba(225, 0, 0, 1)';
ctx.fillRect(50, 50, 75, 50);
{{ ns.ctx }}.fillStyle = 'rgba(0, 0, 255, 1)';
{{ ns.ctx }}.fillRect(60, 60, 75, 50);
{{ ns.ctx }}.globalAlpha = 0.5;
ctx.beginLayer();
ctx.fillStyle = 'rgba(0, 255, 0, 1)';
ctx.fillRect(70, 70, 75, 50);
{{ ns.ctx }}.beginLayer();
{{ ns.ctx }}.fillStyle = 'rgba(225, 0, 0, 1)';
{{ ns.ctx }}.fillRect(50, 50, 75, 50);
ctx.endLayer();
// Missing ctx.endLayer() here.
{{ ns.ctx }}.beginLayer();
{{ ns.ctx }}.fillStyle = 'rgba(0, 255, 0, 1)';
{{ ns.ctx }}.fillRect(70, 70, 75, 50);
{{ ns.ctx }}.endLayer();
// Missing {{ ns.ctx }}.endLayer() here.
{% if canvas_type == 'worker' %}
ctx.drawImage(canvas2, 0, 0);
{% endif %}
reference: |
const canvas1 = document.createElement('canvas');
const ctx1 = canvas1.getContext('2d');
Expand Down Expand Up @@ -443,44 +473,18 @@
canvasType: ['HTMLCanvas']
flush_canvas: canvas.toDataURL();


- name: 2d.layer.render-opportunities.transferToImageBitmap
desc: Checks that transferToImageBitmap flushes and rebuilds the state stack.
size: [200, 200]
- name: 2d.layer.transferToImageBitmap
desc: Check that calling transferToImageBitmap in a layer throws an exception.
canvasType: ['OffscreenCanvas', 'Worker']
code: |
ctx.fillStyle = 'purple';
ctx.fillRect(60, 60, 75, 50);
ctx.globalAlpha = 0.5;
ctx.beginLayer({filter: {name: 'dropShadow', dx: -2, dy: 2}});
ctx.fillRect(40, 40, 75, 50);
ctx.fillStyle = 'grey';
ctx.fillRect(50, 50, 75, 50);
// Force a flush and restoration of the state stack.
// `transferToImageBitmap` clears the frame but preserves render states.
// `transferToImageBitmap` shouldn't throw on it's own.
canvas.transferToImageBitmap();
ctx.fillRect(70, 70, 75, 50);
ctx.fillStyle = 'orange';
ctx.fillRect(80, 80, 75, 50);
ctx.endLayer();
ctx.fillRect(80, 40, 75, 50);
reference: |
ctx.fillStyle = 'purple';
ctx.globalAlpha = 0.5;
ctx.beginLayer({filter: {name: 'dropShadow', dx: -2, dy: 2}});
ctx.fillStyle = 'grey';
ctx.fillRect(70, 70, 75, 50);
ctx.fillStyle = 'orange';
ctx.fillRect(80, 80, 75, 50);
ctx.endLayer();
ctx.fillRect(80, 40, 75, 50);
// Make sure the exception isn't caused by calling the function twice.
canvas.transferToImageBitmap();
// Calling again inside a layer should throw.
ctx.beginLayer();
assert_throws_dom("InvalidStateError",
() => canvas.transferToImageBitmap());
- name: 2d.layer.several-complex
desc: >-
Expand Down

0 comments on commit 5031a60

Please sign in to comment.