From 496b53d84d06bc3ef06df3e788baf65a3fb5415a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lo=C3=AFc=20Mangeonjean?= Date: Mon, 5 May 2025 11:09:37 +0200 Subject: [PATCH 1/3] fix: support shadow dom in webgl renderer --- addons/addon-webgl/src/WebglRenderer.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/addons/addon-webgl/src/WebglRenderer.ts b/addons/addon-webgl/src/WebglRenderer.ts index 576cdad510..e3a2d0c61f 100644 --- a/addons/addon-webgl/src/WebglRenderer.ts +++ b/addons/addon-webgl/src/WebglRenderer.ts @@ -139,7 +139,7 @@ export class WebglRenderer extends Disposable implements IRenderer { [this._rectangleRenderer.value, this._glyphRenderer.value] = this._initializeWebGLState(); - this._isAttached = this._coreBrowserService.window.document.body.contains(this._core.screenElement!); + this._isAttached = this._core.screenElement!.isConnected; this._register(toDisposable(() => { for (const l of this._renderLayers) { @@ -322,7 +322,7 @@ export class WebglRenderer extends Disposable implements IRenderer { public renderRows(start: number, end: number): void { if (!this._isAttached) { - if (this._coreBrowserService.window.document.body.contains(this._core.screenElement!) && this._charSizeService.width && this._charSizeService.height) { + if (this._core.screenElement?.isConnected && this._charSizeService.width && this._charSizeService.height) { this._updateDimensions(); this._refreshCharAtlas(); this._isAttached = true; From 3ad83dd70e73ba38cec87dd66dffa6fd07f50f47 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lo=C3=AFc=20Mangeonjean?= Date: Wed, 21 May 2025 21:51:01 +0200 Subject: [PATCH 2/3] chore: add webgl shadow dom test --- test/playwright/SharedRendererTests.ts | 11 +++++-- test/playwright/TestUtils.ts | 45 +++++++++++++++++++++++--- 2 files changed, 49 insertions(+), 7 deletions(-) diff --git a/test/playwright/SharedRendererTests.ts b/test/playwright/SharedRendererTests.ts index 1d37b5c169..1a35a0e590 100644 --- a/test/playwright/SharedRendererTests.ts +++ b/test/playwright/SharedRendererTests.ts @@ -1280,10 +1280,10 @@ enum CellColorPosition { * treatment. */ export function injectSharedRendererTestsStandalone(ctx: ISharedRendererTestContext, setupCb: () => Promise | void): void { - test.describe('standalone tests', () => { + const setupTests = ({ shadowDom }: { shadowDom: boolean }): void => { test.beforeEach(async () => { // Recreate terminal - await openTerminal(ctx.value); + await openTerminal(ctx.value, {}, { useShadowDom: shadowDom }); await ctx.value.page.evaluate(` window.term.options.minimumContrastRatio = 1; window.term.options.allowTransparency = false; @@ -1308,6 +1308,13 @@ export function injectSharedRendererTestsStandalone(ctx: ISharedRendererTestCont await pollFor(ctx.value.page, () => getCellColor(ctx.value, 1, 1), [0, 0, 0, 255]); }); }); + }; + + test.describe('standalone tests', () => { + setupTests({ shadowDom: false }); + }); + test.describe('standalone tests (Shadow dom)', () => { + setupTests({ shadowDom: true }); }); } diff --git a/test/playwright/TestUtils.ts b/test/playwright/TestUtils.ts index dea3fcc71f..b6f45937de 100644 --- a/test/playwright/TestUtils.ts +++ b/test/playwright/TestUtils.ts @@ -412,7 +412,7 @@ class TerminalCoreProxy { } } -export async function openTerminal(ctx: ITestContext, options: ITerminalOptions | ITerminalInitOnlyOptions = {}, testOptions: { loadUnicodeGraphemesAddon: boolean } = { loadUnicodeGraphemesAddon: true }): Promise { +export async function openTerminal(ctx: ITestContext, options: ITerminalOptions | ITerminalInitOnlyOptions = {}, { useShadowDom = false, loadUnicodeGraphemesAddon = true }: { loadUnicodeGraphemesAddon?: boolean, useShadowDom?: boolean } = { }): Promise { await ctx.page.evaluate(` if ('term' in window) { try { @@ -423,13 +423,48 @@ export async function openTerminal(ctx: ITestContext, options: ITerminalOptions // HACK: Tests may have side effects that could cause the terminal not to be removed. This // assertion catches this case early. strictEqual(await ctx.page.evaluate(`document.querySelector('#terminal-container').children.length`), 0, 'there must be no terminals on the page'); - await ctx.page.evaluate(` + + let script = ` window.term = new window.Terminal(${JSON.stringify({ allowProposedApi: true, ...options })}); - window.term.open(document.querySelector('#terminal-container')); - `); + let element = document.querySelector('#terminal-container'); + + // Remove shadow root if it exists + const newElement = element.cloneNode(false); + element.replaceWith(newElement); + element = newElement +`; + + + if (useShadowDom) { + script += ` + const shadowRoot = element.attachShadow({ mode: "open" }); + + // Copy parent styles to shadow DOM + const styles = Array.from(document.querySelectorAll('link[rel="stylesheet"]')); + styles.forEach((styleEl) => { + const clone = document.createElement('link'); + clone.rel = 'stylesheet'; + clone.href = styleEl.href; + shadowRoot.appendChild(clone); + }); + + // Create new element inside the shadow DOM + element = document.createElement('div'); + element.style.width = '100%'; + element.style.height = '100%'; + shadowRoot.appendChild(element); + `; + } + + script += ` + window.term.open(element); + `; + + await ctx.page.evaluate(script); + // HACK: This is a soft layer breaker that's temporarily included until unicode graphemes have // more complete integration tests. See https://github.com/xtermjs/xterm.js/pull/4519#discussion_r1285234453 - if (testOptions.loadUnicodeGraphemesAddon) { + if (loadUnicodeGraphemesAddon) { await ctx.page.evaluate(` window.unicode = new UnicodeGraphemesAddon(); window.term.loadAddon(window.unicode); From 149527ddb8ed26fb01e44de7b4e39f2770f36b20 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lo=C3=AFc=20Mangeonjean?= Date: Thu, 31 Jul 2025 17:08:30 +0200 Subject: [PATCH 3/3] refactor: apply requested style change --- test/playwright/TestUtils.ts | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/test/playwright/TestUtils.ts b/test/playwright/TestUtils.ts index b6f45937de..271b0393d1 100644 --- a/test/playwright/TestUtils.ts +++ b/test/playwright/TestUtils.ts @@ -412,7 +412,14 @@ class TerminalCoreProxy { } } -export async function openTerminal(ctx: ITestContext, options: ITerminalOptions | ITerminalInitOnlyOptions = {}, { useShadowDom = false, loadUnicodeGraphemesAddon = true }: { loadUnicodeGraphemesAddon?: boolean, useShadowDom?: boolean } = { }): Promise { +export async function openTerminal( + ctx: ITestContext, + options: ITerminalOptions | ITerminalInitOnlyOptions = {}, + testOptions: { useShadowDom?: boolean, loadUnicodeGraphemesAddon?: boolean } = {} +): Promise { + testOptions.useShadowDom ??= false; + testOptions.loadUnicodeGraphemesAddon ??= true; + await ctx.page.evaluate(` if ('term' in window) { try { @@ -435,7 +442,7 @@ export async function openTerminal(ctx: ITestContext, options: ITerminalOptions `; - if (useShadowDom) { + if (testOptions.useShadowDom) { script += ` const shadowRoot = element.attachShadow({ mode: "open" }); @@ -464,7 +471,7 @@ export async function openTerminal(ctx: ITestContext, options: ITerminalOptions // HACK: This is a soft layer breaker that's temporarily included until unicode graphemes have // more complete integration tests. See https://github.com/xtermjs/xterm.js/pull/4519#discussion_r1285234453 - if (loadUnicodeGraphemesAddon) { + if (testOptions.loadUnicodeGraphemesAddon) { await ctx.page.evaluate(` window.unicode = new UnicodeGraphemesAddon(); window.term.loadAddon(window.unicode);