chrome-devtools-frontend 1.0.1689415 → 1.0.1690116

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 (197) hide show
  1. package/eslint.config.mjs +8 -2
  2. package/front_end/core/common/Object.ts +10 -4
  3. package/front_end/core/i18n/collect-ui-strings.js +5 -5
  4. package/front_end/core/i18n/generate-locales-js.js +1 -1
  5. package/front_end/core/i18n/i18n.ts +6 -0
  6. package/front_end/core/root/Runtime.ts +0 -5
  7. package/front_end/core/sdk/CSSMetadata.ts +3 -5
  8. package/front_end/core/sdk/DOMModel.ts +46 -11
  9. package/front_end/core/sdk/EmulationModel.ts +18 -0
  10. package/front_end/core/sdk/SDKSettings.ts +9 -0
  11. package/front_end/core/sdk/Script.ts +0 -1
  12. package/front_end/core/sdk/SecurityOrigin.ts +135 -25
  13. package/front_end/core/sdk/Target.ts +1 -5
  14. package/front_end/models/ai_assistance/AiUtils.ts +0 -10
  15. package/front_end/models/ai_assistance/agents/AccessibilityAgent.ts +6 -10
  16. package/front_end/models/ai_assistance/agents/StylingAgent.ts +0 -1
  17. package/front_end/models/ai_assistance/injected.ts +0 -2
  18. package/front_end/models/javascript_metadata/NativeFunctions.ts +1 -1
  19. package/front_end/models/trace/insights/CLSCulprits.ts +1 -2
  20. package/front_end/models/trace/insights/Cache.ts +1 -3
  21. package/front_end/models/trace/insights/CharacterSet.ts +1 -3
  22. package/front_end/models/trace/insights/DOMSize.ts +1 -3
  23. package/front_end/models/trace/insights/DocumentLatency.ts +1 -3
  24. package/front_end/models/trace/insights/DuplicatedJavaScript.ts +1 -3
  25. package/front_end/models/trace/insights/FontDisplay.ts +1 -2
  26. package/front_end/models/trace/insights/ForcedReflow.ts +1 -2
  27. package/front_end/models/trace/insights/INPBreakdown.ts +1 -3
  28. package/front_end/models/trace/insights/ImageDelivery.ts +1 -3
  29. package/front_end/models/trace/insights/LCPBreakdown.ts +1 -3
  30. package/front_end/models/trace/insights/LCPDiscovery.ts +1 -3
  31. package/front_end/models/trace/insights/LegacyJavaScript.ts +1 -3
  32. package/front_end/models/trace/insights/ModernHTTP.ts +1 -2
  33. package/front_end/models/trace/insights/NetworkDependencyTree.ts +1 -2
  34. package/front_end/models/trace/insights/RenderBlocking.ts +1 -3
  35. package/front_end/models/trace/insights/SlowCSSSelector.ts +1 -3
  36. package/front_end/models/trace/insights/ThirdParties.ts +1 -3
  37. package/front_end/models/trace/insights/Viewport.ts +1 -2
  38. package/front_end/panels/accessibility/ARIAAttributesView.ts +1 -1
  39. package/front_end/panels/accessibility/AccessibilityAnnouncementRecordingView.ts +3 -3
  40. package/front_end/panels/accessibility/AccessibilityNodeView.ts +1 -2
  41. package/front_end/panels/accessibility/AccessibilityStrings.ts +7 -3
  42. package/front_end/panels/ai_assistance/components/AIv2MarkdownRenderer.ts +2 -2
  43. package/front_end/panels/ai_assistance/components/AccessibilityAgentMarkdownRenderer.ts +1 -3
  44. package/front_end/panels/animation/AnimationGroupPreviewUI.ts +1 -1
  45. package/front_end/panels/animation/AnimationTimeline.ts +1 -1
  46. package/front_end/panels/animation/AnimationUI.ts +1 -1
  47. package/front_end/panels/application/AppManifestView.ts +6 -2
  48. package/front_end/panels/application/ApplicationPanelSidebar.ts +2 -4
  49. package/front_end/panels/application/BounceTrackingMitigationsTreeElement.ts +1 -1
  50. package/front_end/panels/application/FrameDetailsView.ts +1 -1
  51. package/front_end/panels/application/IndexedDBViews.ts +3 -2
  52. package/front_end/panels/application/ReportingApiTreeElement.ts +1 -1
  53. package/front_end/panels/application/ReportingApiView.ts +2 -2
  54. package/front_end/panels/application/ServiceWorkerUpdateCycleView.ts +1 -1
  55. package/front_end/panels/application/ServiceWorkersView.ts +1 -1
  56. package/front_end/panels/application/StorageBucketsTreeElement.ts +1 -1
  57. package/front_end/panels/application/StorageItemsToolbar.ts +6 -2
  58. package/front_end/panels/application/StorageView.ts +9 -8
  59. package/front_end/panels/application/TrustTokensTreeElement.ts +1 -1
  60. package/front_end/panels/application/WebMCPView.ts +2 -1
  61. package/front_end/panels/application/components/BounceTrackingMitigationsView.ts +1 -1
  62. package/front_end/panels/application/components/CrashReportContextGrid.ts +1 -1
  63. package/front_end/panels/application/components/EndpointsGrid.ts +1 -1
  64. package/front_end/panels/application/components/ReportsGrid.ts +1 -1
  65. package/front_end/panels/application/components/TrustTokensView.ts +1 -1
  66. package/front_end/panels/application/preloading/components/MismatchedPreloadingGrid.ts +1 -1
  67. package/front_end/panels/application/preloading/components/PreloadingGrid.ts +1 -1
  68. package/front_end/panels/application/preloading/components/RuleSetGrid.ts +7 -2
  69. package/front_end/panels/application/serviceWorkerUpdateCycleView.css +2 -0
  70. package/front_end/panels/autofill/AutofillView.ts +2 -2
  71. package/front_end/panels/browser_debugger/CategorizedBreakpointsSidebarPane.ts +2 -3
  72. package/front_end/panels/browser_debugger/DOMBreakpointsSidebarPane.ts +1 -1
  73. package/front_end/panels/changes/ChangesSidebar.ts +56 -15
  74. package/front_end/panels/changes/ChangesView.ts +1 -1
  75. package/front_end/panels/console/ConsolePrompt.ts +6 -2
  76. package/front_end/panels/console/ConsoleSidebar.ts +6 -2
  77. package/front_end/panels/elements/ElementsTreeElement.ts +12 -0
  78. package/front_end/panels/elements/ElementsTreeOutline.ts +400 -128
  79. package/front_end/panels/elements/PropertyRenderer.ts +22 -15
  80. package/front_end/panels/elements/StandaloneStylesContainer.ts +6 -2
  81. package/front_end/panels/elements/StylePropertyTreeElement.ts +75 -26
  82. package/front_end/panels/elements/StylesSidebarPane.ts +6 -2
  83. package/front_end/panels/elements/elementsTreeOutline.css +4 -0
  84. package/front_end/panels/emulation/DeviceModeView.ts +6 -1
  85. package/front_end/panels/emulation/InspectedPagePlaceholder.ts +6 -2
  86. package/front_end/panels/layer_viewer/LayerDetailsView.ts +6 -2
  87. package/front_end/panels/layer_viewer/LayerTreeOutline.ts +7 -2
  88. package/front_end/panels/layer_viewer/Layers3DView.ts +8 -1
  89. package/front_end/panels/layer_viewer/PaintProfilerView.ts +6 -2
  90. package/front_end/panels/lighthouse/lighthousePanel.css +5 -5
  91. package/front_end/panels/lighthouse/lighthouseStartView.css +13 -13
  92. package/front_end/panels/linear_memory_inspector/LinearMemoryInspectorPane.ts +6 -2
  93. package/front_end/panels/linear_memory_inspector/components/LinearMemoryInspector.ts +6 -2
  94. package/front_end/panels/mobile_throttling/NetworkThrottlingSelector.ts +6 -2
  95. package/front_end/panels/mobile_throttling/ThrottlingManager.ts +25 -54
  96. package/front_end/panels/network/NetworkLogView.ts +8 -3
  97. package/front_end/panels/network/components/RequestHeadersView.css +5 -0
  98. package/front_end/panels/network/networkLogView.css +4 -0
  99. package/front_end/panels/network/requestInitiatorViewTree.css +2 -0
  100. package/front_end/panels/performance_monitor/performanceMonitor.css +3 -3
  101. package/front_end/panels/profiler/HeapDetachedElementsView.ts +7 -2
  102. package/front_end/panels/profiler/HeapProfileView.ts +7 -2
  103. package/front_end/panels/profiler/HeapSnapshotDataGrids.ts +8 -4
  104. package/front_end/panels/profiler/HeapSnapshotGridNodes.ts +8 -4
  105. package/front_end/panels/profiler/HeapSnapshotView.ts +15 -6
  106. package/front_end/panels/profiler/HeapTimelineOverview.ts +6 -2
  107. package/front_end/panels/profiler/ProfileFlameChartDataProvider.ts +12 -5
  108. package/front_end/panels/profiler/ProfileLauncherView.ts +6 -2
  109. package/front_end/panels/profiler/profilesPanel.css +8 -9
  110. package/front_end/panels/profiler/profilesSidebarTree.css +13 -13
  111. package/front_end/panels/protocol_monitor/JSONEditor.css +2 -2
  112. package/front_end/panels/protocol_monitor/JSONEditor.ts +6 -1
  113. package/front_end/panels/protocol_monitor/protocolMonitor.css +2 -2
  114. package/front_end/panels/sources/SourcesView.ts +7 -2
  115. package/front_end/panels/sources/TabbedEditorContainer.ts +6 -2
  116. package/front_end/panels/sources/UISourceCodeFrame.ts +10 -3
  117. package/front_end/panels/timeline/AnimationsTrackAppender.ts +1 -1
  118. package/front_end/panels/timeline/AnnotationHelpers.ts +9 -9
  119. package/front_end/panels/timeline/CountersGraph.ts +8 -8
  120. package/front_end/panels/timeline/EventsTimelineTreeView.ts +3 -3
  121. package/front_end/panels/timeline/ExtensionTrackAppender.ts +1 -1
  122. package/front_end/panels/timeline/GPUTrackAppender.ts +1 -1
  123. package/front_end/panels/timeline/InteractionsTrackAppender.ts +1 -1
  124. package/front_end/panels/timeline/IsolateSelector.ts +2 -2
  125. package/front_end/panels/timeline/LayoutShiftsTrackAppender.ts +3 -3
  126. package/front_end/panels/timeline/NetworkTrackAppender.ts +1 -1
  127. package/front_end/panels/timeline/StatusDialog.ts +5 -6
  128. package/front_end/panels/timeline/ThirdPartyTreeView.ts +4 -4
  129. package/front_end/panels/timeline/ThreadAppender.ts +12 -14
  130. package/front_end/panels/timeline/TimelineController.ts +5 -8
  131. package/front_end/panels/timeline/TimelineDetailsView.ts +13 -9
  132. package/front_end/panels/timeline/TimelineEventOverview.ts +4 -4
  133. package/front_end/panels/timeline/TimelineFlameChartDataProvider.ts +17 -17
  134. package/front_end/panels/timeline/TimelineFlameChartView.ts +8 -3
  135. package/front_end/panels/timeline/TimelineHistoryManager.ts +8 -8
  136. package/front_end/panels/timeline/TimelineLoader.ts +1 -1
  137. package/front_end/panels/timeline/TimelineMiniMap.ts +7 -2
  138. package/front_end/panels/timeline/TimelinePanel.ts +57 -59
  139. package/front_end/panels/timeline/TimelineSelectorStatsView.ts +27 -27
  140. package/front_end/panels/timeline/TimelineTreeView.ts +46 -44
  141. package/front_end/panels/timeline/TimelineUIUtils.ts +142 -116
  142. package/front_end/panels/timeline/TimingsTrackAppender.ts +1 -1
  143. package/front_end/panels/timeline/TrackConfigBanner.ts +4 -4
  144. package/front_end/panels/timeline/UIDevtoolsUtils.ts +15 -15
  145. package/front_end/panels/timeline/components/insights/Table.ts +1 -1
  146. package/front_end/panels/timeline/enable-easter-egg.js +1 -1
  147. package/front_end/panels/timeline/timeline-meta.ts +30 -30
  148. package/front_end/panels/timeline/timelineFlameChartView.css +10 -10
  149. package/front_end/panels/timeline/timelineFlamechartPopover.css +5 -5
  150. package/front_end/panels/timeline/timelineHistoryManager.css +7 -7
  151. package/front_end/panels/timeline/timelinePaintProfiler.css +2 -2
  152. package/front_end/panels/timeline/utils/IgnoreList.ts +4 -4
  153. package/front_end/third_party/chromium/README.chromium +1 -1
  154. package/front_end/ui/comments/CommentAnchorResolver.ts +12 -3
  155. package/front_end/ui/comments/CommentOverlayManager.ts +9 -1
  156. package/front_end/ui/components/buttons/Button.docs.ts +1 -1
  157. package/front_end/ui/components/buttons/button.css +21 -0
  158. package/front_end/ui/components/report_view/ReportView.docs.ts +1 -1
  159. package/front_end/ui/components/snackbars/Snackbars.docs.ts +1 -1
  160. package/front_end/ui/components/spinners/Spinner.ts +1 -1
  161. package/front_end/ui/components/spinners/Spinners.docs.ts +1 -1
  162. package/front_end/ui/components/suggestion_input/SuggestionInput.ts +1 -2
  163. package/front_end/ui/components/switch/Switch.docs.ts +1 -1
  164. package/front_end/ui/components/text_editor/AiCodeCompletionProvider.ts +10 -8
  165. package/front_end/ui/components/text_editor/AiCodeGenerationProvider.ts +2 -1
  166. package/front_end/ui/components/text_editor/ExecutionPositionHighlighter.ts +2 -2
  167. package/front_end/ui/components/text_editor/TextEditor.ts +19 -0
  168. package/front_end/ui/components/text_editor/config.ts +109 -96
  169. package/front_end/ui/components/text_editor/cursor_tooltip.ts +1 -1
  170. package/front_end/ui/components/text_editor/theme.ts +1 -1
  171. package/front_end/ui/components/tooltips/Tooltip.docs.ts +1 -1
  172. package/front_end/ui/components/tooltips/Tooltip.ts +1 -1
  173. package/front_end/ui/components/tree_outline/TreeOutlineUtils.ts +4 -1
  174. package/front_end/ui/kit/cards/Card.ts +1 -1
  175. package/front_end/ui/kit/icons/Icon.ts +1 -1
  176. package/front_end/ui/kit/link/Link.docs.ts +1 -1
  177. package/front_end/ui/kit/link/Link.ts +1 -1
  178. package/front_end/ui/legacy/ContextMenu.docs.ts +1 -1
  179. package/front_end/ui/legacy/Dialog.ts +10 -2
  180. package/front_end/ui/legacy/FilterBar.ts +6 -1
  181. package/front_end/ui/legacy/RadioButton.docs.ts +1 -1
  182. package/front_end/ui/legacy/SelectMenu.docs.ts +1 -1
  183. package/front_end/ui/legacy/Slider.docs.ts +1 -1
  184. package/front_end/ui/legacy/SplitWidget.ts +5 -1
  185. package/front_end/ui/legacy/TabbedPane.ts +5 -1
  186. package/front_end/ui/legacy/components/color_picker/Spectrum.ts +6 -1
  187. package/front_end/ui/legacy/components/data_grid/ViewportDataGrid.ts +6 -2
  188. package/front_end/ui/legacy/components/inline_editor/BezierEditor.ts +6 -1
  189. package/front_end/ui/legacy/components/inline_editor/CSSShadowEditor.ts +6 -2
  190. package/front_end/ui/legacy/components/object_ui/objectPropertiesSection.css +2 -0
  191. package/front_end/ui/legacy/components/perf_ui/FilmStripView.ts +6 -1
  192. package/front_end/ui/legacy/components/perf_ui/FlameChart.ts +6 -2
  193. package/front_end/ui/legacy/components/perf_ui/TimelineOverviewPane.ts +8 -4
  194. package/front_end/ui/legacy/components/quick_open/FilteredListWidget.ts +8 -3
  195. package/front_end/ui/legacy/components/quick_open/QuickOpen.ts +6 -1
  196. package/front_end/ui/legacy/components/source_frame/SourceFrame.ts +7 -2
  197. package/package.json +2 -2
@@ -40,6 +40,7 @@ import * as i18n from '../../core/i18n/i18n.js';
40
40
  import * as SDK from '../../core/sdk/sdk.js';
41
41
  import * as Badges from '../../models/badges/badges.js';
42
42
  import * as Elements from '../../models/elements/elements.js';
43
+ import * as Buttons from '../../ui/components/buttons/buttons.js';
43
44
  import * as CodeHighlighter from '../../ui/components/code_highlighter/code_highlighter.js';
44
45
  import * as Highlighting from '../../ui/components/highlighting/highlighting.js';
45
46
  import * as IssueCounter from '../../ui/components/issue_counter/issue_counter.js';
@@ -179,11 +180,13 @@ interface ViewInput {
179
180
  onToggleAdoptedStyleSheetExpanded?: (sheet: SDK.DOMModel.AdoptedStyleSheet, expanded: boolean) => void;
180
181
  selectedAdoptedStyleSheet?: SDK.DOMModel.AdoptedStyleSheet|null;
181
182
  onSelectAdoptedStyleSheet?: (sheet: SDK.DOMModel.AdoptedStyleSheet) => void;
183
+ expandedChildrenLimit?: (node: SDK.DOMModel.DOMNode) => number;
184
+ onExpandAllChildren?: (node: SDK.DOMModel.DOMNode) => void;
185
+ updateRecordForNode?: (node: SDK.DOMModel.DOMNode) => Elements.ElementUpdateRecord.ElementUpdateRecord | null;
182
186
  }
183
187
 
184
188
  interface ViewOutput {
185
189
  elementsTreeOutline?: ElementsTreeOutline;
186
- imagePreviewPopover?: ImagePreviewPopover;
187
190
  highlightedTreeElement: ElementsTreeElement|null;
188
191
  searchMatchTreeElement?: ElementsTreeElement|null;
189
192
  searchMatchQuery?: string;
@@ -230,84 +233,6 @@ export const DEFAULT_VIEW = (input: ViewInput, output: ViewOutput, target: HTMLE
230
233
  elementsTreeOutline.elementInternal.addEventListener('clipboard-paste', (event: Event) => {
231
234
  input.onPaste?.(event);
232
235
  }, false);
233
- output.imagePreviewPopover = new ImagePreviewPopover(
234
- elementsTreeOutline.contentElement,
235
- event => {
236
- let link: (Element|null) = (event.target as Element | null);
237
- while (link && !ImagePreviewPopover.getImageURL(link)) {
238
- link = link.parentElementOrShadowHost();
239
- }
240
- return link;
241
- },
242
- async link => {
243
- const listItem = UI.UIUtils.enclosingNodeOrSelfWithNodeName(link, 'li');
244
- if (!listItem) {
245
- return undefined;
246
- }
247
-
248
- const treeElement =
249
- (UI.TreeOutline.TreeElement.getTreeElementBylistItemNode(listItem) as ElementsTreeElement | undefined);
250
- return await UIComponentUtils.ImagePreview.loadPrecomputedFeatures(treeElement?.node());
251
- });
252
- // TODO(changhaohan): refactor the popover to use tooltip component.
253
- const popupHelper = new UI.PopoverHelper.PopoverHelper(elementsTreeOutline.elementInternal, event => {
254
- const hoveredNode = event.composedPath()[0];
255
- if (!(hoveredNode instanceof Element) || !hoveredNode.matches('.violating-element')) {
256
- return null;
257
- }
258
-
259
- const listItem = UI.UIUtils.enclosingNodeOrSelfWithNodeName(hoveredNode, 'li');
260
- if (!listItem) {
261
- return null;
262
- }
263
- const treeElement =
264
- UI.TreeOutline.TreeElement.getTreeElementBylistItemNode(listItem) as ElementsTreeElement | undefined;
265
- const node = treeElement?.node();
266
- if (!node) {
267
- return null;
268
- }
269
- let issues = treeElement?.widget?.issues ?? [];
270
- if (hoveredNode.classList.contains('webkit-html-attribute-name')) {
271
- const attrName = hoveredNode.textContent;
272
- issues = issues.filter(issue => getElementIssueDetails(issue)?.attribute === attrName);
273
- } else if (hoveredNode.classList.contains('webkit-html-tag-name')) {
274
- issues = issues.filter(issue => {
275
- const details = getElementIssueDetails(issue);
276
- return Boolean(details && !details.attribute);
277
- });
278
- }
279
- if (issues.length === 0) {
280
- return null;
281
- }
282
-
283
- return {
284
- box: hoveredNode.boxInWindow(),
285
- show: async (popover: UI.GlassPane.GlassPane) => {
286
- popover.setIgnoreLeftMargin(true);
287
- // clang-format off
288
- render(html`
289
- <div class="squiggles-content">
290
- ${issues.map(issue => {
291
- const elementIssueDetails = getElementIssueDetails(issue);
292
- if (!elementIssueDetails) {
293
- return nothing;
294
- }
295
- const issueKindIconName = IssueCounter.IssueCounter.getIssueKindIconName(issue.getKind());
296
- const openIssueEvent = (): Promise<void> => Common.Revealer.reveal(issue);
297
- return html`
298
- <div class="squiggles-content-item">
299
- <devtools-icon .name=${issueKindIconName} @click=${openIssueEvent}></devtools-icon>
300
- <devtools-link class="link" @click=${openIssueEvent}>${i18nString(UIStrings.viewIssue)}</devtools-link>
301
- <span>${elementIssueDetails.tooltip}</span>
302
- </div>`;
303
- })}
304
- </div>`, popover.contentElement);
305
- // clang-format on
306
- return true;
307
- },
308
- };
309
- }, 'elements.issue');
310
- popupHelper.setTimeout(300);
311
236
  target.appendChild(elementsTreeOutline.element);
312
237
  }
313
238
 
@@ -333,9 +258,6 @@ export const DEFAULT_VIEW = (input: ViewInput, output: ViewOutput, target: HTMLE
333
258
  }
334
259
  if (input.visible !== undefined) {
335
260
  output.elementsTreeOutline.setVisible(input.visible);
336
- if (!input.visible) {
337
- output.imagePreviewPopover?.hide();
338
- }
339
261
  }
340
262
  output.elementsTreeOutline.maxRowsShown = input.maxRowsShown;
341
263
  output.elementsTreeOutline.setWordWrap(input.wrap);
@@ -556,11 +478,14 @@ function computeLeftIndent(depth: number, isExpandable: boolean): number {
556
478
  }
557
479
 
558
480
  export const DECLARATIVE_VIEW: View = (input: ViewInput, _output: ViewOutput, target: HTMLElement): void => {
481
+ let allRootNodes: SDK.DOMModel.DOMNode[] = [];
559
482
  let rootNodes: SDK.DOMModel.DOMNode[] = [];
560
483
  const rootDOMNode = input.rootDOMNode;
561
484
  if (rootDOMNode) {
562
485
  if (input.omitRootDOMNode) {
563
- rootNodes = getVisibleChildren(rootDOMNode, input.showComments ?? true);
486
+ allRootNodes = getVisibleChildren(rootDOMNode, input.showComments ?? true);
487
+ const rootLimit = input.expandedChildrenLimit ? input.expandedChildrenLimit(rootDOMNode) : InitialChildrenLimit;
488
+ rootNodes = allRootNodes.slice(0, rootLimit);
564
489
  } else {
565
490
  rootNodes = [rootDOMNode];
566
491
  }
@@ -762,15 +687,14 @@ export const DECLARATIVE_VIEW: View = (input: ViewInput, _output: ViewOutput, ta
762
687
  (input.expandRoot &&
763
688
  (node === input.rootDOMNode || (input.omitRootDOMNode && node.parentNode === input.rootDOMNode)))));
764
689
  const hasChildren = nodeHasVisibleChildren(node, input.rootDOMNode, input.maxTreeDepth, input.omitRootDOMNode);
765
- const children = hasChildren ? getVisibleChildren(node, input.showComments ?? true) : [];
690
+ const allVisibleChildren = hasChildren ? getVisibleChildren(node, input.showComments ?? true) : [];
691
+ const limit = input.expandedChildrenLimit ? input.expandedChildrenLimit(node) : InitialChildrenLimit;
692
+ const children = allVisibleChildren.slice(0, limit);
693
+ const remainingChildrenCount = allVisibleChildren.length - children.length;
766
694
  const tagName = node.nodeName().toLowerCase();
767
695
  const needsClosingTag = node.nodeType() === Node.ELEMENT_NODE && !ForbiddenClosingTagElements.has(tagName) &&
768
696
  !node.pseudoType() && (hasChildren || !ElementsTreeWidget.canShowInlineText(node));
769
697
 
770
- // TODO: Move tree node pagination ("Show all nodes" button and expandedChildrenLimit) to a declarative tree slice model.
771
- // TODO: Move ImagePreviewPopover and issue tooltip helpers into declarative Lit directives or tooltip components.
772
- // TODO: Move DOMModel event subscription and reactive state synchronization (updateRecords, DOM update animations) directly into DOMTreeWidget.
773
-
774
698
  const on = Lit.Directive.directive(Lit.CustomDirectives.InterceptBindingDirective);
775
699
 
776
700
  const onSelect = (): void => {
@@ -893,12 +817,29 @@ export const DECLARATIVE_VIEW: View = (input: ViewInput, _output: ViewOutput, ta
893
817
  input.onContextMenu?.(node, event, widget);
894
818
  }
895
819
  },
820
+ updateRecord: input.updateRecordForNode?.(node) ?? null,
896
821
  })}
897
822
  ${hasChildren ? html`
898
823
  <ul role="group">
899
824
  ${UI.TreeOutline.ifExpanded(html`
900
825
  ${node.adoptedStyleSheetsForNode.length > 0 ? renderAdoptedStyleSheets(node, depth + 1) : nothing}
901
826
  ${children.map(child => renderNode(child, depth + 1))}
827
+ ${remainingChildrenCount > 0 ? html`
828
+ <li role="treeitem"
829
+ class="elements-tree-expand-all"
830
+ style="--indent: ${computeLeftIndent(depth + 1, false)}px"
831
+ jslog=${VisualLogging.treeItem('show-all-nodes').parent('elementsTreeOutline')}>
832
+ <devtools-button
833
+ .variant=${Buttons.Button.Variant.OUTLINED}
834
+ title=${i18nString(UIStrings.showAllNodesDMore, {PH1: remainingChildrenCount})}
835
+ @click=${on((event: Event) => {
836
+ event.stopPropagation();
837
+ input.onExpandAllChildren?.(node);
838
+ })}>
839
+ ${i18nString(UIStrings.showAllNodesDMore, {PH1: remainingChildrenCount})}
840
+ </devtools-button>
841
+ </li>
842
+ ` : nothing}
902
843
  ${node instanceof SDK.DOMModel.DOMDocument ? renderTopLayerContainer(node, depth + 1) : nothing}
903
844
  ${needsClosingTag ? html`
904
845
  <li role="treeitem"
@@ -924,6 +865,7 @@ export const DECLARATIVE_VIEW: View = (input: ViewInput, _output: ViewOutput, ta
924
865
  input.onContextMenu?.(node, event, widget);
925
866
  }
926
867
  },
868
+ updateRecord: input.updateRecordForNode?.(node) ?? null,
927
869
  })}
928
870
  </li>
929
871
  ` : nothing}
@@ -957,7 +899,30 @@ export const DECLARATIVE_VIEW: View = (input: ViewInput, _output: ViewOutput, ta
957
899
  <style>${CodeHighlighter.codeHighlighterStyles}</style>
958
900
  <ul role="tree">
959
901
  ${rootNodes.map(node => renderNode(node))}
960
- ${input.omitRootDOMNode && input.rootDOMNode instanceof SDK.DOMModel.DOMDocument ? renderTopLayerContainer(input.rootDOMNode, 1) : nothing}
902
+ ${input.omitRootDOMNode && input.rootDOMNode ? (() => {
903
+ const remaining = allRootNodes.length - rootNodes.length;
904
+ if (remaining <= 0) {
905
+ return nothing;
906
+ }
907
+ const root = input.rootDOMNode;
908
+ return html`
909
+ <li role="treeitem"
910
+ class="elements-tree-expand-all"
911
+ style="--indent: ${computeLeftIndent(0, false)}px"
912
+ jslog=${VisualLogging.treeItem('show-all-nodes').parent('elementsTreeOutline')}>
913
+ <devtools-button
914
+ .variant=${Buttons.Button.Variant.OUTLINED}
915
+ title=${i18nString(UIStrings.showAllNodesDMore, {PH1: remaining})}
916
+ @click=${on((event: Event) => {
917
+ event.stopPropagation();
918
+ input.onExpandAllChildren?.(root);
919
+ })}>
920
+ ${i18nString(UIStrings.showAllNodesDMore, {PH1: remaining})}
921
+ </devtools-button>
922
+ </li>
923
+ `;
924
+ })() : nothing}
925
+ ${input.omitRootDOMNode && input.rootDOMNode instanceof SDK.DOMModel.DOMDocument ? renderTopLayerContainer(input.rootDOMNode, 0) : nothing}
961
926
  </ul>
962
927
  `}>
963
928
  </devtools-tree>
@@ -966,6 +931,24 @@ export const DECLARATIVE_VIEW: View = (input: ViewInput, _output: ViewOutput, ta
966
931
  /* clang-format on */
967
932
  };
968
933
 
934
+ function getElementsTreeWidgetAndNode(element: Element): {node?: SDK.DOMModel.DOMNode, widget?: ElementsTreeWidget} {
935
+ let current: Element|null = element;
936
+ while (current) {
937
+ if (current.tagName === 'LI') {
938
+ const treeElement = UI.TreeOutline.TreeElement.getTreeElementBylistItemNode(current);
939
+ if (treeElement instanceof ElementsTreeElement) {
940
+ return {node: treeElement.node(), widget: treeElement.widget};
941
+ }
942
+ }
943
+ const widget = UI.Widget.Widget.get(current);
944
+ if (widget instanceof ElementsTreeWidget) {
945
+ return {node: widget.node, widget};
946
+ }
947
+ current = current.parentElementOrShadowHost();
948
+ }
949
+ return {};
950
+ }
951
+
969
952
  /**
970
953
  * The main goal of this presenter is to wrap ElementsTreeOutline until
971
954
  * ElementsTreeOutline can be fully integrated into DOMTreeWidget.
@@ -1022,6 +1005,7 @@ export class DOMTreeWidget extends UI.Widget.Widget {
1022
1005
  #rootDOMNode: SDK.DOMModel.DOMNode|null = null;
1023
1006
  #selectedDOMNode: SDK.DOMModel.DOMNode|null = null;
1024
1007
  #expandedNodes = new Set<SDK.DOMModel.DOMNode>();
1008
+ #expandedChildrenLimitByNode = new WeakMap<SDK.DOMModel.DOMNode, number>();
1025
1009
 
1026
1010
  // FIXME: this is not declarative because ElementsTreeOutline can
1027
1011
  // change root node internally.
@@ -1119,6 +1103,10 @@ export class DOMTreeWidget extends UI.Widget.Widget {
1119
1103
  #adoptedStyleSheetExpanded = new WeakMap<SDK.DOMModel.AdoptedStyleSheet, boolean>();
1120
1104
  #selectedAdoptedStyleSheet: SDK.DOMModel.AdoptedStyleSheet|null = null;
1121
1105
 
1106
+ // TODO: Move #imagePreviewPopover and #issuePopoverHelper to the view once declarative versions exist.
1107
+ #imagePreviewPopover?: ImagePreviewPopover;
1108
+ #issuePopoverHelper?: UI.PopoverHelper.PopoverHelper;
1109
+
1122
1110
  #view: View;
1123
1111
  #viewOutput: ViewOutput = {
1124
1112
  highlightedTreeElement: null,
@@ -1145,6 +1133,92 @@ export class DOMTreeWidget extends UI.Widget.Widget {
1145
1133
  SDK.OverlayModel.OverlayModel, SDK.OverlayModel.Events.INSPECT_MODE_WILL_BE_TOGGLED,
1146
1134
  this.#clearHighlightedNode, this, {scoped: true});
1147
1135
  }
1136
+ this.#setupPopovers();
1137
+ }
1138
+
1139
+ // TODO: Move imagePreviewPopover and issuePopoverHelper to the view once declarative versions exist.
1140
+ #setupPopovers(): void {
1141
+ this.#imagePreviewPopover = new ImagePreviewPopover(
1142
+ this.contentElement,
1143
+ event => {
1144
+ let link: (Element|null) = (event.composedPath()[0] as Element | null);
1145
+ while (link && link !== this.contentElement && !ImagePreviewPopover.getImageURL(link)) {
1146
+ link = link.parentElementOrShadowHost();
1147
+ }
1148
+ return (link && link !== this.contentElement) ? link : null;
1149
+ },
1150
+ async link => {
1151
+ const {node} = getElementsTreeWidgetAndNode(link);
1152
+ return await UIComponentUtils.ImagePreview.loadPrecomputedFeatures(node);
1153
+ });
1154
+
1155
+ this.#issuePopoverHelper = new UI.PopoverHelper.PopoverHelper(this.contentElement, event => {
1156
+ const hoveredNode = event.composedPath()[0];
1157
+ if (!(hoveredNode instanceof Element) || !hoveredNode.matches('.violating-element')) {
1158
+ return null;
1159
+ }
1160
+
1161
+ const {node, widget} = getElementsTreeWidgetAndNode(hoveredNode);
1162
+ if (!node) {
1163
+ return null;
1164
+ }
1165
+ let issues = widget?.issues ?? [];
1166
+ if (hoveredNode.classList.contains('webkit-html-attribute-name')) {
1167
+ const attrName = hoveredNode.textContent;
1168
+ issues = issues.filter(issue => getElementIssueDetails(issue)?.attribute === attrName);
1169
+ } else if (hoveredNode.classList.contains('webkit-html-tag-name')) {
1170
+ issues = issues.filter(issue => {
1171
+ const details = getElementIssueDetails(issue);
1172
+ return Boolean(details && !details.attribute);
1173
+ });
1174
+ }
1175
+ if (issues.length === 0) {
1176
+ return null;
1177
+ }
1178
+
1179
+ return {
1180
+ box: hoveredNode.boxInWindow(),
1181
+ show: async (popover: UI.GlassPane.GlassPane) => {
1182
+ popover.setIgnoreLeftMargin(true);
1183
+ // clang-format off
1184
+ render(html`
1185
+ <div class="squiggles-content">
1186
+ ${issues.map(issue => {
1187
+ const elementIssueDetails = getElementIssueDetails(issue);
1188
+ if (!elementIssueDetails) {
1189
+ return nothing;
1190
+ }
1191
+ const issueKindIconName = IssueCounter.IssueCounter.getIssueKindIconName(issue.getKind());
1192
+ const openIssueEvent = (): Promise<void> => Common.Revealer.reveal(issue);
1193
+ return html`
1194
+ <div class="squiggles-content-item">
1195
+ <devtools-icon .name=${issueKindIconName} @click=${openIssueEvent}></devtools-icon>
1196
+ <devtools-link class="link" @click=${openIssueEvent}>${i18nString(UIStrings.viewIssue)}</devtools-link>
1197
+ <span>${elementIssueDetails.tooltip}</span>
1198
+ </div>`;
1199
+ })}
1200
+ </div>`, popover.contentElement);
1201
+ // clang-format on
1202
+ return true;
1203
+ },
1204
+ };
1205
+ }, 'elements.issue');
1206
+ this.#issuePopoverHelper.setTimeout(300);
1207
+ }
1208
+
1209
+ #updateRecords = new Map<SDK.DOMModel.DOMNode, Elements.ElementUpdateRecord.ElementUpdateRecord>();
1210
+
1211
+ updateRecordsForTest(): Map<SDK.DOMModel.DOMNode, Elements.ElementUpdateRecord.ElementUpdateRecord> {
1212
+ return this.#updateRecords;
1213
+ }
1214
+
1215
+ #addUpdateRecord(node: SDK.DOMModel.DOMNode): Elements.ElementUpdateRecord.ElementUpdateRecord {
1216
+ let record = this.#updateRecords.get(node);
1217
+ if (!record) {
1218
+ record = new Elements.ElementUpdateRecord.ElementUpdateRecord();
1219
+ this.#updateRecords.set(node, record);
1220
+ }
1221
+ return record;
1148
1222
  }
1149
1223
 
1150
1224
  #onDocumentUpdated(event: Common.EventTarget.EventTargetEvent<SDK.DOMModel.DOMModel>): void {
@@ -1153,6 +1227,7 @@ export class DOMTreeWidget extends UI.Widget.Widget {
1153
1227
  this.#selectedDOMNode = null;
1154
1228
  this.#expandedNodes.clear();
1155
1229
  this.#currentHighlightedNode = null;
1230
+ this.#updateRecords.clear();
1156
1231
  }
1157
1232
  if (domModel.existingDocument()) {
1158
1233
  this.rootDOMNode = domModel.existingDocument();
@@ -1160,8 +1235,123 @@ export class DOMTreeWidget extends UI.Widget.Widget {
1160
1235
  this.onDocumentUpdated(domModel);
1161
1236
  }
1162
1237
 
1163
- #onDOMNodeChanged(): void {
1164
- this.requestUpdate();
1238
+ #updateModifiedNodesTimeout?: number;
1239
+
1240
+ #updateModifiedNodesSoon(): void {
1241
+ if (!this.#updateRecords.size) {
1242
+ return;
1243
+ }
1244
+ if (this.#updateModifiedNodesTimeout) {
1245
+ return;
1246
+ }
1247
+ this.#updateModifiedNodesTimeout = window.setTimeout(this.updateModifiedNodes.bind(this), 50);
1248
+ }
1249
+
1250
+ #onNodeInserted(event: Common.EventTarget.EventTargetEvent<SDK.DOMModel.DOMNode>): void {
1251
+ const node = event.data;
1252
+ if (node.parentNode) {
1253
+ this.#addUpdateRecord(node.parentNode).nodeInserted(node);
1254
+ }
1255
+ this.#updateModifiedNodesSoon();
1256
+ }
1257
+
1258
+ #onNodeRemoved(
1259
+ event: Common.EventTarget.EventTargetEvent<{node: SDK.DOMModel.DOMNode, parent: SDK.DOMModel.DOMNode}>): void {
1260
+ const {node, parent} = event.data;
1261
+ this.resetClipboardIfNeeded(node);
1262
+ if (parent) {
1263
+ this.#addUpdateRecord(parent).nodeRemoved(node);
1264
+ }
1265
+ this.#updateModifiedNodesSoon();
1266
+ }
1267
+
1268
+ #onAttrModified(event: Common.EventTarget.EventTargetEvent<{node: SDK.DOMModel.DOMNode, name: string}>): void {
1269
+ const {node, name} = event.data;
1270
+ this.#addUpdateRecord(node).attributeModified(name);
1271
+ this.#updateModifiedNodesSoon();
1272
+ }
1273
+
1274
+ #onAttrRemoved(event: Common.EventTarget.EventTargetEvent<{node: SDK.DOMModel.DOMNode, name: string}>): void {
1275
+ const {node, name} = event.data;
1276
+ this.#addUpdateRecord(node).attributeRemoved(name);
1277
+ this.#updateModifiedNodesSoon();
1278
+ }
1279
+
1280
+ #onCharacterDataModified(event: Common.EventTarget.EventTargetEvent<SDK.DOMModel.DOMNode>): void {
1281
+ const node = event.data;
1282
+ this.#addUpdateRecord(node).charDataModified();
1283
+ if (node.parentNode && node.parentNode.firstChild === node.parentNode.lastChild) {
1284
+ this.#addUpdateRecord(node.parentNode).childrenModified();
1285
+ }
1286
+ this.#updateModifiedNodesSoon();
1287
+ }
1288
+
1289
+ #onChildNodeCountUpdated(event: Common.EventTarget.EventTargetEvent<SDK.DOMModel.DOMNode>): void {
1290
+ const node = event.data;
1291
+ this.#addUpdateRecord(node).childrenModified();
1292
+ this.#updateModifiedNodesSoon();
1293
+ }
1294
+
1295
+ #onAdoptedStyleSheetsModified(event: Common.EventTarget.EventTargetEvent<SDK.DOMModel.DOMNode>): void {
1296
+ const node = event.data;
1297
+ this.#addUpdateRecord(node).childrenModified();
1298
+ this.#updateModifiedNodesSoon();
1299
+ }
1300
+
1301
+ #onDistributedNodesChanged(event: Common.EventTarget.EventTargetEvent<SDK.DOMModel.DOMNode>): void {
1302
+ const node = event.data;
1303
+ this.#addUpdateRecord(node).childrenModified();
1304
+ this.#updateModifiedNodesSoon();
1305
+ }
1306
+
1307
+ #onDocumentURLChanged(event: Common.EventTarget.EventTargetEvent<SDK.DOMModel.DOMDocument>): void {
1308
+ this.#addUpdateRecord(event.data).charDataModified();
1309
+ this.#updateModifiedNodesSoon();
1310
+ }
1311
+
1312
+ #onMarkersChanged(): void {
1313
+ this.#updateModifiedNodesSoon();
1314
+ }
1315
+
1316
+ updateModifiedNodes(): void {
1317
+ if (this.#view === DECLARATIVE_VIEW) {
1318
+ this.performUpdate();
1319
+ return;
1320
+ }
1321
+ this.#viewOutput.elementsTreeOutline?.updateModifiedNodes();
1322
+ }
1323
+
1324
+ #wireDOMModel(domModel: SDK.DOMModel.DOMModel): void {
1325
+ domModel.addEventListener(SDK.DOMModel.Events.DocumentUpdated, this.#onDocumentUpdated, this);
1326
+ domModel.addEventListener(SDK.DOMModel.Events.NodeInserted, this.#onNodeInserted, this);
1327
+ domModel.addEventListener(SDK.DOMModel.Events.NodeRemoved, this.#onNodeRemoved, this);
1328
+ domModel.addEventListener(SDK.DOMModel.Events.AttrModified, this.#onAttrModified, this);
1329
+ domModel.addEventListener(SDK.DOMModel.Events.AttrRemoved, this.#onAttrRemoved, this);
1330
+ domModel.addEventListener(SDK.DOMModel.Events.CharacterDataModified, this.#onCharacterDataModified, this);
1331
+ domModel.addEventListener(SDK.DOMModel.Events.ChildNodeCountUpdated, this.#onChildNodeCountUpdated, this);
1332
+ domModel.addEventListener(SDK.DOMModel.Events.MarkersChanged, this.#onMarkersChanged, this);
1333
+ domModel.addEventListener(SDK.DOMModel.Events.AdoptedStyleSheetsModified, this.#onAdoptedStyleSheetsModified, this);
1334
+ domModel.addEventListener(SDK.DOMModel.Events.TopLayerElementsChanged, this.#onTopLayerElementsChanged, this);
1335
+ domModel.addEventListener(SDK.DOMModel.Events.DistributedNodesChanged, this.#onDistributedNodesChanged, this);
1336
+ domModel.addEventListener(SDK.DOMModel.Events.DocumentURLChanged, this.#onDocumentURLChanged, this);
1337
+ domModel.addEventListener(SDK.DOMModel.Events.AffectedByStartingStylesFlagUpdated, this.requestUpdate, this);
1338
+ }
1339
+
1340
+ #unwireDOMModel(domModel: SDK.DOMModel.DOMModel): void {
1341
+ domModel.removeEventListener(SDK.DOMModel.Events.DocumentUpdated, this.#onDocumentUpdated, this);
1342
+ domModel.removeEventListener(SDK.DOMModel.Events.NodeInserted, this.#onNodeInserted, this);
1343
+ domModel.removeEventListener(SDK.DOMModel.Events.NodeRemoved, this.#onNodeRemoved, this);
1344
+ domModel.removeEventListener(SDK.DOMModel.Events.AttrModified, this.#onAttrModified, this);
1345
+ domModel.removeEventListener(SDK.DOMModel.Events.AttrRemoved, this.#onAttrRemoved, this);
1346
+ domModel.removeEventListener(SDK.DOMModel.Events.CharacterDataModified, this.#onCharacterDataModified, this);
1347
+ domModel.removeEventListener(SDK.DOMModel.Events.ChildNodeCountUpdated, this.#onChildNodeCountUpdated, this);
1348
+ domModel.removeEventListener(SDK.DOMModel.Events.MarkersChanged, this.#onMarkersChanged, this);
1349
+ domModel.removeEventListener(SDK.DOMModel.Events.AdoptedStyleSheetsModified, this.#onAdoptedStyleSheetsModified,
1350
+ this);
1351
+ domModel.removeEventListener(SDK.DOMModel.Events.TopLayerElementsChanged, this.#onTopLayerElementsChanged, this);
1352
+ domModel.removeEventListener(SDK.DOMModel.Events.DistributedNodesChanged, this.#onDistributedNodesChanged, this);
1353
+ domModel.removeEventListener(SDK.DOMModel.Events.DocumentURLChanged, this.#onDocumentURLChanged, this);
1354
+ domModel.removeEventListener(SDK.DOMModel.Events.AffectedByStartingStylesFlagUpdated, this.requestUpdate, this);
1165
1355
  }
1166
1356
 
1167
1357
  #onShowHTMLCommentsChange(): void {
@@ -1192,30 +1382,48 @@ export class DOMTreeWidget extends UI.Widget.Widget {
1192
1382
  }
1193
1383
 
1194
1384
  selectDOMNode(node: SDK.DOMModel.DOMNode|SDK.DOMModel.AdoptedStyleSheet|null, focus?: boolean): void {
1385
+ this.#imagePreviewPopover?.hide();
1386
+ this.#issuePopoverHelper?.hidePopover();
1195
1387
  if (node instanceof SDK.DOMModel.AdoptedStyleSheet) {
1196
1388
  this.highlightAdoptedStyleSheet(node);
1197
1389
  return;
1198
1390
  }
1199
1391
  this.#selectedAdoptedStyleSheet = null;
1200
1392
  if (this.#view === DECLARATIVE_VIEW) {
1201
- if (this.#selectedDOMNode === node) {
1202
- return;
1203
- }
1393
+ const isSameNode = this.#selectedDOMNode === node;
1204
1394
  this.#selectedDOMNode = node;
1205
1395
  if (node) {
1396
+ const ancestors: SDK.DOMModel.DOMNode[] = [];
1206
1397
  for (let current: (SDK.DOMModel.DOMNode|null) = node.parentNode; current; current = current.parentNode) {
1207
- this.#expandedNodes.add(current);
1208
- if (!current.children() && current.childNodeCount()) {
1209
- void current.getChildNodes(() => {
1398
+ ancestors.unshift(current);
1399
+ }
1400
+ for (let i = 0; i < ancestors.length; ++i) {
1401
+ const ancestor = ancestors[i];
1402
+ const child = ancestors[i + 1] || node;
1403
+ this.#expandedNodes.add(ancestor);
1404
+ const visibleChildren = getVisibleChildren(ancestor, this.#showComments);
1405
+ const childIndex = visibleChildren.indexOf(child);
1406
+ if (childIndex !== -1 && childIndex >= this.expandedChildrenLimit(ancestor)) {
1407
+ this.setExpandedChildrenLimit(ancestor, childIndex + 1);
1408
+ }
1409
+ if (!ancestor.children() && ancestor.childNodeCount()) {
1410
+ void ancestor.getChildNodes(() => {
1411
+ const visibleChildren = getVisibleChildren(ancestor, this.#showComments);
1412
+ const childIndex = visibleChildren.indexOf(child);
1413
+ if (childIndex !== -1 && childIndex >= this.expandedChildrenLimit(ancestor)) {
1414
+ this.setExpandedChildrenLimit(ancestor, childIndex + 1);
1415
+ }
1210
1416
  this.performUpdate();
1211
1417
  });
1212
1418
  }
1213
1419
  }
1214
1420
  }
1215
1421
  this.#clearHighlightedNode();
1216
- this.onSelectedNodeChanged(
1217
- {data: {node, focus: Boolean(focus)}} as
1218
- Common.EventTarget.EventTargetEvent<{node: SDK.DOMModel.DOMNode | null, focus: boolean}>);
1422
+ if (!isSameNode) {
1423
+ this.onSelectedNodeChanged(
1424
+ {data: {node, focus: Boolean(focus)}} as
1425
+ Common.EventTarget.EventTargetEvent<{node: SDK.DOMModel.DOMNode | null, focus: boolean}>);
1426
+ }
1219
1427
  this.performUpdate();
1220
1428
  return;
1221
1429
  }
@@ -1281,6 +1489,30 @@ export class DOMTreeWidget extends UI.Widget.Widget {
1281
1489
  return Boolean(this.#viewOutput.elementsTreeOutline?.findTreeElement(node)?.expanded);
1282
1490
  }
1283
1491
 
1492
+ expandedChildrenLimit(node: SDK.DOMModel.DOMNode): number {
1493
+ return this.#expandedChildrenLimitByNode.get(node) ?? InitialChildrenLimit;
1494
+ }
1495
+
1496
+ setExpandedChildrenLimit(node: SDK.DOMModel.DOMNode, limit: number): void {
1497
+ if (this.expandedChildrenLimit(node) === limit) {
1498
+ return;
1499
+ }
1500
+ this.#expandedChildrenLimitByNode.set(node, limit);
1501
+ if (this.#viewOutput.elementsTreeOutline) {
1502
+ const treeElement = this.#viewOutput.elementsTreeOutline.findTreeElement(node);
1503
+ if (treeElement && treeElement.expandedChildrenLimit() !== limit) {
1504
+ this.#viewOutput.elementsTreeOutline.setExpandedChildrenLimit(treeElement, limit);
1505
+ }
1506
+ }
1507
+ this.performUpdate();
1508
+ }
1509
+
1510
+ expandAllChildren(node: SDK.DOMModel.DOMNode): void {
1511
+ const currentLimit = this.expandedChildrenLimit(node);
1512
+ const visibleChildren = getVisibleChildren(node, this.#showComments);
1513
+ this.setExpandedChildrenLimit(node, Math.max(visibleChildren.length, currentLimit + InitialChildrenLimit));
1514
+ }
1515
+
1284
1516
  async expandRecursively(node: SDK.DOMModel.DOMNode, maxDepth = Number.MAX_VALUE): Promise<void> {
1285
1517
  if (this.#view === DECLARATIVE_VIEW) {
1286
1518
  await node.getSubtree(100, true);
@@ -1405,7 +1637,12 @@ export class DOMTreeWidget extends UI.Widget.Widget {
1405
1637
  }
1406
1638
 
1407
1639
  override performUpdate(): void {
1640
+ if (this.#updateModifiedNodesTimeout) {
1641
+ clearTimeout(this.#updateModifiedNodesTimeout);
1642
+ this.#updateModifiedNodesTimeout = undefined;
1643
+ }
1408
1644
  const firstRender = !this.#viewOutput.elementsTreeOutline;
1645
+ const updatedNodes = this.#view === DECLARATIVE_VIEW ? [...this.#updateRecords.keys()] : [];
1409
1646
  this.#view({
1410
1647
  domTreeWidget: this,
1411
1648
  rootDOMNode: this.#rootDOMNode,
@@ -1548,8 +1785,17 @@ export class DOMTreeWidget extends UI.Widget.Widget {
1548
1785
  this.selectDOMNode(null);
1549
1786
  this.performUpdate();
1550
1787
  },
1788
+ expandedChildrenLimit: (node: SDK.DOMModel.DOMNode) => this.expandedChildrenLimit(node),
1789
+ onExpandAllChildren: (node: SDK.DOMModel.DOMNode) => this.expandAllChildren(node),
1790
+ updateRecordForNode: (node: SDK.DOMModel.DOMNode) => this.#updateRecords.get(node) ?? null,
1551
1791
  },
1552
1792
  this.#viewOutput, this.contentElement);
1793
+ if (this.#view === DECLARATIVE_VIEW) {
1794
+ this.#updateRecords.clear();
1795
+ if (updatedNodes.length > 0) {
1796
+ this.onElementsTreeUpdated({data: updatedNodes} as Common.EventTarget.EventTargetEvent<SDK.DOMModel.DOMNode[]>);
1797
+ }
1798
+ }
1553
1799
  if (this.#viewOutput.elementsTreeOutline) {
1554
1800
  this.#viewOutput.elementsTreeOutline.domTreeWidget = this;
1555
1801
  }
@@ -1564,16 +1810,7 @@ export class DOMTreeWidget extends UI.Widget.Widget {
1564
1810
  if (this.#view === DECLARATIVE_VIEW) {
1565
1811
  if (!this.#wiredDOMModels.has(domModel)) {
1566
1812
  this.#wiredDOMModels.add(domModel);
1567
- domModel.addEventListener(SDK.DOMModel.Events.DocumentUpdated, this.#onDocumentUpdated, this);
1568
- domModel.addEventListener(SDK.DOMModel.Events.NodeInserted, this.#onDOMNodeChanged, this);
1569
- domModel.addEventListener(SDK.DOMModel.Events.NodeRemoved, this.#onDOMNodeChanged, this);
1570
- domModel.addEventListener(SDK.DOMModel.Events.AttrModified, this.#onDOMNodeChanged, this);
1571
- domModel.addEventListener(SDK.DOMModel.Events.AttrRemoved, this.#onDOMNodeChanged, this);
1572
- domModel.addEventListener(SDK.DOMModel.Events.CharacterDataModified, this.#onDOMNodeChanged, this);
1573
- domModel.addEventListener(SDK.DOMModel.Events.ChildNodeCountUpdated, this.#onDOMNodeChanged, this);
1574
- domModel.addEventListener(SDK.DOMModel.Events.MarkersChanged, this.#onDOMNodeChanged, this);
1575
- domModel.addEventListener(SDK.DOMModel.Events.AdoptedStyleSheetsModified, this.#onDOMNodeChanged, this);
1576
- domModel.addEventListener(SDK.DOMModel.Events.TopLayerElementsChanged, this.#onTopLayerElementsChanged, this);
1813
+ this.#wireDOMModel(domModel);
1577
1814
  }
1578
1815
  if (this.isShowing() && !domModel.parentModel() &&
1579
1816
  (!this.rootDOMNode || this.rootDOMNode.domModel() !== domModel)) {
@@ -1604,17 +1841,7 @@ export class DOMTreeWidget extends UI.Widget.Widget {
1604
1841
  if (this.#view === DECLARATIVE_VIEW) {
1605
1842
  if (this.#wiredDOMModels.has(domModel)) {
1606
1843
  this.#wiredDOMModels.delete(domModel);
1607
- domModel.removeEventListener(SDK.DOMModel.Events.DocumentUpdated, this.#onDocumentUpdated, this);
1608
- domModel.removeEventListener(SDK.DOMModel.Events.NodeInserted, this.#onDOMNodeChanged, this);
1609
- domModel.removeEventListener(SDK.DOMModel.Events.NodeRemoved, this.#onDOMNodeChanged, this);
1610
- domModel.removeEventListener(SDK.DOMModel.Events.AttrModified, this.#onDOMNodeChanged, this);
1611
- domModel.removeEventListener(SDK.DOMModel.Events.AttrRemoved, this.#onDOMNodeChanged, this);
1612
- domModel.removeEventListener(SDK.DOMModel.Events.CharacterDataModified, this.#onDOMNodeChanged, this);
1613
- domModel.removeEventListener(SDK.DOMModel.Events.ChildNodeCountUpdated, this.#onDOMNodeChanged, this);
1614
- domModel.removeEventListener(SDK.DOMModel.Events.MarkersChanged, this.#onDOMNodeChanged, this);
1615
- domModel.removeEventListener(SDK.DOMModel.Events.AdoptedStyleSheetsModified, this.#onDOMNodeChanged, this);
1616
- domModel.removeEventListener(SDK.DOMModel.Events.TopLayerElementsChanged, this.#onTopLayerElementsChanged,
1617
- this);
1844
+ this.#unwireDOMModel(domModel);
1618
1845
  }
1619
1846
  this.performUpdate();
1620
1847
  return;
@@ -1698,8 +1925,10 @@ export class DOMTreeWidget extends UI.Widget.Widget {
1698
1925
  }
1699
1926
 
1700
1927
  runPendingUpdates(): void {
1701
- this.#viewOutput.elementsTreeOutline?.runPendingUpdates();
1702
- this.performUpdate();
1928
+ this.updateModifiedNodes();
1929
+ if (this.#view !== DECLARATIVE_VIEW) {
1930
+ this.performUpdate();
1931
+ }
1703
1932
  }
1704
1933
 
1705
1934
  override onResize(): void {
@@ -1711,6 +1940,15 @@ export class DOMTreeWidget extends UI.Widget.Widget {
1711
1940
 
1712
1941
  override willHide(): void {
1713
1942
  super.willHide();
1943
+ this.#imagePreviewPopover?.hide();
1944
+ this.#issuePopoverHelper?.hidePopover();
1945
+ if (this.#updateModifiedNodesTimeout) {
1946
+ clearTimeout(this.#updateModifiedNodesTimeout);
1947
+ this.#updateModifiedNodesTimeout = undefined;
1948
+ }
1949
+ if (this.#view === DECLARATIVE_VIEW) {
1950
+ this.#updateRecords.clear();
1951
+ }
1714
1952
  if (this.#multilineEditing) {
1715
1953
  this.#multilineEditing.cancel();
1716
1954
  }
@@ -2316,7 +2554,13 @@ export class DOMTreeWidget extends UI.Widget.Widget {
2316
2554
  override wasHidden(): void {
2317
2555
  super.wasHidden();
2318
2556
  this.#visible = false;
2319
- this.#viewOutput.imagePreviewPopover?.hide();
2557
+ if (this.#updateModifiedNodesTimeout) {
2558
+ clearTimeout(this.#updateModifiedNodesTimeout);
2559
+ this.#updateModifiedNodesTimeout = undefined;
2560
+ }
2561
+ if (this.#view === DECLARATIVE_VIEW) {
2562
+ this.#updateRecords.clear();
2563
+ }
2320
2564
  this.performUpdate();
2321
2565
  }
2322
2566
 
@@ -2324,10 +2568,28 @@ export class DOMTreeWidget extends UI.Widget.Widget {
2324
2568
  super.detach(overrideHideOnDetach);
2325
2569
  this.#visible = false;
2326
2570
  this.#showHTMLCommentsSetting.removeChangeListener(this.#onShowHTMLCommentsChange, this);
2327
- this.#viewOutput.imagePreviewPopover?.hide();
2571
+ if (this.#updateModifiedNodesTimeout) {
2572
+ clearTimeout(this.#updateModifiedNodesTimeout);
2573
+ this.#updateModifiedNodesTimeout = undefined;
2574
+ }
2575
+ if (this.#view === DECLARATIVE_VIEW) {
2576
+ this.#updateRecords.clear();
2577
+ }
2328
2578
  this.performUpdate();
2329
2579
  }
2330
2580
 
2581
+ hideImagePreview(): void {
2582
+ this.#imagePreviewPopover?.hide();
2583
+ }
2584
+
2585
+ imagePreviewPopoverForTest(): ImagePreviewPopover|undefined {
2586
+ return this.#imagePreviewPopover;
2587
+ }
2588
+
2589
+ issuePopoverHelperForTest(): UI.PopoverHelper.PopoverHelper|undefined {
2590
+ return this.#issuePopoverHelper;
2591
+ }
2592
+
2331
2593
  override show(parentElement: Element, insertBefore?: Node|null, suppressOrphanWidgetError = false): void {
2332
2594
  this.performUpdate();
2333
2595
  const domModels = SDK.TargetManager.TargetManager.instance().models(SDK.DOMModel.DOMModel, {scoped: true});
@@ -2355,9 +2617,13 @@ export class DOMTreeWidget extends UI.Widget.Widget {
2355
2617
  }
2356
2618
  }
2357
2619
 
2358
- export class ElementsTreeOutline extends
2359
- Common.ObjectWrapper.eventMixin<ElementsTreeOutline.EventTypes, typeof UI.TreeOutline.TreeOutline>(
2360
- UI.TreeOutline.TreeOutline) {
2620
+ const ElementsTreeOutlineBase:
2621
+ Common.ObjectWrapper.EventMixin<ElementsTreeOutline.EventTypes, typeof UI.TreeOutline.TreeOutline> =
2622
+ Common.ObjectWrapper.eventMixin(
2623
+ UI.TreeOutline.TreeOutline,
2624
+ );
2625
+
2626
+ export class ElementsTreeOutline extends ElementsTreeOutlineBase {
2361
2627
  treeElementByNode: WeakMap<SDK.DOMModel.DOMNode, ElementsTreeElement>;
2362
2628
  private readonly shadowRoot: ShadowRoot;
2363
2629
  readonly elementInternal: HTMLElement;
@@ -3232,6 +3498,9 @@ export class ElementsTreeOutline extends
3232
3498
  return new AdoptedStyleSheetSetTreeElement(node);
3233
3499
  }
3234
3500
  const treeElement = new ElementsTreeElement(node, isClosingTag);
3501
+ if (this.domTreeWidget) {
3502
+ treeElement.setExpandedChildrenLimit(this.domTreeWidget.expandedChildrenLimit(node));
3503
+ }
3235
3504
  treeElement.setExpandable(!isClosingTag && this.hasVisibleChildren(node));
3236
3505
  if (node.nodeType() === Node.ELEMENT_NODE && node.parentNode && node.parentNode.nodeType() === Node.DOCUMENT_NODE &&
3237
3506
  !node.parentNode.parentNode) {
@@ -3373,6 +3642,9 @@ export class ElementsTreeOutline extends
3373
3642
  }
3374
3643
 
3375
3644
  treeElement.setExpandedChildrenLimit(expandedChildrenLimit);
3645
+ if (this.domTreeWidget && this.domTreeWidget.expandedChildrenLimit(treeElement.node()) !== expandedChildrenLimit) {
3646
+ this.domTreeWidget.setExpandedChildrenLimit(treeElement.node(), expandedChildrenLimit);
3647
+ }
3376
3648
  if (treeElement.treeOutline && !this.treeElementsBeingUpdated.has(treeElement)) {
3377
3649
  this.updateModifiedParentNode(treeElement.node());
3378
3650
  }