chrome-devtools-frontend 1.0.1603822 → 1.0.1605219

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 (89) hide show
  1. package/AUTHORS +1 -0
  2. package/front_end/Tests.js +23 -7
  3. package/front_end/core/host/AidaClient.ts +153 -525
  4. package/front_end/core/host/AidaClientTypes.ts +470 -0
  5. package/front_end/core/host/AidaGcaTranslation.ts +231 -122
  6. package/front_end/core/host/GcaClient.ts +112 -0
  7. package/front_end/core/host/GcaTypes.ts +107 -155
  8. package/front_end/core/host/UserMetrics.ts +0 -1
  9. package/front_end/core/host/host.ts +2 -0
  10. package/front_end/core/protocol_client/DevToolsCDPConnection.ts +1 -1
  11. package/front_end/core/root/ExperimentNames.ts +0 -1
  12. package/front_end/core/root/Runtime.ts +5 -0
  13. package/front_end/core/sdk/AutofillModel.ts +3 -2
  14. package/front_end/core/sdk/CSSModel.ts +3 -4
  15. package/front_end/core/sdk/CSSProperty.ts +1 -1
  16. package/front_end/core/sdk/ConsoleModel.ts +15 -13
  17. package/front_end/core/sdk/CookieModel.ts +5 -4
  18. package/front_end/core/sdk/DOMDebuggerModel.ts +20 -14
  19. package/front_end/core/sdk/DOMModel.ts +24 -22
  20. package/front_end/core/sdk/DebuggerModel.ts +21 -36
  21. package/front_end/core/sdk/EmulationModel.ts +21 -23
  22. package/front_end/core/sdk/EventBreakpointsModel.ts +16 -6
  23. package/front_end/core/sdk/HeapProfilerModel.ts +4 -7
  24. package/front_end/core/sdk/IsolateManager.ts +12 -7
  25. package/front_end/core/sdk/NetworkManager.ts +35 -28
  26. package/front_end/core/sdk/OverlayModel.ts +10 -10
  27. package/front_end/core/sdk/PreloadingModel.ts +3 -4
  28. package/front_end/core/sdk/ServiceWorkerManager.ts +2 -3
  29. package/front_end/core/sdk/SourceMapScopesInfo.ts +93 -63
  30. package/front_end/entrypoints/formatter_worker/FormatterWorker.ts +1 -3
  31. package/front_end/entrypoints/heap_snapshot_worker/AllocationProfile.ts +22 -48
  32. package/front_end/entrypoints/heap_snapshot_worker/HeapSnapshot.ts +1 -1
  33. package/front_end/entrypoints/main/MainImpl.ts +0 -4
  34. package/front_end/generated/InspectorBackendCommands.ts +4 -0
  35. package/front_end/generated/SupportedCSSProperties.js +68 -2
  36. package/front_end/generated/protocol-mapping.d.ts +7 -0
  37. package/front_end/generated/protocol-proxy-api.d.ts +14 -0
  38. package/front_end/generated/protocol.ts +22 -0
  39. package/front_end/models/ai_assistance/agents/PerformanceAgent.ts +31 -17
  40. package/front_end/models/ai_assistance/data_formatters/PerformanceInsightFormatter.snapshot.txt +1 -1
  41. package/front_end/models/bindings/TempFile.ts +1 -4
  42. package/front_end/models/emulation/DeviceModeModel.ts +21 -26
  43. package/front_end/models/extensions/ExtensionAPI.ts +1 -1
  44. package/front_end/models/heap_snapshot_model/HeapSnapshotModel.ts +34 -47
  45. package/front_end/models/javascript_metadata/NativeFunctions.js +6 -6
  46. package/front_end/models/trace/insights/LCPDiscovery.ts +14 -6
  47. package/front_end/panels/ai_assistance/AiAssistancePanel.ts +85 -31
  48. package/front_end/panels/ai_assistance/components/ChatMessage.ts +50 -40
  49. package/front_end/panels/ai_assistance/components/ChatView.ts +3 -2
  50. package/front_end/panels/ai_assistance/components/WalkthroughView.ts +35 -7
  51. package/front_end/panels/ai_assistance/components/chatMessage.css +5 -1
  52. package/front_end/panels/application/ApplicationPanelSidebar.ts +6 -0
  53. package/front_end/panels/application/IndexedDBViews.ts +0 -9
  54. package/front_end/panels/application/WebMCPTreeElement.ts +35 -0
  55. package/front_end/panels/application/WebMCPView.ts +118 -0
  56. package/front_end/panels/application/application.ts +4 -0
  57. package/front_end/panels/application/webMCPView.css +65 -0
  58. package/front_end/panels/common/aiCodeCompletionSummaryToolbar.css +1 -1
  59. package/front_end/panels/console/consoleView.css +4 -0
  60. package/front_end/panels/elements/ElementsTreeElement.ts +91 -13
  61. package/front_end/panels/elements/StylesAiCodeCompletionProvider.ts +23 -2
  62. package/front_end/panels/elements/StylesSidebarPane.ts +85 -20
  63. package/front_end/panels/elements/elementsPanel.css +14 -0
  64. package/front_end/panels/elements/elementsTreeOutline.css +18 -0
  65. package/front_end/panels/network/NetworkLogViewColumns.ts +3 -3
  66. package/front_end/panels/profiler/HeapSnapshotDataGrids.ts +15 -19
  67. package/front_end/panels/profiler/HeapSnapshotGridNodes.ts +4 -2
  68. package/front_end/panels/profiler/HeapSnapshotProxy.ts +12 -16
  69. package/front_end/panels/profiler/HeapSnapshotView.ts +3 -10
  70. package/front_end/panels/profiler/ProfilesPanel.ts +5 -7
  71. package/front_end/panels/recorder/components/recordingView.css +0 -6
  72. package/front_end/panels/sensors/SensorsView.ts +28 -61
  73. package/front_end/panels/timeline/EventsTimelineTreeView.ts +2 -2
  74. package/front_end/panels/timeline/ThirdPartyTreeView.ts +35 -36
  75. package/front_end/panels/timeline/TimelineDetailsView.ts +47 -41
  76. package/front_end/panels/timeline/TimelinePanel.ts +1 -3
  77. package/front_end/panels/timeline/TimelineTreeView.ts +13 -13
  78. package/front_end/panels/timeline/components/LiveMetricsView.ts +852 -824
  79. package/front_end/panels/timeline/components/liveMetricsView.css +4 -0
  80. package/front_end/panels/timeline/thirdPartyTreeView.css +2 -2
  81. package/front_end/third_party/chromium/README.chromium +1 -1
  82. package/front_end/ui/components/buttons/button.css +2 -2
  83. package/front_end/ui/components/text_editor/AiCodeCompletionProvider.ts +0 -11
  84. package/front_end/ui/legacy/InspectorDrawerView.ts +206 -0
  85. package/front_end/ui/legacy/InspectorView.ts +41 -91
  86. package/front_end/ui/legacy/ViewManager.ts +14 -5
  87. package/front_end/ui/legacy/inspectorDrawerTabbedPane.css +5 -0
  88. package/front_end/ui/visual_logging/KnownContextValues.ts +2 -1
  89. package/package.json +1 -1
@@ -379,12 +379,6 @@ interface ToolbarViewInput {
379
379
  showChatActions: boolean;
380
380
  showActiveConversationActions: boolean;
381
381
  isLoading: boolean;
382
-
383
- walkthrough: {
384
- isExpanded: boolean,
385
- isInlined: boolean,
386
- onToggle: (isOpen: boolean) => void,
387
- };
388
382
  }
389
383
 
390
384
  export const enum ViewState {
@@ -512,7 +506,7 @@ function defaultView(input: ViewInput, output: PanelViewOutput, target: HTMLElem
512
506
  if (Root.Runtime.hostConfig.devToolsAiAssistanceV2?.enabled ||
513
507
  Greendev.Prototypes.instance().isEnabled('breakpointDebuggerAgent')) {
514
508
 
515
- const shouldShowWalkthrough = input.state === ViewState.CHAT_VIEW && input.walkthrough.isExpanded;
509
+ const shouldShowWalkthrough = input.state === ViewState.CHAT_VIEW && input.props.walkthrough.isExpanded;
516
510
  /**
517
511
  * We want to mark the walkthrough as loading only if it's showing the last
518
512
  * message. Otherwise, a previous walkthrough will show as loading if we
@@ -521,7 +515,7 @@ function defaultView(input: ViewInput, output: PanelViewOutput, target: HTMLElem
521
515
  let walkthroughIsForLastMessage = false;
522
516
  if(input.state === ViewState.CHAT_VIEW) {
523
517
  const lastMessage = input.props.messages.at(-1);
524
- if(lastMessage && input.props.walkthrough.activeMessage === lastMessage) {
518
+ if(lastMessage && input.props.walkthrough.activeSidebarMessage === lastMessage) {
525
519
  walkthroughIsForLastMessage = true;
526
520
  }
527
521
  }
@@ -533,7 +527,7 @@ function defaultView(input: ViewInput, output: PanelViewOutput, target: HTMLElem
533
527
  name="ai-assistance-split-view-state"
534
528
  direction="column"
535
529
  sidebar-position="second"
536
- sidebar-visibility=${shouldShowWalkthrough && !input.walkthrough.isInlined ? 'visible' : 'hidden'}
530
+ sidebar-visibility=${shouldShowWalkthrough && !input.props.walkthrough.isInlined ? 'visible' : 'hidden'}
537
531
  sidebar-initial-size=${WALKTHROUGH_SIDEBAR_INITIAL_WIDTH}
538
532
  >
539
533
  <div slot="main" class="main-view">
@@ -542,7 +536,7 @@ function defaultView(input: ViewInput, output: PanelViewOutput, target: HTMLElem
542
536
  <div slot="sidebar" class="sidebar-view">
543
537
  ${shouldShowWalkthrough ? html`
544
538
  <devtools-widget ${widget(WalkthroughView, {
545
- message: input.props.walkthrough.activeMessage,
539
+ message: input.props.walkthrough.activeSidebarMessage,
546
540
  isLoading: input.props.isLoading && walkthroughIsForLastMessage,
547
541
  markdownRenderer: input.props.markdownRenderer,
548
542
  onToggle: input.props.walkthrough.onToggle,
@@ -610,10 +604,15 @@ function createPerformanceTraceContext(focus: AiAssistanceModel.AIContext.AgentF
610
604
 
611
605
  /**
612
606
  * State relating to the visibility of the Walkthrough.
613
- * Note that we have to track the active message and the visibility as distinct
614
- * state, because you can toggle the walkthrough via the sidebar, in which case
615
- * we need to make it visible/hidden but keep track of the active message for
616
- * when the user expands it again.
607
+ *
608
+ * We track both an `activeSidebarMessage` and a list of `inlineExpandedMessages` because:
609
+ * 1. In Narrow (inline) mode, multiple walkthroughs can be expanded at once,
610
+ * so we need to track them all to render them correctly in the chat.
611
+ * 2. In Wide (sidebar) mode, only one walkthrough can be visible at a time.
612
+ * The `activeSidebarMessage` tracks which one is shown in the sidebar.
613
+ * 3. When transitioning from Narrow to Wide, we use the last message in
614
+ * `inlineExpandedMessages` to determine which one should stay expanded
615
+ * in the sidebar.
617
616
  */
618
617
  interface WalkthroughState {
619
618
  /**
@@ -625,11 +624,15 @@ interface WalkthroughState {
625
624
  */
626
625
  isExpanded: boolean;
627
626
  /**
628
- * The message that the walkthrough is showing all the steps for. A
629
- * conversation can have many Model messages (1 per each user prompt) so we
630
- * need to track which one we are showing for the walkthrough.
627
+ * The message that the walkthrough is showing all the steps for. In Wide mode,
628
+ * this is the message shown in the sidebar. In Narrow mode, it tracks the
629
+ * most recently interacted message.
630
+ */
631
+ activeSidebarMessage: ModelChatMessage|null;
632
+ /**
633
+ * Tracks which messages are expanded in inline mode.
631
634
  */
632
- activeMessage: ModelChatMessage|null;
635
+ inlineExpandedMessages: ModelChatMessage[];
633
636
  }
634
637
 
635
638
  let panelInstance: AiAssistancePanel;
@@ -667,7 +670,8 @@ export class AiAssistancePanel extends UI.Panel.Panel {
667
670
  #walkthrough: WalkthroughState = {
668
671
  isInlined: false,
669
672
  isExpanded: false,
670
- activeMessage: null,
673
+ activeSidebarMessage: null,
674
+ inlineExpandedMessages: [],
671
675
  };
672
676
 
673
677
  constructor(private view: View = defaultView, {aidaClient, aidaAvailability}: {
@@ -704,11 +708,6 @@ export class AiAssistancePanel extends UI.Panel.Panel {
704
708
  onSettingsClick: () => {
705
709
  void UI.ViewManager.ViewManager.instance().showView('chrome-ai');
706
710
  },
707
- walkthrough: {
708
- isExpanded: this.#walkthrough.isExpanded,
709
- isInlined: this.#walkthrough.isInlined,
710
- onToggle: this.#toggleWalkthrough.bind(this),
711
- }
712
711
  };
713
712
  }
714
713
 
@@ -796,7 +795,8 @@ export class AiAssistancePanel extends UI.Panel.Panel {
796
795
  onOpen: this.#openWalkthrough.bind(this),
797
796
  isExpanded: this.#walkthrough.isExpanded,
798
797
  isInlined: this.#walkthrough.isInlined,
799
- activeMessage: this.#walkthrough.activeMessage,
798
+ activeSidebarMessage: this.#walkthrough.activeSidebarMessage,
799
+ inlineExpandedMessages: this.#walkthrough.inlineExpandedMessages,
800
800
  },
801
801
  }
802
802
  };
@@ -820,20 +820,73 @@ export class AiAssistancePanel extends UI.Panel.Panel {
820
820
  if (isNarrow === this.#walkthrough.isInlined) {
821
821
  return;
822
822
  }
823
- // If the UI changed, we reset the visibility of the AI Walkthrough.
824
- this.#resetWalkthrough();
823
+
825
824
  this.#walkthrough.isInlined = isNarrow;
825
+
826
+ if (!this.#walkthrough.isExpanded) {
827
+ // If nothing was expanded, we just ensure the state is clean.
828
+ this.#walkthrough.activeSidebarMessage = null;
829
+ this.#walkthrough.inlineExpandedMessages = [];
830
+ this.requestUpdate();
831
+ return;
832
+ }
833
+
834
+ if (isNarrow) {
835
+ // Wide -> Inline: the walkthrough that was open stays expanded
836
+ this.#walkthrough.inlineExpandedMessages =
837
+ this.#walkthrough.activeSidebarMessage ? [this.#walkthrough.activeSidebarMessage] : [];
838
+ } else {
839
+ // Inline -> Wide: the last walkthrough that the user opened stays expanded
840
+ this.#walkthrough.activeSidebarMessage = this.#walkthrough.inlineExpandedMessages.at(-1) ?? null;
841
+ }
842
+
826
843
  this.requestUpdate();
827
844
  }
828
845
 
829
846
  #openWalkthrough(message: ModelChatMessage): void {
830
- this.#walkthrough.activeMessage = message;
847
+ if (!this.#walkthrough.inlineExpandedMessages.includes(message)) {
848
+ this.#walkthrough.inlineExpandedMessages.push(message);
849
+ }
850
+ this.#walkthrough.activeSidebarMessage = message;
831
851
  this.#walkthrough.isExpanded = true;
832
852
  this.requestUpdate();
833
853
  }
834
854
 
835
- #toggleWalkthrough(isOpen: boolean): void {
836
- this.#walkthrough.isExpanded = isOpen;
855
+ /**
856
+ * Toggles the expanded state of a walkthrough.
857
+ *
858
+ * In Wide (sidebar) mode:
859
+ * - Opening a message's walkthrough shows the sidebar for that message.
860
+ * - Closing the sidebar hides the walkthrough for the currently active message.
861
+ *
862
+ * In Narrow (inline) mode:
863
+ * - Any number of walkthroughs can be open at once.
864
+ * - Opening/closing a message's walkthrough only affects that message's inline display.
865
+ */
866
+ #toggleWalkthrough(isOpen: boolean, message: ModelChatMessage): void {
867
+ if (isOpen) { // If we are opening a walkthrough, ensure it's in our list of expanded messages.
868
+ this.#openWalkthrough(message);
869
+ return;
870
+ }
871
+
872
+ // If we are closing a walkthrough, remove it from the list of expanded messages.
873
+ this.#walkthrough.inlineExpandedMessages = this.#walkthrough.inlineExpandedMessages.filter(m => m !== message);
874
+
875
+ if (this.#walkthrough.isInlined) {
876
+ // In Narrow mode, the global expanded state tracks if at least one walkthrough is open.
877
+ this.#walkthrough.isExpanded = this.#walkthrough.inlineExpandedMessages.length > 0;
878
+ // If the message we just closed was the active one, we pick a new active message
879
+ // from the remaining open ones (if any). This ensures that if the user
880
+ // re-opens the sidebar later, it shows the most recently opened walkthrough.
881
+ if (this.#walkthrough.activeSidebarMessage === message) {
882
+ this.#walkthrough.activeSidebarMessage = this.#walkthrough.inlineExpandedMessages.at(-1) ?? null;
883
+ }
884
+ } else {
885
+ // In Wide mode, closing the sidebar means we are no longer expanded globally.
886
+ this.#walkthrough.isExpanded = false;
887
+ this.#walkthrough.activeSidebarMessage = null;
888
+ }
889
+
837
890
  this.requestUpdate();
838
891
  }
839
892
 
@@ -1565,7 +1618,8 @@ export class AiAssistancePanel extends UI.Panel.Panel {
1565
1618
 
1566
1619
  #resetWalkthrough(): void {
1567
1620
  this.#walkthrough.isExpanded = false;
1568
- this.#walkthrough.activeMessage = null;
1621
+ this.#walkthrough.activeSidebarMessage = null;
1622
+ this.#walkthrough.inlineExpandedMessages = [];
1569
1623
  }
1570
1624
 
1571
1625
  #onDeleteClicked(): void {
@@ -36,7 +36,7 @@ import * as TimelineUtils from '../../timeline/utils/utils.js';
36
36
  import {PanelUtils} from '../../utils/utils.js';
37
37
 
38
38
  import chatMessageStyles from './chatMessage.css.js';
39
- import {walkthroughTitle, WalkthroughView} from './WalkthroughView.js';
39
+ import {walkthroughCloseTitle, walkthroughTitle, WalkthroughView} from './WalkthroughView.js';
40
40
 
41
41
  const {html, Directives: {ref, ifDefined}} = Lit;
42
42
  const lockedString = i18n.i18n.lockedString;
@@ -49,10 +49,6 @@ const SCROLL_ROUNDING_OFFSET = 1;
49
49
  * Strings that don't need to be translated at this time.
50
50
  */
51
51
  const UIStringsNotTranslate = {
52
- /**
53
- * @description Text used in the button to close an open walkthrough
54
- */
55
- closeAgentWalkthrough: 'Close agent walkthrough',
56
52
  /**
57
53
  * @description The title of the button that allows submitting positive
58
54
  * feedback about the response for AI assistance.
@@ -174,11 +170,6 @@ const UIStringsNotTranslate = {
174
170
  * @description Alt text for image when it is not available.
175
171
  */
176
172
  imageUnavailable: 'Image unavailable',
177
- /**
178
- * @description Title for the button that shows the thinking process (walkthrough).
179
- */
180
- showThinking: 'Show thinking',
181
-
182
173
  /**
183
174
  * @description Title for the button that takes the user into other DevTools panels to reveal items the AI references.
184
175
  */
@@ -310,9 +301,10 @@ export interface MessageInput {
310
301
  walkthrough: {
311
302
  onOpen: (message: ModelChatMessage) => void,
312
303
  isExpanded: boolean,
313
- onToggle: (isOpen: boolean) => void,
304
+ onToggle: (isOpen: boolean, message: ModelChatMessage) => void,
314
305
  isInlined: boolean,
315
- activeMessage: ModelChatMessage|null,
306
+ activeSidebarMessage: ModelChatMessage|null,
307
+ inlineExpandedMessages: ModelChatMessage[],
316
308
  };
317
309
  }
318
310
 
@@ -350,12 +342,13 @@ export const DEFAULT_VIEW = (input: ChatMessageViewInput, output: ViewOutput, ta
350
342
  class="chat-message answer ${input.isLastMessage ? 'is-last-message' : ''}"
351
343
  jslog=${VisualLogging.section('answer')}
352
344
  >
353
- <div class="message-info">
354
- <devtools-icon name=${icon}></devtools-icon>
355
- <div class="message-name">
356
- <h2>${AiAssistanceModel.AiUtils.isGeminiBranding() ? lockedString(UIStringsNotTranslate.gemini) : lockedString(UIStringsNotTranslate.ai)}</h2>
357
- </div>
358
- </div>
345
+ ${aiAssistanceV2 ? Lit.nothing : html`
346
+ <div class="message-info">
347
+ <devtools-icon name=${icon}></devtools-icon>
348
+ <div class="message-name">
349
+ <h2>${AiAssistanceModel.AiUtils.isGeminiBranding() ? lockedString(UIStringsNotTranslate.gemini) : lockedString(UIStringsNotTranslate.ai)}</h2>
350
+ </div>
351
+ </div>`}
359
352
  ${aiAssistanceV2 ? renderWalkthroughUI(input, steps) : Lit.nothing}
360
353
  ${Lit.Directives.repeat(
361
354
  message.parts,
@@ -516,9 +509,8 @@ function renderWalkthroughSidebarButton(
516
509
  }
517
510
 
518
511
  const hasOneStepWithWidget = steps.some(step => step.widgets?.length);
519
- const isOpen = input.message === input.walkthrough.activeMessage;
520
-
521
- const title = isOpen ? lockedString(UIStringsNotTranslate.closeAgentWalkthrough) : walkthroughTitle({
512
+ const isExpanded = walkthrough.isExpanded && input.message === input.walkthrough.activeSidebarMessage;
513
+ const title = isExpanded ? walkthroughCloseTitle({hasWidgets: hasOneStepWithWidget}) : walkthroughTitle({
522
514
  isLoading: input.isLoading,
523
515
  hasWidgets: hasOneStepWithWidget,
524
516
  lastStep,
@@ -528,20 +520,23 @@ function renderWalkthroughSidebarButton(
528
520
  // want to change it visually at the end once everything has stopped
529
521
  // loading.
530
522
  const variant = hasOneStepWithWidget && !input.isLoading ? Buttons.Button.Variant.TONAL : Buttons.Button.Variant.TEXT;
523
+ const icon = AiAssistanceModel.AiUtils.getIconName();
531
524
 
532
525
  // clang-format off
533
526
  return html`
534
- <div class="walkthrough-toggle-container">
535
- ${input.isLoading ? html`<devtools-spinner></devtools-spinner>` : Lit.nothing}
527
+ <div class="walkthrough-toggle-container ${hasOneStepWithWidget ? 'has-widgets' : ''}">
528
+ ${input.isLoading ?
529
+ html`<devtools-spinner></devtools-spinner>` :
530
+ html`<devtools-icon name=${icon}></devtools-icon>`}
536
531
  <devtools-button
537
532
  .variant=${variant}
538
533
  .size=${Buttons.Button.Size.SMALL}
539
- .title=${lastStep.isLoading ? titleForStep(lastStep) : lockedString(UIStringsNotTranslate.showThinking)}
534
+ .title=${lastStep.isLoading ? titleForStep(lastStep) : title}
540
535
  .jslogContext=${walkthrough.isExpanded ? 'ai-hide-walkthrough-sidebar' : 'ai-show-walkthrough-sidebar'}
541
536
  data-show-walkthrough
542
537
  @click=${() => {
543
- if(walkthrough.activeMessage === input.message && walkthrough.isExpanded) {
544
- walkthrough.onToggle(false);
538
+ if(walkthrough.activeSidebarMessage === input.message && walkthrough.isExpanded) {
539
+ walkthrough.onToggle(false, message as ModelChatMessage);
545
540
  } else {
546
541
  // Can't just toggle the visibility here; we need to ensure we
547
542
  // update the state with this message as the user could have had
@@ -549,8 +544,8 @@ function renderWalkthroughSidebarButton(
549
544
  walkthrough.onOpen(message as ModelChatMessage);
550
545
  }
551
546
  }}
552
- >
553
- ${title}<devtools-icon class="chevron" .name=${'chevron-right'}></devtools-icon>
547
+ >
548
+ ${title}<devtools-icon class="chevron" .name=${isExpanded ? 'cross' : 'chevron-right'}></devtools-icon>
554
549
  </devtools-button>
555
550
  </div>
556
551
  `;
@@ -577,7 +572,9 @@ function renderWalkthroughUI(input: ChatMessageViewInput, steps: Step[]): Lit.Li
577
572
  // A message's walkthrough is considered expanded if the walkthrough is
578
573
  // open and it is specifically targeting this message. This is necessary
579
574
  // because the walkthrough state is shared across all messages in the chat.
580
- const isExpanded = (input.walkthrough.isExpanded && input.walkthrough.activeMessage === input.message);
575
+ const isExpanded = input.walkthrough.isInlined ?
576
+ input.walkthrough.inlineExpandedMessages.includes(input.message as ModelChatMessage) :
577
+ (input.walkthrough.isExpanded && input.walkthrough.activeSidebarMessage === input.message);
581
578
 
582
579
  // When a side-effect step is present, it's shown in the main chat UI if the
583
580
  // walkthrough is closed, allowing the user to approve it without opening
@@ -1238,11 +1235,12 @@ export class ChatMessage extends UI.Widget.Widget {
1238
1235
  onToggle: () => {},
1239
1236
  isInlined: false,
1240
1237
  isExpanded: false,
1241
- activeMessage: null,
1238
+ activeSidebarMessage: null,
1239
+ inlineExpandedMessages: [],
1242
1240
  };
1243
1241
 
1244
1242
  #suggestionsResizeObserver = new ResizeObserver(() => this.#handleSuggestionsScrollOrResize());
1245
- #suggestionsEvaluateLayoutThrottler = new Common.Throttler.Throttler(50);
1243
+ #suggestionsEvaluateLayoutThrottler = new Common.Throttler.Throttler(100);
1246
1244
 
1247
1245
  #feedbackValue = '';
1248
1246
  #currentRating: Host.AidaClient.Rating|undefined;
@@ -1252,6 +1250,8 @@ export class ChatMessage extends UI.Widget.Widget {
1252
1250
  #view: View;
1253
1251
  #viewOutput: ViewOutput = {};
1254
1252
 
1253
+ #isObservingSuggestions = false;
1254
+
1255
1255
  constructor(element?: HTMLElement, view?: View) {
1256
1256
  super(element);
1257
1257
  this.#view = view ?? DEFAULT_VIEW;
@@ -1261,10 +1261,6 @@ export class ChatMessage extends UI.Widget.Widget {
1261
1261
  super.wasShown();
1262
1262
  void this.performUpdate();
1263
1263
  this.#evaluateSuggestionsLayout();
1264
-
1265
- if (this.#viewOutput.suggestionsScrollContainer) {
1266
- this.#suggestionsResizeObserver.observe(this.#viewOutput.suggestionsScrollContainer);
1267
- }
1268
1264
  }
1269
1265
 
1270
1266
  override performUpdate(): Promise<void>|void {
@@ -1303,6 +1299,11 @@ export class ChatMessage extends UI.Widget.Widget {
1303
1299
  walkthrough: this.walkthrough,
1304
1300
  },
1305
1301
  this.#viewOutput, this.contentElement);
1302
+
1303
+ if (this.#viewOutput.suggestionsScrollContainer && !this.#isObservingSuggestions) {
1304
+ this.#suggestionsResizeObserver.observe(this.#viewOutput.suggestionsScrollContainer);
1305
+ this.#isObservingSuggestions = true;
1306
+ }
1306
1307
  }
1307
1308
 
1308
1309
  #handleInputChange(value: string): void {
@@ -1326,6 +1327,7 @@ export class ChatMessage extends UI.Widget.Widget {
1326
1327
  const shouldShowRightButton = suggestionsScrollContainer.scrollLeft +
1327
1328
  (suggestionsScrollContainer as HTMLElement).offsetWidth + SCROLL_ROUNDING_OFFSET <
1328
1329
  suggestionsScrollContainer.scrollWidth;
1330
+
1329
1331
  leftScrollButtonContainer.classList.toggle('hidden', !shouldShowLeftButton);
1330
1332
  rightScrollButtonContainer.classList.toggle('hidden', !shouldShowRightButton);
1331
1333
  };
@@ -1333,6 +1335,7 @@ export class ChatMessage extends UI.Widget.Widget {
1333
1335
  override willHide(): void {
1334
1336
  super.willHide();
1335
1337
  this.#suggestionsResizeObserver.disconnect();
1338
+ this.#isObservingSuggestions = false;
1336
1339
  }
1337
1340
 
1338
1341
  #handleSuggestionsScrollOrResize(): void {
@@ -1416,8 +1419,8 @@ async function makeTimelineRangeSummaryWidget(widgetData: TimelineRangeSummaryAi
1416
1419
 
1417
1420
  const thirdPartyTree = new Timeline.ThirdPartyTreeView.ThirdPartyTreeViewWidget();
1418
1421
  const mapper = new Trace.EntityMapper.EntityMapper(parsedTrace);
1419
- thirdPartyTree.setModelWithEvents(eventsArray, parsedTrace, mapper);
1420
- thirdPartyTree.updateContents(Timeline.TimelineSelection.selectionFromRangeMicroSeconds(bounds.min, bounds.max));
1422
+ thirdPartyTree.model = {selectedEvents: eventsArray, parsedTrace, entityMapper: mapper};
1423
+ thirdPartyTree.activeSelection = Timeline.TimelineSelection.selectionFromRangeMicroSeconds(bounds.min, bounds.max);
1421
1424
  thirdPartyTree.refreshTree(true);
1422
1425
 
1423
1426
  // clang-format off
@@ -1429,9 +1432,16 @@ async function makeTimelineRangeSummaryWidget(widgetData: TimelineRangeSummaryAi
1429
1432
  events,
1430
1433
  startTime: Trace.Helpers.Timing.microToMilli(bounds.min),
1431
1434
  endTime: Trace.Helpers.Timing.microToMilli(bounds.max),
1432
- thirdPartyTreeTemplate: html`<devtools-performance-third-party-tree-view
1433
- max-rows="10"
1434
- .treeView=${thirdPartyTree}></devtools-performance-third-party-tree-view>`,
1435
+ thirdPartyTreeTemplate: html`${
1436
+ widget(Timeline.ThirdPartyTreeView.ThirdPartyTreeViewWidget, {
1437
+ maxRows: 10,
1438
+ model: {
1439
+ selectedEvents: thirdPartyTree.selectedEvents() ?? null,
1440
+ parsedTrace,
1441
+ entityMapper: thirdPartyTree.entityMapper(),
1442
+ },
1443
+ activeSelection: { bounds },
1444
+ })}`,
1435
1445
  } as TimelineComponents.TimelineRangeSummaryView.TimelineRangeSummaryViewData,
1436
1446
  })}
1437
1447
  ></devtools-widget>`;
@@ -88,10 +88,11 @@ export interface Props {
88
88
  generateConversationSummary: (markdown: string) => Promise<string>;
89
89
  walkthrough: {
90
90
  onOpen: (message: ModelChatMessage) => void,
91
- onToggle: (isOpen: boolean) => void,
91
+ onToggle: (isOpen: boolean, message: ModelChatMessage) => void,
92
92
  isExpanded: boolean,
93
93
  isInlined: boolean,
94
- activeMessage: ModelChatMessage|null,
94
+ activeSidebarMessage: ModelChatMessage|null,
95
+ inlineExpandedMessages: ModelChatMessage[],
95
96
  };
96
97
  }
97
98
 
@@ -36,6 +36,14 @@ const UIStrings = {
36
36
  * @description Title for the button that shows the walkthrough when there are widgets in the walkthrough.
37
37
  */
38
38
  showAgentWalkthrough: 'Show agent walkthrough',
39
+ /**
40
+ * @description Title for the button that hides the walkthrough when there are no widgets in the walkthrough.
41
+ */
42
+ hideThinking: 'Hide thinking',
43
+ /**
44
+ * @description Title for the button that hides the walkthrough when there are widgets in the walkthrough.
45
+ */
46
+ hideAgentWalkthrough: 'Hide agent walkthrough',
39
47
  } as const;
40
48
  const str_ = i18n.i18n.registerUIStrings('panels/ai_assistance/components/WalkthroughView.ts', UIStrings);
41
49
  const i18nString = i18n.i18n.getLocalizedString.bind(undefined, str_);
@@ -46,7 +54,7 @@ export interface ViewInput {
46
54
  markdownRenderer: MarkdownLitRenderer;
47
55
  isInlined: boolean;
48
56
  isExpanded: boolean;
49
- onToggle: (isOpen: boolean) => void;
57
+ onToggle: (isOpen: boolean, message: ModelChatMessage) => void;
50
58
  onOpen: (message: ModelChatMessage) => void;
51
59
  handleScroll: (ev: Event) => void;
52
60
  }
@@ -70,6 +78,19 @@ export function walkthroughTitle(input: {
70
78
  return lockedString(UIStrings.showThinking);
71
79
  }
72
80
 
81
+ export function walkthroughCloseTitle(input: {
82
+ hasWidgets: boolean,
83
+ isInlined?: boolean,
84
+ }): string {
85
+ if (input.isInlined) {
86
+ return i18nString(UIStrings.title);
87
+ }
88
+ if (input.hasWidgets) {
89
+ return lockedString(UIStrings.hideAgentWalkthrough);
90
+ }
91
+ return lockedString(UIStrings.hideThinking);
92
+ }
93
+
73
94
  function renderInlineWalkthrough(input: ViewInput, stepsOutput: Lit.LitTemplate, steps: Step[]): Lit.LitTemplate {
74
95
  const lastStep = steps.at(-1);
75
96
  if (!input.isInlined || !lastStep) {
@@ -78,10 +99,13 @@ function renderInlineWalkthrough(input: ViewInput, stepsOutput: Lit.LitTemplate,
78
99
 
79
100
  function onToggle(event: Event): void {
80
101
  const isOpen = (event.target as HTMLDetailsElement).open;
81
- if (isOpen && input.message) {
102
+ if (!input.message) {
103
+ return;
104
+ }
105
+ if (isOpen) {
82
106
  input.onOpen(input.message);
83
107
  } else {
84
- input.onToggle(isOpen);
108
+ input.onToggle(isOpen, input.message);
85
109
  }
86
110
  }
87
111
 
@@ -92,7 +116,7 @@ function renderInlineWalkthrough(input: ViewInput, stepsOutput: Lit.LitTemplate,
92
116
  <details class="walkthrough-inline" ?open=${input.isExpanded} @toggle=${onToggle}>
93
117
  <summary ?data-has-widgets=${!input.isLoading && hasWidgets}>
94
118
  ${input.isLoading ? html`<devtools-spinner></devtools-spinner>` : Lit.nothing}
95
- ${walkthroughTitle({isLoading: input.isLoading, lastStep, hasWidgets})}
119
+ ${input.isExpanded ? walkthroughCloseTitle({hasWidgets, isInlined: true}) : walkthroughTitle({isLoading: input.isLoading, lastStep, hasWidgets})}
96
120
  <devtools-icon name="chevron-right"></devtools-icon>
97
121
  </summary>
98
122
  ${stepsOutput}
@@ -118,7 +142,11 @@ function renderSidebarWalkthrough(input: ViewInput, stepsOutput: Lit.LitTemplate
118
142
  title: i18nString(UIStrings.close),
119
143
  jslogContext: 'close-walkthrough',
120
144
  } as Buttons.Button.ButtonData}
121
- @click=${() => input.onToggle(false)}
145
+ @click=${() => {
146
+ if (input.message) {
147
+ input.onToggle(false, input.message);
148
+ }
149
+ }}
122
150
  ></devtools-button>
123
151
  </div>
124
152
  ${stepsOutput}
@@ -184,7 +212,7 @@ export class WalkthroughView extends UI.Widget.Widget {
184
212
  #message: ModelChatMessage|null = null;
185
213
  #isLoading = false;
186
214
  #markdownRenderer: MarkdownLitRenderer|null = null;
187
- #onToggle: (isOpen: boolean) => void = () => {};
215
+ #onToggle: (isOpen: boolean, message: ModelChatMessage) => void = () => {};
188
216
  #onOpen: (message: ModelChatMessage) => void = () => {};
189
217
  #isInlined = false;
190
218
  #isExpanded = false;
@@ -318,7 +346,7 @@ export class WalkthroughView extends UI.Widget.Widget {
318
346
  this.requestUpdate();
319
347
  }
320
348
 
321
- set onToggle(onToggle: (isOpen: boolean) => void) {
349
+ set onToggle(onToggle: (isOpen: boolean, message: ModelChatMessage) => void) {
322
350
  this.#onToggle = onToggle;
323
351
  this.requestUpdate();
324
352
  }
@@ -344,9 +344,13 @@
344
344
 
345
345
  .walkthrough-toggle-container {
346
346
  display: flex;
347
- gap: var(--sys-size-4);
347
+ gap: var(--sys-size-2);
348
348
  align-items: center;
349
349
 
350
+ &.has-widgets {
351
+ gap: var(--sys-size-6);
352
+ }
353
+
350
354
  .chevron {
351
355
  color: var(--sys-color-primary);
352
356
  width: var(--sys-size-8);
@@ -92,6 +92,7 @@ import {SharedStorageTreeElement} from './SharedStorageTreeElement.js';
92
92
  import {StorageBucketsTreeParentElement} from './StorageBucketsTreeElement.js';
93
93
  import {StorageView} from './StorageView.js';
94
94
  import {TrustTokensTreeElement} from './TrustTokensTreeElement.js';
95
+ import {WebMCPTreeElement} from './WebMCPTreeElement.js';
95
96
 
96
97
  const UIStrings = {
97
98
  /**
@@ -346,6 +347,7 @@ export class ApplicationPanelSidebar extends UI.Widget.VBox implements SDK.Targe
346
347
  periodicBackgroundSyncTreeElement: BackgroundServiceTreeElement;
347
348
  pushMessagingTreeElement: BackgroundServiceTreeElement;
348
349
  reportingApiTreeElement: ReportingApiTreeElement;
350
+ webMcpTreeElement?: WebMCPTreeElement;
349
351
  deviceBoundSessionsRootTreeElement: DeviceBoundSessionsRootTreeElement|undefined;
350
352
  deviceBoundSessionsModel: DeviceBoundSessionsModel|undefined;
351
353
  preloadingSummaryTreeElement: PreloadingSummaryTreeElement|undefined;
@@ -390,6 +392,10 @@ export class ApplicationPanelSidebar extends UI.Widget.VBox implements SDK.Targe
390
392
  this.applicationTreeElement.appendChild(this.serviceWorkersTreeElement);
391
393
  const clearStorageTreeElement = new ClearStorageTreeElement(panel);
392
394
  this.applicationTreeElement.appendChild(clearStorageTreeElement);
395
+ if (Root.Runtime.hostConfig.devToolsWebMCPSupport?.enabled) {
396
+ this.webMcpTreeElement = new WebMCPTreeElement(panel);
397
+ this.applicationTreeElement.appendChild(this.webMcpTreeElement);
398
+ }
393
399
 
394
400
  const storageSectionTitle = i18nString(UIStrings.storage);
395
401
  const storageTreeElement = this.addSidebarSection(storageSectionTitle, 'storage');
@@ -321,27 +321,19 @@ export class IDBDataView extends UI.View.SimpleView {
321
321
  const keyPath = this.isIndex && this.index ? this.index.keyPath : this.objectStore.keyPath;
322
322
 
323
323
  const columns: DataGrid.DataGrid.ColumnDescriptor[] = [];
324
-
325
- // Create column defaults so that we avoid repetition below.
326
- const columnDefaults: Partial<DataGrid.DataGrid.ColumnDescriptor> = {
327
- sortable: false,
328
- };
329
324
  columns.push({
330
- ...columnDefaults,
331
325
  id: 'number',
332
326
  title: '#' as Platform.UIString.LocalizedString,
333
327
  sortable: false,
334
328
  width: '50px',
335
329
  });
336
330
  columns.push({
337
- ...columnDefaults,
338
331
  id: 'key',
339
332
  titleDOMFragment: this.keyColumnHeaderFragment(i18nString(UIStrings.keyString), keyPath),
340
333
  sortable: false,
341
334
  });
342
335
  if (this.isIndex) {
343
336
  columns.push({
344
- ...columnDefaults,
345
337
  id: 'primary-key',
346
338
  titleDOMFragment: this.keyColumnHeaderFragment(i18nString(UIStrings.primaryKey), this.objectStore.keyPath),
347
339
  sortable: false,
@@ -349,7 +341,6 @@ export class IDBDataView extends UI.View.SimpleView {
349
341
  }
350
342
  const title = i18nString(UIStrings.valueString);
351
343
  columns.push({
352
- ...columnDefaults,
353
344
  id: 'value',
354
345
  title,
355
346
  sortable: false,
@@ -0,0 +1,35 @@
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 type * as Platform from '../../core/platform/platform.js';
7
+ import {createIcon} from '../../ui/kit/kit.js';
8
+
9
+ import {ApplicationPanelTreeElement} from './ApplicationPanelTreeElement.js';
10
+ import type {ResourcesPanel} from './ResourcesPanel.js';
11
+ import {WebMCPView} from './WebMCPView.js';
12
+
13
+ export class WebMCPTreeElement extends ApplicationPanelTreeElement {
14
+ #view?: WebMCPView;
15
+
16
+ constructor(storagePanel: ResourcesPanel) {
17
+ super(storagePanel, 'WebMCP', false, 'web-mcp');
18
+ const icon = createIcon('document');
19
+ this.setLeadingIcons([icon]);
20
+ }
21
+
22
+ override get itemURL(): Platform.DevToolsPath.UrlString {
23
+ return 'webMcp://' as Platform.DevToolsPath.UrlString;
24
+ }
25
+
26
+ override onselect(selectedByUser?: boolean): boolean {
27
+ super.onselect(selectedByUser);
28
+ if (!this.#view) {
29
+ this.#view = new WebMCPView();
30
+ }
31
+ this.showView(this.#view);
32
+ Host.userMetrics.panelShown('web-mcp');
33
+ return false;
34
+ }
35
+ }