chrome-devtools-frontend 1.0.1574367 → 1.0.1575635

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 (107) hide show
  1. package/.stylelintrc.json +1 -1
  2. package/AUTHORS +1 -0
  3. package/docs/contributing/settings-experiments-features.md +5 -2
  4. package/front_end/core/common/Settings.ts +1 -1
  5. package/front_end/core/host/InspectorFrontendHostAPI.ts +7 -1
  6. package/front_end/core/host/InspectorFrontendHostStub.ts +10 -0
  7. package/front_end/core/host/UserMetrics.ts +15 -0
  8. package/front_end/core/root/Runtime.ts +119 -39
  9. package/front_end/core/sdk/CSSMatchedStyles.ts +27 -0
  10. package/front_end/entrypoints/main/MainImpl.ts +25 -7
  11. package/front_end/entrypoints/node_app/app/NodeConnectionsPanel.ts +3 -2
  12. package/front_end/generated/InspectorBackendCommands.ts +37 -0
  13. package/front_end/generated/protocol-mapping.d.ts +257 -0
  14. package/front_end/generated/protocol-proxy-api.d.ts +256 -0
  15. package/front_end/generated/protocol.ts +359 -0
  16. package/front_end/models/ai_assistance/AiConversation.ts +31 -10
  17. package/front_end/models/ai_assistance/AiHistoryStorage.ts +1 -0
  18. package/front_end/models/ai_assistance/agents/AiAgent.ts +29 -5
  19. package/front_end/models/ai_assistance/agents/ContextSelectionAgent.snapshot.txt +73 -0
  20. package/front_end/models/ai_assistance/agents/ContextSelectionAgent.ts +141 -0
  21. package/front_end/models/ai_assistance/agents/StylingAgent.snapshot.txt +3 -3
  22. package/front_end/models/ai_assistance/agents/StylingAgent.ts +5 -4
  23. package/front_end/models/ai_assistance/ai_assistance.ts +2 -0
  24. package/front_end/models/issues_manager/CorsIssue.ts +0 -3
  25. package/front_end/models/javascript_metadata/NativeFunctions.js +5 -0
  26. package/front_end/models/live-metrics/LiveMetrics.ts +31 -51
  27. package/front_end/models/stack_trace/StackTrace.ts +41 -0
  28. package/front_end/panels/ai_assistance/AiAssistancePanel.ts +72 -6
  29. package/front_end/panels/ai_assistance/ai_assistance-meta.ts +1 -0
  30. package/front_end/panels/ai_assistance/components/ChatInput.ts +24 -2
  31. package/front_end/panels/ai_assistance/components/ChatMessage.ts +6 -4
  32. package/front_end/panels/ai_assistance/components/ChatView.ts +5 -6
  33. package/front_end/panels/ai_assistance/components/chatView.css +10 -0
  34. package/front_end/panels/application/StorageView.ts +3 -3
  35. package/front_end/panels/application/preloading/components/preloadingDisabledInfobar.css +1 -1
  36. package/front_end/panels/common/AiCodeGenerationUpgradeDialog.ts +32 -16
  37. package/front_end/panels/common/aiCodeCompletionSummaryToolbar.css +1 -1
  38. package/front_end/panels/console/ConsoleContextSelector.ts +4 -3
  39. package/front_end/panels/console/ConsoleFormat.ts +31 -2
  40. package/front_end/panels/console/ConsoleInsightTeaser.ts +6 -1
  41. package/front_end/panels/elements/ElementsTreeElement.ts +46 -2
  42. package/front_end/panels/elements/ElementsTreeOutline.ts +1 -1
  43. package/front_end/panels/elements/StylePropertyTreeElement.ts +63 -0
  44. package/front_end/panels/elements/StylesSidebarPane.ts +17 -2
  45. package/front_end/panels/elements/stylePropertiesTreeOutline.css +4 -0
  46. package/front_end/panels/issues/AffectedMetadataAllowedSitesView.ts +3 -3
  47. package/front_end/panels/layer_viewer/Layers3DView.ts +2 -1
  48. package/front_end/panels/lighthouse/LighthouseStartView.ts +7 -6
  49. package/front_end/panels/linear_memory_inspector/LinearMemoryInspectorPane.ts +3 -2
  50. package/front_end/panels/network/RequestConditionsDrawer.ts +2 -3
  51. package/front_end/panels/network/SignedExchangeInfoView.ts +3 -4
  52. package/front_end/panels/network/components/HeaderSectionRow.css +1 -1
  53. package/front_end/panels/recorder/components/RecordingView.ts +2 -2
  54. package/front_end/panels/recorder/components/recordingView.css +5 -0
  55. package/front_end/panels/security/SecurityPanelSidebar.ts +10 -9
  56. package/front_end/panels/settings/AISettingsTab.ts +2 -1
  57. package/front_end/panels/settings/KeybindsSettingsTab.ts +3 -5
  58. package/front_end/panels/settings/SettingsScreen.ts +14 -9
  59. package/front_end/panels/settings/settings-meta.ts +1 -0
  60. package/front_end/panels/sources/CallStackSidebarPane.ts +10 -0
  61. package/front_end/panels/sources/DebuggerPlugin.ts +18 -12
  62. package/front_end/panels/sources/ScopeChainSidebarPane.ts +4 -3
  63. package/front_end/panels/sources/SourcesNavigator.ts +2 -2
  64. package/front_end/panels/sources/SourcesPanel.ts +6 -3
  65. package/front_end/panels/sources/TabbedEditorContainer.ts +2 -2
  66. package/front_end/panels/sources/sources-meta.ts +2 -1
  67. package/front_end/panels/timeline/TimelinePanel.ts +2 -1
  68. package/front_end/panels/timeline/TimelineUIUtils.ts +2 -2
  69. package/front_end/panels/timeline/components/DetailsView.ts +7 -7
  70. package/front_end/panels/timeline/components/insights/BaseInsightComponent.ts +2 -2
  71. package/front_end/panels/timeline/overlays/components/EntryLabelOverlay.ts +4 -4
  72. package/front_end/panels/whats_new/ReleaseNoteText.ts +11 -11
  73. package/front_end/panels/whats_new/releaseNoteView.css +1 -1
  74. package/front_end/panels/whats_new/resources/WNDT.md +6 -6
  75. package/front_end/third_party/chromium/README.chromium +1 -1
  76. package/front_end/third_party/puppeteer/README.chromium +2 -2
  77. package/front_end/third_party/puppeteer/package/lib/cjs/puppeteer/cdp/TargetManager.d.ts.map +1 -1
  78. package/front_end/third_party/puppeteer/package/lib/cjs/puppeteer/cdp/TargetManager.js +30 -25
  79. package/front_end/third_party/puppeteer/package/lib/cjs/puppeteer/cdp/TargetManager.js.map +1 -1
  80. package/front_end/third_party/puppeteer/package/lib/cjs/puppeteer/injected/injected.d.ts +1 -1
  81. package/front_end/third_party/puppeteer/package/lib/cjs/puppeteer/util/Mutex.d.ts +2 -2
  82. package/front_end/third_party/puppeteer/package/lib/cjs/puppeteer/util/version.d.ts +1 -1
  83. package/front_end/third_party/puppeteer/package/lib/cjs/puppeteer/util/version.js +1 -1
  84. package/front_end/third_party/puppeteer/package/lib/es5-iife/puppeteer-core-browser.js +30 -21
  85. package/front_end/third_party/puppeteer/package/lib/esm/puppeteer/cdp/TargetManager.d.ts.map +1 -1
  86. package/front_end/third_party/puppeteer/package/lib/esm/puppeteer/cdp/TargetManager.js +31 -26
  87. package/front_end/third_party/puppeteer/package/lib/esm/puppeteer/cdp/TargetManager.js.map +1 -1
  88. package/front_end/third_party/puppeteer/package/lib/esm/puppeteer/util/version.d.ts +1 -1
  89. package/front_end/third_party/puppeteer/package/lib/esm/puppeteer/util/version.js +1 -1
  90. package/front_end/third_party/puppeteer/package/package.json +3 -3
  91. package/front_end/third_party/puppeteer/package/src/cdp/TargetManager.ts +45 -44
  92. package/front_end/third_party/puppeteer/package/src/util/version.ts +1 -1
  93. package/front_end/ui/components/buttons/Button.ts +2 -2
  94. package/front_end/ui/components/panel_feedback/PreviewToggle.ts +1 -1
  95. package/front_end/ui/components/panel_feedback/panelFeedback.css +1 -1
  96. package/front_end/ui/kit/link/Link.ts +31 -0
  97. package/front_end/ui/legacy/LinkContextMenuProvider.ts +3 -5
  98. package/front_end/ui/legacy/components/object_ui/ObjectPopoverHelper.ts +2 -2
  99. package/front_end/ui/legacy/components/object_ui/ObjectPropertiesSection.ts +336 -250
  100. package/front_end/ui/legacy/inspectorCommon.css +0 -1
  101. package/front_end/ui/legacy/legacy.ts +0 -4
  102. package/front_end/ui/visual_logging/KnownContextValues.ts +4 -0
  103. package/front_end/ui/visual_logging/LoggingDriver.ts +10 -6
  104. package/mcp/HostBindings.ts +6 -0
  105. package/package.json +1 -1
  106. package/front_end/ui/legacy/XElement.ts +0 -41
  107. package/front_end/ui/legacy/XLink.ts +0 -128
@@ -0,0 +1,141 @@
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 Platform from '../../../core/platform/platform.js';
7
+ import * as Root from '../../../core/root/root.js';
8
+ import * as Logs from '../../logs/logs.js';
9
+
10
+ import {
11
+ type AgentOptions,
12
+ AiAgent,
13
+ type ContextResponse,
14
+ type RequestOptions,
15
+ } from './AiAgent.js';
16
+
17
+ /**
18
+ * WARNING: preamble defined in code is only used when userTier is
19
+ * TESTERS. Otherwise, a server-side preamble is used (see
20
+ * chrome_preambles.gcl). Sync local changes with the server-side.
21
+ */
22
+ const preamble = `
23
+ You are a Web Development Assistant integrated into Chrome DevTools. Your tone is educational, supportive, and technically precise.
24
+ You aim to help developers of all levels, prioritizing teaching web concepts as the primary entry point for any solution.
25
+
26
+ # Considerations
27
+ * Determine what the question the domain of the question is - styling, network, sources, performance or other part of DevTools.
28
+ * When possible proactively try to gather additional data and select context that they user may find relevant to the question they are asking utilizing the function calls available to you.
29
+ * Avoid making assumptions without sufficient evidence, and always seek further clarification if needed.
30
+ * Always explore multiple possible explanations for the observed behavior before settling on a conclusion.
31
+ * When presenting solutions, clearly distinguish between the primary cause and contributing factors.
32
+ * Please answer only if you are sure about the answer. Otherwise, explain why you're not able to answer.
33
+ * When answering, always consider MULTIPLE possible solutions.
34
+ * If you are unable to gather more information provide a comprehensive guide to how to fix the issue using Chrome DevTools and explain how and why.
35
+ * You can suggest any panel or flow in Chrome DevTools that may help the user out
36
+
37
+ # Formatting Guidelines
38
+ * Use Markdown for all code snippets.
39
+ * Always specify the language for code blocks (e.g., \`\`\`css, \`\`\`javascript).
40
+ * Keep text responses concise and scannable.
41
+
42
+ * 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. You're also capable of executing the fix for the issue user mentioned. Reflect this in your suggestions.
43
+ * **CRITICAL** NEVER write full Python programs - you should only write individual statements that invoke a single function from the provided library.
44
+ * **CRITICAL** NEVER output text before a function call. Always do a function call first.
45
+ * **CRITICAL** You are a debugging assistant in DevTools. 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.
46
+ `;
47
+
48
+ /**
49
+ * One agent instance handles one conversation. Create a new agent
50
+ * instance for a new conversation.
51
+ */
52
+ export class ContextSelectionAgent extends AiAgent<never> {
53
+ readonly preamble = preamble;
54
+ readonly clientFeature = Host.AidaClient.ClientFeature.CHROME_FILE_AGENT;
55
+ get userTier(): string|undefined {
56
+ // TODO: Make this depend on variable.
57
+ return Root.Runtime.hostConfig.devToolsFreestyler?.userTier;
58
+ }
59
+ get options(): RequestOptions {
60
+ const temperature = Root.Runtime.hostConfig.devToolsAiAssistanceFileAgent?.temperature;
61
+ const modelId = Root.Runtime.hostConfig.devToolsAiAssistanceFileAgent?.modelId;
62
+
63
+ return {
64
+ temperature,
65
+ modelId,
66
+ };
67
+ }
68
+
69
+ constructor(opts: AgentOptions) {
70
+ super(opts);
71
+
72
+ this.declareFunction<Record<string, never>>('listNetworkRequests', {
73
+ description: `Gives a list of network requests`,
74
+ parameters: {
75
+ type: Host.AidaClient.ParametersTypes.OBJECT,
76
+ description: '',
77
+ nullable: true,
78
+ required: [],
79
+ properties: {},
80
+ },
81
+ handler: async (_params, options) => {
82
+ if (!options?.approved) {
83
+ return {
84
+ requiresApproval: true,
85
+ };
86
+ }
87
+
88
+ const requestURls = [];
89
+ for (const request of Logs.NetworkLog.NetworkLog.instance().requests()) {
90
+ requestURls.push(request.url());
91
+ }
92
+
93
+ return {
94
+ result: requestURls,
95
+ };
96
+ },
97
+ });
98
+
99
+ this.declareFunction<{url: string}>('selectNetworkRequest', {
100
+ description: `From the list of selected request select one to debug`,
101
+ parameters: {
102
+ type: Host.AidaClient.ParametersTypes.OBJECT,
103
+ description: '',
104
+ nullable: true,
105
+ required: ['url'],
106
+ properties: {
107
+ url: {
108
+ type: Host.AidaClient.ParametersTypes.STRING,
109
+ description: 'The url of the requests',
110
+ nullable: false,
111
+ },
112
+ },
113
+ },
114
+ handler: async ({url}) => {
115
+ // TODO: Switch to using IDs to make is easier to link to as well.
116
+ const request = Logs.NetworkLog.NetworkLog.instance().requests().find(req => {
117
+ return req.url() === Platform.DevToolsPath.urlString`${url}` ||
118
+ req.url() === Platform.DevToolsPath.urlString`${url.slice(0, -1)}` ||
119
+ req.url() === Platform.DevToolsPath.urlString`${url}/`;
120
+ });
121
+
122
+ if (request) {
123
+ return {
124
+ context: request,
125
+ };
126
+ }
127
+
128
+ return {
129
+ error: 'No request found',
130
+ };
131
+ },
132
+ });
133
+ }
134
+
135
+ async * handleContextDetails(): AsyncGenerator<ContextResponse, void, void> {
136
+ }
137
+
138
+ override async enhanceQuery(query: string): Promise<string> {
139
+ return query;
140
+ }
141
+ }
@@ -57,7 +57,7 @@ Content:
57
57
  "function_declarations": [
58
58
  {
59
59
  "name": "getStyles",
60
- "description": "Get computed and source styles for one or multiple elements on the inspected page for multiple elements at once by uid.\n\n**CRITICAL** Use selectors to refer to elements in the text output. Do not use uids.\n**CRITICAL** Always provide the explanation argument to explain what and why you query.",
60
+ "description": "Get computed and source styles for one or multiple elements on the inspected page for multiple elements at once by uid.\n\n**CRITICAL** An element uid is a number, not a selector.\n**CRITICAL** Use selectors to refer to elements in the text output. Do not use uids.\n**CRITICAL** Always provide the explanation argument to explain what and why you query.",
61
61
  "parameters": {
62
62
  "type": 6,
63
63
  "description": "",
@@ -70,9 +70,9 @@ Content:
70
70
  },
71
71
  "elements": {
72
72
  "type": 5,
73
- "description": "A list of element uids to get data for",
73
+ "description": "A list of element uids to get data for. These are numbers, not selectors.",
74
74
  "items": {
75
- "type": 1,
75
+ "type": 3,
76
76
  "description": "An element uid."
77
77
  },
78
78
  "nullable": false
@@ -279,13 +279,14 @@ export class StylingAgent extends AiAgent<SDK.DOMModel.DOMNode> {
279
279
  });
280
280
 
281
281
  this.declareFunction<{
282
- elements: string[],
282
+ elements: number[],
283
283
  styleProperties: string[],
284
284
  explanation: string,
285
285
  }>('getStyles', {
286
286
  description:
287
287
  `Get computed and source styles for one or multiple elements on the inspected page for multiple elements at once by uid.
288
288
 
289
+ **CRITICAL** An element uid is a number, not a selector.
289
290
  **CRITICAL** Use selectors to refer to elements in the text output. Do not use uids.
290
291
  **CRITICAL** Always provide the explanation argument to explain what and why you query.`,
291
292
  parameters: {
@@ -300,8 +301,8 @@ export class StylingAgent extends AiAgent<SDK.DOMModel.DOMNode> {
300
301
  },
301
302
  elements: {
302
303
  type: Host.AidaClient.ParametersTypes.ARRAY,
303
- description: 'A list of element uids to get data for',
304
- items: {type: Host.AidaClient.ParametersTypes.STRING, description: `An element uid.`},
304
+ description: 'A list of element uids to get data for. These are numbers, not selectors.',
305
+ items: {type: Host.AidaClient.ParametersTypes.INTEGER, description: `An element uid.`},
305
306
  nullable: false,
306
307
  },
307
308
  styleProperties: {
@@ -605,7 +606,7 @@ const data = {
605
606
  return this.context?.getItem() ?? null;
606
607
  }
607
608
 
608
- async getStyles(elements: string[], properties: string[]): Promise<FunctionCallHandlerResult<unknown>> {
609
+ async getStyles(elements: number[], properties: string[]): Promise<FunctionCallHandlerResult<unknown>> {
609
610
  const result:
610
611
  Record<string, {computed: Record<string, string|undefined>, authored: Record<string, string|undefined>}> = {};
611
612
  for (const uid of elements) {
@@ -4,6 +4,7 @@
4
4
 
5
5
  import * as AgentProject from './AgentProject.js';
6
6
  import * as AiAgent from './agents/AiAgent.js';
7
+ import * as ContextSelectionAgent from './agents/ContextSelectionAgent.js';
7
8
  import * as FileAgent from './agents/FileAgent.js';
8
9
  import * as NetworkAgent from './agents/NetworkAgent.js';
9
10
  import * as PatchAgent from './agents/PatchAgent.js';
@@ -40,6 +41,7 @@ export {
40
41
  AiUtils,
41
42
  BuiltInAi,
42
43
  ChangeManager,
44
+ ContextSelectionAgent,
43
45
  ConversationHandler,
44
46
  Debug,
45
47
  EvaluateAction,
@@ -94,9 +94,6 @@ function getIssueCode(details: Protocol.Audits.CorsIssueDetails): IssueCode {
94
94
  case Protocol.Network.CorsError.LocalNetworkAccessPermissionDenied:
95
95
  return IssueCode.LOCAL_NETWORK_ACCESS_PERMISSION_DENIED;
96
96
  }
97
- // TODO(b/394636065): Remove this once browser protocol has rolled, as we
98
- // will never hit this case.
99
- return null as unknown as IssueCode;
100
97
  }
101
98
 
102
99
  export class CorsIssue extends Issue<Protocol.Audits.CorsIssueDetails, IssueCode> {
@@ -6044,6 +6044,11 @@ export const NativeFunctions = [
6044
6044
  signatures: [["r"]],
6045
6045
  receivers: ["FetchEvent"]
6046
6046
  },
6047
+ {
6048
+ name: "respondWith",
6049
+ signatures: [["agentResponse"]],
6050
+ receivers: ["SubmitEvent"]
6051
+ },
6047
6052
  {
6048
6053
  name: "respondWith",
6049
6054
  signatures: [["paymentAbortedResponse"]],
@@ -62,7 +62,29 @@ export class LiveMetrics extends Common.ObjectWrapper.ObjectWrapper<EventTypes>
62
62
 
63
63
  private constructor() {
64
64
  super();
65
- SDK.TargetManager.TargetManager.instance().observeTargets(this);
65
+ const targetManager = SDK.TargetManager.TargetManager.instance();
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);
72
+ }
73
+
74
+ #onAvailableTargetsChanged(): void {
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.
78
+ void this.#switchToTarget(primaryTarget);
79
+ }
80
+ }
81
+
82
+ async #switchToTarget(newTarget: SDK.Target.Target): Promise<void> {
83
+ if (this.#target) {
84
+ await this.disable();
85
+ }
86
+ this.#target = newTarget;
87
+ await this.enable();
66
88
  }
67
89
 
68
90
  static instance(opts: {forceNew?: boolean} = {forceNew: false}): LiveMetrics {
@@ -371,31 +393,6 @@ export class LiveMetrics extends Common.ObjectWrapper.ObjectWrapper<EventTypes>
371
393
  this.#sendStatusUpdate();
372
394
  }
373
395
 
374
- async #getFrameForExecutionContextId(executionContextId: Protocol.Runtime.ExecutionContextId):
375
- Promise<SDK.ResourceTreeModel.ResourceTreeFrame|null> {
376
- if (!this.#target) {
377
- return null;
378
- }
379
-
380
- const runtimeModel = this.#target.model(SDK.RuntimeModel.RuntimeModel);
381
- if (!runtimeModel) {
382
- return null;
383
- }
384
-
385
- const executionContext = runtimeModel.executionContext(executionContextId);
386
- if (!executionContext) {
387
- return null;
388
- }
389
-
390
- const frameId = executionContext.frameId;
391
- if (!frameId) {
392
- return null;
393
- }
394
-
395
- const frameManager = SDK.FrameManager.FrameManager.instance();
396
- return await frameManager.getOrWaitForFrame(frameId);
397
- }
398
-
399
396
  async #onBindingCalled(event: {data: Protocol.Runtime.BindingCalledEvent}): Promise<void> {
400
397
  const {data} = event;
401
398
  if (data.name !== Spec.EVENT_BINDING_NAME) {
@@ -407,22 +404,8 @@ export class LiveMetrics extends Common.ObjectWrapper.ObjectWrapper<EventTypes>
407
404
  await this.#mutex.run(async () => {
408
405
  const webVitalsEvent = JSON.parse(data.payload) as Spec.WebVitalsEvent;
409
406
 
410
- // This ensures that `#lastResetContextId` will always be an execution context on the
411
- // primary frame. If we receive events from this execution context then we automatically
412
- // know that they are for the primary frame.
413
- if (this.#lastResetContextId !== data.executionContextId) {
414
- if (webVitalsEvent.name !== 'reset') {
415
- return;
416
- }
417
-
418
- // We should avoid calling this function for every event.
419
- // If an interaction triggers a pre-rendered navigation then the old primary frame could
420
- // be removed before we reach this point, and then it will hang forever.
421
- const frame = await this.#getFrameForExecutionContextId(data.executionContextId);
422
- if (!frame?.isPrimaryFrame()) {
423
- return;
424
- }
425
-
407
+ // Track the execution context from 'reset' events for logInteractionScripts().
408
+ if (webVitalsEvent.name === 'reset') {
426
409
  this.#lastResetContextId = data.executionContextId;
427
410
  }
428
411
 
@@ -464,6 +447,7 @@ export class LiveMetrics extends Common.ObjectWrapper.ObjectWrapper<EventTypes>
464
447
  }
465
448
 
466
449
  async targetAdded(target: SDK.Target.Target): Promise<void> {
450
+ // Scoped observers can also receive events for OOPIFs and workers.
467
451
  if (target !== SDK.TargetManager.TargetManager.instance().primaryPageTarget()) {
468
452
  return;
469
453
  }
@@ -472,20 +456,12 @@ export class LiveMetrics extends Common.ObjectWrapper.ObjectWrapper<EventTypes>
472
456
  }
473
457
 
474
458
  async targetRemoved(target: SDK.Target.Target): Promise<void> {
459
+ // Scoped observers can also receive events for OOPIFs and workers.
475
460
  if (target !== this.#target) {
476
461
  return;
477
462
  }
478
463
  await this.disable();
479
464
  this.#target = undefined;
480
-
481
- // If the user navigates to a page that was pre-rendered then the primary page target
482
- // will be swapped and the old target will be removed. We should ensure live metrics
483
- // remain enabled on the new primary page target.
484
- const primaryPageTarget = SDK.TargetManager.TargetManager.instance().primaryPageTarget();
485
- if (primaryPageTarget) {
486
- this.#target = primaryPageTarget;
487
- await this.enable();
488
- }
489
465
  }
490
466
 
491
467
  async enable(): Promise<void> {
@@ -555,6 +531,10 @@ export class LiveMetrics extends Common.ObjectWrapper.ObjectWrapper<EventTypes>
555
531
  return;
556
532
  }
557
533
 
534
+ // Reset to ensure clean state when re-enabling on a new target.
535
+ // See crbug.com/478832430.
536
+ this.#lastResetContextId = undefined;
537
+
558
538
  await this.#killAllLiveMetricContexts();
559
539
 
560
540
  const runtimeModel = this.#target.model(SDK.RuntimeModel.RuntimeModel);
@@ -62,3 +62,44 @@ export const enum Events {
62
62
  export interface EventTypes {
63
63
  [Events.UPDATED]: void;
64
64
  }
65
+
66
+ /**
67
+ * A small wrapper around a DebuggableFrame usable as a UI.Context flavor.
68
+ * This is necessary as Frame and DebuggableFrame are updated in place, but
69
+ * for UI.Context we need a new instance.
70
+ */
71
+ export class DebuggableFrameFlavor implements DebuggableFrame {
72
+ static #last?: DebuggableFrameFlavor;
73
+
74
+ readonly url?: string;
75
+ readonly uiSourceCode?: Workspace.UISourceCode.UISourceCode;
76
+ readonly name?: string;
77
+ readonly line: number;
78
+ readonly column: number;
79
+ readonly missingDebugInfo?: MissingDebugInfo;
80
+ readonly sdkFrame: SDK.DebuggerModel.CallFrame;
81
+
82
+ /** Use the static {@link for}. Only public to satisfy the `setFlavor` Ctor type */
83
+ constructor(frame: DebuggableFrame) {
84
+ this.url = frame.url;
85
+ this.uiSourceCode = frame.uiSourceCode;
86
+ this.name = frame.name;
87
+ this.line = frame.line;
88
+ this.column = frame.column;
89
+ this.missingDebugInfo = frame.missingDebugInfo;
90
+ this.sdkFrame = frame.sdkFrame;
91
+ }
92
+
93
+ /** @returns the same instance of DebuggableFrameFlavor for repeated calls with the same (i.e. deep equal) DebuggableFrame */
94
+ static for(frame: DebuggableFrame): DebuggableFrameFlavor {
95
+ function equals(a: DebuggableFrame, b: DebuggableFrame): boolean {
96
+ return a.url === b.url && a.uiSourceCode === b.uiSourceCode && a.name === b.name && a.line === b.line &&
97
+ a.column === b.column && a.sdkFrame === b.sdkFrame;
98
+ }
99
+
100
+ if (!DebuggableFrameFlavor.#last || !equals(DebuggableFrameFlavor.#last, frame)) {
101
+ DebuggableFrameFlavor.#last = new DebuggableFrameFlavor(frame);
102
+ }
103
+ return DebuggableFrameFlavor.#last;
104
+ }
105
+ }
@@ -44,10 +44,6 @@ import {MarkdownRendererWithCodeBlock} from './components/MarkdownRendererWithCo
44
44
  import {PerformanceAgentMarkdownRenderer} from './components/PerformanceAgentMarkdownRenderer.js';
45
45
  import {isAiAssistancePatchingEnabled} from './PatchWidget.js';
46
46
 
47
- // FIXME: this export is temporary to avoid rewriting tests.
48
- export {ChatMessageEntity} from './components/ChatMessage.js';
49
- export type {AnswerPart, ModelChatMessage, StepPart} from './components/ChatMessage.js';
50
-
51
47
  const {html} = Lit;
52
48
 
53
49
  const AI_ASSISTANCE_SEND_FEEDBACK = 'https://crbug.com/364805393' as Platform.DevToolsPath.UrlString;
@@ -164,6 +160,10 @@ const UIStringsNotTranslate = {
164
160
  *@description Placeholder text for the chat UI input.
165
161
  */
166
162
  inputPlaceholderForPerformanceTraceNoContext: 'Record or select a performance trace to ask a question',
163
+ /**
164
+ *@description Placeholder text for the chat UI input.
165
+ */
166
+ inputPlaceholderForNoContext: 'Ask AI Assistance',
167
167
  /**
168
168
  * @description Disclaimer text right after the chat input.
169
169
  */
@@ -204,6 +204,7 @@ const UIStringsNotTranslate = {
204
204
  */
205
205
  inputDisclaimerForPerformanceEnterpriseNoLogging:
206
206
  'Chat messages and data from your performance trace are sent to Google. The content you submit and that is generated by this feature will not be used to improve Google’s AI models. This is an experimental AI feature and won’t always get it right.',
207
+
207
208
  } as const;
208
209
 
209
210
  const str_ = i18n.i18n.registerUIStrings('panels/ai_assistance/AiAssistancePanel.ts', UIStrings);
@@ -259,6 +260,14 @@ async function getEmptyStateSuggestions(conversation?: AiAssistanceModel.AiConve
259
260
  ];
260
261
  }
261
262
 
263
+ case AiAssistanceModel.AiHistoryStorage.ConversationType.NONE: {
264
+ return [
265
+ {title: 'How can I use DevTools to debug?', jslogContext: 'empty'},
266
+ {title: 'What performance issues exist with my page?', jslogContext: 'empty'},
267
+ {title: 'What are the slowest requests on this page?', jslogContext: 'empty'},
268
+ ];
269
+ }
270
+
262
271
  default:
263
272
  Platform.assertNever(conversation.type, 'Unknown conversation type');
264
273
  }
@@ -395,7 +404,7 @@ function defaultView(input: ViewInput, output: PanelViewOutput, target: HTMLElem
395
404
  return html`
396
405
  <devtools-ai-chat-view
397
406
  .props=${input.props}
398
- ${Lit.Directives.ref((el: Element | undefined) => {
407
+ ${Lit.Directives.ref(el => {
399
408
  if (!el || !(el instanceof ChatView)) {
400
409
  return;
401
410
  }
@@ -490,6 +499,7 @@ export class AiAssistancePanel extends UI.Panel.Panel {
490
499
  #userInfo: {
491
500
  accountImage?: string,
492
501
  accountFullName?: string,
502
+ accountGivenName?: string,
493
503
  };
494
504
  #timelinePanelInstance: TimelinePanel.TimelinePanel.TimelinePanel|null = null;
495
505
  #runAbortController = new AbortController();
@@ -509,6 +519,7 @@ export class AiAssistancePanel extends UI.Panel.Panel {
509
519
  this.#userInfo = {
510
520
  accountImage: syncInfo.accountImage,
511
521
  accountFullName: syncInfo.accountFullName,
522
+ accountGivenName: syncInfo.accountGivenName,
512
523
  };
513
524
 
514
525
  if (UI.ActionRegistry.ActionRegistry.instance().hasAction('elements.toggle-element-search')) {
@@ -658,6 +669,10 @@ export class AiAssistancePanel extends UI.Panel.Panel {
658
669
  targetConversationType = AiAssistanceModel.AiHistoryStorage.ConversationType.PERFORMANCE;
659
670
  }
660
671
 
672
+ if (isAiAssistanceContextSelectionAgentEnabled() && !targetConversationType) {
673
+ return AiAssistanceModel.AiHistoryStorage.ConversationType.NONE;
674
+ }
675
+
661
676
  return targetConversationType;
662
677
  }
663
678
 
@@ -680,6 +695,7 @@ export class AiAssistancePanel extends UI.Panel.Panel {
680
695
  new AiAssistanceModel.AiConversation.AiConversation(
681
696
  targetConversationType, [], undefined, false, this.#aidaClient, this.#changeManager) :
682
697
  undefined;
698
+
683
699
  this.#updateConversationState(conversation);
684
700
  }
685
701
 
@@ -807,6 +823,7 @@ export class AiAssistancePanel extends UI.Panel.Panel {
807
823
  this.#userInfo = {
808
824
  accountImage: syncInfo.accountImage,
809
825
  accountFullName: syncInfo.accountFullName,
826
+ accountGivenName: syncInfo.accountGivenName,
810
827
  };
811
828
  this.requestUpdate();
812
829
  }
@@ -930,7 +947,7 @@ export class AiAssistancePanel extends UI.Panel.Panel {
930
947
  return true;
931
948
  }
932
949
 
933
- if (!this.#conversation.selectedContext) {
950
+ if (!this.#conversation.selectedContext && !isAiAssistanceContextSelectionAgentEnabled()) {
934
951
  return true;
935
952
  }
936
953
 
@@ -982,6 +999,8 @@ export class AiAssistancePanel extends UI.Panel.Panel {
982
999
 
983
1000
  return lockedString(UIStringsNotTranslate.inputPlaceholderForPerformanceWithNoRecording);
984
1001
  }
1002
+ case AiAssistanceModel.AiHistoryStorage.ConversationType.NONE:
1003
+ return lockedString(UIStringsNotTranslate.inputPlaceholderForNoContext);
985
1004
  }
986
1005
  }
987
1006
 
@@ -1016,6 +1035,9 @@ export class AiAssistancePanel extends UI.Panel.Panel {
1016
1035
  return lockedString(UIStringsNotTranslate.inputDisclaimerForPerformanceEnterpriseNoLogging);
1017
1036
  }
1018
1037
  return lockedString(UIStringsNotTranslate.inputDisclaimerForPerformance);
1038
+
1039
+ case AiAssistanceModel.AiHistoryStorage.ConversationType.NONE:
1040
+ return lockedString(UIStringsNotTranslate.inputDisclaimerForPerformance);
1019
1041
  }
1020
1042
  }
1021
1043
 
@@ -1249,9 +1271,41 @@ export class AiAssistancePanel extends UI.Panel.Panel {
1249
1271
  return this.#selectedRequest;
1250
1272
  case AiAssistanceModel.AiHistoryStorage.ConversationType.PERFORMANCE:
1251
1273
  return this.#selectedPerformanceTrace;
1274
+ case AiAssistanceModel.AiHistoryStorage.ConversationType.NONE:
1275
+ return null;
1252
1276
  }
1253
1277
  }
1254
1278
 
1279
+ #handleConversationContextChange = (data: unknown): void => {
1280
+ if (data instanceof Workspace.UISourceCode.UISourceCode) {
1281
+ if (this.#selectedFile?.getItem() === data) {
1282
+ return;
1283
+ }
1284
+ this.#selectedFile = new AiAssistanceModel.FileAgent.FileContext(data);
1285
+
1286
+ } else if (data instanceof SDK.DOMModel.DOMNode) {
1287
+ if (this.#selectedElement?.getItem() === data ||
1288
+ // Ignore non node type like comments or html tags
1289
+ data.nodeType() === Node.ELEMENT_NODE) {
1290
+ return;
1291
+ }
1292
+ this.#selectedElement = new AiAssistanceModel.StylingAgent.NodeContext(data);
1293
+ } else if (data instanceof SDK.NetworkRequest.NetworkRequest) {
1294
+ if (this.#selectedRequest?.getItem() === data) {
1295
+ return;
1296
+ }
1297
+ const calculator = NetworkPanel.NetworkPanel.NetworkPanel.instance().networkLogView.timeCalculator();
1298
+ this.#selectedRequest = new AiAssistanceModel.NetworkAgent.RequestContext(data, calculator);
1299
+ } else if (data instanceof AiAssistanceModel.AIContext.AgentFocus) {
1300
+ if (this.#selectedPerformanceTrace?.getItem() === data) {
1301
+ return;
1302
+ }
1303
+ this.#selectedPerformanceTrace = new AiAssistanceModel.PerformanceAgent.PerformanceTraceContext(data);
1304
+ }
1305
+
1306
+ this.#updateConversationState(this.#conversation);
1307
+ };
1308
+
1255
1309
  async #startConversation(
1256
1310
  text: string,
1257
1311
  imageInput?: Host.AidaClient.Part,
@@ -1453,6 +1507,14 @@ export class AiAssistancePanel extends UI.Panel.Panel {
1453
1507
  }
1454
1508
  break;
1455
1509
  }
1510
+
1511
+ case AiAssistanceModel.AiAgent.ResponseType.CONTEXT_CHANGE: {
1512
+ this.#handleConversationContextChange(data.context);
1513
+ step.isLoading = false;
1514
+ commitStep();
1515
+
1516
+ break;
1517
+ }
1456
1518
  }
1457
1519
 
1458
1520
  // Commit update intermediate step when not
@@ -1576,6 +1638,10 @@ function isAiAssistanceMultimodalInputEnabled(): boolean {
1576
1638
  return Boolean(Root.Runtime.hostConfig.devToolsFreestyler?.multimodal);
1577
1639
  }
1578
1640
 
1641
+ function isAiAssistanceContextSelectionAgentEnabled(): boolean {
1642
+ return Boolean(Root.Runtime.hostConfig.devToolsAiAssistanceContextSelectionAgent?.enabled);
1643
+ }
1644
+
1579
1645
  function isAiAssistanceServerSideLoggingEnabled(): boolean {
1580
1646
  return !Root.Runtime.hostConfig.aidaAvailability?.disallowLogging;
1581
1647
  }
@@ -174,6 +174,7 @@ UI.ActionRegistration.registerActionExtension({
174
174
  return new AiAssistance.ActionDelegate();
175
175
  },
176
176
  condition: config => isAnyFeatureAvailable(config) && !isPolicyRestricted(config) && !isGeoRestricted(config),
177
+ featurePromotionId: 'debug-with-ai',
177
178
  });
178
179
 
179
180
  UI.ActionRegistration.registerActionExtension({
@@ -135,6 +135,8 @@ export interface ViewInput {
135
135
  onRemoveImageInput: () => void;
136
136
  onImageUpload: (ev: Event) => void;
137
137
  onImagePaste: (event: ClipboardEvent) => void;
138
+ onImageDragOver: (event: DragEvent) => void;
139
+ onImageDrop: (event: DragEvent) => void;
138
140
  }
139
141
 
140
142
  export type ViewOutput = undefined;
@@ -289,6 +291,8 @@ export const DEFAULT_VIEW = (input: ViewInput, output: ViewOutput, target: HTMLE
289
291
  maxlength="10000"
290
292
  @keydown=${input.onTextAreaKeyDown}
291
293
  @paste=${input.onImagePaste}
294
+ @dragover=${input.onImageDragOver}
295
+ @drop=${input.onImageDrop}
292
296
  @input=${(event: KeyboardEvent) => {
293
297
  input.onTextInputChange((event.target as HTMLInputElement).value);
294
298
  }}
@@ -550,12 +554,12 @@ export class ChatInput extends UI.Widget.Widget implements SDK.TargetManager.Obs
550
554
  });
551
555
  }
552
556
 
553
- #handleImagePaste = (event: ClipboardEvent): void => {
557
+ #handleImageDataTransferEvent(dataTransfer: DataTransfer|null, event: Event): void {
554
558
  if (this.conversationType !== AiAssistanceModel.AiHistoryStorage.ConversationType.STYLING) {
555
559
  return;
556
560
  }
557
561
 
558
- const files = event.clipboardData?.files;
562
+ const files = dataTransfer?.files;
559
563
  if (!files || files.length === 0) {
560
564
  return;
561
565
  }
@@ -567,6 +571,22 @@ export class ChatInput extends UI.Widget.Widget implements SDK.TargetManager.Obs
567
571
 
568
572
  event.preventDefault();
569
573
  void this.#handleLoadImage(imageFile);
574
+ }
575
+
576
+ #handleImagePaste = (event: ClipboardEvent): void => {
577
+ this.#handleImageDataTransferEvent(event.clipboardData, event);
578
+ };
579
+
580
+ #handleImageDragOver = (event: DragEvent): void => {
581
+ if (this.conversationType !== AiAssistanceModel.AiHistoryStorage.ConversationType.STYLING) {
582
+ return;
583
+ }
584
+
585
+ event.preventDefault();
586
+ };
587
+
588
+ #handleImageDrop = (event: DragEvent): void => {
589
+ this.#handleImageDataTransferEvent(event.dataTransfer, event);
570
590
  };
571
591
 
572
592
  async #handleLoadImage(file: File): Promise<void> {
@@ -663,6 +683,8 @@ export class ChatInput extends UI.Widget.Widget implements SDK.TargetManager.Obs
663
683
  onTextAreaKeyDown: this.onTextAreaKeyDown,
664
684
  onCancel: this.onCancel,
665
685
  onImageUpload: this.onImageUpload,
686
+ onImageDragOver: this.#handleImageDragOver,
687
+ onImageDrop: this.#handleImageDrop,
666
688
  },
667
689
  undefined, this.contentElement);
668
690
  }