diff --git a/web/packages/teleport/package.json b/web/packages/teleport/package.json index 8875248dabfd6..c4f60ac32db11 100644 --- a/web/packages/teleport/package.json +++ b/web/packages/teleport/package.json @@ -21,8 +21,10 @@ "@gravitational/design": "1.0.0", "@gravitational/shared": "1.0.0", "xterm": "^5.3.0", + "xterm-addon-canvas": "^0.5.0", "xterm-addon-fit": "^0.8.0", - "xterm-addon-web-links": "^0.9.0" + "xterm-addon-web-links": "^0.9.0", + "xterm-addon-webgl": "^0.16.0" }, "devDependencies": { "@gravitational/build": "^1.0.0", diff --git a/web/packages/teleport/src/lib/term/terminal.ts b/web/packages/teleport/src/lib/term/terminal.ts index 77fb4c28acb3b..7707d55f01c40 100644 --- a/web/packages/teleport/src/lib/term/terminal.ts +++ b/web/packages/teleport/src/lib/term/terminal.ts @@ -19,8 +19,10 @@ import 'xterm/css/xterm.css'; import { ITheme, Terminal } from 'xterm'; import { FitAddon } from 'xterm-addon-fit'; +import { WebglAddon } from 'xterm-addon-webgl'; import { debounce, isInteger } from 'shared/utils/highbar'; import { WebLinksAddon } from 'xterm-addon-web-links'; +import { CanvasAddon } from 'xterm-addon-canvas'; import Logger from 'shared/libs/logger'; import cfg from 'teleport/config'; @@ -48,6 +50,8 @@ export default class TtyTerminal { _debouncedResize: DebouncedFunc<() => void>; _fitAddon = new FitAddon(); _webLinksAddon = new WebLinksAddon(); + _webglAddon: WebglAddon; + _canvasAddon = new CanvasAddon(); constructor(tty: Tty, private options: Options) { const { el, scrollBack, fontFamily, fontSize } = options; @@ -78,6 +82,26 @@ export default class TtyTerminal { this.term.loadAddon(this._fitAddon); this.term.loadAddon(this._webLinksAddon); + // handle context loss and load webgl addon + try { + // try to create a new WebglAddon. If webgl is not supported, this + // constructor will throw an error and fallback to canvas. We also fallback + // to canvas if the webgl context is lost after a timeout. + // The "wait for context" timeout for the webgl addon doesn't actually start until the app is + // able to have it back. For example, if the OS takes the gpu away from the browser, the timeout + // wont start looking for the context again until the OS has given the browser the context again. + // When the initial context lost event is fired, the webgl addon consumes the event + // and waits for a bit to see if it can get the context back. If it fails repeatedly, it + // will propagate the context loss event itself in which case we fall back to canvas + this._webglAddon = new WebglAddon(); + this._webglAddon.onContextLoss(() => { + this.fallbackToCanvas(); + }); + this.term.loadAddon(this._webglAddon); + } catch (err) { + this.fallbackToCanvas(); + } + this.term.open(this._el); this._fitAddon.fit(); this.term.focus(); @@ -98,6 +122,21 @@ export default class TtyTerminal { window.addEventListener('resize', this._debouncedResize); } + fallbackToCanvas() { + logger.info('WebGL context lost. Falling back to canvas'); + this._webglAddon?.dispose(); + this._webglAddon = undefined; + try { + this.term.loadAddon(this._canvasAddon); + } catch (err) { + logger.error( + 'Canvas renderer could not be loaded. Falling back to default' + ); + this._canvasAddon?.dispose(); + this._canvasAddon = undefined; + } + } + connect() { this.tty.connect(this.term.cols, this.term.rows); } @@ -110,6 +149,8 @@ export default class TtyTerminal { this._disconnect(); this._debouncedResize.cancel(); this._fitAddon.dispose(); + this._webglAddon?.dispose(); + this._canvasAddon?.dispose(); this._el.innerHTML = null; this.term?.dispose(); diff --git a/yarn.lock b/yarn.lock index 174db3ca6a737..f6b18541c1cfe 100644 --- a/yarn.lock +++ b/yarn.lock @@ -16182,6 +16182,11 @@ xtend@^4.0.0, xtend@^4.0.1: resolved "https://registry.yarnpkg.com/xtend/-/xtend-4.0.2.tgz#bb72779f5fa465186b1f438f674fa347fdb5db54" integrity sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ== +xterm-addon-canvas@^0.5.0: + version "0.5.0" + resolved "https://registry.yarnpkg.com/xterm-addon-canvas/-/xterm-addon-canvas-0.5.0.tgz#95d056cec6da42a51b2c47746a011409020c388c" + integrity sha512-QOo/eZCMrCleAgMimfdbaZCgmQRWOml63Ued6RwQ+UTPvQj3Av9QKx3xksmyYrDGRO/AVRXa9oNuzlYvLdmoLQ== + xterm-addon-fit@^0.8.0: version "0.8.0" resolved "https://registry.yarnpkg.com/xterm-addon-fit/-/xterm-addon-fit-0.8.0.tgz#48ca99015385141918f955ca7819e85f3691d35f" @@ -16192,6 +16197,11 @@ xterm-addon-web-links@^0.9.0: resolved "https://registry.yarnpkg.com/xterm-addon-web-links/-/xterm-addon-web-links-0.9.0.tgz#c65b18588d1f613e703eb6feb7f129e7ff1c63e7" integrity sha512-LIzi4jBbPlrKMZF3ihoyqayWyTXAwGfu4yprz1aK2p71e9UKXN6RRzVONR0L+Zd+Ik5tPVI9bwp9e8fDTQh49Q== +xterm-addon-webgl@^0.16.0: + version "0.16.0" + resolved "https://registry.yarnpkg.com/xterm-addon-webgl/-/xterm-addon-webgl-0.16.0.tgz#9872d08a64136f893b27ef9a6412136d3bf563c4" + integrity sha512-E8cq1AiqNOv0M/FghPT+zPAEnvIQRDbAbkb04rRYSxUym69elPWVJ4sv22FCLBqM/3LcrmBLl/pELnBebVFKgA== + xterm@^5.3.0: version "5.3.0" resolved "https://registry.yarnpkg.com/xterm/-/xterm-5.3.0.tgz#867daf9cc826f3d45b5377320aabd996cb0fce46"