From 9fed8e2a3a250e04734a1b2fa8cb03868504af54 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=B6rg=20Breitbart?= Date: Sun, 4 Jun 2023 21:06:26 +0200 Subject: [PATCH 1/3] defensive underline event coords handling in DOM renderer --- src/browser/renderer/dom/DomRenderer.ts | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) diff --git a/src/browser/renderer/dom/DomRenderer.ts b/src/browser/renderer/dom/DomRenderer.ts index 9cee75a8..e24177d1 100644 --- a/src/browser/renderer/dom/DomRenderer.ts +++ b/src/browser/renderer/dom/DomRenderer.ts @@ -393,8 +393,18 @@ export class DomRenderer extends Disposable implements IRenderer { } private _setCellUnderline(x: number, x2: number, y: number, y2: number, cols: number, enabled: boolean): void { - x = this._cellToRowElements[y][x]; - x2 = this._cellToRowElements[y2][x2]; + const lenY = this._cellToRowElements.length - 1; + if (y < 0) x = 0; + if (y2 < 0) x2 = 0; + y = Math.max(Math.min(y, lenY), 0); + y2 = Math.max(Math.min(y2, lenY), 0); + const elemY = this._cellToRowElements[y]; + const elemY2 = this._cellToRowElements[y2]; + if (x >= elemY.length || x2 >= elemY2.length) { + return; + } + x = elemY[x]; + x2 = elemY2[x2]; if (x === -1 || x2 === -1) { return; From 8de7bd5850c0ebed3cc9c25178a387f9952ccb00 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=B6rg=20Breitbart?= Date: Sun, 4 Jun 2023 21:11:55 +0200 Subject: [PATCH 2/3] rename lneX to maxY --- src/browser/renderer/dom/DomRenderer.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/browser/renderer/dom/DomRenderer.ts b/src/browser/renderer/dom/DomRenderer.ts index e24177d1..58aa48d4 100644 --- a/src/browser/renderer/dom/DomRenderer.ts +++ b/src/browser/renderer/dom/DomRenderer.ts @@ -393,11 +393,11 @@ export class DomRenderer extends Disposable implements IRenderer { } private _setCellUnderline(x: number, x2: number, y: number, y2: number, cols: number, enabled: boolean): void { - const lenY = this._cellToRowElements.length - 1; + const maxY = this._cellToRowElements.length - 1; if (y < 0) x = 0; if (y2 < 0) x2 = 0; - y = Math.max(Math.min(y, lenY), 0); - y2 = Math.max(Math.min(y2, lenY), 0); + y = Math.max(Math.min(y, maxY), 0); + y2 = Math.max(Math.min(y2, maxY), 0); const elemY = this._cellToRowElements[y]; const elemY2 = this._cellToRowElements[y2]; if (x >= elemY.length || x2 >= elemY2.length) { From 90ccc815cb1349875fefc17fbffd20c2629be8cf Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=B6rg=20Breitbart?= Date: Mon, 5 Jun 2023 12:23:56 +0200 Subject: [PATCH 3/3] more verbose comments to get the idea of the code --- src/browser/renderer/dom/DomRenderer.ts | 21 ++++++++++++++++++++- 1 file changed, 20 insertions(+), 1 deletion(-) diff --git a/src/browser/renderer/dom/DomRenderer.ts b/src/browser/renderer/dom/DomRenderer.ts index 58aa48d4..54711ed8 100644 --- a/src/browser/renderer/dom/DomRenderer.ts +++ b/src/browser/renderer/dom/DomRenderer.ts @@ -393,14 +393,33 @@ export class DomRenderer extends Disposable implements IRenderer { } private _setCellUnderline(x: number, x2: number, y: number, y2: number, cols: number, enabled: boolean): void { - const maxY = this._cellToRowElements.length - 1; + /** + * NOTE: The linkifier may send out of viewport y-values if: + * - negative y-value: the link started at a higher line + * - y-value >= maxY: the link ends at a line below viewport + * + * For negative y-values we can simply adjust x = 0, + * as higher up link start means, that everything from + * (0,0) is a link under top-down-left-right char progression + * + * Additionally there might be a small chance of out-of-sync x|y-values + * from a race condition of render updates vs. link event handler execution: + * - (sync) resize: chances terminal buffer in sync, schedules render update async + * - (async) link handler race condition: new buffer metrics, but still on old render state + * - (async) render update: brings term metrics and render state back in sync + */ if (y < 0) x = 0; if (y2 < 0) x2 = 0; + + // avoid out-of-sync y-values, simply clamp into valid area + const maxY = this._cellToRowElements.length - 1; y = Math.max(Math.min(y, maxY), 0); y2 = Math.max(Math.min(y2, maxY), 0); const elemY = this._cellToRowElements[y]; const elemY2 = this._cellToRowElements[y2]; if (x >= elemY.length || x2 >= elemY2.length) { + // avoid out-of-sync x-values + // simply exit early, gets fixed by the next render update return; } x = elemY[x];