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; 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..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 = {}, testOptions: { loadUnicodeGraphemesAddon: boolean } = { loadUnicodeGraphemesAddon: true }): 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 { @@ -423,10 +430,45 @@ 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 (testOptions.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) {