chrome-devtools-mcp 0.1.0 → 0.2.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.
Files changed (114) hide show
  1. package/README.md +33 -11
  2. package/build/node_modules/chrome-devtools-frontend/front_end/core/common/Progress.js +60 -53
  3. package/build/node_modules/chrome-devtools-frontend/front_end/core/common/Settings.js +3 -32
  4. package/build/node_modules/chrome-devtools-frontend/front_end/core/host/GdpClient.js +1 -1
  5. package/build/node_modules/chrome-devtools-frontend/front_end/core/host/UserMetrics.js +5 -2
  6. package/build/node_modules/chrome-devtools-frontend/front_end/core/i18n/i18n.js +35 -8
  7. package/build/node_modules/chrome-devtools-frontend/front_end/core/protocol_client/InspectorBackend.js +2 -0
  8. package/build/node_modules/chrome-devtools-frontend/front_end/core/root/Runtime.js +4 -1
  9. package/build/node_modules/chrome-devtools-frontend/front_end/core/sdk/CSSMatchedStyles.js +11 -10
  10. package/build/node_modules/chrome-devtools-frontend/front_end/core/sdk/CSSModel.js +1 -1
  11. package/build/node_modules/chrome-devtools-frontend/front_end/core/sdk/CSSPropertyParserMatchers.js +24 -4
  12. package/build/node_modules/chrome-devtools-frontend/front_end/core/sdk/DebuggerModel.js +1 -1
  13. package/build/node_modules/chrome-devtools-frontend/front_end/core/sdk/EnhancedTracesParser.js +29 -24
  14. package/build/node_modules/chrome-devtools-frontend/front_end/core/sdk/NetworkManager.js +1 -1
  15. package/build/node_modules/chrome-devtools-frontend/front_end/core/sdk/NetworkRequest.js +1 -1
  16. package/build/node_modules/chrome-devtools-frontend/front_end/core/sdk/RehydratingConnection.js +9 -15
  17. package/build/node_modules/chrome-devtools-frontend/front_end/core/sdk/RemoteObject.js +1 -1
  18. package/build/node_modules/chrome-devtools-frontend/front_end/core/sdk/ResourceTreeModel.js +1 -1
  19. package/build/node_modules/chrome-devtools-frontend/front_end/core/sdk/RuntimeModel.js +1 -1
  20. package/build/node_modules/chrome-devtools-frontend/front_end/core/sdk/ServiceWorkerManager.js +1 -1
  21. package/build/node_modules/chrome-devtools-frontend/front_end/core/sdk/SourceMap.js +4 -31
  22. package/build/node_modules/chrome-devtools-frontend/front_end/core/sdk/TraceObject.js +5 -2
  23. package/build/node_modules/chrome-devtools-frontend/front_end/generated/InspectorBackendCommands.js +4 -4
  24. package/build/node_modules/chrome-devtools-frontend/front_end/generated/SupportedCSSProperties.js +12 -0
  25. package/build/node_modules/chrome-devtools-frontend/front_end/models/ai_assistance/data_formatters/NetworkRequestFormatter.js +6 -4
  26. package/build/node_modules/chrome-devtools-frontend/front_end/models/ai_assistance/data_formatters/PerformanceInsightFormatter.js +259 -179
  27. package/build/node_modules/chrome-devtools-frontend/front_end/models/ai_assistance/data_formatters/PerformanceTraceFormatter.js +366 -0
  28. package/build/node_modules/chrome-devtools-frontend/front_end/models/ai_assistance/data_formatters/UnitFormatters.js +10 -1
  29. package/build/node_modules/chrome-devtools-frontend/front_end/models/ai_assistance/performance/AICallTree.js +366 -0
  30. package/build/node_modules/chrome-devtools-frontend/front_end/models/ai_assistance/performance/AIContext.js +75 -0
  31. package/build/node_modules/chrome-devtools-frontend/front_end/models/ai_assistance/performance/AIQueries.js +105 -0
  32. package/build/node_modules/chrome-devtools-frontend/front_end/models/bindings/CSSWorkspaceBinding.js +243 -0
  33. package/build/node_modules/chrome-devtools-frontend/front_end/models/bindings/CompilerScriptMapping.js +407 -0
  34. package/build/node_modules/chrome-devtools-frontend/front_end/models/bindings/ContentProviderBasedProject.js +130 -0
  35. package/build/node_modules/chrome-devtools-frontend/front_end/models/bindings/DebuggerLanguagePlugins.js +992 -0
  36. package/build/node_modules/chrome-devtools-frontend/front_end/models/bindings/DebuggerWorkspaceBinding.js +574 -0
  37. package/build/node_modules/chrome-devtools-frontend/front_end/models/bindings/DefaultScriptMapping.js +112 -0
  38. package/build/node_modules/chrome-devtools-frontend/front_end/models/bindings/FileUtils.js +186 -0
  39. package/build/node_modules/chrome-devtools-frontend/front_end/models/bindings/LiveLocation.js +60 -0
  40. package/build/node_modules/chrome-devtools-frontend/front_end/models/bindings/NetworkProject.js +107 -0
  41. package/build/node_modules/chrome-devtools-frontend/front_end/models/bindings/PresentationConsoleMessageHelper.js +244 -0
  42. package/build/node_modules/chrome-devtools-frontend/front_end/models/bindings/ResourceMapping.js +473 -0
  43. package/build/node_modules/chrome-devtools-frontend/front_end/models/bindings/ResourceScriptMapping.js +399 -0
  44. package/build/node_modules/chrome-devtools-frontend/front_end/models/bindings/ResourceUtils.js +87 -0
  45. package/build/node_modules/chrome-devtools-frontend/front_end/models/bindings/SASSSourceMapping.js +181 -0
  46. package/build/node_modules/chrome-devtools-frontend/front_end/models/bindings/StylesSourceMapping.js +268 -0
  47. package/build/node_modules/chrome-devtools-frontend/front_end/models/bindings/TempFile.js +55 -0
  48. package/build/node_modules/chrome-devtools-frontend/front_end/models/bindings/bindings.js +20 -0
  49. package/build/node_modules/chrome-devtools-frontend/front_end/models/crux-manager/CrUXManager.js +283 -0
  50. package/build/node_modules/chrome-devtools-frontend/front_end/models/crux-manager/crux-manager.js +4 -0
  51. package/build/node_modules/chrome-devtools-frontend/front_end/models/emulation/DeviceModeModel.js +775 -0
  52. package/build/node_modules/chrome-devtools-frontend/front_end/models/emulation/EmulatedDevices.js +1706 -0
  53. package/build/node_modules/chrome-devtools-frontend/front_end/models/emulation/emulation.js +6 -0
  54. package/build/node_modules/chrome-devtools-frontend/front_end/models/formatter/FormatterWorkerPool.js +131 -0
  55. package/build/node_modules/chrome-devtools-frontend/front_end/models/formatter/ScriptFormatter.js +77 -0
  56. package/build/node_modules/chrome-devtools-frontend/front_end/models/formatter/formatter.js +6 -0
  57. package/build/node_modules/chrome-devtools-frontend/front_end/models/geometry/GeometryImpl.js +347 -0
  58. package/build/node_modules/chrome-devtools-frontend/front_end/models/geometry/geometry.js +4 -0
  59. package/build/node_modules/chrome-devtools-frontend/front_end/models/source_map_scopes/NamesResolver.js +626 -0
  60. package/build/node_modules/chrome-devtools-frontend/front_end/models/source_map_scopes/ScopeChainModel.js +59 -0
  61. package/build/node_modules/chrome-devtools-frontend/front_end/models/source_map_scopes/ScopeTreeCache.js +32 -0
  62. package/build/node_modules/chrome-devtools-frontend/front_end/models/source_map_scopes/source_map_scopes.js +7 -0
  63. package/build/node_modules/chrome-devtools-frontend/front_end/models/stack_trace/StackTrace.js +4 -0
  64. package/build/node_modules/chrome-devtools-frontend/front_end/models/stack_trace/StackTraceImpl.js +67 -0
  65. package/build/node_modules/chrome-devtools-frontend/front_end/models/stack_trace/StackTraceModel.js +97 -0
  66. package/build/node_modules/chrome-devtools-frontend/front_end/models/stack_trace/Trie.js +113 -0
  67. package/build/node_modules/chrome-devtools-frontend/front_end/models/stack_trace/stack_trace.js +5 -0
  68. package/build/node_modules/chrome-devtools-frontend/front_end/models/stack_trace/stack_trace_impl.js +7 -0
  69. package/build/node_modules/chrome-devtools-frontend/front_end/models/text_utils/TextUtils.js +23 -0
  70. package/build/node_modules/chrome-devtools-frontend/front_end/models/trace/ModelImpl.js +4 -9
  71. package/build/node_modules/chrome-devtools-frontend/front_end/models/trace/Processor.js +16 -8
  72. package/build/node_modules/chrome-devtools-frontend/front_end/models/trace/handlers/AuctionWorkletsHandler.js +1 -1
  73. package/build/node_modules/chrome-devtools-frontend/front_end/models/trace/handlers/FramesHandler.js +2 -2
  74. package/build/node_modules/chrome-devtools-frontend/front_end/models/trace/handlers/LayoutShiftsHandler.js +3 -4
  75. package/build/node_modules/chrome-devtools-frontend/front_end/models/trace/handlers/MetaHandler.js +10 -9
  76. package/build/node_modules/chrome-devtools-frontend/front_end/models/trace/handlers/ScreenshotsHandler.js +0 -1
  77. package/build/node_modules/chrome-devtools-frontend/front_end/models/trace/handlers/ScriptsHandler.js +4 -4
  78. package/build/node_modules/chrome-devtools-frontend/front_end/models/trace/handlers/UserInteractionsHandler.js +2 -10
  79. package/build/node_modules/chrome-devtools-frontend/front_end/models/trace/handlers/UserTimingsHandler.js +3 -4
  80. package/build/node_modules/chrome-devtools-frontend/front_end/models/trace/helpers/SamplesIntegrator.js +8 -6
  81. package/build/node_modules/chrome-devtools-frontend/front_end/models/trace/helpers/Trace.js +1 -1
  82. package/build/node_modules/chrome-devtools-frontend/front_end/models/trace/insights/CLSCulprits.js +1 -1
  83. package/build/node_modules/chrome-devtools-frontend/front_end/models/trace/insights/DocumentLatency.js +5 -4
  84. package/build/node_modules/chrome-devtools-frontend/front_end/models/trace/insights/DuplicatedJavaScript.js +1 -1
  85. package/build/node_modules/chrome-devtools-frontend/front_end/models/trace/insights/INPBreakdown.js +1 -1
  86. package/build/node_modules/chrome-devtools-frontend/front_end/models/trace/insights/ImageDelivery.js +1 -1
  87. package/build/node_modules/chrome-devtools-frontend/front_end/models/trace/insights/LCPBreakdown.js +1 -1
  88. package/build/node_modules/chrome-devtools-frontend/front_end/models/trace/insights/LCPDiscovery.js +1 -1
  89. package/build/node_modules/chrome-devtools-frontend/front_end/models/trace/insights/ModernHTTP.js +1 -1
  90. package/build/node_modules/chrome-devtools-frontend/front_end/models/trace/insights/NetworkDependencyTree.js +1 -1
  91. package/build/node_modules/chrome-devtools-frontend/front_end/models/trace/insights/RenderBlocking.js +1 -1
  92. package/build/node_modules/chrome-devtools-frontend/front_end/models/trace/types/TraceEvents.js +21 -21
  93. package/build/node_modules/chrome-devtools-frontend/front_end/models/trace_source_maps_resolver/SourceMapsResolver.js +201 -0
  94. package/build/node_modules/chrome-devtools-frontend/front_end/models/trace_source_maps_resolver/trace_source_maps_resolver.js +4 -0
  95. package/build/node_modules/chrome-devtools-frontend/front_end/models/workspace/FileManager.js +64 -0
  96. package/build/node_modules/chrome-devtools-frontend/front_end/models/workspace/IgnoreListManager.js +511 -0
  97. package/build/node_modules/chrome-devtools-frontend/front_end/models/workspace/SearchConfig.js +113 -0
  98. package/build/node_modules/chrome-devtools-frontend/front_end/models/workspace/UISourceCode.js +563 -0
  99. package/build/node_modules/chrome-devtools-frontend/front_end/models/workspace/WorkspaceImpl.js +204 -0
  100. package/build/node_modules/chrome-devtools-frontend/front_end/models/workspace/workspace.js +9 -0
  101. package/build/src/McpContext.js +84 -19
  102. package/build/src/McpResponse.js +8 -7
  103. package/build/src/WaitForHelper.js +123 -0
  104. package/build/src/browser.js +15 -10
  105. package/build/src/index.js +4 -6
  106. package/build/src/logger.js +1 -0
  107. package/build/src/tools/input.js +12 -13
  108. package/build/src/tools/pages.js +2 -3
  109. package/build/src/tools/performance.js +31 -4
  110. package/build/src/tools/screenshot.js +1 -1
  111. package/build/src/tools/script.js +40 -15
  112. package/build/src/trace-processing/parse.js +26 -22
  113. package/package.json +15 -12
  114. package/build/src/waitForHelpers.js +0 -109
@@ -0,0 +1,32 @@
1
+ // Copyright 2023 The Chromium Authors
2
+ // Use of this source code is governed by a BSD-style license that can be
3
+ // found in the LICENSE file.
4
+ import * as Formatter from '../formatter/formatter.js';
5
+ import * as TextUtils from '../text_utils/text_utils.js';
6
+ /** If a script failed to parse, we stash null in order to prevent unnecessary re-parsing */
7
+ const scopeTrees = new WeakMap();
8
+ /**
9
+ * Computes and caches the scope tree for `script`.
10
+ *
11
+ * We use {@link SDK.Script.Script} as a key to uniquely identify scripts.
12
+ * {@link SDK.Script.Script} boils down to "target" + "script ID". This
13
+ * duplicates work in case of identitical script running on multiple targets
14
+ * (e.g. workers).
15
+ */
16
+ export function scopeTreeForScript(script) {
17
+ let promise = scopeTrees.get(script);
18
+ if (promise === undefined) {
19
+ promise = script.requestContentData().then(content => {
20
+ if (TextUtils.ContentData.ContentData.isError(content)) {
21
+ return null;
22
+ }
23
+ const sourceType = script.isModule ? 'module' : 'script';
24
+ return Formatter.FormatterWorkerPool.formatterWorkerPool()
25
+ .javaScriptScopeTree(content.text, sourceType)
26
+ .catch(() => null);
27
+ });
28
+ scopeTrees.set(script, promise);
29
+ }
30
+ // We intentionally return `null` here if the script already failed to parse once.
31
+ return promise;
32
+ }
@@ -0,0 +1,7 @@
1
+ // Copyright 2022 The Chromium Authors
2
+ // Use of this source code is governed by a BSD-style license that can be
3
+ // found in the LICENSE file.
4
+ import * as NamesResolver from './NamesResolver.js';
5
+ import * as ScopeChainModel from './ScopeChainModel.js';
6
+ import * as ScopeTreeCache from './ScopeTreeCache.js';
7
+ export { NamesResolver, ScopeChainModel, ScopeTreeCache, };
@@ -0,0 +1,4 @@
1
+ // Copyright 2025 The Chromium Authors
2
+ // Use of this source code is governed by a BSD-style license that can be
3
+ // found in the LICENSE file.
4
+ export {};
@@ -0,0 +1,67 @@
1
+ // Copyright 2025 The Chromium Authors
2
+ // Use of this source code is governed by a BSD-style license that can be
3
+ // found in the LICENSE file.
4
+ import * as Common from '../../core/common/common.js';
5
+ export class StackTraceImpl extends Common.ObjectWrapper.ObjectWrapper {
6
+ syncFragment;
7
+ asyncFragments;
8
+ constructor(syncFragment, asyncFragments) {
9
+ super();
10
+ this.syncFragment = syncFragment;
11
+ this.asyncFragments = asyncFragments;
12
+ syncFragment.stackTraces.add(this);
13
+ this.asyncFragments.forEach(asyncFragment => asyncFragment.fragment.stackTraces.add(this));
14
+ }
15
+ }
16
+ export class FragmentImpl {
17
+ node;
18
+ stackTraces = new Set();
19
+ /**
20
+ * Fragments are deduplicated based on the node.
21
+ *
22
+ * In turn, each fragment can be part of multiple stack traces.
23
+ */
24
+ static getOrCreate(node) {
25
+ if (!node.fragment) {
26
+ node.fragment = new FragmentImpl(node);
27
+ }
28
+ return node.fragment;
29
+ }
30
+ constructor(node) {
31
+ this.node = node;
32
+ }
33
+ get frames() {
34
+ const frames = [];
35
+ for (const node of this.node.getCallStack()) {
36
+ frames.push(...node.frames);
37
+ }
38
+ return frames;
39
+ }
40
+ }
41
+ export class AsyncFragmentImpl {
42
+ description;
43
+ fragment;
44
+ constructor(description, fragment) {
45
+ this.description = description;
46
+ this.fragment = fragment;
47
+ }
48
+ get frames() {
49
+ return this.fragment.frames;
50
+ }
51
+ }
52
+ export class FrameImpl {
53
+ url;
54
+ uiSourceCode;
55
+ name;
56
+ line;
57
+ column;
58
+ missingDebugInfo;
59
+ constructor(url, uiSourceCode, name, line, column, missingDebugInfo) {
60
+ this.url = url;
61
+ this.uiSourceCode = uiSourceCode;
62
+ this.name = name;
63
+ this.line = line;
64
+ this.column = column;
65
+ this.missingDebugInfo = missingDebugInfo;
66
+ }
67
+ }
@@ -0,0 +1,97 @@
1
+ // Copyright 2025 The Chromium Authors
2
+ // Use of this source code is governed by a BSD-style license that can be
3
+ // found in the LICENSE file.
4
+ import * as SDK from '../../core/sdk/sdk.js';
5
+ // eslint-disable-next-line rulesdir/es-modules-import
6
+ import * as StackTrace from './stack_trace.js';
7
+ import { AsyncFragmentImpl, FragmentImpl, FrameImpl, StackTraceImpl } from './StackTraceImpl.js';
8
+ import { Trie } from './Trie.js';
9
+ /**
10
+ * The {@link StackTraceModel} is a thin wrapper around a fragment trie.
11
+ *
12
+ * We want to store stack trace fragments per target so a SDKModel is the natural choice.
13
+ */
14
+ export class StackTraceModel extends SDK.SDKModel.SDKModel {
15
+ #trie = new Trie();
16
+ /** @returns the {@link StackTraceModel} for the target, or the model for the primaryPageTarget when passing null/undefined */
17
+ static #modelForTarget(target) {
18
+ const model = (target ?? SDK.TargetManager.TargetManager.instance().primaryPageTarget())?.model(StackTraceModel);
19
+ if (!model) {
20
+ throw new Error('Unable to find StackTraceModel');
21
+ }
22
+ return model;
23
+ }
24
+ async createFromProtocolRuntime(stackTrace, rawFramesToUIFrames) {
25
+ const translatePromises = [];
26
+ const fragment = this.#createFragment(stackTrace.callFrames);
27
+ translatePromises.push(this.#translateFragment(fragment, rawFramesToUIFrames));
28
+ const asyncFragments = [];
29
+ const debuggerModel = this.target().model(SDK.DebuggerModel.DebuggerModel);
30
+ if (debuggerModel) {
31
+ for await (const { stackTrace: asyncStackTrace, target } of debuggerModel.iterateAsyncParents(stackTrace)) {
32
+ const model = StackTraceModel.#modelForTarget(target);
33
+ const asyncFragment = model.#createFragment(asyncStackTrace.callFrames);
34
+ translatePromises.push(model.#translateFragment(asyncFragment, rawFramesToUIFrames));
35
+ asyncFragments.push(new AsyncFragmentImpl(asyncStackTrace.description ?? '', asyncFragment));
36
+ }
37
+ }
38
+ await Promise.all(translatePromises);
39
+ return new StackTraceImpl(fragment, asyncFragments);
40
+ }
41
+ /** Trigger re-translation of all fragments with the provide script in their call stack */
42
+ async scriptInfoChanged(script, translateRawFrames) {
43
+ const translatePromises = [];
44
+ let stackTracesToUpdate = new Set();
45
+ for (const fragment of this.#affectedFragments(script)) {
46
+ // We trigger re-translation only for fragments of leaf-nodes. Any fragment along the ancestor-chain
47
+ // is re-translated as a side-effect.
48
+ // We just need to remember the stack traces of the skipped over fragments, so we can send the
49
+ // UPDATED event also to them.
50
+ if (fragment.node.children.length === 0) {
51
+ translatePromises.push(this.#translateFragment(fragment, translateRawFrames));
52
+ }
53
+ stackTracesToUpdate = stackTracesToUpdate.union(fragment.stackTraces);
54
+ }
55
+ await Promise.all(translatePromises);
56
+ for (const stackTrace of stackTracesToUpdate) {
57
+ stackTrace.dispatchEventToListeners("UPDATED" /* StackTrace.StackTrace.Events.UPDATED */);
58
+ }
59
+ }
60
+ #createFragment(frames) {
61
+ return FragmentImpl.getOrCreate(this.#trie.insert(frames));
62
+ }
63
+ async #translateFragment(fragment, rawFramesToUIFrames) {
64
+ const rawFrames = fragment.node.getCallStack().map(node => node.rawFrame).toArray();
65
+ const uiFrames = await rawFramesToUIFrames(rawFrames, this.target());
66
+ console.assert(rawFrames.length === uiFrames.length, 'Broken rawFramesToUIFrames implementation');
67
+ let i = 0;
68
+ for (const node of fragment.node.getCallStack()) {
69
+ node.frames = uiFrames[i++].map(frame => new FrameImpl(frame.url, frame.uiSourceCode, frame.name, frame.line, frame.column, frame.missingDebugInfo));
70
+ }
71
+ }
72
+ #affectedFragments(script) {
73
+ // 1. Collect branches with the matching script.
74
+ const affectedBranches = new Set();
75
+ this.#trie.walk(null, node => {
76
+ // scriptId has precedence, but if the frame does not have one, check the URL.
77
+ if (node.rawFrame.scriptId === script.scriptId ||
78
+ (!node.rawFrame.scriptId && node.rawFrame.url === script.sourceURL)) {
79
+ affectedBranches.add(node);
80
+ return false;
81
+ }
82
+ return true;
83
+ });
84
+ // 2. For each branch collect all the fragments.
85
+ const fragments = new Set();
86
+ for (const branch of affectedBranches) {
87
+ this.#trie.walk(branch, node => {
88
+ if (node.fragment) {
89
+ fragments.add(node.fragment);
90
+ }
91
+ return true;
92
+ });
93
+ }
94
+ return fragments;
95
+ }
96
+ }
97
+ SDK.SDKModel.SDKModel.register(StackTraceModel, { capabilities: 0 /* SDK.Target.Capability.NONE */, autostart: false });
@@ -0,0 +1,113 @@
1
+ // Copyright 2025 The Chromium Authors
2
+ // Use of this source code is governed by a BSD-style license that can be
3
+ // found in the LICENSE file.
4
+ export class FrameNode {
5
+ parent;
6
+ children = [];
7
+ rawFrame;
8
+ frames = [];
9
+ fragment;
10
+ constructor(rawFrame, parent) {
11
+ this.rawFrame = rawFrame;
12
+ this.parent = parent;
13
+ }
14
+ /**
15
+ * Produces the ancestor chain. Including `this` but excluding the `RootFrameNode`.
16
+ */
17
+ *getCallStack() {
18
+ // The `RootFrameNode` doesn't have an actual frame attached, that's why we check for `node.parent` instead of `node`.
19
+ for (let node = this; node.parent; node = node.parent) {
20
+ yield node;
21
+ }
22
+ }
23
+ }
24
+ /**
25
+ * Stores stack trace fragments in a trie, but does not own them/keep them alive.
26
+ */
27
+ export class Trie {
28
+ #root = { parent: null, children: [] };
29
+ /**
30
+ * Most sources produce stack traces in "top-to-bottom" order, so that is what this method expects.
31
+ *
32
+ * @returns The {@link FrameNode} corresponding to the top-most stack frame.
33
+ */
34
+ insert(frames) {
35
+ if (frames.length === 0) {
36
+ throw new Error('Trie.insert called with an empty frames array.');
37
+ }
38
+ let currentNode = this.#root;
39
+ for (let i = frames.length - 1; i >= 0; --i) {
40
+ currentNode = this.#insert(currentNode, frames[i]);
41
+ }
42
+ return currentNode;
43
+ }
44
+ /**
45
+ * Inserts `rawFrame` into the children of the provided node if not already there.
46
+ *
47
+ * @returns the child node corresponding to `rawFrame`.
48
+ */
49
+ #insert(node, rawFrame) {
50
+ let i = 0;
51
+ for (; i < node.children.length; ++i) {
52
+ const maybeChild = node.children[i];
53
+ const child = maybeChild instanceof WeakRef ? maybeChild.deref() : maybeChild;
54
+ if (!child) {
55
+ continue;
56
+ }
57
+ const compareResult = compareRawFrames(child.rawFrame, rawFrame);
58
+ if (compareResult === 0) {
59
+ return child;
60
+ }
61
+ if (compareResult > 0) {
62
+ break;
63
+ }
64
+ }
65
+ const newNode = new FrameNode(rawFrame, node);
66
+ if (node.parent) {
67
+ node.children.splice(i, 0, newNode);
68
+ }
69
+ else {
70
+ node.children.splice(i, 0, new WeakRef(newNode));
71
+ }
72
+ return newNode;
73
+ }
74
+ /**
75
+ * Traverses the trie in pre-order.
76
+ *
77
+ * @param node Start at `node` or `null` to start with the children of the root.
78
+ * @param visit Called on each node in the trie. Return `true` if the visitor should descend into child nodes of the provided node.
79
+ */
80
+ walk(node, visit) {
81
+ const stack = node ? [node] : [...this.#root.children].map(ref => ref.deref()).filter(node => Boolean(node));
82
+ for (let node = stack.pop(); node; node = stack.pop()) {
83
+ const visitChildren = visit(node);
84
+ if (visitChildren) {
85
+ // Pushing the children in reverse means the "left-most" child is visited first (i.e. pre-order).
86
+ for (let i = node.children.length - 1; i >= 0; --i) {
87
+ stack.push(node.children[i]);
88
+ }
89
+ }
90
+ }
91
+ }
92
+ }
93
+ /**
94
+ * @returns a number < 0, 0 or > 0, if the `a` is smaller then, equal or greater then `b`.
95
+ */
96
+ export function compareRawFrames(a, b) {
97
+ const scriptIdCompare = (a.scriptId ?? '').localeCompare(b.scriptId ?? '');
98
+ if (scriptIdCompare !== 0) {
99
+ return scriptIdCompare;
100
+ }
101
+ const urlCompare = (a.url ?? '').localeCompare(b.url ?? '');
102
+ if (urlCompare !== 0) {
103
+ return urlCompare;
104
+ }
105
+ const nameCompare = (a.functionName ?? '').localeCompare(b.functionName ?? '');
106
+ if (nameCompare !== 0) {
107
+ return nameCompare;
108
+ }
109
+ if (a.lineNumber !== b.lineNumber) {
110
+ return a.lineNumber - b.lineNumber;
111
+ }
112
+ return a.columnNumber - b.columnNumber;
113
+ }
@@ -0,0 +1,5 @@
1
+ // Copyright 2025 The Chromium Authors
2
+ // Use of this source code is governed by a BSD-style license that can be
3
+ // found in the LICENSE file.
4
+ import * as StackTrace from './StackTrace.js';
5
+ export { StackTrace, };
@@ -0,0 +1,7 @@
1
+ // Copyright 2025 The Chromium Authors
2
+ // Use of this source code is governed by a BSD-style license that can be
3
+ // found in the LICENSE file.
4
+ import * as StackTraceImpl from './StackTraceImpl.js';
5
+ import * as StackTraceModel from './StackTraceModel.js';
6
+ import * as Trie from './Trie.js';
7
+ export { StackTraceImpl, StackTraceModel, Trie, };
@@ -330,3 +330,26 @@ export const performSearchInSearchMatches = function (matches, query, caseSensit
330
330
  }
331
331
  return result;
332
332
  };
333
+ /**
334
+ * Finds the longest overlapping string segment between the end of the first
335
+ * string and the beginning of the second string.
336
+ *
337
+ * @param s1 The first string (whose suffix will be checked).
338
+ * @param s2 The second string (whose prefix will be checked).
339
+ * @returns The overlapping string segment, or an empty string ("")
340
+ * if no overlap is found.
341
+ */
342
+ export const getOverlap = function (s1, s2) {
343
+ const minLen = Math.min(s1.length, s2.length);
344
+ // Check from longest possible overlap down to 1
345
+ for (let n = minLen; n > 0; n--) {
346
+ // slice(-n) gets the last 'n' chars
347
+ const suffix = s1.slice(-n);
348
+ // substring(0, n) gets the first 'n' chars
349
+ const prefix = s2.substring(0, n);
350
+ if (suffix === prefix) {
351
+ return suffix;
352
+ }
353
+ }
354
+ return null;
355
+ };
@@ -6,6 +6,9 @@ import * as Handlers from './handlers/handlers.js';
6
6
  import * as Helpers from './helpers/helpers.js';
7
7
  import { TraceParseProgressEvent, TraceProcessor } from './Processor.js';
8
8
  import * as Types from './types/types.js';
9
+ // Note: this model is implemented in a way that can support multiple trace
10
+ // processors. Currently there is only one implemented, but you will see
11
+ // references to "processors" plural because it can easily be extended in the future.
9
12
  /**
10
13
  * The Model is responsible for parsing arrays of raw trace events and storing the
11
14
  * resulting data. It can store multiple traces at once, and can return the data for
@@ -69,8 +72,6 @@ export class Model extends EventTarget {
69
72
  **/
70
73
  async parse(traceEvents, config) {
71
74
  const metadata = config?.metadata || {};
72
- const isFreshRecording = config?.isFreshRecording || false;
73
- const isCPUProfile = metadata?.dataOrigin === "CPUProfile" /* Types.File.DataOrigin.CPU_PROFILE */;
74
75
  // During parsing, periodically update any listeners on each processors'
75
76
  // progress (if they have any updates).
76
77
  const onTraceUpdate = (event) => {
@@ -83,13 +84,7 @@ export class Model extends EventTarget {
83
84
  try {
84
85
  // Wait for all outstanding promises before finishing the async execution,
85
86
  // but perform all tasks in parallel.
86
- const parseConfig = {
87
- isFreshRecording,
88
- isCPUProfile,
89
- metadata,
90
- resolveSourceMap: config?.resolveSourceMap,
91
- };
92
- await this.#processor.parse(traceEvents, parseConfig);
87
+ await this.#processor.parse(traceEvents, config ?? {});
93
88
  if (!this.#processor.data) {
94
89
  throw new Error('processor did not parse trace');
95
90
  }
@@ -1,6 +1,7 @@
1
1
  // Copyright 2023 The Chromium Authors
2
2
  // Use of this source code is governed by a BSD-style license that can be
3
3
  // found in the LICENSE file.
4
+ var _a;
4
5
  import * as Handlers from './handlers/handlers.js';
5
6
  import * as Helpers from './helpers/helpers.js';
6
7
  import * as Insights from './insights/insights.js';
@@ -31,7 +32,7 @@ export class TraceProcessor extends EventTarget {
31
32
  #data = null;
32
33
  #insights = null;
33
34
  static createWithAllHandlers() {
34
- return new TraceProcessor(Handlers.ModelHandlers, Types.Configuration.defaults());
35
+ return new _a(Handlers.ModelHandlers, Types.Configuration.defaults());
35
36
  }
36
37
  /**
37
38
  * This function is kept for testing with `stub`.
@@ -111,6 +112,9 @@ export class TraceProcessor extends EventTarget {
111
112
  if (this.#status !== "IDLE" /* Status.IDLE */) {
112
113
  throw new Error(`Trace processor can't start parsing when not idle. Current state: ${this.#status}`);
113
114
  }
115
+ if (typeof options.isCPUProfile === 'undefined' && options.metadata) {
116
+ options.isCPUProfile = options.metadata.dataOrigin === "CPUProfile" /* Types.File.DataOrigin.CPU_PROFILE */;
117
+ }
114
118
  options.logger?.start('total');
115
119
  try {
116
120
  this.#status = "PARSING" /* Status.PARSING */;
@@ -345,7 +349,8 @@ export class TraceProcessor extends EventTarget {
345
349
  }
346
350
  insightSet.model = newModel;
347
351
  }
348
- #computeInsightSet(data, context, options) {
352
+ #computeInsightSet(data, context) {
353
+ const logger = context.options.logger;
349
354
  let id, urlString, navigation;
350
355
  if (context.navigation) {
351
356
  id = context.navigationId;
@@ -357,10 +362,10 @@ export class TraceProcessor extends EventTarget {
357
362
  urlString = data.Meta.finalDisplayUrlByNavigationId.get('') ?? data.Meta.mainFrameURL;
358
363
  }
359
364
  const insightSetModel = {};
360
- for (const [name, insight] of Object.entries(TraceProcessor.getInsightRunners())) {
365
+ for (const [name, insight] of Object.entries(_a.getInsightRunners())) {
361
366
  let model;
362
367
  try {
363
- options.logger?.start(`insights:${name}`);
368
+ logger?.start(`insights:${name}`);
364
369
  model = insight.generateInsight(data, context);
365
370
  model.frameId = context.frameId;
366
371
  const navId = context.navigation?.args.data?.navigationId;
@@ -376,7 +381,7 @@ export class TraceProcessor extends EventTarget {
376
381
  model = err;
377
382
  }
378
383
  finally {
379
- options.logger?.end(`insights:${name}`);
384
+ logger?.end(`insights:${name}`);
380
385
  }
381
386
  Object.assign(insightSetModel, { [name]: model });
382
387
  }
@@ -420,7 +425,7 @@ export class TraceProcessor extends EventTarget {
420
425
  this.#insights = new Map();
421
426
  }
422
427
  this.#insights.set(insightSet.id, insightSet);
423
- this.sortInsightSet(insightSet, options.metadata ?? null);
428
+ this.sortInsightSet(insightSet, context.options.metadata ?? null);
424
429
  }
425
430
  /**
426
431
  * Run all the insights and set the result to `#insights`.
@@ -449,11 +454,12 @@ export class TraceProcessor extends EventTarget {
449
454
  Helpers.Timing.traceWindowFromMicroSeconds(data.Meta.traceBounds.min, navigations[0].ts) :
450
455
  data.Meta.traceBounds;
451
456
  const context = {
457
+ options,
452
458
  bounds,
453
459
  frameId: data.Meta.mainFrameId,
454
460
  // No navigation or lantern context applies to this initial/no-navigation period.
455
461
  };
456
- this.#computeInsightSet(data, context, options);
462
+ this.#computeInsightSet(data, context);
457
463
  }
458
464
  /**
459
465
  * Computes insights for a specific navigation event.
@@ -497,15 +503,17 @@ export class TraceProcessor extends EventTarget {
497
503
  options.logger?.end('insights:createLanternContext');
498
504
  }
499
505
  const context = {
506
+ options,
500
507
  bounds,
501
508
  frameId,
502
509
  navigation,
503
510
  navigationId,
504
511
  lantern,
505
512
  };
506
- this.#computeInsightSet(data, context, options);
513
+ this.#computeInsightSet(data, context);
507
514
  }
508
515
  }
516
+ _a = TraceProcessor;
509
517
  /**
510
518
  * Some Handlers need data provided by others. Dependencies of a handler handler are
511
519
  * declared in the `deps` field.
@@ -157,6 +157,6 @@ export async function finalize() {
157
157
  }
158
158
  export function data() {
159
159
  return {
160
- worklets: new Map(createdSyntheticEvents),
160
+ worklets: createdSyntheticEvents,
161
161
  };
162
162
  }
@@ -67,8 +67,8 @@ export async function finalize() {
67
67
  }
68
68
  export function data() {
69
69
  return {
70
- frames: model ? Array.from(model.frames()) : [],
71
- framesById: model ? { ...model.framesById() } : {},
70
+ frames: model?.frames() ?? [],
71
+ framesById: model?.framesById() ?? {},
72
72
  };
73
73
  }
74
74
  export function deps() {
@@ -419,15 +419,14 @@ export function data() {
419
419
  prePaintEvents,
420
420
  layoutInvalidationEvents,
421
421
  scheduleStyleInvalidationEvents,
422
- styleRecalcInvalidationEvents: [],
422
+ styleRecalcInvalidationEvents,
423
423
  renderFrameImplCreateChildFrameEvents,
424
424
  domLoadingEvents,
425
425
  layoutImageUnsizedEvents,
426
426
  remoteFonts,
427
427
  scoreRecords,
428
- // TODO(crbug/41484172): change the type so no need to clone
429
- backendNodeIds: [...backendNodeIds],
430
- clustersByNavigationId: new Map(clustersByNavigationId),
428
+ backendNodeIds,
429
+ clustersByNavigationId,
431
430
  paintImageEvents,
432
431
  };
433
432
  }
@@ -21,11 +21,14 @@ let viewportRect = null;
21
21
  let devicePixelRatio = null;
22
22
  let processNames = new Map();
23
23
  let topLevelRendererIds = new Set();
24
- const traceBounds = {
25
- min: Types.Timing.Micro(Number.POSITIVE_INFINITY),
26
- max: Types.Timing.Micro(Number.NEGATIVE_INFINITY),
27
- range: Types.Timing.Micro(Number.POSITIVE_INFINITY),
28
- };
24
+ function makeNewTraceBounds() {
25
+ return {
26
+ min: Types.Timing.Micro(Number.POSITIVE_INFINITY),
27
+ max: Types.Timing.Micro(Number.NEGATIVE_INFINITY),
28
+ range: Types.Timing.Micro(Number.POSITIVE_INFINITY),
29
+ };
30
+ }
31
+ let traceBounds = makeNewTraceBounds();
29
32
  /**
30
33
  * These represent the user navigating. Values such as First Contentful Paint,
31
34
  * etc, are relative to the navigation.
@@ -84,9 +87,7 @@ export function reset() {
84
87
  threadsInProcess = new Map();
85
88
  rendererProcessesByFrameId = new Map();
86
89
  framesByProcessId = new Map();
87
- traceBounds.min = Types.Timing.Micro(Number.POSITIVE_INFINITY);
88
- traceBounds.max = Types.Timing.Micro(Number.NEGATIVE_INFINITY);
89
- traceBounds.range = Types.Timing.Micro(Number.POSITIVE_INFINITY);
90
+ traceBounds = makeNewTraceBounds();
90
91
  traceStartedTimeFromTracingStartedEvent = Types.Timing.Micro(-1);
91
92
  traceIsGeneric = true;
92
93
  }
@@ -377,7 +378,7 @@ export async function finalize() {
377
378
  }
378
379
  export function data() {
379
380
  return {
380
- traceBounds: { ...traceBounds },
381
+ traceBounds,
381
382
  browserProcessId,
382
383
  browserThreadId,
383
384
  processNames,
@@ -86,7 +86,6 @@ function getPresentationTimestamp(screenshotEvent) {
86
86
  // The last one is sometimes missing as because the trace terminates right before the associated PipelineReporter is emitted.
87
87
  return updatedTs ?? screenshotEvent.ts;
88
88
  }
89
- // TODO(crbug/41484172): should be readonly
90
89
  export function data() {
91
90
  return {
92
91
  legacySyntheticScreenshots: syntheticScreenshots.length ? syntheticScreenshots : null,
@@ -19,14 +19,14 @@ export function handleEvent(event) {
19
19
  const key = `${isolate}.${scriptId}`;
20
20
  return Platform.MapUtilities.getWithDefault(scriptById, key, () => ({ isolate, scriptId, frame: '', ts: 0 }));
21
21
  };
22
- if (Types.Events.isTargetRundownEvent(event) && event.args.data) {
22
+ if (Types.Events.isRundownScriptCompiled(event) && event.args.data) {
23
23
  const { isolate, scriptId, frame } = event.args.data;
24
24
  const script = getOrMakeScript(isolate, scriptId);
25
25
  script.frame = frame;
26
26
  script.ts = event.ts;
27
27
  return;
28
28
  }
29
- if (Types.Events.isV8SourceRundownEvent(event)) {
29
+ if (Types.Events.isRundownScript(event)) {
30
30
  const { isolate, scriptId, url, sourceUrl, sourceMapUrl, sourceMapUrlElided } = event.args.data;
31
31
  const script = getOrMakeScript(isolate, scriptId);
32
32
  script.url = url;
@@ -46,13 +46,13 @@ export function handleEvent(event) {
46
46
  }
47
47
  return;
48
48
  }
49
- if (Types.Events.isV8SourceRundownSourcesScriptCatchupEvent(event)) {
49
+ if (Types.Events.isRundownScriptSource(event)) {
50
50
  const { isolate, scriptId, sourceText } = event.args.data;
51
51
  const script = getOrMakeScript(isolate, scriptId);
52
52
  script.content = sourceText;
53
53
  return;
54
54
  }
55
- if (Types.Events.isV8SourceRundownSourcesLargeScriptCatchupEvent(event)) {
55
+ if (Types.Events.isRundownScriptSourceLarge(event)) {
56
56
  const { isolate, scriptId, sourceText } = event.args.data;
57
57
  const script = getOrMakeScript(isolate, scriptId);
58
58
  script.content = (script.content ?? '') + sourceText;
@@ -4,13 +4,8 @@
4
4
  import * as Helpers from '../helpers/helpers.js';
5
5
  import * as Types from '../types/types.js';
6
6
  import { data as metaHandlerData } from './MetaHandler.js';
7
- // This handler serves two purposes. It generates a list of events that are
8
- // used to show user clicks in the timeline. It is also used to gather
9
- // EventTimings into Interactions, which we use to show interactions and
10
- // highlight long interactions to the user, along with INP.
11
- // We don't need to know which process / thread these events occurred in,
12
- // because they are effectively global, so we just track all that we find.
13
- let allEvents = [];
7
+ // This handler gathers EventTimings into Interactions, which we use to show
8
+ // interactions and highlight long interactions to the user, along with INP.
14
9
  let beginCommitCompositorFrameEvents = [];
15
10
  let parseMetaViewportEvents = [];
16
11
  export const LONG_INTERACTION_THRESHOLD = Helpers.Timing.milliToMicro(Types.Timing.Milli(200));
@@ -22,7 +17,6 @@ let interactionEventsWithNoNesting = [];
22
17
  let eventTimingEndEventsById = new Map();
23
18
  let eventTimingStartEventsForInteractions = [];
24
19
  export function reset() {
25
- allEvents = [];
26
20
  beginCommitCompositorFrameEvents = [];
27
21
  parseMetaViewportEvents = [];
28
22
  interactionEvents = [];
@@ -47,7 +41,6 @@ export function handleEvent(event) {
47
41
  // Store the end event; for each start event that is an interaction, we need the matching end event to calculate the duration correctly.
48
42
  eventTimingEndEventsById.set(event.id, event);
49
43
  }
50
- allEvents.push(event);
51
44
  // From this point on we want to find events that represent interactions.
52
45
  // These events are always start events - those are the ones that contain all
53
46
  // the metadata about the interaction.
@@ -268,7 +261,6 @@ export async function finalize() {
268
261
  }
269
262
  export function data() {
270
263
  return {
271
- allEvents,
272
264
  beginCommitCompositorFrameEvents,
273
265
  parseMetaViewportEvents,
274
266
  interactionEvents,