@juxtapose/core 0.9.0 → 0.11.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (44) hide show
  1. package/dist/agent/handoff.d.ts +3 -1
  2. package/dist/agent/handoff.js +17 -0
  3. package/dist/agent/handoff.js.map +1 -1
  4. package/dist/browser/page.d.ts +20 -4
  5. package/dist/browser/page.js +15 -6
  6. package/dist/browser/page.js.map +1 -1
  7. package/dist/capture/capture.d.ts +7 -0
  8. package/dist/capture/capture.js +60 -9
  9. package/dist/capture/capture.js.map +1 -1
  10. package/dist/capture/config-errors.d.ts +10 -0
  11. package/dist/capture/config-errors.js +20 -0
  12. package/dist/capture/config-errors.js.map +1 -0
  13. package/dist/capture/idle.d.ts +88 -0
  14. package/dist/capture/idle.js +558 -0
  15. package/dist/capture/idle.js.map +1 -0
  16. package/dist/capture/network-policy.d.ts +21 -0
  17. package/dist/capture/network-policy.js +114 -0
  18. package/dist/capture/network-policy.js.map +1 -0
  19. package/dist/capture/pool.d.ts +6 -0
  20. package/dist/capture/pool.js +1 -0
  21. package/dist/capture/pool.js.map +1 -1
  22. package/dist/capture/settle-options.d.ts +80 -0
  23. package/dist/capture/settle-options.js +236 -0
  24. package/dist/capture/settle-options.js.map +1 -0
  25. package/dist/capture/settle.d.ts +60 -18
  26. package/dist/capture/settle.js +157 -100
  27. package/dist/capture/settle.js.map +1 -1
  28. package/dist/capture/snap.d.ts +3 -0
  29. package/dist/capture/snap.js +7 -1
  30. package/dist/capture/snap.js.map +1 -1
  31. package/dist/capture/variants.d.ts +1 -4
  32. package/dist/capture/variants.js +25 -10
  33. package/dist/capture/variants.js.map +1 -1
  34. package/dist/diff/structural.d.ts +4 -1
  35. package/dist/diff/structural.js +3 -0
  36. package/dist/diff/structural.js.map +1 -1
  37. package/dist/index.d.ts +3 -1
  38. package/dist/index.js +4 -0
  39. package/dist/index.js.map +1 -1
  40. package/dist/run/run.d.ts +4 -0
  41. package/dist/run/run.js +20 -1
  42. package/dist/run/run.js.map +1 -1
  43. package/dist/types.d.ts +77 -1
  44. package/package.json +1 -1
@@ -0,0 +1,558 @@
1
+ // Event-driven settle: wait as long as the page still has work, not a fixed
2
+ // quiet window.
3
+ //
4
+ // Replaces the 150 ms DOM-quiet window (capped at 500 ms) that ran on every
5
+ // story. Measured in the settle spike (raw CDP replica of this pipeline, 5
6
+ // runs per story, chrome-headless-shell 151.0.7922.34): byte-identical to the
7
+ // quiet window on 29/29 example stories (Storybook 15, Ladle 14), stable 5/5,
8
+ // settle p50 168 -> 39 ms (Storybook) and 162 -> 34 ms (Ladle). On adversarial
9
+ // pages it captures the final state of setTimeout(300) and (800) and of a
10
+ // fetch resolving after 200 ms, all of which the quiet window captured
11
+ // mid-update (0/5), and it does not hang on a lazy image below the fold,
12
+ // where the old uncapped image wait in `drain` never returned.
13
+ //
14
+ // Shipped, through the CLI (2026-10-13, Apple M5 Pro, 15 cores, load average
15
+ // 15-18, median of 5, interleaved with main): `jux run --all` on the
16
+ // Storybook example 1339 -> 653 ms (story p50 203 -> 75 ms), Ladle 1127 ->
17
+ // 603 ms (208 -> 80 ms); a warm `jux snap` of one story 356 -> 209 ms. Every
18
+ // default and variant capture (Storybook 18, Ladle 16) byte-identical to
19
+ // baselines approved by the quiet-window build, fresh launch and daemon.
20
+ //
21
+ // Two halves. An init script, installed before any page script runs, counts
22
+ // the page's own pending work: short timers, animation frames, posted
23
+ // messages, DOM mutations, loading fonts and loading in-viewport images. CDP
24
+ // Network events count in-flight requests, which the page cannot see for
25
+ // itself (a fetch started before any wrapper could be installed, a <link>
26
+ // stylesheet, an image the parser started). Idle is both at zero, re-checked
27
+ // once more after a round trip because renderer and browser events are not
28
+ // ordered against each other.
29
+ //
30
+ // Never throws: a wait that hits its cap is reported, and the page is captured
31
+ // as it is. A slow story must produce a reviewable result, not abort a run.
32
+ import { SendTimeoutError } from '../browser/cdp.js';
33
+ import { matchPattern } from './settle-options.js';
34
+ /**
35
+ * Runs before any page script (Page.addScriptToEvaluateOnNewDocument), so the
36
+ * wrappers are in place before a framework captures the natives. A library
37
+ * that re-patches them later (zone.js, Sentry) is still tracked as long as it
38
+ * calls through, which they do.
39
+ *
40
+ * String timer handlers pass straight through, untracked: wrapping them would
41
+ * need eval, which a page's CSP may forbid.
42
+ */
43
+ const IDLE_INIT = `(function () {
44
+ if (window.__juxIdle) return;
45
+ var W = window;
46
+ var S = { timers: new Map(), rafs: new Set(), sent: 0, delivered: 0, workers: new Map(), mut: 0, ready: false };
47
+ var oST = W.setTimeout, oCT = W.clearTimeout, oSI = W.setInterval, oCI = W.clearInterval;
48
+ var oRAF = W.requestAnimationFrame, oCAF = W.cancelAnimationFrame, oPM = W.postMessage;
49
+ var PP = MessagePort.prototype, oPPM = PP.postMessage, oAEL = PP.addEventListener, oREL = PP.removeEventListener;
50
+
51
+ // Task hop on a private channel, created before MessagePort is patched so
52
+ // its own messages are never counted as the page's.
53
+ var hop = (function () { var ch = new MessageChannel(); var q = [];
54
+ ch.port1.onmessage = function () { var f = q.shift(); if (f) f(); };
55
+ return function (f) { q.push(f); oPPM.call(ch.port2, 0); }; })();
56
+
57
+ // Polls and clocks: a paced timer (delay >= 16 ms, a frame) re-armed from
58
+ // the same call site after an earlier one there fired counts only if the
59
+ // DOM changed since that fire. A poll that changes nothing (same function,
60
+ // fresh closure or arrow) stops counting after its first period; a chain
61
+ // that reveals something each step (retry with backoff, a typewriter, a
62
+ // staged reveal) is waited out. The site is the whole stack minus this
63
+ // wrapper, not the top frame, so a shared sleep() helper is a different site
64
+ // per caller. Mutations are flushed with takeRecords() so a re-arm sees the
65
+ // changes its own callback just made.
66
+ //
67
+ // Measured in the review's prototype (round 2): closure, arrow and
68
+ // same-function 200 ms polls settle in 440-453 ms unflagged (they pinned the
69
+ // 1500 ms cap before); a 3-step 100 ms self-chain, sleep() chains,
70
+ // timer-1200-chain, timeout-chain and the timeout-50/120/300/800 pages all
71
+ // capture their final state 3/3. A clock that repaints every tick still
72
+ // counts and hits the cap, flagged: it needs a mask either way. Faster
73
+ // re-arms (setTimeout(step, 0) chunked work) are never keyed and always count.
74
+ var PACED_MS = 16, sites = new Map(), MO = null;
75
+ var flushMut = function () { if (MO) S.mut += MO.takeRecords().length; };
76
+ W.setTimeout = function (fn, delay) {
77
+ if (typeof fn !== 'function') return oST.apply(W, arguments);
78
+ var args = Array.prototype.slice.call(arguments, 2); var d = Math.max(0, Number(delay) || 0); var id;
79
+ var key = null, counted = true;
80
+ if (d >= PACED_MS) {
81
+ key = (new Error().stack || '').split('\\n').slice(2).join('\\n');
82
+ var site = sites.get(key);
83
+ if (site && site.fired) { flushMut(); counted = S.mut !== site.mutAtFire; }
84
+ }
85
+ var cb = function () { S.timers.delete(id);
86
+ if (key !== null) { flushMut(); sites.set(key, { fired: true, mutAtFire: S.mut }); }
87
+ return fn.apply(W, args); };
88
+ id = oST.call(W, cb, d);
89
+ if (key !== null && !sites.has(key)) sites.set(key, { fired: false });
90
+ if (counted) S.timers.set(id, d);
91
+ return id;
92
+ };
93
+ W.clearTimeout = function (id) { S.timers.delete(id); return oCT.call(W, id); };
94
+ // An interval counts until its first tick only: it must not block forever.
95
+ W.setInterval = function (fn, delay) {
96
+ if (typeof fn !== 'function') return oSI.apply(W, arguments);
97
+ var args = Array.prototype.slice.call(arguments, 2); var d = Math.max(0, Number(delay) || 0); var id; var first = true;
98
+ var cb = function () { if (first) { first = false; S.timers.delete(id); } return fn.apply(W, args); };
99
+ id = oSI.call(W, cb, d); S.timers.set(id, d); return id;
100
+ };
101
+ W.clearInterval = function (id) { S.timers.delete(id); return oCI.call(W, id); };
102
+ // rAF re-armed from inside a rAF callback is an animation loop; stop counting
103
+ // after 3 generations or a canvas animation pins the wait at the cap
104
+ // (measured: a perpetual rAF loop settles in 35-40 ms with this).
105
+ var rafGen = 0;
106
+ W.requestAnimationFrame = function (fn) {
107
+ var gen = rafGen;
108
+ var id = oRAF.call(W, function (t) { S.rafs.delete(id); rafGen = gen + 1; try { return fn(t); } finally { rafGen = 0; } });
109
+ if (gen < 3) S.rafs.add(id); return id;
110
+ };
111
+ W.cancelAnimationFrame = function (id) { S.rafs.delete(id); return oCAF.call(W, id); };
112
+ // Posted messages (React's scheduler uses a MessageChannel): count sends
113
+ // and deliveries, each event once however many listeners see it. Pending
114
+ // is the difference; the waiter treats it as stale only when deliveries
115
+ // stop moving (a port nobody listens to), never while they continue (the
116
+ // review measured 40 x 5 ms of scheduler slices captured mid-render when
117
+ // a steady 1-in-flight count was read as stale).
118
+ var seen = new WeakSet();
119
+ var deliver = function (e) { if (e && typeof e === 'object') { if (seen.has(e)) return; seen.add(e); }
120
+ if (S.delivered < S.sent) S.delivered++; };
121
+ W.postMessage = function () { S.sent++; return oPM.apply(W, arguments); };
122
+ W.addEventListener('message', deliver, true);
123
+ var wrapped = new WeakMap();
124
+ var wrap = function (fn) { if (typeof fn !== 'function') return fn; var w = wrapped.get(fn);
125
+ if (!w) { w = function (e) { deliver(e); return fn.call(this, e); }; wrapped.set(fn, w); } return w; };
126
+ PP.postMessage = function () { S.sent++; return oPPM.apply(this, arguments); };
127
+ PP.addEventListener = function (type, fn, opt) { return oAEL.call(this, type, type === 'message' ? wrap(fn) : fn, opt); };
128
+ PP.removeEventListener = function (type, fn, opt) {
129
+ return oREL.call(this, type, type === 'message' && typeof fn === 'function' ? (wrapped.get(fn) || fn) : fn, opt); };
130
+ var onmsg = Object.getOwnPropertyDescriptor(PP, 'onmessage');
131
+ Object.defineProperty(PP, 'onmessage', { configurable: true,
132
+ get: function () { return onmsg.get.call(this); }, set: function (fn) { return onmsg.set.call(this, wrap(fn)); } });
133
+
134
+ // A dedicated Worker runs where this script can't see; its script request
135
+ // never completes on the page's session either. What is countable is the
136
+ // conversation: a message posted to the worker is pending until a message
137
+ // comes back, within the timer horizon of the post; and a new worker gets a
138
+ // 300 ms startup grace for unsolicited work, ended by its first message,
139
+ // error or terminate(). A silent worker (comlink-style, answering only when
140
+ // asked) cost ~1040 ms per capture when the whole horizon applied
141
+ // (review round 2); with the grace it costs at most 300 ms, and the 80 ms and
142
+ // 120 ms startup probes (worker-80, worker-delay) still capture their
143
+ // answer. Reflect.construct keeps "class X extends Worker" working.
144
+ var OW = W.Worker, wid = 0, STARTUP_MS = 300;
145
+ if (typeof OW === 'function') {
146
+ var JW = function Worker(url, opts) {
147
+ var w = Reflect.construct(OW, Array.prototype.slice.call(arguments), new.target || OW);
148
+ var id = ++wid; var rec = { t: performance.now(), src: String(url), started: true, posted: 0, replies: 0, lastPost: 0 };
149
+ rec.id = id; S.workers.set(id, rec); recs.set(w, rec);
150
+ w.addEventListener('message', function () { rec.started = false; if (rec.replies < rec.posted) rec.replies++; });
151
+ w.addEventListener('error', function () { rec.started = false; rec.replies = rec.posted; });
152
+ return w;
153
+ };
154
+ // On the prototype, not the instance, so a subclass's own overrides win.
155
+ var recs = new WeakMap(), WP = OW.prototype, oWPM = WP.postMessage, oWT = WP.terminate;
156
+ WP.postMessage = function () { var r = recs.get(this); if (r) { r.posted++; r.lastPost = performance.now(); } return oWPM.apply(this, arguments); };
157
+ WP.terminate = function () { var r = recs.get(this); if (r) S.workers.delete(r.id); return oWT.apply(this, arguments); };
158
+ JW.prototype = WP;
159
+ Object.setPrototypeOf(JW, OW);
160
+ W.Worker = JW;
161
+ }
162
+
163
+ MO = new MutationObserver(function (list) { S.mut += list.length; });
164
+ MO.observe(document, { childList: true, subtree: true, attributes: true, characterData: true });
165
+
166
+ // The page's half of settle.ready. Always defined under jux, so a page can
167
+ // call it unconditionally (window.__juxtapose?.ready() outside jux).
168
+ W.__juxtapose = { ready: function () { S.ready = true; } };
169
+
170
+ var pendingImages = function () {
171
+ var vh = W.innerHeight, vw = W.innerWidth, out = [];
172
+ Array.prototype.forEach.call(document.images, function (img) {
173
+ if (img.complete) return;
174
+ // A lazy image outside the viewport, or not rendered at all
175
+ // (display: none), never loads; waiting for it is what hung the old drain.
176
+ if (img.loading === 'lazy') { if (img.getClientRects().length === 0) return;
177
+ var r = img.getBoundingClientRect(); if (r.bottom < 0 || r.top > vh || r.right < 0 || r.left > vw) return; }
178
+ out.push(img.currentSrc || img.src || '(no src)');
179
+ });
180
+ return out;
181
+ };
182
+ var snapshot = function (horizon) {
183
+ var timers = 0; S.timers.forEach(function (d) { if (d <= horizon) timers++; });
184
+ var now = performance.now(), workers = [];
185
+ S.workers.forEach(function (w) {
186
+ if ((w.started && now - w.t <= STARTUP_MS) || (w.replies < w.posted && now - w.lastPost <= horizon)) workers.push(w.src); });
187
+ var imgs = pendingImages();
188
+ return { timers: timers, rafs: S.rafs.size, msgs: S.sent - S.delivered, delivered: S.delivered, mut: S.mut,
189
+ fonts: document.fonts.status, images: imgs.length, imageSrcs: imgs.slice(0, 3),
190
+ workers: workers.length, workerSrcs: workers.slice(0, 3), ready: S.ready };
191
+ };
192
+ // untracked: rerasterLayers (settle.ts) runs its own style toggles through
193
+ // this, so the mutation counter never sees them and a capture does not take
194
+ // the recheck path for jux's own writes. Page mutations queued before fn are
195
+ // counted first; fn's own records are dropped.
196
+ W.__juxIdle = { snapshot: snapshot, reset: function () { S.ready = false; },
197
+ untracked: function (fn) { flushMut(); var m = S.mut; try { return fn(); } finally { if (MO) MO.takeRecords(); S.mut = m; } } };
198
+
199
+ // Resolves 'idle' when two consecutive task hops each see no pending work
200
+ // and no mutation since the previous hop, or 'cap' at capMs.
201
+ W.__juxIdleWait = function (capMs, horizon, needReady) {
202
+ return new Promise(function (resolve) {
203
+ var t0 = performance.now(); var lastMut = -1; var lastDelivered = -1; var quiet = 0; var stale = 0; var done = false; var changing = false;
204
+ var finish = function (status, s) { if (done) return; done = true; oCT.call(W, capTimer);
205
+ s = s || snapshot(horizon); s.changing = changing;
206
+ resolve({ status: status, waitedMs: performance.now() - t0, snap: s }); };
207
+ var capTimer = oST.call(W, function () { finish('cap'); }, capMs);
208
+ var check = function () {
209
+ if (done) return;
210
+ var s = snapshot(horizon);
211
+ if (s.msgs > 0 && s.delivered === lastDelivered) stale++; else stale = 0; lastDelivered = s.delivered;
212
+ var msgsOk = s.msgs === 0 || stale >= 3;
213
+ var noWork = s.timers === 0 && s.rafs === 0 && msgsOk && s.workers === 0 && s.fonts !== 'loading' && s.images === 0 && (!needReady || s.ready);
214
+ changing = s.mut !== lastMut && lastMut !== -1;
215
+ var isQuiet = noWork && s.mut === lastMut;
216
+ lastMut = s.mut;
217
+ quiet = isQuiet ? quiet + 1 : 0;
218
+ if (quiet >= 2) return finish('idle', s);
219
+ if (noWork) hop(check); // a mutation just happened: look again next task
220
+ else if (s.rafs > 0) oRAF.call(W, function () { hop(check); }); // sleep to the frame edge
221
+ else oST.call(W, check, 4); // timer/font/image/worker/ready pending: poll cheaply
222
+ };
223
+ // First check after a frame: style and layout must run once before
224
+ // fonts and in-viewport images start loading.
225
+ oRAF.call(W, function () { hop(check); });
226
+ });
227
+ };
228
+ })();`;
229
+ /** Completed requests kept per document for `require`; past this a page is
230
+ * a firehose and the oldest are dropped. */
231
+ const MAX_DONE = 5000;
232
+ /** In-flight and completed requests on one page, from CDP Network events. */
233
+ export class NetworkTracker {
234
+ inflight = new Map();
235
+ done = [];
236
+ listeners = new Set();
237
+ /** In flight when the current story started on a warm switch: the last
238
+ * story's, never this one's. */
239
+ excluded = new Set();
240
+ /** The committed main document's loader. */
241
+ mainLoader;
242
+ constructor(page, mainFrameId) {
243
+ page.on('Network.requestWillBeSent', (p) => {
244
+ // A dedicated Worker's script request has no loader and never gets
245
+ // loadingFinished on this session (review: every capture of a page
246
+ // with `new Worker('/w.js')` pinned at the cap). The init script counts
247
+ // the worker's conversation instead.
248
+ if (p.loaderId === '')
249
+ return;
250
+ this.inflight.set(p.requestId, {
251
+ url: p.request.url,
252
+ ...(p.type !== undefined ? { type: p.type } : {}),
253
+ // Every frame's request, not only the main frame's: a child frame's
254
+ // request (an iframe's analytics XHR) never gets loadingFailed when
255
+ // the document navigates away, and without a loader it escaped the
256
+ // sweep below and pinned every later story at the cap.
257
+ ...(p.frameId === mainFrameId
258
+ ? { mainLoader: p.loaderId }
259
+ : this.mainLoader !== undefined
260
+ ? { mainLoader: this.mainLoader }
261
+ : {}),
262
+ });
263
+ });
264
+ // The new main document commits. Its predecessor's requests, including
265
+ // ones it started between the navigation's start and this commit, never
266
+ // get loadingFailed (spike: a long poll pinned the next page at the cap;
267
+ // review: a 2 ms poller flagged the following static page 4 of 6
268
+ // times), and its completions must not satisfy this one's `require`.
269
+ // On the commit, not on the Document request: a navigation that never
270
+ // commits (a 204, a download) must leave the live document's requests
271
+ // counted (review round 2). A request is kept by its loader, not dropped
272
+ // on arrival, because Network events and the commit come from different
273
+ // processes and a new document's first fetch can be reported before it.
274
+ page.on('Page.frameNavigated', ({ frame }) => {
275
+ if (frame.parentId !== undefined || frame.id !== mainFrameId)
276
+ return;
277
+ this.mainLoader = frame.loaderId;
278
+ for (const [id, r] of this.inflight)
279
+ if (r.mainLoader !== undefined && r.mainLoader !== frame.loaderId) {
280
+ this.inflight.delete(id);
281
+ this.excluded.delete(id);
282
+ }
283
+ this.done = [];
284
+ this.excluded.clear();
285
+ for (const l of [...this.listeners])
286
+ l();
287
+ });
288
+ const finish = (p) => {
289
+ const r = this.inflight.get(p.requestId);
290
+ if (!r)
291
+ return;
292
+ this.inflight.delete(p.requestId);
293
+ this.excluded.delete(p.requestId);
294
+ this.done.push(r);
295
+ if (this.done.length > MAX_DONE)
296
+ this.done.splice(0, this.done.length - MAX_DONE);
297
+ for (const l of [...this.listeners])
298
+ l();
299
+ };
300
+ page.on('Network.loadingFinished', finish);
301
+ page.on('Network.loadingFailed', finish);
302
+ }
303
+ /** A warm switch renders a new story in the same document: the last
304
+ * story's completions must not satisfy `require`, and its requests still
305
+ * in flight (a long poll) must not pin this one at the cap. */
306
+ reset() {
307
+ this.done = [];
308
+ this.excluded = new Set(this.inflight.keys());
309
+ }
310
+ /** In-flight requests the wait counts: required, or not ignored. */
311
+ counted(s) {
312
+ return [...this.inflight.entries()]
313
+ .filter(([id, r]) => !this.excluded.has(id) &&
314
+ // Another main-frame loader: the previous document's, or a
315
+ // navigation that has not committed (or never will).
316
+ (r.mainLoader === undefined ||
317
+ this.mainLoader === undefined ||
318
+ r.mainLoader === this.mainLoader))
319
+ .map(([, r]) => r)
320
+ .filter((r) => s.require.some((p) => matchPattern(p, r.url, r.type)) ||
321
+ !s.ignore.some((p) => matchPattern(p, r.url, r.type)));
322
+ }
323
+ /** `require` patterns no completed request has matched yet. */
324
+ missing(s) {
325
+ return s.require
326
+ .filter((p) => !this.done.some((r) => matchPattern(p, r.url, r.type)))
327
+ .map((p) => p.source);
328
+ }
329
+ busy(s) {
330
+ return this.counted(s).length > 0 || this.missing(s).length > 0;
331
+ }
332
+ /** Resolves when the wait no longer counts anything, or after `ms`. */
333
+ async settled(s, ms) {
334
+ if (!this.busy(s))
335
+ return;
336
+ await new Promise((resolve) => {
337
+ const done = () => {
338
+ clearTimeout(timer);
339
+ this.listeners.delete(check);
340
+ resolve();
341
+ };
342
+ const check = () => {
343
+ if (!this.busy(s))
344
+ done();
345
+ };
346
+ const timer = setTimeout(done, Math.max(0, ms));
347
+ this.listeners.add(check);
348
+ });
349
+ }
350
+ }
351
+ const installed = new WeakMap();
352
+ /**
353
+ * Install the init script and the request tracker, once per page, before
354
+ * its first navigation: the script applies to every later document on the
355
+ * page. Network.enable is already part of page init.
356
+ */
357
+ export function installIdleDetector(page) {
358
+ let p = installed.get(page);
359
+ if (!p) {
360
+ p = (async () => {
361
+ await page
362
+ .send('Page.addScriptToEvaluateOnNewDocument', { source: IDLE_INIT })
363
+ .catch(() => { });
364
+ if (!page.on)
365
+ return { net: null };
366
+ const tree = await page.send('Page.getFrameTree').catch(() => undefined);
367
+ const mainFrameId = tree?.frameTree?.frame?.id;
368
+ return {
369
+ net: new NetworkTracker(page, mainFrameId),
370
+ };
371
+ })();
372
+ installed.set(page, p);
373
+ }
374
+ return p;
375
+ }
376
+ /** Before a story renders: forget the last story's completions, and on a
377
+ * warm switch (same document) its ready() call. */
378
+ export async function resetIdle(page, idle, warm) {
379
+ idle.net?.reset();
380
+ if (warm)
381
+ await page.evaluate('window.__juxIdle && window.__juxIdle.reset()').catch(() => { });
382
+ }
383
+ const now = () => performance.now();
384
+ const isSnapshot = (v) => typeof v === 'object' && v !== null && typeof v.mut === 'number';
385
+ /**
386
+ * Wait until the page reports no pending work and no counted request is in
387
+ * flight, or `capMs` passes. Never throws.
388
+ */
389
+ export async function waitForIdle(page, idle, s, capMs = s.timeoutMs) {
390
+ const t0 = now();
391
+ let snap;
392
+ const timeout = () => ({
393
+ status: 'timeout',
394
+ waitedMs: now() - t0,
395
+ ...(snap ? { snap } : {}),
396
+ pending: describePending(snap, idle.net, s),
397
+ });
398
+ for (;;) {
399
+ const remaining = capMs - (now() - t0);
400
+ if (remaining <= 0)
401
+ return timeout();
402
+ let r;
403
+ try {
404
+ r = await page.evaluate(`window.__juxIdleWait ? window.__juxIdleWait(${Math.ceil(remaining)}, ${s.horizonMs}, ${s.ready}) : Promise.resolve({ status: 'noinit' })`, undefined,
405
+ // The in-page cap fires first; this only matters if the page is wedged.
406
+ { timeout: Math.ceil(remaining) + 2000 });
407
+ }
408
+ catch (err) {
409
+ // A wedged page (a synchronous loop) would only wedge a second evaluate
410
+ // too: worst case cap + 2 s + 2 s (review round 2). Skip straight to the
411
+ // capture; a navigation mid-wait gets the fallback wait.
412
+ if (err instanceof SendTimeoutError)
413
+ return {
414
+ status: 'fallback',
415
+ waitedMs: now() - t0,
416
+ pending: [
417
+ 'the page stopped answering during the wait; captured without waiting',
418
+ ],
419
+ };
420
+ return fallback(page, t0, capMs, 'the page navigated during the wait');
421
+ }
422
+ const status = typeof r === 'object' && r !== null
423
+ ? r.status
424
+ : undefined;
425
+ const got = r?.snap;
426
+ if (isSnapshot(got))
427
+ snap = got;
428
+ if (status === 'cap')
429
+ return timeout();
430
+ if (status !== 'idle')
431
+ return fallback(page, t0, capMs, 'the idle detector was not installed in this page');
432
+ const net = idle.net;
433
+ if (!net)
434
+ return { status: 'idle', waitedMs: now() - t0, ...(snap ? { snap } : {}) };
435
+ if (net.busy(s)) {
436
+ await net.settled(s, capMs - (now() - t0));
437
+ continue;
438
+ }
439
+ // One more round trip, so the Network event for a request the page
440
+ // started just before it reported idle has arrived.
441
+ await page.evaluate('1').catch(() => { });
442
+ if (net.busy(s)) {
443
+ await net.settled(s, capMs - (now() - t0));
444
+ continue;
445
+ }
446
+ return { status: 'idle', waitedMs: now() - t0, ...(snap ? { snap } : {}) };
447
+ }
448
+ }
449
+ /**
450
+ * When the detector can't run, wait the way the quiet window did: fonts,
451
+ * eager images and 100 ms without a DOM change, all inside what is left of the
452
+ * cap. Flagged `settle-fallback`, so the weaker wait is never silent.
453
+ */
454
+ async function fallback(page, t0, capMs, why) {
455
+ const remaining = Math.max(0, Math.ceil(capMs - (now() - t0)));
456
+ await page
457
+ .evaluate(`new Promise(function (resolve) {
458
+ var cap = setTimeout(resolve, ${remaining});
459
+ Promise.resolve(document.fonts && document.fonts.ready).then(function () {
460
+ return Promise.all(Array.prototype.filter.call(document.images, function (i) { return !i.complete && i.loading !== 'lazy'; })
461
+ .map(function (i) { return new Promise(function (r) { i.addEventListener('load', r, { once: true }); i.addEventListener('error', r, { once: true }); }); }));
462
+ }).then(function () {
463
+ var t; var fin = function () { o.disconnect(); clearTimeout(cap); resolve(); };
464
+ var o = new MutationObserver(function () { clearTimeout(t); t = setTimeout(fin, 100); });
465
+ o.observe(document, { childList: true, subtree: true, attributes: true, characterData: true });
466
+ t = setTimeout(fin, 100);
467
+ });
468
+ })`, undefined, { timeout: remaining + 2000 })
469
+ .catch(() => { });
470
+ return {
471
+ status: 'fallback',
472
+ waitedMs: now() - t0,
473
+ pending: [`${why}; waited for fonts, images and a quiet DOM only`],
474
+ };
475
+ }
476
+ /** After the paint barrier: did the DOM, fonts, images or network move? */
477
+ export async function movedSincePaint(page, idle, before, s) {
478
+ if (!before)
479
+ return false;
480
+ const after = await page
481
+ .evaluate(`window.__juxIdle ? window.__juxIdle.snapshot(${s.horizonMs}) : null`)
482
+ .catch(() => null);
483
+ if (!isSnapshot(after))
484
+ return false;
485
+ // Timers and messages too: a callback inside the barrier can schedule work
486
+ // without touching the DOM yet.
487
+ return (after.mut !== before.mut ||
488
+ after.timers > 0 ||
489
+ after.msgs > 0 ||
490
+ after.workers > 0 ||
491
+ after.fonts === 'loading' ||
492
+ after.images > 0 ||
493
+ idle.net?.busy(s) === true);
494
+ }
495
+ const MAX_REQUEST_LINES = 5;
496
+ /** A request URL as a person would name it: the path and query for http(s),
497
+ * else the URL, either one trimmed. */
498
+ function displayUrl(url) {
499
+ let out = url;
500
+ try {
501
+ const u = new URL(url);
502
+ if (u.protocol === 'http:' || u.protocol === 'https:')
503
+ out = u.pathname + u.search;
504
+ }
505
+ catch {
506
+ // not a URL; show it as is
507
+ }
508
+ return out.length > 100 ? `${out.slice(0, 99)}…` : out;
509
+ }
510
+ const plural = (n, one, many = `${one}s`) => `${n} ${n === 1 ? one : many}`;
511
+ /**
512
+ * What was still pending at the cap, one line per kind, so an agent reading
513
+ * the result can respond: ignore a request, require one, raise the cap, or
514
+ * mask what keeps changing. Never empty.
515
+ */
516
+ export function describePending(snap, net, s) {
517
+ const out = [];
518
+ if (net) {
519
+ const groups = new Map();
520
+ for (const r of net.counted(s)) {
521
+ const key = `${displayUrl(r.url)}\0${r.type ?? 'Other'}`;
522
+ groups.set(key, (groups.get(key) ?? 0) + 1);
523
+ }
524
+ const lines = [...groups.entries()]
525
+ .sort(([a], [b]) => (a < b ? -1 : a > b ? 1 : 0))
526
+ .map(([key, n]) => {
527
+ const [url, type] = key.split('\0');
528
+ return `${plural(n, 'request')} to ${url} in flight (${type})`;
529
+ });
530
+ out.push(...lines.slice(0, MAX_REQUEST_LINES));
531
+ if (lines.length > MAX_REQUEST_LINES)
532
+ out.push(`${lines.length - MAX_REQUEST_LINES} more requests in flight`);
533
+ for (const p of net.missing(s))
534
+ out.push(`no request matching required pattern '${p}' has completed`);
535
+ }
536
+ if (snap) {
537
+ if (s.ready && !snap.ready)
538
+ out.push('window.__juxtapose.ready() was not called');
539
+ if (snap.timers > 0)
540
+ out.push(`${plural(snap.timers, 'timer')} due within ${s.horizonMs} ms`);
541
+ if (snap.rafs > 0)
542
+ out.push(`${plural(snap.rafs, 'animation frame callback')} pending`);
543
+ if (snap.msgs > 0)
544
+ out.push(`${plural(snap.msgs, 'posted message')} undelivered`);
545
+ if (snap.workers > 0)
546
+ out.push(`${plural(snap.workers, 'worker')} not answered yet: ${snap.workerSrcs.map(displayUrl).join(', ')}`);
547
+ if (snap.fonts === 'loading')
548
+ out.push('web fonts still loading');
549
+ if (snap.images > 0)
550
+ out.push(`${plural(snap.images, 'image')} still loading: ${snap.imageSrcs.map(displayUrl).join(', ')}`);
551
+ if (out.length === 0 && snap.changing)
552
+ out.push('the DOM kept changing');
553
+ }
554
+ if (out.length === 0)
555
+ out.push(`the page did not settle within ${s.timeoutMs} ms`);
556
+ return out;
557
+ }
558
+ //# sourceMappingURL=idle.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"idle.js","sourceRoot":"","sources":["../../src/capture/idle.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,21 @@
1
+ import type { Page } from '../browser/page.js';
2
+ export type ExternalNetwork = 'allow' | 'block';
3
+ export interface NetworkLayer {
4
+ network?: {
5
+ external?: ExternalNetwork;
6
+ };
7
+ }
8
+ /** Validate a `network` value where it is declared; `where` names the place to edit. */
9
+ export declare function checkNetwork(raw: unknown, where: string): {
10
+ external?: ExternalNetwork;
11
+ } | undefined;
12
+ /** call > story > project, default 'allow'. */
13
+ export declare function resolveExternal(project: NetworkLayer | undefined, story: NetworkLayer | undefined, call: NetworkLayer | undefined): ExternalNetwork;
14
+ /** Whether `url` is a remote request to a different origin than `origin`. */
15
+ export declare function isExternal(url: string, origin: string): boolean;
16
+ /**
17
+ * Make the page's request policy match `mode` for the origin of `baseUrl`.
18
+ * Called before every navigation; a page keeps its policy until the next call,
19
+ * so a pooled page never carries one capture's block into the next.
20
+ */
21
+ export declare function applyNetworkPolicy(page: Page, mode: ExternalNetwork, baseUrl: string): Promise<void>;