chrome-devtools-frontend 1.0.1697595 → 1.0.1698733

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 (124) hide show
  1. package/.agents/skills/version-control/SKILL.md +21 -9
  2. package/.stylelintignore +2 -0
  3. package/front_end/core/common/VersionController.ts +5 -1
  4. package/front_end/core/platform/HostRuntime.ts +12 -0
  5. package/front_end/core/platform/api/HostRuntime.ts +13 -0
  6. package/front_end/core/platform/browser/HostRuntime.ts +34 -0
  7. package/front_end/core/platform/node/HostRuntime.ts +40 -0
  8. package/front_end/core/sdk/AnimationModel.ts +21 -99
  9. package/front_end/core/sdk/CSSModel.ts +4 -2
  10. package/front_end/core/sdk/CSSPropertyParser.ts +4 -4
  11. package/front_end/core/sdk/CSSPropertyParserMatchers.ts +4 -44
  12. package/front_end/core/sdk/Connections.ts +6 -1
  13. package/front_end/core/sdk/ConsoleModel.ts +1 -12
  14. package/front_end/core/sdk/DOMModel.ts +3 -51
  15. package/front_end/core/sdk/DebuggerModel.ts +9 -8
  16. package/front_end/core/sdk/NetworkRequest.ts +1 -1
  17. package/front_end/core/sdk/PageFunctions.ts +187 -0
  18. package/front_end/core/sdk/Resource.ts +1 -1
  19. package/front_end/core/sdk/RuntimeModel.ts +1 -20
  20. package/front_end/core/sdk/Script.ts +21 -0
  21. package/front_end/core/sdk/SourceMap.ts +15 -2
  22. package/front_end/core/sdk/SourceMapCache.ts +13 -10
  23. package/front_end/core/sdk/SourceMapManager.ts +11 -9
  24. package/front_end/core/sdk/SourceMapScopeChainEntry.ts +59 -17
  25. package/front_end/core/sdk/SourceMapScopesInfo.ts +8 -2
  26. package/front_end/entrypoints/heap_snapshot_worker/ContextAnalyzer.ts +635 -0
  27. package/front_end/entrypoints/heap_snapshot_worker/HeapSnapshot.ts +26 -12
  28. package/front_end/entrypoints/heap_snapshot_worker/HeapSnapshotLoader.ts +16 -2
  29. package/front_end/entrypoints/heap_snapshot_worker/HeapSnapshotWorkerDispatcher.ts +4 -4
  30. package/front_end/entrypoints/heap_snapshot_worker/heap_snapshot_worker.ts +2 -0
  31. package/front_end/entrypoints/shell/shell.ts +1 -1
  32. package/front_end/generated/InspectorBackendCommands.ts +1 -1
  33. package/front_end/generated/protocol.ts +8 -2
  34. package/front_end/models/ai_assistance/DOMHelpers.ts +95 -0
  35. package/front_end/models/ai_assistance/EvaluateAction.ts +5 -42
  36. package/front_end/models/ai_assistance/ExtensionScope.ts +2 -31
  37. package/front_end/models/ai_assistance/agents/AiAgent.ts +2 -1
  38. package/front_end/models/ai_assistance/agents/ContextSelectionAgent.ts +27 -2
  39. package/front_end/models/ai_assistance/ai_assistance.ts +6 -4
  40. package/front_end/models/ai_assistance/contexts/DOMNodeContext.ts +10 -7
  41. package/front_end/models/ai_assistance/contexts/FileContext.ts +2 -10
  42. package/front_end/models/ai_assistance/skills/performance.md +4 -3
  43. package/front_end/models/ai_assistance/tools/GetSourceContent.ts +3 -2
  44. package/front_end/models/ai_assistance/tools/{GetFunctionCode.ts → GetTraceFunctionCode.ts} +26 -13
  45. package/front_end/models/ai_assistance/tools/{GetResourceContent.ts → GetTraceResourceContent.ts} +31 -11
  46. package/front_end/models/ai_assistance/tools/ListSources.ts +21 -14
  47. package/front_end/models/ai_assistance/tools/Tool.ts +2 -2
  48. package/front_end/models/ai_assistance/tools/ToolRegistry.ts +6 -6
  49. package/front_end/models/bindings/CompilerScriptMapping.ts +3 -10
  50. package/front_end/models/bindings/DebuggerLanguagePlugins.ts +48 -17
  51. package/front_end/models/bindings/ResourceScriptMapping.ts +2 -2
  52. package/front_end/models/bindings/StylesSourceMapping.ts +1 -1
  53. package/front_end/models/comment_manager/CD4ABridge.ts +55 -12
  54. package/front_end/models/comment_manager/CommentManager.ts +46 -91
  55. package/front_end/models/comment_manager/CommentThread.ts +145 -0
  56. package/front_end/models/comment_manager/comment_manager.ts +2 -0
  57. package/front_end/models/elements/DOMChanges.ts +238 -0
  58. package/front_end/models/elements/elements.ts +2 -0
  59. package/front_end/models/emulation/DeviceModeModel.ts +53 -0
  60. package/front_end/models/extensions/ExtensionAPI.ts +8 -6
  61. package/front_end/models/extensions/ExtensionEndpoint.ts +11 -8
  62. package/front_end/models/extensions/HostUrlPattern.ts +4 -3
  63. package/front_end/models/extensions/LanguageExtensionEndpoint.ts +3 -2
  64. package/front_end/models/extensions/RecorderExtensionEndpoint.ts +2 -1
  65. package/front_end/models/formatter/FormatterWorkerPool.ts +4 -6
  66. package/front_end/models/heap_snapshot/HeapSnapshotModel.ts +55 -0
  67. package/front_end/models/heap_snapshot/HeapSnapshotProxy.ts +18 -14
  68. package/front_end/models/issues_manager/IssuesManager.ts +5 -0
  69. package/front_end/models/issues_manager/WebInstallIssue.ts +63 -0
  70. package/front_end/models/issues_manager/descriptions/webInstallManifestMissingId.md +3 -0
  71. package/front_end/models/issues_manager/descriptions/webInstallManifestMissingNameOrShortName.md +3 -0
  72. package/front_end/models/issues_manager/descriptions/webInstallManifestParsingOrNetworkError.md +3 -0
  73. package/front_end/models/issues_manager/descriptions/webInstallNoManifest.md +3 -0
  74. package/front_end/models/issues_manager/descriptions/webInstallStartUrlInvalid.md +3 -0
  75. package/front_end/models/issues_manager/issues_manager.ts +2 -0
  76. package/front_end/models/workspace/UISourceCode.ts +20 -0
  77. package/front_end/panels/ai_assistance/AiAssistancePanel.ts +7 -7
  78. package/front_end/panels/ai_assistance/components/AIv2MarkdownRenderer.ts +4 -10
  79. package/front_end/panels/ai_assistance/components/MarkdownRendererWithCodeBlock.ts +25 -5
  80. package/front_end/panels/browser_debugger/ObjectEventListenersSidebarPane.ts +2 -3
  81. package/front_end/panels/common/CommentThreadWidget.ts +221 -0
  82. package/front_end/{ui/comments → panels/common}/CommentsOverlayWidget.ts +117 -28
  83. package/front_end/{ui/comments → panels/common}/CommentsStatusBarPill.ts +5 -5
  84. package/front_end/panels/common/ExtensionServer.ts +2 -1
  85. package/front_end/panels/common/commentThreadWidget.css +173 -0
  86. package/front_end/{ui/comments → panels/common}/commentsOverlay.css +8 -1
  87. package/front_end/{ui/comments/comments-meta.ts → panels/common/common-meta.ts} +11 -11
  88. package/front_end/panels/common/common.ts +3 -0
  89. package/front_end/panels/elements/AdoptedStyleSheetTreeElement.ts +3 -3
  90. package/front_end/panels/elements/ElementsPanel.ts +5 -1
  91. package/front_end/panels/elements/ElementsTreeElement.ts +102 -21
  92. package/front_end/panels/elements/ElementsTreeOutline.ts +326 -91
  93. package/front_end/panels/elements/EventListenersWidget.ts +0 -1
  94. package/front_end/panels/elements/PropertiesWidget.ts +11 -10
  95. package/front_end/panels/elements/PropertyRenderer.ts +29 -25
  96. package/front_end/panels/elements/StylePropertyTreeElement.ts +6 -6
  97. package/front_end/panels/elements/elementsTreeOutline.css +7 -1
  98. package/front_end/panels/emulation/DeviceModeToolbar.ts +2 -3
  99. package/front_end/panels/event_listeners/EventListenersView.ts +235 -302
  100. package/front_end/panels/linear_memory_inspector/LinearMemoryInspectorController.ts +6 -7
  101. package/front_end/panels/linear_memory_inspector/components/linearMemoryInspector.css +5 -1
  102. package/front_end/panels/linear_memory_inspector/linear_memory_inspector-meta.ts +1 -1
  103. package/front_end/panels/mobile_throttling/ThrottlingSettingsTab.ts +10 -2
  104. package/front_end/panels/network/NetworkConfigView.ts +141 -116
  105. package/front_end/panels/network/NetworkPanel.ts +7 -4
  106. package/front_end/panels/network/RequestHeadersView.ts +1 -1
  107. package/front_end/panels/network/forward/BackendLinking.ts +5 -2
  108. package/front_end/panels/recorder/stepView.css +1 -0
  109. package/front_end/panels/security/SecurityPanel.ts +5 -5
  110. package/front_end/panels/security/SecurityPanelSidebar.ts +2 -11
  111. package/front_end/panels/settings/BackendLinkingSettingsTab.ts +7 -2
  112. package/front_end/panels/sources/DebuggerPlugin.ts +1 -1
  113. package/front_end/panels/sources/WatchExpressionsSidebarPane.ts +11 -16
  114. package/front_end/panels/sources/sources-meta.ts +1 -1
  115. package/front_end/panels/timeline/TimelineTreeView.ts +11 -1
  116. package/front_end/panels/timeline/overlays/OverlaysImpl.ts +0 -3
  117. package/front_end/third_party/chromium/README.chromium +1 -1
  118. package/front_end/ui/comments/CommentOverlayManager.ts +89 -42
  119. package/front_end/ui/comments/comments.ts +0 -4
  120. package/front_end/ui/legacy/Treeoutline.ts +4 -0
  121. package/front_end/ui/legacy/components/object_ui/ObjectPropertiesSection.ts +15 -8
  122. package/front_end/ui/legacy/components/perf_ui/FlameChart.ts +4 -0
  123. package/front_end/ui/visual_logging/KnownContextValues.ts +1 -0
  124. 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
  /**
@@ -148,7 +150,7 @@ interface ViewInput {
148
150
  onClearMaxRows?: () => void;
149
151
  onHoverNode?: (node: SDK.DOMModel.DOMNode|null, showInfo?: boolean, isClosingTag?: boolean) => void;
150
152
  onLeave?: () => void;
151
- onToggleHideElement?: (node: SDK.DOMModel.DOMNode) => void;
153
+ onToggleHideElement?: (node: SDK.DOMModel.DOMNode) => Promise<void>;
152
154
  onKeyDown?: (event: KeyboardEvent) => void;
153
155
  isToggledToHidden?: (node: SDK.DOMModel.DOMNode) => boolean;
154
156
  onDuplicateNode?: (node: SDK.DOMModel.DOMNode) => void;
@@ -521,6 +523,13 @@ export const DECLARATIVE_VIEW: View = (input: ViewInput, _output: ViewOutput, ta
521
523
  }
522
524
 
523
525
  const on = Lit.Directive.directive(Lit.CustomDirectives.InterceptBindingDirective);
526
+ const treeItemJslog = (context?: string, drag?: boolean): ReturnType<typeof VisualLogging.treeItem> =>
527
+ VisualLogging.treeItem(context).parent('elementsTreeOutline').track({
528
+ keydown: 'ArrowUp|ArrowDown|ArrowLeft|ArrowRight|Backspace|Delete|Enter|Space|Home|End',
529
+ resize: true,
530
+ ...(drag ? {drag: true} : {}),
531
+ click: true,
532
+ });
524
533
 
525
534
  const renderShortcut = (shortcut: SDK.DOMModel.DOMNodeShortcut, shortcutDepth: number): Lit.LitTemplate => {
526
535
  const hasShortcutChildren = shortcut.childShortcuts.length > 0;
@@ -556,12 +565,11 @@ export const DECLARATIVE_VIEW: View = (input: ViewInput, _output: ViewOutput, ta
556
565
  ?open=${isShortcutExpanded}
557
566
  class="elements-tree-shortcut"
558
567
  style=${styleMap({'--indent': `${computeLeftIndent(shortcutDepth, hasShortcutChildren)}px`})}
559
- @select=${onShortcutSelect}
560
- @expand=${onShortcutExpand}
568
+ @select=${on(onShortcutSelect)}
569
+ @expand=${on(onShortcutExpand)}
561
570
  @mousemove=${on(onShortcutMouseMove)}
562
571
  @mouseleave=${on(onShortcutMouseLeave)}
563
- jslog=${VisualLogging.treeItem().parent('elementsTreeOutline')}>
564
- <div class="selection fill"></div>
572
+ jslog=${treeItemJslog()}>
565
573
  <span class="elements-tree-shortcut-title">\u21AA ${title}</span>
566
574
  <devtools-adorner
567
575
  .name=${ElementsComponents.AdornerManager.RegisteredAdorners.REVEAL}
@@ -581,9 +589,7 @@ export const DECLARATIVE_VIEW: View = (input: ViewInput, _output: ViewOutput, ta
581
589
  </devtools-adorner>
582
590
  ${hasShortcutChildren ? html`
583
591
  <ul role="group">
584
- ${UI.TreeOutline.ifExpanded(html`
585
- ${shortcut.childShortcuts.map(child => renderShortcut(child, shortcutDepth + 1))}
586
- `)}
592
+ ${shortcut.childShortcuts.map(child => renderShortcut(child, shortcutDepth + 1))}
587
593
  </ul>
588
594
  ` : nothing}
589
595
  </li>
@@ -611,15 +617,12 @@ export const DECLARATIVE_VIEW: View = (input: ViewInput, _output: ViewOutput, ta
611
617
  ?open=${isTopLayerExpanded}
612
618
  class="elements-tree-top-layer-container"
613
619
  style=${styleMap({'--indent': `${computeLeftIndent(containerDepth, true)}px`})}
614
- @select=${onTopLayerSelect}
615
- @expand=${onTopLayerExpand}
616
- jslog=${VisualLogging.treeItem().parent('elementsTreeOutline')}>
617
- <div class="selection fill"></div>
620
+ @select=${on(onTopLayerSelect)}
621
+ @expand=${on(onTopLayerExpand)}
622
+ jslog=${treeItemJslog()}>
618
623
  <span class="elements-tree-shortcut-title">#top-layer</span>
619
624
  <ul role="group">
620
- ${UI.TreeOutline.ifExpanded(html`
621
- ${shortcuts.map(sc => renderShortcut(sc, containerDepth + 1))}
622
- `)}
625
+ ${shortcuts.map(sc => renderShortcut(sc, containerDepth + 1))}
623
626
  </ul>
624
627
  </li>
625
628
  `;
@@ -645,10 +648,9 @@ export const DECLARATIVE_VIEW: View = (input: ViewInput, _output: ViewOutput, ta
645
648
  ?open=${isExpanded}
646
649
  class="elements-tree-adopted-style-sheet"
647
650
  style=${styleMap({'--indent': `${computeLeftIndent(depth, true)}px`})}
648
- @select=${onSelect}
649
- @expand=${onExpand}
650
- jslog=${VisualLogging.treeItem('adopted-style-sheet').parent('elementsTreeOutline')}>
651
- <div class="selection fill"></div>
651
+ @select=${on(onSelect)}
652
+ @expand=${on(onExpand)}
653
+ jslog=${treeItemJslog('adopted-style-sheet')}>
652
654
  <span class="elements-tree-shortcut-title">#adopted-style-sheet${linkText ? html` (${UIComponentUtils.Linkifier.Linkifier.linkifyURL(linkText, {
653
655
  text: linkText,
654
656
  preventClick: true,
@@ -660,8 +662,7 @@ export const DECLARATIVE_VIEW: View = (input: ViewInput, _output: ViewOutput, ta
660
662
  <li role="treeitem"
661
663
  class="elements-tree-adopted-style-sheet-contents"
662
664
  style=${styleMap({'--indent': `${computeLeftIndent(depth + 1, false)}px`})}
663
- jslog=${VisualLogging.treeItem('adopted-style-sheet-contents').parent('elementsTreeOutline')}>
664
- <div class="selection fill"></div>
665
+ jslog=${treeItemJslog('adopted-style-sheet-contents')}>
665
666
  ${UI.Widget.widget(AdoptedStyleSheetContentsWidget, {styleSheetHeader: header})}
666
667
  </li>
667
668
  ` : nothing}
@@ -691,10 +692,9 @@ export const DECLARATIVE_VIEW: View = (input: ViewInput, _output: ViewOutput, ta
691
692
  ?open=${isExpanded}
692
693
  class="elements-tree-adopted-style-sheets"
693
694
  style=${styleMap({'--indent': `${computeLeftIndent(depth, true)}px`})}
694
- @select=${onSelect}
695
- @expand=${onExpand}
696
- jslog=${VisualLogging.treeItem('adopted-style-sheets').parent('elementsTreeOutline')}>
697
- <div class="selection fill"></div>
695
+ @select=${on(onSelect)}
696
+ @expand=${on(onExpand)}
697
+ jslog=${treeItemJslog('adopted-style-sheets')}>
698
698
  <span class="elements-tree-shortcut-title">#adopted-style-sheets</span>
699
699
  <ul role="group">
700
700
  ${UI.TreeOutline.ifExpanded(html`
@@ -809,7 +809,8 @@ export const DECLARATIVE_VIEW: View = (input: ViewInput, _output: ViewOutput, ta
809
809
  };
810
810
 
811
811
  const renderNode = (node: SDK.DOMModel.DOMNode, depth = 0): Lit.LitTemplate => {
812
- const isSelected = input.selectedNode === node;
812
+ const isDOMNodeSelected = input.selectedNode === node;
813
+ const isSelected = Boolean(input.selectEnabled) && isDOMNodeSelected;
813
814
  const isOpeningHovered =
814
815
  (input.currentHighlightedNode === node) || (input.hoveredNode === node && !input.hoveredClosingTag);
815
816
  const isClosingHovered = input.hoveredNode === node && Boolean(input.hoveredClosingTag);
@@ -832,12 +833,12 @@ export const DECLARATIVE_VIEW: View = (input: ViewInput, _output: ViewOutput, ta
832
833
 
833
834
  const on = Lit.Directive.directive(Lit.CustomDirectives.InterceptBindingDirective);
834
835
 
835
- const onSelect = (isClosingTag = false): void => {
836
- input.onSelect?.(node, isClosingTag, /* selectedByUser= */ true);
836
+ const onSelect = (isClosingTag = false, selectedByUser = false): void => {
837
+ input.onSelect?.(node, isClosingTag, selectedByUser);
837
838
  };
838
839
 
839
840
  const onExpand = (event: UI.TreeOutline.TreeViewElement.ExpandEvent): void => {
840
- if (isEditingAsHTML) {
841
+ if (isEditingAsHTML || !isCollapsible) {
841
842
  return;
842
843
  }
843
844
  input.onExpand?.(node, event.detail.expanded);
@@ -848,6 +849,7 @@ export const DECLARATIVE_VIEW: View = (input: ViewInput, _output: ViewOutput, ta
848
849
  const isDraggable = !input.disableEdits && Boolean(input.isValidDragSource?.(node));
849
850
 
850
851
  const classes = classMap({
852
+ selected: isSelected && !input.selectedClosingTag,
851
853
  hovered: isOpeningHovered,
852
854
  'in-clipboard': Boolean(input.isNodeInClipboard?.(node)),
853
855
  'elements-drag-over': isDragOver,
@@ -855,13 +857,19 @@ export const DECLARATIVE_VIEW: View = (input: ViewInput, _output: ViewOutput, ta
855
857
  });
856
858
 
857
859
  const onOpeningMouseMove = (event: MouseEvent): void => {
858
- event.stopPropagation();
860
+ const currentTarget = event.currentTarget as Element | null;
861
+ if (currentTarget && (event.target as Element).closest('li[role="treeitem"]') !== currentTarget) {
862
+ return;
863
+ }
859
864
  const showInfo = !UI.KeyboardShortcut.KeyboardShortcut.eventHasEitherCtrlOrMeta(event);
860
865
  input.onHoverNode?.(node, showInfo, /* isClosingTag= */ false);
861
866
  };
862
867
 
863
868
  const onClosingMouseMove = (event: MouseEvent): void => {
864
- event.stopPropagation();
869
+ const currentTarget = event.currentTarget as Element | null;
870
+ if (currentTarget && (event.target as Element).closest('li[role="treeitem"]') !== currentTarget) {
871
+ return;
872
+ }
865
873
  const showInfo = !UI.KeyboardShortcut.KeyboardShortcut.eventHasEitherCtrlOrMeta(event);
866
874
  input.onHoverNode?.(node, showInfo, /* isClosingTag= */ true);
867
875
  };
@@ -914,10 +922,10 @@ export const DECLARATIVE_VIEW: View = (input: ViewInput, _output: ViewOutput, ta
914
922
  ?selected=${isSelected && !input.selectedClosingTag}
915
923
  class=${classes}
916
924
  style=${styleMap({'--indent': `${computeLeftIndent(depth, isExpandable)}px`})}
917
- ?open=${isExpanded && !isEditingAsHTML}
925
+ ?open=${hasChildren && isExpanded && !isEditingAsHTML}
918
926
  draggable=${isDraggable ? 'true' : 'false'}
919
- @select=${on(() => onSelect(false))}
920
- @expand=${onExpand}
927
+ @select=${on((event: UI.TreeOutline.TreeViewElement.SelectEvent) => onSelect(false, Boolean(event.detail?.selectedByUser)))}
928
+ @expand=${on(onExpand)}
921
929
  @mousemove=${on(onOpeningMouseMove)}
922
930
  @contextmenu=${on(onContextMenu)}
923
931
  @dragstart=${on(onDragStart)}
@@ -925,19 +933,15 @@ export const DECLARATIVE_VIEW: View = (input: ViewInput, _output: ViewOutput, ta
925
933
  @dragleave=${on(onDragLeave)}
926
934
  @drop=${on(onDrop)}
927
935
  @dragend=${on(onDragEnd)}
928
- jslog=${VisualLogging.treeItem().parent('elementsTreeOutline').track({
929
- keydown: 'ArrowUp|ArrowDown|ArrowLeft|ArrowRight|Backspace|Delete|Enter|Space|Home|End',
930
- resize: true,
931
- drag: true,
932
- click: true,
933
- })}>
936
+ jslog=${treeItemJslog(undefined, true)}>
934
937
  ${UI.Widget.widget(ElementsTreeWidget, {
935
938
  node,
936
939
  isClosingTag: false,
940
+ renderSelection: false,
937
941
  expanded: isExpanded && !isEditingAsHTML,
938
942
  isExpandable: hasChildren,
939
943
  selected: isSelected && !input.selectedClosingTag,
940
- isDOMNodeSelected: isSelected,
944
+ isDOMNodeSelected,
941
945
  hovered: isOpeningHovered,
942
946
  searchQuery: input.searchMatchNode === node ? (input.searchMatchQuery ?? null) : null,
943
947
  inClipboard: input.isNodeInClipboard?.(node) ?? false,
@@ -963,8 +967,7 @@ export const DECLARATIVE_VIEW: View = (input: ViewInput, _output: ViewOutput, ta
963
967
  input.onSelectNodeAfterEdit?.(wasExpanded, error, newNode, moveDirection);
964
968
  },
965
969
  toggleHideElement: (n: SDK.DOMModel.DOMNode) => {
966
- input.onToggleHideElement?.(n);
967
- return Promise.resolve();
970
+ return input.onToggleHideElement?.(n) ?? Promise.resolve();
968
971
  },
969
972
  isToggledToHidden: (n: SDK.DOMModel.DOMNode) => input.isToggledToHidden?.(n) ?? false,
970
973
  showContextMenu: (event: Event) => {
@@ -978,12 +981,12 @@ export const DECLARATIVE_VIEW: View = (input: ViewInput, _output: ViewOutput, ta
978
981
  <ul role="group">
979
982
  ${UI.TreeOutline.ifExpanded(html`
980
983
  ${node.adoptedStyleSheetsForNode.length > 0 ? renderAdoptedStyleSheets(node, depth + 1) : nothing}
981
- ${children.map(child => renderNode(child, depth + 1))}
984
+ ${repeat(children, child => child.id, child => renderNode(child, depth + 1))}
982
985
  ${remainingChildrenCount > 0 ? html`
983
986
  <li role="treeitem"
984
987
  class="elements-tree-expand-all"
985
988
  style=${styleMap({'--indent': `${computeLeftIndent(depth + 1, false)}px`})}
986
- jslog=${VisualLogging.treeItem('show-all-nodes').parent('elementsTreeOutline')}>
989
+ jslog=${treeItemJslog('show-all-nodes')}>
987
990
  <devtools-button
988
991
  .variant=${Buttons.Button.Variant.OUTLINED}
989
992
  title=${i18nString(UIStrings.showAllNodesDMore, {PH1: remainingChildrenCount})}
@@ -995,15 +998,21 @@ export const DECLARATIVE_VIEW: View = (input: ViewInput, _output: ViewOutput, ta
995
998
  </devtools-button>
996
999
  </li>
997
1000
  ` : nothing}
1001
+ ${node.isInsertionPoint() ? node.distributedNodes().map(distributedNode => renderShortcut(distributedNode, depth + 1)) : nothing}
998
1002
  ${node instanceof SDK.DOMModel.DOMDocument ? renderTopLayerContainer(node, depth + 1) : nothing}
999
1003
  ${needsClosingTag ? html`
1000
1004
  <li role="treeitem"
1001
1005
  ?selected=${isSelected && Boolean(input.selectedClosingTag)}
1002
- class=${classMap({hovered: isClosingHovered, 'elements-drag-over': isClosingTagDragOver})}
1006
+ class=${classMap({
1007
+ selected: isSelected && Boolean(input.selectedClosingTag),
1008
+ hovered: isClosingHovered,
1009
+ 'elements-drag-over': isClosingTagDragOver,
1010
+ })}
1003
1011
  style=${styleMap({'--indent': `${computeLeftIndent(depth + 1, false)}px`})}
1004
1012
  draggable=${isDraggable ? 'true' : 'false'}
1005
- jslog=${VisualLogging.treeItem().parent('elementsTreeOutline')}
1006
- @select=${on(() => onSelect(true))}
1013
+ jslog=${treeItemJslog(undefined, true)}
1014
+ @select=${on((event: UI.TreeOutline.TreeViewElement.SelectEvent) => onSelect(true, Boolean(event.detail?.selectedByUser)))}
1015
+ @contextmenu=${on(onContextMenu)}
1007
1016
  @mousemove=${on(onClosingMouseMove)}
1008
1017
  @dragstart=${on(onDragStart)}
1009
1018
  @dragover=${on(onClosingTagDragOver)}
@@ -1013,10 +1022,11 @@ export const DECLARATIVE_VIEW: View = (input: ViewInput, _output: ViewOutput, ta
1013
1022
  ${UI.Widget.widget(ElementsTreeWidget, {
1014
1023
  node,
1015
1024
  isClosingTag: true,
1025
+ renderSelection: false,
1016
1026
  expanded: false,
1017
1027
  isExpandable: false,
1018
1028
  selected: isSelected && Boolean(input.selectedClosingTag),
1019
- isDOMNodeSelected: isSelected,
1029
+ isDOMNodeSelected,
1020
1030
  hovered: isClosingHovered,
1021
1031
  computeLeftIndent: computeLeftIndent(depth + 1, false),
1022
1032
  disableEdits: input.disableEdits ?? false,
@@ -1062,7 +1072,6 @@ export const DECLARATIVE_VIEW: View = (input: ViewInput, _output: ViewOutput, ta
1062
1072
  <devtools-tree
1063
1073
  class="elements-tree-outline source-code ${input.wrap ? '' : 'elements-tree-nowrap'} ${input.hideGutter ? 'elements-hide-gutter' : ''} ${isSingleNode ? 'single-node' : ''}"
1064
1074
  disclosure-class="elements-disclosure ${isSingleNode ? 'single-node' : ''} ${input.maxRowsShown ? 'elements-tree-truncated' : ''}"
1065
- aria-label=${i18nString(UIStrings.pageDom)}
1066
1075
  jslog=${VisualLogging.tree('elements')}
1067
1076
  ?show-selection-on-keyboard-focus=${input.showSelectionOnKeyboardFocus}
1068
1077
  @enter=${(event: Event) => event.preventDefault()}
@@ -1075,10 +1084,10 @@ export const DECLARATIVE_VIEW: View = (input: ViewInput, _output: ViewOutput, ta
1075
1084
  <style>${UI.inspectorCommonStyles}</style>
1076
1085
  <style>${elementsTreeOutlineStyles}</style>
1077
1086
  <style>${CodeHighlighter.codeHighlighterStyles}</style>
1078
- <ul role="tree">
1087
+ <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
1088
  ${input.omitRootDOMNode && input.rootDOMNode && input.rootDOMNode.adoptedStyleSheetsForNode.length > 0 ?
1080
1089
  renderAdoptedStyleSheets(input.rootDOMNode, 0) : nothing}
1081
- ${rootNodes.map(node => renderNode(node))}
1090
+ ${repeat(rootNodes, node => node.id, node => renderNode(node))}
1082
1091
  ${input.omitRootDOMNode && input.rootDOMNode ? (() => {
1083
1092
  const remaining = allRootNodes.length - rootNodes.length;
1084
1093
  if (remaining <= 0) {
@@ -1089,14 +1098,14 @@ export const DECLARATIVE_VIEW: View = (input: ViewInput, _output: ViewOutput, ta
1089
1098
  <li role="treeitem"
1090
1099
  class="elements-tree-expand-all"
1091
1100
  style=${styleMap({'--indent': `${computeLeftIndent(0, false)}px`})}
1092
- jslog=${VisualLogging.treeItem('show-all-nodes').parent('elementsTreeOutline')}>
1101
+ jslog=${treeItemJslog('show-all-nodes')}>
1093
1102
  <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
- })}>
1103
+ .variant=${Buttons.Button.Variant.OUTLINED}
1104
+ title=${i18nString(UIStrings.showAllNodesDMore, {PH1: remaining})}
1105
+ @click=${on((event: Event) => {
1106
+ event.stopPropagation();
1107
+ input.onExpandAllChildren?.(root);
1108
+ })}>
1100
1109
  ${i18nString(UIStrings.showAllNodesDMore, {PH1: remaining})}
1101
1110
  </devtools-button>
1102
1111
  </li>
@@ -1154,6 +1163,8 @@ function getElementsTreeWidgetAndNode(element: Element): {node?: SDK.DOMModel.DO
1154
1163
  * to DOMTreeWidget.ts.
1155
1164
  */
1156
1165
  export class DOMTreeWidget extends UI.Widget.Widget {
1166
+ static override readonly INJECT: readonly[typeof ChangeTracker.ChangeTracker.ChangeTracker] =
1167
+ [ChangeTracker.ChangeTracker.ChangeTracker] as const;
1157
1168
  omitRootDOMNode = false;
1158
1169
  selectEnabled = false;
1159
1170
  hideGutter = false;
@@ -1179,6 +1190,7 @@ export class DOMTreeWidget extends UI.Widget.Widget {
1179
1190
  #visibleWidth?: number;
1180
1191
  #wrap = false;
1181
1192
  #maxRows?: number;
1193
+ #changeTracker?: ChangeTracker.ChangeTracker.ChangeTracker;
1182
1194
 
1183
1195
  // If maxRows is undefined, all rows are shown. If it is set to a number, only that many rows are shown.
1184
1196
  set maxRows(maxRows: number|undefined) {
@@ -1210,9 +1222,11 @@ export class DOMTreeWidget extends UI.Widget.Widget {
1210
1222
  }
1211
1223
  this.#expandedNodes.add(node);
1212
1224
  const expandChildren = (): void => {
1213
- if (this.#expandRoot && this.omitRootDOMNode && node.children()) {
1214
- for (const child of node.children() ?? []) {
1215
- this.#expandedNodes.add(child);
1225
+ for (const child of node.children() ?? []) {
1226
+ const isNotCollapsible = child.nodeType() === Node.ELEMENT_NODE &&
1227
+ child.parentNode?.nodeType() === Node.DOCUMENT_NODE && !child.parentNode.parentNode;
1228
+ if (isNotCollapsible || (this.#expandRoot && this.omitRootDOMNode)) {
1229
+ this.setNodeExpanded(child, true);
1216
1230
  }
1217
1231
  }
1218
1232
  };
@@ -1275,6 +1289,7 @@ export class DOMTreeWidget extends UI.Widget.Widget {
1275
1289
 
1276
1290
  set showComments(showComments: boolean) {
1277
1291
  this.#showComments = showComments;
1292
+ this.#validateSelectedNode();
1278
1293
  this.performUpdate();
1279
1294
  }
1280
1295
 
@@ -1330,8 +1345,20 @@ export class DOMTreeWidget extends UI.Widget.Widget {
1330
1345
  };
1331
1346
  #highlightThrottler = new Common.Throttler.Throttler(100);
1332
1347
 
1348
+ get changeTracker(): ChangeTracker.ChangeTracker.ChangeTracker|undefined {
1349
+ // The widget is also created without dependency injection (e.g. by
1350
+ // ElementsPanel), so the changeTracker can be empty, relying on the manual
1351
+ // resolution. Note that this only works once the widget is attached to the
1352
+ // DOM.
1353
+ this.#changeTracker ??=
1354
+ UI.Widget.lookupUniverseForElement(this.contentElement)?.get(ChangeTracker.ChangeTracker.ChangeTracker);
1355
+ return this.#changeTracker;
1356
+ }
1357
+
1333
1358
  constructor(
1334
1359
  element?: HTMLElement,
1360
+ [changeTracker]:
1361
+ UI.Widget.WidgetDependencies<typeof DOMTreeWidget>|[ChangeTracker.ChangeTracker.ChangeTracker?] = [],
1335
1362
  view: View = DEFAULT_VIEW,
1336
1363
  ) {
1337
1364
  super(element, {
@@ -1339,6 +1366,7 @@ export class DOMTreeWidget extends UI.Widget.Widget {
1339
1366
  delegatesFocus: false,
1340
1367
  });
1341
1368
  this.#view = view;
1369
+ this.#changeTracker = changeTracker;
1342
1370
  this.#showHTMLCommentsSetting.addChangeListener(this.#onShowHTMLCommentsChange, this);
1343
1371
  if (Common.Settings.Settings.instance().moduleSetting('highlight-node-on-hover-in-overlay').get()) {
1344
1372
  SDK.TargetManager.TargetManager.instance().addModelListener(
@@ -1470,10 +1498,40 @@ export class DOMTreeWidget extends UI.Widget.Widget {
1470
1498
  this.#updateModifiedNodesSoon();
1471
1499
  }
1472
1500
 
1501
+ #findNextNodeOnRemoval(node: SDK.DOMModel.DOMNode, parent: SDK.DOMModel.DOMNode|null): SDK.DOMModel.DOMNode|null {
1502
+ const visibleChildren = parent ? getVisibleChildren(parent, this.#showComments) : [];
1503
+ if (visibleChildren.length > 0) {
1504
+ let candidate: SDK.DOMModel.DOMNode|null = node.nextSibling;
1505
+ while (candidate) {
1506
+ if (visibleChildren.includes(candidate)) {
1507
+ return candidate;
1508
+ }
1509
+ candidate = candidate.nextSibling;
1510
+ }
1511
+ candidate = node.previousSibling;
1512
+ while (candidate) {
1513
+ if (visibleChildren.includes(candidate)) {
1514
+ return candidate;
1515
+ }
1516
+ candidate = candidate.previousSibling;
1517
+ }
1518
+ }
1519
+ if (parent) {
1520
+ if (parent.nodeType() === Node.DOCUMENT_NODE) {
1521
+ return parent.parentNode;
1522
+ }
1523
+ return parent;
1524
+ }
1525
+ return null;
1526
+ }
1527
+
1473
1528
  #onNodeRemoved(
1474
1529
  event: Common.EventTarget.EventTargetEvent<{node: SDK.DOMModel.DOMNode, parent: SDK.DOMModel.DOMNode}>): void {
1475
1530
  const {node, parent} = event.data;
1476
1531
  this.resetClipboardIfNeeded(node);
1532
+ if (this.#selectedDOMNode && (this.#selectedDOMNode === node || node.isAncestor(this.#selectedDOMNode))) {
1533
+ this.selectDOMNode(this.#findNextNodeOnRemoval(node, parent), true);
1534
+ }
1477
1535
  if (parent) {
1478
1536
  this.#addUpdateRecord(parent).nodeRemoved(node);
1479
1537
  }
@@ -1524,8 +1582,12 @@ export class DOMTreeWidget extends UI.Widget.Widget {
1524
1582
  this.#updateModifiedNodesSoon();
1525
1583
  }
1526
1584
 
1527
- #onMarkersChanged(): void {
1528
- this.#updateModifiedNodesSoon();
1585
+ #onMarkersChanged(event: Common.EventTarget.EventTargetEvent<SDK.DOMModel.DOMNode>): void {
1586
+ const node = event.data;
1587
+ if (node) {
1588
+ this.#addUpdateRecord(node);
1589
+ }
1590
+ this.performUpdate();
1529
1591
  }
1530
1592
 
1531
1593
  updateModifiedNodes(): void {
@@ -1534,6 +1596,7 @@ export class DOMTreeWidget extends UI.Widget.Widget {
1534
1596
  this.#updateModifiedNodesTimeout = undefined;
1535
1597
  }
1536
1598
  if (this.#view === DECLARATIVE_VIEW) {
1599
+ this.#validateSelectedNode();
1537
1600
  this.performUpdate();
1538
1601
  return;
1539
1602
  }
@@ -1554,9 +1617,17 @@ export class DOMTreeWidget extends UI.Widget.Widget {
1554
1617
  domModel.addEventListener(SDK.DOMModel.Events.DistributedNodesChanged, this.#onDistributedNodesChanged, this);
1555
1618
  domModel.addEventListener(SDK.DOMModel.Events.DocumentURLChanged, this.#onDocumentURLChanged, this);
1556
1619
  domModel.addEventListener(SDK.DOMModel.Events.AffectedByStartingStylesFlagUpdated, this.requestUpdate, this);
1620
+ const cssModel = domModel.target().model(SDK.CSSModel.CSSModel);
1621
+ if (cssModel) {
1622
+ cssModel.addEventListener(SDK.CSSModel.Events.StyleSheetAdded, this.requestUpdate, this);
1623
+ }
1557
1624
  }
1558
1625
 
1559
1626
  #unwireDOMModel(domModel: SDK.DOMModel.DOMModel): void {
1627
+ const cssModel = domModel.target().model(SDK.CSSModel.CSSModel);
1628
+ if (cssModel) {
1629
+ cssModel.removeEventListener(SDK.CSSModel.Events.StyleSheetAdded, this.requestUpdate, this);
1630
+ }
1560
1631
  domModel.removeEventListener(SDK.DOMModel.Events.DocumentUpdated, this.#onDocumentUpdated, this);
1561
1632
  domModel.removeEventListener(SDK.DOMModel.Events.NodeInserted, this.#onNodeInserted, this);
1562
1633
  domModel.removeEventListener(SDK.DOMModel.Events.NodeRemoved, this.#onNodeRemoved, this);
@@ -1575,6 +1646,7 @@ export class DOMTreeWidget extends UI.Widget.Widget {
1575
1646
 
1576
1647
  #onShowHTMLCommentsChange(): void {
1577
1648
  this.#showComments = this.#showHTMLCommentsSetting.get();
1649
+ this.#validateSelectedNode();
1578
1650
  const selectedNode = this.selectedDOMNode();
1579
1651
  if (selectedNode && selectedNode.nodeType() === Node.COMMENT_NODE && !this.#showComments) {
1580
1652
  this.selectDOMNode(selectedNode.parentNode);
@@ -1601,7 +1673,7 @@ export class DOMTreeWidget extends UI.Widget.Widget {
1601
1673
  }
1602
1674
 
1603
1675
  selectDOMNode(node: SDK.DOMModel.DOMNode|SDK.DOMModel.AdoptedStyleSheet|null, focus?: boolean,
1604
- isClosingTag = false): void {
1676
+ isClosingTag?: boolean): void {
1605
1677
  this.#imagePreviewPopover?.hide();
1606
1678
  this.#issuePopoverHelper?.hidePopover();
1607
1679
  if (node instanceof SDK.DOMModel.AdoptedStyleSheet) {
@@ -1610,9 +1682,9 @@ export class DOMTreeWidget extends UI.Widget.Widget {
1610
1682
  }
1611
1683
  this.#selectedAdoptedStyleSheet = null;
1612
1684
  if (this.#view === DECLARATIVE_VIEW) {
1613
- this.#selectedClosingTag = Boolean(isClosingTag);
1614
- const isSameNode = this.#selectedDOMNode === node;
1685
+ const isSameNode = this.#selectedDOMNode === node && this.#selectedClosingTag === Boolean(isClosingTag);
1615
1686
  this.#selectedDOMNode = node;
1687
+ this.#selectedClosingTag = Boolean(isClosingTag);
1616
1688
  if (node) {
1617
1689
  const ancestors: SDK.DOMModel.DOMNode[] = [];
1618
1690
  for (let current: (SDK.DOMModel.DOMNode|null) = node.parentNode; current; current = current.parentNode) {
@@ -1646,6 +1718,15 @@ export class DOMTreeWidget extends UI.Widget.Widget {
1646
1718
  Common.EventTarget.EventTargetEvent<{node: SDK.DOMModel.DOMNode | null, focus: boolean}>);
1647
1719
  }
1648
1720
  this.performUpdate();
1721
+ if (focus) {
1722
+ this.focus();
1723
+ }
1724
+ return;
1725
+ }
1726
+ if (isClosingTag && node) {
1727
+ const rootTreeElement = this.#viewOutput?.elementsTreeOutline?.findTreeElement(node);
1728
+ const closingTreeElement = rootTreeElement?.childAt(rootTreeElement.childCount() - 1);
1729
+ closingTreeElement?.select(!focus, true);
1649
1730
  return;
1650
1731
  }
1651
1732
  this.#viewOutput?.elementsTreeOutline?.selectDOMNode(node, focus);
@@ -1684,7 +1765,7 @@ export class DOMTreeWidget extends UI.Widget.Widget {
1684
1765
  if (this.#view === DECLARATIVE_VIEW) {
1685
1766
  if (expanded) {
1686
1767
  this.#expandedNodes.add(node);
1687
- if (!node.children() && node.childNodeCount()) {
1768
+ if (!node.children() && (node.childNodeCount() || node.isIframe() || node.nodeType() === Node.DOCUMENT_NODE)) {
1688
1769
  void node.getChildNodes(() => {
1689
1770
  this.performUpdate();
1690
1771
  });
@@ -1710,6 +1791,11 @@ export class DOMTreeWidget extends UI.Widget.Widget {
1710
1791
 
1711
1792
  isNodeExpanded(node: SDK.DOMModel.DOMNode): boolean {
1712
1793
  if (this.#view === DECLARATIVE_VIEW) {
1794
+ const isNotCollapsible = node.nodeType() === Node.ELEMENT_NODE &&
1795
+ node.parentNode?.nodeType() === Node.DOCUMENT_NODE && !node.parentNode.parentNode;
1796
+ if (isNotCollapsible) {
1797
+ return true;
1798
+ }
1713
1799
  return this.#expandedNodes.has(node);
1714
1800
  }
1715
1801
  return Boolean(this.#viewOutput.elementsTreeOutline?.findTreeElement(node)?.expanded);
@@ -1741,22 +1827,26 @@ export class DOMTreeWidget extends UI.Widget.Widget {
1741
1827
 
1742
1828
  async expandRecursively(node: SDK.DOMModel.DOMNode, maxDepth: number = Number.MAX_VALUE): Promise<void> {
1743
1829
  if (this.#view === DECLARATIVE_VIEW) {
1744
- await node.getSubtree(100, true);
1745
1830
  const expand = async(n: SDK.DOMModel.DOMNode, depth: number): Promise<void> => {
1746
1831
  if (depth > maxDepth) {
1747
1832
  return;
1748
1833
  }
1749
- 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() ?? []));
1754
- });
1834
+ if (n.childDocumentPromiseForTesting) {
1835
+ await n.childDocumentPromiseForTesting;
1836
+ }
1837
+ if (!n.children() && !n.contentDocument() &&
1838
+ (n === node || n.childNodeCount() || n.isIframe() || n.nodeType() === Node.DOCUMENT_NODE)) {
1839
+ await n.getSubtree(100, true);
1755
1840
  }
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)));
1841
+ if (nodeHasVisibleChildren(n, this.rootDOMNode, this.maxTreeDepth, this.omitRootDOMNode)) {
1842
+ this.#expandedNodes.add(n);
1843
+ }
1844
+ const visibleChildren = getVisibleChildren(n, this.#showComments);
1845
+ if (visibleChildren.length) {
1846
+ if (visibleChildren.length > this.expandedChildrenLimit(n)) {
1847
+ this.setExpandedChildrenLimit(n, visibleChildren.length);
1848
+ }
1849
+ await Promise.all(visibleChildren.map(child => expand(child, depth + 1)));
1760
1850
  }
1761
1851
  };
1762
1852
  await expand(node, 0);
@@ -1812,7 +1902,6 @@ export class DOMTreeWidget extends UI.Widget.Widget {
1812
1902
  treeElementForNode(node: SDK.DOMModel.DOMNode): ElementsTreeElement|null {
1813
1903
  return this.#viewOutput.elementsTreeOutline?.findTreeElement(node) || null;
1814
1904
  }
1815
-
1816
1905
  #hoveredDOMNode: SDK.DOMModel.DOMNode|null = null;
1817
1906
  #hoveredClosingTag = false;
1818
1907
  #selectedClosingTag = false;
@@ -1827,6 +1916,7 @@ export class DOMTreeWidget extends UI.Widget.Widget {
1827
1916
  #draggedNode: SDK.DOMModel.DOMNode|null = null;
1828
1917
  #draggedNodeWasExpanded = false;
1829
1918
  #dragOverNode: {node: SDK.DOMModel.DOMNode, isClosingTag: boolean}|null = null;
1919
+ #isFocusing = false;
1830
1920
 
1831
1921
  hoveredDOMNode(): SDK.DOMModel.DOMNode|null {
1832
1922
  if (this.#view === DECLARATIVE_VIEW) {
@@ -1871,16 +1961,83 @@ export class DOMTreeWidget extends UI.Widget.Widget {
1871
1961
  if (this.#view === DECLARATIVE_VIEW) {
1872
1962
  this.performUpdate();
1873
1963
  } else {
1874
- const treeElement = node ? this.treeElementForNode(node) : null;
1964
+ let treeElement = node ? this.treeElementForNode(node) : null;
1965
+ if (isClosingTag && treeElement) {
1966
+ treeElement = treeElement.childAt(treeElement.childCount() - 1) as ElementsTreeElement | null;
1967
+ }
1875
1968
  this.#viewOutput.elementsTreeOutline?.setHoverEffect(treeElement);
1876
1969
  }
1877
1970
  }
1878
1971
 
1972
+ #findValidSelectedNode(selectedNode: SDK.DOMModel.DOMNode): SDK.DOMModel.DOMNode|null {
1973
+ if (!this.#rootDOMNode) {
1974
+ return null;
1975
+ }
1976
+ if (selectedNode === this.#rootDOMNode) {
1977
+ return selectedNode;
1978
+ }
1979
+ let current: SDK.DOMModel.DOMNode|null = selectedNode;
1980
+ let fallback: SDK.DOMModel.DOMNode|null = null;
1981
+ while (current && current !== this.#rootDOMNode) {
1982
+ const parent: SDK.DOMModel.DOMNode|null = current.parentNode;
1983
+ if (!parent) {
1984
+ return fallback ?? this.#rootDOMNode;
1985
+ }
1986
+ if (parent.children() !== null || parent.childNodeCount() === 0 || parent.isIframe()) {
1987
+ const visibleChildren = getVisibleChildren(parent, this.#showComments);
1988
+ if (!visibleChildren.includes(current)) {
1989
+ let sibling: SDK.DOMModel.DOMNode|null = null;
1990
+ let candidate: SDK.DOMModel.DOMNode|null = current.nextSibling;
1991
+ while (candidate) {
1992
+ if (visibleChildren.includes(candidate)) {
1993
+ sibling = candidate;
1994
+ break;
1995
+ }
1996
+ candidate = candidate.nextSibling;
1997
+ }
1998
+ if (!sibling) {
1999
+ candidate = current.previousSibling;
2000
+ while (candidate) {
2001
+ if (visibleChildren.includes(candidate)) {
2002
+ sibling = candidate;
2003
+ break;
2004
+ }
2005
+ candidate = candidate.previousSibling;
2006
+ }
2007
+ }
2008
+ if (sibling) {
2009
+ return this.#findValidSelectedNode(sibling);
2010
+ }
2011
+ return this.#findValidSelectedNode(parent);
2012
+ }
2013
+ }
2014
+ fallback = parent;
2015
+ current = parent;
2016
+ }
2017
+ return current === this.#rootDOMNode ? selectedNode : (fallback ?? this.#rootDOMNode);
2018
+ }
2019
+
2020
+ #validateSelectedNode(): void {
2021
+ if (!this.#selectedDOMNode) {
2022
+ return;
2023
+ }
2024
+ const validNode = this.#findValidSelectedNode(this.#selectedDOMNode);
2025
+ if (validNode !== this.#selectedDOMNode) {
2026
+ this.#selectedDOMNode = validNode;
2027
+ this.#selectedClosingTag = false;
2028
+ this.#clearHighlightedNode();
2029
+ this.onSelectedNodeChanged(
2030
+ {data: {node: validNode, focus: false}} as
2031
+ Common.EventTarget.EventTargetEvent<{node: SDK.DOMModel.DOMNode | null, focus: boolean}>);
2032
+ }
2033
+ }
2034
+
1879
2035
  override performUpdate(): void {
1880
2036
  if (this.#updateModifiedNodesTimeout) {
1881
2037
  clearTimeout(this.#updateModifiedNodesTimeout);
1882
2038
  this.#updateModifiedNodesTimeout = undefined;
1883
2039
  }
2040
+ this.#validateSelectedNode();
1884
2041
  const firstRender = !this.#viewOutput.elementsTreeOutline;
1885
2042
  const updatedNodes = this.#view === DECLARATIVE_VIEW ? [...this.#updateRecords.keys()] : [];
1886
2043
  this.#view({
@@ -1931,6 +2088,9 @@ export class DOMTreeWidget extends UI.Widget.Widget {
1931
2088
  this.setHoveredNode(null);
1932
2089
  },
1933
2090
  onSelect: (node: SDK.DOMModel.DOMNode, isClosingTag = false, selectedByUser?: boolean) => {
2091
+ if (!selectedByUser) {
2092
+ return;
2093
+ }
1934
2094
  this.selectDOMNode(node, selectedByUser, isClosingTag);
1935
2095
  },
1936
2096
  onExpand: (node: SDK.DOMModel.DOMNode, expanded: boolean) => {
@@ -1940,7 +2100,7 @@ export class DOMTreeWidget extends UI.Widget.Widget {
1940
2100
  void this.showContextMenu(node, event);
1941
2101
  },
1942
2102
  onToggleHideElement: (node: SDK.DOMModel.DOMNode) => {
1943
- this.toggleHideElement(node);
2103
+ return this.toggleHideElement(node);
1944
2104
  },
1945
2105
  onKeyDown: (event: KeyboardEvent) => {
1946
2106
  this.onKeyDown(event);
@@ -2134,6 +2294,20 @@ export class DOMTreeWidget extends UI.Widget.Widget {
2134
2294
  * FIXME: which node is selected should be part of the view input.
2135
2295
  */
2136
2296
  selectDOMNodeWithoutReveal(node: SDK.DOMModel.DOMNode): void {
2297
+ if (this.#view === DECLARATIVE_VIEW) {
2298
+ const isSameNode = this.#selectedDOMNode === node && !this.#selectedClosingTag;
2299
+ if (isSameNode) {
2300
+ return;
2301
+ }
2302
+ this.#selectedDOMNode = node;
2303
+ this.#selectedClosingTag = false;
2304
+ this.#clearHighlightedNode();
2305
+ this.onSelectedNodeChanged(
2306
+ {data: {node, focus: false}} as
2307
+ Common.EventTarget.EventTargetEvent<{node: SDK.DOMModel.DOMNode | null, focus: boolean}>);
2308
+ this.performUpdate();
2309
+ return;
2310
+ }
2137
2311
  this.#viewOutput.elementsTreeOutline?.findTreeElement(node)?.select();
2138
2312
  }
2139
2313
 
@@ -2165,8 +2339,11 @@ export class DOMTreeWidget extends UI.Widget.Widget {
2165
2339
  }
2166
2340
  }
2167
2341
 
2168
- toggleHideElement(node: SDK.DOMModel.DOMNode): void {
2169
- void node.toggleHideElement();
2342
+ async toggleHideElement(node: SDK.DOMModel.DOMNode): Promise<void> {
2343
+ const changeTracker = this.changeTracker;
2344
+ Elements.DOMChanges.trackVisibilityToggle(changeTracker, node, buildChangeSelector(changeTracker, node),
2345
+ !this.isToggledToHidden(node));
2346
+ await node.toggleHideElement();
2170
2347
  }
2171
2348
 
2172
2349
  async removeNode(node: SDK.DOMModel.DOMNode): Promise<void> {
@@ -2180,7 +2357,14 @@ export class DOMTreeWidget extends UI.Widget.Widget {
2180
2357
  if (!node.parentNode || node.parentNode.nodeType() === Node.DOCUMENT_NODE) {
2181
2358
  return;
2182
2359
  }
2183
- void node.removeNode();
2360
+ // The selector has to be resolved before the node is detached from the tree.
2361
+ const changeTracker = this.changeTracker;
2362
+ const selector = buildChangeSelector(changeTracker, node);
2363
+ await node.removeNode((err: string|null) => {
2364
+ if (!err) {
2365
+ Elements.DOMChanges.trackNodeRemoval(changeTracker, node, selector);
2366
+ }
2367
+ });
2184
2368
  }
2185
2369
 
2186
2370
  isToggledToHidden(node: SDK.DOMModel.DOMNode): boolean {
@@ -2292,7 +2476,14 @@ export class DOMTreeWidget extends UI.Widget.Widget {
2292
2476
  }
2293
2477
 
2294
2478
  duplicateNode(node: SDK.DOMModel.DOMNode): void {
2295
- void node.duplicate();
2479
+ void node.duplicate().then(({error, node: newNode}) => {
2480
+ if (!error) {
2481
+ const duplicatedNode = newNode ?? node;
2482
+ const changeTracker = this.changeTracker;
2483
+ Elements.DOMChanges.trackNodeDuplication(changeTracker, duplicatedNode,
2484
+ buildChangeSelector(changeTracker, duplicatedNode));
2485
+ }
2486
+ });
2296
2487
  }
2297
2488
 
2298
2489
  nodeBeingDragged(): SDK.DOMModel.DOMNode|null {
@@ -2431,6 +2622,11 @@ export class DOMTreeWidget extends UI.Widget.Widget {
2431
2622
  }
2432
2623
  const wasExpanded = this.#draggedNodeWasExpanded;
2433
2624
  draggedNode.moveTo(parentNode, anchorNode, (error, newNode) => {
2625
+ if (!error) {
2626
+ const movedNode = newNode ?? draggedNode;
2627
+ const changeTracker = this.changeTracker;
2628
+ Elements.DOMChanges.trackNodeDrop(changeTracker, movedNode, buildChangeSelector(changeTracker, movedNode));
2629
+ }
2434
2630
  this.selectNodeAfterEdit(wasExpanded, error, newNode);
2435
2631
  });
2436
2632
  }
@@ -2595,7 +2791,8 @@ export class DOMTreeWidget extends UI.Widget.Widget {
2595
2791
  }
2596
2792
  this.performUpdate();
2597
2793
  } else if (nodeType === Node.TEXT_NODE) {
2598
- this.startEditingTextNode(node);
2794
+ this.#nodeToEdit = {node, isTextNode: true};
2795
+ this.performUpdate();
2599
2796
  } else if (nodeType === Node.PROCESSING_INSTRUCTION_NODE) {
2600
2797
  this.#nodeToEdit = {node, isProcessingInstruction: true};
2601
2798
  this.performUpdate();
@@ -2615,6 +2812,12 @@ export class DOMTreeWidget extends UI.Widget.Widget {
2615
2812
  const wasExpanded = this.isNodeExpanded(node);
2616
2813
  if (event.key === 'ArrowUp' && node.previousSibling) {
2617
2814
  node.moveTo(node.parentNode, node.previousSibling, (error, newNode) => {
2815
+ if (!error) {
2816
+ const movedNode = newNode ?? node;
2817
+ const changeTracker = this.changeTracker;
2818
+ Elements.DOMChanges.trackNodeMove(changeTracker, movedNode, buildChangeSelector(changeTracker, movedNode),
2819
+ /* directionUp= */ true);
2820
+ }
2618
2821
  this.selectNodeAfterEdit(wasExpanded, error, newNode);
2619
2822
  });
2620
2823
  event.consume(true);
@@ -2622,6 +2825,12 @@ export class DOMTreeWidget extends UI.Widget.Widget {
2622
2825
  }
2623
2826
  if (event.key === 'ArrowDown' && node.nextSibling) {
2624
2827
  node.moveTo(node.parentNode, node.nextSibling.nextSibling, (error, newNode) => {
2828
+ if (!error) {
2829
+ const movedNode = newNode ?? node;
2830
+ const changeTracker = this.changeTracker;
2831
+ Elements.DOMChanges.trackNodeMove(changeTracker, movedNode, buildChangeSelector(changeTracker, movedNode),
2832
+ /* directionUp= */ false);
2833
+ }
2625
2834
  this.selectNodeAfterEdit(wasExpanded, error, newNode);
2626
2835
  });
2627
2836
  event.consume(true);
@@ -2648,7 +2857,7 @@ export class DOMTreeWidget extends UI.Widget.Widget {
2648
2857
  }
2649
2858
 
2650
2859
  if (event.key === 'h' || event.key === 'H') {
2651
- this.toggleHideElement(node);
2860
+ void this.toggleHideElement(node);
2652
2861
  event.consume(true);
2653
2862
  return true;
2654
2863
  }
@@ -2783,11 +2992,23 @@ export class DOMTreeWidget extends UI.Widget.Widget {
2783
2992
  const clipboardNode = this.#clipboardData.node;
2784
2993
  if (this.#clipboardData.isCut) {
2785
2994
  clipboardNode.moveTo(targetNode, null, (error, newNode) => {
2995
+ if (!error) {
2996
+ const movedNode = newNode ?? clipboardNode;
2997
+ const changeTracker = this.changeTracker;
2998
+ Elements.DOMChanges.trackNodePaste(changeTracker, movedNode, buildChangeSelector(changeTracker, movedNode),
2999
+ /* isCut= */ true);
3000
+ }
2786
3001
  this.selectNodeAfterEdit(wasExpanded, error, newNode);
2787
3002
  });
2788
3003
  this.setClipboardData(null);
2789
3004
  } else {
2790
3005
  clipboardNode.copyTo(targetNode, null, (error, newNode) => {
3006
+ if (!error) {
3007
+ const addedNode = newNode ?? clipboardNode;
3008
+ const changeTracker = this.changeTracker;
3009
+ Elements.DOMChanges.trackNodePaste(changeTracker, addedNode, buildChangeSelector(changeTracker, addedNode),
3010
+ /* isCut= */ false);
3011
+ }
2791
3012
  this.selectNodeAfterEdit(wasExpanded, error, newNode);
2792
3013
  });
2793
3014
  }
@@ -2852,8 +3073,20 @@ export class DOMTreeWidget extends UI.Widget.Widget {
2852
3073
  }
2853
3074
 
2854
3075
  override focus(): void {
2855
- super.focus();
2856
- this.#viewOutput.elementsTreeOutline?.focus();
3076
+ if (this.#isFocusing) {
3077
+ return;
3078
+ }
3079
+ this.#isFocusing = true;
3080
+ try {
3081
+ super.focus();
3082
+ if (this.#view === DECLARATIVE_VIEW) {
3083
+ this.contentElement.querySelector('devtools-tree')?.focus();
3084
+ } else {
3085
+ this.#viewOutput.elementsTreeOutline?.focus();
3086
+ }
3087
+ } finally {
3088
+ this.#isFocusing = false;
3089
+ }
2857
3090
  }
2858
3091
 
2859
3092
  override wasShown(): void {
@@ -2871,6 +3104,7 @@ export class DOMTreeWidget extends UI.Widget.Widget {
2871
3104
  }
2872
3105
  if (this.#view === DECLARATIVE_VIEW) {
2873
3106
  this.#updateRecords.clear();
3107
+ return;
2874
3108
  }
2875
3109
  this.performUpdate();
2876
3110
  }
@@ -2885,6 +3119,7 @@ export class DOMTreeWidget extends UI.Widget.Widget {
2885
3119
  }
2886
3120
  if (this.#view === DECLARATIVE_VIEW) {
2887
3121
  this.#updateRecords.clear();
3122
+ return;
2888
3123
  }
2889
3124
  this.performUpdate();
2890
3125
  }