mirror of
https://github.com/heygen-com/hyperframes.git
synced 2026-10-30 17:27:09 +00:00
perf(core): a preview's composition scripts give the page a turn every 50 ms, not one long task (#5713)
This commit is contained in:
@@ -82,6 +82,24 @@ function runInPlace(el: Element): HTMLScriptElement | undefined {
|
||||
return script;
|
||||
}
|
||||
|
||||
export const PREVIEW_SCRIPT_SLICE_MS = 50;
|
||||
|
||||
/** A render's virtual clock holds every timer until it advances, so only a preview gives the page turns. */
|
||||
const givesTurns = () => !(window as { __HF_VIRTUAL_TIME__?: unknown }).__HF_VIRTUAL_TIME__;
|
||||
|
||||
function nextUnclampedTask(): Promise<void> {
|
||||
const scheduler = (globalThis as { scheduler?: { yield?: () => Promise<void> } }).scheduler;
|
||||
if (typeof scheduler?.yield === "function") return scheduler.yield();
|
||||
return new Promise((resolve) => {
|
||||
const channel = new MessageChannel();
|
||||
channel.port1.onmessage = () => {
|
||||
channel.port1.close();
|
||||
resolve();
|
||||
};
|
||||
channel.port2.postMessage(null);
|
||||
});
|
||||
}
|
||||
|
||||
const loaded = (script: HTMLScriptElement) =>
|
||||
new Promise((resolve) => {
|
||||
script.addEventListener("load", resolve);
|
||||
@@ -113,7 +131,13 @@ export async function runScriptsAfterFonts(
|
||||
const isLate = (el: Element) => typeAfterFonts(el) === "module" || el.matches(DEFERRED_FILE);
|
||||
const held: HeldListener[] = [];
|
||||
const release = [document, window].map((target) => holdPassedLoadEvents(target, held));
|
||||
const turns = givesTurns();
|
||||
let slice = performance.now();
|
||||
for (const el of [...scripts.filter((el) => !isLate(el)), ...scripts.filter(isLate)]) {
|
||||
if (turns && performance.now() - slice > PREVIEW_SCRIPT_SLICE_MS) {
|
||||
await nextUnclampedTask();
|
||||
slice = performance.now();
|
||||
}
|
||||
const script = runInPlace(el);
|
||||
if (script?.hasAttribute("src") && !script.async && !script.noModule) await loaded(script);
|
||||
else if (typeAfterFonts(el) === "module") await afterQueuedModules();
|
||||
|
||||
@@ -4,7 +4,7 @@ import {
|
||||
AFTER_FONTS_SCRIPT_TYPE,
|
||||
deferScriptsUntilFonts,
|
||||
} from "../compiler/scriptRuns";
|
||||
import { FONT_WAIT_TIMEOUT_MS } from "./afterFonts";
|
||||
import { FONT_WAIT_TIMEOUT_MS, PREVIEW_SCRIPT_SLICE_MS } from "./afterFonts";
|
||||
|
||||
type Face = { family: string; status: FontFaceLoadStatus };
|
||||
|
||||
@@ -89,6 +89,37 @@ describe("runtime entry: composition scripts after web fonts", () => {
|
||||
expect(document.querySelectorAll(`script[type="${AFTER_FONTS_SCRIPT_TYPE}"]`)).toHaveLength(0);
|
||||
});
|
||||
|
||||
it.each([
|
||||
[
|
||||
"a preview gives the page a turn once a slice of scripts has run",
|
||||
false,
|
||||
"a in-place turn b ready ",
|
||||
],
|
||||
[
|
||||
"a render's virtual clock gets no turns, so its scripts run back to back",
|
||||
true,
|
||||
"a in-place b ready ",
|
||||
],
|
||||
])("%s", async (_name, rendering, expected) => {
|
||||
const clock = [0, 0, PREVIEW_SCRIPT_SLICE_MS + 1];
|
||||
vi.spyOn(performance, "now").mockImplementation(
|
||||
() => clock.shift() ?? PREVIEW_SCRIPT_SLICE_MS + 1,
|
||||
);
|
||||
const turn = vi.fn(async () => {
|
||||
document.getElementById("log")!.textContent += "turn ";
|
||||
});
|
||||
vi.stubGlobal("scheduler", { yield: turn });
|
||||
if (rendering) vi.stubGlobal("__HF_VIRTUAL_TIME__", {});
|
||||
serveFonts(Promise.resolve(), []);
|
||||
mountDeferredScripts();
|
||||
|
||||
await parseThenLoad();
|
||||
await vi.waitFor(() => expect(window.__player).toBeDefined());
|
||||
expect(log()).toBe(expected);
|
||||
expect(turn).toHaveBeenCalledTimes(rendering ? 0 : 1);
|
||||
vi.unstubAllGlobals();
|
||||
});
|
||||
|
||||
it("keeps an addEventListener wrapper a deferred script installs while its load events are held", async () => {
|
||||
serveFonts(Promise.resolve(), []);
|
||||
document.body.innerHTML =
|
||||
|
||||
Reference in New Issue
Block a user