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
+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