chrome-devtools-frontend 1.0.1628368 → 1.0.1629211
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/front_end/core/host/UserMetrics.ts +0 -1
- package/front_end/core/root/ExperimentNames.ts +0 -2
- package/front_end/core/sdk/DOMModel.ts +0 -5
- package/front_end/entrypoints/greendev_floaty/FloatyEntrypoint.ts +0 -2
- package/front_end/entrypoints/greendev_floaty/greendev_floaty.ts +0 -2
- package/front_end/entrypoints/main/MainImpl.ts +0 -3
- package/front_end/generated/InspectorBackendCommands.ts +2 -2
- package/front_end/generated/protocol.ts +5 -0
- package/front_end/models/emulation/DeviceModeModel.ts +4 -0
- package/front_end/models/issues_manager/GenericIssue.ts +18 -1
- package/front_end/models/issues_manager/descriptions/genericBackUINavigationWouldSkipAd.md +4 -0
- package/front_end/panels/common/freDialog.css +1 -0
- package/front_end/panels/elements/StylesSidebarPane.ts +1 -1
- package/front_end/panels/elements/elements-meta.ts +0 -25
- package/front_end/panels/elements/elements.ts +0 -3
- package/front_end/panels/emulation/DeviceModeToolbar.ts +335 -248
- package/front_end/panels/profiler/ProfilesPanel.ts +7 -1
- package/front_end/panels/sources/WatchExpressionsSidebarPane.ts +29 -18
- package/front_end/panels/sources/sources-meta.ts +7 -0
- package/front_end/third_party/chromium/README.chromium +1 -1
- package/front_end/ui/legacy/TabbedPane.ts +142 -16
- package/front_end/ui/legacy/TextPrompt.docs.ts +51 -0
- package/front_end/ui/legacy/TextPrompt.ts +55 -6
- package/front_end/ui/legacy/Toolbar.ts +2 -1
- package/front_end/ui/legacy/View.ts +2 -1
- package/front_end/ui/legacy/ViewManager.ts +11 -5
- package/front_end/ui/legacy/textPrompt.css +4 -0
- package/mcp/mcp.ts +1 -0
- package/package.json +1 -1
- package/front_end/panels/elements/NodeStackTraceWidget.ts +0 -76
- package/front_end/panels/elements/nodeStackTraceWidget.css +0 -11
|
@@ -37,6 +37,7 @@ import {createIcon} from '../../ui/kit/kit.js';
|
|
|
37
37
|
// eslint-disable-next-line @devtools/es-modules-import
|
|
38
38
|
import objectValueStyles from '../../ui/legacy/components/object_ui/objectValue.css.js';
|
|
39
39
|
import * as UI from '../../ui/legacy/legacy.js';
|
|
40
|
+
import {render} from '../../ui/lit/lit.js';
|
|
40
41
|
import * as VisualLogging from '../../ui/visual_logging/visual_logging.js';
|
|
41
42
|
|
|
42
43
|
import {
|
|
@@ -473,7 +474,12 @@ export class ProfilesPanel extends UI.Panel.PanelWithSidebar implements DataDisp
|
|
|
473
474
|
this.profileViewToolbar.removeToolbarItems();
|
|
474
475
|
|
|
475
476
|
void (view as unknown as UI.View.View).toolbarItems().then(items => {
|
|
476
|
-
|
|
477
|
+
if (Array.isArray(items)) {
|
|
478
|
+
items.map(item => this.profileViewToolbar.appendToolbarItem(item));
|
|
479
|
+
} else {
|
|
480
|
+
// eslint-disable-next-line @devtools/no-lit-render-outside-of-view
|
|
481
|
+
render(items, this.profileViewToolbar);
|
|
482
|
+
}
|
|
477
483
|
});
|
|
478
484
|
|
|
479
485
|
return view;
|
|
@@ -49,7 +49,7 @@ import * as ObjectUI from '../../ui/legacy/components/object_ui/object_ui.js';
|
|
|
49
49
|
import objectValueStyles from '../../ui/legacy/components/object_ui/objectValue.css.js';
|
|
50
50
|
import * as Components from '../../ui/legacy/components/utils/utils.js';
|
|
51
51
|
import * as UI from '../../ui/legacy/legacy.js';
|
|
52
|
-
import {Directives, html, render} from '../../ui/lit/lit.js';
|
|
52
|
+
import {Directives, html, render, type TemplateResult} from '../../ui/lit/lit.js';
|
|
53
53
|
import * as VisualLogging from '../../ui/visual_logging/visual_logging.js';
|
|
54
54
|
|
|
55
55
|
import {UISourceCodeFrame} from './UISourceCodeFrame.js';
|
|
@@ -179,8 +179,6 @@ export class WatchExpressionsSidebarPane extends UI.Widget.VBox implements UI.Ac
|
|
|
179
179
|
UI.Toolbar.ItemsProvider {
|
|
180
180
|
#watchExpressions: WatchExpression[];
|
|
181
181
|
#watchExpressionsSetting: Common.Settings.Setting<string[]>;
|
|
182
|
-
private readonly addButton: UI.Toolbar.ToolbarButton;
|
|
183
|
-
private readonly refreshButton: UI.Toolbar.ToolbarButton;
|
|
184
182
|
private readonly linkifier: Components.Linkifier.Linkifier;
|
|
185
183
|
#view: View;
|
|
186
184
|
#expandControllers = new Map<string, ObjectUI.ObjectPropertiesSection.ObjectTreeExpansionTracker>();
|
|
@@ -192,17 +190,6 @@ export class WatchExpressionsSidebarPane extends UI.Widget.VBox implements UI.Ac
|
|
|
192
190
|
this.#watchExpressionsSetting =
|
|
193
191
|
Common.Settings.Settings.instance().createLocalSetting<string[]>('watch-expressions', []);
|
|
194
192
|
|
|
195
|
-
this.addButton = new UI.Toolbar.ToolbarButton(
|
|
196
|
-
i18nString(UIStrings.addWatchExpression), 'plus', undefined, 'add-watch-expression');
|
|
197
|
-
this.addButton.setSize(Buttons.Button.Size.SMALL);
|
|
198
|
-
this.addButton.addEventListener(UI.Toolbar.ToolbarButton.Events.CLICK, _event => {
|
|
199
|
-
void this.addButtonClicked();
|
|
200
|
-
});
|
|
201
|
-
this.refreshButton = new UI.Toolbar.ToolbarButton(
|
|
202
|
-
i18nString(UIStrings.refreshWatchExpressions), 'refresh', undefined, 'refresh-watch-expressions');
|
|
203
|
-
this.refreshButton.setSize(Buttons.Button.Size.SMALL);
|
|
204
|
-
this.refreshButton.addEventListener(UI.Toolbar.ToolbarButton.Events.CLICK, this.#refreshExpressions, this);
|
|
205
|
-
|
|
206
193
|
UI.Context.Context.instance().addFlavorChangeListener(
|
|
207
194
|
SDK.RuntimeModel.ExecutionContext, this.#refreshExpressions, this);
|
|
208
195
|
UI.Context.Context.instance().addFlavorChangeListener(
|
|
@@ -223,8 +210,27 @@ export class WatchExpressionsSidebarPane extends UI.Widget.VBox implements UI.Ac
|
|
|
223
210
|
return this.#watchExpressions;
|
|
224
211
|
}
|
|
225
212
|
|
|
226
|
-
toolbarItems():
|
|
227
|
-
|
|
213
|
+
toolbarItems(): TemplateResult {
|
|
214
|
+
// clang-format off
|
|
215
|
+
return html`
|
|
216
|
+
<devtools-button .data=${{
|
|
217
|
+
variant: Buttons.Button.Variant.TOOLBAR,
|
|
218
|
+
iconName: 'plus',
|
|
219
|
+
size: Buttons.Button.Size.SMALL,
|
|
220
|
+
title: i18nString(UIStrings.addWatchExpression),
|
|
221
|
+
jslogContext: 'add-watch-expression',
|
|
222
|
+
} as Buttons.Button.ButtonData}
|
|
223
|
+
@click=${(e: Event) => this.addButtonClicked(e)}></devtools-button>
|
|
224
|
+
<devtools-button .data=${{
|
|
225
|
+
variant: Buttons.Button.Variant.TOOLBAR,
|
|
226
|
+
iconName: 'refresh',
|
|
227
|
+
size: Buttons.Button.Size.SMALL,
|
|
228
|
+
title: i18nString(UIStrings.refreshWatchExpressions),
|
|
229
|
+
jslogContext: 'refresh-watch-expressions',
|
|
230
|
+
} as Buttons.Button.ButtonData}
|
|
231
|
+
@click=${(e: Event) => this.refreshButtonClicked(e)}></devtools-button>
|
|
232
|
+
`;
|
|
233
|
+
// clang-format on
|
|
228
234
|
}
|
|
229
235
|
|
|
230
236
|
private saveExpressions(): void {
|
|
@@ -239,7 +245,8 @@ export class WatchExpressionsSidebarPane extends UI.Widget.VBox implements UI.Ac
|
|
|
239
245
|
this.#watchExpressionsSetting.set(toSave);
|
|
240
246
|
}
|
|
241
247
|
|
|
242
|
-
private async addButtonClicked(): Promise<void> {
|
|
248
|
+
private async addButtonClicked(event?: Event): Promise<void> {
|
|
249
|
+
event?.consume(true);
|
|
243
250
|
await UI.ViewManager.ViewManager.instance().showView('sources.watch');
|
|
244
251
|
const watchExpression = this.createWatchExpression(null);
|
|
245
252
|
this.requestUpdate();
|
|
@@ -250,6 +257,11 @@ export class WatchExpressionsSidebarPane extends UI.Widget.VBox implements UI.Ac
|
|
|
250
257
|
watchExpression.startEditing();
|
|
251
258
|
}
|
|
252
259
|
|
|
260
|
+
private refreshButtonClicked(event: Event): void {
|
|
261
|
+
event.consume(true);
|
|
262
|
+
this.#refreshExpressions();
|
|
263
|
+
}
|
|
264
|
+
|
|
253
265
|
#refreshExpressions(): void {
|
|
254
266
|
this.linkifier.reset();
|
|
255
267
|
for (const expression of this.#watchExpressions) {
|
|
@@ -407,7 +419,6 @@ export class WatchExpression extends Common.ObjectWrapper.ObjectWrapper<EventTyp
|
|
|
407
419
|
get result(): SDK.RemoteObject.RemoteObject|null {
|
|
408
420
|
return this.#result ?? null;
|
|
409
421
|
}
|
|
410
|
-
|
|
411
422
|
get updateComplete(): Promise<void> {
|
|
412
423
|
return this.#updateComplete;
|
|
413
424
|
}
|
|
@@ -1076,6 +1076,13 @@ UI.ActionRegistration.registerActionExtension({
|
|
|
1076
1076
|
UI.ActionRegistration.KeybindSet.VS_CODE,
|
|
1077
1077
|
],
|
|
1078
1078
|
},
|
|
1079
|
+
{
|
|
1080
|
+
shortcut: 'Alt+g',
|
|
1081
|
+
keybindSets: [
|
|
1082
|
+
UI.ActionRegistration.KeybindSet.DEVTOOLS_DEFAULT,
|
|
1083
|
+
UI.ActionRegistration.KeybindSet.VS_CODE,
|
|
1084
|
+
],
|
|
1085
|
+
},
|
|
1079
1086
|
],
|
|
1080
1087
|
});
|
|
1081
1088
|
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
Name: Dependencies sourced from the upstream `chromium` repository
|
|
2
2
|
URL: Internal
|
|
3
3
|
Version: N/A
|
|
4
|
-
Revision:
|
|
4
|
+
Revision: f24b2d64e5f1e00acb4dadb6becaa6aef859c307
|
|
5
5
|
Update Mechanism: Manual (https://crbug.com/428069060)
|
|
6
6
|
License: BSD-3-Clause
|
|
7
7
|
License File: LICENSE
|
|
@@ -110,6 +110,8 @@ export class TabbedPane extends Common.ObjectWrapper.eventMixin<EventTypes, type
|
|
|
110
110
|
private measuredDropDownButtonWidth?: number;
|
|
111
111
|
#leftToolbar?: Toolbar;
|
|
112
112
|
#rightToolbar?: Toolbar;
|
|
113
|
+
#trailingSlot!: HTMLSlotElement;
|
|
114
|
+
#lastDispatchedHiddenTabIds = '';
|
|
113
115
|
allowTabReorder?: boolean;
|
|
114
116
|
private automaticReorder?: boolean;
|
|
115
117
|
|
|
@@ -144,6 +146,25 @@ export class TabbedPane extends Common.ObjectWrapper.eventMixin<EventTypes, type
|
|
|
144
146
|
this.tabSlider = document.createElement('div');
|
|
145
147
|
this.tabSlider.classList.add('tabbed-pane-tab-slider');
|
|
146
148
|
this.tabsElement = this.headerContentsElement.createChild('div', 'tabbed-pane-header-tabs');
|
|
149
|
+
|
|
150
|
+
// Declarative slot for a trailing affordance (e.g. a plus button).
|
|
151
|
+
// When something is assigned to this slot, the legacy overflow
|
|
152
|
+
// chevron is suppressed and the slotted element's width is reserved
|
|
153
|
+
// during overflow detection so it always stays visible. The slot
|
|
154
|
+
// itself has `display: contents` and so contributes no layout box
|
|
155
|
+
// when empty.
|
|
156
|
+
this.#trailingSlot = document.createElement('slot');
|
|
157
|
+
this.#trailingSlot.name = 'trailing-button';
|
|
158
|
+
this.#trailingSlot.classList.add('tabbed-pane-trailing-button');
|
|
159
|
+
this.headerContentsElement.appendChild(this.#trailingSlot);
|
|
160
|
+
this.#trailingSlot.addEventListener('slotchange', () => this.requestUpdate());
|
|
161
|
+
|
|
162
|
+
// Re-run overflow detection whenever the available space for tabs
|
|
163
|
+
// changes. The header is a flex container in which `headerContentsElement`
|
|
164
|
+
// is `flex: auto`, so any growth in the left/right toolbars (e.g. the
|
|
165
|
+
// issues counter loading asynchronously into the right toolbar) shrinks
|
|
166
|
+
// `headerContentsElement` and is captured by this single observer.
|
|
167
|
+
new ResizeObserver(() => this.requestUpdate()).observe(this.headerContentsElement);
|
|
147
168
|
this.tabsElement.setAttribute('role', 'tablist');
|
|
148
169
|
this.tabsElement.addEventListener('keydown', this.keyDown.bind(this), false);
|
|
149
170
|
this.#contentElement = this.contentElement.createChild('div', 'tabbed-pane-content');
|
|
@@ -737,15 +758,6 @@ export class TabbedPane extends Common.ObjectWrapper.eventMixin<EventTypes, type
|
|
|
737
758
|
}
|
|
738
759
|
}
|
|
739
760
|
|
|
740
|
-
private showTabElement(index: number, tab: TabbedPaneTab): void {
|
|
741
|
-
if (index >= this.tabsElement.children.length) {
|
|
742
|
-
this.tabsElement.appendChild(tab.tabElement);
|
|
743
|
-
} else {
|
|
744
|
-
this.tabsElement.insertBefore(tab.tabElement, this.tabsElement.children[index]);
|
|
745
|
-
}
|
|
746
|
-
tab.shown = true;
|
|
747
|
-
}
|
|
748
|
-
|
|
749
761
|
private hideTabElement(tab: TabbedPaneTab): void {
|
|
750
762
|
this.tabsElement.removeChild(tab.tabElement);
|
|
751
763
|
tab.shown = false;
|
|
@@ -831,8 +843,16 @@ export class TabbedPane extends Common.ObjectWrapper.eventMixin<EventTypes, type
|
|
|
831
843
|
}
|
|
832
844
|
|
|
833
845
|
private updateTabsDropDown(): void {
|
|
834
|
-
|
|
835
|
-
|
|
846
|
+
// Reserve width for the trailing affordance so it never gets clipped
|
|
847
|
+
// or pushed past the right edge. When a `trailing-button` slot is
|
|
848
|
+
// populated (e.g. with a plus button), reserve that element's width
|
|
849
|
+
// and never use the legacy chevron. Otherwise, fall back to the
|
|
850
|
+
// legacy chevron behavior: reserve its measured width only when at
|
|
851
|
+
// least one tab is overflowed.
|
|
852
|
+
const slottedTrailing = this.#trailingSlot.assignedElements()[0];
|
|
853
|
+
const reservedWidth =
|
|
854
|
+
slottedTrailing ? slottedTrailing.getBoundingClientRect().width : (this.measuredDropDownButtonWidth || 0);
|
|
855
|
+
const tabsToShowIndexes = this.tabsToShowIndexes(this.#tabs, this.tabsHistory, this.totalWidth(), reservedWidth);
|
|
836
856
|
if (this.lastSelectedOverflowTab && this.numberOfTabsShown() !== tabsToShowIndexes.length) {
|
|
837
857
|
delete this.lastSelectedOverflowTab;
|
|
838
858
|
this.updateTabsDropDown();
|
|
@@ -844,24 +864,121 @@ export class TabbedPane extends Common.ObjectWrapper.eventMixin<EventTypes, type
|
|
|
844
864
|
this.hideTabElement(this.#tabs[i]);
|
|
845
865
|
}
|
|
846
866
|
}
|
|
867
|
+
// Synchronize the DOM order of the visible tab row to match
|
|
868
|
+
// `tabsToShowIndexes`. `insertBefore` moves a node if it is
|
|
869
|
+
// already in the DOM (preserving focus/selection) and inserts
|
|
870
|
+
// it otherwise, so a single pass handles both newly-visible
|
|
871
|
+
// tabs and reorderings of currently-visible tabs without any
|
|
872
|
+
// imperative DOM manipulation from callers like `moveTab`.
|
|
847
873
|
for (let i = 0; i < tabsToShowIndexes.length; ++i) {
|
|
848
874
|
const tab = this.#tabs[tabsToShowIndexes[i]];
|
|
849
|
-
if (
|
|
850
|
-
this.
|
|
875
|
+
if (this.tabsElement.children[i] !== tab.tabElement) {
|
|
876
|
+
this.tabsElement.insertBefore(tab.tabElement, this.tabsElement.children[i] ?? null);
|
|
851
877
|
}
|
|
878
|
+
tab.shown = true;
|
|
852
879
|
}
|
|
853
880
|
|
|
854
881
|
this.maybeShowDropDown(tabsToShowIndexes.length !== this.#tabs.length);
|
|
882
|
+
this.#dispatchOverflowTabsChangedIfNeeded();
|
|
855
883
|
}
|
|
856
884
|
|
|
857
885
|
private maybeShowDropDown(hasMoreTabs: boolean): void {
|
|
858
|
-
|
|
886
|
+
// The legacy chevron is suppressed when a trailing-button slot is
|
|
887
|
+
// populated; the slotted element takes its place.
|
|
888
|
+
const shouldShow = this.#trailingSlot.assignedElements().length === 0 && hasMoreTabs;
|
|
889
|
+
if (shouldShow && !this.dropDownButton.parentElement) {
|
|
859
890
|
this.headerContentsElement.appendChild(this.dropDownButton);
|
|
860
|
-
} else if (!
|
|
891
|
+
} else if (!shouldShow && this.dropDownButton.parentElement) {
|
|
861
892
|
this.headerContentsElement.removeChild(this.dropDownButton);
|
|
862
893
|
}
|
|
863
894
|
}
|
|
864
895
|
|
|
896
|
+
/**
|
|
897
|
+
* Dispatches an `overflow-tabs-changed` DOM event whenever the set of
|
|
898
|
+
* hidden (overflowed) tabs changes. The event bubbles and is composed
|
|
899
|
+
* so consumers slotted into `trailing-button` can listen via the
|
|
900
|
+
* standard `addEventListener` / lit-html `@overflow-tabs-changed=`
|
|
901
|
+
* syntax. Payload (`event.detail.hiddenTabs`) lists hidden tabs in
|
|
902
|
+
* tab order.
|
|
903
|
+
*
|
|
904
|
+
* `updateTabsDropDown()` runs on every layout/resize tick (including
|
|
905
|
+
* ResizeObserver callbacks on the toolbars), but in the steady state
|
|
906
|
+
* the overflow set rarely changes. Dedup against the last dispatched
|
|
907
|
+
* tab-id list so consumers (e.g. a plus button rendering the hidden
|
|
908
|
+
* tabs in its menu) are not re-notified on every paint.
|
|
909
|
+
*/
|
|
910
|
+
#dispatchOverflowTabsChangedIfNeeded(): void {
|
|
911
|
+
const hidden = this.hiddenTabs();
|
|
912
|
+
const key = hidden.map(t => t.id).join('\u0000');
|
|
913
|
+
if (key === this.#lastDispatchedHiddenTabIds) {
|
|
914
|
+
return;
|
|
915
|
+
}
|
|
916
|
+
this.#lastDispatchedHiddenTabIds = key;
|
|
917
|
+
this.dispatchDOMEvent(new CustomEvent('overflow-tabs-changed', {
|
|
918
|
+
bubbles: true,
|
|
919
|
+
composed: true,
|
|
920
|
+
detail: {hiddenTabs: hidden},
|
|
921
|
+
}));
|
|
922
|
+
}
|
|
923
|
+
|
|
924
|
+
/**
|
|
925
|
+
* Returns the tabs that are currently hidden because of overflow, in
|
|
926
|
+
* tab order. Mirrors the payload of the `overflow-tabs-changed` event.
|
|
927
|
+
*/
|
|
928
|
+
hiddenTabs(): Array<{id: string, title: string, jslogContext?: string}> {
|
|
929
|
+
return this.#tabs.filter(tab => !tab.shown).map(tab => ({
|
|
930
|
+
id: tab.id,
|
|
931
|
+
title: tab.title,
|
|
932
|
+
jslogContext: tab.jslogContext,
|
|
933
|
+
}));
|
|
934
|
+
}
|
|
935
|
+
|
|
936
|
+
/**
|
|
937
|
+
* Returns the index in tab order of the first overflowed (hidden) tab,
|
|
938
|
+
* or -1 if every tab fits.
|
|
939
|
+
*/
|
|
940
|
+
firstHiddenTabIndex(): number {
|
|
941
|
+
return this.#tabs.findIndex(tab => !tab.shown);
|
|
942
|
+
}
|
|
943
|
+
|
|
944
|
+
/**
|
|
945
|
+
* Reorders `tabId` to position `newIndex` in the tab list. Unlike
|
|
946
|
+
* {@link insertBefore}, this does not require the tab to currently be in
|
|
947
|
+
* the DOM (overflowed tabs are detached). The change is persisted via the
|
|
948
|
+
* {@link Events.TabOrderChanged} event so the new order survives across
|
|
949
|
+
* DevTools reloads.
|
|
950
|
+
*/
|
|
951
|
+
moveTab(tabId: string, newIndex: number): void {
|
|
952
|
+
const tab = this.tabsById.get(tabId);
|
|
953
|
+
if (!tab) {
|
|
954
|
+
return;
|
|
955
|
+
}
|
|
956
|
+
const oldIndex = this.#tabs.indexOf(tab);
|
|
957
|
+
if (oldIndex === -1) {
|
|
958
|
+
return;
|
|
959
|
+
}
|
|
960
|
+
// `newIndex` is the desired final index of the tab. The maximum
|
|
961
|
+
// valid final index for an existing item is `length - 1`; clamp
|
|
962
|
+
// here so the no-op early return below catches calls like
|
|
963
|
+
// `moveTab(lastTabId, 999)` instead of doing redundant work and
|
|
964
|
+
// firing a spurious TabOrderChanged event.
|
|
965
|
+
const targetIndex = Math.max(0, Math.min(newIndex, this.#tabs.length - 1));
|
|
966
|
+
if (oldIndex === targetIndex) {
|
|
967
|
+
return;
|
|
968
|
+
}
|
|
969
|
+
this.#tabs.splice(oldIndex, 1);
|
|
970
|
+
// After the splice-remove the array is one shorter, so inserting at
|
|
971
|
+
// `targetIndex` lands the tab at exactly the requested final index
|
|
972
|
+
// regardless of move direction.
|
|
973
|
+
this.#tabs.splice(targetIndex, 0, tab);
|
|
974
|
+
// The visible row's DOM order is reconciled declaratively in
|
|
975
|
+
// `updateTabsDropDown()` (scheduled by `requestUpdate()` below),
|
|
976
|
+
// so no imperative DOM reordering is needed here.
|
|
977
|
+
const eventData: EventData = {tabId: tab.id, view: tab.view};
|
|
978
|
+
this.dispatchEventToListeners(Events.TabOrderChanged, eventData);
|
|
979
|
+
this.requestUpdate();
|
|
980
|
+
}
|
|
981
|
+
|
|
865
982
|
private measureDropDownButton(): void {
|
|
866
983
|
if (this.measuredDropDownButtonWidth) {
|
|
867
984
|
return;
|
|
@@ -964,11 +1081,20 @@ export class TabbedPane extends Common.ObjectWrapper.eventMixin<EventTypes, type
|
|
|
964
1081
|
if (this.lastSelectedOverflowTab !== undefined) {
|
|
965
1082
|
tabsToLookAt.unshift(tabsToLookAt.splice(tabsToLookAt.indexOf(this.lastSelectedOverflowTab), 1)[0]);
|
|
966
1083
|
}
|
|
1084
|
+
// The trailing affordance only steals space when it is shown. The
|
|
1085
|
+
// legacy chevron is conditional on actual overflow (so it can be
|
|
1086
|
+
// skipped on the very last tab if everything fits), but a slotted
|
|
1087
|
+
// trailing button (e.g. a plus button) is always rendered and must
|
|
1088
|
+
// therefore always be reserved for — even when the last tab would
|
|
1089
|
+
// otherwise just barely fit by itself — otherwise it gets pushed
|
|
1090
|
+
// past the right edge and clipped by the surrounding
|
|
1091
|
+
// `overflow: hidden` containers.
|
|
1092
|
+
const reserveOnLastTab = this.#trailingSlot.assignedElements().length > 0;
|
|
967
1093
|
for (let i = 0; i < tabCount; ++i) {
|
|
968
1094
|
const tab = this.automaticReorder ? tabsHistory[i] : tabsToLookAt[i];
|
|
969
1095
|
totalTabsWidth += tab.width();
|
|
970
1096
|
let minimalRequiredWidth = totalTabsWidth;
|
|
971
|
-
if (i !== tabCount - 1) {
|
|
1097
|
+
if (i !== tabCount - 1 || reserveOnLastTab) {
|
|
972
1098
|
minimalRequiredWidth += measuredDropDownButtonWidth;
|
|
973
1099
|
}
|
|
974
1100
|
if (!this.verticalTabLayout && minimalRequiredWidth > totalWidth) {
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
// Copyright 2026 The Chromium Authors
|
|
2
|
+
// Use of this source code is governed by a BSD-style license that can be
|
|
3
|
+
// found in the LICENSE file.
|
|
4
|
+
|
|
5
|
+
import './legacy.js';
|
|
6
|
+
|
|
7
|
+
import type * as Platform from '../../core/platform/platform.js';
|
|
8
|
+
import * as Lit from '../lit/lit.js';
|
|
9
|
+
|
|
10
|
+
import type * as UI from './legacy.js';
|
|
11
|
+
|
|
12
|
+
const {html} = Lit;
|
|
13
|
+
|
|
14
|
+
export function render(container: HTMLElement): void {
|
|
15
|
+
const validator = (text: string): null|Platform.UIString.LocalizedString => {
|
|
16
|
+
return text === 'invalid' ? 'This value is invalid' as Platform.UIString.LocalizedString : null;
|
|
17
|
+
};
|
|
18
|
+
|
|
19
|
+
Lit.render(
|
|
20
|
+
html`
|
|
21
|
+
<style>
|
|
22
|
+
.example {
|
|
23
|
+
margin: 20px;
|
|
24
|
+
padding: 20px;
|
|
25
|
+
border: 1px solid var(--sys-color-divider);
|
|
26
|
+
}
|
|
27
|
+
.label {
|
|
28
|
+
margin-bottom: 8px;
|
|
29
|
+
font-weight: bold;
|
|
30
|
+
}
|
|
31
|
+
</style>
|
|
32
|
+
<div class="example">
|
|
33
|
+
<div class="label">TextPrompt with validation (type 'invalid' and press Enter):</div>
|
|
34
|
+
<devtools-prompt
|
|
35
|
+
editing
|
|
36
|
+
.validator=${validator}
|
|
37
|
+
@commit=${(e: UI.TextPrompt.TextPromptElement.CommitEvent) => console.log('Committed:', e.detail)}
|
|
38
|
+
></devtools-prompt>
|
|
39
|
+
</div>
|
|
40
|
+
|
|
41
|
+
<div class="example">
|
|
42
|
+
<div class="label">TextPrompt with cancel-on-blur:</div>
|
|
43
|
+
<devtools-prompt
|
|
44
|
+
editing
|
|
45
|
+
cancel-on-blur
|
|
46
|
+
@cancel=${() => console.log('Cancelled')}
|
|
47
|
+
>Initial content</devtools-prompt>
|
|
48
|
+
</div>
|
|
49
|
+
`,
|
|
50
|
+
container);
|
|
51
|
+
}
|
|
@@ -67,19 +67,39 @@ import {cloneCustomElement, ElementFocusRestorer} from './UIUtils.js';
|
|
|
67
67
|
* large.
|
|
68
68
|
*/
|
|
69
69
|
export class TextPromptElement extends HTMLElement {
|
|
70
|
-
static readonly observedAttributes = ['editing', 'completions', 'placeholder'];
|
|
70
|
+
static readonly observedAttributes = ['editing', 'completions', 'placeholder', 'cancel-on-blur'];
|
|
71
|
+
static formAssociated = true;
|
|
71
72
|
readonly #shadow = this.attachShadow({mode: 'open'});
|
|
73
|
+
readonly #internals = this.attachInternals();
|
|
72
74
|
readonly #entrypoint = this.#shadow.createChild('span');
|
|
73
75
|
readonly #slot = this.#entrypoint.createChild('slot');
|
|
74
76
|
readonly #textPrompt = new TextPrompt();
|
|
75
77
|
#completionTimeout: number|null = null;
|
|
76
78
|
#completionObserver = new MutationObserver(this.#onMutate.bind(this));
|
|
79
|
+
#validator?: (value: string) => null | Platform.UIString.LocalizedString;
|
|
80
|
+
#cancelOnBlur = false;
|
|
77
81
|
|
|
78
82
|
constructor() {
|
|
79
83
|
super();
|
|
80
84
|
this.#textPrompt.initialize(this.#willAutoComplete.bind(this));
|
|
81
85
|
}
|
|
82
86
|
|
|
87
|
+
set validator(v: (value: string) => (null | Platform.UIString.LocalizedString)) {
|
|
88
|
+
this.#validator = v;
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
get validator(): undefined|((value: string) => (null | Platform.UIString.LocalizedString)) {
|
|
92
|
+
return this.#validator;
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
set cancelOnBlur(cancelOnBlur: boolean) {
|
|
96
|
+
this.setAttribute('cancel-on-blur', `${cancelOnBlur}`);
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
get cancelOnBlur(): boolean {
|
|
100
|
+
return this.#cancelOnBlur;
|
|
101
|
+
}
|
|
102
|
+
|
|
83
103
|
#onMutate(changes: MutationRecord[]): void {
|
|
84
104
|
const listId = this.getAttribute('completions');
|
|
85
105
|
if (!listId) {
|
|
@@ -103,17 +123,27 @@ export class TextPromptElement extends HTMLElement {
|
|
|
103
123
|
this.#updateCompletions();
|
|
104
124
|
}
|
|
105
125
|
}
|
|
106
|
-
|
|
107
126
|
attributeChangedCallback(name: string, oldValue: string|null, newValue: string|null): void {
|
|
108
127
|
if (oldValue === newValue) {
|
|
109
128
|
return;
|
|
110
129
|
}
|
|
111
130
|
|
|
131
|
+
const isTruthy = (value: string|null): boolean => value !== null && value !== 'false';
|
|
132
|
+
|
|
112
133
|
switch (name) {
|
|
134
|
+
case 'cancel-on-blur':
|
|
135
|
+
if (isTruthy(newValue)) {
|
|
136
|
+
this.#cancelOnBlur = true;
|
|
137
|
+
} else {
|
|
138
|
+
this.#cancelOnBlur = false;
|
|
139
|
+
}
|
|
140
|
+
break;
|
|
113
141
|
case 'editing':
|
|
114
142
|
if (this.isConnected) {
|
|
115
|
-
if (newValue
|
|
116
|
-
|
|
143
|
+
if (isTruthy(newValue)) {
|
|
144
|
+
if (!isTruthy(oldValue)) {
|
|
145
|
+
this.#startEditing();
|
|
146
|
+
}
|
|
117
147
|
} else {
|
|
118
148
|
this.#stopEditing();
|
|
119
149
|
}
|
|
@@ -166,9 +196,11 @@ export class TextPromptElement extends HTMLElement {
|
|
|
166
196
|
}
|
|
167
197
|
this.#slot.remove();
|
|
168
198
|
|
|
169
|
-
const proxy =
|
|
199
|
+
const proxy =
|
|
200
|
+
this.#textPrompt.attachAndStartEditing(placeholder, e => this.#done(e, /* commit=*/ !this.#cancelOnBlur));
|
|
170
201
|
proxy.addEventListener('keydown', this.#editingValueKeyDown.bind(this));
|
|
171
202
|
placeholder.getComponentSelection()?.selectAllChildren(placeholder);
|
|
203
|
+
this.#textPrompt.focus();
|
|
172
204
|
}
|
|
173
205
|
|
|
174
206
|
#stopEditing(): void {
|
|
@@ -183,10 +215,26 @@ export class TextPromptElement extends HTMLElement {
|
|
|
183
215
|
}
|
|
184
216
|
}
|
|
185
217
|
|
|
218
|
+
disconnectedCallback(): void {
|
|
219
|
+
if (this.hasAttribute('editing')) {
|
|
220
|
+
this.#stopEditing();
|
|
221
|
+
}
|
|
222
|
+
}
|
|
223
|
+
|
|
186
224
|
#done(e: Event, commit: boolean): void {
|
|
187
225
|
const target = e.target as HTMLElement;
|
|
188
226
|
const text = target.textContent || '';
|
|
227
|
+
this.#internals.setValidity({});
|
|
189
228
|
if (commit) {
|
|
229
|
+
const validationMessage = this.#validator?.(text) ?? '';
|
|
230
|
+
if (validationMessage) {
|
|
231
|
+
this.#internals.setValidity({customError: true}, validationMessage, this.#entrypoint);
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
if (!this.#internals.reportValidity()) {
|
|
235
|
+
return;
|
|
236
|
+
}
|
|
237
|
+
|
|
190
238
|
this.dispatchEvent(new TextPromptElement.CommitEvent(text));
|
|
191
239
|
} else {
|
|
192
240
|
this.dispatchEvent(new TextPromptElement.CancelEvent());
|
|
@@ -198,6 +246,7 @@ export class TextPromptElement extends HTMLElement {
|
|
|
198
246
|
if (event.handled || !(event instanceof KeyboardEvent)) {
|
|
199
247
|
return;
|
|
200
248
|
}
|
|
249
|
+
this.#internals.setValidity({});
|
|
201
250
|
|
|
202
251
|
if (event.key === 'Enter') {
|
|
203
252
|
this.#done(event, /* commit=*/ true);
|
|
@@ -226,7 +275,7 @@ export namespace TextPromptElement {
|
|
|
226
275
|
super('commit', {detail});
|
|
227
276
|
}
|
|
228
277
|
}
|
|
229
|
-
export class CancelEvent extends CustomEvent<
|
|
278
|
+
export class CancelEvent extends CustomEvent<void> {
|
|
230
279
|
constructor() {
|
|
231
280
|
super('cancel');
|
|
232
281
|
}
|
|
@@ -11,6 +11,7 @@ import * as Root from '../../core/root/root.js';
|
|
|
11
11
|
import * as Buttons from '../../ui/components/buttons/buttons.js';
|
|
12
12
|
import * as VisualLogging from '../../ui/visual_logging/visual_logging.js';
|
|
13
13
|
import {createIcon} from '../kit/kit.js';
|
|
14
|
+
import type {TemplateResult} from '../lit/lit.js';
|
|
14
15
|
|
|
15
16
|
import {type Action, Events as ActionEvents} from './ActionRegistration.js';
|
|
16
17
|
import {ActionRegistry} from './ActionRegistry.js';
|
|
@@ -1204,7 +1205,7 @@ export interface Provider {
|
|
|
1204
1205
|
}
|
|
1205
1206
|
|
|
1206
1207
|
export interface ItemsProvider {
|
|
1207
|
-
toolbarItems(): ToolbarItem[];
|
|
1208
|
+
toolbarItems(): ToolbarItem[]|TemplateResult;
|
|
1208
1209
|
}
|
|
1209
1210
|
|
|
1210
1211
|
export class ToolbarComboBox extends ToolbarItem<void, HTMLSelectElement> {
|
|
@@ -3,6 +3,7 @@
|
|
|
3
3
|
// found in the LICENSE file.
|
|
4
4
|
|
|
5
5
|
import * as Platform from '../../core/platform/platform.js';
|
|
6
|
+
import type {TemplateResult} from '../lit/lit.js';
|
|
6
7
|
|
|
7
8
|
import type {TabbedPane} from './TabbedPane.js';
|
|
8
9
|
import type {ToolbarItem, ToolbarMenuButton} from './Toolbar.js';
|
|
@@ -22,7 +23,7 @@ export interface View {
|
|
|
22
23
|
|
|
23
24
|
isTransient(): boolean;
|
|
24
25
|
|
|
25
|
-
toolbarItems(): Promise<ToolbarItem[]>;
|
|
26
|
+
toolbarItems(): Promise<ToolbarItem[]|TemplateResult>;
|
|
26
27
|
|
|
27
28
|
widget(): Promise<AnyWidget>;
|
|
28
29
|
|
|
@@ -12,6 +12,7 @@ import * as Platform from '../../core/platform/platform.js';
|
|
|
12
12
|
import type * as Root from '../../core/root/root.js';
|
|
13
13
|
import type * as Foundation from '../../foundation/foundation.js';
|
|
14
14
|
import {createIcon, type Icon} from '../kit/kit.js';
|
|
15
|
+
import {render, type TemplateResult} from '../lit/lit.js';
|
|
15
16
|
import * as VisualLogging from '../visual_logging/visual_logging.js';
|
|
16
17
|
|
|
17
18
|
import * as ARIAUtils from './ARIAUtils.js';
|
|
@@ -119,7 +120,7 @@ export class PreRegisteredView implements View {
|
|
|
119
120
|
return this.viewRegistration.persistence;
|
|
120
121
|
}
|
|
121
122
|
|
|
122
|
-
async toolbarItems(): Promise<ToolbarItem[]> {
|
|
123
|
+
async toolbarItems(): Promise<ToolbarItem[]|TemplateResult> {
|
|
123
124
|
if (!this.viewRegistration.hasToolbar) {
|
|
124
125
|
return [];
|
|
125
126
|
}
|
|
@@ -244,13 +245,18 @@ export class ViewManager extends Common.ObjectWrapper.ObjectWrapper<EventTypes>
|
|
|
244
245
|
viewManagerInstance = undefined;
|
|
245
246
|
}
|
|
246
247
|
|
|
247
|
-
static createToolbar(toolbarItems: ToolbarItem[]): Element|null {
|
|
248
|
-
if (!toolbarItems.length) {
|
|
248
|
+
static createToolbar(toolbarItems: ToolbarItem[]|TemplateResult): Element|null {
|
|
249
|
+
if (Array.isArray(toolbarItems) && !toolbarItems.length) {
|
|
249
250
|
return null;
|
|
250
251
|
}
|
|
251
252
|
const toolbar = document.createElement('devtools-toolbar');
|
|
252
|
-
|
|
253
|
-
|
|
253
|
+
if (Array.isArray(toolbarItems)) {
|
|
254
|
+
for (const item of toolbarItems) {
|
|
255
|
+
toolbar.appendToolbarItem(item);
|
|
256
|
+
}
|
|
257
|
+
} else {
|
|
258
|
+
// eslint-disable-next-line @devtools/no-lit-render-outside-of-view
|
|
259
|
+
render(toolbarItems, toolbar);
|
|
254
260
|
}
|
|
255
261
|
return toolbar;
|
|
256
262
|
}
|
|
@@ -37,6 +37,10 @@
|
|
|
37
37
|
color: var(--sys-color-token-subtle) !important; /* stylelint-disable-line declaration-no-important */
|
|
38
38
|
}
|
|
39
39
|
|
|
40
|
+
:host(:invalid) .text-prompt {
|
|
41
|
+
outline: var(--sys-color-error) auto var(--sys-size-1);
|
|
42
|
+
}
|
|
43
|
+
|
|
40
44
|
.text-prompt[data-placeholder] {
|
|
41
45
|
&:empty::before {
|
|
42
46
|
content: attr(data-placeholder);
|
package/mcp/mcp.ts
CHANGED
|
@@ -23,6 +23,7 @@ export type * as CDPConnection from '../front_end/core/protocol_client/CDPConnec
|
|
|
23
23
|
export {ConnectionTransport} from '../front_end/core/protocol_client/ConnectionTransport.js';
|
|
24
24
|
export * as ProtocolClient from '../front_end/core/protocol_client/protocol_client.js';
|
|
25
25
|
export {DebuggerModel} from '../front_end/core/sdk/DebuggerModel.js';
|
|
26
|
+
export * as NetworkManager from '../front_end/core/sdk/NetworkManager.js';
|
|
26
27
|
export {RuntimeModel} from '../front_end/core/sdk/RuntimeModel.js';
|
|
27
28
|
export {Target} from '../front_end/core/sdk/Target.js';
|
|
28
29
|
export {TargetManager} from '../front_end/core/sdk/TargetManager.js';
|
package/package.json
CHANGED