From 861e0d56afd6eab4196c58383ed025b660125394 Mon Sep 17 00:00:00 2001 From: Marco Castelluccio Date: Wed, 8 Nov 2023 15:18:20 +0000 Subject: [PATCH] Bug 1860278 [wpt PR 42662] - Throw an exception in putImageData if canvas layers are opened, a=testonly Automatic update from web-platform-tests 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 Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/4943172 Reviewed-by: Fernando Serboncini Commit-Queue: Jean-Philippe Gravel Cr-Commit-Position: refs/heads/main{#1212741} -- wpt-commits: 6e6f9fbb0746983001d7fe80ab717567c2f73bfc wpt-pr: 42662 UltraBlame original commit: 685e2ff9c9f305fea2dfd442a95067594d05a6d1 --- .../layers/2d.layer.putImageData.html} | 412 +++++++------- ...r-opportunities.putImageData-expected.html | 331 ----------- .../layers/2d.layer.putImageData.html} | 381 +++++++------ .../layers/2d.layer.putImageData.worker.js | 294 ++++++++++ ...r-opportunities.putImageData-expected.html | 331 ----------- ...r.render-opportunities.putImageData.w.html | 520 ------------------ .../html/canvas/tools/yaml-new/layers.yaml | 187 +++++-- 7 files changed, 832 insertions(+), 1624 deletions(-) rename testing/web-platform/tests/html/canvas/{offscreen/layers/2d.layer.render-opportunities.putImageData.html => element/layers/2d.layer.putImageData.html} (52%) delete mode 100644 testing/web-platform/tests/html/canvas/element/layers/2d.layer.render-opportunities.putImageData-expected.html rename testing/web-platform/tests/html/canvas/{element/layers/2d.layer.render-opportunities.putImageData.html => offscreen/layers/2d.layer.putImageData.html} (52%) create mode 100644 testing/web-platform/tests/html/canvas/offscreen/layers/2d.layer.putImageData.worker.js delete mode 100644 testing/web-platform/tests/html/canvas/offscreen/layers/2d.layer.render-opportunities.putImageData-expected.html delete mode 100644 testing/web-platform/tests/html/canvas/offscreen/layers/2d.layer.render-opportunities.putImageData.w.html diff --git a/testing/web-platform/tests/html/canvas/offscreen/layers/2d.layer.render-opportunities.putImageData.html b/testing/web-platform/tests/html/canvas/element/layers/2d.layer.putImageData.html similarity index 52% rename from testing/web-platform/tests/html/canvas/offscreen/layers/2d.layer.render-opportunities.putImageData.html rename to testing/web-platform/tests/html/canvas/element/layers/2d.layer.putImageData.html index 42574c172fbc3..a5cb2647a8569 100644 --- a/testing/web-platform/tests/html/canvas/offscreen/layers/2d.layer.render-opportunities.putImageData.html +++ b/testing/web-platform/tests/html/canvas/element/layers/2d.layer.putImageData.html @@ -32,48 +32,108 @@ - > < -link -rel -= -" -match -" -href -= -" +title +> +Canvas +test +: 2d . layer . -render -- -opportunities -. putImageData -- -expected +< +/ +title +> +< +script +src += +" +/ +resources +/ +testharness . -html +js " > < -title +/ +script > -Canvas -test -: -2d -. -layer +< +script +src += +" +/ +resources +/ +testharnessreport . -render +js +" +> +< +/ +script +> +< +script +src += +" +/ +html +/ +canvas +/ +resources +/ +canvas - -opportunities +tests . -putImageData +js +" +> < / -title +script +> +< +link +rel += +" +stylesheet +" +href += +" +/ +html +/ +canvas +/ +resources +/ +canvas +- +tests +. +css +" +> +< +body +class += +" +show_output +" > < h1 @@ -82,10 +142,6 @@ . layer . -render -- -opportunities -. putImageData < / @@ -101,37 +157,55 @@ > Check that -layers -state -stack -is -flushed -and -rebuilt -on -frame -renders +calling +putImageData +in +a +layer +throws +an +exception . < / p > < +p +class += +" +output +" +> +Actual +output +: +< +/ +p +> +< canvas id = " -canvas +c +" +class += +" +output " width = " -200 +100 " height = " -200 +50 " > < @@ -156,128 +230,56 @@ canvas > < +ul +id += +" +d +" +> +< +/ +ul +> +< script > -const -canvas -= -new -OffscreenCanvas -( -200 -200 -) -; -const -ctx +var +t = -canvas -. -getContext +async_test ( -' -2d -' -) -; -ctx -. -fillStyle -= -' -purple -' -; -ctx +" +Check +that +calling +putImageData +in +a +layer +throws +an +exception . -fillRect -( -60 -60 -75 -50 +" ) ; -ctx -. -globalAlpha -= -0 -. -5 -; -ctx -. -beginLayer +_addTest ( -{ -filter -: -{ -name -: -' -dropShadow -' -dx -: -- -2 -dy -: -2 -} -} -) -; -ctx -. -fillRect +function ( -40 -40 -75 -50 -) -; -ctx -. -fillStyle -= -' -grey -' -; +canvas ctx -. -fillRect -( -50 -50 -75 -50 ) -; -/ -/ -Force -a -flush -and -restoration -of -the -state -stack -: +{ const canvas2 = new OffscreenCanvas ( -200 -200 +100 +50 ) ; const @@ -291,11 +293,9 @@ 2d ' ) -; -ctx -. -putImageData -( +const +data += ctx2 . getImageData @@ -305,81 +305,91 @@ 1 1 ) -0 -0 -) -; -ctx -. -fillRect -( -70 -70 -75 -50 -) ; -ctx -. -fillStyle -= +/ +/ +putImageData +shouldn ' -orange +t +throw +on +it ' -; +s +own +. ctx . -fillRect +putImageData ( -80 -80 -75 -50 +data +0 +0 ) ; +/ +/ +Make +sure +the +exception +isn +' +t +caused +by +calling +the +function +twice +. ctx . -endLayer +putImageData ( +data +0 +0 ) ; +/ +/ +Calling +again +inside +a +layer +should +throw +. ctx . -fillRect +beginLayer ( -80 -40 -75 -50 ) ; -const -outputCanvas -= -document -. -getElementById +assert_throws_dom ( " -canvas +InvalidStateError " -) -; -outputCanvas -. -getContext ( -' -2d -' ) += +> +ctx . -drawImage +putImageData ( -canvas +data 0 0 ) +) +; +} +) ; < / diff --git a/testing/web-platform/tests/html/canvas/element/layers/2d.layer.render-opportunities.putImageData-expected.html b/testing/web-platform/tests/html/canvas/element/layers/2d.layer.render-opportunities.putImageData-expected.html deleted file mode 100644 index cd7aab2bf29ab..0000000000000 --- a/testing/web-platform/tests/html/canvas/element/layers/2d.layer.render-opportunities.putImageData-expected.html +++ /dev/null @@ -1,331 +0,0 @@ -< -! -DOCTYPE -html -> -< -! -- -- -DO -NOT -EDIT -! -This -test -has -been -generated -by -/ -html -/ -canvas -/ -tools -/ -gentest -. -py -. -- -- -> -< -title -> -Canvas -test -: -2d -. -layer -. -render -- -opportunities -. -putImageData -< -/ -title -> -< -h1 -> -2d -. -layer -. -render -- -opportunities -. -putImageData -< -/ -h1 -> -< -p -class -= -" -desc -" -> -Check -that -layers -state -stack -is -flushed -and -rebuilt -on -frame -renders -. -< -/ -p -> -< -canvas -id -= -" -canvas -" -width -= -" -200 -" -height -= -" -200 -" -> -< -p -class -= -" -fallback -" -> -FAIL -( -fallback -content -) -< -/ -p -> -< -/ -canvas -> -< -script -> -const -canvas -= -document -. -getElementById -( -" -canvas -" -) -; -const -ctx -= -canvas -. -getContext -( -' -2d -' -) -; -ctx -. -fillStyle -= -' -purple -' -; -ctx -. -fillRect -( -60 -60 -75 -50 -) -; -ctx -. -globalAlpha -= -0 -. -5 -; -ctx -. -beginLayer -( -{ -filter -: -{ -name -: -' -dropShadow -' -dx -: -- -2 -dy -: -2 -} -} -) -; -ctx -. -fillStyle -= -' -purple -' -; -ctx -. -fillRect -( -40 -40 -75 -50 -) -; -ctx -. -fillStyle -= -' -grey -' -; -ctx -. -fillRect -( -50 -50 -75 -50 -) -; -ctx -. -endLayer -( -) -; -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 -) -; -< -/ -script -> diff --git a/testing/web-platform/tests/html/canvas/element/layers/2d.layer.render-opportunities.putImageData.html b/testing/web-platform/tests/html/canvas/offscreen/layers/2d.layer.putImageData.html similarity index 52% rename from testing/web-platform/tests/html/canvas/element/layers/2d.layer.render-opportunities.putImageData.html rename to testing/web-platform/tests/html/canvas/offscreen/layers/2d.layer.putImageData.html index 26fb818267b6b..f9ffb7d12e420 100644 --- a/testing/web-platform/tests/html/canvas/element/layers/2d.layer.render-opportunities.putImageData.html +++ b/testing/web-platform/tests/html/canvas/offscreen/layers/2d.layer.putImageData.html @@ -32,254 +32,203 @@ - > < -link -rel -= -" -match -" -href -= -" -2d -. -layer -. -render -- -opportunities -. -putImageData -- -expected -. -html -" -> -< title > -Canvas +OffscreenCanvas test : 2d . layer . -render -- -opportunities -. putImageData < / title > < -h1 -> -2d -. -layer -. -render -- -opportunities +script +src += +" +/ +resources +/ +testharness . -putImageData +js +" +> < / -h1 +script > < -p -class +script +src = " -desc +/ +resources +/ +testharnessreport +. +js " > -Check -that -layers -state -stack -is -flushed -and -rebuilt -on -frame -renders -. < / -p +script > < -canvas -id +script +src = " +/ +html +/ canvas -" -width -= -" -200 -" -height -= -" -200 +/ +resources +/ +canvas +- +tests +. +js " > < +/ +script +> +< +h1 +> +2d +. +layer +. +putImageData +< +/ +h1 +> +< p class = " -fallback +desc " > -FAIL -( -fallback -content -) +Check +that +calling +putImageData +in +a +layer +throws +an +exception +. < / p > < -/ -canvas -> -< script > -const -canvas +var +t = -document -. -getElementById +async_test ( " -canvas +Check +that +calling +putImageData +in +a +layer +throws +an +exception +. " ) ; -const -ctx +var +t_pass = -canvas -. -getContext -( -' -2d -' -) -; -ctx +t . -fillStyle -= -' -purple -' -; -ctx +done . -fillRect +bind ( -60 -60 -75 -50 +t ) ; -ctx -. -globalAlpha +var +t_fail = -0 +t . -5 -; -ctx -. -beginLayer +step_func ( +function +( +reason +) { -filter -: -{ -name -: -' -dropShadow -' -dx -: -- -2 -dy -: -2 -} +throw +reason +; } ) ; -ctx +t . -fillRect +step +( +function +( +) +{ +var +canvas += +new +OffscreenCanvas ( -40 -40 -75 +100 50 ) ; +var ctx -. -fillStyle = -' -grey -' -; -ctx +canvas . -fillRect +getContext ( -50 -50 -75 -50 +' +2d +' ) ; -/ -/ -Force -a -flush -and -restoration -of -the -state -stack -: const canvas2 = new OffscreenCanvas ( -200 -200 +100 +50 ) ; const @@ -293,11 +242,9 @@ 2d ' ) -; -ctx -. -putImageData -( +const +data += ctx2 . getImageData @@ -307,52 +254,96 @@ 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 . -fillRect +putImageData ( -70 -70 -75 -50 +data +0 +0 ) ; -ctx +/ +/ +Calling +again +inside +a +layer +should +throw . -fillStyle -= -' -orange -' -; ctx . -fillRect +beginLayer ( -80 -80 -75 -50 ) ; +assert_throws_dom +( +" +InvalidStateError +" +( +) += +> ctx . -endLayer +putImageData ( +data +0 +0 +) ) ; -ctx +t . -fillRect +done ( -80 -40 -75 -50 +) +; +} ) ; < diff --git a/testing/web-platform/tests/html/canvas/offscreen/layers/2d.layer.putImageData.worker.js b/testing/web-platform/tests/html/canvas/offscreen/layers/2d.layer.putImageData.worker.js new file mode 100644 index 0000000000000..f3f5f0efa7457 --- /dev/null +++ b/testing/web-platform/tests/html/canvas/offscreen/layers/2d.layer.putImageData.worker.js @@ -0,0 +1,294 @@ +/ +/ +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/testing/web-platform/tests/html/canvas/offscreen/layers/2d.layer.render-opportunities.putImageData-expected.html b/testing/web-platform/tests/html/canvas/offscreen/layers/2d.layer.render-opportunities.putImageData-expected.html deleted file mode 100644 index cd7aab2bf29ab..0000000000000 --- a/testing/web-platform/tests/html/canvas/offscreen/layers/2d.layer.render-opportunities.putImageData-expected.html +++ /dev/null @@ -1,331 +0,0 @@ -< -! -DOCTYPE -html -> -< -! -- -- -DO -NOT -EDIT -! -This -test -has -been -generated -by -/ -html -/ -canvas -/ -tools -/ -gentest -. -py -. -- -- -> -< -title -> -Canvas -test -: -2d -. -layer -. -render -- -opportunities -. -putImageData -< -/ -title -> -< -h1 -> -2d -. -layer -. -render -- -opportunities -. -putImageData -< -/ -h1 -> -< -p -class -= -" -desc -" -> -Check -that -layers -state -stack -is -flushed -and -rebuilt -on -frame -renders -. -< -/ -p -> -< -canvas -id -= -" -canvas -" -width -= -" -200 -" -height -= -" -200 -" -> -< -p -class -= -" -fallback -" -> -FAIL -( -fallback -content -) -< -/ -p -> -< -/ -canvas -> -< -script -> -const -canvas -= -document -. -getElementById -( -" -canvas -" -) -; -const -ctx -= -canvas -. -getContext -( -' -2d -' -) -; -ctx -. -fillStyle -= -' -purple -' -; -ctx -. -fillRect -( -60 -60 -75 -50 -) -; -ctx -. -globalAlpha -= -0 -. -5 -; -ctx -. -beginLayer -( -{ -filter -: -{ -name -: -' -dropShadow -' -dx -: -- -2 -dy -: -2 -} -} -) -; -ctx -. -fillStyle -= -' -purple -' -; -ctx -. -fillRect -( -40 -40 -75 -50 -) -; -ctx -. -fillStyle -= -' -grey -' -; -ctx -. -fillRect -( -50 -50 -75 -50 -) -; -ctx -. -endLayer -( -) -; -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 -) -; -< -/ -script -> diff --git a/testing/web-platform/tests/html/canvas/offscreen/layers/2d.layer.render-opportunities.putImageData.w.html b/testing/web-platform/tests/html/canvas/offscreen/layers/2d.layer.render-opportunities.putImageData.w.html deleted file mode 100644 index 3ade7f6271e96..0000000000000 --- a/testing/web-platform/tests/html/canvas/offscreen/layers/2d.layer.render-opportunities.putImageData.w.html +++ /dev/null @@ -1,520 +0,0 @@ -< -! -DOCTYPE -html -> -< -! -- -- -DO -NOT -EDIT -! -This -test -has -been -generated -by -/ -html -/ -canvas -/ -tools -/ -gentest -. -py -. -- -- -> -< -html -class -= -" -reftest -- -wait -" -> -< -link -rel -= -" -match -" -href -= -" -2d -. -layer -. -render -- -opportunities -. -putImageData -- -expected -. -html -" -> -< -title -> -Canvas -test -: -2d -. -layer -. -render -- -opportunities -. -putImageData -< -/ -title -> -< -h1 -> -2d -. -layer -. -render -- -opportunities -. -putImageData -< -/ -h1 -> -< -p -class -= -" -desc -" -> -Check -that -layers -state -stack -is -flushed -and -rebuilt -on -frame -renders -. -< -/ -p -> -< -canvas -id -= -" -canvas -" -width -= -" -200 -" -height -= -" -200 -" -> -< -p -class -= -" -fallback -" -> -FAIL -( -fallback -content -) -< -/ -p -> -< -/ -canvas -> -< -script -id -= -' -myWorker -' -type -= -' -text -/ -worker -' -> -self -. -onmessage -= -function -( -e -) -{ -const -canvas -= -new -OffscreenCanvas -( -200 -200 -) -; -const -ctx -= -canvas -. -getContext -( -' -2d -' -) -; -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 -: -const -canvas2 -= -new -OffscreenCanvas -( -200 -200 -) -; -const -ctx2 -= -canvas2 -. -getContext -( -' -2d -' -) -; -ctx -. -putImageData -( -ctx2 -. -getImageData -( -0 -0 -1 -1 -) -0 -0 -) -; -ctx -. -fillRect -( -70 -70 -75 -50 -) -; -ctx -. -fillStyle -= -' -orange -' -; -ctx -. -fillRect -( -80 -80 -75 -50 -) -; -ctx -. -endLayer -( -) -; -ctx -. -fillRect -( -80 -40 -75 -50 -) -; -const -bitmap -= -canvas -. -transferToImageBitmap -( -) -; -self -. -postMessage -( -bitmap -bitmap -) -; -} -; -< -/ -script -> -< -script -> -const -blob -= -new -Blob -( -[ -document -. -getElementById -( -' -myWorker -' -) -. -textContent -] -) -; -const -worker -= -new -Worker -( -URL -. -createObjectURL -( -blob -) -) -; -worker -. -addEventListener -( -' -message -' -msg -= -> -{ -const -outputCtx -= -document -. -getElementById -( -" -canvas -" -) -. -getContext -( -' -2d -' -) -; -outputCtx -. -drawImage -( -msg -. -data -0 -0 -) -; -document -. -documentElement -. -classList -. -remove -( -" -reftest -- -wait -" -) -; -} -) -; -worker -. -postMessage -( -null -) -; -< -/ -script -> -< -/ -html -> diff --git a/testing/web-platform/tests/html/canvas/tools/yaml-new/layers.yaml b/testing/web-platform/tests/html/canvas/tools/yaml-new/layers.yaml index b6eea0561c902..bcec83175eb9b 100644 --- a/testing/web-platform/tests/html/canvas/tools/yaml-new/layers.yaml +++ b/testing/web-platform/tests/html/canvas/tools/yaml-new/layers.yaml @@ -3834,12 +3834,80 @@ resolve ) ) ; -putImageData +toBlob +: +test_type +: +" +promise +" +canvasType : +[ +' +HTMLCanvas +' +] flush_canvas : | - +await +new +Promise +( +resolve += +> +canvas +. +toBlob +( +resolve +) +) +; +toDataURL +: +canvasType +: +[ +' +HTMLCanvas +' +] +flush_canvas +: +canvas +. +toDataURL +( +) +; +- +name +: +2d +. +layer +. +putImageData +desc +: +Check +that +calling +putImageData +in +a +layer +throws +an +exception +. +code +: +| const canvas2 = @@ -3875,11 +3943,9 @@ getContext 2d ' ) -; -ctx -. -putImageData -( +const +data += ctx2 . getImageData @@ -3889,58 +3955,87 @@ getImageData 1 1 ) +; +/ +/ +putImageData +shouldn +' +t +throw +on +it +' +s +own +. +ctx +. +putImageData +( +data 0 0 ) ; -toBlob -: -test_type -: -" -promise -" -canvasType -: -[ -' -HTMLCanvas +/ +/ +Make +sure +the +exception +isn ' -] -flush_canvas -: -| -- -await -new -Promise -( -resolve -= -> -canvas +t +caused +by +calling +the +function +twice . -toBlob +ctx +. +putImageData ( -resolve +data +0 +0 ) +; +/ +/ +Calling +again +inside +a +layer +should +throw +. +ctx +. +beginLayer +( ) ; -toDataURL -: -canvasType -: -[ -' -HTMLCanvas -' -] -flush_canvas -: -canvas +assert_throws_dom +( +" +InvalidStateError +" +( +) += +> +ctx . -toDataURL +putImageData ( +data +0 +0 +) ) ; -