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] 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; /**