diff --git a/.eslintrc.json b/.eslintrc.json index 3a60f5bc77..9510fc6b99 100644 --- a/.eslintrc.json +++ b/.eslintrc.json @@ -147,6 +147,29 @@ ] } ], + "no-restricted-syntax": [ + "warn", + { + "selector": "CallExpression[callee.name='requestAnimationFrame']", + "message": "The global requestAnimationFrame() should be avoided, call it on the parent window from ICoreBrowserService." + }, + { + "selector": "CallExpression[callee.name='cancelAnimationFrame']", + "message": "The global cancelAnimationFrame() should be avoided, call it on the parent window from ICoreBrowserService." + }, + { + "selector": "CallExpression > MemberExpression[object.name='window'][property.name='requestAnimationFrame']", + "message": "window.requestAnimationFrame() should be avoided, call it on the parent window from ICoreBrowserService." + }, + { + "selector": "CallExpression > MemberExpression[object.name='window'][property.name='cancelAnimationFrame']", + "message": "window.cancelAnimationFrame() should be avoided, call it on the parent window from ICoreBrowserService." + }, + { + "selector": "MemberExpression[object.name='window'][property.name='devicePixelRatio']", + "message": "window.devicePixelRatio should be avoided, get it from ICoreBrowserService." + } + ], "no-trailing-spaces": "warn", "no-unsafe-finally": "warn", "no-var": "warn", diff --git a/addons/xterm-addon-canvas/src/BaseRenderLayer.ts b/addons/xterm-addon-canvas/src/BaseRenderLayer.ts index ae39601693..f37ea1b858 100644 --- a/addons/xterm-addon-canvas/src/BaseRenderLayer.ts +++ b/addons/xterm-addon-canvas/src/BaseRenderLayer.ts @@ -15,6 +15,7 @@ import { AttributeData } from 'common/buffer/AttributeData'; import { IColorSet } from 'browser/Types'; import { CellData } from 'common/buffer/CellData'; import { IBufferService, IDecorationService, IOptionsService } from 'common/services/Services'; +import { ICoreBrowserService } from 'browser/services/Services'; import { excludeFromContrastRatioDemands, throwIfFalsy } from 'browser/renderer/RendererUtils'; import { channels, color, rgba } from 'common/Color'; import { removeElementFromParent } from 'browser/Dom'; @@ -60,7 +61,8 @@ export abstract class BaseRenderLayer implements IRenderLayer { private _rendererId: number, protected readonly _bufferService: IBufferService, protected readonly _optionsService: IOptionsService, - protected readonly _decorationService: IDecorationService + protected readonly _decorationService: IDecorationService, + protected readonly _coreBrowserService: ICoreBrowserService ) { this._canvas = document.createElement('canvas'); this._canvas.classList.add(`xterm-${id}-layer`); @@ -125,7 +127,7 @@ export abstract class BaseRenderLayer implements IRenderLayer { if (this._scaledCharWidth <= 0 && this._scaledCharHeight <= 0) { return; } - this._charAtlas = acquireCharAtlas(this._optionsService.rawOptions, this._rendererId, colorSet, this._scaledCharWidth, this._scaledCharHeight); + this._charAtlas = acquireCharAtlas(this._optionsService.rawOptions, this._rendererId, colorSet, this._scaledCharWidth, this._scaledCharHeight, this._coreBrowserService.dpr); this._charAtlas.warmUp(); } @@ -180,9 +182,9 @@ export abstract class BaseRenderLayer implements IRenderLayer { const cellOffset = Math.ceil(this._scaledCellHeight * 0.5); this._ctx.fillRect( x * this._scaledCellWidth, - (y + 1) * this._scaledCellHeight - cellOffset - window.devicePixelRatio, + (y + 1) * this._scaledCellHeight - cellOffset - this._coreBrowserService.dpr, width * this._scaledCellWidth, - window.devicePixelRatio); + this._coreBrowserService.dpr); } /** @@ -194,23 +196,24 @@ export abstract class BaseRenderLayer implements IRenderLayer { protected _fillBottomLineAtCells(x: number, y: number, width: number = 1, pixelOffset: number = 0): void { this._ctx.fillRect( x * this._scaledCellWidth, - (y + 1) * this._scaledCellHeight + pixelOffset - window.devicePixelRatio - 1 /* Ensure it's drawn within the cell */, + (y + 1) * this._scaledCellHeight + pixelOffset - this._coreBrowserService.dpr - 1 /* Ensure it's drawn within the cell */, width * this._scaledCellWidth, - window.devicePixelRatio); + this._coreBrowserService.dpr); } protected _curlyUnderlineAtCell(x: number, y: number, width: number = 1): void { this._ctx.save(); this._ctx.beginPath(); this._ctx.strokeStyle = this._ctx.fillStyle; - this._ctx.lineWidth = window.devicePixelRatio; + const lineWidth = this._coreBrowserService.dpr; + this._ctx.lineWidth = lineWidth; for (let xOffset = 0; xOffset < width; xOffset++) { const xLeft = (x + xOffset) * this._scaledCellWidth; const xMid = (x + xOffset + 0.5) * this._scaledCellWidth; const xRight = (x + xOffset + 1) * this._scaledCellWidth; - const yMid = (y + 1) * this._scaledCellHeight - window.devicePixelRatio - 1; - const yMidBot = yMid - window.devicePixelRatio; - const yMidTop = yMid + window.devicePixelRatio; + const yMid = (y + 1) * this._scaledCellHeight - lineWidth - 1; + const yMidBot = yMid - lineWidth; + const yMidTop = yMid + lineWidth; this._ctx.moveTo(xLeft, yMid); this._ctx.bezierCurveTo( xLeft, yMidBot, @@ -231,10 +234,11 @@ export abstract class BaseRenderLayer implements IRenderLayer { this._ctx.save(); this._ctx.beginPath(); this._ctx.strokeStyle = this._ctx.fillStyle; - this._ctx.lineWidth = window.devicePixelRatio; - this._ctx.setLineDash([window.devicePixelRatio * 2, window.devicePixelRatio]); + const lineWidth = this._coreBrowserService.dpr; + this._ctx.lineWidth = lineWidth; + this._ctx.setLineDash([lineWidth * 2, lineWidth]); const xLeft = x * this._scaledCellWidth; - const yMid = (y + 1) * this._scaledCellHeight - window.devicePixelRatio - 1; + const yMid = (y + 1) * this._scaledCellHeight - lineWidth - 1; this._ctx.moveTo(xLeft, yMid); for (let xOffset = 0; xOffset < width; xOffset++) { // const xLeft = x * this._scaledCellWidth; @@ -250,11 +254,12 @@ export abstract class BaseRenderLayer implements IRenderLayer { this._ctx.save(); this._ctx.beginPath(); this._ctx.strokeStyle = this._ctx.fillStyle; - this._ctx.lineWidth = window.devicePixelRatio; - this._ctx.setLineDash([window.devicePixelRatio * 4, window.devicePixelRatio * 3]); + const lineWidth = this._coreBrowserService.dpr; + this._ctx.lineWidth = lineWidth; + this._ctx.setLineDash([lineWidth * 4, lineWidth * 3]); const xLeft = x * this._scaledCellWidth; const xRight = (x + width) * this._scaledCellWidth; - const yMid = (y + 1) * this._scaledCellHeight - window.devicePixelRatio - 1; + const yMid = (y + 1) * this._scaledCellHeight - lineWidth - 1; this._ctx.moveTo(xLeft, yMid); this._ctx.lineTo(xRight, yMid); this._ctx.stroke(); @@ -272,7 +277,7 @@ export abstract class BaseRenderLayer implements IRenderLayer { this._ctx.fillRect( x * this._scaledCellWidth, y * this._scaledCellHeight, - window.devicePixelRatio * width, + this._coreBrowserService.dpr * width, this._scaledCellHeight); } @@ -283,12 +288,13 @@ export abstract class BaseRenderLayer implements IRenderLayer { * @param y The row to fill. */ protected _strokeRectAtCell(x: number, y: number, width: number, height: number): void { - this._ctx.lineWidth = window.devicePixelRatio; + const lineWidth = this._coreBrowserService.dpr; + this._ctx.lineWidth = lineWidth; this._ctx.strokeRect( - x * this._scaledCellWidth + window.devicePixelRatio / 2, - y * this._scaledCellHeight + (window.devicePixelRatio / 2), - width * this._scaledCellWidth - window.devicePixelRatio, - (height * this._scaledCellHeight) - window.devicePixelRatio); + x * this._scaledCellWidth + lineWidth / 2, + y * this._scaledCellHeight + (lineWidth / 2), + width * this._scaledCellWidth - lineWidth, + (height * this._scaledCellHeight) - lineWidth); } /** @@ -344,7 +350,7 @@ export abstract class BaseRenderLayer implements IRenderLayer { // Draw custom characters if applicable let drawSuccess = false; if (this._optionsService.rawOptions.customGlyphs !== false) { - drawSuccess = tryDrawCustomChar(this._ctx, cell.getChars(), x * this._scaledCellWidth, y * this._scaledCellHeight, this._scaledCellWidth, this._scaledCellHeight, this._optionsService.rawOptions.fontSize); + drawSuccess = tryDrawCustomChar(this._ctx, cell.getChars(), x * this._scaledCellWidth, y * this._scaledCellHeight, this._scaledCellWidth, this._scaledCellHeight, this._optionsService.rawOptions.fontSize, this._coreBrowserService.dpr); } // Draw the character @@ -472,7 +478,7 @@ export abstract class BaseRenderLayer implements IRenderLayer { // Draw custom characters if applicable let drawSuccess = false; if (this._optionsService.rawOptions.customGlyphs !== false) { - drawSuccess = tryDrawCustomChar(this._ctx, cell.getChars(), x * this._scaledCellWidth, y * this._scaledCellHeight, this._scaledCellWidth, this._scaledCellHeight, this._optionsService.rawOptions.fontSize); + drawSuccess = tryDrawCustomChar(this._ctx, cell.getChars(), x * this._scaledCellWidth, y * this._scaledCellHeight, this._scaledCellWidth, this._scaledCellHeight, this._optionsService.rawOptions.fontSize, this._coreBrowserService.dpr); } // Draw the character @@ -509,7 +515,7 @@ export abstract class BaseRenderLayer implements IRenderLayer { const fontWeight = isBold ? this._optionsService.rawOptions.fontWeightBold : this._optionsService.rawOptions.fontWeight; const fontStyle = isItalic ? 'italic' : ''; - return `${fontStyle} ${fontWeight} ${this._optionsService.rawOptions.fontSize * window.devicePixelRatio}px ${this._optionsService.rawOptions.fontFamily}`; + return `${fontStyle} ${fontWeight} ${this._optionsService.rawOptions.fontSize * this._coreBrowserService.dpr}px ${this._optionsService.rawOptions.fontFamily}`; } private _getContrastColor(cell: CellData, x: number, y: number): IColor | undefined { diff --git a/addons/xterm-addon-canvas/src/CanvasRenderer.ts b/addons/xterm-addon-canvas/src/CanvasRenderer.ts index cb30106d37..b642efbcaf 100644 --- a/addons/xterm-addon-canvas/src/CanvasRenderer.ts +++ b/addons/xterm-addon-canvas/src/CanvasRenderer.ts @@ -39,16 +39,16 @@ export class CanvasRenderer extends Disposable implements IRenderer { private readonly _optionsService: IOptionsService, characterJoinerService: ICharacterJoinerService, coreService: ICoreService, - coreBrowserService: ICoreBrowserService, + private readonly _coreBrowserService: ICoreBrowserService, decorationService: IDecorationService ) { super(); const allowTransparency = this._optionsService.rawOptions.allowTransparency; this._renderLayers = [ - new TextRenderLayer(this._screenElement, 0, this._colors, allowTransparency, this._id, this._bufferService, this._optionsService, characterJoinerService, decorationService), - new SelectionRenderLayer(this._screenElement, 1, this._colors, this._id, this._bufferService, coreBrowserService, decorationService, this._optionsService), - new LinkRenderLayer(this._screenElement, 2, this._colors, this._id, linkifier2, this._bufferService, this._optionsService, decorationService), - new CursorRenderLayer(this._screenElement, 3, this._colors, this._id, this._onRequestRedraw, this._bufferService, this._optionsService, coreService, coreBrowserService, decorationService) + new TextRenderLayer(this._screenElement, 0, this._colors, allowTransparency, this._id, this._bufferService, this._optionsService, characterJoinerService, decorationService, this._coreBrowserService), + new SelectionRenderLayer(this._screenElement, 1, this._colors, this._id, this._bufferService, this._coreBrowserService, decorationService, this._optionsService), + new LinkRenderLayer(this._screenElement, 2, this._colors, this._id, linkifier2, this._bufferService, this._optionsService, decorationService, this._coreBrowserService), + new CursorRenderLayer(this._screenElement, 3, this._colors, this._id, this._onRequestRedraw, this._bufferService, this._optionsService, coreService, this._coreBrowserService, decorationService) ]; this.dimensions = { scaledCharWidth: 0, @@ -64,10 +64,10 @@ export class CanvasRenderer extends Disposable implements IRenderer { actualCellWidth: 0, actualCellHeight: 0 }; - this._devicePixelRatio = window.devicePixelRatio; + this._devicePixelRatio = this._coreBrowserService.dpr; this._updateDimensions(); - this.register(observeDevicePixelDimensions(this._renderLayers[0].canvas, (w, h) => this._setCanvasDevicePixelDimensions(w, h))); + this.register(observeDevicePixelDimensions(this._renderLayers[0].canvas, this._coreBrowserService.window, (w, h) => this._setCanvasDevicePixelDimensions(w, h))); this.onOptionsChanged(); } @@ -83,8 +83,8 @@ export class CanvasRenderer extends Disposable implements IRenderer { public onDevicePixelRatioChange(): void { // If the device pixel ratio changed, the char atlas needs to be regenerated // and the terminal needs to refreshed - if (this._devicePixelRatio !== window.devicePixelRatio) { - this._devicePixelRatio = window.devicePixelRatio; + if (this._devicePixelRatio !== this._coreBrowserService.dpr) { + this._devicePixelRatio = this._coreBrowserService.dpr; this.onResize(this._bufferService.cols, this._bufferService.rows); } } @@ -175,16 +175,17 @@ export class CanvasRenderer extends Disposable implements IRenderer { } // See the WebGL renderer for an explanation of this section. - this.dimensions.scaledCharWidth = Math.floor(this._charSizeService.width * window.devicePixelRatio); - this.dimensions.scaledCharHeight = Math.ceil(this._charSizeService.height * window.devicePixelRatio); + const dpr = this._coreBrowserService.dpr; + this.dimensions.scaledCharWidth = Math.floor(this._charSizeService.width * dpr); + this.dimensions.scaledCharHeight = Math.ceil(this._charSizeService.height * dpr); this.dimensions.scaledCellHeight = Math.floor(this.dimensions.scaledCharHeight * this._optionsService.rawOptions.lineHeight); this.dimensions.scaledCharTop = this._optionsService.rawOptions.lineHeight === 1 ? 0 : Math.round((this.dimensions.scaledCellHeight - this.dimensions.scaledCharHeight) / 2); this.dimensions.scaledCellWidth = this.dimensions.scaledCharWidth + Math.round(this._optionsService.rawOptions.letterSpacing); this.dimensions.scaledCharLeft = Math.floor(this._optionsService.rawOptions.letterSpacing / 2); this.dimensions.scaledCanvasHeight = this._bufferService.rows * this.dimensions.scaledCellHeight; this.dimensions.scaledCanvasWidth = this._bufferService.cols * this.dimensions.scaledCellWidth; - this.dimensions.canvasHeight = Math.round(this.dimensions.scaledCanvasHeight / window.devicePixelRatio); - this.dimensions.canvasWidth = Math.round(this.dimensions.scaledCanvasWidth / window.devicePixelRatio); + this.dimensions.canvasHeight = Math.round(this.dimensions.scaledCanvasHeight / dpr); + this.dimensions.canvasWidth = Math.round(this.dimensions.scaledCanvasWidth / dpr); this.dimensions.actualCellHeight = this.dimensions.canvasHeight / this._bufferService.rows; this.dimensions.actualCellWidth = this.dimensions.canvasWidth / this._bufferService.cols; } diff --git a/addons/xterm-addon-canvas/src/CursorRenderLayer.ts b/addons/xterm-addon-canvas/src/CursorRenderLayer.ts index 19d414b60e..61e2d9341b 100644 --- a/addons/xterm-addon-canvas/src/CursorRenderLayer.ts +++ b/addons/xterm-addon-canvas/src/CursorRenderLayer.ts @@ -40,10 +40,10 @@ export class CursorRenderLayer extends BaseRenderLayer { bufferService: IBufferService, optionsService: IOptionsService, private readonly _coreService: ICoreService, - private readonly _coreBrowserService: ICoreBrowserService, + coreBrowserService: ICoreBrowserService, decorationService: IDecorationService ) { - super(container, 'cursor', zIndex, true, colors, rendererId, bufferService, optionsService, decorationService); + super(container, 'cursor', zIndex, true, colors, rendererId, bufferService, optionsService, decorationService, coreBrowserService); this._state = { x: 0, y: 0, @@ -99,7 +99,7 @@ export class CursorRenderLayer extends BaseRenderLayer { if (!this._cursorBlinkStateManager) { this._cursorBlinkStateManager = new CursorBlinkStateManager(this._coreBrowserService.isFocused, () => { this._render(true); - }); + }, this._coreBrowserService); } } else { this._cursorBlinkStateManager?.dispose(); @@ -196,7 +196,7 @@ export class CursorRenderLayer extends BaseRenderLayer { private _clearCursor(): void { if (this._state) { // Avoid potential rounding errors when device pixel ratio is less than 1 - if (window.devicePixelRatio < 1) { + if (this._coreBrowserService.dpr < 1) { this._clearAll(); } else { this._clearCells(this._state.x, this._state.y, this._state.width, 1); @@ -258,7 +258,8 @@ class CursorBlinkStateManager { constructor( isFocused: boolean, - private _renderCallback: () => void + private _renderCallback: () => void, + private _coreBrowserService: ICoreBrowserService ) { this.isCursorVisible = true; if (isFocused) { @@ -270,15 +271,15 @@ class CursorBlinkStateManager { public dispose(): void { if (this._blinkInterval) { - window.clearInterval(this._blinkInterval); + this._coreBrowserService.window.clearInterval(this._blinkInterval); this._blinkInterval = undefined; } if (this._blinkStartTimeout) { - window.clearTimeout(this._blinkStartTimeout); + this._coreBrowserService.window.clearTimeout(this._blinkStartTimeout); this._blinkStartTimeout = undefined; } if (this._animationFrame) { - window.cancelAnimationFrame(this._animationFrame); + this._coreBrowserService.window.cancelAnimationFrame(this._animationFrame); this._animationFrame = undefined; } } @@ -292,7 +293,7 @@ class CursorBlinkStateManager { // Force a cursor render to ensure it's visible and in the correct position this.isCursorVisible = true; if (!this._animationFrame) { - this._animationFrame = window.requestAnimationFrame(() => { + this._animationFrame = this._coreBrowserService.window.requestAnimationFrame(() => { this._renderCallback(); this._animationFrame = undefined; }); @@ -302,7 +303,7 @@ class CursorBlinkStateManager { private _restartInterval(timeToStart: number = BLINK_INTERVAL): void { // Clear any existing interval if (this._blinkInterval) { - window.clearInterval(this._blinkInterval); + this._coreBrowserService.window.clearInterval(this._blinkInterval); this._blinkInterval = undefined; } @@ -310,7 +311,7 @@ class CursorBlinkStateManager { // the regular interval is setup in order to support restarting the blink // animation in a lightweight way (without thrashing clearInterval and // setInterval). - this._blinkStartTimeout = window.setTimeout(() => { + this._blinkStartTimeout = this._coreBrowserService.window.setTimeout(() => { // Check if another animation restart was requested while this was being // started if (this._animationTimeRestarted) { @@ -324,13 +325,13 @@ class CursorBlinkStateManager { // Hide the cursor this.isCursorVisible = false; - this._animationFrame = window.requestAnimationFrame(() => { + this._animationFrame = this._coreBrowserService.window.requestAnimationFrame(() => { this._renderCallback(); this._animationFrame = undefined; }); // Setup the blink interval - this._blinkInterval = window.setInterval(() => { + this._blinkInterval = this._coreBrowserService.window.setInterval(() => { // Adjust the animation time if it was restarted if (this._animationTimeRestarted) { // calc time diff @@ -343,7 +344,7 @@ class CursorBlinkStateManager { // Invert visibility and render this.isCursorVisible = !this.isCursorVisible; - this._animationFrame = window.requestAnimationFrame(() => { + this._animationFrame = this._coreBrowserService.window.requestAnimationFrame(() => { this._renderCallback(); this._animationFrame = undefined; }); @@ -354,15 +355,15 @@ class CursorBlinkStateManager { public pause(): void { this.isCursorVisible = true; if (this._blinkInterval) { - window.clearInterval(this._blinkInterval); + this._coreBrowserService.window.clearInterval(this._blinkInterval); this._blinkInterval = undefined; } if (this._blinkStartTimeout) { - window.clearTimeout(this._blinkStartTimeout); + this._coreBrowserService.window.clearTimeout(this._blinkStartTimeout); this._blinkStartTimeout = undefined; } if (this._animationFrame) { - window.cancelAnimationFrame(this._animationFrame); + this._coreBrowserService.window.cancelAnimationFrame(this._animationFrame); this._animationFrame = undefined; } } diff --git a/addons/xterm-addon-canvas/src/LinkRenderLayer.ts b/addons/xterm-addon-canvas/src/LinkRenderLayer.ts index e514e3d056..c07329fd66 100644 --- a/addons/xterm-addon-canvas/src/LinkRenderLayer.ts +++ b/addons/xterm-addon-canvas/src/LinkRenderLayer.ts @@ -6,6 +6,7 @@ import { IRenderDimensions } from 'browser/renderer/Types'; import { BaseRenderLayer } from './BaseRenderLayer'; import { INVERTED_DEFAULT_COLOR } from 'browser/renderer/Constants'; +import { ICoreBrowserService } from 'browser/services/Services'; import { is256Color } from './atlas/CharAtlasUtils'; import { IColorSet, ILinkifierEvent, ILinkifier2 } from 'browser/Types'; import { IBufferService, IDecorationService, IOptionsService } from 'common/services/Services'; @@ -21,9 +22,10 @@ export class LinkRenderLayer extends BaseRenderLayer { linkifier2: ILinkifier2, bufferService: IBufferService, optionsService: IOptionsService, - decorationService: IDecorationService + decorationService: IDecorationService, + coreBrowserService: ICoreBrowserService ) { - super(container, 'link', zIndex, true, colors, rendererId, bufferService, optionsService, decorationService); + super(container, 'link', zIndex, true, colors, rendererId, bufferService, optionsService, decorationService, coreBrowserService); linkifier2.onShowLinkUnderline(e => this._onShowLinkUnderline(e)); linkifier2.onHideLinkUnderline(e => this._onHideLinkUnderline(e)); diff --git a/addons/xterm-addon-canvas/src/SelectionRenderLayer.ts b/addons/xterm-addon-canvas/src/SelectionRenderLayer.ts index 82aa056bd1..e90007b720 100644 --- a/addons/xterm-addon-canvas/src/SelectionRenderLayer.ts +++ b/addons/xterm-addon-canvas/src/SelectionRenderLayer.ts @@ -25,11 +25,11 @@ export class SelectionRenderLayer extends BaseRenderLayer { colors: IColorSet, rendererId: number, bufferService: IBufferService, - private readonly _coreBrowserService: ICoreBrowserService, + coreBrowserService: ICoreBrowserService, decorationService: IDecorationService, optionsService: IOptionsService ) { - super(container, 'selection', zIndex, true, colors, rendererId, bufferService, optionsService, decorationService); + super(container, 'selection', zIndex, true, colors, rendererId, bufferService, optionsService, decorationService, coreBrowserService); this._clearState(); } diff --git a/addons/xterm-addon-canvas/src/TextRenderLayer.ts b/addons/xterm-addon-canvas/src/TextRenderLayer.ts index 86e21ad5c8..95f22fce73 100644 --- a/addons/xterm-addon-canvas/src/TextRenderLayer.ts +++ b/addons/xterm-addon-canvas/src/TextRenderLayer.ts @@ -12,7 +12,7 @@ import { NULL_CELL_CODE, Content, UnderlineStyle } from 'common/buffer/Constants import { IColorSet } from 'browser/Types'; import { CellData } from 'common/buffer/CellData'; import { IOptionsService, IBufferService, IDecorationService } from 'common/services/Services'; -import { ICharacterJoinerService } from 'browser/services/Services'; +import { ICharacterJoinerService, ICoreBrowserService } from 'browser/services/Services'; import { JoinedCellData } from 'browser/services/CharacterJoinerService'; import { color, css } from 'common/Color'; @@ -39,9 +39,10 @@ export class TextRenderLayer extends BaseRenderLayer { bufferService: IBufferService, optionsService: IOptionsService, private readonly _characterJoinerService: ICharacterJoinerService, - decorationService: IDecorationService + decorationService: IDecorationService, + coreBrowserService: ICoreBrowserService ) { - super(container, 'text', zIndex, alpha, colors, rendererId, bufferService, optionsService, decorationService); + super(container, 'text', zIndex, alpha, colors, rendererId, bufferService, optionsService, decorationService, coreBrowserService); this._state = new GridCache(); } @@ -282,8 +283,8 @@ export class TextRenderLayer extends BaseRenderLayer { } switch (cell.extended.underlineStyle) { case UnderlineStyle.DOUBLE: - this._fillBottomLineAtCells(x, y, cell.getWidth(), -window.devicePixelRatio); - this._fillBottomLineAtCells(x, y, cell.getWidth(), window.devicePixelRatio); + this._fillBottomLineAtCells(x, y, cell.getWidth(), -this._coreBrowserService.dpr); + this._fillBottomLineAtCells(x, y, cell.getWidth(), this._coreBrowserService.dpr); break; case UnderlineStyle.CURLY: this._curlyUnderlineAtCell(x, y, cell.getWidth()); diff --git a/addons/xterm-addon-canvas/src/atlas/CharAtlasCache.ts b/addons/xterm-addon-canvas/src/atlas/CharAtlasCache.ts index 0c78808eee..d9349286f8 100644 --- a/addons/xterm-addon-canvas/src/atlas/CharAtlasCache.ts +++ b/addons/xterm-addon-canvas/src/atlas/CharAtlasCache.ts @@ -29,9 +29,10 @@ export function acquireCharAtlas( rendererId: number, colors: IColorSet, scaledCharWidth: number, - scaledCharHeight: number + scaledCharHeight: number, + devicePixelRatio: number ): BaseCharAtlas { - const newConfig = generateConfig(scaledCharWidth, scaledCharHeight, options, colors); + const newConfig = generateConfig(scaledCharWidth, scaledCharHeight, options, colors, devicePixelRatio); // Check to see if the renderer already owns this config for (let i = 0; i < charAtlasCache.length; i++) { diff --git a/addons/xterm-addon-canvas/src/atlas/CharAtlasUtils.ts b/addons/xterm-addon-canvas/src/atlas/CharAtlasUtils.ts index b5674304d8..b0151e30d9 100644 --- a/addons/xterm-addon-canvas/src/atlas/CharAtlasUtils.ts +++ b/addons/xterm-addon-canvas/src/atlas/CharAtlasUtils.ts @@ -8,7 +8,7 @@ import { DEFAULT_COLOR } from 'common/buffer/Constants'; import { IColorSet, IPartialColorSet } from 'browser/Types'; import { ITerminalOptions } from 'xterm'; -export function generateConfig(scaledCharWidth: number, scaledCharHeight: number, options: Required, colors: IColorSet): ICharAtlasConfig { +export function generateConfig(scaledCharWidth: number, scaledCharHeight: number, options: Required, colors: IColorSet, devicePixelRatio: number): ICharAtlasConfig { // null out some fields that don't matter const clonedColors: IPartialColorSet = { foreground: colors.foreground, @@ -19,7 +19,7 @@ export function generateConfig(scaledCharWidth: number, scaledCharHeight: number ansi: colors.ansi.slice() }; return { - devicePixelRatio: window.devicePixelRatio, + devicePixelRatio, scaledCharWidth, scaledCharHeight, fontFamily: options.fontFamily, diff --git a/addons/xterm-addon-webgl/src/WebglRenderer.ts b/addons/xterm-addon-webgl/src/WebglRenderer.ts index 6420e812f6..d8daa0d187 100644 --- a/addons/xterm-addon-webgl/src/WebglRenderer.ts +++ b/addons/xterm-addon-webgl/src/WebglRenderer.ts @@ -77,7 +77,7 @@ export class WebglRenderer extends Disposable implements IRenderer { this._core = (this._terminal as any)._core; this._renderLayers = [ - new LinkRenderLayer(this._core.screenElement!, 2, this._colors, this._core), + new LinkRenderLayer(this._core.screenElement!, 2, this._colors, this._core, this._coreBrowserService), new CursorRenderLayer(_terminal, this._core.screenElement!, 3, this._colors, this._onRequestRedraw, this._coreBrowserService, coreService) ]; this.dimensions = { @@ -94,7 +94,7 @@ export class WebglRenderer extends Disposable implements IRenderer { actualCellWidth: 0, actualCellHeight: 0 }; - this._devicePixelRatio = window.devicePixelRatio; + this._devicePixelRatio = this._coreBrowserService.dpr; this._updateDimensions(); this._canvas = document.createElement('canvas'); @@ -132,13 +132,13 @@ export class WebglRenderer extends Disposable implements IRenderer { this._requestRedrawViewport(); })); - this.register(observeDevicePixelDimensions(this._canvas, (w, h) => this._setCanvasDevicePixelDimensions(w, h))); + this.register(observeDevicePixelDimensions(this._canvas, this._coreBrowserService.window, (w, h) => this._setCanvasDevicePixelDimensions(w, h))); this._core.screenElement!.appendChild(this._canvas); this._initializeWebGLState(); - this._isAttached = document.body.contains(this._core.screenElement!); + this._isAttached = this._coreBrowserService.window.document.body.contains(this._core.screenElement!); } public dispose(): void { @@ -173,8 +173,8 @@ export class WebglRenderer extends Disposable implements IRenderer { public onDevicePixelRatioChange(): void { // If the device pixel ratio changed, the char atlas needs to be regenerated // and the terminal needs to refreshed - if (this._devicePixelRatio !== window.devicePixelRatio) { - this._devicePixelRatio = window.devicePixelRatio; + if (this._devicePixelRatio !== this._coreBrowserService.dpr) { + this._devicePixelRatio = this._coreBrowserService.dpr; this.onResize(this._terminal.cols, this._terminal.rows); } } @@ -281,7 +281,7 @@ export class WebglRenderer extends Disposable implements IRenderer { return; } - const atlas = acquireCharAtlas(this._terminal, this._colors, this.dimensions.scaledCellWidth, this.dimensions.scaledCellHeight, this.dimensions.scaledCharWidth, this.dimensions.scaledCharHeight); + const atlas = acquireCharAtlas(this._terminal, this._colors, this.dimensions.scaledCellWidth, this.dimensions.scaledCellHeight, this.dimensions.scaledCharWidth, this.dimensions.scaledCharHeight, this._coreBrowserService.dpr); if (!('getRasterizedGlyph' in atlas)) { throw new Error('The webgl renderer only works with the webgl char atlas'); } @@ -329,7 +329,7 @@ export class WebglRenderer extends Disposable implements IRenderer { public renderRows(start: number, end: number): void { if (!this._isAttached) { - if (document.body.contains(this._core.screenElement!) && (this._core as any)._charSizeService.width && (this._core as any)._charSizeService.height) { + if (this._coreBrowserService.window.document.body.contains(this._core.screenElement!) && (this._core as any)._charSizeService.width && (this._core as any)._charSizeService.height) { this._updateDimensions(); this._refreshCharAtlas(); this._isAttached = true; diff --git a/addons/xterm-addon-webgl/src/atlas/CharAtlasCache.ts b/addons/xterm-addon-webgl/src/atlas/CharAtlasCache.ts index 6aba2125bc..893362c867 100644 --- a/addons/xterm-addon-webgl/src/atlas/CharAtlasCache.ts +++ b/addons/xterm-addon-webgl/src/atlas/CharAtlasCache.ts @@ -31,9 +31,10 @@ export function acquireCharAtlas( scaledCellWidth: number, scaledCellHeight: number, scaledCharWidth: number, - scaledCharHeight: number + scaledCharHeight: number, + devicePixelRatio: number ): WebglCharAtlas { - const newConfig = generateConfig(scaledCellWidth, scaledCellHeight, scaledCharWidth, scaledCharHeight, terminal, colors); + const newConfig = generateConfig(scaledCellWidth, scaledCellHeight, scaledCharWidth, scaledCharHeight, terminal, colors, devicePixelRatio); // Check to see if the terminal already owns this config for (let i = 0; i < charAtlasCache.length; i++) { diff --git a/addons/xterm-addon-webgl/src/atlas/CharAtlasUtils.ts b/addons/xterm-addon-webgl/src/atlas/CharAtlasUtils.ts index 18de5739bb..83f82fa768 100644 --- a/addons/xterm-addon-webgl/src/atlas/CharAtlasUtils.ts +++ b/addons/xterm-addon-webgl/src/atlas/CharAtlasUtils.ts @@ -14,7 +14,7 @@ const NULL_COLOR: IColor = { rgba: 0 }; -export function generateConfig(scaledCellWidth: number, scaledCellHeight: number, scaledCharWidth: number, scaledCharHeight: number, terminal: Terminal, colors: IColorSet): ICharAtlasConfig { +export function generateConfig(scaledCellWidth: number, scaledCellHeight: number, scaledCharWidth: number, scaledCharHeight: number, terminal: Terminal, colors: IColorSet, devicePixelRatio: number): ICharAtlasConfig { // null out some fields that don't matter const clonedColors: IColorSet = { foreground: colors.foreground, @@ -33,7 +33,7 @@ export function generateConfig(scaledCellWidth: number, scaledCellHeight: number }; return { customGlyphs: terminal.options.customGlyphs, - devicePixelRatio: window.devicePixelRatio, + devicePixelRatio, letterSpacing: terminal.options.letterSpacing, lineHeight: terminal.options.lineHeight, scaledCellWidth, diff --git a/addons/xterm-addon-webgl/src/atlas/WebglCharAtlas.ts b/addons/xterm-addon-webgl/src/atlas/WebglCharAtlas.ts index e7dbfaa263..b4593d909f 100644 --- a/addons/xterm-addon-webgl/src/atlas/WebglCharAtlas.ts +++ b/addons/xterm-addon-webgl/src/atlas/WebglCharAtlas.ts @@ -409,7 +409,7 @@ export class WebglCharAtlas implements IDisposable { // Draw custom characters if applicable let customGlyph = false; if (this._config.customGlyphs !== false) { - customGlyph = tryDrawCustomChar(this._tmpCtx, chars, padding, padding, this._config.scaledCellWidth, this._config.scaledCellHeight, this._config.fontSize); + customGlyph = tryDrawCustomChar(this._tmpCtx, chars, padding, padding, this._config.scaledCellWidth, this._config.scaledCellHeight, this._config.fontSize, this._config.devicePixelRatio); } // Whether to clear pixels based on a threshold difference between the glyph color and the @@ -427,7 +427,7 @@ export class WebglCharAtlas implements IDisposable { // Draw underline if (underline) { this._tmpCtx.save(); - const lineWidth = Math.max(1, Math.floor(this._config.fontSize * window.devicePixelRatio / 15)); + const lineWidth = Math.max(1, Math.floor(this._config.fontSize * this._config.devicePixelRatio / 15)); // When the line width is odd, draw at a 0.5 position const yOffset = lineWidth % 2 === 1 ? 0.5 : 0; this._tmpCtx.lineWidth = lineWidth; @@ -501,12 +501,12 @@ export class WebglCharAtlas implements IDisposable { ); break; case UnderlineStyle.DOTTED: - this._tmpCtx.setLineDash([window.devicePixelRatio * 2, window.devicePixelRatio]); + this._tmpCtx.setLineDash([this._config.devicePixelRatio * 2, this._config.devicePixelRatio]); this._tmpCtx.moveTo(xChLeft, yTop); this._tmpCtx.lineTo(xChRight, yTop); break; case UnderlineStyle.DASHED: - this._tmpCtx.setLineDash([window.devicePixelRatio * 4, window.devicePixelRatio * 3]); + this._tmpCtx.setLineDash([this._config.devicePixelRatio * 4, this._config.devicePixelRatio * 3]); this._tmpCtx.moveTo(xChLeft, yTop); this._tmpCtx.lineTo(xChRight, yTop); break; @@ -543,7 +543,7 @@ export class WebglCharAtlas implements IDisposable { const clipRegion = new Path2D(); clipRegion.rect(xLeft, yTop - Math.ceil(lineWidth / 2), this._config.scaledCellWidth, yBot - yTop + Math.ceil(lineWidth / 2)); this._tmpCtx.clip(clipRegion); - this._tmpCtx.lineWidth = window.devicePixelRatio * 3; + this._tmpCtx.lineWidth = this._config.devicePixelRatio * 3; this._tmpCtx.strokeStyle = backgroundColor.css; this._tmpCtx.strokeText(chars, padding, padding + this._config.scaledCharHeight); this._tmpCtx.restore(); @@ -578,7 +578,7 @@ export class WebglCharAtlas implements IDisposable { // Draw strokethrough if (strikethrough) { - const lineWidth = Math.max(1, Math.floor(this._config.fontSize * window.devicePixelRatio / 10)); + const lineWidth = Math.max(1, Math.floor(this._config.fontSize * this._config.devicePixelRatio / 10)); const yOffset = this._tmpCtx.lineWidth % 2 === 1 ? 0.5 : 0; // When the width is odd, draw at 0.5 position this._tmpCtx.lineWidth = lineWidth; this._tmpCtx.strokeStyle = this._tmpCtx.fillStyle; diff --git a/addons/xterm-addon-webgl/src/renderLayer/BaseRenderLayer.ts b/addons/xterm-addon-webgl/src/renderLayer/BaseRenderLayer.ts index 9d9207732a..e9c0cf4136 100644 --- a/addons/xterm-addon-webgl/src/renderLayer/BaseRenderLayer.ts +++ b/addons/xterm-addon-webgl/src/renderLayer/BaseRenderLayer.ts @@ -8,6 +8,7 @@ import { acquireCharAtlas } from '../atlas/CharAtlasCache'; import { Terminal } from 'xterm'; import { IColorSet } from 'browser/Types'; import { TEXT_BASELINE } from 'browser/renderer/Constants'; +import { ICoreBrowserService } from 'browser/services/Services'; import { IRenderDimensions } from 'browser/renderer/Types'; import { CellData } from 'common/buffer/CellData'; import { WebglCharAtlas } from 'atlas/WebglCharAtlas'; @@ -30,7 +31,8 @@ export abstract class BaseRenderLayer implements IRenderLayer { id: string, zIndex: number, private _alpha: boolean, - protected _colors: IColorSet + protected _colors: IColorSet, + protected readonly _coreBrowserService: ICoreBrowserService ) { this._canvas = document.createElement('canvas'); this._canvas.classList.add(`xterm-${id}-layer`); @@ -93,7 +95,7 @@ export abstract class BaseRenderLayer implements IRenderLayer { if (this._scaledCharWidth <= 0 && this._scaledCharHeight <= 0) { return; } - this._charAtlas = acquireCharAtlas(terminal, colorSet, this._scaledCellWidth, this._scaledCellHeight, this._scaledCharWidth, this._scaledCharHeight); + this._charAtlas = acquireCharAtlas(terminal, colorSet, this._scaledCellWidth, this._scaledCellHeight, this._scaledCharWidth, this._scaledCharHeight, this._coreBrowserService.dpr); this._charAtlas.warmUp(); } @@ -143,9 +145,9 @@ export abstract class BaseRenderLayer implements IRenderLayer { protected _fillBottomLineAtCells(x: number, y: number, width: number = 1): void { this._ctx.fillRect( x * this._scaledCellWidth, - (y + 1) * this._scaledCellHeight - window.devicePixelRatio - 1 /* Ensure it's drawn within the cell */, + (y + 1) * this._scaledCellHeight - this._coreBrowserService.dpr - 1 /* Ensure it's drawn within the cell */, width * this._scaledCellWidth, - window.devicePixelRatio); + this._coreBrowserService.dpr); } /** @@ -158,7 +160,7 @@ export abstract class BaseRenderLayer implements IRenderLayer { this._ctx.fillRect( x * this._scaledCellWidth, y * this._scaledCellHeight, - window.devicePixelRatio * width, + this._coreBrowserService.dpr * width, this._scaledCellHeight); } @@ -169,12 +171,12 @@ export abstract class BaseRenderLayer implements IRenderLayer { * @param y The row to fill. */ protected _strokeRectAtCell(x: number, y: number, width: number, height: number): void { - this._ctx.lineWidth = window.devicePixelRatio; + this._ctx.lineWidth = this._coreBrowserService.dpr; this._ctx.strokeRect( - x * this._scaledCellWidth + window.devicePixelRatio / 2, - y * this._scaledCellHeight + (window.devicePixelRatio / 2), - width * this._scaledCellWidth - window.devicePixelRatio, - (height * this._scaledCellHeight) - window.devicePixelRatio); + x * this._scaledCellWidth + this._coreBrowserService.dpr / 2, + y * this._scaledCellHeight + (this._coreBrowserService.dpr / 2), + width * this._scaledCellWidth - this._coreBrowserService.dpr, + (height * this._scaledCellHeight) - this._coreBrowserService.dpr); } /** @@ -258,7 +260,7 @@ export abstract class BaseRenderLayer implements IRenderLayer { const fontWeight = isBold ? terminal.options.fontWeightBold : terminal.options.fontWeight; const fontStyle = isItalic ? 'italic' : ''; - return `${fontStyle} ${fontWeight} ${terminal.options.fontSize! * window.devicePixelRatio}px ${terminal.options.fontFamily}`; + return `${fontStyle} ${fontWeight} ${terminal.options.fontSize! * this._coreBrowserService.dpr}px ${terminal.options.fontFamily}`; } } diff --git a/addons/xterm-addon-webgl/src/renderLayer/CursorRenderLayer.ts b/addons/xterm-addon-webgl/src/renderLayer/CursorRenderLayer.ts index 58d2569017..2b274516da 100644 --- a/addons/xterm-addon-webgl/src/renderLayer/CursorRenderLayer.ts +++ b/addons/xterm-addon-webgl/src/renderLayer/CursorRenderLayer.ts @@ -38,10 +38,10 @@ export class CursorRenderLayer extends BaseRenderLayer { zIndex: number, colors: IColorSet, private _onRequestRefreshRowsEvent: IEventEmitter, - private readonly _coreBrowserService: ICoreBrowserService, + coreBrowserService: ICoreBrowserService, private readonly _coreService: ICoreService ) { - super(container, 'cursor', zIndex, true, colors); + super(container, 'cursor', zIndex, true, colors, coreBrowserService); this._state = { x: 0, y: 0, @@ -195,7 +195,7 @@ export class CursorRenderLayer extends BaseRenderLayer { private _clearCursor(): void { if (this._state) { // Avoid potential rounding errors when device pixel ratio is less than 1 - if (window.devicePixelRatio < 1) { + if (this._coreBrowserService.dpr < 1) { this._clearAll(); } else { this._clearCells(this._state.x, this._state.y, this._state.width, 1); @@ -257,10 +257,10 @@ class CursorBlinkStateManager { constructor( private _renderCallback: () => void, - coreBrowserService: ICoreBrowserService + private _coreBrowserService: ICoreBrowserService ) { this.isCursorVisible = true; - if (coreBrowserService.isFocused) { + if (this._coreBrowserService.isFocused) { this._restartInterval(); } } @@ -269,15 +269,15 @@ class CursorBlinkStateManager { public dispose(): void { if (this._blinkInterval) { - window.clearInterval(this._blinkInterval); + this._coreBrowserService.window.clearInterval(this._blinkInterval); this._blinkInterval = undefined; } if (this._blinkStartTimeout) { - window.clearTimeout(this._blinkStartTimeout); + this._coreBrowserService.window.clearTimeout(this._blinkStartTimeout); this._blinkStartTimeout = undefined; } if (this._animationFrame) { - window.cancelAnimationFrame(this._animationFrame); + this._coreBrowserService.window.cancelAnimationFrame(this._animationFrame); this._animationFrame = undefined; } } @@ -291,7 +291,7 @@ class CursorBlinkStateManager { // Force a cursor render to ensure it's visible and in the correct position this.isCursorVisible = true; if (!this._animationFrame) { - this._animationFrame = window.requestAnimationFrame(() => { + this._animationFrame = this._coreBrowserService.window.requestAnimationFrame(() => { this._renderCallback(); this._animationFrame = undefined; }); @@ -301,7 +301,7 @@ class CursorBlinkStateManager { private _restartInterval(timeToStart: number = BLINK_INTERVAL): void { // Clear any existing interval if (this._blinkInterval) { - window.clearInterval(this._blinkInterval); + this._coreBrowserService.window.clearInterval(this._blinkInterval); this._blinkInterval = undefined; } @@ -309,7 +309,7 @@ class CursorBlinkStateManager { // the regular interval is setup in order to support restarting the blink // animation in a lightweight way (without thrashing clearInterval and // setInterval). - this._blinkStartTimeout = window.setTimeout(() => { + this._blinkStartTimeout = this._coreBrowserService.window.setTimeout(() => { // Check if another animation restart was requested while this was being // started if (this._animationTimeRestarted) { @@ -323,13 +323,13 @@ class CursorBlinkStateManager { // Hide the cursor this.isCursorVisible = false; - this._animationFrame = window.requestAnimationFrame(() => { + this._animationFrame = this._coreBrowserService.window.requestAnimationFrame(() => { this._renderCallback(); this._animationFrame = undefined; }); // Setup the blink interval - this._blinkInterval = window.setInterval(() => { + this._blinkInterval = this._coreBrowserService.window.setInterval(() => { // Adjust the animation time if it was restarted if (this._animationTimeRestarted) { // calc time diff @@ -342,7 +342,7 @@ class CursorBlinkStateManager { // Invert visibility and render this.isCursorVisible = !this.isCursorVisible; - this._animationFrame = window.requestAnimationFrame(() => { + this._animationFrame = this._coreBrowserService.window.requestAnimationFrame(() => { this._renderCallback(); this._animationFrame = undefined; }); @@ -353,15 +353,15 @@ class CursorBlinkStateManager { public pause(): void { this.isCursorVisible = true; if (this._blinkInterval) { - window.clearInterval(this._blinkInterval); + this._coreBrowserService.window.clearInterval(this._blinkInterval); this._blinkInterval = undefined; } if (this._blinkStartTimeout) { - window.clearTimeout(this._blinkStartTimeout); + this._coreBrowserService.window.clearTimeout(this._blinkStartTimeout); this._blinkStartTimeout = undefined; } if (this._animationFrame) { - window.cancelAnimationFrame(this._animationFrame); + this._coreBrowserService.window.cancelAnimationFrame(this._animationFrame); this._animationFrame = undefined; } } diff --git a/addons/xterm-addon-webgl/src/renderLayer/LinkRenderLayer.ts b/addons/xterm-addon-webgl/src/renderLayer/LinkRenderLayer.ts index 440dbe7b30..e1ff08d814 100644 --- a/addons/xterm-addon-webgl/src/renderLayer/LinkRenderLayer.ts +++ b/addons/xterm-addon-webgl/src/renderLayer/LinkRenderLayer.ts @@ -9,12 +9,19 @@ import { INVERTED_DEFAULT_COLOR } from 'browser/renderer/Constants'; import { is256Color } from '../atlas/CharAtlasUtils'; import { ITerminal, IColorSet, ILinkifierEvent } from 'browser/Types'; import { IRenderDimensions } from 'browser/renderer/Types'; +import { ICoreBrowserService } from 'browser/services/Services'; export class LinkRenderLayer extends BaseRenderLayer { private _state: ILinkifierEvent | undefined; - constructor(container: HTMLElement, zIndex: number, colors: IColorSet, terminal: ITerminal) { - super(container, 'link', zIndex, true, colors); + constructor( + container: HTMLElement, + zIndex: number, + colors: IColorSet, + terminal: ITerminal, + coreBrowserService: ICoreBrowserService + ) { + super(container, 'link', zIndex, true, colors, coreBrowserService); terminal.linkifier2.onShowLinkUnderline(e => this._onShowLinkUnderline(e)); terminal.linkifier2.onHideLinkUnderline(e => this._onHideLinkUnderline(e)); diff --git a/src/browser/AccessibilityManager.ts b/src/browser/AccessibilityManager.ts index c162abcb74..eba283d54c 100644 --- a/src/browser/AccessibilityManager.ts +++ b/src/browser/AccessibilityManager.ts @@ -98,7 +98,7 @@ export class AccessibilityManager extends Disposable { this.register(this._terminal.onBlur(() => this._clearLiveRegion())); this.register(this._renderService.onDimensionsChange(() => this._refreshRowsDimensions())); - this._screenDprMonitor = new ScreenDprMonitor(); + 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 diff --git a/src/browser/RenderDebouncer.ts b/src/browser/RenderDebouncer.ts index ad2d79b45e..b3118d5f6c 100644 --- a/src/browser/RenderDebouncer.ts +++ b/src/browser/RenderDebouncer.ts @@ -16,13 +16,14 @@ export class RenderDebouncer implements IRenderDebouncerWithCallback { private _refreshCallbacks: FrameRequestCallback[] = []; constructor( + private _parentWindow: Window, private _renderCallback: (start: number, end: number) => void ) { } public dispose(): void { if (this._animationFrame) { - window.cancelAnimationFrame(this._animationFrame); + this._parentWindow.cancelAnimationFrame(this._animationFrame); this._animationFrame = undefined; } } @@ -30,7 +31,7 @@ export class RenderDebouncer implements IRenderDebouncerWithCallback { public addRefreshCallback(callback: FrameRequestCallback): number { this._refreshCallbacks.push(callback); if (!this._animationFrame) { - this._animationFrame = window.requestAnimationFrame(() => this._innerRefresh()); + this._animationFrame = this._parentWindow.requestAnimationFrame(() => this._innerRefresh()); } return this._animationFrame; } @@ -48,7 +49,7 @@ export class RenderDebouncer implements IRenderDebouncerWithCallback { return; } - this._animationFrame = window.requestAnimationFrame(() => this._innerRefresh()); + this._animationFrame = this._parentWindow.requestAnimationFrame(() => this._innerRefresh()); } private _innerRefresh(): void { diff --git a/src/browser/ScreenDprMonitor.ts b/src/browser/ScreenDprMonitor.ts index 27ae231f0b..8129da0768 100644 --- a/src/browser/ScreenDprMonitor.ts +++ b/src/browser/ScreenDprMonitor.ts @@ -18,11 +18,16 @@ export type ScreenDprListener = (newDevicePixelRatio?: number, oldDevicePixelRat * monitor with a different DPI. */ export class ScreenDprMonitor extends Disposable { - private _currentDevicePixelRatio: number = window.devicePixelRatio; + 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; + } + public setListener(listener: ScreenDprListener): void { if (this._listener) { this.clearListener(); @@ -32,7 +37,7 @@ export class ScreenDprMonitor extends Disposable { if (!this._listener) { return; } - this._listener(window.devicePixelRatio, this._currentDevicePixelRatio); + this._listener(this._parentWindow.devicePixelRatio, this._currentDevicePixelRatio); this._updateDpr(); }; this._updateDpr(); @@ -52,8 +57,8 @@ export class ScreenDprMonitor extends Disposable { this._resolutionMediaMatchList?.removeListener(this._outerListener); // Add listeners for new DPR - this._currentDevicePixelRatio = window.devicePixelRatio; - this._resolutionMediaMatchList = window.matchMedia(`screen and (resolution: ${window.devicePixelRatio}dppx)`); + this._currentDevicePixelRatio = this._parentWindow.devicePixelRatio; + this._resolutionMediaMatchList = this._parentWindow.matchMedia(`screen and (resolution: ${this._parentWindow.devicePixelRatio}dppx)`); this._resolutionMediaMatchList.addListener(this._outerListener); } diff --git a/src/browser/Terminal.ts b/src/browser/Terminal.ts index 9ad84fd761..9aba38186d 100644 --- a/src/browser/Terminal.ts +++ b/src/browser/Terminal.ts @@ -495,7 +495,7 @@ export class Terminal extends CoreTerminal implements ITerminal { this.register(addDisposableDomListener(this.textarea, 'blur', () => this._onTextAreaBlur())); this._helperContainer.appendChild(this.textarea); - this._coreBrowserService = this._instantiationService.createInstance(CoreBrowserService, this.textarea); + this._coreBrowserService = this._instantiationService.createInstance(CoreBrowserService, this.textarea, this._document.defaultView ?? window); this._instantiationService.setService(ICoreBrowserService, this._coreBrowserService); this._charSizeService = this._instantiationService.createInstance(CharSizeService, this._document, this._helperContainer); diff --git a/src/browser/TestUtils.test.ts b/src/browser/TestUtils.test.ts index bb7b440cdb..0b5e00c173 100644 --- a/src/browser/TestUtils.test.ts +++ b/src/browser/TestUtils.test.ts @@ -343,6 +343,10 @@ export class MockCompositionHelper implements ICompositionHelper { export class MockCoreBrowserService implements ICoreBrowserService { public serviceBrand: undefined; public isFocused: boolean = true; + public get window(): Window & typeof globalThis { + throw Error('Window object not available in tests'); + } + public dpr: number = 1; } export class MockCharSizeService implements ICharSizeService { diff --git a/src/browser/Viewport.ts b/src/browser/Viewport.ts index 6cff1f98c8..f95e0bb48d 100644 --- a/src/browser/Viewport.ts +++ b/src/browser/Viewport.ts @@ -6,7 +6,7 @@ import { Disposable } from 'common/Lifecycle'; import { addDisposableDomListener } from 'browser/Lifecycle'; import { IColorSet, IViewport } from 'browser/Types'; -import { ICharSizeService, IRenderService } from 'browser/services/Services'; +import { ICharSizeService, ICoreBrowserService, IRenderService } from 'browser/services/Services'; import { IBufferService, IOptionsService } from 'common/services/Services'; import { IBuffer } from 'common/buffer/Types'; import { IRenderDimensions } from 'browser/renderer/Types'; @@ -56,7 +56,8 @@ export class Viewport extends Disposable implements IViewport { @IBufferService private readonly _bufferService: IBufferService, @IOptionsService private readonly _optionsService: IOptionsService, @ICharSizeService private readonly _charSizeService: ICharSizeService, - @IRenderService private readonly _renderService: IRenderService + @IRenderService private readonly _renderService: IRenderService, + @ICoreBrowserService private readonly _coreBrowserService: ICoreBrowserService ) { super(); @@ -88,18 +89,18 @@ export class Viewport extends Disposable implements IViewport { if (immediate) { this._innerRefresh(); if (this._refreshAnimationFrame !== null) { - cancelAnimationFrame(this._refreshAnimationFrame); + this._coreBrowserService.window.cancelAnimationFrame(this._refreshAnimationFrame); } return; } if (this._refreshAnimationFrame === null) { - this._refreshAnimationFrame = requestAnimationFrame(() => this._innerRefresh()); + this._refreshAnimationFrame = this._coreBrowserService.window.requestAnimationFrame(() => this._innerRefresh()); } } private _innerRefresh(): void { if (this._charSizeService.height > 0) { - this._currentRowHeight = this._renderService.dimensions.scaledCellHeight / window.devicePixelRatio; + this._currentRowHeight = this._renderService.dimensions.scaledCellHeight / this._coreBrowserService.dpr; this._currentScaledCellHeight = this._renderService.dimensions.scaledCellHeight; this._lastRecordedViewportHeight = this._viewportElement.offsetHeight; const newBufferHeight = Math.round(this._currentRowHeight * this._lastRecordedBufferLength) + (this._lastRecordedViewportHeight - this._renderService.dimensions.canvasHeight); @@ -191,7 +192,7 @@ export class Viewport extends Disposable implements IViewport { // Continue or finish smooth scroll if (percent < 1) { - window.requestAnimationFrame(() => this._smoothScroll()); + this._coreBrowserService.window.requestAnimationFrame(() => this._smoothScroll()); } else { this._clearSmoothScrollState(); } diff --git a/src/browser/decorations/OverviewRulerRenderer.ts b/src/browser/decorations/OverviewRulerRenderer.ts index 7c284a616c..e7db50c55d 100644 --- a/src/browser/decorations/OverviewRulerRenderer.ts +++ b/src/browser/decorations/OverviewRulerRenderer.ts @@ -5,7 +5,7 @@ import { ColorZoneStore, IColorZone, IColorZoneStore } from 'browser/decorations/ColorZoneStore'; import { addDisposableDomListener } from 'browser/Lifecycle'; -import { IRenderService } from 'browser/services/Services'; +import { ICoreBrowserService, IRenderService } from 'browser/services/Services'; import { Disposable } from 'common/Lifecycle'; import { IBufferService, IDecorationService, IOptionsService } from 'common/services/Services'; @@ -51,7 +51,8 @@ export class OverviewRulerRenderer extends Disposable { @IBufferService private readonly _bufferService: IBufferService, @IDecorationService private readonly _decorationService: IDecorationService, @IRenderService private readonly _renderService: IRenderService, - @IOptionsService private readonly _optionsService: IOptionsService + @IOptionsService private readonly _optionsService: IOptionsService, + @ICoreBrowserService private readonly _coreBrowseService: ICoreBrowserService ) { super(); this._canvas = document.createElement('canvas'); @@ -112,7 +113,7 @@ export class OverviewRulerRenderer extends Disposable { } })); // device pixel ratio changed - this.register(addDisposableDomListener(window, 'resize', () => { + this.register(addDisposableDomListener(this._coreBrowseService.window, 'resize', () => { this._queueRefresh(true); })); // set the canvas dimensions @@ -142,11 +143,11 @@ export class OverviewRulerRenderer extends Disposable { } private _refreshDrawHeightConstants(): void { - drawHeight.full = Math.round(2 * window.devicePixelRatio); + drawHeight.full = Math.round(2 * this._coreBrowseService.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) * window.devicePixelRatio); + const nonFullHeight = Math.round(Math.max(Math.min(pixelsPerLine, 12), 6) * this._coreBrowseService.dpr); drawHeight.left = nonFullHeight; drawHeight.center = nonFullHeight; drawHeight.right = nonFullHeight; @@ -164,9 +165,9 @@ export class OverviewRulerRenderer extends Disposable { private _refreshCanvasDimensions(): void { this._canvas.style.width = `${this._width}px`; - this._canvas.width = Math.round(this._width * window.devicePixelRatio); + this._canvas.width = Math.round(this._width * this._coreBrowseService.dpr); this._canvas.style.height = `${this._screenElement.clientHeight}px`; - this._canvas.height = Math.round(this._screenElement.clientHeight * window.devicePixelRatio); + this._canvas.height = Math.round(this._screenElement.clientHeight * this._coreBrowseService.dpr); this._refreshDrawConstants(); this._refreshColorZonePadding(); } @@ -220,7 +221,7 @@ export class OverviewRulerRenderer extends Disposable { if (this._animationFrame !== undefined) { return; } - this._animationFrame = window.requestAnimationFrame(() => { + this._animationFrame = this._coreBrowseService.window.requestAnimationFrame(() => { this._refreshDecorations(); this._animationFrame = undefined; }); diff --git a/src/browser/renderer/CustomGlyphs.ts b/src/browser/renderer/CustomGlyphs.ts index 2f32f98122..4c3874c7a8 100644 --- a/src/browser/renderer/CustomGlyphs.ts +++ b/src/browser/renderer/CustomGlyphs.ts @@ -383,7 +383,8 @@ export function tryDrawCustomChar( yOffset: number, scaledCellWidth: number, scaledCellHeight: number, - fontSize: number + fontSize: number, + devicePixelRatio: number ): boolean { const blockElementDefinition = blockElementDefinitions[c]; if (blockElementDefinition) { @@ -399,13 +400,13 @@ export function tryDrawCustomChar( const boxDrawingDefinition = boxDrawingDefinitions[c]; if (boxDrawingDefinition) { - drawBoxDrawingChar(ctx, boxDrawingDefinition, xOffset, yOffset, scaledCellWidth, scaledCellHeight); + drawBoxDrawingChar(ctx, boxDrawingDefinition, xOffset, yOffset, scaledCellWidth, scaledCellHeight, devicePixelRatio); return true; } const powerlineDefinition = powerlineDefinitions[c]; if (powerlineDefinition) { - drawPowerlineChar(ctx, powerlineDefinition, xOffset, yOffset, scaledCellWidth, scaledCellHeight, fontSize); + drawPowerlineChar(ctx, powerlineDefinition, xOffset, yOffset, scaledCellWidth, scaledCellHeight, fontSize, devicePixelRatio); return true; } @@ -540,12 +541,13 @@ function drawBoxDrawingChar( xOffset: number, yOffset: number, scaledCellWidth: number, - scaledCellHeight: number + scaledCellHeight: number, + devicePixelRatio: number ): void { ctx.strokeStyle = ctx.fillStyle; for (const [fontWeight, instructions] of Object.entries(charDefinition)) { ctx.beginPath(); - ctx.lineWidth = window.devicePixelRatio * Number.parseInt(fontWeight); + ctx.lineWidth = devicePixelRatio * Number.parseInt(fontWeight); let actualInstructions: string; if (typeof instructions === 'function') { const xp = .15; @@ -565,7 +567,7 @@ function drawBoxDrawingChar( if (!args[0] || !args[1]) { continue; } - f(ctx, translateArgs(args, scaledCellWidth, scaledCellHeight, xOffset, yOffset, true)); + f(ctx, translateArgs(args, scaledCellWidth, scaledCellHeight, xOffset, yOffset, true, devicePixelRatio)); } ctx.stroke(); ctx.closePath(); @@ -579,12 +581,13 @@ function drawPowerlineChar( yOffset: number, scaledCellWidth: number, scaledCellHeight: number, - fontSize: number + fontSize: number, + devicePixelRatio: number ): void { ctx.beginPath(); // Scale the stroke with DPR and font size const cssLineWidth = fontSize / 12; - ctx.lineWidth = window.devicePixelRatio * cssLineWidth; + ctx.lineWidth = devicePixelRatio * cssLineWidth; for (const instruction of charDefinition.d.split(' ')) { const type = instruction[0]; const f = svgToCanvasInstructionMap[type]; @@ -626,7 +629,7 @@ const svgToCanvasInstructionMap: { [index: string]: any } = { 'M': (ctx: CanvasRenderingContext2D, args: number[]) => ctx.moveTo(args[0], args[1]) }; -function translateArgs(args: string[], cellWidth: number, cellHeight: number, xOffset: number, yOffset: number, doClamp: boolean, leftPadding: number = 0, rightPadding: number = 0): number[] { +function translateArgs(args: string[], cellWidth: number, cellHeight: number, xOffset: number, yOffset: number, doClamp: boolean, devicePixelRatio: number, leftPadding: number = 0, rightPadding: number = 0): number[] { const result = args.map(e => parseFloat(e) || parseInt(e)); if (result.length < 2) { @@ -635,14 +638,14 @@ function translateArgs(args: string[], cellWidth: number, cellHeight: number, xO for (let x = 0; x < result.length; x += 2) { // Translate from 0-1 to 0-cellWidth - result[x] *= cellWidth - (leftPadding * window.devicePixelRatio) - (rightPadding * window.devicePixelRatio); + result[x] *= cellWidth - (leftPadding * devicePixelRatio) - (rightPadding * devicePixelRatio); // Ensure coordinate doesn't escape cell bounds and round to the nearest 0.5 to ensure a crisp // line at 100% devicePixelRatio if (doClamp && result[x] !== 0) { result[x] = clamp(Math.round(result[x] + 0.5) - 0.5, cellWidth, 0); } // Apply the cell's offset (ie. x*cellWidth) - result[x] += xOffset + (leftPadding * window.devicePixelRatio); + result[x] += xOffset + (leftPadding * devicePixelRatio); } for (let y = 1; y < result.length; y += 2) { diff --git a/src/browser/renderer/DevicePixelObserver.ts b/src/browser/renderer/DevicePixelObserver.ts index 3aea61f616..38d40eeafe 100644 --- a/src/browser/renderer/DevicePixelObserver.ts +++ b/src/browser/renderer/DevicePixelObserver.ts @@ -6,12 +6,12 @@ import { toDisposable } from 'common/Lifecycle'; import { IDisposable } from 'common/Types'; -export function observeDevicePixelDimensions(element: HTMLElement, callback: (deviceWidth: number, deviceHeight: number) => void): IDisposable { +export function observeDevicePixelDimensions(element: HTMLElement, parentWindow: Window & typeof globalThis, callback: (deviceWidth: number, deviceHeight: number) => void): IDisposable { // Observe any resizes to the element and extract the actual pixel size of the element if the // devicePixelContentBoxSize API is supported. This allows correcting rounding errors when // converting between CSS pixels and device pixels which causes blurry rendering when device // pixel ratio is not a round number. - let observer: ResizeObserver | undefined = new ResizeObserver((entries) => { + let observer: ResizeObserver | undefined = new parentWindow.ResizeObserver((entries) => { const entry = entries.find((entry) => entry.target === element); if (!entry) { return; diff --git a/src/browser/renderer/dom/DomRenderer.ts b/src/browser/renderer/dom/DomRenderer.ts index ec9b835c6f..8df6b30286 100644 --- a/src/browser/renderer/dom/DomRenderer.ts +++ b/src/browser/renderer/dom/DomRenderer.ts @@ -8,7 +8,7 @@ import { BOLD_CLASS, ITALIC_CLASS, CURSOR_CLASS, CURSOR_STYLE_BLOCK_CLASS, CURSO import { INVERTED_DEFAULT_COLOR } from 'browser/renderer/Constants'; import { Disposable } from 'common/Lifecycle'; import { IColorSet, ILinkifierEvent, ILinkifier2 } from 'browser/Types'; -import { ICharSizeService } from 'browser/services/Services'; +import { ICharSizeService, ICoreBrowserService } from 'browser/services/Services'; import { IOptionsService, IBufferService, IInstantiationService } from 'common/services/Services'; import { EventEmitter, IEvent } from 'common/EventEmitter'; import { color } from 'common/Color'; @@ -51,7 +51,8 @@ export class DomRenderer extends Disposable implements IRenderer { @IInstantiationService instantiationService: IInstantiationService, @ICharSizeService private readonly _charSizeService: ICharSizeService, @IOptionsService private readonly _optionsService: IOptionsService, - @IBufferService private readonly _bufferService: IBufferService + @IBufferService private readonly _bufferService: IBufferService, + @ICoreBrowserService private readonly _coreBrowserService: ICoreBrowserService ) { super(); this._rowContainer = document.createElement('div'); @@ -101,16 +102,17 @@ export class DomRenderer extends Disposable implements IRenderer { } private _updateDimensions(): void { - this.dimensions.scaledCharWidth = this._charSizeService.width * window.devicePixelRatio; - this.dimensions.scaledCharHeight = Math.ceil(this._charSizeService.height * window.devicePixelRatio); + const dpr = this._coreBrowserService.dpr; + this.dimensions.scaledCharWidth = this._charSizeService.width * dpr; + this.dimensions.scaledCharHeight = Math.ceil(this._charSizeService.height * dpr); this.dimensions.scaledCellWidth = this.dimensions.scaledCharWidth + Math.round(this._optionsService.rawOptions.letterSpacing); this.dimensions.scaledCellHeight = Math.floor(this.dimensions.scaledCharHeight * this._optionsService.rawOptions.lineHeight); this.dimensions.scaledCharLeft = 0; this.dimensions.scaledCharTop = 0; this.dimensions.scaledCanvasWidth = this.dimensions.scaledCellWidth * this._bufferService.cols; this.dimensions.scaledCanvasHeight = this.dimensions.scaledCellHeight * this._bufferService.rows; - this.dimensions.canvasWidth = Math.round(this.dimensions.scaledCanvasWidth / window.devicePixelRatio); - this.dimensions.canvasHeight = Math.round(this.dimensions.scaledCanvasHeight / window.devicePixelRatio); + this.dimensions.canvasWidth = Math.round(this.dimensions.scaledCanvasWidth / dpr); + this.dimensions.canvasHeight = Math.round(this.dimensions.scaledCanvasHeight / dpr); this.dimensions.actualCellWidth = this.dimensions.canvasWidth / this._bufferService.cols; this.dimensions.actualCellHeight = this.dimensions.canvasHeight / this._bufferService.rows; diff --git a/src/browser/services/CoreBrowserService.ts b/src/browser/services/CoreBrowserService.ts index 4eabc895dc..6504e5e463 100644 --- a/src/browser/services/CoreBrowserService.ts +++ b/src/browser/services/CoreBrowserService.ts @@ -9,12 +9,17 @@ export class CoreBrowserService implements ICoreBrowserService { public serviceBrand: undefined; constructor( - private _textarea: HTMLTextAreaElement + private _textarea: HTMLTextAreaElement, + public readonly window: Window & typeof globalThis ) { } + public get dpr(): number { + return this.window.devicePixelRatio; + } + public get isFocused(): boolean { - const docOrShadowRoot = this._textarea.getRootNode ? this._textarea.getRootNode() as Document | ShadowRoot : document; - return docOrShadowRoot.activeElement === this._textarea && document.hasFocus(); + const docOrShadowRoot = this._textarea.getRootNode ? this._textarea.getRootNode() as Document | ShadowRoot : this._textarea.ownerDocument; + return docOrShadowRoot.activeElement === this._textarea && this._textarea.ownerDocument.hasFocus(); } } diff --git a/src/browser/services/RenderService.ts b/src/browser/services/RenderService.ts index 13cdaf9c12..7db0e5cd1f 100644 --- a/src/browser/services/RenderService.ts +++ b/src/browser/services/RenderService.ts @@ -11,7 +11,7 @@ import { ScreenDprMonitor } from 'browser/ScreenDprMonitor'; import { addDisposableDomListener } from 'browser/Lifecycle'; import { IColorSet, IRenderDebouncerWithCallback } from 'browser/Types'; import { IOptionsService, IBufferService, IDecorationService } from 'common/services/Services'; -import { ICharSizeService, IRenderService } from 'browser/services/Services'; +import { ICharSizeService, ICoreBrowserService, IRenderService } from 'browser/services/Services'; interface ISelectionState { start: [number, number] | undefined; @@ -55,16 +55,17 @@ export class RenderService extends Disposable implements IRenderService { @IOptionsService optionsService: IOptionsService, @ICharSizeService private readonly _charSizeService: ICharSizeService, @IDecorationService decorationService: IDecorationService, - @IBufferService bufferService: IBufferService + @IBufferService bufferService: IBufferService, + @ICoreBrowserService coreBrowserService: ICoreBrowserService ) { super(); this.register({ dispose: () => this._renderer.dispose() }); - this._renderDebouncer = new RenderDebouncer((start, end) => this._renderRows(start, end)); + this._renderDebouncer = new RenderDebouncer(coreBrowserService.window, (start, end) => this._renderRows(start, end)); this.register(this._renderDebouncer); - this._screenDprMonitor = new ScreenDprMonitor(); + this._screenDprMonitor = new ScreenDprMonitor(coreBrowserService.window); this._screenDprMonitor.setListener(() => this.onDevicePixelRatioChange()); this.register(this._screenDprMonitor); @@ -84,12 +85,12 @@ 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. - this.register(addDisposableDomListener(window, 'resize', () => this.onDevicePixelRatioChange())); + this.register(addDisposableDomListener(coreBrowserService.window, 'resize', () => this.onDevicePixelRatioChange())); // Detect whether IntersectionObserver is detected and enable renderer pause // and resume based on terminal visibility if so - if ('IntersectionObserver' in window) { - const observer = new IntersectionObserver(e => this._onIntersectionChange(e[e.length - 1]), { threshold: 0 }); + if ('IntersectionObserver' in coreBrowserService.window) { + const observer = new coreBrowserService.window.IntersectionObserver(e => this._onIntersectionChange(e[e.length - 1]), { threshold: 0 }); observer.observe(screenElement); this.register({ dispose: () => observer.disconnect() }); } diff --git a/src/browser/services/SelectionService.test.ts b/src/browser/services/SelectionService.test.ts index d829e39451..f8a15e51cf 100644 --- a/src/browser/services/SelectionService.test.ts +++ b/src/browser/services/SelectionService.test.ts @@ -10,7 +10,7 @@ import { IBufferLine } from 'common/Types'; import { MockBufferService, MockOptionsService, MockCoreService } from 'common/TestUtils.test'; import { BufferLine } from 'common/buffer/BufferLine'; import { IBufferService, IOptionsService } from 'common/services/Services'; -import { MockMouseService, MockRenderService } from 'browser/TestUtils.test'; +import { MockCoreBrowserService, MockMouseService, MockRenderService } from 'browser/TestUtils.test'; import { CellData } from 'common/buffer/CellData'; import { IBuffer } from 'common/buffer/Types'; import { IRenderService } from 'browser/services/Services'; @@ -21,7 +21,7 @@ class TestSelectionService extends SelectionService { optionsService: IOptionsService, renderService: IRenderService ) { - super(null!, null!, null!, bufferService, new MockCoreService(), new MockMouseService(), optionsService, renderService); + super(null!, null!, null!, bufferService, new MockCoreService(), new MockMouseService(), optionsService, renderService, new MockCoreBrowserService()); } public get model(): SelectionModel { return this._model; } diff --git a/src/browser/services/SelectionService.ts b/src/browser/services/SelectionService.ts index aad2c4664a..4ee1ffa1fc 100644 --- a/src/browser/services/SelectionService.ts +++ b/src/browser/services/SelectionService.ts @@ -10,7 +10,7 @@ import * as Browser from 'common/Platform'; import { SelectionModel } from 'browser/selection/SelectionModel'; import { CellData } from 'common/buffer/CellData'; import { EventEmitter, IEvent } from 'common/EventEmitter'; -import { IMouseService, ISelectionService, IRenderService } from 'browser/services/Services'; +import { IMouseService, ISelectionService, IRenderService, ICoreBrowserService } from 'browser/services/Services'; import { IBufferRange, ILinkifier2 } from 'browser/Types'; import { IBufferService, IOptionsService, ICoreService } from 'common/services/Services'; import { getCoordsRelativeToElement } from 'browser/input/Mouse'; @@ -128,7 +128,8 @@ export class SelectionService extends Disposable implements ISelectionService { @ICoreService private readonly _coreService: ICoreService, @IMouseService private readonly _mouseService: IMouseService, @IOptionsService private readonly _optionsService: IOptionsService, - @IRenderService private readonly _renderService: IRenderService + @IRenderService private readonly _renderService: IRenderService, + @ICoreBrowserService private readonly _coreBrowserService: ICoreBrowserService ) { super(); @@ -270,7 +271,7 @@ export class SelectionService extends Disposable implements ISelectionService { public refresh(isLinuxMouseSelection?: boolean): void { // Queue the refresh for the renderer if (!this._refreshAnimationFrame) { - this._refreshAnimationFrame = window.requestAnimationFrame(() => this._refresh()); + this._refreshAnimationFrame = this._coreBrowserService.window.requestAnimationFrame(() => this._refresh()); } // If the platform is Linux and the refresh call comes from a mouse event, @@ -406,7 +407,7 @@ export class SelectionService extends Disposable implements ISelectionService { * @param event The mouse event. */ private _getMouseEventScrollAmount(event: MouseEvent): number { - let offset = getCoordsRelativeToElement(window, event, this._screenElement)[1]; + let offset = getCoordsRelativeToElement(this._coreBrowserService.window, event, this._screenElement)[1]; const terminalHeight = this._renderService.dimensions.canvasHeight; if (offset >= 0 && offset <= terminalHeight) { return 0; @@ -491,7 +492,7 @@ export class SelectionService extends Disposable implements ISelectionService { this._screenElement.ownerDocument.addEventListener('mousemove', this._mouseMoveListener); this._screenElement.ownerDocument.addEventListener('mouseup', this._mouseUpListener); } - this._dragScrollIntervalTimer = window.setInterval(() => this._dragScroll(), DRAG_SCROLL_INTERVAL); + this._dragScrollIntervalTimer = this._coreBrowserService.window.setInterval(() => this._dragScroll(), DRAG_SCROLL_INTERVAL); } /** @@ -502,7 +503,7 @@ export class SelectionService extends Disposable implements ISelectionService { this._screenElement.ownerDocument.removeEventListener('mousemove', this._mouseMoveListener); this._screenElement.ownerDocument.removeEventListener('mouseup', this._mouseUpListener); } - clearInterval(this._dragScrollIntervalTimer); + this._coreBrowserService.window.clearInterval(this._dragScrollIntervalTimer); this._dragScrollIntervalTimer = undefined; } diff --git a/src/browser/services/Services.ts b/src/browser/services/Services.ts index 31167faf32..ab91f8a32f 100644 --- a/src/browser/services/Services.ts +++ b/src/browser/services/Services.ts @@ -28,6 +28,16 @@ export interface ICoreBrowserService { serviceBrand: undefined; readonly isFocused: boolean; + /** + * Parent window that the terminal is rendered into. DOM and rendering APIs + * (e.g. requestAnimationFrame) should be invoked in the context of this + * window. + */ + readonly window: Window & typeof globalThis; + /** + * Helper for getting the devicePixelRatio of the parent window. + */ + readonly dpr: number; } export const IMouseService = createDecorator('MouseService');