Source code
Revision control
Copy as Markdown
Other Tools
Test Info: Warnings
- This test has a WPT meta file that expects 7 subtest issues.
- This WPT test may be referenced by the following Test IDs:
- /html/canvas/element/manual/draw-element-image/onpaint.tentative.html - WPT Dashboard Interop Dashboard
<!DOCTYPE html>
<title>canvas.onpaint should fire for canvas child changes</title>
<script src="/resources/testharness.js"></script>
<script src="/resources/testharnessreport.js"></script>
<style>
div, canvas {
background: blue;
}
.color-change {
background: green;
}
#child {
width: 100px;
height: 100px;
}
#grandchild {
width: 50px;
height: 50px;
}
#sibling {
width: 100px;
height: 100px;
}
</style>
<div id="container">
<canvas id="canvas" width="200" height="200" layoutsubtree>
<div id="child">
<div id="grandchild"></div>
</div>
</canvas>
<div id="sibling"></div>
</div>
<script>
'use strict';
function waitForOneFrame() {
return new Promise(resolve => {
requestAnimationFrame(() => {
setTimeout(resolve, 0);
});
});
}
promise_test(async t => {
// Wait a frame.
await waitForOneFrame();
let onPaintCount = 0;
canvas.onpaint = () => {
onPaintCount++;
};
await waitForOneFrame();
assert_equals(onPaintCount, 0);
}, 'onpaint should not fire without changes');
promise_test(async t => {
// Wait a frame.
await waitForOneFrame();
let onPaintCount = 0;
let lastChanged = [];
canvas.onpaint = (event) => {
onPaintCount++;
lastChanged = event.changedElements;
};
// 1. Make a change that causes a repaint.
child.classList.toggle('color-change');
// 2. Wait a frame and assert that onpaint fires.
await waitForOneFrame();
assert_equals(onPaintCount, 1,
'onpaint should fire after a child changes.');
assert_array_equals(lastChanged, [child]);
// 3. Wait another frame and assert that onpaint does not fire again.
await waitForOneFrame();
assert_equals(onPaintCount, 1,
'onpaint should not fire without additional changes.');
}, 'onpaint should fire once if a canvas child changes');
promise_test(async t => {
const child = document.getElementById('child');
t.add_cleanup(() => {
canvas.appendChild(child);
});
// Wait a frame.
await waitForOneFrame();
let onPaintCount = 0;
let lastChanged = [];
canvas.onpaint = (event) => {
onPaintCount++;
lastChanged = event.changedElements;
};
// 1. Remove #child and assert that onpaint fires.
canvas.removeChild(child);
await waitForOneFrame();
assert_equals(onPaintCount, 1,
'onpaint should fire after a child is removed.');
assert_array_equals(lastChanged, [],
'#child should not be changed if it is removed.');
// 2. Add and remove #child without painting #child, and assert onpaint does
// not fire, as the rendering of the canvas is unchanged.
canvas.appendChild(child);
canvas.removeChild(child);
await waitForOneFrame();
assert_equals(onPaintCount, 1,
'onpaint should not fire for dom changes that do not affect the canvas.');
// 3. Append #child and assert that onpaint fires.
canvas.appendChild(child);
await waitForOneFrame();
assert_equals(onPaintCount, 2,
'onpaint should fire after a child is added.');
assert_array_equals(lastChanged, [child],
'#child should be changed when added.');
// 4. Remove all children of the canvas and assert that onpaint fires.
canvas.innerHTML = '';
await waitForOneFrame();
assert_equals(onPaintCount, 3,
'onpaint should fire after all children are removed.');
assert_array_equals(lastChanged, [],
'#child should not be changed if it is removed.');
}, 'onpaint should fire if a canvas child is added or removed');
promise_test(async t => {
// Wait a frame.
await waitForOneFrame();
let onPaintCount = 0;
let lastChanged = [];
canvas.onpaint = (event) => {
onPaintCount++;
lastChanged = event.changedElements;
};
// 1. Make a change that causes a repaint.
grandchild.classList.toggle('color-change');
// 2. Wait a frame and assert that onpaint fires.
await waitForOneFrame();
assert_equals(onPaintCount, 1, 'onpaint should fire after a repaint.');
assert_array_equals(lastChanged, [child]);
// 3. Wait another frame and assert that onpaint does not fire again.
await waitForOneFrame();
assert_equals(onPaintCount, 1,
'onpaint should not fire without additional changes.');
}, 'onpaint should fire once if a canvas grandchild changes');
promise_test(async t => {
// Wait a frame.
await waitForOneFrame();
let onPaintCount = 0;
canvas.onpaint = () => {
onPaintCount++;
};
// 1. Make a change to the canvas itself that causes a repaint.
canvas.classList.toggle('color-change');
// 2. Wait a frame and assert that onpaint doesn't fire.
await waitForOneFrame();
assert_equals(onPaintCount, 0);
}, 'onpaint should not fire if the canvas itself changes');
promise_test(async t => {
// Wait a frame.
await waitForOneFrame();
let onPaintCount = 0;
canvas.onpaint = () => {
onPaintCount++;
};
// 1. Make a change to a canvas sibling that causes a repaint.
sibling.classList.toggle('color-change');
// 2. Wait a frame and assert that onpaint doesn't fire.
await waitForOneFrame();
assert_equals(onPaintCount, 0);
}, 'onpaint should not fire if a canvas sibling changes');
promise_test(async t => {
// Wait a frame.
await waitForOneFrame();
let onPaintCount = 0;
canvas.onpaint = () => {
onPaintCount++;
};
// 1. Make a change to a canvas ancestor that causes the ancestor to
// repaint.
container.classList.toggle('color-change');
// 2. Wait a frame and assert that onpaint doesn't fire.
await waitForOneFrame();
assert_equals(onPaintCount, 0);
}, 'onpaint should not fire if a canvas ancestor changes');
promise_test(async t => {
// Wait a frame.
await waitForOneFrame();
let onPaintCount = 0;
let lastChanged = [];
canvas.onpaint = (event) => {
onPaintCount++;
lastChanged = event.changedElements;
child.style.transform = `translateX(${onPaintCount}px)`;
};
// 1. Make a change that causes a repaint.
child.classList.toggle('color-change');
// 2. Wait a frame and assert that onpaint fires.
await waitForOneFrame();
assert_equals(onPaintCount, 1, 'onpaint should fire after a repaint.');
assert_equals(child.style.transform, 'translateX(1px)');
assert_array_equals(lastChanged, [child]);
// 3. Wait another frame and assert that onpaint does not fire again.
await waitForOneFrame();
// TODO(crbug.com/488090082): These assertions fails because we incorrectly
// trigger onpaint for transform changes.
// assert_equals(onPaintCount, 1,
// 'onpaint should not fire after transform changes.');
// assert_equals(child.style.transform, 'translateX(1px)');
}, 'onpaint should not fire after canvas child transform changes');
promise_test(async t => {
// Wait a frame.
await waitForOneFrame();
let onPaintCount = 0;
canvas.onpaint = () => {
onPaintCount++;
const x = onPaintCount;
const y = onPaintCount;
canvas.getContext('2d').drawElementImage(child, x, y);
};
// 1. Make a change that causes a repaint.
child.classList.toggle('color-change');
// 2. Wait a frame and assert that onpaint fires.
await waitForOneFrame();
assert_equals(onPaintCount, 1, 'onpaint should fire after a repaint.');
// 3. Wait another frame and assert that onpaint does not fire again.
await waitForOneFrame();
assert_equals(onPaintCount, 1,
'drawing operations should not cause onpaint to fire.');
}, 'onpaint should not fire after canvas drawing operations');
promise_test(async t => {
// Wait a frame.
await waitForOneFrame();
// Ensure layoutsubtree is present initially (it is in HTML).
assert_true(canvas.hasAttribute('layoutsubtree'));
let onPaintCount = 0;
canvas.onpaint = () => {
onPaintCount++;
};
// 1. Remove layoutsubtree.
canvas.removeAttribute('layoutsubtree');
// 2. Make a change that would normally cause a repaint.
child.classList.toggle('color-change');
// 3. Wait a frame.
await waitForOneFrame();
assert_equals(onPaintCount, 0,
'onpaint should not fire when layoutsubtree is missing.');
// 4. Add layoutsubtree back.
canvas.setAttribute('layoutsubtree', '');
// 5. Make a change.
child.classList.toggle('color-change');
// 6. Wait a frame.
await waitForOneFrame();
assert_equals(onPaintCount, 1,
'onpaint should fire when layoutsubtree is present.');
}, 'onpaint should respect layoutsubtree attribute mutation');
promise_test(async t => {
// Wait a frame.
await waitForOneFrame();
let onPaintCount = 0;
const handler = () => {
onPaintCount++;
};
canvas.addEventListener('paint', handler);
t.add_cleanup(() => canvas.removeEventListener('paint', handler));
// 1. Make a change that causes a repaint.
child.classList.toggle('color-change');
// 2. Wait a frame and assert that onpaint fires.
await waitForOneFrame();
assert_equals(onPaintCount, 1,
'addEventListener should receive the paint event.');
}, 'onpaint should work with addEventListener');
</script>