From 41c62288f055a615302fa7fa95ee7f4b140496a4 Mon Sep 17 00:00:00 2001 From: Daniel Imms <2193314+Tyriar@users.noreply.github.com> Date: Fri, 13 Oct 2023 13:41:53 -0700 Subject: [PATCH 01/12] Expose a documentOverride setting This lets the embedder choose which Document object to use for creating elements, adding event listeners, etc. The reason this exists is because when working with multiple windows it can be convenient to create all elements under the primary window to make instanceof usage consistent. See microsoft/vscode#195595 --- src/browser/Terminal.ts | 3 +++ src/common/services/OptionsService.ts | 1 + src/common/services/Services.ts | 1 + typings/xterm.d.ts | 11 +++++++++++ 4 files changed, 16 insertions(+) diff --git a/src/browser/Terminal.ts b/src/browser/Terminal.ts index 18af3e4e..621ee44c 100644 --- a/src/browser/Terminal.ts +++ b/src/browser/Terminal.ts @@ -398,6 +398,9 @@ export class Terminal extends CoreTerminal implements ITerminal { } this._document = parent.ownerDocument!; + if (this.options.documentOverride && this.options.documentOverride instanceof Document) { + this._document = this.optionsService.rawOptions.documentOverride as Document; + } // Create main element container this.element = this._document.createElement('div'); diff --git a/src/common/services/OptionsService.ts b/src/common/services/OptionsService.ts index 3c572445..eb9dbfa8 100644 --- a/src/common/services/OptionsService.ts +++ b/src/common/services/OptionsService.ts @@ -18,6 +18,7 @@ export const DEFAULT_OPTIONS: Readonly> = { cursorInactiveStyle: 'outline', customGlyphs: true, drawBoldTextInBrightColors: true, + documentOverride: null, fastScrollModifier: 'alt', fastScrollSensitivity: 5, fontFamily: 'courier-new, courier, monospace', diff --git a/src/common/services/Services.ts b/src/common/services/Services.ts index 52c2a79f..6ac73e37 100644 --- a/src/common/services/Services.ts +++ b/src/common/services/Services.ts @@ -217,6 +217,7 @@ export interface ITerminalOptions { cursorInactiveStyle?: CursorInactiveStyle; customGlyphs?: boolean; disableStdin?: boolean; + documentOverride?: any | null; drawBoldTextInBrightColors?: boolean; fastScrollModifier?: 'none' | 'alt' | 'ctrl' | 'shift'; fastScrollSensitivity?: number; diff --git a/typings/xterm.d.ts b/typings/xterm.d.ts index 3f603b9e..b8028a18 100644 --- a/typings/xterm.d.ts +++ b/typings/xterm.d.ts @@ -89,6 +89,17 @@ declare module 'xterm' { */ disableStdin?: boolean; + /** + * A {@link Document} to use instead of the one that xterm.js was attached + * to. The purpose of this is to improve support in multi-window + * applications where HTML elements may be references across multiple + * windows which can cause problems with `instanceof`. + * + * The type is `any` because using `Document` can cause TS to have + * performance/compiler problems. + */ + documentOverride?: any | null; + /** * Whether to draw bold text in bright colors. The default is true. */ From c80453f602e700bb774757c1d86cdf0037e062bc Mon Sep 17 00:00:00 2001 From: Daniel Imms <2193314+Tyriar@users.noreply.github.com> Date: Mon, 16 Oct 2023 11:09:36 -0700 Subject: [PATCH 02/12] Use correct document when creating elements --- addons/xterm-addon-canvas/src/BaseRenderLayer.ts | 2 +- addons/xterm-addon-webgl/src/WebglRenderer.ts | 2 +- .../src/renderLayer/BaseRenderLayer.ts | 2 +- src/browser/AccessibilityManager.ts | 11 ++++++----- src/browser/Terminal.ts | 16 ++++++++-------- src/browser/TestUtils.test.ts | 3 +++ .../decorations/BufferDecorationRenderer.ts | 5 +++-- src/browser/decorations/OverviewRulerRenderer.ts | 16 ++++++++-------- src/browser/renderer/shared/CustomGlyphs.ts | 2 +- src/browser/services/CoreBrowserService.ts | 3 ++- src/browser/services/Services.ts | 5 +++++ src/common/Color.ts | 1 + 12 files changed, 40 insertions(+), 28 deletions(-) diff --git a/addons/xterm-addon-canvas/src/BaseRenderLayer.ts b/addons/xterm-addon-canvas/src/BaseRenderLayer.ts index 8c199336..62fe71a7 100644 --- a/addons/xterm-addon-canvas/src/BaseRenderLayer.ts +++ b/addons/xterm-addon-canvas/src/BaseRenderLayer.ts @@ -59,7 +59,7 @@ export abstract class BaseRenderLayer extends Disposable implements IRenderLayer ) { super(); this._cellColorResolver = new CellColorResolver(this._terminal, this._selectionModel, this._decorationService, this._coreBrowserService, this._themeService); - this._canvas = document.createElement('canvas'); + this._canvas = this._coreBrowserService.mainDocument.createElement('canvas'); this._canvas.classList.add(`xterm-${id}-layer`); this._canvas.style.zIndex = zIndex.toString(); this._initCanvas(); diff --git a/addons/xterm-addon-webgl/src/WebglRenderer.ts b/addons/xterm-addon-webgl/src/WebglRenderer.ts index 8cc81b92..51749984 100644 --- a/addons/xterm-addon-webgl/src/WebglRenderer.ts +++ b/addons/xterm-addon-webgl/src/WebglRenderer.ts @@ -88,7 +88,7 @@ export class WebglRenderer extends Disposable implements IRenderer { this._updateCursorBlink(); this.register(_optionsService.onOptionChange(() => this._handleOptionsChanged())); - this._canvas = document.createElement('canvas'); + this._canvas = this._coreBrowserService.mainDocument.createElement('canvas'); const contextAttributes = { antialias: false, diff --git a/addons/xterm-addon-webgl/src/renderLayer/BaseRenderLayer.ts b/addons/xterm-addon-webgl/src/renderLayer/BaseRenderLayer.ts index 3aaac435..11f96ed8 100644 --- a/addons/xterm-addon-webgl/src/renderLayer/BaseRenderLayer.ts +++ b/addons/xterm-addon-webgl/src/renderLayer/BaseRenderLayer.ts @@ -38,7 +38,7 @@ export abstract class BaseRenderLayer extends Disposable implements IRenderLayer protected readonly _themeService: IThemeService ) { super(); - this._canvas = document.createElement('canvas'); + this._canvas = this._coreBrowserService.mainDocument.createElement('canvas'); this._canvas.classList.add(`xterm-${id}-layer`); this._canvas.style.zIndex = zIndex.toString(); this._initCanvas(); diff --git a/src/browser/AccessibilityManager.ts b/src/browser/AccessibilityManager.ts index c878bd0a..1005414f 100644 --- a/src/browser/AccessibilityManager.ts +++ b/src/browser/AccessibilityManager.ts @@ -8,7 +8,7 @@ import { ITerminal, IRenderDebouncer } from 'browser/Types'; import { TimeBasedDebouncer } from 'browser/TimeBasedDebouncer'; import { Disposable, toDisposable } from 'common/Lifecycle'; import { ScreenDprMonitor } from 'browser/ScreenDprMonitor'; -import { IRenderService } from 'browser/services/Services'; +import { ICoreBrowserService, IRenderService } from 'browser/services/Services'; import { addDisposableDomListener } from 'browser/Lifecycle'; import { IBuffer } from 'common/buffer/Types'; @@ -49,13 +49,14 @@ export class AccessibilityManager extends Disposable { constructor( private readonly _terminal: ITerminal, + @ICoreBrowserService private readonly _coreBrowserService: ICoreBrowserService, @IRenderService private readonly _renderService: IRenderService ) { super(); - this._accessibilityContainer = document.createElement('div'); + this._accessibilityContainer = this._coreBrowserService.mainDocument.createElement('div'); this._accessibilityContainer.classList.add('xterm-accessibility'); - this._rowContainer = document.createElement('div'); + this._rowContainer = this._coreBrowserService.mainDocument.createElement('div'); this._rowContainer.setAttribute('role', 'list'); this._rowContainer.classList.add('xterm-accessibility-tree'); this._rowElements = []; @@ -72,7 +73,7 @@ export class AccessibilityManager extends Disposable { this._refreshRowsDimensions(); this._accessibilityContainer.appendChild(this._rowContainer); - this._liveRegion = document.createElement('div'); + this._liveRegion = this._coreBrowserService.mainDocument.createElement('div'); this._liveRegion.classList.add('live-region'); this._liveRegion.setAttribute('aria-live', 'assertive'); this._accessibilityContainer.appendChild(this._liveRegion); @@ -261,7 +262,7 @@ export class AccessibilityManager extends Disposable { } private _createAccessibilityTreeNode(): HTMLElement { - const element = document.createElement('div'); + const element = this._coreBrowserService.mainDocument.createElement('div'); element.setAttribute('role', 'listitem'); element.tabIndex = -1; this._refreshRowDimensions(element); diff --git a/src/browser/Terminal.ts b/src/browser/Terminal.ts index 621ee44c..9d4ca5a0 100644 --- a/src/browser/Terminal.ts +++ b/src/browser/Terminal.ts @@ -411,25 +411,25 @@ export class Terminal extends CoreTerminal implements ITerminal { // Performance: Use a document fragment to build the terminal // viewport and helper elements detached from the DOM - const fragment = document.createDocumentFragment(); - this._viewportElement = document.createElement('div'); + const fragment = this._document.createDocumentFragment(); + this._viewportElement = this._document.createElement('div'); this._viewportElement.classList.add('xterm-viewport'); fragment.appendChild(this._viewportElement); - this._viewportScrollArea = document.createElement('div'); + this._viewportScrollArea = this._document.createElement('div'); this._viewportScrollArea.classList.add('xterm-scroll-area'); this._viewportElement.appendChild(this._viewportScrollArea); - this.screenElement = document.createElement('div'); + this.screenElement = this._document.createElement('div'); this.screenElement.classList.add('xterm-screen'); // Create the container that will hold helpers like the textarea for // capturing DOM Events. Then produce the helpers. - this._helperContainer = document.createElement('div'); + this._helperContainer = this._document.createElement('div'); this._helperContainer.classList.add('xterm-helpers'); this.screenElement.appendChild(this._helperContainer); fragment.appendChild(this.screenElement); - this.textarea = document.createElement('textarea'); + this.textarea = this._document.createElement('textarea'); this.textarea.classList.add('xterm-helper-textarea'); this.textarea.setAttribute('aria-label', Strings.promptLabel); if (!Browser.isChromeOS) { @@ -444,7 +444,7 @@ export class Terminal extends CoreTerminal implements ITerminal { // Register the core browser service before the generic textarea handlers are registered so it // handles them first. Otherwise the renderers may use the wrong focus state. - this._coreBrowserService = this._instantiationService.createInstance(CoreBrowserService, this.textarea, this._document.defaultView ?? window); + this._coreBrowserService = this._instantiationService.createInstance(CoreBrowserService, this.textarea, parent.ownerDocument.defaultView ?? window, this._document ?? window.document); this._instantiationService.setService(ICoreBrowserService, this._coreBrowserService); this.register(addDisposableDomListener(this.textarea, 'focus', (ev: KeyboardEvent) => this._handleTextAreaFocus(ev))); @@ -466,7 +466,7 @@ export class Terminal extends CoreTerminal implements ITerminal { this.register(this._renderService.onRenderedViewportChange(e => this._onRender.fire(e))); this.onResize(e => this._renderService!.resize(e.cols, e.rows)); - this._compositionView = document.createElement('div'); + this._compositionView = this._document.createElement('div'); this._compositionView.classList.add('composition-view'); this._compositionHelper = this._instantiationService.createInstance(CompositionHelper, this.textarea, this._compositionView); this._helperContainer.appendChild(this._compositionView); diff --git a/src/browser/TestUtils.test.ts b/src/browser/TestUtils.test.ts index 7b464a33..b36e3b49 100644 --- a/src/browser/TestUtils.test.ts +++ b/src/browser/TestUtils.test.ts @@ -355,6 +355,9 @@ export class MockCoreBrowserService implements ICoreBrowserService { public get window(): Window & typeof globalThis { throw Error('Window object not available in tests'); } + public get mainDocument(): Document { + throw Error('Document object not available in tests'); + } public dpr: number = 1; } diff --git a/src/browser/decorations/BufferDecorationRenderer.ts b/src/browser/decorations/BufferDecorationRenderer.ts index ba4f6ca8..0cac7bc7 100644 --- a/src/browser/decorations/BufferDecorationRenderer.ts +++ b/src/browser/decorations/BufferDecorationRenderer.ts @@ -4,7 +4,7 @@ *--------------------------------------------------------------------------------------------*/ import { addDisposableDomListener } from 'browser/Lifecycle'; -import { IRenderService } from 'browser/services/Services'; +import { ICoreBrowserService, IRenderService } from 'browser/services/Services'; import { Disposable, toDisposable } from 'common/Lifecycle'; import { IBufferService, IDecorationService, IInternalDecoration } from 'common/services/Services'; @@ -19,6 +19,7 @@ export class BufferDecorationRenderer extends Disposable { constructor( private readonly _screenElement: HTMLElement, @IBufferService private readonly _bufferService: IBufferService, + @ICoreBrowserService private readonly _coreBrowserService: ICoreBrowserService, @IDecorationService private readonly _decorationService: IDecorationService, @IRenderService private readonly _renderService: IRenderService ) { @@ -70,7 +71,7 @@ export class BufferDecorationRenderer extends Disposable { } private _createElement(decoration: IInternalDecoration): HTMLElement { - const element = document.createElement('div'); + const element = this._coreBrowserService.mainDocument.createElement('div'); element.classList.add('xterm-decoration'); element.classList.toggle('xterm-decoration-top-layer', decoration?.options?.layer === 'top'); element.style.width = `${Math.round((decoration.options.width || 1) * this._renderService.dimensions.css.cell.width)}px`; diff --git a/src/browser/decorations/OverviewRulerRenderer.ts b/src/browser/decorations/OverviewRulerRenderer.ts index d5765385..d63d8745 100644 --- a/src/browser/decorations/OverviewRulerRenderer.ts +++ b/src/browser/decorations/OverviewRulerRenderer.ts @@ -52,10 +52,10 @@ export class OverviewRulerRenderer extends Disposable { @IDecorationService private readonly _decorationService: IDecorationService, @IRenderService private readonly _renderService: IRenderService, @IOptionsService private readonly _optionsService: IOptionsService, - @ICoreBrowserService private readonly _coreBrowseService: ICoreBrowserService + @ICoreBrowserService private readonly _coreBrowserService: ICoreBrowserService ) { super(); - this._canvas = document.createElement('canvas'); + this._canvas = this._coreBrowserService.mainDocument.createElement('canvas'); this._canvas.classList.add('xterm-decoration-overview-ruler'); this._refreshCanvasDimensions(); this._viewportElement.parentElement?.insertBefore(this._canvas, this._viewportElement); @@ -112,7 +112,7 @@ export class OverviewRulerRenderer extends Disposable { // overview ruler width changed this.register(this._optionsService.onSpecificOptionChange('overviewRulerWidth', () => this._queueRefresh(true))); // device pixel ratio changed - this.register(addDisposableDomListener(this._coreBrowseService.window, 'resize', () => this._queueRefresh(true))); + this.register(addDisposableDomListener(this._coreBrowserService.window, 'resize', () => this._queueRefresh(true))); // set the canvas dimensions this._queueRefresh(true); } @@ -135,11 +135,11 @@ export class OverviewRulerRenderer extends Disposable { } private _refreshDrawHeightConstants(): void { - drawHeight.full = Math.round(2 * this._coreBrowseService.dpr); + drawHeight.full = Math.round(2 * this._coreBrowserService.dpr); // Calculate actual pixels per line const pixelsPerLine = this._canvas.height / this._bufferService.buffer.lines.length; // Clamp actual pixels within a range - const nonFullHeight = Math.round(Math.max(Math.min(pixelsPerLine, 12), 6) * this._coreBrowseService.dpr); + const nonFullHeight = Math.round(Math.max(Math.min(pixelsPerLine, 12), 6) * this._coreBrowserService.dpr); drawHeight.left = nonFullHeight; drawHeight.center = nonFullHeight; drawHeight.right = nonFullHeight; @@ -157,9 +157,9 @@ export class OverviewRulerRenderer extends Disposable { private _refreshCanvasDimensions(): void { this._canvas.style.width = `${this._width}px`; - this._canvas.width = Math.round(this._width * this._coreBrowseService.dpr); + this._canvas.width = Math.round(this._width * this._coreBrowserService.dpr); this._canvas.style.height = `${this._screenElement.clientHeight}px`; - this._canvas.height = Math.round(this._screenElement.clientHeight * this._coreBrowseService.dpr); + this._canvas.height = Math.round(this._screenElement.clientHeight * this._coreBrowserService.dpr); this._refreshDrawConstants(); this._refreshColorZonePadding(); } @@ -211,7 +211,7 @@ export class OverviewRulerRenderer extends Disposable { if (this._animationFrame !== undefined) { return; } - this._animationFrame = this._coreBrowseService.window.requestAnimationFrame(() => { + this._animationFrame = this._coreBrowserService.window.requestAnimationFrame(() => { this._refreshDecorations(); this._animationFrame = undefined; }); diff --git a/src/browser/renderer/shared/CustomGlyphs.ts b/src/browser/renderer/shared/CustomGlyphs.ts index c08bc4b1..cf6292af 100644 --- a/src/browser/renderer/shared/CustomGlyphs.ts +++ b/src/browser/renderer/shared/CustomGlyphs.ts @@ -474,7 +474,7 @@ function drawPatternChar( if (!pattern) { const width = charDefinition[0].length; const height = charDefinition.length; - const tmpCanvas = document.createElement('canvas'); + const tmpCanvas = ctx.canvas.ownerDocument.createElement('canvas'); tmpCanvas.width = width; tmpCanvas.height = height; const tmpCtx = throwIfFalsy(tmpCanvas.getContext('2d')); diff --git a/src/browser/services/CoreBrowserService.ts b/src/browser/services/CoreBrowserService.ts index e992f255..ad61deec 100644 --- a/src/browser/services/CoreBrowserService.ts +++ b/src/browser/services/CoreBrowserService.ts @@ -13,7 +13,8 @@ export class CoreBrowserService implements ICoreBrowserService { constructor( private _textarea: HTMLTextAreaElement, - public readonly window: Window & typeof globalThis + public readonly window: Window & typeof globalThis, + public readonly mainDocument: Document ) { this._textarea.addEventListener('focus', () => this._isFocused = true); this._textarea.addEventListener('blur', () => this._isFocused = false); diff --git a/src/browser/services/Services.ts b/src/browser/services/Services.ts index d96285f7..50c71825 100644 --- a/src/browser/services/Services.ts +++ b/src/browser/services/Services.ts @@ -34,6 +34,11 @@ export interface ICoreBrowserService { * window. */ readonly window: Window & typeof globalThis; + /** + * The document of the primary window if working with multiple windows. This + * is set by the documentOverride setting. + */ + readonly mainDocument: Document; /** * Helper for getting the devicePixelRatio of the parent window. */ diff --git a/src/common/Color.ts b/src/common/Color.ts index 108d72f3..9bfed4e6 100644 --- a/src/common/Color.ts +++ b/src/common/Color.ts @@ -113,6 +113,7 @@ export namespace css { let $ctx: CanvasRenderingContext2D | undefined; let $litmusColor: CanvasGradient | undefined; if (!isNode) { + // This is guaranteed to run in the first window, so document should be correct const canvas = document.createElement('canvas'); canvas.width = 1; canvas.height = 1; From 45605db3c5028c5116c1bf7e732722ab2e2bdf86 Mon Sep 17 00:00:00 2001 From: Daniel Imms <2193314+Tyriar@users.noreply.github.com> Date: Tue, 17 Oct 2023 08:39:48 -0700 Subject: [PATCH 03/12] Listen to window change and pass through to ScreenDprMonitor --- src/browser/AccessibilityManager.ts | 4 +++- src/browser/ScreenDprMonitor.ts | 12 +++++++++- src/browser/Terminal.ts | 21 +++++++++++++---- src/browser/TestUtils.test.ts | 1 + .../decorations/OverviewRulerRenderer.ts | 1 + src/browser/services/CoreBrowserService.ts | 23 +++++++++++++++++-- src/browser/services/RenderService.ts | 6 ++--- src/browser/services/Services.ts | 14 ++++++----- 8 files changed, 64 insertions(+), 18 deletions(-) diff --git a/src/browser/AccessibilityManager.ts b/src/browser/AccessibilityManager.ts index 1005414f..e8cd5888 100644 --- a/src/browser/AccessibilityManager.ts +++ b/src/browser/AccessibilityManager.ts @@ -11,6 +11,7 @@ import { ScreenDprMonitor } from 'browser/ScreenDprMonitor'; import { ICoreBrowserService, IRenderService } from 'browser/services/Services'; import { addDisposableDomListener } from 'browser/Lifecycle'; import { IBuffer } from 'common/buffer/Types'; +import { IInstantiationService } from 'common/services/Services'; const MAX_ROWS_TO_READ = 20; @@ -49,6 +50,7 @@ export class AccessibilityManager extends Disposable { constructor( private readonly _terminal: ITerminal, + @IInstantiationService instantiationService: IInstantiationService, @ICoreBrowserService private readonly _coreBrowserService: ICoreBrowserService, @IRenderService private readonly _renderService: IRenderService ) { @@ -95,7 +97,7 @@ export class AccessibilityManager extends Disposable { this.register(this._terminal.onBlur(() => this._clearLiveRegion())); this.register(this._renderService.onDimensionsChange(() => this._refreshRowsDimensions())); - this._screenDprMonitor = new ScreenDprMonitor(window); + this._screenDprMonitor = this.register(instantiationService.createInstance(ScreenDprMonitor)); this.register(this._screenDprMonitor); this._screenDprMonitor.setListener(() => this._refreshRowsDimensions()); // This shouldn't be needed on modern browsers but is present in case the diff --git a/src/browser/ScreenDprMonitor.ts b/src/browser/ScreenDprMonitor.ts index 1c3f31b7..5bb2aa72 100644 --- a/src/browser/ScreenDprMonitor.ts +++ b/src/browser/ScreenDprMonitor.ts @@ -3,6 +3,7 @@ * @license MIT */ +import { ICoreBrowserService } from 'browser/services/Services'; import { Disposable, toDisposable } from 'common/Lifecycle'; export type ScreenDprListener = (newDevicePixelRatio?: number, oldDevicePixelRatio?: number) => void; @@ -22,9 +23,18 @@ export class ScreenDprMonitor extends Disposable { private _outerListener: ((this: MediaQueryList, ev: MediaQueryListEvent) => any) | undefined; private _listener: ScreenDprListener | undefined; private _resolutionMediaMatchList: MediaQueryList | undefined; + private _parentWindow: Window; - constructor(private _parentWindow: Window) { + constructor(@ICoreBrowserService coreBrowserService: ICoreBrowserService) { super(); + this._parentWindow = coreBrowserService.window; + this.register(coreBrowserService.onWindowChange(w => { + this._parentWindow = w; + if (this._listener && this._parentWindow.devicePixelRatio !== this._currentDevicePixelRatio) { + this._listener(this._parentWindow.devicePixelRatio, this._currentDevicePixelRatio); + } + this._updateDpr(); + })); this._currentDevicePixelRatio = this._parentWindow.devicePixelRatio; this.register(toDisposable(() => { this.clearListener(); diff --git a/src/browser/Terminal.ts b/src/browser/Terminal.ts index 9d4ca5a0..b64c3b1d 100644 --- a/src/browser/Terminal.ts +++ b/src/browser/Terminal.ts @@ -58,9 +58,6 @@ import { IDecoration, IDecorationOptions, IDisposable, ILinkProvider, IMarker } import { WindowsOptionsReportType } from '../common/InputHandler'; import { AccessibilityManager } from './AccessibilityManager'; -// Let it work inside Node.js for automated testing purposes. -const document: Document = (typeof window !== 'undefined') ? window.document : null as any; - export class Terminal extends CoreTerminal implements ITerminal { public textarea: HTMLTextAreaElement | undefined; public element: HTMLElement | undefined; @@ -397,7 +394,16 @@ export class Terminal extends CoreTerminal implements ITerminal { this._logService.debug('Terminal.open was called on an element that was not attached to the DOM'); } - this._document = parent.ownerDocument!; + // If the terminal is already opened + if (this.element?.ownerDocument.defaultView && this._coreBrowserService) { + // Adjust the window if needed + if (this.element.ownerDocument.defaultView !== this._coreBrowserService.window) { + this._coreBrowserService.window = this.element.ownerDocument.defaultView; + } + return; + } + + this._document = parent.ownerDocument; if (this.options.documentOverride && this.options.documentOverride instanceof Document) { this._document = this.optionsService.rawOptions.documentOverride as Document; } @@ -444,7 +450,12 @@ export class Terminal extends CoreTerminal implements ITerminal { // Register the core browser service before the generic textarea handlers are registered so it // handles them first. Otherwise the renderers may use the wrong focus state. - this._coreBrowserService = this._instantiationService.createInstance(CoreBrowserService, this.textarea, parent.ownerDocument.defaultView ?? window, this._document ?? window.document); + this._coreBrowserService = this.register(this._instantiationService.createInstance(CoreBrowserService, + this.textarea, + parent.ownerDocument.defaultView ?? window, + // Force unsafe null in node.js environment for tests + this._document ?? (typeof window !== 'undefined') ? window.document : null as any + )); this._instantiationService.setService(ICoreBrowserService, this._coreBrowserService); this.register(addDisposableDomListener(this.textarea, 'focus', (ev: KeyboardEvent) => this._handleTextAreaFocus(ev))); diff --git a/src/browser/TestUtils.test.ts b/src/browser/TestUtils.test.ts index b36e3b49..1ad5bc76 100644 --- a/src/browser/TestUtils.test.ts +++ b/src/browser/TestUtils.test.ts @@ -350,6 +350,7 @@ export class MockCompositionHelper implements ICompositionHelper { } export class MockCoreBrowserService implements ICoreBrowserService { + public onWindowChange = new EventEmitter().event; public serviceBrand: undefined; public isFocused: boolean = true; public get window(): Window & typeof globalThis { diff --git a/src/browser/decorations/OverviewRulerRenderer.ts b/src/browser/decorations/OverviewRulerRenderer.ts index d63d8745..8648248a 100644 --- a/src/browser/decorations/OverviewRulerRenderer.ts +++ b/src/browser/decorations/OverviewRulerRenderer.ts @@ -112,6 +112,7 @@ export class OverviewRulerRenderer extends Disposable { // overview ruler width changed this.register(this._optionsService.onSpecificOptionChange('overviewRulerWidth', () => this._queueRefresh(true))); // device pixel ratio changed + // TODO: Observe DPR instead this.register(addDisposableDomListener(this._coreBrowserService.window, 'resize', () => this._queueRefresh(true))); // set the canvas dimensions this._queueRefresh(true); diff --git a/src/browser/services/CoreBrowserService.ts b/src/browser/services/CoreBrowserService.ts index ad61deec..2c598606 100644 --- a/src/browser/services/CoreBrowserService.ts +++ b/src/browser/services/CoreBrowserService.ts @@ -3,23 +3,42 @@ * @license MIT */ +import { Disposable } from 'common/Lifecycle'; import { ICoreBrowserService } from './Services'; +import { EventEmitter } from 'common/EventEmitter'; -export class CoreBrowserService implements ICoreBrowserService { +export class CoreBrowserService extends Disposable implements ICoreBrowserService { public serviceBrand: undefined; private _isFocused = false; private _cachedIsFocused: boolean | undefined = undefined; + private readonly _onWindowChange = this.register(new EventEmitter()); + public readonly onWindowChange = this._onWindowChange.event; + constructor( private _textarea: HTMLTextAreaElement, - public readonly window: Window & typeof globalThis, + // TODO: Add getter and setter and event + private _window: Window & typeof globalThis, public readonly mainDocument: Document ) { + super(); + this._textarea.addEventListener('focus', () => this._isFocused = true); this._textarea.addEventListener('blur', () => this._isFocused = false); } + public get window(): Window & typeof globalThis { + return this._window; + } + + public set window(value: Window & typeof globalThis) { + if (this._window !== value) { + this._window = value; + this._onWindowChange.fire(this._window); + } + } + public get dpr(): number { return this.window.devicePixelRatio; } diff --git a/src/browser/services/RenderService.ts b/src/browser/services/RenderService.ts index e6d259c2..9fc3f8a9 100644 --- a/src/browser/services/RenderService.ts +++ b/src/browser/services/RenderService.ts @@ -12,7 +12,7 @@ import { ICharSizeService, ICoreBrowserService, IRenderService, IThemeService } import { EventEmitter } from 'common/EventEmitter'; import { Disposable, MutableDisposable } from 'common/Lifecycle'; import { DebouncedIdleTask } from 'common/TaskQueue'; -import { IBufferService, IDecorationService, IOptionsService } from 'common/services/Services'; +import { IBufferService, IDecorationService, IInstantiationService, IOptionsService } from 'common/services/Services'; interface ISelectionState { start: [number, number] | undefined; @@ -59,6 +59,7 @@ export class RenderService extends Disposable implements IRenderService { @IDecorationService decorationService: IDecorationService, @IBufferService bufferService: IBufferService, @ICoreBrowserService coreBrowserService: ICoreBrowserService, + @IInstantiationService instantiationService: IInstantiationService, @IThemeService themeService: IThemeService ) { super(); @@ -66,9 +67,8 @@ export class RenderService extends Disposable implements IRenderService { this._renderDebouncer = new RenderDebouncer(coreBrowserService.window, (start, end) => this._renderRows(start, end)); this.register(this._renderDebouncer); - this._screenDprMonitor = new ScreenDprMonitor(coreBrowserService.window); + this._screenDprMonitor = this.register(instantiationService.createInstance(ScreenDprMonitor)); this._screenDprMonitor.setListener(() => this.handleDevicePixelRatioChange()); - this.register(this._screenDprMonitor); this.register(bufferService.onResize(() => this._fullRefresh())); this.register(bufferService.buffers.onBufferActivate(() => this._renderer.value?.clear())); diff --git a/src/browser/services/Services.ts b/src/browser/services/Services.ts index 50c71825..a1faab3d 100644 --- a/src/browser/services/Services.ts +++ b/src/browser/services/Services.ts @@ -28,15 +28,17 @@ export interface ICoreBrowserService { serviceBrand: undefined; readonly isFocused: boolean; + + onWindowChange: IEvent; /** - * Parent window that the terminal is rendered into. DOM and rendering APIs - * (e.g. requestAnimationFrame) should be invoked in the context of this - * window. + * Gets or sets the parent window that the terminal is rendered into. DOM and rendering APIs (e.g. + * requestAnimationFrame) should be invoked in the context of this window. This should be set when + * the window hosting the xterm.js instance changes. */ - readonly window: Window & typeof globalThis; + window: Window & typeof globalThis; /** - * The document of the primary window if working with multiple windows. This - * is set by the documentOverride setting. + * The document of the primary window to be used to create elements when working with multiple + * windows. This is defined by the documentOverride setting. */ readonly mainDocument: Document; /** From 63790f8854d5b6773b0cf1e303b9e5167da5681e Mon Sep 17 00:00:00 2001 From: Daniel Imms <2193314+Tyriar@users.noreply.github.com> Date: Tue, 17 Oct 2023 08:49:28 -0700 Subject: [PATCH 04/12] Change ScreenDprMonitor to be event based --- src/browser/AccessibilityManager.ts | 4 +- src/browser/ScreenDprMonitor.ts | 52 +++++++++---------- .../decorations/BufferDecorationRenderer.ts | 1 + .../decorations/OverviewRulerRenderer.ts | 2 +- src/browser/services/RenderService.ts | 3 +- 5 files changed, 32 insertions(+), 30 deletions(-) diff --git a/src/browser/AccessibilityManager.ts b/src/browser/AccessibilityManager.ts index e8cd5888..ed2c12b0 100644 --- a/src/browser/AccessibilityManager.ts +++ b/src/browser/AccessibilityManager.ts @@ -98,10 +98,10 @@ export class AccessibilityManager extends Disposable { this.register(this._renderService.onDimensionsChange(() => this._refreshRowsDimensions())); this._screenDprMonitor = this.register(instantiationService.createInstance(ScreenDprMonitor)); - this.register(this._screenDprMonitor); - this._screenDprMonitor.setListener(() => this._refreshRowsDimensions()); + this.register(this._screenDprMonitor.onDprChange(() => this._refreshRowsDimensions())); // This shouldn't be needed on modern browsers but is present in case the // media query that drives the ScreenDprMonitor isn't supported + // TODO: Listen to window change this.register(addDisposableDomListener(window, 'resize', () => this._refreshRowsDimensions())); this._refreshRows(); diff --git a/src/browser/ScreenDprMonitor.ts b/src/browser/ScreenDprMonitor.ts index 5bb2aa72..6ff016e5 100644 --- a/src/browser/ScreenDprMonitor.ts +++ b/src/browser/ScreenDprMonitor.ts @@ -4,10 +4,9 @@ */ import { ICoreBrowserService } from 'browser/services/Services'; +import { EventEmitter } from 'common/EventEmitter'; import { Disposable, toDisposable } from 'common/Lifecycle'; -export type ScreenDprListener = (newDevicePixelRatio?: number, oldDevicePixelRatio?: number) => void; - /** * The screen device pixel ratio monitor allows listening for when the * window.devicePixelRatio value changes. This is done not with polling but with @@ -21,39 +20,41 @@ export type ScreenDprListener = (newDevicePixelRatio?: number, oldDevicePixelRat export class ScreenDprMonitor extends Disposable { private _currentDevicePixelRatio: number; private _outerListener: ((this: MediaQueryList, ev: MediaQueryListEvent) => any) | undefined; - private _listener: ScreenDprListener | undefined; private _resolutionMediaMatchList: MediaQueryList | undefined; private _parentWindow: Window; + private readonly _onDprChange = this.register(new EventEmitter()); + public readonly onDprChange = this._onDprChange.event; + constructor(@ICoreBrowserService coreBrowserService: ICoreBrowserService) { super(); - this._parentWindow = coreBrowserService.window; - this.register(coreBrowserService.onWindowChange(w => { - this._parentWindow = w; - if (this._listener && this._parentWindow.devicePixelRatio !== this._currentDevicePixelRatio) { - this._listener(this._parentWindow.devicePixelRatio, this._currentDevicePixelRatio); - } - this._updateDpr(); - })); - this._currentDevicePixelRatio = this._parentWindow.devicePixelRatio; - this.register(toDisposable(() => { - this.clearListener(); - })); - } - public setListener(listener: ScreenDprListener): void { - if (this._listener) { - this.clearListener(); - } - this._listener = listener; + this._parentWindow = coreBrowserService.window; + + // Initialize listener and dpr value this._outerListener = () => { - if (!this._listener) { - return; + if (this._parentWindow.devicePixelRatio !== this._currentDevicePixelRatio) { + this._onDprChange.fire(this._parentWindow.devicePixelRatio); } - this._listener(this._parentWindow.devicePixelRatio, this._currentDevicePixelRatio); this._updateDpr(); }; + this._currentDevicePixelRatio = this._parentWindow.devicePixelRatio; this._updateDpr(); + + // Listen for window changes + this.register(coreBrowserService.onWindowChange(w => { + this._parentWindow = w; + if (this._parentWindow.devicePixelRatio !== this._currentDevicePixelRatio) { + this._onDprChange.fire(this._parentWindow.devicePixelRatio); + } + this._updateDpr(); + })); + + // Setup additional disposables + this.register(toDisposable(() => this.clearListener())); + } + + public setListener(): void { } private _updateDpr(): void { @@ -71,12 +72,11 @@ export class ScreenDprMonitor extends Disposable { } public clearListener(): void { - if (!this._resolutionMediaMatchList || !this._listener || !this._outerListener) { + if (!this._resolutionMediaMatchList || !this._outerListener) { return; } this._resolutionMediaMatchList.removeListener(this._outerListener); this._resolutionMediaMatchList = undefined; - this._listener = undefined; this._outerListener = undefined; } } diff --git a/src/browser/decorations/BufferDecorationRenderer.ts b/src/browser/decorations/BufferDecorationRenderer.ts index 0cac7bc7..3a0d7862 100644 --- a/src/browser/decorations/BufferDecorationRenderer.ts +++ b/src/browser/decorations/BufferDecorationRenderer.ts @@ -34,6 +34,7 @@ export class BufferDecorationRenderer extends Disposable { this._dimensionsChanged = true; this._queueRefresh(); })); + // TODO: Listen to window change this.register(addDisposableDomListener(window, 'resize', () => this._queueRefresh())); this.register(this._bufferService.buffers.onBufferActivate(() => { this._altBufferIsActive = this._bufferService.buffer === this._bufferService.buffers.alt; diff --git a/src/browser/decorations/OverviewRulerRenderer.ts b/src/browser/decorations/OverviewRulerRenderer.ts index 8648248a..57b6dfc6 100644 --- a/src/browser/decorations/OverviewRulerRenderer.ts +++ b/src/browser/decorations/OverviewRulerRenderer.ts @@ -112,7 +112,7 @@ export class OverviewRulerRenderer extends Disposable { // overview ruler width changed this.register(this._optionsService.onSpecificOptionChange('overviewRulerWidth', () => this._queueRefresh(true))); // device pixel ratio changed - // TODO: Observe DPR instead + // TODO: Observe DPR instead / listen to window change this.register(addDisposableDomListener(this._coreBrowserService.window, 'resize', () => this._queueRefresh(true))); // set the canvas dimensions this._queueRefresh(true); diff --git a/src/browser/services/RenderService.ts b/src/browser/services/RenderService.ts index 9fc3f8a9..60c27798 100644 --- a/src/browser/services/RenderService.ts +++ b/src/browser/services/RenderService.ts @@ -68,7 +68,7 @@ export class RenderService extends Disposable implements IRenderService { this.register(this._renderDebouncer); this._screenDprMonitor = this.register(instantiationService.createInstance(ScreenDprMonitor)); - this._screenDprMonitor.setListener(() => this.handleDevicePixelRatioChange()); + this.register(this._screenDprMonitor.onDprChange(() => this.handleDevicePixelRatioChange())); this.register(bufferService.onResize(() => this._fullRefresh())); this.register(bufferService.buffers.onBufferActivate(() => this._renderer.value?.clear())); @@ -106,6 +106,7 @@ export class RenderService extends Disposable implements IRenderService { // dprchange should handle this case, we need this as well for browsers that don't support the // matchMedia query. + // TODO: Listen to window change this.register(addDisposableDomListener(coreBrowserService.window, 'resize', () => this.handleDevicePixelRatioChange())); this.register(themeService.onChangeColors(() => this._fullRefresh())); From 4cc51ca08fa8a38e8859d60a5a594a876e28c3d5 Mon Sep 17 00:00:00 2001 From: Daniel Imms <2193314+Tyriar@users.noreply.github.com> Date: Tue, 17 Oct 2023 08:55:07 -0700 Subject: [PATCH 05/12] Move ScreenDprMonitor into CoreBrowserService --- src/browser/AccessibilityManager.ts | 6 +----- src/browser/ScreenDprMonitor.ts | 22 +++++++--------------- src/browser/TestUtils.test.ts | 1 + src/browser/services/CoreBrowserService.ts | 10 ++++++++-- src/browser/services/RenderService.ts | 5 +---- src/browser/services/Services.ts | 4 +++- 6 files changed, 21 insertions(+), 27 deletions(-) diff --git a/src/browser/AccessibilityManager.ts b/src/browser/AccessibilityManager.ts index ed2c12b0..703459f4 100644 --- a/src/browser/AccessibilityManager.ts +++ b/src/browser/AccessibilityManager.ts @@ -7,7 +7,6 @@ import * as Strings from 'browser/LocalizableStrings'; import { ITerminal, IRenderDebouncer } from 'browser/Types'; import { TimeBasedDebouncer } from 'browser/TimeBasedDebouncer'; import { Disposable, toDisposable } from 'common/Lifecycle'; -import { ScreenDprMonitor } from 'browser/ScreenDprMonitor'; import { ICoreBrowserService, IRenderService } from 'browser/services/Services'; import { addDisposableDomListener } from 'browser/Lifecycle'; import { IBuffer } from 'common/buffer/Types'; @@ -30,8 +29,6 @@ export class AccessibilityManager extends Disposable { private _liveRegionLineCount: number = 0; private _liveRegionDebouncer: IRenderDebouncer; - private _screenDprMonitor: ScreenDprMonitor; - private _topBoundaryFocusListener: (e: FocusEvent) => void; private _bottomBoundaryFocusListener: (e: FocusEvent) => void; @@ -97,8 +94,7 @@ export class AccessibilityManager extends Disposable { this.register(this._terminal.onBlur(() => this._clearLiveRegion())); this.register(this._renderService.onDimensionsChange(() => this._refreshRowsDimensions())); - this._screenDprMonitor = this.register(instantiationService.createInstance(ScreenDprMonitor)); - this.register(this._screenDprMonitor.onDprChange(() => this._refreshRowsDimensions())); + this.register(this._coreBrowserService.onDprChange(() => this._refreshRowsDimensions())); // This shouldn't be needed on modern browsers but is present in case the // media query that drives the ScreenDprMonitor isn't supported // TODO: Listen to window change diff --git a/src/browser/ScreenDprMonitor.ts b/src/browser/ScreenDprMonitor.ts index 6ff016e5..1152a55a 100644 --- a/src/browser/ScreenDprMonitor.ts +++ b/src/browser/ScreenDprMonitor.ts @@ -3,7 +3,6 @@ * @license MIT */ -import { ICoreBrowserService } from 'browser/services/Services'; import { EventEmitter } from 'common/EventEmitter'; import { Disposable, toDisposable } from 'common/Lifecycle'; @@ -21,16 +20,13 @@ export class ScreenDprMonitor extends Disposable { private _currentDevicePixelRatio: number; private _outerListener: ((this: MediaQueryList, ev: MediaQueryListEvent) => any) | undefined; private _resolutionMediaMatchList: MediaQueryList | undefined; - private _parentWindow: Window; private readonly _onDprChange = this.register(new EventEmitter()); public readonly onDprChange = this._onDprChange.event; - constructor(@ICoreBrowserService coreBrowserService: ICoreBrowserService) { + constructor(private _parentWindow: Window) { super(); - this._parentWindow = coreBrowserService.window; - // Initialize listener and dpr value this._outerListener = () => { if (this._parentWindow.devicePixelRatio !== this._currentDevicePixelRatio) { @@ -41,20 +37,16 @@ export class ScreenDprMonitor extends Disposable { this._currentDevicePixelRatio = this._parentWindow.devicePixelRatio; this._updateDpr(); - // Listen for window changes - this.register(coreBrowserService.onWindowChange(w => { - this._parentWindow = w; - if (this._parentWindow.devicePixelRatio !== this._currentDevicePixelRatio) { - this._onDprChange.fire(this._parentWindow.devicePixelRatio); - } - this._updateDpr(); - })); - // Setup additional disposables this.register(toDisposable(() => this.clearListener())); } - public setListener(): void { + public setWindow(parentWindow: Window): void { + this._parentWindow = parentWindow; + if (this._parentWindow.devicePixelRatio !== this._currentDevicePixelRatio) { + this._onDprChange.fire(this._parentWindow.devicePixelRatio); + } + this._updateDpr(); } private _updateDpr(): void { diff --git a/src/browser/TestUtils.test.ts b/src/browser/TestUtils.test.ts index 1ad5bc76..d557cd98 100644 --- a/src/browser/TestUtils.test.ts +++ b/src/browser/TestUtils.test.ts @@ -350,6 +350,7 @@ export class MockCompositionHelper implements ICompositionHelper { } export class MockCoreBrowserService implements ICoreBrowserService { + public onDprChange = new EventEmitter().event; public onWindowChange = new EventEmitter().event; public serviceBrand: undefined; public isFocused: boolean = true; diff --git a/src/browser/services/CoreBrowserService.ts b/src/browser/services/CoreBrowserService.ts index 2c598606..8a189315 100644 --- a/src/browser/services/CoreBrowserService.ts +++ b/src/browser/services/CoreBrowserService.ts @@ -5,25 +5,31 @@ import { Disposable } from 'common/Lifecycle'; import { ICoreBrowserService } from './Services'; -import { EventEmitter } from 'common/EventEmitter'; +import { EventEmitter, forwardEvent } from 'common/EventEmitter'; +import { ScreenDprMonitor } from 'browser/ScreenDprMonitor'; export class CoreBrowserService extends Disposable implements ICoreBrowserService { public serviceBrand: undefined; private _isFocused = false; private _cachedIsFocused: boolean | undefined = undefined; + private _screenDprMonitor = new ScreenDprMonitor(this._window); + private readonly _onDprChange = this.register(new EventEmitter()); + public readonly onDprChange = this._onDprChange.event; private readonly _onWindowChange = this.register(new EventEmitter()); public readonly onWindowChange = this._onWindowChange.event; constructor( private _textarea: HTMLTextAreaElement, - // TODO: Add getter and setter and event private _window: Window & typeof globalThis, public readonly mainDocument: Document ) { super(); + this.register(this.onWindowChange(w => this._screenDprMonitor.setWindow(w))); + this.register(forwardEvent(this._screenDprMonitor.onDprChange, this._onDprChange)); + this._textarea.addEventListener('focus', () => this._isFocused = true); this._textarea.addEventListener('blur', () => this._isFocused = false); } diff --git a/src/browser/services/RenderService.ts b/src/browser/services/RenderService.ts index 60c27798..179ddeb2 100644 --- a/src/browser/services/RenderService.ts +++ b/src/browser/services/RenderService.ts @@ -5,7 +5,6 @@ import { addDisposableDomListener } from 'browser/Lifecycle'; import { RenderDebouncer } from 'browser/RenderDebouncer'; -import { ScreenDprMonitor } from 'browser/ScreenDprMonitor'; import { IRenderDebouncerWithCallback } from 'browser/Types'; import { IRenderDimensions, IRenderer } from 'browser/renderer/shared/Types'; import { ICharSizeService, ICoreBrowserService, IRenderService, IThemeService } from 'browser/services/Services'; @@ -25,7 +24,6 @@ export class RenderService extends Disposable implements IRenderService { private _renderer: MutableDisposable = this.register(new MutableDisposable()); private _renderDebouncer: IRenderDebouncerWithCallback; - private _screenDprMonitor: ScreenDprMonitor; private _pausedResizeTask = new DebouncedIdleTask(); private _isPaused: boolean = false; @@ -67,8 +65,7 @@ export class RenderService extends Disposable implements IRenderService { this._renderDebouncer = new RenderDebouncer(coreBrowserService.window, (start, end) => this._renderRows(start, end)); this.register(this._renderDebouncer); - this._screenDprMonitor = this.register(instantiationService.createInstance(ScreenDprMonitor)); - this.register(this._screenDprMonitor.onDprChange(() => this.handleDevicePixelRatioChange())); + this.register(coreBrowserService.onDprChange(() => this.handleDevicePixelRatioChange())); this.register(bufferService.onResize(() => this._fullRefresh())); this.register(bufferService.buffers.onBufferActivate(() => this._renderer.value?.clear())); diff --git a/src/browser/services/Services.ts b/src/browser/services/Services.ts index a1faab3d..5c14fa8a 100644 --- a/src/browser/services/Services.ts +++ b/src/browser/services/Services.ts @@ -29,7 +29,9 @@ export interface ICoreBrowserService { readonly isFocused: boolean; - onWindowChange: IEvent; + readonly onDprChange: IEvent; + readonly onWindowChange: IEvent; + /** * Gets or sets the parent window that the terminal is rendered into. DOM and rendering APIs (e.g. * requestAnimationFrame) should be invoked in the context of this window. This should be set when From 92535e539f371d46ea28de7666de1ce68f1f770f Mon Sep 17 00:00:00 2001 From: Daniel Imms <2193314+Tyriar@users.noreply.github.com> Date: Tue, 17 Oct 2023 08:56:02 -0700 Subject: [PATCH 06/12] Inline ScreenDprMonitor into CoreBrowserService file --- src/browser/ScreenDprMonitor.ts | 74 ---------------------- src/browser/services/CoreBrowserService.ts | 71 ++++++++++++++++++++- 2 files changed, 69 insertions(+), 76 deletions(-) delete mode 100644 src/browser/ScreenDprMonitor.ts diff --git a/src/browser/ScreenDprMonitor.ts b/src/browser/ScreenDprMonitor.ts deleted file mode 100644 index 1152a55a..00000000 --- a/src/browser/ScreenDprMonitor.ts +++ /dev/null @@ -1,74 +0,0 @@ -/** - * Copyright (c) 2017 The xterm.js authors. All rights reserved. - * @license MIT - */ - -import { EventEmitter } from 'common/EventEmitter'; -import { Disposable, toDisposable } from 'common/Lifecycle'; - -/** - * The screen device pixel ratio monitor allows listening for when the - * window.devicePixelRatio value changes. This is done not with polling but with - * the use of window.matchMedia to watch media queries. When the event fires, - * the listener will be reattached using a different media query to ensure that - * any further changes will register. - * - * The listener should fire on both window zoom changes and switching to a - * monitor with a different DPI. - */ -export class ScreenDprMonitor extends Disposable { - private _currentDevicePixelRatio: number; - private _outerListener: ((this: MediaQueryList, ev: MediaQueryListEvent) => any) | undefined; - private _resolutionMediaMatchList: MediaQueryList | undefined; - - private readonly _onDprChange = this.register(new EventEmitter()); - public readonly onDprChange = this._onDprChange.event; - - constructor(private _parentWindow: Window) { - super(); - - // Initialize listener and dpr value - this._outerListener = () => { - if (this._parentWindow.devicePixelRatio !== this._currentDevicePixelRatio) { - this._onDprChange.fire(this._parentWindow.devicePixelRatio); - } - this._updateDpr(); - }; - this._currentDevicePixelRatio = this._parentWindow.devicePixelRatio; - this._updateDpr(); - - // Setup additional disposables - this.register(toDisposable(() => this.clearListener())); - } - - public setWindow(parentWindow: Window): void { - this._parentWindow = parentWindow; - if (this._parentWindow.devicePixelRatio !== this._currentDevicePixelRatio) { - this._onDprChange.fire(this._parentWindow.devicePixelRatio); - } - this._updateDpr(); - } - - private _updateDpr(): void { - if (!this._outerListener) { - return; - } - - // Clear listeners for old DPR - this._resolutionMediaMatchList?.removeListener(this._outerListener); - - // Add listeners for new DPR - this._currentDevicePixelRatio = this._parentWindow.devicePixelRatio; - this._resolutionMediaMatchList = this._parentWindow.matchMedia(`screen and (resolution: ${this._parentWindow.devicePixelRatio}dppx)`); - this._resolutionMediaMatchList.addListener(this._outerListener); - } - - public clearListener(): void { - if (!this._resolutionMediaMatchList || !this._outerListener) { - return; - } - this._resolutionMediaMatchList.removeListener(this._outerListener); - this._resolutionMediaMatchList = undefined; - this._outerListener = undefined; - } -} diff --git a/src/browser/services/CoreBrowserService.ts b/src/browser/services/CoreBrowserService.ts index 8a189315..dfd3474d 100644 --- a/src/browser/services/CoreBrowserService.ts +++ b/src/browser/services/CoreBrowserService.ts @@ -3,10 +3,9 @@ * @license MIT */ -import { Disposable } from 'common/Lifecycle'; +import { Disposable, toDisposable } from 'common/Lifecycle'; import { ICoreBrowserService } from './Services'; import { EventEmitter, forwardEvent } from 'common/EventEmitter'; -import { ScreenDprMonitor } from 'browser/ScreenDprMonitor'; export class CoreBrowserService extends Disposable implements ICoreBrowserService { public serviceBrand: undefined; @@ -57,3 +56,71 @@ export class CoreBrowserService extends Disposable implements ICoreBrowserServic return this._cachedIsFocused; } } + + +/** + * The screen device pixel ratio monitor allows listening for when the + * window.devicePixelRatio value changes. This is done not with polling but with + * the use of window.matchMedia to watch media queries. When the event fires, + * the listener will be reattached using a different media query to ensure that + * any further changes will register. + * + * The listener should fire on both window zoom changes and switching to a + * monitor with a different DPI. + */ +class ScreenDprMonitor extends Disposable { + private _currentDevicePixelRatio: number; + private _outerListener: ((this: MediaQueryList, ev: MediaQueryListEvent) => any) | undefined; + private _resolutionMediaMatchList: MediaQueryList | undefined; + + private readonly _onDprChange = this.register(new EventEmitter()); + public readonly onDprChange = this._onDprChange.event; + + constructor(private _parentWindow: Window) { + super(); + + // Initialize listener and dpr value + this._outerListener = () => { + if (this._parentWindow.devicePixelRatio !== this._currentDevicePixelRatio) { + this._onDprChange.fire(this._parentWindow.devicePixelRatio); + } + this._updateDpr(); + }; + this._currentDevicePixelRatio = this._parentWindow.devicePixelRatio; + this._updateDpr(); + + // Setup additional disposables + this.register(toDisposable(() => this.clearListener())); + } + + public setWindow(parentWindow: Window): void { + this._parentWindow = parentWindow; + if (this._parentWindow.devicePixelRatio !== this._currentDevicePixelRatio) { + this._onDprChange.fire(this._parentWindow.devicePixelRatio); + } + this._updateDpr(); + } + + private _updateDpr(): void { + if (!this._outerListener) { + return; + } + + // Clear listeners for old DPR + this._resolutionMediaMatchList?.removeListener(this._outerListener); + + // Add listeners for new DPR + this._currentDevicePixelRatio = this._parentWindow.devicePixelRatio; + this._resolutionMediaMatchList = this._parentWindow.matchMedia(`screen and (resolution: ${this._parentWindow.devicePixelRatio}dppx)`); + this._resolutionMediaMatchList.addListener(this._outerListener); + } + + public clearListener(): void { + if (!this._resolutionMediaMatchList || !this._outerListener) { + return; + } + this._resolutionMediaMatchList.removeListener(this._outerListener); + this._resolutionMediaMatchList = undefined; + this._outerListener = undefined; + } +} From 6e12d94f11bdb2b12d5b6bedd419f893c9004f64 Mon Sep 17 00:00:00 2001 From: Daniel Imms <2193314+Tyriar@users.noreply.github.com> Date: Tue, 17 Oct 2023 09:10:04 -0700 Subject: [PATCH 07/12] Move window resize listeners over to onWindowChange --- src/browser/AccessibilityManager.ts | 9 ++++++--- src/browser/decorations/BufferDecorationRenderer.ts | 9 ++++++--- src/browser/decorations/OverviewRulerRenderer.ts | 9 ++++++--- src/browser/services/RenderService.ts | 8 ++++++-- src/common/EventEmitter.ts | 5 +++++ typings/xterm.d.ts | 3 ++- 6 files changed, 31 insertions(+), 12 deletions(-) diff --git a/src/browser/AccessibilityManager.ts b/src/browser/AccessibilityManager.ts index 703459f4..de00a1d3 100644 --- a/src/browser/AccessibilityManager.ts +++ b/src/browser/AccessibilityManager.ts @@ -6,11 +6,12 @@ import * as Strings from 'browser/LocalizableStrings'; import { ITerminal, IRenderDebouncer } from 'browser/Types'; import { TimeBasedDebouncer } from 'browser/TimeBasedDebouncer'; -import { Disposable, toDisposable } from 'common/Lifecycle'; +import { Disposable, MutableDisposable, toDisposable } from 'common/Lifecycle'; import { ICoreBrowserService, IRenderService } from 'browser/services/Services'; import { addDisposableDomListener } from 'browser/Lifecycle'; import { IBuffer } from 'common/buffer/Types'; import { IInstantiationService } from 'common/services/Services'; +import { runAndSubscribe } from 'common/EventEmitter'; const MAX_ROWS_TO_READ = 20; @@ -97,8 +98,10 @@ export class AccessibilityManager extends Disposable { this.register(this._coreBrowserService.onDprChange(() => this._refreshRowsDimensions())); // This shouldn't be needed on modern browsers but is present in case the // media query that drives the ScreenDprMonitor isn't supported - // TODO: Listen to window change - this.register(addDisposableDomListener(window, 'resize', () => this._refreshRowsDimensions())); + const windowResizeListener = this.register(new MutableDisposable()); + this.register(runAndSubscribe(this._coreBrowserService.onWindowChange, () => { + windowResizeListener.value = addDisposableDomListener(this._coreBrowserService.window, 'resize', () => this._refreshRowsDimensions()); + })); this._refreshRows(); this.register(toDisposable(() => { diff --git a/src/browser/decorations/BufferDecorationRenderer.ts b/src/browser/decorations/BufferDecorationRenderer.ts index 3a0d7862..3fe56512 100644 --- a/src/browser/decorations/BufferDecorationRenderer.ts +++ b/src/browser/decorations/BufferDecorationRenderer.ts @@ -5,7 +5,8 @@ import { addDisposableDomListener } from 'browser/Lifecycle'; import { ICoreBrowserService, IRenderService } from 'browser/services/Services'; -import { Disposable, toDisposable } from 'common/Lifecycle'; +import { runAndSubscribe } from 'common/EventEmitter'; +import { Disposable, MutableDisposable, toDisposable } from 'common/Lifecycle'; import { IBufferService, IDecorationService, IInternalDecoration } from 'common/services/Services'; export class BufferDecorationRenderer extends Disposable { @@ -34,8 +35,10 @@ export class BufferDecorationRenderer extends Disposable { this._dimensionsChanged = true; this._queueRefresh(); })); - // TODO: Listen to window change - this.register(addDisposableDomListener(window, 'resize', () => this._queueRefresh())); + const windowResizeListener = this.register(new MutableDisposable()); + this.register(runAndSubscribe(this._coreBrowserService.onWindowChange, () => { + windowResizeListener.value = addDisposableDomListener(this._coreBrowserService.window, 'resize', () => this._queueRefresh()); + })); this.register(this._bufferService.buffers.onBufferActivate(() => { this._altBufferIsActive = this._bufferService.buffer === this._bufferService.buffers.alt; })); diff --git a/src/browser/decorations/OverviewRulerRenderer.ts b/src/browser/decorations/OverviewRulerRenderer.ts index 57b6dfc6..157775f9 100644 --- a/src/browser/decorations/OverviewRulerRenderer.ts +++ b/src/browser/decorations/OverviewRulerRenderer.ts @@ -6,7 +6,8 @@ import { ColorZoneStore, IColorZone, IColorZoneStore } from 'browser/decorations/ColorZoneStore'; import { addDisposableDomListener } from 'browser/Lifecycle'; import { ICoreBrowserService, IRenderService } from 'browser/services/Services'; -import { Disposable, toDisposable } from 'common/Lifecycle'; +import { runAndSubscribe } from 'common/EventEmitter'; +import { Disposable, MutableDisposable, toDisposable } from 'common/Lifecycle'; import { IBufferService, IDecorationService, IOptionsService } from 'common/services/Services'; // Helper objects to avoid excessive calculation and garbage collection during rendering. These are @@ -112,8 +113,10 @@ export class OverviewRulerRenderer extends Disposable { // overview ruler width changed this.register(this._optionsService.onSpecificOptionChange('overviewRulerWidth', () => this._queueRefresh(true))); // device pixel ratio changed - // TODO: Observe DPR instead / listen to window change - this.register(addDisposableDomListener(this._coreBrowserService.window, 'resize', () => this._queueRefresh(true))); + const windowResizeListener = this.register(new MutableDisposable()); + this.register(runAndSubscribe(this._coreBrowserService.onWindowChange, () => { + windowResizeListener.value = addDisposableDomListener(this._coreBrowserService.window, 'resize', () => this._queueRefresh(true)); + })); // set the canvas dimensions this._queueRefresh(true); } diff --git a/src/browser/services/RenderService.ts b/src/browser/services/RenderService.ts index 179ddeb2..7b9e6d79 100644 --- a/src/browser/services/RenderService.ts +++ b/src/browser/services/RenderService.ts @@ -8,7 +8,7 @@ import { RenderDebouncer } from 'browser/RenderDebouncer'; import { IRenderDebouncerWithCallback } from 'browser/Types'; import { IRenderDimensions, IRenderer } from 'browser/renderer/shared/Types'; import { ICharSizeService, ICoreBrowserService, IRenderService, IThemeService } from 'browser/services/Services'; -import { EventEmitter } from 'common/EventEmitter'; +import { EventEmitter, runAndSubscribe } from 'common/EventEmitter'; import { Disposable, MutableDisposable } from 'common/Lifecycle'; import { DebouncedIdleTask } from 'common/TaskQueue'; import { IBufferService, IDecorationService, IInstantiationService, IOptionsService } from 'common/services/Services'; @@ -103,7 +103,11 @@ export class RenderService extends Disposable implements IRenderService { // dprchange should handle this case, we need this as well for browsers that don't support the // matchMedia query. - // TODO: Listen to window change + // TODO: Merge this into onDprChange? + const windowResizeListener = this.register(new MutableDisposable()); + this.register(runAndSubscribe(coreBrowserService.onWindowChange, () => { + windowResizeListener.value = addDisposableDomListener(coreBrowserService.window, 'resize', () => this.handleDevicePixelRatioChange()); + })); this.register(addDisposableDomListener(coreBrowserService.window, 'resize', () => this.handleDevicePixelRatioChange())); this.register(themeService.onChangeColors(() => this._fullRefresh())); diff --git a/src/common/EventEmitter.ts b/src/common/EventEmitter.ts index fd959042..589748a3 100644 --- a/src/common/EventEmitter.ts +++ b/src/common/EventEmitter.ts @@ -71,3 +71,8 @@ export class EventEmitter implements IEventEmitter { export function forwardEvent(from: IEvent, to: IEventEmitter): IDisposable { return from(e => to.fire(e)); } + +export function runAndSubscribe(event: IEvent, handler: (e: T | undefined) => any): IDisposable { + handler(undefined); + return event(e => handler(e)); +} diff --git a/typings/xterm.d.ts b/typings/xterm.d.ts index b8028a18..b8c11455 100644 --- a/typings/xterm.d.ts +++ b/typings/xterm.d.ts @@ -973,7 +973,8 @@ declare module 'xterm' { resize(columns: number, rows: number): void; /** - * Opens the terminal within an element. + * Opens the terminal within an element. This should also be called if the + * xterm.js element ever changes browser window. * @param parent The element to create the terminal within. This element * must be visible (have dimensions) when `open` is called as several DOM- * based measurements need to be performed when this function is called. From 81e0718a74635840c7fdbcf7bbb96ec3c7708002 Mon Sep 17 00:00:00 2001 From: Daniel Imms <2193314+Tyriar@users.noreply.github.com> Date: Tue, 17 Oct 2023 09:18:46 -0700 Subject: [PATCH 08/12] Move window resize edge case into dpr monitor --- src/browser/AccessibilityManager.ts | 7 ------ .../decorations/BufferDecorationRenderer.ts | 9 ++----- .../decorations/OverviewRulerRenderer.ts | 9 ++----- src/browser/services/CoreBrowserService.ts | 25 +++++++++++++------ src/browser/services/RenderService.ts | 12 +-------- 5 files changed, 23 insertions(+), 39 deletions(-) diff --git a/src/browser/AccessibilityManager.ts b/src/browser/AccessibilityManager.ts index de00a1d3..54f50ef2 100644 --- a/src/browser/AccessibilityManager.ts +++ b/src/browser/AccessibilityManager.ts @@ -94,14 +94,7 @@ export class AccessibilityManager extends Disposable { this.register(this._terminal.onKey(e => this._handleKey(e.key))); this.register(this._terminal.onBlur(() => this._clearLiveRegion())); this.register(this._renderService.onDimensionsChange(() => this._refreshRowsDimensions())); - this.register(this._coreBrowserService.onDprChange(() => this._refreshRowsDimensions())); - // This shouldn't be needed on modern browsers but is present in case the - // media query that drives the ScreenDprMonitor isn't supported - const windowResizeListener = this.register(new MutableDisposable()); - this.register(runAndSubscribe(this._coreBrowserService.onWindowChange, () => { - windowResizeListener.value = addDisposableDomListener(this._coreBrowserService.window, 'resize', () => this._refreshRowsDimensions()); - })); this._refreshRows(); this.register(toDisposable(() => { diff --git a/src/browser/decorations/BufferDecorationRenderer.ts b/src/browser/decorations/BufferDecorationRenderer.ts index 3fe56512..6ba9ae1e 100644 --- a/src/browser/decorations/BufferDecorationRenderer.ts +++ b/src/browser/decorations/BufferDecorationRenderer.ts @@ -3,10 +3,8 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { addDisposableDomListener } from 'browser/Lifecycle'; import { ICoreBrowserService, IRenderService } from 'browser/services/Services'; -import { runAndSubscribe } from 'common/EventEmitter'; -import { Disposable, MutableDisposable, toDisposable } from 'common/Lifecycle'; +import { Disposable, toDisposable } from 'common/Lifecycle'; import { IBufferService, IDecorationService, IInternalDecoration } from 'common/services/Services'; export class BufferDecorationRenderer extends Disposable { @@ -35,10 +33,7 @@ export class BufferDecorationRenderer extends Disposable { this._dimensionsChanged = true; this._queueRefresh(); })); - const windowResizeListener = this.register(new MutableDisposable()); - this.register(runAndSubscribe(this._coreBrowserService.onWindowChange, () => { - windowResizeListener.value = addDisposableDomListener(this._coreBrowserService.window, 'resize', () => this._queueRefresh()); - })); + this.register(this._coreBrowserService.onDprChange(() => this._queueRefresh())); this.register(this._bufferService.buffers.onBufferActivate(() => { this._altBufferIsActive = this._bufferService.buffer === this._bufferService.buffers.alt; })); diff --git a/src/browser/decorations/OverviewRulerRenderer.ts b/src/browser/decorations/OverviewRulerRenderer.ts index 157775f9..103d5d9a 100644 --- a/src/browser/decorations/OverviewRulerRenderer.ts +++ b/src/browser/decorations/OverviewRulerRenderer.ts @@ -4,10 +4,8 @@ *--------------------------------------------------------------------------------------------*/ import { ColorZoneStore, IColorZone, IColorZoneStore } from 'browser/decorations/ColorZoneStore'; -import { addDisposableDomListener } from 'browser/Lifecycle'; import { ICoreBrowserService, IRenderService } from 'browser/services/Services'; -import { runAndSubscribe } from 'common/EventEmitter'; -import { Disposable, MutableDisposable, toDisposable } from 'common/Lifecycle'; +import { Disposable, toDisposable } from 'common/Lifecycle'; import { IBufferService, IDecorationService, IOptionsService } from 'common/services/Services'; // Helper objects to avoid excessive calculation and garbage collection during rendering. These are @@ -113,10 +111,7 @@ export class OverviewRulerRenderer extends Disposable { // overview ruler width changed this.register(this._optionsService.onSpecificOptionChange('overviewRulerWidth', () => this._queueRefresh(true))); // device pixel ratio changed - const windowResizeListener = this.register(new MutableDisposable()); - this.register(runAndSubscribe(this._coreBrowserService.onWindowChange, () => { - windowResizeListener.value = addDisposableDomListener(this._coreBrowserService.window, 'resize', () => this._queueRefresh(true)); - })); + this.register(this._coreBrowserService.onDprChange(() => this._queueRefresh(true))); // set the canvas dimensions this._queueRefresh(true); } diff --git a/src/browser/services/CoreBrowserService.ts b/src/browser/services/CoreBrowserService.ts index dfd3474d..575b62b6 100644 --- a/src/browser/services/CoreBrowserService.ts +++ b/src/browser/services/CoreBrowserService.ts @@ -3,9 +3,10 @@ * @license MIT */ -import { Disposable, toDisposable } from 'common/Lifecycle'; +import { Disposable, MutableDisposable, toDisposable } from 'common/Lifecycle'; import { ICoreBrowserService } from './Services'; import { EventEmitter, forwardEvent } from 'common/EventEmitter'; +import { addDisposableDomListener } from 'browser/Lifecycle'; export class CoreBrowserService extends Disposable implements ICoreBrowserService { public serviceBrand: undefined; @@ -26,6 +27,7 @@ export class CoreBrowserService extends Disposable implements ICoreBrowserServic ) { super(); + // Monitor device pixel ratio this.register(this.onWindowChange(w => this._screenDprMonitor.setWindow(w))); this.register(forwardEvent(this._screenDprMonitor.onDprChange, this._onDprChange)); @@ -72,6 +74,7 @@ class ScreenDprMonitor extends Disposable { private _currentDevicePixelRatio: number; private _outerListener: ((this: MediaQueryList, ev: MediaQueryListEvent) => any) | undefined; private _resolutionMediaMatchList: MediaQueryList | undefined; + private _windowResizeListener = this.register(new MutableDisposable()); private readonly _onDprChange = this.register(new EventEmitter()); public readonly onDprChange = this._onDprChange.event; @@ -80,21 +83,29 @@ class ScreenDprMonitor extends Disposable { super(); // Initialize listener and dpr value - this._outerListener = () => { - if (this._parentWindow.devicePixelRatio !== this._currentDevicePixelRatio) { - this._onDprChange.fire(this._parentWindow.devicePixelRatio); - } - this._updateDpr(); - }; + this._outerListener = () => this._setDprAndFireIfDiffers(); this._currentDevicePixelRatio = this._parentWindow.devicePixelRatio; this._updateDpr(); + // Monitor active window resize + this._setWindowResizeListener(); + // Setup additional disposables this.register(toDisposable(() => this.clearListener())); } + public setWindow(parentWindow: Window): void { this._parentWindow = parentWindow; + this._setWindowResizeListener(); + this._setDprAndFireIfDiffers(); + } + + private _setWindowResizeListener(): void { + this._windowResizeListener.value = addDisposableDomListener(this._parentWindow, 'resize', () => this._setDprAndFireIfDiffers()); + } + + private _setDprAndFireIfDiffers(): void { if (this._parentWindow.devicePixelRatio !== this._currentDevicePixelRatio) { this._onDprChange.fire(this._parentWindow.devicePixelRatio); } diff --git a/src/browser/services/RenderService.ts b/src/browser/services/RenderService.ts index 7b9e6d79..9fa8d234 100644 --- a/src/browser/services/RenderService.ts +++ b/src/browser/services/RenderService.ts @@ -3,12 +3,11 @@ * @license MIT */ -import { addDisposableDomListener } from 'browser/Lifecycle'; import { RenderDebouncer } from 'browser/RenderDebouncer'; import { IRenderDebouncerWithCallback } from 'browser/Types'; import { IRenderDimensions, IRenderer } from 'browser/renderer/shared/Types'; import { ICharSizeService, ICoreBrowserService, IRenderService, IThemeService } from 'browser/services/Services'; -import { EventEmitter, runAndSubscribe } from 'common/EventEmitter'; +import { EventEmitter } from 'common/EventEmitter'; import { Disposable, MutableDisposable } from 'common/Lifecycle'; import { DebouncedIdleTask } from 'common/TaskQueue'; import { IBufferService, IDecorationService, IInstantiationService, IOptionsService } from 'common/services/Services'; @@ -101,15 +100,6 @@ export class RenderService extends Disposable implements IRenderService { 'cursorStyle' ], () => this.refreshRows(bufferService.buffer.y, bufferService.buffer.y, true))); - // dprchange should handle this case, we need this as well for browsers that don't support the - // matchMedia query. - // TODO: Merge this into onDprChange? - const windowResizeListener = this.register(new MutableDisposable()); - this.register(runAndSubscribe(coreBrowserService.onWindowChange, () => { - windowResizeListener.value = addDisposableDomListener(coreBrowserService.window, 'resize', () => this.handleDevicePixelRatioChange()); - })); - this.register(addDisposableDomListener(coreBrowserService.window, 'resize', () => this.handleDevicePixelRatioChange())); - this.register(themeService.onChangeColors(() => this._fullRefresh())); // Detect whether IntersectionObserver is detected and enable renderer pause From 20a26a65897c51dcadc7be244bb8b27b66b03ac4 Mon Sep 17 00:00:00 2001 From: Daniel Imms <2193314+Tyriar@users.noreply.github.com> Date: Tue, 17 Oct 2023 10:54:29 -0700 Subject: [PATCH 09/12] Remove unused imports --- src/browser/AccessibilityManager.ts | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/src/browser/AccessibilityManager.ts b/src/browser/AccessibilityManager.ts index 54f50ef2..5f529589 100644 --- a/src/browser/AccessibilityManager.ts +++ b/src/browser/AccessibilityManager.ts @@ -6,12 +6,10 @@ import * as Strings from 'browser/LocalizableStrings'; import { ITerminal, IRenderDebouncer } from 'browser/Types'; import { TimeBasedDebouncer } from 'browser/TimeBasedDebouncer'; -import { Disposable, MutableDisposable, toDisposable } from 'common/Lifecycle'; +import { Disposable, toDisposable } from 'common/Lifecycle'; import { ICoreBrowserService, IRenderService } from 'browser/services/Services'; -import { addDisposableDomListener } from 'browser/Lifecycle'; import { IBuffer } from 'common/buffer/Types'; import { IInstantiationService } from 'common/services/Services'; -import { runAndSubscribe } from 'common/EventEmitter'; const MAX_ROWS_TO_READ = 20; From 4681b319ca4712ae5d05433f6834a6f8a4a258b5 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 19 Oct 2023 11:47:16 +0000 Subject: [PATCH 10/12] Bump @babel/traverse from 7.22.8 to 7.23.2 Bumps [@babel/traverse](https://github.com/babel/babel/tree/HEAD/packages/babel-traverse) from 7.22.8 to 7.23.2. - [Release notes](https://github.com/babel/babel/releases) - [Changelog](https://github.com/babel/babel/blob/main/CHANGELOG.md) - [Commits](https://github.com/babel/babel/commits/v7.23.2/packages/babel-traverse) --- updated-dependencies: - dependency-name: "@babel/traverse" dependency-type: indirect ... Signed-off-by: dependabot[bot] --- yarn.lock | 94 +++++++++++++++++++++++++++++++++++++++++++++---------- 1 file changed, 77 insertions(+), 17 deletions(-) diff --git a/yarn.lock b/yarn.lock index ffebb5c5..a0cb39da 100644 --- a/yarn.lock +++ b/yarn.lock @@ -15,6 +15,14 @@ "@jridgewell/gen-mapping" "^0.3.0" "@jridgewell/trace-mapping" "^0.3.9" +"@babel/code-frame@^7.22.13": + version "7.22.13" + resolved "https://registry.yarnpkg.com/@babel/code-frame/-/code-frame-7.22.13.tgz#e3c1c099402598483b7a8c46a721d1038803755e" + integrity sha512-XktuhWlJ5g+3TJXc5upd9Ks1HutSArik6jf2eAjYFyIOf4ej3RN+184cZbzDvbPnuTJIUhPKKJE3cIsYTiAT3w== + dependencies: + "@babel/highlight" "^7.22.13" + chalk "^2.4.2" + "@babel/code-frame@^7.22.5": version "7.22.5" resolved "https://registry.yarnpkg.com/@babel/code-frame/-/code-frame-7.22.5.tgz#234d98e1551960604f1246e6475891a570ad5658" @@ -48,7 +56,7 @@ json5 "^2.2.2" semver "^6.3.1" -"@babel/generator@^7.22.7", "@babel/generator@^7.22.9": +"@babel/generator@^7.22.9": version "7.22.9" resolved "https://registry.yarnpkg.com/@babel/generator/-/generator-7.22.9.tgz#572ecfa7a31002fa1de2a9d91621fd895da8493d" integrity sha512-KtLMbmicyuK2Ak/FTCJVbDnkN1SlT8/kceFTiuDiiRUUSMnHMidxSCdG4ndkTOHHpoomWe/4xkvHkEOncwjYIw== @@ -58,6 +66,16 @@ "@jridgewell/trace-mapping" "^0.3.17" jsesc "^2.5.1" +"@babel/generator@^7.23.0": + version "7.23.0" + resolved "https://registry.yarnpkg.com/@babel/generator/-/generator-7.23.0.tgz#df5c386e2218be505b34837acbcb874d7a983420" + integrity sha512-lN85QRR+5IbYrMWM6Y4pE/noaQtg4pNiqeNGX60eqOfo6gtEj6uw/JagelB8vVztSd7R6M5n1+PQkDbHbBRU4g== + dependencies: + "@babel/types" "^7.23.0" + "@jridgewell/gen-mapping" "^0.3.2" + "@jridgewell/trace-mapping" "^0.3.17" + jsesc "^2.5.1" + "@babel/helper-compilation-targets@^7.22.9": version "7.22.9" resolved "https://registry.yarnpkg.com/@babel/helper-compilation-targets/-/helper-compilation-targets-7.22.9.tgz#f9d0a7aaaa7cd32a3f31c9316a69f5a9bcacb892" @@ -69,18 +87,23 @@ lru-cache "^5.1.1" semver "^6.3.1" +"@babel/helper-environment-visitor@^7.22.20": + version "7.22.20" + resolved "https://registry.yarnpkg.com/@babel/helper-environment-visitor/-/helper-environment-visitor-7.22.20.tgz#96159db61d34a29dba454c959f5ae4a649ba9167" + integrity sha512-zfedSIzFhat/gFhWfHtgWvlec0nqB9YEIVrpuwjruLlXfUSnA8cJB0miHKwqDnQ7d32aKo2xt88/xZptwxbfhA== + "@babel/helper-environment-visitor@^7.22.5": version "7.22.5" resolved "https://registry.yarnpkg.com/@babel/helper-environment-visitor/-/helper-environment-visitor-7.22.5.tgz#f06dd41b7c1f44e1f8da6c4055b41ab3a09a7e98" integrity sha512-XGmhECfVA/5sAt+H+xpSg0mfrHq6FzNr9Oxh7PSEBBRUb/mL7Kz3NICXb194rCqAEdxkhPT1a88teizAFyvk8Q== -"@babel/helper-function-name@^7.22.5": - version "7.22.5" - resolved "https://registry.yarnpkg.com/@babel/helper-function-name/-/helper-function-name-7.22.5.tgz#ede300828905bb15e582c037162f99d5183af1be" - integrity sha512-wtHSq6jMRE3uF2otvfuD3DIvVhOsSNshQl0Qrd7qC9oQJzHvOL4qQXlQn2916+CXGywIjpGuIkoyZRRxHPiNQQ== +"@babel/helper-function-name@^7.23.0": + version "7.23.0" + resolved "https://registry.yarnpkg.com/@babel/helper-function-name/-/helper-function-name-7.23.0.tgz#1f9a3cdbd5b2698a670c30d2735f9af95ed52759" + integrity sha512-OErEqsrxjZTJciZ4Oo+eoZqeW9UIiOcuYKRJA4ZAgV9myA+pOXhhmpfNCKjEH/auVfEYVFJ6y1Tc4r0eIApqiw== dependencies: - "@babel/template" "^7.22.5" - "@babel/types" "^7.22.5" + "@babel/template" "^7.22.15" + "@babel/types" "^7.23.0" "@babel/helper-hoist-variables@^7.22.5": version "7.22.5" @@ -126,6 +149,11 @@ resolved "https://registry.yarnpkg.com/@babel/helper-string-parser/-/helper-string-parser-7.22.5.tgz#533f36457a25814cf1df6488523ad547d784a99f" integrity sha512-mM4COjgZox8U+JcXQwPijIZLElkgEpO5rsERVDJTc2qfCDfERyob6k5WegS14SX18IIjv+XD+GrqNumY5JRCDw== +"@babel/helper-validator-identifier@^7.22.20": + version "7.22.20" + resolved "https://registry.yarnpkg.com/@babel/helper-validator-identifier/-/helper-validator-identifier-7.22.20.tgz#c4ae002c61d2879e724581d96665583dbc1dc0e0" + integrity sha512-Y4OZ+ytlatR8AI+8KZfKuL5urKp7qey08ha31L8b3BwewJAoJamTzyvxPR/5D+KkdJCGPq/+8TukHBlY10FX9A== + "@babel/helper-validator-identifier@^7.22.5": version "7.22.5" resolved "https://registry.yarnpkg.com/@babel/helper-validator-identifier/-/helper-validator-identifier-7.22.5.tgz#9544ef6a33999343c8740fa51350f30eeaaaf193" @@ -145,6 +173,15 @@ "@babel/traverse" "^7.22.6" "@babel/types" "^7.22.5" +"@babel/highlight@^7.22.13": + version "7.22.20" + resolved "https://registry.yarnpkg.com/@babel/highlight/-/highlight-7.22.20.tgz#4ca92b71d80554b01427815e06f2df965b9c1f54" + integrity sha512-dkdMCN3py0+ksCgYmGG8jKeGA/8Tk+gJwSYYlFGxG5lmhfKNoAy004YpLxpS1W2J8m/EK2Ew+yOs9pVRwO89mg== + dependencies: + "@babel/helper-validator-identifier" "^7.22.20" + chalk "^2.4.2" + js-tokens "^4.0.0" + "@babel/highlight@^7.22.5": version "7.22.5" resolved "https://registry.yarnpkg.com/@babel/highlight/-/highlight-7.22.5.tgz#aa6c05c5407a67ebce408162b7ede789b4d22031" @@ -154,6 +191,11 @@ chalk "^2.0.0" js-tokens "^4.0.0" +"@babel/parser@^7.22.15", "@babel/parser@^7.23.0": + version "7.23.0" + resolved "https://registry.yarnpkg.com/@babel/parser/-/parser-7.23.0.tgz#da950e622420bf96ca0d0f2909cdddac3acd8719" + integrity sha512-vvPKKdMemU85V9WE/l5wZEmImpCtLqbnTvqDS2U1fJ96KrxoW7KrXhNsNCblQlg8Ck4b85yxdTyelsMUgFUXiw== + "@babel/parser@^7.22.5", "@babel/parser@^7.22.7": version "7.22.7" resolved "https://registry.yarnpkg.com/@babel/parser/-/parser-7.22.7.tgz#df8cf085ce92ddbdbf668a7f186ce848c9036cae" @@ -166,6 +208,15 @@ dependencies: regenerator-runtime "^0.13.11" +"@babel/template@^7.22.15": + version "7.22.15" + resolved "https://registry.yarnpkg.com/@babel/template/-/template-7.22.15.tgz#09576efc3830f0430f4548ef971dde1350ef2f38" + integrity sha512-QPErUVm4uyJa60rkI73qneDacvdvzxshT3kksGqlGWYdOTIUOwJ7RDUL8sGqslY1uXWSL6xMFKEXDS3ox2uF0w== + dependencies: + "@babel/code-frame" "^7.22.13" + "@babel/parser" "^7.22.15" + "@babel/types" "^7.22.15" + "@babel/template@^7.22.5": version "7.22.5" resolved "https://registry.yarnpkg.com/@babel/template/-/template-7.22.5.tgz#0c8c4d944509875849bd0344ff0050756eefc6ec" @@ -176,21 +227,30 @@ "@babel/types" "^7.22.5" "@babel/traverse@^7.22.6", "@babel/traverse@^7.22.8": - version "7.22.8" - resolved "https://registry.yarnpkg.com/@babel/traverse/-/traverse-7.22.8.tgz#4d4451d31bc34efeae01eac222b514a77aa4000e" - integrity sha512-y6LPR+wpM2I3qJrsheCTwhIinzkETbplIgPBbwvqPKc+uljeA5gP+3nP8irdYt1mjQaDnlIcG+dw8OjAco4GXw== + version "7.23.2" + resolved "https://registry.yarnpkg.com/@babel/traverse/-/traverse-7.23.2.tgz#329c7a06735e144a506bdb2cad0268b7f46f4ad8" + integrity sha512-azpe59SQ48qG6nu2CzcMLbxUudtN+dOM9kDbUqGq3HXUJRlo7i8fvPoxQUzYgLZ4cMVmuZgm8vvBpNeRhd6XSw== dependencies: - "@babel/code-frame" "^7.22.5" - "@babel/generator" "^7.22.7" - "@babel/helper-environment-visitor" "^7.22.5" - "@babel/helper-function-name" "^7.22.5" + "@babel/code-frame" "^7.22.13" + "@babel/generator" "^7.23.0" + "@babel/helper-environment-visitor" "^7.22.20" + "@babel/helper-function-name" "^7.23.0" "@babel/helper-hoist-variables" "^7.22.5" "@babel/helper-split-export-declaration" "^7.22.6" - "@babel/parser" "^7.22.7" - "@babel/types" "^7.22.5" + "@babel/parser" "^7.23.0" + "@babel/types" "^7.23.0" debug "^4.1.0" globals "^11.1.0" +"@babel/types@^7.22.15", "@babel/types@^7.23.0": + version "7.23.0" + resolved "https://registry.yarnpkg.com/@babel/types/-/types-7.23.0.tgz#8c1f020c9df0e737e4e247c0619f58c68458aaeb" + integrity sha512-0oIyUfKoI3mSqMvsxBdclDwxXKXAUA8v/apZbc+iSyARYou1o8ZGDxbUYyLFoW2arqS2jDGqJuZvv1d/io1axg== + dependencies: + "@babel/helper-string-parser" "^7.22.5" + "@babel/helper-validator-identifier" "^7.22.20" + to-fast-properties "^2.0.0" + "@babel/types@^7.22.5": version "7.22.5" resolved "https://registry.yarnpkg.com/@babel/types/-/types-7.22.5.tgz#cd93eeaab025880a3a47ec881f4b096a5b786fbe" @@ -1145,7 +1205,7 @@ chai@^4.3.4: pathval "^1.1.1" type-detect "^4.0.5" -chalk@^2.0.0: +chalk@^2.0.0, chalk@^2.4.2: version "2.4.2" resolved "https://registry.yarnpkg.com/chalk/-/chalk-2.4.2.tgz#cd42541677a54333cf541a49108c1432b44c9424" integrity sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ== From 6c5fb36c36d194f8ca990eb68556069f970159ec Mon Sep 17 00:00:00 2001 From: David Fiala Date: Thu, 19 Oct 2023 09:52:29 -0700 Subject: [PATCH 11/12] proper isNode detection for node v21.0.0 --- src/common/Platform.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/common/Platform.ts b/src/common/Platform.ts index 41d8552e..2f329199 100644 --- a/src/common/Platform.ts +++ b/src/common/Platform.ts @@ -13,7 +13,7 @@ interface INavigator { // we want this module to live in common. declare const navigator: INavigator; -export const isNode = (typeof navigator === 'undefined') ? true : false; +export const isNode = (typeof process !== 'undefined') ? true : false; const userAgent = (isNode) ? 'node' : navigator.userAgent; const platform = (isNode) ? 'node' : navigator.platform; From 0d0166b4bcae1db109baf3691d4472ea16171589 Mon Sep 17 00:00:00 2001 From: David Fiala Date: Thu, 19 Oct 2023 11:19:27 -0700 Subject: [PATCH 12/12] provide a declaration for 'process' needed for nodejs detect We provide no further type information as it will not be used except for a presence check for nodejs detection. --- src/common/Platform.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/src/common/Platform.ts b/src/common/Platform.ts index 2f329199..1007fc0a 100644 --- a/src/common/Platform.ts +++ b/src/common/Platform.ts @@ -12,6 +12,7 @@ interface INavigator { // We're declaring a navigator global here as we expect it in all runtimes (node and browser), but // we want this module to live in common. declare const navigator: INavigator; +declare const process: unknown; export const isNode = (typeof process !== 'undefined') ? true : false; const userAgent = (isNode) ? 'node' : navigator.userAgent;