support z-index + dual canvas for DOM. Doesnt work for webgl

This commit is contained in:
Anthony Kim
2026-02-11 14:19:27 -08:00
parent 2c4997f529
commit 1d0cf32b28
7 changed files with 235 additions and 50 deletions
@@ -546,6 +546,85 @@ test.describe('Kitty Graphics Protocol', () => {
});
});
test.describe('Z-index layer placement', () => {
test('default placement (no z key) stores image on top layer', async () => {
await ctx.proxy.write(`\x1b_Ga=T,f=100;${KITTY_BLACK_1X1_BASE64}\x1b\\`);
await timeout(100);
strictEqual(await getImageStorageLength(), 1);
strictEqual(await ctx.page.evaluate(`window.imageAddon._storage._images.get(1).layer`), 'top');
});
test('z=0 stores image on top layer', async () => {
await ctx.proxy.write(`\x1b_Ga=T,f=100,z=0;${KITTY_BLACK_1X1_BASE64}\x1b\\`);
await timeout(100);
strictEqual(await getImageStorageLength(), 1);
strictEqual(await ctx.page.evaluate(`window.imageAddon._storage._images.get(1).layer`), 'top');
});
test('z=1 (positive) stores image on top layer', async () => {
await ctx.proxy.write(`\x1b_Ga=T,f=100,z=1;${KITTY_BLACK_1X1_BASE64}\x1b\\`);
await timeout(100);
strictEqual(await getImageStorageLength(), 1);
strictEqual(await ctx.page.evaluate(`window.imageAddon._storage._images.get(1).layer`), 'top');
});
test('z=-1 falls back to top layer when allowTransparency is disabled', async () => {
await ctx.page.evaluate(`window.term.options.allowTransparency = false`);
await ctx.proxy.write(`\x1b_Ga=T,f=100,z=-1;${KITTY_BLACK_1X1_BASE64}\x1b\\`);
await timeout(100);
strictEqual(await getImageStorageLength(), 1);
strictEqual(await ctx.page.evaluate(`window.imageAddon._storage._images.get(1).layer`), 'top');
});
test('z=-1 (negative) stores image on bottom layer when allowTransparency is enabled', async () => {
await ctx.page.evaluate(`window.term.options.allowTransparency = true`);
await ctx.proxy.write(`\x1b_Ga=T,f=100,z=-1;${KITTY_BLACK_1X1_BASE64}\x1b\\`);
await timeout(100);
strictEqual(await getImageStorageLength(), 1);
strictEqual(await ctx.page.evaluate(`window.imageAddon._storage._images.get(1).layer`), 'bottom');
});
test('z=-100 (large negative) stores image on bottom layer when allowTransparency is enabled', async () => {
await ctx.page.evaluate(`window.term.options.allowTransparency = true`);
await ctx.proxy.write(`\x1b_Ga=T,f=100,z=-100;${KITTY_BLACK_1X1_BASE64}\x1b\\`);
await timeout(100);
strictEqual(await getImageStorageLength(), 1);
strictEqual(await ctx.page.evaluate(`window.imageAddon._storage._images.get(1).layer`), 'bottom');
});
test('top layer canvas has correct CSS class', async () => {
await ctx.proxy.write(`\x1b_Ga=T,f=100;${KITTY_BLACK_1X1_BASE64}\x1b\\`);
await timeout(100);
const hasClass = await ctx.page.evaluate(() => {
const el = document.querySelector('.xterm-image-layer-top');
return el !== null;
});
strictEqual(hasClass, true);
});
test('bottom layer canvas has correct CSS class', async () => {
await ctx.page.evaluate(`window.term.options.allowTransparency = true`);
await ctx.proxy.write(`\x1b_Ga=T,f=100,z=-1;${KITTY_BLACK_1X1_BASE64}\x1b\\`);
await timeout(100);
const hasClass = await ctx.page.evaluate(() => {
const el = document.querySelector('.xterm-image-layer-bottom');
return el !== null;
});
strictEqual(hasClass, true);
});
test('bottom layer canvas is before text canvas in DOM order', async () => {
await ctx.page.evaluate(`window.term.options.allowTransparency = true`);
await ctx.proxy.write(`\x1b_Ga=T,f=100,z=-1;${KITTY_BLACK_1X1_BASE64}\x1b\\`);
await timeout(100);
const isFirst = await ctx.page.evaluate(() => {
const screen = document.querySelector('.xterm-screen');
return screen?.firstElementChild?.classList.contains('xterm-image-layer-bottom') ?? false;
});
strictEqual(isFirst, true);
});
});
test.describe('Pixel verification', () => {
test('renders 1x1 black PNG at cursor position', async () => {
const seq = `\x1b_Ga=T,f=100;${KITTY_BLACK_1X1_BASE64}\x1b\\`;