chrome-devtools-frontend 1.0.1697595 → 1.0.1697998

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 (39) hide show
  1. package/.stylelintignore +2 -0
  2. package/front_end/core/common/VersionController.ts +5 -1
  3. package/front_end/core/sdk/AnimationModel.ts +9 -1
  4. package/front_end/generated/InspectorBackendCommands.ts +1 -1
  5. package/front_end/generated/protocol.ts +8 -2
  6. package/front_end/models/ai_assistance/agents/ContextSelectionAgent.ts +26 -0
  7. package/front_end/models/ai_assistance/ai_assistance.ts +4 -4
  8. package/front_end/models/ai_assistance/skills/performance.md +4 -3
  9. package/front_end/models/ai_assistance/tools/{GetFunctionCode.ts → GetTraceFunctionCode.ts} +26 -13
  10. package/front_end/models/ai_assistance/tools/{GetResourceContent.ts → GetTraceResourceContent.ts} +31 -11
  11. package/front_end/models/ai_assistance/tools/Tool.ts +2 -2
  12. package/front_end/models/ai_assistance/tools/ToolRegistry.ts +6 -6
  13. package/front_end/models/elements/DOMChanges.ts +238 -0
  14. package/front_end/models/elements/elements.ts +2 -0
  15. package/front_end/panels/ai_assistance/AiAssistancePanel.ts +3 -1
  16. package/front_end/panels/ai_assistance/components/MarkdownRendererWithCodeBlock.ts +25 -5
  17. package/front_end/panels/browser_debugger/ObjectEventListenersSidebarPane.ts +2 -3
  18. package/front_end/panels/elements/ElementsTreeElement.ts +98 -20
  19. package/front_end/panels/elements/ElementsTreeOutline.ts +271 -43
  20. package/front_end/panels/elements/EventListenersWidget.ts +0 -1
  21. package/front_end/panels/event_listeners/EventListenersView.ts +241 -297
  22. package/front_end/panels/linear_memory_inspector/LinearMemoryInspectorController.ts +1 -1
  23. package/front_end/panels/mobile_throttling/ThrottlingSettingsTab.ts +10 -2
  24. package/front_end/panels/network/NetworkConfigView.ts +141 -116
  25. package/front_end/panels/network/NetworkPanel.ts +7 -4
  26. package/front_end/panels/network/RequestHeadersView.ts +1 -1
  27. package/front_end/panels/network/forward/BackendLinking.ts +5 -2
  28. package/front_end/panels/settings/BackendLinkingSettingsTab.ts +7 -2
  29. package/front_end/panels/timeline/TimelineTreeView.ts +11 -1
  30. package/front_end/third_party/chromium/README.chromium +1 -1
  31. package/front_end/ui/comments/CommentOverlayManager.ts +169 -40
  32. package/front_end/ui/comments/CommentThreadWidget.ts +221 -0
  33. package/front_end/ui/comments/CommentsOverlayWidget.ts +124 -12
  34. package/front_end/ui/comments/commentThreadWidget.css +173 -0
  35. package/front_end/ui/comments/comments.ts +2 -0
  36. package/front_end/ui/comments/commentsOverlay.css +6 -0
  37. package/front_end/ui/legacy/components/object_ui/ObjectPropertiesSection.ts +1 -1
  38. package/front_end/ui/legacy/components/perf_ui/FlameChart.ts +4 -0
  39. package/package.json +5 -5
@@ -39,6 +39,7 @@ import * as Host from '../../core/host/host.js';
39
39
  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
+ import * as ChangeTracker from '../../models/change_tracker/change_tracker.js';
42
43
  import * as Elements from '../../models/elements/elements.js';
43
44
  import * as Buttons from '../../ui/components/buttons/buttons.js';
44
45
  import * as CodeHighlighter from '../../ui/components/code_highlighter/code_highlighter.js';
@@ -60,6 +61,7 @@ import {showContextMenu} from './DOMTreeContextMenu.js';
60
61
  import {getElementIssueDetails} from './ElementIssueUtils.js';
61
62
  import {
62
63
  adornerRef,
64
+ buildChangeSelector,
63
65
  ElementsTreeElement,
64
66
  ElementsTreeWidget,
65
67
  ForbiddenClosingTagElements,
@@ -73,7 +75,7 @@ import {ImagePreviewPopover} from './ImagePreviewPopover.js';
73
75
  import {ShortcutTreeElement} from './ShortcutTreeElement.js';
74
76
  import {TopLayerContainer} from './TopLayerContainer.js';
75
77
 
76
- const {html, nothing, render, Directives: {classMap, styleMap}} = Lit;
78
+ const {html, nothing, render, Directives: {classMap, repeat, styleMap}} = Lit;
77
79
 
78
80
  const UIStrings = {
79
81
  /**
@@ -832,12 +834,12 @@ export const DECLARATIVE_VIEW: View = (input: ViewInput, _output: ViewOutput, ta
832
834
 
833
835
  const on = Lit.Directive.directive(Lit.CustomDirectives.InterceptBindingDirective);
834
836
 
835
- const onSelect = (isClosingTag = false): void => {
836
- input.onSelect?.(node, isClosingTag, /* selectedByUser= */ true);
837
+ const onSelect = (isClosingTag = false, selectedByUser = false): void => {
838
+ input.onSelect?.(node, isClosingTag, selectedByUser);
837
839
  };
838
840
 
839
841
  const onExpand = (event: UI.TreeOutline.TreeViewElement.ExpandEvent): void => {
840
- if (isEditingAsHTML) {
842
+ if (isEditingAsHTML || !isCollapsible) {
841
843
  return;
842
844
  }
843
845
  input.onExpand?.(node, event.detail.expanded);
@@ -848,6 +850,7 @@ export const DECLARATIVE_VIEW: View = (input: ViewInput, _output: ViewOutput, ta
848
850
  const isDraggable = !input.disableEdits && Boolean(input.isValidDragSource?.(node));
849
851
 
850
852
  const classes = classMap({
853
+ selected: isSelected && !input.selectedClosingTag,
851
854
  hovered: isOpeningHovered,
852
855
  'in-clipboard': Boolean(input.isNodeInClipboard?.(node)),
853
856
  'elements-drag-over': isDragOver,
@@ -855,13 +858,19 @@ export const DECLARATIVE_VIEW: View = (input: ViewInput, _output: ViewOutput, ta
855
858
  });
856
859
 
857
860
  const onOpeningMouseMove = (event: MouseEvent): void => {
858
- event.stopPropagation();
861
+ const currentTarget = event.currentTarget as Element | null;
862
+ if (currentTarget && (event.target as Element).closest('li[role="treeitem"]') !== currentTarget) {
863
+ return;
864
+ }
859
865
  const showInfo = !UI.KeyboardShortcut.KeyboardShortcut.eventHasEitherCtrlOrMeta(event);
860
866
  input.onHoverNode?.(node, showInfo, /* isClosingTag= */ false);
861
867
  };
862
868
 
863
869
  const onClosingMouseMove = (event: MouseEvent): void => {
864
- event.stopPropagation();
870
+ const currentTarget = event.currentTarget as Element | null;
871
+ if (currentTarget && (event.target as Element).closest('li[role="treeitem"]') !== currentTarget) {
872
+ return;
873
+ }
865
874
  const showInfo = !UI.KeyboardShortcut.KeyboardShortcut.eventHasEitherCtrlOrMeta(event);
866
875
  input.onHoverNode?.(node, showInfo, /* isClosingTag= */ true);
867
876
  };
@@ -916,8 +925,8 @@ export const DECLARATIVE_VIEW: View = (input: ViewInput, _output: ViewOutput, ta
916
925
  style=${styleMap({'--indent': `${computeLeftIndent(depth, isExpandable)}px`})}
917
926
  ?open=${isExpanded && !isEditingAsHTML}
918
927
  draggable=${isDraggable ? 'true' : 'false'}
919
- @select=${on(() => onSelect(false))}
920
- @expand=${onExpand}
928
+ @select=${on((event: UI.TreeOutline.TreeViewElement.SelectEvent) => onSelect(false, Boolean(event.detail?.selectedByUser)))}
929
+ @expand=${on(onExpand)}
921
930
  @mousemove=${on(onOpeningMouseMove)}
922
931
  @contextmenu=${on(onContextMenu)}
923
932
  @dragstart=${on(onDragStart)}
@@ -978,7 +987,7 @@ export const DECLARATIVE_VIEW: View = (input: ViewInput, _output: ViewOutput, ta
978
987
  <ul role="group">
979
988
  ${UI.TreeOutline.ifExpanded(html`
980
989
  ${node.adoptedStyleSheetsForNode.length > 0 ? renderAdoptedStyleSheets(node, depth + 1) : nothing}
981
- ${children.map(child => renderNode(child, depth + 1))}
990
+ ${repeat(children, child => child.id, child => renderNode(child, depth + 1))}
982
991
  ${remainingChildrenCount > 0 ? html`
983
992
  <li role="treeitem"
984
993
  class="elements-tree-expand-all"
@@ -995,15 +1004,21 @@ export const DECLARATIVE_VIEW: View = (input: ViewInput, _output: ViewOutput, ta
995
1004
  </devtools-button>
996
1005
  </li>
997
1006
  ` : nothing}
1007
+ ${node.isInsertionPoint() ? node.distributedNodes().map(distributedNode => renderShortcut(distributedNode, depth + 1)) : nothing}
998
1008
  ${node instanceof SDK.DOMModel.DOMDocument ? renderTopLayerContainer(node, depth + 1) : nothing}
999
1009
  ${needsClosingTag ? html`
1000
1010
  <li role="treeitem"
1001
1011
  ?selected=${isSelected && Boolean(input.selectedClosingTag)}
1002
- class=${classMap({hovered: isClosingHovered, 'elements-drag-over': isClosingTagDragOver})}
1012
+ class=${classMap({
1013
+ selected: isSelected && Boolean(input.selectedClosingTag),
1014
+ hovered: isClosingHovered,
1015
+ 'elements-drag-over': isClosingTagDragOver,
1016
+ })}
1003
1017
  style=${styleMap({'--indent': `${computeLeftIndent(depth + 1, false)}px`})}
1004
1018
  draggable=${isDraggable ? 'true' : 'false'}
1005
1019
  jslog=${VisualLogging.treeItem().parent('elementsTreeOutline')}
1006
- @select=${on(() => onSelect(true))}
1020
+ @select=${on((event: UI.TreeOutline.TreeViewElement.SelectEvent) => onSelect(true, Boolean(event.detail?.selectedByUser)))}
1021
+ @contextmenu=${on(onContextMenu)}
1007
1022
  @mousemove=${on(onClosingMouseMove)}
1008
1023
  @dragstart=${on(onDragStart)}
1009
1024
  @dragover=${on(onClosingTagDragOver)}
@@ -1062,7 +1077,6 @@ export const DECLARATIVE_VIEW: View = (input: ViewInput, _output: ViewOutput, ta
1062
1077
  <devtools-tree
1063
1078
  class="elements-tree-outline source-code ${input.wrap ? '' : 'elements-tree-nowrap'} ${input.hideGutter ? 'elements-hide-gutter' : ''} ${isSingleNode ? 'single-node' : ''}"
1064
1079
  disclosure-class="elements-disclosure ${isSingleNode ? 'single-node' : ''} ${input.maxRowsShown ? 'elements-tree-truncated' : ''}"
1065
- aria-label=${i18nString(UIStrings.pageDom)}
1066
1080
  jslog=${VisualLogging.tree('elements')}
1067
1081
  ?show-selection-on-keyboard-focus=${input.showSelectionOnKeyboardFocus}
1068
1082
  @enter=${(event: Event) => event.preventDefault()}
@@ -1075,10 +1089,10 @@ export const DECLARATIVE_VIEW: View = (input: ViewInput, _output: ViewOutput, ta
1075
1089
  <style>${UI.inspectorCommonStyles}</style>
1076
1090
  <style>${elementsTreeOutlineStyles}</style>
1077
1091
  <style>${CodeHighlighter.codeHighlighterStyles}</style>
1078
- <ul role="tree">
1092
+ <ul role="tree" class="elements-tree-outline source-code ${input.wrap ? '' : 'elements-tree-nowrap'} ${input.hideGutter ? 'elements-hide-gutter' : ''} ${isSingleNode ? 'single-node' : ''}" aria-label=${i18nString(UIStrings.pageDom)}>
1079
1093
  ${input.omitRootDOMNode && input.rootDOMNode && input.rootDOMNode.adoptedStyleSheetsForNode.length > 0 ?
1080
1094
  renderAdoptedStyleSheets(input.rootDOMNode, 0) : nothing}
1081
- ${rootNodes.map(node => renderNode(node))}
1095
+ ${repeat(rootNodes, node => node.id, node => renderNode(node))}
1082
1096
  ${input.omitRootDOMNode && input.rootDOMNode ? (() => {
1083
1097
  const remaining = allRootNodes.length - rootNodes.length;
1084
1098
  if (remaining <= 0) {
@@ -1091,12 +1105,12 @@ export const DECLARATIVE_VIEW: View = (input: ViewInput, _output: ViewOutput, ta
1091
1105
  style=${styleMap({'--indent': `${computeLeftIndent(0, false)}px`})}
1092
1106
  jslog=${VisualLogging.treeItem('show-all-nodes').parent('elementsTreeOutline')}>
1093
1107
  <devtools-button
1094
- .variant=${Buttons.Button.Variant.OUTLINED}
1095
- title=${i18nString(UIStrings.showAllNodesDMore, {PH1: remaining})}
1096
- @click=${on((event: Event) => {
1097
- event.stopPropagation();
1098
- input.onExpandAllChildren?.(root);
1099
- })}>
1108
+ .variant=${Buttons.Button.Variant.OUTLINED}
1109
+ title=${i18nString(UIStrings.showAllNodesDMore, {PH1: remaining})}
1110
+ @click=${on((event: Event) => {
1111
+ event.stopPropagation();
1112
+ input.onExpandAllChildren?.(root);
1113
+ })}>
1100
1114
  ${i18nString(UIStrings.showAllNodesDMore, {PH1: remaining})}
1101
1115
  </devtools-button>
1102
1116
  </li>
@@ -1154,6 +1168,8 @@ function getElementsTreeWidgetAndNode(element: Element): {node?: SDK.DOMModel.DO
1154
1168
  * to DOMTreeWidget.ts.
1155
1169
  */
1156
1170
  export class DOMTreeWidget extends UI.Widget.Widget {
1171
+ static override readonly INJECT: readonly[typeof ChangeTracker.ChangeTracker.ChangeTracker] =
1172
+ [ChangeTracker.ChangeTracker.ChangeTracker] as const;
1157
1173
  omitRootDOMNode = false;
1158
1174
  selectEnabled = false;
1159
1175
  hideGutter = false;
@@ -1179,6 +1195,7 @@ export class DOMTreeWidget extends UI.Widget.Widget {
1179
1195
  #visibleWidth?: number;
1180
1196
  #wrap = false;
1181
1197
  #maxRows?: number;
1198
+ #changeTracker?: ChangeTracker.ChangeTracker.ChangeTracker;
1182
1199
 
1183
1200
  // If maxRows is undefined, all rows are shown. If it is set to a number, only that many rows are shown.
1184
1201
  set maxRows(maxRows: number|undefined) {
@@ -1210,9 +1227,11 @@ export class DOMTreeWidget extends UI.Widget.Widget {
1210
1227
  }
1211
1228
  this.#expandedNodes.add(node);
1212
1229
  const expandChildren = (): void => {
1213
- if (this.#expandRoot && this.omitRootDOMNode && node.children()) {
1214
- for (const child of node.children() ?? []) {
1215
- this.#expandedNodes.add(child);
1230
+ for (const child of node.children() ?? []) {
1231
+ const isNotCollapsible = child.nodeType() === Node.ELEMENT_NODE &&
1232
+ child.parentNode?.nodeType() === Node.DOCUMENT_NODE && !child.parentNode.parentNode;
1233
+ if (isNotCollapsible || (this.#expandRoot && this.omitRootDOMNode)) {
1234
+ this.setNodeExpanded(child, true);
1216
1235
  }
1217
1236
  }
1218
1237
  };
@@ -1275,6 +1294,7 @@ export class DOMTreeWidget extends UI.Widget.Widget {
1275
1294
 
1276
1295
  set showComments(showComments: boolean) {
1277
1296
  this.#showComments = showComments;
1297
+ this.#validateSelectedNode();
1278
1298
  this.performUpdate();
1279
1299
  }
1280
1300
 
@@ -1330,8 +1350,20 @@ export class DOMTreeWidget extends UI.Widget.Widget {
1330
1350
  };
1331
1351
  #highlightThrottler = new Common.Throttler.Throttler(100);
1332
1352
 
1353
+ get changeTracker(): ChangeTracker.ChangeTracker.ChangeTracker|undefined {
1354
+ // The widget is also created without dependency injection (e.g. by
1355
+ // ElementsPanel), so the changeTracker can be empty, relying on the manual
1356
+ // resolution. Note that this only works once the widget is attached to the
1357
+ // DOM.
1358
+ this.#changeTracker ??=
1359
+ UI.Widget.lookupUniverseForElement(this.contentElement)?.get(ChangeTracker.ChangeTracker.ChangeTracker);
1360
+ return this.#changeTracker;
1361
+ }
1362
+
1333
1363
  constructor(
1334
1364
  element?: HTMLElement,
1365
+ [changeTracker]:
1366
+ UI.Widget.WidgetDependencies<typeof DOMTreeWidget>|[ChangeTracker.ChangeTracker.ChangeTracker?] = [],
1335
1367
  view: View = DEFAULT_VIEW,
1336
1368
  ) {
1337
1369
  super(element, {
@@ -1339,6 +1371,7 @@ export class DOMTreeWidget extends UI.Widget.Widget {
1339
1371
  delegatesFocus: false,
1340
1372
  });
1341
1373
  this.#view = view;
1374
+ this.#changeTracker = changeTracker;
1342
1375
  this.#showHTMLCommentsSetting.addChangeListener(this.#onShowHTMLCommentsChange, this);
1343
1376
  if (Common.Settings.Settings.instance().moduleSetting('highlight-node-on-hover-in-overlay').get()) {
1344
1377
  SDK.TargetManager.TargetManager.instance().addModelListener(
@@ -1470,10 +1503,40 @@ export class DOMTreeWidget extends UI.Widget.Widget {
1470
1503
  this.#updateModifiedNodesSoon();
1471
1504
  }
1472
1505
 
1506
+ #findNextNodeOnRemoval(node: SDK.DOMModel.DOMNode, parent: SDK.DOMModel.DOMNode|null): SDK.DOMModel.DOMNode|null {
1507
+ const visibleChildren = parent ? getVisibleChildren(parent, this.#showComments) : [];
1508
+ if (visibleChildren.length > 0) {
1509
+ let candidate: SDK.DOMModel.DOMNode|null = node.nextSibling;
1510
+ while (candidate) {
1511
+ if (visibleChildren.includes(candidate)) {
1512
+ return candidate;
1513
+ }
1514
+ candidate = candidate.nextSibling;
1515
+ }
1516
+ candidate = node.previousSibling;
1517
+ while (candidate) {
1518
+ if (visibleChildren.includes(candidate)) {
1519
+ return candidate;
1520
+ }
1521
+ candidate = candidate.previousSibling;
1522
+ }
1523
+ }
1524
+ if (parent) {
1525
+ if (parent.nodeType() === Node.DOCUMENT_NODE) {
1526
+ return parent.parentNode;
1527
+ }
1528
+ return parent;
1529
+ }
1530
+ return null;
1531
+ }
1532
+
1473
1533
  #onNodeRemoved(
1474
1534
  event: Common.EventTarget.EventTargetEvent<{node: SDK.DOMModel.DOMNode, parent: SDK.DOMModel.DOMNode}>): void {
1475
1535
  const {node, parent} = event.data;
1476
1536
  this.resetClipboardIfNeeded(node);
1537
+ if (this.#selectedDOMNode && (this.#selectedDOMNode === node || node.isAncestor(this.#selectedDOMNode))) {
1538
+ this.selectDOMNode(this.#findNextNodeOnRemoval(node, parent), true);
1539
+ }
1477
1540
  if (parent) {
1478
1541
  this.#addUpdateRecord(parent).nodeRemoved(node);
1479
1542
  }
@@ -1524,8 +1587,12 @@ export class DOMTreeWidget extends UI.Widget.Widget {
1524
1587
  this.#updateModifiedNodesSoon();
1525
1588
  }
1526
1589
 
1527
- #onMarkersChanged(): void {
1528
- this.#updateModifiedNodesSoon();
1590
+ #onMarkersChanged(event: Common.EventTarget.EventTargetEvent<SDK.DOMModel.DOMNode>): void {
1591
+ const node = event.data;
1592
+ if (node) {
1593
+ this.#addUpdateRecord(node);
1594
+ }
1595
+ this.performUpdate();
1529
1596
  }
1530
1597
 
1531
1598
  updateModifiedNodes(): void {
@@ -1534,6 +1601,7 @@ export class DOMTreeWidget extends UI.Widget.Widget {
1534
1601
  this.#updateModifiedNodesTimeout = undefined;
1535
1602
  }
1536
1603
  if (this.#view === DECLARATIVE_VIEW) {
1604
+ this.#validateSelectedNode();
1537
1605
  this.performUpdate();
1538
1606
  return;
1539
1607
  }
@@ -1554,9 +1622,17 @@ export class DOMTreeWidget extends UI.Widget.Widget {
1554
1622
  domModel.addEventListener(SDK.DOMModel.Events.DistributedNodesChanged, this.#onDistributedNodesChanged, this);
1555
1623
  domModel.addEventListener(SDK.DOMModel.Events.DocumentURLChanged, this.#onDocumentURLChanged, this);
1556
1624
  domModel.addEventListener(SDK.DOMModel.Events.AffectedByStartingStylesFlagUpdated, this.requestUpdate, this);
1625
+ const cssModel = domModel.target().model(SDK.CSSModel.CSSModel);
1626
+ if (cssModel) {
1627
+ cssModel.addEventListener(SDK.CSSModel.Events.StyleSheetAdded, this.requestUpdate, this);
1628
+ }
1557
1629
  }
1558
1630
 
1559
1631
  #unwireDOMModel(domModel: SDK.DOMModel.DOMModel): void {
1632
+ const cssModel = domModel.target().model(SDK.CSSModel.CSSModel);
1633
+ if (cssModel) {
1634
+ cssModel.removeEventListener(SDK.CSSModel.Events.StyleSheetAdded, this.requestUpdate, this);
1635
+ }
1560
1636
  domModel.removeEventListener(SDK.DOMModel.Events.DocumentUpdated, this.#onDocumentUpdated, this);
1561
1637
  domModel.removeEventListener(SDK.DOMModel.Events.NodeInserted, this.#onNodeInserted, this);
1562
1638
  domModel.removeEventListener(SDK.DOMModel.Events.NodeRemoved, this.#onNodeRemoved, this);
@@ -1575,6 +1651,7 @@ export class DOMTreeWidget extends UI.Widget.Widget {
1575
1651
 
1576
1652
  #onShowHTMLCommentsChange(): void {
1577
1653
  this.#showComments = this.#showHTMLCommentsSetting.get();
1654
+ this.#validateSelectedNode();
1578
1655
  const selectedNode = this.selectedDOMNode();
1579
1656
  if (selectedNode && selectedNode.nodeType() === Node.COMMENT_NODE && !this.#showComments) {
1580
1657
  this.selectDOMNode(selectedNode.parentNode);
@@ -1601,7 +1678,7 @@ export class DOMTreeWidget extends UI.Widget.Widget {
1601
1678
  }
1602
1679
 
1603
1680
  selectDOMNode(node: SDK.DOMModel.DOMNode|SDK.DOMModel.AdoptedStyleSheet|null, focus?: boolean,
1604
- isClosingTag = false): void {
1681
+ isClosingTag?: boolean): void {
1605
1682
  this.#imagePreviewPopover?.hide();
1606
1683
  this.#issuePopoverHelper?.hidePopover();
1607
1684
  if (node instanceof SDK.DOMModel.AdoptedStyleSheet) {
@@ -1610,9 +1687,9 @@ export class DOMTreeWidget extends UI.Widget.Widget {
1610
1687
  }
1611
1688
  this.#selectedAdoptedStyleSheet = null;
1612
1689
  if (this.#view === DECLARATIVE_VIEW) {
1613
- this.#selectedClosingTag = Boolean(isClosingTag);
1614
- const isSameNode = this.#selectedDOMNode === node;
1690
+ const isSameNode = this.#selectedDOMNode === node && this.#selectedClosingTag === Boolean(isClosingTag);
1615
1691
  this.#selectedDOMNode = node;
1692
+ this.#selectedClosingTag = Boolean(isClosingTag);
1616
1693
  if (node) {
1617
1694
  const ancestors: SDK.DOMModel.DOMNode[] = [];
1618
1695
  for (let current: (SDK.DOMModel.DOMNode|null) = node.parentNode; current; current = current.parentNode) {
@@ -1646,6 +1723,9 @@ export class DOMTreeWidget extends UI.Widget.Widget {
1646
1723
  Common.EventTarget.EventTargetEvent<{node: SDK.DOMModel.DOMNode | null, focus: boolean}>);
1647
1724
  }
1648
1725
  this.performUpdate();
1726
+ if (focus) {
1727
+ this.focus();
1728
+ }
1649
1729
  return;
1650
1730
  }
1651
1731
  this.#viewOutput?.elementsTreeOutline?.selectDOMNode(node, focus);
@@ -1684,7 +1764,7 @@ export class DOMTreeWidget extends UI.Widget.Widget {
1684
1764
  if (this.#view === DECLARATIVE_VIEW) {
1685
1765
  if (expanded) {
1686
1766
  this.#expandedNodes.add(node);
1687
- if (!node.children() && node.childNodeCount()) {
1767
+ if (!node.children() && (node.childNodeCount() || node.isIframe() || node.nodeType() === Node.DOCUMENT_NODE)) {
1688
1768
  void node.getChildNodes(() => {
1689
1769
  this.performUpdate();
1690
1770
  });
@@ -1710,6 +1790,11 @@ export class DOMTreeWidget extends UI.Widget.Widget {
1710
1790
 
1711
1791
  isNodeExpanded(node: SDK.DOMModel.DOMNode): boolean {
1712
1792
  if (this.#view === DECLARATIVE_VIEW) {
1793
+ const isNotCollapsible = node.nodeType() === Node.ELEMENT_NODE &&
1794
+ node.parentNode?.nodeType() === Node.DOCUMENT_NODE && !node.parentNode.parentNode;
1795
+ if (isNotCollapsible) {
1796
+ return true;
1797
+ }
1713
1798
  return this.#expandedNodes.has(node);
1714
1799
  }
1715
1800
  return Boolean(this.#viewOutput.elementsTreeOutline?.findTreeElement(node)?.expanded);
@@ -1747,16 +1832,17 @@ export class DOMTreeWidget extends UI.Widget.Widget {
1747
1832
  return;
1748
1833
  }
1749
1834
  this.#expandedNodes.add(n);
1750
- let children = n.children();
1751
- if (!children && n.childNodeCount()) {
1752
- children = await new Promise<SDK.DOMModel.DOMNode[]>(resolve => {
1753
- void n.getChildNodes(() => resolve(n.children() ?? []));
1835
+ if (!n.children() && (n.childNodeCount() || n.isIframe() || n.nodeType() === Node.DOCUMENT_NODE)) {
1836
+ await new Promise<void>(resolve => {
1837
+ void n.getChildNodes(() => resolve());
1754
1838
  });
1755
1839
  }
1756
- const pseudoElements = Array.from(n.pseudoElements().values()).flat();
1757
- const allChildren = [...(children ?? []), ...pseudoElements];
1758
- if (allChildren.length) {
1759
- await Promise.all(allChildren.map(child => expand(child, depth + 1)));
1840
+ const visibleChildren = getVisibleChildren(n, this.#showComments);
1841
+ if (visibleChildren.length) {
1842
+ if (visibleChildren.length > this.expandedChildrenLimit(n)) {
1843
+ this.setExpandedChildrenLimit(n, visibleChildren.length);
1844
+ }
1845
+ await Promise.all(visibleChildren.map(child => expand(child, depth + 1)));
1760
1846
  }
1761
1847
  };
1762
1848
  await expand(node, 0);
@@ -1812,7 +1898,6 @@ export class DOMTreeWidget extends UI.Widget.Widget {
1812
1898
  treeElementForNode(node: SDK.DOMModel.DOMNode): ElementsTreeElement|null {
1813
1899
  return this.#viewOutput.elementsTreeOutline?.findTreeElement(node) || null;
1814
1900
  }
1815
-
1816
1901
  #hoveredDOMNode: SDK.DOMModel.DOMNode|null = null;
1817
1902
  #hoveredClosingTag = false;
1818
1903
  #selectedClosingTag = false;
@@ -1827,6 +1912,7 @@ export class DOMTreeWidget extends UI.Widget.Widget {
1827
1912
  #draggedNode: SDK.DOMModel.DOMNode|null = null;
1828
1913
  #draggedNodeWasExpanded = false;
1829
1914
  #dragOverNode: {node: SDK.DOMModel.DOMNode, isClosingTag: boolean}|null = null;
1915
+ #isFocusing = false;
1830
1916
 
1831
1917
  hoveredDOMNode(): SDK.DOMModel.DOMNode|null {
1832
1918
  if (this.#view === DECLARATIVE_VIEW) {
@@ -1876,11 +1962,75 @@ export class DOMTreeWidget extends UI.Widget.Widget {
1876
1962
  }
1877
1963
  }
1878
1964
 
1965
+ #findValidSelectedNode(selectedNode: SDK.DOMModel.DOMNode): SDK.DOMModel.DOMNode|null {
1966
+ if (!this.#rootDOMNode) {
1967
+ return null;
1968
+ }
1969
+ if (selectedNode === this.#rootDOMNode) {
1970
+ return selectedNode;
1971
+ }
1972
+ let current: SDK.DOMModel.DOMNode|null = selectedNode;
1973
+ let fallback: SDK.DOMModel.DOMNode|null = null;
1974
+ while (current && current !== this.#rootDOMNode) {
1975
+ const parent: SDK.DOMModel.DOMNode|null = current.parentNode;
1976
+ if (!parent) {
1977
+ return fallback ?? this.#rootDOMNode;
1978
+ }
1979
+ if (parent.children() !== null || parent.childNodeCount() === 0 || parent.isIframe()) {
1980
+ const visibleChildren = getVisibleChildren(parent, this.#showComments);
1981
+ if (!visibleChildren.includes(current)) {
1982
+ let sibling: SDK.DOMModel.DOMNode|null = null;
1983
+ let candidate: SDK.DOMModel.DOMNode|null = current.nextSibling;
1984
+ while (candidate) {
1985
+ if (visibleChildren.includes(candidate)) {
1986
+ sibling = candidate;
1987
+ break;
1988
+ }
1989
+ candidate = candidate.nextSibling;
1990
+ }
1991
+ if (!sibling) {
1992
+ candidate = current.previousSibling;
1993
+ while (candidate) {
1994
+ if (visibleChildren.includes(candidate)) {
1995
+ sibling = candidate;
1996
+ break;
1997
+ }
1998
+ candidate = candidate.previousSibling;
1999
+ }
2000
+ }
2001
+ if (sibling) {
2002
+ return this.#findValidSelectedNode(sibling);
2003
+ }
2004
+ return this.#findValidSelectedNode(parent);
2005
+ }
2006
+ }
2007
+ fallback = parent;
2008
+ current = parent;
2009
+ }
2010
+ return current === this.#rootDOMNode ? selectedNode : (fallback ?? this.#rootDOMNode);
2011
+ }
2012
+
2013
+ #validateSelectedNode(): void {
2014
+ if (!this.#selectedDOMNode) {
2015
+ return;
2016
+ }
2017
+ const validNode = this.#findValidSelectedNode(this.#selectedDOMNode);
2018
+ if (validNode !== this.#selectedDOMNode) {
2019
+ this.#selectedDOMNode = validNode;
2020
+ this.#selectedClosingTag = false;
2021
+ this.#clearHighlightedNode();
2022
+ this.onSelectedNodeChanged(
2023
+ {data: {node: validNode, focus: false}} as
2024
+ Common.EventTarget.EventTargetEvent<{node: SDK.DOMModel.DOMNode | null, focus: boolean}>);
2025
+ }
2026
+ }
2027
+
1879
2028
  override performUpdate(): void {
1880
2029
  if (this.#updateModifiedNodesTimeout) {
1881
2030
  clearTimeout(this.#updateModifiedNodesTimeout);
1882
2031
  this.#updateModifiedNodesTimeout = undefined;
1883
2032
  }
2033
+ this.#validateSelectedNode();
1884
2034
  const firstRender = !this.#viewOutput.elementsTreeOutline;
1885
2035
  const updatedNodes = this.#view === DECLARATIVE_VIEW ? [...this.#updateRecords.keys()] : [];
1886
2036
  this.#view({
@@ -1931,6 +2081,9 @@ export class DOMTreeWidget extends UI.Widget.Widget {
1931
2081
  this.setHoveredNode(null);
1932
2082
  },
1933
2083
  onSelect: (node: SDK.DOMModel.DOMNode, isClosingTag = false, selectedByUser?: boolean) => {
2084
+ if (!selectedByUser) {
2085
+ return;
2086
+ }
1934
2087
  this.selectDOMNode(node, selectedByUser, isClosingTag);
1935
2088
  },
1936
2089
  onExpand: (node: SDK.DOMModel.DOMNode, expanded: boolean) => {
@@ -2134,6 +2287,20 @@ export class DOMTreeWidget extends UI.Widget.Widget {
2134
2287
  * FIXME: which node is selected should be part of the view input.
2135
2288
  */
2136
2289
  selectDOMNodeWithoutReveal(node: SDK.DOMModel.DOMNode): void {
2290
+ if (this.#view === DECLARATIVE_VIEW) {
2291
+ const isSameNode = this.#selectedDOMNode === node && !this.#selectedClosingTag;
2292
+ if (isSameNode) {
2293
+ return;
2294
+ }
2295
+ this.#selectedDOMNode = node;
2296
+ this.#selectedClosingTag = false;
2297
+ this.#clearHighlightedNode();
2298
+ this.onSelectedNodeChanged(
2299
+ {data: {node, focus: false}} as
2300
+ Common.EventTarget.EventTargetEvent<{node: SDK.DOMModel.DOMNode | null, focus: boolean}>);
2301
+ this.performUpdate();
2302
+ return;
2303
+ }
2137
2304
  this.#viewOutput.elementsTreeOutline?.findTreeElement(node)?.select();
2138
2305
  }
2139
2306
 
@@ -2166,6 +2333,9 @@ export class DOMTreeWidget extends UI.Widget.Widget {
2166
2333
  }
2167
2334
 
2168
2335
  toggleHideElement(node: SDK.DOMModel.DOMNode): void {
2336
+ const changeTracker = this.changeTracker;
2337
+ Elements.DOMChanges.trackVisibilityToggle(changeTracker, node, buildChangeSelector(changeTracker, node),
2338
+ !this.isToggledToHidden(node));
2169
2339
  void node.toggleHideElement();
2170
2340
  }
2171
2341
 
@@ -2180,7 +2350,14 @@ export class DOMTreeWidget extends UI.Widget.Widget {
2180
2350
  if (!node.parentNode || node.parentNode.nodeType() === Node.DOCUMENT_NODE) {
2181
2351
  return;
2182
2352
  }
2183
- void node.removeNode();
2353
+ // The selector has to be resolved before the node is detached from the tree.
2354
+ const changeTracker = this.changeTracker;
2355
+ const selector = buildChangeSelector(changeTracker, node);
2356
+ await node.removeNode((err: string|null) => {
2357
+ if (!err) {
2358
+ Elements.DOMChanges.trackNodeRemoval(changeTracker, node, selector);
2359
+ }
2360
+ });
2184
2361
  }
2185
2362
 
2186
2363
  isToggledToHidden(node: SDK.DOMModel.DOMNode): boolean {
@@ -2292,7 +2469,14 @@ export class DOMTreeWidget extends UI.Widget.Widget {
2292
2469
  }
2293
2470
 
2294
2471
  duplicateNode(node: SDK.DOMModel.DOMNode): void {
2295
- void node.duplicate();
2472
+ void node.duplicate().then(({error, node: newNode}) => {
2473
+ if (!error) {
2474
+ const duplicatedNode = newNode ?? node;
2475
+ const changeTracker = this.changeTracker;
2476
+ Elements.DOMChanges.trackNodeDuplication(changeTracker, duplicatedNode,
2477
+ buildChangeSelector(changeTracker, duplicatedNode));
2478
+ }
2479
+ });
2296
2480
  }
2297
2481
 
2298
2482
  nodeBeingDragged(): SDK.DOMModel.DOMNode|null {
@@ -2431,6 +2615,11 @@ export class DOMTreeWidget extends UI.Widget.Widget {
2431
2615
  }
2432
2616
  const wasExpanded = this.#draggedNodeWasExpanded;
2433
2617
  draggedNode.moveTo(parentNode, anchorNode, (error, newNode) => {
2618
+ if (!error) {
2619
+ const movedNode = newNode ?? draggedNode;
2620
+ const changeTracker = this.changeTracker;
2621
+ Elements.DOMChanges.trackNodeDrop(changeTracker, movedNode, buildChangeSelector(changeTracker, movedNode));
2622
+ }
2434
2623
  this.selectNodeAfterEdit(wasExpanded, error, newNode);
2435
2624
  });
2436
2625
  }
@@ -2595,7 +2784,8 @@ export class DOMTreeWidget extends UI.Widget.Widget {
2595
2784
  }
2596
2785
  this.performUpdate();
2597
2786
  } else if (nodeType === Node.TEXT_NODE) {
2598
- this.startEditingTextNode(node);
2787
+ this.#nodeToEdit = {node, isTextNode: true};
2788
+ this.performUpdate();
2599
2789
  } else if (nodeType === Node.PROCESSING_INSTRUCTION_NODE) {
2600
2790
  this.#nodeToEdit = {node, isProcessingInstruction: true};
2601
2791
  this.performUpdate();
@@ -2615,6 +2805,12 @@ export class DOMTreeWidget extends UI.Widget.Widget {
2615
2805
  const wasExpanded = this.isNodeExpanded(node);
2616
2806
  if (event.key === 'ArrowUp' && node.previousSibling) {
2617
2807
  node.moveTo(node.parentNode, node.previousSibling, (error, newNode) => {
2808
+ if (!error) {
2809
+ const movedNode = newNode ?? node;
2810
+ const changeTracker = this.changeTracker;
2811
+ Elements.DOMChanges.trackNodeMove(changeTracker, movedNode, buildChangeSelector(changeTracker, movedNode),
2812
+ /* directionUp= */ true);
2813
+ }
2618
2814
  this.selectNodeAfterEdit(wasExpanded, error, newNode);
2619
2815
  });
2620
2816
  event.consume(true);
@@ -2622,6 +2818,12 @@ export class DOMTreeWidget extends UI.Widget.Widget {
2622
2818
  }
2623
2819
  if (event.key === 'ArrowDown' && node.nextSibling) {
2624
2820
  node.moveTo(node.parentNode, node.nextSibling.nextSibling, (error, newNode) => {
2821
+ if (!error) {
2822
+ const movedNode = newNode ?? node;
2823
+ const changeTracker = this.changeTracker;
2824
+ Elements.DOMChanges.trackNodeMove(changeTracker, movedNode, buildChangeSelector(changeTracker, movedNode),
2825
+ /* directionUp= */ false);
2826
+ }
2625
2827
  this.selectNodeAfterEdit(wasExpanded, error, newNode);
2626
2828
  });
2627
2829
  event.consume(true);
@@ -2783,11 +2985,23 @@ export class DOMTreeWidget extends UI.Widget.Widget {
2783
2985
  const clipboardNode = this.#clipboardData.node;
2784
2986
  if (this.#clipboardData.isCut) {
2785
2987
  clipboardNode.moveTo(targetNode, null, (error, newNode) => {
2988
+ if (!error) {
2989
+ const movedNode = newNode ?? clipboardNode;
2990
+ const changeTracker = this.changeTracker;
2991
+ Elements.DOMChanges.trackNodePaste(changeTracker, movedNode, buildChangeSelector(changeTracker, movedNode),
2992
+ /* isCut= */ true);
2993
+ }
2786
2994
  this.selectNodeAfterEdit(wasExpanded, error, newNode);
2787
2995
  });
2788
2996
  this.setClipboardData(null);
2789
2997
  } else {
2790
2998
  clipboardNode.copyTo(targetNode, null, (error, newNode) => {
2999
+ if (!error) {
3000
+ const addedNode = newNode ?? clipboardNode;
3001
+ const changeTracker = this.changeTracker;
3002
+ Elements.DOMChanges.trackNodePaste(changeTracker, addedNode, buildChangeSelector(changeTracker, addedNode),
3003
+ /* isCut= */ false);
3004
+ }
2791
3005
  this.selectNodeAfterEdit(wasExpanded, error, newNode);
2792
3006
  });
2793
3007
  }
@@ -2852,8 +3066,20 @@ export class DOMTreeWidget extends UI.Widget.Widget {
2852
3066
  }
2853
3067
 
2854
3068
  override focus(): void {
2855
- super.focus();
2856
- this.#viewOutput.elementsTreeOutline?.focus();
3069
+ if (this.#isFocusing) {
3070
+ return;
3071
+ }
3072
+ this.#isFocusing = true;
3073
+ try {
3074
+ super.focus();
3075
+ if (this.#view === DECLARATIVE_VIEW) {
3076
+ this.contentElement.querySelector('devtools-tree')?.focus();
3077
+ } else {
3078
+ this.#viewOutput.elementsTreeOutline?.focus();
3079
+ }
3080
+ } finally {
3081
+ this.#isFocusing = false;
3082
+ }
2857
3083
  }
2858
3084
 
2859
3085
  override wasShown(): void {
@@ -2871,6 +3097,7 @@ export class DOMTreeWidget extends UI.Widget.Widget {
2871
3097
  }
2872
3098
  if (this.#view === DECLARATIVE_VIEW) {
2873
3099
  this.#updateRecords.clear();
3100
+ return;
2874
3101
  }
2875
3102
  this.performUpdate();
2876
3103
  }
@@ -2885,6 +3112,7 @@ export class DOMTreeWidget extends UI.Widget.Widget {
2885
3112
  }
2886
3113
  if (this.#view === DECLARATIVE_VIEW) {
2887
3114
  this.#updateRecords.clear();
3115
+ return;
2888
3116
  }
2889
3117
  this.performUpdate();
2890
3118
  }
@@ -125,7 +125,6 @@ export const DEFAULT_VIEW: View = (input, _output, target) => {
125
125
  </devtools-checkbox>
126
126
  </devtools-toolbar>
127
127
  ${widget(EventListeners.EventListenersView.EventListenersView, {
128
- changeCallback: input.onEventListenersViewChange,
129
128
  objects: input.eventListenerObjects,
130
129
  filter: input.filter,
131
130
  })}