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.
Files changed (49) hide show
  1. package/front_end/core/host/UserMetrics.ts +5 -2
  2. package/front_end/core/root/ExperimentNames.ts +1 -0
  3. package/front_end/core/root/Runtime.ts +5 -0
  4. package/front_end/entrypoints/main/MainImpl.ts +8 -0
  5. package/front_end/generated/InspectorBackendCommands.ts +3 -4
  6. package/front_end/generated/SupportedCSSProperties.js +272 -2
  7. package/front_end/generated/protocol-mapping.d.ts +0 -10
  8. package/front_end/generated/protocol-proxy-api.d.ts +0 -8
  9. package/front_end/generated/protocol.ts +5 -7
  10. package/front_end/models/ai_assistance/AiConversation.ts +16 -11
  11. package/front_end/models/ai_assistance/agents/AccessibilityAgent.ts +13 -2
  12. package/front_end/models/ai_assistance/agents/AiAgent.ts +65 -11
  13. package/front_end/models/ai_assistance/agents/ContextSelectionAgent.ts +42 -2
  14. package/front_end/models/ai_assistance/agents/PerformanceAgent.ts +30 -1
  15. package/front_end/models/ai_assistance/data_formatters/PerformanceInsightFormatter.ts +4 -2
  16. package/front_end/models/ai_assistance/data_formatters/PerformanceTraceFormatter.ts +5 -2
  17. package/front_end/models/extensions/RecorderExtensionEndpoint.ts +9 -1
  18. package/front_end/models/extensions/RecorderPluginManager.ts +1 -0
  19. package/front_end/models/trace/handlers/FramesHandler.ts +19 -13
  20. package/front_end/panels/ai_assistance/components/AccessibilityAgentMarkdownRenderer.ts +3 -0
  21. package/front_end/panels/ai_assistance/components/ChatInput.ts +4 -2
  22. package/front_end/panels/ai_assistance/components/ChatMessage.ts +3 -1
  23. package/front_end/panels/application/preloading/components/PreloadingString.ts +0 -8
  24. package/front_end/panels/common/ExtensionServer.ts +34 -11
  25. package/front_end/panels/elements/CSSRuleValidator.ts +37 -34
  26. package/front_end/panels/elements/CSSRuleValidatorHelper.ts +8 -6
  27. package/front_end/panels/elements/ElementsTreeElement.ts +8 -2
  28. package/front_end/panels/elements/components/CSSHintDetailsView.ts +5 -5
  29. package/front_end/panels/js_timeline/js_timeline-meta.ts +30 -0
  30. package/front_end/panels/protocol_monitor/JSONEditor.ts +4 -4
  31. package/front_end/panels/protocol_monitor/ProtocolMonitor.ts +2 -2
  32. package/front_end/panels/recorder/RecorderController.ts +50 -1
  33. package/front_end/panels/recorder/extensions/ExtensionManager.ts +1 -0
  34. package/front_end/panels/recorder/models/RecordingPlayer.ts +12 -3
  35. package/front_end/panels/recorder/testing/RecorderHelpers.ts +2 -0
  36. package/front_end/panels/sources/FilteredUISourceCodeListProvider.ts +5 -2
  37. package/front_end/panels/timeline/timeline-meta.ts +10 -6
  38. package/front_end/third_party/chromium/README.chromium +1 -1
  39. package/front_end/ui/legacy/InspectorDrawerView.ts +2 -1
  40. package/front_end/ui/legacy/InspectorView.ts +1 -1
  41. package/front_end/ui/legacy/PlusButton.ts +269 -0
  42. package/front_end/ui/legacy/ViewManager.ts +38 -11
  43. package/front_end/ui/legacy/components/source_frame/ImageView.ts +16 -0
  44. package/front_end/ui/legacy/components/source_frame/imageView.css +13 -0
  45. package/front_end/ui/legacy/components/utils/Linkifier.ts +1 -1
  46. package/front_end/ui/legacy/legacy.ts +2 -0
  47. package/front_end/ui/visual_logging/KnownContextValues.ts +19 -0
  48. package/mcp/mcp.ts +1 -1
  49. 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
- Common.ParsedURL.schemeIs(step.url as Platform.DevToolsPath.UrlString, 'chrome:')) {
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
- if (uiSourceCode.project().type() === Workspace.Workspace.projectTypes.FileSystem &&
130
- !Persistence.Persistence.PersistenceImpl.instance().binding(uiSourceCode)) {
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.timelineShowAllEvents),
333
- settingName: 'timeline-show-all-events',
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.timelineDebugMode),
342
- settingName: 'timeline-debug-mode',
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.timelineInvalidationTracking),
351
- settingName: 'timeline-invalidation-tracking',
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: e366695d7cbe930bcbfb3a3372dca968d453feeb
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, undefined, options.isVisible, () => new DrawerTabbedPane());
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 type * as Root from '../../core/root/root.js';
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
- defaultTab?: string|null, isLocationVisible?: (() => boolean),
401
- tabbedPaneFactory?: TabbedPaneFactory): TabbedViewLocation {
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, defaultTab?: string|null, isLocationVisible?: (() => boolean),
665
- tabbedPaneFactory?: TabbedPaneFactory) {
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
- () => Host.InspectorFrontendHost.InspectorFrontendHostInstance.openInNewTab(
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 {CrUXManager} from '../front_end/models/crux-manager/CrUXManager.js';
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
@@ -105,5 +105,5 @@
105
105
  "ip-address": "10.1.0",
106
106
  "basic-ftp": "5.2.2"
107
107
  },
108
- "version": "1.0.1632065"
108
+ "version": "1.0.1635648"
109
109
  }