From 1c0a25d01bb329d7470a887f61a481823cd16ff0 Mon Sep 17 00:00:00 2001 From: Simon Siefke Date: Thu, 27 Jul 2023 13:08:22 +0200 Subject: [PATCH 1/3] feature: use constructed style sheets for dom render so that it works with csp style-src 'self' --- src/browser/renderer/dom/DomRenderer.ts | 23 +++++++-------- src/browser/renderer/dom/StyleSheet.ts | 39 +++++++++++++++++++++++++ 2 files changed, 50 insertions(+), 12 deletions(-) create mode 100644 src/browser/renderer/dom/StyleSheet.ts diff --git a/src/browser/renderer/dom/DomRenderer.ts b/src/browser/renderer/dom/DomRenderer.ts index 54711ed84f..f8c3041495 100644 --- a/src/browser/renderer/dom/DomRenderer.ts +++ b/src/browser/renderer/dom/DomRenderer.ts @@ -13,6 +13,7 @@ import { color } from 'common/Color'; import { EventEmitter } from 'common/EventEmitter'; import { Disposable, toDisposable } from 'common/Lifecycle'; import { IBufferService, IInstantiationService, IOptionsService } from 'common/services/Services'; +import { createStyle, StyleSheet } from "./StyleSheet"; const TERMINAL_CLASS_PREFIX = 'xterm-dom-renderer-owner-'; const ROW_CONTAINER_CLASS = 'xterm-rows'; @@ -32,8 +33,8 @@ export class DomRenderer extends Disposable implements IRenderer { private _rowFactory: DomRendererRowFactory; private _terminalClass: number = nextTerminalId++; - private _themeStyleElement!: HTMLStyleElement; - private _dimensionsStyleElement!: HTMLStyleElement; + private _themeStyle!: StyleSheet; + private _dimensionsStyle!: StyleSheet; private _rowContainer: HTMLElement; private _rowElements: HTMLElement[] = []; private _selectionContainer: HTMLElement; @@ -88,8 +89,8 @@ export class DomRenderer extends Disposable implements IRenderer { // https://github.com/xtermjs/xterm.js/issues/2960 this._rowContainer.remove(); this._selectionContainer.remove(); - this._themeStyleElement.remove(); - this._dimensionsStyleElement.remove(); + this._themeStyle.dispose(); + this._dimensionsStyle.dispose(); })); } @@ -116,9 +117,8 @@ export class DomRenderer extends Disposable implements IRenderer { element.style.overflow = 'hidden'; } - if (!this._dimensionsStyleElement) { - this._dimensionsStyleElement = document.createElement('style'); - this._screenElement.appendChild(this._dimensionsStyleElement); + if (!this._dimensionsStyle) { + this._dimensionsStyle = createStyle(this._screenElement); } const styles = @@ -129,7 +129,7 @@ export class DomRenderer extends Disposable implements IRenderer { ` width: ${this.dimensions.css.cell.width}px` + `}`; - this._dimensionsStyleElement.textContent = styles; + this._dimensionsStyle.setCss(styles); this._selectionContainer.style.height = this._viewportElement.style.height; this._screenElement.style.width = `${this.dimensions.css.canvas.width}px`; @@ -137,9 +137,8 @@ export class DomRenderer extends Disposable implements IRenderer { } private _injectCss(colors: ReadonlyColorSet): void { - if (!this._themeStyleElement) { - this._themeStyleElement = document.createElement('style'); - this._screenElement.appendChild(this._themeStyleElement); + if (!this._themeStyle) { + this._themeStyle = createStyle(this._screenElement); } // Base CSS @@ -236,7 +235,7 @@ export class DomRenderer extends Disposable implements IRenderer { `${this._terminalSelector} .${FG_CLASS_PREFIX}${INVERTED_DEFAULT_COLOR}.${DIM_CLASS} { color: ${color.multiplyOpacity(color.opaque(colors.background), 0.5).css}; }` + `${this._terminalSelector} .${BG_CLASS_PREFIX}${INVERTED_DEFAULT_COLOR} { background-color: ${colors.foreground.css}; }`; - this._themeStyleElement.textContent = styles; + this._themeStyle.setCss(styles); } public handleDevicePixelRatioChange(): void { diff --git a/src/browser/renderer/dom/StyleSheet.ts b/src/browser/renderer/dom/StyleSheet.ts new file mode 100644 index 0000000000..26238ade93 --- /dev/null +++ b/src/browser/renderer/dom/StyleSheet.ts @@ -0,0 +1,39 @@ +export interface StyleSheet { + dispose: () => void; + setCss: (value: string) => void; +} + +const createCssStyleSheet = (): StyleSheet => { + const sheet = new CSSStyleSheet(); + document.adoptedStyleSheets.push(sheet); + return { + dispose() { + const index = document.adoptedStyleSheets.indexOf(sheet); + document.adoptedStyleSheets.splice(index, 1); + }, + setCss(css) { + sheet.replace(css); + }, + }; +}; + +const createStyleElement = (parent: HTMLElement): StyleSheet => { + const element = document.createElement("style"); + parent.append(element); + return { + dispose() { + element.remove(); + }, + setCss(css) { + element.textContent = css; + }, + }; +}; + +export const createStyle = (parent: HTMLElement): StyleSheet => { + try { + return createCssStyleSheet(); + } catch { + return createStyleElement(parent); + } +}; From b225b9a7108b4154c7c8f35921b02173f3e433c2 Mon Sep 17 00:00:00 2001 From: Simon Siefke Date: Thu, 27 Jul 2023 17:39:15 +0200 Subject: [PATCH 2/3] fix eslint --- src/browser/renderer/dom/DomRenderer.ts | 6 +++--- src/browser/renderer/dom/StyleSheet.ts | 14 +++++++------- 2 files changed, 10 insertions(+), 10 deletions(-) diff --git a/src/browser/renderer/dom/DomRenderer.ts b/src/browser/renderer/dom/DomRenderer.ts index f8c3041495..593acbb2dc 100644 --- a/src/browser/renderer/dom/DomRenderer.ts +++ b/src/browser/renderer/dom/DomRenderer.ts @@ -13,7 +13,7 @@ import { color } from 'common/Color'; import { EventEmitter } from 'common/EventEmitter'; import { Disposable, toDisposable } from 'common/Lifecycle'; import { IBufferService, IInstantiationService, IOptionsService } from 'common/services/Services'; -import { createStyle, StyleSheet } from "./StyleSheet"; +import { createStyle, IStyleSheet } from './StyleSheet'; const TERMINAL_CLASS_PREFIX = 'xterm-dom-renderer-owner-'; const ROW_CONTAINER_CLASS = 'xterm-rows'; @@ -33,8 +33,8 @@ export class DomRenderer extends Disposable implements IRenderer { private _rowFactory: DomRendererRowFactory; private _terminalClass: number = nextTerminalId++; - private _themeStyle!: StyleSheet; - private _dimensionsStyle!: StyleSheet; + private _themeStyle!: IStyleSheet; + private _dimensionsStyle!: IStyleSheet; private _rowContainer: HTMLElement; private _rowElements: HTMLElement[] = []; private _selectionContainer: HTMLElement; diff --git a/src/browser/renderer/dom/StyleSheet.ts b/src/browser/renderer/dom/StyleSheet.ts index 26238ade93..a9d9d4b35f 100644 --- a/src/browser/renderer/dom/StyleSheet.ts +++ b/src/browser/renderer/dom/StyleSheet.ts @@ -1,9 +1,9 @@ -export interface StyleSheet { +export interface IStyleSheet { dispose: () => void; setCss: (value: string) => void; } -const createCssStyleSheet = (): StyleSheet => { +const createCssStyleSheet = (): IStyleSheet => { const sheet = new CSSStyleSheet(); document.adoptedStyleSheets.push(sheet); return { @@ -13,12 +13,12 @@ const createCssStyleSheet = (): StyleSheet => { }, setCss(css) { sheet.replace(css); - }, + } }; }; -const createStyleElement = (parent: HTMLElement): StyleSheet => { - const element = document.createElement("style"); +const createStyleElement = (parent: HTMLElement): IStyleSheet => { + const element = document.createElement('style'); parent.append(element); return { dispose() { @@ -26,11 +26,11 @@ const createStyleElement = (parent: HTMLElement): StyleSheet => { }, setCss(css) { element.textContent = css; - }, + } }; }; -export const createStyle = (parent: HTMLElement): StyleSheet => { +export const createStyle = (parent: HTMLElement): IStyleSheet => { try { return createCssStyleSheet(); } catch { From 5f80fe00d50275d2d5b6fc2968b30d9049a25173 Mon Sep 17 00:00:00 2001 From: Simon Siefke Date: Thu, 27 Jul 2023 18:33:30 +0200 Subject: [PATCH 3/3] fix: use replacesync instead of replace --- src/browser/renderer/dom/StyleSheet.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/browser/renderer/dom/StyleSheet.ts b/src/browser/renderer/dom/StyleSheet.ts index a9d9d4b35f..c4cb7bd7e8 100644 --- a/src/browser/renderer/dom/StyleSheet.ts +++ b/src/browser/renderer/dom/StyleSheet.ts @@ -12,7 +12,7 @@ const createCssStyleSheet = (): IStyleSheet => { document.adoptedStyleSheets.splice(index, 1); }, setCss(css) { - sheet.replace(css); + sheet.replaceSync(css); } }; };