Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fix a bunch of memory retention problems #4185

Merged
merged 11 commits into from Oct 8, 2022
12 changes: 7 additions & 5 deletions addons/xterm-addon-canvas/src/BaseRenderLayer.ts
Expand Up @@ -19,8 +19,9 @@ import { ICellData } from 'common/Types';
import { Terminal } from 'xterm';
import { IRenderLayer } from './Types';
import { CellColorResolver } from 'browser/renderer/shared/CellColorResolver';
import { Disposable, toDisposable } from 'common/Lifecycle';

export abstract class BaseRenderLayer implements IRenderLayer {
export abstract class BaseRenderLayer extends Disposable implements IRenderLayer {
private _canvas: HTMLCanvasElement;
protected _ctx!: CanvasRenderingContext2D;
private _scaledCharWidth: number = 0;
Expand Down Expand Up @@ -51,18 +52,19 @@ export abstract class BaseRenderLayer implements IRenderLayer {
protected readonly _decorationService: IDecorationService,
protected readonly _coreBrowserService: ICoreBrowserService
) {
super();
this._cellColorResolver = new CellColorResolver(this._terminal, this._colors, this._selectionModel, this._decorationService, this._coreBrowserService);
this._canvas = document.createElement('canvas');
this._canvas.classList.add(`xterm-${id}-layer`);
this._canvas.style.zIndex = zIndex.toString();
this._initCanvas();
this._container.appendChild(this._canvas);
this._refreshCharAtlas(this._colors);
}

public dispose(): void {
removeElementFromParent(this._canvas);
this._charAtlas?.dispose();
this.register(toDisposable(() => {
removeElementFromParent(this._canvas);
this._charAtlas?.dispose();
}));
}

private _initCanvas(): void {
Expand Down
35 changes: 17 additions & 18 deletions addons/xterm-addon-canvas/src/CanvasAddon.ts
Expand Up @@ -9,20 +9,26 @@ import { CanvasRenderer } from './CanvasRenderer';
import { IBufferService, ICoreService, IDecorationService, IOptionsService } from 'common/services/Services';
import { ITerminalAddon, Terminal } from 'xterm';
import { EventEmitter, forwardEvent } from 'common/EventEmitter';
import { Disposable, toDisposable } from 'common/Lifecycle';

export class CanvasAddon implements ITerminalAddon {
export class CanvasAddon extends Disposable implements ITerminalAddon {
private _terminal?: Terminal;
private _renderer?: CanvasRenderer;

private readonly _onChangeTextureAtlas = new EventEmitter<HTMLCanvasElement>();
private readonly _onChangeTextureAtlas = this.register(new EventEmitter<HTMLCanvasElement>());
public readonly onChangeTextureAtlas = this._onChangeTextureAtlas.event;

public get textureAtlas(): HTMLCanvasElement | undefined {
return this._renderer?.textureAtlas;
}

public activate(terminal: Terminal): void {
const core = (terminal as any)._core;
if (!terminal.element) {
core.onWillOpen(() => this.activate(terminal));
this.register(core.onWillOpen(() => this.activate(terminal)));
return;
}

this._terminal = terminal;
const bufferService: IBufferService = core._bufferService;
const renderService: IRenderService = core._renderService;
Expand All @@ -35,24 +41,17 @@ export class CanvasAddon implements ITerminalAddon {
const colors: IColorSet = core._colorManager.colors;
const screenElement: HTMLElement = core.screenElement;
const linkifier = core.linkifier2;

this._renderer = new CanvasRenderer(terminal, colors, screenElement, linkifier, bufferService, charSizeService, optionsService, characterJoinerService, coreService, coreBrowserService, decorationService);
forwardEvent(this._renderer.onChangeTextureAtlas, this._onChangeTextureAtlas);
this.register(forwardEvent(this._renderer.onChangeTextureAtlas, this._onChangeTextureAtlas));
renderService.setRenderer(this._renderer);
renderService.onResize(bufferService.cols, bufferService.rows);
}

public dispose(): void {
if (!this._terminal) {
throw new Error('Cannot dispose CanvasAddon because it is activated');
}
const renderService: IRenderService = (this._terminal as any)._core._renderService;
renderService.setRenderer((this._terminal as any)._core._createRenderer());
renderService.onResize(this._terminal.cols, this._terminal.rows);
this._renderer?.dispose();
this._renderer = undefined;
}

public get textureAtlas(): HTMLCanvasElement | undefined {
return this._renderer?.textureAtlas;
this.register(toDisposable(() => {
renderService.setRenderer((this._terminal as any)._core._createRenderer());
renderService.onResize(terminal.cols, terminal.rows);
this._renderer?.dispose();
this._renderer = undefined;
}));
}
}
19 changes: 9 additions & 10 deletions addons/xterm-addon-canvas/src/CanvasRenderer.ts
Expand Up @@ -9,7 +9,7 @@ import { IRenderDimensions, IRenderer, IRequestRedrawEvent } from 'browser/rende
import { ICharacterJoinerService, ICharSizeService, ICoreBrowserService } from 'browser/services/Services';
import { IColorSet, ILinkifier2 } from 'browser/Types';
import { EventEmitter } from 'common/EventEmitter';
import { Disposable } from 'common/Lifecycle';
import { Disposable, toDisposable } from 'common/Lifecycle';
import { IBufferService, ICoreService, IDecorationService, IOptionsService } from 'common/services/Services';
import { Terminal } from 'xterm';
import { CursorRenderLayer } from './CursorRenderLayer';
Expand All @@ -24,9 +24,9 @@ export class CanvasRenderer extends Disposable implements IRenderer {

public dimensions: IRenderDimensions;

private readonly _onRequestRedraw = new EventEmitter<IRequestRedrawEvent>();
private readonly _onRequestRedraw = this.register(new EventEmitter<IRequestRedrawEvent>());
public readonly onRequestRedraw = this._onRequestRedraw.event;
private readonly _onChangeTextureAtlas = new EventEmitter<HTMLCanvasElement>();
private readonly _onChangeTextureAtlas = this.register(new EventEmitter<HTMLCanvasElement>());
public readonly onChangeTextureAtlas = this._onChangeTextureAtlas.event;

constructor(
Expand Down Expand Up @@ -70,14 +70,13 @@ export class CanvasRenderer extends Disposable implements IRenderer {
this.register(observeDevicePixelDimensions(this._renderLayers[0].canvas, this._coreBrowserService.window, (w, h) => this._setCanvasDevicePixelDimensions(w, h)));

this.onOptionsChanged();
}

public dispose(): void {
for (const l of this._renderLayers) {
l.dispose();
}
super.dispose();
removeTerminalFromCache(this._terminal);
this.register(toDisposable(() => {
for (const l of this._renderLayers) {
l.dispose();
}
removeTerminalFromCache(this._terminal);
}));
}

public get textureAtlas(): HTMLCanvasElement | undefined {
Expand Down
11 changes: 4 additions & 7 deletions addons/xterm-addon-canvas/src/CursorRenderLayer.ts
Expand Up @@ -12,6 +12,7 @@ import { IBufferService, IOptionsService, ICoreService, IDecorationService } fro
import { IEventEmitter } from 'common/EventEmitter';
import { ICoreBrowserService } from 'browser/services/Services';
import { Terminal } from 'xterm';
import { toDisposable } from 'common/Lifecycle';

interface ICursorState {
x: number;
Expand Down Expand Up @@ -57,14 +58,10 @@ export class CursorRenderLayer extends BaseRenderLayer {
'block': this._renderBlockCursor.bind(this),
'underline': this._renderUnderlineCursor.bind(this)
};
}

public dispose(): void {
if (this._cursorBlinkStateManager) {
this._cursorBlinkStateManager.dispose();
this.register(toDisposable(() => {
this._cursorBlinkStateManager?.dispose();
this._cursorBlinkStateManager = undefined;
}
super.dispose();
}));
}

public resize(dim: IRenderDimensions): void {
Expand Down
4 changes: 2 additions & 2 deletions addons/xterm-addon-canvas/src/LinkRenderLayer.ts
Expand Up @@ -28,8 +28,8 @@ export class LinkRenderLayer extends BaseRenderLayer {
) {
super(terminal, container, 'link', zIndex, true, colors, bufferService, optionsService, decorationService, coreBrowserService);

linkifier2.onShowLinkUnderline(e => this._onShowLinkUnderline(e));
linkifier2.onHideLinkUnderline(e => this._onHideLinkUnderline(e));
this.register(linkifier2.onShowLinkUnderline(e => this._onShowLinkUnderline(e)));
this.register(linkifier2.onHideLinkUnderline(e => this._onHideLinkUnderline(e)));
}

public resize(dim: IRenderDimensions): void {
Expand Down
15 changes: 0 additions & 15 deletions addons/xterm-addon-canvas/src/TextRenderLayer.ts
Expand Up @@ -293,19 +293,4 @@ export class TextRenderLayer extends BaseRenderLayer {
this._characterOverlapCache[chars] = overlaps;
return overlaps;
}

/**
* Clear the charcater at the cell specified.
* @param x The column of the char.
* @param y The row of the char.
*/
// private _clearChar(x: number, y: number): void {
// let colsToClear = 1;
// // Clear the adjacent character if it was wide
// const state = this._state.cache[x][y];
// if (state && state[CHAR_DATA_WIDTH_INDEX] === 2) {
// colsToClear = 2;
// }
// this.clearCells(x, y, colsToClear, 1);
// }
}
1 change: 0 additions & 1 deletion addons/xterm-addon-canvas/src/Types.d.ts
Expand Up @@ -41,7 +41,6 @@ export interface IRenderer extends IDisposable {
*/
readonly onRequestRedraw: IEvent<IRequestRedrawEvent>;

dispose(): void;
setColors(colors: IColorSet): void;
onDevicePixelRatioChange(): void;
onResize(cols: number, rows: number): void;
Expand Down
20 changes: 10 additions & 10 deletions addons/xterm-addon-search/src/SearchAddon.ts
Expand Up @@ -5,6 +5,7 @@

import { Terminal, IDisposable, ITerminalAddon, IBufferRange, IDecoration } from 'xterm';
import { EventEmitter } from 'common/EventEmitter';
import { Disposable, toDisposable } from 'common/Lifecycle';

export interface ISearchOptions {
regex?: boolean;
Expand Down Expand Up @@ -50,7 +51,7 @@ type LineCacheEntry = [
const NON_WORD_CHARACTERS = ' ~!@#$%^&*()+`-=[]{}|\\;:"\',./<>?';
const LINES_CACHE_TIME_TO_LIVE = 15 * 1000; // 15 secs

export class SearchAddon implements ITerminalAddon {
export class SearchAddon extends Disposable implements ITerminalAddon {
private _terminal: Terminal | undefined;
private _cachedSearchTerm: string | undefined;
private _selectedDecoration: IDecoration | undefined;
Expand All @@ -72,13 +73,18 @@ export class SearchAddon implements ITerminalAddon {

private _resultIndex: number | undefined;

private readonly _onDidChangeResults = new EventEmitter<{ resultIndex: number, resultCount: number } | undefined>();
private readonly _onDidChangeResults = this.register(new EventEmitter<{ resultIndex: number, resultCount: number } | undefined>());
public readonly onDidChangeResults = this._onDidChangeResults.event;

public activate(terminal: Terminal): void {
this._terminal = terminal;
this._onDataDisposable = this._terminal.onWriteParsed(() => this._updateMatches());
this._onResizeDisposable = this._terminal.onResize(() => this._updateMatches());
this._onDataDisposable = this.register(this._terminal.onWriteParsed(() => this._updateMatches()));
this._onResizeDisposable = this.register(this._terminal.onResize(() => this._updateMatches()));
this.register(toDisposable(() => {
this.clearDecorations();
this._onDataDisposable?.dispose();
this._onResizeDisposable?.dispose();
}));
}

private _updateMatches(): void {
Expand All @@ -94,12 +100,6 @@ export class SearchAddon implements ITerminalAddon {
}
}

public dispose(): void {
this.clearDecorations();
this._onDataDisposable?.dispose();
this._onResizeDisposable?.dispose();
}

public clearDecorations(retainCachedSearchTerm?: boolean): void {
this._selectedDecoration?.dispose();
this._searchResults?.clear();
Expand Down
3 changes: 1 addition & 2 deletions addons/xterm-addon-webgl/src/GlyphRenderer.ts
Expand Up @@ -76,7 +76,7 @@ let $glyph: IRasterizedGlyph | undefined = undefined;
let $leftCellPadding = 0;
let $clippedPixels = 0;

export class GlyphRenderer extends Disposable {
export class GlyphRenderer extends Disposable {
private _atlas: ITextureAtlas | undefined;

private _program: WebGLProgram;
Expand All @@ -99,7 +99,6 @@ export class GlyphRenderer extends Disposable {

constructor(
private _terminal: Terminal,
private _colors: IColorSet,
private _gl: IWebGL2RenderingContext,
private _dimensions: IRenderDimensions
) {
Expand Down
40 changes: 19 additions & 21 deletions addons/xterm-addon-webgl/src/WebglAddon.ts
Expand Up @@ -10,51 +10,49 @@ import { IColorSet } from 'browser/Types';
import { EventEmitter, forwardEvent } from 'common/EventEmitter';
import { isSafari } from 'common/Platform';
import { ICoreService, IDecorationService } from 'common/services/Services';
import { Disposable, toDisposable } from 'common/Lifecycle';

export class WebglAddon implements ITerminalAddon {
export class WebglAddon extends Disposable implements ITerminalAddon {
private _terminal?: Terminal;
private _renderer?: WebglRenderer;

private readonly _onChangeTextureAtlas = new EventEmitter<HTMLElement>();
private readonly _onChangeTextureAtlas = this.register(new EventEmitter<HTMLElement>());
public readonly onChangeTextureAtlas = this._onChangeTextureAtlas.event;
private readonly _onContextLoss = new EventEmitter<void>();
private readonly _onContextLoss = this.register(new EventEmitter<void>());
public readonly onContextLoss = this._onContextLoss.event;

constructor(
private _preserveDrawingBuffer?: boolean
) {}
) {
super();
}

public activate(terminal: Terminal): void {
if (isSafari) {
throw new Error('Webgl is not currently supported on Safari');
}
const core = (terminal as any)._core;
if (!terminal.element) {
core.onWillOpen(() => this.activate(terminal));
this.register(core.onWillOpen(() => this.activate(terminal)));
return;
}
if (isSafari) {
throw new Error('Webgl is not currently supported on Safari');
}
this._terminal = terminal;
const renderService: IRenderService = core._renderService;
const characterJoinerService: ICharacterJoinerService = core._characterJoinerService;
const coreBrowserService: ICoreBrowserService = core._coreBrowserService;
const coreService: ICoreService = core.coreService;
const decorationService: IDecorationService = core._decorationService;
const colors: IColorSet = core._colorManager.colors;
this._renderer = new WebglRenderer(terminal, colors, characterJoinerService, coreBrowserService, coreService, decorationService, this._preserveDrawingBuffer);
forwardEvent(this._renderer.onContextLoss, this._onContextLoss);
forwardEvent(this._renderer.onChangeTextureAtlas, this._onChangeTextureAtlas);
this._renderer = this.register(new WebglRenderer(terminal, colors, characterJoinerService, coreBrowserService, coreService, decorationService, this._preserveDrawingBuffer));
this.register(forwardEvent(this._renderer.onContextLoss, this._onContextLoss));
this.register(forwardEvent(this._renderer.onChangeTextureAtlas, this._onChangeTextureAtlas));
renderService.setRenderer(this._renderer);
}

public dispose(): void {
if (!this._terminal) {
throw new Error('Cannot dispose WebglAddon because it is activated');
}
const renderService: IRenderService = (this._terminal as any)._core._renderService;
renderService.setRenderer((this._terminal as any)._core._createRenderer());
renderService.onResize(this._terminal.cols, this._terminal.rows);
this._renderer?.dispose();
this._renderer = undefined;
this.register(toDisposable(() => {
const renderService: IRenderService = (this._terminal as any)._core._renderService;
renderService.setRenderer((this._terminal as any)._core._createRenderer());
renderService.onResize(terminal.cols, terminal.rows);
}));
}

public get textureAtlas(): HTMLCanvasElement | undefined {
Expand Down