From e48c8a0614f252413d191f49816a4809c44e860b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Miguel=20=C3=81ngel?= Date: Mon, 26 Oct 2026 01:35:00 +0000 Subject: [PATCH] perf(studio): a settled timeline's composition check scans only its hosts, not the whole preview (#5880) --- .../player/lib/timelineIframeHelpers.test.ts | 41 +++++++++++++++++++ .../src/player/lib/timelineIframeHelpers.ts | 7 +++- 2 files changed, 46 insertions(+), 2 deletions(-) diff --git a/packages/studio/src/player/lib/timelineIframeHelpers.test.ts b/packages/studio/src/player/lib/timelineIframeHelpers.test.ts index 7383a4ae31..b49a6e9593 100644 --- a/packages/studio/src/player/lib/timelineIframeHelpers.test.ts +++ b/packages/studio/src/player/lib/timelineIframeHelpers.test.ts @@ -118,6 +118,47 @@ describe("buildMissingCompositionElements — hfId (R7)", () => { }); }); +describe("buildMissingCompositionElements — on a settled timeline", () => { + // Studio runs this on every runtime state tick, so a timeline with nothing to add must cost one host scan. + const settled = () => + makeDoc(` +
+
+ ${Array.from({ length: 40 }, (_, i) => ``).join("")} +
+ `); + const rows = ["intro", ...Array.from({ length: 40 }, (_, i) => `clip-${i}`)].map((id, i) => ({ + id, + key: id, + tag: id === "intro" ? "div" : "video", + start: i, + duration: 1, + track: 0, + })); + + it("walks no more of the document than the hosts when nothing is missing or patchable", () => { + const doc = settled(); + const scans = vi.spyOn(doc, "querySelectorAll"); + const result = buildMissingCompositionElements(doc, window as IframeWindow, rows, 40); + expect(scans.mock.calls.map(([selector]) => selector)).toEqual([ + "[data-composition-id][data-start]", + ]); + expect([result.missing, result.patched, result.updatedEls]).toEqual([[], false, rows]); + }); + + it("still patches a row whose host carries its source", () => { + const doc = settled(); + doc.getElementById("intro")!.setAttribute("data-composition-src", "compositions/intro.html"); + const { patched, updatedEls } = buildMissingCompositionElements( + doc, + window as IframeWindow, + rows, + 40, + ); + expect([patched, updatedEls[0]?.compositionSrc]).toEqual([true, "compositions/intro.html"]); + }); +}); + describe("buildMissingCompositionElements — nested master time", () => { it("places hosts two and three levels deep at their master time", () => { const doc = makeDoc(` diff --git a/packages/studio/src/player/lib/timelineIframeHelpers.ts b/packages/studio/src/player/lib/timelineIframeHelpers.ts index dd75c6d20e..0ee8ce1d60 100644 --- a/packages/studio/src/player/lib/timelineIframeHelpers.ts +++ b/packages/studio/src/player/lib/timelineIframeHelpers.ts @@ -493,7 +493,7 @@ export function buildMissingCompositionElements( const hosts = doc.querySelectorAll("[data-composition-id][data-start]"); const missing: TimelineElement[] = []; - const resolveEnd = createReferenceEndResolver(createTimedElementLookup(doc), iframeWin); + let resolveEnd: ReturnType | undefined; const masterTime = createRuntimeStartTimeResolver({ timelineRegistry: iframeWin.__timelines, includeAuthoredTimingAttrs: true, @@ -505,6 +505,7 @@ export function buildMissingCompositionElements( const compId = el.getAttribute("data-composition-id"); if (!compId || compId === rootCompId) continue; if (existingIds.has(el.id) || existingIds.has(compId)) continue; + resolveEnd ??= createReferenceEndResolver(createTimedElementLookup(doc), iframeWin); const entry = buildMissingCompositionEntry({ doc, iframeWin, @@ -518,7 +519,9 @@ export function buildMissingCompositionElements( if (entry) missing.push(entry); } - // Patch existing elements that are missing compositionSrc + // Patch existing elements that are missing compositionSrc; only a host carrying its source can supply one. + if (!doc.querySelector("[data-composition-src], [data-composition-file]")) + return { missing, updatedEls: currentEls as TimelineElement[], patched: false }; let patched = false; const findHost = previewElementFinder(doc); const updatedEls = (currentEls as TimelineElement[]).map((existing) => {