diff --git a/src/browser/AccessibilityManager.ts b/src/browser/AccessibilityManager.ts index 2a34d6a36..af9fee6fb 100644 --- a/src/browser/AccessibilityManager.ts +++ b/src/browser/AccessibilityManager.ts @@ -12,6 +12,7 @@ import { addDisposableDomListener } from 'browser/Lifecycle'; import { Disposable } from 'common/Lifecycle'; import { ScreenDprMonitor } from 'browser/ScreenDprMonitor'; import { IRenderService } from 'browser/services/Services'; +import { removeElementFromParent } from 'browser/Dom'; const MAX_ROWS_TO_READ = 20; @@ -106,7 +107,7 @@ export class AccessibilityManager extends Disposable { public dispose(): void { super.dispose(); - this._terminal.element?.removeChild(this._accessibilityTreeRoot); + removeElementFromParent(this._accessibilityTreeRoot); this._rowElements.length = 0; } @@ -240,9 +241,7 @@ export class AccessibilityManager extends Disposable { // Only detach/attach on mac as otherwise messages can go unaccounced if (isMac) { - if (this._liveRegion.parentNode) { - this._accessibilityTreeRoot.removeChild(this._liveRegion); - } + removeElementFromParent(this._liveRegion); } } diff --git a/src/browser/Dom.test.ts b/src/browser/Dom.test.ts new file mode 100644 index 000000000..14f3d26f5 --- /dev/null +++ b/src/browser/Dom.test.ts @@ -0,0 +1,41 @@ +/** + * Copyright (c) 2020 The xterm.js authors. All rights reserved. + * @license MIT + */ + +import jsdom = require('jsdom'); +import { removeElementFromParent } from 'browser/Dom'; +import { strictEqual, doesNotThrow } from 'assert'; + +describe('Dom', () => { + const dom = new jsdom.JSDOM(); + const document = dom.window.document; + + describe('removeElementFromParent', () => { + it('should remove single child', () => { + const e = document.createElement('div'); + document.body.appendChild(e); + strictEqual(e.parentElement, document.body); + removeElementFromParent(e); + strictEqual(e.parentElement, null); + }); + it('should remove multiple elements', () => { + const e1 = document.createElement('div'); + const e2 = document.createElement('div'); + document.body.appendChild(e1); + document.body.appendChild(e2); + strictEqual(e1.parentElement, document.body); + strictEqual(e2.parentElement, document.body); + removeElementFromParent(e1, e2); + strictEqual(e1.parentElement, null); + strictEqual(e2.parentElement, null); + }); + it('should not throw on undefined', () => { + const e = document.createElement('div'); + document.body.appendChild(e); + strictEqual(e.parentElement, document.body); + doesNotThrow(() => removeElementFromParent(undefined, e)); + strictEqual(e.parentElement, null); + }); + }); +}); diff --git a/src/browser/Dom.ts b/src/browser/Dom.ts new file mode 100644 index 000000000..c558a8b1b --- /dev/null +++ b/src/browser/Dom.ts @@ -0,0 +1,10 @@ +/** + * Copyright (c) 2020 The xterm.js authors. All rights reserved. + * @license MIT + */ + +export function removeElementFromParent(...elements: (HTMLElement | undefined)[]): void { + for (const e of elements) { + e?.parentElement?.removeChild(e); + } +} diff --git a/src/browser/renderer/BaseRenderLayer.ts b/src/browser/renderer/BaseRenderLayer.ts index 7d9fb5b83..8afec352c 100644 --- a/src/browser/renderer/BaseRenderLayer.ts +++ b/src/browser/renderer/BaseRenderLayer.ts @@ -16,6 +16,7 @@ import { CellData } from 'common/buffer/CellData'; import { IBufferService, IOptionsService } from 'common/services/Services'; import { throwIfFalsy } from 'browser/renderer/RendererUtils'; import { channels, color, rgba } from 'browser/Color'; +import { removeElementFromParent } from 'browser/Dom'; export abstract class BaseRenderLayer implements IRenderLayer { private _canvas: HTMLCanvasElement; @@ -60,7 +61,7 @@ export abstract class BaseRenderLayer implements IRenderLayer { } public dispose(): void { - this._canvas.parentElement?.removeChild(this._canvas); + removeElementFromParent(this._canvas); this._charAtlas?.dispose(); } diff --git a/src/browser/renderer/dom/DomRenderer.ts b/src/browser/renderer/dom/DomRenderer.ts index 9082e36bc..572b71148 100644 --- a/src/browser/renderer/dom/DomRenderer.ts +++ b/src/browser/renderer/dom/DomRenderer.ts @@ -12,6 +12,7 @@ import { ICharSizeService } from 'browser/services/Services'; import { IOptionsService, IBufferService } from 'common/services/Services'; import { EventEmitter, IEvent } from 'common/EventEmitter'; import { color } from 'browser/Color'; +import { removeElementFromParent } from 'browser/Dom'; const TERMINAL_CLASS_PREFIX = 'xterm-dom-renderer-owner-'; const ROW_CONTAINER_CLASS = 'xterm-rows'; @@ -98,10 +99,7 @@ export class DomRenderer extends Disposable implements IRenderer { // Outside influences such as React unmounts may manipulate the DOM before our disposal. // https://github.com/xtermjs/xterm.js/issues/2960 - this._rowContainer.parentElement?.removeChild(this._rowContainer); - this._selectionContainer.parentElement?.removeChild(this._selectionContainer); - this._themeStyleElement.parentElement?.removeChild(this._themeStyleElement); - this._dimensionsStyleElement.parentElement?.removeChild(this._dimensionsStyleElement); + removeElementFromParent(this._rowContainer, this._selectionContainer, this._themeStyleElement, this._dimensionsStyleElement); super.dispose(); }