diff --git a/packages/cli/src/capture/captureCompositionFrame.test.ts b/packages/cli/src/capture/captureCompositionFrame.test.ts index a8954a6e5..37507bed2 100644 --- a/packages/cli/src/capture/captureCompositionFrame.test.ts +++ b/packages/cli/src/capture/captureCompositionFrame.test.ts @@ -339,3 +339,21 @@ describe("runFfmpegOnce", () => { } }); }); + +describe("installPageFunctionGuard", () => { + it("defines the keepNames __name shim in the page before any script runs", async () => { + const evaluateOnNewDocument = vi.fn(async () => undefined); + + await installPageFunctionGuard({ evaluateOnNewDocument }); + + expect(evaluateOnNewDocument).toHaveBeenCalledOnce(); + const source = evaluateOnNewDocument.mock.calls[0]?.[0] as string; + expect(source).toContain("self.__name"); + // The shim must be a no-op passthrough so wrapped functions stay callable. + const shim = new Function(`const self = {}; ${source}; return self.__name;`)() as ( + fn: unknown, + ) => unknown; + const marker = () => 42; + expect(shim(marker)).toBe(marker); + }); +}); diff --git a/packages/cli/src/capture/captureCompositionFrame.ts b/packages/cli/src/capture/captureCompositionFrame.ts index b6350718d..2b0d16409 100644 --- a/packages/cli/src/capture/captureCompositionFrame.ts +++ b/packages/cli/src/capture/captureCompositionFrame.ts @@ -131,6 +131,19 @@ async function waitForCompositionSettle( return runtimeReady; } +// tsx/esbuild-style dev transpilers run with keepNames, which rewrites named +// inner functions in serialized page closures into __name(...) calls — a +// helper that exists in the Node bundle but not in the browser realm, so any +// page.evaluate/waitForFunction whose closure defines a named function throws +// "__name is not defined" when the CLI runs from source. Defining a no-op in +// the page before any script runs immunizes every serialized closure, current +// and future, regardless of how the CLI was built. +export async function installPageFunctionGuard(page: { + evaluateOnNewDocument(source: string): Promise; +}): Promise { + await page.evaluateOnNewDocument("self.__name = self.__name || ((fn) => fn);"); +} + export async function openSettledCompositionPage( html: string, url: string, @@ -154,6 +167,7 @@ export async function openSettledCompositionPage( }); const page = await chromeBrowser.newPage(); + await installPageFunctionGuard(page); await page.setViewport(viewport); await options.beforeNavigate?.(page); await page.goto(url, { waitUntil: "domcontentloaded", timeout: 10000 }); diff --git a/packages/cli/src/commands/layout.ts b/packages/cli/src/commands/layout.ts index 35202c874..a7025d787 100644 --- a/packages/cli/src/commands/layout.ts +++ b/packages/cli/src/commands/layout.ts @@ -28,6 +28,7 @@ import { import { findMotionSpec, readMotionSpec, type MotionSpec } from "../utils/motionSpec.js"; import { AUDIT_SEEK_OPTIONS, + installPageFunctionGuard, seekCompositionTimeline, waitForCompositionFonts, type SeekCompositionTimelineOptions, @@ -214,6 +215,7 @@ async function runLayoutAudit( }); const page = await chromeBrowser.newPage(); + await installPageFunctionGuard(page); await page.setViewport({ width: 1920, height: 1080 }); await page.goto(server.url, { waitUntil: "domcontentloaded", timeout: 10000 }); await alignViewportToComposition(page, server.url); diff --git a/packages/cli/src/commands/validate.ts b/packages/cli/src/commands/validate.ts index 8ce32879d..a3b99ae40 100644 --- a/packages/cli/src/commands/validate.ts +++ b/packages/cli/src/commands/validate.ts @@ -15,6 +15,7 @@ import { resolveCompositionViewportFromHtml } from "../utils/compositionViewport import { c } from "../ui/colors.js"; import { printDeprecationNotice, withMeta } from "../utils/updateCheck.js"; import { + installPageFunctionGuard, resolveCliChromeGpuMode, seekCompositionTimeline, } from "../capture/captureCompositionFrame.js"; @@ -421,6 +422,7 @@ async function validateInBrowser( }); const page = await chromeBrowser.newPage(); + await installPageFunctionGuard(page); await page.setViewport(viewport); page.on("console", (msg) => { diff --git a/registry/examples/motion-blur/index.html b/registry/examples/motion-blur/index.html index dacf6a0e5..034cc42bf 100644 --- a/registry/examples/motion-blur/index.html +++ b/registry/examples/motion-blur/index.html @@ -199,7 +199,9 @@
-
3.0seconds
+
+ 3.0seconds +
shape
text
@@ -225,7 +227,9 @@
-
1.5seconds
+
+ 1.5seconds +
shape
text
@@ -251,7 +255,9 @@
-
0.7seconds
+
+ 0.7seconds +
shape
text
@@ -305,7 +311,9 @@
-
0.2seconds
+
+ 0.2seconds +
shape
text