chrome-devtools-frontend 1.0.1701636 → 1.0.1702515

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 (78) hide show
  1. package/eslint.config.mjs +3 -1
  2. package/front_end/core/host/UserMetricsEnums.ts +2 -1
  3. package/front_end/core/root/ExperimentNames.ts +1 -0
  4. package/front_end/core/sdk/CSSPropertyParserMatchers.ts +2 -7
  5. package/front_end/devtools_compatibility.js +2 -1
  6. package/front_end/entrypoints/main/MainImpl.ts +8 -0
  7. package/front_end/generated/InspectorBackendCommands.ts +3 -3
  8. package/front_end/generated/protocol.ts +0 -3
  9. package/front_end/models/ai_assistance/AiAgent2.ts +13 -1
  10. package/front_end/models/ai_assistance/AiConversation.ts +1 -1
  11. package/front_end/models/ai_assistance/agents/ContextSelectionAgent.ts +13 -19
  12. package/front_end/models/change_tracker/ChangeTracker.ts +5 -64
  13. package/front_end/models/comment_manager/CommentManager.ts +3 -5
  14. package/front_end/models/comment_manager/CommentThread.ts +30 -13
  15. package/front_end/models/issues_manager/CookieDeprecationMetadataIssue.ts +6 -7
  16. package/front_end/models/issues_manager/DeprecationIssue.ts +1 -1
  17. package/front_end/models/issues_manager/MarkdownIssueDescription.ts +14 -5
  18. package/front_end/models/issues_manager/SRIMessageSignatureIssue.ts +2 -4
  19. package/front_end/models/issues_manager/descriptions/cookieWarnMetadataGrantRead.md +0 -1
  20. package/front_end/models/issues_manager/descriptions/cookieWarnMetadataGrantReadOptOut.md +5 -0
  21. package/front_end/models/issues_manager/descriptions/cookieWarnMetadataGrantSet.md +0 -1
  22. package/front_end/models/issues_manager/descriptions/cookieWarnMetadataGrantSetOptOut.md +5 -0
  23. package/front_end/models/issues_manager/descriptions/deprecation.md +1 -1
  24. package/front_end/models/issues_manager/descriptions/sriValidationFailedIntegrityMismatch.md +2 -0
  25. package/front_end/models/persistence/NetworkPersistenceManager.ts +14 -2
  26. package/front_end/panels/common/CommentThreadWidget.ts +29 -10
  27. package/front_end/panels/common/CommentsOverlayWidget.ts +50 -14
  28. package/front_end/panels/common/commentThreadWidget.css +11 -0
  29. package/front_end/panels/elements/StylePropertiesSection.ts +10 -0
  30. package/front_end/panels/elements/StylePropertyTreeElement.ts +1 -1
  31. package/front_end/panels/elements/StylesSidebarPane.ts +101 -49
  32. package/front_end/panels/elements/components/CSSQuery.ts +1 -1
  33. package/front_end/panels/elements/components/cssQuery.css +4 -0
  34. package/front_end/panels/elements/elements.ts +0 -1
  35. package/front_end/panels/elements/stylesSidebarPane.css +6 -0
  36. package/front_end/panels/sources/ResourceOriginPlugin.ts +8 -3
  37. package/front_end/panels/sources/ScopeChainSidebarPane.ts +3 -4
  38. package/front_end/panels/sources/TabbedEditorContainer.ts +339 -126
  39. package/front_end/panels/sources/UISourceCodeFrame.ts +9 -0
  40. package/front_end/third_party/chromium/README.chromium +1 -1
  41. package/front_end/third_party/chromium/client-variations/ClientVariations.js +25 -96
  42. package/front_end/third_party/codemirror.next/chunk/angular.js +1 -1
  43. package/front_end/third_party/codemirror.next/chunk/codemirror.js +1 -1
  44. package/front_end/third_party/codemirror.next/chunk/codemirror.js.map +1 -1
  45. package/front_end/third_party/codemirror.next/chunk/cpp.js +1 -1
  46. package/front_end/third_party/codemirror.next/chunk/cpp.js.map +1 -1
  47. package/front_end/third_party/codemirror.next/chunk/java.js +1 -1
  48. package/front_end/third_party/codemirror.next/chunk/java.js.map +1 -1
  49. package/front_end/third_party/codemirror.next/chunk/legacy.js +1 -1
  50. package/front_end/third_party/codemirror.next/chunk/less.js +1 -1
  51. package/front_end/third_party/codemirror.next/chunk/less.js.map +1 -1
  52. package/front_end/third_party/codemirror.next/chunk/markdown.js +1 -1
  53. package/front_end/third_party/codemirror.next/chunk/markdown.js.map +1 -1
  54. package/front_end/third_party/codemirror.next/chunk/php.js +1 -1
  55. package/front_end/third_party/codemirror.next/chunk/php.js.map +1 -1
  56. package/front_end/third_party/codemirror.next/chunk/python.js +1 -1
  57. package/front_end/third_party/codemirror.next/chunk/python.js.map +1 -1
  58. package/front_end/third_party/codemirror.next/chunk/sass.js +1 -1
  59. package/front_end/third_party/codemirror.next/chunk/sass.js.map +1 -1
  60. package/front_end/third_party/codemirror.next/chunk/svelte.js +1 -1
  61. package/front_end/third_party/codemirror.next/chunk/svelte.js.map +1 -1
  62. package/front_end/third_party/codemirror.next/chunk/vue.js +1 -1
  63. package/front_end/third_party/codemirror.next/chunk/wast.js +1 -1
  64. package/front_end/third_party/codemirror.next/chunk/wast.js.map +1 -1
  65. package/front_end/third_party/codemirror.next/chunk/xml.js +1 -1
  66. package/front_end/third_party/codemirror.next/chunk/xml.js.map +1 -1
  67. package/front_end/third_party/codemirror.next/codemirror.next.js +1 -1
  68. package/front_end/third_party/codemirror.next/package.json +4 -1
  69. package/front_end/third_party/codemirror.next/rebuild.sh +1 -1
  70. package/front_end/ui/comments/CommentOverlayManager.ts +11 -10
  71. package/front_end/ui/components/markdown_view/MarkdownPlaceholderLitRenderer.ts +117 -0
  72. package/front_end/ui/components/markdown_view/markdown_view.ts +2 -0
  73. package/front_end/ui/legacy/Treeoutline.ts +28 -19
  74. package/front_end/ui/legacy/UIUtils.ts +1 -1
  75. package/front_end/ui/legacy/components/object_ui/ObjectPropertiesSection.ts +228 -343
  76. package/front_end/ui/legacy/components/object_ui/objectPropertiesSection.css +2 -27
  77. package/mcp/mcp.ts +1 -0
  78. package/package.json +1 -1
@@ -15,7 +15,7 @@ import * as uiI18n from '../../ui/i18n/i18n.js';
15
15
  import {Icon, Link} from '../../ui/kit/kit.js';
16
16
  import * as SourceFrame from '../../ui/legacy/components/source_frame/source_frame.js';
17
17
  import * as UI from '../../ui/legacy/legacy.js';
18
- import {html, render} from '../../ui/lit/lit.js';
18
+ import {Directives, html, nothing, render, type TemplateResult} from '../../ui/lit/lit.js';
19
19
  import * as VisualLogging from '../../ui/visual_logging/visual_logging.js';
20
20
  import * as PanelCommon from '../common/common.js';
21
21
  import * as Snippets from '../snippets/snippets.js';
@@ -75,6 +75,8 @@ const UIStrings = {
75
75
  } as const;
76
76
  const str_ = i18n.i18n.registerUIStrings('panels/sources/TabbedEditorContainer.ts', UIStrings);
77
77
  const i18nString = i18n.i18n.getLocalizedString.bind(undefined, str_);
78
+ const {repeat} = Directives;
79
+ const {widget} = UI.Widget;
78
80
 
79
81
  const enum SourceViewType {
80
82
  IMAGE_VIEW = 'ImageView',
@@ -84,6 +86,95 @@ const enum SourceViewType {
84
86
  }
85
87
  const HEADER_OVERRIDES_FILENAME = '.headers';
86
88
 
89
+ export interface TabbedEditorViewInput {
90
+ openTabs: Array<{
91
+ tabId: string,
92
+ title: string,
93
+ tooltip: string,
94
+ uiSourceCode: Workspace.UISourceCode.UISourceCode,
95
+ isCloseable: boolean,
96
+ widget?: UI.Widget.Widget,
97
+ icon?: TemplateResult|HTMLElement,
98
+ suffix?: HTMLElement,
99
+ }>;
100
+ activeTabId?: string;
101
+ }
102
+
103
+ export interface TabbedEditorViewOutput {
104
+ onClose(e: Event): void;
105
+ onTabOrderChanged(e: Event): void;
106
+ onSelect(e: Event): void;
107
+ }
108
+
109
+ export type View = (input: TabbedEditorViewInput, output: TabbedEditorViewOutput, target: HTMLElement) => void;
110
+
111
+ const UI_SOURCE_CODE_WIDGET_MAP = new WeakMap<Workspace.UISourceCode.UISourceCode, UI.Widget.Widget>();
112
+
113
+ function getOrCreateSourceView(uiSourceCode: Workspace.UISourceCode.UISourceCode,
114
+ onCreate: (view: UI.Widget.Widget) => void): UI.Widget.Widget {
115
+ const existing = UI_SOURCE_CODE_WIDGET_MAP.get(uiSourceCode);
116
+ if (existing) {
117
+ return existing;
118
+ }
119
+
120
+ let sourceView: UI.Widget.Widget;
121
+ const contentType = uiSourceCode.contentType();
122
+ if (contentType === Common.ResourceType.resourceTypes.Image || uiSourceCode.mimeType().startsWith('image/')) {
123
+ sourceView = new SourceFrame.ImageView.ImageView(uiSourceCode.mimeType(), uiSourceCode);
124
+ } else if (contentType === Common.ResourceType.resourceTypes.Font || uiSourceCode.mimeType().includes('font')) {
125
+ sourceView = new SourceFrame.FontView.FontView(uiSourceCode.mimeType(), uiSourceCode);
126
+ } else if (uiSourceCode.name() === Persistence.NetworkPersistenceManager.HEADERS_FILENAME) {
127
+ sourceView = new Components.HeadersView.HeadersView(uiSourceCode);
128
+ } else {
129
+ sourceView = new UISourceCodeFrame(uiSourceCode);
130
+ }
131
+
132
+ UI_SOURCE_CODE_WIDGET_MAP.set(uiSourceCode, sourceView);
133
+ onCreate(sourceView);
134
+ return sourceView;
135
+ }
136
+
137
+ function recycleUISourceCodeFrame(sourceFrame: UISourceCodeFrame,
138
+ uiSourceCode: Workspace.UISourceCode.UISourceCode): void {
139
+ UI_SOURCE_CODE_WIDGET_MAP.delete(sourceFrame.uiSourceCode());
140
+ sourceFrame.setUISourceCode(uiSourceCode);
141
+ UI_SOURCE_CODE_WIDGET_MAP.set(uiSourceCode, sourceFrame);
142
+ }
143
+
144
+ function getViewByUISourceCode(uiSourceCode: Workspace.UISourceCode.UISourceCode): UI.Widget.Widget|undefined {
145
+ return UI_SOURCE_CODE_WIDGET_MAP.get(uiSourceCode);
146
+ }
147
+
148
+ function removeSourceViewCache(uiSourceCode: Workspace.UISourceCode.UISourceCode): UI.Widget.Widget|undefined {
149
+ const view = UI_SOURCE_CODE_WIDGET_MAP.get(uiSourceCode);
150
+ if (view) {
151
+ UI_SOURCE_CODE_WIDGET_MAP.delete(uiSourceCode);
152
+ }
153
+ return view;
154
+ }
155
+
156
+ export const DEFAULT_VIEW: View = (input, output, target) => {
157
+ // clang-format off
158
+ render(html`
159
+ <devtools-tabbed-pane
160
+ class="flex-auto vbox"
161
+ @close=${output.onClose}
162
+ @taborderchanged=${output.onTabOrderChanged}
163
+ @select=${output.onSelect}
164
+ >
165
+ ${repeat(input.openTabs, tab => tab.tabId, tab => html`
166
+ <div id=${tab.tabId}
167
+ title=${tab.title}
168
+ ?closeable=${tab.isCloseable}
169
+ ?selected=${input.activeTabId === tab.tabId}
170
+ style="display: flex; flex: auto;">
171
+ ${tab.icon ? html`<span slot="icon">${tab.icon}</span>` : nothing}
172
+ ${tab.suffix ? html`<span slot="suffix">${tab.suffix}</span>` : nothing}
173
+ ${tab.widget ? html`${widget(UI.Widget.WrapperWidget, {widget: tab.widget})}` : nothing}
174
+ </div>`)}
175
+ </devtools-tabbed-pane>`, target);
176
+ // clang-format on
177
+ };
87
178
  let tabId = 0;
88
179
 
89
180
  const TabbedEditorContainerBase: Common.ObjectWrapper.EventMixin<EventTypes, typeof UI.Widget.VBox> =
@@ -92,14 +183,70 @@ const TabbedEditorContainerBase: Common.ObjectWrapper.EventMixin<EventTypes, typ
92
183
  );
93
184
 
94
185
  export class TabbedEditorContainer extends TabbedEditorContainerBase {
186
+ override focus(): void {
187
+ if (this.visibleView) {
188
+ this.visibleView.focus();
189
+ }
190
+ }
191
+
192
+ #scheduleUpdate(): void {
193
+ this.#renderView();
194
+ }
195
+
196
+ #renderView(): void {
197
+ const input: TabbedEditorViewInput = {
198
+ openTabs: [...this.files.entries()].map(
199
+ ([tabId, uiSourceCode]) => ({
200
+ tabId,
201
+ title: this.titleForFile(uiSourceCode),
202
+ tooltip: this.tooltipForFile(uiSourceCode),
203
+ uiSourceCode,
204
+ isCloseable: true,
205
+ icon: this.#tabIcons.get(uiSourceCode),
206
+ suffix: this.#tabSuffixes.get(uiSourceCode),
207
+ widget: (this.#currentFile === uiSourceCode) ? this.getOrCreateSourceView(uiSourceCode) :
208
+ this.getCreatedSourceView(uiSourceCode),
209
+ })),
210
+ activeTabId: this.#currentFile ? this.tabIds.get(this.#currentFile) : undefined,
211
+ };
212
+ const output: TabbedEditorViewOutput = {
213
+ onClose: (e: Event) => {
214
+ this.tabClosed((e as CustomEvent<{tabId: string}>).detail.tabId,
215
+ (e as CustomEvent<{isUserGesture?: boolean}>).detail.isUserGesture);
216
+ this.#scheduleUpdate();
217
+ },
218
+ onTabOrderChanged: (e: Event) => {
219
+ const tabIds = (e as CustomEvent<{tabIds: string[]}>).detail.tabIds;
220
+ const newFiles = new Map<string, Workspace.UISourceCode.UISourceCode>();
221
+ for (const id of tabIds) {
222
+ if (this.files.has(id)) {
223
+ const file = this.files.get(id);
224
+ if (file) {
225
+ newFiles.set(id, file);
226
+ }
227
+ }
228
+ }
229
+ this.files = newFiles;
230
+ this.#scheduleUpdate();
231
+ },
232
+ onSelect: (e: Event) => {
233
+ this.tabSelected((e as CustomEvent<{tabId: string}>).detail.tabId,
234
+ (e as CustomEvent<{isUserGesture?: boolean}>).detail.isUserGesture);
235
+ this.#scheduleUpdate();
236
+ },
237
+ };
238
+ DEFAULT_VIEW(input, output, this.contentElement);
239
+ }
95
240
  #historyManager!: EditingLocationHistoryManager;
96
241
  set historyManager(historyManager: EditingLocationHistoryManager) {
97
242
  this.#historyManager = historyManager;
98
243
  }
99
- private readonly sourceViewByUISourceCode = new Map<Workspace.UISourceCode.UISourceCode, UI.Widget.Widget>();
100
- private readonly tabbedPane: UI.TabbedPane.TabbedPane;
244
+
245
+ private readonly tabbedPane: UI.TabbedPane.TabbedPaneElement;
101
246
  private tabIds: Map<Workspace.UISourceCode.UISourceCode, string>;
102
- private readonly files: Map<string, Workspace.UISourceCode.UISourceCode>;
247
+ private files: Map<string, Workspace.UISourceCode.UISourceCode>;
248
+ readonly #tabIcons = new Map<Workspace.UISourceCode.UISourceCode, TemplateResult|HTMLElement>();
249
+ readonly #tabSuffixes = new Map<Workspace.UISourceCode.UISourceCode, HTMLElement>();
103
250
  #previouslyViewedFilesSetting!: Common.Settings.Setting<SerializedHistoryItem[]>;
104
251
  history!: History;
105
252
  set previouslyViewedFilesSetting(setting: Common.Settings.Setting<SerializedHistoryItem[]>) {
@@ -118,43 +265,46 @@ export class TabbedEditorContainer extends TabbedEditorContainerBase {
118
265
  constructor(element?: HTMLElement) {
119
266
  super(element);
120
267
 
121
- this.tabbedPane = new UI.TabbedPane.TabbedPane();
122
- // eslint-disable-next-line @devtools/no-imperative-dom-api
123
- this.tabbedPane.show(this.contentElement);
268
+ this.tabIds = new Map();
269
+ this.files = new Map();
270
+ this.uriToUISourceCode = new Map();
271
+ this.idToUISourceCode = new Map();
272
+ this.reentrantShow = false;
273
+
274
+ this.#renderView();
275
+
276
+ this.tabbedPane = this.contentElement.querySelector('devtools-tabbed-pane') as UI.TabbedPane.TabbedPaneElement;
277
+
124
278
  // eslint-disable-next-line @devtools/no-imperative-dom-api
125
279
  const placeholderElement = document.createElement('div');
126
280
  placeholderElement.classList.add('sources-placeholder');
127
- this.tabbedPane.setPlaceholderElement(placeholderElement);
281
+ this.tabbedPane.placeholder = placeholderElement;
128
282
  this.#renderPlaceholder(placeholderElement as HTMLElement);
129
- this.tabbedPane.setTabDelegate(new EditorContainerTabDelegate(this));
130
-
131
- this.tabbedPane.setCloseableTabs(true);
132
- this.tabbedPane.setAllowTabReorder(true, true);
283
+ this.tabbedPane.tabDelegate = new EditorContainerTabDelegate(this);
133
284
 
134
- this.tabbedPane.addEventListener(UI.TabbedPane.Events.TabClosed, this.tabClosed, this);
135
- this.tabbedPane.addEventListener(UI.TabbedPane.Events.TabSelected, this.tabSelected, this);
285
+ this.tabbedPane.closeableTabs = true;
286
+ this.tabbedPane.allowTabReorder = true;
287
+ this.tabbedPane.automaticReorder = true;
136
288
 
137
- this.tabbedPane.headerElement().setAttribute(
289
+ const widget = UI.Widget.Widget.getOrCreateWidget(this.tabbedPane) as UI.TabbedPane.TabbedPane;
290
+ widget.headerElement().setAttribute(
138
291
  'jslog',
139
292
  `${VisualLogging.toolbar('top').track({keydown: 'ArrowUp|ArrowLeft|ArrowDown|ArrowRight|Enter|Space'})}`);
140
293
 
141
- Persistence.Persistence.PersistenceImpl.instance().addEventListener(
142
- Persistence.Persistence.Events.BindingCreated, this.onBindingCreated, this);
143
- Persistence.Persistence.PersistenceImpl.instance().addEventListener(
144
- Persistence.Persistence.Events.BindingRemoved, this.onBindingRemoved, this);
294
+ Persistence.Persistence.PersistenceImpl.instance().addEventListener(Persistence.Persistence.Events.BindingCreated,
295
+ this.onBindingCreated, this);
296
+ Persistence.Persistence.PersistenceImpl.instance().addEventListener(Persistence.Persistence.Events.BindingRemoved,
297
+ this.onBindingRemoved, this);
145
298
  Persistence.NetworkPersistenceManager.NetworkPersistenceManager.instance().addEventListener(
146
299
  Persistence.NetworkPersistenceManager.Events.REQUEST_FOR_HEADER_OVERRIDES_FILE_CHANGED,
147
300
  this.#onRequestsForHeaderOverridesFileChanged, this);
148
-
149
- this.tabIds = new Map();
150
- this.files = new Map();
151
-
152
- this.uriToUISourceCode = new Map();
153
- this.idToUISourceCode = new Map();
154
- this.reentrantShow = false;
301
+ }
302
+ #tabsHistory: string[] = [];
303
+ #appendHistory(tabId: string): void {
304
+ this.#tabsHistory.unshift(tabId);
155
305
  }
156
306
 
157
- get tabbedPaneForTesting(): UI.TabbedPane.TabbedPane {
307
+ get tabbedPaneForTesting(): UI.TabbedPane.TabbedPaneElement {
158
308
  return this.tabbedPane;
159
309
  }
160
310
 
@@ -177,21 +327,24 @@ export class TabbedEditorContainer extends TabbedEditorContainerBase {
177
327
  }
178
328
 
179
329
  if (!fileSystemTabId) {
180
- const networkView = this.tabbedPane.tabView(networkTabId);
181
- const tabIndex = this.tabbedPane.tabIndex(networkTabId);
330
+ const networkView: UI.Widget.Widget|null = this.viewForFile(binding.network);
331
+ const tabIndex = Array.from(this.files.keys()).indexOf(networkTabId);
182
332
  if (networkView instanceof UISourceCodeFrame) {
183
333
  this.recycleUISourceCodeFrame(networkView, binding.fileSystem);
184
- fileSystemTabId = this.appendFileTab(binding.fileSystem, false, tabIndex, networkView);
334
+ fileSystemTabId = this.appendFileTab(binding.fileSystem, tabIndex, true);
185
335
  } else {
186
- fileSystemTabId = this.appendFileTab(binding.fileSystem, false, tabIndex);
187
- const fileSystemTabView = (this.tabbedPane.tabView(fileSystemTabId) as UI.Widget.Widget);
188
- this.restoreEditorProperties(fileSystemTabView, currentSelectionRange, currentScrollLineNumber);
336
+ fileSystemTabId = this.appendFileTab(binding.fileSystem, tabIndex);
337
+ const fileSystemTabView = this.viewForFile(binding.fileSystem);
338
+ if (fileSystemTabView) {
339
+ this.restoreEditorProperties(fileSystemTabView, currentSelectionRange, currentScrollLineNumber);
340
+ }
189
341
  }
190
342
  }
191
343
 
192
344
  this.closeTabs([networkTabId], true);
193
345
  if (wasSelectedInNetwork) {
194
- this.tabbedPane.selectTab(fileSystemTabId, false);
346
+ this.#currentFile = this.files.get(fileSystemTabId) || null;
347
+ this.#scheduleUpdate();
195
348
  }
196
349
 
197
350
  this.updateHistory();
@@ -209,19 +362,35 @@ export class TabbedEditorContainer extends TabbedEditorContainerBase {
209
362
  }
210
363
 
211
364
  get visibleView(): UI.Widget.Widget|null {
212
- return this.tabbedPane.visibleView as UI.Widget.Widget | null;
365
+ return this.#currentFile ? getViewByUISourceCode(this.#currentFile) || null : null;
213
366
  }
214
367
 
215
368
  fileViews(): UI.Widget.Widget[] {
216
- return this.tabbedPane.tabViews() as UI.Widget.Widget[];
369
+ return Array.from(this.files.values()).map(getViewByUISourceCode).filter(Boolean) as UI.Widget.Widget[];
217
370
  }
218
371
 
372
+ #leftToolbar?: UI.Toolbar.Toolbar;
219
373
  leftToolbar(): UI.Toolbar.Toolbar {
220
- return this.tabbedPane.leftToolbar();
374
+ if (!this.#leftToolbar) {
375
+ this.#leftToolbar =
376
+ document.createElement('devtools-toolbar') /* eslint-disable-line @devtools/no-imperative-dom-api */;
377
+ this.#leftToolbar.classList.add('tabbed-pane-left-toolbar');
378
+ this.#leftToolbar.slot = 'left';
379
+ this.tabbedPane.appendChild(this.#leftToolbar);
380
+ }
381
+ return this.#leftToolbar;
221
382
  }
222
383
 
384
+ #rightToolbar?: UI.Toolbar.Toolbar;
223
385
  rightToolbar(): UI.Toolbar.Toolbar {
224
- return this.tabbedPane.rightToolbar();
386
+ if (!this.#rightToolbar) {
387
+ this.#rightToolbar =
388
+ document.createElement('devtools-toolbar') /* eslint-disable-line @devtools/no-imperative-dom-api */;
389
+ this.#rightToolbar.classList.add('tabbed-pane-right-toolbar');
390
+ this.#rightToolbar.slot = 'right';
391
+ this.tabbedPane.appendChild(this.#rightToolbar);
392
+ }
393
+ return this.#rightToolbar;
225
394
  }
226
395
 
227
396
  showFile(uiSourceCode: Workspace.UISourceCode.UISourceCode): void {
@@ -250,11 +419,13 @@ export class TabbedEditorContainer extends TabbedEditorContainerBase {
250
419
  }
251
420
 
252
421
  closeAllFiles(): void {
253
- this.closeTabs(this.tabbedPane.tabIds());
422
+ this.closeTabs(Array.from(this.files.keys()));
254
423
  }
255
424
 
256
425
  detachEditors(): void {
257
- this.tabbedPane.detachChildWidgets();
426
+ for (const view of this.fileViews()) {
427
+ view.detach();
428
+ }
258
429
  }
259
430
 
260
431
  historyUISourceCodes(): Workspace.UISourceCode.UISourceCode[] {
@@ -269,11 +440,37 @@ export class TabbedEditorContainer extends TabbedEditorContainerBase {
269
440
  }
270
441
 
271
442
  selectNextTab(): void {
272
- this.tabbedPane.selectNextTab();
443
+ const tabIds = Array.from(this.files.keys());
444
+ if (tabIds.length === 0 || !this.#currentFile) {
445
+ return;
446
+ }
447
+ const currentTabId = this.tabIds.get(this.#currentFile);
448
+ if (!currentTabId) {
449
+ return;
450
+ }
451
+ const index = tabIds.indexOf(currentTabId);
452
+ const nextIndex = (index + 1) % tabIds.length;
453
+ const nextFile = this.files.get(tabIds[nextIndex]);
454
+ if (nextFile) {
455
+ this.#showFile(nextFile, false);
456
+ }
273
457
  }
274
458
 
275
459
  selectPrevTab(): void {
276
- this.tabbedPane.selectPrevTab();
460
+ const tabIds = Array.from(this.files.keys());
461
+ if (tabIds.length === 0 || !this.#currentFile) {
462
+ return;
463
+ }
464
+ const currentTabId = this.tabIds.get(this.#currentFile);
465
+ if (!currentTabId) {
466
+ return;
467
+ }
468
+ const index = tabIds.indexOf(currentTabId);
469
+ const prevIndex = (index - 1 + tabIds.length) % tabIds.length;
470
+ const prevFile = this.files.get(tabIds[prevIndex]);
471
+ if (prevFile) {
472
+ this.#showFile(prevFile, false);
473
+ }
277
474
  }
278
475
 
279
476
  private addViewListeners(): void {
@@ -311,8 +508,8 @@ export class TabbedEditorContainer extends TabbedEditorContainerBase {
311
508
  if (update.docChanged || update.selectionSet) {
312
509
  const {main} = update.state.selection;
313
510
  const lineFrom = update.state.doc.lineAt(main.from), lineTo = update.state.doc.lineAt(main.to);
314
- const range = new TextUtils.TextRange.TextRange(
315
- lineFrom.number - 1, main.from - lineFrom.from, lineTo.number - 1, main.to - lineTo.from);
511
+ const range = new TextUtils.TextRange.TextRange(lineFrom.number - 1, main.from - lineFrom.from, lineTo.number - 1,
512
+ main.to - lineTo.from);
316
513
  if (this.#currentFile) {
317
514
  this.history.updateSelectionRange(historyItemKey(this.#currentFile), range);
318
515
  }
@@ -342,9 +539,12 @@ export class TabbedEditorContainer extends TabbedEditorContainerBase {
342
539
  // Selecting the tab may cause showFile to be called again, but with the canonical source code,
343
540
  // which is not what we want, so we prevent reentrant calls.
344
541
  this.reentrantShow = true;
345
- const tabId = this.tabIds.get(canonicalSourceCode) || this.appendFileTab(canonicalSourceCode, userGesture);
542
+ const tabId = this.tabIds.get(canonicalSourceCode) || this.appendFileTab(canonicalSourceCode);
543
+ this.#appendHistory(tabId);
346
544
 
347
- this.tabbedPane.selectTab(tabId, userGesture);
545
+ this.#scheduleUpdate();
546
+ // Force TabbedPaneElement to sync its tabs synchronously to avoid layout races in E2E tests.
547
+ this.tabbedPane.tabs;
348
548
  } finally {
349
549
  this.reentrantShow = false;
350
550
  }
@@ -354,6 +554,9 @@ export class TabbedEditorContainer extends TabbedEditorContainerBase {
354
554
  }
355
555
 
356
556
  const previousView = this.currentView;
557
+ if (uiSourceCode) {
558
+ this.getOrCreateSourceView(uiSourceCode);
559
+ }
357
560
  this.currentView = this.visibleView;
358
561
  this.addViewListeners();
359
562
 
@@ -396,9 +599,13 @@ export class TabbedEditorContainer extends TabbedEditorContainerBase {
396
599
  if (!shouldPrompt || confirm(i18nString(UIStrings.areYouSureYouWantToCloseUnsaved, {PH1: uiSourceCode.name()}))) {
397
600
  uiSourceCode.resetWorkingCopy();
398
601
  if (nextTabId) {
399
- this.tabbedPane.selectTab(nextTabId, true);
602
+ const nextFile = this.files.get(nextTabId);
603
+ if (nextFile) {
604
+ this.#showFile(nextFile, false);
605
+ }
400
606
  }
401
- this.tabbedPane.closeTab(id, true);
607
+ this.tabClosed(id, true);
608
+ this.#scheduleUpdate();
402
609
  return true;
403
610
  }
404
611
  return false;
@@ -419,9 +626,13 @@ export class TabbedEditorContainer extends TabbedEditorContainerBase {
419
626
  }
420
627
  }
421
628
  if (dirtyTabs.length) {
422
- this.tabbedPane.selectTab(dirtyTabs[0], true);
629
+ const dirtyFile = this.files.get(dirtyTabs[0]);
630
+ if (dirtyFile) {
631
+ this.#showFile(dirtyFile, false);
632
+ }
423
633
  }
424
- this.tabbedPane.closeTabs(cleanTabs, true);
634
+ cleanTabs.forEach(id => this.tabClosed(id, true));
635
+ this.#scheduleUpdate();
425
636
  for (let i = 0; i < dirtyTabs.length; ++i) {
426
637
  const nextTabId = i + 1 < dirtyTabs.length ? dirtyTabs[i + 1] : null;
427
638
  if (!this.maybeCloseTab(dirtyTabs[i], nextTabId)) {
@@ -470,7 +681,7 @@ export class TabbedEditorContainer extends TabbedEditorContainerBase {
470
681
  }
471
682
 
472
683
  if (!this.tabIds.has(uiSourceCode)) {
473
- this.appendFileTab(uiSourceCode, false);
684
+ this.appendFileTab(uiSourceCode);
474
685
  }
475
686
 
476
687
  // Select tab if this file was the last to be shown.
@@ -500,16 +711,22 @@ export class TabbedEditorContainer extends TabbedEditorContainerBase {
500
711
  const tabId = this.tabIds.get(uiSourceCode);
501
712
  if (tabId) {
502
713
  tabIds.push(tabId);
714
+ } else {
715
+ this.removeSourceFrame(uiSourceCode);
503
716
  }
504
- if (this.uriToUISourceCode.get(uiSourceCode.url()) === uiSourceCode) {
505
- this.uriToUISourceCode.delete(uiSourceCode.url());
717
+ for (const [k, v] of this.uriToUISourceCode) {
718
+ if (v === uiSourceCode) {
719
+ this.uriToUISourceCode.delete(k);
720
+ }
506
721
  }
507
- if (this.idToUISourceCode.get(uiSourceCode.canonicalScriptId()) === uiSourceCode) {
508
- this.idToUISourceCode.delete(uiSourceCode.canonicalScriptId());
722
+ for (const [k, v] of this.idToUISourceCode) {
723
+ if (v === uiSourceCode) {
724
+ this.idToUISourceCode.delete(k);
725
+ }
509
726
  }
510
- this.removeSourceFrame(uiSourceCode);
511
727
  }
512
- this.tabbedPane.closeTabs(tabIds);
728
+ tabIds.forEach(id => this.tabClosed(id, false));
729
+ this.#scheduleUpdate();
513
730
  }
514
731
 
515
732
  private editorClosedByUserAction(uiSourceCode: Workspace.UISourceCode.UISourceCode): void {
@@ -523,7 +740,7 @@ export class TabbedEditorContainer extends TabbedEditorContainerBase {
523
740
 
524
741
  private updateHistory(): void {
525
742
  const historyItemKeys = [];
526
- for (const tabId of this.tabbedPane.lastOpenedTabIds(MAX_PREVIOUSLY_VIEWED_FILES_COUNT)) {
743
+ for (const tabId of this.#tabsHistory.slice(0, MAX_PREVIOUSLY_VIEWED_FILES_COUNT)) {
527
744
  const uiSourceCode = this.files.get(tabId);
528
745
  if (uiSourceCode !== undefined) {
529
746
  historyItemKeys.push(historyItemKey(uiSourceCode));
@@ -538,24 +755,27 @@ export class TabbedEditorContainer extends TabbedEditorContainerBase {
538
755
  return uiSourceCode.url();
539
756
  }
540
757
 
541
- private appendFileTab(
542
- uiSourceCode: Workspace.UISourceCode.UISourceCode, userGesture?: boolean, index?: number,
543
- replaceView?: UI.Widget.Widget): string {
544
- const view = replaceView || this.viewForFile(uiSourceCode);
545
- const title = this.titleForFile(uiSourceCode);
546
- const tooltip = this.tooltipForFile(uiSourceCode);
547
-
758
+ private appendFileTab(uiSourceCode: Workspace.UISourceCode.UISourceCode, index?: number,
759
+ ignoreHistory?: boolean): string {
548
760
  const tabId = this.generateTabId();
549
761
  this.tabIds.set(uiSourceCode, tabId);
550
- this.files.set(tabId, uiSourceCode);
551
762
 
552
- if (!replaceView) {
553
- const savedSelectionRange = this.history.selectionRange(historyItemKey(uiSourceCode));
554
- const savedScrollLineNumber = this.history.scrollLineNumber(historyItemKey(uiSourceCode));
763
+ if (index !== undefined) {
764
+ const entries = Array.from(this.files.entries());
765
+ entries.splice(index, 0, [tabId, uiSourceCode]);
766
+ this.files = new Map(entries);
767
+ } else {
768
+ this.files.set(tabId, uiSourceCode);
769
+ }
770
+
771
+ const savedSelectionRange = this.history.selectionRange(historyItemKey(uiSourceCode));
772
+ const savedScrollLineNumber = this.history.scrollLineNumber(historyItemKey(uiSourceCode));
773
+ const view = this.viewForFile(uiSourceCode);
774
+ if (view && !ignoreHistory) {
555
775
  this.restoreEditorProperties(view, savedSelectionRange, savedScrollLineNumber);
556
776
  }
557
777
 
558
- this.tabbedPane.appendTab(tabId, title, view, tooltip, userGesture, undefined, undefined, index, 'editor');
778
+ this.#scheduleUpdate();
559
779
 
560
780
  this.updateFileTitle(uiSourceCode);
561
781
  this.addUISourceCodeListeners(uiSourceCode);
@@ -568,6 +788,10 @@ export class TabbedEditorContainer extends TabbedEditorContainerBase {
568
788
  }
569
789
  });
570
790
  }
791
+
792
+ if (!this.#currentFile) {
793
+ this.#showFile(uiSourceCode, false);
794
+ }
571
795
  return tabId;
572
796
  }
573
797
 
@@ -577,13 +801,15 @@ export class TabbedEditorContainer extends TabbedEditorContainerBase {
577
801
  title=${i18nString(UIStrings.unableToLoadThisContent)}>
578
802
  </devtools-icon>`;
579
803
  // clang-format on
580
- if (this.tabbedPane.tabView(tabId)) {
581
- this.tabbedPane.setTrailingTabIcon(tabId, icon);
804
+ const uiSourceCode = this.files.get(tabId);
805
+ if (uiSourceCode) {
806
+ this.#tabIcons.set(uiSourceCode, icon);
807
+ this.#scheduleUpdate();
582
808
  }
583
809
  }
584
810
 
585
- private restoreEditorProperties(
586
- editorView: UI.Widget.Widget, selection?: TextUtils.TextRange.TextRange, firstLineNumber?: number): void {
811
+ private restoreEditorProperties(editorView: UI.Widget.Widget, selection?: TextUtils.TextRange.TextRange,
812
+ firstLineNumber?: number): void {
587
813
  const sourceFrame = editorView instanceof SourceFrame.SourceFrame.SourceFrameImpl ? editorView : null;
588
814
  if (!sourceFrame) {
589
815
  return;
@@ -596,8 +822,7 @@ export class TabbedEditorContainer extends TabbedEditorContainerBase {
596
822
  }
597
823
  }
598
824
 
599
- private tabClosed(event: Common.EventTarget.EventTargetEvent<UI.TabbedPane.EventData>): void {
600
- const {tabId, isUserGesture} = event.data;
825
+ private tabClosed(tabId: string, isUserGesture?: boolean): void {
601
826
  const uiSourceCode = this.files.get(tabId);
602
827
  if (this.#currentFile && this.#currentFile.canonicalScriptId() === uiSourceCode?.canonicalScriptId()) {
603
828
  this.removeViewListeners();
@@ -621,9 +846,7 @@ export class TabbedEditorContainer extends TabbedEditorContainerBase {
621
846
  }
622
847
  }
623
848
 
624
- private tabSelected(event: Common.EventTarget.EventTargetEvent<UI.TabbedPane.EventData>): void {
625
- const {tabId, isUserGesture} = event.data;
626
-
849
+ private tabSelected(tabId: string, isUserGesture?: boolean): void {
627
850
  const uiSourceCode = this.files.get(tabId);
628
851
  if (uiSourceCode) {
629
852
  this.#showFile(uiSourceCode, isUserGesture);
@@ -632,33 +855,32 @@ export class TabbedEditorContainer extends TabbedEditorContainerBase {
632
855
 
633
856
  private addUISourceCodeListeners(uiSourceCode: Workspace.UISourceCode.UISourceCode): void {
634
857
  uiSourceCode.addEventListener(Workspace.UISourceCode.Events.TitleChanged, this.uiSourceCodeTitleChanged, this);
635
- uiSourceCode.addEventListener(
636
- Workspace.UISourceCode.Events.WorkingCopyChanged, this.uiSourceCodeWorkingCopyChanged, this);
637
- uiSourceCode.addEventListener(
638
- Workspace.UISourceCode.Events.WorkingCopyCommitted, this.uiSourceCodeWorkingCopyCommitted, this);
858
+ uiSourceCode.addEventListener(Workspace.UISourceCode.Events.WorkingCopyChanged, this.uiSourceCodeWorkingCopyChanged,
859
+ this);
860
+ uiSourceCode.addEventListener(Workspace.UISourceCode.Events.WorkingCopyCommitted,
861
+ this.uiSourceCodeWorkingCopyCommitted, this);
639
862
  }
640
863
 
641
864
  private removeUISourceCodeListeners(uiSourceCode: Workspace.UISourceCode.UISourceCode): void {
642
865
  uiSourceCode.removeEventListener(Workspace.UISourceCode.Events.TitleChanged, this.uiSourceCodeTitleChanged, this);
643
- uiSourceCode.removeEventListener(
644
- Workspace.UISourceCode.Events.WorkingCopyChanged, this.uiSourceCodeWorkingCopyChanged, this);
645
- uiSourceCode.removeEventListener(
646
- Workspace.UISourceCode.Events.WorkingCopyCommitted, this.uiSourceCodeWorkingCopyCommitted, this);
866
+ uiSourceCode.removeEventListener(Workspace.UISourceCode.Events.WorkingCopyChanged,
867
+ this.uiSourceCodeWorkingCopyChanged, this);
868
+ uiSourceCode.removeEventListener(Workspace.UISourceCode.Events.WorkingCopyCommitted,
869
+ this.uiSourceCodeWorkingCopyCommitted, this);
647
870
  }
648
871
 
649
872
  private updateFileTitle(uiSourceCode: Workspace.UISourceCode.UISourceCode): void {
650
873
  const tabId = this.tabIds.get(uiSourceCode);
651
874
  if (tabId) {
652
- const title = this.titleForFile(uiSourceCode);
653
- const tooltip = this.tooltipForFile(uiSourceCode);
654
- this.tabbedPane.changeTabTitle(tabId, title, tooltip);
875
+ this.#scheduleUpdate();
655
876
  if (uiSourceCode.loadError()) {
656
877
  // clang-format off
657
878
  const icon = html`<devtools-icon class="small" name="cross-circle-filled"
658
879
  title=${i18nString(UIStrings.unableToLoadThisContent)}>
659
880
  </devtools-icon>`;
660
881
  // clang-format on
661
- this.tabbedPane.setTrailingTabIcon(tabId, icon);
882
+ this.#tabIcons.set(uiSourceCode, icon);
883
+ this.#scheduleUpdate();
662
884
  } else if (Persistence.Persistence.PersistenceImpl.instance().hasUnsavedCommittedChanges(uiSourceCode)) {
663
885
  /* eslint-disable @devtools/no-imperative-dom-api --
664
886
  * This is a temporary solution using the <devtools-tooltip>
@@ -691,10 +913,16 @@ export class TabbedEditorContainer extends TabbedEditorContainerBase {
691
913
  }
692
914
  suffixElement.append(icon, tooltip);
693
915
  /* eslint-enable @devtools/no-imperative-dom-api */
694
- this.tabbedPane.setSuffixElement(tabId, suffixElement);
916
+ this.#tabSuffixes.set(uiSourceCode, suffixElement);
917
+ this.#scheduleUpdate();
695
918
  } else {
696
919
  const icon = PanelCommon.PersistenceUtils.PersistenceUtils.iconForUISourceCode(uiSourceCode);
697
- this.tabbedPane.setTrailingTabIcon(tabId, icon);
920
+ if (icon) {
921
+ this.#tabIcons.set(uiSourceCode, icon as TemplateResult);
922
+ } else {
923
+ this.#tabIcons.delete(uiSourceCode);
924
+ }
925
+ this.#scheduleUpdate();
698
926
  }
699
927
  }
700
928
  }
@@ -703,7 +931,7 @@ export class TabbedEditorContainer extends TabbedEditorContainerBase {
703
931
  void {
704
932
  const uiSourceCode = event.data;
705
933
 
706
- const widget = this.sourceViewByUISourceCode.get(uiSourceCode);
934
+ const widget = getViewByUISourceCode(uiSourceCode);
707
935
  if (widget) {
708
936
  if (this.#sourceViewTypeForWidget(widget) !== this.#sourceViewTypeForUISourceCode(uiSourceCode)) {
709
937
  // Remove the existing editor tab and create a new one of the correct type.
@@ -813,49 +1041,35 @@ export class TabbedEditorContainer extends TabbedEditorContainerBase {
813
1041
  }
814
1042
 
815
1043
  getCreatedSourceView(uiSourceCode: Workspace.UISourceCode.UISourceCode): UI.Widget.Widget|undefined {
816
- return this.sourceViewByUISourceCode.get(uiSourceCode);
817
- }
818
-
819
- viewForFile(uiSourceCode: Workspace.UISourceCode.UISourceCode): UI.Widget.Widget {
820
- return this.getOrCreateSourceView(uiSourceCode);
1044
+ return getViewByUISourceCode(uiSourceCode);
821
1045
  }
822
1046
 
823
1047
  private getOrCreateSourceView(uiSourceCode: Workspace.UISourceCode.UISourceCode): UI.Widget.Widget {
824
- return this.sourceViewByUISourceCode.get(uiSourceCode) || this.createSourceView(uiSourceCode);
825
- }
826
-
827
- private createSourceView(uiSourceCode: Workspace.UISourceCode.UISourceCode): UI.Widget.Widget {
828
- let sourceView;
829
- const contentType = uiSourceCode.contentType();
830
-
831
- if (contentType === Common.ResourceType.resourceTypes.Image || uiSourceCode.mimeType().startsWith('image/')) {
832
- sourceView = new SourceFrame.ImageView.ImageView(uiSourceCode.mimeType(), uiSourceCode);
833
- } else if (contentType === Common.ResourceType.resourceTypes.Font || uiSourceCode.mimeType().includes('font')) {
834
- sourceView = new SourceFrame.FontView.FontView(uiSourceCode.mimeType(), uiSourceCode);
835
- } else if (uiSourceCode.name() === HEADER_OVERRIDES_FILENAME) {
836
- sourceView = new Components.HeadersView.HeadersView(uiSourceCode);
837
- } else {
838
- sourceView = new UISourceCodeFrame(uiSourceCode);
839
- this.#historyManager.trackSourceFrameCursorJumps(sourceView);
1048
+ const view = getViewByUISourceCode(uiSourceCode);
1049
+ if (view) {
1050
+ return view;
840
1051
  }
1052
+ return getOrCreateSourceView(uiSourceCode, sourceView => {
1053
+ if (sourceView instanceof UISourceCodeFrame) {
1054
+ this.#historyManager.trackSourceFrameCursorJumps(sourceView);
1055
+ }
1056
+ uiSourceCode.addEventListener(Workspace.UISourceCode.Events.TitleChanged, this.uiSourceCodeTitleChanged, this);
1057
+ });
1058
+ }
841
1059
 
842
- this.sourceViewByUISourceCode.set(uiSourceCode, sourceView);
843
- uiSourceCode.addEventListener(Workspace.UISourceCode.Events.TitleChanged, this.uiSourceCodeTitleChanged, this);
844
- return sourceView;
1060
+ viewForFile(uiSourceCode: Workspace.UISourceCode.UISourceCode): UI.Widget.Widget {
1061
+ return this.getOrCreateSourceView(uiSourceCode);
845
1062
  }
846
1063
 
847
1064
  recycleUISourceCodeFrame(sourceFrame: UISourceCodeFrame, uiSourceCode: Workspace.UISourceCode.UISourceCode): void {
848
1065
  sourceFrame.uiSourceCode().removeEventListener(Workspace.UISourceCode.Events.TitleChanged,
849
1066
  this.uiSourceCodeTitleChanged, this);
850
- this.sourceViewByUISourceCode.delete(sourceFrame.uiSourceCode());
851
- sourceFrame.setUISourceCode(uiSourceCode);
852
- this.sourceViewByUISourceCode.set(uiSourceCode, sourceFrame);
1067
+ recycleUISourceCodeFrame(sourceFrame, uiSourceCode);
853
1068
  uiSourceCode.addEventListener(Workspace.UISourceCode.Events.TitleChanged, this.uiSourceCodeTitleChanged, this);
854
1069
  }
855
1070
 
856
1071
  private removeSourceFrame(uiSourceCode: Workspace.UISourceCode.UISourceCode): void {
857
- const sourceView = this.sourceViewByUISourceCode.get(uiSourceCode);
858
- this.sourceViewByUISourceCode.delete(uiSourceCode);
1072
+ const sourceView = removeSourceViewCache(uiSourceCode);
859
1073
  if (sourceView) {
860
1074
  uiSourceCode.removeEventListener(Workspace.UISourceCode.Events.TitleChanged, this.uiSourceCodeTitleChanged, this);
861
1075
  }
@@ -941,9 +1155,8 @@ export class HistoryItem implements HistoryItemKey {
941
1155
  selectionRange: TextUtils.TextRange.TextRange|undefined;
942
1156
  scrollLineNumber: number|undefined;
943
1157
 
944
- constructor(
945
- url: Platform.DevToolsPath.UrlString, resourceType: Common.ResourceType.ResourceType,
946
- selectionRange?: TextUtils.TextRange.TextRange, scrollLineNumber?: number) {
1158
+ constructor(url: Platform.DevToolsPath.UrlString, resourceType: Common.ResourceType.ResourceType,
1159
+ selectionRange?: TextUtils.TextRange.TextRange, scrollLineNumber?: number) {
947
1160
  this.url = url;
948
1161
  this.resourceType = resourceType;
949
1162
  this.selectionRange = selectionRange;