chrome-devtools-frontend 1.0.1672557 → 1.0.1675430

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 (177) hide show
  1. package/.agents/skills/devtools-ux-writing-refactor/SKILL.md +26 -9
  2. package/front_end/core/common/ParsedURL.ts +5 -1
  3. package/front_end/core/common/SettingRegistration.ts +30 -34
  4. package/front_end/core/common/Settings.ts +4 -64
  5. package/front_end/core/host/UserMetrics.ts +127 -117
  6. package/front_end/core/protocol_client/PuppeteerDevToolsConnection.ts +42 -9
  7. package/front_end/core/root/Runtime.ts +14 -0
  8. package/front_end/core/sdk/NetworkManager.ts +0 -35
  9. package/front_end/core/sdk/SourceMap.ts +1 -19
  10. package/front_end/core/sdk/SourceMapManager.ts +75 -48
  11. package/front_end/core/sdk/SourceMapScopesInfo.ts +0 -62
  12. package/front_end/core/sdk/TargetManager.ts +18 -0
  13. package/front_end/devtools_compatibility.js +1 -1
  14. package/front_end/entrypoints/main/MainImpl.ts +1 -0
  15. package/front_end/foundation/Universe.ts +44 -29
  16. package/front_end/generated/InspectorBackendCommands.ts +4 -3
  17. package/front_end/generated/SupportedCSSProperties.ts +16 -1
  18. package/front_end/generated/protocol-mapping.d.ts +8 -0
  19. package/front_end/generated/protocol-proxy-api.d.ts +6 -0
  20. package/front_end/generated/protocol.ts +16 -2
  21. package/front_end/models/ai_assistance/AiSetting.ts +66 -3
  22. package/front_end/models/ai_assistance/agents/PerformanceAgent.ts +15 -5
  23. package/front_end/models/bindings/CompilerScriptMapping.ts +1 -1
  24. package/front_end/models/emulation/DeviceModeModel.ts +82 -102
  25. package/front_end/models/emulation/EmulatedDevices.ts +18 -66
  26. package/front_end/models/javascript_metadata/NativeFunctions.js +4 -4
  27. package/front_end/models/persistence/AutomaticFileSystemManager.ts +2 -2
  28. package/front_end/models/persistence/IsolatedFileSystemManager.ts +20 -29
  29. package/front_end/models/trace/ModelImpl.ts +5 -0
  30. package/front_end/models/trace/Processor.ts +5 -0
  31. package/front_end/models/trace/handlers/LayoutShiftsHandler.ts +7 -1
  32. package/front_end/models/trace/handlers/MetaHandler.ts +8 -4
  33. package/front_end/models/trace/handlers/PageLoadMetricsHandler.ts +11 -0
  34. package/front_end/models/workspace/FileManager.ts +13 -6
  35. package/front_end/panels/ai_assistance/AiAssistancePanel.ts +11 -10
  36. package/front_end/panels/ai_assistance/ai_assistance-meta.ts +3 -45
  37. package/front_end/panels/application/BackgroundServiceView.ts +42 -40
  38. package/front_end/panels/application/KeyValueStorageItemsView.ts +1 -0
  39. package/front_end/panels/application/WebMCPView.ts +13 -5
  40. package/front_end/panels/application/components/AdsView.ts +24 -1
  41. package/front_end/panels/application/components/adsView.css +27 -21
  42. package/front_end/panels/autofill/AutofillView.ts +12 -12
  43. package/front_end/panels/autofill/autofill-meta.ts +2 -2
  44. package/front_end/panels/common/ExtensionServer.ts +7 -1
  45. package/front_end/panels/common/common.ts +0 -1
  46. package/front_end/panels/console/ConsoleInsightTeaser.ts +7 -6
  47. package/front_end/panels/console/ConsoleViewMessage.ts +1 -1
  48. package/front_end/panels/elements/ClassesPaneWidget.ts +2 -2
  49. package/front_end/panels/elements/ElementsTreeElement.ts +602 -327
  50. package/front_end/panels/elements/ElementsTreeOutline.ts +1 -1
  51. package/front_end/panels/elements/StylesSidebarPane.ts +1 -1
  52. package/front_end/panels/elements/components/AdornerManager.ts +1 -0
  53. package/front_end/panels/emulation/AdvancedApp.ts +3 -3
  54. package/front_end/panels/emulation/DeviceModeToolbar.ts +2 -11
  55. package/front_end/panels/emulation/DeviceModeView.ts +234 -75
  56. package/front_end/panels/emulation/InspectedPagePlaceholder.ts +1 -1
  57. package/front_end/panels/emulation/deviceModeView.css +4 -21
  58. package/front_end/panels/emulation/emulation-meta.ts +4 -30
  59. package/front_end/panels/emulation/emulation.ts +0 -3
  60. package/front_end/panels/explain/components/ConsoleInsight.ts +11 -7
  61. package/front_end/panels/js_timeline/js_timeline-meta.ts +8 -8
  62. package/front_end/panels/layer_viewer/LayerDetailsView.ts +30 -30
  63. package/front_end/panels/layer_viewer/LayerTreeOutline.ts +6 -6
  64. package/front_end/panels/layer_viewer/LayerViewHost.ts +1 -1
  65. package/front_end/panels/layer_viewer/Layers3DView.ts +11 -11
  66. package/front_end/panels/layer_viewer/PaintProfilerView.ts +8 -8
  67. package/front_end/panels/layer_viewer/TransformController.ts +3 -3
  68. package/front_end/panels/layer_viewer/layer_viewer-meta.ts +11 -11
  69. package/front_end/panels/layers/LayersPanel.ts +2 -2
  70. package/front_end/panels/layers/layers-meta.ts +2 -2
  71. package/front_end/panels/lighthouse/LighthouseController.ts +3 -7
  72. package/front_end/panels/mobile_throttling/CPUThrottlingSelector.ts +313 -0
  73. package/front_end/panels/mobile_throttling/NetworkPanelIndicator.ts +0 -10
  74. package/front_end/panels/mobile_throttling/NetworkThrottlingSelector.ts +21 -10
  75. package/front_end/panels/mobile_throttling/ThrottlingManager.ts +1 -58
  76. package/front_end/panels/mobile_throttling/mobile_throttling.ts +2 -9
  77. package/front_end/panels/network/NetworkConfigView.ts +1 -71
  78. package/front_end/panels/network/networkConfigView.css +1 -2
  79. package/front_end/panels/profiler/HeapDetachedElementsDataGrid.ts +3 -3
  80. package/front_end/panels/profiler/HeapDetachedElementsView.ts +5 -5
  81. package/front_end/panels/profiler/HeapProfileView.ts +32 -32
  82. package/front_end/panels/profiler/HeapProfilerPanel.ts +2 -2
  83. package/front_end/panels/profiler/HeapSnapshotDataGrids.ts +33 -37
  84. package/front_end/panels/profiler/HeapSnapshotGridNodes.ts +26 -27
  85. package/front_end/panels/profiler/HeapSnapshotView.ts +48 -48
  86. package/front_end/panels/profiler/IsolateSelector.ts +10 -10
  87. package/front_end/panels/profiler/ModuleUIStrings.ts +25 -25
  88. package/front_end/panels/profiler/ProfileLauncherView.ts +6 -6
  89. package/front_end/panels/profiler/ProfilesPanel.ts +6 -6
  90. package/front_end/panels/profiler/WritableProfileHeader.ts +6 -6
  91. package/front_end/panels/profiler/profiler-meta.ts +7 -7
  92. package/front_end/panels/protocol_monitor/ProtocolMonitor.ts +7 -0
  93. package/front_end/panels/recorder/RecorderPanel.ts +1 -1
  94. package/front_end/panels/settings/AISettingsTab.ts +27 -23
  95. package/front_end/panels/settings/emulation/DevicesSettingsTab.ts +1 -2
  96. package/front_end/panels/timeline/TimelineFlameChartNetworkDataProvider.ts +12 -17
  97. package/front_end/panels/timeline/TimelinePanel.ts +20 -35
  98. package/front_end/panels/timeline/components/LiveMetricsView.ts +27 -6
  99. package/front_end/panels/timeline/components/NetworkTrackWidget.ts +1 -1
  100. package/front_end/panels/timeline/components/components.ts +0 -4
  101. package/front_end/panels/timeline/components/liveMetricsView.css +7 -0
  102. package/front_end/third_party/chromium/README.chromium +1 -1
  103. package/front_end/third_party/source-map-scopes-codec/package/CONTRIBUTING.md +0 -0
  104. package/front_end/third_party/source-map-scopes-codec/package/LICENSE +0 -0
  105. package/front_end/third_party/source-map-scopes-codec/package/README.md +0 -0
  106. package/front_end/third_party/source-map-scopes-codec/package/_dist/src/builder/builder.d.ts +0 -0
  107. package/front_end/third_party/source-map-scopes-codec/package/_dist/src/builder/builder.d.ts.map +1 -1
  108. package/front_end/third_party/source-map-scopes-codec/package/_dist/src/builder/safe_builder.d.ts +0 -0
  109. package/front_end/third_party/source-map-scopes-codec/package/_dist/src/builder/safe_builder.d.ts.map +0 -0
  110. package/front_end/third_party/source-map-scopes-codec/package/_dist/src/decode/decode.d.ts +0 -0
  111. package/front_end/third_party/source-map-scopes-codec/package/_dist/src/decode/decode.d.ts.map +0 -0
  112. package/front_end/third_party/source-map-scopes-codec/package/_dist/src/encode/encode.d.ts +0 -0
  113. package/front_end/third_party/source-map-scopes-codec/package/_dist/src/encode/encode.d.ts.map +0 -0
  114. package/front_end/third_party/source-map-scopes-codec/package/_dist/src/mod.d.ts +0 -0
  115. package/front_end/third_party/source-map-scopes-codec/package/_dist/src/mod.d.ts.map +0 -0
  116. package/front_end/third_party/source-map-scopes-codec/package/_dist/src/scopes.d.ts +0 -0
  117. package/front_end/third_party/source-map-scopes-codec/package/_dist/src/scopes.d.ts.map +0 -0
  118. package/front_end/third_party/source-map-scopes-codec/package/deno.json +1 -1
  119. package/front_end/third_party/source-map-scopes-codec/package/package.json +1 -1
  120. package/front_end/third_party/source-map-scopes-codec/package/src/builder/builder.js +2 -0
  121. package/front_end/third_party/source-map-scopes-codec/package/src/builder/builder.js.map +1 -1
  122. package/front_end/third_party/source-map-scopes-codec/package/src/builder/builder.ts +2 -0
  123. package/front_end/third_party/source-map-scopes-codec/package/src/builder/safe_builder.js +0 -0
  124. package/front_end/third_party/source-map-scopes-codec/package/src/builder/safe_builder.js.map +0 -0
  125. package/front_end/third_party/source-map-scopes-codec/package/src/builder/safe_builder.ts +0 -0
  126. package/front_end/third_party/source-map-scopes-codec/package/src/codec.js +0 -0
  127. package/front_end/third_party/source-map-scopes-codec/package/src/codec.js.map +0 -0
  128. package/front_end/third_party/source-map-scopes-codec/package/src/codec.ts +0 -0
  129. package/front_end/third_party/source-map-scopes-codec/package/src/decode/decode.js +0 -0
  130. package/front_end/third_party/source-map-scopes-codec/package/src/decode/decode.js.map +0 -0
  131. package/front_end/third_party/source-map-scopes-codec/package/src/decode/decode.ts +0 -0
  132. package/front_end/third_party/source-map-scopes-codec/package/src/encode/encode.js +0 -0
  133. package/front_end/third_party/source-map-scopes-codec/package/src/encode/encode.js.map +0 -0
  134. package/front_end/third_party/source-map-scopes-codec/package/src/encode/encode.ts +0 -0
  135. package/front_end/third_party/source-map-scopes-codec/package/src/encode/encoder.js +0 -0
  136. package/front_end/third_party/source-map-scopes-codec/package/src/encode/encoder.js.map +0 -0
  137. package/front_end/third_party/source-map-scopes-codec/package/src/encode/encoder.ts +0 -0
  138. package/front_end/third_party/source-map-scopes-codec/package/src/mod.js +0 -0
  139. package/front_end/third_party/source-map-scopes-codec/package/src/mod.js.map +0 -0
  140. package/front_end/third_party/source-map-scopes-codec/package/src/mod.ts +0 -0
  141. package/front_end/third_party/source-map-scopes-codec/package/src/scopes.js +0 -0
  142. package/front_end/third_party/source-map-scopes-codec/package/src/scopes.js.map +0 -0
  143. package/front_end/third_party/source-map-scopes-codec/package/src/scopes.ts +0 -0
  144. package/front_end/third_party/source-map-scopes-codec/package/src/util.js +0 -0
  145. package/front_end/third_party/source-map-scopes-codec/package/src/util.js.map +0 -0
  146. package/front_end/third_party/source-map-scopes-codec/package/src/util.ts +0 -0
  147. package/front_end/third_party/source-map-scopes-codec/package/src/vlq.js +0 -0
  148. package/front_end/third_party/source-map-scopes-codec/package/src/vlq.js.map +0 -0
  149. package/front_end/third_party/source-map-scopes-codec/package/src/vlq.ts +0 -0
  150. package/front_end/ui/comments/CommentAnchorResolver.ts +132 -0
  151. package/front_end/ui/comments/comments.ts +9 -0
  152. package/front_end/ui/components/dialogs/ShortcutDialog.ts +2 -0
  153. package/front_end/ui/components/settings/SettingCheckbox.ts +4 -12
  154. package/front_end/ui/components/settings/settingCheckbox.css +0 -7
  155. package/front_end/ui/legacy/Treeoutline.ts +14 -14
  156. package/front_end/ui/legacy/UIUtils.ts +2 -47
  157. package/front_end/ui/legacy/Widget.ts +10 -2
  158. package/front_end/ui/legacy/components/data_grid/DataGridElement.ts +40 -0
  159. package/front_end/ui/legacy/components/settings_ui/SettingsUI.ts +1 -1
  160. package/front_end/ui/legacy/components/utils/Linkifier.ts +1 -0
  161. package/front_end/ui/lit/Directives.ts +80 -0
  162. package/front_end/ui/lit/lit.ts +1 -0
  163. package/front_end/ui/lit/render.ts +8 -0
  164. package/front_end/ui/settings/SettingUIRegistration.ts +4 -0
  165. package/front_end/ui/visual_logging/Debugging.ts +1 -5
  166. package/front_end/ui/visual_logging/KnownContextValues.ts +3 -0
  167. package/front_end/ui/visual_logging/LoggingConfig.ts +21 -0
  168. package/front_end/ui/visual_logging/visual_logging.ts +9 -0
  169. package/mcp/mcp.ts +1 -0
  170. package/package.json +1 -1
  171. package/front_end/panels/common/ThrottlingUtils.ts +0 -51
  172. package/front_end/panels/emulation/DeviceModeWrapper.ts +0 -248
  173. package/front_end/panels/mobile_throttling/MobileThrottlingSelector.ts +0 -92
  174. package/front_end/panels/timeline/components/CPUThrottlingSelector.ts +0 -235
  175. package/front_end/panels/timeline/components/NetworkThrottlingSelector.ts +0 -245
  176. package/front_end/panels/timeline/components/cpuThrottlingSelector.css +0 -30
  177. package/front_end/panels/timeline/components/networkThrottlingSelector.css +0 -24
@@ -228,6 +228,8 @@ export class ScopeInfoBuilder {
228
228
  this.#scopes = [];
229
229
  this.#ranges = [];
230
230
  this.#knownScopes.clear();
231
+ this.#keyToScope.clear();
232
+ this.#lastScope = null;
231
233
 
232
234
  return info;
233
235
  }
@@ -0,0 +1,132 @@
1
+ // Copyright 2026 The Chromium Authors
2
+ // Use of this source code is governed by a BSD-style license that can be
3
+ // found in the LICENSE file.
4
+
5
+ import * as VisualLogging from '../../ui/visual_logging/visual_logging.js';
6
+
7
+ export interface CommentAnchorSignature {
8
+ /** Visual logging tree path, e.g. "Panel: elements > Pane: styles > TreeOutline > TreeItem: color" */
9
+ vePath: string;
10
+ /** Normalized text content of the target node */
11
+ textSignature: string;
12
+ /** Text content of the parent container VE node for sibling disambiguation */
13
+ parentTextSignature?: string;
14
+ /** 0-indexed position among siblings sharing the same visual logging path and text signature */
15
+ siblingIndex?: number;
16
+ /** Optional backend RequestId for Network panel elements (`data-network-request-id`) */
17
+ networkRequestId?: string;
18
+ /** Optional backend NodeId for Elements panel DOM nodes (`data-backend-node-id`) */
19
+ backendNodeId?: number;
20
+ /** Optional 1-indexed line number for CodeMirror text editors */
21
+ editorLineNumber?: number;
22
+ /** Optional file path of the document displayed in the CodeMirror editor */
23
+ editorFilePath?: string;
24
+ }
25
+
26
+ export interface CommentThread {
27
+ id: string;
28
+ anchor: CommentAnchorSignature;
29
+ comments: Array<{
30
+ author: 'DEVELOPER' | 'AGENT',
31
+ text: string,
32
+ timestamp: number,
33
+ }>;
34
+ status: 'ACTIVE'|'RESOLVED';
35
+ changes?: Array<Record<string, unknown>>;
36
+ }
37
+
38
+ const IGNORED_MINOR_CONTROLS = new Set<number>([
39
+ VisualLogging.VisualElements.Action,
40
+ VisualLogging.VisualElements.Toggle,
41
+ VisualLogging.VisualElements.Close,
42
+ VisualLogging.VisualElements.Expand,
43
+ VisualLogging.VisualElements.ToggleSubpane,
44
+ VisualLogging.VisualElements.Toolbar,
45
+ ]);
46
+
47
+ export function closestAcrossShadow(element: Element, selector: string): Element|null {
48
+ let current: Element|null = element;
49
+ while (current) {
50
+ if (current.matches(selector)) {
51
+ return current;
52
+ }
53
+ current = current.parentElementOrShadowHost();
54
+ }
55
+ return null;
56
+ }
57
+
58
+ export function isNonEmptyItem(element: Element): boolean {
59
+ return element.deepTextContent().trim().length > 0;
60
+ }
61
+
62
+ export function isTabTitle(element: Element): boolean {
63
+ let current: Element|null = element;
64
+ while (current) {
65
+ if (VisualLogging.needsLogging(current)) {
66
+ try {
67
+ const config = VisualLogging.getLoggingConfig(current);
68
+ if (config.ve === VisualLogging.VisualElements.PanelTabHeader) {
69
+ return true;
70
+ }
71
+ } catch {
72
+ // Ignore
73
+ }
74
+ }
75
+ const role = current.getAttribute('role');
76
+ if (role === 'tab') {
77
+ return true;
78
+ }
79
+ if (current.classList.contains('tab-element') || current.classList.contains('tab-header')) {
80
+ return true;
81
+ }
82
+ current = current.parentElementOrShadowHost();
83
+ }
84
+ return false;
85
+ }
86
+
87
+ export function resolveCommentAnchorElement(element: Element): Element|null {
88
+ if (isTabTitle(element)) {
89
+ return null;
90
+ }
91
+ let target: Element|null = element;
92
+ let fallbackCandidate: Element|null = null;
93
+
94
+ while (target) {
95
+ const hasDomainId = target.hasAttribute('data-network-request-id') || target.hasAttribute('data-backend-node-id');
96
+
97
+ if (hasDomainId) {
98
+ return isNonEmptyItem(target) ? target : null;
99
+ }
100
+ if (VisualLogging.needsLogging(target)) {
101
+ try {
102
+ const config = VisualLogging.getLoggingConfig(target);
103
+ if (config.ve === VisualLogging.VisualElements.TableRow ||
104
+ config.ve === VisualLogging.VisualElements.TreeItem) {
105
+ return isNonEmptyItem(target) ? target : null;
106
+ }
107
+ if (!fallbackCandidate && !IGNORED_MINOR_CONTROLS.has(config.ve)) {
108
+ fallbackCandidate = target;
109
+ }
110
+ } catch {
111
+ // Ignore
112
+ }
113
+ }
114
+ target = target.parentElementOrShadowHost();
115
+ }
116
+
117
+ if (fallbackCandidate && isNonEmptyItem(fallbackCandidate)) {
118
+ return fallbackCandidate;
119
+ }
120
+ return null;
121
+ }
122
+
123
+ export function isElementVisible(element: Element): boolean {
124
+ if (!element.isConnected) {
125
+ return false;
126
+ }
127
+ if (!element.checkVisibility({checkOpacity: true, checkVisibilityCSS: true, contentVisibilityAuto: true})) {
128
+ return false;
129
+ }
130
+ const rect = element.getBoundingClientRect();
131
+ return rect.width > 0 && rect.height > 0;
132
+ }
@@ -0,0 +1,9 @@
1
+ // Copyright 2026 The Chromium Authors
2
+ // Use of this source code is governed by a BSD-style license that can be
3
+ // found in the LICENSE file.
4
+
5
+ import * as CommentAnchorResolver from './CommentAnchorResolver.js';
6
+
7
+ export {
8
+ CommentAnchorResolver,
9
+ };
@@ -13,6 +13,7 @@ import * as ComponentHelpers from '../../../ui/components/helpers/helpers.js';
13
13
  import {html, nothing, render, type TemplateResult} from '../../../ui/lit/lit.js';
14
14
 
15
15
  import type {ButtonDialogData} from './ButtonDialog.js';
16
+ import {DialogHorizontalAlignment} from './Dialog.js';
16
17
  import shortcutDialogStyles from './shortcutDialog.css.js';
17
18
 
18
19
  const UIStrings = {
@@ -116,6 +117,7 @@ export class ShortcutDialog extends HTMLElement {
116
117
  variant: Buttons.Button.Variant.TOOLBAR,
117
118
  iconName: 'help',
118
119
  iconTitle: i18nString(UIStrings.showShortcutTitle),
120
+ horizontalAlignment: DialogHorizontalAlignment.AUTO,
119
121
  } as ButtonDialogData}>
120
122
  <ul class="keybinds-list">
121
123
  ${(this.#prependedElement) ? html`${this.#prependedElement}` : nothing}
@@ -17,7 +17,7 @@ import * as Input from '../input/input.js';
17
17
 
18
18
  import settingCheckboxStyles from './settingCheckbox.css.js';
19
19
 
20
- const {html, Directives: {ifDefined}} = Lit;
20
+ const {html} = Lit;
21
21
 
22
22
  const UIStrings = {
23
23
  /**
@@ -123,7 +123,7 @@ export class SettingCheckbox extends HTMLElement {
123
123
  }
124
124
 
125
125
  get checked(): boolean {
126
- if (!this.#setting || this.#setting.disabledReasons().length > 0) {
126
+ if (!this.#setting) {
127
127
  return false;
128
128
  }
129
129
 
@@ -141,14 +141,6 @@ export class SettingCheckbox extends HTMLElement {
141
141
 
142
142
  const icon = this.icon();
143
143
  const title = learnMore?.tooltip?.() ?? '';
144
- const disabledReasons = this.#setting.disabledReasons();
145
- const reason = disabledReasons.length ?
146
- html`
147
- <devtools-button class="disabled-reason" .iconName=${'info'} .variant=${Buttons.Button.Variant.ICON} .size=${
148
- Buttons.Button.Size.SMALL} title=${ifDefined(disabledReasons.join('\n'))} @click=${
149
- onclick}></devtools-button>
150
- ` :
151
- Lit.nothing;
152
144
  Lit.render(html`
153
145
  <style>${Input.checkboxStyles}</style>
154
146
  <style>${settingCheckboxStyles}</style>
@@ -157,12 +149,12 @@ export class SettingCheckbox extends HTMLElement {
157
149
  <input
158
150
  type="checkbox"
159
151
  .checked=${this.checked}
160
- ?disabled=${this.#disabled || this.#setting.disabled()}
152
+ ?disabled=${this.#disabled}
161
153
  @change=${this.#checkboxChanged}
162
154
  jslog=${VisualLogging.toggle().track({change: true}).context(this.#setting.name)}
163
155
  aria-label=${titleText}
164
156
  />
165
- ${this.#textOverride || titleText}${reason}
157
+ ${this.#textOverride || titleText}
166
158
  </label>
167
159
  ${icon}
168
160
  </p>`,
@@ -28,13 +28,6 @@ p {
28
28
  margin: 6px 0;
29
29
  }
30
30
 
31
- .disabled-reason {
32
- box-sizing: border-box;
33
- margin-left: var(--sys-size-2);
34
- width: var(--sys-size-9);
35
- height: var(--sys-size-9);
36
- }
37
-
38
31
  .info-icon {
39
32
  cursor: pointer;
40
33
  position: relative;
@@ -58,7 +58,6 @@ import {
58
58
  deepElementFromPoint,
59
59
  enclosingNodeOrSelfWithNodeNameInArray,
60
60
  HTMLElementWithLightDOMTemplate,
61
- InterceptBindingDirective,
62
61
  isEditing,
63
62
  } from './UIUtils.js';
64
63
 
@@ -1658,7 +1657,7 @@ class TreeViewTreeElement extends TreeElement {
1658
1657
  }
1659
1658
 
1660
1659
  refresh(): void {
1661
- const expandable = Boolean(this.configElement.querySelector('ul[role="group"]'));
1660
+ const expandable = Boolean(this.configElement.querySelector(':scope > ul[role="group"]'));
1662
1661
  this.setExpandable(expandable);
1663
1662
 
1664
1663
  this.titleElement.textContent = '';
@@ -1677,7 +1676,9 @@ class TreeViewTreeElement extends TreeElement {
1677
1676
  this.listItemElement.classList.add(className);
1678
1677
  this.#clonedClasses.add(className);
1679
1678
  }
1680
- InterceptBindingDirective.setEventListeners(this.configElement, this.listItemElement);
1679
+ const childUl = this.configElement.querySelector(':scope > ul[role="group"]');
1680
+ const templateElements = childUl ? [this.configElement, childUl] : [this.configElement];
1681
+ Lit.CustomDirectives.InterceptBindingDirective.setEventListeners(templateElements, this.listItemElement);
1681
1682
 
1682
1683
  for (const child of this.configElement.childNodes) {
1683
1684
  if (child instanceof HTMLUListElement && child.role === 'group') {
@@ -1697,9 +1698,9 @@ class TreeViewTreeElement extends TreeElement {
1697
1698
  }
1698
1699
 
1699
1700
  remove(): void {
1700
- removeNode(
1701
- this,
1702
- Boolean(this.parent && (this.parent as TreeViewTreeElement).configElement?.querySelector('ul[role="group"]')));
1701
+ removeNode(this,
1702
+ Boolean(this.parent &&
1703
+ (this.parent as TreeViewTreeElement).configElement?.querySelector(':scope > ul[role="group"]')));
1703
1704
  TreeViewTreeElement.#elementToTreeElement.delete(this.configElement);
1704
1705
  }
1705
1706
  }
@@ -1804,10 +1805,10 @@ function removeNode(node: TreeElement, preserveParentExpandable = false): void {
1804
1805
  * This section is only relevant if NOT using the `template`.
1805
1806
  *
1806
1807
  * Since config elements are cloned into the shadow DOM, it's not possible to directly attach event listeners to the
1807
- * children of config elements. Instead, the `UI.UIUtils.InterceptBindingDirective` directive needs to be used as a
1808
+ * children of config elements. Instead, the `Lit.CustomDirectives.InterceptBindingDirective` directive needs to be used as a
1808
1809
  * wrapper:
1809
1810
  * ```
1810
- * const on = Lit.Directive.directive(UI.UIUtils.InterceptBindingDirective);
1811
+ * const on = Lit.Directive.directive(Lit.CustomDirectives.InterceptBindingDirective);
1811
1812
  *
1812
1813
  * html`<li role="treeitem">
1813
1814
  * <button @click=${on(clickHandler)}>click me</button>
@@ -1933,7 +1934,7 @@ export class TreeViewElement extends HTMLElementWithLightDOMTemplate {
1933
1934
  let treeElement;
1934
1935
  if (node instanceof HTMLLIElement) {
1935
1936
  treeElement = new TreeViewTreeElement(this.#treeOutline, node);
1936
- const expandable = Boolean(node.querySelector('ul[role="group"]'));
1937
+ const expandable = Boolean(node.querySelector(':scope > ul[role="group"]'));
1937
1938
  treeElement.setExpandable(expandable);
1938
1939
  treeElement.updateExpansionFromAttribute();
1939
1940
  } else {
@@ -1965,11 +1966,10 @@ export class TreeViewElement extends HTMLElementWithLightDOMTemplate {
1965
1966
  if (node instanceof HTMLLIElement) {
1966
1967
  TreeViewTreeElement.get(node)?.remove();
1967
1968
  } else if (node.treeElement) {
1968
- removeNode(
1969
- node.treeElement,
1970
- Boolean(
1971
- node.treeElement.parent &&
1972
- (node.treeElement.parent as TreeViewTreeElement).configElement?.querySelector('ul[role="group"]')));
1969
+ removeNode(node.treeElement,
1970
+ Boolean(node.treeElement.parent &&
1971
+ (node.treeElement.parent as TreeViewTreeElement)
1972
+ .configElement?.querySelector(':scope > ul[role="group"]')));
1973
1973
  }
1974
1974
  }
1975
1975
  }
@@ -2026,51 +2026,6 @@ export function bindToAction(actionName: string): ReturnType<typeof Directives.r
2026
2026
  });
2027
2027
  }
2028
2028
 
2029
- /* eslint-disable-next-line @typescript-eslint/no-explicit-any */
2030
- type BindingEventListener = (arg: any) => any;
2031
- export class InterceptBindingDirective extends Lit.Directive.Directive {
2032
- static readonly #interceptedBindings = new WeakMap<Element, Map<string, BindingEventListener>>();
2033
- static readonly #attachedBindings = new WeakMap<Element, Map<string, BindingEventListener>>();
2034
-
2035
- override update(part: Lit.Directive.Part, [listener]: [BindingEventListener]): unknown {
2036
- if (part.type !== Lit.Directive.PartType.EVENT) {
2037
- return listener;
2038
- }
2039
- let eventListeners = InterceptBindingDirective.#interceptedBindings.get(part.element);
2040
- if (!eventListeners) {
2041
- eventListeners = new Map();
2042
- InterceptBindingDirective.#interceptedBindings.set(part.element, eventListeners);
2043
- }
2044
- eventListeners.set(part.name, listener);
2045
-
2046
- return this.render(listener);
2047
- }
2048
-
2049
- /* eslint-disable-next-line @typescript-eslint/no-unsafe-function-type */
2050
- render(listener: Function): Function {
2051
- return listener;
2052
- }
2053
-
2054
- static setEventListeners(templateElement: Element, renderedElement: Element): void {
2055
- const attachedListeners = InterceptBindingDirective.#attachedBindings.get(renderedElement);
2056
- if (attachedListeners) {
2057
- for (const [name, listener] of attachedListeners) {
2058
- renderedElement.removeEventListener(name, listener);
2059
- }
2060
- }
2061
-
2062
- const newListeners = InterceptBindingDirective.#interceptedBindings.get(templateElement);
2063
- if (newListeners?.size) {
2064
- for (const [name, listener] of newListeners) {
2065
- renderedElement.addEventListener(name, listener);
2066
- }
2067
- InterceptBindingDirective.#attachedBindings.set(renderedElement, new Map(newListeners));
2068
- } else {
2069
- InterceptBindingDirective.#attachedBindings.delete(renderedElement);
2070
- }
2071
- }
2072
- }
2073
-
2074
2029
  export const cloneCustomElement = <T extends HTMLElement>(element: T, deep?: boolean): T => {
2075
2030
  const clone = document.createElement(element.localName) as T;
2076
2031
  for (const attribute of element.attributes) {
@@ -2099,13 +2054,13 @@ export class HTMLElementWithLightDOMTemplate extends HTMLElement {
2099
2054
  clone.appendChild(HTMLElementWithLightDOMTemplate.cloneNode(child));
2100
2055
  }
2101
2056
  if (node instanceof Element && clone instanceof Element) {
2102
- InterceptBindingDirective.setEventListeners(node, clone);
2057
+ Lit.CustomDirectives.InterceptBindingDirective.setEventListeners(node, clone);
2103
2058
  }
2104
2059
  return clone;
2105
2060
  }
2106
2061
 
2107
2062
  private static patchLitTemplate(template: Lit.LitTemplate): void {
2108
- const interceptingWrapper = Lit.Directive.directive(InterceptBindingDirective);
2063
+ const interceptingWrapper = Lit.Directive.directive(Lit.CustomDirectives.InterceptBindingDirective);
2109
2064
  // eslint-disable-next-line @typescript-eslint/no-explicit-any
2110
2065
  const patchingWrapper = <Args extends any[], R>(fn: (...args: Args) => R): ((...args: Args) => R) => {
2111
2066
  return function(this: unknown, ...args: Args): R {
@@ -1144,8 +1144,12 @@ export class Widget<ContentTypeT extends HTMLElement|DocumentFragment = HTMLElem
1144
1144
  }
1145
1145
 
1146
1146
  setMinimumAndPreferredSizes(width: number, height: number, preferredWidth: number, preferredHeight: number): void {
1147
- this.#constraints =
1147
+ const newConstraints =
1148
1148
  new Geometry.Constraints(new Geometry.Size(width, height), new Geometry.Size(preferredWidth, preferredHeight));
1149
+ if (this.#constraints?.isEqual(newConstraints)) {
1150
+ return;
1151
+ }
1152
+ this.#constraints = newConstraints;
1149
1153
  this.invalidateConstraints();
1150
1154
  }
1151
1155
 
@@ -1154,7 +1158,11 @@ export class Widget<ContentTypeT extends HTMLElement|DocumentFragment = HTMLElem
1154
1158
  }
1155
1159
 
1156
1160
  set minimumSize(size: Geometry.Size) {
1157
- this.#constraints = new Geometry.Constraints(size);
1161
+ const newConstraints = new Geometry.Constraints(size);
1162
+ if (this.#constraints?.isEqual(newConstraints)) {
1163
+ return;
1164
+ }
1165
+ this.#constraints = newConstraints;
1158
1166
  this.invalidateConstraints();
1159
1167
  }
1160
1168
 
@@ -3,6 +3,7 @@
3
3
  // found in the LICENSE file.
4
4
  /* eslint-disable @devtools/no-imperative-dom-api */
5
5
 
6
+ import type * as Common from '../../../../core/common/common.js';
6
7
  import type * as Platform from '../../../../core/platform/platform.js';
7
8
  import type * as TextUtils from '../../../../core/text_utils/text_utils.js';
8
9
  import * as Lit from '../../../lit/lit.js';
@@ -79,6 +80,39 @@ export class DataGridElement extends UI.UIUtils.HTMLElementWithLightDOMTemplate
79
80
  #hiddenColumns = new Set<string>();
80
81
  #usedCreationNode: DataGridElementNode|null = null;
81
82
  #sortingChangedScheduled = false;
83
+ #columnsVisibilitySetting?: Common.Settings.Setting<Record<string, {visible: boolean}>>;
84
+
85
+ set columnsVisibilitySetting(setting: Common.Settings.Setting<Record<string, {visible: boolean}>>) {
86
+ this.#columnsVisibilitySetting = setting;
87
+ this.#applyColumnVisibilitySetting();
88
+ }
89
+
90
+ get columnsVisibilitySetting(): Common.Settings.Setting<Record<string, {visible: boolean}>>|undefined {
91
+ return this.#columnsVisibilitySetting;
92
+ }
93
+
94
+ #applyColumnVisibilitySetting(): void {
95
+ if (!this.#columnsVisibilitySetting) {
96
+ return;
97
+ }
98
+ const settingValue = this.#columnsVisibilitySetting.get();
99
+ let changed = false;
100
+ for (const column of this.#columns) {
101
+ if (this.#hideableColumns.has(column.id) && settingValue[column.id]?.visible === false) {
102
+ if (!this.#hiddenColumns.has(column.id)) {
103
+ this.#hiddenColumns.add(column.id);
104
+ changed = true;
105
+ }
106
+ } else if (this.#hiddenColumns.has(column.id)) {
107
+ this.#hiddenColumns.delete(column.id);
108
+ changed = true;
109
+ }
110
+ }
111
+ if (changed) {
112
+ const visibleColumns = new Set(this.#columns.map(({id}) => id).filter(id => !this.#hiddenColumns.has(id)));
113
+ this.#dataGrid.setColumnsVisibility(visibleColumns);
114
+ }
115
+ }
82
116
 
83
117
  constructor() {
84
118
  super();
@@ -133,6 +167,11 @@ export class DataGridElement extends UI.UIUtils.HTMLElementWithLightDOMTemplate
133
167
  }
134
168
  this.#dataGrid.setColumnsVisibility(
135
169
  new Set(this.#columns.map(({id}) => id).filter(column => !this.#hiddenColumns.has(column))));
170
+ if (this.#columnsVisibilitySetting) {
171
+ const settingValue = this.#columnsVisibilitySetting.get();
172
+ settingValue[column.id] = {visible: !this.#hiddenColumns.has(column.id)};
173
+ this.#columnsVisibilitySetting.set(settingValue);
174
+ }
136
175
  }, {checked: !this.#hiddenColumns.has(column.id)});
137
176
  }
138
177
  }
@@ -320,6 +359,7 @@ export class DataGridElement extends UI.UIUtils.HTMLElementWithLightDOMTemplate
320
359
  const visibleColumns = new Set(this.#columns.map(({id}) => id).filter(id => !this.#hiddenColumns.has(id)));
321
360
  this.#dataGrid.setColumnsVisibility(visibleColumns);
322
361
  this.#dataGrid.setEditCallback(hasEditableColumn ? this.#editCallback.bind(this) : undefined, INTERNAL_TOKEN);
362
+ this.#applyColumnVisibilitySetting();
323
363
  }
324
364
 
325
365
  #needUpdateColumns(mutationList: MutationRecord[]): boolean {
@@ -80,7 +80,7 @@ export function renderSettingSelect(setting: Common.Settings.Setting<unknown>, s
80
80
  <select
81
81
  id=${controlId}
82
82
  aria-label=${name}
83
- .disabled=${disabled ?? setting.disabled()}
83
+ .disabled=${Boolean(disabled)}
84
84
  @change=${onSelectChange}
85
85
  jslog=${VisualLogging.dropDown().track({change: true}).context(setting.name)}
86
86
  >
@@ -709,6 +709,7 @@ export class Linkifier extends Common.ObjectWrapper.ObjectWrapper<EventTypes> im
709
709
  if (text instanceof HTMLElement) {
710
710
  link.appendChild(text);
711
711
  } else if (bypassURLTrimming) {
712
+ link.removeChildren();
712
713
  link.classList.add('devtools-link-styled-trim');
713
714
  Linkifier.appendTextWithoutHashes(link, text);
714
715
  } else {
@@ -0,0 +1,80 @@
1
+ // Copyright 2026 The Chromium Authors
2
+ // Use of this source code is governed by a BSD-style license that can be
3
+ // found in the LICENSE file.
4
+
5
+ import * as Lit from '../../third_party/lit/lit.js';
6
+
7
+ /* eslint-disable-next-line @typescript-eslint/no-explicit-any */
8
+ export type BindingEventListener = (arg: any) => any;
9
+
10
+ export class InterceptBindingDirective extends Lit.Directive.Directive {
11
+ static readonly #interceptedBindings = new WeakMap<Element, Map<string, BindingEventListener>>();
12
+ static readonly #attachedBindings = new WeakMap<Element, Map<string, Set<BindingEventListener>>>();
13
+
14
+ override update(part: Lit.Directive.Part, [listener]: [BindingEventListener]): unknown {
15
+ if (part.type !== Lit.Directive.PartType.EVENT) {
16
+ return listener;
17
+ }
18
+ let eventListeners = InterceptBindingDirective.#interceptedBindings.get(part.element);
19
+ if (!eventListeners) {
20
+ eventListeners = new Map();
21
+ InterceptBindingDirective.#interceptedBindings.set(part.element, eventListeners);
22
+ }
23
+ eventListeners.set(part.name, listener);
24
+
25
+ return this.render(listener);
26
+ }
27
+
28
+ /* eslint-disable-next-line @typescript-eslint/no-unsafe-function-type */
29
+ render(listener: Function): Function {
30
+ return listener;
31
+ }
32
+
33
+ static setEventListeners(templateElements: Element|Iterable<Element>, renderedElement: Element): void {
34
+ const attachedListeners = InterceptBindingDirective.#attachedBindings.get(renderedElement);
35
+ if (attachedListeners) {
36
+ for (const [name, listeners] of attachedListeners) {
37
+ for (const listener of listeners) {
38
+ renderedElement.removeEventListener(name, listener);
39
+ }
40
+ }
41
+ }
42
+
43
+ const elements = templateElements instanceof Element ? [templateElements] : templateElements;
44
+ const newAttachedListeners = new Map<string, Set<BindingEventListener>>();
45
+ for (const templateElement of elements) {
46
+ const newListeners = InterceptBindingDirective.#interceptedBindings.get(templateElement);
47
+ if (newListeners) {
48
+ for (const [name, listener] of newListeners) {
49
+ renderedElement.addEventListener(name, listener);
50
+ let listenersSet = newAttachedListeners.get(name);
51
+ if (!listenersSet) {
52
+ listenersSet = new Set();
53
+ newAttachedListeners.set(name, listenersSet);
54
+ }
55
+ listenersSet.add(listener);
56
+ }
57
+ }
58
+ }
59
+
60
+ if (newAttachedListeners.size) {
61
+ InterceptBindingDirective.#attachedBindings.set(renderedElement, newAttachedListeners);
62
+ } else {
63
+ InterceptBindingDirective.#attachedBindings.delete(renderedElement);
64
+ }
65
+ }
66
+
67
+ static registerListeners(element: Element, listeners?: Record<string, EventListenerOrEventListenerObject>): void {
68
+ if (!listeners) {
69
+ InterceptBindingDirective.#interceptedBindings.delete(element);
70
+ return;
71
+ }
72
+ const map = new Map<string, BindingEventListener>();
73
+ for (const [name, listener] of Object.entries(listeners)) {
74
+ map.set(name,
75
+ typeof listener === 'function' ? listener as BindingEventListener :
76
+ listener.handleEvent.bind(listener) as BindingEventListener);
77
+ }
78
+ InterceptBindingDirective.#interceptedBindings.set(element, map);
79
+ }
80
+ }
@@ -18,6 +18,7 @@ export {
18
18
  svg,
19
19
  type TemplateResult,
20
20
  } from '../../third_party/lit/lit.js';
21
+ export * as CustomDirectives from './Directives.js';
21
22
  export {
22
23
  i18nTemplate,
23
24
  } from './i18n-template.js';