mirror of
https://github.com/wavetermdev/xterm.js.git
synced 2026-08-05 13:43:48 -07:00
Merge pull request #3399 from pattch/master
Add a Time-Based Debouncer to update Screen Readers at most once per second
This commit is contained in:
@@ -4,10 +4,10 @@
|
||||
*/
|
||||
|
||||
import * as Strings from 'browser/LocalizableStrings';
|
||||
import { ITerminal } from 'browser/Types';
|
||||
import { ITerminal, IRenderDebouncer } from 'browser/Types';
|
||||
import { IBuffer } from 'common/buffer/Types';
|
||||
import { isMac } from 'common/Platform';
|
||||
import { RenderDebouncer } from 'browser/RenderDebouncer';
|
||||
import { TimeBasedDebouncer } from 'browser/TimeBasedDebouncer';
|
||||
import { addDisposableDomListener } from 'browser/Lifecycle';
|
||||
import { Disposable } from 'common/Lifecycle';
|
||||
import { ScreenDprMonitor } from 'browser/ScreenDprMonitor';
|
||||
@@ -28,7 +28,7 @@ export class AccessibilityManager extends Disposable {
|
||||
private _liveRegion: HTMLElement;
|
||||
private _liveRegionLineCount: number = 0;
|
||||
|
||||
private _renderRowsDebouncer: RenderDebouncer;
|
||||
private _renderRowsDebouncer: IRenderDebouncer;
|
||||
private _screenDprMonitor: ScreenDprMonitor;
|
||||
|
||||
private _topBoundaryFocusListener: (e: FocusEvent) => void;
|
||||
@@ -72,7 +72,7 @@ export class AccessibilityManager extends Disposable {
|
||||
this._refreshRowsDimensions();
|
||||
this._accessibilityTreeRoot.appendChild(this._rowContainer);
|
||||
|
||||
this._renderRowsDebouncer = new RenderDebouncer(this._renderRows.bind(this));
|
||||
this._renderRowsDebouncer = new TimeBasedDebouncer(this._renderRows.bind(this));
|
||||
this._refreshRows();
|
||||
|
||||
this._liveRegion = document.createElement('div');
|
||||
|
||||
@@ -3,12 +3,12 @@
|
||||
* @license MIT
|
||||
*/
|
||||
|
||||
import { IDisposable } from 'common/Types';
|
||||
import { IRenderDebouncer } from 'browser/Types';
|
||||
|
||||
/**
|
||||
* Debounces calls to render terminal rows using animation frames.
|
||||
*/
|
||||
export class RenderDebouncer implements IDisposable {
|
||||
export class RenderDebouncer implements IRenderDebouncer {
|
||||
private _rowStart: number | undefined;
|
||||
private _rowEnd: number | undefined;
|
||||
private _rowCount: number | undefined;
|
||||
|
||||
@@ -0,0 +1,86 @@
|
||||
/**
|
||||
* Copyright (c) 2018 The xterm.js authors. All rights reserved.
|
||||
* @license MIT
|
||||
*/
|
||||
|
||||
const RENDER_DEBOUNCE_THRESHOLD_MS = 1000; // 1 Second
|
||||
|
||||
import { IRenderDebouncer } from 'browser/Types';
|
||||
|
||||
/**
|
||||
* Debounces calls to update screen readers to update at most once configurable interval of time.
|
||||
*/
|
||||
export class TimeBasedDebouncer implements IRenderDebouncer {
|
||||
private _rowStart: number | undefined;
|
||||
private _rowEnd: number | undefined;
|
||||
private _rowCount: number | undefined;
|
||||
|
||||
// The last moment that the Terminal was refreshed at
|
||||
private _lastRefreshMs = 0;
|
||||
// Whether a trailing refresh should be triggered due to a refresh request that was throttled
|
||||
private _additionalRefreshRequested = false;
|
||||
|
||||
private _refreshTimeoutID: number | undefined;
|
||||
|
||||
constructor(
|
||||
private _renderCallback: (start: number, end: number) => void,
|
||||
private readonly _debounceThresholdMS = RENDER_DEBOUNCE_THRESHOLD_MS
|
||||
) {
|
||||
}
|
||||
|
||||
public dispose(): void {
|
||||
if (this._refreshTimeoutID) {
|
||||
clearTimeout(this._refreshTimeoutID);
|
||||
}
|
||||
}
|
||||
|
||||
public refresh(rowStart: number | undefined, rowEnd: number | undefined, rowCount: number): void {
|
||||
this._rowCount = rowCount;
|
||||
// Get the min/max row start/end for the arg values
|
||||
rowStart = rowStart !== undefined ? rowStart : 0;
|
||||
rowEnd = rowEnd !== undefined ? rowEnd : this._rowCount - 1;
|
||||
// Set the properties to the updated values
|
||||
this._rowStart = this._rowStart !== undefined ? Math.min(this._rowStart, rowStart) : rowStart;
|
||||
this._rowEnd = this._rowEnd !== undefined ? Math.max(this._rowEnd, rowEnd) : rowEnd;
|
||||
|
||||
// Only refresh if the time since last refresh is above a threshold, otherwise wait for
|
||||
// enough time to pass before refreshing again.
|
||||
const refreshRequestTime: number = Date.now();
|
||||
if (refreshRequestTime - this._lastRefreshMs >= this._debounceThresholdMS) {
|
||||
// Enough time has lapsed since the last refresh; refresh immediately
|
||||
this._lastRefreshMs = refreshRequestTime;
|
||||
this._innerRefresh();
|
||||
} else if (!this._additionalRefreshRequested) {
|
||||
// This is the first additional request throttled; set up trailing refresh
|
||||
const elapsed = refreshRequestTime - this._lastRefreshMs;
|
||||
const waitPeriodBeforeTrailingRefresh = this._debounceThresholdMS - elapsed;
|
||||
this._additionalRefreshRequested = true;
|
||||
|
||||
this._refreshTimeoutID = window.setTimeout(() => {
|
||||
this._lastRefreshMs = Date.now();
|
||||
this._innerRefresh();
|
||||
this._additionalRefreshRequested = false;
|
||||
this._refreshTimeoutID = undefined; // No longer need to clear the timeout
|
||||
}, waitPeriodBeforeTrailingRefresh);
|
||||
}
|
||||
}
|
||||
|
||||
private _innerRefresh(): void {
|
||||
// Make sure values are set
|
||||
if (this._rowStart === undefined || this._rowEnd === undefined || this._rowCount === undefined) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Clamp values
|
||||
const start = Math.max(this._rowStart, 0);
|
||||
const end = Math.min(this._rowEnd, this._rowCount - 1);
|
||||
|
||||
// Reset debouncer (this happens before render callback as the render could trigger it again)
|
||||
this._rowStart = undefined;
|
||||
this._rowEnd = undefined;
|
||||
|
||||
// Run render callback
|
||||
this._renderCallback(start, end);
|
||||
}
|
||||
}
|
||||
|
||||
Vendored
+4
@@ -309,3 +309,7 @@ export interface ICharacterJoiner {
|
||||
id: number;
|
||||
handler: CharacterJoinerHandler;
|
||||
}
|
||||
|
||||
export interface IRenderDebouncer extends IDisposable {
|
||||
refresh(rowStart: number | undefined, rowEnd: number | undefined, rowCount: number): void;
|
||||
}
|
||||
|
||||
@@ -9,7 +9,7 @@ import { EventEmitter, IEvent } from 'common/EventEmitter';
|
||||
import { Disposable } from 'common/Lifecycle';
|
||||
import { ScreenDprMonitor } from 'browser/ScreenDprMonitor';
|
||||
import { addDisposableDomListener } from 'browser/Lifecycle';
|
||||
import { IColorSet } from 'browser/Types';
|
||||
import { IColorSet, IRenderDebouncer } from 'browser/Types';
|
||||
import { IOptionsService, IBufferService } from 'common/services/Services';
|
||||
import { ICharSizeService, IRenderService } from 'browser/services/Services';
|
||||
|
||||
@@ -22,7 +22,7 @@ interface ISelectionState {
|
||||
export class RenderService extends Disposable implements IRenderService {
|
||||
public serviceBrand: undefined;
|
||||
|
||||
private _renderDebouncer: RenderDebouncer;
|
||||
private _renderDebouncer: IRenderDebouncer;
|
||||
private _screenDprMonitor: ScreenDprMonitor;
|
||||
|
||||
private _isPaused: boolean = false;
|
||||
|
||||
Reference in New Issue
Block a user