chrome-devtools-frontend 1.0.1605390 → 1.0.1606789

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 (60) hide show
  1. package/front_end/core/host/UserMetrics.ts +0 -1
  2. package/front_end/core/platform/api/HostRuntime.ts +9 -6
  3. package/front_end/core/platform/browser/HostRuntime.ts +2 -2
  4. package/front_end/core/platform/node/HostRuntime.ts +7 -7
  5. package/front_end/core/protocol_client/InspectorBackend.ts +4 -0
  6. package/front_end/core/root/ExperimentNames.ts +0 -1
  7. package/front_end/core/sdk/CrashReportContextModel.ts +28 -0
  8. package/front_end/core/sdk/sdk.ts +2 -2
  9. package/front_end/entrypoints/heap_snapshot_worker/HeapSnapshotWorkerDispatcher.ts +3 -3
  10. package/front_end/entrypoints/heap_snapshot_worker/heap_snapshot_worker-entrypoint.ts +5 -4
  11. package/front_end/entrypoints/main/MainImpl.ts +0 -101
  12. package/front_end/models/ai_assistance/ChangeManager.ts +6 -6
  13. package/front_end/models/ai_assistance/agents/AccessibilityAgent.ts +112 -5
  14. package/front_end/models/ai_assistance/agents/AiAgent.ts +0 -24
  15. package/front_end/models/ai_assistance/agents/ContextSelectionAgent.snapshot.txt +2 -2
  16. package/front_end/models/ai_assistance/agents/ContextSelectionAgent.ts +6 -3
  17. package/front_end/models/ai_assistance/agents/ExecuteJavascript.ts +13 -1
  18. package/front_end/models/ai_assistance/agents/FileAgent.ts +15 -1
  19. package/front_end/models/ai_assistance/agents/NetworkAgent.ts +15 -1
  20. package/front_end/models/ai_assistance/agents/StylingAgent.ts +24 -15
  21. package/front_end/models/ai_assistance/ai_assistance.ts +0 -2
  22. package/front_end/models/javascript_metadata/NativeFunctions.js +1 -5
  23. package/front_end/models/web_mcp/WebMCPModel.ts +187 -0
  24. package/front_end/models/web_mcp/web_mcp.ts +9 -0
  25. package/front_end/panels/ai_assistance/AiAssistancePanel.ts +8 -1
  26. package/front_end/panels/ai_assistance/ai_assistance.ts +1 -0
  27. package/front_end/panels/ai_assistance/components/AccessibilityAgentMarkdownRenderer.ts +88 -0
  28. package/front_end/panels/ai_assistance/components/ChatMessage.ts +19 -3
  29. package/front_end/panels/ai_assistance/components/ChatView.ts +11 -4
  30. package/front_end/panels/ai_assistance/components/ExportForAgentsDialog.ts +12 -3
  31. package/front_end/panels/ai_assistance/components/chatMessage.css +10 -0
  32. package/front_end/panels/ai_assistance/components/chatView.css +0 -2
  33. package/front_end/panels/ai_assistance/components/exportForAgentsDialog.css +13 -0
  34. package/front_end/panels/application/WebMCPView.ts +126 -30
  35. package/front_end/panels/application/webMCPView.css +28 -2
  36. package/front_end/panels/elements/ElementsTreeElement.ts +1 -1
  37. package/front_end/panels/elements/StylePropertiesSection.ts +0 -4
  38. package/front_end/panels/elements/elements.ts +3 -0
  39. package/front_end/panels/elements/stylePropertiesTreeOutline.css +7 -0
  40. package/front_end/panels/lighthouse/LighthousePanel.ts +7 -1
  41. package/front_end/panels/lighthouse/LighthouseProtocolService.ts +25 -2
  42. package/front_end/panels/profiler/HeapSnapshotProxy.ts +2 -6
  43. package/front_end/panels/profiler/HeapSnapshotView.ts +7 -16
  44. package/front_end/panels/profiler/ProfileHeader.ts +1 -4
  45. package/front_end/panels/protocol_monitor/ProtocolMonitor.ts +27 -22
  46. package/front_end/panels/timeline/TimelinePanel.ts +0 -153
  47. package/front_end/panels/timeline/TimelineTreeView.ts +11 -1
  48. package/front_end/panels/timeline/TimelineUIUtils.ts +2 -1
  49. package/front_end/third_party/chromium/README.chromium +1 -1
  50. package/front_end/ui/components/markdown_view/CodeBlock.ts +47 -1
  51. package/front_end/ui/components/markdown_view/codeBlock.css +8 -0
  52. package/front_end/ui/legacy/TabbedPane.ts +123 -3
  53. package/front_end/ui/legacy/Widget.ts +67 -28
  54. package/front_end/ui/legacy/components/source_frame/JSONView.ts +1 -1
  55. package/front_end/ui/legacy/components/utils/jsUtils.css +2 -0
  56. package/front_end/ui/legacy/infobar.css +1 -0
  57. package/front_end/ui/visual_logging/KnownContextValues.ts +0 -1
  58. package/package.json +1 -1
  59. package/front_end/core/sdk/WebMCPModel.ts +0 -159
  60. package/front_end/models/ai_assistance/ConversationHandler.ts +0 -325
@@ -815,7 +815,6 @@ export enum DevtoolsExperiments {
815
815
  'live-heap-profile' = 11,
816
816
  'protocol-monitor' = 13,
817
817
  'sampling-heap-profiler-timeline' = 17,
818
- 'show-option-to-expose-internals-in-heap-snapshot' = 18,
819
818
  'timeline-invalidation-tracking' = 26,
820
819
  'timeline-show-all-events' = 27,
821
820
  apca = 39,
@@ -25,21 +25,24 @@ export interface Worker {
25
25
  set onerror(listener: (event: any) => void);
26
26
  }
27
27
 
28
+ type WorkerMessagePort = typeof MessagePort.prototype;
29
+
28
30
  /**
29
- * Currently we ony transfer MessagePorts to workers, but it's possible to add
31
+ * Currently we only transfer MessagePorts to workers, but it's possible to add
30
32
  * more things (like ReadableStream) as long as it's present in all runtimes.
31
33
  */
32
- export type WorkerTransferable = typeof MessagePort.prototype;
34
+ export type WorkerTransferable = WorkerMessagePort;
33
35
 
34
36
  /**
35
37
  * Used by workers to communicate with their parent.
36
38
  */
37
39
  export interface WorkerScope {
38
40
  postMessage(message: unknown): void;
39
- set onmessage(listener: (event: WorkerMessageEvent) => void);
41
+ set onmessage(listener: (event: WorkerMessageEvent) => Promise<void>| void);
40
42
  }
41
43
 
42
- export interface WorkerMessageEvent {
43
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
44
- readonly data: any;
44
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
45
+ export interface WorkerMessageEvent<T = any> {
46
+ readonly data: T;
47
+ ports: readonly WorkerMessagePort[];
45
48
  }
@@ -9,8 +9,8 @@ class WebWorkerScope implements Api.HostRuntime.WorkerScope {
9
9
  self.postMessage(message);
10
10
  }
11
11
 
12
- set onmessage(listener: (event: Api.HostRuntime.WorkerMessageEvent) => void) {
13
- self.onmessage = listener;
12
+ set onmessage(listener: (event: Api.HostRuntime.WorkerMessageEvent) => Promise<void>| void) {
13
+ self.addEventListener('message', listener);
14
14
  }
15
15
  }
16
16
 
@@ -12,8 +12,8 @@ class NodeWorkerScope implements Api.HostRuntime.WorkerScope {
12
12
  }
13
13
 
14
14
  set onmessage(listener: (event: Api.HostRuntime.WorkerMessageEvent) => void) {
15
- WorkerThreads.parentPort?.on('message', data => {
16
- listener({data});
15
+ WorkerThreads.parentPort?.addEventListener('message', msg => {
16
+ listener(msg as unknown as Api.HostRuntime.WorkerMessageEvent);
17
17
  });
18
18
  }
19
19
  }
@@ -36,10 +36,10 @@ class NodeWorker implements Api.HostRuntime.Worker {
36
36
  });
37
37
  }
38
38
 
39
- postMessage(message: unknown): void {
39
+ postMessage(message: unknown, transfer?: Api.HostRuntime.WorkerTransferable[]): void {
40
40
  void this.#workerPromise.then(worker => {
41
41
  if (!this.#disposed) {
42
- worker.postMessage(message);
42
+ worker.postMessage(message, transfer);
43
43
  }
44
44
  });
45
45
  }
@@ -56,11 +56,11 @@ class NodeWorker implements Api.HostRuntime.Worker {
56
56
  this.dispose();
57
57
  }
58
58
 
59
- set onmessage(listener: (event: unknown) => void) {
59
+ set onmessage(listener: (event: Api.HostRuntime.WorkerMessageEvent) => void) {
60
60
  void this.#workerPromise.then(worker => {
61
- worker.on('message', data => {
61
+ worker.on('message', (data: unknown) => {
62
62
  if (!this.#disposed) {
63
- listener({data});
63
+ listener({data, ports: []});
64
64
  }
65
65
  });
66
66
  });
@@ -351,6 +351,10 @@ export class TargetBase {
351
351
  return this.getAgent('CacheStorage');
352
352
  }
353
353
 
354
+ crashReportContextAgent(): ProtocolProxyApi.CrashReportContextApi {
355
+ return this.getAgent('CrashReportContext');
356
+ }
357
+
354
358
  cssAgent(): ProtocolProxyApi.CSSApi {
355
359
  return this.getAgent('CSS');
356
360
  }
@@ -8,7 +8,6 @@ export enum ExperimentName {
8
8
  LIVE_HEAP_PROFILE = 'live-heap-profile',
9
9
  PROTOCOL_MONITOR = 'protocol-monitor',
10
10
  SAMPLING_HEAP_PROFILER_TIMELINE = 'sampling-heap-profiler-timeline',
11
- SHOW_OPTION_TO_EXPOSE_INTERNALS_IN_HEAP_SNAPSHOT = 'show-option-to-expose-internals-in-heap-snapshot',
12
11
  TIMELINE_INVALIDATION_TRACKING = 'timeline-invalidation-tracking',
13
12
  TIMELINE_SHOW_ALL_EVENTS = 'timeline-show-all-events',
14
13
  APCA = 'apca',
@@ -0,0 +1,28 @@
1
+ // Copyright 2026 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
+ import type * as ProtocolProxyApi from '../../generated/protocol-proxy-api.js';
6
+ import type * as Protocol from '../../generated/protocol.js';
7
+
8
+ import {SDKModel} from './SDKModel.js';
9
+ import {Capability, type Target} from './Target.js';
10
+
11
+ export class CrashReportContextModel extends SDKModel<void> {
12
+ readonly #agent: ProtocolProxyApi.CrashReportContextApi;
13
+
14
+ constructor(target: Target) {
15
+ super(target);
16
+ this.#agent = target.crashReportContextAgent();
17
+ }
18
+
19
+ async getEntries(): Promise<Protocol.CrashReportContext.CrashReportContextEntry[]|null> {
20
+ const response = await this.#agent.invoke_getEntries();
21
+ if (response.getError()) {
22
+ return null;
23
+ }
24
+ return response.entries;
25
+ }
26
+ }
27
+
28
+ SDKModel.register(CrashReportContextModel, {capabilities: Capability.JS, autostart: false});
@@ -22,6 +22,7 @@ import * as CookieModel from './CookieModel.js';
22
22
  import * as CookieParser from './CookieParser.js';
23
23
  import * as CPUProfilerModel from './CPUProfilerModel.js';
24
24
  import * as CPUThrottlingManager from './CPUThrottlingManager.js';
25
+ import * as CrashReportContextModel from './CrashReportContextModel.js';
25
26
  import * as CSSContainerQuery from './CSSContainerQuery.js';
26
27
  import * as CSSFontFace from './CSSFontFace.js';
27
28
  import * as CSSLayer from './CSSLayer.js';
@@ -90,7 +91,6 @@ import * as Target from './Target.js';
90
91
  import * as TargetManager from './TargetManager.js';
91
92
  import * as TraceObject from './TraceObject.js';
92
93
  import * as WebAuthnModel from './WebAuthnModel.js';
93
- import * as WebMCPModel from './WebMCPModel.js';
94
94
 
95
95
  export {
96
96
  AccessibilityModel,
@@ -106,6 +106,7 @@ export {
106
106
  CookieParser,
107
107
  CPUProfilerModel,
108
108
  CPUThrottlingManager,
109
+ CrashReportContextModel,
109
110
  CSSContainerQuery,
110
111
  CSSFontFace,
111
112
  CSSLayer,
@@ -174,5 +175,4 @@ export {
174
175
  TargetManager,
175
176
  TraceObject,
176
177
  WebAuthnModel,
177
- WebMCPModel,
178
178
  };
@@ -2,6 +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
 
5
+ import type * as PlatformApi from '../../core/platform/api/api.js';
5
6
  import * as HeapSnapshotModel from '../../models/heap_snapshot_model/heap_snapshot_model.js';
6
7
 
7
8
  // We mirror what heap_snapshot_worker.ts does, but we can't use it here as we'd have a
@@ -33,9 +34,8 @@ export class HeapSnapshotWorkerDispatcher {
33
34
  this.#postMessage({eventName: name, data});
34
35
  }
35
36
 
36
- async dispatchMessage({data, ports}:
37
- {data: HeapSnapshotModel.HeapSnapshotModel.WorkerCommand, ports: readonly MessagePort[]}):
38
- Promise<void> {
37
+ async dispatchMessage({data, ports}: PlatformApi.HostRuntime
38
+ .WorkerMessageEvent<HeapSnapshotModel.HeapSnapshotModel.WorkerCommand>): Promise<void> {
39
39
  const response: DispatcherResponse = {
40
40
  callId: data.callId,
41
41
  result: null,
@@ -1,10 +1,11 @@
1
1
  // Copyright 2020 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
+ import * as Platform from '../../core/platform/platform.js';
4
5
 
5
6
  import * as HeapSnapshotWorker from './heap_snapshot_worker.js';
6
7
 
7
- const dispatcher =
8
- new HeapSnapshotWorker.HeapSnapshotWorkerDispatcher.HeapSnapshotWorkerDispatcher(self.postMessage.bind(self));
9
- self.addEventListener('message', dispatcher.dispatchMessage.bind(dispatcher), false);
10
- self.postMessage('workerReady');
8
+ const dispatcher = new HeapSnapshotWorker.HeapSnapshotWorkerDispatcher.HeapSnapshotWorkerDispatcher(
9
+ Platform.HostRuntime.HOST_RUNTIME.workerScope.postMessage.bind(Platform.HostRuntime.HOST_RUNTIME.workerScope));
10
+ Platform.HostRuntime.HOST_RUNTIME.workerScope.onmessage = dispatcher.dispatchMessage.bind(dispatcher);
11
+ Platform.HostRuntime.HOST_RUNTIME.workerScope.postMessage('workerReady');
@@ -121,10 +121,6 @@ const UIStrings = {
121
121
  * @description Text describing how to navigate the dock side menu
122
122
  */
123
123
  dockSideNavigation: 'Use left and right arrow keys to navigate the options',
124
- /**
125
- * @description Notification shown to the user whenever DevTools receives an external request.
126
- */
127
- externalRequestReceived: '`DevTools` received an external request',
128
124
  /**
129
125
  * @description Notification shown to the user whenever DevTools has finished downloading a local AI model.
130
126
  */
@@ -360,9 +356,6 @@ export class MainImpl {
360
356
  Root.ExperimentNames.ExperimentName.PROTOCOL_MONITOR, protocolMonitorExperiment);
361
357
  Root.Runtime.experiments.register(
362
358
  Root.ExperimentNames.ExperimentName.SAMPLING_HEAP_PROFILER_TIMELINE, 'Sampling heap profiler timeline');
363
- Root.Runtime.experiments.register(
364
- Root.ExperimentNames.ExperimentName.SHOW_OPTION_TO_EXPOSE_INTERNALS_IN_HEAP_SNAPSHOT,
365
- 'Show option to expose internals in heap snapshots');
366
359
 
367
360
  // Timeline
368
361
  Root.Runtime.experiments.register(
@@ -581,14 +574,6 @@ export class MainImpl {
581
574
  });
582
575
  }
583
576
 
584
- const conversationHandler = AiAssistanceModel.ConversationHandler.ConversationHandler.instance();
585
- conversationHandler.addEventListener(
586
- AiAssistanceModel.ConversationHandler.ConversationHandlerEvents.EXTERNAL_REQUEST_RECEIVED,
587
- () => Snackbar.Snackbar.Snackbar.show({message: i18nString(UIStrings.externalRequestReceived)}));
588
- conversationHandler.addEventListener(
589
- AiAssistanceModel.ConversationHandler.ConversationHandlerEvents.EXTERNAL_CONVERSATION_STARTED,
590
- event => void VisualLogging.logFunctionCall(`start-conversation-${event.data}`, 'external'));
591
-
592
577
  if (Root.Runtime.hostConfig.devToolsGeminiRebranding?.enabled) {
593
578
  await PanelCommon.GeminiRebrandPromoDialog.maybeShow();
594
579
  }
@@ -1104,89 +1089,3 @@ export class ReloadActionDelegate implements UI.ActionRegistration.ActionDelegat
1104
1089
  return false;
1105
1090
  }
1106
1091
  }
1107
-
1108
- type ExternalRequestInput = {
1109
- kind: 'LIVE_STYLE_DEBUGGER',
1110
- args: {prompt: string, selector: string},
1111
- }|{
1112
- kind: 'PERFORMANCE_RELOAD_GATHER_INSIGHTS',
1113
- }|{
1114
- kind: 'PERFORMANCE_ANALYZE',
1115
- args: {prompt: string},
1116
- }|{
1117
- kind: 'NETWORK_DEBUGGER',
1118
- args: {requestUrl: string, prompt: string},
1119
- };
1120
-
1121
- /**
1122
- * For backwards-compatibility we iterate over the generator and drop the
1123
- * intermediate results. The final response is transformed to its legacy type.
1124
- * Instead of sending responses of type error, errors are throws.
1125
- **/
1126
- export async function handleExternalRequest(input: ExternalRequestInput):
1127
- Promise<{response: string, devToolsLogs: object[]}> {
1128
- const generator = await handleExternalRequestGenerator(input);
1129
- let result: IteratorResult<
1130
- AiAssistanceModel.AiAgent.ExternalRequestResponse, AiAssistanceModel.AiAgent.ExternalRequestResponse>;
1131
- do {
1132
- result = await generator.next();
1133
- } while (!result.done);
1134
- const response = result.value;
1135
- if (response.type === AiAssistanceModel.AiAgent.ExternalRequestResponseType.ERROR) {
1136
- throw new Error(response.message);
1137
- }
1138
- if (response.type === AiAssistanceModel.AiAgent.ExternalRequestResponseType.ANSWER) {
1139
- return {
1140
- response: response.message,
1141
- devToolsLogs: response.devToolsLogs,
1142
- };
1143
- }
1144
- throw new Error('Received no response of type answer or type error');
1145
- }
1146
-
1147
- // @ts-expect-error
1148
- globalThis.handleExternalRequest = handleExternalRequest;
1149
-
1150
- export async function handleExternalRequestGenerator(input: ExternalRequestInput): Promise<AsyncGenerator<
1151
- AiAssistanceModel.AiAgent.ExternalRequestResponse, AiAssistanceModel.AiAgent.ExternalRequestResponse>> {
1152
- switch (input.kind) {
1153
- case 'PERFORMANCE_RELOAD_GATHER_INSIGHTS': {
1154
- const TimelinePanel = await import('../../panels/timeline/timeline.js');
1155
- return TimelinePanel.TimelinePanel.TimelinePanel.handleExternalRecordRequest();
1156
- }
1157
- case 'PERFORMANCE_ANALYZE': {
1158
- const TimelinePanel = await import('../../panels/timeline/timeline.js');
1159
- return await TimelinePanel.TimelinePanel.TimelinePanel.handleExternalAnalyzeRequest(input.args.prompt);
1160
- }
1161
- case 'NETWORK_DEBUGGER': {
1162
- const AiAssistanceModel = await import('../../models/ai_assistance/ai_assistance.js');
1163
- const conversationHandler = AiAssistanceModel.ConversationHandler.ConversationHandler.instance();
1164
- return await conversationHandler.handleExternalRequest({
1165
- conversationType: AiAssistanceModel.AiHistoryStorage.ConversationType.NETWORK,
1166
- prompt: input.args.prompt,
1167
- requestUrl: input.args.requestUrl,
1168
- });
1169
- }
1170
- case 'LIVE_STYLE_DEBUGGER': {
1171
- const AiAssistanceModel = await import('../../models/ai_assistance/ai_assistance.js');
1172
- const conversationHandler = AiAssistanceModel.ConversationHandler.ConversationHandler.instance();
1173
- return await conversationHandler.handleExternalRequest({
1174
- conversationType: AiAssistanceModel.AiHistoryStorage.ConversationType.STYLING,
1175
- prompt: input.args.prompt,
1176
- selector: input.args.selector,
1177
- });
1178
- }
1179
- }
1180
- // eslint-disable-next-line require-yield
1181
- return (async function*(): AsyncGenerator<
1182
- AiAssistanceModel.AiAgent.ExternalRequestResponse, AiAssistanceModel.AiAgent.ExternalRequestResponse> {
1183
- return {
1184
- type: AiAssistanceModel.AiAgent.ExternalRequestResponseType.ERROR,
1185
- // @ts-expect-error
1186
- message: `Debugging with an agent of type '${input.kind}' is not implemented yet.`,
1187
- };
1188
- })();
1189
- }
1190
-
1191
- // @ts-expect-error
1192
- globalThis.handleExternalRequestGenerator = handleExternalRequestGenerator;
@@ -117,11 +117,11 @@ export class ChangeManager {
117
117
  return content;
118
118
  }
119
119
 
120
- formatChangesForPatching(groupId: string, includeSourceLocation = false): string {
120
+ formatChangesForPatching(groupId: string, includeMetadata = false): string {
121
121
  return Array.from(this.#stylesheetChanges.values())
122
122
  .flatMap(
123
123
  changesPerStylesheet => changesPerStylesheet.filter(change => change.groupId === groupId)
124
- .map(change => this.#formatChange(change, includeSourceLocation)))
124
+ .map(change => this.#formatChange(change, includeMetadata)))
125
125
  .filter(change => change !== '')
126
126
  .join('\n\n');
127
127
  }
@@ -150,13 +150,13 @@ ${formatStyles(change.styles, 4)}
150
150
  .join('\n');
151
151
  }
152
152
 
153
- #formatChange(change: Change, includeSourceLocation = false): string {
153
+ #formatChange(change: Change, includeMetadata = false): string {
154
154
  const sourceLocation =
155
- includeSourceLocation && change.sourceLocation ? `/* related resource: ${change.sourceLocation} */\n` : '';
156
- // TODO: includeSourceLocation indicates whether we are using Patch
155
+ includeMetadata && change.sourceLocation ? `/* related resource: ${change.sourceLocation} */\n` : '';
156
+ // TODO: includeMetadata indicates whether we are using Patch
157
157
  // agent. If needed we can have an separate knob.
158
158
  const simpleSelector =
159
- includeSourceLocation && change.simpleSelector ? ` /* the element was ${change.simpleSelector} */` : '';
159
+ includeMetadata && change.simpleSelector ? ` /* the element was ${change.simpleSelector} */` : '';
160
160
  return `${sourceLocation}${change.selector} {${simpleSelector}
161
161
  ${formatStyles(change.styles)}
162
162
  }`;
@@ -7,18 +7,27 @@ import * as i18n from '../../../core/i18n/i18n.js';
7
7
  import * as Root from '../../../core/root/root.js';
8
8
  import * as SDK from '../../../core/sdk/sdk.js';
9
9
  import type * as LHModel from '../../lighthouse/lighthouse.js';
10
+ import {ChangeManager} from '../ChangeManager.js';
10
11
  import {LighthouseFormatter} from '../data_formatters/LighthouseFormatter.js';
11
12
  import {debugLog} from '../debug.js';
13
+ import {ExtensionScope} from '../ExtensionScope.js';
12
14
 
13
15
  import {
14
- type AgentOptions,
15
16
  AiAgent,
17
+ type AiWidget,
16
18
  type ContextDetail,
17
19
  type ContextResponse,
18
20
  ConversationContext,
19
21
  type RequestOptions,
20
22
  ResponseType,
21
23
  } from './AiAgent.js';
24
+ import {
25
+ type CreateExtensionScopeFunction,
26
+ executeJavaScriptFunction,
27
+ type ExecuteJsAgentOptions,
28
+ executeJsCode,
29
+ JavascriptExecutor
30
+ } from './ExecuteJavascript.js';
22
31
 
23
32
  /**
24
33
  * WARNING: preamble defined in code is only used when userTier is
@@ -29,7 +38,7 @@ const preamble = `You are an accessibility expert agent integrated into Chrome D
29
38
  Your role is to help users understand and fix accessibility issues found in Lighthouse reports.
30
39
 
31
40
  # Style Guidelines
32
- * **Concise and Direct**: Use short sentences and bullet points. Avoid paragraphs and long explanations.
41
+ * **General style**: Use the precision of Strunk & White, the brevity of Hemingway, and the simple clarity of Vonnegut. Don't add repeated information, and keep the whole answer short.
33
42
  * **Structured**: Organize your findings by problem, root cause, and next steps, but do NOT use those literal words as headings.
34
43
  * **No Internal Identifiers**: NEVER show Lighthouse paths (e.g., "1,HTML,1,BODY...") to the user. Refer to elements by their tag name, classes, or IDs.
35
44
  * **Managing Volume**: If the report contains many issues, provide a brief summary of the top 2-3 most critical ones. Tell the user that there are more issues and invite them to ask for more details or to explore a specific area.
@@ -45,10 +54,28 @@ Your role is to help users understand and fix accessibility issues found in Ligh
45
54
  * \`runAccessibilityAudits\`: Trigger new accessibility snapshot audits.
46
55
  * \`getStyles\`: Get computed styles for an element by its path.
47
56
  * \`getElementAccessibilityDetails\`: Get A11y properties for an element by its path.
57
+ * \`executeJavaScript\`: Run JavaScript code on the inspected page to gather additional information or investigate the page state.
58
+
59
+ # Linkification
60
+ * **Linkify elements**: When you know the Lighthouse path of an element (found in the report audits), linkify it using \`([Label](#path-PATH))\` syntax. Never show the path to the user directly, only use it in the link href.
48
61
 
49
62
  # Constraints
50
63
  * **CRITICAL**: ALWAYS call a tool before providing an answer if an element path is available.
51
64
  * **CRITICAL**: You are an accessibility agent. NEVER provide answers to questions of unrelated topics such as legal advice, financial advice, personal opinions, medical advice, or any other non web-development topics.
65
+
66
+ ## Response Structure
67
+
68
+ If the user asks a question that requires an investigation of a problem, use this structure:
69
+ - If available, point out the root cause(s) of the problem.
70
+ - Example: "**Root Cause**: The page is slow because of [reason]."
71
+ - Example: "**Root Causes**:"
72
+ - [Reason 1]
73
+ - [Reason 2]
74
+ - if applicable, list actionable solution suggestion(s) in order of impact:
75
+ - Example: "**Suggestion**: [Suggestion 1]
76
+ - Example: "**Suggestions**:"
77
+ - [Suggestion 1]
78
+ - [Suggestion 2]
52
79
  `;
53
80
 
54
81
  export class AccessibilityContext extends ConversationContext<LHModel.ReporterTypes.ReportJSON> {
@@ -86,15 +113,40 @@ export class AccessibilityAgent extends AiAgent<LHModel.ReporterTypes.ReportJSON
86
113
  readonly #lighthouseRecording?:
87
114
  (overrides?: LHModel.RunTypes.RunOverrides) => Promise<LHModel.ReporterTypes.ReportJSON|null>;
88
115
 
89
- constructor(opts: AgentOptions) {
116
+ #execJs: typeof executeJsCode;
117
+ #javascriptExecutor: JavascriptExecutor;
118
+ #changes: ChangeManager;
119
+ #createExtensionScope: CreateExtensionScopeFunction;
120
+ #currentTurnId = 0;
121
+
122
+ constructor(opts: ExecuteJsAgentOptions) {
90
123
  super(opts);
91
124
  this.#lighthouseRecording = opts.lighthouseRecording;
125
+ this.#changes = opts.changeManager || new ChangeManager();
126
+ this.#execJs = opts.execJs ?? executeJsCode;
127
+ this.#createExtensionScope =
128
+ opts.createExtensionScope ?? ((changes: ChangeManager) => {
129
+ return new ExtensionScope(changes, this.sessionId, this.#getDocumentBodyNode(), this.#currentTurnId);
130
+ });
131
+ this.#javascriptExecutor = new JavascriptExecutor(
132
+ {
133
+ executionMode: this.executionMode,
134
+ getContextNode: () => this.#getDocumentBodyNode(),
135
+ createExtensionScope: this.#createExtensionScope.bind(this),
136
+ changes: this.#changes,
137
+ },
138
+ this.#execJs);
92
139
  }
93
140
 
94
141
  get userTier(): string|undefined {
95
142
  return Root.Runtime.hostConfig.devToolsFreestyler?.userTier;
96
143
  }
97
144
 
145
+ get executionMode(): Root.Runtime.HostConfigFreestylerExecutionMode {
146
+ return Root.Runtime.hostConfig.devToolsFreestyler?.executionMode ??
147
+ Root.Runtime.HostConfigFreestylerExecutionMode.ALL_SCRIPTS;
148
+ }
149
+
98
150
  get options(): RequestOptions {
99
151
  // TODO(b/491772868): tidy up userTier & feature flags in the backend.
100
152
  const temperature = Root.Runtime.hostConfig.devToolsAiAssistanceFileAgent?.temperature;
@@ -106,6 +158,38 @@ export class AccessibilityAgent extends AiAgent<LHModel.ReporterTypes.ReportJSON
106
158
  };
107
159
  }
108
160
 
161
+ override preambleFeatures(): string[] {
162
+ return ['function_calling'];
163
+ }
164
+
165
+ protected override async preRun(): Promise<void> {
166
+ this.#currentTurnId++;
167
+ const target = SDK.TargetManager.TargetManager.instance().primaryPageTarget();
168
+ const domModel = target?.model(SDK.DOMModel.DOMModel);
169
+ // We need to ensure the document is requested so that #getDocumentBodyNode()
170
+ // can return a valid node for the JavaScript execution context.
171
+ if (domModel && !domModel.existingDocument()) {
172
+ try {
173
+ await domModel.requestDocument();
174
+ } catch (e) {
175
+ debugLog('Failed to request document', e);
176
+ }
177
+ }
178
+ }
179
+
180
+ /**
181
+ * For the Accessibility Agent, there is no single "selected" node.
182
+ * We use the document body as the default context node for JavaScript execution
183
+ * so that the AI has a valid $0 to start with.
184
+ */
185
+ #getDocumentBodyNode(): SDK.DOMModel.DOMNode|null {
186
+ const document = SDK.TargetManager.TargetManager.instance()
187
+ .primaryPageTarget()
188
+ ?.model(SDK.DOMModel.DOMModel)
189
+ ?.existingDocument();
190
+ return document?.body ?? document ?? null;
191
+ }
192
+
109
193
  async *
110
194
  handleContextDetails(lhr: ConversationContext<LHModel.ReporterTypes.ReportJSON>|null):
111
195
  AsyncGenerator<ContextResponse, void, void> {
@@ -136,6 +220,8 @@ export class AccessibilityAgent extends AiAgent<LHModel.ReporterTypes.ReportJSON
136
220
  }
137
221
 
138
222
  #declareFunctions(): void {
223
+ this.declareFunction('executeJavaScript', executeJavaScriptFunction(this.#javascriptExecutor));
224
+
139
225
  this.declareFunction<{explanation: string}, {audits: string}>('runAccessibilityAudits', {
140
226
  description:
141
227
  'Triggers new Lighthouse accessibility audits in snapshot mode. Use this if the user has made changes to the page and you want to re-evaluate the accessibility audits.',
@@ -262,11 +348,31 @@ export class AccessibilityAgent extends AiAgent<LHModel.ReporterTypes.ReportJSON
262
348
  if (!styles) {
263
349
  return {error: 'Could not get computed styles.'};
264
350
  }
265
- const result: Record<string, string|undefined> = {};
351
+ const result: Record<string, string|number|undefined> = {};
266
352
  for (const prop of params.styleProperties) {
267
353
  result[prop] = styles.get(prop);
268
354
  }
269
- return {result: JSON.stringify(result, null, 2)};
355
+
356
+ result['backendNodeId'] = node.backendNodeId();
357
+
358
+ const widgets: AiWidget[] = [];
359
+ const matchedStyles = await node.domModel().cssModel().getMatchedStyles(node.id);
360
+ if (matchedStyles) {
361
+ widgets.push({
362
+ name: 'COMPUTED_STYLES',
363
+ data: {
364
+ computedStyles: styles,
365
+ backendNodeId: node.backendNodeId(),
366
+ matchedCascade: matchedStyles,
367
+ properties: params.styleProperties,
368
+ }
369
+ });
370
+ }
371
+
372
+ return {
373
+ result: JSON.stringify(result, null, 2),
374
+ widgets: widgets.length > 0 ? widgets : undefined,
375
+ };
270
376
  },
271
377
  });
272
378
 
@@ -337,6 +443,7 @@ export class AccessibilityAgent extends AiAgent<LHModel.ReporterTypes.ReportJSON
337
443
  {} as Record<string, string>),
338
444
  isIgnored: axNode.ignored(),
339
445
  ignoredReasons: axNode.ignoredReasons(),
446
+ backendNodeId: node.backendNodeId(),
340
447
  };
341
448
 
342
449
  return {result: JSON.stringify(result, null, 2)};
@@ -172,30 +172,6 @@ export interface ConversationSuggestion {
172
172
  /** At least one. */
173
173
  export type ConversationSuggestions = [ConversationSuggestion, ...ConversationSuggestion[]];
174
174
 
175
- export const enum ExternalRequestResponseType {
176
- ANSWER = 'answer',
177
- NOTIFICATION = 'notification',
178
- ERROR = 'error',
179
- }
180
-
181
- export interface ExternalRequestAnswer {
182
- type: ExternalRequestResponseType.ANSWER;
183
- message: string;
184
- devToolsLogs: object[];
185
- }
186
-
187
- export interface ExternalRequestNotification {
188
- type: ExternalRequestResponseType.NOTIFICATION;
189
- message: string;
190
- }
191
-
192
- export interface ExternalRequestError {
193
- type: ExternalRequestResponseType.ERROR;
194
- message: string;
195
- }
196
-
197
- export type ExternalRequestResponse = ExternalRequestAnswer|ExternalRequestNotification|ExternalRequestError;
198
-
199
175
  export abstract class ConversationContext<T> {
200
176
  abstract getOrigin(): string;
201
177
  abstract getItem(): T;
@@ -91,7 +91,7 @@ Content:
91
91
  },
92
92
  {
93
93
  "name": "performanceRecordAndReload",
94
- "description": "Records a new performance trace, to help debug performance issue.",
94
+ "description": "Records a new performance trace. Use this to measure and debug performance metrics and Core Web Vitals like Largest Contentful Paint (LCP), Interaction to Next Paint (INP), and Cumulative Layout Shift (CLS).",
95
95
  "parameters": {
96
96
  "type": 6,
97
97
  "description": "",
@@ -102,7 +102,7 @@ Content:
102
102
  },
103
103
  {
104
104
  "name": "runLighthouseAudits",
105
- "description": "Records a Lighthouse audit on the current page, to help debug accessibility issues.",
105
+ "description": "Records a Lighthouse audit on the current page. Use this to debug accessibility, SEO, and best practices. (For performance metrics like LCP, use performanceRecordAndReload instead).",
106
106
  "parameters": {
107
107
  "type": 6,
108
108
  "description": "",
@@ -36,6 +36,7 @@ You aim to help developers of all levels, prioritizing teaching web concepts as
36
36
 
37
37
  # Considerations
38
38
  * Determine what is the domain of the question - styling, network, sources, performance or other part of DevTools.
39
+ * For questions about web performance metrics (e.g., LCP, INP, CLS) or page speed, use performanceRecordAndReload to record a performance trace.
39
40
  * Proactively try to gather additional data. If a select specific data can be selected, select one.
40
41
  * Always try select single specific context before answering the question.
41
42
  * Avoid making assumptions without sufficient evidence, and always seek further clarification if needed.
@@ -47,7 +48,7 @@ You aim to help developers of all levels, prioritizing teaching web concepts as
47
48
  # Formatting Guidelines
48
49
  * Use Markdown for all code snippets.
49
50
  * Always specify the language for code blocks (e.g., \`\`\`css, \`\`\`javascript).
50
- * Keep text responses concise and scannable.
51
+ * **CRITICAL**: Use the precision of Strunk & White, the brevity of Hemingway, and the simple clarity of Vonnegut. Don't add repeated information, and keep the whole answer short.
51
52
 
52
53
  * **CRITICAL** If a tool returns an empty list, immediately pivot to the next logical tool (e.g., from sources to network).
53
54
  * **CRITICAL** Always exhaust all possible way to find and select context from different domains.
@@ -273,7 +274,8 @@ export class ContextSelectionAgent extends AiAgent<never> {
273
274
  });
274
275
 
275
276
  this.declareFunction('performanceRecordAndReload', {
276
- description: 'Records a new performance trace, to help debug performance issue.',
277
+ description:
278
+ 'Records a new performance trace. Use this to measure and debug performance metrics and Core Web Vitals like Largest Contentful Paint (LCP), Interaction to Next Paint (INP), and Cumulative Layout Shift (CLS).',
277
279
  parameters: {
278
280
  type: Host.AidaClient.ParametersTypes.OBJECT,
279
281
  description: '',
@@ -304,7 +306,8 @@ export class ContextSelectionAgent extends AiAgent<never> {
304
306
  });
305
307
 
306
308
  this.declareFunction('runLighthouseAudits', {
307
- description: 'Records a Lighthouse audit on the current page, to help debug accessibility issues.',
309
+ description:
310
+ 'Records a Lighthouse audit on the current page. Use this to debug accessibility, SEO, and best practices. (For performance metrics like LCP, use performanceRecordAndReload instead).',
308
311
  parameters: {
309
312
  type: Host.AidaClient.ParametersTypes.OBJECT,
310
313
  description: '',