chrome-devtools-frontend 1.0.1516909 → 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 (94) 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 +4 -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 +166 -49
  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 +17 -11
  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 +21 -3
  34. package/front_end/models/javascript_metadata/NativeFunctions.js +1 -1
  35. package/front_end/models/trace/README.md +28 -1
  36. package/front_end/models/trace/handlers/UserTimingsHandler.ts +1 -1
  37. package/front_end/models/trace/helpers/Trace.ts +99 -43
  38. package/front_end/models/trace/types/TraceEvents.ts +9 -0
  39. package/front_end/panels/accessibility/ARIAAttributesView.ts +113 -191
  40. package/front_end/panels/accessibility/AccessibilityNodeView.ts +9 -9
  41. package/front_end/panels/accessibility/AccessibilitySubPane.ts +6 -4
  42. package/front_end/panels/accessibility/accessibilityProperties.css +2 -0
  43. package/front_end/panels/ai_assistance/AiAssistancePanel.ts +16 -2
  44. package/front_end/panels/ai_assistance/components/ChatView.ts +9 -10
  45. package/front_end/panels/ai_assistance/components/PerformanceAgentMarkdownRenderer.ts +42 -0
  46. package/front_end/panels/common/AiCodeCompletionDisclaimer.ts +32 -9
  47. package/front_end/panels/common/AiCodeCompletionSummaryToolbar.ts +7 -1
  48. package/front_end/panels/common/BadgeNotification.ts +21 -5
  49. package/front_end/panels/common/GdpSignUpDialog.ts +18 -9
  50. package/front_end/panels/console/ConsolePrompt.ts +1 -1
  51. package/front_end/panels/console/ConsoleView.ts +6 -2
  52. package/front_end/panels/elements/ElementsPanel.ts +4 -0
  53. package/front_end/panels/elements/ElementsTreeElement.ts +18 -0
  54. package/front_end/panels/elements/ElementsTreeOutline.ts +13 -0
  55. package/front_end/panels/elements/StylePropertyTreeElement.ts +21 -6
  56. package/front_end/panels/media/TickingFlameChart.ts +1 -1
  57. package/front_end/panels/profiler/HeapSnapshotView.ts +34 -19
  58. package/front_end/panels/search/SearchResultsPane.ts +124 -128
  59. package/front_end/panels/search/SearchView.ts +24 -17
  60. package/front_end/panels/settings/components/SyncSection.ts +16 -8
  61. package/front_end/panels/sources/AiCodeCompletionPlugin.ts +6 -1
  62. package/front_end/panels/sources/SourcesPanel.ts +3 -0
  63. package/front_end/panels/timeline/AppenderUtils.ts +2 -2
  64. package/front_end/panels/timeline/ExtensionTrackAppender.ts +13 -4
  65. package/front_end/panels/timeline/GPUTrackAppender.ts +2 -1
  66. package/front_end/panels/timeline/InteractionsTrackAppender.ts +5 -1
  67. package/front_end/panels/timeline/LayoutShiftsTrackAppender.ts +2 -1
  68. package/front_end/panels/timeline/ThreadAppender.ts +12 -3
  69. package/front_end/panels/timeline/TimelineFlameChartDataProvider.ts +9 -4
  70. package/front_end/panels/timeline/TimelinePanel.ts +3 -2
  71. package/front_end/panels/timeline/TimelineUIUtils.ts +5 -4
  72. package/front_end/panels/timeline/TimingsTrackAppender.ts +6 -1
  73. package/front_end/panels/timeline/components/CPUThrottlingSelector.ts +95 -82
  74. package/front_end/panels/timeline/components/LiveMetricsView.ts +2 -2
  75. package/front_end/panels/timeline/components/cpuThrottlingSelector.css +17 -15
  76. package/front_end/panels/timeline/components/insights/BaseInsightComponent.ts +3 -0
  77. package/front_end/third_party/chromium/README.chromium +1 -1
  78. package/front_end/third_party/codemirror.next/chunk/codemirror.js +1 -1
  79. package/front_end/third_party/codemirror.next/chunk/codemirror.js.map +1 -1
  80. package/front_end/third_party/codemirror.next/codemirror.next.d.ts +6 -9
  81. package/front_end/third_party/codemirror.next/package.json +2 -1
  82. package/front_end/third_party/diff/README.chromium +1 -0
  83. package/front_end/ui/components/text_editor/config.ts +6 -7
  84. package/front_end/ui/components/tooltips/Tooltip.ts +70 -31
  85. package/front_end/ui/legacy/README.md +33 -24
  86. package/front_end/ui/legacy/SearchableView.ts +19 -26
  87. package/front_end/ui/legacy/TextPrompt.ts +166 -1
  88. package/front_end/ui/legacy/Treeoutline.ts +16 -2
  89. package/front_end/ui/legacy/UIUtils.ts +15 -2
  90. package/front_end/ui/legacy/XElement.ts +0 -43
  91. package/front_end/ui/legacy/components/perf_ui/FlameChart.ts +20 -4
  92. package/front_end/ui/visual_logging/KnownContextValues.ts +19 -6
  93. package/front_end/ui/visual_logging/README.md +43 -27
  94. package/package.json +1 -1
@@ -7728,13 +7728,6 @@ parser](https://github.com/lezer-parser/html), extended with the
7728
7728
  JavaScript and CSS parsers to parse the content of `<script>` and
7729
7729
  `<style>` tags.
7730
7730
  */
7731
- declare const htmlPlain: LRLanguage;
7732
- /**
7733
- A language provider based on the [Lezer HTML
7734
- parser](https://github.com/lezer-parser/html), extended with the
7735
- JavaScript and CSS parsers to parse the content of `<script>` and
7736
- `<style>` tags.
7737
- */
7738
7731
  declare const htmlLanguage: LRLanguage;
7739
7732
  /**
7740
7733
  Language support for HTML, including
@@ -7749,6 +7742,11 @@ declare function html(config?: {
7749
7742
  document).
7750
7743
  */
7751
7744
  matchClosingTags?: boolean;
7745
+ /**
7746
+ By default, the parser does not allow arbitrary self-closing tags.
7747
+ Set this to `true` to turn on support for `/>` self-closing tag
7748
+ syntax.
7749
+ */
7752
7750
  selfClosingTags?: boolean;
7753
7751
  /**
7754
7752
  Determines whether [`autoCloseTags`](https://codemirror.net/6/docs/ref/#lang-html.autoCloseTags)
@@ -7786,9 +7784,8 @@ declare const index_d$1_html: typeof html;
7786
7784
  declare const index_d$1_htmlCompletionSource: typeof htmlCompletionSource;
7787
7785
  declare const index_d$1_htmlCompletionSourceWith: typeof htmlCompletionSourceWith;
7788
7786
  declare const index_d$1_htmlLanguage: typeof htmlLanguage;
7789
- declare const index_d$1_htmlPlain: typeof htmlPlain;
7790
7787
  declare namespace index_d$1 {
7791
- export { type index_d$1_TagSpec as TagSpec, autoCloseTags$1 as autoCloseTags, index_d$1_html as html, index_d$1_htmlCompletionSource as htmlCompletionSource, index_d$1_htmlCompletionSourceWith as htmlCompletionSourceWith, index_d$1_htmlLanguage as htmlLanguage, index_d$1_htmlPlain as htmlPlain };
7788
+ export { type index_d$1_TagSpec as TagSpec, autoCloseTags$1 as autoCloseTags, index_d$1_html as html, index_d$1_htmlCompletionSource as htmlCompletionSource, index_d$1_htmlCompletionSourceWith as htmlCompletionSourceWith, index_d$1_htmlLanguage as htmlLanguage };
7792
7789
  }
7793
7790
 
7794
7791
  type Severity = "hint" | "info" | "warning" | "error";
@@ -9,7 +9,7 @@
9
9
  "@codemirror/lang-angular": "0.1.3",
10
10
  "@codemirror/lang-cpp": "6.0.2",
11
11
  "@codemirror/lang-css": "6.3.0",
12
- "@codemirror/lang-html": "6.4.7",
12
+ "@codemirror/lang-html": "6.4.10",
13
13
  "@codemirror/lang-java": "6.0.1",
14
14
  "@codemirror/lang-javascript": "6.2.1",
15
15
  "@codemirror/lang-less": "6.0.2",
@@ -36,6 +36,7 @@
36
36
  "style-mod": "4.1.0"
37
37
  },
38
38
  "devDependencies": {
39
+ "rollup": "4.50.2",
39
40
  "rollup-plugin-dts": "6.1.1"
40
41
  }
41
42
  }
@@ -6,6 +6,7 @@ Revision: 0ae308daa77aeddb089cd6b7b0a443fca026266e
6
6
  Update Mechanism: Manual
7
7
  License: Apache-2.0
8
8
  License File: LICENSE
9
+ Update Mechanism: Static.HardFork (https://crbug.com/422918273)
9
10
  Security Critical: no
10
11
  Shipped: yes
11
12
 
@@ -620,18 +620,16 @@ export const aiAutoCompleteSuggestion: CM.Extension = [
620
620
  this.decorations = CM.Decoration.none;
621
621
  return;
622
622
  }
623
- // If the user typed the full selected completion, then we don't check for overlap.
624
- // (e.g. the user wrote `flex`, traditional suggestion is `flex` and the AI autocompletion is
625
- // `;\njustify-content: center`. Then, we want to show the AI completion)
626
- const endsWithCompleteSelectedCompletion =
627
- update.state.doc.sliceString(head - selectedCompletion.label.length, head) === selectedCompletion.label;
628
623
  // If a traditional autocomplete menu is shown, the AI suggestion is only
629
624
  // shown if it builds upon the currently selected item. If there is no
630
625
  // overlap, we hide the AI suggestion. For example, for the text `console`
631
626
  // if the traditional autocomplete suggests `log` and the AI
632
627
  // suggests `warn`, there is no overlap and the AI suggestion is hidden.
633
- if (!endsWithCompleteSelectedCompletion &&
634
- !TextUtils.TextUtils.getOverlap(selectedCompletion.label, ghostText)) {
628
+ const overlappingText = TextUtils.TextUtils.getOverlap(selectedCompletion.label, ghostText) ?? '';
629
+ const lineAtAiSuggestion = update.state.doc.lineAt(activeSuggestion.from).text;
630
+ const overlapsWithSelectedCompletion =
631
+ (lineAtAiSuggestion + overlappingText).endsWith(selectedCompletion.label);
632
+ if (!overlapsWithSelectedCompletion) {
635
633
  this.decorations = CM.Decoration.none;
636
634
  return;
637
635
  }
@@ -664,6 +662,7 @@ export const aiAutoCompleteSuggestion: CM.Extension = [
664
662
  const latency = performance.now() - activeSuggestion.startTime;
665
663
  // only register impression for the first time AI generated suggestion is shown to the user.
666
664
  activeSuggestion.onImpression(activeSuggestion.rpcGlobalId, activeSuggestion.sampleId, latency);
665
+ this.#lastLoggedSuggestion = activeSuggestion;
667
666
  }
668
667
  },
669
668
  {decorations: p => p.decorations}),
@@ -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
  }
@@ -391,8 +428,10 @@ export class Tooltip extends HTMLElement {
391
428
  this.#previousPopoverRect = currentPopoverRect;
392
429
 
393
430
  const inspectorViewRect = UI.InspectorView.InspectorView.instance().element.getBoundingClientRect();
431
+ const preferredPositions =
432
+ this.preferSpanLeft ? [PositionOption.BOTTOM_SPAN_LEFT, PositionOption.TOP_SPAN_LEFT] : [];
394
433
  const proposedPopoverRect = this.variant === 'rich' ?
395
- proposedRectForRichTooltip({inspectorViewRect, anchorRect, currentPopoverRect}) :
434
+ proposedRectForRichTooltip({inspectorViewRect, anchorRect, currentPopoverRect, preferredPositions}) :
396
435
  proposedRectForSimpleTooltip({inspectorViewRect, anchorRect, currentPopoverRect});
397
436
  this.style.left = `${proposedPopoverRect.left}px`;
398
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;