chrome-devtools-frontend 1.0.1642845 → 1.0.1642899

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 (51) hide show
  1. package/SECURITY.md +1 -0
  2. package/front_end/core/host/UserMetrics.ts +2 -1
  3. package/front_end/core/sdk/CSSMatchedStyles.ts +55 -26
  4. package/front_end/core/sdk/CSSRule.ts +1 -0
  5. package/front_end/core/sdk/DebuggerModel.ts +5 -0
  6. package/front_end/entrypoints/greendev_floaty/FloatyEntrypoint.ts +4 -3
  7. package/front_end/entrypoints/greendev_floaty/greendev_floaty.ts +4 -3
  8. package/front_end/models/ai_assistance/AiAgent2.ts +80 -16
  9. package/front_end/models/ai_assistance/AiConversation.ts +3 -2
  10. package/front_end/models/ai_assistance/README.md +8 -0
  11. package/front_end/models/ai_assistance/agents/AccessibilityAgent.ts +50 -35
  12. package/front_end/models/ai_assistance/agents/AiAgent.ts +16 -0
  13. package/front_end/models/ai_assistance/agents/ContextSelectionAgent.ts +2 -2
  14. package/front_end/models/ai_assistance/agents/PerformanceAgent.ts +195 -147
  15. package/front_end/models/ai_assistance/agents/StylingAgent.snapshot.txt +0 -25
  16. package/front_end/models/ai_assistance/agents/StylingAgent.ts +24 -305
  17. package/front_end/models/ai_assistance/ai_assistance.ts +8 -0
  18. package/front_end/models/ai_assistance/contexts/DOMNodeContext.snapshot.txt +51 -0
  19. package/front_end/models/ai_assistance/contexts/DOMNodeContext.ts +200 -0
  20. package/front_end/models/ai_assistance/skills/styling.md +36 -2
  21. package/front_end/models/ai_assistance/tools/GetStyles.ts +137 -0
  22. package/front_end/models/ai_assistance/tools/Tool.ts +55 -0
  23. package/front_end/models/ai_assistance/tools/ToolRegistry.ts +34 -0
  24. package/front_end/models/lighthouse/LighthouseReporterTypes.ts +5 -0
  25. package/front_end/models/live-metrics/LiveMetrics.ts +24 -13
  26. package/front_end/models/stack_trace/DetailedErrorStackParser.ts +2 -2
  27. package/front_end/models/stack_trace/StackTrace.ts +4 -1
  28. package/front_end/models/stack_trace/StackTraceImpl.ts +9 -2
  29. package/front_end/models/stack_trace/StackTraceModel.ts +17 -4
  30. package/front_end/models/stack_trace/Trie.ts +1 -1
  31. package/front_end/panels/ai_assistance/AiAssistancePanel.ts +19 -15
  32. package/front_end/panels/ai_assistance/ai_assistance-meta.ts +16 -0
  33. package/front_end/panels/ai_assistance/components/ChatInput.ts +2 -2
  34. package/front_end/panels/application/DOMStorageItemsView.ts +4 -0
  35. package/front_end/panels/application/KeyValueStorageItemsView.ts +39 -7
  36. package/front_end/panels/common/ExtensionServer.ts +26 -15
  37. package/front_end/panels/elements/StandaloneStylesContainer.ts +1 -1
  38. package/front_end/panels/elements/StylePropertiesSection.ts +8 -0
  39. package/front_end/panels/elements/StylePropertyHighlighter.ts +4 -2
  40. package/front_end/panels/elements/StylePropertyTreeElement.ts +6 -5
  41. package/front_end/panels/elements/StylesContainer.ts +1 -1
  42. package/front_end/panels/elements/StylesSidebarPane.ts +4 -4
  43. package/front_end/panels/layer_viewer/PaintProfilerView.ts +106 -132
  44. package/front_end/panels/lighthouse/LighthousePanel.ts +4 -3
  45. package/front_end/panels/network/NetworkLogView.ts +3 -0
  46. package/front_end/panels/network/networkLogView.css +0 -15
  47. package/front_end/ui/legacy/components/cookie_table/CookiesTable.ts +36 -3
  48. package/front_end/ui/legacy/components/data_grid/dataGridAiButton.css +20 -0
  49. package/front_end/ui/legacy/components/utils/Linkifier.ts +19 -4
  50. package/front_end/ui/visual_logging/KnownContextValues.ts +1 -0
  51. package/package.json +1 -1
@@ -1,6 +1,40 @@
1
1
  ---
2
2
  name: styling
3
3
  description: Helping with CSS and styling
4
- allowed-tools: []
4
+ allowed-tools:
5
+ - getStyles
5
6
  ---
6
- You are a CSS expert helping the user style elements.
7
+ You are a CSS/DOM/HTML debugging assistant.
8
+ You always suggest considering the best web development practices and the newest platform features such as view transitions.
9
+ The user selected a DOM element in the browser's DevTools and sends a query about the page or the selected DOM element.
10
+ First, examine the provided context, then use the getStyles function to gather additional context and resolve the user request.
11
+
12
+ # Considerations
13
+
14
+ * Meticulously investigate all potential causes for the observed behavior before moving on. Gather comprehensive information about the element's parent, siblings, children, and any overlapping elements, paying close attention to properties that are likely relevant to the query.
15
+ * Be aware of the different node types (element, text, comment, document fragment, etc.) and their properties. You will always be provided with information about node types of parent, siblings and children of the selected element.
16
+ * Avoid making assumptions without sufficient evidence, and always seek further clarification if needed.
17
+ * Always explore multiple possible explanations for the observed behavior before settling on a conclusion.
18
+ * When presenting solutions, clearly distinguish between the primary cause and contributing factors.
19
+ * Please answer only if you are sure about the answer. Otherwise, explain why you're not able to answer.
20
+ * When answering, always consider MULTIPLE possible solutions.
21
+ * When answering, remember to consider CSS concepts such as the CSS cascade, explicit and implicit stacking contexts and various CSS layout types.
22
+ * Use the getStyles function available to you to investigate and fulfill the user request.
23
+ * ALWAYS OUTPUT a list of follow-up queries at the end of your text response. The format is SUGGESTIONS: ["suggestion1", "suggestion2", "suggestion3"]. Make sure that the array and the `SUGGESTIONS: ` text is in the same line.
24
+ * Use the precision of Strunk & White, the brevity of Hemingway, and the simple clarity of Vonnegut. Don't add repeated information, and keep the whole answer short.
25
+ * **CRITICAL** When answering questions about positioning or layout, ALWAYS inspect `position`, `display` and all other related properties. You MUST provide a specific list of CSS property names when calling getStyles. Do not use generic values like "all" or "*".
26
+ * **CRITICAL** You are a CSS/DOM/HTML debugging assistant. NEVER provide answers to questions of unrelated topics such as legal advice, financial advice, personal opinions, medical advice, religion, race, politics, sexuality, gender, or any other non web-development topics. Answer "Sorry, I can't answer that. I'm best at questions about debugging web pages." to such questions.
27
+
28
+ ## Response Structure
29
+
30
+ If the user asks a question that requires an investigation of a problem, use this structure:
31
+ - If available, point out the root cause(s) of the problem.
32
+ - Example: "**Root Cause**: The page is slow because of [reason]."
33
+ - Example: "**Root Causes**:"
34
+ - [Reason 1]
35
+ - [Reason 2]
36
+ - if applicable, list actionable solution suggestion(s) in order of impact:
37
+ - Example: "**Suggestion**: [Suggestion 1]
38
+ - Example: "**Suggestions**:"
39
+ - [Suggestion 1]
40
+ - [Suggestion 2]
@@ -0,0 +1,137 @@
1
+ // Copyright 2026 The Chromium Authors
2
+ // Use of this source code is governed by a BSD-style license that can be
3
+ // found in the LICENSE file.
4
+
5
+ import * as Host from '../../../core/host/host.js';
6
+ import * as SDK from '../../../core/sdk/sdk.js';
7
+ import type * as Protocol from '../../../generated/protocol.js';
8
+ import type {ComputedStyleAiWidget, FunctionCallHandlerResult} from '../agents/AiAgent.js';
9
+ import {DOMNodeContext} from '../contexts/DOMNodeContext.js';
10
+ import {debugLog} from '../debug.js';
11
+
12
+ import {type Tool, type ToolArgs, type ToolContext, ToolName} from './Tool.js';
13
+
14
+ export interface GetStylesArgs extends ToolArgs {
15
+ elements: number[];
16
+ styleProperties: string[];
17
+ explanation: string;
18
+ }
19
+
20
+ export class GetStylesTool implements Tool<GetStylesArgs, unknown> {
21
+ readonly name = ToolName.GET_STYLES;
22
+ readonly description =
23
+ `Get computed and source styles for one or multiple elements on the inspected page for multiple elements at once by uid.
24
+
25
+ **CRITICAL** An element uid is a number, not a selector.
26
+ **CRITICAL** Use selectors to refer to elements in the text output. Do not use uids.
27
+ **CRITICAL** Always provide the explanation argument to explain what and why you query.
28
+ **CRITICAL** You MUST provide a specific list of CSS property names. Do not use generic values like "all" or "*".`;
29
+
30
+ readonly parameters: Host.AidaClient.FunctionObjectParam<keyof GetStylesArgs> = {
31
+ type: Host.AidaClient.ParametersTypes.OBJECT,
32
+ description: '',
33
+ nullable: false,
34
+ properties: {
35
+ explanation: {
36
+ type: Host.AidaClient.ParametersTypes.STRING,
37
+ description: 'Explain why you want to get styles',
38
+ nullable: false,
39
+ },
40
+ elements: {
41
+ type: Host.AidaClient.ParametersTypes.ARRAY,
42
+ description: 'A list of element uids to get data for. These are numbers, not selectors.',
43
+ items: {type: Host.AidaClient.ParametersTypes.INTEGER, description: 'An element uid.'},
44
+ nullable: false,
45
+ },
46
+ styleProperties: {
47
+ type: Host.AidaClient.ParametersTypes.ARRAY,
48
+ description:
49
+ 'One or more specific CSS style property names to fetch. Generic values like "all" or "*" are not supported.',
50
+ nullable: false,
51
+ items: {
52
+ type: Host.AidaClient.ParametersTypes.STRING,
53
+ description: 'A CSS style property name to retrieve. For example, \'background-color\'.'
54
+ }
55
+ },
56
+ },
57
+ required: ['explanation', 'elements', 'styleProperties']
58
+ };
59
+
60
+ displayInfoFromArgs(params: GetStylesArgs): {
61
+ title: string,
62
+ thought: string,
63
+ action: string,
64
+ } {
65
+ return {
66
+ title: 'Reading computed and source styles',
67
+ thought: params.explanation,
68
+ action: `getStyles(${JSON.stringify(params.elements)}, ${JSON.stringify(params.styleProperties)})`,
69
+ };
70
+ }
71
+
72
+ async handler(params: GetStylesArgs, context: ToolContext): Promise<FunctionCallHandlerResult<unknown>> {
73
+ const widgets: ComputedStyleAiWidget[] = [];
74
+ const result:
75
+ Record<string, {computed: Record<string, string|undefined>, authored: Record<string, string|undefined>}> = {};
76
+
77
+ const activeContext = context.conversationContext;
78
+ if (!activeContext || !(activeContext instanceof DOMNodeContext)) {
79
+ return {error: 'Error: Could not find the currently selected element.'};
80
+ }
81
+
82
+ const selectedNode = activeContext.getItem();
83
+ if (!selectedNode) {
84
+ return {error: 'Error: Could not find the currently selected element.'};
85
+ }
86
+
87
+ for (const uid of params.elements) {
88
+ result[uid] = {computed: {}, authored: {}};
89
+ debugLog(`Action to execute: uid=${uid}`);
90
+ const node =
91
+ new SDK.DOMModel.DeferredDOMNode(selectedNode.domModel().target(), uid as Protocol.DOM.BackendNodeId);
92
+ const resolved = await node.resolvePromise();
93
+ if (!resolved) {
94
+ return {error: 'Error: Could not find the element with uid=' + uid};
95
+ }
96
+ const newContext = new DOMNodeContext(resolved);
97
+ if (activeContext.getOrigin() !== newContext.getOrigin()) {
98
+ return {error: 'Error: Node does not belong to the current origin.'};
99
+ }
100
+ const styles = await resolved.domModel().cssModel().getComputedStyle(resolved.id);
101
+ if (!styles) {
102
+ return {error: 'Error: Could not get computed styles.'};
103
+ }
104
+ const matchedStyles = await resolved.domModel().cssModel().getMatchedStyles(resolved.id);
105
+ if (!matchedStyles) {
106
+ return {error: 'Error: Could not get authored styles.'};
107
+ }
108
+ widgets.push({
109
+ name: 'COMPUTED_STYLES',
110
+ data: {
111
+ computedStyles: styles,
112
+ backendNodeId: node.backendNodeId(),
113
+ matchedCascade: matchedStyles,
114
+ properties: params.styleProperties,
115
+ }
116
+ });
117
+ for (const prop of params.styleProperties) {
118
+ result[uid].computed[prop] = styles.get(prop);
119
+ }
120
+ for (const style of matchedStyles.nodeStyles()) {
121
+ for (const property of style.allProperties()) {
122
+ if (!params.styleProperties.includes(property.name)) {
123
+ continue;
124
+ }
125
+ const state = matchedStyles.propertyState(property);
126
+ if (state === SDK.CSSMatchedStyles.PropertyState.ACTIVE) {
127
+ result[uid].authored[property.name] = property.value;
128
+ }
129
+ }
130
+ }
131
+ }
132
+ return {
133
+ result: JSON.stringify(result, null, 2),
134
+ widgets,
135
+ };
136
+ }
137
+ }
@@ -0,0 +1,55 @@
1
+ // Copyright 2026 The Chromium Authors
2
+ // Use of this source code is governed by a BSD-style license that can be
3
+ // found in the LICENSE file.
4
+
5
+ import type * as Host from '../../../core/host/host.js';
6
+ import type {ConversationContext, FunctionCallHandlerResult,} from '../agents/AiAgent.js';
7
+
8
+ /**
9
+ * Context provided to the tool's handler execution.
10
+ */
11
+ export interface ToolContext {
12
+ conversationContext: ConversationContext<unknown>|null;
13
+ }
14
+
15
+ /**
16
+ * Base argument type for AI Tools.
17
+ */
18
+ export type ToolArgs = Record<string, unknown>;
19
+
20
+ export const enum ToolName {
21
+ GET_STYLES = 'getStyles',
22
+ }
23
+
24
+ /**
25
+ * Non-generic metadata interface for a Tool.
26
+ * Used for storing and retrieving tools generically without type-erasure concerns.
27
+ */
28
+ export interface BaseTool {
29
+ readonly name: ToolName;
30
+ readonly description: string;
31
+ /**
32
+ * JSON schema representing the parameters this tool accepts.
33
+ */
34
+ readonly parameters: Host.AidaClient.FunctionObjectParam<string|number|symbol>;
35
+ }
36
+
37
+ /**
38
+ * Main generic interface for defining a Tool.
39
+ * Binds the parameter schema properties and the handler implementation to a strict `Args` contract.
40
+ *
41
+ * @template Args - The expected object type for tool arguments. Must be an object type.
42
+ * @template ReturnType - The type of data returned by the handler function.
43
+ */
44
+ export interface Tool<Args extends ToolArgs = ToolArgs, ReturnType = unknown, > extends BaseTool {
45
+ readonly parameters: Host.AidaClient.FunctionObjectParam<keyof Args>;
46
+ readonly displayInfoFromArgs?: (
47
+ args: Args,
48
+ ) => {
49
+ title?: string, thought?: string, action?: string, suggestions?: [string, ...string[]],
50
+ };
51
+ handler(
52
+ args: Args,
53
+ context: ToolContext,
54
+ ): Promise<FunctionCallHandlerResult<ReturnType>>;
55
+ }
@@ -0,0 +1,34 @@
1
+ // Copyright 2026 The Chromium Authors
2
+ // Use of this source code is governed by a BSD-style license that can be
3
+ // found in the LICENSE file.
4
+
5
+ import {GetStylesTool} from './GetStyles.js';
6
+ import {type Tool, ToolName} from './Tool.js';
7
+
8
+ /**
9
+ * Plain object registry containing concrete instantiated tools.
10
+ * Keep this type concrete (no type-erasure) to preserve exact tool types.
11
+ */
12
+ export const TOOLS = {
13
+ [ToolName.GET_STYLES]: new GetStylesTool(),
14
+ };
15
+
16
+ /**
17
+ * Registry class for registering and querying AI Assistance Tools.
18
+ */
19
+ export class ToolRegistry {
20
+ /**
21
+ * Retrieves a tool by its literal name with 100% type safety.
22
+ *
23
+ * @template K - A key from the `TOOLS` registry.
24
+ * @returns The concrete class type of the requested tool.
25
+ */
26
+ static get<K extends keyof typeof TOOLS>(name: K): typeof TOOLS[K];
27
+ /**
28
+ * Fallback retrieval signature for general or runtime string lookups.
29
+ */
30
+ static get(name: string): Tool|undefined;
31
+ static get(name: string): Tool|undefined {
32
+ return Object.prototype.hasOwnProperty.call(TOOLS, name) ? TOOLS[name as keyof typeof TOOLS] as Tool : undefined;
33
+ }
34
+ }
@@ -77,6 +77,11 @@ export interface ReportJSON {
77
77
  audits: Record<string, AuditResultJSON>;
78
78
  categories: Record<CategoryId, CategoryJSON>;
79
79
  categoryGroups: Record<string, GroupJSON>;
80
+ /**
81
+ * Identifies if the report was imported from a file (untrusted).
82
+ * Used to disable page-touching AI assistance tools for security.
83
+ */
84
+ isImported?: boolean;
80
85
  }
81
86
 
82
87
  export type DetailsJSON =
@@ -64,17 +64,22 @@ export class LiveMetrics extends Common.ObjectWrapper.ObjectWrapper<EventTypes>
64
64
  super();
65
65
  const targetManager = SDK.TargetManager.TargetManager.instance();
66
66
  targetManager.observeTargets(this, {scoped: true});
67
- // Listen for target info changes to detect prerender activation.
68
- // Scoped observers don't receive events when a prerendered target becomes
69
- // primary because setScopeTarget() isn't called during that transition.
70
- targetManager.addEventListener(
71
- SDK.TargetManager.Events.AVAILABLE_TARGETS_CHANGED, this.#onAvailableTargetsChanged, this);
67
+ targetManager.addModelListener(
68
+ SDK.ResourceTreeModel.ResourceTreeModel, SDK.ResourceTreeModel.Events.PrimaryPageChanged,
69
+ this.#onPrimaryPageChanged, this);
72
70
  }
73
71
 
74
- #onAvailableTargetsChanged(): void {
72
+ #onPrimaryPageChanged(
73
+ event: Common.EventTarget.EventTargetEvent<
74
+ {frame: SDK.ResourceTreeModel.ResourceTreeFrame, type: SDK.ResourceTreeModel.PrimaryPageChangeType}>): void {
75
75
  const primaryTarget = SDK.TargetManager.TargetManager.instance().primaryPageTarget();
76
- if (primaryTarget && primaryTarget !== this.#target) {
77
- // Primary target changed (e.g., prerender activation). Switch to it.
76
+ if (!primaryTarget) {
77
+ return;
78
+ }
79
+ if (primaryTarget !== this.#target || event.data.type === SDK.ResourceTreeModel.PrimaryPageChangeType.ACTIVATION) {
80
+ // Primary target changed or prerender activated. Switch to it and reset metrics.
81
+ this.#clearMetrics();
82
+ this.#sendStatusUpdate();
78
83
  void this.#switchToTarget(primaryTarget);
79
84
  }
80
85
  }
@@ -381,11 +386,7 @@ export class LiveMetrics extends Common.ObjectWrapper.ObjectWrapper<EventTypes>
381
386
  break;
382
387
  }
383
388
  case 'reset': {
384
- this.#lcpValue = undefined;
385
- this.#clsValue = undefined;
386
- this.#inpValue = undefined;
387
- this.#interactions.clear();
388
- this.#layoutShifts = [];
389
+ this.#clearMetrics();
389
390
  break;
390
391
  }
391
392
  }
@@ -393,6 +394,15 @@ export class LiveMetrics extends Common.ObjectWrapper.ObjectWrapper<EventTypes>
393
394
  this.#sendStatusUpdate();
394
395
  }
395
396
 
397
+ #clearMetrics(): void {
398
+ this.#lcpValue = undefined;
399
+ this.#clsValue = undefined;
400
+ this.#inpValue = undefined;
401
+ this.#interactions.clear();
402
+ this.#interactionsByGroupId.clear();
403
+ this.#layoutShifts = [];
404
+ }
405
+
396
406
  #isPrimaryFrameExecutionContext(executionContextId: Protocol.Runtime.ExecutionContextId): boolean {
397
407
  if (!this.#target) {
398
408
  return false;
@@ -460,6 +470,7 @@ export class LiveMetrics extends Common.ObjectWrapper.ObjectWrapper<EventTypes>
460
470
 
461
471
  clearInteractions(): void {
462
472
  this.#interactions.clear();
473
+ this.#interactionsByGroupId.clear();
463
474
  this.#sendStatusUpdate();
464
475
  }
465
476
 
@@ -142,12 +142,12 @@ export function parseRawFramesFromErrorStack(stack: string): RawFrame[]|null {
142
142
  functionName,
143
143
  lineNumber,
144
144
  columnNumber,
145
+ isWasm,
145
146
  parsedFrameInfo: {
146
147
  isAsync,
147
148
  isConstructor,
148
149
  isEval,
149
150
  evalOrigin,
150
- isWasm,
151
151
  wasmModuleName,
152
152
  wasmFunctionIndex,
153
153
  typeName,
@@ -181,7 +181,7 @@ export function parseMessage(stack: string): string {
181
181
  export function augmentRawFramesWithScriptIds(
182
182
  rawFrames: RawFrame[], protocolStackTrace: Protocol.Runtime.StackTrace): void {
183
183
  function augmentFrame(rawFrame: RawFrame): void {
184
- const isWasm = rawFrame.parsedFrameInfo?.isWasm;
184
+ const isWasm = rawFrame.isWasm;
185
185
  const protocolFrame = protocolStackTrace.callFrames.find(frame => {
186
186
  if (isWasm) {
187
187
  // The parser parses Wasm offsets into the `columnNumber` field. The `lineNumber` is always -1.
@@ -44,6 +44,10 @@ export interface Frame {
44
44
  * of the containing function.
45
45
  */
46
46
  readonly rawName?: string;
47
+ /**
48
+ * Whether the corresponding raw frame is JS or WASM.
49
+ */
50
+ readonly isWasm?: boolean;
47
51
  }
48
52
 
49
53
  export interface ParsedErrorStackFrame extends Frame {
@@ -51,7 +55,6 @@ export interface ParsedErrorStackFrame extends Frame {
51
55
  readonly isConstructor?: boolean;
52
56
  readonly isEval?: boolean;
53
57
  readonly evalOrigin?: ParsedErrorStackFrame;
54
- readonly isWasm?: boolean;
55
58
  readonly wasmModuleName?: string;
56
59
  readonly wasmFunctionIndex?: number;
57
60
  readonly typeName?: string;
@@ -88,10 +88,12 @@ export class FrameImpl implements StackTrace.StackTrace.Frame {
88
88
 
89
89
  readonly missingDebugInfo?: StackTrace.StackTrace.MissingDebugInfo;
90
90
  readonly rawName?: string;
91
+ readonly isWasm?: boolean;
91
92
 
92
93
  constructor(
93
94
  url: string|undefined, uiSourceCode: Workspace.UISourceCode.UISourceCode|undefined, name: string|undefined,
94
- line: number, column: number, missingDebugInfo?: StackTrace.StackTrace.MissingDebugInfo, rawName?: string) {
95
+ line: number, column: number, missingDebugInfo?: StackTrace.StackTrace.MissingDebugInfo, rawName?: string,
96
+ isWasm?: boolean) {
95
97
  this.url = url;
96
98
  this.uiSourceCode = uiSourceCode;
97
99
  this.name = name;
@@ -99,6 +101,7 @@ export class FrameImpl implements StackTrace.StackTrace.Frame {
99
101
  this.column = column;
100
102
  this.missingDebugInfo = missingDebugInfo;
101
103
  this.rawName = rawName;
104
+ this.isWasm = isWasm;
102
105
  }
103
106
  }
104
107
 
@@ -192,7 +195,7 @@ export class ParsedErrorStackFrameImpl implements StackTrace.StackTrace.ParsedEr
192
195
  return this.#evalOrigin;
193
196
  }
194
197
  get isWasm(): boolean|undefined {
195
- return this.#parsedFrameInfo?.isWasm;
198
+ return this.#frame.isWasm;
196
199
  }
197
200
  get wasmModuleName(): string|undefined {
198
201
  return this.#parsedFrameInfo?.wasmModuleName;
@@ -284,6 +287,10 @@ export class DebuggableFrameImpl implements StackTrace.StackTrace.DebuggableFram
284
287
  return this.#frame.rawName;
285
288
  }
286
289
 
290
+ get isWasm(): boolean|undefined {
291
+ return this.#frame.isWasm;
292
+ }
293
+
287
294
  get sdkFrame(): SDK.DebuggerModel.CallFrame {
288
295
  return this.#sdkFrame;
289
296
  }
@@ -48,8 +48,14 @@ export class StackTraceModel extends SDK.SDKModel.SDKModel<unknown> {
48
48
 
49
49
  async createFromProtocolRuntime(stackTrace: Protocol.Runtime.StackTrace, rawFramesToUIFrames: TranslateRawFrames):
50
50
  Promise<StackTrace.StackTrace.StackTrace> {
51
+ const debuggerModel = this.target().model(SDK.DebuggerModel.DebuggerModel);
52
+ const syncFrames = stackTrace.callFrames.map((frame): RawFrame => {
53
+ const isWasm = debuggerModel?.isWasm(frame.scriptId) ?? false;
54
+ return {...frame, isWasm};
55
+ });
56
+
51
57
  const [syncFragment, asyncFragments] = await Promise.all([
52
- this.#createFragment(stackTrace.callFrames, rawFramesToUIFrames),
58
+ this.#createFragment(syncFrames, rawFramesToUIFrames),
53
59
  this.#createAsyncFragments(stackTrace, rawFramesToUIFrames),
54
60
  ]);
55
61
 
@@ -126,6 +132,7 @@ export class StackTraceModel extends SDK.SDKModel.SDKModel<unknown> {
126
132
  functionName: frame.functionName,
127
133
  lineNumber: frame.location().lineNumber,
128
134
  columnNumber: frame.location().columnNumber,
135
+ isWasm: frame.script.isWasm(),
129
136
  })),
130
137
  rawFramesToUIFrames);
131
138
  return new DebuggableFragmentImpl(fragment, pausedDetails.callFrames);
@@ -144,8 +151,14 @@ export class StackTraceModel extends SDK.SDKModel.SDKModel<unknown> {
144
151
  continue;
145
152
  }
146
153
  const model = StackTraceModel.#modelForTarget(target);
154
+ const targetDebuggerModel = target.model(SDK.DebuggerModel.DebuggerModel);
155
+ const asyncFrames = asyncStackTrace.callFrames.map((frame): RawFrame => {
156
+ const isWasm = targetDebuggerModel?.isWasm(frame.scriptId) ?? false;
157
+ return {...frame, isWasm};
158
+ });
159
+
147
160
  const asyncFragmentPromise =
148
- model.#createFragment(asyncStackTrace.callFrames, rawFramesToUIFrames)
161
+ model.#createFragment(asyncFrames, rawFramesToUIFrames)
149
162
  .then(fragment => new AsyncFragmentImpl(asyncStackTrace.description ?? '', fragment));
150
163
  asyncFragments.push(asyncFragmentPromise);
151
164
  }
@@ -201,7 +214,7 @@ export class StackTraceModel extends SDK.SDKModel.SDKModel<unknown> {
201
214
  node.frames = uiFrames[i++].map(
202
215
  frame => new FrameImpl(
203
216
  frame.url, frame.uiSourceCode, frame.name, frame.line, frame.column, frame.missingDebugInfo,
204
- node.rawFrame.functionName));
217
+ node.rawFrame.functionName, node.rawFrame.isWasm));
205
218
 
206
219
  if (node.parsedFrameInfo?.evalOrigin) {
207
220
  node.evalOrigin = evalOrigins[evalI++];
@@ -243,7 +256,7 @@ async function translateEvalOrigin(
243
256
  const frames = uiFrames[0].map(
244
257
  frame => new FrameImpl(
245
258
  frame.url, frame.uiSourceCode, frame.name, frame.line, frame.column, frame.missingDebugInfo,
246
- rawFrame.functionName));
259
+ rawFrame.functionName, rawFrame.isWasm));
247
260
 
248
261
  let parentEvalOrigin: EvalOrigin|undefined;
249
262
  if (rawFrame.parsedFrameInfo?.evalOrigin) {
@@ -11,7 +11,6 @@ export interface ParsedFrameInfo {
11
11
  readonly isConstructor?: boolean;
12
12
  readonly isEval?: boolean;
13
13
  readonly evalOrigin?: RawFrame;
14
- readonly isWasm?: boolean;
15
14
  readonly wasmModuleName?: string;
16
15
  readonly wasmFunctionIndex?: number;
17
16
  readonly typeName?: string;
@@ -30,6 +29,7 @@ export interface RawFrame {
30
29
  readonly columnNumber: number;
31
30
 
32
31
  readonly parsedFrameInfo?: ParsedFrameInfo;
32
+ readonly isWasm?: boolean;
33
33
  }
34
34
 
35
35
  /**
@@ -362,23 +362,22 @@ function getMarkdownRenderer(conversation?: AiAssistanceModel.AiConversation.AiC
362
362
  const context = conversation?.selectedContext;
363
363
 
364
364
  if (context instanceof AiAssistanceModel.PerformanceAgent.PerformanceTraceContext) {
365
- if (!context.external) {
366
- const focus = context.getItem();
367
- return new PerformanceAgentMarkdownRenderer(
368
- focus.parsedTrace.data.Meta.mainFrameId, focus.lookupEvent.bind(focus));
369
- }
370
- } else if (conversation?.type === AiAssistanceModel.AiHistoryStorage.ConversationType.PERFORMANCE) {
365
+ const focus = context.getItem();
366
+ return new PerformanceAgentMarkdownRenderer(focus.parsedTrace.data.Meta.mainFrameId, focus.lookupEvent.bind(focus));
367
+ }
368
+ if (conversation?.type === AiAssistanceModel.AiHistoryStorage.ConversationType.PERFORMANCE) {
371
369
  // Handle historical conversations (can't linkify anything).
372
370
  return new PerformanceAgentMarkdownRenderer();
373
- } else if (
374
- Greendev.Prototypes.instance().isEnabled('emulationCapabilities') &&
371
+ }
372
+ if (Greendev.Prototypes.instance().isEnabled('emulationCapabilities') &&
375
373
  conversation?.type === AiAssistanceModel.AiHistoryStorage.ConversationType.STYLING &&
376
374
  SDK.TargetManager.TargetManager.instance().primaryPageTarget()?.model(SDK.DOMModel.DOMModel)) {
377
375
  const domModel = SDK.TargetManager.TargetManager.instance().primaryPageTarget()?.model(SDK.DOMModel.DOMModel);
378
376
  const resourceTreeModel = domModel?.target().model(SDK.ResourceTreeModel.ResourceTreeModel);
379
377
  const mainFrameId = resourceTreeModel?.mainFrame?.id;
380
378
  return new StylingAgentMarkdownRenderer(mainFrameId);
381
- } else if (conversation?.type === AiAssistanceModel.AiHistoryStorage.ConversationType.ACCESSIBILITY) {
379
+ }
380
+ if (conversation?.type === AiAssistanceModel.AiHistoryStorage.ConversationType.ACCESSIBILITY) {
382
381
  const domModel = SDK.TargetManager.TargetManager.instance().primaryPageTarget()?.model(SDK.DOMModel.DOMModel);
383
382
  const mainDocumentURL = domModel?.existingDocument()?.documentURL;
384
383
  return new AccessibilityAgentMarkdownRenderer(mainDocumentURL);
@@ -570,11 +569,11 @@ function defaultView(input: ViewInput, output: PanelViewOutput, target: HTMLElem
570
569
  // clang-format on
571
570
  }
572
571
 
573
- function createNodeContext(node: SDK.DOMModel.DOMNode|null): AiAssistanceModel.StylingAgent.NodeContext|null {
572
+ function createDOMNodeContext(node: SDK.DOMModel.DOMNode|null): AiAssistanceModel.DOMNodeContext.DOMNodeContext|null {
574
573
  if (!node) {
575
574
  return null;
576
575
  }
577
- return new AiAssistanceModel.StylingAgent.NodeContext(node);
576
+ return new AiAssistanceModel.DOMNodeContext.DOMNodeContext(node);
578
577
  }
579
578
 
580
579
  function createFileContext(file: Workspace.UISourceCode.UISourceCode|null): AiAssistanceModel.FileAgent.FileContext|
@@ -668,7 +667,7 @@ export class AiAssistancePanel extends UI.Panel.Panel {
668
667
  #conversation?: AiAssistanceModel.AiConversation.AiConversation;
669
668
 
670
669
  #selectedFile: AiAssistanceModel.FileAgent.FileContext|null = null;
671
- #selectedElement: AiAssistanceModel.StylingAgent.NodeContext|null = null;
670
+ #selectedElement: AiAssistanceModel.DOMNodeContext.DOMNodeContext|null = null;
672
671
  #selectedPerformanceTrace: AiAssistanceModel.PerformanceAgent.PerformanceTraceContext|null = null;
673
672
  #selectedRequest: AiAssistanceModel.NetworkAgent.RequestContext|null = null;
674
673
 
@@ -1117,7 +1116,7 @@ export class AiAssistancePanel extends UI.Panel.Panel {
1117
1116
  this.#viewOutput.chatView?.focusTextInput();
1118
1117
  void this.#handleAidaAvailabilityChange();
1119
1118
  this.#selectedElement =
1120
- createNodeContext(selectedElementFilter(UI.Context.Context.instance().flavor(SDK.DOMModel.DOMNode)));
1119
+ createDOMNodeContext(selectedElementFilter(UI.Context.Context.instance().flavor(SDK.DOMModel.DOMNode)));
1121
1120
  this.#selectedRequest =
1122
1121
  createRequestContext(UI.Context.Context.instance().flavor(SDK.NetworkRequest.NetworkRequest));
1123
1122
  this.#selectedPerformanceTrace =
@@ -1225,7 +1224,7 @@ export class AiAssistancePanel extends UI.Panel.Panel {
1225
1224
  return;
1226
1225
  }
1227
1226
 
1228
- this.#selectedElement = createNodeContext(selectedElementFilter(ev.data));
1227
+ this.#selectedElement = createDOMNodeContext(selectedElementFilter(ev.data));
1229
1228
  this.#updateConversationState(this.#conversation);
1230
1229
  };
1231
1230
 
@@ -1576,6 +1575,11 @@ export class AiAssistancePanel extends UI.Panel.Panel {
1576
1575
  targetConversationType = AiAssistanceModel.AiHistoryStorage.ConversationType.FILE;
1577
1576
  break;
1578
1577
  }
1578
+ case 'ai-assistance.storage-floating-button': {
1579
+ Host.userMetrics.actionTaken(Host.UserMetrics.Action.AiAssistanceOpenedFromStoragePanelFloatingButton);
1580
+ targetConversationType = AiAssistanceModel.AiHistoryStorage.ConversationType.STORAGE;
1581
+ break;
1582
+ }
1579
1583
  }
1580
1584
 
1581
1585
  if (!targetConversationType) {
@@ -1727,7 +1731,7 @@ export class AiAssistancePanel extends UI.Panel.Panel {
1727
1731
  #handleConversationContextChange = (data: unknown): void => {
1728
1732
  if (data instanceof AiAssistanceModel.FileAgent.FileContext) {
1729
1733
  this.#selectedFile = data;
1730
- } else if (data instanceof AiAssistanceModel.StylingAgent.NodeContext) {
1734
+ } else if (data instanceof AiAssistanceModel.DOMNodeContext.DOMNodeContext) {
1731
1735
  this.#selectedElement = data;
1732
1736
  } else if (data instanceof AiAssistanceModel.NetworkAgent.RequestContext) {
1733
1737
  this.#selectedRequest = data;
@@ -297,3 +297,19 @@ UI.ActionRegistration.registerActionExtension({
297
297
  },
298
298
  condition: config => isFileAgentFeatureAvailable(config) && !isPolicyRestricted(config) && !isGeoRestricted(config),
299
299
  });
300
+
301
+ UI.ActionRegistration.registerActionExtension({
302
+ actionId: 'ai-assistance.storage-floating-button',
303
+ contextTypes(): [] {
304
+ return [];
305
+ },
306
+ category: UI.ActionRegistration.ActionCategory.GLOBAL,
307
+ title: i18nAiBrandedString(UIStrings.debugWithGemini, UIStrings.debugWithAi),
308
+ configurableBindings: false,
309
+ async loadActionDelegate() {
310
+ const AiAssistance = await loadAiAssistanceModule();
311
+ return new AiAssistance.ActionDelegate();
312
+ },
313
+ condition: config =>
314
+ isStorageAgentFeatureAvailable(config) && !isPolicyRestricted(config) && !isGeoRestricted(config),
315
+ });
@@ -174,7 +174,7 @@ function getContextRemoveLabel(context: AiAssistanceModel.AiAgent.ConversationCo
174
174
  if (context instanceof AiAssistanceModel.FileAgent.FileContext) {
175
175
  return lockedString(UIStringsNotTranslate.removeContextFile);
176
176
  }
177
- if (context instanceof AiAssistanceModel.StylingAgent.NodeContext) {
177
+ if (context instanceof AiAssistanceModel.DOMNodeContext.DOMNodeContext) {
178
178
  return lockedString(UIStringsNotTranslate.removeContextElement);
179
179
  }
180
180
  if (context instanceof AiAssistanceModel.NetworkAgent.RequestContext) {
@@ -336,7 +336,7 @@ export const DEFAULT_VIEW = (input: ViewInput, _output: ViewOutput, target: HTML
336
336
  })}
337
337
  >
338
338
  ${
339
- input.context instanceof AiAssistanceModel.StylingAgent.NodeContext ?
339
+ input.context instanceof AiAssistanceModel.DOMNodeContext.DOMNodeContext ?
340
340
  html`
341
341
  <devtools-widget
342
342
  class="title"
@@ -205,6 +205,10 @@ export class DOMStorageItemsView extends KeyValueStorageItemsView {
205
205
  UI.Context.Context.instance().setFlavor(AiAssistanceModel.StorageItem.StorageItem, storageItem);
206
206
  }
207
207
 
208
+ protected override isAiButtonEnabled(): boolean {
209
+ return UI.ActionRegistry.ActionRegistry.instance().hasAction('ai-assistance.storage-floating-button');
210
+ }
211
+
208
212
  protected removeItem(key: string): void {
209
213
  this.domStorage?.removeItem(key);
210
214
  }