@deeeed/metamask-harness 0.82.1 → 0.84.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 (73) hide show
  1. package/CHANGELOG.md +48 -0
  2. package/README.md +8 -1
  3. package/adapters/core/cleanup.sh +5 -29
  4. package/adapters/core/controller-import-probe.mjs +26 -0
  5. package/adapters/manifest.json +22 -102
  6. package/adapters/shared/session-lock.mjs +43 -0
  7. package/adapters/web-dapp/metamask-signer.mjs +212 -0
  8. package/dist/adapters/core/actions.js +0 -3
  9. package/dist/adapters/core/overlay.js +1 -5
  10. package/dist/adapters/core/readiness.js +133 -2
  11. package/dist/adapters/core/surface.js +53 -41
  12. package/dist/adapters/extension/extension-id.js +1 -0
  13. package/dist/adapters/extension/fixtures.js +60 -1
  14. package/dist/adapters/extension/network-observer.js +19 -206
  15. package/dist/adapters/extension/run.js +5 -3
  16. package/dist/adapters/extension/runtime.js +112 -20
  17. package/dist/adapters/mobile/android-gfxinfo-performance.js +3 -1
  18. package/dist/adapters/mobile/devices.js +5 -1
  19. package/dist/adapters/mobile/performance-observer.js +9 -6
  20. package/dist/adapters/mobile/run.js +1 -1
  21. package/dist/adapters/mobile/surface.js +1 -2
  22. package/dist/adapters/performance/trace-kinds.js +36 -0
  23. package/dist/adapters/plugin-surface.js +34 -34
  24. package/dist/adapters/session-lock.js +23 -0
  25. package/dist/adapters/web-dapp/console-capture.js +2 -20
  26. package/dist/adapters/web-dapp/run.js +31 -25
  27. package/dist/adapters/web-dapp/surface.js +32 -108
  28. package/dist/command-contract.js +1363 -630
  29. package/dist/commands/core-readiness.js +1 -9
  30. package/dist/commands/doctor.js +1 -1
  31. package/dist/commands/launch/device.js +1 -1
  32. package/dist/doctor.js +5 -2
  33. package/dist/mm-harness-cli.js +228 -1210
  34. package/dist/performance-observation.js +28 -5
  35. package/docs/CONTRIBUTING.md +6 -4
  36. package/docs/cli-contract.md +1 -1
  37. package/docs/feature-guard.md +1 -1
  38. package/library/actions/core/perps/_controller.mjs +10 -0
  39. package/library/actions/extension/wallet/import.mjs +21 -4
  40. package/library/actions/extension/wallet/visible-session.mjs +32 -12
  41. package/library/actions/shared/console-collector.mjs +1 -1
  42. package/library/actions/web-dapp/app/launch.mjs +1 -1
  43. package/library/actions/web-dapp/platform/adapter-web-lib.mjs +18 -0
  44. package/library/actions/web-dapp/platform/hud.mjs +1 -1
  45. package/library/actions/web-dapp/platform/page.mjs +2 -3
  46. package/library/actions/web-dapp/platform/runtime.mjs +29 -178
  47. package/package.json +7 -6
  48. package/scripts/completions.sh +1 -1
  49. package/adapters/web-dapp/cleanup.mjs +0 -69
  50. package/adapters/web-dapp/inject.mjs +0 -75
  51. package/adapters/web-dapp/launch.mjs +0 -631
  52. package/adapters/web-dapp/lib/blocked-hosts.mjs +0 -24
  53. package/adapters/web-dapp/lib/cdp-client.mjs +0 -84
  54. package/adapters/web-dapp/lib/display.mjs +0 -115
  55. package/adapters/web-dapp/lib/hud.mjs +0 -148
  56. package/adapters/web-dapp/lib/policy.mjs +0 -79
  57. package/adapters/web-dapp/lib/processes.mjs +0 -187
  58. package/adapters/web-dapp/lib/readiness.mjs +0 -272
  59. package/adapters/web-dapp/stop.mjs +0 -38
  60. package/adapters/web-dapp/verify.mjs +0 -51
  61. package/adapters/web-dapp/wallet-host.mjs +0 -520
  62. package/dist/adapters/core/live-script-env.js +0 -36
  63. package/dist/adapters/extension/browser-cdp.js +0 -174
  64. package/dist/adapters/extension/performance-observer.js +0 -75
  65. package/dist/adapters/performance/cdp-trace.js +0 -406
  66. package/dist/adapters/performance/js-task-metrics.js +0 -35
  67. package/dist/adapters/web-dapp/doctor-checks.js +0 -33
  68. package/dist/adapters/web-dapp/launch.js +0 -87
  69. package/dist/adapters/web-dapp/overlay.js +0 -21
  70. package/dist/adapters/web-dapp/readiness.js +0 -10
  71. package/dist/adapters/web-dapp/stop.js +0 -20
  72. package/dist/async.js +0 -19
  73. package/dist/cli.js +0 -202
@@ -1,174 +0,0 @@
1
- import { WebSocket } from "ws";
2
- import { withTimeout } from "../../async.js";
3
- const DEFAULT_TIMEOUT_MS = 1e4;
4
- async function createBrowserCdpClient(cdpPort) {
5
- if (!Number.isInteger(cdpPort) || cdpPort <= 0) {
6
- throw new Error("Extension CDP requires a valid port.");
7
- }
8
- const version = asRecord(
9
- await fetchJson(`http://127.0.0.1:${cdpPort}/json/version`)
10
- );
11
- const webSocketUrl = String(version.webSocketDebuggerUrl ?? "");
12
- if (!webSocketUrl) {
13
- throw new Error("Extension browser CDP WebSocket is unavailable.");
14
- }
15
- const socket = new WebSocket(webSocketUrl);
16
- await waitForOpen(socket);
17
- let nextId = 0;
18
- const pending = /* @__PURE__ */ new Map();
19
- const handlers = /* @__PURE__ */ new Set();
20
- const closeHandlers = /* @__PURE__ */ new Set();
21
- socket.on("message", (data) => {
22
- let message;
23
- try {
24
- message = JSON.parse(String(data));
25
- } catch {
26
- return;
27
- }
28
- if (message.id && pending.has(message.id)) {
29
- const command = pending.get(message.id);
30
- pending.delete(message.id);
31
- if (!command) return;
32
- clearTimeout(command.timer);
33
- if (message.error) {
34
- command.reject(
35
- new Error(
36
- String(message.error.message ?? "Extension CDP command failed")
37
- )
38
- );
39
- } else {
40
- command.resolve(message.result ?? {});
41
- }
42
- return;
43
- }
44
- for (const handler of handlers) handler(message);
45
- });
46
- socket.on("close", () => {
47
- for (const command of pending.values()) {
48
- clearTimeout(command.timer);
49
- command.reject(new Error("Extension browser CDP connection closed"));
50
- }
51
- pending.clear();
52
- handlers.clear();
53
- for (const handler of closeHandlers) handler();
54
- closeHandlers.clear();
55
- });
56
- return {
57
- send(method, params = {}, sessionId, timeoutMs = DEFAULT_TIMEOUT_MS) {
58
- return new Promise((resolve, reject) => {
59
- const id = ++nextId;
60
- const timer = setTimeout(() => {
61
- pending.delete(id);
62
- reject(new Error(`Extension CDP command timed out: ${method}`));
63
- }, timeoutMs);
64
- pending.set(id, { resolve, reject, timer });
65
- socket.send(
66
- JSON.stringify({
67
- id,
68
- method,
69
- params,
70
- ...sessionId ? { sessionId } : {}
71
- })
72
- );
73
- });
74
- },
75
- onMessage(handler) {
76
- handlers.add(handler);
77
- return () => handlers.delete(handler);
78
- },
79
- onClose(handler) {
80
- closeHandlers.add(handler);
81
- return () => closeHandlers.delete(handler);
82
- },
83
- close() {
84
- socket.close();
85
- }
86
- };
87
- }
88
- async function fetchCdpJson(cdpPort, path = "/json") {
89
- return fetchJson(`http://127.0.0.1:${cdpPort}${path}`);
90
- }
91
- function asBrowserCdpTarget(value) {
92
- const target = asRecord(value);
93
- const targetId = String(target.targetId ?? target.id ?? "");
94
- const type = String(target.type ?? "");
95
- const url = String(target.url ?? "");
96
- return targetId && type && url ? { targetId, type, url } : null;
97
- }
98
- function extensionIdFromCdpTargets(value) {
99
- const targets = Array.isArray(value) ? value : [];
100
- for (const candidate of targets) {
101
- const target = asBrowserCdpTarget(candidate);
102
- if (!target) continue;
103
- try {
104
- const url = new URL(target.url);
105
- if (url.protocol === "chrome-extension:" && url.hostname) {
106
- return url.hostname;
107
- }
108
- } catch {
109
- continue;
110
- }
111
- }
112
- return null;
113
- }
114
- function selectExtensionUiTarget(value, extensionId) {
115
- const targets = Array.isArray(value) ? value : [];
116
- const candidates = targets.map(asBrowserCdpTarget).filter((candidate) => {
117
- if (!candidate || !["page", "other"].includes(candidate.type)) return false;
118
- try {
119
- const url = new URL(candidate.url);
120
- return url.protocol === "chrome-extension:" && url.hostname === extensionId;
121
- } catch {
122
- return false;
123
- }
124
- });
125
- const matchingPath = (target, pathname) => {
126
- try {
127
- return new URL(target.url).pathname === pathname;
128
- } catch {
129
- return false;
130
- }
131
- };
132
- const home = candidates.filter((target) => matchingPath(target, "/home.html"));
133
- if (home.length > 0) return home.length === 1 ? home[0] : null;
134
- const sidepanel = candidates.filter(
135
- (target) => matchingPath(target, "/sidepanel.html")
136
- );
137
- return sidepanel.length === 1 ? sidepanel[0] : null;
138
- }
139
- async function fetchJson(url) {
140
- const response = await fetch(url);
141
- if (!response.ok) {
142
- throw new Error(`Extension CDP returned HTTP ${response.status}.`);
143
- }
144
- return response.json();
145
- }
146
- function waitForOpen(socket) {
147
- const onOpen = (resolve) => () => resolve();
148
- let openHandler;
149
- let errorHandler;
150
- const opened = new Promise((resolve, reject) => {
151
- openHandler = onOpen(resolve);
152
- errorHandler = reject;
153
- socket.once("open", openHandler);
154
- socket.once("error", errorHandler);
155
- });
156
- return withTimeout(opened, {
157
- message: "Extension browser CDP connection timed out.",
158
- timeoutMs: DEFAULT_TIMEOUT_MS,
159
- onTimeout: () => socket.close()
160
- }).finally(() => {
161
- if (openHandler) socket.off("open", openHandler);
162
- if (errorHandler) socket.off("error", errorHandler);
163
- });
164
- }
165
- function asRecord(value) {
166
- return value && typeof value === "object" && !Array.isArray(value) ? value : {};
167
- }
168
- export {
169
- asBrowserCdpTarget,
170
- createBrowserCdpClient,
171
- extensionIdFromCdpTargets,
172
- fetchCdpJson,
173
- selectExtensionUiTarget
174
- };
@@ -1,75 +0,0 @@
1
- import {
2
- createCdpTraceCollector
3
- } from "../performance/cdp-trace.js";
4
- import {
5
- createBrowserCdpClient,
6
- fetchCdpJson,
7
- selectExtensionUiTarget
8
- } from "./browser-cdp.js";
9
- import { resolveExtensionId } from "./extension-id.js";
10
- async function createExtensionPerformanceBackend(cdpPort, target) {
11
- const extensionId = (await resolveExtensionId(target, { cdpPort })).extensionId;
12
- if (!extensionId) {
13
- throw new Error("Extension performance observation could not resolve the MetaMask extension id.");
14
- }
15
- const uiTarget = selectExtensionUiTarget(
16
- await fetchCdpJson(cdpPort),
17
- extensionId
18
- );
19
- if (!uiTarget) {
20
- throw new Error("Extension performance observation requires an open MetaMask UI target.");
21
- }
22
- const browser = await createBrowserCdpClient(cdpPort);
23
- const attached = asRecord(
24
- await browser.send(
25
- "Target.attachToTarget",
26
- { targetId: uiTarget.targetId, flatten: true },
27
- void 0,
28
- 1e4
29
- )
30
- );
31
- const sessionId = String(attached.sessionId ?? "");
32
- if (!sessionId) {
33
- browser.close();
34
- throw new Error("Extension performance observation could not attach to the MetaMask UI target.");
35
- }
36
- const client = browserTraceClient(browser);
37
- return createCdpTraceCollector(client, "extension", async (name) => {
38
- const before = Date.now();
39
- await browser.send(
40
- "Runtime.evaluate",
41
- {
42
- expression: `performance.mark(${JSON.stringify(name)})`,
43
- returnByValue: true
44
- },
45
- sessionId,
46
- 1e4
47
- );
48
- const after = Date.now();
49
- return {
50
- hostEpochMs: (before + after) / 2,
51
- uncertaintyMs: (after - before) / 2
52
- };
53
- });
54
- }
55
- function browserTraceClient(browser) {
56
- return {
57
- send(method, params = {}, timeoutMs) {
58
- return browser.send(method, params, void 0, timeoutMs);
59
- },
60
- on(method, handler) {
61
- return browser.onMessage((message) => {
62
- if (message.method === method) handler(message.params ?? {});
63
- });
64
- },
65
- close() {
66
- browser.close();
67
- }
68
- };
69
- }
70
- function asRecord(value) {
71
- return value && typeof value === "object" && !Array.isArray(value) ? value : {};
72
- }
73
- export {
74
- createExtensionPerformanceBackend
75
- };
@@ -1,406 +0,0 @@
1
- import {
2
- summarizeFrames
3
- } from "@farmslot/adapter-rn";
4
- import { withTimeout } from "../../async.js";
5
- import {
6
- summarizeJavaScriptTasks
7
- } from "./js-task-metrics.js";
8
- const MAX_TRACE_BYTES = 16 * 1024 * 1024;
9
- const MAX_TRACE_EVENTS = 25e4;
10
- const MOBILE_TRACE_CATEGORIES = [
11
- "blink.user_timing",
12
- "disabled-by-default-devtools.timeline.frame"
13
- ];
14
- const EXTENSION_TRACE_CATEGORIES = [
15
- "blink.user_timing",
16
- "devtools.timeline",
17
- "disabled-by-default-devtools.timeline",
18
- "disabled-by-default-devtools.timeline.frame",
19
- "v8.execute"
20
- ];
21
- const MOBILE_TRACE_KIND = {
22
- categories: MOBILE_TRACE_CATEGORIES,
23
- rendererScoped: false,
24
- nativeSource: "react-native-cdp-frame-timings",
25
- javascriptTasks: false
26
- };
27
- const TRACE_KINDS = {
28
- extension: {
29
- categories: EXTENSION_TRACE_CATEGORIES,
30
- rendererScoped: true,
31
- nativeSource: "chromium-cdp-frame-timings",
32
- javascriptTasks: true
33
- },
34
- ios: MOBILE_TRACE_KIND,
35
- android: MOBILE_TRACE_KIND
36
- };
37
- function createCdpTraceCollector(client, platform, marker) {
38
- let active;
39
- const offData = client.on("Tracing.dataCollected", (params) => {
40
- if (!active || !Array.isArray(params.value)) return;
41
- for (const value of params.value) {
42
- if (!value || typeof value !== "object" || Array.isArray(value)) continue;
43
- const event = value;
44
- const bytes = Buffer.byteLength(JSON.stringify(event));
45
- if (active.events.length >= MAX_TRACE_EVENTS || active.bytes + bytes > MAX_TRACE_BYTES) {
46
- active.overflow = true;
47
- continue;
48
- }
49
- active.events.push(event);
50
- active.bytes += bytes;
51
- }
52
- });
53
- const offComplete = client.on("Tracing.tracingComplete", (params) => {
54
- if (!active) return;
55
- active.dataLossOccurred = params.dataLossOccurred === true;
56
- active.complete?.();
57
- });
58
- return {
59
- async start(id) {
60
- if (active) throw new Error("Only one CDP performance trace may be active.");
61
- let complete;
62
- const completePromise = new Promise((resolve) => {
63
- complete = resolve;
64
- });
65
- active = {
66
- id,
67
- events: [],
68
- bytes: 0,
69
- overflow: false,
70
- dataLossOccurred: false,
71
- complete,
72
- completePromise
73
- };
74
- let tracingStarted = false;
75
- try {
76
- await client.send(
77
- "Tracing.start",
78
- {
79
- categories: TRACE_KINDS[platform].categories.join(","),
80
- transferMode: "ReportEvents"
81
- },
82
- 15e3
83
- );
84
- tracingStarted = true;
85
- const markerName = `mmh-clock-${id}-${Date.now()}`;
86
- const sync = await marker(markerName);
87
- active.markerName = markerName;
88
- active.markerHostEpochMs = sync.hostEpochMs;
89
- active.markerUncertaintyMs = sync.uncertaintyMs;
90
- } catch (error) {
91
- if (tracingStarted) {
92
- await client.send("Tracing.end", {}, 5e3).catch(() => void 0);
93
- await withTimeout(active.completePromise, {
94
- message: "CDP tracing cleanup did not report tracingComplete.",
95
- timeoutMs: 1e3
96
- }).catch(() => void 0);
97
- }
98
- active = void 0;
99
- throw error;
100
- }
101
- },
102
- async end(id) {
103
- const capture = active;
104
- if (!capture || capture.id !== id) {
105
- throw new Error(`Performance capture is not active: ${id}`);
106
- }
107
- try {
108
- await client.send("Tracing.end", {}, 15e3);
109
- await withTimeout(capture.completePromise, {
110
- message: "CDP tracing did not report tracingComplete.",
111
- timeoutMs: 3e4
112
- });
113
- } finally {
114
- active = void 0;
115
- }
116
- return parseTraceCapture({
117
- platform,
118
- events: capture.events,
119
- markerName: capture.markerName,
120
- markerHostEpochMs: capture.markerHostEpochMs,
121
- markerUncertaintyMs: capture.markerUncertaintyMs,
122
- dataLossOccurred: capture.dataLossOccurred,
123
- overflow: capture.overflow,
124
- unavailableReasons: [],
125
- coverageGapReasons: []
126
- });
127
- },
128
- async close() {
129
- if (active) {
130
- await client.send("Tracing.end", {}, 5e3).catch(() => void 0);
131
- await withTimeout(active.completePromise, {
132
- message: "CDP tracing cleanup did not report tracingComplete.",
133
- timeoutMs: 1e3
134
- }).catch(() => void 0);
135
- active = void 0;
136
- }
137
- offData();
138
- offComplete();
139
- client.close();
140
- }
141
- };
142
- }
143
- function parseTraceCapture(capture) {
144
- const marker = findClockMarker(capture.events, capture.markerName);
145
- const kind = TRACE_KINDS[capture.platform];
146
- if (kind.rendererScoped && !finite(marker?.pid)) {
147
- const reason = "Extension trace marker did not identify a renderer process.";
148
- return {
149
- platform: capture.platform,
150
- javascript: unavailableJavaScriptSource("cdp-js-runtime-tasks", reason),
151
- nativeUi: unavailableNativeUiSource("cdp-native-frame-timings", reason),
152
- trace: traceEvidence(capture, "unresolved")
153
- };
154
- }
155
- const scopedEvents = kind.rendererScoped && finite(marker?.pid) ? capture.events.filter((event) => event.pid === marker.pid) : capture.events;
156
- const scope = !kind.rendererScoped ? "mobile-host" : finite(marker?.pid) ? "extension-renderer" : "unresolved";
157
- const trace = traceEvidence(
158
- { ...capture, events: scopedEvents },
159
- scope,
160
- kind.rendererScoped && finite(marker?.pid) ? marker.pid : void 0
161
- );
162
- if (!finite(marker?.ts) || capture.markerHostEpochMs === void 0 || capture.markerUncertaintyMs === void 0) {
163
- const reason = "CDP trace clock marker was unavailable.";
164
- return {
165
- platform: capture.platform,
166
- javascript: unavailableJavaScriptSource("cdp-js-runtime-tasks", reason),
167
- nativeUi: unavailableNativeUiSource(
168
- "cdp-native-frame-timings",
169
- reason
170
- ),
171
- trace
172
- };
173
- }
174
- const traceToEpochMs = (timestampUs) => capture.markerHostEpochMs + (timestampUs - marker.ts) / 1e3;
175
- const gapReasons = [
176
- ...capture.coverageGapReasons,
177
- ...capture.overflow ? ["CDP trace retention limit was reached."] : [],
178
- ...capture.dataLossOccurred ? ["CDP reported trace data loss."] : []
179
- ];
180
- const nativeSamples = nativeFrameSamples(
181
- scopedEvents,
182
- capture.platform,
183
- traceToEpochMs
184
- );
185
- const javascriptSamples = javascriptTaskSamples(
186
- scopedEvents,
187
- traceToEpochMs
188
- );
189
- const status = capture.overflow || capture.dataLossOccurred ? "partial" : "complete";
190
- return {
191
- platform: capture.platform,
192
- javascript: javascriptSourceResult(
193
- "cdp-js-runtime-tasks",
194
- javascriptSamples,
195
- status,
196
- gapReasons,
197
- traceEventNames(scopedEvents),
198
- capture.markerUncertaintyMs
199
- ),
200
- nativeUi: nativeUiSourceResult(
201
- kind.nativeSource,
202
- nativeSamples,
203
- status,
204
- gapReasons,
205
- traceEventNames(scopedEvents),
206
- capture.markerUncertaintyMs
207
- ),
208
- trace,
209
- rawTraceEvents: normalizeTraceTimestamps(scopedEvents, traceToEpochMs)
210
- };
211
- }
212
- function normalizeTraceTimestamps(events, traceToEpochMs) {
213
- return events.map(
214
- (event) => finite(event.ts) ? { ...event, ts: traceToEpochMs(event.ts) * 1e3 } : { ...event }
215
- );
216
- }
217
- function traceEvidence(capture, scope, rendererProcessId) {
218
- const counts = /* @__PURE__ */ new Map();
219
- for (const event of capture.events) {
220
- const name = String(event.name ?? "");
221
- counts.set(name, (counts.get(name) ?? 0) + 1);
222
- }
223
- return {
224
- beginFrameCount: counts.get("BeginFrame") ?? 0,
225
- dataLossOccurred: capture.dataLossOccurred,
226
- drawFrameCount: counts.get("DrawFrame") ?? 0,
227
- overflow: capture.overflow,
228
- profileChunkCount: counts.get("ProfileChunk") ?? 0,
229
- ...rendererProcessId === void 0 ? {} : { rendererProcessId },
230
- runTaskCount: counts.get("RunTask") ?? 0,
231
- scope,
232
- totalEventCount: capture.events.length
233
- };
234
- }
235
- function nativeFrameSamples(events, platform, traceToEpochMs) {
236
- const begins = /* @__PURE__ */ new Map();
237
- const frames = [];
238
- for (const event of events) {
239
- if (event.name !== "BeginFrame" && event.name !== "DrawFrame") continue;
240
- const sequence = frameSequence(event);
241
- if (!sequence || !finite(event.ts)) continue;
242
- if (event.name === "BeginFrame") {
243
- begins.set(sequence, event);
244
- continue;
245
- }
246
- const begin = begins.get(sequence);
247
- if (!begin || !finite(begin.ts)) continue;
248
- frames.push({ beginUs: begin.ts, endUs: event.ts });
249
- }
250
- frames.sort((first, second) => first.beginUs - second.beginUs);
251
- const frameBudgetMs = estimateFrameBudgetMs(frames);
252
- if (platform === "ios") {
253
- const samples = [];
254
- for (let index = 1; index < frames.length; index += 1) {
255
- const previous = frames[index - 1];
256
- const current = frames[index];
257
- if (!previous || !current) continue;
258
- const durationMs = (current.beginUs - previous.beginUs) / 1e3;
259
- if (durationMs <= 0) continue;
260
- samples.push({
261
- cadenceInterval: true,
262
- completedAtEpochMs: traceToEpochMs(current.beginUs),
263
- durationMs,
264
- ...frameBudgetMs ? {
265
- frameBudgetMs,
266
- overBudget: durationMs > frameBudgetMs * 1.01
267
- } : {},
268
- startedAtEpochMs: traceToEpochMs(previous.beginUs)
269
- });
270
- }
271
- return samples;
272
- }
273
- return frames.map((frame) => {
274
- const durationMs = (frame.endUs - frame.beginUs) / 1e3;
275
- return {
276
- completedAtEpochMs: traceToEpochMs(frame.endUs),
277
- durationMs,
278
- ...frameBudgetMs ? {
279
- frameBudgetMs,
280
- overBudget: durationMs > frameBudgetMs * 1.01
281
- } : {},
282
- startedAtEpochMs: traceToEpochMs(frame.beginUs)
283
- };
284
- }).filter((sample) => sample.durationMs > 0);
285
- }
286
- function estimateFrameBudgetMs(frames) {
287
- const cadenceMs = [];
288
- for (let index = 1; index < frames.length; index += 1) {
289
- const previous = frames[index - 1];
290
- const current = frames[index];
291
- if (!previous || !current) continue;
292
- const intervalMs = (current.beginUs - previous.beginUs) / 1e3;
293
- if (intervalMs >= 3.5 && intervalMs <= 40) cadenceMs.push(intervalMs);
294
- }
295
- if (cadenceMs.length < 5) return void 0;
296
- cadenceMs.sort((first, second) => first - second);
297
- const observedBudgetMs = percentile(cadenceMs, 0.1);
298
- const refreshRateHz = Math.round(1e3 / observedBudgetMs);
299
- return refreshRateHz >= 24 && refreshRateHz <= 240 ? 1e3 / refreshRateHz : void 0;
300
- }
301
- function percentile(sorted, quantile) {
302
- if (sorted.length === 1) return sorted[0] ?? 0;
303
- const position = (sorted.length - 1) * quantile;
304
- const lower = Math.floor(position);
305
- const upper = Math.ceil(position);
306
- const lowerValue = sorted[lower] ?? 0;
307
- const upperValue = sorted[upper] ?? lowerValue;
308
- return lowerValue + (upperValue - lowerValue) * (position - lower);
309
- }
310
- function javascriptTaskSamples(events, traceToEpochMs) {
311
- return events.filter(
312
- (event) => event.name === "RunTask" && event.ph === "X" && finite(event.ts) && finite(event.dur) && event.dur > 0
313
- ).map((event) => ({
314
- completedAtEpochMs: traceToEpochMs(event.ts + event.dur),
315
- durationMs: event.dur / 1e3
316
- }));
317
- }
318
- function findClockMarker(events, markerName) {
319
- if (!markerName) return void 0;
320
- return events.find(
321
- (candidate) => candidate.name === markerName || asRecord(candidate.args).sync_id === markerName || asRecord(asRecord(candidate.args).data).sync_id === markerName
322
- );
323
- }
324
- function frameSequence(event) {
325
- const args = asRecord(event.args);
326
- const value = args.frameSeqId ?? asRecord(args.data).frameSeqId;
327
- return value === void 0 ? void 0 : String(value);
328
- }
329
- function javascriptSourceResult(kind, samples, status, coverageGapReasons, observedEventNames, clockSyncUncertaintyMs) {
330
- return {
331
- applicability: "applicable",
332
- status: samples.length > 0 ? status : "partial",
333
- kind,
334
- unavailableReasons: [],
335
- coverageGapReasons: [
336
- ...coverageGapReasons,
337
- ...samples.length > 0 ? [] : [
338
- `No ${kind} samples were recorded. Observed trace events: ${observedEventNames.join(", ") || "none"}.`
339
- ]
340
- ],
341
- samples,
342
- summary: summarizeJavaScriptTasks(samples),
343
- clockSyncUncertaintyMs
344
- };
345
- }
346
- function nativeUiSourceResult(kind, samples, status, coverageGapReasons, observedEventNames, clockSyncUncertaintyMs) {
347
- const summary = summarizeFrames(samples);
348
- const hasUnclassifiedCadenceGap = (summary.cadenceGapCount ?? 0) > 0;
349
- return {
350
- status: samples.length === 0 ? "partial" : hasUnclassifiedCadenceGap ? "partial" : status,
351
- kind,
352
- unavailableReasons: [],
353
- coverageGapReasons: [
354
- ...coverageGapReasons,
355
- ...samples.length > 0 ? [] : [
356
- `No ${kind} samples were recorded. Observed trace events: ${observedEventNames.join(", ") || "none"}.`
357
- ],
358
- ...hasUnclassifiedCadenceGap ? [
359
- "Unclassified native cadence gap prevents a complete FPS result."
360
- ] : []
361
- ],
362
- samples,
363
- summary,
364
- clockSyncUncertaintyMs
365
- };
366
- }
367
- function traceEventNames(events) {
368
- const counts = /* @__PURE__ */ new Map();
369
- for (const event of events) {
370
- const name = String(event.name ?? "unknown");
371
- counts.set(name, (counts.get(name) ?? 0) + 1);
372
- }
373
- return [...counts.entries()].sort((first, second) => second[1] - first[1]).slice(0, 20).map(([name, count]) => `${name}(${count})`);
374
- }
375
- function unavailableJavaScriptSource(kind, reason) {
376
- return {
377
- applicability: "applicable",
378
- status: "unavailable",
379
- kind,
380
- unavailableReasons: [reason],
381
- coverageGapReasons: [],
382
- samples: [],
383
- summary: { taskCount: 0 }
384
- };
385
- }
386
- function unavailableNativeUiSource(kind, reason) {
387
- return {
388
- status: "unavailable",
389
- kind,
390
- unavailableReasons: [reason],
391
- coverageGapReasons: [],
392
- samples: [],
393
- summary: { frameCount: 0 }
394
- };
395
- }
396
- function finite(value) {
397
- return typeof value === "number" && Number.isFinite(value);
398
- }
399
- function asRecord(value) {
400
- return value && typeof value === "object" && !Array.isArray(value) ? value : {};
401
- }
402
- export {
403
- TRACE_KINDS,
404
- createCdpTraceCollector,
405
- parseTraceCapture
406
- };
@@ -1,35 +0,0 @@
1
- const LONG_TASK_MS = 50;
2
- function summarizeJavaScriptTasks(samples) {
3
- const durations = samples.map((sample) => sample.durationMs).filter((duration) => Number.isFinite(duration) && duration > 0).sort((first, second) => first - second);
4
- if (durations.length === 0) return { taskCount: 0 };
5
- const longTaskCount = durations.filter(
6
- (duration) => duration >= LONG_TASK_MS
7
- ).length;
8
- return {
9
- longestTaskMs: round(durations.at(-1) ?? 0),
10
- longTaskCount,
11
- longTaskPercent: round(longTaskCount / durations.length * 100),
12
- p50TaskMs: round(percentile(durations, 0.5)),
13
- p95TaskMs: round(percentile(durations, 0.95)),
14
- p99TaskMs: round(percentile(durations, 0.99)),
15
- taskCount: durations.length,
16
- totalTaskTimeMs: round(
17
- durations.reduce((total, duration) => total + duration, 0)
18
- )
19
- };
20
- }
21
- function percentile(sorted, quantile) {
22
- if (sorted.length === 1) return sorted[0] ?? 0;
23
- const position = (sorted.length - 1) * quantile;
24
- const lower = Math.floor(position);
25
- const upper = Math.ceil(position);
26
- const lowerValue = sorted[lower] ?? 0;
27
- const upperValue = sorted[upper] ?? lowerValue;
28
- return lowerValue + (upperValue - lowerValue) * (position - lower);
29
- }
30
- function round(value) {
31
- return Math.round(value * 1e3) / 1e3;
32
- }
33
- export {
34
- summarizeJavaScriptTasks
35
- };