mirror of
https://github.com/heygen-com/hyperframes.git
synced 2026-09-03 04:38:33 +00:00
fix(cli): settle post-seek font subset loads before audit screenshots
This commit is contained in:
@@ -47,15 +47,26 @@ afterEach(() => {
|
||||
});
|
||||
|
||||
describe("seekCompositionTimeline", () => {
|
||||
it("keeps the existing raced double-frame settle as the default", async () => {
|
||||
it("keeps the raced double-frame settle and adds a bounded font wait by default", async () => {
|
||||
const { page, evaluate, waitForFunction } = fakeSeekPage();
|
||||
|
||||
await seekCompositionTimeline(page, 1.25);
|
||||
|
||||
expect(waitForFunction).not.toHaveBeenCalled();
|
||||
expect(evaluate).toHaveBeenCalledTimes(2);
|
||||
expect(evaluate).toHaveBeenCalledTimes(3);
|
||||
expect(evaluate).toHaveBeenNthCalledWith(1, expect.any(Function), 1.25, false);
|
||||
expect(evaluate.mock.calls[1]?.[0]).toContain("window.setTimeout(finish, 100)");
|
||||
// Post-seek font settle: a seek can reveal glyphs whose unicode-range
|
||||
// subsets only start loading after the next layout (CJK snapshot reports).
|
||||
expect(evaluate).toHaveBeenNthCalledWith(3, expect.any(Function), 500);
|
||||
});
|
||||
|
||||
it("waitForFontsMs: 0 disables the post-seek font wait", async () => {
|
||||
const { page, evaluate } = fakeSeekPage();
|
||||
|
||||
await seekCompositionTimeline(page, 1.25, { waitForFontsMs: 0 });
|
||||
|
||||
expect(evaluate).toHaveBeenCalledTimes(2);
|
||||
});
|
||||
|
||||
it("prefers renderSeek so the runtime synchronizes clip visibility", async () => {
|
||||
@@ -128,6 +139,7 @@ describe("seekCompositionTimeline", () => {
|
||||
fallbackToBridgeAndTimelines: true,
|
||||
waitForPreferredSeekTargetMs: 500,
|
||||
animationFrameSettle: "none",
|
||||
waitForFontsMs: 0,
|
||||
settleMs: 150,
|
||||
});
|
||||
await vi.advanceTimersByTimeAsync(150);
|
||||
|
||||
@@ -6,6 +6,7 @@ import { resolveCompositionViewportFromHtml } from "../utils/compositionViewport
|
||||
const SHADER_TRANSITIONS_TIMEOUT_MS = 90_000;
|
||||
const CAPTURE_SETTLE_MS = 1500;
|
||||
const PREFERRED_SEEK_TARGET_WAIT_MS = 500;
|
||||
const DEFAULT_POST_SEEK_FONT_WAIT_MS = 500;
|
||||
|
||||
// The audit-grade seek tuning shared by check and the deprecated inspect/layout:
|
||||
// bridge+timeline fallback, ordered double-rAF settle, bounded font wait, sleep.
|
||||
@@ -257,8 +258,13 @@ export async function seekCompositionTimeline(
|
||||
);
|
||||
}
|
||||
|
||||
if (options.waitForFontsMs !== undefined) {
|
||||
await waitForCompositionFonts(page, options.waitForFontsMs);
|
||||
// On by default: every seek caller here is a visual-audit path (snapshot,
|
||||
// check, compare, validate, layout) and a seek that reveals unrequested
|
||||
// glyphs must not screenshot before their font subsets load. Costs one
|
||||
// evaluate + one rAF when nothing is loading. Pass 0 to disable.
|
||||
const waitForFontsMs = options.waitForFontsMs ?? DEFAULT_POST_SEEK_FONT_WAIT_MS;
|
||||
if (waitForFontsMs > 0) {
|
||||
await waitForCompositionFonts(page, waitForFontsMs);
|
||||
}
|
||||
if (options.settleMs !== undefined) {
|
||||
const settleMs = Math.max(0, options.settleMs);
|
||||
@@ -299,12 +305,31 @@ export async function waitForCompositionFonts(
|
||||
.evaluate((ms: number) => {
|
||||
const fonts = Reflect.get(document, "fonts");
|
||||
if (typeof fonts !== "object" || fonts === null) return Promise.resolve();
|
||||
const ready = Reflect.get(fonts, "ready");
|
||||
if (!ready) return Promise.resolve();
|
||||
return Promise.race([
|
||||
Promise.resolve(ready).then(() => undefined),
|
||||
new Promise<void>((resolve) => setTimeout(resolve, ms)),
|
||||
]);
|
||||
// A seek can reveal glyphs whose font faces were never requested —
|
||||
// CJK @font-face splits into unicode-range subsets that only load when
|
||||
// first laid out. `fonts.ready` is already-resolved at that moment, so
|
||||
// awaiting it immediately races the load request and screenshots blank
|
||||
// glyphs (wild reports: Traditional Chinese / Microsoft YaHei check
|
||||
// snapshots). Force a synchronous layout so pending subsets actually
|
||||
// start loading, give the loader one frame to flip `fonts.status`,
|
||||
// THEN await readiness.
|
||||
void document.body?.offsetHeight;
|
||||
return new Promise<void>((resolveWait) => {
|
||||
const deadline = setTimeout(resolveWait, ms);
|
||||
requestAnimationFrame(() => {
|
||||
const status = Reflect.get(fonts, "status");
|
||||
const ready = Reflect.get(fonts, "ready");
|
||||
if (status !== "loading" || !ready) {
|
||||
clearTimeout(deadline);
|
||||
resolveWait();
|
||||
return;
|
||||
}
|
||||
Promise.resolve(ready).then(() => {
|
||||
clearTimeout(deadline);
|
||||
resolveWait();
|
||||
});
|
||||
});
|
||||
});
|
||||
}, timeoutMs)
|
||||
.catch(() => {});
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user