Skip to content

perf(studio): stop font list work from landing in drag frames - #4794

Open
miguel-heygen wants to merge 6 commits into
mainfrom
perf/studio-font-list-on-demand
Open

miguel-heygen wants to merge 6 commits into
mainfrom
perf/studio-font-list-on-demand

Conversation

@miguel-heygen

@miguel-heygen miguel-heygen commented Sep 30, 2026 •

Copy link
Copy Markdown
Collaborator

What

Selecting an element used to fetch both font lists, and the ~1.8k Google font names were then deduped (uniqueFontFamilies) and scanned again with toLowerCase inside the font field. The response usually lands a few frames after the selection, so a drag that starts right away paid 1.4-2.9 ms of that JS inside one of its first frames.

Now:

  • Both lists are fetched once per session, when the first font field mounts, and live in a small module store (useSyncExternalStore).
  • The Google names are deduped, and their lowercase key set is built, only once input has been idle for the overlay loop's awake window. The work runs in requestIdleCallback slices of at most 3 ms, and input arriving between slices holds the next one back. The helper for this is runWhenInputIdle in overlayFrameLoop.ts, which reuses the loop's own last-input clock.
  • The trigger's stylesheet check is one Set lookup, so a selection does no list work.
  • A failed fetch clears the request, so the next mount or picker open retries.
  • The Local/Google tagging no longer walks the Google set. That walk was redundant, because uniqueFontOptions already keeps the Google entry first.

Tests

  • propertyPanelFont.test.tsx, "does no list work in a drag's frames when the lists land mid-drag": the lists land 200 ms into a 600 ms stream of pointermoves. It expects no uniqueFontFamilies call until input goes idle, and exactly one after.
  • "retries a failed list fetch when the picker opens".
  • overlayFrameLoop.test.ts, "runs idle work in capped slices, only while no input arrives".

With the three source files reverted to main, all three fail. With runWhenInputIdle turned into an immediate call, the mid-drag test fails.

Measurements

These come from traces with 0.1 ms JS samples, taken on the bench fixture on a shared 32-core Linux box under load. Each case is the first drag right after selecting.

  • Font-list JS inside the drag went from 1.4-2.9 ms (in one of frames 1-4) to 0.1-0.5 ms. What remains is the callback that receives the two responses and schedules the idle work.
  • The idle slice itself takes 2.8-3.6 ms of wall time, including the React update it triggers, and ran 1.4-2.4 s after the drag ended.

The bench's per-frame work on the first-drag rows (px, r0/r30, root, z100) does not resolve a ~2 ms change in one frame of ~40. Medians of work p95 / max, in ms:

gesture main this PR, run 1 this PR, run 2
move 6.36 / 7.33 6.55 / 7.22 6.28 / 7.66
resize 6.66 / 7.81 6.39 / 7.49 7.44 / 8.23
rotate 4.79 / 5.81 3.70 / 4.75 4.08 / 4.41
crop 5.53 / 6.01 5.31 / 5.33 5.70 / 6.18
nudge 5.30 / 5.30 6.99 / 6.99 6.04 / 6.04

On this box, two runs of the same build differ by up to ~1 ms per gesture, so these rows show no regression and no resolvable gain. The trace numbers above are the measurement.

Before

Before: 1.4-2.9 ms of font-list JS inside one of the drag's first frames

After

After: 0.1-0.5 ms in the drag; the list is processed in an idle slice after input stops

@miguel-heygen miguel-heygen changed the title perf(studio): load the font lists when the font picker first opens perf(studio): stop font list work from landing in drag frames Sep 30, 2026
@miguel-heygen
miguel-heygen marked this pull request as ready for review October 1, 2026 00:42

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant