chrome-devtools-frontend 1.0.1654411 → 1.0.1656897

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 (204) hide show
  1. package/.agents/skills/ui-eng-vision-local-lit-renderer/SKILL.md +116 -0
  2. package/.agents/skills/ui-eng-vision-logic-consolidator/SKILL.md +82 -0
  3. package/.agents/skills/ui-eng-vision-orchestrator/SKILL.md +123 -0
  4. package/.agents/skills/ui-eng-vision-test-scaffolder/SKILL.md +125 -0
  5. package/.agents/skills/ui-eng-vision-widget-promoter/SKILL.md +75 -0
  6. package/AUTHORS +1 -0
  7. package/config/owner/COMMON_OWNERS +0 -4
  8. package/docs/playbook.md +1 -1
  9. package/front_end/Tests.js +1 -0
  10. package/front_end/core/common/Settings.ts +47 -30
  11. package/front_end/core/sdk/CPUThrottlingManager.ts +10 -159
  12. package/front_end/core/sdk/ConsoleModel.ts +13 -11
  13. package/front_end/core/sdk/DOMDebuggerModel.ts +11 -5
  14. package/front_end/core/sdk/DOMModel.ts +7 -5
  15. package/front_end/core/sdk/EventBreakpointsModel.ts +9 -5
  16. package/front_end/core/sdk/IsolateManager.ts +6 -6
  17. package/front_end/core/sdk/ResourceTreeModel.ts +5 -3
  18. package/front_end/core/sdk/SourceMapCache.ts +2 -4
  19. package/front_end/core/sdk/SourceMapManager.ts +8 -7
  20. package/front_end/core/sdk/TargetManager.ts +17 -0
  21. package/front_end/entrypoints/devtools_app/devtools_app.ts +0 -1
  22. package/front_end/entrypoints/main/MainImpl.ts +12 -36
  23. package/front_end/entrypoints/main/SimpleApp.ts +0 -13
  24. package/front_end/entrypoints/main/main-meta.ts +1 -1
  25. package/front_end/foundation/Universe.ts +110 -3
  26. package/front_end/generated/InspectorBackendCommands.ts +5 -1
  27. package/front_end/generated/SupportedCSSProperties.js +2 -6
  28. package/front_end/generated/protocol-mapping.d.ts +8 -0
  29. package/front_end/generated/protocol-proxy-api.d.ts +15 -0
  30. package/front_end/generated/protocol.ts +36 -1
  31. package/front_end/models/ai_assistance/AiConversation.ts +0 -6
  32. package/front_end/models/ai_assistance/agents/NetworkAgent.snapshot.txt +1 -1
  33. package/front_end/models/ai_assistance/agents/PerformanceAgent.ts +2 -156
  34. package/front_end/models/ai_assistance/agents/StylingAgent.snapshot.txt +0 -24
  35. package/front_end/models/ai_assistance/agents/StylingAgent.ts +2 -358
  36. package/front_end/models/ai_assistance/ai_assistance.ts +0 -6
  37. package/front_end/models/ai_assistance/contexts/RequestContext.snapshot.txt +0 -1
  38. package/front_end/models/ai_assistance/data_formatters/NetworkRequestFormatter.ts +0 -2
  39. package/front_end/models/ai_assistance/data_formatters/PerformanceTraceFormatter.ts +1 -14
  40. package/front_end/models/autofill_manager/AutofillManager.ts +1 -2
  41. package/front_end/models/crux-manager/CrUXManager.ts +16 -11
  42. package/front_end/models/javascript_metadata/NativeFunctions.js +1 -1
  43. package/front_end/models/persistence/AutomaticFileSystemManager.ts +14 -13
  44. package/front_end/models/persistence/AutomaticFileSystemWorkspaceBinding.ts +12 -9
  45. package/front_end/models/persistence/IsolatedFileSystemManager.ts +18 -9
  46. package/front_end/models/project_settings/ProjectSettingsModel.ts +12 -11
  47. package/front_end/models/trace/handlers/ScreenshotsHandler.ts +14 -59
  48. package/front_end/panels/ai_assistance/AiAssistancePanel.ts +3 -29
  49. package/front_end/panels/application/DeviceBoundSessionsView.ts +15 -3
  50. package/front_end/panels/application/FrameDetailsView.ts +2 -1
  51. package/front_end/panels/application/ServiceWorkersView.ts +193 -161
  52. package/front_end/panels/application/serviceWorkersView.css +8 -0
  53. package/front_end/panels/common/CPUThrottlingOption.ts +142 -0
  54. package/front_end/panels/common/ThrottlingUtils.ts +9 -4
  55. package/front_end/panels/common/common.ts +1 -1
  56. package/front_end/panels/console/ConsoleViewMessage.ts +45 -0
  57. package/front_end/panels/elements/ElementsPanel.ts +35 -102
  58. package/front_end/panels/elements/ElementsTreeOutline.ts +0 -4
  59. package/front_end/panels/elements/elements-meta.ts +3 -2
  60. package/front_end/panels/emulation/DeviceModeToolbar.ts +46 -45
  61. package/front_end/panels/mobile_throttling/CalibrationController.ts +6 -5
  62. package/front_end/panels/mobile_throttling/MobileThrottlingSelector.ts +1 -1
  63. package/front_end/panels/mobile_throttling/ThrottlingManager.ts +40 -13
  64. package/front_end/panels/mobile_throttling/ThrottlingPresets.ts +13 -17
  65. package/front_end/panels/mobile_throttling/ThrottlingSettingsTab.ts +15 -13
  66. package/front_end/panels/network/NetworkLogView.ts +0 -9
  67. package/front_end/panels/network/NetworkPanel.ts +1 -63
  68. package/front_end/panels/network/RequestInitiatorView.ts +29 -4
  69. package/front_end/panels/network/components/HeaderSectionRow.css +9 -7
  70. package/front_end/panels/network/components/HeaderSectionRow.ts +20 -1
  71. package/front_end/panels/network/components/RequestHeaderSection.css +5 -3
  72. package/front_end/panels/network/components/RequestHeaderSection.ts +1 -2
  73. package/front_end/panels/network/components/RequestHeadersView.css +1 -1
  74. package/front_end/panels/security/SecurityPanel.ts +36 -23
  75. package/front_end/panels/settings/SettingsScreen.ts +18 -103
  76. package/front_end/panels/settings/settings-meta.ts +0 -24
  77. package/front_end/panels/timeline/TimelineController.ts +3 -2
  78. package/front_end/panels/timeline/TimelinePanel.ts +20 -3
  79. package/front_end/panels/timeline/components/CPUThrottlingSelector.ts +15 -14
  80. package/front_end/panels/timeline/timelineFlamechartPopover.css +2 -0
  81. package/front_end/panels/utils/utils.ts +25 -24
  82. package/front_end/third_party/chromium/README.chromium +1 -1
  83. package/front_end/third_party/puppeteer/README.chromium +2 -2
  84. package/front_end/third_party/puppeteer/package/lib/es5-iife/puppeteer-core-browser.d.ts +34 -55
  85. package/front_end/third_party/puppeteer/package/lib/es5-iife/puppeteer-core-browser.js +96 -83
  86. package/front_end/third_party/puppeteer/package/lib/puppeteer/api/Browser.d.ts +10 -1
  87. package/front_end/third_party/puppeteer/package/lib/puppeteer/api/Browser.d.ts.map +1 -1
  88. package/front_end/third_party/puppeteer/package/lib/puppeteer/api/Browser.js.map +1 -1
  89. package/front_end/third_party/puppeteer/package/lib/puppeteer/api/BrowserContext.d.ts.map +1 -1
  90. package/front_end/third_party/puppeteer/package/lib/puppeteer/api/BrowserContext.js +1 -1
  91. package/front_end/third_party/puppeteer/package/lib/puppeteer/api/BrowserContext.js.map +1 -1
  92. package/front_end/third_party/puppeteer/package/lib/puppeteer/api/CDPSession.d.ts.map +1 -1
  93. package/front_end/third_party/puppeteer/package/lib/puppeteer/api/CDPSession.js.map +1 -1
  94. package/front_end/third_party/puppeteer/package/lib/puppeteer/api/HTTPResponse.d.ts +3 -2
  95. package/front_end/third_party/puppeteer/package/lib/puppeteer/api/HTTPResponse.d.ts.map +1 -1
  96. package/front_end/third_party/puppeteer/package/lib/puppeteer/api/HTTPResponse.js.map +1 -1
  97. package/front_end/third_party/puppeteer/package/lib/puppeteer/api/Realm.d.ts.map +1 -1
  98. package/front_end/third_party/puppeteer/package/lib/puppeteer/api/Realm.js.map +1 -1
  99. package/front_end/third_party/puppeteer/package/lib/puppeteer/api/Target.d.ts +1 -1
  100. package/front_end/third_party/puppeteer/package/lib/puppeteer/api/Target.js +1 -1
  101. package/front_end/third_party/puppeteer/package/lib/puppeteer/api/locators/locators.d.ts.map +1 -1
  102. package/front_end/third_party/puppeteer/package/lib/puppeteer/api/locators/locators.js.map +1 -1
  103. package/front_end/third_party/puppeteer/package/lib/puppeteer/bidi/HTTPResponse.d.ts.map +1 -1
  104. package/front_end/third_party/puppeteer/package/lib/puppeteer/bidi/HTTPResponse.js +2 -1
  105. package/front_end/third_party/puppeteer/package/lib/puppeteer/bidi/HTTPResponse.js.map +1 -1
  106. package/front_end/third_party/puppeteer/package/lib/puppeteer/bidi/core/Realm.d.ts.map +1 -1
  107. package/front_end/third_party/puppeteer/package/lib/puppeteer/bidi/core/Realm.js.map +1 -1
  108. package/front_end/third_party/puppeteer/package/lib/puppeteer/cdp/Browser.d.ts +2 -2
  109. package/front_end/third_party/puppeteer/package/lib/puppeteer/cdp/Browser.d.ts.map +1 -1
  110. package/front_end/third_party/puppeteer/package/lib/puppeteer/cdp/Browser.js +5 -2
  111. package/front_end/third_party/puppeteer/package/lib/puppeteer/cdp/Browser.js.map +1 -1
  112. package/front_end/third_party/puppeteer/package/lib/puppeteer/cdp/Frame.js +1 -1
  113. package/front_end/third_party/puppeteer/package/lib/puppeteer/cdp/Frame.js.map +1 -1
  114. package/front_end/third_party/puppeteer/package/lib/puppeteer/cdp/FrameManager.d.ts.map +1 -1
  115. package/front_end/third_party/puppeteer/package/lib/puppeteer/cdp/FrameManager.js +23 -13
  116. package/front_end/third_party/puppeteer/package/lib/puppeteer/cdp/FrameManager.js.map +1 -1
  117. package/front_end/third_party/puppeteer/package/lib/puppeteer/cdp/HTTPResponse.d.ts.map +1 -1
  118. package/front_end/third_party/puppeteer/package/lib/puppeteer/cdp/HTTPResponse.js +4 -1
  119. package/front_end/third_party/puppeteer/package/lib/puppeteer/cdp/HTTPResponse.js.map +1 -1
  120. package/front_end/third_party/puppeteer/package/lib/puppeteer/cdp/Input.js +1 -1
  121. package/front_end/third_party/puppeteer/package/lib/puppeteer/cdp/LifecycleWatcher.d.ts.map +1 -1
  122. package/front_end/third_party/puppeteer/package/lib/puppeteer/cdp/LifecycleWatcher.js.map +1 -1
  123. package/front_end/third_party/puppeteer/package/lib/puppeteer/cdp/Target.d.ts.map +1 -1
  124. package/front_end/third_party/puppeteer/package/lib/puppeteer/cdp/Target.js.map +1 -1
  125. package/front_end/third_party/puppeteer/package/lib/puppeteer/cdp/WebMCP.d.ts +3 -41
  126. package/front_end/third_party/puppeteer/package/lib/puppeteer/cdp/WebMCP.d.ts.map +1 -1
  127. package/front_end/third_party/puppeteer/package/lib/puppeteer/cdp/WebMCP.js.map +1 -1
  128. package/front_end/third_party/puppeteer/package/lib/puppeteer/common/ConnectOptions.d.ts.map +1 -1
  129. package/front_end/third_party/puppeteer/package/lib/puppeteer/node/BrowserLauncher.d.ts.map +1 -1
  130. package/front_end/third_party/puppeteer/package/lib/puppeteer/node/BrowserLauncher.js +4 -2
  131. package/front_end/third_party/puppeteer/package/lib/puppeteer/node/BrowserLauncher.js.map +1 -1
  132. package/front_end/third_party/puppeteer/package/lib/puppeteer/node/LaunchOptions.d.ts +5 -0
  133. package/front_end/third_party/puppeteer/package/lib/puppeteer/node/LaunchOptions.d.ts.map +1 -1
  134. package/front_end/third_party/puppeteer/package/lib/puppeteer/revisions.d.ts +1 -1
  135. package/front_end/third_party/puppeteer/package/lib/puppeteer/revisions.js +1 -1
  136. package/front_end/third_party/puppeteer/package/lib/puppeteer/util/httpUtils.d.ts +1 -1
  137. package/front_end/third_party/puppeteer/package/lib/puppeteer/util/httpUtils.d.ts.map +1 -1
  138. package/front_end/third_party/puppeteer/package/lib/puppeteer/util/httpUtils.js +5 -5
  139. package/front_end/third_party/puppeteer/package/lib/puppeteer/util/httpUtils.js.map +1 -1
  140. package/front_end/third_party/puppeteer/package/lib/puppeteer/util/version.d.ts +1 -1
  141. package/front_end/third_party/puppeteer/package/lib/puppeteer/util/version.js +1 -1
  142. package/front_end/third_party/puppeteer/package/lib/types.d.ts +34 -55
  143. package/front_end/third_party/puppeteer/package/package.json +4 -4
  144. package/front_end/third_party/puppeteer/package/src/api/Browser.ts +13 -1
  145. package/front_end/third_party/puppeteer/package/src/api/BrowserContext.ts +2 -3
  146. package/front_end/third_party/puppeteer/package/src/api/CDPSession.ts +3 -1
  147. package/front_end/third_party/puppeteer/package/src/api/HTTPResponse.ts +3 -2
  148. package/front_end/third_party/puppeteer/package/src/api/Realm.ts +1 -2
  149. package/front_end/third_party/puppeteer/package/src/api/Target.ts +1 -1
  150. package/front_end/third_party/puppeteer/package/src/api/locators/locators.ts +1 -2
  151. package/front_end/third_party/puppeteer/package/src/bidi/HTTPResponse.ts +3 -1
  152. package/front_end/third_party/puppeteer/package/src/bidi/core/Realm.ts +1 -3
  153. package/front_end/third_party/puppeteer/package/src/cdp/Browser.ts +13 -3
  154. package/front_end/third_party/puppeteer/package/src/cdp/Frame.ts +1 -1
  155. package/front_end/third_party/puppeteer/package/src/cdp/FrameManager.ts +24 -13
  156. package/front_end/third_party/puppeteer/package/src/cdp/HTTPResponse.ts +4 -1
  157. package/front_end/third_party/puppeteer/package/src/cdp/Input.ts +1 -1
  158. package/front_end/third_party/puppeteer/package/src/cdp/LifecycleWatcher.ts +1 -4
  159. package/front_end/third_party/puppeteer/package/src/cdp/Target.ts +2 -4
  160. package/front_end/third_party/puppeteer/package/src/cdp/WebMCP.ts +7 -76
  161. package/front_end/third_party/puppeteer/package/src/common/ConnectOptions.ts +1 -4
  162. package/front_end/third_party/puppeteer/package/src/node/BrowserLauncher.ts +4 -1
  163. package/front_end/third_party/puppeteer/package/src/node/LaunchOptions.ts +5 -0
  164. package/front_end/third_party/puppeteer/package/src/revisions.ts +1 -1
  165. package/front_end/third_party/puppeteer/package/src/util/httpUtils.ts +5 -5
  166. package/front_end/third_party/puppeteer/package/src/util/version.ts +1 -1
  167. package/front_end/third_party/puppeteer/third_party/parsel/README.chromium +1 -1
  168. package/front_end/ui/components/dialogs/Dialog.ts +3 -0
  169. package/front_end/ui/components/markdown_view/MarkdownLinksMap.ts +0 -7
  170. package/front_end/ui/helpers/OpenInNewTab.ts +15 -7
  171. package/front_end/ui/legacy/TabbedPane.ts +1 -108
  172. package/front_end/ui/legacy/components/data_grid/DataGrid.ts +9 -0
  173. package/front_end/ui/legacy/components/data_grid/DataGridElement.ts +172 -123
  174. package/front_end/ui/legacy/components/data_grid/DataGridExporter.ts +89 -0
  175. package/front_end/ui/legacy/components/data_grid/dataGrid.css +10 -0
  176. package/front_end/ui/legacy/components/data_grid/data_grid.ts +3 -1
  177. package/front_end/ui/legacy/components/perf_ui/FlameChart.ts +89 -40
  178. package/front_end/ui/visual_logging/KnownContextValues.ts +2 -0
  179. package/package.json +1 -1
  180. package/front_end/Images/navigationControls.png +0 -0
  181. package/front_end/Images/navigationControls_2x.png +0 -0
  182. package/front_end/Images/popoverArrows.png +0 -0
  183. package/front_end/entrypoints/greendev_floaty/FloatyEntrypoint.ts +0 -746
  184. package/front_end/entrypoints/greendev_floaty/floaty.css +0 -301
  185. package/front_end/entrypoints/greendev_floaty/floaty.html +0 -38
  186. package/front_end/entrypoints/greendev_floaty/greendev_floaty.ts +0 -415
  187. package/front_end/models/ai_assistance/agents/GreenDevAgent.ts +0 -834
  188. package/front_end/models/ai_assistance/agents/GreenDevAgentAntigravityCliSocketClient.ts +0 -53
  189. package/front_end/models/ai_assistance/agents/GreenDevAgentGeminiCliSocketClient.ts +0 -117
  190. package/front_end/models/annotations/AnnotationRepository.ts +0 -238
  191. package/front_end/models/annotations/AnnotationType.ts +0 -10
  192. package/front_end/models/annotations/README.md +0 -7
  193. package/front_end/models/annotations/annotations.ts +0 -6
  194. package/front_end/models/greendev/Prototypes.ts +0 -68
  195. package/front_end/models/greendev/README.md +0 -5
  196. package/front_end/models/greendev/greendev.ts +0 -5
  197. package/front_end/panels/common/Annotation.ts +0 -184
  198. package/front_end/panels/common/AnnotationManager.ts +0 -208
  199. package/front_end/panels/common/annotation.css +0 -40
  200. package/front_end/panels/greendev/GreenDevPanel.css +0 -282
  201. package/front_end/panels/greendev/GreenDevPanel.ts +0 -364
  202. package/front_end/panels/greendev/GreenDevShared.ts +0 -13
  203. package/front_end/panels/greendev/greendev-meta.ts +0 -52
  204. package/front_end/panels/greendev/greendev.ts +0 -9
@@ -12,6 +12,8 @@ import dataGridStyles from './dataGrid.css.js';
12
12
  import {
13
13
  Align,
14
14
  type ColumnDescriptor,
15
+ DataGridImpl,
16
+ DataGridNode,
15
17
  DataType,
16
18
  Events as DataGridEvents,
17
19
  Order,
@@ -50,10 +52,15 @@ const DUMMY_COLUMN_ID = 'dummy'; // SortableDataGrid.create requires at least o
50
52
  * @attribute striped If true, the data grid will have striped rows.
51
53
  * @attribute displayName
52
54
  */
55
+ type DataGridElementNode = SortableNode|DynamicHeightNode;
56
+
57
+ const elementToNode = new WeakMap<Element, DataGridElementNode>();
58
+
53
59
  export class DataGridElement extends UI.UIUtils.HTMLElementWithLightDOMTemplate {
54
60
  static readonly observedAttributes = ['striped', 'name', 'inline', 'resize'];
55
61
 
56
- #dataGrid = SortableDataGrid.create([DUMMY_COLUMN_ID], [], '') as SortableDataGrid<DataGridElementNode>;
62
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
63
+ #dataGrid!: DataGridImpl<any>;
57
64
  #resizeObserver = new ResizeObserver(() => {
58
65
  if (!this.inline) {
59
66
  this.#dataGrid.onResize();
@@ -69,6 +76,18 @@ export class DataGridElement extends UI.UIUtils.HTMLElementWithLightDOMTemplate
69
76
  super();
70
77
  // TODO(dsv): Move this to the data_grid.css once all the data grid usage is migrated to this web component.
71
78
  this.style.display = 'flex';
79
+
80
+ const autoRowHeight = this.getAttribute('row-height') === 'auto';
81
+ if (autoRowHeight) {
82
+ this.#dataGrid = new DataGridImpl<DynamicHeightNode>({
83
+ displayName: this.getAttribute('name') ?? '',
84
+ columns: [],
85
+ });
86
+ this.#dataGrid.element.classList.add('auto-row-height');
87
+ } else {
88
+ this.#dataGrid = SortableDataGrid.create([DUMMY_COLUMN_ID], [], '') as SortableDataGrid<SortableNode>;
89
+ }
90
+
72
91
  this.#dataGrid.element.style.flex = 'auto';
73
92
 
74
93
  this.#shadowRoot = UI.UIUtils.createShadowRootWithCoreStyles(this, {delegatesFocus: true, cssFile: dataGridStyles});
@@ -77,8 +96,8 @@ export class DataGridElement extends UI.UIUtils.HTMLElementWithLightDOMTemplate
77
96
  this.#dataGrid.addEventListener(
78
97
  DataGridEvents.SELECTED_NODE,
79
98
  e => (e.data as DataGridElementNode).configElement.dispatchEvent(new CustomEvent('select')));
80
- this.#dataGrid.addEventListener(
81
- DataGridEvents.DESELECTED_NODE, () => this.dispatchEvent(new CustomEvent('deselect')));
99
+ this.#dataGrid.addEventListener(DataGridEvents.DESELECTED_NODE,
100
+ () => this.dispatchEvent(new CustomEvent('deselect')));
82
101
  this.#dataGrid.addEventListener(
83
102
  DataGridEvents.OPENED_NODE,
84
103
  e => (e.data as DataGridElementNode).configElement.dispatchEvent(new CustomEvent('open')));
@@ -169,8 +188,12 @@ export class DataGridElement extends UI.UIUtils.HTMLElementWithLightDOMTemplate
169
188
  }
170
189
 
171
190
  set filters(filters: TextUtils.TextUtils.ParsedFilter[]) {
172
- this.#dataGrid.setFilters(filters);
173
- this.#dataGrid.element.setAttribute('aria-rowcount', String(this.#dataGrid.getNumberOfRows()));
191
+ if (this.#dataGrid instanceof SortableDataGrid) {
192
+ this.#dataGrid.setFilters(filters);
193
+ this.#dataGrid.element.setAttribute('aria-rowcount', String(this.#dataGrid.getNumberOfRows()));
194
+ } else {
195
+ this.#dataGrid.element.setAttribute('aria-rowcount', String(this.#dataGrid.rootNode().children.length));
196
+ }
174
197
  }
175
198
 
176
199
  get columns(): ColumnDescriptor[] {
@@ -239,9 +262,7 @@ export class DataGridElement extends UI.UIUtils.HTMLElementWithLightDOMTemplate
239
262
  }
240
263
  }
241
264
  const visibleColumns = new Set(this.#columns.map(({id}) => id).filter(id => !this.#hiddenColumns.has(id)));
242
- if (visibleColumns.size) {
243
- this.#dataGrid.setColumnsVisibility(visibleColumns);
244
- }
265
+ this.#dataGrid.setColumnsVisibility(visibleColumns);
245
266
  this.#dataGrid.setEditCallback(hasEditableColumn ? this.#editCallback.bind(this) : undefined, INTERNAL_TOKEN);
246
267
  this.#dataGrid.deleteCallback = hasEditableColumn ? this.#deleteCallback.bind(this) : undefined;
247
268
  }
@@ -306,7 +327,9 @@ export class DataGridElement extends UI.UIUtils.HTMLElementWithLightDOMTemplate
306
327
  const parentNode = parentDataGridNode || this.#dataGrid.rootNode();
307
328
  const nextNode = this.#findNextExistingNode(element);
308
329
  const index = nextNode ? parentNode.children.indexOf(nextNode) : parentNode.children.length;
309
- const node = new DataGridElementNode(element, this);
330
+
331
+ const node = this.#dataGrid instanceof SortableDataGrid ? new SortableNode(element, this) :
332
+ new DynamicHeightNode(element, this);
310
333
  this.#updateHasChildren(node, element);
311
334
  if ((parentRow || node.hasChildren()) && !this.#dataGrid.disclosureColumnId) {
312
335
  this.#dataGrid.disclosureColumnId = this.#columns[0].id;
@@ -382,7 +405,9 @@ export class DataGridElement extends UI.UIUtils.HTMLElementWithLightDOMTemplate
382
405
  this.#dataGrid.creationNode = undefined;
383
406
  } else if (!getNode(placeholder)) {
384
407
  this.#dataGrid.creationNode?.remove();
385
- const node = new DataGridElementNode(placeholder, this);
408
+
409
+ const node = this.#dataGrid instanceof SortableDataGrid ? new SortableNode(placeholder, this) :
410
+ new DynamicHeightNode(placeholder, this);
386
411
  this.#dataGrid.creationNode = node;
387
412
  this.#dataGrid.rootNode().appendChild(node);
388
413
  }
@@ -404,9 +429,8 @@ export class DataGridElement extends UI.UIUtils.HTMLElementWithLightDOMTemplate
404
429
  }
405
430
  }
406
431
 
407
- #editCallback(
408
- node: DataGridElementNode, columnId: string, valueBeforeEditing: string, newText: string,
409
- moveDirection?: string): void {
432
+ #editCallback(node: DataGridElementNode, columnId: string, valueBeforeEditing: string, newText: string,
433
+ moveDirection?: string): void {
410
434
  if (node.isCreationNode) {
411
435
  this.#usedCreationNode = node;
412
436
  let hasNextEditableColumn = false;
@@ -432,9 +456,8 @@ export class DataGridElement extends UI.UIUtils.HTMLElementWithLightDOMTemplate
432
456
  override addEventListener<K extends keyof HTMLElementEventMap>(
433
457
  type: K, listener: (this: HTMLElement, ev: HTMLElementEventMap[K]) => void,
434
458
  options?: boolean|AddEventListenerOptions|undefined): void;
435
- override addEventListener(
436
- type: string, listener: EventListenerOrEventListenerObject,
437
- options?: boolean|AddEventListenerOptions|undefined): void;
459
+ override addEventListener(type: string, listener: EventListenerOrEventListenerObject,
460
+ options?: boolean|AddEventListenerOptions|undefined): void;
438
461
  override addEventListener(...args: Parameters<HTMLElement['addEventListener']>): void {
439
462
  super.addEventListener(...args);
440
463
  if (args[0] === 'refresh') {
@@ -447,137 +470,163 @@ export class DataGridElement extends UI.UIUtils.HTMLElementWithLightDOMTemplate
447
470
  }
448
471
  }
449
472
 
450
- const elementToNode = new WeakMap<Element, DataGridElementNode>();
451
-
452
- class DataGridElementNode extends SortableDataGridNode<DataGridElementNode> {
453
- #configElement: Element;
454
- #dataGridElement: DataGridElement;
455
- #addedClasses = new Set<string>();
456
- constructor(configElement: Element, dataGridElement: DataGridElement) {
457
- super();
458
- this.#configElement = configElement;
459
- elementToNode.set(configElement, this);
460
- this.#dataGridElement = dataGridElement;
461
- this.#updateData();
462
- this.isCreationNode = hasBooleanAttribute(this.#configElement, 'placeholder');
463
- }
473
+ type Constructor<T = object> = new (...args: any[]) => T;
464
474
 
465
- get configElement(): Element {
466
- return this.#configElement;
467
- }
475
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any, @typescript-eslint/explicit-function-return-type
476
+ function nodeMixin<TBase extends Constructor<DataGridNode<any>>>(base: TBase) {
477
+ return class extends base {
478
+ #configElement!: Element;
479
+ #dataGridElement!: DataGridElement;
480
+ #addedClasses = new Set<string>();
468
481
 
469
- #updateData(): void {
470
- const cells = [...this.#configElement.children].filter(c => c.tagName === 'TD');
471
- for (let i = 0; i < this.#dataGridElement.columns.length; ++i) {
472
- const cell = cells[i] as HTMLElement;
473
- if (!cell) {
474
- continue;
475
- }
476
- const column = this.#dataGridElement.columns[i];
477
- if (column.dataType === DataType.BOOLEAN) {
478
- this.data[column.id] = hasBooleanAttribute(cell, 'data-value') || cell.textContent === 'true';
479
- } else {
480
- this.data[column.id] = cell.dataset.value ?? cell.textContent ?? '';
481
- }
482
+ initElementNode(configElement: Element, dataGridElement: DataGridElement): void {
483
+ this.#configElement = configElement;
484
+ this.#dataGridElement = dataGridElement;
485
+ this.#updateData();
486
+ this.isCreationNode = hasBooleanAttribute(this.#configElement, 'placeholder');
482
487
  }
483
- }
484
488
 
485
- override createElement(): HTMLElement {
486
- const element = super.createElement();
487
- element.addEventListener('click', this.#onRowMouseEvent.bind(this));
488
- element.addEventListener('mouseenter', this.#onRowMouseEvent.bind(this));
489
- element.addEventListener('mouseleave', this.#onRowMouseEvent.bind(this));
490
- if (this.#configElement.hasAttribute('style')) {
491
- element.setAttribute('style', this.#configElement.getAttribute('style') || '');
492
- }
493
- for (const classToAdd of this.#configElement.classList) {
494
- element.classList.add(classToAdd);
489
+ get configElement(): Element {
490
+ return this.#configElement;
495
491
  }
496
- return element;
497
- }
498
492
 
499
- override refresh(): void {
500
- this.#updateData();
501
- super.refresh();
502
- const existingElement = this.existingElement();
503
- if (!existingElement) {
504
- return;
505
- }
506
- if (this.#configElement.hasAttribute('style')) {
507
- existingElement.setAttribute('style', this.#configElement.getAttribute('style') || '');
493
+ get dataGridElement(): DataGridElement {
494
+ return this.#dataGridElement;
508
495
  }
509
- for (const addedClass of this.#addedClasses) {
510
- existingElement.classList.remove(addedClass);
496
+
497
+ #updateData(): void {
498
+ const cells = [...this.#configElement.children].filter(c => c.tagName === 'TD');
499
+ for (let i = 0; i < this.#dataGridElement.columns.length; ++i) {
500
+ const cell = cells[i] as HTMLElement;
501
+ if (!cell) {
502
+ continue;
503
+ }
504
+ const column = this.#dataGridElement.columns[i];
505
+ if (column.dataType === DataType.BOOLEAN) {
506
+ this.data[column.id] = hasBooleanAttribute(cell, 'data-value') || cell.textContent === 'true';
507
+ } else {
508
+ this.data[column.id] = cell.dataset.value ?? cell.textContent ?? '';
509
+ }
510
+ }
511
511
  }
512
- for (const classToAdd of this.#configElement.classList) {
513
- existingElement.classList.add(classToAdd);
512
+
513
+ override createElement(): HTMLElement {
514
+ const element = super.createElement();
515
+ element.addEventListener('click', this.#onRowMouseEvent.bind(this));
516
+ element.addEventListener('mouseenter', this.#onRowMouseEvent.bind(this));
517
+ element.addEventListener('mouseleave', this.#onRowMouseEvent.bind(this));
518
+ if (this.#configElement.hasAttribute('style')) {
519
+ element.setAttribute('style', this.#configElement.getAttribute('style') || '');
520
+ }
521
+ for (const classToAdd of this.#configElement.classList) {
522
+ element.classList.add(classToAdd);
523
+ }
524
+ return element;
514
525
  }
515
- }
516
526
 
517
- #onRowMouseEvent(event: MouseEvent): void {
518
- const targetInConfigRow = UI.UIUtils.HTMLElementWithLightDOMTemplate.findCorrespondingElement(
519
- event.target as HTMLElement, event.currentTarget as HTMLElement, this.#configElement);
520
- if (!targetInConfigRow) {
521
- throw new Error('Cell click event target not found in the data grid');
527
+ override refresh(): void {
528
+ this.#updateData();
529
+ super.refresh();
530
+ const existingElement = this.existingElement();
531
+ if (!existingElement) {
532
+ return;
533
+ }
534
+ if (this.#configElement.hasAttribute('style')) {
535
+ existingElement.setAttribute('style', this.#configElement.getAttribute('style') || '');
536
+ }
537
+ for (const addedClass of this.#addedClasses) {
538
+ existingElement.classList.remove(addedClass);
539
+ }
540
+ for (const classToAdd of this.#configElement.classList) {
541
+ existingElement.classList.add(classToAdd);
542
+ }
522
543
  }
523
544
 
524
- if (targetInConfigRow instanceof HTMLElement) {
525
- targetInConfigRow?.dispatchEvent(new MouseEvent(event.type, {bubbles: true, composed: true}));
545
+ #onRowMouseEvent(event: MouseEvent): void {
546
+ const targetInConfigRow = UI.UIUtils.HTMLElementWithLightDOMTemplate.findCorrespondingElement(
547
+ event.target as HTMLElement, event.currentTarget as HTMLElement, this.#configElement);
548
+ if (!targetInConfigRow) {
549
+ throw new Error('Cell click event target not found in the data grid');
550
+ }
551
+
552
+ if (targetInConfigRow instanceof HTMLElement) {
553
+ targetInConfigRow?.dispatchEvent(new MouseEvent(event.type, {bubbles: true, composed: true}));
554
+ }
526
555
  }
527
- }
528
556
 
529
- override createCells(element: Element): void {
530
- const configCells = [...this.#configElement.querySelectorAll('td')];
531
- const hasCollspan = configCells.some(cell => cell.hasAttribute('colspan'));
532
- if (!hasCollspan) {
533
- super.createCells(element);
534
- } else {
535
- for (const cell of configCells) {
536
- element.appendChild(cell.cloneNode(true));
557
+ override createCells(element: Element): void {
558
+ const configCells = [...this.#configElement.querySelectorAll('td')];
559
+ const hasCollspan = configCells.some(cell => cell.hasAttribute('colspan'));
560
+ if (!hasCollspan) {
561
+ super.createCells(element);
562
+ } else {
563
+ for (const cell of configCells) {
564
+ element.appendChild(cell.cloneNode(true));
565
+ }
537
566
  }
538
567
  }
539
- }
540
568
 
541
- override createCell(columnId: string): HTMLElement {
542
- const index = this.#dataGridElement.columns.findIndex(({id}) => id === columnId);
543
- if (this.#dataGridElement.columns[index].dataType === DataType.BOOLEAN) {
544
- const cell = super.createCell(columnId);
569
+ override createCell(columnId: string): HTMLElement {
570
+ const index = this.#dataGridElement.columns.findIndex(({id}) => id === columnId);
571
+ if (this.#dataGridElement.columns[index].dataType === DataType.BOOLEAN) {
572
+ const cell = super.createCell(columnId);
573
+ cell.setAttribute('part', `${columnId}-column`);
574
+ return cell;
575
+ }
576
+ const cell = this.createTD(columnId);
545
577
  cell.setAttribute('part', `${columnId}-column`);
578
+ const configCells = [...this.#configElement.children].filter(c => c.tagName === 'TD') as HTMLTableCellElement[];
579
+ const configCell = configCells[index];
580
+ if (this.isCreationNode && !configCell) {
581
+ return cell;
582
+ }
583
+ if (!configCell) {
584
+ throw new Error(`Column ${columnId} not found in the data grid`);
585
+ }
586
+ for (const child of configCell.childNodes) {
587
+ cell.appendChild(child.cloneNode(true));
588
+ }
589
+ for (const cssClass of configCell.classList) {
590
+ cell.classList.add(cssClass);
591
+ }
592
+ cell.title = configCell.title;
593
+ if (configCell.hasAttribute('aria-label')) {
594
+ this.setCellAccessibleName(configCell.getAttribute('aria-label') || '', cell, columnId);
595
+ }
596
+ const style = configCell.getAttribute('style');
597
+ if (style !== null) {
598
+ cell.setAttribute('style', style);
599
+ }
600
+
546
601
  return cell;
547
602
  }
548
- const cell = this.createTD(columnId);
549
- cell.setAttribute('part', `${columnId}-column`);
550
- const configCells = [...this.#configElement.children].filter(c => c.tagName === 'TD') as HTMLTableCellElement[];
551
- const configCell = configCells[index];
552
- if (this.isCreationNode && !configCell) {
553
- return cell;
554
- }
555
- if (!configCell) {
556
- throw new Error(`Column ${columnId} not found in the data grid`);
557
- }
558
- for (const child of configCell.childNodes) {
559
- cell.appendChild(child.cloneNode(true));
560
- }
561
- for (const cssClass of configCell.classList) {
562
- cell.classList.add(cssClass);
563
- }
564
- cell.title = configCell.title;
565
- if (configCell.hasAttribute('aria-label')) {
566
- this.setCellAccessibleName(configCell.getAttribute('aria-label') || '', cell, columnId);
567
- }
568
- const style = configCell.getAttribute('style');
569
- if (style !== null) {
570
- cell.setAttribute('style', style);
603
+
604
+ override deselect(): void {
605
+ super.deselect();
606
+ if (this.isCreationNode) {
607
+ this.#dataGridElement.dispatchEvent(new CustomEvent('create', {detail: this.data}));
608
+ }
571
609
  }
610
+ };
611
+ }
572
612
 
573
- return cell;
613
+ // clang-format off
614
+ class SortableNode extends nodeMixin(SortableDataGridNode<SortableNode>) {
615
+ // clang-format on
616
+ constructor(configElement: Element, dataGridElement: DataGridElement) {
617
+ super();
618
+ this.initElementNode(configElement, dataGridElement);
619
+ elementToNode.set(configElement, this);
574
620
  }
621
+ }
575
622
 
576
- override deselect(): void {
577
- super.deselect();
578
- if (this.isCreationNode) {
579
- this.#dataGridElement.dispatchEvent(new CustomEvent('create', {detail: this.data}));
580
- }
623
+ // clang-format off
624
+ class DynamicHeightNode extends nodeMixin(DataGridNode<DynamicHeightNode>) {
625
+ // clang-format on
626
+ constructor(configElement: Element, dataGridElement: DataGridElement) {
627
+ super();
628
+ this.initElementNode(configElement, dataGridElement);
629
+ elementToNode.set(configElement, this);
581
630
  }
582
631
  }
583
632
 
@@ -0,0 +1,89 @@
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
+ import type {DataGridImpl} from './DataGrid.js';
6
+
7
+ /**
8
+ * Exports the visual contents of a DataGrid as a GitHub Flavored Markdown table.
9
+ */
10
+ export function exportToMarkdown(dataGrid: DataGridImpl<unknown>): string {
11
+ const grid = serializeGrid(dataGrid, escapeMarkdown);
12
+ if (grid.length === 0) {
13
+ return '';
14
+ }
15
+ const header = '| ' + grid[0].join(' | ') + ' |';
16
+ const separator = '| ' + grid[0].map(() => '---').join(' | ') + ' |';
17
+ const body = grid.slice(1).map(row => '| ' + row.join(' | ') + ' |');
18
+ return [header, separator, ...body].join('\n');
19
+ }
20
+
21
+ export function exportToCSV(dataGrid: DataGridImpl<unknown>): string {
22
+ const grid = serializeGrid(dataGrid, escapeCSV);
23
+ return grid.map(row => row.join(',')).join('\n');
24
+ }
25
+
26
+ function serializeGrid(dataGrid: DataGridImpl<unknown>, cellEscaper: (val: string) => string): string[][] {
27
+ const columns = dataGrid.visibleColumnsArray;
28
+ if (columns.length === 0) {
29
+ return [];
30
+ }
31
+ const rows = dataGrid.rootNode().children;
32
+
33
+ const result: string[][] = [];
34
+ // Header
35
+ result.push(columns.map(col => cellEscaper(String(col.title || ''))));
36
+
37
+ // Body
38
+ for (const row of rows) {
39
+ result.push(columns.map(col => {
40
+ const cellValue = row.data[col.id];
41
+ const cellText = extractText(cellValue);
42
+ return cellEscaper(cellText);
43
+ }));
44
+ }
45
+
46
+ return result;
47
+ }
48
+
49
+ function extractText(value: unknown): string {
50
+ if (value instanceof Node) {
51
+ return value.textContent || '';
52
+ }
53
+ if (value === undefined || value === null) {
54
+ return '';
55
+ }
56
+ return String(value);
57
+ }
58
+
59
+ /**
60
+ * Escapes a string so it can be safely rendered within a Markdown table cell.
61
+ *
62
+ * It performs three operations:
63
+ * 1. HTML-escapes `<`, `>`, and `&` to prevent text from being parsed as raw HTML.
64
+ * 2. Backslash-escapes Markdown control characters (e.g., `*`, `_`, `[`) to avoid unexpected formatting.
65
+ * 3. Replaces newlines with `<br>` tags since Markdown tables do not support multiline cells.
66
+ */
67
+ function escapeMarkdown(val: string): string {
68
+ return val.replace(/&/g, '&amp;')
69
+ .replace(/</g, '&lt;')
70
+ .replace(/>/g, '&gt;')
71
+ .replace(/([\\`*_{}[\]()#+\-.!|])/g, '\\$1')
72
+ .replace(/\r?\n/g, '<br>');
73
+ }
74
+
75
+ function escapeCSV(val: string): string {
76
+ let needQuotes = false;
77
+ let escaped = val;
78
+ if (escaped.includes('"')) {
79
+ escaped = escaped.replace(/"/g, '""');
80
+ needQuotes = true;
81
+ }
82
+ if (escaped.includes(',') || escaped.includes('\n') || escaped.includes('\r')) {
83
+ needQuotes = true;
84
+ }
85
+ if (needQuotes) {
86
+ return `"${escaped}"`;
87
+ }
88
+ return escaped;
89
+ }
@@ -110,6 +110,16 @@
110
110
  padding: 1px 4px;
111
111
  }
112
112
 
113
+ .data-grid.auto-row-height tbody tr {
114
+ height: auto;
115
+ }
116
+
117
+ .data-grid.auto-row-height td {
118
+ white-space: normal;
119
+ height: auto;
120
+ }
121
+
122
+
113
123
  .data-grid td {
114
124
  vertical-align: top;
115
125
  user-select: text;
@@ -7,11 +7,13 @@ import './ViewportDataGrid.js';
7
7
  import './SortableDataGrid.js';
8
8
  import './ShowMoreDataGridNode.js';
9
9
  import './DataGridElement.js';
10
+ import './DataGridExporter.js';
10
11
 
11
12
  import * as DataGrid from './DataGrid.js';
13
+ import * as DataGridExporter from './DataGridExporter.js';
12
14
  import * as ShowMoreDataGridNode from './ShowMoreDataGridNode.js';
13
15
  import * as SortableDataGrid from './SortableDataGrid.js';
14
16
  import * as ViewportDataGrid from './ViewportDataGrid.js';
15
17
 
16
18
  export {ifExpanded} from './DataGridElement.js';
17
- export {DataGrid, ShowMoreDataGridNode, SortableDataGrid, ViewportDataGrid};
19
+ export {DataGrid, DataGridExporter, ShowMoreDataGridNode, SortableDataGrid, ViewportDataGrid};