chrome-devtools-frontend 1.0.1699061 → 1.0.1701636

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 (157) hide show
  1. package/front_end/core/root/Runtime.ts +5 -0
  2. package/front_end/core/sdk/CSSPropertyParserMatchers.ts +16 -11
  3. package/front_end/core/sdk/NetworkManager.ts +7 -0
  4. package/front_end/core/sdk/SecurityOrigin.ts +1 -0
  5. package/front_end/core/sdk/ServiceWorkerCacheModel.ts +3 -0
  6. package/front_end/core/sdk/SourceMap.ts +12 -1
  7. package/front_end/core/sdk/SourceMapCache.ts +12 -0
  8. package/front_end/core/sdk/SourceMapManager.ts +17 -8
  9. package/front_end/core/sdk/SourceMapScopeChainEntry.ts +2 -19
  10. package/front_end/core/sdk/SourceMapScopesInfo.ts +21 -0
  11. package/front_end/core/sdk/StorageBucketsModel.ts +2 -0
  12. package/front_end/entrypoints/formatter_worker/ScopeParser.ts +7 -3
  13. package/front_end/entrypoints/formatter_worker/Substitute.ts +90 -26
  14. package/front_end/entrypoints/formatter_worker/formatter_worker-entrypoint.ts +1 -1
  15. package/front_end/entrypoints/main/ExecutionContextSelector.ts +17 -4
  16. package/front_end/entrypoints/main/GlobalAiButton.ts +2 -6
  17. package/front_end/entrypoints/main/MainImpl.ts +8 -1
  18. package/front_end/generated/InspectorBackendCommands.ts +13 -5
  19. package/front_end/generated/SupportedCSSProperties.ts +14 -0
  20. package/front_end/generated/protocol-mapping.d.ts +48 -9
  21. package/front_end/generated/protocol-proxy-api.d.ts +37 -7
  22. package/front_end/generated/protocol.ts +135 -22
  23. package/front_end/models/ai_assistance/debug.ts +10 -2
  24. package/front_end/models/bindings/NetworkProject.ts +30 -0
  25. package/front_end/models/bindings/ResourceScriptMapping.ts +62 -0
  26. package/front_end/models/change_tracker/ChangeTracker.ts +1 -1
  27. package/front_end/models/comment_manager/CD4ABridge.ts +4 -4
  28. package/front_end/models/comment_manager/CommentManager.ts +21 -0
  29. package/front_end/models/extensions/ExtensionAPI.ts +4 -2
  30. package/front_end/models/formatter/FormatterWorkerPool.ts +2 -2
  31. package/front_end/models/issues_manager/CookieIssue.ts +0 -2
  32. package/front_end/models/javascript_metadata/NativeFunctions.ts +7 -2
  33. package/front_end/models/persistence/NetworkPersistenceManager.ts +29 -4
  34. package/front_end/models/source_map_scopes/NamesResolver.ts +65 -40
  35. package/front_end/models/source_map_scopes/ScopeChainModel.ts +21 -1
  36. package/front_end/panels/accessibility/AccessibilityAnnouncementRecordingView.ts +70 -1
  37. package/front_end/panels/ai_assistance/ai_assistance-meta.ts +5 -8
  38. package/front_end/panels/ai_assistance/components/ChatMessage.ts +1 -1
  39. package/front_end/panels/application/AppManifestView.ts +128 -134
  40. package/front_end/panels/application/CrashReportContextView.ts +5 -5
  41. package/front_end/panels/application/DeviceBoundSessionsTreeElement.ts +17 -17
  42. package/front_end/panels/application/DeviceBoundSessionsView.ts +136 -137
  43. package/front_end/panels/application/IndexedDBModel.ts +3 -0
  44. package/front_end/panels/application/OriginTrialTreeView.ts +18 -23
  45. package/front_end/panels/application/ReportingApiTreeElement.ts +3 -3
  46. package/front_end/panels/application/ReportingApiView.ts +6 -14
  47. package/front_end/panels/application/application-meta.ts +7 -7
  48. package/front_end/panels/common/CommentThreadWidget.ts +19 -6
  49. package/front_end/panels/common/CommentsOverlayWidget.ts +143 -10
  50. package/front_end/panels/common/CommentsStatusBarPill.ts +13 -1
  51. package/front_end/panels/common/ExtensionServer.ts +61 -42
  52. package/front_end/panels/common/GeminiRebrandPromoDialog.ts +1 -1
  53. package/front_end/panels/common/commentThreadWidget.css +18 -0
  54. package/front_end/panels/common/common-meta.ts +4 -1
  55. package/front_end/panels/console/ConsolePrompt.ts +1 -1
  56. package/front_end/panels/console/ConsoleViewMessage.ts +1 -1
  57. package/front_end/panels/elements/ClassesPaneWidget.ts +2 -1
  58. package/front_end/panels/elements/DOMTreeContextMenu.ts +1 -1
  59. package/front_end/panels/elements/{ElementsTreeOutline.ts → DOMTreeWidget.ts} +45 -31
  60. package/front_end/panels/elements/ElementsPanel.ts +1 -1
  61. package/front_end/panels/elements/ElementsTreeElement.ts +3 -1
  62. package/front_end/panels/elements/ShortcutTreeElement.ts +1 -1
  63. package/front_end/panels/elements/StylePropertyTreeElement.ts +1 -1
  64. package/front_end/panels/elements/StylesSidebarPane.ts +4 -6
  65. package/front_end/panels/elements/TopLayerContainer.ts +1 -1
  66. package/front_end/panels/elements/elements.ts +4 -3
  67. package/front_end/panels/elements/elementsTreeOutline.css +4 -0
  68. package/front_end/panels/event_listeners/EventListenersView.ts +9 -4
  69. package/front_end/panels/issues/IssuesPane.ts +34 -22
  70. package/front_end/panels/network/RequestPayloadView.ts +17 -15
  71. package/front_end/panels/profiler/HeapDetachedElementsDataGrid.ts +1 -1
  72. package/front_end/panels/profiler/HeapProfileView.ts +8 -6
  73. package/front_end/panels/profiler/HeapSnapshotDataGrids.ts +8 -1
  74. package/front_end/panels/profiler/HeapSnapshotView.ts +6 -4
  75. package/front_end/panels/recorder/RecordingView.ts +32 -53
  76. package/front_end/panels/recorder/controlButton.css +6 -0
  77. package/front_end/panels/recorder/recordingView.css +11 -2
  78. package/front_end/panels/security/SecurityPanel.ts +87 -99
  79. package/front_end/panels/sensors/SensorsView.ts +87 -0
  80. package/front_end/panels/sensors/sensors-meta.ts +69 -1
  81. package/front_end/panels/settings/keybindsSettingsTab.css +1 -1
  82. package/front_end/panels/sources/DebuggerPlugin.ts +136 -55
  83. package/front_end/panels/sources/ScopeChainSidebarPane.ts +17 -15
  84. package/front_end/panels/timeline/components/IgnoreListSetting.ts +0 -2
  85. package/front_end/panels/timeline/components/LiveMetricsView.ts +59 -64
  86. package/front_end/panels/timeline/components/liveMetricsView.css +1 -0
  87. package/front_end/third_party/chromium/README.chromium +1 -1
  88. package/front_end/third_party/lighthouse/README.chromium +2 -2
  89. package/front_end/third_party/lighthouse/lighthouse-dt-bundle.js +1580 -1524
  90. package/front_end/third_party/lighthouse/locales/ar-XB.json +111 -72
  91. package/front_end/third_party/lighthouse/locales/ar.json +113 -74
  92. package/front_end/third_party/lighthouse/locales/bg.json +112 -73
  93. package/front_end/third_party/lighthouse/locales/ca.json +112 -73
  94. package/front_end/third_party/lighthouse/locales/cs.json +112 -73
  95. package/front_end/third_party/lighthouse/locales/da.json +112 -73
  96. package/front_end/third_party/lighthouse/locales/de.json +112 -73
  97. package/front_end/third_party/lighthouse/locales/el.json +112 -73
  98. package/front_end/third_party/lighthouse/locales/en-GB.json +111 -72
  99. package/front_end/third_party/lighthouse/locales/en-US.json +102 -66
  100. package/front_end/third_party/lighthouse/locales/en-XA.json +111 -72
  101. package/front_end/third_party/lighthouse/locales/en-XL.json +102 -66
  102. package/front_end/third_party/lighthouse/locales/es-419.json +112 -73
  103. package/front_end/third_party/lighthouse/locales/es.json +112 -73
  104. package/front_end/third_party/lighthouse/locales/fi.json +112 -73
  105. package/front_end/third_party/lighthouse/locales/fil.json +112 -73
  106. package/front_end/third_party/lighthouse/locales/fr.json +112 -73
  107. package/front_end/third_party/lighthouse/locales/he.json +112 -73
  108. package/front_end/third_party/lighthouse/locales/hi.json +112 -73
  109. package/front_end/third_party/lighthouse/locales/hr.json +112 -73
  110. package/front_end/third_party/lighthouse/locales/hu.json +112 -73
  111. package/front_end/third_party/lighthouse/locales/id.json +112 -73
  112. package/front_end/third_party/lighthouse/locales/it.json +113 -74
  113. package/front_end/third_party/lighthouse/locales/ja.json +112 -73
  114. package/front_end/third_party/lighthouse/locales/ko.json +112 -73
  115. package/front_end/third_party/lighthouse/locales/lt.json +112 -73
  116. package/front_end/third_party/lighthouse/locales/lv.json +112 -73
  117. package/front_end/third_party/lighthouse/locales/nl.json +112 -73
  118. package/front_end/third_party/lighthouse/locales/no.json +112 -73
  119. package/front_end/third_party/lighthouse/locales/pl.json +112 -73
  120. package/front_end/third_party/lighthouse/locales/pt-PT.json +112 -73
  121. package/front_end/third_party/lighthouse/locales/pt.json +112 -73
  122. package/front_end/third_party/lighthouse/locales/ro.json +112 -73
  123. package/front_end/third_party/lighthouse/locales/ru.json +112 -73
  124. package/front_end/third_party/lighthouse/locales/sk.json +112 -73
  125. package/front_end/third_party/lighthouse/locales/sl.json +112 -73
  126. package/front_end/third_party/lighthouse/locales/sr-Latn.json +112 -73
  127. package/front_end/third_party/lighthouse/locales/sr.json +112 -73
  128. package/front_end/third_party/lighthouse/locales/sv.json +124 -85
  129. package/front_end/third_party/lighthouse/locales/ta.json +112 -73
  130. package/front_end/third_party/lighthouse/locales/te.json +112 -73
  131. package/front_end/third_party/lighthouse/locales/th.json +112 -73
  132. package/front_end/third_party/lighthouse/locales/tr.json +113 -74
  133. package/front_end/third_party/lighthouse/locales/uk.json +112 -73
  134. package/front_end/third_party/lighthouse/locales/vi.json +112 -73
  135. package/front_end/third_party/lighthouse/locales/zh-HK.json +112 -73
  136. package/front_end/third_party/lighthouse/locales/zh-TW.json +113 -74
  137. package/front_end/third_party/lighthouse/locales/zh.json +113 -74
  138. package/front_end/third_party/lighthouse/report/bundle.d.ts +4 -4
  139. package/front_end/third_party/lighthouse/report/bundle.js +10 -3
  140. package/front_end/third_party/lighthouse/report-assets/report-generator.mjs +2 -2
  141. package/front_end/third_party/lit/lit.ts +3 -0
  142. package/front_end/third_party/lit/package.json +3 -0
  143. package/front_end/third_party/lit/rebuild.sh +3 -2
  144. package/front_end/ui/comments/CommentAnchorResolver.ts +8 -2
  145. package/front_end/ui/comments/CommentOverlayManager.ts +2 -1
  146. package/front_end/ui/legacy/InspectorView.ts +4 -3
  147. package/front_end/ui/legacy/TabbedPane.ts +121 -80
  148. package/front_end/ui/legacy/Treeoutline.ts +37 -7
  149. package/front_end/ui/legacy/UIUtils.ts +7 -0
  150. package/front_end/ui/legacy/components/data_grid/DataGridElement.ts +1 -1
  151. package/front_end/ui/legacy/components/object_ui/ObjectPropertiesSection.ts +135 -146
  152. package/front_end/ui/legacy/treeoutline.css +2 -2
  153. package/front_end/ui/lit/lit.ts +1 -1
  154. package/front_end/ui/visual_logging/KnownContextValues.ts +11 -0
  155. package/inspector_overlay/highlight_anchor.ts +197 -0
  156. package/inspector_overlay/tool_highlight.ts +8 -0
  157. package/package.json +1 -1
@@ -9,4 +9,7 @@ import * as Directive from './lib/directive.js';
9
9
  import * as Decorators from './lib/decorators.js';
10
10
  import * as StaticHtml from './lib/static-html.js';
11
11
 
12
+ // Required for lit-analyzer
13
+ export type {DirectiveResult} from './lib/directive.js';
14
+
12
15
  export {AsyncDirective, Directives, Directive, Decorators, StaticHtml};
@@ -6,6 +6,9 @@
6
6
  "lit": "3.3.3"
7
7
  },
8
8
  "devDependencies": {
9
+ "@rollup/plugin-node-resolve": "16.0.3",
10
+ "@rollup/plugin-terser": "1.0.0",
11
+ "rollup": "4.62.4",
9
12
  "rollup-plugin-dts": "6.4.1"
10
13
  }
11
14
  }
@@ -1,6 +1,7 @@
1
+ set -e
1
2
  npm install
2
- ../../../node_modules/.bin/tsc --ignoreConfig -d -t esnext -m esnext --moduleResolution bundler src/*
3
- ../../../node_modules/rollup/dist/bin/rollup -c
3
+ vpython3 ../../../third_party/typescript/typescript.py --ignoreConfig --skipLibCheck -d -t esnext -m esnext --moduleResolution bundler src/*
4
+ node_modules/rollup/dist/bin/rollup -c
4
5
  rm -rf node_modules src/*.js src/*.d.ts
5
6
  # Because there's a bug in clang causing it to reformat import lists even where formatting is disabled, run it right away
6
7
  git cl format --js
@@ -29,6 +29,12 @@ const DISALLOWED_COMMENT_TARGETS = new Set<number>([
29
29
  VisualLogging.VisualElements.Toolbar,
30
30
  ]);
31
31
 
32
+ /**
33
+ * The comment thread UI itself is never a valid comment target: anything inside it (including the
34
+ * DOM node link in its header) must stay inert while comment mode is on.
35
+ */
36
+ export const COMMENT_THREAD_UI_SELECTOR = '.comment-thread-widget';
37
+
32
38
  /**
33
39
  * Finds the closest ancestor (or the element itself) matching a CSS selector,
34
40
  * traversing across Shadow DOM boundaries (shadow root boundaries to shadow hosts).
@@ -268,7 +274,7 @@ function resolveCodeMirrorLineInfo(element: Element): CodeMirrorLineInfo|null {
268
274
  * Resolves an arbitrary clicked or targeted DOM element to its appropriate semantic comment anchor element.
269
275
  *
270
276
  * Traversal hierarchy:
271
- * 1. Checks if the element is part of a tab title (returns null if so).
277
+ * 1. Checks if the element is part of a tab title or of the comment thread UI (returns null if so).
272
278
  * 2. Escalates CodeMirror line/gutter elements to .cm-editor (only if the clicked line is non-empty).
273
279
  * 3. Checks for domain IDs (`data-network-request-id` or `data-backend-node-id`) across shadow boundaries,
274
280
  * returning the owning domain element.
@@ -281,7 +287,7 @@ function resolveCodeMirrorLineInfo(element: Element): CodeMirrorLineInfo|null {
281
287
  */
282
288
  export function resolveCommentAnchorElement(
283
289
  element: Element, options?: {clientX: number, clientY: number, forHover?: boolean}): Element|null {
284
- if (isTabTitle(element)) {
290
+ if (isTabTitle(element) || closestAcrossShadow(element, COMMENT_THREAD_UI_SELECTOR)) {
285
291
  return null;
286
292
  }
287
293
  const customResolver = getCustomAnchorResolverForElement(element);
@@ -7,6 +7,7 @@ import * as CommentManager from '../../models/comment_manager/comment_manager.js
7
7
 
8
8
  import {
9
9
  closestAcrossShadow,
10
+ COMMENT_THREAD_UI_SELECTOR,
10
11
  type CommentAnchorSignature,
11
12
  type CommentThread,
12
13
  computeVisibleRect,
@@ -238,7 +239,7 @@ export class CommentOverlayManager extends Common.ObjectWrapper.ObjectWrapper<Ev
238
239
  }
239
240
 
240
241
  handleElementClick(element: Element, options?: {clientX: number, clientY: number}): boolean {
241
- if (!this.isCommentMode() || closestAcrossShadow(element, '.comment-thread-widget')) {
242
+ if (!this.isCommentMode() || closestAcrossShadow(element, COMMENT_THREAD_UI_SELECTOR)) {
242
243
  return false;
243
244
  }
244
245
  this.clearDraftThreads();
@@ -853,15 +853,16 @@ export class InspectorView extends VBox implements ViewLocationResolver {
853
853
  }
854
854
  }
855
855
 
856
- renderStatusBar(): void {
856
+ renderStatusBar(visible = true): void {
857
857
  if (!this.#statusBarContainer) {
858
858
  this.#statusBarContainer = document.createElement('div');
859
859
  this.#statusBarContainer.style.display = 'contents';
860
860
  this.element.appendChild(this.#statusBarContainer);
861
861
  }
862
862
  // eslint-disable-next-line @devtools/no-lit-render-outside-of-view
863
- Lit.render(html`<devtools-widget class="flex-none" ${widget(StatusBarWidget)}></devtools-widget>`,
864
- this.#statusBarContainer);
863
+ Lit.render(
864
+ visible ? html`<devtools-widget class="flex-none" ${widget(StatusBarWidget)}></devtools-widget>` : Lit.nothing,
865
+ this.#statusBarContainer);
865
866
  }
866
867
 
867
868
  private createInfoBarDiv(): void {
@@ -77,6 +77,8 @@ export interface TabInfo {
77
77
  jslogContext?: string;
78
78
  enabled?: boolean;
79
79
  selected?: boolean;
80
+ icon?: Element|null;
81
+ suffix?: Element|null;
80
82
  }
81
83
 
82
84
  const TabbedPaneBase: Common.ObjectWrapper.EventMixin<EventTypes, typeof VBox> = Common.ObjectWrapper.eventMixin(
@@ -120,6 +122,8 @@ export class TabbedPane extends TabbedPaneBase {
120
122
  this.registerRequiredCSS(tabbedPaneStyles);
121
123
  this.element.classList.add('tabbed-pane');
122
124
  this.contentElement.classList.add('tabbed-pane-shadow');
125
+ this.element.classList.add('flex-auto', 'vbox');
126
+ this.contentElement.classList.add('flex-auto', 'vbox');
123
127
  this.contentElement.tabIndex = -1;
124
128
  this.setDefaultFocusedElement(this.contentElement);
125
129
  this.#headerElement = this.contentElement.createChild('div', 'tabbed-pane-header');
@@ -565,47 +569,6 @@ export class TabbedPane extends TabbedPaneBase {
565
569
  }));
566
570
  }
567
571
 
568
- set tabs(tabs: TabInfo[]) {
569
- const newIds = new Set(tabs.map(tab => tab.id));
570
- for (const id of this.tabsById.keys()) {
571
- if (!newIds.has(id)) {
572
- this.#closeTab(id);
573
- }
574
- }
575
- let index = 0;
576
- for (const tab of tabs) {
577
- const existingTab = this.tabsById.get(tab.id);
578
- if (existingTab) {
579
- this.changeTabView(tab.id, tab.view);
580
- this.changeTabTitle(tab.id, tab.title, tab.tabTooltip);
581
- if (tab.jslogContext !== undefined) {
582
- existingTab.jslogContext = tab.jslogContext;
583
- }
584
- if (tab.isCloseable !== undefined) {
585
- existingTab.closeable = tab.isCloseable;
586
- }
587
- if (tab.previewFeature !== undefined) {
588
- existingTab.previewFeature = tab.previewFeature;
589
- }
590
- const currentIndex = this.#tabs.indexOf(existingTab);
591
- if (currentIndex !== index) {
592
- this.insertBefore(existingTab, index);
593
- }
594
- } else {
595
- this.appendTab(
596
- tab.id, tab.title, tab.view, tab.tabTooltip, /* userGesture=*/ false, tab.isCloseable, tab.previewFeature,
597
- index, tab.jslogContext);
598
- }
599
- if (tab.enabled !== undefined) {
600
- this.setTabEnabled(tab.id, tab.enabled);
601
- }
602
- if (tab.selected) {
603
- this.selectTab(tab.id);
604
- }
605
- ++index;
606
- }
607
- }
608
-
609
572
  override onResize(): void {
610
573
  if (this.currentDevicePixelRatio !== window.devicePixelRatio) {
611
574
  // Force recalculation of all tab widths on a DPI change
@@ -1694,42 +1657,72 @@ export class TabbedPaneElement extends WidgetElement<TabbedPane> {
1694
1657
  this.#automaticReorder = automatic;
1695
1658
  this.getWidget()?.setAllowTabReorder(this.#allowTabReorder, this.#automaticReorder);
1696
1659
  }
1660
+
1661
+ get tabs(): TabInfo[] {
1662
+ const widget = Widget.getOrCreateWidget(this) as TabbedPane;
1663
+ if (widget) {
1664
+ this.#updateTabs(widget);
1665
+ return widget.tabs;
1666
+ }
1667
+ return [];
1668
+ }
1669
+
1670
+ #delegate?: TabbedPaneTabDelegate;
1671
+ set tabDelegate(delegate: TabbedPaneTabDelegate) {
1672
+ this.#delegate = delegate;
1673
+ this.getWidget()?.setTabDelegate(delegate);
1674
+ }
1675
+
1676
+ #placeholderElement?: Element;
1677
+ #managedTabIds = new Set<string>();
1678
+ set placeholder(element: Element) {
1679
+ this.#placeholderElement = element;
1680
+ this.getWidget()?.setPlaceholderElement(element);
1681
+ }
1682
+
1697
1683
  readonly #tabObserver = new MutationObserver(() => this.#updateTabs());
1698
1684
 
1699
1685
  constructor() {
1700
1686
  super();
1701
1687
 
1702
- registerWidgetConfig(this, widgetConfig(element => {
1703
- const widget = new TabbedPane(element as TabbedPaneElement);
1704
- widget.setCloseableTabs(this.#closeableTabs);
1705
- widget.setAllowTabReorder(this.#allowTabReorder, this.#automaticReorder);
1706
- const slot = widget.contentElement.querySelector('slot:not([name])');
1707
- if (slot) {
1708
- slot.addEventListener('slotchange', () => this.#syncTabs());
1709
- }
1710
- widget.addEventListener(Events.TabSelected, () => {
1711
- const slot =
1712
- widget.contentElement.querySelector('slot:not([name])') as HTMLSlotElement | null;
1713
- const nodes = slot ? slot.assignedElements() : [];
1714
- for (const child of nodes) {
1715
- if (child.id === widget.selectedTabId) {
1716
- child.setAttribute('selected', '');
1717
- } else {
1718
- child.removeAttribute('selected');
1719
- }
1720
- }
1721
- this.dispatchEvent(new CustomEvent('select', {detail: {tabId: widget.selectedTabId}}));
1722
- });
1723
- widget.addEventListener(Events.TabClosed, event => {
1724
- this.dispatchEvent(new CustomEvent('close', {detail: {tabId: event.data.tabId}}));
1725
- });
1726
- widget.addEventListener(Events.TabOrderChanged, event => {
1727
- this.dispatchEvent(new CustomEvent(
1728
- 'taborderchanged', {detail: {tabId: event.data.tabId, tabIds: widget.tabIds()}}));
1729
- });
1730
- this.#syncTabs(widget);
1731
- return widget;
1732
- }));
1688
+ registerWidgetConfig(
1689
+ this, widgetConfig(element => {
1690
+ const widget = new TabbedPane(element as TabbedPaneElement);
1691
+ widget.setCloseableTabs(this.#closeableTabs);
1692
+ widget.setAllowTabReorder(this.#allowTabReorder, this.#automaticReorder);
1693
+ if (this.#delegate) {
1694
+ widget.setTabDelegate(this.#delegate);
1695
+ }
1696
+ const slot = widget.contentElement.querySelector('slot:not([name])');
1697
+ if (slot) {
1698
+ slot.addEventListener('slotchange', () => this.#syncTabs());
1699
+ }
1700
+ widget.addEventListener(Events.TabSelected, event => {
1701
+ const nodes = this.#getTabNodes(widget);
1702
+ for (const child of nodes) {
1703
+ if (child.id === widget.selectedTabId) {
1704
+ child.setAttribute('selected', '');
1705
+ } else {
1706
+ child.removeAttribute('selected');
1707
+ }
1708
+ }
1709
+ this.dispatchEvent(new CustomEvent(
1710
+ 'select', {detail: {tabId: widget.selectedTabId, isUserGesture: event.data?.isUserGesture}}));
1711
+ });
1712
+ widget.addEventListener(Events.TabClosed, event => {
1713
+ this.dispatchEvent(
1714
+ new CustomEvent('close', {detail: {tabId: event.data.tabId, isUserGesture: event.data.isUserGesture}}));
1715
+ });
1716
+ widget.addEventListener(Events.TabOrderChanged, event => {
1717
+ this.dispatchEvent(
1718
+ new CustomEvent('taborderchanged', {detail: {tabId: event.data.tabId, tabIds: widget.tabIds()}}));
1719
+ });
1720
+ if (this.#placeholderElement) {
1721
+ widget.setPlaceholderElement(this.#placeholderElement);
1722
+ }
1723
+ this.#syncTabs(widget);
1724
+ return widget;
1725
+ }));
1733
1726
  }
1734
1727
 
1735
1728
  override disconnectedCallback(): void {
@@ -1747,8 +1740,7 @@ export class TabbedPaneElement extends WidgetElement<TabbedPane> {
1747
1740
 
1748
1741
  #updateObserver(widget: TabbedPane): void {
1749
1742
  this.#tabObserver.disconnect();
1750
- const slot = widget.contentElement.querySelector('slot:not([name])') as HTMLSlotElement | null;
1751
- const nodes = slot ? slot.assignedElements() : [];
1743
+ const nodes = this.#getTabNodes(widget);
1752
1744
  for (const child of nodes) {
1753
1745
  this.#tabObserver.observe(child, {
1754
1746
  attributes: true,
@@ -1757,13 +1749,18 @@ export class TabbedPaneElement extends WidgetElement<TabbedPane> {
1757
1749
  }
1758
1750
  }
1759
1751
 
1752
+ #getTabNodes(widget: TabbedPane): Element[] {
1753
+ return Array.from(widget.element.children)
1754
+ .filter(c => c.id !== '' && !c.hasAttribute('slot') && !c.classList.contains('tabbed-pane-header') &&
1755
+ !c.classList.contains('tabbed-pane-content'));
1756
+ }
1757
+
1760
1758
  #updateTabs(widget = this.getWidget()): void {
1761
1759
  if (!widget) {
1762
1760
  return;
1763
1761
  }
1764
1762
  const tabs: TabInfo[] = [];
1765
- const slot = widget.contentElement.querySelector('slot:not([name])') as HTMLSlotElement | null;
1766
- const nodes = slot ? slot.assignedElements() : [];
1763
+ const nodes = this.#getTabNodes(widget);
1767
1764
  for (const child of nodes) {
1768
1765
  const id = child.id;
1769
1766
  const title = child.getAttribute('title') || '';
@@ -1772,13 +1769,12 @@ export class TabbedPaneElement extends WidgetElement<TabbedPane> {
1772
1769
  const enabled = !child.hasAttribute('disabled');
1773
1770
  const isCloseable =
1774
1771
  child.hasAttribute('closeable') ? true : (child.hasAttribute('uncloseable') ? false : undefined);
1772
+ const icon = child.querySelector('[slot="icon"]') ?? undefined;
1773
+ const suffix = child.querySelector('[slot="suffix"]') ?? undefined;
1775
1774
  const view = Widget.getOrCreateWidget(child as HTMLElement);
1776
1775
  view.setHideOnDetach();
1777
1776
  if (widget.selectedTabId !== id) {
1778
- view.hideWidget();
1779
1777
  child.classList.add('hidden');
1780
- } else {
1781
- view.showWidget();
1782
1778
  }
1783
1779
  tabs.push({
1784
1780
  id,
@@ -1788,10 +1784,55 @@ export class TabbedPaneElement extends WidgetElement<TabbedPane> {
1788
1784
  selected,
1789
1785
  enabled,
1790
1786
  isCloseable,
1787
+ icon,
1788
+ suffix,
1791
1789
  });
1792
1790
  }
1793
1791
 
1794
- widget.tabs = tabs;
1792
+ const newIds = new Set(tabs.map(tab => tab.id));
1793
+ for (const id of this.#managedTabIds) {
1794
+ if (!newIds.has(id)) {
1795
+ widget.closeTab(id);
1796
+ }
1797
+ }
1798
+ this.#managedTabIds = newIds;
1799
+ let index = 0;
1800
+ for (const tab of tabs) {
1801
+ const existingTab = widget.tabsById.get(tab.id);
1802
+ if (existingTab) {
1803
+ widget.changeTabView(tab.id, tab.view);
1804
+ widget.changeTabTitle(tab.id, tab.title, tab.tabTooltip);
1805
+ if (tab.jslogContext !== undefined) {
1806
+ existingTab.jslogContext = tab.jslogContext;
1807
+ }
1808
+ if (tab.isCloseable !== undefined) {
1809
+ existingTab.closeable = tab.isCloseable;
1810
+ }
1811
+ if (tab.previewFeature !== undefined) {
1812
+ existingTab.previewFeature = tab.previewFeature;
1813
+ }
1814
+ const currentIndex = widget.tabIndex(tab.id);
1815
+ if (currentIndex !== index) {
1816
+ widget.insertBefore(existingTab, index);
1817
+ }
1818
+ } else {
1819
+ widget.appendTab(tab.id, tab.title, tab.view, tab.tabTooltip, /* userGesture=*/ false, tab.isCloseable,
1820
+ tab.previewFeature, index, tab.jslogContext);
1821
+ }
1822
+ if (tab.icon !== undefined) {
1823
+ widget.setTabIcon(tab.id, tab.icon as unknown as Icon);
1824
+ }
1825
+ if (tab.suffix !== undefined) {
1826
+ widget.setSuffixElement(tab.id, tab.suffix as unknown as HTMLElement);
1827
+ }
1828
+ if (tab.enabled !== undefined) {
1829
+ widget.setTabEnabled(tab.id, tab.enabled);
1830
+ }
1831
+ if (tab.selected) {
1832
+ widget.selectTab(tab.id);
1833
+ }
1834
+ ++index;
1835
+ }
1795
1836
  }
1796
1837
  }
1797
1838
 
@@ -60,6 +60,7 @@ import {
60
60
  HTMLElementWithLightDOMTemplate,
61
61
  isEditing,
62
62
  } from './UIUtils.js';
63
+ import {Widget} from './Widget.js';
63
64
 
64
65
  const UIStrings = {
65
66
  /**
@@ -1304,9 +1305,9 @@ export class TreeElement {
1304
1305
  }
1305
1306
  }
1306
1307
 
1307
- revealAndSelect(omitFocus?: boolean): void {
1308
+ revealAndSelect(omitFocus?: boolean, selectedByUser?: boolean): void {
1308
1309
  this.reveal(true);
1309
- this.select(omitFocus);
1310
+ this.select(omitFocus, selectedByUser ?? false);
1310
1311
  }
1311
1312
 
1312
1313
  deselect(): void {
@@ -1653,11 +1654,18 @@ class TreeViewTreeElement extends TreeElement {
1653
1654
  }
1654
1655
  this.#refreshScheduled = true;
1655
1656
  queueMicrotask(() => {
1656
- this.#refreshScheduled = false;
1657
- this.refresh();
1657
+ if (this.#refreshScheduled) {
1658
+ this.refresh();
1659
+ }
1658
1660
  });
1659
1661
  }
1660
1662
 
1663
+ flushPendingRefreshForTesting(): void {
1664
+ if (this.#refreshScheduled) {
1665
+ this.refresh();
1666
+ }
1667
+ }
1668
+
1661
1669
  updateAttributes(): void {
1662
1670
  const expandable = Boolean(this.configElement.querySelector(':scope > ul[role="group"]'));
1663
1671
  this.setExpandable(expandable);
@@ -1668,7 +1676,8 @@ class TreeViewTreeElement extends TreeElement {
1668
1676
  this.#clonedClasses.clear();
1669
1677
  for (let i = 0; i < this.configElement.attributes.length; ++i) {
1670
1678
  const attribute = this.configElement.attributes.item(i);
1671
- if (attribute && attribute.name !== 'role' && TreeViewTreeElement.CLONED_ATTRIBUTES.has(attribute.name)) {
1679
+ if (attribute && attribute.name !== 'role' &&
1680
+ (TreeViewTreeElement.CLONED_ATTRIBUTES.has(attribute.name) || attribute.name.startsWith('data-'))) {
1672
1681
  this.listItemElement.setAttribute(attribute.name, attribute.value);
1673
1682
  this.#clonedAttributes.add(attribute.name);
1674
1683
  }
@@ -1684,6 +1693,7 @@ class TreeViewTreeElement extends TreeElement {
1684
1693
  }
1685
1694
 
1686
1695
  refresh(): void {
1696
+ this.#refreshScheduled = false;
1687
1697
  const hadFocus = this.listItemElement.hasFocus();
1688
1698
  this.titleElement.textContent = '';
1689
1699
  this.updateAttributes();
@@ -1726,7 +1736,7 @@ class TreeViewTreeElement extends TreeElement {
1726
1736
  return super.onenter();
1727
1737
  }
1728
1738
 
1729
- remove(): void {
1739
+ removeFromTree(): void {
1730
1740
  removeNode(this,
1731
1741
  Boolean(this.parent &&
1732
1742
  (this.parent as TreeViewTreeElement).configElement?.querySelector(':scope > ul[role="group"]')));
@@ -1892,6 +1902,26 @@ export class TreeViewElement extends HTMLElementWithLightDOMTemplate {
1892
1902
  return this.#treeOutline;
1893
1903
  }
1894
1904
 
1905
+ flushPendingUpdatesForTesting(): void {
1906
+ this.flushPendingMutationsForTesting();
1907
+ const stack: TreeElement[] = [...this.#treeOutline.rootElement().children()];
1908
+ while (stack.length > 0) {
1909
+ const item = stack.pop();
1910
+ if (!item) {
1911
+ continue;
1912
+ }
1913
+ if (item instanceof TreeViewTreeElement) {
1914
+ item.flushPendingRefreshForTesting();
1915
+ }
1916
+ if (item.children()) {
1917
+ stack.push(...item.children());
1918
+ }
1919
+ }
1920
+ for (const widgetEl of this.#treeOutline.shadowRoot.querySelectorAll('devtools-widget')) {
1921
+ void Widget.get(widgetEl)?.performUpdate();
1922
+ }
1923
+ }
1924
+
1895
1925
  override focus(): void {
1896
1926
  if (!this.#treeOutline.selectedTreeElement && this.#treeOutline.firstChild()) {
1897
1927
  this.#treeOutline.firstChild()?.select(/* omitFocus */ true, /* selectedByUser */ false);
@@ -2030,7 +2060,7 @@ export class TreeViewElement extends HTMLElementWithLightDOMTemplate {
2030
2060
  protected override removeNodes(nodes: NodeList|Node[]): void {
2031
2061
  for (const node of getTreeNodes(nodes)) {
2032
2062
  if (node instanceof HTMLLIElement) {
2033
- TreeViewTreeElement.get(node)?.remove();
2063
+ TreeViewTreeElement.get(node)?.removeFromTree();
2034
2064
  } else if (node.treeElement) {
2035
2065
  removeNode(node.treeElement,
2036
2066
  Boolean(node.treeElement.parent &&
@@ -2196,6 +2196,13 @@ export class HTMLElementWithLightDOMTemplate extends HTMLElement {
2196
2196
  render(template, this.#contentTemplate.content);
2197
2197
  }
2198
2198
 
2199
+ flushPendingMutationsForTesting(): void {
2200
+ const records = this.#mutationObserver.takeRecords();
2201
+ if (records.length > 0) {
2202
+ this.#onChange(records);
2203
+ }
2204
+ }
2205
+
2199
2206
  #onChange(mutationList: MutationRecord[]): void {
2200
2207
  this.onChange(mutationList);
2201
2208
  const addedNodes = new Set<Node>();
@@ -646,7 +646,7 @@ function nodeMixin<TBase extends Constructor<DataGridNode<any>>>(base: TBase) {
646
646
 
647
647
  override createElement(): HTMLElement {
648
648
  const element = super.createElement();
649
- element.addEventListener('click', this.#onRowMouseEvent.bind(this));
649
+ element.addEventListener('click', this.#onRowMouseEvent.bind(this), {capture: true});
650
650
  element.addEventListener('mouseenter', this.#onRowMouseEvent.bind(this));
651
651
  element.addEventListener('mouseleave', this.#onRowMouseEvent.bind(this));
652
652
  if (this.#configElement.hasAttribute('style')) {