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
+14 -11
View File
@@ -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) {
+2 -2
View File
@@ -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;
+8 -6
View File
@@ -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;