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
@@ -0,0 +1,173 @@
1
+ /*
2
+ * Copyright 2026 The Chromium Authors
3
+ * Use of this source code is governed by a BSD-style license that can be
4
+ * found in the LICENSE file.
5
+ */
6
+
7
+ @scope to (devtools-widget > *) {
8
+ .comment-thread-widget {
9
+ display: flex;
10
+ width: var(--sys-size-30);
11
+ padding: var(--sys-size-8);
12
+ flex-direction: column;
13
+ align-items: flex-start;
14
+ gap: var(--sys-size-4);
15
+ border-radius: var(--sys-shape-corner-medium);
16
+ background: var(--sys-color-base);
17
+ box-shadow: var(--sys-elevation-level2);
18
+ cursor: default;
19
+ }
20
+
21
+ .comment-thread-widget.submitted {
22
+ background: var(--sys-color-surface2);
23
+ }
24
+
25
+ .header {
26
+ display: flex;
27
+ width: 100%;
28
+ justify-content: space-between;
29
+ align-items: center;
30
+ }
31
+
32
+ .sent-status {
33
+ display: flex;
34
+ align-items: center;
35
+ gap: var(--sys-size-2);
36
+ font-size: var(--sys-typescale-body5-size);
37
+ color: var(--sys-color-on-surface-subtle);
38
+ }
39
+
40
+ .check-icon {
41
+ width: var(--sys-size-7);
42
+ height: var(--sys-size-7);
43
+ color: var(--sys-color-on-surface-subtle);
44
+ }
45
+
46
+ .selected-item {
47
+ display: flex;
48
+ height: calc(var(--sys-size-8) + var(--sys-size-2));
49
+ padding: var(--sys-size-2) var(--sys-size-4) var(--sys-size-2) var(--sys-size-3);
50
+ align-items: center;
51
+ border-radius: var(--sys-shape-corner-extra-small);
52
+ border: var(--sys-size-1) solid var(--sys-color-divider);
53
+ background: var(--sys-color-base);
54
+ font-family: var(--source-code-font-family);
55
+ font-size: var(--monospace-font-size);
56
+ color: var(--sys-color-token-tag);
57
+ overflow: hidden;
58
+ max-width: 100%;
59
+ min-width: 0;
60
+ }
61
+
62
+ .comment-text {
63
+ width: 100%;
64
+ font-family: var(--default-font-family);
65
+ font-size: var(--sys-typescale-body4-size);
66
+ color: var(--sys-color-on-surface-subtle);
67
+ line-height: var(--sys-size-9);
68
+ overflow-wrap: break-word;
69
+ white-space: pre-wrap;
70
+ }
71
+
72
+ .agent-response {
73
+ display: flex;
74
+ gap: var(--sys-size-2);
75
+ margin-top: var(--sys-size-4);
76
+ width: 100%;
77
+ align-items: flex-start;
78
+ }
79
+
80
+ .elbow {
81
+ width: var(--sys-size-6);
82
+ height: var(--sys-size-6);
83
+ margin-left: var(--sys-size-3);
84
+ margin-right: var(--sys-size-3);
85
+ border-left: var(--sys-size-1) solid var(--sys-color-neutral-outline);
86
+ border-bottom: var(--sys-size-1) solid var(--sys-color-neutral-outline);
87
+ border-bottom-left-radius: var(--sys-size-6);
88
+ flex-shrink: 0;
89
+ }
90
+
91
+ .response-content {
92
+ display: flex;
93
+ flex-direction: column;
94
+ flex: 1;
95
+ min-width: 0;
96
+
97
+ --code-block-background-color: var(--sys-color-base);
98
+ --code-background-color: var(--sys-color-base);
99
+ }
100
+
101
+ .response-header {
102
+ font-weight: bold;
103
+ margin-bottom: var(--sys-size-2);
104
+ font-family: var(--default-font-family);
105
+ font-size: var(--sys-typescale-body5-size);
106
+ line-height: var(--sys-size-8);
107
+ color: var(--sys-color-on-surface-subtle);
108
+ }
109
+
110
+ .response-content devtools-markdown-view {
111
+ font-family: var(--default-font-family);
112
+ font-size: var(--sys-typescale-body4-size);
113
+ color: var(--sys-color-on-surface-subtle);
114
+ line-height: var(--sys-size-9);
115
+ }
116
+
117
+ .input {
118
+ height: auto;
119
+ min-height: var(--sys-size-14);
120
+ max-height: var(--sys-size-27);
121
+ field-sizing: content;
122
+ resize: none;
123
+ scrollbar-width: none;
124
+ padding: var(--sys-size-5) var(--sys-size-8);
125
+ border-radius: var(--sys-shape-corner-small);
126
+ border: var(--sys-size-1) solid var(--sys-color-outline);
127
+ background: var(--sys-color-base);
128
+ font-family: var(--default-font-family);
129
+ font-size: var(--sys-typescale-body5-size);
130
+ line-height: var(--sys-size-9);
131
+ letter-spacing: 0;
132
+ }
133
+
134
+ .input::placeholder {
135
+ color: var(--sys-color-on-surface-subtle);
136
+ }
137
+
138
+ .footer {
139
+ display: flex;
140
+ width: 100%;
141
+ justify-content: flex-end;
142
+ align-items: center;
143
+ gap: var(--sys-size-5);
144
+ }
145
+
146
+ .info-icon {
147
+ color: var(--sys-color-on-surface-subtle);
148
+ cursor: pointer;
149
+ }
150
+
151
+ .info-tooltip-container {
152
+ max-width: var(--sys-size-28);
153
+ padding: var(--sys-size-4) var(--sys-size-5);
154
+ }
155
+
156
+ .tooltip-link {
157
+ display: block;
158
+ margin-top: var(--sys-size-4);
159
+ color: var(--sys-color-primary);
160
+ padding-left: 0;
161
+ background: none;
162
+ border: none;
163
+ font: inherit;
164
+ text-decoration: underline;
165
+ cursor: pointer;
166
+ }
167
+
168
+ .selected-item-text {
169
+ overflow: hidden;
170
+ white-space: nowrap;
171
+ text-overflow: ellipsis;
172
+ }
173
+ }
@@ -10,7 +10,8 @@
10
10
  position: fixed;
11
11
  inset: 0;
12
12
  pointer-events: none;
13
- /* Needs to be above regular panel widgets (e.g. flame chart at z-index 2000) but below floating glass panes (which start at z-index 3000). */
13
+ /* Needs to be above regular panel widgets (e.g. flame chart at z-index 2000)
14
+ but below floating glass panes (which start at z-index 3000). */
14
15
  z-index: 2500;
15
16
  overflow: hidden;
16
17
  width: 100vw;
@@ -66,4 +67,10 @@
66
67
  background-color: color-mix(in srgb, var(--sys-color-primary), transparent 85%);
67
68
  box-sizing: border-box;
68
69
  }
70
+
71
+ .comment-popup-widget {
72
+ position: absolute;
73
+ z-index: 2501;
74
+ pointer-events: auto;
75
+ }
69
76
  }
@@ -6,7 +6,7 @@ import * as i18n from '../../core/i18n/i18n.js';
6
6
  import type * as Root from '../../core/root/root.js';
7
7
  import * as UI from '../../ui/legacy/legacy.js';
8
8
 
9
- import type * as Comments from './comments.js';
9
+ import type * as Common from './common.js';
10
10
 
11
11
  const UIStrings = {
12
12
  /**
@@ -22,16 +22,16 @@ const UIStrings = {
22
22
  */
23
23
  comments: 'Comments',
24
24
  } as const;
25
- const str_ = i18n.i18n.registerUIStrings('ui/comments/comments-meta.ts', UIStrings);
25
+ const str_ = i18n.i18n.registerUIStrings('panels/common/common-meta.ts', UIStrings);
26
26
  const i18nLazyString = i18n.i18n.getLazilyComputedLocalizedString.bind(undefined, str_);
27
27
 
28
- let loadedCommentsModule: (typeof Comments|undefined);
28
+ let loadedCommonModule: (typeof Common|undefined);
29
29
 
30
- async function loadCommentsModule(): Promise<typeof Comments> {
31
- if (!loadedCommentsModule) {
32
- loadedCommentsModule = await import('./comments.js');
30
+ async function loadCommonModule(): Promise<typeof Common> {
31
+ if (!loadedCommonModule) {
32
+ loadedCommonModule = await import('./common.js');
33
33
  }
34
- return loadedCommentsModule;
34
+ return loadedCommonModule;
35
35
  }
36
36
 
37
37
  function isCommentsEnabled(config?: Root.Runtime.HostConfig): boolean {
@@ -46,8 +46,8 @@ UI.ViewManager.registerViewExtension({
46
46
  commandPrompt: i18nLazyString(UIStrings.showComments),
47
47
  title: i18nLazyString(UIStrings.comments),
48
48
  async loadView(universe) {
49
- const Comments = await loadCommentsModule();
50
- return new Comments.CommentsStatusBarPill.CommentsStatusBarPill(undefined, [universe.commentManager]);
49
+ const Common = await loadCommonModule();
50
+ return new Common.CommentsStatusBarPill.CommentsStatusBarPill(undefined, [universe.commentManager]);
51
51
  },
52
52
  });
53
53
 
@@ -59,8 +59,8 @@ UI.ActionRegistration.registerActionExtension({
59
59
  toggleable: true,
60
60
  condition: isCommentsEnabled,
61
61
  async loadActionDelegate() {
62
- const Comments = await loadCommentsModule();
63
- return new Comments.CommentsOverlayWidget.ActionDelegate();
62
+ const Common = await loadCommonModule();
63
+ return new Common.CommentsOverlayWidget.ActionDelegate();
64
64
  },
65
65
  });
66
66
 
@@ -13,3 +13,6 @@ export * as ExtensionView from './ExtensionView.js';
13
13
  export * as PersistenceUtils from './PersistenceUtils.js';
14
14
  export * as DOMLinkifier from './DOMLinkifier.js';
15
15
  export * as ExtensionIframe from './ExtensionView.js';
16
+ export * as CommentThreadWidget from './CommentThreadWidget.js';
17
+ export * as CommentsOverlayWidget from './CommentsOverlayWidget.js';
18
+ export * as CommentsStatusBarPill from './CommentsStatusBarPill.js';
@@ -20,7 +20,7 @@ const {Directives: {ref}, html, render} = Lit;
20
20
 
21
21
  export class AdoptedStyleSheetSetTreeElement extends UI.TreeOutline.TreeElement {
22
22
  constructor(readonly adoptedStyleSheets: SDK.DOMModel.AdoptedStyleSheet[]) {
23
- super('');
23
+ super('', undefined, 'adopted-style-sheets');
24
24
  const documentElement = this.listItemElement.createChild('span');
25
25
  UI.UIUtils.createTextChild(documentElement, '#adopted-style-sheets');
26
26
  for (const adoptedStyleSheet of adoptedStyleSheets) {
@@ -33,7 +33,7 @@ export class AdoptedStyleSheetTreeElement extends UI.TreeOutline.TreeElement {
33
33
  private eventListener: Common.EventTarget.EventDescriptor|null = null;
34
34
 
35
35
  constructor(readonly adoptedStyleSheet: SDK.DOMModel.AdoptedStyleSheet) {
36
- super('');
36
+ super('', undefined, 'adopted-style-sheet');
37
37
  const header = adoptedStyleSheet.cssModel.styleSheetHeaderForId(adoptedStyleSheet.id);
38
38
  if (header) {
39
39
  AdoptedStyleSheetTreeElement.createContents(header, this);
@@ -79,7 +79,7 @@ export class AdoptedStyleSheetContentsTreeElement extends UI.TreeOutline.TreeEle
79
79
  private readonly widgetWrapper: HTMLElement;
80
80
 
81
81
  constructor(styleSheetHeader: SDK.CSSStyleSheetHeader.CSSStyleSheetHeader) {
82
- super('');
82
+ super('', undefined, 'adopted-style-sheet-contents');
83
83
  this.widgetWrapper = document.createElement('div');
84
84
  this.widgetWrapper.style.display = 'contents';
85
85
  this.title = this.widgetWrapper;
@@ -228,6 +228,10 @@ export class ElementsPanel extends UI.Panel.Panel implements UI.SearchableView.S
228
228
  return this.#domTreeWidget.getTreeOutlineForTesting();
229
229
  }
230
230
 
231
+ getDOMTreeWidgetForTesting(): DOMTreeWidget {
232
+ return this.#domTreeWidget;
233
+ }
234
+
231
235
  constructor(
232
236
  targetManager?: SDK.TargetManager.TargetManager,
233
237
  settings?: Common.Settings.Settings,
@@ -1300,7 +1304,7 @@ export class ElementsPanel extends UI.Panel.Panel implements UI.SearchableView.S
1300
1304
  }
1301
1305
 
1302
1306
  toggleHideElement(node: SDK.DOMModel.DOMNode): void {
1303
- this.#domTreeWidget.toggleHideElement(node);
1307
+ void this.#domTreeWidget.toggleHideElement(node);
1304
1308
  }
1305
1309
 
1306
1310
  toggleEditAsHTML(node: SDK.DOMModel.DOMNode): void {
@@ -48,7 +48,8 @@ import * as Protocol from '../../generated/protocol.js';
48
48
  import * as AIAssistance from '../../models/ai_assistance/ai_assistance.js';
49
49
  import * as Badges from '../../models/badges/badges.js';
50
50
  import * as Bindings from '../../models/bindings/bindings.js';
51
- import type * as Elements from '../../models/elements/elements.js';
51
+ import * as ChangeTracker from '../../models/change_tracker/change_tracker.js';
52
+ import * as Elements from '../../models/elements/elements.js';
52
53
  import * as IssuesManager from '../../models/issues_manager/issues_manager.js';
53
54
  import * as Workspace from '../../models/workspace/workspace.js';
54
55
  import * as CodeMirror from '../../third_party/codemirror.next/codemirror.next.js';
@@ -65,6 +66,7 @@ import * as PanelsCommon from '../common/common.js';
65
66
  import * as Media from '../media/media.js';
66
67
 
67
68
  import * as ElementsComponents from './components/components.js';
69
+ import {cssPath} from './DOMPath.js';
68
70
  import {getElementIssueDetails} from './ElementIssueUtils.js';
69
71
  import {ElementsPanel} from './ElementsPanel.js';
70
72
  import * as ElementStatePaneWidget from './ElementStatePaneWidget.js';
@@ -72,6 +74,33 @@ import {type ElementsTreeOutline, MappedCharToEntity} from './ElementsTreeOutlin
72
74
  import {ImagePreviewPopover} from './ImagePreviewPopover.js';
73
75
  import {getRegisteredDecorators, type MarkerDecorator, type MarkerDecoratorRegistration} from './MarkerDecorator.js';
74
76
 
77
+ /**
78
+ * Returns the CSS selector of `node` that is used as the text signature of the anchor a change is
79
+ * recorded on, or `undefined` if `tracker` is absent or not recording changes.
80
+ *
81
+ * Resolving the selector walks the ancestor chain and scans sibling lists, so it is skipped
82
+ * entirely while change tracking is disabled.
83
+ */
84
+ export function buildChangeSelector(tracker: ChangeTracker.ChangeTracker.ChangeTracker|undefined,
85
+ node: SDK.DOMModel.DOMNode): string|undefined {
86
+ if (!tracker?.isTracking) {
87
+ return undefined;
88
+ }
89
+ if (node.nodeType() === Node.ELEMENT_NODE) {
90
+ const selector = cssPath(node, true);
91
+ if (selector) {
92
+ return selector;
93
+ }
94
+ } else if (node.parentNode && node.parentNode.nodeType() === Node.ELEMENT_NODE) {
95
+ const selector = cssPath(node.parentNode, true);
96
+ if (selector) {
97
+ return selector;
98
+ }
99
+ }
100
+
101
+ return undefined;
102
+ }
103
+
75
104
  const {html, nothing, render, Directives: {classMap, ref, repeat, until}} = Lit;
76
105
  const {animateOn} = UI.UIUtils;
77
106
 
@@ -325,6 +354,7 @@ export interface ViewInput {
325
354
  descendantDecorations: Decoration[];
326
355
  decorationsTooltip: string;
327
356
  indent: number;
357
+ renderSelection?: boolean;
328
358
 
329
359
  editorState: CodeMirror.EditorState|null;
330
360
  editorWidth: number|null;
@@ -770,14 +800,6 @@ function renderTag(node: SDK.DOMModel.DOMNode, tagName: string, isClosingTag: bo
770
800
  hasUpdates = hasUpdates || (!expanded && updateRecord.hasChangedChildren());
771
801
  }
772
802
 
773
- // We are taking full text content of the tag, including attributes and children, to set the aria label.
774
- // FIXME: we should compute the aria label ourselves if it is event needed.
775
- const setAriaLabel = ref(el => {
776
- if (el?.textContent) {
777
- UI.ARIAUtils.setLabel(el, el.textContent);
778
- }
779
- });
780
-
781
803
  const tagNameClass = isClosingTag ? 'webkit-html-close-tag-name' : 'webkit-html-tag-name';
782
804
  const hasTagIssues = !isClosingTag && Boolean(issues?.some(issue => {
783
805
  const details = getElementIssueDetails(issue);
@@ -790,8 +812,13 @@ function renderTag(node: SDK.DOMModel.DOMNode, tagName: string, isClosingTag: bo
790
812
  const tagString = (isClosingTag ? '/' : '') + tagName;
791
813
  const jslog = !isClosingTag ? VisualLogging.value('tag-name').track({change: true, dblclick: true}) : '';
792
814
 
815
+ const ariaLabel = isClosingTag ?
816
+ `</${tagName}>` :
817
+ `<${tagName}${
818
+ attributes.map(attr => (attr.value ? ` ${attr.name}="${attr.value}"` : ` ${attr.name}`)).join('')}>`;
819
+
793
820
  return html`<span
794
- class=${classMap(tagClasses)} ${setAriaLabel}
821
+ class=${classMap(tagClasses)} aria-label=${ariaLabel}
795
822
  >&lt;<span class=${classMap(tagNameClasses)} jslog=${jslog || nothing} ${
796
823
  animateOn(hasUpdates, DOM_UPDATE_ANIMATION_CLASS_NAME)}>${tagString}</span>${
797
824
  attributes.map(attr => html` ${renderAttribute(attr, updateRecord, false, node, issues)}`)}&gt;</span>\u200B`;
@@ -880,7 +907,7 @@ export const DEFAULT_VIEW = (input: ViewInput, output: ViewOutput, target: HTMLE
880
907
  input.onExpand,
881
908
  input.issues,
882
909
  )}</span>` : nothing}
883
- <div class="selection fill ${input.editorState ? 'hidden' : ''}" style=${`margin-left: ${-input.indent}px`}></div>
910
+ ${input.renderSelection !== false ? html`<div class="selection fill ${input.editorState ? 'hidden' : ''}" style=${`margin-left: ${-input.indent}px`}></div>` : nothing}
884
911
  <div class=${classMap(gutterContainerClasses)}
885
912
  style="left: ${-input.indent}px"
886
913
  @click=${input.onGutterClick}>
@@ -1102,7 +1129,10 @@ export const DEFAULT_VIEW = (input: ViewInput, output: ViewOutput, target: HTMLE
1102
1129
  if (event.key === 'Escape') {
1103
1130
  event.consume(true);
1104
1131
  }
1105
- }} class="source-code elements-tree-editor" style="width: ${input.editorWidth ?? 0}px;">
1132
+ }}
1133
+ @mousedown=${(event: Event) => event.stopPropagation()}
1134
+ @click=${(event: Event) => event.stopPropagation()}
1135
+ class="source-code elements-tree-editor" style="width: ${input.editorWidth ?? 0}px;">
1106
1136
  <devtools-text-editor .state=${input.editorState} ${ref(el => {
1107
1137
  output.editorRef = el as TextEditor.TextEditor.TextEditor;
1108
1138
  })}></devtools-text-editor>
@@ -1124,9 +1154,14 @@ export interface InitialEditState {
1124
1154
  }
1125
1155
 
1126
1156
  export class ElementsTreeWidget extends UI.Widget.Widget {
1127
- static override readonly INJECT: readonly[typeof IssuesManager.DOMIssuesManager.DOMIssuesManager] =
1128
- [IssuesManager.DOMIssuesManager.DOMIssuesManager] as const;
1157
+ static override readonly INJECT: readonly[typeof IssuesManager.DOMIssuesManager.DOMIssuesManager,
1158
+ typeof ChangeTracker.ChangeTracker.ChangeTracker] =
1159
+ [
1160
+ IssuesManager.DOMIssuesManager.DOMIssuesManager,
1161
+ ChangeTracker.ChangeTracker.ChangeTracker,
1162
+ ] as const;
1129
1163
  #domIssuesManager?: IssuesManager.DOMIssuesManager.DOMIssuesManager;
1164
+ #changeTracker?: ChangeTracker.ChangeTracker.ChangeTracker;
1130
1165
 
1131
1166
  #node!: SDK.DOMModel.DOMNode;
1132
1167
  #eventsBound = false;
@@ -1137,6 +1172,7 @@ export class ElementsTreeWidget extends UI.Widget.Widget {
1137
1172
  isXMLMimeType = false;
1138
1173
  disableEdits = false;
1139
1174
  showAIButton = false;
1175
+ renderSelection = true;
1140
1176
  isDOMNodeSelected = false;
1141
1177
  initialEdit?: InitialEditState|null;
1142
1178
  onInitialEditCompleted?: () => void;
@@ -1370,13 +1406,25 @@ export class ElementsTreeWidget extends UI.Widget.Widget {
1370
1406
  return this.#domIssuesManager?.issuesForNode(this.node) ?? [];
1371
1407
  }
1372
1408
 
1409
+ get changeTracker(): ChangeTracker.ChangeTracker.ChangeTracker|undefined {
1410
+ // The widget is currently created for non-widget ElementsTreeElement so
1411
+ // the changeTracker can be empty, relying on the manual resolution.
1412
+ // Note that this only works once the widget is attached to the DOM.
1413
+ this.#changeTracker ??=
1414
+ UI.Widget.lookupUniverseForElement(this.contentElement)?.get(ChangeTracker.ChangeTracker.ChangeTracker);
1415
+ return this.#changeTracker;
1416
+ }
1417
+
1373
1418
  constructor(
1374
1419
  element?: HTMLElement,
1375
- [domIssuesManager]: UI.Widget.WidgetDependencies<typeof ElementsTreeWidget>|[undefined] = [undefined],
1420
+ [domIssuesManager, changeTracker]:
1421
+ UI.Widget.WidgetDependencies<typeof ElementsTreeWidget>|[IssuesManager.DOMIssuesManager.DOMIssuesManager?,
1422
+ ChangeTracker.ChangeTracker.ChangeTracker?] = [],
1376
1423
  view: View = DEFAULT_VIEW,
1377
1424
  ) {
1378
1425
  super(element);
1379
1426
  this.#domIssuesManager = domIssuesManager;
1427
+ this.#changeTracker = changeTracker;
1380
1428
  this.#view = view;
1381
1429
 
1382
1430
  this.#expandedChildrenLimit = InitialChildrenLimit;
@@ -1461,6 +1509,7 @@ export class ElementsTreeWidget extends UI.Widget.Widget {
1461
1509
  }
1462
1510
 
1463
1511
  #clearDOMNextUpdate = false;
1512
+
1464
1513
  #isBound = false;
1465
1514
 
1466
1515
  override wasShown(): void {
@@ -1537,6 +1586,7 @@ export class ElementsTreeWidget extends UI.Widget.Widget {
1537
1586
  descendantDecorations: this.#expanded ? [] : this.#descendantDecorations,
1538
1587
  decorationsTooltip: this.#decorationsTooltip,
1539
1588
  indent: this.#getLeftIndent(),
1589
+ renderSelection: this.renderSelection,
1540
1590
  showScrollSnapAdorner: Boolean(this.#layout?.hasScroll) && !isClosingTag,
1541
1591
  scrollSnapAdornerActive: this.#scrollSnapAdornerActive,
1542
1592
  showSlotAdorner: Boolean(this.node.assignedSlot) && !isClosingTag,
@@ -2448,7 +2498,8 @@ export class ElementsTreeWidget extends UI.Widget.Widget {
2448
2498
  }
2449
2499
  // The relatedTarget is null when no element gains focus, e.g. switching windows.
2450
2500
  const relatedTarget = (event.relatedTarget as Node | null);
2451
- if (relatedTarget && !relatedTarget.isSelfOrDescendant(this.#editorRef)) {
2501
+ if (relatedTarget && !relatedTarget.isSelfOrDescendant(this.#editorRef) &&
2502
+ !relatedTarget.isSelfOrDescendant(this.contentElement)) {
2452
2503
  this.editing?.commit();
2453
2504
  }
2454
2505
  },
@@ -2595,10 +2646,16 @@ export class ElementsTreeWidget extends UI.Widget.Widget {
2595
2646
  }
2596
2647
 
2597
2648
  if (attributeName !== null && (attributeName.trim() || newText.trim()) && oldText !== newText) {
2649
+ const edit = {attributeName, oldText, newText};
2598
2650
  this.node.setAttribute(attributeName, newText, (error: string|null) => {
2651
+ if (!error) {
2652
+ const changeTracker = this.changeTracker;
2653
+ Elements.DOMChanges.trackAttributeEdit(changeTracker, this.node,
2654
+ buildChangeSelector(changeTracker, this.node), edit);
2655
+ Badges.UserBadges.instance().recordAction(Badges.BadgeAction.DOM_ELEMENT_OR_ATTRIBUTE_EDITED);
2656
+ }
2599
2657
  moveToNextAttributeIfNeeded.call(this, error);
2600
2658
  });
2601
- Badges.UserBadges.instance().recordAction(Badges.BadgeAction.DOM_ELEMENT_OR_ATTRIBUTE_EDITED);
2602
2659
  return;
2603
2660
  }
2604
2661
 
@@ -2660,6 +2717,9 @@ export class ElementsTreeWidget extends UI.Widget.Widget {
2660
2717
  }
2661
2718
 
2662
2719
  Badges.UserBadges.instance().recordAction(Badges.BadgeAction.DOM_ELEMENT_OR_ATTRIBUTE_EDITED);
2720
+ const changeTracker = this.changeTracker;
2721
+ Elements.DOMChanges.trackTagNameEdit(changeTracker, newNode, buildChangeSelector(changeTracker, newNode),
2722
+ oldText ?? tagName ?? '', newText);
2663
2723
  if (this.selectNodeAfterEdit) {
2664
2724
  this.selectNodeAfterEdit(wasExpanded, error, newNode, moveDirection);
2665
2725
  }
@@ -2668,8 +2728,14 @@ export class ElementsTreeWidget extends UI.Widget.Widget {
2668
2728
 
2669
2729
  private textNodeEditingCommitted(textNode: SDK.DOMModel.DOMNode, _element: Element, newText: string): void {
2670
2730
  this.editing = null;
2731
+ const oldValue = textNode.nodeValue() ?? '';
2671
2732
 
2672
- function callback(this: ElementsTreeWidget): void {
2733
+ function callback(this: ElementsTreeWidget, error?: string|null): void {
2734
+ if (!error && oldValue !== newText) {
2735
+ const changeTracker = this.changeTracker;
2736
+ Elements.DOMChanges.trackTextNodeEdit(changeTracker, textNode, buildChangeSelector(changeTracker, textNode),
2737
+ oldValue, newText);
2738
+ }
2673
2739
  this.#clearDOMNextUpdate = true;
2674
2740
  this.updateTitle();
2675
2741
  }
@@ -2821,7 +2887,7 @@ export class ElementsTreeWidget extends UI.Widget.Widget {
2821
2887
  async remove(): Promise<void> {
2822
2888
  if (this.isToggledToHidden?.(this.node)) {
2823
2889
  // Unhide the node before removing. This avoids inconsistent state if the node is restored via undo.
2824
- await this.toggleHideElement?.(this.node);
2890
+ await this.node.toggleHideElement();
2825
2891
  }
2826
2892
  if (this.node.pseudoType()) {
2827
2893
  return;
@@ -2830,7 +2896,14 @@ export class ElementsTreeWidget extends UI.Widget.Widget {
2830
2896
  if (!this.node.parentNode || this.node.parentNode.nodeType() === Node.DOCUMENT_NODE) {
2831
2897
  return;
2832
2898
  }
2833
- void this.node.removeNode();
2899
+ // The selector has to be resolved before the node is detached from the tree.
2900
+ const changeTracker = this.changeTracker;
2901
+ const selector = buildChangeSelector(changeTracker, this.node);
2902
+ await this.node.removeNode((err: string|null) => {
2903
+ if (!err) {
2904
+ Elements.DOMChanges.trackNodeRemoval(changeTracker, this.node, selector);
2905
+ }
2906
+ });
2834
2907
  }
2835
2908
 
2836
2909
  toggleEditAsHTML(callback?: ((arg0: boolean) => void), startEditing?: boolean): void {
@@ -2851,7 +2924,15 @@ export class ElementsTreeWidget extends UI.Widget.Widget {
2851
2924
 
2852
2925
  const commitChange = (initialValue: string, value: string): void => {
2853
2926
  if (initialValue !== value) {
2854
- node.setOuterHTML(value, selectNode);
2927
+ // The selector has to be resolved before the node is detached from the tree.
2928
+ const changeTracker = this.changeTracker;
2929
+ const selector = buildChangeSelector(changeTracker, node);
2930
+ node.setOuterHTML(value, (error: string|null) => {
2931
+ if (!error) {
2932
+ Elements.DOMChanges.trackHTMLEdit(changeTracker, node, selector, initialValue, value);
2933
+ }
2934
+ selectNode(error);
2935
+ });
2855
2936
  }
2856
2937
  };
2857
2938