plugged memory leaks

This commit is contained in:
Eugene Pankov
2021-05-13 16:40:23 +02:00
parent c98fd2042d
commit 5c22e22caa
18 changed files with 211 additions and 144 deletions

View File

@@ -4,7 +4,7 @@ import { first } from 'rxjs/operators'
import colors from 'ansi-colors'
import { NgZone, OnInit, OnDestroy, Injector, ViewChild, HostBinding, Input, ElementRef, InjectFlags } from '@angular/core'
import { trigger, transition, style, animate, AnimationTriggerMetadata } from '@angular/animations'
import { AppService, ConfigService, BaseTabComponent, ElectronService, HostAppService, HotkeysService, NotificationsService, Platform, LogService, Logger, TabContextMenuItemProvider, SplitTabComponent } from 'terminus-core'
import { AppService, ConfigService, BaseTabComponent, ElectronService, HostAppService, HotkeysService, NotificationsService, Platform, LogService, Logger, TabContextMenuItemProvider, SplitTabComponent, SubscriptionContainer } from 'terminus-core'
import { BaseSession, SessionsService } from '../services/sessions.service'
import { TerminalFrontendService } from '../services/terminalFrontend.service'
@@ -95,12 +95,10 @@ export class BaseTerminalTabComponent extends BaseTabComponent implements OnInit
protected logger: Logger
protected output = new Subject<string>()
protected sessionChanged = new Subject<BaseSession|null>()
private sessionCloseSubscription: Subscription
private hotkeysSubscription: Subscription
private bellPlayer: HTMLAudioElement
private termContainerSubscriptions: Subscription[] = []
private termContainerSubscriptions = new SubscriptionContainer()
private allFocusModeSubscription: Subscription|null = null
private sessionHandlers: Subscription[] = []
private sessionHandlers = new SubscriptionContainer()
get input$ (): Observable<Buffer> {
if (!this.frontend) {
@@ -149,7 +147,7 @@ export class BaseTerminalTabComponent extends BaseTabComponent implements OnInit
this.logger = this.log.create('baseTerminalTab')
this.setTitle('Terminal')
this.hotkeysSubscription = this.hotkeys.matchedHotkey.subscribe(async hotkey => {
this.subscribeUntilDestroyed(this.hotkeys.matchedHotkey, async hotkey => {
if (!this.hasFocus) {
return
}
@@ -475,7 +473,13 @@ export class BaseTerminalTabComponent extends BaseTabComponent implements OnInit
/** @hidden */
ngOnDestroy (): void {
super.ngOnDestroy()
}
async destroy (): Promise<void> {
this.frontend?.detach(this.content.nativeElement)
this.frontend = undefined
this.content.nativeElement.remove()
this.detachTermContainerHandlers()
this.config.enabledServices(this.decorators).forEach(decorator => {
try {
@@ -484,14 +488,8 @@ export class BaseTerminalTabComponent extends BaseTabComponent implements OnInit
this.logger.warn('Decorator attach() throws', e)
}
})
this.hotkeysSubscription.unsubscribe()
if (this.sessionCloseSubscription) {
this.sessionCloseSubscription.unsubscribe()
}
this.output.complete()
}
async destroy (): Promise<void> {
super.destroy()
if (this.session?.open) {
await this.session.destroy()
@@ -499,10 +497,7 @@ export class BaseTerminalTabComponent extends BaseTabComponent implements OnInit
}
protected detachTermContainerHandlers (): void {
for (const subscription of this.termContainerSubscriptions) {
subscription.unsubscribe()
}
this.termContainerSubscriptions = []
this.termContainerSubscriptions.cancelAll()
}
protected attachTermContainerHandlers (): void {
@@ -518,71 +513,69 @@ export class BaseTerminalTabComponent extends BaseTabComponent implements OnInit
}
}
this.termContainerSubscriptions = [
this.frontend.title$.subscribe(title => this.zone.run(() => {
if (this.enableDynamicTitle) {
this.setTitle(title)
this.termContainerSubscriptions.subscribe(this.frontend.title$, title => this.zone.run(() => {
if (this.enableDynamicTitle) {
this.setTitle(title)
}
}))
this.termContainerSubscriptions.subscribe(this.focused$, () => this.frontend && (this.frontend.enableResizing = true))
this.termContainerSubscriptions.subscribe(this.blurred$, () => this.frontend && (this.frontend.enableResizing = false))
this.termContainerSubscriptions.subscribe(this.frontend.mouseEvent$, async event => {
if (event.type === 'mousedown') {
if (event.which === 2) {
if (this.config.store.terminal.pasteOnMiddleClick) {
this.paste()
}
event.preventDefault()
event.stopPropagation()
return
}
})),
this.focused$.subscribe(() => this.frontend && (this.frontend.enableResizing = true)),
this.blurred$.subscribe(() => this.frontend && (this.frontend.enableResizing = false)),
this.frontend.mouseEvent$.subscribe(async event => {
if (event.type === 'mousedown') {
if (event.which === 2) {
if (this.config.store.terminal.pasteOnMiddleClick) {
this.paste()
}
event.preventDefault()
event.stopPropagation()
return
}
if (event.which === 3 || event.which === 1 && event.ctrlKey) {
if (this.config.store.terminal.rightClick === 'menu') {
this.hostApp.popupContextMenu(await this.buildContextMenu())
} else if (this.config.store.terminal.rightClick === 'paste') {
this.paste()
}
event.preventDefault()
event.stopPropagation()
return
if (event.which === 3 || event.which === 1 && event.ctrlKey) {
if (this.config.store.terminal.rightClick === 'menu') {
this.hostApp.popupContextMenu(await this.buildContextMenu())
} else if (this.config.store.terminal.rightClick === 'paste') {
this.paste()
}
event.preventDefault()
event.stopPropagation()
return
}
if (event.type === 'mousewheel') {
let wheelDeltaY = 0
}
if (event.type === 'mousewheel') {
let wheelDeltaY = 0
if ('wheelDeltaY' in event) {
wheelDeltaY = (event as MouseWheelEvent)['wheelDeltaY']
} else {
wheelDeltaY = (event as MouseWheelEvent)['deltaY']
}
if (event.altKey) {
event.preventDefault()
const delta = Math.round(wheelDeltaY / 50)
this.sendInput((delta > 0 ? '\u001bOA' : '\u001bOB').repeat(Math.abs(delta)))
}
if ('wheelDeltaY' in event) {
wheelDeltaY = (event as MouseWheelEvent)['wheelDeltaY']
} else {
wheelDeltaY = (event as MouseWheelEvent)['deltaY']
}
}),
this.frontend.input$.subscribe(data => {
this.sendInput(data)
}),
if (event.altKey) {
event.preventDefault()
const delta = Math.round(wheelDeltaY / 50)
this.sendInput((delta > 0 ? '\u001bOA' : '\u001bOB').repeat(Math.abs(delta)))
}
}
})
this.frontend.resize$.subscribe(({ columns, rows }) => {
this.logger.debug(`Resizing to ${columns}x${rows}`)
this.size = { columns, rows }
this.zone.run(() => {
if (this.session?.open) {
this.session.resize(columns, rows)
}
})
}),
this.termContainerSubscriptions.subscribe(this.frontend.input$, data => {
this.sendInput(data)
})
this.hostApp.displayMetricsChanged$.subscribe(maybeConfigure),
this.hostApp.windowMoved$.subscribe(maybeConfigure),
]
this.termContainerSubscriptions.subscribe(this.frontend.resize$, ({ columns, rows }) => {
this.logger.debug(`Resizing to ${columns}x${rows}`)
this.size = { columns, rows }
this.zone.run(() => {
if (this.session?.open) {
this.session.resize(columns, rows)
}
})
})
this.termContainerSubscriptions.subscribe(this.hostApp.displayMetricsChanged$, maybeConfigure)
this.termContainerSubscriptions.subscribe(this.hostApp.windowMoved$, maybeConfigure)
}
setSession (session: BaseSession|null, destroyOnSessionClose = false): void {
@@ -600,8 +593,8 @@ export class BaseTerminalTabComponent extends BaseTabComponent implements OnInit
this.sessionChanged.next(session)
}
protected attachSessionHandler (subscription: Subscription): void {
this.sessionHandlers.push(subscription)
protected attachSessionHandler <T> (observable: Observable<T>, handler: (v: T) => void): void {
this.sessionHandlers.subscribe(observable, handler)
}
protected attachSessionHandlers (destroyOnSessionClose = false): void {
@@ -610,29 +603,26 @@ export class BaseTerminalTabComponent extends BaseTabComponent implements OnInit
}
// this.session.output$.bufferTime(10).subscribe((datas) => {
this.attachSessionHandler(this.session.output$.subscribe(data => {
this.attachSessionHandler(this.session.output$, data => {
if (this.enablePassthrough) {
this.output.next(data)
this.write(data)
}
}))
})
if (destroyOnSessionClose) {
this.attachSessionHandler(this.sessionCloseSubscription = this.session.closed$.subscribe(() => {
this.attachSessionHandler(this.session.closed$, () => {
this.frontend?.destroy()
this.destroy()
}))
})
}
this.attachSessionHandler(this.session.destroyed$.subscribe(() => {
this.attachSessionHandler(this.session.destroyed$, () => {
this.setSession(null)
}))
})
}
protected detachSessionHandlers (): void {
for (const s of this.sessionHandlers) {
s.unsubscribe()
}
this.sessionHandlers = []
this.sessionHandlers.cancelAll()
}
}

View File

@@ -1,5 +1,4 @@
import { Component, Input, Injector } from '@angular/core'
import { Subscription } from 'rxjs'
import { BaseTabProcess, WIN_BUILD_CONPTY_SUPPORTED, isWindowsBuild } from 'terminus-core'
import { BaseTerminalTabComponent } from '../api/baseTerminalTab.component'
import { SessionOptions } from '../api/interfaces'
@@ -14,7 +13,6 @@ import { Session } from '../services/sessions.service'
})
export class TerminalTabComponent extends BaseTerminalTabComponent {
@Input() sessionOptions: SessionOptions
private homeEndSubscription: Subscription
session: Session|null = null
// eslint-disable-next-line @typescript-eslint/no-useless-constructor
@@ -30,7 +28,7 @@ export class TerminalTabComponent extends BaseTerminalTabComponent {
const isConPTY = isWindowsBuild(WIN_BUILD_CONPTY_SUPPORTED) && this.config.store.terminal.useConPTY
this.homeEndSubscription = this.hotkeys.matchedHotkey.subscribe(hotkey => {
this.subscribeUntilDestroyed(this.hotkeys.matchedHotkey, hotkey => {
if (!this.hasFocus) {
return
}
@@ -106,7 +104,6 @@ export class TerminalTabComponent extends BaseTerminalTabComponent {
}
ngOnDestroy (): void {
this.homeEndSubscription.unsubscribe()
super.ngOnDestroy()
this.session?.destroy()
}

View File

@@ -25,7 +25,7 @@ export class DebugDecorator extends TerminalDecorator {
}
}))
terminal.content.nativeElement.addEventListener('keyup', e => {
terminal.addEventListenerUntilDestroyed(terminal.content.nativeElement, 'keyup', (e: KeyboardEvent) => {
// Ctrl-Shift-Alt-1
if (e.which === 49 && e.ctrlKey && e.shiftKey && e.altKey) {
this.doSaveState(terminal)

View File

@@ -34,7 +34,9 @@ export class XTermFrontend extends Frontend {
private fitAddon = new FitAddon()
private serializeAddon = new SerializeAddon()
private ligaturesAddon?: LigaturesAddon
private webGLAddon?: WebglAddon
private opened = false
private resizeObserver?: any
constructor () {
super()
@@ -141,7 +143,8 @@ export class XTermFrontend extends Frontend {
await new Promise(resolve => setTimeout(resolve, process.env.XWEB ? 1000 : 0))
if (this.enableWebGL) {
this.xterm.loadAddon(new WebglAddon())
this.webGLAddon = new WebglAddon()
this.xterm.loadAddon(this.webGLAddon)
}
this.ready.next()
@@ -160,12 +163,19 @@ export class XTermFrontend extends Frontend {
host.addEventListener('mouseup', event => this.mouseEvent.next(event))
host.addEventListener('mousewheel', event => this.mouseEvent.next(event as MouseEvent))
const ro = new window['ResizeObserver'](() => setTimeout(() => this.resizeHandler()))
ro.observe(host)
this.resizeObserver = new window['ResizeObserver'](() => setTimeout(() => this.resizeHandler()))
this.resizeObserver.observe(host)
}
detach (_host: HTMLElement): void {
window.removeEventListener('resize', this.resizeHandler)
this.resizeObserver?.disconnect()
}
destroy (): void {
super.destroy()
this.webGLAddon?.dispose()
this.xterm.dispose()
}
getSelection (): string {