Make terminal rendering work in popout windows

Add support for the parent element (what is passed to Terminal.open)
being in a different (same origin) window. Accesses to DOM APIs such
as requestAnimationFrame and devicePixelRatio thus need to be scoped
to the corrent window, instead of assuming that it's the same as the
window/global scope where the code is running.

This is done by inferring a parent window at the creation time, and
then storing it in CoreBrowserService, which is already passed to most
places that need it.

To catch future regressions, an ESLint rule that checks for global
accesses is added (it uses AST selectors via the no-restricted-syntax
rule).

This should also be applicable when the parent element is an iframe.

Fixes #3758
This commit is contained in:
Mihai Parparita
2022-09-08 16:59:31 -07:00
parent 2935d9feb7
commit a39a468f19
31 changed files with 255 additions and 176 deletions
+23
View File
@@ -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",
@@ -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 {
+14 -13
View File
@@ -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;
}
@@ -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;
}
}
@@ -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));
@@ -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();
}
@@ -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<CharData>();
}
@@ -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());
@@ -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++) {
@@ -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<ITerminalOptions>, colors: IColorSet): ICharAtlasConfig {
export function generateConfig(scaledCharWidth: number, scaledCharHeight: number, options: Required<ITerminalOptions>, 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,
@@ -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;
@@ -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++) {
@@ -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,
@@ -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;
@@ -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}`;
}
}
@@ -38,10 +38,10 @@ export class CursorRenderLayer extends BaseRenderLayer {
zIndex: number,
colors: IColorSet,
private _onRequestRefreshRowsEvent: IEventEmitter<IRequestRedrawEvent>,
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;
}
}
@@ -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));
+1 -1
View File
@@ -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
+4 -3
View File
@@ -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 {
+9 -4
View File
@@ -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);
}
+1 -1
View File
@@ -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);

Some files were not shown because too many files have changed in this diff Show More