From 958b16df650f6756b4c0c83f1f2494b4a138e21d Mon Sep 17 00:00:00 2001 From: Jean-Philippe Gravel Date: Fri, 20 Oct 2023 06:48:04 -0700 Subject: [PATCH] Throw an exception in putImageData 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. putImageData however is supposed to write to the canvas bitmap wholesale, bypassing all render states. This means that we can't write to the layer's content because the written pixels would then get filtered when the layer is closed. We can't write to the main canvas either because these pixels would later be overwritten by the layer's result with draw calls that potentially happened before (e.g. in the sequence `beginLayer(); fillRect(); putImageData(); endLayer();`, `fillRect()` would write over `putImageData()`. 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: https://github.com/whatwg/html/pull/9537 Change-Id: I266a3155c32919a68dbbb093e4aff9b1dd13a3b5 Bug: 1484741 --- .../element/layers/2d.layer.putImageData.html | 35 +++++++++++++ ...r-opportunities.putImageData-expected.html | 32 ------------ ...yer.render-opportunities.putImageData.html | 34 ------------- .../layers/2d.layer.putImageData.html | 36 +++++++++++++ .../layers/2d.layer.putImageData.worker.js | 31 +++++++++++ ...r-opportunities.putImageData-expected.html | 32 ------------ ...yer.render-opportunities.putImageData.html | 37 -------------- ...r.render-opportunities.putImageData.w.html | 51 ------------------- html/canvas/tools/yaml-new/layers.yaml | 19 +++++-- 9 files changed, 116 insertions(+), 191 deletions(-) create mode 100644 html/canvas/element/layers/2d.layer.putImageData.html delete mode 100644 html/canvas/element/layers/2d.layer.render-opportunities.putImageData-expected.html delete mode 100644 html/canvas/element/layers/2d.layer.render-opportunities.putImageData.html create mode 100644 html/canvas/offscreen/layers/2d.layer.putImageData.html create mode 100644 html/canvas/offscreen/layers/2d.layer.putImageData.worker.js delete mode 100644 html/canvas/offscreen/layers/2d.layer.render-opportunities.putImageData-expected.html delete mode 100644 html/canvas/offscreen/layers/2d.layer.render-opportunities.putImageData.html delete mode 100644 html/canvas/offscreen/layers/2d.layer.render-opportunities.putImageData.w.html diff --git a/html/canvas/element/layers/2d.layer.putImageData.html b/html/canvas/element/layers/2d.layer.putImageData.html new file mode 100644 index 000000000000000..1e4d3331d8f0f7c --- /dev/null +++ b/html/canvas/element/layers/2d.layer.putImageData.html @@ -0,0 +1,35 @@ + + +Canvas test: 2d.layer.putImageData + + + + + + +

2d.layer.putImageData

+

Check that calling putImageData in a layer throws an exception.

+ + +

Actual output:

+

FAIL (fallback content)

+ + + + diff --git a/html/canvas/element/layers/2d.layer.render-opportunities.putImageData-expected.html b/html/canvas/element/layers/2d.layer.render-opportunities.putImageData-expected.html deleted file mode 100644 index c73da157b12a227..000000000000000 --- a/html/canvas/element/layers/2d.layer.render-opportunities.putImageData-expected.html +++ /dev/null @@ -1,32 +0,0 @@ - - -Canvas test: 2d.layer.render-opportunities.putImageData -

2d.layer.render-opportunities.putImageData

-

Check that layers state stack is flushed and rebuilt on frame renders.

- -

FAIL (fallback content)

-
- diff --git a/html/canvas/element/layers/2d.layer.render-opportunities.putImageData.html b/html/canvas/element/layers/2d.layer.render-opportunities.putImageData.html deleted file mode 100644 index 6a1215dd604e7c0..000000000000000 --- a/html/canvas/element/layers/2d.layer.render-opportunities.putImageData.html +++ /dev/null @@ -1,34 +0,0 @@ - - - -Canvas test: 2d.layer.render-opportunities.putImageData -

2d.layer.render-opportunities.putImageData

-

Check that layers state stack is flushed and rebuilt on frame renders.

- -

FAIL (fallback content)

-
- diff --git a/html/canvas/offscreen/layers/2d.layer.putImageData.html b/html/canvas/offscreen/layers/2d.layer.putImageData.html new file mode 100644 index 000000000000000..b46b80c1f46e39b --- /dev/null +++ b/html/canvas/offscreen/layers/2d.layer.putImageData.html @@ -0,0 +1,36 @@ + + +OffscreenCanvas test: 2d.layer.putImageData + + + + +

2d.layer.putImageData

+

Check that calling putImageData in a layer throws an exception.

+ + + diff --git a/html/canvas/offscreen/layers/2d.layer.putImageData.worker.js b/html/canvas/offscreen/layers/2d.layer.putImageData.worker.js new file mode 100644 index 000000000000000..dd4b3576f66e780 --- /dev/null +++ b/html/canvas/offscreen/layers/2d.layer.putImageData.worker.js @@ -0,0 +1,31 @@ +// DO NOT EDIT! This test has been generated by /html/canvas/tools/gentest.py. +// OffscreenCanvas test in a worker:2d.layer.putImageData +// Description:Check that calling putImageData 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 putImageData 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'); + + const canvas2 = new OffscreenCanvas(100, 50); + const ctx2 = canvas2.getContext('2d') + const data = ctx2.getImageData(0, 0, 1, 1); + // `putImageData` shouldn't throw on it's own. + ctx.putImageData(data, 0, 0); + // Make sure the exception isn't caused by calling the function twice. + ctx.putImageData(data, 0, 0); + // Calling again inside a layer should throw. + ctx.beginLayer(); + assert_throws_dom("InvalidStateError", () => ctx.putImageData(data, 0, 0)); + t.done(); +}); +done(); diff --git a/html/canvas/offscreen/layers/2d.layer.render-opportunities.putImageData-expected.html b/html/canvas/offscreen/layers/2d.layer.render-opportunities.putImageData-expected.html deleted file mode 100644 index c73da157b12a227..000000000000000 --- a/html/canvas/offscreen/layers/2d.layer.render-opportunities.putImageData-expected.html +++ /dev/null @@ -1,32 +0,0 @@ - - -Canvas test: 2d.layer.render-opportunities.putImageData -

2d.layer.render-opportunities.putImageData

-

Check that layers state stack is flushed and rebuilt on frame renders.

- -

FAIL (fallback content)

-
- diff --git a/html/canvas/offscreen/layers/2d.layer.render-opportunities.putImageData.html b/html/canvas/offscreen/layers/2d.layer.render-opportunities.putImageData.html deleted file mode 100644 index 2caf797db75fb06..000000000000000 --- a/html/canvas/offscreen/layers/2d.layer.render-opportunities.putImageData.html +++ /dev/null @@ -1,37 +0,0 @@ - - - -Canvas test: 2d.layer.render-opportunities.putImageData -

2d.layer.render-opportunities.putImageData

-

Check that layers state stack is flushed and rebuilt on frame renders.

- -

FAIL (fallback content)

-
- diff --git a/html/canvas/offscreen/layers/2d.layer.render-opportunities.putImageData.w.html b/html/canvas/offscreen/layers/2d.layer.render-opportunities.putImageData.w.html deleted file mode 100644 index be9b4fd454da75f..000000000000000 --- a/html/canvas/offscreen/layers/2d.layer.render-opportunities.putImageData.w.html +++ /dev/null @@ -1,51 +0,0 @@ - - - - -Canvas test: 2d.layer.render-opportunities.putImageData -

2d.layer.render-opportunities.putImageData

-

Check that layers state stack is flushed and rebuilt on frame renders.

- -

FAIL (fallback content)

-
- - - diff --git a/html/canvas/tools/yaml-new/layers.yaml b/html/canvas/tools/yaml-new/layers.yaml index 41cd2ddfb3d88aa..56cd0331f29c024 100644 --- a/html/canvas/tools/yaml-new/layers.yaml +++ b/html/canvas/tools/yaml-new/layers.yaml @@ -459,11 +459,6 @@ test_type: "promise" flush_canvas: |- await new Promise(resolve => requestAnimationFrame(resolve)); - putImageData: - flush_canvas: |- - const canvas2 = new OffscreenCanvas({{ size[0] }}, {{ size[1] }}); - const ctx2 = canvas2.getContext('2d'); - ctx.putImageData(ctx2.getImageData(0, 0, 1, 1), 0, 0); toBlob: test_type: "promise" canvasType: ['HTMLCanvas'] @@ -473,6 +468,20 @@ canvasType: ['HTMLCanvas'] flush_canvas: canvas.toDataURL(); +- name: 2d.layer.putImageData + desc: Check that calling putImageData in a layer throws an exception. + code: | + const canvas2 = new OffscreenCanvas({{ size[0] }}, {{ size[1] }}); + const ctx2 = canvas2.getContext('2d') + const data = ctx2.getImageData(0, 0, 1, 1); + // `putImageData` shouldn't throw on it's own. + ctx.putImageData(data, 0, 0); + // Make sure the exception isn't caused by calling the function twice. + ctx.putImageData(data, 0, 0); + // Calling again inside a layer should throw. + ctx.beginLayer(); + assert_throws_dom("InvalidStateError", () => ctx.putImageData(data, 0, 0)); + - name: 2d.layer.transferToImageBitmap desc: Check that calling transferToImageBitmap in a layer throws an exception. canvasType: ['OffscreenCanvas', 'Worker']