mirror of
https://github.com/wavetermdev/xterm.js.git
synced 2026-08-05 13:43:48 -07:00
Merge branch 'master' into fix-search-options-change
This commit is contained in:
@@ -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();
|
||||
|
||||
@@ -31,11 +31,11 @@ perfContext('Terminal: sh -c "dd if=/dev/urandom count=40 bs=1k | hexdump | lolc
|
||||
});
|
||||
const chunks: Buffer[] = [];
|
||||
let length = 0;
|
||||
p.on('data', data => {
|
||||
p.onData(data => {
|
||||
chunks.push(data as unknown as Buffer);
|
||||
length += data.length;
|
||||
});
|
||||
await new Promise<void>(resolve => p.on('exit', () => resolve()));
|
||||
await new Promise<void>(resolve => p.onExit(() => resolve()));
|
||||
contentUtf8 = Buffer.concat(chunks, length);
|
||||
// translate to content string
|
||||
const buffer = new Uint32Array(contentUtf8.length);
|
||||
|
||||
@@ -8,9 +8,7 @@ The file `src/UnicodeProperties.ts` is generated and depends on the Unicode vers
|
||||
|
||||
### Install
|
||||
|
||||
```bash
|
||||
npm install --save xterm-addon-unicode-graphemes
|
||||
```
|
||||
This addon is not yet published to npm
|
||||
|
||||
### Usage
|
||||
|
||||
|
||||
@@ -34,11 +34,11 @@ perfContext('Terminal: ls -lR /usr/lib', () => {
|
||||
});
|
||||
const chunks: Buffer[] = [];
|
||||
let length = 0;
|
||||
p.on('data', data => {
|
||||
p.onData(data => {
|
||||
chunks.push(data as unknown as Buffer);
|
||||
length += data.length;
|
||||
});
|
||||
await new Promise<void>(resolve => p.on('exit', () => resolve()));
|
||||
await new Promise<void>(resolve => p.onExit(() => resolve()));
|
||||
contentUtf8 = Buffer.concat(chunks, length);
|
||||
// translate to content string
|
||||
const buffer = new Uint32Array(contentUtf8.length);
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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();
|
||||
|
||||
+1
-1
@@ -86,7 +86,7 @@
|
||||
"jsdom": "^18.0.1",
|
||||
"mocha": "^10.1.0",
|
||||
"mustache": "^4.2.0",
|
||||
"node-pty": "^0.10.1",
|
||||
"node-pty": "1.1.0-beta5",
|
||||
"nyc": "^15.1.0",
|
||||
"source-map-loader": "^3.0.0",
|
||||
"source-map-support": "^0.5.20",
|
||||
|
||||
@@ -7,10 +7,9 @@ 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 { IRenderService } from 'browser/services/Services';
|
||||
import { addDisposableDomListener } from 'browser/Lifecycle';
|
||||
import { ICoreBrowserService, IRenderService } from 'browser/services/Services';
|
||||
import { IBuffer } from 'common/buffer/Types';
|
||||
import { IInstantiationService } from 'common/services/Services';
|
||||
|
||||
const MAX_ROWS_TO_READ = 20;
|
||||
|
||||
@@ -29,8 +28,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;
|
||||
|
||||
@@ -49,13 +46,15 @@ export class AccessibilityManager extends Disposable {
|
||||
|
||||
constructor(
|
||||
private readonly _terminal: ITerminal,
|
||||
@IInstantiationService instantiationService: IInstantiationService,
|
||||
@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 +71,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);
|
||||
@@ -93,13 +92,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._screenDprMonitor = new ScreenDprMonitor(window);
|
||||
this.register(this._screenDprMonitor);
|
||||
this._screenDprMonitor.setListener(() => 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
|
||||
this.register(addDisposableDomListener(window, 'resize', () => this._refreshRowsDimensions()));
|
||||
this.register(this._coreBrowserService.onDprChange(() => this._refreshRowsDimensions()));
|
||||
|
||||
this._refreshRows();
|
||||
this.register(toDisposable(() => {
|
||||
@@ -261,7 +254,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);
|
||||
|
||||
@@ -1,72 +0,0 @@
|
||||
/**
|
||||
* Copyright (c) 2017 The xterm.js authors. All rights reserved.
|
||||
* @license MIT
|
||||
*/
|
||||
|
||||
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
|
||||
* 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 _listener: ScreenDprListener | undefined;
|
||||
private _resolutionMediaMatchList: MediaQueryList | undefined;
|
||||
|
||||
constructor(private _parentWindow: Window) {
|
||||
super();
|
||||
this._currentDevicePixelRatio = this._parentWindow.devicePixelRatio;
|
||||
this.register(toDisposable(() => {
|
||||
this.clearListener();
|
||||
}));
|
||||
}
|
||||
|
||||
public setListener(listener: ScreenDprListener): void {
|
||||
if (this._listener) {
|
||||
this.clearListener();
|
||||
}
|
||||
this._listener = listener;
|
||||
this._outerListener = () => {
|
||||
if (!this._listener) {
|
||||
return;
|
||||
}
|
||||
this._listener(this._parentWindow.devicePixelRatio, this._currentDevicePixelRatio);
|
||||
this._updateDpr();
|
||||
};
|
||||
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._listener || !this._outerListener) {
|
||||
return;
|
||||
}
|
||||
this._resolutionMediaMatchList.removeListener(this._outerListener);
|
||||
this._resolutionMediaMatchList = undefined;
|
||||
this._listener = undefined;
|
||||
this._outerListener = undefined;
|
||||
}
|
||||
}
|
||||
+26
-12
@@ -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,19 @@ 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;
|
||||
}
|
||||
|
||||
// Create main element container
|
||||
this.element = this._document.createElement('div');
|
||||
@@ -408,25 +417,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) {
|
||||
@@ -441,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, this._document.defaultView ?? window);
|
||||
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)));
|
||||
@@ -463,7 +477,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);
|
||||
|
||||
@@ -350,11 +350,16 @@ export class MockCompositionHelper implements ICompositionHelper {
|
||||
}
|
||||
|
||||
export class MockCoreBrowserService implements ICoreBrowserService {
|
||||
public onDprChange = new EventEmitter<number>().event;
|
||||
public onWindowChange = new EventEmitter<Window & typeof globalThis, void>().event;
|
||||
public serviceBrand: undefined;
|
||||
public isFocused: boolean = true;
|
||||
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;
|
||||
}
|
||||
|
||||
|
||||
@@ -3,8 +3,7 @@
|
||||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
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 +18,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
|
||||
) {
|
||||
@@ -33,7 +33,7 @@ export class BufferDecorationRenderer extends Disposable {
|
||||
this._dimensionsChanged = true;
|
||||
this._queueRefresh();
|
||||
}));
|
||||
this.register(addDisposableDomListener(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;
|
||||
}));
|
||||
@@ -70,7 +70,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`;
|
||||
|
||||
@@ -4,7 +4,6 @@
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
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 { IBufferService, IDecorationService, IOptionsService } from 'common/services/Services';
|
||||
@@ -52,10 +51,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 +111,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(this._coreBrowserService.onDprChange(() => this._queueRefresh(true)));
|
||||
// set the canvas dimensions
|
||||
this._queueRefresh(true);
|
||||
}
|
||||
@@ -135,11 +134,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 +156,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 +210,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;
|
||||
});
|
||||
|
||||
@@ -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'));
|
||||
|
||||
@@ -3,22 +3,49 @@
|
||||
* @license MIT
|
||||
*/
|
||||
|
||||
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 implements ICoreBrowserService {
|
||||
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<number>());
|
||||
public readonly onDprChange = this._onDprChange.event;
|
||||
private readonly _onWindowChange = this.register(new EventEmitter<Window & typeof globalThis>());
|
||||
public readonly onWindowChange = this._onWindowChange.event;
|
||||
|
||||
constructor(
|
||||
private _textarea: HTMLTextAreaElement,
|
||||
public readonly window: Window & typeof globalThis
|
||||
private _window: Window & typeof globalThis,
|
||||
public readonly mainDocument: Document
|
||||
) {
|
||||
super();
|
||||
|
||||
// Monitor device pixel ratio
|
||||
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);
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
@@ -31,3 +58,80 @@ export class CoreBrowserService implements ICoreBrowserService {
|
||||
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 _windowResizeListener = this.register(new MutableDisposable());
|
||||
|
||||
private readonly _onDprChange = this.register(new EventEmitter<number>());
|
||||
public readonly onDprChange = this._onDprChange.event;
|
||||
|
||||
constructor(private _parentWindow: Window) {
|
||||
super();
|
||||
|
||||
// Initialize listener and dpr value
|
||||
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);
|
||||
}
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,16 +3,14 @@
|
||||
* @license MIT
|
||||
*/
|
||||
|
||||
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';
|
||||
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;
|
||||
@@ -25,7 +23,6 @@ export class RenderService extends Disposable implements IRenderService {
|
||||
|
||||
private _renderer: MutableDisposable<IRenderer> = this.register(new MutableDisposable());
|
||||
private _renderDebouncer: IRenderDebouncerWithCallback;
|
||||
private _screenDprMonitor: ScreenDprMonitor;
|
||||
private _pausedResizeTask = new DebouncedIdleTask();
|
||||
|
||||
private _isPaused: boolean = false;
|
||||
@@ -59,6 +56,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 +64,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 = new ScreenDprMonitor(coreBrowserService.window);
|
||||
this._screenDprMonitor.setListener(() => this.handleDevicePixelRatioChange());
|
||||
this.register(this._screenDprMonitor);
|
||||
this.register(coreBrowserService.onDprChange(() => this.handleDevicePixelRatioChange()));
|
||||
|
||||
this.register(bufferService.onResize(() => this._fullRefresh()));
|
||||
this.register(bufferService.buffers.onBufferActivate(() => this._renderer.value?.clear()));
|
||||
@@ -104,10 +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.
|
||||
this.register(addDisposableDomListener(coreBrowserService.window, 'resize', () => this.handleDevicePixelRatioChange()));
|
||||
|
||||
this.register(themeService.onChangeColors(() => this._fullRefresh()));
|
||||
|
||||
// Detect whether IntersectionObserver is detected and enable renderer pause
|
||||
|
||||
@@ -28,12 +28,21 @@ export interface ICoreBrowserService {
|
||||
serviceBrand: undefined;
|
||||
|
||||
readonly isFocused: boolean;
|
||||
|
||||
readonly onDprChange: IEvent<number>;
|
||||
readonly onWindowChange: IEvent<Window & typeof globalThis>;
|
||||
|
||||
/**
|
||||
* 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 to be used to create elements when working with multiple
|
||||
* windows. This is defined by the documentOverride setting.
|
||||
*/
|
||||
readonly mainDocument: Document;
|
||||
/**
|
||||
* Helper for getting the devicePixelRatio of the parent window.
|
||||
*/
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -71,3 +71,8 @@ export class EventEmitter<T, U = void> implements IEventEmitter<T, U> {
|
||||
export function forwardEvent<T>(from: IEvent<T>, to: IEventEmitter<T>): IDisposable {
|
||||
return from(e => to.fire(e));
|
||||
}
|
||||
|
||||
export function runAndSubscribe<T>(event: IEvent<T>, handler: (e: T | undefined) => any): IDisposable {
|
||||
handler(undefined);
|
||||
return event(e => handler(e));
|
||||
}
|
||||
|
||||
@@ -12,8 +12,9 @@ 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 navigator === 'undefined') ? true : false;
|
||||
export const isNode = (typeof process !== 'undefined') ? true : false;
|
||||
const userAgent = (isNode) ? 'node' : navigator.userAgent;
|
||||
const platform = (isNode) ? 'node' : navigator.platform;
|
||||
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user