From fae6f03f90f0e07ec8e7a90fee16ee5f4f55e015 Mon Sep 17 00:00:00 2001 From: Daniel Imms <2193314+Tyriar@users.noreply.github.com> Date: Mon, 2 Feb 2026 07:09:33 -0800 Subject: [PATCH] Consolidate dom and browser helpers, remove unused --- src/browser/Dom.ts | 47 +++++ src/browser/scrollable/abstractScrollbar.ts | 2 +- src/browser/scrollable/browser.ts | 14 -- src/browser/scrollable/dom.ts | 192 ------------------ .../scrollable/globalPointerMoveMonitor.ts | 2 +- src/browser/scrollable/mouseEvent.ts | 9 +- src/browser/scrollable/scrollableElement.ts | 19 +- src/browser/scrollable/scrollbarArrow.ts | 2 +- src/browser/scrollable/touch.ts | 12 +- src/browser/scrollable/widget.ts | 2 +- src/common/Platform.ts | 9 + 11 files changed, 72 insertions(+), 238 deletions(-) delete mode 100644 src/browser/scrollable/browser.ts delete mode 100644 src/browser/scrollable/dom.ts diff --git a/src/browser/Dom.ts b/src/browser/Dom.ts index 8a43e2b0..56e7c041 100644 --- a/src/browser/Dom.ts +++ b/src/browser/Dom.ts @@ -5,6 +5,7 @@ * Minimal DOM helpers for xterm.js browser code. */ +import { IntervalTimer } from 'common/Async'; import { IDisposable } from 'common/Lifecycle'; export function getWindow(e: Node | UIEvent | undefined | null): Window { @@ -52,6 +53,39 @@ export function addDisposableListener(node: EventTarget, type: string, handler: return new DomListener(node, type, handler, useCaptureOrOptions); } +export function addStandardDisposableListener(node: HTMLElement, type: string, handler: (event: any) => void, useCapture?: boolean): IDisposable { + return addDisposableListener(node, type, handler, useCapture); +} + +export const eventType = { + CLICK: 'click', + MOUSE_DOWN: 'mousedown', + MOUSE_OVER: 'mouseover', + MOUSE_LEAVE: 'mouseleave', + KEY_DOWN: 'keydown', + KEY_UP: 'keyup', + INPUT: 'input', + BLUR: 'blur', + FOCUS: 'focus', + CHANGE: 'change', + POINTER_DOWN: 'pointerdown', + POINTER_MOVE: 'pointermove', + POINTER_UP: 'pointerup', + MOUSE_WHEEL: 'wheel', + WHEEL: 'wheel' +} as const; + +export function getDomNodePagePosition(domNode: HTMLElement): { left: number, top: number, width: number, height: number } { + const bb = domNode.getBoundingClientRect(); + const win = getWindow(domNode); + return { + left: bb.left + win.scrollX, + top: bb.top + win.scrollY, + width: bb.width, + height: bb.height + }; +} + class AnimationFrameQueueItem implements IDisposable { private _canceled = false; @@ -129,3 +163,16 @@ export function scheduleAtNextAnimationFrame(targetWindow: Window, runner: () => return item; } + +export class WindowIntervalTimer extends IntervalTimer { + private readonly _defaultTarget?: Window; + + constructor(node?: Node) { + super(); + this._defaultTarget = node ? getWindow(node) : undefined; + } + + public cancelAndSet(runner: () => void, interval: number, targetWindow?: Window): void { + super.cancelAndSet(runner, interval, targetWindow ?? this._defaultTarget ?? window); + } +} diff --git a/src/browser/scrollable/abstractScrollbar.ts b/src/browser/scrollable/abstractScrollbar.ts index b330fe1c..111479ff 100644 --- a/src/browser/scrollable/abstractScrollbar.ts +++ b/src/browser/scrollable/abstractScrollbar.ts @@ -3,7 +3,7 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import * as dom from './dom'; +import * as dom from '../Dom'; import { createFastDomNode, FastDomNode } from './fastDomNode'; import { GlobalPointerMoveMonitor } from './globalPointerMoveMonitor'; import { StandardWheelEvent } from './mouseEvent'; diff --git a/src/browser/scrollable/browser.ts b/src/browser/scrollable/browser.ts deleted file mode 100644 index 93031758..00000000 --- a/src/browser/scrollable/browser.ts +++ /dev/null @@ -1,14 +0,0 @@ -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the MIT License. See License.txt in the project root for license information. - *--------------------------------------------------------------------------------------------*/ - -const userAgent = typeof navigator === 'object' ? navigator.userAgent : ''; - -export const isFirefox = (userAgent.indexOf('Firefox') >= 0); -export const isChrome = (userAgent.indexOf('Chrome') >= 0); -export const isSafari = (!isChrome && (userAgent.indexOf('Safari') >= 0)); - -export function getZoomFactor(_targetWindow: Window): number { - return 1; -} diff --git a/src/browser/scrollable/dom.ts b/src/browser/scrollable/dom.ts deleted file mode 100644 index 53d717b3..00000000 --- a/src/browser/scrollable/dom.ts +++ /dev/null @@ -1,192 +0,0 @@ -/** - * Copyright (c) 2026 The xterm.js authors. All rights reserved. - * @license MIT - */ - -import { IntervalTimer } from 'common/Async'; -import { Emitter, IEvent } from 'common/Event'; -import { DisposableStore, IDisposable } from 'common/Lifecycle'; - -export interface IRegisteredWindow { - readonly window: Window; - readonly disposables: DisposableStore; -} - -const onDidRegisterWindowEmitter = new Emitter(); -export const onDidRegisterWindow: IEvent = onDidRegisterWindowEmitter.event; - -export function registerWindow(window: Window): IDisposable { - const disposables = new DisposableStore(); - onDidRegisterWindowEmitter.fire({ window, disposables }); - return disposables; -} - -export function getWindow(e: Node | UIEvent | undefined | null): Window { - const candidateNode = e as Node | undefined | null; - if (candidateNode?.ownerDocument?.defaultView) { - return candidateNode.ownerDocument.defaultView; - } - - const candidateEvent = e as UIEvent | undefined | null; - if (candidateEvent?.view) { - return candidateEvent.view; - } - - return window; -} - -class DomListener implements IDisposable { - private _handler: ((e: any) => void) | null; - private _node: EventTarget | null; - private readonly _type: string; - private readonly _options: boolean | AddEventListenerOptions | undefined; - - constructor(node: EventTarget, type: string, handler: (e: any) => void, options?: boolean | AddEventListenerOptions) { - this._node = node; - this._type = type; - this._handler = handler; - this._options = options; - node.addEventListener(type, handler, options); - } - - public dispose(): void { - if (!this._node || !this._handler) { - return; - } - this._node.removeEventListener(this._type, this._handler, this._options); - this._node = null; - this._handler = null; - } -} - -export function addDisposableListener(node: EventTarget, type: K, handler: (event: GlobalEventHandlersEventMap[K]) => void, useCapture?: boolean): IDisposable; -export function addDisposableListener(node: EventTarget, type: string, handler: (event: any) => void, useCapture?: boolean): IDisposable; -export function addDisposableListener(node: EventTarget, type: string, handler: (event: any) => void, options: AddEventListenerOptions): IDisposable; -export function addDisposableListener(node: EventTarget, type: string, handler: (event: any) => void, useCaptureOrOptions?: boolean | AddEventListenerOptions): IDisposable { - return new DomListener(node, type, handler, useCaptureOrOptions); -} - -export function addStandardDisposableListener(node: HTMLElement, type: string, handler: (event: any) => void, useCapture?: boolean): IDisposable { - return addDisposableListener(node, type, handler, useCapture); -} - -export const eventType = { - CLICK: 'click', - MOUSE_DOWN: 'mousedown', - MOUSE_OVER: 'mouseover', - MOUSE_LEAVE: 'mouseleave', - KEY_DOWN: 'keydown', - KEY_UP: 'keyup', - INPUT: 'input', - BLUR: 'blur', - FOCUS: 'focus', - CHANGE: 'change', - POINTER_DOWN: 'pointerdown', - POINTER_MOVE: 'pointermove', - POINTER_UP: 'pointerup', - MOUSE_WHEEL: 'wheel', - WHEEL: 'wheel' -} as const; - - -export function getDomNodePagePosition(domNode: HTMLElement): { left: number, top: number, width: number, height: number } { - const bb = domNode.getBoundingClientRect(); - const win = getWindow(domNode); - return { - left: bb.left + win.scrollX, - top: bb.top + win.scrollY, - width: bb.width, - height: bb.height - }; -} - -class AnimationFrameQueueItem implements IDisposable { - private _canceled = false; - - constructor(private readonly _runner: () => void, public priority: number) { - } - - public dispose(): void { - this._canceled = true; - } - - public execute(): void { - if (this._canceled) { - return; - } - try { - this._runner(); - } catch (e) { - console.error(e); - } - } - - public static sort(a: AnimationFrameQueueItem, b: AnimationFrameQueueItem): number { - return b.priority - a.priority; - } -} - -interface IWindowAnimationFrameState { - next: AnimationFrameQueueItem[]; - current: AnimationFrameQueueItem[]; - animFrameRequested: boolean; - inAnimationFrameRunner: boolean; -} - -const animationFrameState = new Map(); - -function getAnimationFrameState(targetWindow: Window): IWindowAnimationFrameState { - let state = animationFrameState.get(targetWindow); - if (!state) { - state = { - next: [], - current: [], - animFrameRequested: false, - inAnimationFrameRunner: false - }; - animationFrameState.set(targetWindow, state); - } - return state; -} - -function animationFrameRunner(targetWindow: Window): void { - const state = getAnimationFrameState(targetWindow); - state.animFrameRequested = false; - - state.current = state.next; - state.next = []; - - state.inAnimationFrameRunner = true; - while (state.current.length > 0) { - state.current.sort(AnimationFrameQueueItem.sort); - const top = state.current.shift()!; - top.execute(); - } - state.inAnimationFrameRunner = false; -} - -export function scheduleAtNextAnimationFrame(targetWindow: Window, runner: () => void, priority: number = 0): IDisposable { - const state = getAnimationFrameState(targetWindow); - const item = new AnimationFrameQueueItem(runner, priority); - state.next.push(item); - - if (!state.animFrameRequested) { - state.animFrameRequested = true; - targetWindow.requestAnimationFrame(() => animationFrameRunner(targetWindow)); - } - - return item; -} - -export class WindowIntervalTimer extends IntervalTimer { - private readonly _defaultTarget?: Window; - - constructor(node?: Node) { - super(); - this._defaultTarget = node ? getWindow(node) : undefined; - } - - public cancelAndSet(runner: () => void, interval: number, targetWindow?: Window): void { - super.cancelAndSet(runner, interval, targetWindow ?? this._defaultTarget ?? window); - } -} diff --git a/src/browser/scrollable/globalPointerMoveMonitor.ts b/src/browser/scrollable/globalPointerMoveMonitor.ts index ca0f2ca5..9aa1c470 100644 --- a/src/browser/scrollable/globalPointerMoveMonitor.ts +++ b/src/browser/scrollable/globalPointerMoveMonitor.ts @@ -3,7 +3,7 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import * as dom from './dom'; +import * as dom from '../Dom'; import { DisposableStore, IDisposable, toDisposable } from 'common/Lifecycle'; type PointerMoveCallback = (event: PointerEvent) => void; diff --git a/src/browser/scrollable/mouseEvent.ts b/src/browser/scrollable/mouseEvent.ts index 44fd50e3..442c53ce 100644 --- a/src/browser/scrollable/mouseEvent.ts +++ b/src/browser/scrollable/mouseEvent.ts @@ -3,7 +3,6 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import * as browser from './browser'; import { IframeUtils } from './iframe'; import * as platform from 'common/Platform'; @@ -125,7 +124,7 @@ export class StandardWheelEvent { this.deltaX = deltaX; let shouldFactorDPR: boolean = false; - if (browser.isChrome) { + if (platform.isChrome) { const chromeVersionMatch = navigator.userAgent.match(/Chrome\/(\d+)/); const chromeMajorVersion = chromeVersionMatch ? parseInt(chromeVersionMatch[1]) : 123; shouldFactorDPR = chromeMajorVersion <= 122; @@ -148,7 +147,7 @@ export class StandardWheelEvent { const ev = e as unknown as WheelEvent; if (ev.deltaMode === ev.DOM_DELTA_LINE) { - if (browser.isFirefox && !platform.isMac) { + if (platform.isFirefox && !platform.isMac) { this.deltaY = -e.deltaY / 3; } else { this.deltaY = -e.deltaY; @@ -159,7 +158,7 @@ export class StandardWheelEvent { } if (typeof e1.wheelDeltaX !== 'undefined') { - if (browser.isSafari && platform.isWindows) { + if (platform.isSafari && platform.isWindows) { this.deltaX = -(e1.wheelDeltaX / 120); } else if (shouldFactorDPR) { this.deltaX = e1.wheelDeltaX / (120 * devicePixelRatio); @@ -172,7 +171,7 @@ export class StandardWheelEvent { const ev = e as unknown as WheelEvent; if (ev.deltaMode === ev.DOM_DELTA_LINE) { - if (browser.isFirefox && !platform.isMac) { + if (platform.isFirefox && !platform.isMac) { this.deltaX = -e.deltaX / 3; } else { this.deltaX = -e.deltaX; diff --git a/src/browser/scrollable/scrollableElement.ts b/src/browser/scrollable/scrollableElement.ts index 44ff67d6..7734d530 100644 --- a/src/browser/scrollable/scrollableElement.ts +++ b/src/browser/scrollable/scrollableElement.ts @@ -3,8 +3,7 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { getZoomFactor, isChrome } from './browser'; -import * as dom from './dom'; +import * as dom from '../Dom'; import { FastDomNode, createFastDomNode } from './fastDomNode'; import { IMouseEvent, IMouseWheelEvent, StandardWheelEvent } from './mouseEvent'; import { IScrollbarHost } from './abstractScrollbar'; @@ -23,11 +22,6 @@ const HIDE_TIMEOUT = 500; const SCROLL_WHEEL_SENSITIVITY = 50; const SCROLL_WHEEL_SMOOTH_SCROLL_ENABLED = true; -export interface IOverviewRulerLayoutInfo { - parent: HTMLElement; - insertBefore: HTMLElement; -} - class MouseWheelClassifierItem { public timestamp: number; public deltaX: number; @@ -85,9 +79,9 @@ export class MouseWheelClassifier { } public acceptStandardWheelEvent(e: StandardWheelEvent): void { - if (isChrome) { + if (platform.isChrome) { const targetWindow = dom.getWindow(e.browserEvent); - const pageZoomFactor = getZoomFactor(targetWindow); + const pageZoomFactor = platform.getZoomFactor(targetWindow); this.accept(Date.now(), e.deltaX * pageZoomFactor, e.deltaY * pageZoomFactor); } else { this.accept(Date.now(), e.deltaX, e.deltaY); @@ -260,13 +254,6 @@ export abstract class AbstractScrollableElement extends Widget { return this._domNode; } - public getOverviewRulerLayoutInfo(): IOverviewRulerLayoutInfo { - return { - parent: this._domNode, - insertBefore: this._verticalScrollbar.domNode.domNode, - }; - } - public delegateVerticalScrollbarPointerDown(browserEvent: PointerEvent): void { this._verticalScrollbar.delegatePointerDown(browserEvent); } diff --git a/src/browser/scrollable/scrollbarArrow.ts b/src/browser/scrollable/scrollbarArrow.ts index b3ac995b..5dc19da4 100644 --- a/src/browser/scrollable/scrollbarArrow.ts +++ b/src/browser/scrollable/scrollbarArrow.ts @@ -6,7 +6,7 @@ import { GlobalPointerMoveMonitor } from './globalPointerMoveMonitor'; import { Widget } from './widget'; import { TimeoutTimer } from 'common/Async'; -import * as dom from './dom'; +import * as dom from '../Dom'; /** * The arrow image size. diff --git a/src/browser/scrollable/touch.ts b/src/browser/scrollable/touch.ts index 67a4417e..bab46416 100644 --- a/src/browser/scrollable/touch.ts +++ b/src/browser/scrollable/touch.ts @@ -3,11 +3,10 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import * as DomUtils from './dom'; +import * as DomUtils from '../Dom'; import { mainWindow } from './window'; import * as arrays from './arrays'; import { memoize } from './decorators'; -import { EventUtils } from 'common/Event'; import { Disposable, IDisposable, toDisposable } from 'common/Lifecycle'; import { LinkedList } from './linkedList'; @@ -92,11 +91,10 @@ export class Gesture extends Disposable { this._handle = null; this._lastSetTapCountTime = 0; - this._register(EventUtils.runAndSubscribe(DomUtils.onDidRegisterWindow, ({ window, disposables }) => { - disposables.add(DomUtils.addDisposableListener(window.document, 'touchstart', (e: ITouchEvent) => this._handleTouchStart(e), { passive: false })); - disposables.add(DomUtils.addDisposableListener(window.document, 'touchend', (e: ITouchEvent) => this._handleTouchEnd(window, e))); - disposables.add(DomUtils.addDisposableListener(window.document, 'touchmove', (e: ITouchEvent) => this._handleTouchMove(e), { passive: false })); - }, { window: mainWindow, disposables: this._store })); + const targetWindow = mainWindow; + this._register(DomUtils.addDisposableListener(targetWindow.document, 'touchstart', (e: ITouchEvent) => this._handleTouchStart(e), { passive: false })); + this._register(DomUtils.addDisposableListener(targetWindow.document, 'touchend', (e: ITouchEvent) => this._handleTouchEnd(targetWindow, e))); + this._register(DomUtils.addDisposableListener(targetWindow.document, 'touchmove', (e: ITouchEvent) => this._handleTouchMove(e), { passive: false })); } public static addTarget(element: HTMLElement): IDisposable { diff --git a/src/browser/scrollable/widget.ts b/src/browser/scrollable/widget.ts index e61a7012..f3d33797 100644 --- a/src/browser/scrollable/widget.ts +++ b/src/browser/scrollable/widget.ts @@ -3,7 +3,7 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import * as dom from './dom'; +import * as dom from '../Dom'; import { IKeyboardEvent, StandardKeyboardEvent } from './keyboardEvent'; import { IMouseEvent, StandardMouseEvent } from './mouseEvent'; import { Gesture } from './touch'; diff --git a/src/common/Platform.ts b/src/common/Platform.ts index cc13c1af..09ba097e 100644 --- a/src/common/Platform.ts +++ b/src/common/Platform.ts @@ -22,8 +22,17 @@ const userAgent = (isNode) ? 'node' : navigator.userAgent; const platform = (isNode) ? 'node' : navigator.platform; export const isFirefox = userAgent.includes('Firefox'); +export const isChrome = userAgent.includes('Chrome'); export const isLegacyEdge = userAgent.includes('Edge'); export const isSafari = /^((?!chrome|android).)*safari/i.test(userAgent); + +interface IZoomWindow { + devicePixelRatio?: number; +} + +export function getZoomFactor(_targetWindow: IZoomWindow): number { + return 1; +} export function getSafariVersion(): number { if (!isSafari) { return 0;