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
152 lines
5.3 KiB
TypeScript
152 lines
5.3 KiB
TypeScript
/**
|
|
* Copyright (c) 2017 The xterm.js authors. All rights reserved.
|
|
* @license MIT
|
|
*/
|
|
|
|
import { IRenderDimensions } from 'browser/renderer/Types';
|
|
import { BaseRenderLayer } from './BaseRenderLayer';
|
|
import { IColorSet } from 'browser/Types';
|
|
import { IBufferService, IDecorationService, IOptionsService } from 'common/services/Services';
|
|
import { ICoreBrowserService } from 'browser/services/Services';
|
|
|
|
interface ISelectionState {
|
|
start?: [number, number];
|
|
end?: [number, number];
|
|
columnSelectMode?: boolean;
|
|
ydisp?: number;
|
|
}
|
|
|
|
export class SelectionRenderLayer extends BaseRenderLayer {
|
|
private _state!: ISelectionState;
|
|
|
|
constructor(
|
|
container: HTMLElement,
|
|
zIndex: number,
|
|
colors: IColorSet,
|
|
rendererId: number,
|
|
bufferService: IBufferService,
|
|
coreBrowserService: ICoreBrowserService,
|
|
decorationService: IDecorationService,
|
|
optionsService: IOptionsService
|
|
) {
|
|
super(container, 'selection', zIndex, true, colors, rendererId, bufferService, optionsService, decorationService, coreBrowserService);
|
|
this._clearState();
|
|
}
|
|
|
|
private _clearState(): void {
|
|
this._state = {
|
|
start: undefined,
|
|
end: undefined,
|
|
columnSelectMode: undefined,
|
|
ydisp: undefined
|
|
};
|
|
}
|
|
|
|
public resize(dim: IRenderDimensions): void {
|
|
super.resize(dim);
|
|
// On resize use the base render layer's cached selection values since resize clears _state
|
|
// inside reset.
|
|
if (this._selectionStart && this._selectionEnd) {
|
|
this._redrawSelection(this._selectionStart, this._selectionEnd, this._columnSelectMode);
|
|
}
|
|
}
|
|
|
|
public reset(): void {
|
|
if (this._state.start && this._state.end) {
|
|
this._clearState();
|
|
this._clearAll();
|
|
}
|
|
}
|
|
|
|
public onBlur(): void {
|
|
this.reset();
|
|
this._redrawSelection(this._selectionStart, this._selectionEnd, this._columnSelectMode);
|
|
}
|
|
|
|
public onFocus(): void {
|
|
this.reset();
|
|
this._redrawSelection(this._selectionStart, this._selectionEnd, this._columnSelectMode);
|
|
}
|
|
|
|
public onSelectionChanged(start: [number, number] | undefined, end: [number, number] | undefined, columnSelectMode: boolean): void {
|
|
super.onSelectionChanged(start, end, columnSelectMode);
|
|
this._redrawSelection(start, end, columnSelectMode);
|
|
}
|
|
|
|
private _redrawSelection(start: [number, number] | undefined, end: [number, number] | undefined, columnSelectMode: boolean): void {
|
|
// Selection has not changed
|
|
if (!this._didStateChange(start, end, columnSelectMode, this._bufferService.buffer.ydisp)) {
|
|
return;
|
|
}
|
|
|
|
// Remove all selections
|
|
this._clearAll();
|
|
|
|
// Selection does not exist
|
|
if (!start || !end) {
|
|
this._clearState();
|
|
return;
|
|
}
|
|
|
|
// Translate from buffer position to viewport position
|
|
const viewportStartRow = start[1] - this._bufferService.buffer.ydisp;
|
|
const viewportEndRow = end[1] - this._bufferService.buffer.ydisp;
|
|
const viewportCappedStartRow = Math.max(viewportStartRow, 0);
|
|
const viewportCappedEndRow = Math.min(viewportEndRow, this._bufferService.rows - 1);
|
|
|
|
// No need to draw the selection
|
|
if (viewportCappedStartRow >= this._bufferService.rows || viewportCappedEndRow < 0) {
|
|
this._state.ydisp = this._bufferService.buffer.ydisp;
|
|
return;
|
|
}
|
|
|
|
this._ctx.fillStyle = (this._coreBrowserService.isFocused
|
|
? this._colors.selectionBackgroundTransparent
|
|
: this._colors.selectionInactiveBackgroundTransparent).css;
|
|
|
|
if (columnSelectMode) {
|
|
const startCol = start[0];
|
|
const width = end[0] - startCol;
|
|
const height = viewportCappedEndRow - viewportCappedStartRow + 1;
|
|
this._fillCells(startCol, viewportCappedStartRow, width, height);
|
|
} else {
|
|
// Draw first row
|
|
const startCol = viewportStartRow === viewportCappedStartRow ? start[0] : 0;
|
|
const startRowEndCol = viewportCappedStartRow === viewportEndRow ? end[0] : this._bufferService.cols;
|
|
this._fillCells(startCol, viewportCappedStartRow, startRowEndCol - startCol, 1);
|
|
|
|
// Draw middle rows
|
|
const middleRowsCount = Math.max(viewportCappedEndRow - viewportCappedStartRow - 1, 0);
|
|
this._fillCells(0, viewportCappedStartRow + 1, this._bufferService.cols, middleRowsCount);
|
|
|
|
// Draw final row
|
|
if (viewportCappedStartRow !== viewportCappedEndRow) {
|
|
// Only draw viewportEndRow if it's not the same as viewportStartRow
|
|
const endCol = viewportEndRow === viewportCappedEndRow ? end[0] : this._bufferService.cols;
|
|
this._fillCells(0, viewportCappedEndRow, endCol, 1);
|
|
}
|
|
}
|
|
|
|
// Save state for next render
|
|
this._state.start = [start[0], start[1]];
|
|
this._state.end = [end[0], end[1]];
|
|
this._state.columnSelectMode = columnSelectMode;
|
|
this._state.ydisp = this._bufferService.buffer.ydisp;
|
|
}
|
|
|
|
private _didStateChange(start: [number, number] | undefined, end: [number, number] | undefined, columnSelectMode: boolean, ydisp: number): boolean {
|
|
return !this._areCoordinatesEqual(start, this._state.start) ||
|
|
!this._areCoordinatesEqual(end, this._state.end) ||
|
|
columnSelectMode !== this._state.columnSelectMode ||
|
|
ydisp !== this._state.ydisp;
|
|
}
|
|
|
|
private _areCoordinatesEqual(coord1: [number, number] | undefined, coord2: [number, number] | undefined): boolean {
|
|
if (!coord1 || !coord2) {
|
|
return false;
|
|
}
|
|
|
|
return coord1[0] === coord2[0] && coord1[1] === coord2[1];
|
|
}
|
|
}
|