Source code
Revision control
Copy as Markdown
Other Tools
Test Info: Warnings
- This test has a WPT meta file that expects 6 subtest issues.
- This WPT test may be referenced by the following Test IDs:
- /html/canvas/element/manual/draw-element-image/captureElementImage.tentative.html - WPT Dashboard Interop Dashboard
<!DOCTYPE HTML>
<title>canvas.captureElementImage returns a snapshot of an element that can be drawn into the canvas</title>
<script src='/resources/testharness.js'></script>
<script src='/resources/testharnessreport.js'></script>
<script src="/html/canvas/resources/wait-for-canvas-paint.js"></script>
<style>
#child {
width: 100px;
height: 100px;
background: green;
}
</style>
<canvas id="canvas" width="200" height="200" content=drawable>
<div drawable id="child"></div>
</canvas>
<script>
'use strict';
promise_test(async t => {
const child = document.getElementById('child');
t.add_cleanup(() => {
child.style.width = '100px';
});
await waitForCanvasPaint(canvas);
let elementImageA = canvas.captureElementImage(child);
assert_equals(elementImageA.width, 100);
assert_equals(elementImageA.height, 100);
// Change the child size.
child.style.width = '150px';
// `captureElementImage` should still return the last painted element image.
let elementImageB = canvas.captureElementImage(child);
assert_equals(elementImageB.width, 100);
assert_equals(elementImageB.height, 100);
await waitForCanvasPaint(canvas);
// `captureElementImage` should return the updated element image size.
let elementImageC = canvas.captureElementImage(child);
assert_equals(elementImageC.width, 150);
assert_equals(elementImageC.height, 100);
// The previous element images should not be affected by the changed size.
assert_equals(elementImageA.width, 100);
assert_equals(elementImageA.height, 100);
assert_equals(elementImageB.width, 100);
assert_equals(elementImageB.height, 100);
}, 'captureElementImage size should be the most recently painted size');
promise_test(async t => {
const child = document.getElementById('child');
t.add_cleanup(() => {
child.style.background = 'green';
});
let context = canvas.getContext('2d', { willReadFrequently: true });
context.reset();
await waitForCanvasPaint(canvas);
let greenElementImage = canvas.captureElementImage(child);
assert_equals(greenElementImage.width, 100);
assert_equals(greenElementImage.height, 100);
context.drawElementImage(greenElementImage, 0, 0);
assert_array_equals(context.getImageData(0, 0, 1, 1).data, [0, 128, 0, 255]);
// Change the child to blue.
child.style.background = 'blue';
await waitForCanvasPaint(canvas);
let blueElementImage = canvas.captureElementImage(child);
assert_equals(blueElementImage.width, 100);
assert_equals(blueElementImage.height, 100);
context.drawElementImage(blueElementImage, 0, 0);
assert_array_equals(context.getImageData(0, 0, 1, 1).data, [0, 0, 255, 255]);
// `greenElementImage` should still be green.
assert_equals(greenElementImage.width, 100);
assert_equals(greenElementImage.height, 100);
context.drawElementImage(greenElementImage, 0, 0);
assert_array_equals(context.getImageData(0, 0, 1, 1).data, [0, 128, 0, 255],
'greenElementImage should still draw green');
}, 'captureElementImage should return an object that can be drawn');
promise_test(async t => {
await waitForCanvasPaint(canvas);
let elementImage = canvas.captureElementImage(child);
assert_equals(elementImage.width, 100);
assert_equals(elementImage.height, 100);
assert_true('close' in elementImage,
'ElementImage should have a close method');
// Ensure an ElementImage cannot be drawn after `close()`.
elementImage.close();
assert_equals(elementImage.width, 0);
assert_equals(elementImage.height, 0);
assert_throws_dom('InvalidStateError',
() => canvas.getContext('2d').drawElementImage(elementImage, 0, 0),
'A closed ElementImage cannot be drawn.');
}, 'ElementImage.close() invalidates the image for drawing');
promise_test(async t => {
const nestedCanvas = document.createElement('canvas');
nestedCanvas.content = 'drawable';
nestedCanvas.width = 100;
nestedCanvas.height = 100;
const childDiv = document.createElement('div');
childDiv.setAttribute('drawable', '');
childDiv.style.width = '100px';
childDiv.style.height = '100px';
nestedCanvas.appendChild(childDiv);
const outerCanvas = document.getElementById('canvas');
outerCanvas.appendChild(nestedCanvas);
t.add_cleanup(() => {
nestedCanvas.remove();
});
await waitForCanvasPaint(canvas);
let elementImage = nestedCanvas.captureElementImage(childDiv);
assert_equals(elementImage.width, 100);
assert_equals(elementImage.height, 100);
}, 'captureElementImage on a nested canvas should succeed');
promise_test(async t => {
const child = document.getElementById('child');
canvas.style.width = '200px';
canvas.style.height = '200px';
t.add_cleanup(async () => {
canvas.style.width = '';
canvas.style.height = '';
canvas.width = 200;
canvas.height = 200;
await waitForCanvasPaint(canvas);
});
const entry = await new Promise(resolve => {
const observer = new ResizeObserver(entries => {
observer.disconnect();
resolve(entries[0]);
});
observer.observe(canvas);
});
const dpWidth = entry.devicePixelContentBoxSize[0].inlineSize;
const dpHeight = entry.devicePixelContentBoxSize[0].blockSize;
// Set canvas grid dimensions different from devicePixelContentBoxSize
// (scale factor 2x horizontally, 0.5x vertically).
canvas.width = dpWidth * 2;
canvas.height = dpHeight * 0.5;
await waitForCanvasPaint(canvas);
let elementImage = canvas.captureElementImage(child);
// Child is 100px x 100px inside a 200px x 200px canvas (0.5x of canvas box).
// In canvas pixel grid coordinate space:
// width = (dpWidth * 2) * 0.5 = dpWidth
// height = (dpHeight * 0.5) * 0.5 = dpHeight * 0.25
assert_equals(elementImage.width, dpWidth);
assert_equals(elementImage.height, dpHeight * 0.25);
// Verify that drawing the ElementImage at 1:1 canvas grid scale matches
// its reported width and height in canvas pixel grid coordinates.
let context = canvas.getContext('2d', { willReadFrequently: true });
context.reset();
context.drawElementImage(elementImage, 0, 0);
const drawnWidth = Math.floor(elementImage.width);
const drawnHeight = Math.floor(elementImage.height);
assert_array_equals(
context.getImageData(drawnWidth - 1, drawnHeight - 1, 1, 1).data,
[0, 128, 0, 255],
'Pixel inside ElementImage bounds should be green');
assert_array_equals(
context.getImageData(drawnWidth + 1, drawnHeight - 1, 1, 1).data,
[0, 0, 0, 0],
'Pixel outside ElementImage width should be transparent');
assert_array_equals(
context.getImageData(drawnWidth - 1, drawnHeight + 1, 1, 1).data,
[0, 0, 0, 0],
'Pixel outside ElementImage height should be transparent');
}, 'ElementImage width and height should be in canvas pixel grid coordinate space');
promise_test(async t => {
const child = document.getElementById('child');
canvas.style.width = '200px';
canvas.style.height = '200px';
t.add_cleanup(async () => {
canvas.style.width = '';
canvas.style.height = '';
canvas.width = 200;
canvas.height = 200;
await waitForCanvasPaint(canvas);
});
const entry = await new Promise(resolve => {
const observer = new ResizeObserver(entries => {
observer.disconnect();
resolve(entries[0]);
});
observer.observe(canvas);
});
const dpWidth = entry.devicePixelContentBoxSize[0].inlineSize;
const dpHeight = entry.devicePixelContentBoxSize[0].blockSize;
canvas.width = dpWidth * 2;
canvas.height = dpHeight * 0.5;
await waitForCanvasPaint(canvas);
let elementImageA = canvas.captureElementImage(child);
assert_equals(elementImageA.width, dpWidth);
assert_equals(elementImageA.height, dpHeight * 0.25);
// Change canvas pixel grid size to a different non-standard scale factor.
canvas.width = dpWidth * 3;
canvas.height = dpHeight * 1.5;
// ElementImage width and height are based on the current canvas grid size,
// so existing and newly captured ElementImages immediately reflect the new
// canvas pixel grid dimensions even before the next paint.
assert_equals(elementImageA.width, dpWidth * 1.5);
assert_equals(elementImageA.height, dpHeight * 0.75);
let elementImageB = canvas.captureElementImage(child);
assert_equals(elementImageB.width, dpWidth * 1.5);
assert_equals(elementImageB.height, dpHeight * 0.75);
await waitForCanvasPaint(canvas);
let elementImageC = canvas.captureElementImage(child);
assert_equals(elementImageC.width, dpWidth * 1.5);
assert_equals(elementImageC.height, dpHeight * 0.75);
assert_equals(elementImageA.width, dpWidth * 1.5);
assert_equals(elementImageA.height, dpHeight * 0.75);
assert_equals(elementImageB.width, dpWidth * 1.5);
assert_equals(elementImageB.height, dpHeight * 0.75);
}, 'ElementImage width and height update when canvas pixel grid size changes');
</script>