chrome-devtools-frontend 1.0.1515988 → 1.0.1518653

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 (122) hide show
  1. package/docs/checklist/README.md +2 -2
  2. package/docs/checklist/javascript.md +1 -1
  3. package/docs/contributing/README.md +1 -1
  4. package/docs/contributing/settings-experiments-features.md +9 -8
  5. package/docs/cookbook/devtools_on_devtools.md +2 -2
  6. package/docs/cookbook/localization.md +10 -10
  7. package/docs/devtools-protocol.md +9 -8
  8. package/docs/ecosystem/automatic_workspace_folders.md +3 -3
  9. package/docs/get_the_code.md +0 -2
  10. package/docs/styleguide/ux/components.md +166 -85
  11. package/docs/styleguide/ux/numbers.md +3 -4
  12. package/front_end/core/common/README.md +13 -12
  13. package/front_end/core/host/GdpClient.ts +16 -1
  14. package/front_end/core/host/UserMetrics.ts +8 -2
  15. package/front_end/core/root/Runtime.ts +13 -0
  16. package/front_end/core/sdk/CSSMatchedStyles.ts +5 -1
  17. package/front_end/entrypoints/main/MainImpl.ts +6 -3
  18. package/front_end/generated/InspectorBackendCommands.js +10 -7
  19. package/front_end/generated/SupportedCSSProperties.js +21 -7
  20. package/front_end/generated/protocol-mapping.d.ts +16 -1
  21. package/front_end/generated/protocol-proxy-api.d.ts +13 -1
  22. package/front_end/generated/protocol.ts +95 -0
  23. package/front_end/models/ai_assistance/agents/PerformanceAgent.ts +170 -54
  24. package/front_end/models/ai_assistance/data_formatters/PerformanceInsightFormatter.snapshot.txt +14 -181
  25. package/front_end/models/ai_assistance/data_formatters/PerformanceInsightFormatter.ts +13 -315
  26. package/front_end/models/ai_assistance/data_formatters/PerformanceTraceFormatter.snapshot.txt +224 -50
  27. package/front_end/models/ai_assistance/data_formatters/PerformanceTraceFormatter.ts +310 -11
  28. package/front_end/models/ai_assistance/performance/AIContext.ts +15 -2
  29. package/front_end/models/ai_code_completion/AiCodeCompletion.ts +41 -19
  30. package/front_end/models/badges/Badge.ts +8 -3
  31. package/front_end/models/badges/CodeWhispererBadge.ts +2 -4
  32. package/front_end/models/badges/StarterBadge.ts +2 -2
  33. package/front_end/models/badges/UserBadges.ts +59 -6
  34. package/front_end/models/formatter/FormatterWorkerPool.ts +3 -3
  35. package/front_end/models/javascript_metadata/NativeFunctions.js +1 -1
  36. package/front_end/models/trace/README.md +28 -1
  37. package/front_end/models/trace/handlers/UserTimingsHandler.ts +1 -1
  38. package/front_end/models/trace/helpers/Trace.ts +99 -43
  39. package/front_end/models/trace/types/TraceEvents.ts +9 -0
  40. package/front_end/panels/accessibility/ARIAAttributesView.ts +113 -191
  41. package/front_end/panels/accessibility/AccessibilityNodeView.ts +9 -9
  42. package/front_end/panels/accessibility/AccessibilitySubPane.ts +6 -4
  43. package/front_end/panels/accessibility/accessibilityProperties.css +2 -0
  44. package/front_end/panels/ai_assistance/AiAssistancePanel.ts +16 -2
  45. package/front_end/panels/ai_assistance/components/ChatView.ts +9 -10
  46. package/front_end/panels/ai_assistance/components/PerformanceAgentMarkdownRenderer.ts +42 -0
  47. package/front_end/panels/common/AiCodeCompletionDisclaimer.ts +32 -9
  48. package/front_end/panels/common/AiCodeCompletionSummaryToolbar.ts +7 -1
  49. package/front_end/panels/common/BadgeNotification.ts +67 -15
  50. package/front_end/panels/common/GdpSignUpDialog.ts +18 -9
  51. package/front_end/panels/console/ConsolePrompt.ts +1 -1
  52. package/front_end/panels/console/ConsoleView.ts +6 -2
  53. package/front_end/panels/elements/ComputedStyleWidget.ts +1 -2
  54. package/front_end/panels/elements/ElementsPanel.ts +4 -0
  55. package/front_end/panels/elements/ElementsTreeElement.ts +18 -0
  56. package/front_end/panels/elements/ElementsTreeOutline.ts +13 -0
  57. package/front_end/panels/elements/LayoutPane.ts +1 -1
  58. package/front_end/panels/elements/StylePropertyTreeElement.ts +21 -6
  59. package/front_end/panels/media/TickingFlameChart.ts +1 -1
  60. package/front_end/panels/network/NetworkLogView.ts +5 -1
  61. package/front_end/panels/profiler/HeapSnapshotView.ts +34 -19
  62. package/front_end/panels/search/SearchResultsPane.ts +126 -145
  63. package/front_end/panels/search/SearchView.ts +43 -59
  64. package/front_end/panels/settings/components/SyncSection.ts +16 -8
  65. package/front_end/panels/sources/AiCodeCompletionPlugin.ts +6 -1
  66. package/front_end/panels/sources/OutlineQuickOpen.ts +3 -1
  67. package/front_end/panels/sources/SourcesPanel.ts +3 -0
  68. package/front_end/panels/timeline/AppenderUtils.ts +2 -2
  69. package/front_end/panels/timeline/ExtensionTrackAppender.ts +13 -4
  70. package/front_end/panels/timeline/GPUTrackAppender.ts +2 -1
  71. package/front_end/panels/timeline/InteractionsTrackAppender.ts +5 -1
  72. package/front_end/panels/timeline/LayoutShiftsTrackAppender.ts +2 -1
  73. package/front_end/panels/timeline/ThreadAppender.ts +12 -3
  74. package/front_end/panels/timeline/TimelineFlameChartDataProvider.ts +9 -4
  75. package/front_end/panels/timeline/TimelinePanel.ts +3 -2
  76. package/front_end/panels/timeline/TimelineUIUtils.ts +18 -12
  77. package/front_end/panels/timeline/TimingsTrackAppender.ts +6 -1
  78. package/front_end/panels/timeline/components/CPUThrottlingSelector.ts +95 -82
  79. package/front_end/panels/timeline/components/LiveMetricsView.ts +2 -2
  80. package/front_end/panels/timeline/components/cpuThrottlingSelector.css +17 -15
  81. package/front_end/panels/timeline/components/insights/BaseInsightComponent.ts +3 -0
  82. package/front_end/third_party/chromium/README.chromium +1 -1
  83. package/front_end/third_party/codemirror.next/chunk/codemirror.js +1 -1
  84. package/front_end/third_party/codemirror.next/chunk/codemirror.js.map +1 -1
  85. package/front_end/third_party/codemirror.next/codemirror.next.d.ts +6 -9
  86. package/front_end/third_party/codemirror.next/package.json +2 -1
  87. package/front_end/third_party/diff/README.chromium +1 -0
  88. package/front_end/third_party/puppeteer/README.chromium +2 -2
  89. package/front_end/third_party/puppeteer/package/lib/cjs/puppeteer/bidi/core/Realm.d.ts +2 -2
  90. package/front_end/third_party/puppeteer/package/lib/cjs/puppeteer/cdp/Accessibility.js +0 -20
  91. package/front_end/third_party/puppeteer/package/lib/cjs/puppeteer/cdp/Accessibility.js.map +1 -1
  92. package/front_end/third_party/puppeteer/package/lib/cjs/puppeteer/generated/version.d.ts +1 -1
  93. package/front_end/third_party/puppeteer/package/lib/cjs/puppeteer/generated/version.js +1 -1
  94. package/front_end/third_party/puppeteer/package/lib/cjs/puppeteer/injected/injected.d.ts +1 -1
  95. package/front_end/third_party/puppeteer/package/lib/cjs/puppeteer/revisions.d.ts +1 -1
  96. package/front_end/third_party/puppeteer/package/lib/cjs/puppeteer/revisions.js +1 -1
  97. package/front_end/third_party/puppeteer/package/lib/cjs/puppeteer/revisions.js.map +1 -1
  98. package/front_end/third_party/puppeteer/package/lib/cjs/puppeteer/util/Mutex.d.ts +2 -2
  99. package/front_end/third_party/puppeteer/package/lib/es5-iife/puppeteer-core-browser.js +2 -23
  100. package/front_end/third_party/puppeteer/package/lib/esm/puppeteer/cdp/Accessibility.js +0 -20
  101. package/front_end/third_party/puppeteer/package/lib/esm/puppeteer/cdp/Accessibility.js.map +1 -1
  102. package/front_end/third_party/puppeteer/package/lib/esm/puppeteer/generated/version.d.ts +1 -1
  103. package/front_end/third_party/puppeteer/package/lib/esm/puppeteer/generated/version.js +1 -1
  104. package/front_end/third_party/puppeteer/package/lib/esm/puppeteer/revisions.d.ts +1 -1
  105. package/front_end/third_party/puppeteer/package/lib/esm/puppeteer/revisions.js +1 -1
  106. package/front_end/third_party/puppeteer/package/lib/esm/puppeteer/revisions.js.map +1 -1
  107. package/front_end/third_party/puppeteer/package/package.json +1 -1
  108. package/front_end/third_party/puppeteer/package/src/cdp/Accessibility.ts +1 -21
  109. package/front_end/third_party/puppeteer/package/src/generated/version.ts +1 -1
  110. package/front_end/third_party/puppeteer/package/src/revisions.ts +1 -1
  111. package/front_end/ui/components/text_editor/config.ts +36 -8
  112. package/front_end/ui/components/tooltips/Tooltip.ts +71 -34
  113. package/front_end/ui/legacy/README.md +33 -24
  114. package/front_end/ui/legacy/SearchableView.ts +19 -26
  115. package/front_end/ui/legacy/TextPrompt.ts +166 -1
  116. package/front_end/ui/legacy/Treeoutline.ts +16 -2
  117. package/front_end/ui/legacy/UIUtils.ts +15 -2
  118. package/front_end/ui/legacy/XElement.ts +0 -43
  119. package/front_end/ui/legacy/components/perf_ui/FlameChart.ts +20 -4
  120. package/front_end/ui/visual_logging/KnownContextValues.ts +24 -6
  121. package/front_end/ui/visual_logging/README.md +43 -27
  122. package/package.json +1 -1
@@ -21,6 +21,13 @@ interface PositioningParams {
21
21
  currentPopoverRect: DOMRect;
22
22
  }
23
23
 
24
+ enum PositionOption {
25
+ BOTTOM_SPAN_RIGHT = 'bottom-span-right',
26
+ BOTTOM_SPAN_LEFT = 'bottom-span-left',
27
+ TOP_SPAN_RIGHT = 'top-span-right',
28
+ TOP_SPAN_LEFT = 'top-span-left',
29
+ }
30
+
24
31
  const positioningUtils = {
25
32
  bottomSpanRight: ({anchorRect}: PositioningParams): ProposedRect => {
26
33
  return {
@@ -93,36 +100,50 @@ const positioningUtils = {
93
100
  }
94
101
  };
95
102
 
96
- const proposedRectForRichTooltip =
97
- ({inspectorViewRect, anchorRect, currentPopoverRect}:
98
- {inspectorViewRect: DOMRect, anchorRect: DOMRect, currentPopoverRect: DOMRect}): ProposedRect => {
99
- // Tries the default positioning of bottom right, bottom left, top right and top left.
100
- // If they don't work out, we default back to showing in bottom right and adjust its insets so that the popover is inside the inspector view bounds.
101
- let proposedRect = positioningUtils.bottomSpanRight({anchorRect, currentPopoverRect});
102
- if (positioningUtils.isInBounds({inspectorViewRect, currentPopoverRect, proposedRect})) {
103
- return proposedRect;
104
- }
105
-
106
- proposedRect = positioningUtils.bottomSpanLeft({anchorRect, currentPopoverRect});
107
- if (positioningUtils.isInBounds({inspectorViewRect, currentPopoverRect, proposedRect})) {
108
- return proposedRect;
109
- }
110
-
111
- proposedRect = positioningUtils.topSpanRight({anchorRect, currentPopoverRect});
112
- if (positioningUtils.isInBounds({inspectorViewRect, currentPopoverRect, proposedRect})) {
113
- return proposedRect;
114
- }
115
-
116
- proposedRect = positioningUtils.topSpanLeft({anchorRect, currentPopoverRect});
117
- if (positioningUtils.isInBounds({inspectorViewRect, currentPopoverRect, proposedRect})) {
118
- return proposedRect;
119
- }
103
+ const proposedRectForRichTooltip = ({inspectorViewRect, anchorRect, currentPopoverRect, preferredPositions}: {
104
+ inspectorViewRect: DOMRect,
105
+ anchorRect: DOMRect,
106
+ currentPopoverRect: DOMRect,
107
+ preferredPositions: PositionOption[],
108
+ }): ProposedRect => {
109
+ // The default positioning order is `BOTTOM_SPAN_RIGHT`, `BOTTOM_SPAN_LEFT`, `TOP_SPAN_RIGHT`
110
+ // and `TOP_SPAN_LEFT`. If `preferredPositions` are given, those are tried first, before
111
+ // continuing with the remaining options in default order. Duplicate entries are removed.
112
+ const uniqueOrder = [
113
+ ...new Set([
114
+ ...preferredPositions,
115
+ ...Object.values(PositionOption),
116
+ ]),
117
+ ];
118
+
119
+ // Tries the positioning options in the order given by `uniqueOrder`.
120
+ // If none of them work out, we default to showing the tooltip in the bottom right and adjust
121
+ // its insets so that the tooltip is inside the inspector view bounds.
122
+ for (const positionOption of uniqueOrder) {
123
+ let proposedRect;
124
+ switch (positionOption) {
125
+ case PositionOption.BOTTOM_SPAN_RIGHT:
126
+ proposedRect = positioningUtils.bottomSpanRight({anchorRect, currentPopoverRect});
127
+ break;
128
+ case PositionOption.BOTTOM_SPAN_LEFT:
129
+ proposedRect = positioningUtils.bottomSpanLeft({anchorRect, currentPopoverRect});
130
+ break;
131
+ case PositionOption.TOP_SPAN_RIGHT:
132
+ proposedRect = positioningUtils.topSpanRight({anchorRect, currentPopoverRect});
133
+ break;
134
+ case PositionOption.TOP_SPAN_LEFT:
135
+ proposedRect = positioningUtils.topSpanLeft({anchorRect, currentPopoverRect});
136
+ }
137
+ if (positioningUtils.isInBounds({inspectorViewRect, currentPopoverRect, proposedRect})) {
138
+ return proposedRect;
139
+ }
140
+ }
120
141
 
121
- // If none of the options work above, we position to bottom right
122
- // and adjust the insets so that it does not go out of bounds.
123
- proposedRect = positioningUtils.bottomSpanRight({anchorRect, currentPopoverRect});
124
- return positioningUtils.insetAdjustedRect({anchorRect, currentPopoverRect, inspectorViewRect, proposedRect});
125
- };
142
+ // If none of the options work above, we position to bottom right
143
+ // and adjust the insets so that it does not go out of bounds.
144
+ const proposedRect = positioningUtils.bottomSpanRight({anchorRect, currentPopoverRect});
145
+ return positioningUtils.insetAdjustedRect({anchorRect, currentPopoverRect, inspectorViewRect, proposedRect});
146
+ };
126
147
 
127
148
  const proposedRectForSimpleTooltip =
128
149
  ({inspectorViewRect, anchorRect, currentPopoverRect}:
@@ -162,7 +183,8 @@ export interface TooltipProperties {
162
183
  * @property variant - reflects the `"variant"` attribute.
163
184
  * @property padding - reflects the `"padding"` attribute.
164
185
  * @property useClick - reflects the `"click"` attribute.
165
- * @property verticalDistanceIncrease - reflexts the `"vertical-distance-increase"` attribute.
186
+ * @property verticalDistanceIncrease - reflects the `"vertical-distance-increase"` attribute.
187
+ * @property preferSpanLeft - reflects the `"prefer-span-left"` attribute.
166
188
  * @attribute id - Id of the tooltip. Used for searching an anchor element with aria-describedby.
167
189
  * @attribute hover-delay - Hover length in ms before the tooltip is shown and hidden.
168
190
  * @attribute variant - Variant of the tooltip, `"simple"` for strings only, inverted background,
@@ -170,6 +192,9 @@ export interface TooltipProperties {
170
192
  * @attribute padding - Which padding to use, defaults to `"small"`. Use `"large"` for richer content.
171
193
  * @attribute use-click - If present, the tooltip will be shown on click instead of on hover.
172
194
  * @attribute vertical-distance-increase - The tooltip is moved vertically this many pixels further away from its anchor.
195
+ * @attribute prefer-span-left - If present, the tooltip's preferred position is `"span-left"` (The right
196
+ * side of the tooltip and its anchor are aligned. The tooltip expands to the left from
197
+ * there.). Applies to rich tooltips only.
173
198
  * @attribute use-hotkey - If present, the tooltip will be shown on hover but not when receiving focus.
174
199
  * Requires a hotkey to open when fosed (Alt-down). When `"use-click"` is present
175
200
  * as well, use-click takes precedence.
@@ -254,6 +279,18 @@ export class Tooltip extends HTMLElement {
254
279
  this.setAttribute('vertical-distance-increase', increase.toString());
255
280
  }
256
281
 
282
+ get preferSpanLeft(): boolean {
283
+ return this.hasAttribute('prefer-span-left');
284
+ }
285
+
286
+ set preferSpanLeft(value: boolean) {
287
+ if (value) {
288
+ this.setAttribute('prefer-span-left', '');
289
+ } else {
290
+ this.removeAttribute('prefer-span-left');
291
+ }
292
+ }
293
+
257
294
  get anchor(): HTMLElement|null {
258
295
  return this.#anchor;
259
296
  }
@@ -360,9 +397,7 @@ export class Tooltip extends HTMLElement {
360
397
  if (this.open && Tooltip.lastOpenedTooltipId === this.id) {
361
398
  Tooltip.lastOpenedTooltipId = null;
362
399
  }
363
- this.#timeout = window.setTimeout(() => {
364
- this.hidePopover();
365
- }, this.hoverDelay);
400
+ this.hidePopover();
366
401
  };
367
402
 
368
403
  toggle = (): void => {
@@ -393,8 +428,10 @@ export class Tooltip extends HTMLElement {
393
428
  this.#previousPopoverRect = currentPopoverRect;
394
429
 
395
430
  const inspectorViewRect = UI.InspectorView.InspectorView.instance().element.getBoundingClientRect();
431
+ const preferredPositions =
432
+ this.preferSpanLeft ? [PositionOption.BOTTOM_SPAN_LEFT, PositionOption.TOP_SPAN_LEFT] : [];
396
433
  const proposedPopoverRect = this.variant === 'rich' ?
397
- proposedRectForRichTooltip({inspectorViewRect, anchorRect, currentPopoverRect}) :
434
+ proposedRectForRichTooltip({inspectorViewRect, anchorRect, currentPopoverRect, preferredPositions}) :
398
435
  proposedRectForSimpleTooltip({inspectorViewRect, anchorRect, currentPopoverRect});
399
436
  this.style.left = `${proposedPopoverRect.left}px`;
400
437
 
@@ -11,7 +11,7 @@ For a detailed explanation of the behavior of each property, please see the docu
11
11
 
12
12
  As an example, take the registration of the `Network conditions` pane, which is located in the drawer:
13
13
 
14
- ```js
14
+ ```ts
15
15
  UI.ViewManager.registerViewExtension({
16
16
  location: UI.ViewManager.ViewLocationValues.DRAWER_VIEW,
17
17
  id: 'network.config',
@@ -24,11 +24,11 @@ UI.ViewManager.registerViewExtension({
24
24
  ls`network throttling`,
25
25
  ls`useragent`,
26
26
  ls`user agent`,
27
- ls`user-agent`
27
+ ls`user-agent`,
28
28
  ],
29
- async loadView(): UI.Widget.Widget {
30
- const Network = await loadNetworkModule();
31
- return Network.NetworkConfigView.NetworkConfigView.instance();
29
+ async loadView(): UI.Widget.Widget {
30
+ const Network = await loadNetworkModule();
31
+ return Network.NetworkConfigView.NetworkConfigView.instance();
32
32
  },
33
33
  });
34
34
  ```
@@ -43,38 +43,40 @@ For a detailed explanation of the behavior of each property, please see the docu
43
43
 
44
44
  As an example, take the registration of the `network.toggle-recording` action, which allows to start or stop recording a network log:
45
45
 
46
- ```js
46
+ ```ts
47
47
  UI.ActionRegistration.registerActionExtension({
48
- actionId: 'network.toggle-recording',
48
+ actionId: 'network.toggle-recording',
49
49
  category: UI.ActionRegistration.ActionCategory.NETWORK,
50
50
  iconClass: UI.ActionRegistration.IconClass.LARGEICON_START_RECORDING,
51
- toggleable: true,
51
+ toggleable: true,
52
52
  toggledIconClass: UI.ActionRegistration.IconClass.LARGEICON_STOP_RECORDING,
53
- toggleWithRedColor: true,
53
+ toggleWithRedColor: true,
54
54
  contextTypes(): unknown[] {
55
- return maybeRetrieveContextTypes(Network => [Network.NetworkPanel.NetworkPanel]);
55
+ return maybeRetrieveContextTypes(Network => [
56
+ Network.NetworkPanel.NetworkPanel,
57
+ ]);
56
58
  },
57
- async loadActionDelegate(): UI.ActionRegistration.ActionDelegate {
58
- const Network = await loadNetworkModule();
59
- return Network.NetworkPanel.ActionDelegate.instance();
59
+ async loadActionDelegate(): UI.ActionRegistration.ActionDelegate {
60
+ const Network = await loadNetworkModule();
61
+ return Network.NetworkPanel.ActionDelegate.instance();
60
62
  },
61
- options: [
63
+ options: [
62
64
  {
63
- value: true,
65
+ value: true,
64
66
  title: ls`Record network log`,
65
67
  },
66
68
  {
67
- value: false,
69
+ value: false,
68
70
  title: ls`Stop recording network log`,
69
71
  },
70
72
  ],
71
- bindings: [
73
+ bindings: [
72
74
  {
73
- shortcut: 'Ctrl+E',
75
+ shortcut: 'Ctrl+E',
74
76
  platform: UI.ActionRegistration.Platforms.WindowsLinux,
75
77
  },
76
78
  {
77
- shortcut: 'Meta+E',
79
+ shortcut: 'Meta+E',
78
80
  platform: UI.ActionRegistration.Platforms.Mac,
79
81
  },
80
82
  ],
@@ -105,10 +107,12 @@ A key binding will be used when the selected preset matches any of the keybind s
105
107
  If you don't specify additional constraints, the keybinding will be always available.
106
108
  It is possible that this results in duplicate keybindings, for example:
107
109
 
108
- ```js
110
+ ```ts
109
111
  UI.ActionRegistration.registerActionExtension({
110
112
  contextTypes() {
111
- return maybeRetrieveContextTypes(Elements => [Elements.ElementsPanel.ElementsPanel]);
113
+ return maybeRetrieveContextTypes(Elements => [
114
+ Elements.ElementsPanel.ElementsPanel,
115
+ ]);
112
116
  },
113
117
  bindings: [
114
118
  {
@@ -133,10 +137,12 @@ These two key bindings both allocate the same key for the `windows, linux` platf
133
137
  If key bindings were declared such that **at least one** of the three variables (`platform, contextTypes` and `keybindSets`) doesn't overlap, then no duplication would occur.
134
138
  For example, the following is **not** a form of duplication:
135
139
 
136
- ```js
140
+ ```ts
137
141
  UI.ActionRegistration.registerActionExtension({
138
142
  contextTypes() {
139
- return maybeRetrieveContextTypes(Elements => [Elements.ElementsPanel.ElementsPanel]);
143
+ return maybeRetrieveContextTypes(Elements => [
144
+ Elements.ElementsPanel.ElementsPanel,
145
+ ]);
140
146
  },
141
147
  bindings: [
142
148
  {
@@ -146,8 +152,10 @@ UI.ActionRegistration.registerActionExtension({
146
152
  ],
147
153
  });
148
154
  ```
155
+
149
156
  And
150
- ```js
157
+
158
+ ```ts
151
159
  UI.ActionRegistration.registerActionExtension({
152
160
  contextTypes: undefined,
153
161
  bindings: [
@@ -159,4 +167,5 @@ UI.ActionRegistration.registerActionExtension({
159
167
  ],
160
168
  });
161
169
  ```
170
+
162
171
  In this case, they keybindings are declared for different keybind sets and thus do not result in duplcation.
@@ -414,21 +414,19 @@ export class SearchableView extends VBox {
414
414
  }
415
415
 
416
416
  updateSearchMatchesCount(matches: number): void {
417
- // TODO(crbug.com/1172300) Ignored during the jsdoc to ts migration
418
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
419
- const untypedSearchProvider = (this.searchProvider as any);
420
- if (untypedSearchProvider.currentSearchMatches === matches) {
417
+ if (this.searchProvider.currentSearchMatches === matches) {
421
418
  return;
422
419
  }
423
- untypedSearchProvider.currentSearchMatches = matches;
424
- this.updateSearchMatchesCountAndCurrentMatchIndex(untypedSearchProvider.currentQuery ? matches : 0, -1);
420
+ this.searchProvider.currentSearchMatches = matches;
421
+ this.updateSearchMatchesCountAndCurrentMatchIndex(this.searchProvider.currentQuery ? matches : 0, -1);
425
422
  }
426
423
 
427
424
  updateCurrentMatchIndex(currentMatchIndex: number): void {
428
- // TODO(crbug.com/1172300) Ignored during the jsdoc to ts migration
429
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
430
- const untypedSearchProvider = (this.searchProvider as any);
431
- this.updateSearchMatchesCountAndCurrentMatchIndex(untypedSearchProvider.currentSearchMatches, currentMatchIndex);
425
+ if (!this.searchProvider.currentSearchMatches) {
426
+ return;
427
+ }
428
+
429
+ this.updateSearchMatchesCountAndCurrentMatchIndex(this.searchProvider.currentSearchMatches, currentMatchIndex);
432
430
  }
433
431
 
434
432
  closeSearch(): void {
@@ -595,12 +593,9 @@ export class SearchableView extends VBox {
595
593
  }
596
594
 
597
595
  private clearSearch(): void {
598
- // TODO(crbug.com/1172300) Ignored during the jsdoc to ts migration
599
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
600
- const untypedSearchProvider = (this.searchProvider as any);
601
- delete this.currentQuery;
602
- if (Boolean(untypedSearchProvider.currentQuery)) {
603
- delete untypedSearchProvider.currentQuery;
596
+ this.currentQuery = undefined;
597
+ if (Boolean(this.searchProvider.currentQuery)) {
598
+ this.searchProvider.currentQuery = undefined;
604
599
  this.searchProvider.onSearchCanceled();
605
600
  }
606
601
  this.updateSearchMatchesCountAndCurrentMatchIndex(0, -1);
@@ -614,9 +609,7 @@ export class SearchableView extends VBox {
614
609
  }
615
610
 
616
611
  this.currentQuery = query;
617
- // TODO(crbug.com/1172300) Ignored during the jsdoc to ts migration
618
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
619
- (this.searchProvider as any).currentQuery = query;
612
+ this.searchProvider.currentQuery = query;
620
613
 
621
614
  const searchConfig = this.currentSearchConfig();
622
615
  this.searchProvider.performSearch(searchConfig, shouldJump, jumpBackwards);
@@ -644,17 +637,17 @@ export class SearchableView extends VBox {
644
637
 
645
638
  private replace(): void {
646
639
  if (!this.replaceProvider) {
647
- throw new Error('No \'replacable\' provided to SearchableView!');
640
+ throw new Error('No \'replaceable\' provided to SearchableView!');
648
641
  }
649
642
  const searchConfig = this.currentSearchConfig();
650
643
  this.replaceProvider.replaceSelectionWith(searchConfig, this.replaceInputElement.value);
651
- delete this.currentQuery;
644
+ this.currentQuery = undefined;
652
645
  this.performSearch(true, true);
653
646
  }
654
647
 
655
648
  private replaceAll(): void {
656
649
  if (!this.replaceProvider) {
657
- throw new Error('No \'replacable\' provided to SearchableView!');
650
+ throw new Error('No \'replaceable\' provided to SearchableView!');
658
651
  }
659
652
  const searchConfig = this.currentSearchConfig();
660
653
  this.replaceProvider.replaceAllWith(searchConfig, this.replaceInputElement.value);
@@ -666,9 +659,7 @@ export class SearchableView extends VBox {
666
659
  return;
667
660
  }
668
661
 
669
- if (this.valueChangedTimeoutId) {
670
- clearTimeout(this.valueChangedTimeoutId);
671
- }
662
+ clearTimeout(this.valueChangedTimeoutId);
672
663
  const timeout = this.searchInputElement.value.length < 3 ? 200 : 0;
673
664
  this.valueChangedTimeoutId = window.setTimeout(this.onValueChanged.bind(this), timeout);
674
665
  }
@@ -677,7 +668,7 @@ export class SearchableView extends VBox {
677
668
  if (!this.searchIsVisible) {
678
669
  return;
679
670
  }
680
- delete this.valueChangedTimeoutId;
671
+ this.valueChangedTimeoutId = undefined;
681
672
  this.performSearch(false, true);
682
673
  }
683
674
  }
@@ -685,6 +676,8 @@ export class SearchableView extends VBox {
685
676
  const searchableViewsByElement = new WeakMap<Element, SearchableView>();
686
677
 
687
678
  export interface Searchable {
679
+ currentQuery?: string;
680
+ currentSearchMatches?: number;
688
681
  onSearchCanceled(): void;
689
682
  // Called when the search toolbar is closed
690
683
  onSearchClosed?: () => void;
@@ -41,7 +41,168 @@ import * as ARIAUtils from './ARIAUtils.js';
41
41
  import {SuggestBox, type SuggestBoxDelegate, type Suggestion} from './SuggestBox.js';
42
42
  import textPromptStyles from './textPrompt.css.js';
43
43
  import {Tooltip} from './Tooltip.js';
44
- import {ElementFocusRestorer} from './UIUtils.js';
44
+ import {cloneCustomElement, ElementFocusRestorer} from './UIUtils.js';
45
+
46
+ /**
47
+ * A custom element wrapper around TextPrompt that allows text-editing contents in-place.
48
+ *
49
+ * ## Usage ##
50
+ *
51
+ * ```
52
+ * <devtools-prompt>
53
+ * <b>Structured</b> content
54
+ * </devtools-prompt>
55
+ *
56
+ * ```
57
+ *
58
+ * @property completionTimeout Sets the delay for showing the autocomplete suggestion box.
59
+ * @event commit Editing is done and the result was accepted.
60
+ * @event expand Editing was canceled.
61
+ * @event beforeautocomplete This is sent before the autocomplete suggestion box is triggered and before the <datalist>
62
+ * is read.
63
+ * @attribute editing Setting/removing this attribute starts/stops editing.
64
+ * @attribute completions Sets the `id` of the <datalist> containing the autocomplete options.
65
+ */
66
+ export class TextPromptElement extends HTMLElement {
67
+ static readonly observedAttributes = ['editing', 'completions'];
68
+ readonly #shadow = this.attachShadow({mode: 'open'});
69
+ readonly #entrypoint = this.#shadow.createChild('span');
70
+ readonly #slot = this.#entrypoint.createChild('slot');
71
+ readonly #textPrompt = new TextPrompt();
72
+ #completionTimeout: number|null = null;
73
+
74
+ constructor() {
75
+ super();
76
+ this.#textPrompt.initialize(this.#willAutoComplete.bind(this));
77
+ }
78
+
79
+ attributeChangedCallback(name: string, oldValue: string|null, newValue: string|null): void {
80
+ if (oldValue === newValue || !this.isConnected) {
81
+ return;
82
+ }
83
+
84
+ switch (name) {
85
+ case 'editing':
86
+ if (newValue !== null && newValue !== 'false' && oldValue === null) {
87
+ this.#startEditing();
88
+ } else {
89
+ this.#stopEditing();
90
+ }
91
+ break;
92
+ case 'completions':
93
+ if (this.#textPrompt.isSuggestBoxVisible()) {
94
+ void this.#textPrompt.complete(/* force=*/ true);
95
+ }
96
+ break;
97
+ }
98
+ }
99
+
100
+ async #willAutoComplete(expression?: string, filter?: string, force?: boolean): Promise<Suggestion[]> {
101
+ if (!force) {
102
+ this.dispatchEvent(new TextPromptElement.BeforeAutoCompleteEvent({expression, filter}));
103
+ }
104
+
105
+ const listId = this.getAttribute('completions');
106
+ if (!listId) {
107
+ return [];
108
+ }
109
+
110
+ const datalist = this.getComponentRoot()?.querySelectorAll<HTMLOptionElement>(`datalist#${listId} > option`);
111
+ if (!datalist?.length) {
112
+ return [];
113
+ }
114
+
115
+ filter = filter?.toLowerCase();
116
+ return datalist.values()
117
+ .filter(option => option.textContent.startsWith(filter ?? ''))
118
+ .map(option => ({text: option.textContent}))
119
+ .toArray();
120
+ }
121
+
122
+ #startEditing(): void {
123
+ const placeholder = this.#entrypoint.createChild('span');
124
+ placeholder.textContent = this.#slot.deepInnerText();
125
+ this.#slot.remove();
126
+
127
+ const proxy = this.#textPrompt.attachAndStartEditing(placeholder, e => this.#done(e, /* commit=*/ true));
128
+ proxy.addEventListener('keydown', this.#editingValueKeyDown.bind(this));
129
+ placeholder.getComponentSelection()?.selectAllChildren(placeholder);
130
+ }
131
+
132
+ #stopEditing(): void {
133
+ this.#entrypoint.removeChildren();
134
+ this.#entrypoint.appendChild(this.#slot);
135
+ this.#textPrompt.detach();
136
+ }
137
+
138
+ connectedCallback(): void {
139
+ if (this.hasAttribute('editing')) {
140
+ this.attributeChangedCallback('editing', null, '');
141
+ }
142
+ }
143
+
144
+ #done(e: Event, commit: boolean): void {
145
+ const target = e.target as HTMLElement;
146
+ const text = target.textContent || '';
147
+ if (commit) {
148
+ this.dispatchEvent(new TextPromptElement.CommitEvent(text));
149
+ } else {
150
+ this.dispatchEvent(new TextPromptElement.CancelEvent());
151
+ }
152
+ e.consume();
153
+ }
154
+
155
+ #editingValueKeyDown(event: Event): void {
156
+ if (event.handled || !(event instanceof KeyboardEvent)) {
157
+ return;
158
+ }
159
+
160
+ if (event.key === 'Enter') {
161
+ this.#done(event, /* commit=*/ true);
162
+ } else if (Platform.KeyboardUtilities.isEscKey(event)) {
163
+ this.#done(event, /* commit=*/ false);
164
+ }
165
+ }
166
+
167
+ set completionTimeout(timeout: number) {
168
+ this.#completionTimeout = timeout;
169
+ this.#textPrompt.setAutocompletionTimeout(timeout);
170
+ }
171
+
172
+ override cloneNode(): Node {
173
+ const clone = cloneCustomElement(this);
174
+ if (this.#completionTimeout !== null) {
175
+ clone.completionTimeout = this.#completionTimeout;
176
+ }
177
+ return clone;
178
+ }
179
+ }
180
+
181
+ export namespace TextPromptElement {
182
+ export class CommitEvent extends CustomEvent<string> {
183
+ constructor(detail: string) {
184
+ super('commit', {detail});
185
+ }
186
+ }
187
+ export class CancelEvent extends CustomEvent<string> {
188
+ constructor() {
189
+ super('cancel');
190
+ }
191
+ }
192
+ export class BeforeAutoCompleteEvent extends CustomEvent<{expression?: string, filter?: string}> {
193
+ constructor(detail: {expression?: string, filter?: string}) {
194
+ super('beforeautocomplete', {detail});
195
+ }
196
+ }
197
+ }
198
+
199
+ customElements.define('devtools-prompt', TextPromptElement);
200
+
201
+ declare global {
202
+ interface HTMLElementTagNameMap {
203
+ 'devtools-prompt': TextPromptElement;
204
+ }
205
+ }
45
206
 
46
207
  export class TextPrompt extends Common.ObjectWrapper.ObjectWrapper<EventTypes> implements SuggestBoxDelegate {
47
208
  private proxyElement!: HTMLElement|undefined;
@@ -510,6 +671,10 @@ export class TextPrompt extends Common.ObjectWrapper.ObjectWrapper<EventTypes> i
510
671
 
511
672
  async complete(force?: boolean): Promise<void> {
512
673
  this.clearAutocompleteTimeout();
674
+ if (!this.element().isConnected) {
675
+ return;
676
+ }
677
+
513
678
  const selection = this.element().getComponentSelection();
514
679
  if (!selection || selection.rangeCount === 0) {
515
680
  return;
@@ -55,6 +55,7 @@ import {
55
55
  deepElementFromPoint,
56
56
  enclosingNodeOrSelfWithNodeNameInArray,
57
57
  HTMLElementWithLightDOMTemplate,
58
+ InterceptBindingDirective,
58
59
  isEditing,
59
60
  } from './UIUtils.js';
60
61
 
@@ -1549,6 +1550,7 @@ class ActiveHighlights {
1549
1550
  class TreeViewTreeElement extends TreeElement {
1550
1551
  #activeHighlights = new ActiveHighlights();
1551
1552
  #clonedAttributes = new Set<string>();
1553
+ #clonedClasses = new Set<string>();
1552
1554
 
1553
1555
  static #elementToTreeElement = new WeakMap<Node, TreeViewTreeElement>();
1554
1556
  readonly configElement: HTMLLIElement;
@@ -1569,7 +1571,9 @@ class TreeViewTreeElement extends TreeElement {
1569
1571
  refresh(): void {
1570
1572
  this.titleElement.textContent = '';
1571
1573
  this.#clonedAttributes.forEach(attr => this.listItemElement.attributes.removeNamedItem(attr));
1574
+ this.#clonedClasses.forEach(className => this.listItemElement.classList.remove(className));
1572
1575
  this.#clonedAttributes.clear();
1576
+ this.#clonedClasses.clear();
1573
1577
  for (let i = 0; i < this.configElement.attributes.length; ++i) {
1574
1578
  const attribute = this.configElement.attributes.item(i);
1575
1579
  if (attribute && attribute.name !== 'role' && SDK.DOMModel.ARIA_ATTRIBUTES.has(attribute.name)) {
@@ -1577,6 +1581,11 @@ class TreeViewTreeElement extends TreeElement {
1577
1581
  this.#clonedAttributes.add(attribute.name);
1578
1582
  }
1579
1583
  }
1584
+ for (const className of this.configElement.classList) {
1585
+ this.listItemElement.classList.add(className);
1586
+ this.#clonedClasses.add(className);
1587
+ }
1588
+ InterceptBindingDirective.attachEventListeners(this.configElement, this.listItemElement);
1580
1589
 
1581
1590
  for (const child of this.configElement.childNodes) {
1582
1591
  if (child instanceof HTMLUListElement && child.role === 'group') {
@@ -1753,7 +1762,7 @@ export class TreeViewElement extends HTMLElementWithLightDOMTemplate {
1753
1762
  }
1754
1763
  }
1755
1764
 
1756
- protected override addNodes(nodes: NodeList|Node[]): void {
1765
+ protected override addNodes(nodes: NodeList|Node[], nextSibling?: Node|null): void {
1757
1766
  for (const node of getTreeNodes(nodes)) {
1758
1767
  if (TreeViewTreeElement.get(node)) {
1759
1768
  continue; // Not sure this can happen
@@ -1762,8 +1771,13 @@ export class TreeViewElement extends HTMLElementWithLightDOMTemplate {
1762
1771
  if (!parent) {
1763
1772
  continue;
1764
1773
  }
1774
+ while (nextSibling && nextSibling.nodeType !== Node.ELEMENT_NODE) {
1775
+ nextSibling = nextSibling.nextSibling;
1776
+ }
1777
+ const nextElement = nextSibling ? TreeViewTreeElement.get(nextSibling) : null;
1778
+ const index = nextElement ? parent.treeElement.indexOfChild(nextElement) : parent.treeElement.children().length;
1765
1779
  const treeElement = new TreeViewTreeElement(this.#treeOutline, node);
1766
- parent.treeElement.appendChild(treeElement, /* FIXME comparator */);
1780
+ parent.treeElement.insertChild(treeElement, index);
1767
1781
  if (hasBooleanAttribute(node, 'selected')) {
1768
1782
  treeElement.revealAndSelect(true);
1769
1783
  }
@@ -2216,6 +2216,19 @@ export class InterceptBindingDirective extends Lit.Directive.Directive {
2216
2216
  }
2217
2217
  }
2218
2218
 
2219
+ export const cloneCustomElement = <T extends HTMLElement>(element: T, deep?: boolean): T => {
2220
+ const clone = document.createElement(element.localName) as T;
2221
+ for (const attribute of element.attributes) {
2222
+ clone.setAttribute(attribute.name, attribute.value);
2223
+ }
2224
+ if (deep) {
2225
+ for (const child of element.childNodes) {
2226
+ clone.appendChild(child.cloneNode(deep));
2227
+ }
2228
+ }
2229
+ return clone;
2230
+ };
2231
+
2219
2232
  export class HTMLElementWithLightDOMTemplate extends HTMLElement {
2220
2233
  readonly #mutationObserver = new MutationObserver(this.#onChange.bind(this));
2221
2234
  #contentTemplate: HTMLTemplateElement|null = null;
@@ -2286,7 +2299,7 @@ export class HTMLElementWithLightDOMTemplate extends HTMLElement {
2286
2299
  this.onChange(mutationList);
2287
2300
  for (const mutation of mutationList) {
2288
2301
  this.removeNodes(mutation.removedNodes);
2289
- this.addNodes(mutation.addedNodes);
2302
+ this.addNodes(mutation.addedNodes, mutation.nextSibling);
2290
2303
  this.updateNode(mutation.target, mutation.attributeName);
2291
2304
  }
2292
2305
  }
@@ -2297,7 +2310,7 @@ export class HTMLElementWithLightDOMTemplate extends HTMLElement {
2297
2310
  protected updateNode(_node: Node, _attributeName: string|null): void {
2298
2311
  }
2299
2312
 
2300
- protected addNodes(_nodes: NodeList|Node[]): void {
2313
+ protected addNodes(_nodes: NodeList|Node[], _nextSibling?: Node|null): void {
2301
2314
  }
2302
2315
 
2303
2316
  protected removeNodes(_nodes: NodeList): void {