chrome-devtools-frontend 1.0.1646286 → 1.0.1649421

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 (57) hide show
  1. package/SECURITY.md +1 -1
  2. package/extension-api/ExtensionAPI.d.ts +26 -0
  3. package/front_end/core/common/Debouncer.ts +9 -1
  4. package/front_end/core/sdk/CSSMatchedStyles.ts +3 -1
  5. package/front_end/core/sdk/CSSMetadata.ts +1 -0
  6. package/front_end/core/sdk/CSSPropertyParserMatchers.ts +65 -0
  7. package/front_end/generated/InspectorBackendCommands.ts +2 -1
  8. package/front_end/generated/SupportedCSSProperties.js +739 -21
  9. package/front_end/generated/protocol.ts +26 -0
  10. package/front_end/models/ai_assistance/AiAgent2.ts +14 -9
  11. package/front_end/models/ai_assistance/agents/AiAgent.ts +10 -3
  12. package/front_end/models/ai_assistance/agents/PerformanceAgent.ts +7 -0
  13. package/front_end/models/ai_assistance/agents/StorageAgent.ts +45 -0
  14. package/front_end/models/ai_assistance/ai_assistance.ts +4 -0
  15. package/front_end/models/ai_assistance/skills/Skill.ts +1 -1
  16. package/front_end/models/ai_assistance/skills/SkillRegistry.ts +2 -0
  17. package/front_end/models/ai_assistance/skills/network.md +16 -0
  18. package/front_end/models/ai_assistance/skills/styling.md +1 -1
  19. package/front_end/models/ai_assistance/tools/GetNetworkRequestDetails.ts +118 -0
  20. package/front_end/models/ai_assistance/tools/ListNetworkRequests.ts +124 -0
  21. package/front_end/models/ai_assistance/tools/Tool.ts +2 -0
  22. package/front_end/models/ai_assistance/tools/ToolRegistry.ts +4 -0
  23. package/front_end/models/emulation/EmulatedDevices.ts +430 -12
  24. package/front_end/models/extensions/ExtensionAPI.ts +47 -21
  25. package/front_end/models/javascript_metadata/NativeFunctions.js +5 -1
  26. package/front_end/panels/ai_assistance/AiAssistancePanel.ts +40 -1
  27. package/front_end/panels/ai_assistance/ai_assistance.ts +1 -0
  28. package/front_end/panels/ai_assistance/components/AIv2MarkdownRenderer.ts +228 -0
  29. package/front_end/panels/ai_assistance/components/ChatMessage.ts +39 -2
  30. package/front_end/panels/application/ApplicationPanelSidebar.ts +4 -0
  31. package/front_end/panels/application/CookieItemsView.ts +55 -6
  32. package/front_end/panels/application/DOMStorageItemsView.ts +43 -8
  33. package/front_end/panels/application/KeyValueStorageItemsView.ts +17 -9
  34. package/front_end/panels/application/WebMCPTreeElement.ts +8 -0
  35. package/front_end/panels/elements/StylePropertyTreeElement.ts +73 -16
  36. package/front_end/panels/elements/StylesAiCodeCompletionProvider.ts +9 -1
  37. package/front_end/panels/elements/StylesSidebarPane.ts +22 -8
  38. package/front_end/panels/layer_viewer/PaintProfilerView.ts +309 -182
  39. package/front_end/panels/layer_viewer/paintProfiler.css +27 -23
  40. package/front_end/panels/layers/LayerPaintProfilerView.ts +86 -29
  41. package/front_end/panels/sources/WatchExpressionsSidebarPane.ts +210 -356
  42. package/front_end/panels/sources/watchExpressionsSidebarPane.css +7 -0
  43. package/front_end/panels/timeline/TimelinePaintProfilerView.ts +5 -5
  44. package/front_end/panels/timeline/components/NetworkTrackWidget.ts +122 -0
  45. package/front_end/panels/timeline/components/components.ts +2 -0
  46. package/front_end/panels/timeline/components/networkTrackWidget.css +31 -0
  47. package/front_end/panels/timeline/timeline.ts +2 -0
  48. package/front_end/third_party/chromium/README.chromium +1 -1
  49. package/front_end/ui/components/buttons/FloatingButton.ts +17 -2
  50. package/front_end/ui/components/buttons/floatingButton.css +2 -2
  51. package/front_end/ui/components/text_editor/AiCodeCompletionProvider.ts +1 -1
  52. package/front_end/ui/legacy/TextPrompt.ts +24 -18
  53. package/front_end/ui/legacy/Treeoutline.ts +31 -4
  54. package/front_end/ui/legacy/components/cookie_table/CookiesTable.ts +50 -24
  55. package/front_end/ui/legacy/components/utils/Linkifier.ts +10 -0
  56. package/front_end/ui/visual_logging/KnownContextValues.ts +13 -0
  57. package/package.json +1 -1
@@ -42,6 +42,11 @@
42
42
  display: flex;
43
43
  }
44
44
 
45
+ li.watch-expression-tree-item > span.tree-element-title {
46
+ display: block;
47
+ width: 100%;
48
+ }
49
+
45
50
  .watch-expression-title:hover,
46
51
  .watch-expression-title:focus-within {
47
52
  padding-right: 26px;
@@ -55,6 +60,8 @@
55
60
  position: relative;
56
61
  flex: auto;
57
62
  min-height: 20px;
63
+ display: block;
64
+ width: 100%;
58
65
  }
59
66
 
60
67
  .watch-expression .name {
@@ -41,8 +41,8 @@ export class TimelinePaintProfilerView extends UI.SplitWidget.SplitWidget {
41
41
  this.imageView = new TimelinePaintImageView();
42
42
  this.logAndImageSplitWidget.setMainWidget(this.imageView);
43
43
 
44
- this.paintProfilerView =
45
- new LayerViewer.PaintProfilerView.PaintProfilerView(this.imageView.showImage.bind(this.imageView));
44
+ this.paintProfilerView = new LayerViewer.PaintProfilerView.PaintProfilerView();
45
+ this.paintProfilerView.showImageCallback = this.imageView.showImage.bind(this.imageView);
46
46
  this.paintProfilerView.addEventListener(
47
47
  LayerViewer.PaintProfilerView.Events.WINDOW_CHANGED, this.onWindowChanged, this);
48
48
  this.setSidebarWidget(this.paintProfilerView);
@@ -142,7 +142,7 @@ export class TimelinePaintProfilerView extends UI.SplitWidget.SplitWidget {
142
142
  }
143
143
 
144
144
  update(): void {
145
- this.logTreeView.setCommandLog([]);
145
+ this.logTreeView.commandLog = [];
146
146
  void this.paintProfilerView.setSnapshotAndLog(null, [], null);
147
147
 
148
148
  let snapshotPromise: Promise<{
@@ -188,7 +188,7 @@ export class TimelinePaintProfilerView extends UI.SplitWidget.SplitWidget {
188
188
  function onCommandLogDone(
189
189
  this: TimelinePaintProfilerView, snapshot: SDK.PaintProfiler.PaintProfilerSnapshot,
190
190
  clipRect: Protocol.DOM.Rect|null, log?: SDK.PaintProfiler.PaintProfilerLogItem[]): void {
191
- this.logTreeView.setCommandLog(log || []);
191
+ this.logTreeView.commandLog = log || [];
192
192
  void this.paintProfilerView.setSnapshotAndLog(snapshot, log || [], clipRect);
193
193
  }
194
194
  }
@@ -202,7 +202,7 @@ export class TimelinePaintProfilerView extends UI.SplitWidget.SplitWidget {
202
202
  }
203
203
 
204
204
  private onWindowChanged(): void {
205
- this.logTreeView.updateWindow(this.paintProfilerView.selectionWindow());
205
+ this.logTreeView.selectionWindow = this.paintProfilerView.selectionWindow();
206
206
  }
207
207
  }
208
208
 
@@ -0,0 +1,122 @@
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 Trace from '../../../models/trace/trace.js';
6
+ import * as PerfUI from '../../../ui/legacy/components/perf_ui/perf_ui.js';
7
+ import * as Lit from '../../../ui/lit/lit.js';
8
+
9
+ import networkTrackWidgetStyles from './networkTrackWidget.css.js';
10
+
11
+ const {html} = Lit;
12
+
13
+ // TODO: Do not redeclare the DataProvider here.
14
+ // To prevent circular dependencies with the parent timeline module (since timeline.ts
15
+ // imports and re-exports components/components.js, which in turn imports this file),
16
+ // we must not import any classes from '../../timeline/timeline.js' here.
17
+ // Instead, we declare a decoupled local `NetworkDataProvider` interface that extends
18
+ // the legacy PerfUI FlameChart data provider, and have the widget accept it.
19
+ export interface NetworkDataProvider extends PerfUI.FlameChart.FlameChartDataProvider {
20
+ setModel(parsedTrace: Trace.TraceModel.ParsedTrace, entityMapper: Trace.EntityMapper.EntityMapper): void;
21
+ setWindowTimes(min: number, max: number): void;
22
+ timelineData(): PerfUI.FlameChart.FlameChartTimelineData;
23
+ preparePopoverElement(index: number): Element|null;
24
+ }
25
+
26
+ export interface NetworkTrackWidgetData {
27
+ parsedTrace: Trace.TraceModel.ParsedTrace|null;
28
+ bounds: Trace.Types.Timing.TraceWindowMicro;
29
+ dataProvider: NetworkDataProvider;
30
+ }
31
+
32
+ export class NetworkTrackWidget extends HTMLElement implements PerfUI.FlameChart.FlameChartDelegate {
33
+ readonly #shadow = this.attachShadow({mode: 'open'});
34
+
35
+ #flameChartContainer = document.createElement('div');
36
+ #flameChart: PerfUI.FlameChart.FlameChart|null = null;
37
+ #dataProvider: NetworkDataProvider|null = null;
38
+ #parsedTrace: Trace.TraceModel.ParsedTrace|null = null;
39
+
40
+ constructor() {
41
+ super();
42
+ this.#flameChartContainer.classList.add('container');
43
+ }
44
+
45
+ set data(data: NetworkTrackWidgetData) {
46
+ const parsedTrace = data.parsedTrace;
47
+ const dataProvider = data.dataProvider;
48
+ if (!parsedTrace || !dataProvider) {
49
+ return;
50
+ }
51
+
52
+ const isDataProviderChanged = dataProvider !== this.#dataProvider;
53
+ this.#dataProvider = dataProvider;
54
+ this.#parsedTrace = parsedTrace;
55
+
56
+ // 1. Render first to mount the container in the DOM so show() succeeds
57
+ this.#render();
58
+
59
+ // 2. Instantiate and show the flame chart in the mounted container
60
+ if (isDataProviderChanged || !this.#flameChart) {
61
+ this.#flameChartContainer.innerHTML = '';
62
+ this.#flameChart = new PerfUI.FlameChart.FlameChart(this.#dataProvider, this);
63
+ this.#flameChart.show(this.#flameChartContainer, undefined, true);
64
+ }
65
+
66
+ const entityMapper = Trace.EntityMapper.EntityMapper.getOrCreate(parsedTrace);
67
+ // Override preparePopoverElement to always return null, preventing any hover tooltips
68
+ // from being generated or rendered when the user hovers over the network request blocks.
69
+ this.#dataProvider.preparePopoverElement = () => null;
70
+ this.#dataProvider.setModel(parsedTrace, entityMapper);
71
+ const timelineData = this.#dataProvider.timelineData();
72
+ // Clearing the groups array hides the collapsible "Network" track header on the left.
73
+ // When the groups array is empty, the FlameChart component directly renders the track
74
+ // contents (the network requests) starting right from the top of the canvas.
75
+ timelineData.groups = [];
76
+
77
+ const bounds = Trace.Helpers.Timing.traceWindowMicroSecondsToMilliSeconds({
78
+ min: Trace.Types.Timing.Micro(data.bounds.min),
79
+ max: Trace.Types.Timing.Micro(data.bounds.max),
80
+ range: Trace.Types.Timing.Micro(data.bounds.range),
81
+ });
82
+
83
+ this.#dataProvider.setWindowTimes(bounds.min, bounds.max);
84
+ this.#flameChart.setWindowTimes(bounds.min, bounds.max);
85
+ this.#render();
86
+ }
87
+
88
+ #render(): void {
89
+ if (!this.#parsedTrace) {
90
+ return;
91
+ }
92
+ const output = html`
93
+ <style>${networkTrackWidgetStyles}</style>
94
+ ${this.#flameChartContainer}
95
+ `;
96
+ // eslint-disable-next-line @devtools/no-lit-render-outside-of-view
97
+ Lit.render(output, this.#shadow, {host: this});
98
+
99
+ if (this.#flameChart) {
100
+ this.#flameChart.update();
101
+ }
102
+ }
103
+
104
+ windowChanged(_windowStartTime: number, _windowEndTime: number, _animate: boolean): void {
105
+ }
106
+
107
+ updateRangeSelection(_startTime: number, _endTime: number): void {
108
+ }
109
+
110
+ updateSelectedGroup(_flameChart: PerfUI.FlameChart.FlameChart, _group: PerfUI.FlameChart.Group|null): void {
111
+ }
112
+ }
113
+
114
+ if (!customElements.get('devtools-performance-agent-network-track')) {
115
+ customElements.define('devtools-performance-agent-network-track', NetworkTrackWidget);
116
+ }
117
+
118
+ declare global {
119
+ interface HTMLElementTagNameMap {
120
+ 'devtools-performance-agent-network-track': NetworkTrackWidget;
121
+ }
122
+ }
@@ -17,6 +17,7 @@ import * as MetricCard from './MetricCard.js';
17
17
  import * as NetworkRequestDetails from './NetworkRequestDetails.js';
18
18
  import * as NetworkRequestTooltip from './NetworkRequestTooltip.js';
19
19
  import * as NetworkThrottlingSelector from './NetworkThrottlingSelector.js';
20
+ import * as NetworkTrackWidget from './NetworkTrackWidget.js';
20
21
  import * as OriginMap from './OriginMap.js';
21
22
  import * as RelatedInsightChips from './RelatedInsightChips.js';
22
23
  import * as Sidebar from './Sidebar.js';
@@ -43,6 +44,7 @@ export {
43
44
  NetworkRequestDetails,
44
45
  NetworkRequestTooltip,
45
46
  NetworkThrottlingSelector,
47
+ NetworkTrackWidget,
46
48
  OriginMap,
47
49
  RelatedInsightChips,
48
50
  Sidebar,
@@ -0,0 +1,31 @@
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
+ :host {
6
+ display: flex;
7
+ }
8
+
9
+ .container {
10
+ display: flex;
11
+ width: 100%;
12
+ height: 150px;
13
+ background-color: var(--sys-color-cdt-base-container);
14
+ border-radius: 8px;
15
+ border: 1px solid var(--sys-color-divider);
16
+ }
17
+
18
+ .container canvas {
19
+ /* stylelint-disable-next-line declaration-no-important */
20
+ pointer-events: none !important;
21
+ }
22
+
23
+ .flex-auto {
24
+ flex: auto;
25
+ }
26
+
27
+ .vbox {
28
+ display: flex;
29
+ flex-direction: column;
30
+ position: relative;
31
+ }
@@ -7,6 +7,7 @@ import * as AnnotationHelpers from './AnnotationHelpers.js';
7
7
  import * as AppenderUtils from './AppenderUtils.js';
8
8
  import * as BenchmarkEvents from './BenchmarkEvents.js';
9
9
  import * as CompatibilityTracksAppender from './CompatibilityTracksAppender.js';
10
+ import * as NetworkTrackWidget from './components/NetworkTrackWidget.js';
10
11
  import * as CountersGraph from './CountersGraph.js';
11
12
  import * as EntriesFilter from './EntriesFilter.js';
12
13
  import * as EventsTimelineTreeView from './EventsTimelineTreeView.js';
@@ -61,6 +62,7 @@ export {
61
62
  LayoutShiftsTrackAppender,
62
63
  ModificationsManager,
63
64
  NetworkTrackAppender,
65
+ NetworkTrackWidget,
64
66
  RecordingMetadata,
65
67
  SaveFileFormatter,
66
68
  TargetForEvent,
@@ -1,7 +1,7 @@
1
1
  Name: Dependencies sourced from the upstream `chromium` repository
2
2
  URL: Internal
3
3
  Version: N/A
4
- Revision: e32418f5632b194cc1b5a5a0c0b1510d33edcc15
4
+ Revision: 619f853fcd9eb9a1f7b73de8a235a34248fd4a88
5
5
  Update Mechanism: Manual (https://crbug.com/428069060)
6
6
  License: BSD-3-Clause
7
7
  License File: LICENSE
@@ -38,7 +38,7 @@ const {html, Directives: {classMap}} = Lit;
38
38
  * provided context.
39
39
  */
40
40
  export class FloatingButton extends HTMLElement {
41
- static readonly observedAttributes = ['icon-name', 'jslogcontext'];
41
+ static readonly observedAttributes = ['icon-name', 'jslogcontext', 'disabled'];
42
42
 
43
43
  readonly #shadow = this.attachShadow({mode: 'open'});
44
44
 
@@ -83,6 +83,18 @@ export class FloatingButton extends HTMLElement {
83
83
  }
84
84
  }
85
85
 
86
+ get disabled(): boolean {
87
+ return this.hasAttribute('disabled');
88
+ }
89
+
90
+ set disabled(disabled: boolean) {
91
+ if (disabled) {
92
+ this.setAttribute('disabled', '');
93
+ } else {
94
+ this.removeAttribute('disabled');
95
+ }
96
+ }
97
+
86
98
  attributeChangedCallback(name: string, oldValue: string|null, newValue: string|null): void {
87
99
  if (oldValue === newValue) {
88
100
  return;
@@ -93,6 +105,9 @@ export class FloatingButton extends HTMLElement {
93
105
  if (name === 'jslogcontext') {
94
106
  this.#updateJslog();
95
107
  }
108
+ if (name === 'disabled') {
109
+ this.#render();
110
+ }
96
111
  }
97
112
 
98
113
  #render(): void {
@@ -103,7 +118,7 @@ export class FloatingButton extends HTMLElement {
103
118
  // clang-format off
104
119
  Lit.render(html`
105
120
  <style>${floatingButtonStyles}</style>
106
- <button class=${classes}><devtools-icon .name=${this.iconName}></devtools-icon></button>`,
121
+ <button class=${classes} ?disabled=${this.disabled}><devtools-icon .name=${this.iconName}></devtools-icon></button>`,
107
122
  this.#shadow, {host: this});
108
123
  // clang-format on
109
124
  }
@@ -36,11 +36,11 @@ button {
36
36
  height: var(--sys-size-7);
37
37
  }
38
38
 
39
- :host-context(:not(.theme-with-dark-background)) & > devtools-icon {
39
+ &:not(:disabled):host-context(:not(.theme-with-dark-background)) & > devtools-icon {
40
40
  color: var(--sys-color-on-tonal-container);
41
41
  }
42
42
 
43
- :host-context(.theme-with-dark-background) & > devtools-icon {
43
+ &:not(:disabled):host-context(.theme-with-dark-background) & > devtools-icon {
44
44
  color: var(--sys-color-on-primary);
45
45
  }
46
46
 
@@ -61,7 +61,7 @@ export interface AiCodeCompletionConfig {
61
61
 
62
62
  export const DELAY_BEFORE_SHOWING_RESPONSE_MS = 500;
63
63
  export const AIDA_REQUEST_DEBOUNCE_TIMEOUT_MS = 200;
64
- const MAX_PREFIX_SUFFIX_LENGTH = 20_000;
64
+ export const MAX_PREFIX_SUFFIX_LENGTH = 20_000;
65
65
 
66
66
  export class AiCodeCompletionProvider {
67
67
  #aidaClient: Host.AidaClient.AidaClient = new Host.AidaClient.AidaClient();
@@ -65,6 +65,7 @@ import {cloneCustomElement, ElementFocusRestorer} from './UIUtils.js';
65
65
  * @attribute completions Sets the `id` of the <datalist> containing the autocomplete options.
66
66
  * @attribute placeholder Sets a placeholder that's shown in place of the text contents when editing if the text is too
67
67
  * large.
68
+ * @attribute value Sets the initial text value that's edited when editing starts. If not provided, the slot's inner text is used.
68
69
  */
69
70
  export class TextPromptElement extends HTMLElement {
70
71
  static readonly observedAttributes = ['editing', 'completions', 'placeholder', 'cancel-on-blur'];
@@ -189,10 +190,11 @@ export class TextPromptElement extends HTMLElement {
189
190
  #startEditing(): void {
190
191
  const truncatedTextPlaceholder = this.getAttribute('placeholder');
191
192
  const placeholder = this.#entrypoint.createChild('span');
193
+ const initialText = this.getAttribute('value') ?? this.#slot.deepInnerText();
192
194
  if (truncatedTextPlaceholder === null) {
193
- placeholder.textContent = this.#slot.deepInnerText();
195
+ placeholder.textContent = initialText;
194
196
  } else {
195
- placeholder.setTextContentTruncatedIfNeeded(this.#slot.deepInnerText(), truncatedTextPlaceholder);
197
+ placeholder.setTextContentTruncatedIfNeeded(initialText, truncatedTextPlaceholder);
196
198
  }
197
199
  this.#slot.remove();
198
200
 
@@ -302,7 +304,7 @@ export class TextPrompt extends Common.ObjectWrapper.ObjectWrapper<EventTypes> i
302
304
  #title: string;
303
305
  protected queryRange: TextUtils.TextRange.TextRange|null;
304
306
  private previousText: string;
305
- private currentSuggestion: Suggestion|null;
307
+ #currentSuggestion: Suggestion|null;
306
308
  private completionRequestId: number;
307
309
  private ghostTextElement: HTMLSpanElement;
308
310
  private leftParenthesesIndices: number[];
@@ -337,7 +339,7 @@ export class TextPrompt extends Common.ObjectWrapper.ObjectWrapper<EventTypes> i
337
339
  this.#title = '';
338
340
  this.queryRange = null;
339
341
  this.previousText = '';
340
- this.currentSuggestion = null;
342
+ this.#currentSuggestion = null;
341
343
  this.completionRequestId = 0;
342
344
  this.ghostTextElement = document.createElement('span');
343
345
  this.ghostTextElement.classList.add('auto-complete-text');
@@ -458,10 +460,10 @@ export class TextPrompt extends Common.ObjectWrapper.ObjectWrapper<EventTypes> i
458
460
 
459
461
  textWithCurrentSuggestion(): string {
460
462
  const text = this.text();
461
- if (!this.queryRange || !this.currentSuggestion) {
463
+ if (!this.queryRange || !this.#currentSuggestion) {
462
464
  return text;
463
465
  }
464
- const suggestion = this.currentSuggestion.text;
466
+ const suggestion = this.#currentSuggestion.text;
465
467
  return text.substring(0, this.queryRange.startColumn) + suggestion + text.substring(this.queryRange.endColumn);
466
468
  }
467
469
 
@@ -623,7 +625,7 @@ export class TextPrompt extends Common.ObjectWrapper.ObjectWrapper<EventTypes> i
623
625
  }
624
626
  break;
625
627
  case 'Escape':
626
- if (this.isSuggestBoxVisible() || this.currentSuggestion) {
628
+ if (this.isSuggestBoxVisible() || this.#currentSuggestion) {
627
629
  this.clearAutocomplete();
628
630
  handled = true;
629
631
  }
@@ -646,14 +648,14 @@ export class TextPrompt extends Common.ObjectWrapper.ObjectWrapper<EventTypes> i
646
648
  }
647
649
 
648
650
  private acceptSuggestionOnStopCharacters(key: string): boolean {
649
- if (!this.currentSuggestion || !this.queryRange || key.length !== 1 ||
651
+ if (!this.#currentSuggestion || !this.queryRange || key.length !== 1 ||
650
652
  !this.completionStopCharacters?.includes(key) ||
651
- this.currentSuggestion.disableAcceptSuggestionOnStopCharacters) {
653
+ this.#currentSuggestion.disableAcceptSuggestionOnStopCharacters) {
652
654
  return false;
653
655
  }
654
656
 
655
657
  const query = this.text().substring(this.queryRange.startColumn, this.queryRange.endColumn);
656
- if (query && this.currentSuggestion.text.startsWith(query + key)) {
658
+ if (query && this.#currentSuggestion.text.startsWith(query + key)) {
657
659
  this.queryRange.endColumn += 1;
658
660
  return this.acceptAutoComplete();
659
661
  }
@@ -728,7 +730,7 @@ export class TextPrompt extends Common.ObjectWrapper.ObjectWrapper<EventTypes> i
728
730
  if (beforeText !== this.textWithCurrentSuggestion()) {
729
731
  this.dispatchEventToListeners(Events.TEXT_CHANGED);
730
732
  }
731
- this.currentSuggestion = null;
733
+ this.#currentSuggestion = null;
732
734
  }
733
735
 
734
736
  private onBlur(): void {
@@ -736,14 +738,14 @@ export class TextPrompt extends Common.ObjectWrapper.ObjectWrapper<EventTypes> i
736
738
  }
737
739
 
738
740
  private refreshGhostText(): void {
739
- if (this.currentSuggestion?.hideGhostText) {
741
+ if (this.#currentSuggestion?.hideGhostText) {
740
742
  this.ghostTextElement.remove();
741
743
  return;
742
744
  }
743
- if (this.queryRange && this.currentSuggestion && this.isCaretAtEndOfPrompt() &&
744
- this.currentSuggestion.text.startsWith(this.text().substring(this.queryRange.startColumn))) {
745
+ if (this.queryRange && this.#currentSuggestion && this.isCaretAtEndOfPrompt() &&
746
+ this.#currentSuggestion.text.startsWith(this.text().substring(this.queryRange.startColumn))) {
745
747
  this.ghostTextElement.textContent =
746
- this.currentSuggestion.text.substring(this.queryRange.endColumn - this.queryRange.startColumn);
748
+ this.#currentSuggestion.text.substring(this.queryRange.endColumn - this.queryRange.startColumn);
747
749
  this.element().appendChild(this.ghostTextElement);
748
750
  } else {
749
751
  this.ghostTextElement.remove();
@@ -875,7 +877,7 @@ export class TextPrompt extends Common.ObjectWrapper.ObjectWrapper<EventTypes> i
875
877
  }
876
878
 
877
879
  applySuggestion(suggestion: Suggestion|null, isIntermediateSuggestion?: boolean): void {
878
- this.currentSuggestion = suggestion;
880
+ this.#currentSuggestion = suggestion;
879
881
  this.refreshGhostText();
880
882
  if (isIntermediateSuggestion) {
881
883
  this.dispatchEventToListeners(Events.TEXT_CHANGED);
@@ -891,8 +893,8 @@ export class TextPrompt extends Common.ObjectWrapper.ObjectWrapper<EventTypes> i
891
893
  return false;
892
894
  }
893
895
 
894
- const suggestionLength = this.currentSuggestion ? this.currentSuggestion.text.length : 0;
895
- const selectionRange = this.currentSuggestion ? this.currentSuggestion.selectionRange : null;
896
+ const suggestionLength = this.#currentSuggestion ? this.#currentSuggestion.text.length : 0;
897
+ const selectionRange = this.#currentSuggestion ? this.#currentSuggestion.selectionRange : null;
896
898
  const endColumn = selectionRange ? selectionRange.endColumn : suggestionLength;
897
899
  const startColumn = selectionRange ? selectionRange.startColumn : suggestionLength;
898
900
  this.element().textContent = this.textWithCurrentSuggestion();
@@ -1043,6 +1045,10 @@ export class TextPrompt extends Common.ObjectWrapper.ObjectWrapper<EventTypes> i
1043
1045
  suggestBoxForTest(): SuggestBox|undefined {
1044
1046
  return this.suggestBox;
1045
1047
  }
1048
+
1049
+ currentSuggestion(): Suggestion|null {
1050
+ return this.#currentSuggestion;
1051
+ }
1046
1052
  }
1047
1053
 
1048
1054
  const DefaultAutocompletionTimeout = 250;
@@ -409,11 +409,11 @@ export class TreeOutlineInShadow extends TreeOutline {
409
409
  shadowRoot: ShadowRoot;
410
410
  private readonly disclosureElement: Element;
411
411
  override renderSelection: boolean;
412
- constructor(variant: TreeVariant = TreeVariant.OTHER, element?: HTMLElement) {
412
+ constructor(variant: TreeVariant = TreeVariant.OTHER, element?: HTMLElement, delegatesFocus?: boolean) {
413
413
  super();
414
414
  this.contentElement.classList.add('tree-outline');
415
415
  this.element = element ?? document.createElement('div');
416
- this.shadowRoot = createShadowRootWithCoreStyles(this.element, {cssFile: treeoutlineStyles});
416
+ this.shadowRoot = createShadowRootWithCoreStyles(this.element, {cssFile: treeoutlineStyles, delegatesFocus});
417
417
  this.disclosureElement = this.shadowRoot.createChild('div', 'tree-outline-disclosure');
418
418
  this.disclosureElement.appendChild(this.contentElement);
419
419
  this.renderSelection = true;
@@ -1542,6 +1542,7 @@ class TreeViewTreeElement extends TreeElement {
1542
1542
  #userExpanded = false;
1543
1543
  #isProcessingAttribute = false;
1544
1544
  #previousOpenAttributeValue?: string|null;
1545
+ #refreshScheduled = false;
1545
1546
 
1546
1547
  static #elementToTreeElement = new WeakMap<Node, TreeViewTreeElement>();
1547
1548
  readonly configElement: HTMLLIElement;
@@ -1589,6 +1590,17 @@ class TreeViewTreeElement extends TreeElement {
1589
1590
  }
1590
1591
  }
1591
1592
 
1593
+ refreshSoon(): void {
1594
+ if (this.#refreshScheduled) {
1595
+ return;
1596
+ }
1597
+ this.#refreshScheduled = true;
1598
+ queueMicrotask(() => {
1599
+ this.#refreshScheduled = false;
1600
+ this.refresh();
1601
+ });
1602
+ }
1603
+
1592
1604
  refresh(): void {
1593
1605
  const expandable = Boolean(this.configElement.querySelector('ul[role="group"]'));
1594
1606
  this.setExpandable(expandable);
@@ -1754,10 +1766,17 @@ function removeNode(node: TreeElement, preserveParentExpandable = false): void {
1754
1766
  export class TreeViewElement extends HTMLElementWithLightDOMTemplate {
1755
1767
  static readonly observedAttributes =
1756
1768
  ['navigation-variant', 'hide-overflow', 'dense', 'show-selection-on-keyboard-focus'];
1757
- readonly #treeOutline = new TreeOutlineInShadow(undefined, this);
1769
+ readonly #treeOutline = new TreeOutlineInShadow(undefined, this, true);
1758
1770
 
1759
1771
  constructor() {
1760
1772
  super();
1773
+ this.addEventListener('focusin', (event: Event) => {
1774
+ const actualTarget = event.composedPath()[0];
1775
+ if (actualTarget === this.#treeOutline.contentElement && !this.#treeOutline.selectedTreeElement &&
1776
+ this.#treeOutline.firstChild()) {
1777
+ this.#treeOutline.firstChild()?.select(/* omitFocus */ true, /* selectedByUser */ false);
1778
+ }
1779
+ });
1761
1780
  this.#treeOutline.addEventListener(Events.ElementSelected, event => {
1762
1781
  if (event.data instanceof TreeViewTreeElement) {
1763
1782
  event.data.listItemElement.dispatchEvent(new TreeViewElement.SelectEvent());
@@ -1784,6 +1803,14 @@ export class TreeViewElement extends HTMLElementWithLightDOMTemplate {
1784
1803
  return this.#treeOutline;
1785
1804
  }
1786
1805
 
1806
+ override focus(): void {
1807
+ if (!this.#treeOutline.selectedTreeElement && this.#treeOutline.firstChild()) {
1808
+ this.#treeOutline.firstChild()?.select(/* omitFocus */ true, /* selectedByUser */ false);
1809
+ } else {
1810
+ this.#treeOutline.focus();
1811
+ }
1812
+ }
1813
+
1787
1814
  #getParentTreeElement(element: HTMLLIElement|TreeElementWrapper):
1788
1815
  {treeElement: TreeElement, expanded: boolean, classes: DOMTokenList}|null {
1789
1816
  const subtreeRoot = element.parentElement;
@@ -1813,7 +1840,7 @@ export class TreeViewElement extends HTMLElementWithLightDOMTemplate {
1813
1840
  if (!treeElement) {
1814
1841
  return;
1815
1842
  }
1816
- treeElement.refresh();
1843
+ treeElement.refreshSoon();
1817
1844
  if (node === treeNode && attributeName === 'selected' && hasBooleanAttribute(treeNode, 'selected')) {
1818
1845
  treeElement.revealAndSelect(true);
1819
1846
  }