chrome-devtools-frontend 1.0.1602348 → 1.0.1603822

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 (105) hide show
  1. package/.agents/skills/version-control/SKILL.md +17 -2
  2. package/AUTHORS +1 -0
  3. package/front_end/core/common/Settings.ts +1 -871
  4. package/front_end/core/common/VersionController.ts +880 -0
  5. package/front_end/core/common/common.ts +2 -0
  6. package/front_end/core/platform/HostRuntime.ts +9 -3
  7. package/front_end/core/protocol_client/InspectorBackend.ts +8 -0
  8. package/front_end/core/root/Runtime.ts +5 -0
  9. package/front_end/core/sdk/OverlayModel.ts +13 -13
  10. package/front_end/core/sdk/OverlayPersistentHighlighter.ts +13 -10
  11. package/front_end/core/sdk/RuntimeModel.ts +5 -5
  12. package/front_end/core/sdk/ServiceWorkerManager.ts +4 -2
  13. package/front_end/core/sdk/Target.ts +5 -0
  14. package/front_end/core/sdk/TargetManager.ts +5 -0
  15. package/front_end/core/sdk/WebMCPModel.ts +104 -0
  16. package/front_end/core/sdk/sdk.ts +2 -0
  17. package/front_end/entrypoints/heap_snapshot_worker/HeapSnapshot.ts +13 -19
  18. package/front_end/generated/InspectorBackendCommands.ts +3 -0
  19. package/front_end/generated/protocol-mapping.d.ts +8 -0
  20. package/front_end/generated/protocol-proxy-api.d.ts +10 -0
  21. package/front_end/generated/protocol.ts +57 -0
  22. package/front_end/models/ai_assistance/AiConversation.ts +3 -3
  23. package/front_end/models/ai_assistance/agents/AccessibilityAgent.ts +50 -0
  24. package/front_end/models/ai_assistance/agents/AiAgent.ts +14 -1
  25. package/front_end/models/ai_assistance/agents/ContextSelectionAgent.ts +19 -4
  26. package/front_end/models/ai_assistance/agents/NetworkAgent.ts +7 -1
  27. package/front_end/models/ai_assistance/agents/PerformanceAgent.ts +30 -7
  28. package/front_end/models/ai_assistance/agents/StylingAgent.ts +0 -23
  29. package/front_end/models/ai_assistance/data_formatters/LighthouseFormatter.snapshot.txt +60 -9
  30. package/front_end/models/ai_assistance/data_formatters/LighthouseFormatter.ts +71 -15
  31. package/front_end/models/heap_snapshot_model/HeapSnapshotModel.ts +7 -7
  32. package/front_end/models/javascript_metadata/NativeFunctions.js +6 -2
  33. package/front_end/models/lighthouse/LighthouseReporterTypes.ts +10 -8
  34. package/front_end/models/lighthouse/RunTypes.ts +3 -1
  35. package/front_end/panels/ai_assistance/AiAssistancePanel.ts +13 -5
  36. package/front_end/panels/ai_assistance/components/ChatMessage.ts +143 -18
  37. package/front_end/panels/ai_assistance/components/WalkthroughView.ts +30 -10
  38. package/front_end/panels/ai_assistance/components/chatMessage.css +58 -3
  39. package/front_end/panels/ai_assistance/components/walkthroughView.css +35 -5
  40. package/front_end/panels/application/AppManifestView.ts +2 -2
  41. package/front_end/panels/application/BackgroundServiceView.ts +9 -9
  42. package/front_end/panels/application/FrameDetailsView.ts +4 -13
  43. package/front_end/panels/application/IndexedDBViews.ts +1 -1
  44. package/front_end/panels/application/ServiceWorkerCacheViews.ts +3 -3
  45. package/front_end/panels/application/components/PermissionsPolicySection.ts +1 -1
  46. package/front_end/panels/application/components/permissionsPolicySection.css +54 -52
  47. package/front_end/panels/application/frameDetailsReportView.css +13 -1
  48. package/front_end/panels/common/ThrottlingUtils.ts +46 -0
  49. package/front_end/panels/common/common.ts +1 -0
  50. package/front_end/panels/elements/CSSValueTraceView.ts +9 -1
  51. package/front_end/panels/elements/ComputedStyleWidget.ts +11 -1
  52. package/front_end/panels/elements/PropertiesWidget.ts +14 -13
  53. package/front_end/panels/elements/PropertyRenderer.ts +10 -9
  54. package/front_end/panels/elements/StandaloneStylesContainer.ts +15 -5
  55. package/front_end/panels/elements/StylePropertiesSection.ts +0 -49
  56. package/front_end/panels/elements/StylePropertyTreeElement.ts +20 -0
  57. package/front_end/panels/elements/StylesContainer.ts +0 -1
  58. package/front_end/panels/elements/StylesSidebarPane.ts +65 -31
  59. package/front_end/panels/elements/stylePropertiesTreeOutline.css +5 -0
  60. package/front_end/panels/issues/AffectedSelectivePermissionsInterventionView.ts +2 -2
  61. package/front_end/panels/lighthouse/LighthouseController.ts +4 -1
  62. package/front_end/panels/media/EventDisplayTable.ts +3 -2
  63. package/front_end/panels/mobile_throttling/NetworkThrottlingSelector.ts +14 -0
  64. package/front_end/panels/network/EventSourceMessagesView.ts +2 -2
  65. package/front_end/panels/network/ResourceChunkView.ts +1 -1
  66. package/front_end/panels/profiler/HeapDetachedElementsView.ts +9 -4
  67. package/front_end/panels/profiler/HeapProfileView.ts +8 -4
  68. package/front_end/panels/profiler/HeapSnapshotDataGrids.ts +40 -33
  69. package/front_end/panels/profiler/HeapSnapshotGridNodes.ts +1 -1
  70. package/front_end/panels/profiler/HeapSnapshotProxy.ts +1 -1
  71. package/front_end/panels/profiler/HeapSnapshotView.ts +12 -18
  72. package/front_end/panels/profiler/LiveHeapProfileView.ts +3 -4
  73. package/front_end/panels/profiler/ProfileHeader.ts +5 -11
  74. package/front_end/panels/profiler/ProfileView.ts +1 -10
  75. package/front_end/panels/sensors/SensorsView.ts +24 -43
  76. package/front_end/panels/settings/keybindsSettingsTab.css +25 -31
  77. package/front_end/panels/timeline/EventsTimelineTreeView.ts +2 -2
  78. package/front_end/panels/timeline/ThirdPartyTreeView.ts +8 -0
  79. package/front_end/panels/timeline/TimelinePanel.ts +14 -2
  80. package/front_end/panels/timeline/TimelineTreeView.ts +19 -17
  81. package/front_end/panels/timeline/components/FieldSettingsDialog.ts +5 -9
  82. package/front_end/panels/timeline/components/LiveMetricsView.ts +1 -2
  83. package/front_end/panels/timeline/components/OriginMap.ts +176 -159
  84. package/front_end/panels/timeline/components/originMap.css +4 -51
  85. package/front_end/panels/timeline/components/timelineRangeSummaryView.css +3 -1
  86. package/front_end/panels/timeline/thirdPartyTreeView.css +6 -0
  87. package/front_end/panels/timeline/timeline-meta.ts +11 -0
  88. package/front_end/panels/timeline/utils/Helpers.ts +5 -25
  89. package/front_end/panels/webauthn/WebauthnPane.ts +52 -0
  90. package/front_end/third_party/acorn/README.chromium +1 -0
  91. package/front_end/third_party/chromium/README.chromium +1 -1
  92. package/front_end/ui/components/{list → lists}/List.ts +1 -1
  93. package/front_end/ui/components/{list → lists}/list.css +0 -1
  94. package/front_end/ui/legacy/Treeoutline.ts +7 -3
  95. package/front_end/ui/legacy/UIUtils.ts +13 -11
  96. package/front_end/ui/legacy/Widget.ts +5 -0
  97. package/front_end/ui/legacy/components/data_grid/DataGrid.ts +1 -1
  98. package/front_end/ui/legacy/components/data_grid/DataGridElement.ts +3 -3
  99. package/front_end/ui/legacy/components/data_grid/SortableDataGrid.ts +2 -2
  100. package/front_end/ui/legacy/components/object_ui/ObjectPropertiesSection.ts +39 -23
  101. package/front_end/ui/legacy/components/perf_ui/GCActionDelegate.ts +20 -4
  102. package/front_end/ui/legacy/treeoutline.css +4 -0
  103. package/front_end/ui/visual_logging/KnownContextValues.ts +2 -0
  104. package/package.json +1 -1
  105. /package/front_end/ui/components/{list → lists}/lists.ts +0 -0
@@ -82,10 +82,17 @@ export interface ReportJSON {
82
82
  export type DetailsJSON =
83
83
  |TableDetailsJSON|OpportunityDetailsJSON|FilmstripDetailsJSON|ScreenshotDetailsJSON|DebugDataDetailsJSON;
84
84
 
85
+ export interface SubItemsJSON {
86
+ type: 'subitems';
87
+ items: Array<Record<string, TableItemValue>>;
88
+ }
89
+
90
+ export type TableItem = Record<string, TableItemValue|SubItemsJSON|undefined>;
91
+
85
92
  export interface TableDetailsJSON {
86
93
  type: 'table';
87
94
  headings: TableHeadingJSON[];
88
- items: Array<Record<string, TableItemValue>>;
95
+ items: TableItem[];
89
96
  summary?: OpportunitySummary;
90
97
  }
91
98
 
@@ -96,7 +103,7 @@ export interface TableHeadingJSON {
96
103
  itemType?: string;
97
104
  subItemsHeading?: {
98
105
  key: string,
99
- valueType: string,
106
+ valueType?: string,
100
107
  };
101
108
  }
102
109
 
@@ -109,12 +116,7 @@ export type TableItemValue =|string|number|NodeDetailsJSON|SourceLocationDetails
109
116
  export interface OpportunityDetailsJSON {
110
117
  type: 'opportunity';
111
118
  headings: TableHeadingJSON[];
112
- items: Array<{
113
- url: string,
114
- wastedBytes?: number,
115
- wastedMs?: number,
116
- totalBytes?: number, [key: string]: unknown,
117
- }>;
119
+ items: TableItem[];
118
120
  overallSavingsMs: number;
119
121
  overallSavingsBytes?: number;
120
122
  }
@@ -6,6 +6,7 @@ import type * as Host from '../../core/host/host.js';
6
6
  import type * as Platform from '../../core/platform/platform.js';
7
7
 
8
8
  export type CategoryId = 'performance'|'accessibility'|'best-practices'|'seo';
9
+ export type RunMode = 'navigation'|'timespan'|'snapshot';
9
10
 
10
11
  export interface Preset {
11
12
  setting: Common.Settings.Setting<boolean>;
@@ -33,10 +34,11 @@ export interface Flags {
33
34
  disableStorageReset?: boolean;
34
35
  throttlingMethod?: string;
35
36
  formFactor?: string|boolean;
36
- mode?: string|boolean;
37
+ mode?: RunMode;
37
38
  }
38
39
 
39
40
  export interface RunOverrides {
40
41
  categoryIds?: CategoryId[];
42
+ mode?: RunMode;
41
43
  isAIControlled?: boolean;
42
44
  }
@@ -821,7 +821,7 @@ export class AiAssistancePanel extends UI.Panel.Panel {
821
821
  return;
822
822
  }
823
823
  // If the UI changed, we reset the visibility of the AI Walkthrough.
824
- this.#clearWalkthrough();
824
+ this.#resetWalkthrough();
825
825
  this.#walkthrough.isInlined = isNarrow;
826
826
  this.requestUpdate();
827
827
  }
@@ -888,8 +888,12 @@ export class AiAssistancePanel extends UI.Panel.Panel {
888
888
  return await TimelinePanel.TimelinePanel.TimelinePanel.executeRecordAndReload();
889
889
  }
890
890
 
891
- async #handleLighthouseRun(): Promise<LHModel.ReporterTypes.ReportJSON|null> {
892
- return await LighthousePanel.LighthousePanel.LighthousePanel.executeLighthouseRecording({isAIControlled: true});
891
+ async #handleLighthouseRun(overrides?: LHModel.RunTypes.RunOverrides):
892
+ Promise<LHModel.ReporterTypes.ReportJSON|null> {
893
+ return await LighthousePanel.LighthousePanel.LighthousePanel.executeLighthouseRecording({
894
+ isAIControlled: true,
895
+ ...overrides,
896
+ });
893
897
  }
894
898
 
895
899
  #getDefaultConversationType(): AiAssistanceModel.AiHistoryStorage.ConversationType|undefined {
@@ -988,6 +992,7 @@ export class AiAssistancePanel extends UI.Panel.Panel {
988
992
  performanceRecordAndReload: this.#handlePerformanceRecordAndReload.bind(this),
989
993
  onInspectElement: this.#handleInspectElement.bind(this),
990
994
  networkTimeCalculator: NetworkPanel.NetworkPanel.NetworkPanel.instance().networkLogView.timeCalculator(),
995
+ lighthouseRecording: this.#handleLighthouseRun.bind(this),
991
996
  });
992
997
  }
993
998
  }
@@ -1026,6 +1031,7 @@ export class AiAssistancePanel extends UI.Panel.Panel {
1026
1031
  performanceRecordAndReload: this.#handlePerformanceRecordAndReload.bind(this),
1027
1032
  onInspectElement: this.#handleInspectElement.bind(this),
1028
1033
  networkTimeCalculator: NetworkPanel.NetworkPanel.NetworkPanel.instance().networkLogView.timeCalculator(),
1034
+ lighthouseRecording: this.#handleLighthouseRun.bind(this),
1029
1035
  });
1030
1036
  this.#updateConversationState(conversation);
1031
1037
  this.#conversation?.setContext(context);
@@ -1501,6 +1507,7 @@ export class AiAssistancePanel extends UI.Panel.Panel {
1501
1507
  performanceRecordAndReload: this.#handlePerformanceRecordAndReload.bind(this),
1502
1508
  onInspectElement: this.#handleInspectElement.bind(this),
1503
1509
  networkTimeCalculator: NetworkPanel.NetworkPanel.NetworkPanel.instance().networkLogView.timeCalculator(),
1510
+ lighthouseRecording: this.#handleLighthouseRun.bind(this),
1504
1511
  });
1505
1512
  }
1506
1513
  this.#updateConversationState(conversation);
@@ -1556,7 +1563,7 @@ export class AiAssistancePanel extends UI.Panel.Panel {
1556
1563
  this.#updateConversationState();
1557
1564
  }
1558
1565
 
1559
- #clearWalkthrough(): void {
1566
+ #resetWalkthrough(): void {
1560
1567
  this.#walkthrough.isExpanded = false;
1561
1568
  this.#walkthrough.activeMessage = null;
1562
1569
  }
@@ -1567,7 +1574,7 @@ export class AiAssistancePanel extends UI.Panel.Panel {
1567
1574
  }
1568
1575
 
1569
1576
  // Ensure we clear the walkthrough so it doesn't hold onto a chat that is about to be deleted.
1570
- this.#clearWalkthrough();
1577
+ this.#resetWalkthrough();
1571
1578
 
1572
1579
  void AiAssistanceModel.AiHistoryStorage.AiHistoryStorage.instance().deleteHistoryEntry(this.#conversation.id);
1573
1580
  this.#updateConversationState();
@@ -1592,6 +1599,7 @@ export class AiAssistancePanel extends UI.Panel.Panel {
1592
1599
 
1593
1600
  #handleNewChatRequest(): void {
1594
1601
  this.#updateConversationState();
1602
+ this.#resetWalkthrough();
1595
1603
  UI.ARIAUtils.LiveAnnouncer.alert(i18nString(UIStrings.newChatCreated));
1596
1604
 
1597
1605
  if (Annotations.AnnotationRepository.annotationsEnabled()) {
@@ -14,9 +14,11 @@ 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, ComputedStyleAiWidget, CoreVitalsAiWidget, DomTreeAiWidget, LcpBreakdownAiWidget, PerformanceTraceAiWidget,
17
- StylePropertiesAiWidget} from '../../../models/ai_assistance/agents/AiAgent.js';
17
+ StylePropertiesAiWidget, TimelineRangeSummaryAiWidget} from '../../../models/ai_assistance/agents/AiAgent.js';
18
18
  import * as AiAssistanceModel from '../../../models/ai_assistance/ai_assistance.js';
19
19
  import * as ComputedStyle from '../../../models/computed_style/computed_style.js';
20
+ import * as Trace from '../../../models/trace/trace.js';
21
+ import * as PanelsCommon from '../../../panels/common/common.js';
20
22
  import * as Marked from '../../../third_party/marked/marked.js';
21
23
  import * as Buttons from '../../../ui/components/buttons/buttons.js';
22
24
  import * as Input from '../../../ui/components/input/input.js';
@@ -47,7 +49,10 @@ const SCROLL_ROUNDING_OFFSET = 1;
47
49
  * Strings that don't need to be translated at this time.
48
50
  */
49
51
  const UIStringsNotTranslate = {
50
-
52
+ /**
53
+ * @description Text used in the button to close an open walkthrough
54
+ */
55
+ closeAgentWalkthrough: 'Close agent walkthrough',
51
56
  /**
52
57
  * @description The title of the button that allows submitting positive
53
58
  * feedback about the response for AI assistance.
@@ -181,7 +186,23 @@ const UIStringsNotTranslate = {
181
186
  /**
182
187
  * @description Title used for revealing the performance trace.
183
188
  */
184
- revealTrace: 'Reveal trace'
189
+ revealTrace: 'Reveal trace',
190
+ /**
191
+ * @description Title for the core web vitals widget.
192
+ */
193
+ coreVitals: 'Core Web Vitals',
194
+ /**
195
+ * @description Title for the LCP breakdown widget.
196
+ */
197
+ lcpBreakdown: 'LCP breakdown',
198
+ /**
199
+ * @description Title for the LCP element widget.
200
+ */
201
+ lcpElement: 'LCP element',
202
+ /**
203
+ * @description Title for the performance summary widget.
204
+ */
205
+ performanceSummary: 'Performance summary'
185
206
  } as const;
186
207
 
187
208
  export interface Step {
@@ -495,18 +516,25 @@ function renderWalkthroughSidebarButton(
495
516
  }
496
517
 
497
518
  const hasOneStepWithWidget = steps.some(step => step.widgets?.length);
498
- const title = walkthroughTitle({
519
+ const isOpen = input.message === input.walkthrough.activeMessage;
520
+
521
+ const title = isOpen ? lockedString(UIStringsNotTranslate.closeAgentWalkthrough) : walkthroughTitle({
499
522
  isLoading: input.isLoading,
500
523
  hasWidgets: hasOneStepWithWidget,
501
524
  lastStep,
502
525
  });
503
526
 
527
+ // The button should be tonal when there are widgets, but we only
528
+ // want to change it visually at the end once everything has stopped
529
+ // loading.
530
+ const variant = hasOneStepWithWidget && !input.isLoading ? Buttons.Button.Variant.TONAL : Buttons.Button.Variant.TEXT;
531
+
504
532
  // clang-format off
505
533
  return html`
506
534
  <div class="walkthrough-toggle-container">
507
535
  ${input.isLoading ? html`<devtools-spinner></devtools-spinner>` : Lit.nothing}
508
536
  <devtools-button
509
- .variant=${Buttons.Button.Variant.OUTLINED}
537
+ .variant=${variant}
510
538
  .size=${Buttons.Button.Size.SMALL}
511
539
  .title=${lastStep.isLoading ? titleForStep(lastStep) : lockedString(UIStringsNotTranslate.showThinking)}
512
540
  .jslogContext=${walkthrough.isExpanded ? 'ai-hide-walkthrough-sidebar' : 'ai-show-walkthrough-sidebar'}
@@ -521,7 +549,9 @@ function renderWalkthroughSidebarButton(
521
549
  walkthrough.onOpen(message as ModelChatMessage);
522
550
  }
523
551
  }}
524
- >${title}</devtools-button>
552
+ >
553
+ ${title}<devtools-icon class="chevron" .name=${'chevron-right'}></devtools-icon>
554
+ </devtools-button>
525
555
  </div>
526
556
  `;
527
557
  // clang-format on
@@ -656,6 +686,8 @@ interface WidgetMakerResponse {
656
686
  renderedWidget: Lit.LitTemplate|null;
657
687
  revealable: unknown;
658
688
  customRevealTitle?: Platform.UIString.LocalizedString;
689
+ // Can be null if the widget is only used to add the Reveal CTA.
690
+ title: Lit.LitTemplate|Platform.UIString.LocalizedString|null;
659
691
  }
660
692
 
661
693
  const nodeCache = new Map<Protocol.DOM.BackendNodeId, SDK.DOMModel.DOMNode>();
@@ -694,11 +726,20 @@ async function makeComputedStyleWidget(widgetData: ComputedStyleAiWidget): Promi
694
726
  // This disables showing the nested traces and detailed information in the widget.
695
727
  propertyTraces: null,
696
728
  allowUserControl: false,
697
- filterText: new RegExp(widgetData.data.properties.join('|'), 'i')
729
+ filterText: new RegExp(widgetData.data.properties.join('|'), 'i'),
730
+ enableNarrowViewResizing: false,
698
731
  })}></devtools-widget>`;
699
732
  // clang-format on
700
733
 
701
- return {renderedWidget, revealable: new Elements.ElementsPanel.NodeComputedStyles(domNodeForId)};
734
+ return {
735
+ renderedWidget,
736
+ revealable: new Elements.ElementsPanel.NodeComputedStyles(domNodeForId),
737
+ title: html`<devtools-widget
738
+ ${widget(PanelsCommon.DOMLinkifier.DOMNodeLink, {
739
+ node: domNodeForId,
740
+ })}
741
+ ></devtools-widget>`,
742
+ };
702
743
  }
703
744
 
704
745
  async function makeCoreVitalsWidget(widgetData: CoreVitalsAiWidget): Promise<WidgetMakerResponse|null> {
@@ -708,7 +749,11 @@ async function makeCoreVitalsWidget(widgetData: CoreVitalsAiWidget): Promise<Wid
708
749
  </devtools-widget>`;
709
750
  // clang-format on
710
751
 
711
- return {renderedWidget, revealable: new TimelineUtils.Helpers.RevealableCoreVitals(widgetData.data.insightSetKey)};
752
+ return {
753
+ renderedWidget,
754
+ revealable: new TimelineUtils.Helpers.RevealableCoreVitals(widgetData.data.insightSetKey),
755
+ title: lockedString(UIStringsNotTranslate.coreVitals),
756
+ };
712
757
  }
713
758
 
714
759
  async function makeStylePropertiesWidget(widgetData: StylePropertiesAiWidget): Promise<WidgetMakerResponse|null> {
@@ -727,7 +772,15 @@ async function makeStylePropertiesWidget(widgetData: StylePropertiesAiWidget): P
727
772
  </devtools-widget>`;
728
773
  // clang-format on
729
774
 
730
- return {renderedWidget, revealable: domNodeForId};
775
+ return {
776
+ renderedWidget,
777
+ revealable: domNodeForId,
778
+ title: html`<devtools-widget
779
+ ${widget(PanelsCommon.DOMLinkifier.DOMNodeLink, {
780
+ node: domNodeForId,
781
+ })}
782
+ ></devtools-widget>`,
783
+ };
731
784
  }
732
785
 
733
786
  async function makeLcpBreakdownWidget(widgetData: LcpBreakdownAiWidget): Promise<WidgetMakerResponse|null> {
@@ -745,7 +798,11 @@ async function makeLcpBreakdownWidget(widgetData: LcpBreakdownAiWidget): Promise
745
798
  })}></devtools-widget>`;
746
799
  // clang-format on
747
800
 
748
- return {renderedWidget, revealable: new TimelineUtils.Helpers.RevealableInsight(insight)};
801
+ return {
802
+ renderedWidget,
803
+ revealable: new TimelineUtils.Helpers.RevealableInsight(insight),
804
+ title: lockedString(UIStringsNotTranslate.lcpBreakdown),
805
+ };
749
806
  }
750
807
 
751
808
  function renderWidgetResponse(response: WidgetMakerResponse|null): Lit.LitTemplate {
@@ -764,21 +821,38 @@ function renderWidgetResponse(response: WidgetMakerResponse|null): Lit.LitTempla
764
821
  'widget-and-revealer-container': true,
765
822
  'revealer-only': response.renderedWidget === null,
766
823
  });
824
+
825
+ const revealButton = html`
826
+ <devtools-button class="widget-reveal-button"
827
+ .variant=${Buttons.Button.Variant.TEXT}
828
+ @click=${onReveal}
829
+ >
830
+ ${response.customRevealTitle ?? lockedString(UIStringsNotTranslate.reveal)}
831
+ <devtools-icon name='tab-move'></devtools-icon>
832
+ </devtools-button>
833
+ `;
834
+
767
835
  // clang-format off
768
836
  return html`
769
837
  <div class=${classes}>
838
+ ${response.title ? html`
839
+ <div class="widget-header">
840
+ <div class="widget-name">${response.title}</div>
841
+ <div class="widget-reveal-container">
842
+ ${revealButton}
843
+ </div>
844
+ </div>
845
+ ` : Lit.nothing}
770
846
  ${response.renderedWidget ? html`
771
847
  <div class="widget-content-container">
772
848
  ${response.renderedWidget}
773
849
  </div>` : Lit.nothing
774
850
  }
775
- <div class="widget-reveal-container">
776
- <devtools-button class="widget-reveal"
777
- .iconName=${'tab-move'}
778
- .variant=${Buttons.Button.Variant.TEXT}
779
- @click=${onReveal}
780
- >${response.customRevealTitle ?? lockedString(UIStringsNotTranslate.reveal)}</devtools-button>
781
- </div>
851
+ ${!response.title ? html`
852
+ <div class="widget-reveal-container">
853
+ ${revealButton}
854
+ </div>
855
+ ` : Lit.nothing}
782
856
  </div>
783
857
  `;
784
858
  // clang-format on
@@ -787,6 +861,7 @@ function renderWidgetResponse(response: WidgetMakerResponse|null): Lit.LitTempla
787
861
  async function makePerformanceTraceWidget(widgetData: PerformanceTraceAiWidget): Promise<WidgetMakerResponse|null> {
788
862
  return {
789
863
  renderedWidget: null,
864
+ title: null,
790
865
  revealable: new Timeline.TimelinePanel.ParsedTraceRevealable(widgetData.data.parsedTrace),
791
866
  customRevealTitle: lockedString(UIStringsNotTranslate.revealTrace),
792
867
  };
@@ -846,6 +921,7 @@ async function makeDomTreeWidget(widgetData: DomTreeAiWidget): Promise<WidgetMak
846
921
  return {
847
922
  renderedWidget,
848
923
  revealable: new SDK.DOMModel.DeferredDOMNode(root.domModel().target(), root.backendNodeId()),
924
+ title: lockedString(UIStringsNotTranslate.lcpElement),
849
925
  };
850
926
  }
851
927
 
@@ -892,6 +968,9 @@ async function renderWidgets(
892
968
  case 'LCP_BREAKDOWN':
893
969
  response = await makeLcpBreakdownWidget(widgetData);
894
970
  break;
971
+ case 'TIMELINE_RANGE_SUMMARY':
972
+ response = await makeTimelineRangeSummaryWidget(widgetData);
973
+ break;
895
974
  default:
896
975
  Platform.assertNever(widgetData, 'Unknown AiWidget name');
897
976
  }
@@ -1318,3 +1397,49 @@ export class ChatMessage extends UI.Widget.Widget {
1318
1397
  void this.performUpdate();
1319
1398
  }
1320
1399
  }
1400
+
1401
+ async function makeTimelineRangeSummaryWidget(widgetData: TimelineRangeSummaryAiWidget):
1402
+ Promise<WidgetMakerResponse|null> {
1403
+ const {bounds, parsedTrace, track} = widgetData.data;
1404
+ let events: readonly Trace.Types.Events.Event[] = [];
1405
+ if (track === 'main') {
1406
+ const flameChartView = Timeline.TimelinePanel.TimelinePanel.instance().getFlameChart();
1407
+ const mainDataProvider = flameChartView.getMainDataProvider();
1408
+ const mainTrack =
1409
+ mainDataProvider.timelineData().groups.find((group: {name: string}) => group.name.startsWith('Main \u2014 '));
1410
+ if (mainTrack) {
1411
+ events = mainDataProvider.groupTreeEvents(mainTrack) ?? [];
1412
+ }
1413
+ }
1414
+ const eventsArray = Array.from(events);
1415
+ eventsArray.sort((a, b) => a.ts - b.ts);
1416
+
1417
+ const thirdPartyTree = new Timeline.ThirdPartyTreeView.ThirdPartyTreeViewWidget();
1418
+ const mapper = new Trace.EntityMapper.EntityMapper(parsedTrace);
1419
+ thirdPartyTree.setModelWithEvents(eventsArray, parsedTrace, mapper);
1420
+ thirdPartyTree.updateContents(Timeline.TimelineSelection.selectionFromRangeMicroSeconds(bounds.min, bounds.max));
1421
+ thirdPartyTree.refreshTree(true);
1422
+
1423
+ // clang-format off
1424
+ const template = html`
1425
+ <devtools-widget
1426
+ ${widget(TimelineComponents.TimelineRangeSummaryView.TimelineRangeSummaryView, {
1427
+ data: {
1428
+ parsedTrace,
1429
+ events,
1430
+ startTime: Trace.Helpers.Timing.microToMilli(bounds.min),
1431
+ 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
+ } as TimelineComponents.TimelineRangeSummaryView.TimelineRangeSummaryViewData,
1436
+ })}
1437
+ ></devtools-widget>`;
1438
+ // clang-format on
1439
+
1440
+ return {
1441
+ renderedWidget: template,
1442
+ revealable: new TimelineUtils.Helpers.RevealableTimeRange(bounds),
1443
+ title: lockedString(UIStringsNotTranslate.performanceSummary),
1444
+ };
1445
+ }
@@ -90,10 +90,10 @@ function renderInlineWalkthrough(input: ViewInput, stepsOutput: Lit.LitTemplate,
90
90
  // clang-format off
91
91
  return html`
92
92
  <details class="walkthrough-inline" ?open=${input.isExpanded} @toggle=${onToggle}>
93
- <summary>
93
+ <summary ?data-has-widgets=${!input.isLoading && hasWidgets}>
94
94
  ${input.isLoading ? html`<devtools-spinner></devtools-spinner>` : Lit.nothing}
95
95
  ${walkthroughTitle({isLoading: input.isLoading, lastStep, hasWidgets})}
96
- <devtools-icon name="chevron-down"></devtools-icon>
96
+ <devtools-icon name="chevron-right"></devtools-icon>
97
97
  </summary>
98
98
  ${stepsOutput}
99
99
  </details>
@@ -182,8 +182,6 @@ export class WalkthroughView extends UI.Widget.Widget {
182
182
  #view: View;
183
183
 
184
184
  #message: ModelChatMessage|null = null;
185
- // TODO(b/487921187): fix loading state - also unsure if we need this vs
186
- // looking at the loading state in the message's steps.
187
185
  #isLoading = false;
188
186
  #markdownRenderer: MarkdownLitRenderer|null = null;
189
187
  #onToggle: (isOpen: boolean) => void = () => {};
@@ -196,6 +194,7 @@ export class WalkthroughView extends UI.Widget.Widget {
196
194
 
197
195
  #output: ViewOutput = {};
198
196
  #stepsContainerResizeObserver = new ResizeObserver(() => this.#handleStepsContainerResize());
197
+ #lastStepsContainerWidth = 0;
199
198
 
200
199
  constructor(element?: HTMLElement, view: View = DEFAULT_VIEW) {
201
200
  super(element);
@@ -218,12 +217,27 @@ export class WalkthroughView extends UI.Widget.Widget {
218
217
  }
219
218
  }
220
219
 
221
- override onResize(): void {
222
- this.#handleStepsContainerResize();
223
- }
224
-
225
220
  #handleStepsContainerResize(): void {
226
- if (!this.#pinScrollToBottom) {
221
+ const width = this.#output.stepsContainer?.offsetWidth ?? 0;
222
+ /**
223
+ * If the width has changed, it's likely due to a manual resize (e.g., the
224
+ * user dragging the sidebar). In these cases, we want to avoid jumping the
225
+ * scroll position to the bottom, as it can be jarring for the user. We
226
+ * only auto-scroll if the width remains the same, meaning only the height
227
+ * has changed (likely due to new content being added).
228
+ */
229
+ if (width !== this.#lastStepsContainerWidth) {
230
+ this.#lastStepsContainerWidth = width;
231
+ return;
232
+ }
233
+
234
+ /**
235
+ * We only want to auto-scroll if the walkthrough is "live", which means it's
236
+ * currently loading. If it's not loading, it's a walkthrough for a previous
237
+ * message, and we don't want to jump the user to the bottom if they've
238
+ * scrolled away.
239
+ */
240
+ if (!this.#pinScrollToBottom || !this.#isLoading) {
227
241
  return;
228
242
  }
229
243
 
@@ -338,7 +352,13 @@ export class WalkthroughView extends UI.Widget.Widget {
338
352
 
339
353
  this.#registerResizeObservers();
340
354
 
341
- if (this.#pinScrollToBottom) {
355
+ /**
356
+ * We only want to auto-scroll if the walkthrough is "live", which means it's
357
+ * currently loading. If it's not loading, it's a walkthrough for a previous
358
+ * message, and we don't want to jump the user to the bottom if they've
359
+ * scrolled away.
360
+ */
361
+ if (this.#pinScrollToBottom && this.#isLoading) {
342
362
  this.scrollToBottom();
343
363
  }
344
364
  }
@@ -346,6 +346,13 @@
346
346
  display: flex;
347
347
  gap: var(--sys-size-4);
348
348
  align-items: center;
349
+
350
+ .chevron {
351
+ color: var(--sys-color-primary);
352
+ width: var(--sys-size-8);
353
+ height: var(--sys-size-8);
354
+ margin-left: var(--sys-size-2);
355
+ }
349
356
  }
350
357
 
351
358
 
@@ -372,14 +379,50 @@
372
379
  gap: var(--sys-size-5);
373
380
  }
374
381
 
382
+ .widget-header {
383
+ display: flex;
384
+ justify-content: space-between;
385
+ height: var(--sys-size-11);
386
+ align-items: center;
387
+ background: var(--sys-color-surface5);
388
+ padding: var(--sys-size-2) var(--sys-size-4);
389
+ border-top-left-radius: var(--sys-shape-corner-small);
390
+ border-top-right-radius: var(--sys-shape-corner-small);
391
+
392
+ .widget-name {
393
+ font: var(--sys-typescale-body4-regular);
394
+ }
395
+
396
+ .widget-reveal-container {
397
+ padding: 0;
398
+ background: none;
399
+ border-radius: 0;
400
+ }
401
+ }
402
+
403
+ .widget-reveal-button {
404
+ display: flex;
405
+ align-items: center;
406
+
407
+ devtools-icon {
408
+ margin-left: var(--sys-size-3);
409
+ color: var(--sys-color-primary);
410
+ width: var(--sys-size-8);
411
+ height: var(--sys-size-8);
412
+ }
413
+
414
+ }
415
+
375
416
  .widget-and-revealer-container {
376
417
  width: 100%;
418
+ min-width: var(--sys-size-30);
419
+ max-width: var(--sys-size-33);
377
420
  }
378
421
 
379
422
  .widget-reveal-container {
380
423
  background: var(--sys-color-surface5);
381
- border-bottom-right-radius: var(--sys-shape-corner-medium);
382
- border-bottom-left-radius: var(--sys-shape-corner-medium);
424
+ border-bottom-right-radius: var(--sys-shape-corner-small);
425
+ border-bottom-left-radius: var(--sys-shape-corner-small);
383
426
  padding: 0 var(--sys-size-4) var(--sys-size-4) 0;
384
427
  }
385
428
 
@@ -389,13 +432,25 @@
389
432
  }
390
433
 
391
434
  .widget-content-container {
392
- padding: var(--sys-size-4);
435
+ padding: var(--sys-size-4) var(--sys-size-5);
393
436
  border-top-left-radius: var(--sys-shape-corner-medium);
394
437
  border-top-right-radius: var(--sys-shape-corner-medium);
395
438
  overflow-x: auto;
396
439
  background-color: var(--sys-color-surface3);
397
440
 
398
441
  --override-computed-style-property-white-space: normal;
442
+
443
+ /* When header is present, content follows it and shouldn't have top radii */
444
+ .widget-header+& {
445
+ border-top-left-radius: 0;
446
+ border-top-right-radius: 0;
447
+ }
448
+
449
+ /* When header is present, content is the last child and needs bottom radii */
450
+ .widget-header+&:last-child {
451
+ border-bottom-left-radius: var(--sys-shape-corner-medium);
452
+ border-bottom-right-radius: var(--sys-shape-corner-medium);
453
+ }
399
454
  }
400
455
 
401
456
  .network-request-preview {
@@ -100,25 +100,45 @@
100
100
  .walkthrough-inline {
101
101
  border-radius: var(--sys-size-5);
102
102
  overflow: hidden;
103
- background-color: var(--sys-color-surface1);
103
+ background-color: var(--sys-color-surface2);
104
104
  width: fit-content;
105
105
 
106
106
  &[open] {
107
107
  width: auto;
108
+ background-color: var(--sys-color-surface1);
108
109
  }
109
110
  }
110
111
 
111
112
  .walkthrough-inline > summary {
112
113
  display: flex;
113
114
  align-items: center;
114
- padding: var(--sys-size-4) var(--sys-size-6);
115
+ padding: 0 var(--sys-size-6);
115
116
  cursor: pointer;
116
117
  background-color: transparent;
118
+ /* The same height as a DevTools Button */
119
+ height: var(--sys-size-11);
117
120
  font: var(--sys-typescale-body4-regular);
121
+ font-weight:var(--ref-typeface-weight-medium);
118
122
  user-select: none;
119
123
  list-style: none; /* Hide default triangle */
120
124
  justify-content: flex-start;
121
125
  gap: var(--sys-size-4);
126
+ color: var(--sys-color-primary);
127
+
128
+ devtools-icon {
129
+ color: var(--sys-color-primary);
130
+ }
131
+
132
+ /* Align the summary to look like the tonal button */
133
+ &[data-has-widgets] {
134
+ background: var(--sys-color-tonal-container);
135
+ color: var(--sys-color-on-tonal-container);
136
+ border-radius: var(--sys-shape-corner-full);
137
+
138
+ devtools-icon {
139
+ color: var(--sys-color-on-tonal-container);
140
+ }
141
+ }
122
142
  }
123
143
 
124
144
  .walkthrough-inline > summary::-webkit-details-marker {
@@ -131,8 +151,6 @@
131
151
 
132
152
  .walkthrough-inline .steps-container {
133
153
  padding: var(--sys-size-6);
134
- max-height: 300px; /* Limit height for inline view */
135
- overflow-y: auto;
136
154
  border-top: 1px solid var(--sys-color-divider);
137
155
  background-color: transparent;
138
156
  }
@@ -141,9 +159,21 @@
141
159
  width: var(--sys-size-8);
142
160
  height: var(--sys-size-8);
143
161
  transition: transform 0.2s;
162
+ margin-left: auto;
163
+ }
164
+
165
+ .walkthrough-inline[open] > summary {
166
+ border-radius: var(--sys-shape-corner-medium-small);
167
+ border-bottom-right-radius: 0;
168
+ border-bottom-left-radius: 0;
169
+ background: var(--sys-color-surface5);
170
+ }
171
+
172
+ .walkthrough-inline .step {
173
+ background-color: var(--sys-color-surface5);
144
174
  }
145
175
 
146
176
  .walkthrough-inline[open] > summary > devtools-icon {
147
- transform: rotate(180deg);
177
+ transform: rotate(270deg);
148
178
  }
149
179
  }
@@ -1106,8 +1106,8 @@ export const DEFAULT_VIEW: View = (input, output, target) => {
1106
1106
  ${identityData && onCopyId ? renderIdentity(identityData, onCopyId, output) : nothing}
1107
1107
  ${presentationData ? renderPresentation(presentationData, output) : nothing}
1108
1108
  ${protocolHandlersData ? renderProtocolHandlers(protocolHandlersData, output) : nothing}
1109
- ${iconsData && onToggleIconMasked && maskedIcons ?
1110
- renderIcons(iconsData, maskedIcons, onToggleIconMasked, output) : nothing}
1109
+ ${iconsData && onToggleIconMasked ?
1110
+ renderIcons(iconsData, Boolean(maskedIcons), onToggleIconMasked, output) : nothing}
1111
1111
  ${windowControlsData && output ? renderWindowControlsSection(
1112
1112
  windowControlsData, selectedPlatform, onSelectOs, onToggleWcoToolbar, output) : nothing}
1113
1113
  ${shortcutsData ? renderShortcuts(shortcutsData) : nothing}