Merge pull request #5770 from mgabor3141/fix/viewport-sync-output-flicker

fix: defer viewport DOM sync during synchronized output (DEC 2026)
This commit is contained in:
Daniel Imms
2026-03-15 04:35:17 +09:00
committed by GitHub
2 changed files with 24 additions and 2 deletions
+5 -1
View File
@@ -617,8 +617,12 @@ function addDomListener(element: HTMLElement, type: string, handler: (...args: a
}
function updateTerminalSize(): void {
const showScrollbar = term!.options.scrollbar?.showScrollbar ?? true;
const scrollBarWidth = (term!.options.scrollback === 0 || !showScrollbar)
? 0
: (term!.options.scrollbar?.width ?? 14);
const width = optionsWindow.autoResize ? '100%'
: ((term as any).dimensions.css.canvas.width + (term as any)._core.viewport.scrollBarWidth).toString() + 'px';
: ((term as any).dimensions.css.canvas.width + scrollBarWidth).toString() + 'px';
const height = optionsWindow.autoResize ? '100%'
: ((term as any).dimensions.css.canvas.height).toString() + 'px';
terminalContainer!.style.width = width;
+19 -1
View File
@@ -6,7 +6,7 @@
import { ICoreBrowserService, IRenderService, IThemeService } from 'browser/services/Services';
import { ViewportConstants } from 'browser/shared/Constants';
import { Disposable, toDisposable } from 'common/Lifecycle';
import { IBufferService, IMouseStateService, IOptionsService } from 'common/services/Services';
import { IBufferService, ICoreService, IMouseStateService, IOptionsService } from 'common/services/Services';
import { CoreMouseEventType } from 'common/Types';
import { scheduleAtNextAnimationFrame } from 'browser/Dom';
import { SmoothScrollableElement } from 'browser/scrollable/scrollableElement';
@@ -27,12 +27,14 @@ export class Viewport extends Disposable {
private _isSyncing: boolean = false;
private _isHandlingScroll: boolean = false;
private _suppressOnScrollHandler: boolean = false;
private _needsSyncOnRender: boolean = false;
constructor(
element: HTMLElement,
screenElement: HTMLElement,
@IBufferService private readonly _bufferService: IBufferService,
@ICoreBrowserService coreBrowserService: ICoreBrowserService,
@ICoreService private readonly _coreService: ICoreService,
@IMouseStateService mouseStateService: IMouseStateService,
@IThemeService themeService: IThemeService,
@IOptionsService private readonly _optionsService: IOptionsService,
@@ -104,6 +106,16 @@ export class Viewport extends Disposable {
}));
this._register(this._bufferService.onScroll(() => this._sync()));
// Flush deferred viewport sync after a render completes (e.g. after ESU ends
// synchronized output mode). This ensures DOM scroll position updates atomically
// with the canvas render.
this._register(this._renderService.onRender(() => {
if (this._needsSyncOnRender) {
this._needsSyncOnRender = false;
this._sync();
}
}));
this._register(this._scrollableElement.onScroll(e => this._handleScroll(e)));
}
@@ -161,6 +173,12 @@ export class Viewport extends Disposable {
if (!this._renderService || this._isSyncing) {
return;
}
// Defer DOM scroll updates during synchronized output to prevent visible
// scroll position flickering while the canvas content is frozen.
if (this._coreService.decPrivateModes.synchronizedOutput) {
this._needsSyncOnRender = true;
return;
}
this._isSyncing = true;
// Ignore any onScroll event that happens as a result of dimensions changing as this should