chrome-devtools-mcp 0.9.0 → 0.10.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 (66) hide show
  1. package/README.md +14 -9
  2. package/build/node_modules/chrome-devtools-frontend/front_end/core/common/Console.js +1 -8
  3. package/build/node_modules/chrome-devtools-frontend/front_end/core/common/ParsedURL.js +10 -20
  4. package/build/node_modules/chrome-devtools-frontend/front_end/core/common/SegmentedRange.js +1 -2
  5. package/build/node_modules/chrome-devtools-frontend/front_end/core/common/Settings.js +3 -0
  6. package/build/node_modules/chrome-devtools-frontend/front_end/core/common/StringOutputStream.js +1 -4
  7. package/build/node_modules/chrome-devtools-frontend/front_end/core/host/AidaClient.js +19 -0
  8. package/build/node_modules/chrome-devtools-frontend/front_end/core/host/DispatchHttpRequestClient.js +54 -0
  9. package/build/node_modules/chrome-devtools-frontend/front_end/core/host/GdpClient.js +6 -51
  10. package/build/node_modules/chrome-devtools-frontend/front_end/core/host/InspectorFrontendHost.js +2 -2
  11. package/build/node_modules/chrome-devtools-frontend/front_end/core/host/InspectorFrontendHostAPI.js +32 -29
  12. package/build/node_modules/chrome-devtools-frontend/front_end/core/host/UserMetrics.js +14 -6
  13. package/build/node_modules/chrome-devtools-frontend/front_end/core/host/host.js +2 -1
  14. package/build/node_modules/chrome-devtools-frontend/front_end/core/protocol_client/CDPConnection.js +17 -0
  15. package/build/node_modules/chrome-devtools-frontend/front_end/core/protocol_client/InspectorBackend.js +68 -188
  16. package/build/node_modules/chrome-devtools-frontend/front_end/core/protocol_client/protocol_client.js +2 -1
  17. package/build/node_modules/chrome-devtools-frontend/front_end/core/sdk/AnimationModel.js +1 -2
  18. package/build/node_modules/chrome-devtools-frontend/front_end/core/sdk/CSSMatchedStyles.js +3 -3
  19. package/build/node_modules/chrome-devtools-frontend/front_end/core/sdk/CSSModel.js +1 -1
  20. package/build/node_modules/chrome-devtools-frontend/front_end/core/sdk/CSSProperty.js +3 -6
  21. package/build/node_modules/chrome-devtools-frontend/front_end/core/sdk/CSSPropertyParserMatchers.js +14 -10
  22. package/build/node_modules/chrome-devtools-frontend/front_end/core/sdk/CSSStyleDeclaration.js +4 -4
  23. package/build/node_modules/chrome-devtools-frontend/front_end/core/sdk/ChildTargetManager.js +5 -33
  24. package/build/node_modules/chrome-devtools-frontend/front_end/core/sdk/Connections.js +9 -46
  25. package/build/node_modules/chrome-devtools-frontend/front_end/core/sdk/DOMModel.js +1 -0
  26. package/build/node_modules/chrome-devtools-frontend/front_end/core/sdk/DebuggerModel.js +1 -2
  27. package/build/node_modules/chrome-devtools-frontend/front_end/core/sdk/EnhancedTracesParser.js +17 -3
  28. package/build/node_modules/chrome-devtools-frontend/front_end/core/sdk/NetworkManager.js +59 -37
  29. package/build/node_modules/chrome-devtools-frontend/front_end/core/sdk/NetworkRequest.js +5 -0
  30. package/build/node_modules/chrome-devtools-frontend/front_end/core/sdk/RehydratingConnection.js +102 -4
  31. package/build/node_modules/chrome-devtools-frontend/front_end/core/sdk/SourceMap.js +2 -3
  32. package/build/node_modules/chrome-devtools-frontend/front_end/core/sdk/sdk-meta.js +8 -1
  33. package/build/node_modules/chrome-devtools-frontend/front_end/generated/InspectorBackendCommands.js +1 -39
  34. package/build/node_modules/chrome-devtools-frontend/front_end/generated/SupportedCSSProperties.js +58 -0
  35. package/build/node_modules/chrome-devtools-frontend/front_end/models/ai_assistance/data_formatters/PerformanceTraceFormatter.js +46 -45
  36. package/build/node_modules/chrome-devtools-frontend/front_end/models/ai_assistance/performance/AIContext.js +10 -25
  37. package/build/node_modules/chrome-devtools-frontend/front_end/models/bindings/CompilerScriptMapping.js +1 -1
  38. package/build/node_modules/chrome-devtools-frontend/front_end/models/bindings/DebuggerWorkspaceBinding.js +1 -1
  39. package/build/node_modules/chrome-devtools-frontend/front_end/models/cpu_profile/ProfileTreeModel.js +6 -7
  40. package/build/node_modules/chrome-devtools-frontend/front_end/models/stack_trace/StackTraceModel.js +1 -1
  41. package/build/node_modules/chrome-devtools-frontend/front_end/models/trace/handlers/NetworkRequestsHandler.js +12 -3
  42. package/build/node_modules/chrome-devtools-frontend/front_end/models/trace/types/TraceEvents.js +3 -0
  43. package/build/node_modules/chrome-devtools-frontend/front_end/models/trace_source_maps_resolver/SourceMapsResolver.js +1 -1
  44. package/build/node_modules/chrome-devtools-frontend/mcp/mcp.js +14 -0
  45. package/build/src/DevToolsConnectionAdapter.js +1 -0
  46. package/build/src/DevtoolsUtils.js +44 -0
  47. package/build/src/McpContext.js +117 -7
  48. package/build/src/McpResponse.js +32 -21
  49. package/build/src/PageCollector.js +21 -9
  50. package/build/src/browser.js +8 -8
  51. package/build/src/cli.js +8 -3
  52. package/build/src/formatters/networkFormatter.js +2 -2
  53. package/build/src/formatters/snapshotFormatter.js +18 -6
  54. package/build/src/main.js +7 -2
  55. package/build/src/third_party/THIRD_PARTY_NOTICES +72 -52
  56. package/build/src/third_party/index.js +12684 -6052
  57. package/build/src/tools/emulation.js +37 -44
  58. package/build/src/tools/input.js +36 -6
  59. package/build/src/tools/network.js +27 -5
  60. package/build/src/tools/pages.js +59 -33
  61. package/build/src/tools/performance.js +5 -2
  62. package/build/src/tools/screenshot.js +2 -1
  63. package/build/src/tools/snapshot.js +13 -4
  64. package/build/src/trace-processing/parse.js +6 -16
  65. package/build/src/utils/keyboard.js +291 -0
  66. package/package.json +7 -6
@@ -2,39 +2,6 @@
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
4
  // File is generated by scripts/build/code_generator_frontend.py
5
- /**
6
- * @typedef {{
7
- * registerCommand: function(
8
- * string&any,
9
- * !Array.<!{
10
- * name: string,
11
- * type: string,
12
- * optional: boolean,
13
- * description: string,
14
- * typeRef: string | null
15
- * }>,
16
- * !Array.<string>,
17
- * string
18
- * ): void,
19
- * registerEnum: function(string&any, !Object<string, string>): void,
20
- * registerEvent: function(string&any, !Array<string>): void,
21
- * registerType: function(
22
- * string&any,
23
- * !Array.<!{
24
- * name: string,
25
- * type: string,
26
- * optional: boolean,
27
- * description: string,
28
- * typeRef: string | null
29
- * }>
30
- * ): void,
31
- * }}
32
- */
33
- // @ts-expect-error typedef
34
- export let InspectorBackendAPI;
35
- /**
36
- * @param {!InspectorBackendAPI} inspectorBackend
37
- */
38
5
  export function registerCommands(inspectorBackend) {
39
6
  // Accessibility.
40
7
  inspectorBackend.registerEnum("Accessibility.AXValueType", { Boolean: "boolean", Tristate: "tristate", BooleanOrUndefined: "booleanOrUndefined", Idref: "idref", IdrefList: "idrefList", Integer: "integer", Node: "node", NodeList: "nodeList", Number: "number", String: "string", ComputedString: "computedString", Token: "token", TokenList: "tokenList", DomRelation: "domRelation", Role: "role", InternalRole: "internalRole", ValueUndefined: "valueUndefined" });
@@ -804,10 +771,6 @@ export function registerCommands(inspectorBackend) {
804
771
  inspectorBackend.registerEnum("Network.TrustTokenOperationDoneEventStatus", { Ok: "Ok", InvalidArgument: "InvalidArgument", MissingIssuerKeys: "MissingIssuerKeys", FailedPrecondition: "FailedPrecondition", ResourceExhausted: "ResourceExhausted", AlreadyExists: "AlreadyExists", ResourceLimited: "ResourceLimited", Unauthorized: "Unauthorized", BadResponse: "BadResponse", InternalError: "InternalError", UnknownError: "UnknownError", FulfilledLocally: "FulfilledLocally", SiteIssuerLimit: "SiteIssuerLimit" });
805
772
  inspectorBackend.registerEvent("Network.trustTokenOperationDone", ["status", "type", "requestId", "topLevelOrigin", "issuerOrigin", "issuedTokenCount"]);
806
773
  inspectorBackend.registerEvent("Network.policyUpdated", []);
807
- inspectorBackend.registerEvent("Network.subresourceWebBundleMetadataReceived", ["requestId", "urls"]);
808
- inspectorBackend.registerEvent("Network.subresourceWebBundleMetadataError", ["requestId", "errorMessage"]);
809
- inspectorBackend.registerEvent("Network.subresourceWebBundleInnerResponseParsed", ["innerRequestId", "innerRequestURL", "bundleRequestId"]);
810
- inspectorBackend.registerEvent("Network.subresourceWebBundleInnerResponseError", ["innerRequestId", "innerRequestURL", "errorMessage", "bundleRequestId"]);
811
774
  inspectorBackend.registerEvent("Network.reportingApiReportAdded", ["report"]);
812
775
  inspectorBackend.registerEvent("Network.reportingApiReportUpdated", ["report"]);
813
776
  inspectorBackend.registerEvent("Network.reportingApiEndpointsChangedForOrigin", ["origin", "endpoints"]);
@@ -1299,8 +1262,7 @@ export function registerCommands(inspectorBackend) {
1299
1262
  inspectorBackend.registerType("SystemInfo.Size", [{ "name": "width", "type": "number", "optional": false, "description": "Width in pixels.", "typeRef": null }, { "name": "height", "type": "number", "optional": false, "description": "Height in pixels.", "typeRef": null }]);
1300
1263
  inspectorBackend.registerType("SystemInfo.VideoDecodeAcceleratorCapability", [{ "name": "profile", "type": "string", "optional": false, "description": "Video codec profile that is supported, e.g. VP9 Profile 2.", "typeRef": null }, { "name": "maxResolution", "type": "object", "optional": false, "description": "Maximum video dimensions in pixels supported for this |profile|.", "typeRef": "SystemInfo.Size" }, { "name": "minResolution", "type": "object", "optional": false, "description": "Minimum video dimensions in pixels supported for this |profile|.", "typeRef": "SystemInfo.Size" }]);
1301
1264
  inspectorBackend.registerType("SystemInfo.VideoEncodeAcceleratorCapability", [{ "name": "profile", "type": "string", "optional": false, "description": "Video codec profile that is supported, e.g H264 Main.", "typeRef": null }, { "name": "maxResolution", "type": "object", "optional": false, "description": "Maximum video dimensions in pixels supported for this |profile|.", "typeRef": "SystemInfo.Size" }, { "name": "maxFramerateNumerator", "type": "number", "optional": false, "description": "Maximum encoding framerate in frames per second supported for this |profile|, as fraction's numerator and denominator, e.g. 24/1 fps, 24000/1001 fps, etc.", "typeRef": null }, { "name": "maxFramerateDenominator", "type": "number", "optional": false, "description": "", "typeRef": null }]);
1302
- inspectorBackend.registerType("SystemInfo.ImageDecodeAcceleratorCapability", [{ "name": "imageType", "type": "string", "optional": false, "description": "Image coded, e.g. Jpeg.", "typeRef": "SystemInfo.ImageType" }, { "name": "maxDimensions", "type": "object", "optional": false, "description": "Maximum supported dimensions of the image in pixels.", "typeRef": "SystemInfo.Size" }, { "name": "minDimensions", "type": "object", "optional": false, "description": "Minimum supported dimensions of the image in pixels.", "typeRef": "SystemInfo.Size" }, { "name": "subsamplings", "type": "array", "optional": false, "description": "Optional array of supported subsampling formats, e.g. 4:2:0, if known.", "typeRef": "SystemInfo.SubsamplingFormat" }]);
1303
- inspectorBackend.registerType("SystemInfo.GPUInfo", [{ "name": "devices", "type": "array", "optional": false, "description": "The graphics devices on the system. Element 0 is the primary GPU.", "typeRef": "SystemInfo.GPUDevice" }, { "name": "auxAttributes", "type": "object", "optional": true, "description": "An optional dictionary of additional GPU related attributes.", "typeRef": null }, { "name": "featureStatus", "type": "object", "optional": true, "description": "An optional dictionary of graphics features and their status.", "typeRef": null }, { "name": "driverBugWorkarounds", "type": "array", "optional": false, "description": "An optional array of GPU driver bug workarounds.", "typeRef": "string" }, { "name": "videoDecoding", "type": "array", "optional": false, "description": "Supported accelerated video decoding capabilities.", "typeRef": "SystemInfo.VideoDecodeAcceleratorCapability" }, { "name": "videoEncoding", "type": "array", "optional": false, "description": "Supported accelerated video encoding capabilities.", "typeRef": "SystemInfo.VideoEncodeAcceleratorCapability" }, { "name": "imageDecoding", "type": "array", "optional": false, "description": "Supported accelerated image decoding capabilities.", "typeRef": "SystemInfo.ImageDecodeAcceleratorCapability" }]);
1265
+ inspectorBackend.registerType("SystemInfo.GPUInfo", [{ "name": "devices", "type": "array", "optional": false, "description": "The graphics devices on the system. Element 0 is the primary GPU.", "typeRef": "SystemInfo.GPUDevice" }, { "name": "auxAttributes", "type": "object", "optional": true, "description": "An optional dictionary of additional GPU related attributes.", "typeRef": null }, { "name": "featureStatus", "type": "object", "optional": true, "description": "An optional dictionary of graphics features and their status.", "typeRef": null }, { "name": "driverBugWorkarounds", "type": "array", "optional": false, "description": "An optional array of GPU driver bug workarounds.", "typeRef": "string" }, { "name": "videoDecoding", "type": "array", "optional": false, "description": "Supported accelerated video decoding capabilities.", "typeRef": "SystemInfo.VideoDecodeAcceleratorCapability" }, { "name": "videoEncoding", "type": "array", "optional": false, "description": "Supported accelerated video encoding capabilities.", "typeRef": "SystemInfo.VideoEncodeAcceleratorCapability" }]);
1304
1266
  inspectorBackend.registerType("SystemInfo.ProcessInfo", [{ "name": "type", "type": "string", "optional": false, "description": "Specifies process type.", "typeRef": null }, { "name": "id", "type": "number", "optional": false, "description": "Specifies process id.", "typeRef": null }, { "name": "cpuTime", "type": "number", "optional": false, "description": "Specifies cumulative CPU usage in seconds across all threads of the process since the process start.", "typeRef": null }]);
1305
1267
  // Target.
1306
1268
  inspectorBackend.registerEnum("Target.WindowState", { Normal: "normal", Minimized: "minimized", Maximized: "maximized", Fullscreen: "fullscreen" });
@@ -420,6 +420,7 @@ export const generatedProperties = [
420
420
  "column-rule-color",
421
421
  "column-rule-outset",
422
422
  "column-rule-style",
423
+ "column-rule-visibility-items",
423
424
  "column-rule-width",
424
425
  "column-span",
425
426
  "column-width",
@@ -517,6 +518,7 @@ export const generatedProperties = [
517
518
  "inset-block-start",
518
519
  "inset-inline-end",
519
520
  "inset-inline-start",
521
+ "interactivity",
520
522
  "interest-delay-end",
521
523
  "interest-delay-start",
522
524
  "interpolate-size",
@@ -638,6 +640,7 @@ export const generatedProperties = [
638
640
  "row-rule-color",
639
641
  "row-rule-outset",
640
642
  "row-rule-style",
643
+ "row-rule-visibility-items",
641
644
  "row-rule-width",
642
645
  "ruby-align",
643
646
  "ruby-overhang",
@@ -719,6 +722,7 @@ export const generatedProperties = [
719
722
  "text-emphasis-style",
720
723
  "text-grow",
721
724
  "text-indent",
725
+ "text-justify",
722
726
  "text-orientation",
723
727
  "text-overflow",
724
728
  "text-rendering",
@@ -1735,6 +1739,16 @@ export const generatedProperties = [
1735
1739
  ],
1736
1740
  "name": "column-rule-style"
1737
1741
  },
1742
+ {
1743
+ "inherited": false,
1744
+ "keywords": [
1745
+ "all",
1746
+ "around",
1747
+ "between",
1748
+ "none"
1749
+ ],
1750
+ "name": "column-rule-visibility-items"
1751
+ },
1738
1752
  {
1739
1753
  "keywords": [
1740
1754
  "thin",
@@ -3713,6 +3727,16 @@ export const generatedProperties = [
3713
3727
  ],
3714
3728
  "name": "row-rule-style"
3715
3729
  },
3730
+ {
3731
+ "inherited": false,
3732
+ "keywords": [
3733
+ "all",
3734
+ "around",
3735
+ "between",
3736
+ "none"
3737
+ ],
3738
+ "name": "row-rule-visibility-items"
3739
+ },
3716
3740
  {
3717
3741
  "keywords": [
3718
3742
  "thin",
@@ -4320,6 +4344,16 @@ export const generatedProperties = [
4320
4344
  "inherited": true,
4321
4345
  "name": "text-indent"
4322
4346
  },
4347
+ {
4348
+ "inherited": true,
4349
+ "keywords": [
4350
+ "auto",
4351
+ "none",
4352
+ "inter-word",
4353
+ "inter-character"
4354
+ ],
4355
+ "name": "text-justify"
4356
+ },
4323
4357
  {
4324
4358
  "inherited": true,
4325
4359
  "keywords": [
@@ -5363,6 +5397,14 @@ export const generatedPropertyValues = {
5363
5397
  "double"
5364
5398
  ]
5365
5399
  },
5400
+ "column-rule-visibility-items": {
5401
+ "values": [
5402
+ "all",
5403
+ "around",
5404
+ "between",
5405
+ "none"
5406
+ ]
5407
+ },
5366
5408
  "column-rule-width": {
5367
5409
  "values": [
5368
5410
  "thin",
@@ -6479,6 +6521,14 @@ export const generatedPropertyValues = {
6479
6521
  "double"
6480
6522
  ]
6481
6523
  },
6524
+ "row-rule-visibility-items": {
6525
+ "values": [
6526
+ "all",
6527
+ "around",
6528
+ "between",
6529
+ "none"
6530
+ ]
6531
+ },
6482
6532
  "row-rule-width": {
6483
6533
  "values": [
6484
6534
  "thin",
@@ -6772,6 +6822,14 @@ export const generatedPropertyValues = {
6772
6822
  "currentcolor"
6773
6823
  ]
6774
6824
  },
6825
+ "text-justify": {
6826
+ "values": [
6827
+ "auto",
6828
+ "none",
6829
+ "inter-word",
6830
+ "inter-character"
6831
+ ]
6832
+ },
6775
6833
  "text-orientation": {
6776
6834
  "values": [
6777
6835
  "sideways",
@@ -15,7 +15,7 @@ export class PerformanceTraceFormatter {
15
15
  constructor(focus) {
16
16
  this.#focus = focus;
17
17
  this.#parsedTrace = focus.parsedTrace;
18
- this.#insightSet = focus.insightSet;
18
+ this.#insightSet = focus.primaryInsightSet;
19
19
  this.#eventsSerializer = focus.eventsSerializer;
20
20
  }
21
21
  serializeEvent(event) {
@@ -91,58 +91,62 @@ export class PerformanceTraceFormatter {
91
91
  }
92
92
  formatTraceSummary() {
93
93
  const parsedTrace = this.#parsedTrace;
94
- const insightSet = this.#insightSet;
95
94
  const traceMetadata = this.#parsedTrace.metadata;
96
95
  const data = parsedTrace.data;
97
96
  const parts = [];
98
- const lcp = insightSet ? Trace.Insights.Common.getLCP(insightSet) : null;
99
- const cls = insightSet ? Trace.Insights.Common.getCLS(insightSet) : null;
100
- const inp = insightSet ? Trace.Insights.Common.getINP(insightSet) : null;
101
97
  parts.push(`URL: ${data.Meta.mainFrameURL}`);
102
- parts.push(`Bounds: ${this.serializeBounds(data.Meta.traceBounds)}`);
98
+ parts.push(`Trace bounds: ${this.serializeBounds(data.Meta.traceBounds)}`);
103
99
  parts.push('CPU throttling: ' + (traceMetadata.cpuThrottling ? `${traceMetadata.cpuThrottling}x` : 'none'));
104
100
  parts.push(`Network throttling: ${traceMetadata.networkThrottling ?? 'none'}`);
105
- if (lcp || cls || inp) {
106
- parts.push('Metrics (lab / observed):');
107
- if (lcp) {
108
- const nodeId = insightSet?.model.LCPBreakdown.lcpEvent?.args.data?.nodeId;
109
- const nodeIdText = nodeId !== undefined ? `, nodeId: ${nodeId}` : '';
110
- parts.push(` - LCP: ${Math.round(lcp.value / 1000)} ms, event: ${this.serializeEvent(lcp.event)}${nodeIdText}`);
111
- const subparts = insightSet?.model.LCPBreakdown.subparts;
112
- if (subparts) {
113
- const serializeSubpart = (subpart) => {
114
- return `${micros(subpart.range)}, bounds: ${this.serializeBounds(subpart)}`;
115
- };
116
- parts.push(' - LCP breakdown:');
117
- parts.push(` - TTFB: ${serializeSubpart(subparts.ttfb)}`);
118
- if (subparts.loadDelay !== undefined) {
119
- parts.push(` - Load delay: ${serializeSubpart(subparts.loadDelay)}`);
120
- }
121
- if (subparts.loadDuration !== undefined) {
122
- parts.push(` - Load duration: ${serializeSubpart(subparts.loadDuration)}`);
101
+ parts.push('\n# Available insight sets\n');
102
+ parts.push('The following is a list of insight sets. An insight set covers a specific part of the trace, split by navigations. The insights within each insight set are specific to that part of the trace. Be sure to consider the insight set id and bounds when calling functions. If no specific insight set or navigation is mentioned, assume the user is referring to the first one.');
103
+ for (const insightSet of parsedTrace.insights?.values() ?? []) {
104
+ const lcp = insightSet ? Trace.Insights.Common.getLCP(insightSet) : null;
105
+ const cls = insightSet ? Trace.Insights.Common.getCLS(insightSet) : null;
106
+ const inp = insightSet ? Trace.Insights.Common.getINP(insightSet) : null;
107
+ parts.push(`\n## insight set id: ${insightSet.id}\n`);
108
+ parts.push(`URL: ${insightSet.url}`);
109
+ parts.push(`Bounds: ${this.serializeBounds(insightSet.bounds)}`);
110
+ if (lcp || cls || inp) {
111
+ parts.push('Metrics (lab / observed):');
112
+ if (lcp) {
113
+ const nodeId = insightSet?.model.LCPBreakdown.lcpEvent?.args.data?.nodeId;
114
+ const nodeIdText = nodeId !== undefined ? `, nodeId: ${nodeId}` : '';
115
+ parts.push(` - LCP: ${Math.round(lcp.value / 1000)} ms, event: ${this.serializeEvent(lcp.event)}${nodeIdText}`);
116
+ const subparts = insightSet?.model.LCPBreakdown.subparts;
117
+ if (subparts) {
118
+ const serializeSubpart = (subpart) => {
119
+ return `${micros(subpart.range)}, bounds: ${this.serializeBounds(subpart)}`;
120
+ };
121
+ parts.push(' - LCP breakdown:');
122
+ parts.push(` - TTFB: ${serializeSubpart(subparts.ttfb)}`);
123
+ if (subparts.loadDelay !== undefined) {
124
+ parts.push(` - Load delay: ${serializeSubpart(subparts.loadDelay)}`);
125
+ }
126
+ if (subparts.loadDuration !== undefined) {
127
+ parts.push(` - Load duration: ${serializeSubpart(subparts.loadDuration)}`);
128
+ }
129
+ parts.push(` - Render delay: ${serializeSubpart(subparts.renderDelay)}`);
123
130
  }
124
- parts.push(` - Render delay: ${serializeSubpart(subparts.renderDelay)}`);
131
+ }
132
+ if (inp) {
133
+ parts.push(` - INP: ${Math.round(inp.value / 1000)} ms, event: ${this.serializeEvent(inp.event)}`);
134
+ }
135
+ if (cls) {
136
+ const eventText = cls.worstClusterEvent ? `, event: ${this.serializeEvent(cls.worstClusterEvent)}` : '';
137
+ parts.push(` - CLS: ${cls.value.toFixed(2)}${eventText}`);
125
138
  }
126
139
  }
127
- if (inp) {
128
- parts.push(` - INP: ${Math.round(inp.value / 1000)} ms, event: ${this.serializeEvent(inp.event)}`);
140
+ else {
141
+ parts.push('Metrics (lab / observed): n/a');
129
142
  }
130
- if (cls) {
131
- const eventText = cls.worstClusterEvent ? `, event: ${this.serializeEvent(cls.worstClusterEvent)}` : '';
132
- parts.push(` - CLS: ${cls.value.toFixed(2)}${eventText}`);
143
+ const cruxParts = insightSet && this.#getCruxTraceSummary(insightSet);
144
+ if (cruxParts?.length) {
145
+ parts.push(...cruxParts);
146
+ }
147
+ else {
148
+ parts.push('Metrics (field / real users): n/a – no data for this page in CrUX');
133
149
  }
134
- }
135
- else {
136
- parts.push('Metrics (lab / observed): n/a');
137
- }
138
- const cruxParts = insightSet && this.#getCruxTraceSummary(insightSet);
139
- if (cruxParts?.length) {
140
- parts.push(...cruxParts);
141
- }
142
- else {
143
- parts.push('Metrics (field / real users): n/a – no data for this page in CrUX');
144
- }
145
- if (insightSet) {
146
150
  parts.push('Available insights:');
147
151
  for (const [insightName, model] of Object.entries(insightSet.model)) {
148
152
  if (model.state === 'pass') {
@@ -172,9 +176,6 @@ export class PerformanceTraceFormatter {
172
176
  parts.push(` - ${insightPartsText}`);
173
177
  }
174
178
  }
175
- else {
176
- parts.push('Available insights: none');
177
- }
178
179
  return parts.join('\n');
179
180
  }
180
181
  formatCriticalRequests() {
@@ -9,11 +9,8 @@ import { AICallTree } from './AICallTree.js';
9
9
  * 2. If there are more, prefer the first we find that has a navigation associated with it.
10
10
  * 3. If none with a navigation are found, fallback to the first one.
11
11
  * 4. Otherwise, return null.
12
- *
13
- * TODO(cjamcl): we should just give the agent the entire insight set, and give
14
- * summary detail about all of them + the ability to query each.
15
12
  */
16
- function getFirstInsightSet(insights) {
13
+ function getPrimaryInsightSet(insights) {
17
14
  const insightSets = Array.from(insights.values());
18
15
  if (insightSets.length === 0) {
19
16
  return null;
@@ -28,10 +25,8 @@ export class AgentFocus {
28
25
  if (!parsedTrace.insights) {
29
26
  throw new Error('missing insights');
30
27
  }
31
- const insightSet = getFirstInsightSet(parsedTrace.insights);
32
28
  return new AgentFocus({
33
29
  parsedTrace,
34
- insightSet,
35
30
  event: null,
36
31
  callTree: null,
37
32
  insight: null,
@@ -41,10 +36,8 @@ export class AgentFocus {
41
36
  if (!parsedTrace.insights) {
42
37
  throw new Error('missing insights');
43
38
  }
44
- const insightSet = getFirstInsightSet(parsedTrace.insights);
45
39
  return new AgentFocus({
46
40
  parsedTrace,
47
- insightSet,
48
41
  event: null,
49
42
  callTree: null,
50
43
  insight,
@@ -54,35 +47,27 @@ export class AgentFocus {
54
47
  if (!parsedTrace.insights) {
55
48
  throw new Error('missing insights');
56
49
  }
57
- const insightSet = getFirstInsightSet(parsedTrace.insights);
58
50
  const result = AgentFocus.#getCallTreeOrEvent(parsedTrace, event);
59
- return new AgentFocus({ parsedTrace, insightSet, event: result.event, callTree: result.callTree, insight: null });
51
+ return new AgentFocus({ parsedTrace, event: result.event, callTree: result.callTree, insight: null });
60
52
  }
61
53
  static fromCallTree(callTree) {
62
- const insights = callTree.parsedTrace.insights;
63
- // Select the insight set containing the call tree.
64
- // If for some reason that fails, fallback to the first one.
65
- let insightSet = null;
66
- if (insights) {
67
- const callTreeTimeRange = Trace.Helpers.Timing.traceWindowFromEvent(callTree.rootNode.event);
68
- insightSet = insights.values().find(set => Trace.Helpers.Timing.boundsIncludeTimeRange({
69
- timeRange: callTreeTimeRange,
70
- bounds: set.bounds,
71
- })) ??
72
- getFirstInsightSet(insights);
73
- }
74
- return new AgentFocus({ parsedTrace: callTree.parsedTrace, insightSet, event: null, callTree, insight: null });
54
+ return new AgentFocus({ parsedTrace: callTree.parsedTrace, event: null, callTree, insight: null });
75
55
  }
76
56
  #data;
57
+ #primaryInsightSet;
77
58
  eventsSerializer = new Trace.EventsSerializer.EventsSerializer();
78
59
  constructor(data) {
60
+ if (!data.parsedTrace.insights) {
61
+ throw new Error('missing insights');
62
+ }
79
63
  this.#data = data;
64
+ this.#primaryInsightSet = getPrimaryInsightSet(data.parsedTrace.insights);
80
65
  }
81
66
  get parsedTrace() {
82
67
  return this.#data.parsedTrace;
83
68
  }
84
- get insightSet() {
85
- return this.#data.insightSet;
69
+ get primaryInsightSet() {
70
+ return this.#primaryInsightSet;
86
71
  }
87
72
  /** Note: at most one of event or callTree is non-null. */
88
73
  get event() {
@@ -4,7 +4,7 @@
4
4
  import * as Common from '../../core/common/common.js';
5
5
  import * as Platform from '../../core/platform/platform.js';
6
6
  import * as SDK from '../../core/sdk/sdk.js';
7
- // eslint-disable-next-line rulesdir/es-modules-import
7
+ // eslint-disable-next-line @devtools/es-modules-import
8
8
  import * as StackTraceImpl from '../stack_trace/stack_trace_impl.js';
9
9
  import * as TextUtils from '../text_utils/text_utils.js';
10
10
  import * as Workspace from '../workspace/workspace.js';
@@ -4,7 +4,7 @@
4
4
  import * as Common from '../../core/common/common.js';
5
5
  import * as Platform from '../../core/platform/platform.js';
6
6
  import * as SDK from '../../core/sdk/sdk.js';
7
- // eslint-disable-next-line rulesdir/es-modules-import
7
+ // eslint-disable-next-line @devtools/es-modules-import
8
8
  import * as StackTraceImpl from '../stack_trace/stack_trace_impl.js';
9
9
  import * as Workspace from '../workspace/workspace.js';
10
10
  import { CompilerScriptMapping } from './CompilerScriptMapping.js';
@@ -9,7 +9,7 @@ export class ProfileNode {
9
9
  id;
10
10
  parent;
11
11
  children;
12
- functionName;
12
+ originalFunctionName = null;
13
13
  depth;
14
14
  deoptReason;
15
15
  constructor(callFrame) {
@@ -18,7 +18,6 @@ export class ProfileNode {
18
18
  this.self = 0;
19
19
  this.total = 0;
20
20
  this.id = 0;
21
- this.functionName = callFrame.functionName;
22
21
  this.parent = null;
23
22
  this.children = [];
24
23
  }
@@ -34,11 +33,11 @@ export class ProfileNode {
34
33
  get columnNumber() {
35
34
  return this.callFrame.columnNumber;
36
35
  }
37
- setFunctionName(name) {
38
- if (name === null) {
39
- return;
40
- }
41
- this.functionName = name;
36
+ get functionName() {
37
+ return this.originalFunctionName ?? this.callFrame.functionName;
38
+ }
39
+ setOriginalFunctionName(name) {
40
+ this.originalFunctionName = name;
42
41
  }
43
42
  }
44
43
  export class ProfileTreeModel {
@@ -2,7 +2,7 @@
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
4
  import * as SDK from '../../core/sdk/sdk.js';
5
- // eslint-disable-next-line rulesdir/es-modules-import
5
+ // eslint-disable-next-line @devtools/es-modules-import
6
6
  import * as StackTrace from './stack_trace.js';
7
7
  import { AsyncFragmentImpl, FragmentImpl, FrameImpl, StackTraceImpl } from './StackTraceImpl.js';
8
8
  import { Trie } from './Trie.js';
@@ -98,6 +98,9 @@ export function handleEvent(event) {
98
98
  storeTraceEventWithRequestId(event.args.data.requestId, 'resourceMarkAsCached', event);
99
99
  return;
100
100
  }
101
+ if (Types.Events.isPreloadRenderBlockingStatusChangeEvent(event)) {
102
+ storeTraceEventWithRequestId(event.args.data.requestId, 'preloadRenderBlockingStatusChange', [event]);
103
+ }
101
104
  if (Types.Events.isWebSocketCreate(event) || Types.Events.isWebSocketInfo(event) ||
102
105
  Types.Events.isWebSocketTransfer(event)) {
103
106
  const identifier = event.args.data.identifier;
@@ -387,11 +390,18 @@ export async function finalize() {
387
390
  Types.Timing.Micro((timing.connectEnd - timing.connectStart) * MILLISECONDS_TO_MICROSECONDS) :
388
391
  Types.Timing.Micro(0);
389
392
  // Finally get some of the general data from the trace events.
390
- const { frame, url, renderBlocking } = finalSendRequest.args.data;
393
+ const { frame, url, renderBlocking: sendRequestIsRenderBlocking } = finalSendRequest.args.data;
391
394
  const { encodedDataLength, decodedBodyLength } = request.resourceFinish ? request.resourceFinish.args.data : { encodedDataLength: 0, decodedBodyLength: 0 };
392
395
  const parsedUrl = new URL(url);
393
396
  const isHttps = parsedUrl.protocol === 'https:';
394
397
  const requestingFrameUrl = Helpers.Trace.activeURLForFrameAtTime(frame, finalSendRequest.ts, rendererProcessesByFrame) || '';
398
+ // A resource that is preloaded (and not marked as render blocking) can
399
+ // become render blocked later via a PreloadRenderBlockingStatusChange. In
400
+ // this case, we take the render blocking value of the last
401
+ // PreloadRenderBlockingStatusChange for this request.
402
+ const preloadRenderBlockingStatusChange = request.preloadRenderBlockingStatusChange?.at(-1)?.args.data.renderBlocking;
403
+ // In the event the property isn't set, assume non-blocking.
404
+ const isRenderBlocking = preloadRenderBlockingStatusChange ?? sendRequestIsRenderBlocking ?? 'non_blocking';
395
405
  // Construct a synthetic trace event for this network request.
396
406
  const networkEvent = Helpers.SyntheticEvents.SyntheticEventsManager.registerSyntheticEvent({
397
407
  rawSourceEvent: finalSendRequest,
@@ -432,8 +442,7 @@ export async function finalize() {
432
442
  initialPriority,
433
443
  protocol: request.receiveResponse?.args.data.protocol ?? 'unknown',
434
444
  redirects,
435
- // In the event the property isn't set, assume non-blocking.
436
- renderBlocking: renderBlocking ?? 'non_blocking',
445
+ renderBlocking: isRenderBlocking,
437
446
  requestId,
438
447
  requestingFrameUrl,
439
448
  requestMethod: finalSendRequest.args.data.requestMethod,
@@ -574,3 +574,6 @@ export function isRundownScriptSourceLarge(event) {
574
574
  export function isAnyScriptSourceEvent(event) {
575
575
  return event.cat === 'disabled-by-default-devtools.v8-source-rundown-sources';
576
576
  }
577
+ export function isPreloadRenderBlockingStatusChangeEvent(event) {
578
+ return event.name === "PreloadRenderBlockingStatusChange" /* Name.PRELOAD_RENDER_BLOCKING_STATUS_CHANGE */;
579
+ }
@@ -142,7 +142,7 @@ export class SourceMapsResolver extends EventTarget {
142
142
  for (const node of nodes) {
143
143
  const resolvedFunctionName = await SourceMapScopes.NamesResolver.resolveProfileFrameFunctionName(node.callFrame, target);
144
144
  updatedMappings ||= Boolean(resolvedFunctionName);
145
- node.setFunctionName(resolvedFunctionName);
145
+ node.setOriginalFunctionName(resolvedFunctionName);
146
146
  const debuggerModel = target.model(SDK.DebuggerModel.DebuggerModel);
147
147
  const script = debuggerModel?.scriptForId(node.scriptId) || null;
148
148
  const location = debuggerModel &&
@@ -0,0 +1,14 @@
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
+ /**
5
+ * IMPORTANT! Make sure that any class that is exported here has related unit
6
+ * tests added to foundation_unittests. See front_end/core/i18n/BUILD.gn as an
7
+ * example.
8
+ */
9
+ export * as I18n from '../front_end/core/i18n/i18n.js';
10
+ export { ConnectionTransport } from '../front_end/core/protocol_client/ConnectionTransport.js';
11
+ export { PerformanceInsightFormatter } from '../front_end/models/ai_assistance/data_formatters/PerformanceInsightFormatter.js';
12
+ export { PerformanceTraceFormatter } from '../front_end/models/ai_assistance/data_formatters/PerformanceTraceFormatter.js';
13
+ export { AgentFocus } from '../front_end/models/ai_assistance/performance/AIContext.js';
14
+ export * as TraceEngine from '../front_end/models/trace/trace.js';
@@ -3,6 +3,7 @@
3
3
  * Copyright 2025 Google LLC
4
4
  * SPDX-License-Identifier: Apache-2.0
5
5
  */
6
+ // eslint-disable-next-line no-restricted-imports
6
7
  import { ConnectionTransport as DevToolsConnectionTransport } from '../node_modules/chrome-devtools-frontend/front_end/core/protocol_client/ConnectionTransport.js';
7
8
  /**
8
9
  * Allows a puppeteer {@link ConnectionTransport} to act like a DevTools {@link Connection}.
@@ -0,0 +1,44 @@
1
+ /**
2
+ * @license
3
+ * Copyright 2025 Google LLC
4
+ * SPDX-License-Identifier: Apache-2.0
5
+ */
6
+ export function extractUrlLikeFromDevToolsTitle(title) {
7
+ const match = title.match(new RegExp(`DevTools - (.*)`));
8
+ return match?.[1] ?? undefined;
9
+ }
10
+ export function urlsEqual(url1, url2) {
11
+ const normalizedUrl1 = normalizeUrl(url1);
12
+ const normalizedUrl2 = normalizeUrl(url2);
13
+ return normalizedUrl1 === normalizedUrl2;
14
+ }
15
+ /**
16
+ * For the sake of the MCP server, when we determine if two URLs are equal we
17
+ * remove some parts:
18
+ *
19
+ * 1. We do not care about the protocol.
20
+ * 2. We do not care about trailing slashes.
21
+ * 3. We do not care about "www".
22
+ *
23
+ * For example, if the user types "record a trace on foo.com", we would want to
24
+ * match a tab in the connected Chrome instance that is showing "www.foo.com/"
25
+ */
26
+ function normalizeUrl(url) {
27
+ let result = url.trim();
28
+ // Remove protocols
29
+ if (result.startsWith('https://')) {
30
+ result = result.slice(8);
31
+ }
32
+ else if (result.startsWith('http://')) {
33
+ result = result.slice(7);
34
+ }
35
+ // Remove 'www.'. This ensures that we find the right URL regardless of if the user adds `www` or not.
36
+ if (result.startsWith('www.')) {
37
+ result = result.slice(4);
38
+ }
39
+ // Remove trailing slash
40
+ if (result.endsWith('/')) {
41
+ result = result.slice(0, -1);
42
+ }
43
+ return result;
44
+ }