diff --git a/src/browser/RenderDebouncer.ts b/src/browser/RenderDebouncer.ts index 025210707c..ad2d79b45e 100644 --- a/src/browser/RenderDebouncer.ts +++ b/src/browser/RenderDebouncer.ts @@ -3,16 +3,17 @@ * @license MIT */ -import { IRenderDebouncer } from 'browser/Types'; +import { IRenderDebouncerWithCallback } from 'browser/Types'; /** * Debounces calls to render terminal rows using animation frames. */ -export class RenderDebouncer implements IRenderDebouncer { +export class RenderDebouncer implements IRenderDebouncerWithCallback { private _rowStart: number | undefined; private _rowEnd: number | undefined; private _rowCount: number | undefined; private _animationFrame: number | undefined; + private _refreshCallbacks: FrameRequestCallback[] = []; constructor( private _renderCallback: (start: number, end: number) => void @@ -26,6 +27,14 @@ export class RenderDebouncer implements IRenderDebouncer { } } + public addRefreshCallback(callback: FrameRequestCallback): number { + this._refreshCallbacks.push(callback); + if (!this._animationFrame) { + this._animationFrame = window.requestAnimationFrame(() => this._innerRefresh()); + } + return this._animationFrame; + } + 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 @@ -43,8 +52,11 @@ export class RenderDebouncer implements IRenderDebouncer { } private _innerRefresh(): void { + this._animationFrame = undefined; + // Make sure values are set if (this._rowStart === undefined || this._rowEnd === undefined || this._rowCount === undefined) { + this._runRefreshCallbacks(); return; } @@ -55,9 +67,16 @@ export class RenderDebouncer implements IRenderDebouncer { // Reset debouncer (this happens before render callback as the render could trigger it again) this._rowStart = undefined; this._rowEnd = undefined; - this._animationFrame = undefined; // Run render callback this._renderCallback(start, end); + this._runRefreshCallbacks(); + } + + private _runRefreshCallbacks(): void { + for (const callback of this._refreshCallbacks) { + callback(0); + } + this._refreshCallbacks = []; } } diff --git a/src/browser/TestUtils.test.ts b/src/browser/TestUtils.test.ts index cb487dbc6a..c3376b21dd 100644 --- a/src/browser/TestUtils.test.ts +++ b/src/browser/TestUtils.test.ts @@ -7,7 +7,7 @@ import { IDisposable, IMarker, ISelectionPosition, ILinkProvider, IDecorationOpt import { IEvent, EventEmitter } from 'common/EventEmitter'; import { ICharacterJoinerService, ICharSizeService, IMouseService, IRenderService, ISelectionService } from 'browser/services/Services'; import { IRenderDimensions, IRenderer, IRequestRedrawEvent } from 'browser/renderer/Types'; -import { IColorSet, ILinkMatcherOptions, ITerminal, ILinkifier, ILinkifier2, IBrowser, IViewport, IColorManager, ICompositionHelper, CharacterJoinerHandler } from 'browser/Types'; +import { IColorSet, ILinkMatcherOptions, ITerminal, ILinkifier, ILinkifier2, IBrowser, IViewport, IColorManager, ICompositionHelper, CharacterJoinerHandler, IRenderDebouncer } from 'browser/Types'; import { IBuffer, IBufferStringIterator, IBufferSet } from 'common/buffer/Types'; import { IBufferLine, ICellData, IAttributeData, ICircularList, XtermListener, ICharset, ITerminalOptions } from 'common/Types'; import { Buffer } from 'common/buffer/Buffer'; @@ -390,6 +390,9 @@ export class MockRenderService implements IRenderService { public refreshRows(start: number, end: number): void { throw new Error('Method not implemented.'); } + public addRefreshCallback(callback: FrameRequestCallback): number { + throw new Error('Method not implemented.'); + } public clearTextureAtlas(): void { throw new Error('Method not implemented.'); } diff --git a/src/browser/Types.d.ts b/src/browser/Types.d.ts index 0e83c213c2..a472326a84 100644 --- a/src/browser/Types.d.ts +++ b/src/browser/Types.d.ts @@ -309,3 +309,7 @@ export interface ICharacterJoiner { export interface IRenderDebouncer extends IDisposable { refresh(rowStart: number | undefined, rowEnd: number | undefined, rowCount: number): void; } + +export interface IRenderDebouncerWithCallback extends IRenderDebouncer { + addRefreshCallback(callback: FrameRequestCallback): number; +} diff --git a/src/browser/decorations/BufferDecorationRenderer.ts b/src/browser/decorations/BufferDecorationRenderer.ts index 00b2b00b48..5c9b1282c2 100644 --- a/src/browser/decorations/BufferDecorationRenderer.ts +++ b/src/browser/decorations/BufferDecorationRenderer.ts @@ -51,7 +51,7 @@ export class BufferDecorationRenderer extends Disposable { if (this._animationFrame !== undefined) { return; } - this._animationFrame = window.requestAnimationFrame(() => { + this._animationFrame = this._renderService.addRefreshCallback(() => { this.refreshDecorations(); this._animationFrame = undefined; }); diff --git a/src/browser/services/RenderService.ts b/src/browser/services/RenderService.ts index 75c2d3c898..852d8dc420 100644 --- a/src/browser/services/RenderService.ts +++ b/src/browser/services/RenderService.ts @@ -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, IRenderDebouncer } from 'browser/Types'; +import { IColorSet, IRenderDebouncer, IRenderDebouncerWithCallback } from 'browser/Types'; import { IOptionsService, IBufferService, IDecorationService } 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: IRenderDebouncer; + private _renderDebouncer: IRenderDebouncerWithCallback; private _screenDprMonitor: ScreenDprMonitor; private _isPaused: boolean = false; @@ -171,6 +171,10 @@ export class RenderService extends Disposable implements IRenderService { this._fullRefresh(); } + public addRefreshCallback(callback: FrameRequestCallback): number { + return this._renderDebouncer.addRefreshCallback(callback); + } + private _fullRefresh(): void { if (this._isPaused) { this._needsFullRefresh = true; diff --git a/src/browser/services/Services.ts b/src/browser/services/Services.ts index a9f76a9097..056bf91c86 100644 --- a/src/browser/services/Services.ts +++ b/src/browser/services/Services.ts @@ -5,7 +5,7 @@ import { IEvent } from 'common/EventEmitter'; import { IRenderDimensions, IRenderer } from 'browser/renderer/Types'; -import { IColorSet } from 'browser/Types'; +import { IColorSet, IRenderDebouncer } from 'browser/Types'; import { ISelectionRedrawRequestEvent as ISelectionRequestRedrawEvent, ISelectionRequestScrollLinesEvent } from 'browser/selection/Types'; import { createDecorator } from 'common/services/ServiceRegistry'; import { IDisposable } from 'common/Types'; @@ -58,6 +58,8 @@ export interface IRenderService extends IDisposable { dimensions: IRenderDimensions; + addRefreshCallback(callback: FrameRequestCallback): number; + refreshRows(start: number, end: number): void; clearTextureAtlas(): void; resize(cols: number, rows: number): void;