chrome-devtools-frontend 1.0.1642845 → 1.0.1643099

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 (128) hide show
  1. package/SECURITY.md +1 -0
  2. package/front_end/core/host/UserMetrics.ts +2 -1
  3. package/front_end/core/protocol_client/InspectorBackend.ts +4 -0
  4. package/front_end/core/sdk/CSSMatchedStyles.ts +55 -26
  5. package/front_end/core/sdk/CSSRule.ts +1 -0
  6. package/front_end/core/sdk/DebuggerModel.ts +5 -0
  7. package/front_end/entrypoints/greendev_floaty/FloatyEntrypoint.ts +4 -3
  8. package/front_end/entrypoints/greendev_floaty/greendev_floaty.ts +4 -3
  9. package/front_end/entrypoints/heap_snapshot_worker/HeapSnapshot.ts +4 -5
  10. package/front_end/generated/InspectorBackendCommands.ts +1 -1
  11. package/front_end/generated/protocol.ts +7 -0
  12. package/front_end/models/ai_assistance/AiAgent2.ts +100 -18
  13. package/front_end/models/ai_assistance/AiConversation.ts +18 -14
  14. package/front_end/models/ai_assistance/AiUtils.ts +71 -0
  15. package/front_end/models/ai_assistance/ChangeManager.ts +2 -5
  16. package/front_end/models/ai_assistance/{agents/ConversationSummaryAgent.ts → ConversationSummary.ts} +29 -66
  17. package/front_end/models/ai_assistance/ExtensionScope.ts +1 -4
  18. package/front_end/models/ai_assistance/{agents/PerformanceAnnotationsAgent.ts → PerformanceAnnotations.ts} +47 -89
  19. package/front_end/models/ai_assistance/README.md +8 -0
  20. package/front_end/models/ai_assistance/agents/AccessibilityAgent.ts +65 -40
  21. package/front_end/models/ai_assistance/agents/AiAgent.ts +37 -6
  22. package/front_end/models/ai_assistance/agents/ContextSelectionAgent.snapshot.txt +11 -0
  23. package/front_end/models/ai_assistance/agents/ContextSelectionAgent.ts +55 -5
  24. package/front_end/models/ai_assistance/agents/ExecuteJavascript.ts +2 -0
  25. package/front_end/models/ai_assistance/agents/PerformanceAgent.ts +119 -78
  26. package/front_end/models/ai_assistance/agents/StorageAgent.ts +47 -38
  27. package/front_end/models/ai_assistance/agents/StylingAgent.snapshot.txt +0 -25
  28. package/front_end/models/ai_assistance/agents/StylingAgent.ts +46 -326
  29. package/front_end/models/ai_assistance/ai_assistance.ts +14 -4
  30. package/front_end/models/ai_assistance/contexts/DOMNodeContext.snapshot.txt +51 -0
  31. package/front_end/models/ai_assistance/contexts/DOMNodeContext.ts +200 -0
  32. package/front_end/models/ai_assistance/skills/styling.md +44 -2
  33. package/front_end/models/ai_assistance/tools/ExecuteJavaScript.ts +140 -0
  34. package/front_end/models/ai_assistance/tools/GetStyles.ts +141 -0
  35. package/front_end/models/ai_assistance/tools/Tool.ts +64 -0
  36. package/front_end/models/ai_assistance/tools/ToolRegistry.ts +36 -0
  37. package/front_end/models/heap_snapshot/HeapSnapshotProxy.ts +5 -7
  38. package/front_end/models/lighthouse/LighthouseReporterTypes.ts +5 -0
  39. package/front_end/models/live-metrics/LiveMetrics.ts +24 -13
  40. package/front_end/models/stack_trace/DetailedErrorStackParser.ts +2 -2
  41. package/front_end/models/stack_trace/StackTrace.ts +4 -1
  42. package/front_end/models/stack_trace/StackTraceImpl.ts +9 -2
  43. package/front_end/models/stack_trace/StackTraceModel.ts +17 -4
  44. package/front_end/models/stack_trace/Trie.ts +1 -1
  45. package/front_end/panels/ai_assistance/AiAssistancePanel.ts +25 -22
  46. package/front_end/panels/ai_assistance/ai_assistance-meta.ts +16 -0
  47. package/front_end/panels/ai_assistance/components/ChatInput.ts +2 -2
  48. package/front_end/panels/ai_assistance/components/ChatMessage.ts +96 -4
  49. package/front_end/panels/ai_assistance/components/chatMessage.css +6 -0
  50. package/front_end/panels/application/DOMStorageItemsView.ts +4 -0
  51. package/front_end/panels/application/KeyValueStorageItemsView.ts +39 -7
  52. package/front_end/panels/application/components/AdsView.ts +219 -0
  53. package/front_end/panels/application/components/adsView.css +54 -0
  54. package/front_end/panels/application/components/components.ts +2 -0
  55. package/front_end/panels/common/ExtensionServer.ts +26 -15
  56. package/front_end/panels/console/SymbolizedErrorWidget.ts +73 -22
  57. package/front_end/panels/elements/StandaloneStylesContainer.ts +1 -1
  58. package/front_end/panels/elements/StylePropertiesSection.ts +8 -0
  59. package/front_end/panels/elements/StylePropertyHighlighter.ts +4 -2
  60. package/front_end/panels/elements/StylePropertyTreeElement.ts +6 -5
  61. package/front_end/panels/elements/StylesContainer.ts +1 -1
  62. package/front_end/panels/elements/StylesSidebarPane.ts +4 -4
  63. package/front_end/panels/layer_viewer/PaintProfilerView.ts +106 -132
  64. package/front_end/panels/lighthouse/LighthousePanel.ts +4 -3
  65. package/front_end/panels/network/NetworkLogView.ts +8 -1
  66. package/front_end/panels/network/networkLogView.css +0 -15
  67. package/front_end/panels/timeline/overlays/components/EntryLabelOverlay.ts +5 -4
  68. package/front_end/third_party/chromium/README.chromium +1 -1
  69. package/front_end/third_party/lighthouse/README.chromium +2 -2
  70. package/front_end/third_party/lighthouse/lighthouse-dt-bundle.js +1607 -5733
  71. package/front_end/third_party/lighthouse/locales/ar-XB.json +290 -65
  72. package/front_end/third_party/lighthouse/locales/ar.json +290 -65
  73. package/front_end/third_party/lighthouse/locales/bg.json +290 -65
  74. package/front_end/third_party/lighthouse/locales/ca.json +295 -70
  75. package/front_end/third_party/lighthouse/locales/cs.json +290 -65
  76. package/front_end/third_party/lighthouse/locales/da.json +294 -69
  77. package/front_end/third_party/lighthouse/locales/de.json +295 -70
  78. package/front_end/third_party/lighthouse/locales/el.json +290 -65
  79. package/front_end/third_party/lighthouse/locales/en-GB.json +290 -65
  80. package/front_end/third_party/lighthouse/locales/en-US.json +79 -67
  81. package/front_end/third_party/lighthouse/locales/en-XA.json +253 -64
  82. package/front_end/third_party/lighthouse/locales/en-XL.json +79 -67
  83. package/front_end/third_party/lighthouse/locales/es-419.json +290 -65
  84. package/front_end/third_party/lighthouse/locales/es.json +298 -73
  85. package/front_end/third_party/lighthouse/locales/fi.json +290 -65
  86. package/front_end/third_party/lighthouse/locales/fil.json +290 -65
  87. package/front_end/third_party/lighthouse/locales/fr.json +294 -69
  88. package/front_end/third_party/lighthouse/locales/he.json +293 -68
  89. package/front_end/third_party/lighthouse/locales/hi.json +291 -66
  90. package/front_end/third_party/lighthouse/locales/hr.json +290 -65
  91. package/front_end/third_party/lighthouse/locales/hu.json +290 -65
  92. package/front_end/third_party/lighthouse/locales/id.json +290 -65
  93. package/front_end/third_party/lighthouse/locales/it.json +294 -69
  94. package/front_end/third_party/lighthouse/locales/ja.json +290 -65
  95. package/front_end/third_party/lighthouse/locales/ko.json +290 -65
  96. package/front_end/third_party/lighthouse/locales/lt.json +290 -65
  97. package/front_end/third_party/lighthouse/locales/lv.json +290 -65
  98. package/front_end/third_party/lighthouse/locales/nl.json +290 -65
  99. package/front_end/third_party/lighthouse/locales/no.json +290 -65
  100. package/front_end/third_party/lighthouse/locales/pl.json +290 -65
  101. package/front_end/third_party/lighthouse/locales/pt-PT.json +291 -66
  102. package/front_end/third_party/lighthouse/locales/pt.json +290 -65
  103. package/front_end/third_party/lighthouse/locales/ro.json +290 -65
  104. package/front_end/third_party/lighthouse/locales/ru.json +301 -76
  105. package/front_end/third_party/lighthouse/locales/sk.json +291 -66
  106. package/front_end/third_party/lighthouse/locales/sl.json +290 -65
  107. package/front_end/third_party/lighthouse/locales/sr-Latn.json +290 -65
  108. package/front_end/third_party/lighthouse/locales/sr.json +290 -65
  109. package/front_end/third_party/lighthouse/locales/sv.json +297 -72
  110. package/front_end/third_party/lighthouse/locales/ta.json +291 -66
  111. package/front_end/third_party/lighthouse/locales/te.json +293 -68
  112. package/front_end/third_party/lighthouse/locales/th.json +291 -66
  113. package/front_end/third_party/lighthouse/locales/tr.json +290 -65
  114. package/front_end/third_party/lighthouse/locales/uk.json +290 -65
  115. package/front_end/third_party/lighthouse/locales/vi.json +291 -66
  116. package/front_end/third_party/lighthouse/locales/zh-HK.json +292 -67
  117. package/front_end/third_party/lighthouse/locales/zh-TW.json +291 -66
  118. package/front_end/third_party/lighthouse/locales/zh.json +291 -66
  119. package/front_end/third_party/lighthouse/report/bundle.d.ts +6 -6
  120. package/front_end/third_party/lighthouse/report/bundle.js +4 -7
  121. package/front_end/third_party/lighthouse/report-assets/report-generator.mjs +2 -2
  122. package/front_end/ui/legacy/Widget.ts +32 -8
  123. package/front_end/ui/legacy/components/cookie_table/CookiesTable.ts +36 -3
  124. package/front_end/ui/legacy/components/data_grid/dataGridAiButton.css +20 -0
  125. package/front_end/ui/legacy/components/utils/Linkifier.ts +19 -4
  126. package/front_end/ui/visual_logging/KnownContextValues.ts +3 -0
  127. package/mcp/mcp.ts +1 -0
  128. package/package.json +1 -1
package/SECURITY.md CHANGED
@@ -22,6 +22,7 @@ Chrome DevTools is a privileged web app running in a sandboxed renderer process,
22
22
  * **Legitimate Data Exposure:** Displaying local user data, cookies, tokens, or auth headers within UI panels.
23
23
  * **Local Data Persistence:** Saving user-initiated traces, heaps, profiles, or logs to disk.
24
24
  * **Correctness & Availability:** Stale, misleading, or missing UI information (classified as functional bugs).
25
+ * **Denial of Service:** Chrome extensions or debug targets that disrupt DevTools' availability, e.g. through performance degradation.
25
26
  * **Disabled-by-default Experimental Features:** Experimental features or capabilities that are disabled by default, e.g. requiring a command-line flag to be activated, or a specific runtime setting within DevTools to be manually turned on.
26
27
 
27
28
  ## Specific Severity Classification Rules (S1–S4)
@@ -496,7 +496,8 @@ export enum Action {
496
496
  AiCodeGenerationRequestTriggeredFromSources = 205,
497
497
  AiCodeCompletionFreCompletedFromConsole = 206,
498
498
  AiCodeCompletionFreCompletedFromSources = 207,
499
- MAX_VALUE = 208,
499
+ AiAssistanceOpenedFromStoragePanelFloatingButton = 208,
500
+ MAX_VALUE = 209,
500
501
  /* eslint-enable @typescript-eslint/naming-convention */
501
502
  }
502
503
 
@@ -339,6 +339,10 @@ export class TargetBase {
339
339
  return this.getAgent('Autofill');
340
340
  }
341
341
 
342
+ adsAgent(): ProtocolProxyApi.AdsApi {
343
+ return this.getAgent('Ads');
344
+ }
345
+
342
346
  browserAgent(): ProtocolProxyApi.BrowserApi {
343
347
  return this.getAgent('Browser');
344
348
  }
@@ -196,6 +196,36 @@ function queryMatches(style: CSSStyleDeclaration): boolean {
196
196
  return true;
197
197
  }
198
198
 
199
+ function treeScopeDistance(node: DOMNode, property: CSSProperty): number {
200
+ if (!property.ownerStyle.parentRule && property.ownerStyle.type !== Type.Inline) {
201
+ return -1;
202
+ }
203
+ const root = node.getTreeRoot();
204
+ const nodeId = property.ownerStyle.parentRule?.treeScope ?? root?.backendNodeId();
205
+ if (nodeId === undefined) {
206
+ return -1;
207
+ }
208
+ return distanceToTreeScope(node, nodeId);
209
+ }
210
+
211
+ /**
212
+ * Distance from node to parent that is the tree scope. Returns -1 if tree scope is not found.
213
+ *
214
+ * @param node The child node to start from.
215
+ * @param treeScope The tree scope node ID.
216
+ * @returns The distance to the tree scope node, or -1 if not found.
217
+ */
218
+ export function distanceToTreeScope(node: DOMNode, treeScope: Protocol.DOM.BackendNodeId): number {
219
+ let distance = 0;
220
+ for (let ancestor: DOMNode|null = node; ancestor; ancestor = ancestor.parentNode) {
221
+ if (ancestor.backendNodeId() === treeScope) {
222
+ return distance;
223
+ }
224
+ distance++;
225
+ }
226
+ return -1;
227
+ }
228
+
199
229
  export interface CSSMatchedStylesPayload {
200
230
  cssModel: CSSModel;
201
231
  node: DOMNode;
@@ -307,7 +337,7 @@ export class CSSMatchedStyles {
307
337
  #customHighlightPseudoDOMCascades?: Map<string, DOMInheritanceCascade>;
308
338
  #functionRules: CSSFunctionRule[];
309
339
  #atRules: CSSAtRule[];
310
- #functionRuleMap = new Map<string, CSSFunctionRule>();
340
+ #functionRuleMap = new Map<string, CSSFunctionRule[]>();
311
341
  #environmentVariables: Record<string, string> = {};
312
342
 
313
343
  static async create(payload: CSSMatchedStylesPayload): Promise<CSSMatchedStyles> {
@@ -381,7 +411,9 @@ export class CSSMatchedStyles {
381
411
  }
382
412
 
383
413
  for (const rule of this.#functionRules) {
384
- this.#functionRuleMap.set(rule.functionName().text, rule);
414
+ const rules = this.#functionRuleMap.get(rule.functionName().text) ?? [];
415
+ rules.push(rule);
416
+ this.#functionRuleMap.set(rule.functionName().text, rules);
385
417
  }
386
418
  }
387
419
 
@@ -790,9 +822,25 @@ export class CSSMatchedStyles {
790
822
  return this.#registeredPropertyMap.get(name);
791
823
  }
792
824
 
793
- getRegisteredFunction(name: string): string|undefined {
794
- const functionRule = this.#functionRuleMap.get(name);
795
- return functionRule ? functionRule.nameWithParameters() : undefined;
825
+ getRegisteredFunction(name: string, sourceProperty: CSSProperty):
826
+ {treeScopeDistance: number, registeredFunction?: string} {
827
+ const minTreeScopeDistance = treeScopeDistance(this.#node, sourceProperty);
828
+ const functionRules = this.#functionRuleMap.get(name) ?? [];
829
+ let result: {treeScopeDistance: number, registeredFunction?: string} = {treeScopeDistance: -1};
830
+ for (const functionRule of functionRules) {
831
+ if (!functionRule.treeScope) {
832
+ continue;
833
+ }
834
+ const distance = distanceToTreeScope(this.#node, functionRule.treeScope);
835
+ if (distance === -1 || distance < minTreeScopeDistance) {
836
+ continue;
837
+ }
838
+
839
+ if (result.treeScopeDistance === -1 || distance < result.treeScopeDistance) {
840
+ result = {registeredFunction: functionRule.nameWithParameters(), treeScopeDistance: distance};
841
+ }
842
+ }
843
+ return result;
796
844
  }
797
845
 
798
846
  functionRules(): CSSFunctionRule[] {
@@ -1045,26 +1093,6 @@ class NodeCascade {
1045
1093
  }
1046
1094
  }
1047
1095
 
1048
- #treeScopeDistance(property: CSSProperty): number {
1049
- if (!property.ownerStyle.parentRule && property.ownerStyle.type !== Type.Inline) {
1050
- return -1;
1051
- }
1052
- const root = this.#node.getTreeRoot();
1053
- const nodeId = property.ownerStyle.parentRule?.treeScope ?? root?.backendNodeId();
1054
- if (nodeId === undefined) {
1055
- return -1;
1056
- }
1057
-
1058
- let distance = 0;
1059
- for (let ancestor: DOMNode|null = this.#node; ancestor; ancestor = ancestor.parentNode) {
1060
- if (ancestor.backendNodeId() === nodeId) {
1061
- return distance;
1062
- }
1063
- distance++;
1064
- }
1065
- return -1;
1066
- }
1067
-
1068
1096
  #needsCascadeContextStep(): boolean {
1069
1097
  if (!this.#node.isInShadowTree()) {
1070
1098
  return false;
@@ -1084,7 +1112,8 @@ class NodeCascade {
1084
1112
  const activeProperty = this.activeProperties.get(canonicalName);
1085
1113
  if (activeProperty?.important && !propertyWithHigherSpecificity.important ||
1086
1114
  activeProperty && this.#needsCascadeContextStep() &&
1087
- this.#treeScopeDistance(activeProperty) > this.#treeScopeDistance(propertyWithHigherSpecificity)) {
1115
+ treeScopeDistance(this.#node, activeProperty) >
1116
+ treeScopeDistance(this.#node, propertyWithHigherSpecificity)) {
1088
1117
  this.propertiesState.set(propertyWithHigherSpecificity, PropertyState.OVERLOADED);
1089
1118
  return;
1090
1119
  }
@@ -418,6 +418,7 @@ export class CSSFunctionRule extends CSSRule {
418
418
  styleSheetId: payload.styleSheetId
419
419
  },
420
420
  header: styleSheetHeaderForRule(cssModel, payload),
421
+ originTreeScopeNodeId: payload.originTreeScopeNodeId
421
422
  });
422
423
  this.#name = new CSSValue(payload.name);
423
424
  this.#parameters = payload.parameters.map(({name}) => name);
@@ -535,6 +535,11 @@ export class DebuggerModel extends SDKModel<EventTypes> {
535
535
  return this.#scripts.get(scriptId) || null;
536
536
  }
537
537
 
538
+ isWasm(scriptId: string): boolean {
539
+ const script = this.scriptForId(scriptId);
540
+ return script ? script.isWasm() : false;
541
+ }
542
+
538
543
  /**
539
544
  * Returns all `Script` objects with the same provided `sourceURL`. The
540
545
  * resulting array is sorted by time with the newest `Script` in the front.
@@ -41,7 +41,7 @@ class GreenDevFloaty {
41
41
  #playButton!: HTMLButtonElement;
42
42
  #node?: SDK.DOMModel.DOMNode;
43
43
  #agent?: AiAssistance.GreenDevAgent.GreenDevAgent|AiAssistance.StylingAgent.StylingAgent;
44
- #nodeContext?: AiAssistance.StylingAgent.NodeContext;
44
+ #nodeContext?: AiAssistance.DOMNodeContext.DOMNodeContext;
45
45
  #backendNodeId?: Protocol.DOM.BackendNodeId;
46
46
  #syncChannel: BroadcastChannel;
47
47
  #isFloatyWindow: boolean;
@@ -341,7 +341,7 @@ class GreenDevFloaty {
341
341
  });
342
342
  } else {
343
343
  this.#agent = new AiAssistance.StylingAgent.StylingAgent({aidaClient});
344
- this.#nodeContext = new AiAssistance.StylingAgent.NodeContext(this.#node);
344
+ this.#nodeContext = new AiAssistance.DOMNodeContext.DOMNodeContext(this.#node);
345
345
  }
346
346
  }
347
347
 
@@ -428,7 +428,8 @@ class GreenDevFloaty {
428
428
  'Could not get the backendNodeId for the selected element.';
429
429
 
430
430
  // --- Get some context information about the selected node ---
431
- const elementContext = await AiAssistance.StylingAgent.StylingAgent.describeElement(this.#node);
431
+ const domNodeContext = new AiAssistance.DOMNodeContext.DOMNodeContext(this.#node);
432
+ const elementContext = await domNodeContext.describe();
432
433
 
433
434
  // Now construct the full context.
434
435
  const context = `# Page URL
@@ -20,7 +20,8 @@ import * as AiAssistance from '../../models/ai_assistance/ai_assistance.js';
20
20
 
21
21
  const {AidaClient} = Host.AidaClient;
22
22
  const {ResponseType} = AiAssistance.AiAgent;
23
- const {NodeContext, StylingAgent} = AiAssistance.StylingAgent;
23
+ const {DOMNodeContext} = AiAssistance.DOMNodeContext;
24
+ const {StylingAgent} = AiAssistance.StylingAgent;
24
25
 
25
26
  class GreenDevFloaty {
26
27
  #chatContainer: HTMLDivElement;
@@ -28,7 +29,7 @@ class GreenDevFloaty {
28
29
  #playButton: HTMLButtonElement;
29
30
  #node?: SDK.DOMModel.DOMNode;
30
31
  #agent?: StylingAgent;
31
- #nodeContext?: NodeContext;
32
+ #nodeContext?: DOMNodeContext;
32
33
  #backendNodeId?: Protocol.DOM.BackendNodeId;
33
34
  // Switching this to false can help while investigating tool conflicts.
34
35
  #highlightNodeOnWindowFocus = false;
@@ -170,7 +171,7 @@ class GreenDevFloaty {
170
171
  if (!this.#agent) {
171
172
  const aidaClient = new AidaClient();
172
173
  this.#agent = new StylingAgent({aidaClient});
173
- this.#nodeContext = new NodeContext(this.#node);
174
+ this.#nodeContext = new DOMNodeContext(this.#node);
174
175
  }
175
176
 
176
177
  this.#addMessage(query, true);
@@ -876,7 +876,7 @@ export abstract class HeapSnapshot {
876
876
  readonly #progress: HeapSnapshotProgress;
877
877
  readonly #noDistance = -5;
878
878
  rootNodeIndexInternal = 0;
879
- #snapshotDiffs: Record<string, Record<string, HeapSnapshotModel.HeapSnapshotModel.Diff>> = {};
879
+ #snapshotDiffs: Record<number, Record<string, HeapSnapshotModel.HeapSnapshotModel.Diff>> = {};
880
880
  #aggregatesForDiff?: {
881
881
  interfaceDefinitions: string,
882
882
  aggregates: Record<string, HeapSnapshotModel.HeapSnapshotModel.AggregateForDiff>,
@@ -2688,9 +2688,8 @@ export abstract class HeapSnapshot {
2688
2688
  throw new Error('Not implemented');
2689
2689
  }
2690
2690
 
2691
- calculateSnapshotDiff(
2692
- baseSnapshotId: string,
2693
- baseSnapshotAggregates: Record<string, HeapSnapshotModel.HeapSnapshotModel.AggregateForDiff>):
2691
+ calculateSnapshotDiff(baseSnapshotId: number,
2692
+ baseSnapshotAggregates: Record<string, HeapSnapshotModel.HeapSnapshotModel.AggregateForDiff>):
2694
2693
  Record<string, HeapSnapshotModel.HeapSnapshotModel.Diff> {
2695
2694
  let snapshotDiff: Record<string, HeapSnapshotModel.HeapSnapshotModel.Diff> = this.#snapshotDiffs[baseSnapshotId];
2696
2695
  if (snapshotDiff) {
@@ -2976,7 +2975,7 @@ export abstract class HeapSnapshot {
2976
2975
  return {paths, limitsReached};
2977
2976
  }
2978
2977
 
2979
- createAddedNodesProvider(baseSnapshotId: string, classKey: string): HeapSnapshotNodesProvider {
2978
+ createAddedNodesProvider(baseSnapshotId: number, classKey: string): HeapSnapshotNodesProvider {
2980
2979
  const snapshotDiff = this.#snapshotDiffs[baseSnapshotId];
2981
2980
  const diffForClass = snapshotDiff[classKey];
2982
2981
  return new HeapSnapshotNodesProvider(this, diffForClass.addedIndexes);
@@ -435,7 +435,7 @@ inspectorBackend.registerCommand("DOM.getFrameOwner", [{"name": "frameId", "type
435
435
  inspectorBackend.registerCommand("DOM.getContainerForNode", [{"name": "nodeId", "type": "number", "optional": false, "description": "", "typeRef": "DOM.NodeId"}, {"name": "containerName", "type": "string", "optional": true, "description": "", "typeRef": null}, {"name": "physicalAxes", "type": "string", "optional": true, "description": "", "typeRef": "DOM.PhysicalAxes"}, {"name": "logicalAxes", "type": "string", "optional": true, "description": "", "typeRef": "DOM.LogicalAxes"}, {"name": "queriesScrollState", "type": "boolean", "optional": true, "description": "", "typeRef": null}, {"name": "queriesAnchored", "type": "boolean", "optional": true, "description": "", "typeRef": null}], ["nodeId"], "Returns the query container of the given node based on container query conditions: containerName, physical and logical axes, and whether it queries scroll-state or anchored elements. If no axes are provided and queriesScrollState is false, the style container is returned, which is the direct parent or the closest element with a matching container-name.");
436
436
  inspectorBackend.registerCommand("DOM.getQueryingDescendantsForContainer", [{"name": "nodeId", "type": "number", "optional": false, "description": "Id of the container node to find querying descendants from.", "typeRef": "DOM.NodeId"}], ["nodeIds"], "Returns the descendants of a container query container that have container queries against this container.");
437
437
  inspectorBackend.registerCommand("DOM.getAnchorElement", [{"name": "nodeId", "type": "number", "optional": false, "description": "Id of the positioned element from which to find the anchor.", "typeRef": "DOM.NodeId"}, {"name": "anchorSpecifier", "type": "string", "optional": true, "description": "An optional anchor specifier, as defined in https://www.w3.org/TR/css-anchor-position-1/#anchor-specifier. If not provided, it will return the implicit anchor element for the given positioned element.", "typeRef": null}], ["nodeId"], "Returns the target anchor element of the given anchor query according to https://www.w3.org/TR/css-anchor-position-1/#target.");
438
- inspectorBackend.registerCommand("DOM.forceShowPopover", [{"name": "nodeId", "type": "number", "optional": false, "description": "Id of the popover HTMLElement", "typeRef": "DOM.NodeId"}, {"name": "enable", "type": "boolean", "optional": false, "description": "If true, opens the popover and keeps it open. If false, closes the popover if it was previously force-opened.", "typeRef": null}], ["nodeIds"], "When enabling, this API force-opens the popover identified by nodeId and keeps it open until disabled.");
438
+ inspectorBackend.registerCommand("DOM.forceShowPopover", [{"name": "nodeId", "type": "number", "optional": false, "description": "Id of the popover HTMLElement", "typeRef": "DOM.NodeId"}, {"name": "enable", "type": "boolean", "optional": false, "description": "If true, opens the popover and keeps it open. If false, closes the popover if it was previously force-opened.", "typeRef": null}, {"name": "invokerNodeId", "type": "number", "optional": true, "description": "Optional ID of the element invoking this popover, used to establish the implicit anchor. If not provided, it will fall back to the first invoker in the document, preferring elements with a popovertarget attribute over those with a commandfor attribute. Note that if there are multiple invokers, this is just an estimate.", "typeRef": "DOM.BackendNodeId"}], ["nodeIds"], "When enabling, this API force-opens the popover identified by nodeId and keeps it open until disabled.");
439
439
  inspectorBackend.registerType("DOM.BackendNode", [{"name": "nodeType", "type": "number", "optional": false, "description": "`Node`'s nodeType.", "typeRef": null}, {"name": "nodeName", "type": "string", "optional": false, "description": "`Node`'s nodeName.", "typeRef": null}, {"name": "backendNodeId", "type": "number", "optional": false, "description": "", "typeRef": "DOM.BackendNodeId"}]);
440
440
  inspectorBackend.registerType("DOM.Node", [{"name": "nodeId", "type": "number", "optional": false, "description": "Node identifier that is passed into the rest of the DOM messages as the `nodeId`. Backend will only push node with given `id` once. It is aware of all requested nodes and will only fire DOM events for nodes known to the client.", "typeRef": "DOM.NodeId"}, {"name": "parentId", "type": "number", "optional": true, "description": "The id of the parent node if any.", "typeRef": "DOM.NodeId"}, {"name": "backendNodeId", "type": "number", "optional": false, "description": "The BackendNodeId for this node.", "typeRef": "DOM.BackendNodeId"}, {"name": "nodeType", "type": "number", "optional": false, "description": "`Node`'s nodeType.", "typeRef": null}, {"name": "nodeName", "type": "string", "optional": false, "description": "`Node`'s nodeName.", "typeRef": null}, {"name": "localName", "type": "string", "optional": false, "description": "`Node`'s localName.", "typeRef": null}, {"name": "nodeValue", "type": "string", "optional": false, "description": "`Node`'s nodeValue.", "typeRef": null}, {"name": "childNodeCount", "type": "number", "optional": true, "description": "Child count for `Container` nodes.", "typeRef": null}, {"name": "children", "type": "array", "optional": true, "description": "Child nodes of this node when requested with children.", "typeRef": "DOM.Node"}, {"name": "attributes", "type": "array", "optional": true, "description": "Attributes of the `Element` node in the form of flat array `[name1, value1, name2, value2]`.", "typeRef": "string"}, {"name": "documentURL", "type": "string", "optional": true, "description": "Document URL that `Document` or `FrameOwner` node points to.", "typeRef": null}, {"name": "baseURL", "type": "string", "optional": true, "description": "Base URL that `Document` or `FrameOwner` node uses for URL completion.", "typeRef": null}, {"name": "publicId", "type": "string", "optional": true, "description": "`DocumentType`'s publicId.", "typeRef": null}, {"name": "systemId", "type": "string", "optional": true, "description": "`DocumentType`'s systemId.", "typeRef": null}, {"name": "internalSubset", "type": "string", "optional": true, "description": "`DocumentType`'s internalSubset.", "typeRef": null}, {"name": "xmlVersion", "type": "string", "optional": true, "description": "`Document`'s XML version in case of XML documents.", "typeRef": null}, {"name": "name", "type": "string", "optional": true, "description": "`Attr`'s name.", "typeRef": null}, {"name": "value", "type": "string", "optional": true, "description": "`Attr`'s value.", "typeRef": null}, {"name": "pseudoType", "type": "string", "optional": true, "description": "Pseudo element type for this node.", "typeRef": "DOM.PseudoType"}, {"name": "pseudoIdentifier", "type": "string", "optional": true, "description": "Pseudo element identifier for this node. Only present if there is a valid pseudoType.", "typeRef": null}, {"name": "shadowRootType", "type": "string", "optional": true, "description": "Shadow root type.", "typeRef": "DOM.ShadowRootType"}, {"name": "frameId", "type": "string", "optional": true, "description": "Frame ID for frame owner elements.", "typeRef": "Page.FrameId"}, {"name": "contentDocument", "type": "object", "optional": true, "description": "Content document for frame owner elements.", "typeRef": "DOM.Node"}, {"name": "shadowRoots", "type": "array", "optional": true, "description": "Shadow root list for given element host.", "typeRef": "DOM.Node"}, {"name": "templateContent", "type": "object", "optional": true, "description": "Content document fragment for template elements.", "typeRef": "DOM.Node"}, {"name": "pseudoElements", "type": "array", "optional": true, "description": "Pseudo elements associated with this node.", "typeRef": "DOM.Node"}, {"name": "importedDocument", "type": "object", "optional": true, "description": "Deprecated, as the HTML Imports API has been removed (crbug.com/937746). This property used to return the imported document for the HTMLImport links. The property is always undefined now.", "typeRef": "DOM.Node"}, {"name": "distributedNodes", "type": "array", "optional": true, "description": "Distributed nodes for given insertion point.", "typeRef": "DOM.BackendNode"}, {"name": "isSVG", "type": "boolean", "optional": true, "description": "Whether the node is SVG.", "typeRef": null}, {"name": "compatibilityMode", "type": "string", "optional": true, "description": "", "typeRef": "DOM.CompatibilityMode"}, {"name": "assignedSlot", "type": "object", "optional": true, "description": "", "typeRef": "DOM.BackendNode"}, {"name": "isScrollable", "type": "boolean", "optional": true, "description": "", "typeRef": null}, {"name": "affectedByStartingStyles", "type": "boolean", "optional": true, "description": "", "typeRef": null}, {"name": "adoptedStyleSheets", "type": "array", "optional": true, "description": "", "typeRef": "DOM.StyleSheetId"}, {"name": "adProvenance", "type": "object", "optional": true, "description": "", "typeRef": "Network.AdProvenance"}]);
441
441
  inspectorBackend.registerType("DOM.DetachedElementInfo", [{"name": "treeNode", "type": "object", "optional": false, "description": "", "typeRef": "DOM.Node"}, {"name": "retainedNodeIds", "type": "array", "optional": false, "description": "", "typeRef": "DOM.NodeId"}]);
@@ -5849,6 +5849,13 @@ export namespace DOM {
5849
5849
  * popover if it was previously force-opened.
5850
5850
  */
5851
5851
  enable: boolean;
5852
+ /**
5853
+ * Optional ID of the element invoking this popover, used to establish the implicit anchor.
5854
+ * If not provided, it will fall back to the first invoker in the document, preferring
5855
+ * elements with a popovertarget attribute over those with a commandfor attribute. Note that
5856
+ * if there are multiple invokers, this is just an estimate.
5857
+ */
5858
+ invokerNodeId?: BackendNodeId;
5852
5859
  }
5853
5860
 
5854
5861
  export interface ForceShowPopoverResponse extends ProtocolResponseWithError {
@@ -5,16 +5,26 @@
5
5
  import * as Host from '../../core/host/host.js';
6
6
 
7
7
  import {
8
- type AgentOptions,
9
8
  AiAgent,
10
9
  type ContextResponse,
11
10
  type ConversationContext,
11
+ type MultimodalInputType,
12
12
  type RequestOptions,
13
13
  ResponseType
14
14
  } from './agents/AiAgent.js';
15
+ import {type ExecuteJsAgentOptions, executeJsCode} from './agents/ExecuteJavascript.js';
16
+ import {ChangeManager} from './ChangeManager.js';
17
+ import {DOMNodeContext} from './contexts/DOMNodeContext.js';
15
18
  import {debugLog} from './debug.js';
19
+ import {ExtensionScope} from './ExtensionScope.js';
16
20
  import type {Skill, SkillName} from './skills/Skill.js';
17
21
  import {SKILLS} from './skills/SkillRegistry.js';
22
+ import type {Tool} from './tools/Tool.js';
23
+ import {ToolRegistry} from './tools/ToolRegistry.js';
24
+
25
+ const SKILL_DISPLAY_NAMES: Record<SkillName, string> = {
26
+ styling: 'CSS and styling',
27
+ };
18
28
 
19
29
  export class AiAgent2 extends AiAgent<unknown> {
20
30
  // TODO: The static preamble is a placeholder and will eventually live server-side.
@@ -23,15 +33,20 @@ export class AiAgent2 extends AiAgent<unknown> {
23
33
  readonly userTier = 'TESTERS';
24
34
 
25
35
  #skillsInjected = false;
36
+ #changes = new ChangeManager();
37
+ #execJs: typeof executeJsCode;
26
38
 
27
39
  get options(): RequestOptions {
28
40
  return {};
29
41
  }
30
42
 
31
43
  readonly #activeSkills = new Set<SkillName>();
44
+ readonly #declaredTools = new Set<string>();
32
45
 
33
- constructor(opts: AgentOptions) {
46
+ constructor(opts: ExecuteJsAgentOptions) {
34
47
  super(opts);
48
+ this.#execJs = opts.execJs ?? executeJsCode;
49
+ this.#declaredTools.add('learnSkills');
35
50
  const skillsList = Object.keys(SKILLS).join(', ');
36
51
  this.declareFunction<{skills: SkillName[]}>('learnSkills', {
37
52
  description: `Load skills to help with the task. Available skills: ${skillsList}.`,
@@ -51,8 +66,12 @@ export class AiAgent2 extends AiAgent<unknown> {
51
66
  required: ['skills'],
52
67
  },
53
68
  displayInfoFromArgs: args => {
69
+ const isSingular = args.skills.length === 1;
70
+ const prefix = isSingular ? 'Learning skill' : 'Learning skills';
71
+ const names = args.skills.map(name => SKILL_DISPLAY_NAMES[name] ?? name).join(', ');
54
72
  return {
55
- title: `Learning skills: ${args.skills.join(', ')}`,
73
+ title: `${prefix}: ${names}`,
74
+ action: `learnSkills(${args.skills.map(name => `'${name}'`).join(', ')})`,
56
75
  };
57
76
  },
58
77
  handler: async args => {
@@ -62,33 +81,58 @@ export class AiAgent2 extends AiAgent<unknown> {
62
81
  });
63
82
  }
64
83
 
65
- override async enhanceQuery(query: string): Promise<string> {
84
+ override async enhanceQuery(
85
+ query: string,
86
+ selected: ConversationContext<unknown>|null = null,
87
+ // TODO: support multimodal input in AiAgent2.
88
+ _multimodalInputType?: MultimodalInputType,
89
+ ): Promise<string> {
90
+ let enhancedQuery = query;
91
+ if (selected) {
92
+ const promptDetails = await selected.getPromptDetails();
93
+ if (promptDetails) {
94
+ enhancedQuery = `${promptDetails}
95
+
96
+ # User request
97
+
98
+ QUERY: ${query}`;
99
+ }
100
+ }
101
+
66
102
  if (this.#skillsInjected) {
67
- return query;
103
+ return enhancedQuery;
68
104
  }
69
105
  this.#skillsInjected = true;
70
- const skillsManifest = Object.entries(SKILLS).map(([name, skill]) => `- ${name}: ${skill.description}`).join('\n');
106
+ const skillsManifest =
107
+ Object.entries(this.getSkills()).map(([name, skill]) => `- ${name}: ${skill.description}`).join('\n');
71
108
  return `Available skills:
72
109
  ${skillsManifest}
73
110
 
74
111
  You must call \`learnSkills\` to load a skill before you can use it.
75
112
 
76
- User query: ${query}`;
113
+ User query: ${enhancedQuery}`;
114
+ }
115
+
116
+ override async *
117
+ handleContextDetails(selected: ConversationContext<unknown>|null): AsyncGenerator<ContextResponse, void, void> {
118
+ if (selected) {
119
+ const details = await selected.getUserFacingDetails();
120
+ if (details) {
121
+ yield {
122
+ type: ResponseType.CONTEXT,
123
+ details,
124
+ };
125
+ }
126
+ }
77
127
  }
78
128
 
79
- async *
80
- handleContextDetails(_select: ConversationContext<unknown>|null): AsyncGenerator<ContextResponse, void, void> {
81
- yield {
82
- type: ResponseType.CONTEXT,
83
- details: [{
84
- title: 'Status',
85
- text: 'Minimal agent initialized.',
86
- }],
87
- };
129
+ getSkills(): Record<SkillName, Skill> {
130
+ return SKILLS;
88
131
  }
89
132
 
90
133
  async learnSkill(names: SkillName[]): Promise<string> {
91
134
  let response = '';
135
+ const skills = this.getSkills();
92
136
  for (const name of names) {
93
137
  debugLog(`AiAgent2: Attempting to load skill ${name}`);
94
138
  if (this.#activeSkills.has(name)) {
@@ -97,19 +141,57 @@ User query: ${query}`;
97
141
  continue;
98
142
  }
99
143
 
100
- const skillObj: Skill = SKILLS[name];
144
+ const skillObj: Skill = skills[name];
101
145
  if (skillObj) {
102
146
  this.#activeSkills.add(name);
103
147
  debugLog(`AiAgent2: Skill ${name} loaded successfully`);
104
148
  response += `Skill ${name} loaded. Instructions:\n${skillObj.instructions}\n`;
149
+ for (const toolName of skillObj.allowedTools) {
150
+ const tool = ToolRegistry.get(toolName);
151
+ if (tool) {
152
+ this.#declareTool(tool);
153
+ }
154
+ }
105
155
  } else {
106
156
  debugLog(`AiAgent2: Failed to load skill ${name}`);
107
- response += `Failed to load skill ${name}. Valid skills are: ${Object.keys(SKILLS).join(', ')}.\n`;
157
+ response += `Failed to load skill ${name}. Valid skills are: ${Object.keys(skills).join(', ')}.\n`;
108
158
  }
109
159
  }
110
160
  return response.trim();
111
161
  }
112
162
 
163
+ #createExtensionScope(changes: ChangeManager): {install(): Promise<void>, uninstall(): Promise<void>} {
164
+ const selectedNode = this.context && this.context instanceof DOMNodeContext ? this.context.getItem() : null;
165
+ return new ExtensionScope(changes, this.sessionId, selectedNode);
166
+ }
167
+
168
+ /**
169
+ * Declares a tool to be available to the agent model, verifying first that
170
+ * it hasn't already been declared to prevent duplicate declaration errors.
171
+ */
172
+ #declareTool(tool: Tool): void {
173
+ if (this.#declaredTools.has(tool.name)) {
174
+ debugLog(`AiAgent2: Tool ${tool.name} is already declared`);
175
+ return;
176
+ }
177
+ this.#declaredTools.add(tool.name);
178
+ this.declareFunction(tool.name, {
179
+ description: tool.description,
180
+ parameters: tool.parameters,
181
+ displayInfoFromArgs: tool.displayInfoFromArgs,
182
+ handler: (args, options) => tool.handler(
183
+ args,
184
+ {
185
+ conversationContext: this.context ?? null,
186
+ changeManager: this.#changes,
187
+ createExtensionScope: this.#createExtensionScope.bind(this),
188
+ execJs: this.#execJs,
189
+ },
190
+ options,
191
+ ),
192
+ });
193
+ }
194
+
113
195
  get activeSkills(): Set<SkillName> {
114
196
  return this.#activeSkills;
115
197
  }
@@ -29,10 +29,11 @@ import {FileAgent, FileContext} from './agents/FileAgent.js';
29
29
  import {NetworkAgent, RequestContext} from './agents/NetworkAgent.js';
30
30
  import {PerformanceAgent, PerformanceTraceContext} from './agents/PerformanceAgent.js';
31
31
  import {StorageAgent, StorageContext} from './agents/StorageAgent.js';
32
- import {NodeContext, StylingAgent} from './agents/StylingAgent.js';
32
+ import {StylingAgent} from './agents/StylingAgent.js';
33
33
  import {AiAgent2} from './AiAgent2.js';
34
34
  import {AiHistoryStorage, ConversationType, type SerializedConversation} from './AiHistoryStorage.js';
35
35
  import type {ChangeManager} from './ChangeManager.js';
36
+ import {DOMNodeContext} from './contexts/DOMNodeContext.js';
36
37
 
37
38
  export const NOT_FOUND_IMAGE_DATA = '';
38
39
  export const CONTEXT_TITLE = 'Analyzing data';
@@ -182,7 +183,7 @@ export class AiConversation {
182
183
  if (isAiAssistanceContextSelectionAgentEnabled()) {
183
184
  if (updateContext instanceof FileContext) {
184
185
  this.#updateAgent(ConversationType.FILE);
185
- } else if (updateContext instanceof NodeContext) {
186
+ } else if (updateContext instanceof DOMNodeContext) {
186
187
  this.#updateAgent(ConversationType.STYLING);
187
188
  } else if (updateContext instanceof RequestContext) {
188
189
  this.#updateAgent(ConversationType.NETWORK);
@@ -327,24 +328,27 @@ export class AiConversation {
327
328
  };
328
329
  }
329
330
 
331
+ #filterHistoryForNewAgent(): Host.AidaClient.Content[] {
332
+ return this.#agent?.history
333
+ .map(content => {
334
+ return {
335
+ ...content,
336
+ parts: content.parts.filter(part => !('functionCall' in part) && !('functionResponse' in part)),
337
+ };
338
+ })
339
+ .filter(content => content.parts.length > 0) ??
340
+ [];
341
+ }
342
+
330
343
  #updateAgent(type: ConversationType): void {
331
344
  if (this.#type === type) {
332
345
  return;
333
346
  }
334
347
 
335
- this.#type = type;
348
+ const isTransitioningFromStorage = this.#type === ConversationType.STORAGE && type !== ConversationType.STORAGE;
349
+ const history = isTransitioningFromStorage ? [] : this.#filterHistoryForNewAgent();
336
350
 
337
- // We need to filter out the function calls
338
- // as the LLM tries to call the existing ones.
339
- const history =
340
- this.#agent?.history
341
- .map(content => {
342
- return {
343
- ...content,
344
- parts: content.parts.filter(part => !('functionCall' in part) && !('functionResponse' in part)),
345
- };
346
- })
347
- .filter(content => content.parts.length > 0);
351
+ this.#type = type;
348
352
 
349
353
  const options = {
350
354
  aidaClient: this.#aidaClient,
@@ -8,6 +8,8 @@ import * as i18n from '../../core/i18n/i18n.js';
8
8
  import type * as Platform from '../../core/platform/platform.js';
9
9
  import * as Root from '../../core/root/root.js';
10
10
 
11
+ import {debugLog} from './debug.js';
12
+
11
13
  const UIStrings = {
12
14
  /**
13
15
  * @description Message shown to the user if the age check is not successful.
@@ -71,3 +73,72 @@ export function isSameOrigin(url1: Platform.DevToolsPath.UrlString, url2: Platfo
71
73
  const origin2 = Common.ParsedURL.ParsedURL.extractOrigin(url2);
72
74
  return origin1 !== '' && origin1 === origin2;
73
75
  }
76
+
77
+ export interface OneShotPromptRequest {
78
+ aidaClient: Host.AidaClient.AidaClient;
79
+ preamble: string;
80
+ query: string;
81
+ clientFeature: Host.AidaClient.ClientFeature;
82
+ temperature?: number;
83
+ modelId?: string;
84
+ userTier?: string;
85
+ serverSideLoggingEnabled?: boolean;
86
+ signal?: AbortSignal;
87
+ }
88
+
89
+ export async function runOneShotPrompt({
90
+ aidaClient,
91
+ preamble,
92
+ query,
93
+ clientFeature,
94
+ temperature,
95
+ modelId,
96
+ userTier,
97
+ serverSideLoggingEnabled,
98
+ signal,
99
+ }: OneShotPromptRequest): Promise<string> {
100
+ const chromeVersion = Root.Runtime.getChromeVersion();
101
+ if (!chromeVersion) {
102
+ throw new Error('Cannot determine Chrome version');
103
+ }
104
+ const disallowLogging = !serverSideLoggingEnabled;
105
+ const sessionId = crypto.randomUUID();
106
+
107
+ const userTierEnum = Host.AidaClient.convertToUserTierEnum(userTier);
108
+ const finalPreamble = userTierEnum === Host.AidaClient.UserTier.TESTERS ? preamble : undefined;
109
+
110
+ const request: Host.AidaClient.DoConversationRequest = {
111
+ client: Host.AidaClient.CLIENT_NAME,
112
+ current_message: {
113
+ parts: [{text: query}],
114
+ role: Host.AidaClient.Role.USER,
115
+ },
116
+ preamble: finalPreamble,
117
+ options: {
118
+ temperature: typeof temperature === 'number' && temperature >= 0 ? temperature : undefined,
119
+ model_id: modelId || undefined,
120
+ },
121
+ metadata: {
122
+ disable_user_content_logging: disallowLogging,
123
+ string_session_id: sessionId,
124
+ user_tier: userTierEnum,
125
+ client_version: chromeVersion,
126
+ },
127
+ functionality_type: Host.AidaClient.FunctionalityType.CHAT,
128
+ client_feature: clientFeature,
129
+ };
130
+
131
+ let textResponse = '';
132
+ try {
133
+ for await (const response of aidaClient.doConversation(request, {signal})) {
134
+ if (response.explanation) {
135
+ textResponse = response.explanation;
136
+ }
137
+ }
138
+ } catch (err) {
139
+ debugLog('Error calling AIDA for one-shot prompt', err);
140
+ throw err;
141
+ }
142
+
143
+ return textResponse;
144
+ }