@buoy-gg/js-top 7.0.41 → 7.0.43

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 (118) hide show
  1. package/README.md +12 -71
  2. package/lib/commonjs/index.js +1 -99
  3. package/lib/commonjs/js-top/components/BusySparkline.js +1 -56
  4. package/lib/commonjs/js-top/components/JsTopHudOverlay.js +1 -79
  5. package/lib/commonjs/js-top/components/JsTopModal.js +1 -349
  6. package/lib/commonjs/js-top/components/TaskDetailView.js +1 -330
  7. package/lib/commonjs/js-top/components/TaskTable.js +1 -360
  8. package/lib/commonjs/js-top/engine/JsTopController.js +1 -350
  9. package/lib/commonjs/js-top/engine/aggregator.js +1 -148
  10. package/lib/commonjs/js-top/engine/busyProbe.js +1 -101
  11. package/lib/commonjs/js-top/engine/frameStack.js +1 -133
  12. package/lib/commonjs/js-top/engine/longtasks.js +1 -141
  13. package/lib/commonjs/js-top/engine/now.js +1 -38
  14. package/lib/commonjs/js-top/engine/originRegistry.js +1 -402
  15. package/lib/commonjs/js-top/engine/patches/bridge.js +1 -60
  16. package/lib/commonjs/js-top/engine/patches/microtasks.js +1 -27
  17. package/lib/commonjs/js-top/engine/patches/promises.js +1 -65
  18. package/lib/commonjs/js-top/engine/patches/timers.js +1 -66
  19. package/lib/commonjs/js-top/engine/state.js +1 -63
  20. package/lib/commonjs/js-top/engine/symbolicate.js +1 -95
  21. package/lib/commonjs/js-top/sync/jsTopSyncAdapter.js +1 -74
  22. package/lib/commonjs/js-top/types/index.js +1 -46
  23. package/lib/commonjs/js-top/utils/preferences.js +1 -31
  24. package/lib/commonjs/preset.js +1 -75
  25. package/lib/module/index.js +1 -49
  26. package/lib/module/js-top/components/BusySparkline.js +1 -52
  27. package/lib/module/js-top/components/JsTopHudOverlay.js +1 -75
  28. package/lib/module/js-top/components/JsTopModal.js +1 -345
  29. package/lib/module/js-top/components/TaskDetailView.js +1 -326
  30. package/lib/module/js-top/components/TaskTable.js +1 -353
  31. package/lib/module/js-top/engine/JsTopController.js +1 -347
  32. package/lib/module/js-top/engine/aggregator.js +1 -136
  33. package/lib/module/js-top/engine/busyProbe.js +1 -94
  34. package/lib/module/js-top/engine/frameStack.js +1 -126
  35. package/lib/module/js-top/engine/longtasks.js +1 -133
  36. package/lib/module/js-top/engine/now.js +1 -33
  37. package/lib/module/js-top/engine/originRegistry.js +1 -388
  38. package/lib/module/js-top/engine/patches/bridge.js +1 -56
  39. package/lib/module/js-top/engine/patches/microtasks.js +1 -23
  40. package/lib/module/js-top/engine/patches/promises.js +1 -62
  41. package/lib/module/js-top/engine/patches/timers.js +1 -62
  42. package/lib/module/js-top/engine/state.js +1 -54
  43. package/lib/module/js-top/engine/symbolicate.js +1 -91
  44. package/lib/module/js-top/sync/jsTopSyncAdapter.js +1 -70
  45. package/lib/module/js-top/types/index.js +1 -42
  46. package/lib/module/js-top/utils/preferences.js +1 -26
  47. package/lib/module/preset.js +1 -71
  48. package/lib/web/index.mjs +2 -3
  49. package/package.json +3 -6
  50. package/lib/commonjs/index.js.map +0 -1
  51. package/lib/commonjs/js-top/components/BusySparkline.js.map +0 -1
  52. package/lib/commonjs/js-top/components/JsTopHudOverlay.js.map +0 -1
  53. package/lib/commonjs/js-top/components/JsTopModal.js.map +0 -1
  54. package/lib/commonjs/js-top/components/TaskDetailView.js.map +0 -1
  55. package/lib/commonjs/js-top/components/TaskTable.js.map +0 -1
  56. package/lib/commonjs/js-top/engine/JsTopController.js.map +0 -1
  57. package/lib/commonjs/js-top/engine/aggregator.js.map +0 -1
  58. package/lib/commonjs/js-top/engine/busyProbe.js.map +0 -1
  59. package/lib/commonjs/js-top/engine/frameStack.js.map +0 -1
  60. package/lib/commonjs/js-top/engine/longtasks.js.map +0 -1
  61. package/lib/commonjs/js-top/engine/now.js.map +0 -1
  62. package/lib/commonjs/js-top/engine/originRegistry.js.map +0 -1
  63. package/lib/commonjs/js-top/engine/patches/bridge.js.map +0 -1
  64. package/lib/commonjs/js-top/engine/patches/microtasks.js.map +0 -1
  65. package/lib/commonjs/js-top/engine/patches/promises.js.map +0 -1
  66. package/lib/commonjs/js-top/engine/patches/timers.js.map +0 -1
  67. package/lib/commonjs/js-top/engine/state.js.map +0 -1
  68. package/lib/commonjs/js-top/engine/symbolicate.js.map +0 -1
  69. package/lib/commonjs/js-top/sync/jsTopSyncAdapter.js.map +0 -1
  70. package/lib/commonjs/js-top/types/index.js.map +0 -1
  71. package/lib/commonjs/js-top/utils/preferences.js.map +0 -1
  72. package/lib/commonjs/preset.js.map +0 -1
  73. package/lib/module/index.js.map +0 -1
  74. package/lib/module/js-top/components/BusySparkline.js.map +0 -1
  75. package/lib/module/js-top/components/JsTopHudOverlay.js.map +0 -1
  76. package/lib/module/js-top/components/JsTopModal.js.map +0 -1
  77. package/lib/module/js-top/components/TaskDetailView.js.map +0 -1
  78. package/lib/module/js-top/components/TaskTable.js.map +0 -1
  79. package/lib/module/js-top/engine/JsTopController.js.map +0 -1
  80. package/lib/module/js-top/engine/aggregator.js.map +0 -1
  81. package/lib/module/js-top/engine/busyProbe.js.map +0 -1
  82. package/lib/module/js-top/engine/frameStack.js.map +0 -1
  83. package/lib/module/js-top/engine/longtasks.js.map +0 -1
  84. package/lib/module/js-top/engine/now.js.map +0 -1
  85. package/lib/module/js-top/engine/originRegistry.js.map +0 -1
  86. package/lib/module/js-top/engine/patches/bridge.js.map +0 -1
  87. package/lib/module/js-top/engine/patches/microtasks.js.map +0 -1
  88. package/lib/module/js-top/engine/patches/promises.js.map +0 -1
  89. package/lib/module/js-top/engine/patches/timers.js.map +0 -1
  90. package/lib/module/js-top/engine/state.js.map +0 -1
  91. package/lib/module/js-top/engine/symbolicate.js.map +0 -1
  92. package/lib/module/js-top/sync/jsTopSyncAdapter.js.map +0 -1
  93. package/lib/module/js-top/types/index.js.map +0 -1
  94. package/lib/module/js-top/utils/preferences.js.map +0 -1
  95. package/lib/module/preset.js.map +0 -1
  96. package/lib/typescript/index.d.ts.map +0 -1
  97. package/lib/typescript/js-top/components/BusySparkline.d.ts.map +0 -1
  98. package/lib/typescript/js-top/components/JsTopHudOverlay.d.ts.map +0 -1
  99. package/lib/typescript/js-top/components/JsTopModal.d.ts.map +0 -1
  100. package/lib/typescript/js-top/components/TaskDetailView.d.ts.map +0 -1
  101. package/lib/typescript/js-top/components/TaskTable.d.ts.map +0 -1
  102. package/lib/typescript/js-top/engine/JsTopController.d.ts.map +0 -1
  103. package/lib/typescript/js-top/engine/aggregator.d.ts.map +0 -1
  104. package/lib/typescript/js-top/engine/busyProbe.d.ts.map +0 -1
  105. package/lib/typescript/js-top/engine/frameStack.d.ts.map +0 -1
  106. package/lib/typescript/js-top/engine/longtasks.d.ts.map +0 -1
  107. package/lib/typescript/js-top/engine/now.d.ts.map +0 -1
  108. package/lib/typescript/js-top/engine/originRegistry.d.ts.map +0 -1
  109. package/lib/typescript/js-top/engine/patches/bridge.d.ts.map +0 -1
  110. package/lib/typescript/js-top/engine/patches/microtasks.d.ts.map +0 -1
  111. package/lib/typescript/js-top/engine/patches/promises.d.ts.map +0 -1
  112. package/lib/typescript/js-top/engine/patches/timers.d.ts.map +0 -1
  113. package/lib/typescript/js-top/engine/state.d.ts.map +0 -1
  114. package/lib/typescript/js-top/engine/symbolicate.d.ts.map +0 -1
  115. package/lib/typescript/js-top/sync/jsTopSyncAdapter.d.ts.map +0 -1
  116. package/lib/typescript/js-top/types/index.d.ts.map +0 -1
  117. package/lib/typescript/js-top/utils/preferences.d.ts.map +0 -1
  118. package/lib/typescript/preset.d.ts.map +0 -1
@@ -1,388 +1 @@
1
- "use strict";
2
-
3
- /**
4
- * Origin registry — the "process table". An origin is the identity of a task
5
- * scheduling site.
6
- *
7
- * Two-stage identity keeps the hot path free of stack captures:
8
- * - Cheap key `${api}|${fnName}` — computed on every schedule, just string
9
- * concat + Map lookup.
10
- * - Once an origin gets hot (PROMOTE_MIN_CALLS schedules or
11
- * PROMOTE_MIN_TOTAL_MS booked), the next schedule captures ONE
12
- * `Error().stack` (throttled per-key and globally) and refines the key
13
- * with the scheduling site's caller frame. Hermes dev stacks reference
14
- * bundle offsets, not source files — the CALLER FUNCTION NAME is the
15
- * readable part, so labels read `setInterval ← startPolling`; raw frames
16
- * are kept on the origin for future desktop symbolication.
17
- */
18
-
19
- import { MAX_ORIGINS, PROMOTE_MIN_CALLS, PROMOTE_MIN_TOTAL_MS, WINDOW_BUCKETS, BUCKET_MS } from "../types";
20
- import { bookAttributedRange, epochOf } from "./aggregator";
21
- import { buoyJsTopSymbolicateOrigin } from "./symbolicate";
22
- const STACK_CAPTURE_MIN_INTERVAL_PER_KEY_MS = 500;
23
- const STACK_CAPTURES_PER_SEC_GLOBAL = 20;
24
- const origins = new Map();
25
-
26
- /** Pinned bucket that absorbs evicted cold origins. */
27
- export const OTHER_KEY = "other";
28
- let globalCaptureWindowStart = 0;
29
- let globalCapturesInWindow = 0;
30
- function makeOrigin(key, api, label) {
31
- return {
32
- key,
33
- api,
34
- label,
35
- calls: 0,
36
- totalMs: 0,
37
- maxMs: 0,
38
- scheduleCount: 0,
39
- lastSeenAt: 0,
40
- promoted: false,
41
- lastCaptureAt: 0,
42
- ring: new Float64Array(WINDOW_BUCKETS),
43
- ringStamps: new Float64Array(WINDOW_BUCKETS).fill(-1)
44
- };
45
- }
46
- function windowMsOf(o, nowT) {
47
- const current = epochOf(nowT);
48
- let sum = 0;
49
- for (let e = current - WINDOW_BUCKETS + 1; e <= current; e++) {
50
- const i = e % WINDOW_BUCKETS;
51
- if (i >= 0 && o.ringStamps[i] === e) sum += o.ring[i];
52
- }
53
- return sum;
54
- }
55
- function evictColdest(nowT) {
56
- let coldest = null;
57
- let coldestScore = Infinity;
58
- for (const o of origins.values()) {
59
- if (o.key === OTHER_KEY) continue;
60
- // Rank by recent cost, tiebreak by staleness.
61
- const score = windowMsOf(o, nowT) * 1e6 + o.lastSeenAt;
62
- if (score < coldestScore) {
63
- coldestScore = score;
64
- coldest = o;
65
- }
66
- }
67
- if (!coldest) return;
68
- const other = getOrCreate(OTHER_KEY, "other", "other (evicted origins)");
69
- other.calls += coldest.calls;
70
- other.totalMs += coldest.totalMs;
71
- other.maxMs = Math.max(other.maxMs, coldest.maxMs);
72
- other.lastSeenAt = Math.max(other.lastSeenAt, coldest.lastSeenAt);
73
- origins.delete(coldest.key);
74
- }
75
- function getOrCreate(key, api, label) {
76
- let o = origins.get(key);
77
- if (!o) {
78
- o = makeOrigin(key, api, label);
79
- origins.set(key, o);
80
- }
81
- return o;
82
- }
83
-
84
- /**
85
- * Parse a Hermes stack into frames, skipping engine/wrapper frames. Engine
86
- * functions are deliberately named with a `buoyJsTop` prefix so they are
87
- * identifiable here even in bundle-offset stacks.
88
- */
89
- export function parseStack(stack) {
90
- const frames = [];
91
- let caller;
92
- const lines = stack.split("\n");
93
- for (const lineText of lines) {
94
- const m = /at\s+(.*?)\s+\((?:address at\s+)?(.*?):(\d+):(\d+)\)/.exec(lineText) ?? /at\s+()(.*?):(\d+):(\d+)$/.exec(lineText.trim());
95
- if (!m) continue;
96
- const fnName = m[1] ?? "";
97
- if (fnName.includes("buoyJsTop") || fnName === "Error" || m[2]?.includes("/js-top/")) {
98
- continue;
99
- }
100
- frames.push({
101
- file: m[2] ?? "",
102
- line: Number(m[3]),
103
- col: Number(m[4])
104
- });
105
- // React splices internal frame names around effect callbacks
106
- // (react_stack_bottom_frame etc.) — their OFFSETS map to user code, so
107
- // keep the frame, but never present them as the "caller".
108
- const isReactInternal = /^(react_stack_bottom_frame|runWithFiberInDEV|commitHookEffectList|flushPassive|performWork|renderWithHooks|safelyCall)/.test(fnName) ||
109
- // Promise/polyfill machinery — technically the caller, never the answer.
110
- /^(then|catch|finally|resolve|reject|fulfilled|rejected|step|tryCallOne|tryCallTwo|tryCatch|invokeCallback|notify)$/.test(fnName);
111
- if (!caller && fnName && fnName !== "anonymous" && !isReactInternal) {
112
- caller = fnName;
113
- }
114
- if (frames.length >= 3) break;
115
- }
116
- return {
117
- caller,
118
- frames
119
- };
120
- }
121
- function allowCapture(o, nowT) {
122
- if (nowT - o.lastCaptureAt < STACK_CAPTURE_MIN_INTERVAL_PER_KEY_MS) {
123
- return false;
124
- }
125
- if (nowT - globalCaptureWindowStart > 1000) {
126
- globalCaptureWindowStart = nowT;
127
- globalCapturesInWindow = 0;
128
- }
129
- if (globalCapturesInWindow >= STACK_CAPTURES_PER_SEC_GLOBAL) return false;
130
- globalCapturesInWindow++;
131
- o.lastCaptureAt = nowT;
132
- return true;
133
- }
134
- function shortFile(file) {
135
- if (!file) return undefined;
136
- // Strip scheme://host:port/ and query — keeps the bundle-relative tail.
137
- // Metro dev URLs carry params after "//&" instead of "?" — strip both.
138
- const noQuery = (file.split("?")[0] ?? file).replace(/\/\/&.*$/, "");
139
- const m = /^[a-z]+:\/\/[^/]+\/(.*)$/.exec(noQuery);
140
- const tail = m ? m[1] : noQuery;
141
- const parts = tail.split("/");
142
- return parts.slice(-2).join("/");
143
- }
144
-
145
- /**
146
- * Resolve the origin key for a schedule. Called from patched globals at
147
- * schedule time — this is the hot path; the common case is one Map get and
148
- * two counter bumps.
149
- */
150
- export function buoyJsTopOriginExists(key) {
151
- return origins.has(key);
152
- }
153
- export function buoyJsTopScheduleOrigin(api, fn, nowT,
154
- /**
155
- * False when identity is resolved at FIRE time (callback scheduled while
156
- * the engine was off, or stats cleared since): a runtime stack is not a
157
- * scheduling site, so stack capture is skipped on that path.
158
- */
159
- allowStackCapture = true) {
160
- const fnLike = fn;
161
- const name = typeof fnLike.displayName === "string" && fnLike.displayName || typeof fnLike.name === "string" && fnLike.name || "anonymous";
162
- const cheapKey = `${api}|${name}`;
163
- let o = origins.get(cheapKey);
164
- if (!o) {
165
- if (origins.size >= MAX_ORIGINS) evictColdest(nowT);
166
- o = makeOrigin(cheapKey, api, name === "anonymous" ? apiLabel(api) : `${apiLabel(api)} ${name}`);
167
- origins.set(cheapKey, o);
168
- }
169
- o.scheduleCount++;
170
- if (!o.promoted && (o.scheduleCount >= PROMOTE_MIN_CALLS || o.totalMs >= PROMOTE_MIN_TOTAL_MS)) {
171
- o.promoted = true;
172
- }
173
- // Intervals schedule exactly ONCE — this call is the only chance to see
174
- // their scheduling stack, so capture eagerly instead of waiting for
175
- // promotion (which is driven by schedule counts they'll never accrue).
176
- if ((o.promoted || api === "interval") && allowStackCapture && allowCapture(o, nowT)) {
177
- const {
178
- caller,
179
- frames
180
- } = parseStack(new Error().stack ?? "");
181
- const top = frames[0];
182
- if (top || caller) {
183
- // Keys stay short + token-cheap: bundle URLs are stripped down to the
184
- // trailing path segment (full frames live on the origin record).
185
- const site = top ? `${shortFile(top.file) ?? "?"}:${top.line}` : "?";
186
- const refinedKey = `${cheapKey}@${caller ?? "?"}:${site}`;
187
- let refined = origins.get(refinedKey);
188
- if (!refined) {
189
- if (origins.size >= MAX_ORIGINS) evictColdest(nowT);
190
- // Label rule: the function name (when it exists) is the anchor and
191
- // is never discarded; a readable caller or site is appended.
192
- const base = name === "anonymous" ? apiLabel(api) : `${apiLabel(api)} ${name}`;
193
- refined = makeOrigin(refinedKey, api, caller ? `${base} ← ${caller}` : name === "anonymous" ? `${base} @ ${shortFile(top?.file ?? "") ?? "?"}` : base);
194
- refined.caller = caller;
195
- refined.file = top ? shortFile(top.file) : undefined;
196
- refined.line = top?.line;
197
- refined.frames = frames;
198
- refined.promoted = true;
199
- // Migrate the cheap origin's accumulated stats into the refined one
200
- // and zero the cheap record — the table must never show the same
201
- // scheduling site as two rows (pre- and post-refinement).
202
- refined.calls = o.calls;
203
- refined.totalMs = o.totalMs;
204
- refined.maxMs = o.maxMs;
205
- refined.scheduleCount = o.scheduleCount;
206
- refined.lastSeenAt = o.lastSeenAt;
207
- refined.ring.set(o.ring);
208
- refined.ringStamps.set(o.ringStamps);
209
- o.calls = 0;
210
- o.totalMs = 0;
211
- o.maxMs = 0;
212
- o.ring.fill(0);
213
- o.ringStamps.fill(-1);
214
- origins.set(refinedKey, refined);
215
- // Upgrade the label to real source coordinates when Metro is around.
216
- buoyJsTopSymbolicateOrigin(refined, apiLabel(api));
217
- }
218
- o.activeRefinedKey = refinedKey;
219
- }
220
- }
221
- return o.activeRefinedKey ?? cheapKey;
222
- }
223
- function apiLabel(api) {
224
- switch (api) {
225
- case "timeout":
226
- return "setTimeout";
227
- case "interval":
228
- return "setInterval";
229
- case "immediate":
230
- return "setImmediate";
231
- case "raf":
232
- return "requestAnimationFrame";
233
- case "microtask":
234
- return "queueMicrotask";
235
- case "then":
236
- return "Promise.then";
237
- case "bridge":
238
- return "bridge";
239
- default:
240
- return api;
241
- }
242
- }
243
-
244
- /** Book a completed call's exclusive time onto its origin + global buckets. */
245
- export function buoyJsTopBookExclusive(originKey, exclusiveMs, start, end) {
246
- const o = origins.get(originKey);
247
- if (!o) return;
248
- o.calls++;
249
- o.totalMs += exclusiveMs;
250
- if (exclusiveMs > o.maxMs) o.maxMs = exclusiveMs;
251
- o.lastSeenAt = end;
252
- // Promotion by accumulated cost must ALSO happen at book time: an
253
- // interval never re-schedules, so the schedule-time check alone would
254
- // leave a 1000ms/run interval "not hot" forever.
255
- if (!o.promoted && o.totalMs >= PROMOTE_MIN_TOTAL_MS) o.promoted = true;
256
- // Per-origin ring, spread across the call's span like the global buckets.
257
- const span = end - start;
258
- const ratio = span > 0 ? Math.min(1, exclusiveMs / span) : 1;
259
- const clampedStart = Math.max(start, end - WINDOW_BUCKETS * BUCKET_MS);
260
- let e = epochOf(clampedStart);
261
- const lastEpoch = epochOf(end);
262
- let cursor = clampedStart;
263
- while (e <= lastEpoch) {
264
- const bucketEnd = (e + 1) * BUCKET_MS;
265
- const sliceEnd = Math.min(end, bucketEnd);
266
- const ms = sliceEnd - cursor;
267
- if (ms > 0) {
268
- const i = e % WINDOW_BUCKETS;
269
- if (i >= 0) {
270
- if (o.ringStamps[i] !== e) {
271
- o.ringStamps[i] = e;
272
- o.ring[i] = 0;
273
- }
274
- o.ring[i] += span > 0 ? ms * ratio : exclusiveMs;
275
- }
276
- }
277
- cursor = sliceEnd;
278
- e++;
279
- }
280
- bookAttributedRange(start, end, exclusiveMs);
281
- }
282
- export function labelForOrigin(key) {
283
- return origins.get(key)?.label ?? null;
284
- }
285
-
286
- /** Build the ranked table rows (excludes the pinned unattributed row — the
287
- * controller assembles that from aggregator residuals). */
288
- export function buildRows(nowT, windowBusy, topN) {
289
- const rows = [];
290
- for (const o of origins.values()) {
291
- // Cheap keys that have handed off to a refined key stop accumulating new
292
- // work but keep their pre-promotion totals; hide them once idle.
293
- const windowMs = windowMsOf(o, nowT);
294
- if (windowMs <= 0 && o.totalMs < 1) continue;
295
- rows.push({
296
- key: o.key,
297
- label: o.label,
298
- api: o.api,
299
- file: o.file,
300
- line: o.line,
301
- windowMs: Math.round(windowMs * 10) / 10,
302
- pctOfBusy: windowBusy > 0 ? Math.min(100, Math.round(windowMs / windowBusy * 1000) / 10) : 0,
303
- totalMs: Math.round(o.totalMs * 10) / 10,
304
- calls: o.calls,
305
- avgMs: o.calls > 0 ? Math.round(o.totalMs / o.calls * 100) / 100 : 0,
306
- maxMs: Math.round(o.maxMs * 100) / 100,
307
- lastSeenAgoMs: o.lastSeenAt > 0 ? Math.max(0, nowT - o.lastSeenAt) : 0,
308
- // Filled in by the controller from the freeze-stats provider.
309
- freezes: 0,
310
- worstFreezeMs: 0
311
- });
312
- }
313
- rows.sort((a, b) => b.windowMs - a.windowMs || b.totalMs - a.totalMs);
314
- // Noise gate: rows that can't matter (<1ms window, <0.5% busy, no freezes)
315
- // collapse into a single aggregate so the table stays a shortlist.
316
- const majors = [];
317
- const minors = [];
318
- for (const r of rows) {
319
- (r.windowMs >= 1 || r.pctOfBusy >= 0.5 || r.freezes > 0 ? majors : minors).push(r);
320
- }
321
- if (minors.length >= 2) {
322
- majors.push({
323
- key: "minor",
324
- label: `${minors.length} minor origins (≤1ms each)`,
325
- api: "other",
326
- windowMs: Math.round(minors.reduce((sum, r) => sum + r.windowMs, 0) * 10) / 10,
327
- pctOfBusy: Math.round(minors.reduce((sum, r) => sum + r.pctOfBusy, 0) * 10) / 10,
328
- totalMs: Math.round(minors.reduce((sum, r) => sum + r.totalMs, 0) * 10) / 10,
329
- calls: minors.reduce((sum, r) => sum + r.calls, 0),
330
- avgMs: 0,
331
- maxMs: Math.max(0, ...minors.map(r => r.maxMs)),
332
- lastSeenAgoMs: Math.min(...minors.map(r => r.lastSeenAgoMs)),
333
- freezes: 0,
334
- worstFreezeMs: 0
335
- });
336
- return majors.slice(0, topN);
337
- }
338
- return rows.slice(0, topN);
339
- }
340
-
341
- /**
342
- * Full origin detail minus freeze stats (the controller merges those). The
343
- * bucket array is the origin's 5s activity trend, oldest→newest.
344
- */
345
- export function getOriginDetailBase(key, nowT, windowBusy) {
346
- const o = origins.get(key);
347
- if (!o) return null;
348
- const current = epochOf(nowT);
349
- const buckets = [];
350
- let windowMs = 0;
351
- for (let e = current - WINDOW_BUCKETS + 1; e <= current; e++) {
352
- const i = e % WINDOW_BUCKETS;
353
- const ms = i >= 0 && o.ringStamps[i] === e ? Math.round(o.ring[i] * 10) / 10 : 0;
354
- buckets.push(ms);
355
- windowMs += ms;
356
- }
357
- return {
358
- key: o.key,
359
- label: o.label,
360
- api: o.api,
361
- caller: o.caller,
362
- file: o.file,
363
- line: o.line,
364
- frames: o.frames,
365
- promoted: o.promoted,
366
- scheduleCount: o.scheduleCount,
367
- calls: o.calls,
368
- totalMs: Math.round(o.totalMs * 10) / 10,
369
- avgMs: o.calls > 0 ? Math.round(o.totalMs / o.calls * 100) / 100 : 0,
370
- maxMs: Math.round(o.maxMs * 100) / 100,
371
- windowMs: Math.round(windowMs * 10) / 10,
372
- pctOfBusy: windowBusy > 0 ? Math.min(100, Math.round(windowMs / windowBusy * 1000) / 10) : 0,
373
- lastSeenAgoMs: o.lastSeenAt > 0 ? Math.max(0, nowT - o.lastSeenAt) : 0,
374
- buckets
375
- };
376
- }
377
- export function resetOrigins() {
378
- origins.clear();
379
- }
380
-
381
- /** Test-only introspection. */
382
- export function debugGetOrigin(key) {
383
- return origins.get(key);
384
- }
385
- export function originCount() {
386
- return origins.size;
387
- }
388
- //# sourceMappingURL=originRegistry.js.map
1
+ "use strict";import{MAX_ORIGINS as b,PROMOTE_MIN_CALLS as _,PROMOTE_MIN_TOTAL_MS as C,WINDOW_BUCKETS as M,BUCKET_MS as O}from"../types";import{bookAttributedRange as $,epochOf as g}from"./aggregator";import{buoyJsTopSymbolicateOrigin as R}from"./symbolicate";const I=500;const T=20;const c=new Map;export const OTHER_KEY="other";let w=0;let y=0;function x(s,o,l){return{key:s,api:o,label:l,calls:0,totalMs:0,maxMs:0,scheduleCount:0,lastSeenAt:0,promoted:false,lastCaptureAt:0,ring:new Float64Array(M),ringStamps:new Float64Array(M).fill(-1)}}function k(s,o){const l=g(o);let e=0;for(let r=l-M+1;r<=l;r++){const a=r%M;if(a>=0&&s.ringStamps[a]===r)e+=s.ring[a]}return e}function E(s){let o=null;let l=Infinity;for(const r of c.values()){if(r.key===OTHER_KEY)continue;const a=k(r,s)*1e6+r.lastSeenAt;if(a<l){l=a;o=r}}if(!o)return;const e=v(OTHER_KEY,"other","other (evicted origins)");e.calls+=o.calls;e.totalMs+=o.totalMs;e.maxMs=Math.max(e.maxMs,o.maxMs);e.lastSeenAt=Math.max(e.lastSeenAt,o.lastSeenAt);c.delete(o.key)}function v(s,o,l){let e=c.get(s);if(!e){e=x(s,o,l);c.set(s,e)}return e}export function parseStack(s){const o=[];let l;const e=s.split("\n");for(const r of e){const a=/at\s+(.*?)\s+\((?:address at\s+)?(.*?):(\d+):(\d+)\)/.exec(r)??/at\s+()(.*?):(\d+):(\d+)$/.exec(r.trim());if(!a)continue;const t=a[1]??"";if(t.includes("buoyJsTop")||t==="Error"||a[2]?.includes("/js-top/")){continue}o.push({file:a[2]??"",line:Number(a[3]),col:Number(a[4])});const n=/^(react_stack_bottom_frame|runWithFiberInDEV|commitHookEffectList|flushPassive|performWork|renderWithHooks|safelyCall)/.test(t)||/^(then|catch|finally|resolve|reject|fulfilled|rejected|step|tryCallOne|tryCallTwo|tryCatch|invokeCallback|notify)$/.test(t);if(!l&&t&&t!=="anonymous"&&!n){l=t}if(o.length>=3)break}return{caller:l,frames:o}}function K(s,o){if(o-s.lastCaptureAt<I){return false}if(o-w>1e3){w=o;y=0}if(y>=T)return false;y++;s.lastCaptureAt=o;return true}function A(s){if(!s)return void 0;const o=(s.split("?")[0]??s).replace(/\/\/&.*$/,"");const l=/^[a-z]+:\/\/[^/]+\/(.*)$/.exec(o);const e=l?l[1]:o;const r=e.split("/");return r.slice(-2).join("/")}export function buoyJsTopOriginExists(s){return c.has(s)}export function buoyJsTopScheduleOrigin(s,o,l,e=true){const r=o;const a=typeof r.displayName==="string"&&r.displayName||typeof r.name==="string"&&r.name||"anonymous";const t=`${s}|${a}`;let n=c.get(t);if(!n){if(c.size>=b)E(l);n=x(t,s,a==="anonymous"?h(s):`${h(s)} ${a}`);c.set(t,n)}n.scheduleCount++;if(!n.promoted&&(n.scheduleCount>=_||n.totalMs>=C)){n.promoted=true}if((n.promoted||s==="interval")&&e&&K(n,l)){const{caller:u,frames:d}=parseStack(new Error().stack??"");const f=d[0];if(f||u){const S=f?`${A(f.file)??"?"}:${f.line}`:"?";const p=`${t}@${u??"?"}:${S}`;let i=c.get(p);if(!i){if(c.size>=b)E(l);const m=a==="anonymous"?h(s):`${h(s)} ${a}`;i=x(p,s,u?`${m} \u2190 ${u}`:a==="anonymous"?`${m} @ ${A(f?.file??"")??"?"}`:m);i.caller=u;i.file=f?A(f.file):void 0;i.line=f?.line;i.frames=d;i.promoted=true;i.calls=n.calls;i.totalMs=n.totalMs;i.maxMs=n.maxMs;i.scheduleCount=n.scheduleCount;i.lastSeenAt=n.lastSeenAt;i.ring.set(n.ring);i.ringStamps.set(n.ringStamps);n.calls=0;n.totalMs=0;n.maxMs=0;n.ring.fill(0);n.ringStamps.fill(-1);c.set(p,i);R(i,h(s))}n.activeRefinedKey=p}}return n.activeRefinedKey??t}function h(s){switch(s){case"timeout":return"setTimeout";case"interval":return"setInterval";case"immediate":return"setImmediate";case"raf":return"requestAnimationFrame";case"microtask":return"queueMicrotask";case"then":return"Promise.then";case"bridge":return"bridge";default:return s}}export function buoyJsTopBookExclusive(s,o,l,e){const r=c.get(s);if(!r)return;r.calls++;r.totalMs+=o;if(o>r.maxMs)r.maxMs=o;r.lastSeenAt=e;if(!r.promoted&&r.totalMs>=C)r.promoted=true;const a=e-l;const t=a>0?Math.min(1,o/a):1;const n=Math.max(l,e-M*O);let u=g(n);const d=g(e);let f=n;while(u<=d){const S=(u+1)*O;const p=Math.min(e,S);const i=p-f;if(i>0){const m=u%M;if(m>=0){if(r.ringStamps[m]!==u){r.ringStamps[m]=u;r.ring[m]=0}r.ring[m]+=a>0?i*t:o}}f=p;u++}$(l,e,o)}export function labelForOrigin(s){return c.get(s)?.label??null}export function buildRows(s,o,l){const e=[];for(const t of c.values()){const n=k(t,s);if(n<=0&&t.totalMs<1)continue;e.push({key:t.key,label:t.label,api:t.api,file:t.file,line:t.line,windowMs:Math.round(n*10)/10,pctOfBusy:o>0?Math.min(100,Math.round(n/o*1e3)/10):0,totalMs:Math.round(t.totalMs*10)/10,calls:t.calls,avgMs:t.calls>0?Math.round(t.totalMs/t.calls*100)/100:0,maxMs:Math.round(t.maxMs*100)/100,lastSeenAgoMs:t.lastSeenAt>0?Math.max(0,s-t.lastSeenAt):0,freezes:0,worstFreezeMs:0})}e.sort((t,n)=>n.windowMs-t.windowMs||n.totalMs-t.totalMs);const r=[];const a=[];for(const t of e){(t.windowMs>=1||t.pctOfBusy>=.5||t.freezes>0?r:a).push(t)}if(a.length>=2){r.push({key:"minor",label:`${a.length} minor origins (\u22641ms each)`,api:"other",windowMs:Math.round(a.reduce((t,n)=>t+n.windowMs,0)*10)/10,pctOfBusy:Math.round(a.reduce((t,n)=>t+n.pctOfBusy,0)*10)/10,totalMs:Math.round(a.reduce((t,n)=>t+n.totalMs,0)*10)/10,calls:a.reduce((t,n)=>t+n.calls,0),avgMs:0,maxMs:Math.max(0,...a.map(t=>t.maxMs)),lastSeenAgoMs:Math.min(...a.map(t=>t.lastSeenAgoMs)),freezes:0,worstFreezeMs:0});return r.slice(0,l)}return e.slice(0,l)}export function getOriginDetailBase(s,o,l){const e=c.get(s);if(!e)return null;const r=g(o);const a=[];let t=0;for(let n=r-M+1;n<=r;n++){const u=n%M;const d=u>=0&&e.ringStamps[u]===n?Math.round(e.ring[u]*10)/10:0;a.push(d);t+=d}return{key:e.key,label:e.label,api:e.api,caller:e.caller,file:e.file,line:e.line,frames:e.frames,promoted:e.promoted,scheduleCount:e.scheduleCount,calls:e.calls,totalMs:Math.round(e.totalMs*10)/10,avgMs:e.calls>0?Math.round(e.totalMs/e.calls*100)/100:0,maxMs:Math.round(e.maxMs*100)/100,windowMs:Math.round(t*10)/10,pctOfBusy:l>0?Math.min(100,Math.round(t/l*1e3)/10):0,lastSeenAgoMs:e.lastSeenAt>0?Math.max(0,o-e.lastSeenAt):0,buckets:a}}export function resetOrigins(){c.clear()}export function debugGetOrigin(s){return c.get(s)}export function originCount(){return c.size}
@@ -1,56 +1 @@
1
- "use strict";
2
-
3
- /**
4
- * Tier 2 patch: legacy-bridge call-ins. On Paper/legacy-bridge runtimes,
5
- * every native→JS call (touch events via RCTEventEmitter, timer flushes via
6
- * JSTimers.callTimers, module callbacks) enters through
7
- * `__fbBatchedBridge.callFunctionReturnFlushedQueue` /
8
- * `invokeCallbackAndReturnFlushedQueue`. Wrapping them attributes event and
9
- * native-callback cost by module.method.
10
- *
11
- * On New Architecture (bridgeless) these globals don't exist — install
12
- * reports `bridge: false` and the UI banners the coverage gap. Nested
13
- * wrapped work (e.g. our wrapped rAF callbacks running inside a
14
- * callTimers call-in) books exclusively via the frame stack, so this tier
15
- * only ever adds its own un-nested remainder.
16
- */
17
-
18
- import { buoyJsTopRunAs } from "../frameStack";
19
- import { g, isActive, PATCHED_TAG } from "../state";
20
- export function installBridgePatch() {
21
- const bridge = g.__fbBatchedBridge;
22
- if (!bridge) return {
23
- bridge: false
24
- };
25
- let patchedAny = false;
26
- if (typeof bridge.callFunctionReturnFlushedQueue === "function" && !bridge.callFunctionReturnFlushedQueue[PATCHED_TAG]) {
27
- const original = bridge.callFunctionReturnFlushedQueue.bind(bridge);
28
- const buoyJsTopCallFunction = function (module, method,
29
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
30
- args) {
31
- if (!isActive()) return original(module, method, args);
32
- return buoyJsTopRunAs("bridge", `${module}.${method}`, () => original(module, method, args));
33
- };
34
- buoyJsTopCallFunction[PATCHED_TAG] = true;
35
- bridge.callFunctionReturnFlushedQueue = buoyJsTopCallFunction;
36
- patchedAny = true;
37
- }
38
- if (typeof bridge.invokeCallbackAndReturnFlushedQueue === "function" && !bridge.invokeCallbackAndReturnFlushedQueue[PATCHED_TAG]) {
39
- const original = bridge.invokeCallbackAndReturnFlushedQueue.bind(bridge);
40
- // Callback ids are per-call and meaningless as identity — one shared
41
- // origin covers all native-module completion callbacks.
42
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
43
- const buoyJsTopInvokeCallback = function (cbID, args) {
44
- if (!isActive()) return original(cbID, args);
45
- return buoyJsTopRunAs("bridge", "nativeModuleCallback", () => original(cbID, args));
46
- };
47
- buoyJsTopInvokeCallback[PATCHED_TAG] = true;
48
- bridge.invokeCallbackAndReturnFlushedQueue = buoyJsTopInvokeCallback;
49
- patchedAny = true;
50
- }
51
- const alreadyPatched = bridge.callFunctionReturnFlushedQueue?.[PATCHED_TAG] === true;
52
- return {
53
- bridge: patchedAny || alreadyPatched
54
- };
55
- }
56
- //# sourceMappingURL=bridge.js.map
1
+ "use strict";import{buoyJsTopRunAs as c}from"../frameStack";import{g as s,isActive as a,PATCHED_TAG as l}from"../state";export function installBridgePatch(){const e=s.__fbBatchedBridge;if(!e)return{bridge:false};let r=false;if(typeof e.callFunctionReturnFlushedQueue==="function"&&!e.callFunctionReturnFlushedQueue[l]){const t=e.callFunctionReturnFlushedQueue.bind(e);const i=function(u,n,o){if(!a())return t(u,n,o);return c("bridge",`${u}.${n}`,()=>t(u,n,o))};i[l]=true;e.callFunctionReturnFlushedQueue=i;r=true}if(typeof e.invokeCallbackAndReturnFlushedQueue==="function"&&!e.invokeCallbackAndReturnFlushedQueue[l]){const t=e.invokeCallbackAndReturnFlushedQueue.bind(e);const i=function(u,n){if(!a())return t(u,n);return c("bridge","nativeModuleCallback",()=>t(u,n))};i[l]=true;e.invokeCallbackAndReturnFlushedQueue=i;r=true}const d=e.callFunctionReturnFlushedQueue?.[l]===true;return{bridge:r||d}}
@@ -1,23 +1 @@
1
- "use strict";
2
-
3
- /**
4
- * Tier 1 patch: queueMicrotask. Same install-once contract as timers.ts.
5
- * (Promise reactions are Tier 1.5 — see promises.ts.)
6
- */
7
-
8
- import { buoyJsTopWrapTask } from "../frameStack";
9
- import { captureOriginals, g, PATCHED_TAG } from "../state";
10
- export function installMicrotaskPatches() {
11
- const originals = captureOriginals();
12
- if (typeof g.queueMicrotask === "function" && !g.queueMicrotask[PATCHED_TAG] && originals.queueMicrotask) {
13
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
14
- const buoyJsTopQueueMicrotask = function (fn) {
15
- const cb = buoyJsTopWrapTask(fn, "microtask");
16
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
17
- return originals.queueMicrotask(cb);
18
- };
19
- buoyJsTopQueueMicrotask[PATCHED_TAG] = true;
20
- g.queueMicrotask = buoyJsTopQueueMicrotask;
21
- }
22
- }
23
- //# sourceMappingURL=microtasks.js.map
1
+ "use strict";import{buoyJsTopWrapTask as e}from"../frameStack";import{captureOriginals as c,g as o,PATCHED_TAG as u}from"../state";export function installMicrotaskPatches(){const t=c();if(typeof o.queueMicrotask==="function"&&!o.queueMicrotask[u]&&t.queueMicrotask){const r=function(i){const s=e(i,"microtask");return t.queueMicrotask(s)};r[u]=true;o.queueMicrotask=r}}
@@ -1,62 +1 @@
1
- "use strict";
2
-
3
- /**
4
- * Tier 1.5 patch: Promise reactions via Promise.prototype.then/catch/finally.
5
- *
6
- * All three are patched: in Hermes, catch/finally delegate to then, and the
7
- * WRAPPED_TAG check in wrapTask makes the delegation harmless (a reaction
8
- * wrapped by our catch patch passes through our then patch untouched).
9
- *
10
- * Known, documented gap: native async/await resumption never goes through
11
- * `.then` — async-function bodies land in the "unattributed" row, not here.
12
- *
13
- * The wrapper is passthrough-first: non-function reactions
14
- * (`.then(undefined, fn)`), `this` binding, and return values are preserved
15
- * exactly; when the engine is inactive the original is called with the raw
16
- * arguments.
17
- */
18
-
19
- import { buoyJsTopWrapTask } from "../frameStack";
20
- import { g, PATCHED_TAG } from "../state";
21
-
22
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
23
-
24
- function patchMethod(
25
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
26
- proto, name) {
27
- const original = proto[name];
28
- if (typeof original !== "function") return false;
29
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
30
- if (original[PATCHED_TAG]) return true;
31
-
32
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
33
- const patched = function (...reactions) {
34
- for (let i = 0; i < reactions.length; i++) {
35
- if (typeof reactions[i] === "function") {
36
- reactions[i] = buoyJsTopWrapTask(reactions[i], "then");
37
- }
38
- }
39
- return original.apply(this, reactions);
40
- };
41
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
42
- patched[PATCHED_TAG] = true;
43
- Object.defineProperty(patched, "name", {
44
- value: name,
45
- configurable: true
46
- });
47
- proto[name] = patched;
48
- return true;
49
- }
50
- export function installPromisePatches() {
51
- const proto = g.Promise?.prototype;
52
- if (!proto) return {
53
- promises: false
54
- };
55
- const ok = patchMethod(proto, "then");
56
- patchMethod(proto, "catch");
57
- patchMethod(proto, "finally");
58
- return {
59
- promises: ok
60
- };
61
- }
62
- //# sourceMappingURL=promises.js.map
1
+ "use strict";import{buoyJsTopWrapTask as p}from"../frameStack";import{g as s,PATCHED_TAG as u}from"../state";function f(t,e){const n=t[e];if(typeof n!=="function")return false;if(n[u])return true;const i=function(...r){for(let o=0;o<r.length;o++){if(typeof r[o]==="function"){r[o]=p(r[o],"then")}}return n.apply(this,r)};i[u]=true;Object.defineProperty(i,"name",{value:e,configurable:true});t[e]=i;return true}export function installPromisePatches(){const t=s.Promise?.prototype;if(!t)return{promises:false};const e=f(t,"then");f(t,"catch");f(t,"finally");return{promises:e}}
@@ -1,62 +1 @@
1
- "use strict";
2
-
3
- /**
4
- * Tier 1 patches: setTimeout / setInterval / setImmediate /
5
- * requestAnimationFrame. Public globals only — RN internals (JSTimers) are
6
- * private API and churn across versions.
7
- *
8
- * Install-once, never unpatch (see state.ts). Handles are returned from the
9
- * originals unchanged, so clearTimeout/clearInterval/cancelAnimationFrame
10
- * need no patching. Extra setTimeout args pass through.
11
- */
12
-
13
- import { buoyJsTopWrapTask } from "../frameStack";
14
- import { captureOriginals, g, PATCHED_TAG } from "../state";
15
- export function installTimerPatches() {
16
- const originals = captureOriginals();
17
- if (typeof g.setTimeout === "function" && !g.setTimeout[PATCHED_TAG]) {
18
- const buoyJsTopSetTimeout = function (
19
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
20
- fn, timeout,
21
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
22
- ...args) {
23
- const cb = buoyJsTopWrapTask(fn, "timeout");
24
- return originals.setTimeout(cb, timeout, ...args);
25
- };
26
- buoyJsTopSetTimeout[PATCHED_TAG] = true;
27
- g.setTimeout = buoyJsTopSetTimeout;
28
- }
29
- if (typeof g.setInterval === "function" && !g.setInterval[PATCHED_TAG]) {
30
- const buoyJsTopSetInterval = function (
31
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
32
- fn, timeout,
33
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
34
- ...args) {
35
- const cb = buoyJsTopWrapTask(fn, "interval");
36
- return originals.setInterval(cb, timeout, ...args);
37
- };
38
- buoyJsTopSetInterval[PATCHED_TAG] = true;
39
- g.setInterval = buoyJsTopSetInterval;
40
- }
41
- if (typeof g.setImmediate === "function" && !g.setImmediate[PATCHED_TAG] && originals.setImmediate) {
42
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
43
- const buoyJsTopSetImmediate = function (fn, ...args) {
44
- const cb = buoyJsTopWrapTask(fn, "immediate");
45
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
46
- return originals.setImmediate(cb, ...args);
47
- };
48
- buoyJsTopSetImmediate[PATCHED_TAG] = true;
49
- g.setImmediate = buoyJsTopSetImmediate;
50
- }
51
- if (typeof g.requestAnimationFrame === "function" && !g.requestAnimationFrame[PATCHED_TAG] && originals.requestAnimationFrame) {
52
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
53
- const buoyJsTopRequestAnimationFrame = function (fn) {
54
- const cb = buoyJsTopWrapTask(fn, "raf");
55
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
56
- return originals.requestAnimationFrame(cb);
57
- };
58
- buoyJsTopRequestAnimationFrame[PATCHED_TAG] = true;
59
- g.requestAnimationFrame = buoyJsTopRequestAnimationFrame;
60
- }
61
- }
62
- //# sourceMappingURL=timers.js.map
1
+ "use strict";import{buoyJsTopWrapTask as m}from"../frameStack";import{captureOriginals as a,g as t,PATCHED_TAG as n}from"../state";export function installTimerPatches(){const r=a();if(typeof t.setTimeout==="function"&&!t.setTimeout[n]){const e=function(o,i,...s){const u=m(o,"timeout");return r.setTimeout(u,i,...s)};e[n]=true;t.setTimeout=e}if(typeof t.setInterval==="function"&&!t.setInterval[n]){const e=function(o,i,...s){const u=m(o,"interval");return r.setInterval(u,i,...s)};e[n]=true;t.setInterval=e}if(typeof t.setImmediate==="function"&&!t.setImmediate[n]&&r.setImmediate){const e=function(o,...i){const s=m(o,"immediate");return r.setImmediate(s,...i)};e[n]=true;t.setImmediate=e}if(typeof t.requestAnimationFrame==="function"&&!t.requestAnimationFrame[n]&&r.requestAnimationFrame){const e=function(o){const i=m(o,"raf");return r.requestAnimationFrame(i)};e[n]=true;t.requestAnimationFrame=e}}
@@ -1,54 +1 @@
1
- "use strict";
2
-
3
- /**
4
- * Shared engine state + pre-patch originals.
5
- *
6
- * `active` is the single gate every patched global checks at schedule time:
7
- * patches install once and are NEVER removed (restoring originals would
8
- * clobber any wrapper another library — Sentry, Reanimated — layered on top
9
- * of ours after install; same reasoning as highlight-updates' CommitProfiler
10
- * hook). When inactive, a patched global forwards the raw callback to the
11
- * original at the cost of one boolean check.
12
- *
13
- * `originals` are captured BEFORE patching so the engine's own probe and
14
- * notify timers never flow through the wrappers — JS TOP must not list
15
- * itself as a task.
16
- */
17
-
18
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
19
- export const g =
20
- // eslint-disable-next-line no-restricted-globals
21
- typeof globalThis !== "undefined" ? globalThis : {};
22
- let active = false;
23
- export function isActive() {
24
- return active;
25
- }
26
- export function setActive(on) {
27
- active = on;
28
- }
29
- let originals = null;
30
-
31
- /** Capture pre-patch globals. Idempotent; first call wins. */
32
- export function captureOriginals() {
33
- if (!originals) {
34
- originals = {
35
- setTimeout: g.setTimeout?.bind(g),
36
- setInterval: g.setInterval?.bind(g),
37
- clearInterval: g.clearInterval?.bind(g),
38
- clearTimeout: g.clearTimeout?.bind(g),
39
- setImmediate: g.setImmediate?.bind(g),
40
- requestAnimationFrame: g.requestAnimationFrame?.bind(g),
41
- queueMicrotask: g.queueMicrotask?.bind(g)
42
- };
43
- }
44
- return originals;
45
- }
46
- export function getOriginals() {
47
- return captureOriginals();
48
- }
49
-
50
- /** Tag interned on patched globals so duplicate module instances skip re-patching. */
51
- export const PATCHED_TAG = Symbol.for("buoy.jsTop.patched");
52
- /** Marker set on wrapper functions so a wrapped fn is never wrapped twice. */
53
- export const WRAPPED_TAG = Symbol.for("buoy.jsTop.wrapped");
54
- //# sourceMappingURL=state.js.map
1
+ "use strict";export const g=typeof globalThis!=="undefined"?globalThis:{};let t=false;export function isActive(){return t}export function setActive(i){t=i}let e=null;export function captureOriginals(){if(!e){e={setTimeout:g.setTimeout?.bind(g),setInterval:g.setInterval?.bind(g),clearInterval:g.clearInterval?.bind(g),clearTimeout:g.clearTimeout?.bind(g),setImmediate:g.setImmediate?.bind(g),requestAnimationFrame:g.requestAnimationFrame?.bind(g),queueMicrotask:g.queueMicrotask?.bind(g)}}return e}export function getOriginals(){return captureOriginals()}export const PATCHED_TAG=Symbol.for("buoy.jsTop.patched");export const WRAPPED_TAG=Symbol.for("buoy.jsTop.wrapped");