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.
- package/front_end/entrypoints/formatter_worker/FormatterActions.ts +2 -0
- package/front_end/entrypoints/formatter_worker/ScopeParser.ts +75 -7
- package/front_end/entrypoints/formatter_worker/Substitute.ts +1 -1
- package/front_end/generated/InspectorBackendCommands.ts +1 -1
- package/front_end/generated/protocol.ts +0 -1
- package/front_end/models/ai_assistance/AiConversation.ts +71 -10
- package/front_end/models/ai_assistance/ArtifactsManager.ts +67 -0
- package/front_end/models/ai_assistance/ConversationHandler.ts +3 -2
- package/front_end/models/ai_assistance/agents/AiAgent.ts +17 -27
- package/front_end/models/ai_assistance/agents/PerformanceAgent.ts +151 -3
- package/front_end/models/ai_assistance/agents/StylingAgent.ts +1 -1
- package/front_end/models/ai_assistance/ai_assistance.ts +2 -0
- package/front_end/models/ai_assistance/data_formatters/PerformanceInsightFormatter.ts +0 -2
- package/front_end/models/annotations/AnnotationRepository.ts +2 -2
- package/front_end/models/greendev/Prototypes.ts +56 -0
- package/front_end/models/greendev/README.md +5 -0
- package/front_end/models/greendev/greendev.ts +5 -0
- package/front_end/models/trace/extras/TraceTree.ts +4 -2
- package/front_end/models/trace/insights/LCPDiscovery.ts +0 -2
- package/front_end/models/trace/types/TraceEvents.ts +0 -1
- package/front_end/panels/ai_assistance/AiAssistancePanel.ts +96 -91
- package/front_end/panels/ai_assistance/aiAssistancePanel.css +16 -0
- package/front_end/panels/ai_assistance/components/ArtifactsViewer.ts +109 -7
- package/front_end/panels/ai_assistance/components/ChatView.ts +2 -2
- package/front_end/panels/ai_assistance/components/CollapsibleAssistanceContentWidget.ts +7 -8
- package/front_end/panels/ai_assistance/components/PerformanceAgentFlameChart.ts +15 -8
- package/front_end/panels/ai_assistance/components/PerformanceAgentMarkdownRenderer.ts +9 -9
- package/front_end/panels/ai_assistance/components/artifactsViewer.css +6 -1
- package/front_end/panels/ai_assistance/components/collapsibleAssistanceContentWidget.css +5 -6
- package/front_end/panels/application/AppManifestView.ts +263 -205
- package/front_end/panels/application/ApplicationPanelSidebar.ts +24 -57
- package/front_end/panels/application/OpenedWindowDetailsView.ts +2 -0
- package/front_end/panels/application/ServiceWorkersView.ts +2 -0
- package/front_end/panels/application/StorageView.ts +1 -0
- package/front_end/panels/application/appManifestView.css +48 -0
- package/front_end/panels/application/components/ProtocolHandlersView.ts +2 -2
- package/front_end/panels/elements/ElementsTreeOutline.ts +1 -1
- package/front_end/panels/linear_memory_inspector/components/LinearMemoryInspector.ts +4 -8
- package/front_end/panels/linear_memory_inspector/components/LinearMemoryValueInterpreter.ts +148 -97
- package/front_end/panels/linear_memory_inspector/components/LinearMemoryViewer.ts +1 -1
- package/front_end/panels/linear_memory_inspector/components/linearMemoryValueInterpreter.css +37 -35
- package/front_end/panels/settings/SettingsScreen.ts +133 -1
- package/front_end/panels/settings/settings-meta.ts +24 -0
- package/front_end/panels/settings/settingsScreen.css +4 -0
- package/front_end/panels/sources/UISourceCodeFrame.ts +3 -17
- package/front_end/panels/timeline/components/insights/BaseInsightComponent.ts +2 -1
- package/front_end/third_party/acorn/estree-legacy.d.ts +2 -0
- package/front_end/third_party/chromium/README.chromium +1 -1
- package/front_end/third_party/puppeteer/README.chromium +2 -2
- package/front_end/third_party/puppeteer/package/lib/cjs/puppeteer/api/CDPSession.d.ts.map +1 -1
- package/front_end/third_party/puppeteer/package/lib/cjs/puppeteer/api/CDPSession.js.map +1 -1
- package/front_end/third_party/puppeteer/package/lib/cjs/puppeteer/api/ElementHandle.d.ts.map +1 -1
- package/front_end/third_party/puppeteer/package/lib/cjs/puppeteer/api/ElementHandle.js.map +1 -1
- package/front_end/third_party/puppeteer/package/lib/cjs/puppeteer/api/Frame.d.ts.map +1 -1
- package/front_end/third_party/puppeteer/package/lib/cjs/puppeteer/api/Frame.js.map +1 -1
- package/front_end/third_party/puppeteer/package/lib/cjs/puppeteer/api/Page.d.ts.map +1 -1
- package/front_end/third_party/puppeteer/package/lib/cjs/puppeteer/api/Page.js.map +1 -1
- package/front_end/third_party/puppeteer/package/lib/cjs/puppeteer/bidi/core/Connection.d.ts.map +1 -1
- package/front_end/third_party/puppeteer/package/lib/cjs/puppeteer/common/BrowserConnector.js +21 -7
- package/front_end/third_party/puppeteer/package/lib/cjs/puppeteer/common/BrowserConnector.js.map +1 -1
- package/front_end/third_party/puppeteer/package/lib/cjs/puppeteer/common/EventEmitter.d.ts.map +1 -1
- package/front_end/third_party/puppeteer/package/lib/cjs/puppeteer/injected/injected.d.ts +1 -1
- package/front_end/third_party/puppeteer/package/lib/cjs/puppeteer/util/Mutex.d.ts +2 -2
- package/front_end/third_party/puppeteer/package/lib/cjs/puppeteer/util/version.d.ts +1 -1
- package/front_end/third_party/puppeteer/package/lib/cjs/puppeteer/util/version.js +1 -1
- package/front_end/third_party/puppeteer/package/lib/es5-iife/puppeteer-core-browser.js +15 -6
- package/front_end/third_party/puppeteer/package/lib/esm/puppeteer/api/CDPSession.d.ts.map +1 -1
- package/front_end/third_party/puppeteer/package/lib/esm/puppeteer/api/CDPSession.js.map +1 -1
- package/front_end/third_party/puppeteer/package/lib/esm/puppeteer/api/ElementHandle.d.ts.map +1 -1
- package/front_end/third_party/puppeteer/package/lib/esm/puppeteer/api/ElementHandle.js.map +1 -1
- package/front_end/third_party/puppeteer/package/lib/esm/puppeteer/api/Frame.d.ts.map +1 -1
- package/front_end/third_party/puppeteer/package/lib/esm/puppeteer/api/Frame.js.map +1 -1
- package/front_end/third_party/puppeteer/package/lib/esm/puppeteer/api/Page.d.ts.map +1 -1
- package/front_end/third_party/puppeteer/package/lib/esm/puppeteer/api/Page.js.map +1 -1
- package/front_end/third_party/puppeteer/package/lib/esm/puppeteer/bidi/core/Connection.d.ts.map +1 -1
- package/front_end/third_party/puppeteer/package/lib/esm/puppeteer/common/BrowserConnector.js +21 -7
- package/front_end/third_party/puppeteer/package/lib/esm/puppeteer/common/BrowserConnector.js.map +1 -1
- package/front_end/third_party/puppeteer/package/lib/esm/puppeteer/common/EventEmitter.d.ts.map +1 -1
- package/front_end/third_party/puppeteer/package/lib/esm/puppeteer/util/version.d.ts +1 -1
- package/front_end/third_party/puppeteer/package/lib/esm/puppeteer/util/version.js +1 -1
- package/front_end/third_party/puppeteer/package/package.json +2 -2
- package/front_end/third_party/puppeteer/package/src/api/CDPSession.ts +1 -2
- package/front_end/third_party/puppeteer/package/src/api/ElementHandle.ts +2 -4
- package/front_end/third_party/puppeteer/package/src/api/Frame.ts +2 -4
- package/front_end/third_party/puppeteer/package/src/api/Page.ts +2 -4
- package/front_end/third_party/puppeteer/package/src/bidi/core/Connection.ts +3 -2
- package/front_end/third_party/puppeteer/package/src/common/BrowserConnector.ts +29 -10
- package/front_end/third_party/puppeteer/package/src/common/EventEmitter.ts +3 -3
- package/front_end/third_party/puppeteer/package/src/util/version.ts +1 -1
- package/front_end/ui/components/report_view/ReportView.docs.ts +37 -0
- package/front_end/ui/components/report_view/ReportView.ts +1 -4
- package/front_end/ui/components/settings/SettingCheckbox.ts +1 -1
- package/front_end/ui/legacy/Floaty.ts +5 -9
- package/front_end/ui/legacy/InspectorView.ts +2 -1
- package/front_end/ui/legacy/ReportView.ts +5 -4
- package/front_end/ui/legacy/Widget.ts +7 -0
- package/front_end/ui/legacy/components/perf_ui/FlameChart.ts +0 -1
- package/front_end/ui/legacy/reportView.css +0 -24
- package/front_end/ui/visual_logging/KnownContextValues.ts +7 -0
- 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(
|
|
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(
|
|
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"
|
|
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"});
|
|
@@ -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
|
|
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
|
|
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
|
|
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: {
|
|
330
|
-
|
|
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
|
-
|
|
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
|
|
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.#
|
|
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.
|
|
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
|
-
|
|
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
|
|
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
|
|
397
|
-
return this.#
|
|
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
|
-
|
|
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*
|
|
600
|
-
|
|
601
|
-
|
|
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:
|
|
713
|
+
approved: true,
|
|
724
714
|
});
|
|
725
715
|
}
|
|
726
716
|
|