diff --git a/web/packages/teleport/package.json b/web/packages/teleport/package.json index b73d9ebbea9ed..cff6935379bda 100644 --- a/web/packages/teleport/package.json +++ b/web/packages/teleport/package.json @@ -20,8 +20,10 @@ "@gravitational/design": "1.0.0", "@gravitational/shared": "1.0.0", "xterm": "^5.0.0", + "xterm-addon-canvas": "^0.5.0", "xterm-addon-fit": "^0.7.0", - "xterm-addon-web-links": "^0.8.0" + "xterm-addon-web-links": "^0.8.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 7fec05b42bded..6db112bc4d33a 100644 --- a/web/packages/teleport/src/lib/term/terminal.ts +++ b/web/packages/teleport/src/lib/term/terminal.ts @@ -18,6 +18,8 @@ import { ITheme, Terminal } from 'xterm'; import { FitAddon } from 'xterm-addon-fit'; import { debounce, isInteger } from 'shared/utils/highbar'; import { WebLinksAddon } from 'xterm-addon-web-links'; +import { WebglAddon } from 'xterm-addon-webgl'; +import { CanvasAddon } from 'xterm-addon-canvas'; import Logger from 'shared/libs/logger'; import cfg from 'teleport/config'; @@ -45,6 +47,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; @@ -75,6 +79,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(); @@ -95,6 +119,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); } @@ -107,6 +146,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 9262169a99e1d..dc94907a4dfa4 100644 --- a/yarn.lock +++ b/yarn.lock @@ -15578,6 +15578,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.7.0: version "0.7.0" resolved "https://registry.yarnpkg.com/xterm-addon-fit/-/xterm-addon-fit-0.7.0.tgz#b8ade6d96e63b47443862088f6670b49fb752c6a" @@ -15588,6 +15593,11 @@ xterm-addon-web-links@^0.8.0: resolved "https://registry.yarnpkg.com/xterm-addon-web-links/-/xterm-addon-web-links-0.8.0.tgz#2cb1d57129271022569208578b0bf4774e7e6ea9" integrity sha512-J4tKngmIu20ytX9SEJjAP3UGksah7iALqBtfTwT9ZnmFHVplCumYQsUJfKuS+JwMhjsjH61YXfndenLNvjRrEw== +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.0.0: version "5.1.0" resolved "https://registry.yarnpkg.com/xterm/-/xterm-5.1.0.tgz#3e160d60e6801c864b55adf19171c49d2ff2b4fc"