tor-browser

The Tor Browser
git clone https://git.dasho.dev/tor-browser.git
Log | Files | Refs | README | LICENSE

layers.yaml (44498B)


      1 - name: 2d.layer.global-states
      2  desc: Checks that layers correctly use global render states.
      3  size: [90, 90]
      4  code: |
      5    {{ transform_statement }}
      6 
      7    ctx.fillStyle = 'rgba(128, 128, 128, 1)';
      8    ctx.fillRect(20, 15, 50, 50);
      9 
     10    {{ alpha_statement }}
     11    {{ composite_op_statement }}
     12    {{ shadow_statement }}
     13    {{ filter_statement }}
     14 
     15    ctx.beginLayer();
     16 
     17    // Enable compositing in the layer to validate that draw calls in the layer
     18    // won't individually composite with the background.
     19    ctx.globalCompositeOperation = 'screen';
     20 
     21    ctx.fillStyle = 'rgba(255, 0, 0, 1)';
     22    ctx.fillRect(10, 25, 40, 45);
     23    ctx.fillStyle = 'rgba(0, 255, 0, 1)';
     24    ctx.fillRect(30, 5, 45, 40);
     25 
     26    ctx.endLayer();
     27  reference: |
     28    {{ transform_statement }}
     29 
     30    ctx.fillStyle = 'rgba(128, 128, 128, 1)';
     31    ctx.fillRect(20, 15, 50, 50);
     32 
     33    {{ alpha_statement }}
     34    {{ composite_op_statement }}
     35    {{ shadow_statement }}
     36    {{ filter_statement }}
     37 
     38    const canvas2 = document.createElement("canvas");
     39    const ctx2 = canvas2.getContext("2d");
     40 
     41    ctx2.globalCompositeOperation = 'screen';
     42    ctx2.fillStyle = 'rgba(255, 0, 0, 1)';
     43    ctx2.fillRect(10, 25, 40, 45);
     44    ctx2.fillStyle = 'rgba(0, 255, 0, 1)';
     45    ctx2.fillRect(30, 5, 45, 40);
     46 
     47    ctx.drawImage(canvas2, 0, 0);
     48  variants_layout:
     49    [single_file, single_file, single_file, multi_files, multi_files]
     50  grid_width: 8
     51  variants: &global-state-variants
     52  - no-globalAlpha:
     53      alpha_statement: // No globalAlpha.
     54    globalAlpha:
     55      alpha_statement: ctx.globalAlpha = 0.75;
     56  - no-composite-op:
     57      composite_op_statement: // No globalCompositeOperation.
     58    blending:
     59      composite_op_statement: ctx.globalCompositeOperation = 'multiply';
     60    composite:
     61      composite_op_statement: ctx.globalCompositeOperation = 'source-in';
     62    copy:
     63      composite_op_statement: ctx.globalCompositeOperation = 'copy';
     64  - no-shadow:
     65      shadow_statement: // No shadow.
     66    shadow:
     67      shadow_statement: |-
     68        ctx.shadowOffsetX = -7;
     69        ctx.shadowOffsetY = 7;
     70        ctx.shadowColor = 'rgba(255, 165, 0, 0.5)';
     71        ctx.shadowBlur = 3;
     72  - no-cxt-filter:
     73      filter_statement: // No filter.
     74    ctx-filter:
     75      filter_statement: ctx.filter = 'drop-shadow(-4px -4px 0 fuchsia)';
     76  - no-transform:
     77      transform_statement: // No transform.
     78    rotation:
     79      transform_statement: |-
     80        ctx.translate(50, 40);
     81        ctx.rotate(Math.PI);
     82        ctx.translate(-45, -45);
     83 
     84 
     85 - name: >-
     86    2d.layer.global-states.filter.{{ file_variant_name }}.tentative
     87  desc: Checks that layers with filters correctly use global render states.
     88  size: [90, 90]
     89  code: |
     90    {{ transform_statement }}
     91 
     92    ctx.fillStyle = 'rgba(128, 128, 128, 1)';
     93    ctx.fillRect(20, 15, 50, 50);
     94 
     95    {{ alpha_statement }}
     96    {{ composite_op_statement }}
     97    {{ shadow_statement }}
     98    {{ filter_statement }}
     99 
    100    ctx.beginLayer({filter: [
    101        {name: 'dropShadow',
    102            dx: 8, dy: 8, stdDeviation: 0, floodColor: '#00f'},
    103        {name: 'componentTransfer',
    104            funcA: {type: "table", tableValues: [0, 0.8]}}]});
    105 
    106    ctx.fillStyle = 'rgba(255, 0, 0, 1)';
    107    ctx.fillRect(10, 25, 40, 45);
    108    ctx.fillStyle = 'rgba(0, 255, 0, 1)';
    109    ctx.fillRect(30, 5, 45, 40);
    110 
    111    ctx.endLayer();
    112  reference: |
    113    const svg = `
    114      <svg xmlns="http://www.w3.org/2000/svg"
    115            width="{{ size[0] }}" height="{{ size[1] }}"
    116            color-interpolation-filters="sRGB">
    117        <filter id="filter" x="-100%" y="-100%" width="300%" height="300%">
    118          <feDropShadow dx="8" dy="8" stdDeviation="0" flood-color="#00f" />
    119          <feComponentTransfer>
    120            <feFuncA type="table" tableValues="0 0.8"></feFuncA>
    121          </feComponentTransfer>
    122        </filter>
    123        <g filter="url(#filter)">
    124          <rect x="10" y="25" width="40" height="45" fill="rgba(255, 0, 0, 1)"/>
    125          <rect x="30" y="5" width="45" height="40" fill="rgba(0, 255, 0, 1)"/>
    126        </g>
    127      </svg>`;
    128 
    129    const img = new Image();
    130    img.width = {{ size[0] }};
    131    img.height = {{ size[1] }};
    132    img.onload = () => {
    133      {{ transform_statement | indent(2) }}
    134 
    135      ctx.fillStyle = 'rgba(128, 128, 128, 1)';
    136      ctx.fillRect(20, 15, 50, 50);
    137 
    138      {{ alpha_statement | indent(2) }}
    139      {{ composite_op_statement | indent(2) }}
    140      {{ shadow_statement | indent(2) }}
    141      {{ filter_statement | indent(2) }}
    142 
    143      ctx.drawImage(img, 0, 0);
    144    };
    145    img.src = 'data:image/svg+xml;base64,' + btoa(svg);
    146  append_variants_to_name: false
    147  variants_layout:
    148    [single_file, single_file, single_file, multi_files, multi_files]
    149  grid_width: 8
    150  variants: *global-state-variants
    151 
    152 - name: 2d.layer.globalCompositeOperation
    153  desc: Checks that layers work with all globalCompositeOperation values.
    154  size: [90, 90]
    155  code: |
    156    ctx.translate(50, 50);
    157    ctx.scale(2, 2);
    158    ctx.rotate(Math.PI);
    159    ctx.translate(-25, -25);
    160 
    161    ctx.fillStyle = 'rgba(0, 0, 255, 0.8)';
    162    ctx.fillRect(15, 15, 25, 25);
    163 
    164    ctx.globalAlpha = 0.75;
    165    ctx.globalCompositeOperation = '{{ variant_name }}';
    166    ctx.shadowOffsetX = 7;
    167    ctx.shadowOffsetY = 7;
    168    ctx.shadowColor = 'rgba(255, 165, 0, 0.5)';
    169 
    170    ctx.beginLayer();
    171 
    172    ctx.fillStyle = 'rgba(204, 0, 0, 1)';
    173    ctx.fillRect(10, 25, 25, 20);
    174    ctx.fillStyle = 'rgba(0, 204, 0, 1)';
    175    ctx.fillRect(25, 10, 20, 25);
    176 
    177    ctx.endLayer();
    178  reference: |
    179    ctx.translate(50, 50);
    180    ctx.scale(2, 2);
    181    ctx.rotate(Math.PI);
    182    ctx.translate(-25, -25);
    183 
    184    ctx.fillStyle = 'rgba(0, 0, 255, 0.8)';
    185    ctx.fillRect(15, 15, 25, 25);
    186 
    187    ctx.globalAlpha = 0.75;
    188    ctx.globalCompositeOperation = '{{ variant_name }}';
    189    ctx.shadowOffsetX = 7;
    190    ctx.shadowOffsetY = 7;
    191    ctx.shadowColor = 'rgba(255, 165, 0, 0.5)';
    192 
    193    const canvas2 = document.createElement("canvas");
    194    const ctx2 = canvas2.getContext("2d");
    195 
    196    ctx2.fillStyle = 'rgba(204, 0, 0, 1)';
    197    ctx2.fillRect(10, 25, 25, 20);
    198    ctx2.fillStyle = 'rgba(0, 204, 0, 1)';
    199    ctx2.fillRect(25, 10, 20, 25);
    200 
    201    ctx.imageSmoothingEnabled = false;
    202    ctx.drawImage(canvas2, 0, 0);
    203  variants_layout: [single_file]
    204  grid_width: 7
    205  variants:
    206  - source-over:
    207    source-in:
    208    source-out:
    209    source-atop:
    210    destination-over:
    211    destination-in:
    212    destination-out:
    213    destination-atop:
    214    lighter:
    215    copy:
    216    xor:
    217    multiply:
    218    screen:
    219    overlay:
    220    darken:
    221    lighten:
    222    color-dodge:
    223    color-burn:
    224    hard-light:
    225    soft-light:
    226    difference:
    227    exclusion:
    228    hue:
    229    saturation:
    230    color:
    231    luminosity:
    232 
    233 - name: 2d.layer.non-invertible-matrix
    234  desc: Test drawing layers when the transform is not invertible.
    235  size: [200, 200]
    236  code: |
    237    ctx.fillStyle = 'blue';
    238    ctx.fillRect(30, 30, 50, 50);
    239 
    240    // Anything below will be non-rasterizable.
    241    ctx.scale(1, 0);
    242 
    243    // Open the layer with a non-invertible matrix. The whole layer will be
    244    // non-rasterizable.
    245    ctx.beginLayer();
    246 
    247    // Because the transform is global, the matrix is still non-invertible.
    248    ctx.fillStyle = 'rgba(225, 0, 0, 1)';
    249    ctx.fillRect(40, 70, 50, 50);
    250 
    251    // Set a valid matrix in the middle of the layer. This makes the global
    252    // matrix invertible again, but because because the layer was opened with a
    253    // non-invertible matrix, the whole layer remains non-rasterizable.
    254    ctx.setTransform(1, 0, 0, 1, 0, 0);
    255 
    256    ctx.fillStyle = 'green';
    257    ctx.fillRect(70, 40, 50, 50);
    258 
    259    ctx.endLayer();
    260  reference: |
    261    ctx.fillStyle = 'blue';
    262    ctx.fillRect(30, 30, 50, 50);
    263 
    264 - name: 2d.layer.non-invertible-matrix.shadow
    265  desc: Test drawing layers when the transform is not invertible.
    266  size: [200, 200]
    267  code: |
    268    ctx.fillStyle = 'blue';
    269    ctx.fillRect(30, 30, 50, 50);
    270 
    271    // Anything below will be non-rasterizable.
    272    ctx.scale(1, 0);
    273 
    274    ctx.shadowOffsetX = 0;
    275    ctx.shadowOffsetY = 20;
    276    ctx.shadowColor = 'rgba(255, 165, 0, 0.6)';
    277 
    278    // Open the layer with a non-invertible matrix. The whole layer will be
    279    // non-rasterizable.
    280    ctx.beginLayer();
    281 
    282    // Because the transform is global, the matrix is still non-invertible.
    283    ctx.fillStyle = 'rgba(225, 0, 0, 1)';
    284    ctx.fillRect(40, 70, 50, 50);
    285 
    286    // Set a valid matrix in the middle of the layer. This makes the global
    287    // matrix invertible again, but because because the layer was opened with a
    288    // non-invertible matrix, the whole layer remains non-rasterizable.
    289    ctx.setTransform(1, 0, 0, 1, 0, 0);
    290 
    291    ctx.fillStyle = 'green';
    292    ctx.fillRect(70, 40, 50, 50);
    293 
    294    ctx.endLayer();
    295  reference: |
    296    ctx.fillStyle = 'blue';
    297    ctx.fillRect(30, 30, 50, 50);
    298 
    299 - name: >-
    300   2d.layer.non-invertible-matrix.with-render-states.{{ file_variant_name }}{{
    301   tentative }}
    302  desc: Test drawing layers when the transform is not invertible.
    303  size: [200, 200]
    304  code: |
    305    ctx.fillStyle = 'blue';
    306    ctx.fillRect(30, 30, 50, 50);
    307 
    308    // Anything below will be non-rasterizable.
    309    ctx.scale(1, 0);
    310 
    311    ctx.globalAlpha = 0.8;
    312    ctx.globalCompositeOperation = 'multiply';
    313    ctx.shadowOffsetX = 0;
    314    ctx.shadowOffsetY = 20;
    315    ctx.shadowColor = 'rgba(255, 165, 0, 0.6)';
    316    ctx.filter = 'blur(10px)';
    317 
    318    // Open the layer with a non-invertible matrix. The whole layer will be
    319    // non-rasterizable.
    320    {{ begin_layer }}
    321 
    322    // Because the transform is global, the matrix is still non-invertible.
    323    ctx.fillStyle = 'rgba(225, 0, 0, 1)';
    324    ctx.fillRect(40, 70, 50, 50);
    325 
    326    // Set a valid matrix in the middle of the layer. This makes the global
    327    // matrix invertible again, but because because the layer was opened with a
    328    // non-invertible matrix, the whole layer remains non-rasterizable.
    329    ctx.setTransform(1, 0, 0, 1, 0, 0);
    330 
    331    ctx.fillStyle = 'green';
    332    ctx.fillRect(70, 40, 50, 50);
    333 
    334    ctx.endLayer();
    335  reference: |
    336    ctx.fillStyle = 'blue';
    337    ctx.fillRect(30, 30, 50, 50);
    338  append_variants_to_name: false
    339  variants:
    340  - plain-layer:
    341      begin_layer: ctx.beginLayer();
    342    filtered-layer:
    343      begin_layer: |-
    344        ctx.beginLayer({filter:
    345                        {name: 'dropShadow', dx: 10, dy: 0, stdDeviation: 0,
    346                        floodColor: 'rgba(128, 128, 255, 0.7)'}});
    347      tentative: .tentative
    348 
    349 - name: 2d.layer.nested
    350  desc: Tests nested canvas layers.
    351  size: [200, 200]
    352  code: |
    353    var circle = new Path2D();
    354    circle.arc(90, 90, 40, 0, 2 * Math.PI);
    355    ctx.fill(circle);
    356 
    357    ctx.globalCompositeOperation = 'source-in';
    358 
    359    ctx.beginLayer();
    360 
    361    ctx.fillStyle = 'rgba(0, 0, 255, 1)';
    362    ctx.fillRect(60, 60, 75, 50);
    363 
    364    ctx.globalAlpha = 0.5;
    365 
    366    ctx.beginLayer();
    367 
    368    ctx.fillStyle = 'rgba(225, 0, 0, 1)';
    369    ctx.fillRect(50, 50, 75, 50);
    370    ctx.fillStyle = 'rgba(0, 255, 0, 1)';
    371    ctx.fillRect(70, 70, 75, 50);
    372 
    373    ctx.endLayer();
    374    ctx.endLayer();
    375  reference: |
    376    var circle = new Path2D();
    377    circle.arc(90, 90, 40, 0, 2 * Math.PI);
    378    ctx.fill(circle);
    379 
    380    ctx.globalCompositeOperation = 'source-in';
    381 
    382    canvas2 = document.createElement("canvas");
    383    ctx2 = canvas2.getContext("2d");
    384 
    385    ctx2.fillStyle = 'rgba(0, 0, 255, 1)';
    386    ctx2.fillRect(60, 60, 75, 50);
    387 
    388    ctx2.globalAlpha = 0.5;
    389 
    390    canvas3 = document.createElement("canvas");
    391    ctx3 = canvas3.getContext("2d");
    392 
    393    ctx3.fillStyle = 'rgba(225, 0, 0, 1)';
    394    ctx3.fillRect(50, 50, 75, 50);
    395    ctx3.fillStyle = 'rgba(0, 255, 0, 1)';
    396    ctx3.fillRect(70, 70, 75, 50);
    397 
    398    ctx2.drawImage(canvas3, 0, 0);
    399    ctx.drawImage(canvas2, 0, 0);
    400 
    401 
    402 - name: 2d.layer.restore-style
    403  desc: Test that ensure layers restores style values upon endLayer.
    404  size: [200, 200]
    405  fuzzy: maxDifference=0-1; totalPixels=0-950
    406  code: |
    407    ctx.fillStyle = 'rgba(0,0,255,1)';
    408    ctx.fillRect(50, 50, 75, 50);
    409    ctx.globalAlpha = 0.5;
    410 
    411    ctx.beginLayer();
    412    ctx.fillStyle = 'rgba(225, 0, 0, 1)';
    413    ctx.fillRect(60, 60, 75, 50);
    414    ctx.endLayer();
    415 
    416    ctx.fillRect(70, 70, 75, 50);
    417  reference: |
    418    ctx.fillStyle = 'rgba(0, 0, 255, 1)';
    419    ctx.fillRect(50, 50, 75, 50);
    420    ctx.globalAlpha = 0.5;
    421 
    422    canvas2 = document.createElement("canvas");
    423    ctx2 = canvas2.getContext("2d");
    424    ctx2.fillStyle = 'rgba(225, 0, 0, 1)';
    425    ctx2.fillRect(60, 60, 75, 50);
    426    ctx.drawImage(canvas2, 0, 0);
    427 
    428    ctx.fillRect(70, 70, 75, 50);
    429 
    430 - name: 2d.layer.layer-rendering-state-reset-in-layer
    431  desc: Tests that rendering states are reset in layers and restored after.
    432  test_type: sync
    433  code: |
    434    ctx.globalAlpha = 0.5;
    435    ctx.globalCompositeOperation = 'xor';
    436    ctx.shadowColor = '#0000ff';
    437    ctx.shadowOffsetX = 10;
    438    ctx.shadowOffsetY = 20;
    439    ctx.shadowBlur = 30;
    440    ctx.filter = 'blur(5px)';
    441 
    442    @assert ctx.globalAlpha === 0.5;
    443    @assert ctx.globalCompositeOperation === 'xor';
    444    @assert ctx.shadowColor === '#0000ff';
    445    @assert ctx.shadowOffsetX === 10;
    446    @assert ctx.shadowOffsetY === 20;
    447    @assert ctx.shadowBlur === 30;
    448    @assert ctx.filter === 'blur(5px)';
    449 
    450    ctx.beginLayer();
    451 
    452    @assert ctx.globalAlpha === 1.0;
    453    @assert ctx.globalCompositeOperation === 'source-over';
    454    @assert ctx.shadowColor === 'rgba(0, 0, 0, 0)';
    455    @assert ctx.shadowOffsetX === 0;
    456    @assert ctx.shadowOffsetY === 0;
    457    @assert ctx.shadowBlur === 0;
    458    @assert ctx.filter === 'none';
    459 
    460    ctx.endLayer();
    461 
    462    @assert ctx.globalAlpha === 0.5;
    463    @assert ctx.globalCompositeOperation === 'xor';
    464    @assert ctx.shadowColor === '#0000ff';
    465    @assert ctx.shadowOffsetX === 10;
    466    @assert ctx.shadowOffsetY === 20;
    467    @assert ctx.shadowBlur === 30;
    468    @assert ctx.filter === 'blur(5px)';
    469 
    470 - name: 2d.layer.clip-outside.{{ file_variant_name }}{{ tentative }}
    471  desc: Check clipping set outside the layer
    472  size: [100, 100]
    473  code: |
    474    ctx.beginPath();
    475    ctx.rect(15, 15, 70, 70);
    476    ctx.clip();
    477 
    478    {{ filtered_layer }}
    479    ctx.fillStyle = 'blue';
    480    ctx.fillRect(10, 10, 80, 80);
    481    ctx.endLayer();
    482  reference: |
    483    const canvas2 = new OffscreenCanvas(200, 200);
    484    const ctx2 = canvas2.getContext('2d');
    485 
    486    ctx2.filter = 'blur(12px)';
    487    ctx2.beginLayer();
    488    ctx2.fillStyle = 'blue';
    489    ctx2.fillRect(10, 10, 80, 80);
    490    ctx2.endLayer();
    491 
    492    ctx.beginPath();
    493    ctx.rect(15, 15, 70, 70);
    494    ctx.clip();
    495 
    496    ctx.drawImage(canvas2, 0, 0);
    497  append_variants_to_name: false
    498  variants:
    499  - ctx-filter:
    500      filtered_layer: |-
    501        ctx.filter = 'blur(12px)';
    502        ctx.beginLayer();
    503    layer-filter:
    504      filtered_layer: |-
    505        ctx.beginLayer({filter: {name: "gaussianBlur", stdDeviation: 12}});
    506      tentative: .tentative
    507 
    508 - name: 2d.layer.clip-inside.{{ file_variant_name }}{{ tentative }}
    509  desc: Check clipping set inside the layer
    510  size: [100, 100]
    511  code: |
    512    {{ filtered_layer }}
    513 
    514    ctx.beginPath();
    515    ctx.rect(15, 15, 70, 70);
    516    ctx.clip();
    517 
    518    ctx.fillStyle = 'blue';
    519    ctx.fillRect(10, 10, 80, 80);
    520    ctx.endLayer();
    521  reference: |
    522    const canvas2 = new OffscreenCanvas(200, 200);
    523    const ctx2 = canvas2.getContext('2d');
    524 
    525    ctx2.beginPath();
    526    ctx2.rect(15, 15, 70, 70);
    527    ctx2.clip();
    528 
    529    ctx2.fillStyle = 'blue';
    530    ctx2.fillRect(10, 10, 80, 80);
    531 
    532    ctx.filter = 'blur(12px)';
    533    ctx.beginLayer();
    534    ctx.drawImage(canvas2, 0, 0);
    535    ctx.endLayer();
    536  append_variants_to_name: false
    537  variants:
    538  - ctx-filter:
    539      filtered_layer: |-
    540        ctx.filter = 'blur(12px)';
    541        ctx.beginLayer();
    542    layer-filter:
    543      filtered_layer: |-
    544        ctx.beginLayer({filter: {name: "gaussianBlur", stdDeviation: 12}});
    545      tentative: .tentative
    546 
    547 - name: 2d.layer.clip-inside-and-outside.{{ file_variant_name }}{{ tentative }}
    548  desc: Check clipping set inside and outside the layer
    549  size: [100, 100]
    550  code: |
    551    ctx.beginPath();
    552    ctx.rect(15, 15, 70, 70);
    553    ctx.clip();
    554 
    555    {{ filtered_layer }}
    556 
    557    ctx.beginPath();
    558    ctx.rect(15, 15, 70, 70);
    559    ctx.clip();
    560 
    561    ctx.fillStyle = 'blue';
    562    ctx.fillRect(10, 10, 80, 80);
    563    ctx.endLayer();
    564  reference: |
    565    const canvas2 = new OffscreenCanvas(200, 200);
    566    const ctx2 = canvas2.getContext('2d');
    567 
    568    ctx2.beginPath();
    569    ctx2.rect(15, 15, 70, 70);
    570    ctx2.clip();
    571 
    572    ctx2.fillStyle = 'blue';
    573    ctx2.fillRect(10, 10, 80, 80);
    574 
    575    const canvas3 = new OffscreenCanvas(200, 200);
    576    const ctx3 = canvas3.getContext('2d');
    577 
    578    ctx3.filter = 'blur(12px)';
    579    ctx3.beginLayer();
    580    ctx3.drawImage(canvas2, 0, 0);
    581    ctx3.endLayer();
    582 
    583    ctx.beginPath();
    584    ctx.rect(15, 15, 70, 70);
    585    ctx.clip();
    586    ctx.drawImage(canvas3, 0, 0);
    587  append_variants_to_name: false
    588  variants:
    589  - ctx-filter:
    590      filtered_layer: |-
    591        ctx.filter = 'blur(12px)';
    592        ctx.beginLayer();
    593    layer-filter:
    594      filtered_layer: |-
    595        ctx.beginLayer({filter: {name: "gaussianBlur", stdDeviation: 12}});
    596      tentative: .tentative
    597 
    598 - name: 2d.layer.ctm.layer-filter.tentative
    599  desc: Checks that parent transforms affect layer filters.
    600  size: [200, 200]
    601  code: |
    602    // Transforms inside the layer should not apply to the layer's filter.
    603    ctx.beginLayer({filter: 'drop-shadow(5px 5px 0px grey)'});
    604    ctx.translate(30, 90);
    605    ctx.scale(2, 2);
    606    ctx.rotate(Math.PI / 2);
    607    ctx.fillRect(-30, -5, 60, 10);
    608    ctx.endLayer();
    609 
    610    // Transforms in the layer's parent should apply to the layer's filter.
    611    ctx.translate(80, 90);
    612    ctx.scale(2, 2);
    613    ctx.rotate(Math.PI / 2);
    614    ctx.beginLayer({filter: 'drop-shadow(5px 5px 0px grey)'});
    615    ctx.fillRect(-30, -5, 60, 10);
    616    ctx.endLayer();
    617  html_reference: |
    618    <svg xmlns="http://www.w3.org/2000/svg"
    619          width="{{ size[0] }}" height="{{ size[1] }}"
    620          color-interpolation-filters="sRGB">
    621      <filter id="filter" x="-100%" y="-100%" width="300%" height="300%">
    622        <feDropShadow dx="5" dy="5" stdDeviation="0" flood-color="grey" />
    623      </filter>
    624 
    625      <g filter="url(#filter)">
    626        <g transform="translate(30, 90) scale(2) rotate(90)">
    627          <rect x="-30" y="-5" width=60 height=10></rect>
    628        </g>
    629      </g>
    630 
    631      <g transform="translate(80, 90) scale(2) rotate(90)">
    632        <g filter="url(#filter)">
    633          <rect x="-30" y="-5" width=60 height=10></rect>
    634        </g>
    635      </g>
    636    </svg>
    637 
    638 - name: 2d.layer.ctm.ctx-filter
    639  desc: Checks that parent transforms don't affect context filters.
    640  size: [200, 200]
    641  code: |
    642    ctx.fillStyle = 'grey';
    643    ctx.translate(30, 90);
    644    ctx.scale(2, 2);
    645    ctx.rotate(Math.PI / 2);
    646 
    647    // The transform doesn't apply to context filter on normal draw calls.
    648    ctx.save();
    649    ctx.filter = 'drop-shadow(10px 10px 0px red)';
    650    ctx.fillRect(-30, -5, 60, 10);
    651    ctx.restore();
    652 
    653    // Likewise, the transform doesn't apply to context filter applied on layer.
    654    ctx.save();
    655    ctx.filter = 'drop-shadow(10px 10px 0px green)';
    656    ctx.beginLayer();
    657    ctx.fillRect(-30, -25, 60, 10);
    658    ctx.endLayer();
    659    ctx.restore();
    660 
    661    // Filters inside layers aren't affected by transform either.
    662    ctx.beginLayer();
    663    ctx.filter = 'drop-shadow(5px 5px 0px blue)';
    664    ctx.fillRect(-30, -45, 60, 10);
    665    ctx.endLayer();
    666  reference: |
    667    ctx.fillStyle = 'red';
    668    ctx.fillRect(30, 40, 20, 120);
    669    ctx.fillStyle = 'grey';
    670    ctx.fillRect(20, 30, 20, 120);
    671 
    672    ctx.fillStyle = 'green';
    673    ctx.fillRect(70, 40, 20, 120);
    674    ctx.fillStyle = 'grey';
    675    ctx.fillRect(60, 30, 20, 120);
    676 
    677    ctx.fillStyle = 'blue';
    678    ctx.fillRect(105, 35, 20, 120);
    679    ctx.fillStyle = 'grey';
    680    ctx.fillRect(100, 30, 20, 120);
    681 
    682 - name: 2d.layer.ctm.shadow-in-transformed-layer
    683  desc: Check shadows inside of a transformed layer.
    684  size: [200, 200]
    685  code: |
    686    ctx.translate(80, 90);
    687    ctx.scale(2, 2);
    688    ctx.rotate(Math.PI / 2);
    689 
    690    ctx.beginLayer();
    691    ctx.shadowOffsetX = 10;
    692    ctx.shadowOffsetY = 10;
    693    ctx.shadowColor = 'grey';
    694    ctx.fillRect(-30, -5, 60, 10);
    695 
    696    const canvas2 = new OffscreenCanvas(100, 100);
    697    const ctx2 = canvas2.getContext('2d');
    698    ctx2.fillStyle = 'blue';
    699    ctx2.fillRect(0, 0, 40, 10);
    700    ctx.drawImage(canvas2, -30, -30);
    701 
    702    ctx.endLayer();
    703  reference: |
    704    ctx.translate(80, 90);
    705    ctx.scale(2, 2);
    706    ctx.rotate(Math.PI / 2);
    707 
    708    ctx.shadowOffsetX = 10;
    709    ctx.shadowOffsetY = 10;
    710    ctx.shadowColor = 'grey';
    711    ctx.fillRect(-30, -5, 60, 10);
    712 
    713    const canvas2 = new OffscreenCanvas(100, 100);
    714    const ctx2 = canvas2.getContext('2d');
    715    ctx2.fillStyle = 'blue';
    716    ctx2.fillRect(0, 0, 40, 10);
    717    ctx.drawImage(canvas2, -30, -30);
    718 
    719 - name: 2d.layer.ctm.getTransform
    720  desc: Tests getTransform inside layers.
    721  test_type: sync
    722  code: |
    723    ctx.translate(10, 20);
    724    ctx.beginLayer();
    725    ctx.scale(2, 3);
    726    const m = ctx.getTransform();
    727    assert_array_equals([m.a, m.b, m.c, m.d, m.e, m.f], [2, 0, 0, 3, 10, 20]);
    728    ctx.endLayer();
    729 
    730 - name: 2d.layer.ctm.setTransform
    731  desc: Tests setTransform inside layers.
    732  code: |
    733    ctx.translate(80, 0);
    734 
    735    ctx.beginLayer();
    736    ctx.rotate(2);
    737    ctx.beginLayer();
    738    ctx.scale(5, 6);
    739    ctx.setTransform(4, 0, 0, 2, 20, 10);
    740    ctx.fillStyle = 'blue';
    741    ctx.fillRect(0, 0, 10, 10);
    742    ctx.endLayer();
    743    ctx.endLayer();
    744 
    745    ctx.fillStyle = 'green';
    746    ctx.fillRect(0, 0, 20, 20);
    747  reference: |
    748    ctx.translate(80, 0);
    749    ctx.fillStyle = 'green';
    750    ctx.fillRect(0, 0, 20, 20);
    751 
    752    ctx.setTransform(4, 0, 0, 2, 20, 10);
    753    ctx.fillStyle = 'blue';
    754    ctx.fillRect(0, 0, 10, 10);
    755 
    756 - name: 2d.layer.ctm.resetTransform
    757  desc: Tests resetTransform inside layers.
    758  code: |
    759    ctx.translate(40, 0);
    760 
    761    ctx.beginLayer();
    762    ctx.rotate(2);
    763    ctx.beginLayer();
    764    ctx.scale(5, 6);
    765    ctx.resetTransform();
    766    ctx.fillStyle = 'blue';
    767    ctx.fillRect(0, 0, 20, 20);
    768    ctx.endLayer();
    769    ctx.endLayer();
    770 
    771    ctx.fillStyle = 'green';
    772    ctx.fillRect(0, 0, 20, 20);
    773  reference: |
    774    ctx.fillStyle = 'blue';
    775    ctx.fillRect(0, 0, 20, 20);
    776 
    777    ctx.translate(40, 0);
    778    ctx.fillStyle = 'green';
    779    ctx.fillRect(0, 0, 20, 20);
    780 
    781 - name: 2d.layer.flush-on-frame-presentation.{{ file_variant_name }}{{ tentative }}
    782  desc: Check that layers state stack is flushed and rebuilt on frame renders.
    783  size: [200, 200]
    784  canvas_types: ['HtmlCanvas']
    785  test_type: promise
    786  code: |
    787    ctx.fillStyle = 'purple';
    788    ctx.fillRect(60, 60, 75, 50);
    789    ctx.globalAlpha = 0.5;
    790    ctx.filter = 'blur(1px)';
    791 
    792    {{ begin_layer }}
    793    ctx.fillRect(40, 40, 75, 50);
    794    ctx.fillStyle = 'grey';
    795    ctx.fillRect(50, 50, 75, 50);
    796 
    797    // Force a flush and restoration of the state stack:
    798    await new Promise(resolve => requestAnimationFrame(resolve));
    799 
    800    ctx.fillRect(70, 70, 75, 50);
    801    ctx.fillStyle = 'orange';
    802    ctx.fillRect(80, 80, 75, 50);
    803    ctx.endLayer();
    804 
    805    ctx.fillRect(80, 40, 75, 50);
    806  reference: |
    807    ctx.fillStyle = 'purple';
    808    ctx.fillRect(60, 60, 75, 50);
    809    ctx.globalAlpha = 0.5;
    810    ctx.filter = 'blur(1px)';
    811 
    812    {{ begin_layer }}
    813    ctx.fillStyle = 'purple';
    814    ctx.fillRect(40, 40, 75, 50);
    815    ctx.fillStyle = 'grey';
    816    ctx.fillRect(50, 50, 75, 50);
    817 
    818    ctx.fillStyle = 'grey';
    819    ctx.fillRect(70, 70, 75, 50);
    820    ctx.fillStyle = 'orange';
    821    ctx.fillRect(80, 80, 75, 50);
    822    ctx.endLayer();
    823 
    824    ctx.fillRect(80, 40, 75, 50);
    825  append_variants_to_name: false
    826  variants:
    827  - plain_layer:
    828      begin_layer: ctx.beginLayer();
    829    filtered_layer:
    830      begin_layer: |-
    831        ctx.beginLayer({filter: {name: 'dropShadow', dx: -2, dy: 2}});
    832      tentative: .tentative
    833 
    834 - name: 2d.layer.malformed-operations
    835  desc: Throws if {{ variant_name }} is called while layers are open.
    836  size: [200, 200]
    837  test_type: sync
    838  code: |
    839    {{ setup }}
    840    // Shouldn't throw on its own.
    841    {{ operation }};
    842    // Make sure the exception isn't caused by calling the function twice.
    843    {{ operation }};
    844    // Calling again inside a layer should throw.
    845    ctx.beginLayer();
    846    assert_throws_dom("InvalidStateError",
    847                      () => {{ operation }});
    848  variants_layout: [single_file]
    849  variants:
    850  - createPattern:
    851      operation: ctx.createPattern(canvas, 'repeat')
    852    drawImage:
    853      setup: |-
    854        const canvas2 = new OffscreenCanvas({{ size[0] }}, {{ size[1] }});
    855        const ctx2 = canvas2.getContext('2d');
    856      operation: |-
    857        ctx2.drawImage(canvas, 0, 0)
    858    getImageData:
    859      operation: ctx.getImageData(0, 0, {{ size[0] }}, {{ size[1] }})
    860    putImageData:
    861      setup: |-
    862        const canvas2 = new OffscreenCanvas({{ size[0] }}, {{ size[1] }});
    863        const ctx2 = canvas2.getContext('2d')
    864        const data = ctx2.getImageData(0, 0, 1, 1);
    865      operation: |-
    866        ctx.putImageData(data, 0, 0)
    867    toDataURL:
    868      canvas_types: ['HtmlCanvas']
    869      operation: canvas.toDataURL()
    870    transferToImageBitmap:
    871      canvas_types: ['OffscreenCanvas', 'Worker']
    872      operation: canvas.transferToImageBitmap()
    873 
    874 - name: 2d.layer.malformed-operations-with-promises
    875  desc: Throws if {{ variant_name }} is called while layers are open.
    876  size: [200, 200]
    877  test_type: promise
    878  code: |
    879    // Shouldn't throw on its own.
    880    await {{ operation }};
    881    // Make sure the exception isn't caused by calling the function twice.
    882    await {{ operation }};
    883    // Calling again inside a layer should throw.
    884    ctx.beginLayer();
    885    await promise_rejects_dom(t, 'InvalidStateError',
    886                              {{ operation }});
    887  variants_layout: [single_file]
    888  variants:
    889  - convertToBlob:
    890      canvas_types: ['OffscreenCanvas', 'Worker']
    891      operation: |-
    892        canvas.convertToBlob()
    893    createImageBitmap:
    894      operation: createImageBitmap(canvas)
    895    toBlob:
    896      canvas_types: ['HtmlCanvas']
    897      operation: |-
    898        new Promise(resolve => canvas.toBlob(resolve))
    899 
    900 - name: 2d.layer.several-complex
    901  desc: >-
    902    Test to ensure beginlayer works for filter, alpha and shadow, even with
    903    consecutive layers.
    904  size: [500, 500]
    905  fuzzy: maxDifference=0-3; totalPixels=0-6318
    906  code: |
    907    ctx.fillStyle = 'rgba(0, 0, 255, 1)';
    908    ctx.fillRect(50, 50, 95, 70);
    909 
    910    ctx.globalAlpha = 0.5;
    911    ctx.shadowOffsetX = -10;
    912    ctx.shadowOffsetY = 10;
    913    ctx.shadowColor = 'orange';
    914    ctx.shadowBlur = 3
    915 
    916    for (let i = 0; i < 5; i++) {
    917      ctx.beginLayer();
    918 
    919      ctx.fillStyle = 'rgba(225, 0, 0, 1)';
    920      ctx.fillRect(60 + i, 40 + i, 75, 50);
    921      ctx.fillStyle = 'rgba(0, 255, 0, 1)';
    922      ctx.fillRect(80 + i, 60 + i, 75, 50);
    923 
    924      ctx.endLayer();
    925    }
    926  reference: |
    927    ctx.fillStyle = 'rgba(0, 0, 255, 1)';
    928    ctx.fillRect(50, 50, 95, 70);
    929 
    930    ctx.globalAlpha = 0.5;
    931    ctx.shadowOffsetX = -10;
    932    ctx.shadowOffsetY = 10;
    933    ctx.shadowColor = 'orange';
    934    ctx.shadowBlur = 3;
    935 
    936    var canvas2 = [5];
    937    var ctx2 = [5];
    938 
    939    for (let i = 0; i < 5; i++) {
    940      canvas2[i] = document.createElement("canvas");
    941      ctx2[i] = canvas2[i].getContext("2d");
    942      ctx2[i].fillStyle = 'rgba(225, 0, 0, 1)';
    943      ctx2[i].fillRect(60, 40, 75, 50);
    944      ctx2[i].fillStyle = 'rgba(0, 255, 0, 1)';
    945      ctx2[i].fillRect(80, 60, 75, 50);
    946 
    947      ctx.drawImage(canvas2[i], i, i);
    948    }
    949 
    950 - name: 2d.layer.reset.{{ file_variant_name }}{{ tentative }}
    951  desc: Checks that reset discards any pending layers.
    952  code: |
    953    // Global states:
    954    ctx.globalAlpha = 0.3;
    955    ctx.globalCompositeOperation = 'source-in';
    956    ctx.shadowOffsetX = -3;
    957    ctx.shadowOffsetY = 3;
    958    ctx.shadowColor = 'rgba(0, 30, 0, 0.3)';
    959    ctx.shadowBlur = 3;
    960    ctx.filter = 'blur(5px)';
    961 
    962    {{ begin_layer }}
    963 
    964    // Layer states:
    965    ctx.globalAlpha = 0.6;
    966    ctx.globalCompositeOperation = 'source-in';
    967    ctx.shadowOffsetX = -6;
    968    ctx.shadowOffsetY = 6;
    969    ctx.shadowColor = 'rgba(0, 60, 0, 0.6)';
    970    ctx.shadowBlur = 3;
    971    ctx.filter = 'blur(15px)';
    972 
    973    ctx.reset();
    974 
    975    ctx.fillRect(10, 10, 75, 50);
    976  reference:
    977    ctx.fillRect(10, 10, 75, 50);
    978  append_variants_to_name: false
    979  variants:
    980  - plain_layer:
    981      begin_layer: ctx.beginLayer();
    982    filtered_layer:
    983      begin_layer: |-
    984        ctx.beginLayer({filter: {name: 'dropShadow', dx: -3, dy: 3}});
    985      tentative: .tentative
    986 
    987 - name: 2d.layer.clearRect.partial
    988  desc: clearRect inside a layer can clear a portion of the layer content.
    989  size: [100, 100]
    990  code: |
    991    ctx.fillStyle = 'blue';
    992    ctx.fillRect(10, 10, 80, 50);
    993 
    994    ctx.beginLayer();
    995    ctx.fillStyle = 'red';
    996    ctx.fillRect(20, 20, 80, 50);
    997    ctx.clearRect(30, 30, 60, 30);
    998    ctx.endLayer();
    999  reference: |
   1000    ctx.fillStyle = 'blue';
   1001    ctx.fillRect(10, 10, 80, 50);
   1002 
   1003    ctx.fillStyle = 'red';
   1004    ctx.fillRect(20, 20, 80, 10);
   1005    ctx.fillRect(20, 60, 80, 10);
   1006    ctx.fillRect(20, 20, 10, 50);
   1007    ctx.fillRect(90, 20, 10, 50);
   1008 
   1009 - name: 2d.layer.clearRect.full
   1010  desc: clearRect inside a layer can clear all of the layer content.
   1011  size: [100, 100]
   1012  code: |
   1013    ctx.fillStyle = 'blue';
   1014    ctx.fillRect(10, 10, 80, 50);
   1015 
   1016    ctx.beginLayer();
   1017    ctx.fillStyle = 'red';
   1018    ctx.fillRect(20, 20, 80, 50);
   1019    ctx.fillStyle = 'green';
   1020    ctx.clearRect(0, 0, {{ size[0] }}, {{ size[1] }});
   1021    ctx.endLayer();
   1022  reference: |
   1023    ctx.fillStyle = 'blue';
   1024    ctx.fillRect(10, 10, 80, 50);
   1025 
   1026 - name: 2d.layer.drawImage.{{ file_variant_name }}{{ tentative }}
   1027  size: [200, 200]
   1028  desc: >-
   1029    Checks that drawImage writes the image to the layer and not the parent
   1030    directly.
   1031  code: |
   1032    ctx.fillStyle = 'skyblue';
   1033    ctx.fillRect(0, 0, 100, 100);
   1034 
   1035    {{ filtered_layer }}
   1036 
   1037    ctx.fillStyle = 'maroon';
   1038    ctx.fillRect(20, 20, 50, 50);
   1039 
   1040    ctx.globalCompositeOperation = 'xor';
   1041 
   1042    // The image should xor only with the layer content, not the parents'.
   1043    const canvas_image = new OffscreenCanvas(200,200);
   1044    const ctx_image = canvas_image.getContext("2d");
   1045    ctx_image.fillStyle = 'pink';
   1046    ctx_image.fillRect(40, 40, 50, 50);
   1047    ctx.drawImage(canvas_image, 0, 0);
   1048 
   1049    ctx.endLayer();
   1050  reference: |
   1051    ctx.fillStyle = 'skyblue';
   1052    ctx.fillRect(0, 0, 100, 100);
   1053 
   1054    ctx.filter = 'drop-shadow(-10px -10px 0px navy)';
   1055    ctx.beginLayer();
   1056 
   1057    ctx.fillStyle = 'maroon';
   1058    ctx.fillRect(20, 20, 50, 50);
   1059 
   1060    ctx.globalCompositeOperation = 'xor';
   1061 
   1062    // Should xor only with the layer content, not the parents'.
   1063    ctx.fillStyle = 'pink';
   1064    ctx.fillRect(40, 40, 50, 50);
   1065 
   1066    ctx.endLayer();
   1067  append_variants_to_name: false
   1068  variants:
   1069  - ctx-filter:
   1070      filtered_layer: |-
   1071        ctx.filter = 'drop-shadow(-10px -10px 0px navy)';
   1072        ctx.beginLayer();
   1073    layer-filter:
   1074      filtered_layer: |-
   1075        ctx.beginLayer({filter: {name: 'dropShadow', dx: -10, dy: -10,
   1076                                 stdDeviation: 0, floodColor: 'navy'}});
   1077      tentative: .tentative
   1078 
   1079 - name: 2d.layer.valid-calls
   1080  desc: No exception raised on {{ variant_desc }}.
   1081  variants:
   1082  - save:
   1083      variant_desc: lone save() calls
   1084      code: ctx.save();
   1085    beginLayer:
   1086      variant_desc: lone beginLayer() calls
   1087      code: ctx.beginLayer();
   1088    restore:
   1089      variant_desc: lone restore() calls
   1090      code: ctx.restore();
   1091    save_restore:
   1092      variant_desc: save() + restore()
   1093      code: |-
   1094        ctx.save();
   1095        ctx.restore();
   1096    save_reset_restore:
   1097      variant_desc: save() + reset() + restore()
   1098      code: |-
   1099        ctx.save();
   1100        ctx.reset();
   1101        ctx.restore();
   1102    beginLayer-endLayer:
   1103      variant_desc: beginLayer() + endLayer()
   1104      code: |-
   1105        ctx.beginLayer();
   1106        ctx.save();
   1107    save-beginLayer:
   1108      variant_desc: save() + beginLayer()
   1109      code: |-
   1110        ctx.save();
   1111        ctx.beginLayer();
   1112    beginLayer-save:
   1113      variant_desc: beginLayer() + save()
   1114      code: |-
   1115        ctx.beginLayer();
   1116        ctx.save();
   1117 
   1118 - name: 2d.layer.invalid-calls
   1119  desc: Raises exception on {{ variant_desc }}.
   1120  test_type: sync
   1121  code: |
   1122    assert_throws_dom("INVALID_STATE_ERR", function() {
   1123      {{ call_sequence | indent(2) }}
   1124    });
   1125  variants:
   1126  - endLayer:
   1127      variant_desc: lone endLayer calls
   1128      call_sequence: ctx.endLayer();
   1129    save-endLayer:
   1130      variant_desc: save() + endLayer()
   1131      call_sequence: |-
   1132        ctx.save();
   1133        ctx.endLayer();
   1134    beginLayer-restore:
   1135      variant_desc: beginLayer() + restore()
   1136      call_sequence: |-
   1137        ctx.beginLayer();
   1138        ctx.restore();
   1139    save-beginLayer-restore:
   1140      variant_desc: save() + beginLayer() + restore()
   1141      call_sequence: |-
   1142        ctx.save();
   1143        ctx.beginLayer();
   1144        ctx.restore();
   1145    beginLayer-save-endLayer:
   1146      variant_desc: beginLayer() + save() + endLayer()
   1147      call_sequence: |-
   1148        ctx.beginLayer();
   1149        ctx.save();
   1150        ctx.endLayer();
   1151    beginLayer-reset-endLayer:
   1152      variant_desc: beginLayer() + reset() + endLayer()
   1153      call_sequence: |-
   1154        ctx.beginLayer();
   1155        ctx.reset();
   1156        ctx.endLayer();
   1157 
   1158 - name: 2d.layer.exceptions-are-no-op.tentative
   1159  desc: Checks that the context state is left unchanged if beginLayer throws.
   1160  test_type: sync
   1161  code: |
   1162    // Get `beginLayer` to throw while parsing the filter.
   1163    assert_throws_js(TypeError,
   1164                     () => ctx.beginLayer({filter: {name: 'colorMatrix',
   1165                                                    values: 'foo'}}));
   1166    // `beginLayer` shouldn't have opened the layer, so `endLayer` should throw.
   1167    assert_throws_dom("InvalidStateError", () => ctx.endLayer());
   1168 
   1169 - name: 2d.layer.cross-layer-paths
   1170  desc: Checks that path defined in a layer is usable outside.
   1171  code: |
   1172    ctx.beginLayer();
   1173    ctx.translate(50, 0);
   1174    ctx.moveTo(0, 0);
   1175    ctx.endLayer();
   1176    ctx.lineTo(50, 100);
   1177    ctx.stroke();
   1178  reference:
   1179    ctx.moveTo(50, 0);
   1180    ctx.lineTo(50, 100);
   1181    ctx.stroke();
   1182 
   1183 - name: 2d.layer.beginLayer-options.tentative
   1184  desc: Checks beginLayer works for different option parameter values
   1185  test_type: sync
   1186  code: |
   1187    ctx.beginLayer(); ctx.endLayer();
   1188    ctx.beginLayer(null); ctx.endLayer();
   1189    ctx.beginLayer(undefined); ctx.endLayer();
   1190    ctx.beginLayer([]); ctx.endLayer();
   1191    ctx.beginLayer({}); ctx.endLayer();
   1192 
   1193    @assert throws TypeError ctx.beginLayer('');
   1194    @assert throws TypeError ctx.beginLayer(0);
   1195    @assert throws TypeError ctx.beginLayer(1);
   1196    @assert throws TypeError ctx.beginLayer(true);
   1197    @assert throws TypeError ctx.beginLayer(false);
   1198 
   1199    ctx.beginLayer({filter: null}); ctx.endLayer();
   1200    ctx.beginLayer({filter: undefined}); ctx.endLayer();
   1201    ctx.beginLayer({filter: []}); ctx.endLayer();
   1202    ctx.beginLayer({filter: {}}); ctx.endLayer();
   1203    ctx.beginLayer({filter: {name: "unknown"}}); ctx.endLayer();
   1204    ctx.beginLayer({filter: ''}); ctx.endLayer();
   1205 
   1206    // These cases don't throw TypeError since they can be casted to a
   1207    // DOMString.
   1208    ctx.beginLayer({filter: 0}); ctx.endLayer();
   1209    ctx.beginLayer({filter: 1}); ctx.endLayer();
   1210    ctx.beginLayer({filter: true}); ctx.endLayer();
   1211    ctx.beginLayer({filter: false}); ctx.endLayer();
   1212 
   1213 - name: 2d.layer.blur-from-outside-canvas.{{ file_variant_name }}{{ tentative }}
   1214  desc: Checks blur leaking inside from drawing outside the canvas
   1215  size: [200, 200]
   1216  code: |
   1217    {{ clipping }}
   1218 
   1219    {{ filtered_layer }}
   1220 
   1221    ctx.fillStyle = 'turquoise';
   1222    ctx.fillRect(201, 50, 100, 100);
   1223    ctx.fillStyle = 'indigo';
   1224    ctx.fillRect(50, 201, 100, 100);
   1225    ctx.fillStyle = 'orange';
   1226    ctx.fillRect(-1, 50, -100, 100);
   1227    ctx.fillStyle = 'brown';
   1228    ctx.fillRect(50, -1, 100, -100);
   1229 
   1230    ctx.endLayer();
   1231  reference: |
   1232    const svg = `
   1233    <svg xmlns="http://www.w3.org/2000/svg"
   1234          width="{{ size[0] }}" height="{{ size[1] }}"
   1235          color-interpolation-filters="sRGB">
   1236      <filter id="filter" x="-100%" y="-100%" width="300%" height="300%">
   1237        <feGaussianBlur in="SourceGraphic" stdDeviation="30" />
   1238      </filter>
   1239      <g filter="url(#filter)">
   1240        <rect x="201" y="50" width="100" height="100" fill="turquoise"/>
   1241        <rect x="50" y="201" width="100" height="100" fill="indigo"/>
   1242        <rect x="-101" y="50" width="100" height="100" fill="orange"/>
   1243        <rect x="50" y="-101" width="100" height="100" fill="brown"/>
   1244      </g>
   1245    </svg>`;
   1246    const img = new Image();
   1247    img.width = {{ size[0] }};
   1248    img.height = {{ size[1] }};
   1249    img.onload = () => {
   1250        {{ clipping | indent(4) }}
   1251 
   1252        ctx.drawImage(img, 0, 0);
   1253    };
   1254    img.src = 'data:image/svg+xml;base64,' + btoa(svg);
   1255  append_variants_to_name: false
   1256  variants:
   1257  - no-clipping:
   1258      clipping: // No clipping.
   1259    with-clipping:
   1260      clipping: |-
   1261        const clipRegion = new Path2D();
   1262        clipRegion.rect(20, 20, 160, 160);
   1263        ctx.clip(clipRegion);
   1264  - ctx-filter:
   1265      filtered_layer: |-
   1266        ctx.filter = 'blur(30px)';
   1267        ctx.beginLayer();
   1268    layer-filter:
   1269      filtered_layer: |-
   1270        ctx.beginLayer({filter: [ {name: 'gaussianBlur', stdDeviation: 30} ]});
   1271      tentative: .tentative
   1272 
   1273 
   1274 - name: >-
   1275    2d.layer.shadow-from-outside-canvas.{{ file_variant_name }}{{ tentative }}
   1276  desc: Checks shadow produced by object drawn outside the canvas
   1277  size: [200, 200]
   1278  code: |
   1279    {{ distance }}
   1280 
   1281    {{ clipping }}
   1282 
   1283    {{ filtered_layer }}
   1284 
   1285    ctx.fillStyle = 'red';
   1286    ctx.fillRect({{ size[0] }} + delta, {{ size[1] }} + delta, 100, 100);
   1287 
   1288    ctx.endLayer();
   1289  reference: |
   1290    {{ distance }}
   1291 
   1292    {{ clipping }}
   1293 
   1294    ctx.fillStyle = 'green';
   1295    ctx.fillRect(0, 0, 100, 100);
   1296  append_variants_to_name: false
   1297  variants:
   1298  - short-distance:
   1299      distance: |-
   1300        const delta = 1;
   1301      clipping: // No clipping.
   1302    short-distance-with-clipping:
   1303      distance: |-
   1304        const delta = 1;
   1305      clipping: |-
   1306        const clipRegion = new Path2D();
   1307        clipRegion.rect(20, 20, 160, 160);
   1308        ctx.clip(clipRegion);
   1309    long-distance:
   1310      distance: |-
   1311        const delta = 10000;
   1312      clipping: // No clipping.
   1313    long-distance-with-clipping:
   1314      distance: |-
   1315        const delta = 10000;
   1316      clipping: |-
   1317        const clipRegion = new Path2D();
   1318        clipRegion.rect(20, 20, 160, 160);
   1319        ctx.clip(clipRegion);
   1320  - ctx-filter:
   1321      filtered_layer: |-
   1322        const dx = -({{ size[0] }} + delta);
   1323        const dy = -({{ size[1] }} + delta);
   1324        ctx.filter = 'drop-shadow(' + dx + 'px ' + dy + 'px 0px green)';
   1325        ctx.beginLayer();
   1326    layer-filter:
   1327      filtered_layer: |-
   1328        ctx.beginLayer({filter: [
   1329            {name: 'dropShadow', dx: -({{ size[0] }} + delta),
   1330              dy: -({{ size[1] }} + delta), stdDeviation: 0,
   1331              floodColor: 'green'},
   1332            ]});
   1333      tentative: .tentative
   1334 
   1335 - name: 2d.layer.opaque-canvas.{{ file_variant_name }}{{ tentative }}
   1336  desc: Checks that layer blending works inside opaque canvas
   1337  size: [300, 300]
   1338  code: |
   1339    {% if canvas_type == 'HtmlCanvas' %}
   1340    const canvas2 = document.createElement('canvas');
   1341    canvas2.width = 200;
   1342    canvas2.height = 200;
   1343    {% else %}
   1344    const canvas2 = new OffscreenCanvas(200, 200);
   1345    {% endif %}
   1346    const ctx2 = canvas2.getContext('2d', {alpha: false});
   1347 
   1348    ctx2.fillStyle = 'purple';
   1349    ctx2.fillRect(10, 10, 100, 100);
   1350 
   1351    {{ filtered_layer }}
   1352    ctx2.fillStyle = 'green';
   1353    ctx2.fillRect(50, 50, 100, 100);
   1354    ctx2.globalAlpha = 0.8;
   1355    ctx2.fillStyle = 'yellow';
   1356    ctx2.fillRect(75, 25, 100, 100);
   1357    ctx2.endLayer();
   1358 
   1359    ctx.fillStyle = 'blue';
   1360    ctx.fillRect(0, 0, 300, 300);
   1361    ctx.drawImage(canvas2, 0, 0);
   1362  reference: |
   1363    ctx.fillStyle = 'blue';
   1364    ctx.fillRect(0, 0, 300, 300);
   1365 
   1366    ctx.fillStyle = 'black';
   1367    ctx.fillRect(0, 0, 200, 200);
   1368 
   1369    ctx.fillStyle = 'purple';
   1370    ctx.fillRect(10, 10, 100, 100);
   1371 
   1372    const canvas2 = new OffscreenCanvas(200, 200);
   1373    const ctx2 = canvas2.getContext('2d');
   1374    ctx2.fillStyle = 'green';
   1375    ctx2.fillRect(50, 50, 100, 100);
   1376    ctx2.globalAlpha = 0.8;
   1377    ctx2.fillStyle = 'yellow';
   1378    ctx2.fillRect(75, 25, 100, 100);
   1379 
   1380    ctx.shadowColor = 'rgba(200, 100, 50, 0.5)';
   1381    ctx.shadowOffsetX = -10;
   1382    ctx.shadowOffsetY = 10;
   1383    ctx.drawImage(canvas2, 0, 0);
   1384  append_variants_to_name: false
   1385  variants:
   1386  - ctx-filter:
   1387      filtered_layer: |-
   1388        ctx2.filter = 'drop-shadow(-10px 10px 0px rgba(200, 100, 50, 0.5))';
   1389        ctx2.beginLayer();
   1390    layer-filter:
   1391      filtered_layer: |-
   1392        ctx2.beginLayer({filter: {name: 'dropShadow', dx: -10, dy: 10,
   1393                                  stdDeviation: 0,
   1394                                  floodColor: 'rgba(200, 100, 50, 0.5)'}});
   1395      tentative: .tentative
   1396 
   1397 - name: 2d.layer.css-filters.{{ file_variant_name }}.tentative
   1398  desc: Checks that beginLayer works with a CSS filter string as input.
   1399  size: [200, 200]
   1400  code: &filter-test-code |
   1401    ctx.beginLayer({filter: {{ ctx_filter }}});
   1402 
   1403    ctx.fillStyle = 'teal';
   1404    ctx.fillRect(50, 50, 100, 100);
   1405 
   1406    ctx.endLayer();
   1407  html_reference: &filter-test-reference |
   1408    <svg xmlns="http://www.w3.org/2000/svg"
   1409          width="{{ size[0] }}" height="{{ size[1] }}"
   1410          color-interpolation-filters="sRGB">
   1411      <filter id="filter" x="-100%" y="-100%" width="300%" height="300%">
   1412        {{ svg_filter | indent(4) }}
   1413      </filter>
   1414      <g filter="url(#filter)">
   1415        <rect x="50" y="50" width="100" height="100" fill="teal"/>
   1416      </g>
   1417    </svg>
   1418  append_variants_to_name: false
   1419  variants:
   1420  - blur:
   1421      ctx_filter: |-
   1422        'blur(10px)'
   1423      svg_filter: |-
   1424        <feGaussianBlur stdDeviation="10" />
   1425    shadow:
   1426      ctx_filter: |-
   1427        'drop-shadow(-10px -10px 5px purple)'
   1428      svg_filter: |-
   1429        <feDropShadow dx="-10" dy="-10" stdDeviation="5" flood-color="purple" />
   1430    blur-and-shadow:
   1431      ctx_filter: |-
   1432        'blur(5px) drop-shadow(10px 10px 5px orange)'
   1433      svg_filter: |-
   1434        <feGaussianBlur stdDeviation="5" />
   1435        <feDropShadow dx="10" dy="10" stdDeviation="5" flood-color="orange" />
   1436 
   1437 - name: 2d.layer.anisotropic-blur.{{ file_variant_name }}.tentative
   1438  desc: Checks that layers allow gaussian blur with separate X and Y components.
   1439  size: [200, 200]
   1440  code: *filter-test-code
   1441  html_reference: *filter-test-reference
   1442  append_variants_to_name: false
   1443  variants:
   1444  - x-only:
   1445      ctx_filter: |-
   1446        { name: 'gaussianBlur', stdDeviation: [4, 0] }
   1447      svg_filter: |-
   1448        <feGaussianBlur stdDeviation="4 0" />
   1449    mostly-x:
   1450      ctx_filter: |-
   1451        { name: 'gaussianBlur', stdDeviation: [4, 1] }
   1452      svg_filter: |-
   1453        <feGaussianBlur stdDeviation="4 1" />
   1454    isotropic:
   1455      ctx_filter: |-
   1456        { name: 'gaussianBlur', stdDeviation: [4, 4] }
   1457      svg_filter: |-
   1458        <feGaussianBlur stdDeviation="4 4" />
   1459    mostly-y:
   1460      ctx_filter: |-
   1461        { name: 'gaussianBlur', stdDeviation: [1, 4] }
   1462      svg_filter: |-
   1463        <feGaussianBlur stdDeviation="1 4" />
   1464    y-only:
   1465      ctx_filter: |-
   1466        { name: 'gaussianBlur', stdDeviation: [0, 4] }
   1467      svg_filter: |-
   1468        <feGaussianBlur stdDeviation="0 4" />
   1469 
   1470 - name: 2d.layer.nested-filters.tentative
   1471  desc: Checks that nested layers work properly when both apply filters.
   1472  size: [400, 200]
   1473  code: |
   1474    ctx.beginLayer({filter: {name: 'dropShadow', dx: -20, dy: -20,
   1475      stdDeviation: 0, floodColor: 'yellow'}});
   1476    ctx.beginLayer({filter: 'drop-shadow(-10px -10px 0 blue)'});
   1477 
   1478    ctx.fillStyle = 'red';
   1479    ctx.fillRect(50, 50, 100, 100);
   1480 
   1481    ctx.endLayer();
   1482    ctx.endLayer();
   1483 
   1484    ctx.beginLayer({filter: 'drop-shadow(20px 20px 0 blue)'});
   1485    ctx.beginLayer({filter: {name: 'dropShadow', dx: 10, dy: 10,
   1486      stdDeviation: 0, floodColor: 'yellow'}});
   1487 
   1488    ctx.fillStyle = 'red';
   1489    ctx.fillRect(250, 50, 100, 100);
   1490 
   1491    ctx.endLayer();
   1492    ctx.endLayer();
   1493  reference: |
   1494    ctx.fillStyle = 'yellow';
   1495    ctx.fillRect(20, 20, 100, 100);
   1496    ctx.fillRect(30, 30, 100, 100);
   1497    ctx.fillStyle = 'blue';
   1498    ctx.fillRect(40, 40, 100, 100);
   1499    ctx.fillStyle = 'red';
   1500    ctx.fillRect(50, 50, 100, 100);
   1501 
   1502    ctx.fillStyle = 'blue';
   1503    ctx.fillRect(280, 80, 100, 100);
   1504    ctx.fillRect(270, 70, 100, 100);
   1505    ctx.fillStyle = 'yellow';
   1506    ctx.fillRect(260, 60, 100, 100);
   1507    ctx.fillStyle = 'red';
   1508    ctx.fillRect(250, 50, 100, 100);
   1509 
   1510 - name: 2d.layer.nested-ctx-filter
   1511  desc: Tests nested canvas layers with context filters.
   1512  size: [200, 200]
   1513  code: |
   1514    ctx.filter = 'drop-shadow(20px 20px 0px red)';
   1515    ctx.beginLayer();
   1516    ctx.filter = 'drop-shadow(10px 10px 0px green)';
   1517    ctx.beginLayer();
   1518    ctx.filter = 'drop-shadow(5px 5px 0px blue)';
   1519    ctx.fillStyle = 'grey';
   1520    ctx.fillRect(10, 10, 100, 100);
   1521    ctx.endLayer();
   1522    ctx.endLayer();
   1523  reference: |
   1524    ctx.fillStyle = 'red';
   1525    ctx.fillRect(45, 45, 100, 100);
   1526    ctx.fillRect(40, 40, 100, 100);
   1527    ctx.fillRect(35, 35, 100, 100);
   1528    ctx.fillRect(30, 30, 100, 100);
   1529    ctx.fillStyle = 'green';
   1530    ctx.fillRect(25, 25, 100, 100);
   1531    ctx.fillRect(20, 20, 100, 100);
   1532    ctx.fillStyle = 'blue';
   1533    ctx.fillRect(15, 15, 100, 100);
   1534    ctx.fillStyle = 'grey';
   1535    ctx.fillRect(10, 10, 100, 100);