catalyst-core 0.2.0-beta.3 → 0.3.0-beta.1
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/babel.native.config.cjs +8 -0
- package/bin/catalyst.js +49 -105
- package/changelog.md +8 -0
- package/dist/caching.js +1 -1
- package/dist/index.jsx +8 -0
- package/dist/logger.js +74 -3
- package/dist/native/androidSetup.js +300 -8
- package/dist/native/bridge/WebBridge.js +481 -59
- package/dist/native/bridge/constants/NativeInterfaces.js +290 -45
- package/dist/native/bridge/errors.js +250 -26
- package/dist/native/bridge/hooks/index.js +125 -1
- package/dist/native/bridge/hooks/useCamera.js +347 -9
- package/dist/native/bridge/hooks/useDataProtection.js +200 -2
- package/dist/native/bridge/hooks/useDeviceInfo.js +71 -2
- package/dist/native/bridge/hooks/useFilePicker.js +429 -2
- package/dist/native/bridge/hooks/useGoogleSignIn.js +81 -1
- package/dist/native/bridge/hooks/useHapticFeedback.js +196 -1
- package/dist/native/bridge/hooks/useIntent.js +95 -1
- package/dist/native/bridge/hooks/useNativeTransition.js +202 -27
- package/dist/native/bridge/hooks/useNetworkStatus.js +84 -3
- package/dist/native/bridge/hooks/useNotification.js +318 -2
- package/dist/native/bridge/hooks/useSafeArea.js +84 -2
- package/dist/native/bridge/hooks/useVideoStream.js +434 -6
- package/dist/native/bridge/hooks/utils.js +27 -1
- package/dist/native/bridge/hooks.js +16 -1
- package/dist/native/bridge/perf/collectors/ApiCollector.js +66 -3
- package/dist/native/bridge/perf/collectors/CacheCollector.js +186 -11
- package/dist/native/bridge/perf/collectors/HookCollector.js +141 -19
- package/dist/native/bridge/perf/collectors/InsightsCollector.js +481 -23
- package/dist/native/bridge/perf/collectors/InteractionCollector.js +245 -10
- package/dist/native/bridge/perf/collectors/KeyboardCollector.js +107 -8
- package/dist/native/bridge/perf/collectors/NavigationCollector.js +273 -32
- package/dist/native/bridge/perf/collectors/NetworkTimingCollector.js +178 -2
- package/dist/native/bridge/perf/collectors/RenderCollector.js +258 -14
- package/dist/native/bridge/perf/collectors/ScrollCollector.js +95 -9
- package/dist/native/bridge/perf/collectors/ThreadCollector.js +90 -6
- package/dist/native/bridge/perf/core/constants.js +103 -20
- package/dist/native/bridge/perf/core/measure.js +179 -30
- package/dist/native/bridge/perf/core/session.js +89 -23
- package/dist/native/bridge/perf/core/store.js +554 -1
- package/dist/native/bridge/perf/core/summary.js +174 -1
- package/dist/native/bridge/perf/core/traceExport.js +240 -1
- package/dist/native/bridge/perf/core/waterfalls.js +146 -1
- package/dist/native/bridge/perf/index.js +359 -38
- package/dist/native/bridge/safeArea.js +53 -5
- package/dist/native/bridge/standardizedHooks.js +595 -82
- package/dist/native/bridge/useBaseHook.js +274 -38
- package/dist/native/bridge/utils/CameraUtils.js +201 -56
- package/dist/native/bridge/utils/FileObjectConverter.js +129 -18
- package/dist/native/bridge/utils/NativeBridge.js +425 -141
- package/dist/native/buildAndroid/assets.js +510 -13
- package/dist/native/buildAndroid/build.js +296 -19
- package/dist/native/buildAndroid/config.js +87 -1
- package/dist/native/buildAndroid/index.js +374 -1
- package/dist/native/buildAppAndroid.js +34 -2
- package/dist/native/buildAppIos.js +62 -2
- package/dist/native/buildIos/assets.js +609 -8
- package/dist/native/buildIos/build.js +639 -20
- package/dist/native/buildIos/config.js +294 -12
- package/dist/native/buildIos/index.js +343 -6
- package/dist/native/buildIos/plugins.js +343 -16
- package/dist/native/internal-plugins/device-info-plugin/android/DeviceInfoPlugin.kt +43 -0
- package/dist/native/internal-plugins/device-info-plugin/ios/DeviceInfoPlugin.swift +28 -0
- package/dist/native/internal-plugins/device-info-plugin/manifest.json +19 -0
- package/dist/native/internalPluginUtils.js +232 -7
- package/dist/native/package.json +3 -0
- package/dist/native/plugin-bridge/PluginBridge.js +227 -1
- package/dist/native/pluginComposerAndroid.js +407 -23
- package/dist/native/pluginComposerIos.js +435 -11
- package/dist/native/renameAndroidProject.js +1094 -88
- package/dist/native/setupEmulatorIos.js +289 -22
- package/dist/native/setupServer.js +160 -19
- package/dist/native/terminalProgress.js +199 -11
- package/dist/native/utils.js +193 -15
- package/dist/offline/catalyst-sw.js +145 -1
- package/dist/offline/registerServiceWorker.js +33 -1
- package/dist/otel.js +375 -46
- package/dist/router/ClientRouter.js +14 -2
- package/dist/router/ServerRouter.js +9 -1
- package/dist/router.js +1 -1
- package/dist/scripts/build.js +112 -3
- package/dist/scripts/checkVersion.js +3 -1
- package/dist/scripts/cleanCache.js +30 -1
- package/dist/scripts/generateOfflineManifest.js +116 -1
- package/dist/scripts/loadEnvironmentVariables.js +32 -6
- package/dist/scripts/plugins.js +309 -1
- package/dist/scripts/preServerInit.js +13 -0
- package/dist/scripts/scriptUtils.js +54 -3
- package/dist/scripts/serve.js +51 -4
- package/dist/scripts/start.js +47 -10
- package/dist/scripts/validator.js +149 -1
- package/dist/sentry.cjs +127 -0
- package/dist/sentry.js +125 -5
- package/dist/server/expressServer.js +250 -5
- package/dist/server/manifestCache.js +36 -0
- package/dist/server/renderer/ChunkExtractor.js +144 -0
- package/dist/server/renderer/document/Body.jsx +59 -0
- package/dist/server/renderer/document/FastRefresh.jsx +20 -0
- package/dist/server/renderer/document/Head.jsx +60 -0
- package/dist/server/renderer/document/index.jsx +2 -0
- package/dist/server/renderer/extract.js +149 -31
- package/dist/server/renderer/handler.jsx +433 -0
- package/dist/server/renderer/index.js +8 -1
- package/dist/server/renderer/render.js +47 -0
- package/dist/server/utils/botDetectionMiddleware.js +29 -0
- package/dist/server/utils/cjsRequire.js +10 -0
- package/dist/server/utils/userAgentUtil.js +87 -9
- package/dist/server/utils/validator.js +164 -1
- package/dist/vite/FastRefresh.jsx +20 -0
- package/dist/vite/inject-cache-key-plugin.js +276 -0
- package/dist/vite/loadCustomViteConfig.js +22 -0
- package/dist/vite/manifest-categorization-plugin.js +455 -0
- package/dist/vite/node-loader.mjs +458 -0
- package/dist/vite/scssParams.js +21 -0
- package/dist/vite/vite.config.client.js +130 -0
- package/dist/vite/vite.config.js +422 -0
- package/dist/vite/vite.config.server.js +84 -0
- package/dist/web-router/components/MetaTag.jsx +34 -0
- package/dist/web-router/components/RouterDataProvider.jsx +332 -0
- package/dist/web-router/components/Split.jsx +200 -0
- package/dist/web-router/components/SplitInview.jsx +106 -0
- package/dist/web-router/components/SsrRequestContext.jsx +14 -0
- package/dist/web-router/context.jsx +12 -0
- package/dist/web-router/hooks.jsx +72 -0
- package/dist/web-router/types/components/MetaTag.d.ts +2 -0
- package/dist/web-router/types/components/RouterDataProvider.d.ts +95 -0
- package/dist/web-router/types/context.d.ts +14 -0
- package/dist/web-router/types/hooks.d.ts +1 -0
- package/dist/web-router/types/index.d.ts +3 -0
- package/dist/web-router/types/utils/metaDataUtils.d.ts +24 -0
- package/dist/web-router/utils/metaDataUtils.jsx +119 -0
- package/mcp_v2/conversion-tasks.json +35 -17
- package/mcp_v2/knowledge-base.json +124 -13
- package/mcp_v2/lib/helpers.js +10 -0
- package/mcp_v2/mcp.js +4 -4
- package/mcp_v2/package.json +1 -0
- package/mcp_v2/tools/build.js +116 -20
- package/mcp_v2/tools/conversion.js +106 -5
- package/mcp_v2/tools/tasks.js +18 -14
- package/package.json +35 -68
- package/babel.config.js +0 -32
- package/dist/index.js +0 -1
- package/dist/scripts/devBuild.js +0 -3
- package/dist/scripts/devServe.js +0 -3
- package/dist/scripts/loadScriptsBeforeServerStarts.js +0 -4
- package/dist/scripts/registerAliases.js +0 -6
- package/dist/server/renderer/document/Body.js +0 -11
- package/dist/server/renderer/document/Head.js +0 -9
- package/dist/server/renderer/document/index.js +0 -1
- package/dist/server/renderer/handler.js +0 -47
- package/dist/server/startServer.js +0 -57
- package/dist/webpack/babel-plugins/remove-client.plugin.js +0 -5
- package/dist/webpack/babel-plugins/remove-ssr.plugin.js +0 -5
- package/dist/webpack/babel.config.client.js +0 -1
- package/dist/webpack/babel.config.ssr.js +0 -1
- package/dist/webpack/base.babel.js +0 -7
- package/dist/webpack/development.client.babel.js +0 -10
- package/dist/webpack/production.client.babel.js +0 -4
- package/dist/webpack/production.ssr.babel.js +0 -8
- package/dist/webpack/scssParams.js +0 -9
|
@@ -1,4 +1,11 @@
|
|
|
1
|
-
"use strict";
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
Object.defineProperty(exports, "__esModule", {
|
|
4
|
+
value: true
|
|
5
|
+
});
|
|
6
|
+
exports.RenderCollector = void 0;
|
|
7
|
+
var _constants = require("../core/constants.js");
|
|
8
|
+
/**
|
|
2
9
|
* RenderCollector.js
|
|
3
10
|
*
|
|
4
11
|
* Instruments: bugs #1, #6, #10, #14, #15, #16
|
|
@@ -25,16 +32,253 @@
|
|
|
25
32
|
* Notifies:
|
|
26
33
|
* ScrollCollector.onLoaf() — so scroll session can increment its loafCount
|
|
27
34
|
* HookCollector.onLoaf() — so hook mount sessions can detect LoAF bursts
|
|
28
|
-
*/
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
this.
|
|
37
|
-
this._loafListeners
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
35
|
+
*/
|
|
36
|
+
|
|
37
|
+
class RenderCollector {
|
|
38
|
+
constructor(measure, getSessionContext, nativeToWeb) {
|
|
39
|
+
this._measure = measure;
|
|
40
|
+
// getSessionContext() returns 'navigation'|'scroll'|'keyboard'|'none'
|
|
41
|
+
// provided by index.js so RenderCollector doesn't import other collectors
|
|
42
|
+
this._getSessionContext = getSessionContext;
|
|
43
|
+
this._nativeToWeb = nativeToWeb ?? (t => t);
|
|
44
|
+
this._loafListeners = []; // [fn] called on every LoAF
|
|
45
|
+
this._getInteraction = null; // set by index.js after construction
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
/** Called by index.js to wire up interaction session context. */
|
|
49
|
+
setInteractionSource(fn) {
|
|
50
|
+
this._getInteraction = fn;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
/** Called by index.js to wire up insights notifications. */
|
|
54
|
+
setInsightsSource(fn) {
|
|
55
|
+
this._notifyInsights = fn;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
/** Called by index.js to feed the normalized perf store. */
|
|
59
|
+
setWaterfallSource(fn) {
|
|
60
|
+
this._notifyWaterfall = fn;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
// Allow other collectors to subscribe to LoAF events
|
|
64
|
+
addLoafListener(fn) {
|
|
65
|
+
this._loafListeners.push(fn);
|
|
66
|
+
}
|
|
67
|
+
init() {
|
|
68
|
+
this._observeLoAF();
|
|
69
|
+
this._observeLayoutShift();
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
// ─── Native MetricsMonitor events ────────────────────────────────────────
|
|
73
|
+
|
|
74
|
+
onNativeEvent(event) {
|
|
75
|
+
const t = this._nativeToWeb(event.nativeTime);
|
|
76
|
+
switch (event.type) {
|
|
77
|
+
case "fps-drop-episode":
|
|
78
|
+
{
|
|
79
|
+
const start = Math.max(0, this._nativeToWeb(event.startNativeTime));
|
|
80
|
+
const end = Math.max(start, this._nativeToWeb(event.endNativeTime));
|
|
81
|
+
const markName = `${_constants.PREFIX.FPS_DROP}:${Math.round(start)}`;
|
|
82
|
+
performance.mark(markName, {
|
|
83
|
+
startTime: start
|
|
84
|
+
});
|
|
85
|
+
const interactionId = this._getInteraction?.()?.interactionIdForRange(start, end) ?? null;
|
|
86
|
+
if (interactionId) this._getInteraction?.()?.onFpsDrop();
|
|
87
|
+
this._measure.emit(`${interactionId ? `[${interactionId}] ` : ""}FPS drop: ${event.minFps.toFixed(1)}fps - ${Math.round(event.durationMs)}ms`, markName, end, {
|
|
88
|
+
interactionId,
|
|
89
|
+
minFps: event.minFps,
|
|
90
|
+
avgFps: event.avgFps,
|
|
91
|
+
durationMs: event.durationMs,
|
|
92
|
+
sessionContext: this._getSessionContext(),
|
|
93
|
+
thread: event.thread ?? null,
|
|
94
|
+
simulatorValid: false
|
|
95
|
+
}, _constants.TRACK.RENDER, event.minFps < 30 ? "error" : "tertiary");
|
|
96
|
+
this._notifyWaterfall?.({
|
|
97
|
+
metric: "fps-drop",
|
|
98
|
+
startTime: start,
|
|
99
|
+
endTime: end,
|
|
100
|
+
durationMs: event.durationMs,
|
|
101
|
+
minFps: event.minFps,
|
|
102
|
+
avgFps: event.avgFps,
|
|
103
|
+
sessionId: interactionId
|
|
104
|
+
});
|
|
105
|
+
return {
|
|
106
|
+
interactionId,
|
|
107
|
+
startTime: start,
|
|
108
|
+
endTime: end
|
|
109
|
+
};
|
|
110
|
+
}
|
|
111
|
+
case "long-task":
|
|
112
|
+
{
|
|
113
|
+
const markName = `${_constants.PREFIX.LONG_TASK}:${Math.round(t)}`;
|
|
114
|
+
const interactionId = this._getInteraction?.()?.interactionIdForRange(t, t + event.durationMs) ?? null;
|
|
115
|
+
performance.mark(markName, {
|
|
116
|
+
startTime: t
|
|
117
|
+
});
|
|
118
|
+
this._measure.emit(`${interactionId ? `[${interactionId}] ` : ""}Long task: ${event.durationMs}ms`, markName, t + event.durationMs, {
|
|
119
|
+
interactionId,
|
|
120
|
+
durationMs: event.durationMs,
|
|
121
|
+
sessionContext: this._getSessionContext(),
|
|
122
|
+
thread: event.thread ?? null,
|
|
123
|
+
simulatorValid: true
|
|
124
|
+
}, _constants.TRACK.RENDER, event.durationMs > 100 ? "error" : "tertiary");
|
|
125
|
+
this._notifyWaterfall?.({
|
|
126
|
+
metric: "long-task",
|
|
127
|
+
startTime: t,
|
|
128
|
+
endTime: t + event.durationMs,
|
|
129
|
+
durationMs: event.durationMs,
|
|
130
|
+
sessionId: interactionId
|
|
131
|
+
});
|
|
132
|
+
break;
|
|
133
|
+
}
|
|
134
|
+
case "memory-snapshot":
|
|
135
|
+
{
|
|
136
|
+
const jvmMark = `catalyst:mem/jvm:${Math.round(t)}`;
|
|
137
|
+
performance.mark(jvmMark, {
|
|
138
|
+
startTime: t
|
|
139
|
+
});
|
|
140
|
+
this._measure.emit(`JVM memory: ${event.jvmMb.toFixed(1)}MB`, jvmMark, t + 1, {
|
|
141
|
+
memoryId: `memory-jvm-${Math.round(t)}`,
|
|
142
|
+
jvmMb: event.jvmMb,
|
|
143
|
+
peakMb: event.peakMb,
|
|
144
|
+
thread: event.thread ?? null,
|
|
145
|
+
simulatorValid: false,
|
|
146
|
+
...(event.coldStartMs != null ? {
|
|
147
|
+
coldStartMs: event.coldStartMs
|
|
148
|
+
} : {})
|
|
149
|
+
}, _constants.TRACK.MEMORY, "primary");
|
|
150
|
+
const webviewMemoryId = `memory-webview-${Math.round(t)}`;
|
|
151
|
+
const webviewMark = `catalyst:mem/webview:${Math.round(t)}`;
|
|
152
|
+
performance.mark(webviewMark, {
|
|
153
|
+
startTime: t
|
|
154
|
+
});
|
|
155
|
+
this._measure.emit(`WebView memory: ${event.webviewMb.toFixed(1)}MB`, webviewMark, t + 1, {
|
|
156
|
+
memoryId: webviewMemoryId,
|
|
157
|
+
webviewMb: event.webviewMb,
|
|
158
|
+
otherMb: event.otherMb,
|
|
159
|
+
peakMb: event.peakMb,
|
|
160
|
+
thread: event.thread ?? null,
|
|
161
|
+
simulatorValid: false,
|
|
162
|
+
...(event.coldStartMs != null ? {
|
|
163
|
+
coldStartMs: event.coldStartMs
|
|
164
|
+
} : {})
|
|
165
|
+
}, _constants.TRACK.MEMORY, "tertiary-dark");
|
|
166
|
+
const totalMark = `${_constants.PREFIX.MEMORY_TOTAL}:${Math.round(t)}`;
|
|
167
|
+
performance.mark(totalMark, {
|
|
168
|
+
startTime: t
|
|
169
|
+
});
|
|
170
|
+
this._measure.emit(`Total memory: ${event.totalMb.toFixed(1)}MB`, totalMark, t + 1, {
|
|
171
|
+
memoryId: `memory-total-${Math.round(t)}`,
|
|
172
|
+
totalMb: event.totalMb,
|
|
173
|
+
jvmMb: event.jvmMb,
|
|
174
|
+
webviewMb: event.webviewMb,
|
|
175
|
+
otherMb: event.otherMb,
|
|
176
|
+
peakMb: event.peakMb,
|
|
177
|
+
thread: event.thread ?? null,
|
|
178
|
+
simulatorValid: false,
|
|
179
|
+
...(event.coldStartMs != null ? {
|
|
180
|
+
coldStartMs: event.coldStartMs
|
|
181
|
+
} : {})
|
|
182
|
+
}, _constants.TRACK.MEMORY, event.totalMb > 400 ? "error" : "secondary");
|
|
183
|
+
this._notifyInsights?.({
|
|
184
|
+
currentMb: event.webviewMb,
|
|
185
|
+
startTime: t,
|
|
186
|
+
endTime: t + 1,
|
|
187
|
+
memoryId: webviewMemoryId
|
|
188
|
+
});
|
|
189
|
+
this._notifyWaterfall?.({
|
|
190
|
+
metric: "memory",
|
|
191
|
+
startTime: t,
|
|
192
|
+
endTime: t + 1,
|
|
193
|
+
durationMs: 1,
|
|
194
|
+
totalMb: event.totalMb,
|
|
195
|
+
webviewMb: event.webviewMb
|
|
196
|
+
});
|
|
197
|
+
break;
|
|
198
|
+
}
|
|
199
|
+
}
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
// ─── Long Animation Frames ────────────────────────────────────────────────
|
|
203
|
+
|
|
204
|
+
_observeLoAF() {
|
|
205
|
+
if (!PerformanceObserver.supportedEntryTypes?.includes("long-animation-frame")) return;
|
|
206
|
+
const obs = new PerformanceObserver(list => {
|
|
207
|
+
for (const entry of list.getEntries()) {
|
|
208
|
+
const start = entry.startTime;
|
|
209
|
+
const end = start + entry.duration;
|
|
210
|
+
const context = this._getSessionContext();
|
|
211
|
+
|
|
212
|
+
// Register with shared Measure so all collectors get overlap detection
|
|
213
|
+
this._measure.recordLoaf(start, end);
|
|
214
|
+
|
|
215
|
+
// Notify subscribers (ScrollCollector, HookCollector)
|
|
216
|
+
this._loafListeners.forEach(fn => fn(start, end));
|
|
217
|
+
const markName = `${_constants.PREFIX.LOAF}:${Math.round(start)}`;
|
|
218
|
+
performance.mark(markName, {
|
|
219
|
+
startTime: start
|
|
220
|
+
});
|
|
221
|
+
this._measure.emit(`${_constants.PREFIX.LOAF}|${Math.round(entry.duration)}ms`, markName, end, {
|
|
222
|
+
blockingDuration: entry.blockingDuration,
|
|
223
|
+
scriptCount: entry.scripts?.length ?? 0,
|
|
224
|
+
sessionContext: context,
|
|
225
|
+
simulatorValid: false
|
|
226
|
+
}, _constants.TRACK.RENDER);
|
|
227
|
+
this._notifyWaterfall?.({
|
|
228
|
+
metric: "loaf",
|
|
229
|
+
startTime: start,
|
|
230
|
+
endTime: end,
|
|
231
|
+
durationMs: entry.duration
|
|
232
|
+
});
|
|
233
|
+
}
|
|
234
|
+
});
|
|
235
|
+
obs.observe({
|
|
236
|
+
type: "long-animation-frame",
|
|
237
|
+
buffered: true
|
|
238
|
+
});
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
// ─── Layout shift ─────────────────────────────────────────────────────────
|
|
242
|
+
|
|
243
|
+
_observeLayoutShift() {
|
|
244
|
+
if (!PerformanceObserver.supportedEntryTypes?.includes("layout-shift")) return;
|
|
245
|
+
const obs = new PerformanceObserver(list => {
|
|
246
|
+
for (const entry of list.getEntries()) {
|
|
247
|
+
if (entry.hadRecentInput) continue; // user-initiated shift, ignore
|
|
248
|
+
|
|
249
|
+
const context = this._getSessionContext();
|
|
250
|
+
const markName = `catalyst:cls:${Math.round(entry.startTime)}`;
|
|
251
|
+
performance.mark(markName, {
|
|
252
|
+
startTime: entry.startTime
|
|
253
|
+
});
|
|
254
|
+
if (entry.value > 0.1) {
|
|
255
|
+
this._notifyInsights?.({
|
|
256
|
+
value: entry.value,
|
|
257
|
+
sessionContext: context,
|
|
258
|
+
startTime: entry.startTime,
|
|
259
|
+
endTime: entry.startTime + 1
|
|
260
|
+
});
|
|
261
|
+
}
|
|
262
|
+
this._measure.emit(`${_constants.PREFIX.LAYOUT_SHIFT}|${entry.value.toFixed(4)}`, markName, entry.startTime + 1,
|
|
263
|
+
// point-in-time event, minimal duration
|
|
264
|
+
{
|
|
265
|
+
value: entry.value,
|
|
266
|
+
sessionContext: context,
|
|
267
|
+
simulatorValid: false
|
|
268
|
+
}, _constants.TRACK.RENDER);
|
|
269
|
+
this._notifyWaterfall?.({
|
|
270
|
+
metric: "layout-shift",
|
|
271
|
+
startTime: entry.startTime,
|
|
272
|
+
endTime: entry.startTime + 1,
|
|
273
|
+
durationMs: 1,
|
|
274
|
+
value: entry.value
|
|
275
|
+
});
|
|
276
|
+
}
|
|
277
|
+
});
|
|
278
|
+
obs.observe({
|
|
279
|
+
type: "layout-shift",
|
|
280
|
+
buffered: true
|
|
281
|
+
});
|
|
282
|
+
}
|
|
283
|
+
}
|
|
284
|
+
exports.RenderCollector = RenderCollector;
|
|
@@ -1,4 +1,12 @@
|
|
|
1
|
-
"use strict";
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
Object.defineProperty(exports, "__esModule", {
|
|
4
|
+
value: true
|
|
5
|
+
});
|
|
6
|
+
exports.ScrollCollector = void 0;
|
|
7
|
+
var _constants = require("../core/constants.js");
|
|
8
|
+
var _session = require("../core/session.js");
|
|
9
|
+
/**
|
|
2
10
|
* ScrollCollector.js
|
|
3
11
|
*
|
|
4
12
|
* Instruments: bugs #3, #13
|
|
@@ -15,11 +23,89 @@
|
|
|
15
23
|
*
|
|
16
24
|
* fps-drop-episode routed here (Option B) when scroll session is open:
|
|
17
25
|
* index.js checks this._scroll._session.isOpen before calling onFpsDrop(event)
|
|
18
|
-
*/
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
+
*/
|
|
27
|
+
|
|
28
|
+
class ScrollCollector {
|
|
29
|
+
constructor(measure, nativeToWeb) {
|
|
30
|
+
this._measure = measure;
|
|
31
|
+
this._nativeToWeb = nativeToWeb;
|
|
32
|
+
this._session = new _session.Session(_constants.PREFIX.SESSION_SCROLL, measure, _constants.TRACK.INPUT);
|
|
33
|
+
this._insightsCb = null; // set by index.js via setInsightsSource
|
|
34
|
+
this._frameDropCount = 0;
|
|
35
|
+
this._minFps = 60;
|
|
36
|
+
}
|
|
37
|
+
init() {
|
|
38
|
+
// No DOM listener — scroll events come from native GestureDetector
|
|
39
|
+
}
|
|
40
|
+
setInsightsSource(cb) {
|
|
41
|
+
this._insightsCb = cb;
|
|
42
|
+
}
|
|
43
|
+
setWaterfallSource(fn) {
|
|
44
|
+
this._notifyWaterfall = fn;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
// Called by RenderCollector when a LoAF fires (shared notification, kept for compat)
|
|
48
|
+
onLoaf() {
|
|
49
|
+
if (this._session.isOpen) {
|
|
50
|
+
this._session.increment("loafCount");
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
// ─── Native event handlers ────────────────────────────────────────────────
|
|
55
|
+
|
|
56
|
+
onScrollStart(event) {
|
|
57
|
+
if (this._session.isOpen) return; // already tracking, ignore duplicate start
|
|
58
|
+
const webNow = this._nativeToWeb(event.nativeTime);
|
|
59
|
+
this._frameDropCount = 0;
|
|
60
|
+
this._minFps = 60;
|
|
61
|
+
this._session.open({
|
|
62
|
+
loafCount: 0
|
|
63
|
+
}, webNow);
|
|
64
|
+
}
|
|
65
|
+
onScrollEnd(event) {
|
|
66
|
+
if (!this._session.isOpen) return;
|
|
67
|
+
const startTime = this._session.startTime;
|
|
68
|
+
const webNow = this._nativeToWeb(event.nativeTime);
|
|
69
|
+
const loafCount = this._session.get("loafCount") ?? 0;
|
|
70
|
+
const janky = this._frameDropCount > 0 || this._minFps < 55;
|
|
71
|
+
const durationMs = Math.round(webNow - startTime);
|
|
72
|
+
this._session.close({
|
|
73
|
+
loafCount,
|
|
74
|
+
frameDropCount: this._frameDropCount,
|
|
75
|
+
minFps: this._minFps,
|
|
76
|
+
janky,
|
|
77
|
+
simulatorValid: false
|
|
78
|
+
}, webNow);
|
|
79
|
+
if (janky && this._insightsCb) {
|
|
80
|
+
this._insightsCb({
|
|
81
|
+
minFps: this._minFps,
|
|
82
|
+
frameDropCount: this._frameDropCount,
|
|
83
|
+
startTime,
|
|
84
|
+
endTime: webNow
|
|
85
|
+
});
|
|
86
|
+
}
|
|
87
|
+
this._notifyWaterfall?.({
|
|
88
|
+
kind: "scroll",
|
|
89
|
+
startTime,
|
|
90
|
+
endTime: webNow,
|
|
91
|
+
durationMs,
|
|
92
|
+
loafCount,
|
|
93
|
+
frameDropCount: this._frameDropCount,
|
|
94
|
+
minFps: this._minFps,
|
|
95
|
+
janky
|
|
96
|
+
});
|
|
97
|
+
this._frameDropCount = 0;
|
|
98
|
+
this._minFps = 60;
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
// ─── fps-drop-episode correlation (Option B) ──────────────────────────────
|
|
102
|
+
// Called from index.js _routeNativeEvent when scroll session is open.
|
|
103
|
+
|
|
104
|
+
onFpsDrop(event) {
|
|
105
|
+
this._frameDropCount++;
|
|
106
|
+
if (event.minFps != null && event.minFps < this._minFps) {
|
|
107
|
+
this._minFps = event.minFps;
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
exports.ScrollCollector = ScrollCollector;
|
|
@@ -1,4 +1,11 @@
|
|
|
1
|
-
"use strict";
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
Object.defineProperty(exports, "__esModule", {
|
|
4
|
+
value: true
|
|
5
|
+
});
|
|
6
|
+
exports.ThreadCollector = void 0;
|
|
7
|
+
var _constants = require("../core/constants.js");
|
|
8
|
+
/**
|
|
2
9
|
* ThreadCollector.js
|
|
3
10
|
*
|
|
4
11
|
* Instruments: thread visibility gap during cache serving.
|
|
@@ -20,8 +27,85 @@
|
|
|
20
27
|
* Overlap detection:
|
|
21
28
|
* If a LoAF overlaps a software-render window → notifyInsights so
|
|
22
29
|
* InsightsCollector can fire an 'hw-accel-during-frame' critical insight.
|
|
23
|
-
*/
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
30
|
+
*/
|
|
31
|
+
|
|
32
|
+
class ThreadCollector {
|
|
33
|
+
constructor(measure, nativeToWeb) {
|
|
34
|
+
this._measure = measure;
|
|
35
|
+
this._nativeToWeb = nativeToWeb;
|
|
36
|
+
|
|
37
|
+
// Track open software-render windows: { startMark, startTime, thread }
|
|
38
|
+
this._openWindow = null;
|
|
39
|
+
this._notifyInsights = null;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
/** Called by index.js to wire up insights notifications. */
|
|
43
|
+
setInsightsSource(fn) {
|
|
44
|
+
this._notifyInsights = fn;
|
|
45
|
+
}
|
|
46
|
+
init() {
|
|
47
|
+
// No web-side observers — all events come from native via onNativeEvent()
|
|
48
|
+
}
|
|
49
|
+
onNativeEvent(event) {
|
|
50
|
+
if (event.type !== "hw-accel-change") return;
|
|
51
|
+
const {
|
|
52
|
+
state,
|
|
53
|
+
trigger,
|
|
54
|
+
thread,
|
|
55
|
+
nativeTime
|
|
56
|
+
} = event;
|
|
57
|
+
const webTime = nativeTime != null ? this._nativeToWeb(nativeTime) : performance.now();
|
|
58
|
+
if (state === "off") {
|
|
59
|
+
// Software-render window opens — record start, wait for 'on' to close span
|
|
60
|
+
const markName = `${_constants.PREFIX.HW_ACCEL}/off:start:${Math.round(webTime)}`;
|
|
61
|
+
performance.mark(markName, {
|
|
62
|
+
startTime: webTime
|
|
63
|
+
});
|
|
64
|
+
this._openWindow = {
|
|
65
|
+
startMark: markName,
|
|
66
|
+
startTime: webTime,
|
|
67
|
+
thread: thread ?? null,
|
|
68
|
+
trigger: trigger ?? null
|
|
69
|
+
};
|
|
70
|
+
} else if (state === "on") {
|
|
71
|
+
const endTime = webTime;
|
|
72
|
+
if (this._openWindow) {
|
|
73
|
+
const {
|
|
74
|
+
startMark,
|
|
75
|
+
startTime,
|
|
76
|
+
thread: offThread,
|
|
77
|
+
trigger: offTrigger
|
|
78
|
+
} = this._openWindow;
|
|
79
|
+
this._openWindow = null;
|
|
80
|
+
const loafOverlap = this._measure.overlapsLoaf(startTime, endTime);
|
|
81
|
+
this._measure.emit(`${_constants.PREFIX.HW_ACCEL}/sw-render-window|${offTrigger ?? "unknown"}`, startMark, endTime, {
|
|
82
|
+
trigger: offTrigger ?? null,
|
|
83
|
+
thread: offThread ?? null,
|
|
84
|
+
loafOverlap,
|
|
85
|
+
simulatorValid: false
|
|
86
|
+
}, _constants.TRACK.RENDER, loafOverlap ? _constants.COLOR.ERROR : _constants.COLOR.SECONDARY_DARK);
|
|
87
|
+
if (loafOverlap) {
|
|
88
|
+
this._notifyInsights?.({
|
|
89
|
+
durationMs: endTime - startTime,
|
|
90
|
+
thread: offThread,
|
|
91
|
+
trigger: offTrigger,
|
|
92
|
+
startTime,
|
|
93
|
+
endTime
|
|
94
|
+
});
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
// Emit a point-in-time marker for hw-accel restored
|
|
99
|
+
const onMark = `${_constants.PREFIX.HW_ACCEL}/on:${Math.round(endTime)}`;
|
|
100
|
+
performance.mark(onMark, {
|
|
101
|
+
startTime: endTime
|
|
102
|
+
});
|
|
103
|
+
this._measure.emit(`${_constants.PREFIX.HW_ACCEL}/restored`, onMark, endTime + 1, {
|
|
104
|
+
trigger: trigger ?? null,
|
|
105
|
+
thread: thread ?? null,
|
|
106
|
+
simulatorValid: false
|
|
107
|
+
}, _constants.TRACK.RENDER, _constants.COLOR.SECONDARY);
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
exports.ThreadCollector = ThreadCollector;
|
|
@@ -1,26 +1,97 @@
|
|
|
1
|
-
"use strict";
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
Object.defineProperty(exports, "__esModule", {
|
|
4
|
+
value: true
|
|
5
|
+
});
|
|
6
|
+
exports.TRACK_GROUP = exports.TRACK = exports.THRESHOLD = exports.PREFIX = exports.COLOR = void 0;
|
|
7
|
+
/**
|
|
2
8
|
* constants.js
|
|
3
9
|
*
|
|
4
10
|
* Single source of truth for track names, thresholds, and color rules.
|
|
5
11
|
* All collectors import from here — no magic strings scattered across files.
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
12
|
+
*/
|
|
13
|
+
|
|
14
|
+
// ─── Chrome DevTools track group ─────────────────────────────────────────────
|
|
15
|
+
|
|
16
|
+
const TRACK_GROUP = exports.TRACK_GROUP = "Catalyst";
|
|
17
|
+
const TRACK = exports.TRACK = {
|
|
18
|
+
NAVIGATION: "Navigation",
|
|
19
|
+
SCROLL: "Scroll",
|
|
20
|
+
INPUT: "Input",
|
|
21
|
+
KEYBOARD: "Keyboard",
|
|
22
|
+
// alias kept for backward compat — collectors use TRACK.INPUT
|
|
23
|
+
RENDER: "Render",
|
|
24
|
+
MEMORY: "Memory",
|
|
25
|
+
CACHE: "Cache",
|
|
26
|
+
NETWORK: "Network",
|
|
27
|
+
HOOK: "Hook",
|
|
28
|
+
INTERACTION: "Interaction",
|
|
29
|
+
INSIGHTS: "Insights",
|
|
30
|
+
NATIVE_API: "Native API"
|
|
31
|
+
};
|
|
32
|
+
|
|
33
|
+
// ─── Measure name prefixes ────────────────────────────────────────────────────
|
|
34
|
+
|
|
35
|
+
const PREFIX = exports.PREFIX = {
|
|
36
|
+
SESSION_NAV: "catalyst:session/navigation",
|
|
37
|
+
SESSION_SCROLL: "catalyst:session/scroll",
|
|
38
|
+
SESSION_INPUT: "catalyst:session/input",
|
|
39
|
+
SESSION_KEYBOARD: "catalyst:session/keyboard",
|
|
40
|
+
// alias — KeyboardCollector still uses this
|
|
41
|
+
LCP: "catalyst:lcp",
|
|
42
|
+
FCP: "catalyst:fcp",
|
|
43
|
+
LOAF: "catalyst:loaf",
|
|
44
|
+
LAYOUT_SHIFT: "catalyst:layout-shift",
|
|
45
|
+
VIEWPORT_RESIZE: "catalyst:viewport-resize",
|
|
46
|
+
ROUTE_TRANSITION: "catalyst:route-transition",
|
|
47
|
+
CACHE: "catalyst:cache",
|
|
48
|
+
CACHE_GROUP: "catalyst:cache-group",
|
|
49
|
+
NETWORK_TIMING: "catalyst:network",
|
|
50
|
+
NATIVE_API: "catalyst:native-api",
|
|
51
|
+
HOOK_MOUNT: "catalyst:hook/mount",
|
|
52
|
+
HOOK_LAZY: "catalyst:hook/lazy",
|
|
53
|
+
INTERACTION: "catalyst:interaction",
|
|
54
|
+
SESSION_INTERACTION: "catalyst:session/interaction",
|
|
55
|
+
LONG_PRESS: "catalyst:long-press",
|
|
56
|
+
INSIGHT: "catalyst:insight",
|
|
57
|
+
HW_ACCEL: "catalyst:hw-accel",
|
|
58
|
+
// Native MetricsMonitor events
|
|
59
|
+
PAGE_LOAD: "catalyst:page-load",
|
|
60
|
+
COLD_START: "catalyst:cold-start",
|
|
61
|
+
FPS_DROP: "catalyst:fps-drop",
|
|
62
|
+
LONG_TASK: "catalyst:long-task",
|
|
63
|
+
MEMORY_JVM: "catalyst:mem/jvm",
|
|
64
|
+
MEMORY_NATIVE: "catalyst:mem/native",
|
|
65
|
+
MEMORY_TOTAL: "catalyst:mem/total"
|
|
66
|
+
};
|
|
67
|
+
|
|
68
|
+
// ─── Thresholds ───────────────────────────────────────────────────────────────
|
|
69
|
+
|
|
70
|
+
const THRESHOLD = exports.THRESHOLD = {
|
|
71
|
+
LCP_BAD_MS: 2500,
|
|
72
|
+
// LCP > 2.5s = poor (Core Web Vitals)
|
|
73
|
+
NAV_SESSION_BAD_MS: 2500,
|
|
74
|
+
// navigation session > 2.5s = slow page
|
|
75
|
+
FETCH_SLOW_MS: 300,
|
|
76
|
+
// network fetch > 300ms = slow
|
|
77
|
+
SCROLL_IDLE_MS: 150,
|
|
78
|
+
// scroll ends after 150ms of no scroll events
|
|
79
|
+
VIEWPORT_MIN_DELTA: 50,
|
|
80
|
+
// ignore viewport resize < 50px (not keyboard)
|
|
81
|
+
CACHE_GROUP_IDLE_MS: 300,
|
|
82
|
+
// close cache group after 300ms idle
|
|
83
|
+
LOAF_WINDOW: 5,
|
|
84
|
+
// keep last N LoAF entries for overlap detection
|
|
85
|
+
LONG_PRESS_MS: 500,
|
|
86
|
+
// pointerdown held > 500ms = long press
|
|
87
|
+
INTERACTION_SLOW_MS: 100,
|
|
88
|
+
// tap → paint > 100ms = slow response
|
|
89
|
+
LAZY_LOAF_WINDOW_MS: 2000,
|
|
90
|
+
// watch for LoAF bursts up to 2s after interaction
|
|
91
|
+
CLS_BAD: 0.1 // CLS > 0.1 per entry = significant shift
|
|
92
|
+
};
|
|
93
|
+
|
|
94
|
+
// ─── Colors ───────────────────────────────────────────────────────────────────
|
|
24
95
|
// Chrome DevTools Extensibility API color values (all 9 values):
|
|
25
96
|
//
|
|
26
97
|
// primary / primary-light / primary-dark → blue tones
|
|
@@ -40,4 +111,16 @@ CLS_BAD:0.1// CLS > 0.1 per entry = significant shift
|
|
|
40
111
|
// tertiary = network fetch after miss (expected but slow)
|
|
41
112
|
// tertiary-light = small CLS, long-press (low signal)
|
|
42
113
|
// tertiary-dark = insight warning (non-critical)
|
|
43
|
-
|
|
114
|
+
|
|
115
|
+
const COLOR = exports.COLOR = {
|
|
116
|
+
ERROR: "error",
|
|
117
|
+
PRIMARY: "primary",
|
|
118
|
+
PRIMARY_LIGHT: "primary-light",
|
|
119
|
+
PRIMARY_DARK: "primary-dark",
|
|
120
|
+
SECONDARY: "secondary",
|
|
121
|
+
SECONDARY_LIGHT: "secondary-light",
|
|
122
|
+
SECONDARY_DARK: "secondary-dark",
|
|
123
|
+
TERTIARY: "tertiary",
|
|
124
|
+
TERTIARY_LIGHT: "tertiary-light",
|
|
125
|
+
TERTIARY_DARK: "tertiary-dark"
|
|
126
|
+
};
|