chrome-devtools-frontend 1.0.1643099 → 1.0.1645245
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/eslint.config.mjs +3 -1
- package/extension-api/ExtensionAPI.d.ts +83 -12
- package/front_end/core/host/UserMetrics.ts +3 -3
- package/front_end/core/root/ExperimentNames.ts +0 -1
- package/front_end/core/sdk/CSSPropertyParserMatchers.ts +2 -3
- package/front_end/core/sdk/ConsoleModel.ts +4 -0
- package/front_end/core/sdk/NetworkRequest.ts +12 -1
- package/front_end/core/sdk/SourceMap.ts +15 -18
- 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/heap_snapshot_worker/HeapSnapshot.ts +37 -0
- package/front_end/entrypoints/main/MainImpl.ts +0 -6
- package/front_end/generated/SupportedCSSProperties.js +4 -2
- package/front_end/models/ai_assistance/AiAgent2.ts +23 -12
- package/front_end/models/ai_assistance/README.md +5 -4
- package/front_end/models/ai_assistance/agents/AccessibilityAgent.ts +22 -16
- package/front_end/models/ai_assistance/agents/AiAgent.ts +19 -6
- package/front_end/models/ai_assistance/agents/ContextSelectionAgent.ts +5 -5
- package/front_end/models/ai_assistance/agents/ExecuteJavascript.ts +1 -94
- package/front_end/models/ai_assistance/agents/NetworkAgent.ts +25 -0
- package/front_end/models/ai_assistance/agents/PerformanceAgent.ts +37 -0
- package/front_end/models/ai_assistance/agents/README.md +57 -0
- package/front_end/models/ai_assistance/agents/StorageAgent.ts +54 -1
- package/front_end/models/ai_assistance/agents/StylingAgent.snapshot.txt +1 -2
- package/front_end/models/ai_assistance/agents/StylingAgent.ts +31 -3
- package/front_end/models/ai_assistance/tools/ExecuteJavaScript.ts +12 -21
- package/front_end/models/ai_assistance/tools/GetStyles.ts +19 -12
- package/front_end/models/ai_assistance/tools/README.md +45 -0
- package/front_end/models/ai_assistance/tools/Tool.ts +78 -9
- package/front_end/models/ai_assistance/tools/ToolRegistry.ts +21 -5
- package/front_end/models/bindings/DebuggerWorkspaceBinding.ts +6 -9
- package/front_end/models/bindings/DefaultScriptMapping.ts +2 -1
- package/front_end/models/bindings/SymbolizedError.ts +45 -35
- package/front_end/models/extensions/ExtensionAPI.ts +138 -47
- package/front_end/models/har/Importer.ts +1 -0
- package/front_end/models/heap_snapshot/HeapSnapshotModel.ts +18 -2
- package/front_end/models/heap_snapshot/HeapSnapshotProxy.ts +4 -0
- package/front_end/models/source_map_scopes/FunctionCodeResolver.ts +12 -2
- package/front_end/models/stack_trace/DetailedErrorStackParser.ts +58 -52
- package/front_end/models/stack_trace/StackTrace.ts +7 -0
- package/front_end/models/stack_trace/StackTraceImpl.ts +13 -4
- package/front_end/models/stack_trace/StackTraceModel.ts +43 -9
- package/front_end/models/trace/Styles.ts +29 -7
- package/front_end/models/trace/handlers/PageLoadMetricsHandler.ts +33 -4
- package/front_end/models/trace/helpers/Timing.ts +10 -0
- package/front_end/models/trace/types/TraceEvents.ts +22 -2
- package/front_end/panels/accessibility/AccessibilitySidebarView.ts +2 -1
- package/front_end/panels/ai_assistance/AiAssistancePanel.ts +9 -2
- package/front_end/panels/ai_assistance/ai_assistance-meta.ts +16 -0
- package/front_end/panels/application/ApplicationPanelSidebar.ts +83 -0
- package/front_end/panels/application/ApplicationPanelTreeElement.ts +39 -0
- package/front_end/panels/application/CookieItemsView.ts +2 -2
- package/front_end/panels/application/ServiceWorkersView.ts +2 -2
- package/front_end/panels/application/WebMCPView.ts +0 -1
- package/front_end/panels/application/components/BackForwardCacheView.ts +1 -2
- package/front_end/panels/application/resourcesSidebar.css +11 -0
- package/front_end/panels/console/ConsoleView.ts +6 -1
- package/front_end/panels/console/ConsoleViewMessage.ts +46 -213
- package/front_end/panels/console/SymbolizedErrorWidget.ts +14 -8
- package/front_end/panels/elements/AdoptedStyleSheetTreeElement.ts +0 -1
- package/front_end/panels/elements/ElementsTreeElement.ts +0 -2
- package/front_end/panels/elements/PropertyRenderer.ts +0 -1
- package/front_end/panels/elements/StylesSidebarPane.ts +9 -2
- package/front_end/panels/issues/AffectedResourcesView.ts +1 -1
- package/front_end/panels/issues/AffectedSourcesView.ts +1 -1
- package/front_end/panels/lighthouse/LighthouseReportRenderer.ts +0 -1
- package/front_end/panels/mobile_throttling/ThrottlingSettingsTab.ts +10 -0
- package/front_end/panels/network/NetworkDataGridNode.ts +1 -2
- package/front_end/panels/network/NetworkLogView.ts +34 -7
- package/front_end/panels/profiler/HeapProfileView.ts +0 -1
- package/front_end/panels/profiler/HeapSnapshotGridNodes.ts +0 -1
- package/front_end/panels/profiler/HeapSnapshotView.ts +1 -1
- package/front_end/panels/settings/components/SyncSection.ts +1 -1
- package/front_end/panels/settings/emulation/DevicesSettingsTab.ts +1 -0
- package/front_end/panels/sources/SourcesPanel.ts +2 -1
- package/front_end/panels/timeline/TimelineFlameChartView.ts +5 -4
- package/front_end/panels/timeline/TimelinePanel.ts +7 -0
- package/front_end/panels/timeline/TimelineUIUtils.ts +13 -14
- package/front_end/panels/timeline/TimingsTrackAppender.ts +7 -5
- package/front_end/panels/timeline/components/LayoutShiftDetails.ts +0 -1
- package/front_end/panels/timeline/components/NetworkRequestDetails.ts +0 -2
- package/front_end/panels/timeline/components/insights/ForcedReflow.ts +0 -1
- package/front_end/panels/timeline/components/insights/NodeLink.ts +0 -1
- package/front_end/panels/timeline/overlays/OverlaysImpl.ts +2 -0
- package/front_end/third_party/chromium/README.chromium +1 -1
- package/front_end/ui/helpers/OpenInNewTab.ts +3 -3
- package/front_end/ui/kit/link/Link.ts +16 -2
- package/front_end/ui/legacy/InspectorDrawerView.ts +14 -5
- package/front_end/ui/legacy/InspectorView.ts +4 -1
- package/front_end/ui/legacy/PlusButton.ts +6 -1
- package/front_end/ui/legacy/StackedPane.ts +229 -0
- package/front_end/ui/legacy/ViewManager.ts +59 -169
- package/front_end/ui/legacy/Widget.ts +19 -1
- package/front_end/ui/legacy/components/object_ui/ObjectPropertiesSection.ts +95 -31
- package/front_end/ui/legacy/components/utils/JSPresentationUtils.ts +0 -1
- package/front_end/ui/legacy/components/utils/Linkifier.ts +2 -16
- package/front_end/ui/legacy/legacy.ts +3 -1
- package/front_end/ui/visual_logging/KnownContextValues.ts +5 -0
- package/package.json +1 -1
|
@@ -4,6 +4,7 @@
|
|
|
4
4
|
|
|
5
5
|
import * as Common from '../../core/common/common.js';
|
|
6
6
|
import * as i18n from '../../core/i18n/i18n.js';
|
|
7
|
+
import * as Root from '../../core/root/root.js';
|
|
7
8
|
import * as VisualLogging from '../visual_logging/visual_logging.js';
|
|
8
9
|
|
|
9
10
|
import * as ARIAUtils from './ARIAUtils.js';
|
|
@@ -129,11 +130,19 @@ export class InspectorDrawerView {
|
|
|
129
130
|
this.#isConsoleOpenInMainAndDrawer = options.isConsoleOpenInMainAndDrawer;
|
|
130
131
|
this.#drawerMinimizedSetting =
|
|
131
132
|
Common.Settings.Settings.instance().createLocalSetting('inspector.drawer-minimized', false);
|
|
132
|
-
this.tabbedLocation = ViewManager.instance().createTabbedLocation(
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
133
|
+
this.tabbedLocation = ViewManager.instance().createTabbedLocation(options.revealDrawer, 'drawer-view', true, true, {
|
|
134
|
+
isLocationVisible: options.isVisible,
|
|
135
|
+
tabbedPaneFactory: () => new DrawerTabbedPane(),
|
|
136
|
+
plusButton: {title: i18nString(UIStrings.moreTools), jslogContext: 'plus-button-drawer'},
|
|
137
|
+
});
|
|
138
|
+
// When the plus button is disabled we keep the legacy left-toolbar
|
|
139
|
+
// three-dot menu so users can still reach hidden / addable tools.
|
|
140
|
+
if (Root.Runtime.hostConfig.devToolsPlusButton?.enabled) {
|
|
141
|
+
this.#moreTabsButton = null;
|
|
142
|
+
} else {
|
|
143
|
+
this.#moreTabsButton = this.tabbedLocation.enableMoreTabsButton();
|
|
144
|
+
this.#moreTabsButton.setTitle(i18nString(UIStrings.moreTools));
|
|
145
|
+
}
|
|
137
146
|
this.tabbedPane = this.tabbedLocation.tabbedPane() as DrawerTabbedPane;
|
|
138
147
|
this.tabbedPane.element.classList.add('drawer-tabbed-pane');
|
|
139
148
|
this.tabbedPane.element.setAttribute('jslog', `${VisualLogging.drawer()}`);
|
|
@@ -228,7 +228,10 @@ export class InspectorView extends VBox implements ViewLocationResolver {
|
|
|
228
228
|
this.tabbedLocation = ViewManager.instance().createTabbedLocation(
|
|
229
229
|
Host.InspectorFrontendHost.InspectorFrontendHostInstance.bringToFront.bind(
|
|
230
230
|
Host.InspectorFrontendHost.InspectorFrontendHostInstance),
|
|
231
|
-
'panel', true, true, {
|
|
231
|
+
'panel', true, true, {
|
|
232
|
+
defaultTab: Root.Runtime.Runtime.queryParam('panel'),
|
|
233
|
+
plusButton: {jslogContext: 'plus-button-panel'},
|
|
234
|
+
});
|
|
232
235
|
|
|
233
236
|
this.tabbedPane = this.tabbedLocation.tabbedPane();
|
|
234
237
|
this.tabbedPane.setMinimumSize(MIN_MAIN_PANEL_WIDTH, 0);
|
|
@@ -108,8 +108,13 @@ export class PlusButtonPresenter {
|
|
|
108
108
|
for (const view of views()) {
|
|
109
109
|
// Skip views that already have a tab. Hidden tabs are already listed
|
|
110
110
|
// in the overflow section above, and visible tabs are accessible
|
|
111
|
-
// directly in the tab strip.
|
|
111
|
+
// directly in the tab strip. Track their id and title so the
|
|
112
|
+
// cross-location loop below cannot offer a same-titled duplicate
|
|
113
|
+
// (e.g. drawer "Console" while the panel "Console" is already
|
|
114
|
+
// visible — they have different view ids).
|
|
112
115
|
if (tabbedPane.hasTab(view.viewId())) {
|
|
116
|
+
seenIds.add(view.viewId());
|
|
117
|
+
seenTitles.add(view.title());
|
|
113
118
|
continue;
|
|
114
119
|
}
|
|
115
120
|
// Transient views are not user-addable.
|
|
@@ -0,0 +1,229 @@
|
|
|
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
|
+
/* eslint-disable @devtools/no-imperative-dom-api */
|
|
6
|
+
|
|
7
|
+
import type * as Lit from '../../ui/lit/lit.js';
|
|
8
|
+
import {createIcon, type Icon} from '../kit/kit.js';
|
|
9
|
+
import * as VisualLogging from '../visual_logging/visual_logging.js';
|
|
10
|
+
|
|
11
|
+
import * as ARIAUtils from './ARIAUtils.js';
|
|
12
|
+
import type * as Toolbar from './Toolbar.js';
|
|
13
|
+
import {createTextChild} from './UIUtils.js';
|
|
14
|
+
import type {View} from './View.js';
|
|
15
|
+
import viewContainersStyles from './viewContainers.css.js';
|
|
16
|
+
import {type AnyWidget, VBox} from './Widget.js';
|
|
17
|
+
|
|
18
|
+
type CreateToolbarFn = (toolbarItems: Toolbar.ToolbarItem[]|Lit.TemplateResult) => Element|null;
|
|
19
|
+
type SetWidgetForViewFn = (view: View, widget: AnyWidget) => void;
|
|
20
|
+
|
|
21
|
+
export class ExpandableContainerWidget extends VBox {
|
|
22
|
+
private titleElement: HTMLDivElement;
|
|
23
|
+
private readonly titleExpandIcon: Icon;
|
|
24
|
+
private readonly view: View;
|
|
25
|
+
private widget?: AnyWidget;
|
|
26
|
+
private materializePromise?: Promise<void>;
|
|
27
|
+
|
|
28
|
+
constructor(
|
|
29
|
+
view: View,
|
|
30
|
+
private readonly createToolbar: CreateToolbarFn,
|
|
31
|
+
private readonly setWidgetForView: SetWidgetForViewFn,
|
|
32
|
+
private readonly onVisibilityChanged?: (isExpanded: boolean) => void,
|
|
33
|
+
) {
|
|
34
|
+
super({useShadowDom: true});
|
|
35
|
+
this.element.classList.add('flex-none');
|
|
36
|
+
this.registerRequiredCSS(viewContainersStyles);
|
|
37
|
+
|
|
38
|
+
this.onVisibilityChanged = onVisibilityChanged;
|
|
39
|
+
this.createToolbar = createToolbar;
|
|
40
|
+
|
|
41
|
+
this.titleElement = document.createElement('div');
|
|
42
|
+
this.titleElement.classList.add('expandable-view-title');
|
|
43
|
+
this.titleElement.setAttribute('jslog', `${VisualLogging.sectionHeader().context(view.viewId()).track({
|
|
44
|
+
click: true,
|
|
45
|
+
keydown: 'Enter|Space|ArrowLeft|ArrowRight',
|
|
46
|
+
})}`);
|
|
47
|
+
ARIAUtils.markAsTreeitem(this.titleElement);
|
|
48
|
+
this.titleExpandIcon = createIcon('triangle-right', 'title-expand-icon');
|
|
49
|
+
this.titleElement.appendChild(this.titleExpandIcon);
|
|
50
|
+
const titleText = view.title();
|
|
51
|
+
createTextChild(this.titleElement, titleText);
|
|
52
|
+
ARIAUtils.setLabel(this.titleElement, titleText);
|
|
53
|
+
ARIAUtils.setExpanded(this.titleElement, false);
|
|
54
|
+
this.titleElement.tabIndex = 0;
|
|
55
|
+
self.onInvokeElement(this.titleElement, this.toggleExpanded.bind(this));
|
|
56
|
+
this.titleElement.addEventListener('keydown', this.onTitleKeyDown.bind(this), false);
|
|
57
|
+
this.contentElement.insertBefore(this.titleElement, this.contentElement.firstChild);
|
|
58
|
+
|
|
59
|
+
ARIAUtils.setControls(this.titleElement, this.contentElement.createChild('slot'));
|
|
60
|
+
this.view = view;
|
|
61
|
+
expandableContainerForView.set(view, this);
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
isExpanded(): boolean {
|
|
65
|
+
return this.titleElement.classList.contains('expanded');
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
override wasShown(): void {
|
|
69
|
+
super.wasShown();
|
|
70
|
+
if (this.widget && this.materializePromise) {
|
|
71
|
+
void this.materializePromise.then(() => {
|
|
72
|
+
if (this.isExpanded() && this.widget) {
|
|
73
|
+
this.widget.show(this.element);
|
|
74
|
+
}
|
|
75
|
+
});
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
private materialize(): Promise<void> {
|
|
80
|
+
if (this.materializePromise) {
|
|
81
|
+
return this.materializePromise;
|
|
82
|
+
}
|
|
83
|
+
// TODO(crbug.com/1006759): Transform to async-await
|
|
84
|
+
const promises = [];
|
|
85
|
+
promises.push(this.view.toolbarItems().then(toolbarItems => {
|
|
86
|
+
const toolbarElement = this.createToolbar(toolbarItems);
|
|
87
|
+
if (toolbarElement) {
|
|
88
|
+
this.titleElement.appendChild(toolbarElement);
|
|
89
|
+
}
|
|
90
|
+
}));
|
|
91
|
+
promises.push(this.view.widget().then(widget => {
|
|
92
|
+
this.widget = widget;
|
|
93
|
+
this.setWidgetForView(this.view, widget);
|
|
94
|
+
}));
|
|
95
|
+
this.materializePromise = Promise.all(promises).then(() => {});
|
|
96
|
+
return this.materializePromise;
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
expand(): Promise<void> {
|
|
100
|
+
if (this.isExpanded()) {
|
|
101
|
+
return this.materialize();
|
|
102
|
+
}
|
|
103
|
+
this.titleElement.classList.add('expanded');
|
|
104
|
+
ARIAUtils.setExpanded(this.titleElement, true);
|
|
105
|
+
this.titleExpandIcon.name = 'triangle-down';
|
|
106
|
+
this.onVisibilityChanged?.(true);
|
|
107
|
+
return this.materialize().then(() => {
|
|
108
|
+
if (this.isExpanded() && this.widget) {
|
|
109
|
+
this.widget.show(this.element);
|
|
110
|
+
}
|
|
111
|
+
});
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
private collapse(): void {
|
|
115
|
+
if (!this.isExpanded()) {
|
|
116
|
+
return;
|
|
117
|
+
}
|
|
118
|
+
this.titleElement.classList.remove('expanded');
|
|
119
|
+
ARIAUtils.setExpanded(this.titleElement, false);
|
|
120
|
+
this.titleExpandIcon.name = 'triangle-right';
|
|
121
|
+
this.onVisibilityChanged?.(false);
|
|
122
|
+
void this.materialize().then(() => {
|
|
123
|
+
if (this.widget) {
|
|
124
|
+
this.widget.detach();
|
|
125
|
+
}
|
|
126
|
+
});
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
private toggleExpanded(event: Event): void {
|
|
130
|
+
if (event.type === 'keydown' && event.target !== this.titleElement) {
|
|
131
|
+
return;
|
|
132
|
+
}
|
|
133
|
+
if (this.isExpanded()) {
|
|
134
|
+
this.collapse();
|
|
135
|
+
} else {
|
|
136
|
+
void this.expand();
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
private onTitleKeyDown(event: Event): void {
|
|
141
|
+
if (event.target !== this.titleElement) {
|
|
142
|
+
return;
|
|
143
|
+
}
|
|
144
|
+
const keyEvent = (event as KeyboardEvent);
|
|
145
|
+
if (keyEvent.key === 'ArrowLeft') {
|
|
146
|
+
this.collapse();
|
|
147
|
+
} else if (keyEvent.key === 'ArrowRight') {
|
|
148
|
+
if (!this.isExpanded()) {
|
|
149
|
+
void this.expand();
|
|
150
|
+
} else if (this.widget) {
|
|
151
|
+
this.widget.focus();
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
const expandableContainerForView = new WeakMap<View, ExpandableContainerWidget>();
|
|
158
|
+
|
|
159
|
+
export class StackedPane extends VBox {
|
|
160
|
+
readonly expandableContainers = new Map<string, ExpandableContainerWidget>();
|
|
161
|
+
constructor(
|
|
162
|
+
private readonly createToolbar: CreateToolbarFn,
|
|
163
|
+
private readonly setWidgetForView: SetWidgetForViewFn,
|
|
164
|
+
private readonly onViewVisibilityChanged?: (viewId: string, isExpanded: boolean) => void,
|
|
165
|
+
) {
|
|
166
|
+
super();
|
|
167
|
+
this.createToolbar = createToolbar;
|
|
168
|
+
this.onViewVisibilityChanged = onViewVisibilityChanged;
|
|
169
|
+
ARIAUtils.markAsTree(this.element);
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
appendView(view: View, insertBefore?: View|null): void {
|
|
173
|
+
let container = this.expandableContainers.get(view.viewId());
|
|
174
|
+
if (!container) {
|
|
175
|
+
container =
|
|
176
|
+
new ExpandableContainerWidget(view, this.createToolbar, this.setWidgetForView,
|
|
177
|
+
isExpanded => this.onViewVisibilityChanged?.(view.viewId(), isExpanded));
|
|
178
|
+
let beforeElement: Node|null = null;
|
|
179
|
+
if (insertBefore) {
|
|
180
|
+
const beforeContainer = expandableContainerForView.get(insertBefore);
|
|
181
|
+
beforeElement = beforeContainer ? beforeContainer.element : null;
|
|
182
|
+
}
|
|
183
|
+
container.show(this.contentElement, beforeElement);
|
|
184
|
+
this.expandableContainers.set(view.viewId(), container);
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
override wasShown(): void {
|
|
189
|
+
super.wasShown();
|
|
190
|
+
for (const [viewId, container] of this.expandableContainers) {
|
|
191
|
+
if (container.isExpanded()) {
|
|
192
|
+
this.onViewVisibilityChanged?.(viewId, true);
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
override willHide(): void {
|
|
198
|
+
super.willHide();
|
|
199
|
+
for (const [viewId, container] of this.expandableContainers) {
|
|
200
|
+
if (container.isExpanded()) {
|
|
201
|
+
this.onViewVisibilityChanged?.(viewId, false);
|
|
202
|
+
}
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
removeView(view: View): void {
|
|
207
|
+
const container = this.expandableContainers.get(view.viewId());
|
|
208
|
+
if (container) {
|
|
209
|
+
container.detach();
|
|
210
|
+
this.expandableContainers.delete(view.viewId());
|
|
211
|
+
}
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
async expandView(view: View): Promise<void> {
|
|
215
|
+
const container = this.expandableContainers.get(view.viewId());
|
|
216
|
+
if (container) {
|
|
217
|
+
await container.expand();
|
|
218
|
+
}
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
isViewExpanded(viewId: string): boolean {
|
|
222
|
+
const container = this.expandableContainers.get(viewId);
|
|
223
|
+
return container ? container.isExpanded() : false;
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
getContainerForView(view: View): ExpandableContainerWidget|undefined {
|
|
227
|
+
return this.expandableContainers.get(view.viewId());
|
|
228
|
+
}
|
|
229
|
+
}
|
|
@@ -11,18 +11,17 @@ import * as i18n from '../../core/i18n/i18n.js';
|
|
|
11
11
|
import * as Platform from '../../core/platform/platform.js';
|
|
12
12
|
import * as Root from '../../core/root/root.js';
|
|
13
13
|
import type * as Foundation from '../../foundation/foundation.js';
|
|
14
|
-
import {createIcon
|
|
14
|
+
import {createIcon} from '../kit/kit.js';
|
|
15
15
|
import {render, type TemplateResult} from '../lit/lit.js';
|
|
16
16
|
import * as VisualLogging from '../visual_logging/visual_logging.js';
|
|
17
17
|
|
|
18
18
|
import * as ARIAUtils from './ARIAUtils.js';
|
|
19
19
|
import type {ContextMenu} from './ContextMenu.js';
|
|
20
20
|
import * as PlusButton from './PlusButton.js';
|
|
21
|
+
import {StackedPane} from './StackedPane.js';
|
|
21
22
|
import {type EventData, Events as TabbedPaneEvents, TabbedPane} from './TabbedPane.js';
|
|
22
23
|
import {type ItemsProvider, type ToolbarItem, ToolbarMenuButton} from './Toolbar.js';
|
|
23
|
-
import {createTextChild} from './UIUtils.js';
|
|
24
24
|
import type {TabbedViewLocation, View, ViewLocation} from './View.js';
|
|
25
|
-
import viewContainersStyles from './viewContainers.css.js';
|
|
26
25
|
import {
|
|
27
26
|
getLocalizedViewLocationCategory,
|
|
28
27
|
getRegisteredLocationResolvers,
|
|
@@ -262,6 +261,10 @@ export class ViewManager extends Common.ObjectWrapper.ObjectWrapper<EventTypes>
|
|
|
262
261
|
return toolbar;
|
|
263
262
|
}
|
|
264
263
|
|
|
264
|
+
static setWidgetForView(view: View, widget: AnyWidget): void {
|
|
265
|
+
widgetForView.set(view, widget);
|
|
266
|
+
}
|
|
267
|
+
|
|
265
268
|
getRegisteredViewExtensions(): PreRegisteredView[] {
|
|
266
269
|
return this.preRegisteredViews;
|
|
267
270
|
}
|
|
@@ -402,7 +405,7 @@ export class ViewManager extends Common.ObjectWrapper.ObjectWrapper<EventTypes>
|
|
|
402
405
|
return new TabbedLocation(this, revealCallback, location, restoreSelection, allowReorder, options);
|
|
403
406
|
}
|
|
404
407
|
|
|
405
|
-
createStackLocation(revealCallback?: (() => void), location?: string, jslogContext?: string):
|
|
408
|
+
createStackLocation(revealCallback?: (() => void), location?: string, jslogContext?: string): StackLocation {
|
|
406
409
|
return new StackLocation(this, revealCallback, location, jslogContext);
|
|
407
410
|
}
|
|
408
411
|
|
|
@@ -453,7 +456,7 @@ export class ContainerWidget extends VBox {
|
|
|
453
456
|
// Move focus from |this| to loaded |widget| if any.
|
|
454
457
|
const shouldFocus = this.element.hasFocus();
|
|
455
458
|
this.setDefaultFocusedElement(null);
|
|
456
|
-
|
|
459
|
+
ViewManager.setWidgetForView(this.view, widget);
|
|
457
460
|
widget.show(this.element);
|
|
458
461
|
if (shouldFocus) {
|
|
459
462
|
widget.focus();
|
|
@@ -479,131 +482,6 @@ export class ContainerWidget extends VBox {
|
|
|
479
482
|
}
|
|
480
483
|
}
|
|
481
484
|
|
|
482
|
-
class ExpandableContainerWidget extends VBox {
|
|
483
|
-
private titleElement: HTMLDivElement;
|
|
484
|
-
private readonly titleExpandIcon: Icon;
|
|
485
|
-
private readonly view: View;
|
|
486
|
-
private widget?: AnyWidget;
|
|
487
|
-
private materializePromise?: Promise<void>;
|
|
488
|
-
|
|
489
|
-
constructor(view: View) {
|
|
490
|
-
super({useShadowDom: true});
|
|
491
|
-
this.element.classList.add('flex-none');
|
|
492
|
-
this.registerRequiredCSS(viewContainersStyles);
|
|
493
|
-
|
|
494
|
-
this.titleElement = document.createElement('div');
|
|
495
|
-
this.titleElement.classList.add('expandable-view-title');
|
|
496
|
-
this.titleElement.setAttribute('jslog', `${VisualLogging.sectionHeader().context(view.viewId()).track({
|
|
497
|
-
click: true,
|
|
498
|
-
keydown: 'Enter|Space|ArrowLeft|ArrowRight',
|
|
499
|
-
})}`);
|
|
500
|
-
ARIAUtils.markAsTreeitem(this.titleElement);
|
|
501
|
-
this.titleExpandIcon = createIcon('triangle-right', 'title-expand-icon');
|
|
502
|
-
this.titleElement.appendChild(this.titleExpandIcon);
|
|
503
|
-
const titleText = view.title();
|
|
504
|
-
createTextChild(this.titleElement, titleText);
|
|
505
|
-
ARIAUtils.setLabel(this.titleElement, titleText);
|
|
506
|
-
ARIAUtils.setExpanded(this.titleElement, false);
|
|
507
|
-
this.titleElement.tabIndex = 0;
|
|
508
|
-
self.onInvokeElement(this.titleElement, this.toggleExpanded.bind(this));
|
|
509
|
-
this.titleElement.addEventListener('keydown', this.onTitleKeyDown.bind(this), false);
|
|
510
|
-
this.contentElement.insertBefore(this.titleElement, this.contentElement.firstChild);
|
|
511
|
-
|
|
512
|
-
ARIAUtils.setControls(this.titleElement, this.contentElement.createChild('slot'));
|
|
513
|
-
this.view = view;
|
|
514
|
-
expandableContainerForView.set(view, this);
|
|
515
|
-
}
|
|
516
|
-
|
|
517
|
-
override wasShown(): void {
|
|
518
|
-
super.wasShown();
|
|
519
|
-
if (this.widget && this.materializePromise) {
|
|
520
|
-
void this.materializePromise.then(() => {
|
|
521
|
-
if (this.titleElement.classList.contains('expanded') && this.widget) {
|
|
522
|
-
this.widget.show(this.element);
|
|
523
|
-
}
|
|
524
|
-
});
|
|
525
|
-
}
|
|
526
|
-
}
|
|
527
|
-
|
|
528
|
-
private materialize(): Promise<void> {
|
|
529
|
-
if (this.materializePromise) {
|
|
530
|
-
return this.materializePromise;
|
|
531
|
-
}
|
|
532
|
-
// TODO(crbug.com/1006759): Transform to async-await
|
|
533
|
-
const promises = [];
|
|
534
|
-
promises.push(this.view.toolbarItems().then(toolbarItems => {
|
|
535
|
-
const toolbarElement = ViewManager.createToolbar(toolbarItems);
|
|
536
|
-
if (toolbarElement) {
|
|
537
|
-
this.titleElement.appendChild(toolbarElement);
|
|
538
|
-
}
|
|
539
|
-
}));
|
|
540
|
-
promises.push(this.view.widget().then(widget => {
|
|
541
|
-
this.widget = widget;
|
|
542
|
-
widgetForView.set(this.view, widget);
|
|
543
|
-
widget.show(this.element);
|
|
544
|
-
}));
|
|
545
|
-
this.materializePromise = Promise.all(promises).then(() => {});
|
|
546
|
-
return this.materializePromise;
|
|
547
|
-
}
|
|
548
|
-
|
|
549
|
-
expand(): Promise<void> {
|
|
550
|
-
if (this.titleElement.classList.contains('expanded')) {
|
|
551
|
-
return this.materialize();
|
|
552
|
-
}
|
|
553
|
-
this.titleElement.classList.add('expanded');
|
|
554
|
-
ARIAUtils.setExpanded(this.titleElement, true);
|
|
555
|
-
this.titleExpandIcon.name = 'triangle-down';
|
|
556
|
-
return this.materialize().then(() => {
|
|
557
|
-
if (this.widget) {
|
|
558
|
-
this.widget.show(this.element);
|
|
559
|
-
}
|
|
560
|
-
});
|
|
561
|
-
}
|
|
562
|
-
|
|
563
|
-
private collapse(): void {
|
|
564
|
-
if (!this.titleElement.classList.contains('expanded')) {
|
|
565
|
-
return;
|
|
566
|
-
}
|
|
567
|
-
this.titleElement.classList.remove('expanded');
|
|
568
|
-
ARIAUtils.setExpanded(this.titleElement, false);
|
|
569
|
-
this.titleExpandIcon.name = 'triangle-right';
|
|
570
|
-
void this.materialize().then(() => {
|
|
571
|
-
if (this.widget) {
|
|
572
|
-
this.widget.detach();
|
|
573
|
-
}
|
|
574
|
-
});
|
|
575
|
-
}
|
|
576
|
-
|
|
577
|
-
private toggleExpanded(event: Event): void {
|
|
578
|
-
if (event.type === 'keydown' && event.target !== this.titleElement) {
|
|
579
|
-
return;
|
|
580
|
-
}
|
|
581
|
-
if (this.titleElement.classList.contains('expanded')) {
|
|
582
|
-
this.collapse();
|
|
583
|
-
} else {
|
|
584
|
-
void this.expand();
|
|
585
|
-
}
|
|
586
|
-
}
|
|
587
|
-
|
|
588
|
-
private onTitleKeyDown(event: Event): void {
|
|
589
|
-
if (event.target !== this.titleElement) {
|
|
590
|
-
return;
|
|
591
|
-
}
|
|
592
|
-
const keyEvent = (event as KeyboardEvent);
|
|
593
|
-
if (keyEvent.key === 'ArrowLeft') {
|
|
594
|
-
this.collapse();
|
|
595
|
-
} else if (keyEvent.key === 'ArrowRight') {
|
|
596
|
-
if (!this.titleElement.classList.contains('expanded')) {
|
|
597
|
-
void this.expand();
|
|
598
|
-
} else if (this.widget) {
|
|
599
|
-
this.widget.focus();
|
|
600
|
-
}
|
|
601
|
-
}
|
|
602
|
-
}
|
|
603
|
-
}
|
|
604
|
-
|
|
605
|
-
const expandableContainerForView = new WeakMap<View, ExpandableContainerWidget>();
|
|
606
|
-
|
|
607
485
|
class Location {
|
|
608
486
|
protected readonly manager: ViewManager;
|
|
609
487
|
private readonly revealCallback: (() => void)|undefined;
|
|
@@ -958,68 +836,80 @@ class TabbedLocation extends Location implements TabbedViewLocation {
|
|
|
958
836
|
static orderStep = 10; // Keep in sync with descriptors.
|
|
959
837
|
}
|
|
960
838
|
|
|
961
|
-
class StackLocation extends Location implements ViewLocation {
|
|
962
|
-
private readonly
|
|
963
|
-
private readonly
|
|
964
|
-
|
|
965
|
-
|
|
966
|
-
|
|
967
|
-
|
|
968
|
-
|
|
969
|
-
|
|
970
|
-
|
|
971
|
-
|
|
972
|
-
|
|
839
|
+
export class StackLocation extends Location implements ViewLocation {
|
|
840
|
+
private readonly stackedPane: StackedPane;
|
|
841
|
+
private readonly location: string;
|
|
842
|
+
#isVisible: boolean;
|
|
843
|
+
|
|
844
|
+
constructor(manager: ViewManager, revealCallback?: (() => void), location?: string, jslogContext?: string,
|
|
845
|
+
initialVisibility = true) {
|
|
846
|
+
const stackedPane =
|
|
847
|
+
new StackedPane(ViewManager.createToolbar, ViewManager.setWidgetForView, (viewId, isExpanded) => {
|
|
848
|
+
if (this.#isVisible) {
|
|
849
|
+
manager.dispatchEventToListeners(Events.VIEW_VISIBILITY_CHANGED, {
|
|
850
|
+
location: this.location,
|
|
851
|
+
revealedViewId: isExpanded ? viewId : undefined,
|
|
852
|
+
hiddenViewId: isExpanded ? undefined : viewId,
|
|
853
|
+
});
|
|
854
|
+
}
|
|
855
|
+
});
|
|
856
|
+
stackedPane.element.setAttribute('jslog', `${VisualLogging.pane(jslogContext || 'sidebar').track({resize: true})}`);
|
|
857
|
+
super(manager, stackedPane, revealCallback);
|
|
858
|
+
this.location = location || '';
|
|
859
|
+
this.stackedPane = stackedPane;
|
|
860
|
+
this.#isVisible = initialVisibility;
|
|
973
861
|
|
|
974
862
|
if (location) {
|
|
975
863
|
this.appendApplicableItems(location);
|
|
976
864
|
}
|
|
977
865
|
}
|
|
978
866
|
|
|
867
|
+
// Blink test(s) rely on this
|
|
868
|
+
get expandableContainers(): Map<string, AnyWidget> {
|
|
869
|
+
return this.stackedPane.expandableContainers;
|
|
870
|
+
}
|
|
871
|
+
|
|
872
|
+
notifyVisibilityChanged(isVisible: boolean): void {
|
|
873
|
+
if (this.#isVisible === isVisible) {
|
|
874
|
+
return;
|
|
875
|
+
}
|
|
876
|
+
this.#isVisible = isVisible;
|
|
877
|
+
|
|
878
|
+
for (const [viewId, container] of this.stackedPane.expandableContainers) {
|
|
879
|
+
if (container.isExpanded()) {
|
|
880
|
+
this.manager.dispatchEventToListeners(Events.VIEW_VISIBILITY_CHANGED, {
|
|
881
|
+
location: this.location,
|
|
882
|
+
revealedViewId: isVisible ? viewId : undefined,
|
|
883
|
+
hiddenViewId: isVisible ? undefined : viewId,
|
|
884
|
+
});
|
|
885
|
+
}
|
|
886
|
+
}
|
|
887
|
+
}
|
|
888
|
+
|
|
979
889
|
appendView(view: View, insertBefore?: View|null): void {
|
|
980
890
|
const oldLocation = locationForView.get(view);
|
|
981
891
|
if (oldLocation && oldLocation !== this) {
|
|
982
892
|
oldLocation.removeView(view);
|
|
983
893
|
}
|
|
984
894
|
|
|
985
|
-
|
|
986
|
-
|
|
987
|
-
|
|
988
|
-
this.manager.views.set(view.viewId(), view);
|
|
989
|
-
container = new ExpandableContainerWidget(view);
|
|
990
|
-
let beforeElement: Node|null = null;
|
|
991
|
-
if (insertBefore) {
|
|
992
|
-
const beforeContainer = expandableContainerForView.get(insertBefore);
|
|
993
|
-
beforeElement = beforeContainer ? beforeContainer.element : null;
|
|
994
|
-
}
|
|
995
|
-
container.show(this.vbox.contentElement, beforeElement);
|
|
996
|
-
this.expandableContainers.set(view.viewId(), container);
|
|
997
|
-
}
|
|
895
|
+
locationForView.set(view, this);
|
|
896
|
+
this.manager.views.set(view.viewId(), view);
|
|
897
|
+
this.stackedPane.appendView(view, insertBefore);
|
|
998
898
|
}
|
|
999
899
|
|
|
1000
900
|
override async showView(view: View, insertBefore?: View|null): Promise<void> {
|
|
1001
901
|
this.appendView(view, insertBefore);
|
|
1002
|
-
|
|
1003
|
-
if (container) {
|
|
1004
|
-
await container.expand();
|
|
1005
|
-
}
|
|
902
|
+
await this.stackedPane.expandView(view);
|
|
1006
903
|
}
|
|
1007
904
|
|
|
1008
905
|
override removeView(view: View): void {
|
|
1009
|
-
|
|
1010
|
-
if (!container) {
|
|
1011
|
-
return;
|
|
1012
|
-
}
|
|
1013
|
-
|
|
1014
|
-
container.detach();
|
|
1015
|
-
this.expandableContainers.delete(view.viewId());
|
|
906
|
+
this.stackedPane.removeView(view);
|
|
1016
907
|
locationForView.delete(view);
|
|
1017
908
|
this.manager.views.delete(view.viewId());
|
|
1018
909
|
}
|
|
1019
910
|
|
|
1020
|
-
override isViewVisible(
|
|
1021
|
-
|
|
1022
|
-
throw new Error('not implemented');
|
|
911
|
+
override isViewVisible(view: View): boolean {
|
|
912
|
+
return this.#isVisible && this.stackedPane.isViewExpanded(view.viewId());
|
|
1023
913
|
}
|
|
1024
914
|
|
|
1025
915
|
appendApplicableItems(locationName: string): void {
|
|
@@ -483,6 +483,12 @@ export type WidgetOptions<ContentTypeT extends HTMLElement|DocumentFragment = HT
|
|
|
483
483
|
classes?: never,
|
|
484
484
|
});
|
|
485
485
|
|
|
486
|
+
const enum UpdateState {
|
|
487
|
+
NORMAL = 'NORMAL', // Standard state: update can be aborted for efficiency.
|
|
488
|
+
INTERRUPTED = 'INTERRUPTED', // An update was physically running and got aborted; replacement must be shielded.
|
|
489
|
+
SHIELDED = 'SHIELDED', // Current update is a replacement for an interrupted one and cannot be aborted.
|
|
490
|
+
}
|
|
491
|
+
|
|
486
492
|
export class Widget<ContentTypeT extends HTMLElement|DocumentFragment = HTMLElement> {
|
|
487
493
|
readonly element: HTMLElement;
|
|
488
494
|
#contentElement: ContentTypeT;
|
|
@@ -501,6 +507,7 @@ export class Widget<ContentTypeT extends HTMLElement|DocumentFragment = HTMLElem
|
|
|
501
507
|
#externallyManaged?: boolean;
|
|
502
508
|
#updateComplete = UPDATE_COMPLETE;
|
|
503
509
|
#updateController?: AbortController;
|
|
510
|
+
#updateState = UpdateState.NORMAL;
|
|
504
511
|
|
|
505
512
|
/**
|
|
506
513
|
* Constructs a new `Widget` with the given `options`.
|
|
@@ -1165,12 +1172,17 @@ export class Widget<ContentTypeT extends HTMLElement|DocumentFragment = HTMLElem
|
|
|
1165
1172
|
}
|
|
1166
1173
|
|
|
1167
1174
|
addUpdateController(controller: AbortController): void {
|
|
1175
|
+
const wasInterrupted = this.#updateState === UpdateState.INTERRUPTED;
|
|
1168
1176
|
this.#updateController?.abort();
|
|
1169
1177
|
this.#updateController = controller;
|
|
1178
|
+
// Transition to SHIELDED if we are replacing a starved update, otherwise reset to NORMAL.
|
|
1179
|
+
this.#updateState = wasInterrupted ? UpdateState.SHIELDED : UpdateState.NORMAL;
|
|
1170
1180
|
}
|
|
1171
1181
|
|
|
1172
1182
|
cancelUpdateController(): void {
|
|
1173
1183
|
this.#updateController?.abort();
|
|
1184
|
+
this.#updateController = undefined;
|
|
1185
|
+
this.#updateState = UpdateState.NORMAL;
|
|
1174
1186
|
}
|
|
1175
1187
|
|
|
1176
1188
|
/**
|
|
@@ -1180,7 +1192,13 @@ export class Widget<ContentTypeT extends HTMLElement|DocumentFragment = HTMLElem
|
|
|
1180
1192
|
* frame.
|
|
1181
1193
|
*/
|
|
1182
1194
|
requestUpdate(): void {
|
|
1183
|
-
|
|
1195
|
+
// If the state is SHIELDED, we skip the abort call entirely to break the starvation loop.
|
|
1196
|
+
if (this.#updateState !== UpdateState.SHIELDED) {
|
|
1197
|
+
if (currentlyProcessed.has(this)) {
|
|
1198
|
+
this.#updateState = UpdateState.INTERRUPTED;
|
|
1199
|
+
}
|
|
1200
|
+
this.#updateController?.abort();
|
|
1201
|
+
}
|
|
1184
1202
|
this.#updateComplete = enqueueWidgetUpdate(this);
|
|
1185
1203
|
}
|
|
1186
1204
|
|