chrome-devtools-frontend 1.0.1614363 → 1.0.1615539

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 (27) hide show
  1. package/docs/contributing/infrastructure.md +0 -1
  2. package/front_end/core/common/MapWithDefault.ts +2 -2
  3. package/front_end/core/common/Object.ts +9 -6
  4. package/front_end/entrypoints/greendev_floaty/floaty.css +3 -0
  5. package/front_end/generated/InspectorBackendCommands.ts +4 -2
  6. package/front_end/generated/protocol-mapping.d.ts +14 -0
  7. package/front_end/generated/protocol-proxy-api.d.ts +10 -0
  8. package/front_end/generated/protocol.ts +28 -0
  9. package/front_end/models/ai_assistance/agents/PerformanceAgent.snapshot.txt +4 -1
  10. package/front_end/models/ai_assistance/agents/PerformanceAgent.ts +39 -10
  11. package/front_end/models/issues_manager/IssuesManager.ts +4 -0
  12. package/front_end/models/stack_trace/DetailedErrorStackParser.ts +19 -0
  13. package/front_end/models/stack_trace/StackTraceModel.ts +39 -0
  14. package/front_end/panels/ai_assistance/components/AccessibilityAgentMarkdownRenderer.ts +55 -14
  15. package/front_end/panels/application/WebMCPView.ts +21 -1
  16. package/front_end/panels/application/webMCPView.css +4 -1
  17. package/front_end/panels/console/ConsoleViewMessage.ts +8 -2
  18. package/front_end/panels/console/consoleView.css +4 -0
  19. package/front_end/panels/protocol_monitor/JSONEditor.ts +1 -1
  20. package/front_end/panels/recorder/models/RecordingPlayer.ts +1 -1
  21. package/front_end/services/puppeteer/PuppeteerConnection.ts +1 -1
  22. package/front_end/third_party/chromium/README.chromium +1 -1
  23. package/front_end/ui/kit/icons/Icon.ts +1 -0
  24. package/front_end/ui/legacy/Treeoutline.ts +3 -2
  25. package/front_end/ui/legacy/components/data_grid/DataGridElement.ts +4 -3
  26. package/front_end/ui/visual_logging/KnownContextValues.ts +2 -0
  27. package/package.json +2 -2
@@ -112,7 +112,6 @@ for the current logic.
112
112
  Some of the filters currently in use are:
113
113
 
114
114
  - `cpp_debug_extension` builders only trigger on changes related to the extension
115
- - `dtf_check_no_bundle` builder only trigger on GN changes
116
115
  - all other builders will not trigger if only documentation files are updated
117
116
 
118
117
  ## Branch cutting process
@@ -8,7 +8,7 @@
8
8
  * TODO: Once the proposal is merged, just replace `MapWithDefault` with `Map` and remove it.
9
9
  **/
10
10
  export class MapWithDefault<K, V> extends Map<K, V> {
11
- getOrInsert(key: K, defaultValue: V): V {
11
+ override getOrInsert(key: K, defaultValue: V): V {
12
12
  if (!this.has(key)) {
13
13
  this.set(key, defaultValue);
14
14
  }
@@ -16,7 +16,7 @@ export class MapWithDefault<K, V> extends Map<K, V> {
16
16
  return this.get(key) as V;
17
17
  }
18
18
 
19
- getOrInsertComputed(key: K, callbackFunction: (key: K) => V): V {
19
+ override getOrInsertComputed(key: K, callbackFunction: (key: K) => V): V {
20
20
  if (!this.has(key)) {
21
21
  this.set(key, callbackFunction(key));
22
22
  }
@@ -119,31 +119,34 @@ export class ObjectWrapper<Events> implements EventTarget<Events> {
119
119
  export function eventMixin<Events, Base extends Platform.Constructor.Constructor<object>>(base: Base) {
120
120
  console.assert(base !== HTMLElement);
121
121
  return class EventHandling extends base implements EventTarget<Events> {
122
- #events = new ObjectWrapper<Events>();
122
+ // Note that the weird name is due to TSC disallowing private/protected fields in
123
+ // anonmous exported classes. We use a `__` prefix to prevent clashes with `base`.
124
+ // eslint-disable-next-line @devtools/no-underscored-properties, @typescript-eslint/naming-convention
125
+ __events = new ObjectWrapper<Events>();
123
126
 
124
127
  addEventListener<T extends keyof Events>(
125
128
  eventType: T, listener: (arg0: EventTargetEvent<Events[T]>) => void,
126
129
  thisObject?: Object): EventDescriptor<Events, T> {
127
- return this.#events.addEventListener(eventType, listener, thisObject);
130
+ return this.__events.addEventListener(eventType, listener, thisObject);
128
131
  }
129
132
 
130
133
  once<T extends keyof Events>(eventType: T): Promise<Events[T]> {
131
- return this.#events.once(eventType);
134
+ return this.__events.once(eventType);
132
135
  }
133
136
 
134
137
  removeEventListener<T extends keyof Events>(
135
138
  eventType: T, listener: (arg0: EventTargetEvent<Events[T]>) => void, thisObject?: Object): void {
136
- this.#events.removeEventListener(eventType, listener, thisObject);
139
+ this.__events.removeEventListener(eventType, listener, thisObject);
137
140
  }
138
141
 
139
142
  hasEventListeners(eventType: keyof Events): boolean {
140
- return this.#events.hasEventListeners(eventType);
143
+ return this.__events.hasEventListeners(eventType);
141
144
  }
142
145
 
143
146
  dispatchEventToListeners<T extends keyof Events>(
144
147
  eventType: Platform.TypeScriptUtilities.NoUnion<T>,
145
148
  ...eventData: EventPayloadToRestParameters<Events, T>): void {
146
- this.#events.dispatchEventToListeners(eventType, ...eventData);
149
+ this.__events.dispatchEventToListeners(eventType, ...eventData);
147
150
  }
148
151
  };
149
152
  }
@@ -99,6 +99,8 @@ html, body {
99
99
  overflow-wrap: break-word;
100
100
  line-height: 1.4;
101
101
  white-space: pre-wrap; /* Preserve newlines */
102
+ flex-shrink: 0;
103
+ min-height: min-content;
102
104
  }
103
105
 
104
106
  .user-message {
@@ -182,6 +184,7 @@ html, body {
182
184
  flex-direction: column;
183
185
  gap: 8px;
184
186
  border: 1px solid #d3e3fd;
187
+ flex-shrink: 0;
185
188
  }
186
189
 
187
190
  .green-dev-floaty-dialog-node-description {
@@ -808,8 +808,8 @@ inspectorBackend.registerEnum("Network.ReportStatus", {Queued: "Queued", Pending
808
808
  inspectorBackend.registerEnum("Network.DeviceBoundSessionWithUsageUsage", {NotInScope: "NotInScope", InScopeRefreshNotYetNeeded: "InScopeRefreshNotYetNeeded", InScopeRefreshNotAllowed: "InScopeRefreshNotAllowed", ProactiveRefreshNotPossible: "ProactiveRefreshNotPossible", ProactiveRefreshAttempted: "ProactiveRefreshAttempted", Deferred: "Deferred"});
809
809
  inspectorBackend.registerEnum("Network.DeviceBoundSessionUrlRuleRuleType", {Exclude: "Exclude", Include: "Include"});
810
810
  inspectorBackend.registerEnum("Network.DeviceBoundSessionFetchResult", {Success: "Success", KeyError: "KeyError", SigningError: "SigningError", ServerRequestedTermination: "ServerRequestedTermination", InvalidSessionId: "InvalidSessionId", InvalidChallenge: "InvalidChallenge", TooManyChallenges: "TooManyChallenges", InvalidFetcherUrl: "InvalidFetcherUrl", InvalidRefreshUrl: "InvalidRefreshUrl", TransientHttpError: "TransientHttpError", ScopeOriginSameSiteMismatch: "ScopeOriginSameSiteMismatch", RefreshUrlSameSiteMismatch: "RefreshUrlSameSiteMismatch", MismatchedSessionId: "MismatchedSessionId", MissingScope: "MissingScope", NoCredentials: "NoCredentials", SubdomainRegistrationWellKnownUnavailable: "SubdomainRegistrationWellKnownUnavailable", SubdomainRegistrationUnauthorized: "SubdomainRegistrationUnauthorized", SubdomainRegistrationWellKnownMalformed: "SubdomainRegistrationWellKnownMalformed", SessionProviderWellKnownUnavailable: "SessionProviderWellKnownUnavailable", RelyingPartyWellKnownUnavailable: "RelyingPartyWellKnownUnavailable", FederatedKeyThumbprintMismatch: "FederatedKeyThumbprintMismatch", InvalidFederatedSessionUrl: "InvalidFederatedSessionUrl", InvalidFederatedKey: "InvalidFederatedKey", TooManyRelyingOriginLabels: "TooManyRelyingOriginLabels", BoundCookieSetForbidden: "BoundCookieSetForbidden", NetError: "NetError", ProxyError: "ProxyError", EmptySessionConfig: "EmptySessionConfig", InvalidCredentialsConfig: "InvalidCredentialsConfig", InvalidCredentialsType: "InvalidCredentialsType", InvalidCredentialsEmptyName: "InvalidCredentialsEmptyName", InvalidCredentialsCookie: "InvalidCredentialsCookie", PersistentHttpError: "PersistentHttpError", RegistrationAttemptedChallenge: "RegistrationAttemptedChallenge", InvalidScopeOrigin: "InvalidScopeOrigin", ScopeOriginContainsPath: "ScopeOriginContainsPath", RefreshInitiatorNotString: "RefreshInitiatorNotString", RefreshInitiatorInvalidHostPattern: "RefreshInitiatorInvalidHostPattern", InvalidScopeSpecification: "InvalidScopeSpecification", MissingScopeSpecificationType: "MissingScopeSpecificationType", EmptyScopeSpecificationDomain: "EmptyScopeSpecificationDomain", EmptyScopeSpecificationPath: "EmptyScopeSpecificationPath", InvalidScopeSpecificationType: "InvalidScopeSpecificationType", InvalidScopeIncludeSite: "InvalidScopeIncludeSite", MissingScopeIncludeSite: "MissingScopeIncludeSite", FederatedNotAuthorizedByProvider: "FederatedNotAuthorizedByProvider", FederatedNotAuthorizedByRelyingParty: "FederatedNotAuthorizedByRelyingParty", SessionProviderWellKnownMalformed: "SessionProviderWellKnownMalformed", SessionProviderWellKnownHasProviderOrigin: "SessionProviderWellKnownHasProviderOrigin", RelyingPartyWellKnownMalformed: "RelyingPartyWellKnownMalformed", RelyingPartyWellKnownHasRelyingOrigins: "RelyingPartyWellKnownHasRelyingOrigins", InvalidFederatedSessionProviderSessionMissing: "InvalidFederatedSessionProviderSessionMissing", InvalidFederatedSessionWrongProviderOrigin: "InvalidFederatedSessionWrongProviderOrigin", InvalidCredentialsCookieCreationTime: "InvalidCredentialsCookieCreationTime", InvalidCredentialsCookieName: "InvalidCredentialsCookieName", InvalidCredentialsCookieParsing: "InvalidCredentialsCookieParsing", InvalidCredentialsCookieUnpermittedAttribute: "InvalidCredentialsCookieUnpermittedAttribute", InvalidCredentialsCookieInvalidDomain: "InvalidCredentialsCookieInvalidDomain", InvalidCredentialsCookiePrefix: "InvalidCredentialsCookiePrefix", InvalidScopeRulePath: "InvalidScopeRulePath", InvalidScopeRuleHostPattern: "InvalidScopeRuleHostPattern", ScopeRuleOriginScopedHostPatternMismatch: "ScopeRuleOriginScopedHostPatternMismatch", ScopeRuleSiteScopedHostPatternMismatch: "ScopeRuleSiteScopedHostPatternMismatch", SigningQuotaExceeded: "SigningQuotaExceeded", InvalidConfigJson: "InvalidConfigJson", InvalidFederatedSessionProviderFailedToRestoreKey: "InvalidFederatedSessionProviderFailedToRestoreKey", FailedToUnwrapKey: "FailedToUnwrapKey", SessionDeletedDuringRefresh: "SessionDeletedDuringRefresh"});
811
- inspectorBackend.registerEnum("Network.RefreshEventDetailsRefreshResult", {Refreshed: "Refreshed", InitializedService: "InitializedService", Unreachable: "Unreachable", ServerError: "ServerError", RefreshQuotaExceeded: "RefreshQuotaExceeded", FatalError: "FatalError", SigningQuotaExceeded: "SigningQuotaExceeded"});
812
- inspectorBackend.registerEnum("Network.TerminationEventDetailsDeletionReason", {Expired: "Expired", FailedToRestoreKey: "FailedToRestoreKey", FailedToUnwrapKey: "FailedToUnwrapKey", StoragePartitionCleared: "StoragePartitionCleared", ClearBrowsingData: "ClearBrowsingData", ServerRequested: "ServerRequested", InvalidSessionParams: "InvalidSessionParams", RefreshFatalError: "RefreshFatalError"});
811
+ inspectorBackend.registerEnum("Network.RefreshEventDetailsRefreshResult", {Refreshed: "Refreshed", RefreshedAsWaiter: "RefreshedAsWaiter", InitializedService: "InitializedService", Unreachable: "Unreachable", ServerError: "ServerError", RefreshQuotaExceeded: "RefreshQuotaExceeded", FatalError: "FatalError", SigningQuotaExceeded: "SigningQuotaExceeded"});
812
+ inspectorBackend.registerEnum("Network.TerminationEventDetailsDeletionReason", {Expired: "Expired", FailedToRestoreKey: "FailedToRestoreKey", FailedToUnwrapKey: "FailedToUnwrapKey", StoragePartitionCleared: "StoragePartitionCleared", ClearBrowsingData: "ClearBrowsingData", ServerRequested: "ServerRequested", InvalidSessionParams: "InvalidSessionParams", RefreshFatalError: "RefreshFatalError", DevTools: "DevTools"});
813
813
  inspectorBackend.registerEnum("Network.ChallengeEventDetailsChallengeResult", {Success: "Success", NoSessionId: "NoSessionId", NoSessionMatch: "NoSessionMatch", CantSetBoundCookie: "CantSetBoundCookie"});
814
814
  inspectorBackend.registerEvent("Network.dataReceived", ["requestId", "timestamp", "dataLength", "encodedDataLength", "data"]);
815
815
  inspectorBackend.registerEvent("Network.eventSourceMessageReceived", ["requestId", "timestamp", "eventName", "eventId", "data"]);
@@ -893,6 +893,7 @@ inspectorBackend.registerCommand("Network.streamResourceContent", [{"name": "req
893
893
  inspectorBackend.registerCommand("Network.getSecurityIsolationStatus", [{"name": "frameId", "type": "string", "optional": true, "description": "If no frameId is provided, the status of the target is provided.", "typeRef": "Page.FrameId"}], ["status"], "Returns information about the COEP/COOP isolation status.");
894
894
  inspectorBackend.registerCommand("Network.enableReportingApi", [{"name": "enable", "type": "boolean", "optional": false, "description": "Whether to enable or disable events for the Reporting API", "typeRef": null}], [], "Enables tracking for the Reporting API, events generated by the Reporting API will now be delivered to the client. Enabling triggers 'reportingApiReportAdded' for all existing reports.");
895
895
  inspectorBackend.registerCommand("Network.enableDeviceBoundSessions", [{"name": "enable", "type": "boolean", "optional": false, "description": "Whether to enable or disable events.", "typeRef": null}], [], "Sets up tracking device bound sessions and fetching of initial set of sessions.");
896
+ inspectorBackend.registerCommand("Network.deleteDeviceBoundSession", [{"name": "key", "type": "object", "optional": false, "description": "", "typeRef": "Network.DeviceBoundSessionKey"}], [], "Deletes a device bound session.");
896
897
  inspectorBackend.registerCommand("Network.fetchSchemefulSite", [{"name": "origin", "type": "string", "optional": false, "description": "The URL origin.", "typeRef": null}], ["schemefulSite"], "Fetches the schemeful site for a specific origin.");
897
898
  inspectorBackend.registerCommand("Network.loadNetworkResource", [{"name": "frameId", "type": "string", "optional": true, "description": "Frame id to get the resource for. Mandatory for frame targets, and should be omitted for worker targets.", "typeRef": "Page.FrameId"}, {"name": "url", "type": "string", "optional": false, "description": "URL of the resource to get content for.", "typeRef": null}, {"name": "options", "type": "object", "optional": false, "description": "Options for the request.", "typeRef": "Network.LoadNetworkResourceOptions"}], ["resource"], "Fetches the resource and returns the content.");
898
899
  inspectorBackend.registerCommand("Network.setCookieControls", [{"name": "enableThirdPartyCookieRestriction", "type": "boolean", "optional": false, "description": "Whether 3pc restriction is enabled.", "typeRef": null}], [], "Sets Controls for third-party cookie access Page reload is required before the new cookie behavior will be observed");
@@ -1496,6 +1497,7 @@ inspectorBackend.registerEvent("WebMCP.toolInvoked", ["toolName", "frameId", "in
1496
1497
  inspectorBackend.registerEvent("WebMCP.toolResponded", ["invocationId", "status", "output", "errorText", "exception"]);
1497
1498
  inspectorBackend.registerCommand("WebMCP.enable", [], [], "Enables the WebMCP domain, allowing events to be sent. Enabling the domain will trigger a toolsAdded event for all currently registered tools.");
1498
1499
  inspectorBackend.registerCommand("WebMCP.disable", [], [], "Disables the WebMCP domain.");
1500
+ inspectorBackend.registerCommand("WebMCP.invokeTool", [{"name": "frameId", "type": "string", "optional": false, "description": "Frame in which to invoke the tool.", "typeRef": "Page.FrameId"}, {"name": "toolName", "type": "string", "optional": false, "description": "Name of the tool to invoke.", "typeRef": null}, {"name": "input", "type": "object", "optional": false, "description": "Input parameters for the tool, matching the tool's inputSchema.", "typeRef": null}], ["invocationId"], "Invokes a registered tool.");
1499
1501
  inspectorBackend.registerType("WebMCP.Annotation", [{"name": "readOnly", "type": "boolean", "optional": true, "description": "A hint indicating that the tool does not modify any state.", "typeRef": null}, {"name": "autosubmit", "type": "boolean", "optional": true, "description": "If the declarative tool was declared with the autosubmit attribute.", "typeRef": null}]);
1500
1502
  inspectorBackend.registerType("WebMCP.Tool", [{"name": "name", "type": "string", "optional": false, "description": "Tool name.", "typeRef": null}, {"name": "description", "type": "string", "optional": false, "description": "Tool description.", "typeRef": null}, {"name": "inputSchema", "type": "object", "optional": true, "description": "Schema for the tool's input parameters.", "typeRef": null}, {"name": "annotations", "type": "object", "optional": true, "description": "Optional annotations for the tool.", "typeRef": "WebMCP.Annotation"}, {"name": "frameId", "type": "string", "optional": false, "description": "Frame identifier associated with the tool registration.", "typeRef": "Page.FrameId"}, {"name": "backendNodeId", "type": "number", "optional": true, "description": "Optional node ID for declarative tools.", "typeRef": "DOM.BackendNodeId"}, {"name": "stackTrace", "type": "object", "optional": true, "description": "The stack trace at the time of the registration.", "typeRef": "Runtime.StackTrace"}]);
1501
1503
 
@@ -3719,6 +3719,13 @@ export namespace ProtocolMapping {
3719
3719
  paramsType: [Protocol.Network.EnableDeviceBoundSessionsRequest];
3720
3720
  returnType: void;
3721
3721
  };
3722
+ /**
3723
+ * Deletes a device bound session.
3724
+ */
3725
+ 'Network.deleteDeviceBoundSession': {
3726
+ paramsType: [Protocol.Network.DeleteDeviceBoundSessionRequest];
3727
+ returnType: void;
3728
+ };
3722
3729
  /**
3723
3730
  * Fetches the schemeful site for a specific origin.
3724
3731
  */
@@ -5375,6 +5382,13 @@ export namespace ProtocolMapping {
5375
5382
  paramsType: [];
5376
5383
  returnType: void;
5377
5384
  };
5385
+ /**
5386
+ * Invokes a registered tool.
5387
+ */
5388
+ 'WebMCP.invokeTool': {
5389
+ paramsType: [Protocol.WebMCP.InvokeToolRequest];
5390
+ returnType: Protocol.WebMCP.InvokeToolResponse;
5391
+ };
5378
5392
  /**
5379
5393
  * Continues execution until specific location is reached.
5380
5394
  */
@@ -2731,6 +2731,11 @@ declare namespace ProtocolProxyApi {
2731
2731
  */
2732
2732
  invoke_enableDeviceBoundSessions(params: Protocol.Network.EnableDeviceBoundSessionsRequest): Promise<Protocol.ProtocolResponseWithError>;
2733
2733
 
2734
+ /**
2735
+ * Deletes a device bound session.
2736
+ */
2737
+ invoke_deleteDeviceBoundSession(params: Protocol.Network.DeleteDeviceBoundSessionRequest): Promise<Protocol.ProtocolResponseWithError>;
2738
+
2734
2739
  /**
2735
2740
  * Fetches the schemeful site for a specific origin.
2736
2741
  */
@@ -4848,6 +4853,11 @@ declare namespace ProtocolProxyApi {
4848
4853
  */
4849
4854
  invoke_disable(): Promise<Protocol.ProtocolResponseWithError>;
4850
4855
 
4856
+ /**
4857
+ * Invokes a registered tool.
4858
+ */
4859
+ invoke_invokeTool(params: Protocol.WebMCP.InvokeToolRequest): Promise<Protocol.WebMCP.InvokeToolResponse>;
4860
+
4851
4861
  }
4852
4862
  export interface WebMCPDispatcher {
4853
4863
  /**
@@ -11922,6 +11922,7 @@ export namespace Network {
11922
11922
 
11923
11923
  export const enum RefreshEventDetailsRefreshResult {
11924
11924
  Refreshed = 'Refreshed',
11925
+ RefreshedAsWaiter = 'RefreshedAsWaiter',
11925
11926
  InitializedService = 'InitializedService',
11926
11927
  Unreachable = 'Unreachable',
11927
11928
  ServerError = 'ServerError',
@@ -11967,6 +11968,7 @@ export namespace Network {
11967
11968
  ServerRequested = 'ServerRequested',
11968
11969
  InvalidSessionParams = 'InvalidSessionParams',
11969
11970
  RefreshFatalError = 'RefreshFatalError',
11971
+ DevTools = 'DevTools',
11970
11972
  }
11971
11973
 
11972
11974
  /**
@@ -12555,6 +12557,10 @@ export namespace Network {
12555
12557
  enable: boolean;
12556
12558
  }
12557
12559
 
12560
+ export interface DeleteDeviceBoundSessionRequest {
12561
+ key: DeviceBoundSessionKey;
12562
+ }
12563
+
12558
12564
  export interface FetchSchemefulSiteRequest {
12559
12565
  /**
12560
12566
  * The URL origin.
@@ -20356,6 +20362,28 @@ export namespace WebMCP {
20356
20362
  stackTrace?: Runtime.StackTrace;
20357
20363
  }
20358
20364
 
20365
+ export interface InvokeToolRequest {
20366
+ /**
20367
+ * Frame in which to invoke the tool.
20368
+ */
20369
+ frameId: Page.FrameId;
20370
+ /**
20371
+ * Name of the tool to invoke.
20372
+ */
20373
+ toolName: string;
20374
+ /**
20375
+ * Input parameters for the tool, matching the tool's inputSchema.
20376
+ */
20377
+ input: any;
20378
+ }
20379
+
20380
+ export interface InvokeToolResponse extends ProtocolResponseWithError {
20381
+ /**
20382
+ * Unique identifier for this invocation. Response is sent before tool events.
20383
+ */
20384
+ invocationId: string;
20385
+ }
20386
+
20359
20387
  /**
20360
20388
  * Event fired when new tools are added.
20361
20389
  */
@@ -11,7 +11,10 @@ Content:
11
11
  ],
12
12
  "widgets": [
13
13
  {
14
- "name": "CORE_VITALS"
14
+ "name": "TIMELINE_RANGE_SUMMARY"
15
+ },
16
+ {
17
+ "name": "BOTTOM_UP_TREE"
15
18
  }
16
19
  ]
17
20
  },
@@ -432,6 +432,7 @@ export class PerformanceAgent extends AiAgent<AgentFocus> {
432
432
  * we only show it once.
433
433
  */
434
434
  #hasShownWidgetForInsightSet = new WeakSet<Trace.Insights.Types.InsightSet>();
435
+ #hasShownWidgetForCallTree = new WeakSet<AICallTree>();
435
436
 
436
437
  get preamble(): string {
437
438
  return buildPreamble();
@@ -472,16 +473,44 @@ export class PerformanceAgent extends AiAgent<AgentFocus> {
472
473
  contextDisclosure.push(...this.#additionalSelectionsForQuery);
473
474
 
474
475
  const widgets: AiWidget[] = [];
475
- const primaryInsightSet = context.getItem().primaryInsightSet;
476
- if (primaryInsightSet && !this.#hasShownWidgetForInsightSet.has(primaryInsightSet)) {
477
- widgets.push({
478
- name: 'CORE_VITALS',
479
- data: {
480
- parsedTrace: context.getItem().parsedTrace,
481
- insightSetKey: primaryInsightSet.id,
482
- },
483
- });
484
- this.#hasShownWidgetForInsightSet.add(primaryInsightSet);
476
+ const focus = context.getItem();
477
+
478
+ // If the user has selected a specific task (call tree) as context, show the summary and bottom-up tree for it.
479
+ // Otherwise, show the high-level Core Web Vitals widget for the trace or insight.
480
+ if (focus.callTree && !this.#hasShownWidgetForCallTree.has(focus.callTree)) {
481
+ const event = focus.callTree.selectedNode?.event;
482
+ if (event) {
483
+ const {startTime, endTime} = Trace.Helpers.Timing.eventTimingsMicroSeconds(event);
484
+ const bounds = Trace.Helpers.Timing.traceWindowFromMicroSeconds(startTime, endTime);
485
+ widgets.push({
486
+ name: 'TIMELINE_RANGE_SUMMARY',
487
+ data: {
488
+ bounds,
489
+ parsedTrace: focus.parsedTrace,
490
+ track: 'main',
491
+ },
492
+ });
493
+ widgets.push({
494
+ name: 'BOTTOM_UP_TREE',
495
+ data: {
496
+ bounds,
497
+ parsedTrace: focus.parsedTrace,
498
+ },
499
+ });
500
+ this.#hasShownWidgetForCallTree.add(focus.callTree);
501
+ }
502
+ } else {
503
+ const primaryInsightSet = focus.primaryInsightSet;
504
+ if (primaryInsightSet && !this.#hasShownWidgetForInsightSet.has(primaryInsightSet)) {
505
+ widgets.push({
506
+ name: 'CORE_VITALS',
507
+ data: {
508
+ parsedTrace: focus.parsedTrace,
509
+ insightSetKey: primaryInsightSet.id,
510
+ },
511
+ });
512
+ this.#hasShownWidgetForInsightSet.add(primaryInsightSet);
513
+ }
485
514
  }
486
515
 
487
516
  yield {
@@ -155,6 +155,10 @@ const issueCodeHandlers = new Map<
155
155
  ],
156
156
  ]);
157
157
 
158
+ export function isIssueCodeSupported(code: Protocol.Audits.InspectorIssueCode): boolean {
159
+ return issueCodeHandlers.has(code);
160
+ }
161
+
158
162
  /**
159
163
  * Each issue reported by the backend can result in multiple `Issue` instances.
160
164
  * Handlers are simple functions hard-coded into a map.
@@ -4,6 +4,7 @@
4
4
 
5
5
  import * as Common from '../../core/common/common.js';
6
6
  import type * as Platform from '../../core/platform/platform.js';
7
+ import type * as Protocol from '../../generated/protocol.js';
7
8
 
8
9
  import type {RawFrame} from './Trie.js';
9
10
 
@@ -140,3 +141,21 @@ export function parseRawFramesFromErrorStack(stack: string): RawFrame[] {
140
141
  }
141
142
  return rawFrames;
142
143
  }
144
+
145
+ /**
146
+ * Error#stack output only contains script URLs. In some cases we are able to
147
+ * retrieve additional exception details from V8 that we can use to augment
148
+ * the parsed Error#stack with script IDs.
149
+ */
150
+ export function augmentRawFramesWithScriptIds(
151
+ rawFrames: RawFrame[], protocolStackTrace: Protocol.Runtime.StackTrace): void {
152
+ for (const rawFrame of rawFrames) {
153
+ const protocolFrame = protocolStackTrace.callFrames.find(
154
+ frame => rawFrame.url === frame.url && rawFrame.lineNumber === frame.lineNumber &&
155
+ rawFrame.columnNumber === frame.columnNumber);
156
+ if (protocolFrame) {
157
+ // @ts-expect-error scriptId is a readonly property.
158
+ rawFrame.scriptId = protocolFrame.scriptId;
159
+ }
160
+ }
161
+ }
@@ -6,6 +6,7 @@ import * as Common from '../../core/common/common.js';
6
6
  import * as SDK from '../../core/sdk/sdk.js';
7
7
  import type * as Protocol from '../../generated/protocol.js';
8
8
 
9
+ import {augmentRawFramesWithScriptIds, parseRawFramesFromErrorStack} from './DetailedErrorStackParser.js';
9
10
  // eslint-disable-next-line @devtools/es-modules-import
10
11
  import * as StackTrace from './stack_trace.js';
11
12
  import {
@@ -14,6 +15,7 @@ import {
14
15
  DebuggableFragmentImpl,
15
16
  FragmentImpl,
16
17
  FrameImpl,
18
+ ParsedErrorStackFragmentImpl,
17
19
  StackTraceImpl
18
20
  } from './StackTraceImpl.js';
19
21
  import {type FrameNode, type RawFrame, Trie} from './Trie.js';
@@ -54,6 +56,23 @@ export class StackTraceModel extends SDK.SDKModel.SDKModel<unknown> {
54
56
  return new StackTraceImpl(syncFragment, asyncFragments);
55
57
  }
56
58
 
59
+ async createFromErrorStackLikeString(
60
+ stack: string, rawFramesToUIFrames: TranslateRawFrames,
61
+ exceptionDetails?: Protocol.Runtime.ExceptionDetails): Promise<StackTrace.StackTrace.ParsedErrorStackTrace> {
62
+ const rawFrames = parseRawFramesFromErrorStack(stack);
63
+ if (exceptionDetails?.stackTrace) {
64
+ augmentRawFramesWithScriptIds(rawFrames, exceptionDetails.stackTrace);
65
+ }
66
+
67
+ const [syncFragment, asyncFragments] = await Promise.all([
68
+ this.#createFragment(rawFrames, rawFramesToUIFrames),
69
+ exceptionDetails?.stackTrace ? this.#createAsyncFragments(exceptionDetails.stackTrace, rawFramesToUIFrames) :
70
+ Promise.resolve([]),
71
+ ]);
72
+
73
+ return new StackTraceImpl(new ParsedErrorStackFragmentImpl(syncFragment), asyncFragments);
74
+ }
75
+
57
76
  async createFromDebuggerPaused(
58
77
  pausedDetails: SDK.DebuggerModel.DebuggerPausedDetails,
59
78
  rawFramesToUIFrames: TranslateRawFrames): Promise<StackTrace.StackTrace.DebuggableStackTrace> {
@@ -162,12 +181,32 @@ export class StackTraceModel extends SDK.SDKModel.SDKModel<unknown> {
162
181
  const uiFrames = await rawFramesToUIFrames(rawFrames, this.target());
163
182
  console.assert(rawFrames.length === uiFrames.length, 'Broken rawFramesToUIFrames implementation');
164
183
 
184
+ const evalOriginPromises: Array<ReturnType<TranslateRawFrames>> = [];
185
+ for (const node of fragment.node.getCallStack()) {
186
+ if (node.parsedFrameInfo?.evalOrigin) {
187
+ // Evaluate each eval origin individually, as they are not a contiguous stack trace.
188
+ evalOriginPromises.push(rawFramesToUIFrames([node.parsedFrameInfo.evalOrigin], this.target()));
189
+ }
190
+ }
191
+
192
+ const evalUiFrames = await Promise.all(evalOriginPromises);
193
+
165
194
  let i = 0;
195
+ let evalI = 0;
166
196
  for (const node of fragment.node.getCallStack()) {
167
197
  node.frames = uiFrames[i++].map(
168
198
  frame => new FrameImpl(
169
199
  frame.url, frame.uiSourceCode, frame.name, frame.line, frame.column, frame.missingDebugInfo,
170
200
  node.rawFrame.functionName));
201
+
202
+ if (node.parsedFrameInfo?.evalOrigin) {
203
+ const evalOriginRawFrame = node.parsedFrameInfo.evalOrigin;
204
+ // evalUiFrames[evalI] is Array<Array<Frame>>, and since we passed a 1-element array, we take [0]
205
+ node.evalOriginFrames = evalUiFrames[evalI++][0].map(
206
+ frame => new FrameImpl(
207
+ frame.url, frame.uiSourceCode, frame.name, frame.line, frame.column, frame.missingDebugInfo,
208
+ evalOriginRawFrame.functionName));
209
+ }
171
210
  }
172
211
  }
173
212
 
@@ -13,6 +13,18 @@ import {MarkdownRendererWithCodeBlock} from './MarkdownRendererWithCodeBlock.js'
13
13
  const {html} = Lit.StaticHtml;
14
14
  const {until} = Lit.Directives;
15
15
 
16
+ /**
17
+ * Represents the different types of links that can be parsed from the AI agent's response.
18
+ * The agent can linkify a node either by its backend node ID or by its full DOM path.
19
+ */
20
+ type ParsedLink = {
21
+ type: 'path',
22
+ path: string,
23
+ }|{
24
+ type: 'node',
25
+ nodeId: Protocol.DOM.BackendNodeId,
26
+ };
27
+
16
28
  export class AccessibilityAgentMarkdownRenderer extends MarkdownRendererWithCodeBlock {
17
29
  constructor(
18
30
  private mainFrameId = '',
@@ -22,28 +34,54 @@ export class AccessibilityAgentMarkdownRenderer extends MarkdownRendererWithCode
22
34
 
23
35
  override templateForToken(token: Marked.Marked.MarkedToken): Lit.LitTemplate|null {
24
36
  if (token.type === 'link' && token.href.startsWith('#')) {
25
- if (token.href.startsWith('#path-')) {
26
- const path = token.href.replace('#path-', '');
27
- return html`<span>${
28
- until(this.#linkifyPath(path, token.text).then(node => node || token.text), token.text)}</span>`;
29
- }
37
+ const parsed = this.#parseLink(token.href);
38
+ if (parsed) {
39
+ const resultPromise = parsed.type === 'path' ? this.#linkifyPath(parsed.path, token.text) :
40
+ this.#linkifyNode(parsed.nodeId, token.text);
30
41
 
31
- let nodeId = undefined;
32
- if (token.href.startsWith('#node-')) {
33
- nodeId = Number(token.href.replace('#node-', '')) as Protocol.DOM.BackendNodeId;
34
- } else if (token.href.startsWith('#')) {
35
- nodeId = Number(token.href.replace('#', '')) as Protocol.DOM.BackendNodeId;
42
+ return html`<span>${until(resultPromise.then(node => node || token.text), token.text)}</span>`;
36
43
  }
44
+ }
45
+
46
+ return super.templateForToken(token);
47
+ }
37
48
 
38
- if (nodeId) {
39
- return html`<span>${
40
- until(this.#linkifyNode(nodeId, token.text).then(node => node || token.text), token.text)}</span>`;
49
+ /**
50
+ * Parses a link href to determine if it's a node ID or a DOM path.
51
+ *
52
+ * The AI agent is instructed to use #node-ID or #path-PATH, but
53
+ * sometimes it omits the prefixes, in which case we try to detect
54
+ * paths by looking for `#1,HTML` which is often how paths in LH
55
+ * start.
56
+ */
57
+ #parseLink(href: string): ParsedLink|null {
58
+ if (href.startsWith('#path-')) {
59
+ return {type: 'path', path: href.replace('#path-', '')};
60
+ }
61
+ if (href.startsWith('#1,HTML')) {
62
+ return {type: 'path', path: href.slice(1)};
63
+ }
64
+
65
+ let nodeIdStr = '';
66
+ if (href.startsWith('#node-')) {
67
+ nodeIdStr = href.replace('#node-', '');
68
+ } else if (href.startsWith('#')) {
69
+ nodeIdStr = href.slice(1);
70
+ }
71
+
72
+ if (nodeIdStr.trim() !== '') {
73
+ const nodeId = Number(nodeIdStr);
74
+ if (Number.isInteger(nodeId)) {
75
+ return {type: 'node', nodeId: nodeId as Protocol.DOM.BackendNodeId};
41
76
  }
42
77
  }
43
78
 
44
- return super.templateForToken(token);
79
+ return null;
45
80
  }
46
81
 
82
+ /**
83
+ * Linkifies a node using its backend node ID.
84
+ */
47
85
  async #linkifyNode(backendNodeId: Protocol.DOM.BackendNodeId, label: string): Promise<Lit.LitTemplate|undefined> {
48
86
  if (backendNodeId === undefined) {
49
87
  return;
@@ -68,6 +106,9 @@ export class AccessibilityAgentMarkdownRenderer extends MarkdownRendererWithCode
68
106
  return linkedNode;
69
107
  }
70
108
 
109
+ /**
110
+ * Linkifies a node using its full DOM path (e.g. "1,HTML,1,BODY,...").
111
+ */
71
112
  async #linkifyPath(path: string, label: string): Promise<Lit.LitTemplate|undefined> {
72
113
  const target = SDK.TargetManager.TargetManager.instance().primaryPageTarget();
73
114
  const domModel = target?.model(SDK.DOMModel.DOMModel);
@@ -11,6 +11,7 @@ import '../../ui/legacy/legacy.js';
11
11
  import type {JSONSchema7, JSONSchema7Definition} from 'json-schema';
12
12
 
13
13
  import * as Common from '../../core/common/common.js';
14
+ import * as Host from '../../core/host/host.js';
14
15
  import * as i18n from '../../core/i18n/i18n.js';
15
16
  import * as Platform from '../../core/platform/platform.js';
16
17
  import * as SDK from '../../core/sdk/sdk.js';
@@ -158,6 +159,14 @@ const UIStrings = {
158
159
  * @example {1} PH1
159
160
  */
160
161
  inProgressCount: '{PH1} In Progress',
162
+ /**
163
+ * @description Context menu action to copy the name of a tool
164
+ */
165
+ copyName: 'Copy name',
166
+ /**
167
+ * @description Context menu action to copy the description of a tool
168
+ */
169
+ copyDescription: 'Copy description',
161
170
  } as const;
162
171
  const str_ = i18n.i18n.registerUIStrings('panels/application/WebMCPView.ts', UIStrings);
163
172
  const i18nString = i18n.i18n.getLocalizedString.bind(undefined, str_);
@@ -350,6 +359,16 @@ export const DEFAULT_VIEW: View = (input, output, target) => {
350
359
  return i18nString(UIStrings.inProgress);
351
360
  }
352
361
  };
362
+ const onToolContextMenu = (event: Event, tool: WebMCP.WebMCPModel.Tool): void => {
363
+ const contextMenu = new UI.ContextMenu.ContextMenu(event);
364
+ contextMenu.defaultSection().appendItem(i18nString(UIStrings.copyName), () => {
365
+ Host.InspectorFrontendHost.InspectorFrontendHostInstance.copyText(tool.name);
366
+ }, {jslogContext: 'webmcp.copy-tool-name'});
367
+ contextMenu.defaultSection().appendItem(i18nString(UIStrings.copyDescription), () => {
368
+ Host.InspectorFrontendHost.InspectorFrontendHostInstance.copyText(tool.description);
369
+ }, {jslogContext: 'webmcp.copy-tool-description'});
370
+ void contextMenu.show();
371
+ };
353
372
  // clang-format off
354
373
  render(html`
355
374
  <style>${webMCPViewStyles}</style>
@@ -491,7 +510,8 @@ export const DEFAULT_VIEW: View = (input, output, target) => {
491
510
  const groups = getIconGroupsFromStats(toolStats);
492
511
  return html`
493
512
  <div class=${Directives.classMap({'tool-item': true, selected: tool === input.selectedTool})}
494
- @click=${() => input.onToolSelect(tool)}>
513
+ @click=${() => input.onToolSelect(tool)}
514
+ @contextmenu=${(e: Event) => onToolContextMenu(e, tool)}>
495
515
  <div class="tool-name-container">
496
516
  <div class="tool-name source-code">${tool.name}</div>
497
517
  ${groups.length > 0 ? html`<icon-button .data=${
@@ -77,7 +77,7 @@
77
77
  height: 100%;
78
78
  display: flex;
79
79
  flex-direction: column;
80
- overflow: auto;
80
+ overflow: hidden;
81
81
  }
82
82
 
83
83
  .tool-details-grid {
@@ -86,6 +86,7 @@
86
86
  gap: 0 var(--sys-size-16);
87
87
  padding: calc(0.5*var(--sys-size-6)) var(--sys-size-8);
88
88
  align-items: flex-start;
89
+ overflow-y: auto;
89
90
 
90
91
  .label {
91
92
  color: var(--sys-color-on-surface-subtle);
@@ -94,6 +95,8 @@
94
95
  }
95
96
 
96
97
  .value {
98
+ user-select: text;
99
+
97
100
  &.source-code {
98
101
  color: var(--sys-color-token-attribute);
99
102
  }
@@ -1592,7 +1592,10 @@ export class ConsoleViewMessage implements ConsoleViewportElement {
1592
1592
  const label = document.createElement('div');
1593
1593
  label.classList.add('button-label');
1594
1594
  const text = document.createElement('div');
1595
- text.innerText = this.getExplainLabel();
1595
+ // We use a data attribute and a CSS pseudo-element for the button label
1596
+ // to prevent the text from being picked up by the console's custom
1597
+ // copy-to-clipboard traversal, which only collects actual text nodes.
1598
+ text.setAttribute('data-text', this.getExplainLabel());
1596
1599
  label.append(text);
1597
1600
  button.append(label);
1598
1601
  button.classList.add('hover-button');
@@ -1615,7 +1618,10 @@ export class ConsoleViewMessage implements ConsoleViewportElement {
1615
1618
  const label = document.createElement('div');
1616
1619
  label.classList.add('button-label');
1617
1620
  const text = document.createElement('div');
1618
- text.innerText = 'Debug with breakpoint AI';
1621
+ // We use a data attribute and a CSS pseudo-element for the button label
1622
+ // to prevent the text from being picked up by the console's custom
1623
+ // copy-to-clipboard traversal, which only collects actual text nodes.
1624
+ text.setAttribute('data-text', 'Debug with breakpoint AI');
1619
1625
  label.append(text);
1620
1626
  button.append(label);
1621
1627
  button.classList.add('hover-button');
@@ -615,6 +615,10 @@
615
615
  & div {
616
616
  display: inline-block;
617
617
  vertical-align: -1px;
618
+
619
+ &::after {
620
+ content: attr(data-text);
621
+ }
618
622
  }
619
623
  }
620
624
 
@@ -410,7 +410,7 @@ export class JSONEditor extends Common.ObjectWrapper.eventMixin<EventTypes, type
410
410
  typeRef: schema?.typeRef,
411
411
  value,
412
412
  description,
413
- } as Parameter;
413
+ } as unknown as Parameter;
414
414
  }
415
415
 
416
416
  #convertPrimitiveParameter(key: string, value: unknown, schema?: Parameter): Parameter {
@@ -133,7 +133,7 @@ export class RecordingPlayer extends Common.ObjectWrapper.ObjectWrapper<EventTyp
133
133
  throw new Error('could not find main page!');
134
134
  }
135
135
 
136
- browser.on('targetdiscovered', (targetInfo: Protocol.Target.TargetInfo) => {
136
+ browser.on('targetdiscovered', (targetInfo: ReturnType<puppeteer.Target['_getTargetInfo']>) => {
137
137
  // Pop-ups opened by the main target won't be auto-attached. Therefore,
138
138
  // we need to create a session for them explicitly. We user openedId
139
139
  // and type to classify a target as requiring a session.
@@ -91,7 +91,7 @@ export class PuppeteerConnectionHelper {
91
91
  undefined /* process */,
92
92
  undefined /* closeCallback */,
93
93
  undefined /* targetFilterCallback */,
94
- target => isPageTargetCallback((target as puppeteer.Target)._getTargetInfo()),
94
+ target => isPageTargetCallback((target as puppeteer.Target)._getTargetInfo() as Protocol.Target.TargetInfo),
95
95
  false /* waitForInitiallyDiscoveredTargets */,
96
96
  );
97
97
 
@@ -1,7 +1,7 @@
1
1
  Name: Dependencies sourced from the upstream `chromium` repository
2
2
  URL: Internal
3
3
  Version: N/A
4
- Revision: aa779a3f59f2eda5d9befe0632bc75c1a05fe8dd
4
+ Revision: 7e9e4ee5d284534f90cf424bf02eb9d65ed151ec
5
5
  Update Mechanism: Manual (https://crbug.com/428069060)
6
6
  License: BSD-3-Clause
7
7
  License File: LICENSE
@@ -3,6 +3,7 @@
3
3
  // found in the LICENSE file.
4
4
  /* eslint-disable @devtools/no-imperative-dom-api */
5
5
 
6
+ // @ts-expect-error tsc 6 can't find type declarations for this file.
6
7
  import '../../../Images/Images.js';
7
8
 
8
9
  import iconStyles from './icon.css.js';
@@ -1932,7 +1932,7 @@ export namespace TreeViewElement {
1932
1932
  }
1933
1933
  }
1934
1934
 
1935
- export const ifExpanded = Lit.Directive.directive(class extends Lit.Directive.Directive {
1935
+ class IfExpandedDirective extends Lit.Directive.Directive {
1936
1936
  #partInfo: {type: Lit.Directive.PartType, startNode: Node};
1937
1937
  constructor(partInfo: Lit.Directive.PartInfo) {
1938
1938
  if (partInfo.type !== Lit.Directive.PartType.CHILD) {
@@ -1969,7 +1969,8 @@ export const ifExpanded = Lit.Directive.directive(class extends Lit.Directive.Di
1969
1969
  }
1970
1970
  return node.expanded;
1971
1971
  }
1972
- });
1972
+ }
1973
+ export const ifExpanded = Lit.Directive.directive(IfExpandedDirective);
1973
1974
 
1974
1975
  export class TreeElementWrapper extends HTMLElement {
1975
1976
  #treeElement?: TreeElement;
@@ -50,7 +50,7 @@ const DUMMY_COLUMN_ID = 'dummy'; // SortableDataGrid.create requires at least o
50
50
  * @attribute striped If true, the data grid will have striped rows.
51
51
  * @attribute displayName
52
52
  */
53
- class DataGridElement extends UI.UIUtils.HTMLElementWithLightDOMTemplate {
53
+ export class DataGridElement extends UI.UIUtils.HTMLElementWithLightDOMTemplate {
54
54
  static readonly observedAttributes = ['striped', 'name', 'inline', 'resize'];
55
55
 
56
56
  #dataGrid = SortableDataGrid.create([DUMMY_COLUMN_ID], [], '') as SortableDataGrid<DataGridElementNode>;
@@ -609,7 +609,7 @@ const INTERNAL_TOKEN: DataGridInternalToken = {
609
609
  token: 'DataGridInternalToken'
610
610
  };
611
611
 
612
- export const ifExpanded = Lit.Directive.directive(class extends Lit.Directive.Directive {
612
+ class IfExpandedDirective extends Lit.Directive.Directive {
613
613
  #partInfo: {type: Lit.Directive.PartType, startNode: Node};
614
614
  constructor(partInfo: Lit.Directive.PartInfo) {
615
615
  if (partInfo.type !== Lit.Directive.PartType.CHILD) {
@@ -639,4 +639,5 @@ export const ifExpanded = Lit.Directive.directive(class extends Lit.Directive.Di
639
639
  }
640
640
  return node.expanded;
641
641
  }
642
- });
642
+ }
643
+ export const ifExpanded = Lit.Directive.directive(IfExpandedDirective);
@@ -4350,6 +4350,8 @@ export const knownContextValues = new Set([
4350
4350
  'webmcp.call-inputs',
4351
4351
  'webmcp.call-outputs',
4352
4352
  'webmcp.completed',
4353
+ 'webmcp.copy-tool-description',
4354
+ 'webmcp.copy-tool-name',
4353
4355
  'webmcp.declarative',
4354
4356
  'webmcp.error',
4355
4357
  'webmcp.imperative',
package/package.json CHANGED
@@ -89,7 +89,7 @@
89
89
  "svgo": "3.3.2",
90
90
  "terser": "5.44.1",
91
91
  "ts-lit-plugin": "2.0.2",
92
- "typescript": "5.9.3",
92
+ "typescript": "6.0.2",
93
93
  "typescript-eslint": "8.58.0",
94
94
  "uuid": "13.0.0",
95
95
  "webidl2": "24.5.0",
@@ -104,5 +104,5 @@
104
104
  "flat-cache": "6.1.12"
105
105
  }
106
106
  },
107
- "version": "1.0.1614363"
107
+ "version": "1.0.1615539"
108
108
  }