@buoy-gg/js-top 7.0.40 → 7.0.42
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.
- package/README.md +12 -67
- package/lib/commonjs/index.js +1 -97
- package/lib/commonjs/js-top/components/BusySparkline.js +1 -56
- package/lib/commonjs/js-top/components/JsTopHudOverlay.js +1 -79
- package/lib/commonjs/js-top/components/JsTopModal.js +1 -349
- package/lib/commonjs/js-top/components/TaskDetailView.js +1 -330
- package/lib/commonjs/js-top/components/TaskTable.js +1 -360
- package/lib/commonjs/js-top/engine/JsTopController.js +1 -350
- package/lib/commonjs/js-top/engine/aggregator.js +1 -148
- package/lib/commonjs/js-top/engine/busyProbe.js +1 -101
- package/lib/commonjs/js-top/engine/frameStack.js +1 -133
- package/lib/commonjs/js-top/engine/longtasks.js +1 -141
- package/lib/commonjs/js-top/engine/now.js +1 -38
- package/lib/commonjs/js-top/engine/originRegistry.js +1 -402
- package/lib/commonjs/js-top/engine/patches/bridge.js +1 -60
- package/lib/commonjs/js-top/engine/patches/microtasks.js +1 -27
- package/lib/commonjs/js-top/engine/patches/promises.js +1 -65
- package/lib/commonjs/js-top/engine/patches/timers.js +1 -66
- package/lib/commonjs/js-top/engine/state.js +1 -63
- package/lib/commonjs/js-top/engine/symbolicate.js +1 -95
- package/lib/commonjs/js-top/sync/jsTopSyncAdapter.js +1 -74
- package/lib/commonjs/js-top/types/index.js +1 -46
- package/lib/commonjs/js-top/utils/preferences.js +1 -31
- package/lib/commonjs/preset.js +1 -75
- package/lib/module/index.js +1 -47
- package/lib/module/js-top/components/BusySparkline.js +1 -52
- package/lib/module/js-top/components/JsTopHudOverlay.js +1 -75
- package/lib/module/js-top/components/JsTopModal.js +1 -345
- package/lib/module/js-top/components/TaskDetailView.js +1 -326
- package/lib/module/js-top/components/TaskTable.js +1 -353
- package/lib/module/js-top/engine/JsTopController.js +1 -347
- package/lib/module/js-top/engine/aggregator.js +1 -136
- package/lib/module/js-top/engine/busyProbe.js +1 -94
- package/lib/module/js-top/engine/frameStack.js +1 -126
- package/lib/module/js-top/engine/longtasks.js +1 -133
- package/lib/module/js-top/engine/now.js +1 -33
- package/lib/module/js-top/engine/originRegistry.js +1 -388
- package/lib/module/js-top/engine/patches/bridge.js +1 -56
- package/lib/module/js-top/engine/patches/microtasks.js +1 -23
- package/lib/module/js-top/engine/patches/promises.js +1 -62
- package/lib/module/js-top/engine/patches/timers.js +1 -62
- package/lib/module/js-top/engine/state.js +1 -54
- package/lib/module/js-top/engine/symbolicate.js +1 -91
- package/lib/module/js-top/sync/jsTopSyncAdapter.js +1 -70
- package/lib/module/js-top/types/index.js +1 -42
- package/lib/module/js-top/utils/preferences.js +1 -26
- package/lib/module/preset.js +1 -71
- package/lib/web/index.mjs +2 -0
- package/package.json +39 -5
- package/lib/commonjs/index.js.map +0 -1
- package/lib/commonjs/js-top/components/BusySparkline.js.map +0 -1
- package/lib/commonjs/js-top/components/JsTopHudOverlay.js.map +0 -1
- package/lib/commonjs/js-top/components/JsTopModal.js.map +0 -1
- package/lib/commonjs/js-top/components/TaskDetailView.js.map +0 -1
- package/lib/commonjs/js-top/components/TaskTable.js.map +0 -1
- package/lib/commonjs/js-top/engine/JsTopController.js.map +0 -1
- package/lib/commonjs/js-top/engine/aggregator.js.map +0 -1
- package/lib/commonjs/js-top/engine/busyProbe.js.map +0 -1
- package/lib/commonjs/js-top/engine/frameStack.js.map +0 -1
- package/lib/commonjs/js-top/engine/longtasks.js.map +0 -1
- package/lib/commonjs/js-top/engine/now.js.map +0 -1
- package/lib/commonjs/js-top/engine/originRegistry.js.map +0 -1
- package/lib/commonjs/js-top/engine/patches/bridge.js.map +0 -1
- package/lib/commonjs/js-top/engine/patches/microtasks.js.map +0 -1
- package/lib/commonjs/js-top/engine/patches/promises.js.map +0 -1
- package/lib/commonjs/js-top/engine/patches/timers.js.map +0 -1
- package/lib/commonjs/js-top/engine/state.js.map +0 -1
- package/lib/commonjs/js-top/engine/symbolicate.js.map +0 -1
- package/lib/commonjs/js-top/sync/jsTopSyncAdapter.js.map +0 -1
- package/lib/commonjs/js-top/types/index.js.map +0 -1
- package/lib/commonjs/js-top/utils/preferences.js.map +0 -1
- package/lib/commonjs/preset.js.map +0 -1
- package/lib/module/index.js.map +0 -1
- package/lib/module/js-top/components/BusySparkline.js.map +0 -1
- package/lib/module/js-top/components/JsTopHudOverlay.js.map +0 -1
- package/lib/module/js-top/components/JsTopModal.js.map +0 -1
- package/lib/module/js-top/components/TaskDetailView.js.map +0 -1
- package/lib/module/js-top/components/TaskTable.js.map +0 -1
- package/lib/module/js-top/engine/JsTopController.js.map +0 -1
- package/lib/module/js-top/engine/aggregator.js.map +0 -1
- package/lib/module/js-top/engine/busyProbe.js.map +0 -1
- package/lib/module/js-top/engine/frameStack.js.map +0 -1
- package/lib/module/js-top/engine/longtasks.js.map +0 -1
- package/lib/module/js-top/engine/now.js.map +0 -1
- package/lib/module/js-top/engine/originRegistry.js.map +0 -1
- package/lib/module/js-top/engine/patches/bridge.js.map +0 -1
- package/lib/module/js-top/engine/patches/microtasks.js.map +0 -1
- package/lib/module/js-top/engine/patches/promises.js.map +0 -1
- package/lib/module/js-top/engine/patches/timers.js.map +0 -1
- package/lib/module/js-top/engine/state.js.map +0 -1
- package/lib/module/js-top/engine/symbolicate.js.map +0 -1
- package/lib/module/js-top/sync/jsTopSyncAdapter.js.map +0 -1
- package/lib/module/js-top/types/index.js.map +0 -1
- package/lib/module/js-top/utils/preferences.js.map +0 -1
- package/lib/module/preset.js.map +0 -1
- package/lib/typescript/index.d.ts.map +0 -1
- package/lib/typescript/js-top/components/BusySparkline.d.ts.map +0 -1
- package/lib/typescript/js-top/components/JsTopHudOverlay.d.ts.map +0 -1
- package/lib/typescript/js-top/components/JsTopModal.d.ts.map +0 -1
- package/lib/typescript/js-top/components/TaskDetailView.d.ts.map +0 -1
- package/lib/typescript/js-top/components/TaskTable.d.ts.map +0 -1
- package/lib/typescript/js-top/engine/JsTopController.d.ts.map +0 -1
- package/lib/typescript/js-top/engine/aggregator.d.ts.map +0 -1
- package/lib/typescript/js-top/engine/busyProbe.d.ts.map +0 -1
- package/lib/typescript/js-top/engine/frameStack.d.ts.map +0 -1
- package/lib/typescript/js-top/engine/longtasks.d.ts.map +0 -1
- package/lib/typescript/js-top/engine/now.d.ts.map +0 -1
- package/lib/typescript/js-top/engine/originRegistry.d.ts.map +0 -1
- package/lib/typescript/js-top/engine/patches/bridge.d.ts.map +0 -1
- package/lib/typescript/js-top/engine/patches/microtasks.d.ts.map +0 -1
- package/lib/typescript/js-top/engine/patches/promises.d.ts.map +0 -1
- package/lib/typescript/js-top/engine/patches/timers.d.ts.map +0 -1
- package/lib/typescript/js-top/engine/state.d.ts.map +0 -1
- package/lib/typescript/js-top/engine/symbolicate.d.ts.map +0 -1
- package/lib/typescript/js-top/sync/jsTopSyncAdapter.d.ts.map +0 -1
- package/lib/typescript/js-top/types/index.d.ts.map +0 -1
- package/lib/typescript/js-top/utils/preferences.d.ts.map +0 -1
- 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");
|