@mui/internal-docs-infra
Version:
MUI Infra - internal documentation creation tools.
951 lines (928 loc) • 36 kB
JavaScript
import { performanceMeasure } from "../pipeline/loadPrecomputedCodeHighlighter/performanceLogger.mjs";
// `yieldToMain` is used here to push a user-supplied `preload` into a fresh
// macrotask so the browser can paint the just-announced loading state before the
// (potentially CPU-bound) preload monopolizes the main thread.
import { yieldToMain } from "./scheduleTasks.mjs";
/**
* Generic same-tab preference coordinator. Its primary purpose is
* to **fold many concurrent value changes into a single layout-shift
* commit**: sibling component instances that share a `channelKey`
* coordinate so the visible flip happens together rather than as a
* cascade of independent re-layouts.
*
* Each peer self-classifies a given target value via
* `causesLayoutShift` (consulted for non-originator peers only;
* originators always take the barrier path so a user click always
* feels coordinated):
*
* - **`causesLayoutShift(target) === true`** — the peer joins a
* channel-wide barrier. Every joining peer's `preload` runs
* serially across the channel (so no main-thread contention
* while we prepare the swap), and all `onCommit`s fire together
* in a single microtask once everyone is ready. Use for changes
* that visibly resize content (collapse/expand, code transforms,
* image swaps with different aspect ratios).
* - **`causesLayoutShift(target) === false`** — the peer runs its
* `preload`+`commit` on its own self-serial chain. Multiple
* peers' lazy chains run concurrently with each other and with
* any in-flight barrier. Use for changes that are visually
* non-disruptive (e.g. updating a value that only shows on hover).
*
* Different peers may classify the same target differently — each
* peer's classification governs only that peer's path through the
* coordinator.
*
* **Cross-tab behavior is intentionally out of scope.** Tabs sync via
* the underlying state primitive (`useLocalStorageState` etc.); this
* coordinator only handles peers in the same JS context. A receiving
* tab independently runs its own barrier across its local peers,
* which is sequenced naturally after the originator's commit because
* the originator defers the underlying `setValue` write until its
* own barrier commits (see `useCoordinated`).
*
* No React, no DOM, no BroadcastChannel — pure module-scoped state
* suitable for any state primitive.
*
* **Browser-only.** All state (channels, barriers, lazy queues) is
* held in module scope and would persist across requests if this
* module were ever evaluated in a long-lived server-side runtime.
* The consuming surface is the `useCoordinated` React hook, whose
* `registerPeer`/`announceTarget` calls are gated behind
* `useLayoutEffect`/event handlers and therefore never reached
* during SSR. Do not import this module from server-side code
* paths that fan out per request.
*/
/** Identifier assigned to each peer at registration time. */
/** Channel scope. Peers sharing a `channelKey` coordinate with each other. */
/**
* Sentinel returned by the coordinator's `announceTarget` when no
* commit is needed for this peer (e.g. its current value already
* matches the target). Always-defined to keep the API ergonomic.
*/
const DEFAULT_MIN_WAIT_MS = 0;
const DEFAULT_GRACE_PERIOD_MS = 300;
const DEFAULT_ULTIMATE_TIMEOUT_MS = 10_000;
const PERF_FUNCTION_NAME = 'Coordinate Preference';
/**
* Detects a browser-like host. Used by the public entry points to
* no-op when the module is reached outside the browser. The
* consuming `useCoordinated` hook already gates its calls behind
* `useLayoutEffect` and event handlers (which never run on the
* server), but this is defense-in-depth: a stray non-effect caller
* would otherwise leak module-state across SSR requests as warned
* in the file header. Detection runs once at module-evaluation time
* — re-evaluating per call would pessimize the hot path and the
* runtime can't switch between server and browser mid-process.
*/
const IS_BROWSER_HOST = typeof window !== 'undefined' && typeof document !== 'undefined';
const NOOP_UNREGISTER = () => {};
const NOOP_ANNOUNCE_HANDLE = {
cancel: () => {},
settled: Promise.resolve()
};
/**
* Fired on a registered peer when *another* peer in the same channel
* calls `announceTarget`. Lets a peer learn about a sibling-driven
* change without having to wait for the underlying state primitive
* (e.g. `useLocalStorageState`) to echo the new value back — that
* echo only happens after the originator commits, which itself is
* gated on every sibling joining the barrier. Without this hook
* sibling peers would deadlock the barrier until
* `ultimateTimeoutMs` for any same-tab coordination where the
* underlying primitive only notifies after the originator's write.
*
* Implementations should typically call into their local equivalent
* of `runCoordination(target, isOriginator=false)` so the peer
* joins the active barrier (or kicks off its own lazy chain on the
* same wall-clock window). Implementations must be idempotent for
* repeated calls with the same `target` because notifications can
* fan out from each subsequent join.
*/
const channels = new Map();
/**
* Override hook for tests that need a deterministic target encoder
* (e.g. when the value type contains unstable references). Production
* callers should leave this alone — the default `JSON.stringify` is
* sufficient for primitive and plain-object values.
*/
let encodeTargetImpl = value => {
if (value === null) {
return '\u0000null';
}
if (value === undefined) {
return '\u0000undefined';
}
if (typeof value === 'string') {
return `s:${value}`;
}
if (typeof value === 'number' || typeof value === 'boolean') {
return `p:${String(value)}`;
}
try {
return `j:${JSON.stringify(value)}`;
} catch {
return `o:${Object.prototype.toString.call(value)}`;
}
};
/**
* Set a custom target encoder. Returns a function that restores the
* previous encoder. Intended for tests; consumed via
* `coordinatePreference.testUtils`.
*/
function setTargetEncoder(impl) {
const previous = encodeTargetImpl;
encodeTargetImpl = impl;
return () => {
encodeTargetImpl = previous;
};
}
function encodeTarget(value) {
return encodeTargetImpl(value);
}
function getOrCreateChannel(channelKey) {
let channel = channels.get(channelKey);
if (!channel) {
channel = {
channelKey,
peers: new Map(),
hasEverAnnounced: false,
barrierTail: Promise.resolve(),
pendingBarriers: new Map()
};
channels.set(channelKey, channel);
}
return channel;
}
function disposeChannelIfEmpty(channel) {
if (channel.peers.size === 0 && channel.pendingBarriers.size === 0) {
channels.delete(channel.channelKey);
}
}
/**
* Register a peer with a channel. Returns an `unregister` function
* that removes the peer; calling it cancels any in-flight lazy-path
* work owned by the peer and drops it from any open barrier-path barriers.
*
* Pass `onSiblingAnnounce` to learn about target announcements made
* by other peers on the channel — this is what lets a peer join the
* originator's barrier window without waiting for the underlying
* state primitive to echo the new value (which only happens after
* the originator commits, creating a deadlock when every peer is
* waiting on it).
*/
export function registerPeer(channelKey, peerId, onSiblingAnnounce) {
if (!IS_BROWSER_HOST) {
return NOOP_UNREGISTER;
}
const channel = getOrCreateChannel(channelKey);
if (channel.peers.has(peerId)) {
throw /* minify-error */new Error(`coordinatePreference: peer '${peerId}' is already registered on channel '${channelKey}'. ` + 'Each peer must have a unique id within a channel. ' + 'See https://mui.com/r/docs-infra-coordinate-preference for more info.');
}
const peer = {
id: peerId,
currentValue: {
has: false
},
onSiblingAnnounce: onSiblingAnnounce,
lazyInFlight: new Map(),
lazyQueue: [],
lazyActive: false
};
channel.peers.set(peerId, peer);
// Replay every open barrier's announcement to the newcomer so it
// joins the quorum (as waiter or skipped) instead of stalling the
// barrier until its `ultimateTimer` fires. Without this fan-out,
// a peer that registers after a barrier has opened never learns
// about the in-flight target: `maybeResolveBarrier` keeps blocking
// on `waiters + skipped >= peers.size` while the newcomer sits
// idle (it has nothing to receive against because no one announced
// *to* it). The newcomer's own `onSiblingAnnounce` is the exact
// hook the within-tab `notifySiblings` path uses for the analogous
// already-registered case, so reusing it keeps both paths in sync.
//
// Deferred to a microtask because consumers register from
// `useInsertionEffect` (where React forbids scheduling updates),
// and the announce callback ultimately drives `setState` through
// `runCoordination`. The microtask still fires before any paint so
// the barrier sees the newcomer join the same flush.
if (onSiblingAnnounce && channel.pendingBarriers.size > 0) {
const callback = onSiblingAnnounce;
const barriersSnapshot = Array.from(channel.pendingBarriers.values());
queueMicrotask(() => {
if (channel.peers.get(peerId) !== peer) {
return;
}
for (const barrier of barriersSnapshot) {
const barrierKey = encodeTarget(barrier.target);
if (channel.pendingBarriers.get(barrierKey) !== barrier) {
continue;
}
// Mirror the `notifySiblings` skip rule: if the newcomer has
// already reported a committed value matching the barrier
// target (e.g. `useTransformManagement` persists the
// transform before dispatch, so a late-mounting demo reads
// the target immediately and calls `reportValue` with it
// during its own insertion-effect), record it as skipped
// rather than firing the receiver flow. Without this, the
// replayed announce can re-enter `runCoordination`, get
// classified as layout-shifting, and turn a peer that
// should stay skipped into a waiter — needlessly extending
// the barrier and the preload work.
if (peer.currentValue.has && Object.is(peer.currentValue.value, barrier.target)) {
if (!barrier.waiters.has(peerId) && !barrier.skipped.has(peerId)) {
barrier.skipped.add(peerId);
maybeResolveBarrier(channel, barrier);
}
continue;
}
if (barrier.waiters.has(peerId) || barrier.skipped.has(peerId)) {
continue;
}
try {
callback(barrier.target);
} catch (err) {
console.error(`[docs-infra/coordinatePreference] onSiblingAnnounce on register for peer ` + `'${peerId}' on channel '${channelKey}' threw:`, err);
}
}
});
}
return () => {
const stillPresent = channel.peers.get(peerId);
if (stillPresent !== peer) {
return;
}
channel.peers.delete(peerId);
for (const controller of peer.lazyInFlight.keys()) {
controller.abort();
}
peer.lazyInFlight.clear();
peer.lazyQueue.length = 0;
peer.lazyActive = false;
// Drop this peer from any open barriers and re-check resolution.
// We must also clear it from `skipped` so a later replacement peer
// registering with the same coordinates isn't silently "covered"
// by the stale entry — the quorum check counts
// `waiters + skipped` against the current peer set, so a leftover
// skipped id could let a barrier commit without the new peer ever
// joining. We re-check every barrier (not just ones the peer was
// a waiter on) because shrinking `channel.peers.size` can make a
// barrier resolvable even when the departing peer had only
// registered and never joined — otherwise the barrier would sit
// open until the next unrelated event or the ultimate timeout.
for (const barrier of channel.pendingBarriers.values()) {
const waiter = barrier.waiters.get(peerId);
if (waiter) {
waiter.abort.abort();
barrier.waiters.delete(peerId);
}
barrier.skipped.delete(peerId);
maybeResolveBarrier(channel, barrier);
}
disposeChannelIfEmpty(channel);
};
}
/**
* Report a peer's current value to the coordinator. Used to exclude
* already-at-target peers from barrier expectations.
*/
export function reportValue(channelKey, peerId, currentValue) {
const channel = channels.get(channelKey);
if (!channel) {
return;
}
const peer = channel.peers.get(peerId);
if (!peer) {
return;
}
peer.currentValue = {
has: true,
value: currentValue
};
// A peer that is already committed to an open barrier's target does
// not need to announce again. Mark it as satisfied so the barrier
// doesn't wait for a no-op receiver flow that will never run.
for (const barrier of channel.pendingBarriers.values()) {
if (barrier.waiters.has(peerId)) {
continue;
}
if (!Object.is(barrier.target, currentValue)) {
continue;
}
barrier.skipped.add(peerId);
maybeResolveBarrier(channel, barrier);
}
}
/**
* Announce a target value for this peer. Routes into the barrier or
* lazy path based on `causesLayoutShift(target)`.
*
* For the barrier path: the peer joins the channel-wide barrier for
* this target (creating it if needed), enqueues its `preload` into
* the channel's serial queue, and awaits the barrier's batched
* commit.
*
* For the lazy path: the peer enqueues `preload` + `onCommit` onto
* its own self-serial chain and returns immediately. Multiple peers'
* lazy chains run concurrently with each other and with any
* in-flight barrier work.
*/
export function announceTarget(channelKey, peerId, target, options) {
if (!IS_BROWSER_HOST) {
return NOOP_ANNOUNCE_HANDLE;
}
const channel = getOrCreateChannel(channelKey);
const peer = channel.peers.get(peerId);
if (!peer) {
throw /* minify-error */new Error(`coordinatePreference: peer '${peerId}' is not registered on channel '${channelKey}'. ` + 'Call `registerPeer` before `announceTarget`. ' + 'See https://mui.com/r/docs-infra-coordinate-preference for more info.');
}
if (options.isOriginator || options.causesLayoutShift(target)) {
channel.hasEverAnnounced = true;
performanceMeasure(undefined, {
mark: 'announce-barrier',
measure: 'announce-barrier'
}, [PERF_FUNCTION_NAME, channelKey, peerId]);
const handle = joinOrOpenBarrier(channel, peer, target, options);
notifySiblings(channel, peer, target);
return handle;
}
channel.hasEverAnnounced = true;
performanceMeasure(undefined, {
mark: 'announce-lazy',
measure: 'announce-lazy'
}, [PERF_FUNCTION_NAME, channelKey, peerId]);
const handle = enqueueLazy(channel, peer, target, options);
notifySiblings(channel, peer, target);
return handle;
}
/**
* Synchronously fan out a target announcement to every other peer on
* the channel whose state is known to differ from the target (or
* whose state is unknown). Peers that have already joined the active
* barrier for this target (as waiter or skipped) are excluded so we
* don't re-enter their `runCoordination` and cancel the in-flight
* announcement we're about to satisfy.
*
* This is the within-tab analogue of cross-tab storage echoes: it
* lets sibling peers join the originator's barrier window before the
* originator has written through to the underlying state primitive.
* Without it, two sibling peers sharing a `useLocalStorageState` (or
* any other primitive that only fires on commit) would deadlock the
* barrier until `ultimateTimeoutMs`.
*/
function notifySiblings(channel, announcer, target) {
if (channel.peers.size <= 1) {
return;
}
const barrierKey = encodeTarget(target);
const barrier = channel.pendingBarriers.get(barrierKey);
// Snapshot the peer list first — a callback may register or
// unregister peers reentrantly which would invalidate live iteration.
const siblings = [];
for (const otherPeer of channel.peers.values()) {
if (otherPeer.id === announcer.id) {
continue;
}
if (!otherPeer.onSiblingAnnounce) {
continue;
}
if (otherPeer.currentValue.has && Object.is(otherPeer.currentValue.value, target)) {
continue;
}
if (barrier && (barrier.waiters.has(otherPeer.id) || barrier.skipped.has(otherPeer.id))) {
continue;
}
siblings.push(otherPeer);
}
for (const otherPeer of siblings) {
try {
otherPeer.onSiblingAnnounce(target);
} catch (err) {
console.error(`[docs-infra/coordinatePreference] onSiblingAnnounce for peer '${otherPeer.id}' on channel ` + `'${channel.channelKey}' threw:`, err);
}
}
}
function joinOrOpenBarrier(channel, peer, target, options) {
const barrierKey = encodeTarget(target);
let barrier = channel.pendingBarriers.get(barrierKey);
const announceTime = options.announceTime;
const minWaitMs = (options.minWaitMs ?? DEFAULT_MIN_WAIT_MS) + (channel.peers.size > 1 ? options.multiPeerExtraMinWaitMs ?? 0 : 0);
const gracePeriodMs = options.gracePeriodMs ?? DEFAULT_GRACE_PERIOD_MS;
const ultimateTimeoutMs = options.ultimateTimeoutMs ?? DEFAULT_ULTIMATE_TIMEOUT_MS;
if (!barrier) {
const now = Date.now();
const minRemaining = Math.max(0, announceTime + minWaitMs - now);
const waitingRemaining = Math.max(minRemaining, announceTime + minWaitMs + gracePeriodMs - now);
const ultimateRemaining = Math.max(waitingRemaining, announceTime + ultimateTimeoutMs - now);
const created = {
target,
announceTime,
waiters: new Map(),
skipped: new Set(),
minWaitPassed: minRemaining === 0,
minWaitTimer: setTimeout(() => {
const current = channel.pendingBarriers.get(barrierKey);
if (!current || current !== barrier) {
return;
}
current.minWaitPassed = true;
maybeResolveBarrier(channel, current);
}, minRemaining),
waitingForPeersTimer: setTimeout(() => {
notifyWaitingForPeers(channel, barrierKey);
}, waitingRemaining),
waitingForPeersNotified: false,
ultimateTimer: setTimeout(() => {
const current = channel.pendingBarriers.get(barrierKey);
if (!current) {
return;
}
console.warn(`[docs-infra/coordinatePreference] Barrier on channel '${channel.channelKey}' ` + `force-resolved after ${ultimateTimeoutMs}ms; ` + `${current.waiters.size} waiter(s) still pending. ` + 'A peer likely unmounted or crashed mid-preload.');
forceResolveBarrier(channel, barrierKey);
}, ultimateRemaining),
ultimateTimeoutMs,
deferredLazyReleases: []
};
barrier = created;
channel.pendingBarriers.set(barrierKey, barrier);
created.openMark = performanceMeasure(undefined, {
mark: 'barrier-open',
measure: 'barrier-open'
}, [PERF_FUNCTION_NAME, channel.channelKey, barrierKey]);
// Peers that already routed to the lazy path for *this same
// target* shouldn't gate the new barrier — they'll commit
// lazily on their own clock and we'd otherwise wait for a peer
// that has no intention of joining. Crucially, we must NOT skip
// a peer whose pending lazy work is for a *different* target:
// the upcoming `notifySiblings` call will pull that peer onto
// the new barrier, but if the barrier is also zero-wait with a
// synchronous originator preload it can `maybeResolveBarrier`
// before that notification runs, leaving the peer stranded on
// the wrong value.
for (const otherPeer of channel.peers.values()) {
if (otherPeer.id === peer.id) {
continue;
}
if (otherPeer.currentValue.has && Object.is(otherPeer.currentValue.value, target)) {
created.skipped.add(otherPeer.id);
continue;
}
for (const lazyTarget of otherPeer.lazyInFlight.values()) {
if (Object.is(lazyTarget, target)) {
created.skipped.add(otherPeer.id);
break;
}
}
}
}
let settleResolver = () => {};
const settled = new Promise(resolve => {
settleResolver = resolve;
});
const abort = new AbortController();
const waiter = {
peerId: peer.id,
isOriginator: options.isOriginator,
preloaded: {
has: false
},
onCommit: options.onCommit,
onWaitingForPeers: options.onWaitingForPeers,
settle: settleResolver,
abort
};
barrier.waiters.set(peer.id, waiter);
barrier.skipped.delete(peer.id);
// If grace already fired, fire this waiter's onWaitingForPeers now
// so late originators still get the cue.
if (barrier.waitingForPeersNotified && waiter.onWaitingForPeers && waiter.isOriginator) {
try {
waiter.onWaitingForPeers();
} catch {
// Swallow per-waiter errors.
}
}
// Queue this peer's preload onto the channel's serial barrier
// tail. Each peer's (possibly CPU-bound) preload runs in its own
// macrotask via `yieldToMain` so the browser can paint any
// intermediate loading state before preload monopolizes the main
// thread, and so siblings on the same barrier serialize cleanly
// instead of piling onto the announce-fanout task.
const preload = options.preload;
if (!preload) {
waiter.preloaded = {
has: true,
value: undefined
};
maybeResolveBarrier(channel, barrier);
} else {
const runPreload = async () => {
if (abort.signal.aborted) {
return;
}
await yieldToMain();
if (abort.signal.aborted) {
return;
}
let value;
try {
value = await preload(target, abort.signal);
} catch (err) {
if (abort.signal.aborted) {
return;
}
console.error(`[docs-infra/coordinatePreference] Preload for peer '${peer.id}' on channel ` + `'${channel.channelKey}' threw; treating as no-op. Error:`, err);
}
if (abort.signal.aborted) {
return;
}
waiter.preloaded = {
has: true,
value
};
maybeResolveBarrier(channel, barrier);
};
const previousTail = channel.barrierTail;
const myTurn = previousTail.then(runPreload);
channel.barrierTail = myTurn.catch(() => undefined);
}
return {
cancel: () => {
abort.abort();
const stillPending = channel.pendingBarriers.get(barrierKey);
if (stillPending && stillPending === barrier) {
stillPending.waiters.delete(peer.id);
if (stillPending.waiters.size === 0) {
clearTimeout(stillPending.minWaitTimer);
clearTimeout(stillPending.waitingForPeersTimer);
clearTimeout(stillPending.ultimateTimer);
channel.pendingBarriers.delete(barrierKey);
disposeChannelIfEmpty(channel);
} else {
maybeResolveBarrier(channel, stillPending);
}
}
settleResolver();
},
settled
};
}
function notifyWaitingForPeers(channel, barrierKey) {
const barrier = channel.pendingBarriers.get(barrierKey);
if (!barrier || barrier.waitingForPeersNotified) {
return;
}
barrier.waitingForPeersNotified = true;
for (const waiter of barrier.waiters.values()) {
if (!waiter.onWaitingForPeers || !waiter.isOriginator) {
continue;
}
try {
waiter.onWaitingForPeers();
} catch {
// Swallow per-waiter errors.
}
}
}
function maybeResolveBarrier(channel, barrier) {
if (!barrier.minWaitPassed) {
return;
}
// Wait for every *registered* peer on this channel to join the
// barrier, not just the ones that have already announced. A peer
// that registered (`registerPeer`) but hasn't yet announced a
// target is still expected to participate — resolving without it
// would let the originator commit early and break lockstep.
// The barrier's `ultimateTimer` is the safety net for a peer that
// never joins (crashed / unmounted mid-precompute).
if (barrier.waiters.size + barrier.skipped.size < channel.peers.size) {
return;
}
for (const waiter of barrier.waiters.values()) {
if (!waiter.preloaded.has) {
return;
}
}
const barrierKey = encodeTarget(barrier.target);
forceResolveBarrier(channel, barrierKey);
}
function forceResolveBarrier(channel, barrierKey) {
const barrier = channel.pendingBarriers.get(barrierKey);
if (!barrier) {
return;
}
performanceMeasure(barrier.openMark, {
mark: 'barrier-resolve',
measure: 'barrier-resolve'
}, [PERF_FUNCTION_NAME, channel.channelKey, barrierKey], true);
clearTimeout(barrier.minWaitTimer);
clearTimeout(barrier.waitingForPeersTimer);
clearTimeout(barrier.ultimateTimer);
channel.pendingBarriers.delete(barrierKey);
// Fire all onCommits in the same microtask so React batches them.
for (const waiter of barrier.waiters.values()) {
const preloaded = waiter.preloaded.has ? waiter.preloaded.value : undefined;
try {
waiter.onCommit(barrier.target, preloaded);
} catch (err) {
console.error(`[docs-infra/coordinatePreference] onCommit for peer '${waiter.peerId}' on channel ` + `'${channel.channelKey}' threw:`, err);
}
waiter.settle();
}
// Release any lazy peers that were gated on this barrier. The
// macrotask hop puts their commits in the *render after* the
// barrier's batched commit — keeping the main thread clear while
// the layout-shifting siblings paint, and ensuring the visible
// flip on the lazy peers never beats the barrier siblings to the
// DOM.
const releases = barrier.deferredLazyReleases;
if (releases.length > 0) {
setTimeout(() => {
for (const release of releases) {
try {
release();
} catch (err) {
console.error(`[docs-infra/coordinatePreference] deferred lazy release on channel ` + `'${channel.channelKey}' threw:`, err);
}
}
}, 0);
}
disposeChannelIfEmpty(channel);
}
function enqueueLazy(channel, peer, target, options) {
// Barrier-coordination is resolved inside `gateStart` below (one
// microtask after enqueue) so a barrier opened by a sibling peer
// in the same sync flush is observable before we route.
let settleResolver = () => {};
const settled = new Promise(resolve => {
settleResolver = resolve;
});
const abort = new AbortController();
peer.lazyInFlight.set(abort, target);
const lazyWait = options.lazyMinWaitMs ?? options.minWaitMs ?? 0;
// Per-peer serialization: callbacks for preload, timer, and commit
// are kicked off only when this peer's lazy queue reaches us. We
// chain via callbacks (not Promise.then) so the entire pipeline
// stays on macrotasks and can be driven by `vi.advanceTimersByTime`
// without manual microtask drains.
let preloaded;
let preloadDone = !options.preload;
let preloadStarted = false;
let preloadAwaiters = [];
const drainNext = () => {
const next = peer.lazyQueue.shift();
if (next) {
next();
} else {
peer.lazyActive = false;
}
};
const finishCancelled = () => {
peer.lazyInFlight.delete(abort);
settleResolver();
drainNext();
};
const doCommit = () => {
if (abort.signal.aborted) {
finishCancelled();
return;
}
try {
options.onCommit(target, preloaded);
} catch (err) {
console.error(`[docs-infra/coordinatePreference] lazy-path onCommit for peer '${peer.id}' on channel ` + `'${channel.channelKey}' threw:`, err);
}
performanceMeasure(undefined, {
mark: 'lazy-commit',
measure: 'lazy-commit'
}, [PERF_FUNCTION_NAME, channel.channelKey, peer.id]);
peer.lazyInFlight.delete(abort);
settleResolver();
drainNext();
};
const scheduleIdleCommit = () => {
if (abort.signal.aborted) {
doCommit();
return;
}
if (options.lazyCommitPriority === 'normal') {
doCommit();
return;
}
const ric = globalThis.requestIdleCallback;
const cic = globalThis.cancelIdleCallback;
if (typeof ric === 'function') {
const handle = ric(doCommit);
abort.signal.addEventListener('abort', () => {
if (typeof cic === 'function') {
cic(handle);
}
});
} else {
doCommit();
}
};
const onTimerFired = () => {
if (abort.signal.aborted) {
doCommit();
return;
}
if (preloadDone) {
scheduleIdleCommit();
} else {
preloadAwaiters.push(scheduleIdleCommit);
}
};
const startPreload = () => {
if (preloadStarted) {
return;
}
preloadStarted = true;
if (!options.preload) {
preloadDone = true;
return;
}
const userPreload = options.preload;
// Always run preload in a fresh macrotask so the browser can
// paint any intermediate loading state before the (potentially
// CPU-bound) preload runs. Note: this doesn't change *when* the
// lazy path is allowed to start — `gateStart` still gates lazy
// preloads on the barrier's deferred-release fanout, which only
// fires one macrotask after the batched barrier commit.
const yielded = yieldToMain().then(() => {
if (abort.signal.aborted) {
return undefined;
}
try {
return userPreload(target, abort.signal);
} catch (err) {
console.error(`[docs-infra/coordinatePreference] lazy-path preload for peer '${peer.id}' on channel ` + `'${channel.channelKey}' threw; treating as no-op. Error:`, err);
return undefined;
}
});
yielded.then(value => {
preloaded = value;
preloadDone = true;
const awaiters = preloadAwaiters;
preloadAwaiters = [];
for (const fn of awaiters) {
fn();
}
}, err => {
console.error(`[docs-infra/coordinatePreference] lazy-path preload for peer '${peer.id}' on channel ` + `'${channel.channelKey}' threw; treating as no-op. Error:`, err);
preloadDone = true;
const awaiters = preloadAwaiters;
preloadAwaiters = [];
for (const fn of awaiters) {
fn();
}
});
};
const startTimerAndCommit = () => {
if (lazyWait > 0) {
const timer = setTimeout(onTimerFired, lazyWait);
abort.signal.addEventListener('abort', () => clearTimeout(timer));
} else {
onTimerFired();
}
};
const runPipeline = () => {
if (abort.signal.aborted) {
finishCancelled();
return;
}
startPreload();
startTimerAndCommit();
};
// Decide barrier-gating one microtask after enqueue. By then any
// sibling peer that announced a barrier on the same sync flush has
// opened its barrier, so we can route consistently regardless of
// hook-declaration order.
const gateStart = () => {
if (abort.signal.aborted) {
finishCancelled();
return;
}
const barrierKey = encodeTarget(target);
const existingBarrier = channel.pendingBarriers.get(barrierKey);
if (!existingBarrier) {
runPipeline();
return;
}
// A same-target barrier is pending. Push our deferred release
// FIRST so that if `maybeResolveBarrier` (called below)
// synchronously force-resolves the barrier, our release is
// included in its `setTimeout` schedule. Then mark this peer as
// skipped and (optionally) overlap our preload with the
// barrier's via `preloadAll`. The barrier fires the deferred
// releases one macrotask after its batched commit so the
// visible flip on the lazy peers lands in the render *after*
// the layout-shifting barrier siblings have updated the DOM.
existingBarrier.deferredLazyReleases.push(() => {
if (abort.signal.aborted) {
finishCancelled();
return;
}
if (preloadStarted) {
startTimerAndCommit();
} else {
runPipeline();
}
});
if (!existingBarrier.waiters.has(peer.id)) {
existingBarrier.skipped.add(peer.id);
maybeResolveBarrier(channel, existingBarrier);
}
if (options.preloadAll) {
startPreload();
}
};
const enqueueEntry = () => {
// If a same-target barrier is already open at enqueue time (the
// common case: this lazy announce was triggered by an
// originator's `notifySiblings` immediately after they opened
// the barrier), join it synchronously. Waiting one microtask
// would let the originator's `minWaitTimer` fire first under
// faked timers (`queueMicrotask` is faked by vitest), at which
// point `maybeResolveBarrier` would see `skipped.size === 0`
// and force-resolve only the originator's waiter — leaving us
// stranded. The microtask path below still covers the
// hook-declaration-order case where the barrier hasn't opened
// yet by the time we enqueue.
const barrierKey = encodeTarget(target);
if (channel.pendingBarriers.has(barrierKey)) {
gateStart();
return;
}
queueMicrotask(gateStart);
};
if (peer.lazyActive) {
peer.lazyQueue.push(enqueueEntry);
} else {
peer.lazyActive = true;
enqueueEntry();
}
return {
cancel: () => {
abort.abort();
peer.lazyInFlight.delete(abort);
settleResolver();
// If this was queued but never started, remove it from the
// queue so the next caller's `start` actually runs. If it had
// already started, the in-flight setTimeout/preload paths will
// call `drainNext` themselves when they observe `aborted`.
const idx = peer.lazyQueue.indexOf(enqueueEntry);
if (idx !== -1) {
peer.lazyQueue.splice(idx, 1);
}
},
settled
};
}
/**
* Returns `true` if any peer has ever called `announceTarget` on this
* channel since the channel was created (i.e., since the first peer
* registered without an existing channel object). Useful for
* first-render reconciliation: a peer that wakes up post-hydration
* and finds the channel "fresh" (no announcements yet) can safely
* fast-forward its committed value to the latest underlying value
* without going through a barrier, because no peer is mid-animation.
*
* Returns `false` when the channel doesn't exist (no peers have
* registered yet) or exists but hasn't seen an announce.
*/
export function hasEverAnnounced(channelKey) {
const channel = channels.get(channelKey);
return channel ? channel.hasEverAnnounced : false;
}
/**
* Returns the `announceTime` recorded when the active barrier for
* `target` was opened, or `null` if no barrier is currently pending
* for that target on `channelKey`. Late-joining peers can use this
* to anchor their local timers to the originator's wall-clock window
* instead of restarting a fresh one \u2014 e.g. a peer whose state
* propagated 200ms after the originator's click should commit 200ms
* earlier than its local `Date.now()` would suggest, so the visible
* paint lines up.
*/
export function getBarrierAnnounceTime(channelKey, target) {
const channel = channels.get(channelKey);
if (!channel) {
return null;
}
const barrierKey = encodeTarget(target);
const barrier = channel.pendingBarriers.get(barrierKey);
return barrier ? barrier.announceTime : null;
}
/**
* Internal handles for the `coordinatePreference.testUtils` sibling.
*
* Not part of the public API. Do not import this from production
* code or from tests directly — use the helpers re-exported from
* `./coordinatePreference.testUtils` instead so that the boundary
* between runtime API and test affordances stays clear.
*/
// eslint-disable-next-line @typescript-eslint/naming-convention, no-underscore-dangle -- intentional sentinel name marking this as a test-only sibling import
export const __testInternals = {
channels,
setTargetEncoder
};