Skip to content

Commit 31d3ae4

Browse files
authored
Merge pull request #2899 from Tyriar/memoryleak
Fix listener leaks causing Terminal memory from not getting freed
2 parents 66b2c01 + e413133 commit 31d3ae4

File tree

16 files changed

+106
-52
lines changed

16 files changed

+106
-52
lines changed

demo/client.ts

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -112,9 +112,15 @@ const disposeRecreateButtonHandler = () => {
112112
term = null;
113113
window.term = null;
114114
socket = null;
115+
addons.attach.instance = undefined;
116+
addons.fit.instance = undefined;
117+
addons.search.instance = undefined;
118+
addons.serialize.instance = undefined;
119+
addons.unicode11.instance = undefined;
120+
addons['web-links'].instance = undefined;
121+
addons.webgl.instance = undefined;
115122
document.getElementById('dispose').innerHTML = 'Recreate Terminal';
116-
}
117-
else {
123+
} else {
118124
createTerminal();
119125
document.getElementById('dispose').innerHTML = 'Dispose terminal';
120126
}
@@ -357,7 +363,7 @@ function initAddons(term: TerminalType): void {
357363
if (!addon.canChange) {
358364
checkbox.disabled = true;
359365
}
360-
checkbox.addEventListener('change', () => {
366+
addDomListener(checkbox, 'change', () => {
361367
if (checkbox.checked) {
362368
addon.instance = new addon.ctor();
363369
term.loadAddon(addon.instance);

src/browser/Linkifier2.ts

Lines changed: 11 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -8,13 +8,15 @@ import { IDisposable } from 'common/Types';
88
import { IMouseService, IRenderService } from './services/Services';
99
import { IBufferService } from 'common/services/Services';
1010
import { EventEmitter, IEvent } from 'common/EventEmitter';
11+
import { Disposable, getDisposeArrayDisposable } from 'common/Lifecycle';
12+
import { addDisposableDomListener } from 'browser/Lifecycle';
1113

1214
interface ILinkState {
1315
decorations: ILinkDecorations;
1416
isHovered: boolean;
1517
}
1618

17-
export class Linkifier2 implements ILinkifier2 {
19+
export class Linkifier2 extends Disposable implements ILinkifier2 {
1820
private _element: HTMLElement | undefined;
1921
private _mouseService: IMouseService | undefined;
2022
private _renderService: IRenderService | undefined;
@@ -26,15 +28,16 @@ export class Linkifier2 implements ILinkifier2 {
2628
private _lastBufferCell: IBufferCellPosition | undefined;
2729
private _isMouseOut: boolean = true;
2830

29-
private _onShowLinkUnderline = new EventEmitter<ILinkifierEvent>();
31+
private _onShowLinkUnderline = this.register(new EventEmitter<ILinkifierEvent>());
3032
public get onShowLinkUnderline(): IEvent<ILinkifierEvent> { return this._onShowLinkUnderline.event; }
31-
private _onHideLinkUnderline = new EventEmitter<ILinkifierEvent>();
33+
private _onHideLinkUnderline = this.register(new EventEmitter<ILinkifierEvent>());
3234
public get onHideLinkUnderline(): IEvent<ILinkifierEvent> { return this._onHideLinkUnderline.event; }
3335

3436
constructor(
3537
@IBufferService private readonly _bufferService: IBufferService
3638
) {
37-
39+
super();
40+
this.register(getDisposeArrayDisposable(this._linkCacheDisposables));
3841
}
3942

4043
public registerLinkProvider(linkProvider: ILinkProvider): IDisposable {
@@ -56,12 +59,12 @@ export class Linkifier2 implements ILinkifier2 {
5659
this._mouseService = mouseService;
5760
this._renderService = renderService;
5861

59-
this._element.addEventListener('mouseleave', () => {
62+
this.register(addDisposableDomListener(this._element, 'mouseleave', () => {
6063
this._isMouseOut = true;
6164
this._clearCurrentLink();
62-
});
63-
this._element.addEventListener('mousemove', this._onMouseMove.bind(this));
64-
this._element.addEventListener('click', this._onClick.bind(this));
65+
}));
66+
this.register(addDisposableDomListener(this._element, 'mousemove', this._onMouseMove.bind(this)));
67+
this.register(addDisposableDomListener(this._element, 'click', this._onClick.bind(this)));
6568
}
6669

6770
private _onMouseMove(event: MouseEvent): void {

src/browser/Terminal.ts

Lines changed: 11 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -140,7 +140,7 @@ export class Terminal extends CoreTerminal implements ITerminal {
140140
this._setup();
141141

142142
this.linkifier = this._instantiationService.createInstance(Linkifier);
143-
this.linkifier2 = this._instantiationService.createInstance(Linkifier2);
143+
this.linkifier2 = this.register(this._instantiationService.createInstance(Linkifier2));
144144

145145
// Setup InputHandler listeners
146146
this.register(this._inputHandler.onRequestBell(() => this.bell()));
@@ -154,7 +154,7 @@ export class Terminal extends CoreTerminal implements ITerminal {
154154
this.register(forwardEvent(this._inputHandler.onA11yTab, this._onA11yTabEmitter));
155155

156156
// Setup listeners
157-
this._bufferService.onResize(e => this._afterResize(e.cols, e.rows));
157+
this.register(this._bufferService.onResize(e => this._afterResize(e.cols, e.rows)));
158158
}
159159

160160
public dispose(): void {
@@ -413,13 +413,13 @@ export class Terminal extends CoreTerminal implements ITerminal {
413413

414414
this._theme = this.options.theme || this._theme;
415415
this._colorManager = new ColorManager(document, this.options.allowTransparency);
416-
this.optionsService.onOptionChange(e => this._colorManager!.onOptionsChange(e));
416+
this.register(this.optionsService.onOptionChange(e => this._colorManager!.onOptionsChange(e)));
417417
this._colorManager.setTheme(this._theme);
418418

419419
const renderer = this._createRenderer();
420-
this._renderService = this._instantiationService.createInstance(RenderService, renderer, this.rows, this.screenElement);
420+
this._renderService = this.register(this._instantiationService.createInstance(RenderService, renderer, this.rows, this.screenElement));
421421
this._instantiationService.setService(IRenderService, this._renderService);
422-
this._renderService.onRenderedBufferChange(e => this._onRender.fire(e));
422+
this.register(this._renderService.onRenderedBufferChange(e => this._onRender.fire(e)));
423423
this.onResize(e => this._renderService!.resize(e.cols, e.rows));
424424

425425
this._soundService = this._instantiationService.createInstance(SoundService);
@@ -442,13 +442,13 @@ export class Terminal extends CoreTerminal implements ITerminal {
442442
this.register(this.onFocus(() => this._renderService!.onFocus()));
443443
this.register(this._renderService.onDimensionsChange(() => this.viewport!.syncScrollArea()));
444444

445-
this._selectionService = this._instantiationService.createInstance(SelectionService,
446-
(amount: number, suppressEvent: boolean) => this.scrollLines(amount, suppressEvent),
445+
this._selectionService = this.register(this._instantiationService.createInstance(SelectionService,
447446
this.element,
448-
this.screenElement);
447+
this.screenElement));
449448
this._instantiationService.setService(ISelectionService, this._selectionService);
449+
this.register(this._selectionService.onRequestScrollLines(e => this.scrollLines(e.amount, e.suppressScrollEvent)));
450450
this.register(this._selectionService.onSelectionChange(() => this._onSelectionChange.fire()));
451-
this.register(this._selectionService.onRedrawRequest(e => this._renderService!.onSelectionChanged(e.start, e.end, e.columnSelectMode)));
451+
this.register(this._selectionService.onRequestRedraw(e => this._renderService!.onSelectionChanged(e.start, e.end, e.columnSelectMode)));
452452
this.register(this._selectionService.onLinuxMouseSelection(text => {
453453
// If there's a new selection, put it into the textarea, focus and select it
454454
// in order to register it as a selection on the OS. This event is fired
@@ -646,7 +646,7 @@ export class Terminal extends CoreTerminal implements ITerminal {
646646
}
647647
}
648648
};
649-
this._coreMouseService.onProtocolChange(events => {
649+
this.register(this._coreMouseService.onProtocolChange(events => {
650650
// apply global changes on events
651651
if (events) {
652652
if (this.optionsService.options.logLevel === 'debug') {
@@ -691,7 +691,7 @@ export class Terminal extends CoreTerminal implements ITerminal {
691691
} else if (!requestedEvents.mousedrag) {
692692
requestedEvents.mousedrag = eventListeners.mousedrag;
693693
}
694-
});
694+
}));
695695
// force initial onProtocolChange so we dont miss early mouse requests
696696
this._coreMouseService.activeProtocol = this._coreMouseService.activeProtocol;
697697

src/browser/renderer/BaseRenderLayer.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -60,7 +60,7 @@ export abstract class BaseRenderLayer implements IRenderLayer {
6060
}
6161

6262
public dispose(): void {
63-
this._container.removeChild(this._canvas);
63+
this._canvas.parentElement?.removeChild(this._canvas);
6464
this._charAtlas?.dispose();
6565
}
6666

src/browser/renderer/Renderer.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -71,8 +71,8 @@ export class Renderer extends Disposable implements IRenderer {
7171
}
7272

7373
public dispose(): void {
74-
super.dispose();
7574
this._renderLayers.forEach(l => l.dispose());
75+
super.dispose();
7676
removeTerminalFromCache(this._id);
7777
}
7878

src/browser/selection/Types.d.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,3 +8,8 @@ export interface ISelectionRedrawRequestEvent {
88
end: [number, number] | undefined;
99
columnSelectMode: boolean;
1010
}
11+
12+
export interface ISelectionRequestScrollLinesEvent {
13+
amount: number;
14+
suppressScrollEvent: boolean;
15+
}

src/browser/services/RenderService.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -52,7 +52,7 @@ export class RenderService extends Disposable implements IRenderService {
5252
screenElement: HTMLElement,
5353
@IOptionsService optionsService: IOptionsService,
5454
@ICharSizeService charSizeService: ICharSizeService,
55-
@IBufferService private readonly _bufferService: IBufferService
55+
@IBufferService bufferService: IBufferService
5656
) {
5757
super();
5858
this._renderDebouncer = new RenderDebouncer((start, end) => this._renderRows(start, end));
@@ -62,7 +62,7 @@ export class RenderService extends Disposable implements IRenderService {
6262
this._screenDprMonitor.setListener(() => this.onDevicePixelRatioChange());
6363
this.register(this._screenDprMonitor);
6464

65-
this.register(this._bufferService.onResize(e => this._fullRefresh()));
65+
this.register(bufferService.onResize(e => this._fullRefresh()));
6666
this.register(optionsService.onOptionChange(() => this._renderer.onOptionsChanged()));
6767
this.register(charSizeService.onCharSizeChange(() => this.onCharSizeChanged()));
6868

src/browser/services/SelectionService.test.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ class TestSelectionService extends SelectionService {
2121
optionsService: IOptionsService,
2222
renderService: IRenderService
2323
) {
24-
super(() => {}, null!, null!, bufferService, new MockCoreService(), new MockMouseService(), optionsService, renderService);
24+
super(null!, null!, bufferService, new MockCoreService(), new MockMouseService(), optionsService, renderService);
2525
}
2626

2727
public get model(): SelectionModel { return this._model; }

src/browser/services/SelectionService.ts

Lines changed: 13 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
* @license MIT
44
*/
55

6-
import { ISelectionRedrawRequestEvent } from 'browser/selection/Types';
6+
import { ISelectionRedrawRequestEvent, ISelectionRequestScrollLinesEvent } from 'browser/selection/Types';
77
import { IBuffer } from 'common/buffer/Types';
88
import { IBufferLine, IDisposable } from 'common/Types';
99
import * as Browser from 'common/Platform';
@@ -14,6 +14,7 @@ import { ICharSizeService, IMouseService, ISelectionService, IRenderService } fr
1414
import { IBufferService, IOptionsService, ICoreService } from 'common/services/Services';
1515
import { getCoordsRelativeToElement } from 'browser/input/Mouse';
1616
import { moveToCellSequence } from 'browser/input/MoveToCell';
17+
import { Disposable } from 'common/Lifecycle';
1718

1819
/**
1920
* The number of pixels the mouse needs to be above or below the viewport in
@@ -66,7 +67,7 @@ export const enum SelectionMode {
6667
* not handled by the SelectionService but the onRedrawRequest event is fired
6768
* when the selection is ready to be redrawn (on an animation frame).
6869
*/
69-
export class SelectionService implements ISelectionService {
70+
export class SelectionService extends Disposable implements ISelectionService {
7071
public serviceBrand: undefined;
7172

7273
protected _model: SelectionModel;
@@ -105,15 +106,16 @@ export class SelectionService implements ISelectionService {
105106

106107
private _mouseDownTimeStamp: number = 0;
107108

108-
private _onLinuxMouseSelection = new EventEmitter<string>();
109+
private _onLinuxMouseSelection = this.register(new EventEmitter<string>());
109110
public get onLinuxMouseSelection(): IEvent<string> { return this._onLinuxMouseSelection.event; }
110-
private _onRedrawRequest = new EventEmitter<ISelectionRedrawRequestEvent>();
111-
public get onRedrawRequest(): IEvent<ISelectionRedrawRequestEvent> { return this._onRedrawRequest.event; }
112-
private _onSelectionChange = new EventEmitter<void>();
111+
private _onRedrawRequest = this.register(new EventEmitter<ISelectionRedrawRequestEvent>());
112+
public get onRequestRedraw(): IEvent<ISelectionRedrawRequestEvent> { return this._onRedrawRequest.event; }
113+
private _onSelectionChange = this.register(new EventEmitter<void>());
113114
public get onSelectionChange(): IEvent<void> { return this._onSelectionChange.event; }
115+
private _onRequestScrollLines = this.register(new EventEmitter<ISelectionRequestScrollLinesEvent>());
116+
public get onRequestScrollLines(): IEvent<ISelectionRequestScrollLinesEvent> { return this._onRequestScrollLines.event; }
114117

115118
constructor(
116-
private readonly _scrollLines: (amount: number, suppressEvent: boolean) => void,
117119
private readonly _element: HTMLElement,
118120
private readonly _screenElement: HTMLElement,
119121
@IBufferService private readonly _bufferService: IBufferService,
@@ -122,6 +124,8 @@ export class SelectionService implements ISelectionService {
122124
@IOptionsService private readonly _optionsService: IOptionsService,
123125
@IRenderService private readonly _renderService: IRenderService
124126
) {
127+
super();
128+
125129
// Init listeners
126130
this._mouseMoveListener = event => this._onMouseMove(<MouseEvent>event);
127131
this._mouseUpListener = event => this._onMouseUp(<MouseEvent>event);
@@ -131,7 +135,7 @@ export class SelectionService implements ISelectionService {
131135
}
132136
});
133137
this._trimListener = this._bufferService.buffer.lines.onTrim(amount => this._onTrim(amount));
134-
this._bufferService.buffers.onBufferActivate(e => this._onBufferActivate(e));
138+
this.register(this._bufferService.buffers.onBufferActivate(e => this._onBufferActivate(e)));
135139

136140
this.enable();
137141

@@ -633,7 +637,7 @@ export class SelectionService implements ISelectionService {
633637
return;
634638
}
635639
if (this._dragScrollAmount) {
636-
this._scrollLines(this._dragScrollAmount, false);
640+
this._onRequestScrollLines.fire({ amount: this._dragScrollAmount, suppressScrollEvent: false });
637641
// Re-evaluate selection
638642
// If the cursor was above or below the viewport, make sure it's at the
639643
// start or end of the viewport respectively. This should only happen when

src/browser/services/Services.ts

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66
import { IEvent } from 'common/EventEmitter';
77
import { IRenderDimensions, IRenderer, CharacterJoinerHandler } from 'browser/renderer/Types';
88
import { IColorSet } from 'browser/Types';
9-
import { ISelectionRedrawRequestEvent } from 'browser/selection/Types';
9+
import { ISelectionRedrawRequestEvent as ISelectionRequestRedrawEvent, ISelectionRequestScrollLinesEvent } from 'browser/selection/Types';
1010
import { createDecorator } from 'common/services/ServiceRegistry';
1111
import { IDisposable } from 'common/Types';
1212

@@ -80,7 +80,8 @@ export interface ISelectionService {
8080
readonly selectionEnd: [number, number] | undefined;
8181

8282
readonly onLinuxMouseSelection: IEvent<string>;
83-
readonly onRedrawRequest: IEvent<ISelectionRedrawRequestEvent>;
83+
readonly onRequestRedraw: IEvent<ISelectionRequestRedrawEvent>;
84+
readonly onRequestScrollLines: IEvent<ISelectionRequestScrollLinesEvent>;
8485
readonly onSelectionChange: IEvent<void>;
8586

8687
disable(): void;

0 commit comments

Comments
 (0)