chrome-devtools-frontend 1.0.1642899 → 1.0.1643855

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 (125) hide show
  1. package/eslint.config.mjs +3 -1
  2. package/extension-api/ExtensionAPI.d.ts +83 -12
  3. package/front_end/core/host/UserMetrics.ts +0 -1
  4. package/front_end/core/protocol_client/InspectorBackend.ts +4 -0
  5. package/front_end/core/root/ExperimentNames.ts +0 -1
  6. package/front_end/core/sdk/ConsoleModel.ts +4 -0
  7. package/front_end/core/sdk/NetworkRequest.ts +12 -0
  8. package/front_end/core/sdk/SourceMap.ts +15 -18
  9. package/front_end/entrypoints/greendev_floaty/FloatyEntrypoint.ts +0 -2
  10. package/front_end/entrypoints/greendev_floaty/greendev_floaty.ts +0 -2
  11. package/front_end/entrypoints/heap_snapshot_worker/HeapSnapshot.ts +4 -5
  12. package/front_end/entrypoints/main/MainImpl.ts +0 -6
  13. package/front_end/generated/InspectorBackendCommands.ts +1 -1
  14. package/front_end/generated/protocol.ts +7 -0
  15. package/front_end/models/ai_assistance/AiAgent2.ts +24 -5
  16. package/front_end/models/ai_assistance/AiConversation.ts +15 -12
  17. package/front_end/models/ai_assistance/AiUtils.ts +71 -0
  18. package/front_end/models/ai_assistance/ChangeManager.ts +2 -5
  19. package/front_end/models/ai_assistance/{agents/ConversationSummaryAgent.ts → ConversationSummary.ts} +29 -66
  20. package/front_end/models/ai_assistance/ExtensionScope.ts +1 -4
  21. package/front_end/models/ai_assistance/{agents/PerformanceAnnotationsAgent.ts → PerformanceAnnotations.ts} +47 -89
  22. package/front_end/models/ai_assistance/agents/AccessibilityAgent.ts +47 -31
  23. package/front_end/models/ai_assistance/agents/AiAgent.ts +40 -12
  24. package/front_end/models/ai_assistance/agents/ContextSelectionAgent.snapshot.txt +11 -0
  25. package/front_end/models/ai_assistance/agents/ContextSelectionAgent.ts +58 -8
  26. package/front_end/models/ai_assistance/agents/ExecuteJavascript.ts +1 -92
  27. package/front_end/models/ai_assistance/agents/NetworkAgent.ts +25 -0
  28. package/front_end/models/ai_assistance/agents/PerformanceAgent.ts +94 -79
  29. package/front_end/models/ai_assistance/agents/StorageAgent.ts +101 -39
  30. package/front_end/models/ai_assistance/agents/StylingAgent.snapshot.txt +1 -2
  31. package/front_end/models/ai_assistance/agents/StylingAgent.ts +27 -21
  32. package/front_end/models/ai_assistance/ai_assistance.ts +6 -4
  33. package/front_end/models/ai_assistance/skills/styling.md +12 -4
  34. package/front_end/models/ai_assistance/tools/ExecuteJavaScript.ts +134 -0
  35. package/front_end/models/ai_assistance/tools/GetStyles.ts +6 -2
  36. package/front_end/models/ai_assistance/tools/Tool.ts +16 -1
  37. package/front_end/models/ai_assistance/tools/ToolRegistry.ts +2 -0
  38. package/front_end/models/bindings/DebuggerWorkspaceBinding.ts +6 -9
  39. package/front_end/models/bindings/DefaultScriptMapping.ts +2 -1
  40. package/front_end/models/bindings/SymbolizedError.ts +45 -35
  41. package/front_end/models/extensions/ExtensionAPI.ts +138 -47
  42. package/front_end/models/har/Importer.ts +1 -0
  43. package/front_end/models/heap_snapshot/HeapSnapshotProxy.ts +5 -7
  44. package/front_end/models/source_map_scopes/FunctionCodeResolver.ts +12 -2
  45. package/front_end/models/stack_trace/DetailedErrorStackParser.ts +44 -51
  46. package/front_end/models/stack_trace/StackTrace.ts +7 -0
  47. package/front_end/models/stack_trace/StackTraceImpl.ts +13 -4
  48. package/front_end/models/stack_trace/StackTraceModel.ts +9 -8
  49. package/front_end/panels/accessibility/AccessibilitySidebarView.ts +2 -1
  50. package/front_end/panels/ai_assistance/AiAssistancePanel.ts +8 -8
  51. package/front_end/panels/ai_assistance/components/ChatMessage.ts +96 -4
  52. package/front_end/panels/ai_assistance/components/chatMessage.css +6 -0
  53. package/front_end/panels/application/ApplicationPanelSidebar.ts +39 -0
  54. package/front_end/panels/application/ApplicationPanelTreeElement.ts +39 -0
  55. package/front_end/panels/application/CookieItemsView.ts +2 -2
  56. package/front_end/panels/application/components/AdsView.ts +219 -0
  57. package/front_end/panels/application/components/adsView.css +54 -0
  58. package/front_end/panels/application/components/components.ts +2 -0
  59. package/front_end/panels/application/resourcesSidebar.css +11 -0
  60. package/front_end/panels/console/SymbolizedErrorWidget.ts +79 -25
  61. package/front_end/panels/network/NetworkLogView.ts +5 -1
  62. package/front_end/panels/settings/emulation/DevicesSettingsTab.ts +1 -0
  63. package/front_end/panels/sources/SourcesPanel.ts +2 -1
  64. package/front_end/panels/timeline/overlays/components/EntryLabelOverlay.ts +5 -4
  65. package/front_end/third_party/chromium/README.chromium +1 -1
  66. package/front_end/third_party/lighthouse/README.chromium +2 -2
  67. package/front_end/third_party/lighthouse/lighthouse-dt-bundle.js +1607 -5733
  68. package/front_end/third_party/lighthouse/locales/ar-XB.json +290 -65
  69. package/front_end/third_party/lighthouse/locales/ar.json +290 -65
  70. package/front_end/third_party/lighthouse/locales/bg.json +290 -65
  71. package/front_end/third_party/lighthouse/locales/ca.json +295 -70
  72. package/front_end/third_party/lighthouse/locales/cs.json +290 -65
  73. package/front_end/third_party/lighthouse/locales/da.json +294 -69
  74. package/front_end/third_party/lighthouse/locales/de.json +295 -70
  75. package/front_end/third_party/lighthouse/locales/el.json +290 -65
  76. package/front_end/third_party/lighthouse/locales/en-GB.json +290 -65
  77. package/front_end/third_party/lighthouse/locales/en-US.json +79 -67
  78. package/front_end/third_party/lighthouse/locales/en-XA.json +253 -64
  79. package/front_end/third_party/lighthouse/locales/en-XL.json +79 -67
  80. package/front_end/third_party/lighthouse/locales/es-419.json +290 -65
  81. package/front_end/third_party/lighthouse/locales/es.json +298 -73
  82. package/front_end/third_party/lighthouse/locales/fi.json +290 -65
  83. package/front_end/third_party/lighthouse/locales/fil.json +290 -65
  84. package/front_end/third_party/lighthouse/locales/fr.json +294 -69
  85. package/front_end/third_party/lighthouse/locales/he.json +293 -68
  86. package/front_end/third_party/lighthouse/locales/hi.json +291 -66
  87. package/front_end/third_party/lighthouse/locales/hr.json +290 -65
  88. package/front_end/third_party/lighthouse/locales/hu.json +290 -65
  89. package/front_end/third_party/lighthouse/locales/id.json +290 -65
  90. package/front_end/third_party/lighthouse/locales/it.json +294 -69
  91. package/front_end/third_party/lighthouse/locales/ja.json +290 -65
  92. package/front_end/third_party/lighthouse/locales/ko.json +290 -65
  93. package/front_end/third_party/lighthouse/locales/lt.json +290 -65
  94. package/front_end/third_party/lighthouse/locales/lv.json +290 -65
  95. package/front_end/third_party/lighthouse/locales/nl.json +290 -65
  96. package/front_end/third_party/lighthouse/locales/no.json +290 -65
  97. package/front_end/third_party/lighthouse/locales/pl.json +290 -65
  98. package/front_end/third_party/lighthouse/locales/pt-PT.json +291 -66
  99. package/front_end/third_party/lighthouse/locales/pt.json +290 -65
  100. package/front_end/third_party/lighthouse/locales/ro.json +290 -65
  101. package/front_end/third_party/lighthouse/locales/ru.json +301 -76
  102. package/front_end/third_party/lighthouse/locales/sk.json +291 -66
  103. package/front_end/third_party/lighthouse/locales/sl.json +290 -65
  104. package/front_end/third_party/lighthouse/locales/sr-Latn.json +290 -65
  105. package/front_end/third_party/lighthouse/locales/sr.json +290 -65
  106. package/front_end/third_party/lighthouse/locales/sv.json +297 -72
  107. package/front_end/third_party/lighthouse/locales/ta.json +291 -66
  108. package/front_end/third_party/lighthouse/locales/te.json +293 -68
  109. package/front_end/third_party/lighthouse/locales/th.json +291 -66
  110. package/front_end/third_party/lighthouse/locales/tr.json +290 -65
  111. package/front_end/third_party/lighthouse/locales/uk.json +290 -65
  112. package/front_end/third_party/lighthouse/locales/vi.json +291 -66
  113. package/front_end/third_party/lighthouse/locales/zh-HK.json +292 -67
  114. package/front_end/third_party/lighthouse/locales/zh-TW.json +291 -66
  115. package/front_end/third_party/lighthouse/locales/zh.json +291 -66
  116. package/front_end/third_party/lighthouse/report/bundle.d.ts +6 -6
  117. package/front_end/third_party/lighthouse/report/bundle.js +4 -7
  118. package/front_end/third_party/lighthouse/report-assets/report-generator.mjs +2 -2
  119. package/front_end/ui/legacy/StackedPane.ts +229 -0
  120. package/front_end/ui/legacy/ViewManager.ts +59 -169
  121. package/front_end/ui/legacy/Widget.ts +32 -8
  122. package/front_end/ui/legacy/legacy.ts +3 -1
  123. package/front_end/ui/visual_logging/KnownContextValues.ts +2 -0
  124. package/mcp/mcp.ts +1 -0
  125. package/package.json +1 -1
@@ -211,10 +211,11 @@ export class StackTraceModel extends SDK.SDKModel.SDKModel<unknown> {
211
211
  let i = 0;
212
212
  let evalI = 0;
213
213
  for (const node of fragment.node.getCallStack()) {
214
- node.frames = uiFrames[i++].map(
215
- frame => new FrameImpl(
216
- frame.url, frame.uiSourceCode, frame.name, frame.line, frame.column, frame.missingDebugInfo,
217
- node.rawFrame.functionName, node.rawFrame.isWasm));
214
+ const group = uiFrames[i++];
215
+ node.frames =
216
+ group.map((frame, index) => new FrameImpl(frame.url, frame.uiSourceCode, frame.name, frame.line, frame.column,
217
+ frame.missingDebugInfo, node.rawFrame.functionName,
218
+ node.rawFrame.isWasm, index < group.length - 1));
218
219
 
219
220
  if (node.parsedFrameInfo?.evalOrigin) {
220
221
  node.evalOrigin = evalOrigins[evalI++];
@@ -253,10 +254,10 @@ async function translateEvalOrigin(
253
254
  rawFrame: RawFrame, rawFramesToUIFrames: TranslateRawFrames,
254
255
  target: SDK.Target.Target): Promise<EvalOrigin|undefined> {
255
256
  const uiFrames = await rawFramesToUIFrames([rawFrame], target);
256
- const frames = uiFrames[0].map(
257
- frame => new FrameImpl(
258
- frame.url, frame.uiSourceCode, frame.name, frame.line, frame.column, frame.missingDebugInfo,
259
- rawFrame.functionName, rawFrame.isWasm));
257
+ const group = uiFrames[0];
258
+ const frames = group.map((frame, index) => new FrameImpl(frame.url, frame.uiSourceCode, frame.name, frame.line,
259
+ frame.column, frame.missingDebugInfo, rawFrame.functionName,
260
+ rawFrame.isWasm, index < group.length - 1));
260
261
 
261
262
  let parentEvalOrigin: EvalOrigin|undefined;
262
263
  if (rawFrame.parsedFrameInfo?.evalOrigin) {
@@ -33,7 +33,7 @@ export class AccessibilitySidebarView extends UI.Widget.VBox {
33
33
  #node: SDK.DOMModel.DOMNode|null;
34
34
  #axNode: SDK.AccessibilityModel.AccessibilityNode|null;
35
35
  private skipNextPullNode: boolean;
36
- private readonly sidebarPaneStack: UI.View.ViewLocation;
36
+ private readonly sidebarPaneStack: UI.ViewManager.StackLocation;
37
37
  private readonly ariaSubPane: ARIAAttributesPane;
38
38
  private readonly axNodeSubPane: AXNodeSubPane;
39
39
  private readonly sourceOrderSubPane: SourceOrderPane;
@@ -159,6 +159,7 @@ export class AccessibilitySidebarView extends UI.Widget.VBox {
159
159
 
160
160
  private updateToggle(): void {
161
161
  const isToggled = this.toggleAction.toggled();
162
+ this.sidebarPaneStack.notifyVisibilityChanged(isToggled);
162
163
  // eslint-disable-next-line @devtools/no-lit-render-outside-of-view
163
164
  render(
164
165
  html`
@@ -657,7 +657,7 @@ export class AiAssistancePanel extends UI.Panel.Panel {
657
657
  // NodeJS debugging does not have Elements panel, thus this action might not exist.
658
658
  #toggleSearchElementAction?: UI.ActionRegistration.Action;
659
659
  #aidaClient: Host.AidaClient.AidaClient;
660
- #conversationSummaryAgent?: AiAssistanceModel.ConversationSummaryAgent.ConversationSummaryAgent;
660
+ #conversationSummary?: AiAssistanceModel.ConversationSummary.ConversationSummary;
661
661
  #viewOutput: PanelViewOutput = {};
662
662
  #serverSideLoggingEnabled = isAiAssistanceServerSideLoggingEnabled();
663
663
  #aiAssistanceEnabledSetting: Common.Settings.Setting<boolean>|undefined;
@@ -784,17 +784,16 @@ export class AiAssistancePanel extends UI.Panel.Panel {
784
784
  markdownRenderer,
785
785
  conversationMarkdown: this.#conversation.getConversationMarkdown(),
786
786
  generateConversationSummary: async (markdown: string) => {
787
- if (!this.#conversationSummaryAgent) {
788
- this.#conversationSummaryAgent = new AiAssistanceModel.ConversationSummaryAgent.ConversationSummaryAgent({
787
+ if (!this.#conversationSummary) {
788
+ this.#conversationSummary = new AiAssistanceModel.ConversationSummary.ConversationSummary({
789
789
  aidaClient: this.#aidaClient,
790
790
  serverSideLoggingEnabled: this.#serverSideLoggingEnabled,
791
791
  });
792
792
  }
793
- return await this.#conversationSummaryAgent.summarizeConversation(markdown);
793
+ return await this.#conversationSummary.summarizeConversation(markdown);
794
794
  },
795
- onTextSubmit: async (
796
- text: string, imageInput?: Host.AidaClient.Part,
797
- multimodalInputType?: AiAssistanceModel.AiAgent.MultimodalInputType) => {
795
+ onTextSubmit: async (text: string, imageInput?: Host.AidaClient.Part,
796
+ multimodalInputType?: AiAssistanceModel.AiAgent.MultimodalInputType) => {
798
797
  const submit = (): void => {
799
798
  Host.userMetrics.actionTaken(Host.UserMetrics.Action.AiAssistanceQuerySubmitted);
800
799
  void this.#startConversation(text, imageInput, multimodalInputType);
@@ -2116,7 +2115,8 @@ export class ActionDelegate implements UI.ActionRegistration.ActionDelegate {
2116
2115
  case 'drjones.network-panel-context':
2117
2116
  case 'drjones.performance-panel-context':
2118
2117
  case 'drjones.sources-floating-button':
2119
- case 'drjones.sources-panel-context': {
2118
+ case 'drjones.sources-panel-context':
2119
+ case 'ai-assistance.storage-floating-button': {
2120
2120
  void (async () => {
2121
2121
  const view = UI.ViewManager.ViewManager.instance().view(
2122
2122
  AiAssistancePanel.panelName,
@@ -14,9 +14,9 @@ import * as SDK from '../../../core/sdk/sdk.js';
14
14
  import type * as Protocol from '../../../generated/protocol.js';
15
15
  import type {
16
16
  AiWidget, BottomUpTreeAiWidget, ComputedStyleAiWidget, CoreVitalsAiWidget, DomTreeAiWidget, LighthouseReportAiWidget,
17
- NetworkRequestGeneralHeadersAiWidget, PerfInsightAiWidget, PerformanceTraceAiWidget, SourceCodeAiWidget,
18
- SourceFileAiWidget, SourceFilesListAiWidget, StylePropertiesAiWidget, TimelineEventSummaryAiWidget,
19
- TimelineRangeSummaryAiWidget} from '../../../models/ai_assistance/agents/AiAgent.js';
17
+ NetworkRequestGeneralHeadersAiWidget, NetworkRequestsListAiWidget, PerfInsightAiWidget, PerformanceTraceAiWidget,
18
+ SourceCodeAiWidget, SourceFileAiWidget, SourceFilesListAiWidget, StylePropertiesAiWidget,
19
+ TimelineEventSummaryAiWidget, TimelineRangeSummaryAiWidget} from '../../../models/ai_assistance/agents/AiAgent.js';
20
20
  import * as AiAssistanceModel from '../../../models/ai_assistance/ai_assistance.js';
21
21
  import * as ComputedStyle from '../../../models/computed_style/computed_style.js';
22
22
  import * as Formatter from '../../../models/formatter/formatter.js';
@@ -30,6 +30,7 @@ import * as Buttons from '../../../ui/components/buttons/buttons.js';
30
30
  import * as Input from '../../../ui/components/input/input.js';
31
31
  import type * as MarkdownView from '../../../ui/components/markdown_view/markdown_view.js';
32
32
  import type {MarkdownLitRenderer} from '../../../ui/components/markdown_view/MarkdownView.js';
33
+ import * as Snackbars from '../../../ui/components/snackbars/snackbars.js';
33
34
  import * as UIHelpers from '../../../ui/helpers/helpers.js';
34
35
  import * as UI from '../../../ui/legacy/legacy.js';
35
36
  import * as Lit from '../../../ui/lit/lit.js';
@@ -414,6 +415,14 @@ const UIStringsNotTranslate = {
414
415
  * @description Title for the character set declaration widget.
415
416
  */
416
417
  characterSet: 'Character set declaration',
418
+ /**
419
+ * @description Title for the network requests list widget.
420
+ */
421
+ networkRequests: 'Network requests',
422
+ /**
423
+ * @description Accessible label for the reveal button in the network requests list widget.
424
+ */
425
+ revealFirstNetworkRequest: 'Reveal first network request in Network panel',
417
426
  /**
418
427
  * @description Title for the source files list widget.
419
428
  */
@@ -1300,7 +1309,12 @@ function renderWidgetResponse(response: WidgetMakerResponse|null): Lit.LitTempla
1300
1309
  if (response === null) {
1301
1310
  return;
1302
1311
  }
1303
- void Common.Revealer.reveal(response?.revealable);
1312
+ Common.Revealer.reveal(response?.revealable).catch((error: Error) => {
1313
+ if (!error.message) {
1314
+ return;
1315
+ }
1316
+ Snackbars.Snackbar.Snackbar.show({message: error.message});
1317
+ });
1304
1318
  }
1305
1319
 
1306
1320
  const classes = Lit.Directives.classMap({
@@ -1466,6 +1480,79 @@ async function makeSourceFilesListWidget(widgetData: SourceFilesListAiWidget): P
1466
1480
  };
1467
1481
  }
1468
1482
 
1483
+ const expandedNetworkRequestsWidgets = new WeakSet<NetworkRequestsListAiWidget>();
1484
+
1485
+ // A widget with a table of the list of network requests sent to the agent.
1486
+ // Only show 15 requests maximum in collapsed version. The rest of the requests
1487
+ // will be hidden unless the user clicks "Show all".
1488
+ async function makeNetworkRequestsListWidget(widgetData: NetworkRequestsListAiWidget):
1489
+ Promise<WidgetMakerResponse|null> {
1490
+ const requests = widgetData.data.requests;
1491
+ if (requests.length === 0) {
1492
+ return null;
1493
+ }
1494
+
1495
+ const isExpanded = expandedNetworkRequestsWidgets.has(widgetData);
1496
+ // We only want just expanded widget to be expanded, if the user closed and reopened the walkthrought, the widget should be collapsed again.
1497
+ // Therefore, after rendering the widget, we remove the widget from the set of expanded widgets so that it is collapsed on next render.
1498
+ if (isExpanded) {
1499
+ expandedNetworkRequestsWidgets.delete(widgetData);
1500
+ }
1501
+ const displayedRequests = isExpanded ? requests : requests.slice(0, 15);
1502
+
1503
+ // The table contains same fields as the ones sent to the agent.
1504
+ // clang-format off
1505
+ const renderedWidget = html`
1506
+ <div class="network-requests-widget">
1507
+ <devtools-data-grid striped inline>
1508
+ <table>
1509
+ <tr>
1510
+ <th id="name" weight="4">${i18n.i18n.lockedString('Name')}</th>
1511
+ <th id="status" weight="1">${i18n.i18n.lockedString('Status')}</th>
1512
+ <th id="size" weight="1">${i18n.i18n.lockedString('Size')}</th>
1513
+ <th id="time" weight="1">${i18n.i18n.lockedString('Time')}</th>
1514
+ </tr>
1515
+ ${displayedRequests.map(request => html`
1516
+ <tr>
1517
+ <td>${request.name()}</td>
1518
+ <td>${request.statusCode}</td>
1519
+ <td>${i18n.ByteUtilities.formatBytesToKb(request.transferSize)}</td>
1520
+ <td>${i18n.TimeUtilities.secondsToString(request.duration)}</td>
1521
+ </tr>
1522
+ `)}
1523
+ </table>
1524
+ </devtools-data-grid>
1525
+ ${!isExpanded && requests.length > 15 ? html`
1526
+ <div class="show-all-container">
1527
+ <button class="show-all-widget-requests-button text-button"
1528
+ jslog=${VisualLogging.action('show-all-widget-requests-button').track({click: true})}
1529
+ @click=${(e: Event) => {
1530
+ expandedNetworkRequestsWidgets.add(widgetData);
1531
+ const widgetEl = (e.target as HTMLElement).closest('.widget');
1532
+ if (widgetEl) {
1533
+ const widget = UI.Widget.Widget.get(widgetEl) as ChatMessage;
1534
+ if (widget && widget.performUpdate) {
1535
+ void widget.performUpdate();
1536
+ }
1537
+ }
1538
+ }}>
1539
+ ${i18n.i18n.lockedString(`Show all ${requests.length} network requests`)}
1540
+ </button>
1541
+ </div>
1542
+ ` : Lit.nothing}
1543
+ </div>
1544
+ `;
1545
+ // clang-format on
1546
+
1547
+ return {
1548
+ renderedWidget,
1549
+ title: lockedString(UIStringsNotTranslate.networkRequests),
1550
+ revealable: requests[0],
1551
+ accessibleRevealLabel: lockedString(UIStringsNotTranslate.revealFirstNetworkRequest),
1552
+ jslogContext: 'network-requests-list-widget',
1553
+ };
1554
+ }
1555
+
1469
1556
  function renderNetworkRequestPreview(networkRequest: NonNullable<DomTreeAiWidget['data']['networkRequest']>):
1470
1557
  Lit.TemplateResult {
1471
1558
  const filename = networkRequest.url.split('/').pop() || networkRequest.url;
@@ -1583,6 +1670,8 @@ export function getWidgetSignature(widget: AiWidget): string {
1583
1670
  return `${widget.name}:${widget.data.request.requestId()}`;
1584
1671
  case 'SOURCE_CODE':
1585
1672
  return `${widget.name}:${widget.data.url}:${widget.data.line ?? ''}:${widget.data.column ?? ''}`;
1673
+ case 'NETWORK_REQUESTS_LIST':
1674
+ return `${widget.name}:${widget.data.requests.map(r => r.requestId()).join(',')}`;
1586
1675
  default:
1587
1676
  Platform.assertNever(widget, 'Unknown AiWidget name');
1588
1677
  }
@@ -1672,6 +1761,9 @@ async function renderWidgets(
1672
1761
  case 'SOURCE_FILES_LIST':
1673
1762
  response = await makeSourceFilesListWidget(widgetData);
1674
1763
  break;
1764
+ case 'NETWORK_REQUESTS_LIST':
1765
+ response = await makeNetworkRequestsListWidget(widgetData);
1766
+ break;
1675
1767
  case 'LIGHTHOUSE_REPORT':
1676
1768
  response = await makeLighthouseReportWidget(widgetData);
1677
1769
  break;
@@ -624,4 +624,10 @@
624
624
  display: none;
625
625
  }
626
626
  }
627
+
628
+ .network-requests-widget {
629
+ display: flex;
630
+ flex-direction: column;
631
+ gap: var(--sys-size-4);
632
+ }
627
633
  }
@@ -40,6 +40,7 @@ import * as Platform from '../../core/platform/platform.js';
40
40
  import * as Root from '../../core/root/root.js';
41
41
  import * as SDK from '../../core/sdk/sdk.js';
42
42
  import * as Protocol from '../../generated/protocol.js';
43
+ import * as AiAssistance from '../../models/ai_assistance/ai_assistance.js';
43
44
  import * as LegacyWrapper from '../../ui/components/legacy_wrapper/legacy_wrapper.js';
44
45
  import {createIcon} from '../../ui/kit/kit.js';
45
46
  import * as SourceFrame from '../../ui/legacy/components/source_frame/source_frame.js';
@@ -1762,9 +1763,29 @@ export class DOMStorageTreeElement extends ApplicationPanelTreeElement {
1762
1763
  return false;
1763
1764
  }
1764
1765
 
1766
+ /**
1767
+ * Resolves the DOM storage partition context (`localStorage` or `sessionStorage`)
1768
+ * associated with this tree element for AI assistance.
1769
+ */
1770
+ #getStorageItem(): AiAssistance.StorageItem.StorageItem|null {
1771
+ const target = SDK.TargetManager.TargetManager.instance().primaryPageTarget();
1772
+ const mainPageOrigin =
1773
+ target?.inspectedURL() ? Common.ParsedURL.ParsedURL.extractOrigin(target.inspectedURL()) : '';
1774
+ if (!mainPageOrigin || !this.domStorage.storageKey) {
1775
+ return null;
1776
+ }
1777
+ const origin = SDK.StorageKeyManager.parseStorageKey(this.domStorage.storageKey).origin;
1778
+ const storageType = this.domStorage.isLocalStorage ? 'localStorage' : 'sessionStorage';
1779
+ return new AiAssistance.StorageItem.DOMStorageItem(mainPageOrigin, origin, this.domStorage.storageKey, storageType);
1780
+ }
1781
+
1765
1782
  override onattach(): void {
1766
1783
  super.onattach();
1767
1784
  this.listItemElement.addEventListener('contextmenu', this.handleContextMenuEvent.bind(this), true);
1785
+ const storageItem = this.#getStorageItem();
1786
+ if (storageItem) {
1787
+ this.createAiButton(storageItem);
1788
+ }
1768
1789
  }
1769
1790
 
1770
1791
  private handleContextMenuEvent(event: MouseEvent): void {
@@ -1851,9 +1872,27 @@ export class CookieTreeElement extends ApplicationPanelTreeElement {
1851
1872
  return this.#cookieDomain;
1852
1873
  }
1853
1874
 
1875
+ /**
1876
+ * Resolves the cookie domain security context associated with this tree element
1877
+ * for AI assistance.
1878
+ */
1879
+ #getStorageItem(): AiAssistance.StorageItem.StorageItem|null {
1880
+ const primaryTarget = SDK.TargetManager.TargetManager.instance().primaryPageTarget();
1881
+ const mainPageOrigin =
1882
+ primaryTarget?.inspectedURL() ? Common.ParsedURL.ParsedURL.extractOrigin(primaryTarget.inspectedURL()) : '';
1883
+ if (!mainPageOrigin || !this.#cookieDomain) {
1884
+ return null;
1885
+ }
1886
+ return new AiAssistance.StorageItem.CookieItem(mainPageOrigin, this.#cookieDomain);
1887
+ }
1888
+
1854
1889
  override onattach(): void {
1855
1890
  super.onattach();
1856
1891
  this.listItemElement.addEventListener('contextmenu', this.handleContextMenuEvent.bind(this), true);
1892
+ const storageItem = this.#getStorageItem();
1893
+ if (storageItem) {
1894
+ this.createAiButton(storageItem);
1895
+ }
1857
1896
  }
1858
1897
 
1859
1898
  private handleContextMenuEvent(event: Event): void {
@@ -2,15 +2,24 @@
2
2
  // Use of this source code is governed by a BSD-style license that can be
3
3
  // found in the LICENSE file.
4
4
 
5
+ /* eslint-disable @devtools/no-lit-render-outside-of-view */
6
+
7
+ import '../../ui/components/buttons/buttons.js';
8
+
5
9
  import * as Common from '../../core/common/common.js';
6
10
  import type * as Platform from '../../core/platform/platform.js';
11
+ import * as AiAssistance from '../../models/ai_assistance/ai_assistance.js';
7
12
  import * as UI from '../../ui/legacy/legacy.js';
13
+ import * as Lit from '../../ui/lit/lit.js';
8
14
 
9
15
  import type {ResourcesPanel} from './ResourcesPanel.js';
10
16
 
17
+ const {html} = Lit;
18
+
11
19
  export class ApplicationPanelTreeElement extends UI.TreeOutline.TreeElement {
12
20
  protected readonly resourcesPanel: ResourcesPanel;
13
21
  private customItemURL?: Platform.DevToolsPath.UrlString;
22
+ protected aiButtonContainer?: HTMLElement;
14
23
 
15
24
  constructor(resourcesPanel: ResourcesPanel, title: string, expandable: boolean, jslogContext: string) {
16
25
  super(title, expandable, jslogContext);
@@ -56,6 +65,36 @@ export class ApplicationPanelTreeElement extends UI.TreeOutline.TreeElement {
56
65
  showView(view: UI.Widget.AnyWidget|null): void {
57
66
  this.resourcesPanel.showView(view);
58
67
  }
68
+
69
+ protected createAiButton(storageItem: AiAssistance.StorageItem.StorageItem): void {
70
+ const STORAGE_FLOATING_BUTTON_ACTION_ID = 'ai-assistance.storage-floating-button';
71
+ const actionRegistry = UI.ActionRegistry.ActionRegistry.instance();
72
+ if (!actionRegistry.hasAction(STORAGE_FLOATING_BUTTON_ACTION_ID)) {
73
+ return;
74
+ }
75
+ const action = actionRegistry.getAction(STORAGE_FLOATING_BUTTON_ACTION_ID);
76
+ if (!this.aiButtonContainer) {
77
+ this.aiButtonContainer = this.listItemElement.createChild('span', 'ai-button-container');
78
+ const icon = AiAssistance.AiUtils.getIconName();
79
+ const onClick = (ev: Event): void => {
80
+ ev.stopPropagation();
81
+ UI.Context.Context.instance().setFlavor(AiAssistance.StorageItem.StorageItem, storageItem);
82
+ void action.execute();
83
+ };
84
+
85
+ // clang-format off
86
+ Lit.render(html`
87
+ <devtools-floating-button
88
+ icon-name=${icon}
89
+ title=${action.title()}
90
+ jslogcontext="ask-ai"
91
+ @click=${onClick}
92
+ @mousedown=${(ev: Event) => ev.stopPropagation()}>
93
+ </devtools-floating-button>
94
+ `, this.aiButtonContainer);
95
+ // clang-format on
96
+ }
97
+ }
59
98
  }
60
99
 
61
100
  export class ExpandableApplicationPanelTreeElement extends ApplicationPanelTreeElement {
@@ -317,7 +317,7 @@ export class CookieItemsView extends UI.Widget.VBox {
317
317
  }
318
318
 
319
319
  private updateAiAssistanceContext(cookie: SDK.Cookie.Cookie|null): void {
320
- if (!cookie || cookie.httpOnly()) {
320
+ if (cookie && cookie.httpOnly()) {
321
321
  UI.Context.Context.instance().setFlavor(AiAssistanceModel.StorageItem.StorageItem, null);
322
322
  return;
323
323
  }
@@ -332,7 +332,7 @@ export class CookieItemsView extends UI.Widget.VBox {
332
332
  return;
333
333
  }
334
334
 
335
- const storageItem = new AiAssistanceModel.StorageItem.CookieItem(mainPageOrigin, this.cookieDomain, cookie.name());
335
+ const storageItem = new AiAssistanceModel.StorageItem.CookieItem(mainPageOrigin, this.cookieDomain, cookie?.name());
336
336
  UI.Context.Context.instance().setFlavor(AiAssistanceModel.StorageItem.StorageItem, storageItem);
337
337
  }
338
338
 
@@ -0,0 +1,219 @@
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 i18n from '../../../core/i18n/i18n.js';
6
+ import * as SDK from '../../../core/sdk/sdk.js';
7
+ import type * as Protocol from '../../../generated/protocol.js';
8
+ import * as UI from '../../../ui/legacy/legacy.js';
9
+ import * as Lit from '../../../ui/lit/lit.js';
10
+
11
+ import adsViewStyles from './adsView.css.js';
12
+
13
+ const {html} = Lit;
14
+
15
+ const UIStrings = {
16
+ /**
17
+ * @description Title for a metric showing the percentage of the viewport covered by ads.
18
+ */
19
+ viewportAdDensity: 'Viewport ad density',
20
+ /**
21
+ * @description Title for a metric showing the number of ads in the viewport.
22
+ */
23
+ viewportAdCount: 'Viewport ad count',
24
+ /**
25
+ * @description Title for a metric showing the total CPU usage by ads.
26
+ */
27
+ totalCpuUsage: 'Total CPU usage by ads',
28
+ /**
29
+ * @description Title for a metric showing the total network usage by ads.
30
+ */
31
+ totalNetworkUsage: 'Total network usage by ads',
32
+ /**
33
+ * @description Subtext showing the average value of a metric.
34
+ * @example {5.00%} PH1
35
+ */
36
+ average: '(Average: {PH1})',
37
+ } as const;
38
+
39
+ const str_ = i18n.i18n.registerUIStrings('panels/application/components/AdsView.ts', UIStrings);
40
+ const i18nString = i18n.i18n.getLocalizedString.bind(undefined, str_);
41
+
42
+ export interface ViewInput {
43
+ metrics: Protocol.Ads.AdMetrics;
44
+ }
45
+
46
+ export type View = (input: ViewInput, output: undefined, target: HTMLElement|DocumentFragment) => void;
47
+
48
+ const DEFAULT_VIEW: View = (input, output, target) => {
49
+ const metrics = input.metrics;
50
+
51
+ const formatValue = (val: number, isPercentage: boolean): string => {
52
+ if (isPercentage) {
53
+ return new Intl
54
+ .NumberFormat(i18n.DevToolsLocale.DevToolsLocale.instance().locale, {
55
+ style: 'percent',
56
+ maximumFractionDigits: 0,
57
+ })
58
+ .format(val / 100);
59
+ }
60
+ return new Intl.NumberFormat(i18n.DevToolsLocale.DevToolsLocale.instance().locale).format(val);
61
+ };
62
+
63
+ const formatAverage = (val: number, isPercentage: boolean): string => {
64
+ if (isPercentage) {
65
+ return new Intl
66
+ .NumberFormat(i18n.DevToolsLocale.DevToolsLocale.instance().locale, {
67
+ style: 'percent',
68
+ minimumFractionDigits: 2,
69
+ maximumFractionDigits: 2,
70
+ })
71
+ .format(val / 100);
72
+ }
73
+ return new Intl
74
+ .NumberFormat(i18n.DevToolsLocale.DevToolsLocale.instance().locale, {
75
+ minimumFractionDigits: 2,
76
+ maximumFractionDigits: 2,
77
+ })
78
+ .format(val);
79
+ };
80
+
81
+ const formatCpu = (val: number): string => {
82
+ return i18n.TimeUtilities.preciseMillisToString(val, 1);
83
+ };
84
+
85
+ const formatNetwork = (val: number): string => {
86
+ return i18n.ByteUtilities.bytesToString(val);
87
+ };
88
+
89
+ // clang-format off
90
+ Lit.render(html`
91
+ <style>${adsViewStyles}</style>
92
+ <dl class="metrics-container">
93
+ <div class="metric-box">
94
+ <dt class="metric-title">${i18nString(UIStrings.viewportAdDensity)}</dt>
95
+ <dd class="metric-value">
96
+ <span>${formatValue(metrics.viewportAdDensityByArea, true)}</span>
97
+ <span class="metric-average">${i18nString(UIStrings.average, {PH1: formatAverage(metrics.averageViewportAdDensityByArea, true)})}</span>
98
+ </dd>
99
+ </div>
100
+ <div class="metric-box">
101
+ <dt class="metric-title">${i18nString(UIStrings.viewportAdCount)}</dt>
102
+ <dd class="metric-value">
103
+ <span>${formatValue(metrics.viewportAdCount, false)}</span>
104
+ <span class="metric-average">${i18nString(UIStrings.average, {PH1: formatAverage(metrics.averageViewportAdCount, false)})}</span>
105
+ </dd>
106
+ </div>
107
+ <div class="metric-box">
108
+ <dt class="metric-title">${i18nString(UIStrings.totalCpuUsage)}</dt>
109
+ <dd class="metric-value">
110
+ <span>${formatCpu(metrics.totalAdCpuTime)}</span>
111
+ </dd>
112
+ </div>
113
+ <div class="metric-box">
114
+ <dt class="metric-title">${i18nString(UIStrings.totalNetworkUsage)}</dt>
115
+ <dd class="metric-value">
116
+ <span>${formatNetwork(metrics.totalAdNetworkBytes)}</span>
117
+ </dd>
118
+ </div>
119
+ </dl>
120
+ `, target);
121
+ // clang-format on
122
+ };
123
+
124
+ export class AdsView extends UI.Widget.Widget {
125
+ #currentMetrics: Protocol.Ads.AdMetrics;
126
+ #pollTimer?: number;
127
+ #isPolling = false;
128
+ #pollSessionId = 0;
129
+ #view: View;
130
+
131
+ constructor(view: View = DEFAULT_VIEW) {
132
+ super({useShadowDom: true});
133
+ this.#view = view;
134
+ this.#currentMetrics = {
135
+ viewportAdDensityByArea: 0,
136
+ averageViewportAdDensityByArea: 0,
137
+ viewportAdCount: 0,
138
+ averageViewportAdCount: 0,
139
+ totalAdCpuTime: 0,
140
+ totalAdNetworkBytes: 0,
141
+ };
142
+ this.requestUpdate();
143
+ }
144
+
145
+ override wasShown(): void {
146
+ super.wasShown();
147
+ this.#startPolling();
148
+ SDK.TargetManager.TargetManager.instance().addModelListener(SDK.ResourceTreeModel.ResourceTreeModel,
149
+ SDK.ResourceTreeModel.Events.PrimaryPageChanged,
150
+ this.#onPrimaryPageChanged, this);
151
+ }
152
+
153
+ override willHide(): void {
154
+ this.#stopPolling();
155
+ SDK.TargetManager.TargetManager.instance().removeModelListener(SDK.ResourceTreeModel.ResourceTreeModel,
156
+ SDK.ResourceTreeModel.Events.PrimaryPageChanged,
157
+ this.#onPrimaryPageChanged, this);
158
+ super.willHide();
159
+ }
160
+
161
+ #startPolling(): void {
162
+ if (this.#isPolling) {
163
+ return;
164
+ }
165
+ this.#isPolling = true;
166
+ this.#pollSessionId++;
167
+ void this.#pollMetrics(this.#pollSessionId);
168
+ }
169
+
170
+ #stopPolling(): void {
171
+ this.#isPolling = false;
172
+ if (this.#pollTimer !== undefined) {
173
+ window.clearTimeout(this.#pollTimer);
174
+ this.#pollTimer = undefined;
175
+ }
176
+ }
177
+
178
+ async #pollMetrics(sessionId: number): Promise<void> {
179
+ if (!this.#isPolling || this.#pollSessionId !== sessionId) {
180
+ return;
181
+ }
182
+ const target = SDK.TargetManager.TargetManager.instance().primaryPageTarget();
183
+ if (target) {
184
+ const adsAgent = target.adsAgent();
185
+ if (adsAgent) {
186
+ const response = await adsAgent.invoke_getAdMetrics();
187
+ if (!this.#isPolling || this.#pollSessionId !== sessionId) {
188
+ return;
189
+ }
190
+ if (!response.getError()) {
191
+ this.#currentMetrics = response.metrics;
192
+ this.requestUpdate();
193
+ }
194
+ }
195
+ }
196
+ if (this.#isPolling && this.#pollSessionId === sessionId) {
197
+ this.#pollTimer = window.setTimeout(() => this.#pollMetrics(sessionId), 500);
198
+ }
199
+ }
200
+
201
+ #onPrimaryPageChanged(): void {
202
+ this.#currentMetrics = {
203
+ viewportAdDensityByArea: 0,
204
+ averageViewportAdDensityByArea: 0,
205
+ viewportAdCount: 0,
206
+ averageViewportAdCount: 0,
207
+ totalAdCpuTime: 0,
208
+ totalAdNetworkBytes: 0,
209
+ };
210
+ this.requestUpdate();
211
+ }
212
+
213
+ override performUpdate(): void {
214
+ const viewInput: ViewInput = {
215
+ metrics: this.#currentMetrics,
216
+ };
217
+ this.#view(viewInput, undefined, this.contentElement);
218
+ }
219
+ }
@@ -0,0 +1,54 @@
1
+ /*
2
+ * Copyright 2026 The Chromium Authors
3
+ * Use of this source code is governed by a BSD-style license that can be
4
+ * found in the LICENSE file.
5
+ */
6
+
7
+ :host {
8
+ padding: 12px;
9
+ display: flex;
10
+ flex-direction: column;
11
+ overflow: auto;
12
+ }
13
+
14
+ .metrics-container {
15
+ flex: 0 0 auto;
16
+ margin: 0 0 24px;
17
+ border: 1px solid var(--sys-color-divider);
18
+ display: grid;
19
+ grid-template-columns: repeat(2, 1fr);
20
+ gap: 1px;
21
+ background-color: var(--sys-color-divider);
22
+ }
23
+
24
+ .metric-box {
25
+ background-color: var(--sys-color-surface);
26
+ padding: 12px;
27
+ display: flex;
28
+ flex-direction: column;
29
+ align-items: center;
30
+ justify-content: center;
31
+ }
32
+
33
+ .metric-title {
34
+ font-size: 12px;
35
+ color: var(--sys-color-on-surface-subtle);
36
+ margin: 0 0 4px;
37
+ }
38
+
39
+ .metric-value {
40
+ font-size: 18px;
41
+ font-weight: bold;
42
+ color: var(--sys-color-on-surface);
43
+ display: flex;
44
+ flex-direction: column;
45
+ align-items: center;
46
+ margin: 0;
47
+ gap: 2px;
48
+ }
49
+
50
+ .metric-average {
51
+ font-size: 12px;
52
+ font-weight: normal;
53
+ color: var(--sys-color-on-surface-subtle);
54
+ }