Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
30 changes: 30 additions & 0 deletions packages/engine/src/services/browserManager.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,36 @@ describe("buildChromeArgs browser GPU mode", () => {
});
});

describe("buildChromeArgs compositor determinism flags", () => {
const base = { width: 1920, height: 1080 };

it("includes compositor determinism flags in screenshot mode", () => {
const args = buildChromeArgs({ ...base, captureMode: "screenshot" });
expect(args).toContain("--run-all-compositor-stages-before-draw");
expect(args).toContain("--disable-threaded-animation");
expect(args).toContain("--disable-threaded-scrolling");
expect(args).toContain("--enable-surface-synchronization");
expect(args).toContain("--disable-checker-imaging");
expect(args).toContain("--disable-image-animation-resync");
expect(args).toContain("--disable-new-content-rendering-timeout");
});

it("excludes beginFrame-exclusive flags in screenshot mode", () => {
const args = buildChromeArgs({ ...base, captureMode: "screenshot" });
expect(args).not.toContain("--deterministic-mode");
expect(args).not.toContain("--enable-begin-frame-control");
});

it("includes both compositor and beginFrame-exclusive flags in beginframe mode", () => {
const args = buildChromeArgs({ ...base, captureMode: "beginframe" });
expect(args).toContain("--run-all-compositor-stages-before-draw");
expect(args).toContain("--disable-threaded-animation");
expect(args).toContain("--enable-surface-synchronization");
expect(args).toContain("--deterministic-mode");
expect(args).toContain("--enable-begin-frame-control");
});
});

describe("resolveBrowserGpuMode", () => {
beforeEach(() => {
_resetAutoBrowserGpuModeCacheForTests();
Expand Down
41 changes: 22 additions & 19 deletions packages/engine/src/services/browserManager.ts
Original file line number Diff line number Diff line change
Expand Up @@ -77,24 +77,32 @@ let pooledCaptureMode: CaptureMode = "screenshot";
// Preserve the producer-era export so re-export shims keep the same public API.
export const ENABLE_BROWSER_POOL = DEFAULT_CONFIG.enableBrowserPool;

// Flags only meaningful when Chrome's compositor is driven by
// HeadlessExperimental.beginFrame. If we fall back to screenshot mode they
// must be stripped — `--enable-begin-frame-control` in particular makes the
// compositor wait for frames we'll never send, producing blank screenshots.
const BEGINFRAME_ONLY_FLAGS = new Set([
// Flags that ONLY work with HeadlessExperimental.beginFrame and must be
// stripped in screenshot mode. `--enable-begin-frame-control` pauses the
// compositor until explicit beginFrame calls — in screenshot mode this
// produces blank captures. `--deterministic-mode` freezes the internal
// clock, which can stall page-load timers in screenshot mode.
const BEGINFRAME_EXCLUSIVE_FLAGS = new Set([
"--deterministic-mode",
"--enable-begin-frame-control",
]);

// Compositor determinism flags safe for BOTH beginframe and screenshot modes.
// Without these, Chrome's compositor (especially Metal on Apple Silicon)
// accumulates state drift over sustained frame captures, producing vertical
// shifts after ~12s of rendering. Applied unconditionally in buildChromeArgs.
const COMPOSITOR_DETERMINISM_FLAGS = [
"--disable-new-content-rendering-timeout",
"--run-all-compositor-stages-before-draw",
"--disable-threaded-animation",
"--disable-threaded-scrolling",
"--disable-checker-imaging",
"--disable-image-animation-resync",
"--enable-surface-synchronization",
]);
];

function stripBeginFrameFlags(args: string[]): string[] {
return args.filter((a) => !BEGINFRAME_ONLY_FLAGS.has(a));
return args.filter((a) => !BEGINFRAME_EXCLUSIVE_FLAGS.has(a));
}

/**
Expand Down Expand Up @@ -431,19 +439,14 @@ export function buildChromeArgs(
"--disable-features=AudioServiceOutOfProcess,IsolateOrigins,site-per-process,Translate,BackForwardCache,IntensiveWakeUpThrottling",
];

// BeginFrame flags — only when using chrome-headless-shell on Linux
// Compositor determinism flags — safe for all capture modes. Prevents
// Metal compositor drift on Apple Silicon that causes vertical frame
// shifts after sustained screenshot captures.
chromeArgs.push(...COMPOSITOR_DETERMINISM_FLAGS);

// BeginFrame-exclusive flags — only when using chrome-headless-shell on Linux
if (options.captureMode !== "screenshot") {
chromeArgs.push(
"--deterministic-mode",
"--enable-begin-frame-control",
"--disable-new-content-rendering-timeout",
"--run-all-compositor-stages-before-draw",
"--disable-threaded-animation",
"--disable-threaded-scrolling",
"--disable-checker-imaging",
"--disable-image-animation-resync",
"--enable-surface-synchronization",
);
chromeArgs.push("--deterministic-mode", "--enable-begin-frame-control");
}

if (gpuDisabled) {
Expand Down
Loading