From a0da287b9c29aae7881badf4c7753ce80fe8eac6 Mon Sep 17 00:00:00 2001 From: Jason Lin Date: Thu, 9 Nov 2023 16:56:07 +1100 Subject: [PATCH 1/3] Use the right document in AccessibilityManager.ts --- src/browser/AccessibilityManager.ts | 17 +++++++++-------- 1 file changed, 9 insertions(+), 8 deletions(-) diff --git a/src/browser/AccessibilityManager.ts b/src/browser/AccessibilityManager.ts index 68c65ebd..4f550014 100644 --- a/src/browser/AccessibilityManager.ts +++ b/src/browser/AccessibilityManager.ts @@ -58,10 +58,11 @@ export class AccessibilityManager extends Disposable { @IRenderService private readonly _renderService: IRenderService ) { super(); - this._accessibilityContainer = this._coreBrowserService.mainDocument.createElement('div'); + const doc = this._coreBrowserService.mainDocument; + this._accessibilityContainer = doc.createElement('div'); this._accessibilityContainer.classList.add('xterm-accessibility'); - this._rowContainer = this._coreBrowserService.mainDocument.createElement('div'); + this._rowContainer = doc.createElement('div'); this._rowContainer.setAttribute('role', 'list'); this._rowContainer.classList.add('xterm-accessibility-tree'); this._rowElements = []; @@ -78,7 +79,7 @@ export class AccessibilityManager extends Disposable { this._refreshRowsDimensions(); this._accessibilityContainer.appendChild(this._rowContainer); - this._liveRegion = this._coreBrowserService.mainDocument.createElement('div'); + this._liveRegion = doc.createElement('div'); this._liveRegion.classList.add('live-region'); this._liveRegion.setAttribute('aria-live', 'assertive'); this._accessibilityContainer.appendChild(this._liveRegion); @@ -93,12 +94,12 @@ export class AccessibilityManager extends Disposable { this._rowContainer.classList.add('debug'); // Use a `
` container so that the css will still apply. - this._debugRootContainer = document.createElement('div'); + this._debugRootContainer = doc.createElement('div'); this._debugRootContainer.classList.add('xterm'); - this._debugRootContainer.appendChild(document.createTextNode('------start a11y------')); + this._debugRootContainer.appendChild(doc.createTextNode('------start a11y------')); this._debugRootContainer.appendChild(this._accessibilityContainer); - this._debugRootContainer.appendChild(document.createTextNode('------end a11y------')); + this._debugRootContainer.appendChild(doc.createTextNode('------end a11y------')); this._terminal.element.insertAdjacentElement('afterend', this._debugRootContainer); } else { @@ -115,7 +116,7 @@ export class AccessibilityManager extends Disposable { this.register(this._terminal.onKey(e => this._handleKey(e.key))); this.register(this._terminal.onBlur(() => this._clearLiveRegion())); this.register(this._renderService.onDimensionsChange(() => this._refreshRowsDimensions())); - this.register(addDisposableDomListener(document, 'selectionchange', () => this._handleSelectionChange())); + this.register(addDisposableDomListener(doc, 'selectionchange', () => this._handleSelectionChange())); this.register(this._coreBrowserService.onDprChange(() => this._refreshRowsDimensions())); this._refreshRows(); @@ -270,7 +271,7 @@ export class AccessibilityManager extends Disposable { return; } - const selection = document.getSelection(); + const selection = this._coreBrowserService.mainDocument.getSelection(); if (!selection) { return; } From 26dc69446ef85a20a6db52004260029c3fb0accd Mon Sep 17 00:00:00 2001 From: Jason Lin Date: Fri, 10 Nov 2023 21:21:50 +1100 Subject: [PATCH 2/3] Align character position in a11y tree with the actual rendering This helps screen readers to draw selection outline at the correct position. --- css/xterm.css | 6 ++++++ src/browser/AccessibilityManager.ts | 31 +++++++++++++++++++++++++++-- 2 files changed, 35 insertions(+), 2 deletions(-) diff --git a/css/xterm.css b/css/xterm.css index e97b6439..51e9b39e 100644 --- a/css/xterm.css +++ b/css/xterm.css @@ -157,10 +157,16 @@ } .xterm .xterm-accessibility-tree { + font-family: monospace; user-select: text; white-space: pre; } +.xterm .xterm-accessibility-tree > div { + transform-origin: left; + width: fit-content; +} + .xterm .live-region { position: absolute; left: -9999px; diff --git a/src/browser/AccessibilityManager.ts b/src/browser/AccessibilityManager.ts index 4f550014..665968df 100644 --- a/src/browser/AccessibilityManager.ts +++ b/src/browser/AccessibilityManager.ts @@ -76,7 +76,6 @@ export class AccessibilityManager extends Disposable { this._rowElements[0].addEventListener('focus', this._topBoundaryFocusListener); this._rowElements[this._rowElements.length - 1].addEventListener('focus', this._bottomBoundaryFocusListener); - this._refreshRowsDimensions(); this._accessibilityContainer.appendChild(this._rowContainer); this._liveRegion = doc.createElement('div'); @@ -119,6 +118,7 @@ export class AccessibilityManager extends Disposable { this.register(addDisposableDomListener(doc, 'selectionchange', () => this._handleSelectionChange())); this.register(this._coreBrowserService.onDprChange(() => this._refreshRowsDimensions())); + this._refreshRowsDimensions(); this._refreshRows(); this.register(toDisposable(() => { if (DEBUG) { @@ -193,6 +193,7 @@ export class AccessibilityManager extends Disposable { } element.setAttribute('aria-posinset', posInSet); element.setAttribute('aria-setsize', setSize); + this._alignRowWidth(element); } } this._announceCharacters(); @@ -390,19 +391,45 @@ export class AccessibilityManager extends Disposable { this._refreshRowDimensions(element); return element; } + private _refreshRowsDimensions(): void { if (!this._renderService.dimensions.css.cell.height) { return; } - this._accessibilityContainer.style.width = `${this._renderService.dimensions.css.canvas.width}px`; + Object.assign(this._accessibilityContainer.style, { + width: `${this._renderService.dimensions.css.canvas.width}px`, + fontSize: `${this._terminal.options.fontSize}px`, + }); if (this._rowElements.length !== this._terminal.rows) { this._handleResize(this._terminal.rows); } for (let i = 0; i < this._terminal.rows; i++) { this._refreshRowDimensions(this._rowElements[i]); + this._alignRowWidth(this._rowElements[i]); } } + private _refreshRowDimensions(element: HTMLElement): void { element.style.height = `${this._renderService.dimensions.css.cell.height}px`; } + + /** + * Scale the width of a row so that each of the character is (mostly) aligned + * with the actual rendering. This will allow the screen reader to draw + * selection outline at the correct position. + * + * On top of using the "monospace" font and correct font size, the scaling + * here is necessary to handle characters that are not covered by the font + * (e.g. CJK). + */ + private _alignRowWidth(element: HTMLElement): void { + element.style.transform = ''; + const width = element.getBoundingClientRect().width; + const lastColumn = this._rowColumns.get(element)?.slice(-1)?.[0]; + if (!lastColumn) { + return; + } + const targetWidth = lastColumn * this._renderService.dimensions.css.cell.width; + element.style.transform = `scaleX(${targetWidth / width})`; + } } From 12e5841959b8f9a4be5b599250f0e959a5bc5fd5 Mon Sep 17 00:00:00 2001 From: Daniel Imms <2193314+Tyriar@users.noreply.github.com> Date: Tue, 4 Jun 2024 05:30:54 -0700 Subject: [PATCH 3/3] Fix lint --- src/browser/AccessibilityManager.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/browser/AccessibilityManager.ts b/src/browser/AccessibilityManager.ts index 665968df..a4a58187 100644 --- a/src/browser/AccessibilityManager.ts +++ b/src/browser/AccessibilityManager.ts @@ -398,7 +398,7 @@ export class AccessibilityManager extends Disposable { } Object.assign(this._accessibilityContainer.style, { width: `${this._renderService.dimensions.css.canvas.width}px`, - fontSize: `${this._terminal.options.fontSize}px`, + fontSize: `${this._terminal.options.fontSize}px` }); if (this._rowElements.length !== this._terminal.rows) { this._handleResize(this._terminal.rows);