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.
- package/docs/checklist/README.md +2 -2
- package/docs/checklist/javascript.md +1 -1
- package/docs/contributing/README.md +1 -1
- package/docs/contributing/settings-experiments-features.md +9 -8
- package/docs/cookbook/devtools_on_devtools.md +2 -2
- package/docs/cookbook/localization.md +10 -10
- package/docs/devtools-protocol.md +9 -8
- package/docs/ecosystem/automatic_workspace_folders.md +3 -3
- package/docs/get_the_code.md +0 -2
- package/docs/styleguide/ux/components.md +166 -85
- package/docs/styleguide/ux/numbers.md +3 -4
- package/front_end/core/common/README.md +13 -12
- package/front_end/core/host/GdpClient.ts +16 -1
- package/front_end/core/host/UserMetrics.ts +8 -2
- package/front_end/core/root/Runtime.ts +13 -0
- package/front_end/core/sdk/CSSMatchedStyles.ts +5 -1
- package/front_end/entrypoints/main/MainImpl.ts +6 -3
- package/front_end/generated/InspectorBackendCommands.js +10 -7
- package/front_end/generated/SupportedCSSProperties.js +21 -7
- package/front_end/generated/protocol-mapping.d.ts +16 -1
- package/front_end/generated/protocol-proxy-api.d.ts +13 -1
- package/front_end/generated/protocol.ts +95 -0
- package/front_end/models/ai_assistance/agents/PerformanceAgent.ts +170 -54
- package/front_end/models/ai_assistance/data_formatters/PerformanceInsightFormatter.snapshot.txt +14 -181
- package/front_end/models/ai_assistance/data_formatters/PerformanceInsightFormatter.ts +13 -315
- package/front_end/models/ai_assistance/data_formatters/PerformanceTraceFormatter.snapshot.txt +224 -50
- package/front_end/models/ai_assistance/data_formatters/PerformanceTraceFormatter.ts +310 -11
- package/front_end/models/ai_assistance/performance/AIContext.ts +15 -2
- package/front_end/models/ai_code_completion/AiCodeCompletion.ts +41 -19
- package/front_end/models/badges/Badge.ts +8 -3
- package/front_end/models/badges/CodeWhispererBadge.ts +2 -4
- package/front_end/models/badges/StarterBadge.ts +2 -2
- package/front_end/models/badges/UserBadges.ts +59 -6
- package/front_end/models/formatter/FormatterWorkerPool.ts +3 -3
- package/front_end/models/javascript_metadata/NativeFunctions.js +1 -1
- package/front_end/models/trace/README.md +28 -1
- package/front_end/models/trace/handlers/UserTimingsHandler.ts +1 -1
- package/front_end/models/trace/helpers/Trace.ts +99 -43
- package/front_end/models/trace/types/TraceEvents.ts +9 -0
- package/front_end/panels/accessibility/ARIAAttributesView.ts +113 -191
- package/front_end/panels/accessibility/AccessibilityNodeView.ts +9 -9
- package/front_end/panels/accessibility/AccessibilitySubPane.ts +6 -4
- package/front_end/panels/accessibility/accessibilityProperties.css +2 -0
- package/front_end/panels/ai_assistance/AiAssistancePanel.ts +16 -2
- package/front_end/panels/ai_assistance/components/ChatView.ts +9 -10
- package/front_end/panels/ai_assistance/components/PerformanceAgentMarkdownRenderer.ts +42 -0
- package/front_end/panels/common/AiCodeCompletionDisclaimer.ts +32 -9
- package/front_end/panels/common/AiCodeCompletionSummaryToolbar.ts +7 -1
- package/front_end/panels/common/BadgeNotification.ts +67 -15
- package/front_end/panels/common/GdpSignUpDialog.ts +18 -9
- package/front_end/panels/console/ConsolePrompt.ts +1 -1
- package/front_end/panels/console/ConsoleView.ts +6 -2
- package/front_end/panels/elements/ComputedStyleWidget.ts +1 -2
- package/front_end/panels/elements/ElementsPanel.ts +4 -0
- package/front_end/panels/elements/ElementsTreeElement.ts +18 -0
- package/front_end/panels/elements/ElementsTreeOutline.ts +13 -0
- package/front_end/panels/elements/LayoutPane.ts +1 -1
- package/front_end/panels/elements/StylePropertyTreeElement.ts +21 -6
- package/front_end/panels/media/TickingFlameChart.ts +1 -1
- package/front_end/panels/network/NetworkLogView.ts +5 -1
- package/front_end/panels/profiler/HeapSnapshotView.ts +34 -19
- package/front_end/panels/search/SearchResultsPane.ts +126 -145
- package/front_end/panels/search/SearchView.ts +43 -59
- package/front_end/panels/settings/components/SyncSection.ts +16 -8
- package/front_end/panels/sources/AiCodeCompletionPlugin.ts +6 -1
- package/front_end/panels/sources/OutlineQuickOpen.ts +3 -1
- package/front_end/panels/sources/SourcesPanel.ts +3 -0
- package/front_end/panels/timeline/AppenderUtils.ts +2 -2
- package/front_end/panels/timeline/ExtensionTrackAppender.ts +13 -4
- package/front_end/panels/timeline/GPUTrackAppender.ts +2 -1
- package/front_end/panels/timeline/InteractionsTrackAppender.ts +5 -1
- package/front_end/panels/timeline/LayoutShiftsTrackAppender.ts +2 -1
- package/front_end/panels/timeline/ThreadAppender.ts +12 -3
- package/front_end/panels/timeline/TimelineFlameChartDataProvider.ts +9 -4
- package/front_end/panels/timeline/TimelinePanel.ts +3 -2
- package/front_end/panels/timeline/TimelineUIUtils.ts +18 -12
- package/front_end/panels/timeline/TimingsTrackAppender.ts +6 -1
- package/front_end/panels/timeline/components/CPUThrottlingSelector.ts +95 -82
- package/front_end/panels/timeline/components/LiveMetricsView.ts +2 -2
- package/front_end/panels/timeline/components/cpuThrottlingSelector.css +17 -15
- package/front_end/panels/timeline/components/insights/BaseInsightComponent.ts +3 -0
- package/front_end/third_party/chromium/README.chromium +1 -1
- package/front_end/third_party/codemirror.next/chunk/codemirror.js +1 -1
- package/front_end/third_party/codemirror.next/chunk/codemirror.js.map +1 -1
- package/front_end/third_party/codemirror.next/codemirror.next.d.ts +6 -9
- package/front_end/third_party/codemirror.next/package.json +2 -1
- package/front_end/third_party/diff/README.chromium +1 -0
- package/front_end/third_party/puppeteer/README.chromium +2 -2
- package/front_end/third_party/puppeteer/package/lib/cjs/puppeteer/bidi/core/Realm.d.ts +2 -2
- package/front_end/third_party/puppeteer/package/lib/cjs/puppeteer/cdp/Accessibility.js +0 -20
- package/front_end/third_party/puppeteer/package/lib/cjs/puppeteer/cdp/Accessibility.js.map +1 -1
- package/front_end/third_party/puppeteer/package/lib/cjs/puppeteer/generated/version.d.ts +1 -1
- package/front_end/third_party/puppeteer/package/lib/cjs/puppeteer/generated/version.js +1 -1
- package/front_end/third_party/puppeteer/package/lib/cjs/puppeteer/injected/injected.d.ts +1 -1
- package/front_end/third_party/puppeteer/package/lib/cjs/puppeteer/revisions.d.ts +1 -1
- package/front_end/third_party/puppeteer/package/lib/cjs/puppeteer/revisions.js +1 -1
- package/front_end/third_party/puppeteer/package/lib/cjs/puppeteer/revisions.js.map +1 -1
- package/front_end/third_party/puppeteer/package/lib/cjs/puppeteer/util/Mutex.d.ts +2 -2
- package/front_end/third_party/puppeteer/package/lib/es5-iife/puppeteer-core-browser.js +2 -23
- package/front_end/third_party/puppeteer/package/lib/esm/puppeteer/cdp/Accessibility.js +0 -20
- package/front_end/third_party/puppeteer/package/lib/esm/puppeteer/cdp/Accessibility.js.map +1 -1
- package/front_end/third_party/puppeteer/package/lib/esm/puppeteer/generated/version.d.ts +1 -1
- package/front_end/third_party/puppeteer/package/lib/esm/puppeteer/generated/version.js +1 -1
- package/front_end/third_party/puppeteer/package/lib/esm/puppeteer/revisions.d.ts +1 -1
- package/front_end/third_party/puppeteer/package/lib/esm/puppeteer/revisions.js +1 -1
- package/front_end/third_party/puppeteer/package/lib/esm/puppeteer/revisions.js.map +1 -1
- package/front_end/third_party/puppeteer/package/package.json +1 -1
- package/front_end/third_party/puppeteer/package/src/cdp/Accessibility.ts +1 -21
- package/front_end/third_party/puppeteer/package/src/generated/version.ts +1 -1
- package/front_end/third_party/puppeteer/package/src/revisions.ts +1 -1
- package/front_end/ui/components/text_editor/config.ts +36 -8
- package/front_end/ui/components/tooltips/Tooltip.ts +71 -34
- package/front_end/ui/legacy/README.md +33 -24
- package/front_end/ui/legacy/SearchableView.ts +19 -26
- package/front_end/ui/legacy/TextPrompt.ts +166 -1
- package/front_end/ui/legacy/Treeoutline.ts +16 -2
- package/front_end/ui/legacy/UIUtils.ts +15 -2
- package/front_end/ui/legacy/XElement.ts +0 -43
- package/front_end/ui/legacy/components/perf_ui/FlameChart.ts +20 -4
- package/front_end/ui/visual_logging/KnownContextValues.ts +24 -6
- package/front_end/ui/visual_logging/README.md +43 -27
- 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
|
-
|
98
|
-
|
99
|
-
|
100
|
-
|
101
|
-
|
102
|
-
|
103
|
-
|
104
|
-
|
105
|
-
|
106
|
-
|
107
|
-
|
108
|
-
|
109
|
-
|
110
|
-
|
111
|
-
|
112
|
-
|
113
|
-
|
114
|
-
|
115
|
-
|
116
|
-
|
117
|
-
|
118
|
-
|
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
|
-
|
122
|
-
|
123
|
-
|
124
|
-
|
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 -
|
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
|
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
|
-
```
|
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
|
31
|
-
return
|
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
|
-
```
|
46
|
+
```ts
|
47
47
|
UI.ActionRegistration.registerActionExtension({
|
48
|
-
actionId:
|
48
|
+
actionId: 'network.toggle-recording',
|
49
49
|
category: UI.ActionRegistration.ActionCategory.NETWORK,
|
50
50
|
iconClass: UI.ActionRegistration.IconClass.LARGEICON_START_RECORDING,
|
51
|
-
toggleable:
|
51
|
+
toggleable: true,
|
52
52
|
toggledIconClass: UI.ActionRegistration.IconClass.LARGEICON_STOP_RECORDING,
|
53
|
-
toggleWithRedColor:
|
53
|
+
toggleWithRedColor: true,
|
54
54
|
contextTypes(): unknown[] {
|
55
|
-
return maybeRetrieveContextTypes(Network
|
55
|
+
return maybeRetrieveContextTypes(Network => [
|
56
|
+
Network.NetworkPanel.NetworkPanel,
|
57
|
+
]);
|
56
58
|
},
|
57
|
-
async loadActionDelegate(): UI.ActionRegistration.ActionDelegate
|
58
|
-
const
|
59
|
-
return
|
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:
|
65
|
+
value: true,
|
64
66
|
title: ls`Record network log`,
|
65
67
|
},
|
66
68
|
{
|
67
|
-
value:
|
69
|
+
value: false,
|
68
70
|
title: ls`Stop recording network log`,
|
69
71
|
},
|
70
72
|
],
|
71
|
-
bindings:
|
73
|
+
bindings: [
|
72
74
|
{
|
73
|
-
shortcut:
|
75
|
+
shortcut: 'Ctrl+E',
|
74
76
|
platform: UI.ActionRegistration.Platforms.WindowsLinux,
|
75
77
|
},
|
76
78
|
{
|
77
|
-
shortcut:
|
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
|
-
```
|
110
|
+
```ts
|
109
111
|
UI.ActionRegistration.registerActionExtension({
|
110
112
|
contextTypes() {
|
111
|
-
return maybeRetrieveContextTypes(Elements => [
|
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
|
-
```
|
140
|
+
```ts
|
137
141
|
UI.ActionRegistration.registerActionExtension({
|
138
142
|
contextTypes() {
|
139
|
-
return maybeRetrieveContextTypes(Elements => [
|
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
|
-
|
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
|
-
|
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
|
-
|
424
|
-
this.updateSearchMatchesCountAndCurrentMatchIndex(
|
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
|
-
|
429
|
-
|
430
|
-
|
431
|
-
|
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
|
-
|
599
|
-
|
600
|
-
|
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
|
-
|
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 \'
|
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
|
-
|
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 \'
|
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
|
-
|
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
|
-
|
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.
|
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 {
|