mirror of
https://github.com/heygen-com/hyperframes.git
synced 2026-09-03 04:38:33 +00:00
fix(core): route window.__hyperframes to the scoped variant in sub-comps (#1931)
Sub-composition scripts run inside a wrapper that passes the SCOPED
__hyperframes (per-instance getVariables) as a bare script param, while
`window` is a Proxy. That proxy intercepted only __timelines, so
`window.__hyperframes` fell through to the HOST page's base
__hyperframes — whose getVariables reads the host's variables, not this
instance's. So the two documented spellings diverged: the bare
`__hyperframes.getVariables()` param returned the correct per-instance
values, but `window.__hyperframes.getVariables()` returned the wrong
(host / empty) ones, silently rendering every reused instance with the
first instance's content (or defaults).
docs/concepts/variables.mdx already promises both forms "work in both
top-level and sub-composition scripts ... each instance sees its own
resolved values" — the runtime just didn't honor it. Reported directly
(a user lost significant debugging time across three parametrized
sub-comps before discovering the bare param was the only form that
worked), and matches an earlier deferred finding that getVariables()
returns {} for reused sub-comp instances.
Fix: the scoped `window` proxy now returns the scoped __hyperframes for
`prop === "__hyperframes"`, so window.__hyperframes.getVariables() and
the bare param resolve identically to this composition's own variables.
The scoped variant is Object.assign({}, base, { getVariables }), so all
other __hyperframes members still pass through to the base unchanged.
Test: two new executed-wrapper cases (new Function(...)(fakeWindow)) —
window.__hyperframes.getVariables() now returns the per-comp variables
instead of the TOP-LEVEL-LEAK host value, and a non-getVariables member
(fitTextFontSize) still reaches the base. Full core suite (1092) passes.
This commit is contained in:
@@ -79,6 +79,59 @@ body { margin: 0; }
|
||||
expect(fakeWindow.__captured).toEqual({ title: "Pro", price: "$29" });
|
||||
});
|
||||
|
||||
it("routes the documented window.__hyperframes.getVariables() to the scoped variant too", () => {
|
||||
// Regression: the docs (variables-and-media.md) show `window.__hyperframes.
|
||||
// getVariables()`, but inside a sub-comp the scoped `window` proxy used to
|
||||
// fall through to the HOST page's base __hyperframes, returning the wrong
|
||||
// (or empty) variables — the bare `__hyperframes` param was the only form
|
||||
// that worked. Both spellings must now resolve to this comp's variables.
|
||||
const { document } = parseHTML(`<div data-composition-id="card-1"></div>`);
|
||||
const fakeWindow: Record<string, unknown> = {
|
||||
document,
|
||||
__timelines: {},
|
||||
__hfVariablesByComp: {
|
||||
"card-1": { title: "Pro", price: "$29" },
|
||||
"card-2": { title: "Enterprise", price: "Custom" },
|
||||
},
|
||||
__hyperframes: {
|
||||
getVariables: () => ({ title: "TOP-LEVEL-LEAK" }),
|
||||
fitTextFontSize: () => undefined,
|
||||
},
|
||||
};
|
||||
const wrapped = wrapScopedCompositionScript(
|
||||
`window.__captured = window.__hyperframes.getVariables();`,
|
||||
"card-1",
|
||||
);
|
||||
|
||||
new Function("window", wrapped)(fakeWindow);
|
||||
|
||||
expect(fakeWindow.__captured).toEqual({ title: "Pro", price: "$29" });
|
||||
});
|
||||
|
||||
it("preserves non-getVariables members on window.__hyperframes (only getVariables is rescoped)", () => {
|
||||
const { document } = parseHTML(`<div data-composition-id="card-1"></div>`);
|
||||
let fitCalled = false;
|
||||
const fakeWindow: Record<string, unknown> = {
|
||||
document,
|
||||
__timelines: {},
|
||||
__hfVariablesByComp: { "card-1": { title: "Pro" } },
|
||||
__hyperframes: {
|
||||
getVariables: () => ({ title: "TOP-LEVEL-LEAK" }),
|
||||
fitTextFontSize: () => {
|
||||
fitCalled = true;
|
||||
},
|
||||
},
|
||||
};
|
||||
const wrapped = wrapScopedCompositionScript(
|
||||
`window.__hyperframes.fitTextFontSize();`,
|
||||
"card-1",
|
||||
);
|
||||
|
||||
new Function("window", wrapped)(fakeWindow);
|
||||
|
||||
expect(fitCalled).toBe(true);
|
||||
});
|
||||
|
||||
it("scoped getVariables reads from the runtime composition id when it differs", () => {
|
||||
const { document } = parseHTML(`<div data-composition-id="scene"></div>`);
|
||||
const fakeWindow: Record<string, unknown> = {
|
||||
|
||||
@@ -396,6 +396,16 @@ export function wrapScopedCompositionScript(
|
||||
? new Proxy(window, {
|
||||
get: function(target, prop, receiver) {
|
||||
if (prop === "__timelines") return __hfGetTimelineRegistry();
|
||||
// Inside a sub-composition, __hyperframes is passed as a bare script
|
||||
// param bound to the SCOPED variant (per-comp getVariables). But
|
||||
// authors routinely write the documented window.__hyperframes.
|
||||
// getVariables() form, which would otherwise fall through to the host
|
||||
// page's base __hyperframes and return the WRONG (or empty) variables
|
||||
// for this instance. Route it to the scoped variant too so both
|
||||
// spellings resolve to this composition's own variables.
|
||||
// (__hfScopedHyperframes is a hoisted var assigned below, before any
|
||||
// sub-comp script -- the only code that reads this -- runs.)
|
||||
if (prop === "__hyperframes") return __hfScopedHyperframes;
|
||||
return Reflect.get(target, prop, target);
|
||||
},
|
||||
set: function(target, prop, value, receiver) {
|
||||
|
||||
Reference in New Issue
Block a user