chrome-devtools-frontend 1.0.1555430 → 1.0.1556696

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 (100) hide show
  1. package/front_end/entrypoints/formatter_worker/FormatterActions.ts +2 -0
  2. package/front_end/entrypoints/formatter_worker/ScopeParser.ts +75 -7
  3. package/front_end/entrypoints/formatter_worker/Substitute.ts +1 -1
  4. package/front_end/generated/InspectorBackendCommands.ts +1 -1
  5. package/front_end/generated/protocol.ts +0 -1
  6. package/front_end/models/ai_assistance/AiConversation.ts +71 -10
  7. package/front_end/models/ai_assistance/ArtifactsManager.ts +67 -0
  8. package/front_end/models/ai_assistance/ConversationHandler.ts +3 -2
  9. package/front_end/models/ai_assistance/agents/AiAgent.ts +17 -27
  10. package/front_end/models/ai_assistance/agents/PerformanceAgent.ts +151 -3
  11. package/front_end/models/ai_assistance/agents/StylingAgent.ts +1 -1
  12. package/front_end/models/ai_assistance/ai_assistance.ts +2 -0
  13. package/front_end/models/ai_assistance/data_formatters/PerformanceInsightFormatter.ts +0 -2
  14. package/front_end/models/annotations/AnnotationRepository.ts +2 -2
  15. package/front_end/models/greendev/Prototypes.ts +56 -0
  16. package/front_end/models/greendev/README.md +5 -0
  17. package/front_end/models/greendev/greendev.ts +5 -0
  18. package/front_end/models/trace/extras/TraceTree.ts +4 -2
  19. package/front_end/models/trace/insights/LCPDiscovery.ts +0 -2
  20. package/front_end/models/trace/types/TraceEvents.ts +0 -1
  21. package/front_end/panels/ai_assistance/AiAssistancePanel.ts +96 -91
  22. package/front_end/panels/ai_assistance/aiAssistancePanel.css +16 -0
  23. package/front_end/panels/ai_assistance/components/ArtifactsViewer.ts +109 -7
  24. package/front_end/panels/ai_assistance/components/ChatView.ts +2 -2
  25. package/front_end/panels/ai_assistance/components/CollapsibleAssistanceContentWidget.ts +7 -8
  26. package/front_end/panels/ai_assistance/components/PerformanceAgentFlameChart.ts +15 -8
  27. package/front_end/panels/ai_assistance/components/PerformanceAgentMarkdownRenderer.ts +9 -9
  28. package/front_end/panels/ai_assistance/components/artifactsViewer.css +6 -1
  29. package/front_end/panels/ai_assistance/components/collapsibleAssistanceContentWidget.css +5 -6
  30. package/front_end/panels/application/AppManifestView.ts +263 -205
  31. package/front_end/panels/application/ApplicationPanelSidebar.ts +24 -57
  32. package/front_end/panels/application/OpenedWindowDetailsView.ts +2 -0
  33. package/front_end/panels/application/ServiceWorkersView.ts +2 -0
  34. package/front_end/panels/application/StorageView.ts +1 -0
  35. package/front_end/panels/application/appManifestView.css +48 -0
  36. package/front_end/panels/application/components/ProtocolHandlersView.ts +2 -2
  37. package/front_end/panels/elements/ElementsTreeOutline.ts +1 -1
  38. package/front_end/panels/linear_memory_inspector/components/LinearMemoryInspector.ts +4 -8
  39. package/front_end/panels/linear_memory_inspector/components/LinearMemoryValueInterpreter.ts +148 -97
  40. package/front_end/panels/linear_memory_inspector/components/LinearMemoryViewer.ts +1 -1
  41. package/front_end/panels/linear_memory_inspector/components/linearMemoryValueInterpreter.css +37 -35
  42. package/front_end/panels/settings/SettingsScreen.ts +133 -1
  43. package/front_end/panels/settings/settings-meta.ts +24 -0
  44. package/front_end/panels/settings/settingsScreen.css +4 -0
  45. package/front_end/panels/sources/UISourceCodeFrame.ts +3 -17
  46. package/front_end/panels/timeline/components/insights/BaseInsightComponent.ts +2 -1
  47. package/front_end/third_party/acorn/estree-legacy.d.ts +2 -0
  48. package/front_end/third_party/chromium/README.chromium +1 -1
  49. package/front_end/third_party/puppeteer/README.chromium +2 -2
  50. package/front_end/third_party/puppeteer/package/lib/cjs/puppeteer/api/CDPSession.d.ts.map +1 -1
  51. package/front_end/third_party/puppeteer/package/lib/cjs/puppeteer/api/CDPSession.js.map +1 -1
  52. package/front_end/third_party/puppeteer/package/lib/cjs/puppeteer/api/ElementHandle.d.ts.map +1 -1
  53. package/front_end/third_party/puppeteer/package/lib/cjs/puppeteer/api/ElementHandle.js.map +1 -1
  54. package/front_end/third_party/puppeteer/package/lib/cjs/puppeteer/api/Frame.d.ts.map +1 -1
  55. package/front_end/third_party/puppeteer/package/lib/cjs/puppeteer/api/Frame.js.map +1 -1
  56. package/front_end/third_party/puppeteer/package/lib/cjs/puppeteer/api/Page.d.ts.map +1 -1
  57. package/front_end/third_party/puppeteer/package/lib/cjs/puppeteer/api/Page.js.map +1 -1
  58. package/front_end/third_party/puppeteer/package/lib/cjs/puppeteer/bidi/core/Connection.d.ts.map +1 -1
  59. package/front_end/third_party/puppeteer/package/lib/cjs/puppeteer/common/BrowserConnector.js +21 -7
  60. package/front_end/third_party/puppeteer/package/lib/cjs/puppeteer/common/BrowserConnector.js.map +1 -1
  61. package/front_end/third_party/puppeteer/package/lib/cjs/puppeteer/common/EventEmitter.d.ts.map +1 -1
  62. package/front_end/third_party/puppeteer/package/lib/cjs/puppeteer/injected/injected.d.ts +1 -1
  63. package/front_end/third_party/puppeteer/package/lib/cjs/puppeteer/util/Mutex.d.ts +2 -2
  64. package/front_end/third_party/puppeteer/package/lib/cjs/puppeteer/util/version.d.ts +1 -1
  65. package/front_end/third_party/puppeteer/package/lib/cjs/puppeteer/util/version.js +1 -1
  66. package/front_end/third_party/puppeteer/package/lib/es5-iife/puppeteer-core-browser.js +15 -6
  67. package/front_end/third_party/puppeteer/package/lib/esm/puppeteer/api/CDPSession.d.ts.map +1 -1
  68. package/front_end/third_party/puppeteer/package/lib/esm/puppeteer/api/CDPSession.js.map +1 -1
  69. package/front_end/third_party/puppeteer/package/lib/esm/puppeteer/api/ElementHandle.d.ts.map +1 -1
  70. package/front_end/third_party/puppeteer/package/lib/esm/puppeteer/api/ElementHandle.js.map +1 -1
  71. package/front_end/third_party/puppeteer/package/lib/esm/puppeteer/api/Frame.d.ts.map +1 -1
  72. package/front_end/third_party/puppeteer/package/lib/esm/puppeteer/api/Frame.js.map +1 -1
  73. package/front_end/third_party/puppeteer/package/lib/esm/puppeteer/api/Page.d.ts.map +1 -1
  74. package/front_end/third_party/puppeteer/package/lib/esm/puppeteer/api/Page.js.map +1 -1
  75. package/front_end/third_party/puppeteer/package/lib/esm/puppeteer/bidi/core/Connection.d.ts.map +1 -1
  76. package/front_end/third_party/puppeteer/package/lib/esm/puppeteer/common/BrowserConnector.js +21 -7
  77. package/front_end/third_party/puppeteer/package/lib/esm/puppeteer/common/BrowserConnector.js.map +1 -1
  78. package/front_end/third_party/puppeteer/package/lib/esm/puppeteer/common/EventEmitter.d.ts.map +1 -1
  79. package/front_end/third_party/puppeteer/package/lib/esm/puppeteer/util/version.d.ts +1 -1
  80. package/front_end/third_party/puppeteer/package/lib/esm/puppeteer/util/version.js +1 -1
  81. package/front_end/third_party/puppeteer/package/package.json +2 -2
  82. package/front_end/third_party/puppeteer/package/src/api/CDPSession.ts +1 -2
  83. package/front_end/third_party/puppeteer/package/src/api/ElementHandle.ts +2 -4
  84. package/front_end/third_party/puppeteer/package/src/api/Frame.ts +2 -4
  85. package/front_end/third_party/puppeteer/package/src/api/Page.ts +2 -4
  86. package/front_end/third_party/puppeteer/package/src/bidi/core/Connection.ts +3 -2
  87. package/front_end/third_party/puppeteer/package/src/common/BrowserConnector.ts +29 -10
  88. package/front_end/third_party/puppeteer/package/src/common/EventEmitter.ts +3 -3
  89. package/front_end/third_party/puppeteer/package/src/util/version.ts +1 -1
  90. package/front_end/ui/components/report_view/ReportView.docs.ts +37 -0
  91. package/front_end/ui/components/report_view/ReportView.ts +1 -4
  92. package/front_end/ui/components/settings/SettingCheckbox.ts +1 -1
  93. package/front_end/ui/legacy/Floaty.ts +5 -9
  94. package/front_end/ui/legacy/InspectorView.ts +2 -1
  95. package/front_end/ui/legacy/ReportView.ts +5 -4
  96. package/front_end/ui/legacy/Widget.ts +7 -0
  97. package/front_end/ui/legacy/components/perf_ui/FlameChart.ts +0 -1
  98. package/front_end/ui/legacy/reportView.css +0 -24
  99. package/front_end/ui/visual_logging/KnownContextValues.ts +7 -0
  100. package/package.json +1 -1
@@ -55,6 +55,8 @@ export interface ScopeTreeNode {
55
55
  variables: Array<{name: string, kind: DefinitionKind, offsets: number[]}>;
56
56
  start: number;
57
57
  end: number;
58
+ // If present, apply source map mappings to these locations to figure out the original function name.
59
+ nameMappingLocations?: number[];
58
60
  kind: ScopeKind;
59
61
  children: ScopeTreeNode[];
60
62
  }
@@ -17,7 +17,7 @@ export function parseScopes(expression: string, sourceType: 'module'|'script' =
17
17
  } catch {
18
18
  return null;
19
19
  }
20
- return new ScopeVariableAnalysis(root).run();
20
+ return new ScopeVariableAnalysis(root, expression).run();
21
21
  }
22
22
 
23
23
  export interface Use {
@@ -37,13 +37,15 @@ export class Scope {
37
37
  readonly start: number;
38
38
  readonly end: number;
39
39
  readonly kind: ScopeKind;
40
+ readonly nameMappingLocations?: number[];
40
41
  readonly children: Scope[] = [];
41
42
 
42
- constructor(start: number, end: number, parent: Scope|null, kind: ScopeKind) {
43
+ constructor(start: number, end: number, parent: Scope|null, kind: ScopeKind, nameMappingLocations?: number[]) {
43
44
  this.start = start;
44
45
  this.end = end;
45
46
  this.parent = parent;
46
47
  this.kind = kind;
48
+ this.nameMappingLocations = nameMappingLocations;
47
49
  if (parent) {
48
50
  parent.children.push(this);
49
51
  }
@@ -64,6 +66,7 @@ export class Scope {
64
66
  end: this.end,
65
67
  variables,
66
68
  kind: this.kind,
69
+ nameMappingLocations: this.nameMappingLocations,
67
70
  children,
68
71
  };
69
72
  }
@@ -137,9 +140,12 @@ export class ScopeVariableAnalysis {
137
140
  readonly #allNames = new Set<string>();
138
141
  #currentScope: Scope;
139
142
  readonly #rootNode: Acorn.ESTree.Node;
143
+ readonly #sourceText: string;
144
+ #additionalMappingLocations: number[] = [];
140
145
 
141
- constructor(node: Acorn.ESTree.Node) {
146
+ constructor(node: Acorn.ESTree.Node, sourceText: string) {
142
147
  this.#rootNode = node;
148
+ this.#sourceText = sourceText;
143
149
  this.#rootScope = new Scope(node.start, node.end, null, ScopeKind.GLOBAL);
144
150
  this.#currentScope = this.#rootScope;
145
151
  }
@@ -253,7 +259,9 @@ export class ScopeVariableAnalysis {
253
259
  break;
254
260
  case 'FunctionDeclaration':
255
261
  this.#processNodeAsDefinition(DefinitionKind.VAR, false, node.id);
256
- this.#pushScope(node.id?.end ?? node.start, node.end, ScopeKind.FUNCTION);
262
+ this.#pushScope(
263
+ node.id?.end ?? node.start, node.end, ScopeKind.FUNCTION,
264
+ mappingLocationsForFunctionDeclaration(node, this.#sourceText));
257
265
  this.#addVariable('this', node.start, DefinitionKind.FIXED);
258
266
  this.#addVariable('arguments', node.start, DefinitionKind.FIXED);
259
267
  node.params.forEach(this.#processNodeAsDefinition.bind(this, DefinitionKind.LET, false));
@@ -262,7 +270,9 @@ export class ScopeVariableAnalysis {
262
270
  this.#popScope(true);
263
271
  break;
264
272
  case 'FunctionExpression':
265
- this.#pushScope(node.id?.end ?? node.start, node.end, ScopeKind.FUNCTION);
273
+ this.#pushScope(
274
+ node.id?.end ?? node.start, node.end, ScopeKind.FUNCTION,
275
+ [...this.#additionalMappingLocations, ...mappingLocationsForFunctionExpression(node, this.#sourceText)]);
266
276
  this.#addVariable('this', node.start, DefinitionKind.FIXED);
267
277
  this.#addVariable('arguments', node.start, DefinitionKind.FIXED);
268
278
  node.params.forEach(this.#processNodeAsDefinition.bind(this, DefinitionKind.LET, false));
@@ -286,8 +296,11 @@ export class ScopeVariableAnalysis {
286
296
  case 'MethodDefinition':
287
297
  if (node.computed) {
288
298
  this.#processNode(node.key);
299
+ } else {
300
+ this.#additionalMappingLocations = mappingLocationsForMethodDefinition(node);
289
301
  }
290
302
  this.#processNode(node.value);
303
+ this.#additionalMappingLocations = [];
291
304
  break;
292
305
  case 'NewExpression':
293
306
  this.#processNode(node.callee);
@@ -424,8 +437,8 @@ export class ScopeVariableAnalysis {
424
437
  return this.#allNames;
425
438
  }
426
439
 
427
- #pushScope(start: number, end: number, kind: ScopeKind): void {
428
- this.#currentScope = new Scope(start, end, this.#currentScope, kind);
440
+ #pushScope(start: number, end: number, kind: ScopeKind, nameMappingLocations?: number[]): void {
441
+ this.#currentScope = new Scope(start, end, this.#currentScope, kind, nameMappingLocations);
429
442
  }
430
443
 
431
444
  #popScope(isFunctionContext: boolean): void {
@@ -488,3 +501,58 @@ export class ScopeVariableAnalysis {
488
501
  this.#processNode(decl.init ?? null);
489
502
  }
490
503
  }
504
+
505
+ function mappingLocationsForFunctionDeclaration(node: Acorn.ESTree.FunctionDeclaration, sourceText: string): number[] {
506
+ // For function declarations we prefer the position of the identifier as per spec, but we'll also return
507
+ // the beginning of the opening parenthesis '('.
508
+ const result = [node.id.start];
509
+
510
+ const searchParenEndPos = node.params.length ? node.params[0].start : node.body.start;
511
+ const parenPos = indexOfCharInBounds(sourceText, '(', node.id.end, searchParenEndPos);
512
+ if (parenPos >= 0) {
513
+ // Note that this is not 100% accurate as there might be a comment containing a open parenthesis between
514
+ // the identifier the and the argument list (unlikely though).
515
+ result.push(parenPos);
516
+ }
517
+
518
+ return result;
519
+ }
520
+
521
+ function mappingLocationsForFunctionExpression(node: Acorn.ESTree.FunctionExpression, sourceText: string): number[] {
522
+ // For function expressions we prefer the position of the identifier or '(' if none is present, as per spec.
523
+ const result = [];
524
+ if (node.id) {
525
+ result.push(node.id.start);
526
+ }
527
+
528
+ const searchParenStartPos = node.id ? node.id.end : node.start;
529
+ const searchParenEndPos = node.params.length ? node.params[0].start : node.body.start;
530
+ const parenPos = indexOfCharInBounds(sourceText, '(', searchParenStartPos, searchParenEndPos);
531
+ if (parenPos >= 0) {
532
+ // Note that this is not 100% accurate as there might be a comment containing a open parenthesis between
533
+ // the identifier the and the argument list (unlikely though).
534
+ result.push(parenPos);
535
+ }
536
+
537
+ return result;
538
+ }
539
+
540
+ function mappingLocationsForMethodDefinition(node: Acorn.ESTree.MethodDefinition): number[] {
541
+ // Method definitions use a FunctionExpression as their "value" child. So we only
542
+ // record the start of the "key" here and let 'mappingLocationsForFunctionExpression' handle
543
+ // the parenthesis.
544
+ if (node.key.type === 'Identifier' || node.key.type === 'PrivateIdentifier') {
545
+ const id = node.key as Acorn.ESTree.Identifier | Acorn.ESTree.PrivateIdentifier;
546
+ return [id.start];
547
+ }
548
+ return [];
549
+ }
550
+
551
+ function indexOfCharInBounds(str: string, needle: string, start: number, end: number): number {
552
+ for (let i = start; i < end; ++i) {
553
+ if (str[i] === needle) {
554
+ return i;
555
+ }
556
+ }
557
+ return -1;
558
+ }
@@ -35,7 +35,7 @@ function computeSubstitution(expression: string, nameMap: Map<string, string|nul
35
35
  checkPrivateFields: false,
36
36
  ranges: false,
37
37
  } as acorn.Options) as Acorn.ESTree.Node;
38
- const scopeVariables = new ScopeVariableAnalysis(root);
38
+ const scopeVariables = new ScopeVariableAnalysis(root, expression);
39
39
  scopeVariables.run();
40
40
  const freeVariables = scopeVariables.getFreeVariables();
41
41
  const result: Replacement[] = [];
@@ -340,7 +340,7 @@ inspectorBackend.registerCommand("Cast.stopCasting", [{"name": "sinkName", "type
340
340
  inspectorBackend.registerType("Cast.Sink", [{"name": "name", "type": "string", "optional": false, "description": "", "typeRef": null}, {"name": "id", "type": "string", "optional": false, "description": "", "typeRef": null}, {"name": "session", "type": "string", "optional": true, "description": "Text describing the current session. Present only if there is an active session on the sink.", "typeRef": null}]);
341
341
 
342
342
  // DOM.
343
- inspectorBackend.registerEnum("DOM.PseudoType", {FirstLine: "first-line", FirstLetter: "first-letter", Checkmark: "checkmark", Before: "before", After: "after", PickerIcon: "picker-icon", InterestHint: "interest-hint", Marker: "marker", Backdrop: "backdrop", Column: "column", Selection: "selection", SearchText: "search-text", TargetText: "target-text", SpellingError: "spelling-error", GrammarError: "grammar-error", Highlight: "highlight", FirstLineInherited: "first-line-inherited", ScrollMarker: "scroll-marker", ScrollMarkerGroup: "scroll-marker-group", ScrollButton: "scroll-button", Scrollbar: "scrollbar", ScrollbarThumb: "scrollbar-thumb", ScrollbarButton: "scrollbar-button", ScrollbarTrack: "scrollbar-track", ScrollbarTrackPiece: "scrollbar-track-piece", ScrollbarCorner: "scrollbar-corner", Resizer: "resizer", InputListButton: "input-list-button", ViewTransition: "view-transition", ViewTransitionGroup: "view-transition-group", ViewTransitionImagePair: "view-transition-image-pair", ViewTransitionGroupChildren: "view-transition-group-children", ViewTransitionOld: "view-transition-old", ViewTransitionNew: "view-transition-new", Placeholder: "placeholder", FileSelectorButton: "file-selector-button", DetailsContent: "details-content", Picker: "picker", PermissionIcon: "permission-icon", OverscrollAreaParent: "overscroll-area-parent", OverscrollClientArea: "overscroll-client-area"});
343
+ inspectorBackend.registerEnum("DOM.PseudoType", {FirstLine: "first-line", FirstLetter: "first-letter", Checkmark: "checkmark", Before: "before", After: "after", PickerIcon: "picker-icon", InterestHint: "interest-hint", Marker: "marker", Backdrop: "backdrop", Column: "column", Selection: "selection", SearchText: "search-text", TargetText: "target-text", SpellingError: "spelling-error", GrammarError: "grammar-error", Highlight: "highlight", FirstLineInherited: "first-line-inherited", ScrollMarker: "scroll-marker", ScrollMarkerGroup: "scroll-marker-group", ScrollButton: "scroll-button", Scrollbar: "scrollbar", ScrollbarThumb: "scrollbar-thumb", ScrollbarButton: "scrollbar-button", ScrollbarTrack: "scrollbar-track", ScrollbarTrackPiece: "scrollbar-track-piece", ScrollbarCorner: "scrollbar-corner", Resizer: "resizer", InputListButton: "input-list-button", ViewTransition: "view-transition", ViewTransitionGroup: "view-transition-group", ViewTransitionImagePair: "view-transition-image-pair", ViewTransitionGroupChildren: "view-transition-group-children", ViewTransitionOld: "view-transition-old", ViewTransitionNew: "view-transition-new", Placeholder: "placeholder", FileSelectorButton: "file-selector-button", DetailsContent: "details-content", Picker: "picker", PermissionIcon: "permission-icon", OverscrollAreaParent: "overscroll-area-parent"});
344
344
  inspectorBackend.registerEnum("DOM.ShadowRootType", {UserAgent: "user-agent", Open: "open", Closed: "closed"});
345
345
  inspectorBackend.registerEnum("DOM.CompatibilityMode", {QuirksMode: "QuirksMode", LimitedQuirksMode: "LimitedQuirksMode", NoQuirksMode: "NoQuirksMode"});
346
346
  inspectorBackend.registerEnum("DOM.PhysicalAxes", {Horizontal: "Horizontal", Vertical: "Vertical", Both: "Both"});
@@ -4531,7 +4531,6 @@ export namespace DOM {
4531
4531
  Picker = 'picker',
4532
4532
  PermissionIcon = 'permission-icon',
4533
4533
  OverscrollAreaParent = 'overscroll-area-parent',
4534
- OverscrollClientArea = 'overscroll-client-area',
4535
4534
  }
4536
4535
 
4537
4536
  /**
@@ -58,7 +58,7 @@ export class AiConversation {
58
58
  }
59
59
 
60
60
  readonly id: string;
61
- type: ConversationType;
61
+ #type: ConversationType;
62
62
  #isReadOnly: boolean;
63
63
  readonly history: ResponseData[];
64
64
  #isExternal: boolean;
@@ -66,6 +66,9 @@ export class AiConversation {
66
66
  #aidaClient: Host.AidaClient.AidaClient;
67
67
  #changeManager: ChangeManager|undefined;
68
68
  #agent: AiAgent<unknown>;
69
+ #origin?: string;
70
+
71
+ #contexts: Array<ConversationContext<unknown>> = [];
69
72
 
70
73
  constructor(
71
74
  type: ConversationType,
@@ -78,13 +81,13 @@ export class AiConversation {
78
81
  ) {
79
82
  this.#changeManager = changeManager;
80
83
  this.#aidaClient = aidaClient;
81
- this.type = type;
82
- this.#agent = this.#createAgent();
84
+ this.#type = type;
83
85
 
84
86
  this.id = id;
85
87
  this.#isReadOnly = isReadOnly;
86
88
  this.#isExternal = isExternal;
87
89
  this.history = this.#reconstructHistory(data);
90
+ this.#agent = this.#createAgent();
88
91
  }
89
92
 
90
93
  get isReadOnly(): boolean {
@@ -109,6 +112,25 @@ export class AiConversation {
109
112
  return this.history.length === 0;
110
113
  }
111
114
 
115
+ #setOriginIfEmpty(newOrigin: string|undefined): void {
116
+ if (!this.#origin) {
117
+ this.#origin = newOrigin;
118
+ }
119
+ }
120
+
121
+ setContext(updateContext: ConversationContext<unknown>|null): void {
122
+ if (!updateContext) {
123
+ this.#contexts = [];
124
+ return;
125
+ }
126
+
127
+ this.#contexts = [updateContext];
128
+ }
129
+
130
+ get selectedContext(): ConversationContext<unknown>|undefined {
131
+ return this.#contexts.at(0);
132
+ }
133
+
112
134
  #reconstructHistory(historyWithoutImages: ResponseData[]): ResponseData[] {
113
135
  const imageHistory = AiHistoryStorage.instance().getImageHistory();
114
136
  if (imageHistory && imageHistory.length > 0) {
@@ -214,7 +236,7 @@ export class AiConversation {
214
236
  }
215
237
  return item;
216
238
  }),
217
- type: this.type,
239
+ type: this.#type,
218
240
  isExternal: this.#isExternal,
219
241
  };
220
242
  }
@@ -223,10 +245,11 @@ export class AiConversation {
223
245
  const options = {
224
246
  aidaClient: this.#aidaClient,
225
247
  serverSideLoggingEnabled: isAiAssistanceServerSideLoggingEnabled(),
248
+ sessionId: this.id,
226
249
  changeManager: this.#changeManager,
227
250
  };
228
251
  let agent: AiAgent<unknown>;
229
- switch (this.type) {
252
+ switch (this.#type) {
230
253
  case ConversationType.STYLING: {
231
254
  agent = new StylingAgent(options);
232
255
  break;
@@ -326,24 +349,62 @@ Time: ${micros(time)}`;
326
349
  async *
327
350
  run(
328
351
  initialQuery: string,
329
- options: {selected: ConversationContext<unknown>|null, signal?: AbortSignal, extraContext?: ExtraContext[]},
330
- multimodalInput?: MultimodalInput,
352
+ options: {
353
+
354
+ signal?: AbortSignal,
355
+ extraContext?: ExtraContext[],
356
+ multimodalInput?: MultimodalInput,
357
+ } = {},
331
358
  ): AsyncGenerator<ResponseData, void, void> {
332
359
  if (options.extraContext) {
333
360
  await this.#createFactsForExtraContext(options.extraContext);
334
361
  }
335
- for await (const data of this.#agent.run(initialQuery, options, multimodalInput)) {
362
+ this.#setOriginIfEmpty(this.selectedContext?.getOrigin());
363
+
364
+ if (this.isBlockedByOrigin) {
365
+ throw new Error('Cross-origin context data should not be included');
366
+ }
367
+
368
+ function shouldAddToHistory(data: ResponseData): boolean {
336
369
  // We don't want to save partial responses to the conversation history.
337
370
  // TODO(crbug.com/463325400): We should save interleaved answers to the history as well.
338
- if (data.type !== ResponseType.ANSWER || data.complete) {
371
+ if (data.type === ResponseType.ANSWER && !data.complete) {
372
+ return false;
373
+ }
374
+
375
+ return true;
376
+ }
377
+
378
+ for await (const data of this.#agent.run(
379
+ initialQuery,
380
+ {
381
+ signal: options.signal,
382
+ selected: this.selectedContext ?? null,
383
+ },
384
+ options.multimodalInput,
385
+ )) {
386
+ if (shouldAddToHistory(data)) {
339
387
  void this.addHistoryItem(data);
340
388
  }
341
389
  yield data;
342
390
  }
343
391
  }
344
392
 
393
+ /**
394
+ * Indicates whether the new conversation context is blocked due to cross-origin restrictions.
395
+ * This happens when the conversation's context has a different
396
+ * origin than the selected context.
397
+ */
398
+ get isBlockedByOrigin(): boolean {
399
+ return !this.#contexts.every(context => context.isOriginAllowed(this.#origin));
400
+ }
401
+
345
402
  get origin(): string|undefined {
346
- return this.#agent.origin;
403
+ return this.#origin;
404
+ }
405
+
406
+ get type(): ConversationType {
407
+ return this.#type;
347
408
  }
348
409
  }
349
410
 
@@ -0,0 +1,67 @@
1
+ // Copyright 2025 The Chromium Authors
2
+ // Use of this source code is governed by a BSD-style license that can be
3
+ // found in the LICENSE file.
4
+
5
+ import type * as SDK from '../../core/sdk/sdk.js';
6
+ import type * as Trace from '../../models/trace/trace.js';
7
+ import type {InsightKeys} from '../trace/insights/types.js';
8
+
9
+ export interface InsightArtifact {
10
+ type: 'insight';
11
+ insightType: InsightKeys;
12
+ }
13
+
14
+ export interface NetworkRequestArtifact {
15
+ type: 'network-request';
16
+ request: SDK.NetworkRequest.NetworkRequest|Trace.Types.Events.SyntheticNetworkRequest;
17
+ }
18
+
19
+ export interface FlameChartArtifact {
20
+ type: 'flamechart';
21
+ start: Trace.Types.Timing.Micro;
22
+ end: Trace.Types.Timing.Micro;
23
+ }
24
+
25
+ export type Artifact = InsightArtifact|NetworkRequestArtifact|FlameChartArtifact;
26
+
27
+ export class ArtifactAddedEvent extends Event {
28
+ static readonly eventName = 'artifactadded';
29
+
30
+ constructor(public artifact: Artifact) {
31
+ super(ArtifactAddedEvent.eventName);
32
+ }
33
+ }
34
+
35
+ let instance: ArtifactsManager|null = null;
36
+
37
+ export class ArtifactsManager extends EventTarget {
38
+ #artifacts: Artifact[] = [];
39
+
40
+ static instance(): ArtifactsManager {
41
+ if (!instance) {
42
+ instance = new ArtifactsManager();
43
+ }
44
+ return instance;
45
+ }
46
+
47
+ static removeInstance(): void {
48
+ instance = null;
49
+ }
50
+
51
+ private constructor() {
52
+ super();
53
+ }
54
+
55
+ get artifacts(): Artifact[] {
56
+ return this.#artifacts;
57
+ }
58
+
59
+ addArtifact(artifact: Artifact): void {
60
+ this.#artifacts.push(artifact);
61
+ this.dispatchEvent(new ArtifactAddedEvent(artifact));
62
+ }
63
+
64
+ clear(): void {
65
+ this.#artifacts = [];
66
+ }
67
+ }
@@ -213,7 +213,7 @@ export class ConversationHandler extends Common.ObjectWrapper.ObjectWrapper<Even
213
213
  const conversation = new AiConversation(
214
214
  conversationType,
215
215
  [],
216
- aiAgent.id,
216
+ aiAgent.sessionId,
217
217
  /* isReadOnly */ true,
218
218
  this.#aidaClient,
219
219
  undefined,
@@ -228,7 +228,8 @@ export class ConversationHandler extends Common.ObjectWrapper.ObjectWrapper<Even
228
228
  selected: NodeContext|PerformanceTraceContext|RequestContext|null,
229
229
  }): AsyncGenerator<ExternalRequestResponse, ExternalRequestResponse> {
230
230
  const {conversation, prompt, selected} = opts;
231
- const generator = conversation.run(prompt, {selected});
231
+ conversation.setContext(selected);
232
+ const generator = conversation.run(prompt);
232
233
  const devToolsLogs: object[] = [];
233
234
  for await (const data of generator) {
234
235
  if (data.type !== ResponseType.ANSWER || data.complete) {
@@ -125,6 +125,7 @@ export interface RequestOptions {
125
125
  export interface AgentOptions {
126
126
  aidaClient: Host.AidaClient.AidaClient;
127
127
  serverSideLoggingEnabled?: boolean;
128
+ sessionId?: string;
128
129
  confirmSideEffectForTest?: typeof Promise.withResolvers;
129
130
  }
130
131
 
@@ -270,7 +271,7 @@ export abstract class AiAgent<T> {
270
271
  abstract readonly userTier: string|undefined;
271
272
  abstract handleContextDetails(select: ConversationContext<T>|null): AsyncGenerator<ContextResponse, void, void>;
272
273
 
273
- readonly #sessionId: string = crypto.randomUUID();
274
+ readonly #sessionId: string;
274
275
  readonly #aidaClient: Host.AidaClient.AidaClient;
275
276
  readonly #serverSideLoggingEnabled: boolean;
276
277
  readonly confirmSideEffect: typeof Promise.withResolvers;
@@ -284,12 +285,6 @@ export abstract class AiAgent<T> {
284
285
  aidaResponse: Host.AidaClient.DoConversationResponse,
285
286
  }> = [];
286
287
 
287
- /**
288
- * Might need to be part of history in case we allow chatting in
289
- * historical conversations.
290
- */
291
- #origin?: string;
292
-
293
288
  /**
294
289
  * `context` does not change during `AiAgent.run()`, ensuring that calls to JS
295
290
  * have the correct `context`. We don't want element selection by the user to
@@ -297,7 +292,6 @@ export abstract class AiAgent<T> {
297
292
  */
298
293
  protected context?: ConversationContext<T>;
299
294
 
300
- #id: string = crypto.randomUUID();
301
295
  #history: Host.AidaClient.Content[] = [];
302
296
 
303
297
  #facts: Set<Host.AidaClient.RequestFact> = new Set<Host.AidaClient.RequestFact>();
@@ -305,6 +299,7 @@ export abstract class AiAgent<T> {
305
299
  constructor(opts: AgentOptions) {
306
300
  this.#aidaClient = opts.aidaClient;
307
301
  this.#serverSideLoggingEnabled = opts.serverSideLoggingEnabled ?? false;
302
+ this.#sessionId = opts.sessionId ?? crypto.randomUUID();
308
303
  this.confirmSideEffect = opts.confirmSideEffectForTest ?? (() => Promise.withResolvers());
309
304
  }
310
305
 
@@ -393,12 +388,8 @@ export abstract class AiAgent<T> {
393
388
  return request;
394
389
  }
395
390
 
396
- get id(): string {
397
- return this.#id;
398
- }
399
-
400
- get origin(): string|undefined {
401
- return this.#origin;
391
+ get sessionId(): string {
392
+ return this.#sessionId;
402
393
  }
403
394
 
404
395
  /**
@@ -496,15 +487,8 @@ export abstract class AiAgent<T> {
496
487
  multimodalInput?: MultimodalInput,
497
488
  ): AsyncGenerator<ResponseData, void, void> {
498
489
  await options.selected?.refresh();
499
-
500
490
  if (options.selected) {
501
- // First context set on the agent determines its origin from now on.
502
- if (this.#origin === undefined) {
503
- this.#origin = options.selected.getOrigin();
504
- }
505
- if (options.selected.isOriginAllowed(this.#origin)) {
506
- this.context = options.selected;
507
- }
491
+ this.context = options.selected;
508
492
  }
509
493
 
510
494
  const enhancedQuery = await this.enhanceQuery(initialQuery, options.selected, multimodalInput?.type);
@@ -596,10 +580,16 @@ export abstract class AiAgent<T> {
596
580
 
597
581
  if (functionCall) {
598
582
  try {
599
- const result = yield* this.#callFunction(functionCall.name, functionCall.args, {
600
- ...options,
601
- explanation: textResponse,
602
- });
583
+ const result = yield*
584
+ this.#callFunction(
585
+ functionCall.name,
586
+ functionCall.args,
587
+ {
588
+ ...options,
589
+ explanation: textResponse,
590
+ },
591
+ );
592
+
603
593
  if (options.signal?.aborted) {
604
594
  yield this.#createErrorResponse(ErrorType.ABORT);
605
595
  break;
@@ -720,7 +710,7 @@ export abstract class AiAgent<T> {
720
710
 
721
711
  result = await call.handler(args, {
722
712
  ...options,
723
- approved: approvedRun,
713
+ approved: true,
724
714
  });
725
715
  }
726
716