chrome-devtools-frontend 1.0.1632065 → 1.0.1635648
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 +5 -2
- package/front_end/core/root/ExperimentNames.ts +1 -0
- package/front_end/core/root/Runtime.ts +5 -0
- package/front_end/entrypoints/main/MainImpl.ts +8 -0
- package/front_end/generated/InspectorBackendCommands.ts +3 -4
- package/front_end/generated/SupportedCSSProperties.js +272 -2
- package/front_end/generated/protocol-mapping.d.ts +0 -10
- package/front_end/generated/protocol-proxy-api.d.ts +0 -8
- package/front_end/generated/protocol.ts +5 -7
- package/front_end/models/ai_assistance/AiConversation.ts +16 -11
- package/front_end/models/ai_assistance/agents/AccessibilityAgent.ts +13 -2
- package/front_end/models/ai_assistance/agents/AiAgent.ts +65 -11
- package/front_end/models/ai_assistance/agents/ContextSelectionAgent.ts +42 -2
- package/front_end/models/ai_assistance/agents/PerformanceAgent.ts +30 -1
- package/front_end/models/ai_assistance/data_formatters/PerformanceInsightFormatter.ts +4 -2
- package/front_end/models/ai_assistance/data_formatters/PerformanceTraceFormatter.ts +5 -2
- package/front_end/models/extensions/RecorderExtensionEndpoint.ts +9 -1
- package/front_end/models/extensions/RecorderPluginManager.ts +1 -0
- package/front_end/models/trace/handlers/FramesHandler.ts +19 -13
- package/front_end/panels/ai_assistance/components/AccessibilityAgentMarkdownRenderer.ts +3 -0
- package/front_end/panels/ai_assistance/components/ChatInput.ts +4 -2
- package/front_end/panels/ai_assistance/components/ChatMessage.ts +3 -1
- package/front_end/panels/application/preloading/components/PreloadingString.ts +0 -8
- package/front_end/panels/common/ExtensionServer.ts +34 -11
- package/front_end/panels/elements/CSSRuleValidator.ts +37 -34
- package/front_end/panels/elements/CSSRuleValidatorHelper.ts +8 -6
- package/front_end/panels/elements/ElementsTreeElement.ts +8 -2
- package/front_end/panels/elements/components/CSSHintDetailsView.ts +5 -5
- package/front_end/panels/js_timeline/js_timeline-meta.ts +30 -0
- package/front_end/panels/protocol_monitor/JSONEditor.ts +4 -4
- package/front_end/panels/protocol_monitor/ProtocolMonitor.ts +2 -2
- package/front_end/panels/recorder/RecorderController.ts +50 -1
- package/front_end/panels/recorder/extensions/ExtensionManager.ts +1 -0
- package/front_end/panels/recorder/models/RecordingPlayer.ts +12 -3
- package/front_end/panels/recorder/testing/RecorderHelpers.ts +2 -0
- package/front_end/panels/sources/FilteredUISourceCodeListProvider.ts +5 -2
- package/front_end/panels/timeline/timeline-meta.ts +10 -6
- package/front_end/third_party/chromium/README.chromium +1 -1
- package/front_end/ui/legacy/InspectorDrawerView.ts +2 -1
- package/front_end/ui/legacy/InspectorView.ts +1 -1
- package/front_end/ui/legacy/PlusButton.ts +269 -0
- package/front_end/ui/legacy/ViewManager.ts +38 -11
- package/front_end/ui/legacy/components/source_frame/ImageView.ts +16 -0
- package/front_end/ui/legacy/components/source_frame/imageView.css +13 -0
- package/front_end/ui/legacy/components/utils/Linkifier.ts +1 -1
- package/front_end/ui/legacy/legacy.ts +2 -0
- package/front_end/ui/visual_logging/KnownContextValues.ts +19 -0
- package/mcp/mcp.ts +1 -1
- package/package.json +1 -1
|
@@ -43,6 +43,16 @@ function isPageTarget(target: Protocol.Target.TargetInfo): boolean {
|
|
|
43
43
|
target.type === 'background_page' || target.type === 'webview');
|
|
44
44
|
}
|
|
45
45
|
|
|
46
|
+
function checkNavigationUrl(url: Platform.DevToolsPath.UrlString): void {
|
|
47
|
+
const allowedSchemes = ['http:', 'https:', 'data:'];
|
|
48
|
+
const isAllowed = allowedSchemes.some(scheme => Common.ParsedURL.schemeIs(url, scheme));
|
|
49
|
+
// We allow only about:blank but any other about: pages are intentionally
|
|
50
|
+
// blocked as they may navigate to other internal pages.
|
|
51
|
+
if (!isAllowed && url !== 'about:blank') {
|
|
52
|
+
throw new Error(`Navigation to ${url} is not allowed, due to blocked schema`);
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
|
|
46
56
|
export class RecordingPlayer extends Common.ObjectWrapper.ObjectWrapper<EventTypes> {
|
|
47
57
|
userFlow: UserFlow;
|
|
48
58
|
speed: PlayRecordingSpeed;
|
|
@@ -273,9 +283,8 @@ export class RecordingPlayer extends Common.ObjectWrapper.ObjectWrapper<EventTyp
|
|
|
273
283
|
(step.type === 'setViewport' || step.type === 'navigate')) {
|
|
274
284
|
return;
|
|
275
285
|
}
|
|
276
|
-
if (step.type === 'navigate'
|
|
277
|
-
|
|
278
|
-
throw new Error('Not allowed to replay on chrome:// URLs');
|
|
286
|
+
if (step.type === 'navigate') {
|
|
287
|
+
checkNavigationUrl(step.url as Platform.DevToolsPath.UrlString);
|
|
279
288
|
}
|
|
280
289
|
// Focus the target in case it's not focused.
|
|
281
290
|
await this.page.bringToFront();
|
|
@@ -23,7 +23,9 @@ export const installMocksForRecordingPlayer = (): void => {
|
|
|
23
23
|
}),
|
|
24
24
|
frames: () => [{
|
|
25
25
|
client: {send: sinon.stub().resolves()},
|
|
26
|
+
goto: sinon.stub().resolves(),
|
|
26
27
|
}],
|
|
28
|
+
mainFrame: () => mock.page.frames()[0],
|
|
27
29
|
evaluate: () => '',
|
|
28
30
|
url() {
|
|
29
31
|
return '';
|
|
@@ -126,8 +126,11 @@ export class FilteredUISourceCodeListProvider extends QuickOpen.FilteredListWidg
|
|
|
126
126
|
}
|
|
127
127
|
|
|
128
128
|
let multiplier = 10;
|
|
129
|
-
|
|
130
|
-
|
|
129
|
+
const isSnippet = Common.ParsedURL.schemeIs(uiSourceCode.url(), 'snippet:');
|
|
130
|
+
const isUnboundLocalFile = uiSourceCode.project().type() === Workspace.Workspace.projectTypes.FileSystem &&
|
|
131
|
+
!Persistence.Persistence.PersistenceImpl.instance().binding(uiSourceCode) && !isSnippet;
|
|
132
|
+
|
|
133
|
+
if (isUnboundLocalFile) {
|
|
131
134
|
multiplier = 5;
|
|
132
135
|
}
|
|
133
136
|
|
|
@@ -329,26 +329,30 @@ Common.Settings.registerSettingExtension({
|
|
|
329
329
|
Common.Settings.registerSettingExtension({
|
|
330
330
|
category: Common.Settings.SettingCategory.PERFORMANCE,
|
|
331
331
|
storageType: Common.Settings.SettingStorageType.SYNCED,
|
|
332
|
-
title: i18nLazyString(UIStrings.
|
|
333
|
-
settingName: 'timeline-
|
|
332
|
+
title: i18nLazyString(UIStrings.timelineInvalidationTracking),
|
|
333
|
+
settingName: 'timeline-invalidation-tracking',
|
|
334
334
|
settingType: Common.Settings.SettingType.BOOLEAN,
|
|
335
335
|
defaultValue: false,
|
|
336
336
|
});
|
|
337
337
|
|
|
338
|
+
// IMPORTANT: if you are updating this, you should also update the setting in
|
|
339
|
+
// js_timeline-meta.
|
|
338
340
|
Common.Settings.registerSettingExtension({
|
|
339
341
|
category: Common.Settings.SettingCategory.PERFORMANCE,
|
|
340
342
|
storageType: Common.Settings.SettingStorageType.SYNCED,
|
|
341
|
-
title: i18nLazyString(UIStrings.
|
|
342
|
-
settingName: 'timeline-
|
|
343
|
+
title: i18nLazyString(UIStrings.timelineShowAllEvents),
|
|
344
|
+
settingName: 'timeline-show-all-events',
|
|
343
345
|
settingType: Common.Settings.SettingType.BOOLEAN,
|
|
344
346
|
defaultValue: false,
|
|
345
347
|
});
|
|
346
348
|
|
|
349
|
+
// IMPORTANT: if you are updating this, you should also update the setting in
|
|
350
|
+
// js_timeline-meta.
|
|
347
351
|
Common.Settings.registerSettingExtension({
|
|
348
352
|
category: Common.Settings.SettingCategory.PERFORMANCE,
|
|
349
353
|
storageType: Common.Settings.SettingStorageType.SYNCED,
|
|
350
|
-
title: i18nLazyString(UIStrings.
|
|
351
|
-
settingName: 'timeline-
|
|
354
|
+
title: i18nLazyString(UIStrings.timelineDebugMode),
|
|
355
|
+
settingName: 'timeline-debug-mode',
|
|
352
356
|
settingType: Common.Settings.SettingType.BOOLEAN,
|
|
353
357
|
defaultValue: false,
|
|
354
358
|
});
|
|
@@ -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: 268971579b74f0369f828111e6c8f5bafbacd5ee
|
|
5
5
|
Update Mechanism: Manual (https://crbug.com/428069060)
|
|
6
6
|
License: BSD-3-Clause
|
|
7
7
|
License File: LICENSE
|
|
@@ -130,7 +130,8 @@ export class InspectorDrawerView {
|
|
|
130
130
|
this.#drawerMinimizedSetting =
|
|
131
131
|
Common.Settings.Settings.instance().createLocalSetting('inspector.drawer-minimized', false);
|
|
132
132
|
this.tabbedLocation = ViewManager.instance().createTabbedLocation(
|
|
133
|
-
options.revealDrawer, 'drawer-view', true, true,
|
|
133
|
+
options.revealDrawer, 'drawer-view', true, true,
|
|
134
|
+
{isLocationVisible: options.isVisible, tabbedPaneFactory: () => new DrawerTabbedPane()});
|
|
134
135
|
this.#moreTabsButton = this.tabbedLocation.enableMoreTabsButton();
|
|
135
136
|
this.#moreTabsButton.setTitle(i18nString(UIStrings.moreTools));
|
|
136
137
|
this.tabbedPane = this.tabbedLocation.tabbedPane() as DrawerTabbedPane;
|
|
@@ -228,7 +228,7 @@ 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, Root.Runtime.Runtime.queryParam('panel'));
|
|
231
|
+
'panel', true, true, {defaultTab: Root.Runtime.Runtime.queryParam('panel')});
|
|
232
232
|
|
|
233
233
|
this.tabbedPane = this.tabbedLocation.tabbedPane();
|
|
234
234
|
this.tabbedPane.setMinimumSize(MIN_MAIN_PANEL_WIDTH, 0);
|
|
@@ -0,0 +1,269 @@
|
|
|
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
|
+
// Side-effect import: registers the `<devtools-menu-button>` custom element
|
|
6
|
+
// used by `PLUS_BUTTON_VIEW` below. The named imports are type-only.
|
|
7
|
+
import './ContextMenu.js';
|
|
8
|
+
|
|
9
|
+
import * as Host from '../../core/host/host.js';
|
|
10
|
+
import * as i18n from '../../core/i18n/i18n.js';
|
|
11
|
+
import type * as Platform from '../../core/platform/platform.js';
|
|
12
|
+
import {Directives, html, render} from '../lit/lit.js';
|
|
13
|
+
|
|
14
|
+
import type {ContextMenu, MenuButton} from './ContextMenu.js';
|
|
15
|
+
import type {View} from './View.js';
|
|
16
|
+
import {ViewLocationValues} from './ViewRegistration.js';
|
|
17
|
+
|
|
18
|
+
const UIStrings = {
|
|
19
|
+
/**
|
|
20
|
+
* @description Default tooltip / accessible name of the "plus" button shown
|
|
21
|
+
* after the visible tabs in a tab strip. Clicking it opens a menu listing
|
|
22
|
+
* tools that are not currently shown as a visible tab.
|
|
23
|
+
*/
|
|
24
|
+
moreTools: 'More tools',
|
|
25
|
+
} as const;
|
|
26
|
+
const str_ = i18n.i18n.registerUIStrings('ui/legacy/PlusButton.ts', UIStrings);
|
|
27
|
+
const i18nString = i18n.i18n.getLocalizedString.bind(undefined, str_);
|
|
28
|
+
|
|
29
|
+
/** Declarative configuration for the plus button. */
|
|
30
|
+
export interface PlusButtonOptions {
|
|
31
|
+
title?: Platform.UIString.LocalizedString;
|
|
32
|
+
jslogContext?: string;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* Minimal `TabbedPane` surface read by the populator. Defined as an
|
|
37
|
+
* interface so test doubles can satisfy it without an `as unknown as
|
|
38
|
+
* TabbedPane` double-cast.
|
|
39
|
+
*/
|
|
40
|
+
export interface PlusButtonTabbedPane {
|
|
41
|
+
element: HTMLElement;
|
|
42
|
+
hiddenTabs(): ReadonlyArray<{id: string, title: string, jslogContext?: string}>;
|
|
43
|
+
hasTab(id: string): boolean;
|
|
44
|
+
firstHiddenTabIndex(): number;
|
|
45
|
+
moveTab(tabId: string, newIndex: number): void;
|
|
46
|
+
selectTab(tabId: string, userGesture?: boolean, forceFocus?: boolean): boolean;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
export interface PlusButtonMenuContext {
|
|
50
|
+
tabbedPane: PlusButtonTabbedPane;
|
|
51
|
+
location: string;
|
|
52
|
+
/**
|
|
53
|
+
* Production callers pass `() => location.views.values()` (NOT
|
|
54
|
+
* `manager.viewsForLocation(location)`) so views moved in via
|
|
55
|
+
* `appendView` are reflected immediately. Called fresh on every
|
|
56
|
+
* menu open.
|
|
57
|
+
*/
|
|
58
|
+
views: () => Iterable<View>;
|
|
59
|
+
manager: {
|
|
60
|
+
viewsForLocation(location: string): View[],
|
|
61
|
+
moveView(viewId: string, locationName: string): void,
|
|
62
|
+
};
|
|
63
|
+
showView: (view: View) => void;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
interface AddToolEntry {
|
|
67
|
+
title: string;
|
|
68
|
+
jslogContext: string;
|
|
69
|
+
isPreviewFeature: boolean;
|
|
70
|
+
action: () => void;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
export interface OverflowTabModel {
|
|
74
|
+
id: string;
|
|
75
|
+
title: string;
|
|
76
|
+
jslogContext?: string;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
export interface PlusButtonMenuModel {
|
|
80
|
+
overflowTabs: readonly OverflowTabModel[];
|
|
81
|
+
addToolEntries: readonly AddToolEntry[];
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
/**
|
|
85
|
+
* Presenter (MVP) for the plus-button menu. {@link buildModel} is called
|
|
86
|
+
* fresh on every menu open so newly-registered views — or views that
|
|
87
|
+
* just left the visible tab strip — are reflected immediately.
|
|
88
|
+
*/
|
|
89
|
+
export class PlusButtonPresenter {
|
|
90
|
+
readonly #context: PlusButtonMenuContext;
|
|
91
|
+
|
|
92
|
+
constructor(context: PlusButtonMenuContext) {
|
|
93
|
+
this.#context = context;
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
buildModel(): PlusButtonMenuModel {
|
|
97
|
+
const {tabbedPane, location, views, manager} = this.#context;
|
|
98
|
+
const overflowTabs: readonly OverflowTabModel[] =
|
|
99
|
+
tabbedPane.hiddenTabs().map(tab => ({id: tab.id, title: tab.title, jslogContext: tab.jslogContext}));
|
|
100
|
+
|
|
101
|
+
const addToolEntries: AddToolEntry[] = [];
|
|
102
|
+
// Seed dedup sets from the overflowed tabs so an addable entry
|
|
103
|
+
// (e.g. a closeable view in the other main location) sharing an id
|
|
104
|
+
// or title with an overflowed tab is not listed twice.
|
|
105
|
+
const seenIds = new Set<string>(overflowTabs.map(tab => tab.id));
|
|
106
|
+
const seenTitles = new Set<string>(overflowTabs.map(tab => tab.title));
|
|
107
|
+
|
|
108
|
+
for (const view of views()) {
|
|
109
|
+
// Skip views that already have a tab. Hidden tabs are already listed
|
|
110
|
+
// in the overflow section above, and visible tabs are accessible
|
|
111
|
+
// directly in the tab strip.
|
|
112
|
+
if (tabbedPane.hasTab(view.viewId())) {
|
|
113
|
+
continue;
|
|
114
|
+
}
|
|
115
|
+
// Transient views are not user-addable.
|
|
116
|
+
if (view.isTransient()) {
|
|
117
|
+
continue;
|
|
118
|
+
}
|
|
119
|
+
if (seenIds.has(view.viewId()) || seenTitles.has(view.title())) {
|
|
120
|
+
continue;
|
|
121
|
+
}
|
|
122
|
+
seenIds.add(view.viewId());
|
|
123
|
+
seenTitles.add(view.title());
|
|
124
|
+
const isIssuesPane = view.viewId() === 'issues-pane';
|
|
125
|
+
addToolEntries.push({
|
|
126
|
+
title: view.title(),
|
|
127
|
+
jslogContext: view.viewId(),
|
|
128
|
+
isPreviewFeature: view.isPreviewFeature(),
|
|
129
|
+
action: () => {
|
|
130
|
+
if (isIssuesPane) {
|
|
131
|
+
// Distinct from `HAMBURGER_MENU` so plus-button opens are
|
|
132
|
+
// not conflated with three-dot-menu opens in the dashboard.
|
|
133
|
+
Host.userMetrics.issuesPanelOpenedFrom(Host.UserMetrics.IssueOpener.MORE_TOOLS_MENU);
|
|
134
|
+
}
|
|
135
|
+
this.#context.showView(view);
|
|
136
|
+
},
|
|
137
|
+
});
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
// Offer cross-location moves between the two main surfaces: the
|
|
141
|
+
// panel plus button lists drawer views and vice versa.
|
|
142
|
+
const otherLocation = location === ViewLocationValues.PANEL ? ViewLocationValues.DRAWER_VIEW :
|
|
143
|
+
location === ViewLocationValues.DRAWER_VIEW ? ViewLocationValues.PANEL :
|
|
144
|
+
null;
|
|
145
|
+
if (otherLocation) {
|
|
146
|
+
for (const view of manager.viewsForLocation(otherLocation)) {
|
|
147
|
+
// Non-closeable views (e.g. Console) cannot be moved between
|
|
148
|
+
// locations, so they're excluded here. They still appear in the
|
|
149
|
+
// overflow section when their own location's tab strip overflows.
|
|
150
|
+
if (view.isTransient() || !view.isCloseable() || seenIds.has(view.viewId()) || seenTitles.has(view.title())) {
|
|
151
|
+
continue;
|
|
152
|
+
}
|
|
153
|
+
seenIds.add(view.viewId());
|
|
154
|
+
seenTitles.add(view.title());
|
|
155
|
+
const viewId = view.viewId();
|
|
156
|
+
addToolEntries.push({
|
|
157
|
+
title: view.title(),
|
|
158
|
+
jslogContext: viewId,
|
|
159
|
+
isPreviewFeature: view.isPreviewFeature(),
|
|
160
|
+
action: () => manager.moveView(viewId, location),
|
|
161
|
+
});
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
addToolEntries.sort((a, b) => a.title.localeCompare(b.title));
|
|
166
|
+
|
|
167
|
+
return {overflowTabs, addToolEntries};
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
/**
|
|
172
|
+
* Renders the plus-button menu by asking {@link PlusButtonPresenter}
|
|
173
|
+
* for a model and pushing it into `contextMenu`. Overflowed tabs (in
|
|
174
|
+
* tab order) come first, followed by deduplicated "add tool" entries
|
|
175
|
+
* sorted alphabetically.
|
|
176
|
+
*/
|
|
177
|
+
export function populatePlusButtonMenu(contextMenu: ContextMenu, context: PlusButtonMenuContext): void {
|
|
178
|
+
const model = new PlusButtonPresenter(context).buildModel();
|
|
179
|
+
const hasOverflow = model.overflowTabs.length > 0;
|
|
180
|
+
|
|
181
|
+
// When there are no overflowed tabs, surface the add-tool entries in
|
|
182
|
+
// the default section so they are not visually demoted to a footer.
|
|
183
|
+
for (const tab of model.overflowTabs) {
|
|
184
|
+
contextMenu.defaultSection().appendItem(
|
|
185
|
+
tab.title, () => revealOverflowTab(context.tabbedPane, tab.id), {jslogContext: tab.jslogContext ?? tab.id});
|
|
186
|
+
}
|
|
187
|
+
const addToolSection = hasOverflow ? contextMenu.footerSection() : contextMenu.defaultSection();
|
|
188
|
+
for (const entry of model.addToolEntries) {
|
|
189
|
+
addToolSection.appendItem(
|
|
190
|
+
entry.title, entry.action, {isPreviewFeature: entry.isPreviewFeature, jslogContext: entry.jslogContext});
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
/**
|
|
195
|
+
* Reveals an overflowed tab and persists its new position via
|
|
196
|
+
* `moveTab(firstHidden - 1)` so the tab stays in the visible region
|
|
197
|
+
* after a reload — independent of any runtime `currentTab` /
|
|
198
|
+
* `lastSelectedOverflowTab` priority logic. The previously-last-visible
|
|
199
|
+
* tab is pushed to the start of the overflow region, matching the
|
|
200
|
+
* intuition that the newly opened tab replaces the one the user
|
|
201
|
+
* implicitly stopped using.
|
|
202
|
+
*
|
|
203
|
+
* Exported only for testing.
|
|
204
|
+
*/
|
|
205
|
+
export function revealOverflowTab(tabbedPane: PlusButtonTabbedPane, tabId: string): void {
|
|
206
|
+
const firstHidden = tabbedPane.firstHiddenTabIndex();
|
|
207
|
+
if (firstHidden > 0) {
|
|
208
|
+
// `firstHidden - 1` is the index of the last currently-visible tab.
|
|
209
|
+
tabbedPane.moveTab(tabId, firstHidden - 1);
|
|
210
|
+
}
|
|
211
|
+
tabbedPane.selectTab(tabId, /* userGesture */ true, /* forceFocus */ true);
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
interface PlusButtonViewInput {
|
|
215
|
+
title: string;
|
|
216
|
+
jslogContext: string;
|
|
217
|
+
populateMenuCall: (menu: ContextMenu) => void;
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
/**
|
|
221
|
+
* Standard `(input, output, target)` view function so `Lit.render` is
|
|
222
|
+
* called inside a view (per `@devtools/no-lit-render-outside-of-view`).
|
|
223
|
+
* `output.button` is captured via `ref` to avoid a `querySelector`
|
|
224
|
+
* round-trip in {@link installPlusButton}.
|
|
225
|
+
*
|
|
226
|
+
* `slot` is set declaratively in the template so the attribute is
|
|
227
|
+
* present on the very first connection — the first `slotchange` then
|
|
228
|
+
* sees the button as the trailing-slot target and no extra layout pass
|
|
229
|
+
* is needed.
|
|
230
|
+
*/
|
|
231
|
+
export const PLUS_BUTTON_VIEW =
|
|
232
|
+
(input: PlusButtonViewInput, output: {button?: MenuButton}, target: HTMLElement): void => {
|
|
233
|
+
render(
|
|
234
|
+
html`
|
|
235
|
+
<devtools-menu-button
|
|
236
|
+
${Directives.ref(el => {
|
|
237
|
+
output.button = el as MenuButton | undefined;
|
|
238
|
+
})}
|
|
239
|
+
slot="trailing-button"
|
|
240
|
+
.iconName=${'plus'}
|
|
241
|
+
.title=${input.title}
|
|
242
|
+
.jslogContext=${input.jslogContext}
|
|
243
|
+
.populateMenuCall=${input.populateMenuCall}>
|
|
244
|
+
</devtools-menu-button>`,
|
|
245
|
+
target);
|
|
246
|
+
};
|
|
247
|
+
|
|
248
|
+
/**
|
|
249
|
+
* Renders a `<devtools-menu-button>` configured as the plus button into
|
|
250
|
+
* `tabbedPane`'s `trailing-button` slot and returns the slotted host.
|
|
251
|
+
* The returned `MenuButton` is used by the next CL to toggle visibility
|
|
252
|
+
* (e.g. when the drawer is minimized).
|
|
253
|
+
*/
|
|
254
|
+
export function installPlusButton(context: PlusButtonMenuContext, options: PlusButtonOptions = {}): MenuButton {
|
|
255
|
+
const output: {button?: MenuButton} = {};
|
|
256
|
+
// `render` is synchronous and the `ref` directive fires during render,
|
|
257
|
+
// so `output.button` is assigned by the time the view returns.
|
|
258
|
+
PLUS_BUTTON_VIEW(
|
|
259
|
+
{
|
|
260
|
+
title: options.title ?? i18nString(UIStrings.moreTools),
|
|
261
|
+
jslogContext: options.jslogContext ?? '',
|
|
262
|
+
populateMenuCall: menu => populatePlusButtonMenu(menu, context),
|
|
263
|
+
},
|
|
264
|
+
output, context.tabbedPane.element);
|
|
265
|
+
if (!output.button) {
|
|
266
|
+
throw new Error('installPlusButton: ref directive did not capture <devtools-menu-button>');
|
|
267
|
+
}
|
|
268
|
+
return output.button;
|
|
269
|
+
}
|
|
@@ -9,7 +9,7 @@ import * as Common from '../../core/common/common.js';
|
|
|
9
9
|
import * as Host from '../../core/host/host.js';
|
|
10
10
|
import * as i18n from '../../core/i18n/i18n.js';
|
|
11
11
|
import * as Platform from '../../core/platform/platform.js';
|
|
12
|
-
import
|
|
12
|
+
import * 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
15
|
import {render, type TemplateResult} from '../lit/lit.js';
|
|
@@ -17,6 +17,7 @@ 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
|
+
import * as PlusButton from './PlusButton.js';
|
|
20
21
|
import {type EventData, Events as TabbedPaneEvents, TabbedPane} from './TabbedPane.js';
|
|
21
22
|
import {type ItemsProvider, type ToolbarItem, ToolbarMenuButton} from './Toolbar.js';
|
|
22
23
|
import {createTextChild} from './UIUtils.js';
|
|
@@ -397,11 +398,8 @@ export class ViewManager extends Common.ObjectWrapper.ObjectWrapper<EventTypes>
|
|
|
397
398
|
|
|
398
399
|
createTabbedLocation(
|
|
399
400
|
revealCallback: (() => void), location: string, restoreSelection?: boolean, allowReorder?: boolean,
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
return new TabbedLocation(
|
|
403
|
-
this, revealCallback, location, restoreSelection, allowReorder, defaultTab, isLocationVisible,
|
|
404
|
-
tabbedPaneFactory);
|
|
401
|
+
options?: TabbedLocationOptions): TabbedViewLocation {
|
|
402
|
+
return new TabbedLocation(this, revealCallback, location, restoreSelection, allowReorder, options);
|
|
405
403
|
}
|
|
406
404
|
|
|
407
405
|
createStackLocation(revealCallback?: (() => void), location?: string, jslogContext?: string): ViewLocation {
|
|
@@ -648,6 +646,17 @@ type CloseableTabSetting = Record<string, boolean>;
|
|
|
648
646
|
|
|
649
647
|
type TabOrderSetting = Record<string, number>;
|
|
650
648
|
|
|
649
|
+
export interface TabbedLocationOptions {
|
|
650
|
+
defaultTab?: string|null;
|
|
651
|
+
isLocationVisible?: () => boolean;
|
|
652
|
+
tabbedPaneFactory?: TabbedPaneFactory;
|
|
653
|
+
/**
|
|
654
|
+
* Installed into the `TabbedPane`'s `trailing-button` slot before any
|
|
655
|
+
* tabs are appended, so the very first layout pass reserves width for it.
|
|
656
|
+
*/
|
|
657
|
+
plusButton?: PlusButton.PlusButtonOptions;
|
|
658
|
+
}
|
|
659
|
+
|
|
651
660
|
class TabbedLocation extends Location implements TabbedViewLocation {
|
|
652
661
|
#tabbedPane: TabbedPane;
|
|
653
662
|
private readonly location: string;
|
|
@@ -661,9 +670,8 @@ class TabbedLocation extends Location implements TabbedViewLocation {
|
|
|
661
670
|
|
|
662
671
|
constructor(
|
|
663
672
|
manager: ViewManager, revealCallback: (() => void), location: string, restoreSelection?: boolean,
|
|
664
|
-
allowReorder?: boolean,
|
|
665
|
-
|
|
666
|
-
const tabbedPane = tabbedPaneFactory ? tabbedPaneFactory() : new TabbedPane();
|
|
673
|
+
allowReorder?: boolean, options?: TabbedLocationOptions) {
|
|
674
|
+
const tabbedPane = options?.tabbedPaneFactory ? options.tabbedPaneFactory() : new TabbedPane();
|
|
667
675
|
if (allowReorder) {
|
|
668
676
|
tabbedPane.setAllowTabReorder(true);
|
|
669
677
|
}
|
|
@@ -687,8 +695,27 @@ class TabbedLocation extends Location implements TabbedViewLocation {
|
|
|
687
695
|
if (restoreSelection) {
|
|
688
696
|
this.lastSelectedTabSetting = Common.Settings.Settings.instance().createSetting(location + '-selected-tab', '');
|
|
689
697
|
}
|
|
690
|
-
this.defaultTab = defaultTab;
|
|
691
|
-
this.isLocationVisible = isLocationVisible;
|
|
698
|
+
this.defaultTab = options?.defaultTab;
|
|
699
|
+
this.isLocationVisible = options?.isLocationVisible;
|
|
700
|
+
|
|
701
|
+
// Install before `appendApplicableItems` so the very first layout pass
|
|
702
|
+
// reserves width for the button and we avoid a reflow that snaps the
|
|
703
|
+
// last tab into the overflow menu.
|
|
704
|
+
if (options?.plusButton && Root.Runtime.hostConfig.devToolsPlusButton?.enabled) {
|
|
705
|
+
PlusButton.installPlusButton(
|
|
706
|
+
{
|
|
707
|
+
tabbedPane: this.#tabbedPane,
|
|
708
|
+
location: this.location,
|
|
709
|
+
// Use the local `views` map (not `manager.viewsForLocation`) so
|
|
710
|
+
// cross-location moves added via `appendView` are reflected.
|
|
711
|
+
views: () => this.views.values(),
|
|
712
|
+
manager: this.manager,
|
|
713
|
+
showView: view => {
|
|
714
|
+
this.showView(view, undefined, /* userGesture */ true).catch(err => console.error(err));
|
|
715
|
+
},
|
|
716
|
+
},
|
|
717
|
+
options.plusButton);
|
|
718
|
+
}
|
|
692
719
|
|
|
693
720
|
if (location) {
|
|
694
721
|
this.appendApplicableItems(location);
|
|
@@ -37,6 +37,7 @@ import * as i18n from '../../../../core/i18n/i18n.js';
|
|
|
37
37
|
import * as Platform from '../../../../core/platform/platform.js';
|
|
38
38
|
import * as TextUtils from '../../../../models/text_utils/text_utils.js';
|
|
39
39
|
import * as Workspace from '../../../../models/workspace/workspace.js';
|
|
40
|
+
import {createIcon} from '../../../kit/kit.js';
|
|
40
41
|
import * as VisualLogging from '../../../visual_logging/visual_logging.js';
|
|
41
42
|
import * as UI from '../../legacy.js';
|
|
42
43
|
|
|
@@ -82,6 +83,10 @@ const UIStrings = {
|
|
|
82
83
|
* @description The default file name when downloading a file
|
|
83
84
|
*/
|
|
84
85
|
download: 'download',
|
|
86
|
+
/**
|
|
87
|
+
* @description Text indicating an image is too large to display and offering to open it in a new tab
|
|
88
|
+
*/
|
|
89
|
+
thisImageIsTooBig: 'This image is too big to display in DevTools. Click here to open it in a new tab.',
|
|
85
90
|
} as const;
|
|
86
91
|
const str_ = i18n.i18n.registerUIStrings('ui/legacy/components/source_frame/ImageView.ts', UIStrings);
|
|
87
92
|
const i18nString = i18n.i18n.getLocalizedString.bind(undefined, str_);
|
|
@@ -97,6 +102,7 @@ export class ImageView extends UI.View.SimpleView {
|
|
|
97
102
|
private readonly mimeTypeLabel: UI.Toolbar.ToolbarText;
|
|
98
103
|
private readonly container: HTMLElement;
|
|
99
104
|
private imagePreviewElement: HTMLImageElement;
|
|
105
|
+
private imageUnavailableElement: HTMLElement;
|
|
100
106
|
private cachedContent?: TextUtils.ContentData.ContentData;
|
|
101
107
|
constructor(mimeType: string, contentProvider: TextUtils.ContentProvider.ContentProvider) {
|
|
102
108
|
super({
|
|
@@ -125,6 +131,14 @@ export class ImageView extends UI.View.SimpleView {
|
|
|
125
131
|
this.container = this.element.createChild('div', 'image');
|
|
126
132
|
this.imagePreviewElement = this.container.createChild('img', 'resource-image-view');
|
|
127
133
|
this.imagePreviewElement.addEventListener('contextmenu', this.contextMenu.bind(this), true);
|
|
134
|
+
|
|
135
|
+
const link = document.createElement('devtools-link');
|
|
136
|
+
link.setAttribute('href', this.url);
|
|
137
|
+
link.classList.add('resource-image-unavailable', 'hidden');
|
|
138
|
+
link.appendChild(createIcon('open-externally'));
|
|
139
|
+
link.appendChild(document.createTextNode(i18nString(UIStrings.thisImageIsTooBig)));
|
|
140
|
+
this.container.appendChild(link);
|
|
141
|
+
this.imageUnavailableElement = link;
|
|
128
142
|
}
|
|
129
143
|
|
|
130
144
|
override async toolbarItems(): Promise<UI.Toolbar.ToolbarItem[]> {
|
|
@@ -165,8 +179,10 @@ export class ImageView extends UI.View.SimpleView {
|
|
|
165
179
|
this.cachedContent = content;
|
|
166
180
|
const imageSrc = content.asImagePreviewUrl();
|
|
167
181
|
if (imageSrc === null) {
|
|
182
|
+
this.imageUnavailableElement.classList.remove('hidden');
|
|
168
183
|
return;
|
|
169
184
|
}
|
|
185
|
+
this.imageUnavailableElement.classList.add('hidden');
|
|
170
186
|
const loadPromise = new Promise(x => {
|
|
171
187
|
this.imagePreviewElement.onload = x;
|
|
172
188
|
});
|
|
@@ -21,3 +21,16 @@
|
|
|
21
21
|
user-select: text;
|
|
22
22
|
-webkit-user-drag: auto;
|
|
23
23
|
}
|
|
24
|
+
|
|
25
|
+
.resource-image-unavailable {
|
|
26
|
+
display: flex;
|
|
27
|
+
align-items: center;
|
|
28
|
+
justify-content: center;
|
|
29
|
+
gap: var(--sys-size-4);
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
.resource-image-unavailable devtools-icon {
|
|
33
|
+
color: var(--sys-color-primary);
|
|
34
|
+
width: var(--sys-size-7);
|
|
35
|
+
height: var(--sys-size-7);
|
|
36
|
+
}
|
|
@@ -1112,7 +1112,7 @@ export class ContentProviderContextMenuProvider implements
|
|
|
1112
1112
|
if (!Common.ParsedURL.schemeIs(contentUrl, 'file:')) {
|
|
1113
1113
|
contextMenu.revealSection().appendItem(
|
|
1114
1114
|
UI.UIUtils.openLinkExternallyLabel(),
|
|
1115
|
-
() =>
|
|
1115
|
+
() => UIHelpers.openInNewTab(
|
|
1116
1116
|
contentUrl.endsWith(':formatted') ?
|
|
1117
1117
|
Common.ParsedURL.ParsedURL.slice(contentUrl, 0, contentUrl.lastIndexOf(':')) :
|
|
1118
1118
|
contentUrl),
|
|
@@ -29,6 +29,7 @@ import * as ListControl from './ListControl.js';
|
|
|
29
29
|
import * as ListModel from './ListModel.js';
|
|
30
30
|
import * as ListWidget from './ListWidget.js';
|
|
31
31
|
import * as Panel from './Panel.js';
|
|
32
|
+
import * as PlusButton from './PlusButton.js';
|
|
32
33
|
import * as PopoverHelper from './PopoverHelper.js';
|
|
33
34
|
import * as ProgressIndicator from './ProgressIndicator.js';
|
|
34
35
|
import * as RemoteDebuggingTerminatedScreen from './RemoteDebuggingTerminatedScreen.js';
|
|
@@ -82,6 +83,7 @@ export {
|
|
|
82
83
|
ListModel,
|
|
83
84
|
ListWidget,
|
|
84
85
|
Panel,
|
|
86
|
+
PlusButton,
|
|
85
87
|
PopoverHelper,
|
|
86
88
|
ProgressIndicator,
|
|
87
89
|
RemoteDebuggingTerminatedScreen,
|
|
@@ -1074,21 +1074,37 @@ export const knownContextValues = new Set([
|
|
|
1074
1074
|
'copy-xpath',
|
|
1075
1075
|
'core-web-vitals',
|
|
1076
1076
|
'corner',
|
|
1077
|
+
'corner-block-end',
|
|
1077
1078
|
'corner-block-end-shape',
|
|
1079
|
+
'corner-block-start',
|
|
1078
1080
|
'corner-block-start-shape',
|
|
1081
|
+
'corner-bottom',
|
|
1082
|
+
'corner-bottom-left',
|
|
1079
1083
|
'corner-bottom-left-shape',
|
|
1084
|
+
'corner-bottom-right',
|
|
1080
1085
|
'corner-bottom-right-shape',
|
|
1081
1086
|
'corner-bottom-shape',
|
|
1087
|
+
'corner-end-end',
|
|
1082
1088
|
'corner-end-end-shape',
|
|
1089
|
+
'corner-end-start',
|
|
1083
1090
|
'corner-end-start-shape',
|
|
1091
|
+
'corner-inline-end',
|
|
1084
1092
|
'corner-inline-end-shape',
|
|
1093
|
+
'corner-inline-start',
|
|
1085
1094
|
'corner-inline-start-shape',
|
|
1095
|
+
'corner-left',
|
|
1086
1096
|
'corner-left-shape',
|
|
1097
|
+
'corner-right',
|
|
1087
1098
|
'corner-right-shape',
|
|
1088
1099
|
'corner-shape',
|
|
1100
|
+
'corner-start-end',
|
|
1089
1101
|
'corner-start-end-shape',
|
|
1102
|
+
'corner-start-start',
|
|
1090
1103
|
'corner-start-start-shape',
|
|
1104
|
+
'corner-top',
|
|
1105
|
+
'corner-top-left',
|
|
1091
1106
|
'corner-top-left-shape',
|
|
1107
|
+
'corner-top-right',
|
|
1092
1108
|
'corner-top-right-shape',
|
|
1093
1109
|
'corner-top-shape',
|
|
1094
1110
|
'corners',
|
|
@@ -2316,6 +2332,7 @@ export const knownContextValues = new Set([
|
|
|
2316
2332
|
'lighthouse',
|
|
2317
2333
|
'lighthouse-report-widget',
|
|
2318
2334
|
'lighthouse-show-settings-toolbar',
|
|
2335
|
+
'lighthouse-snapshot-report-widget',
|
|
2319
2336
|
'lighthouse.audit-summary.average',
|
|
2320
2337
|
'lighthouse.audit-summary.fail',
|
|
2321
2338
|
'lighthouse.audit-summary.informative',
|
|
@@ -3053,6 +3070,7 @@ export const knownContextValues = new Set([
|
|
|
3053
3070
|
'play-recording',
|
|
3054
3071
|
'player',
|
|
3055
3072
|
'playing',
|
|
3073
|
+
'plus-button',
|
|
3056
3074
|
'pointer',
|
|
3057
3075
|
'pointer-32-bit',
|
|
3058
3076
|
'pointer-64-bit',
|
|
@@ -3441,6 +3459,7 @@ export const knownContextValues = new Set([
|
|
|
3441
3459
|
'script-text-node',
|
|
3442
3460
|
'scripting',
|
|
3443
3461
|
'scroll',
|
|
3462
|
+
'scroll-axis-lock',
|
|
3444
3463
|
'scroll-behavior',
|
|
3445
3464
|
'scroll-initial-target',
|
|
3446
3465
|
'scroll-into-view',
|
package/mcp/mcp.ts
CHANGED
|
@@ -38,7 +38,7 @@ export {
|
|
|
38
38
|
} from '../front_end/models/ai_assistance/data_formatters/PerformanceTraceFormatter.js';
|
|
39
39
|
export {AgentFocus} from '../front_end/models/ai_assistance/performance/AIContext.js';
|
|
40
40
|
export {DebuggerWorkspaceBinding} from '../front_end/models/bindings/DebuggerWorkspaceBinding.js';
|
|
41
|
-
export
|
|
41
|
+
export * as CrUXManager from '../front_end/models/crux-manager/CrUXManager.js';
|
|
42
42
|
export * as Formatter from '../front_end/models/formatter/formatter.js';
|
|
43
43
|
export * as HeapSnapshotModel from '../front_end/models/heap_snapshot/heap_snapshot.js';
|
|
44
44
|
export {Issue} from '../front_end/models/issues_manager/Issue.js';
|
package/package.json
CHANGED