chrome-devtools-frontend 1.0.1702515 → 1.0.1704172

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 (84) hide show
  1. package/AUTHORS +1 -0
  2. package/front_end/core/common/ParsedURL.ts +59 -1
  3. package/front_end/core/platform/StringUtilities.ts +65 -0
  4. package/front_end/core/sdk/CPUThrottlingManager.ts +3 -3
  5. package/front_end/core/sdk/SourceMap.ts +3 -3
  6. package/front_end/core/sdk/SourceMapScopeChainEntry.ts +4 -3
  7. package/front_end/core/sdk/SourceMapScopesInfo.ts +17 -5
  8. package/front_end/core/text_utils/Markdown.ts +73 -0
  9. package/front_end/core/text_utils/text_utils.ts +2 -0
  10. package/front_end/entrypoints/main/MainImpl.ts +3 -3
  11. package/front_end/generated/InspectorBackendCommands.ts +3 -1
  12. package/front_end/generated/protocol-mapping.d.ts +8 -0
  13. package/front_end/generated/protocol-proxy-api.d.ts +6 -0
  14. package/front_end/generated/protocol.ts +22 -0
  15. package/front_end/models/bindings/CompilerScriptMapping.ts +1 -1
  16. package/front_end/models/comment_manager/CD4ABridge.ts +5 -0
  17. package/front_end/models/har/HARFormat.ts +7 -2
  18. package/front_end/models/issues_manager/MarkdownIssueDescription.ts +4 -47
  19. package/front_end/models/persistence/AutomaticFileSystemWorkspaceBinding.ts +1 -1
  20. package/front_end/models/source_map_scopes/NamesResolver.ts +13 -5
  21. package/front_end/panels/accessibility/AccessibilityAnnouncementRecordingListView.ts +33 -1
  22. package/front_end/panels/accessibility/AccessibilityAnnouncementRecordingView.ts +4 -12
  23. package/front_end/panels/application/ApplicationPanelSidebar.ts +58 -60
  24. package/front_end/panels/application/BackForwardCacheTreeElement.ts +1 -1
  25. package/front_end/panels/application/FrameDetailsView.ts +64 -82
  26. package/front_end/panels/application/OpenedWindowDetailsView.ts +20 -21
  27. package/front_end/panels/application/PreloadingTreeElement.ts +3 -3
  28. package/front_end/panels/common/CommentsOverlayWidget.ts +145 -72
  29. package/front_end/panels/common/CommentsStatusBarPill.ts +40 -7
  30. package/front_end/panels/common/commentsOverlay.css +34 -23
  31. package/front_end/panels/common/commentsStatusBarPill.css +13 -0
  32. package/front_end/panels/elements/DOMTreeWidget.ts +29 -10
  33. package/front_end/panels/issues/IssueView.ts +5 -1
  34. package/front_end/panels/network/RequestPayloadView.ts +7 -2
  35. package/front_end/panels/network/ShowMoreDetailsWidget.ts +12 -9
  36. package/front_end/panels/sources/DebuggerPlugin.ts +49 -16
  37. package/front_end/panels/sources/SourcesPanel.ts +1 -1
  38. package/front_end/panels/sources/SourcesView.ts +119 -148
  39. package/front_end/panels/sources/TabbedEditorContainer.ts +263 -207
  40. package/front_end/panels/sources/components/HeadersView.ts +193 -123
  41. package/front_end/third_party/chromium/README.chromium +1 -1
  42. package/front_end/third_party/puppeteer/README.chromium +2 -2
  43. package/front_end/third_party/puppeteer/package/lib/es5-iife/puppeteer-core-browser.d.ts +5 -0
  44. package/front_end/third_party/puppeteer/package/lib/es5-iife/puppeteer-core-browser.js +24 -32
  45. package/front_end/third_party/puppeteer/package/lib/puppeteer/api/ElementHandle.d.ts +1 -1
  46. package/front_end/third_party/puppeteer/package/lib/puppeteer/api/ElementHandle.d.ts.map +1 -1
  47. package/front_end/third_party/puppeteer/package/lib/puppeteer/api/ElementHandle.js +15 -1
  48. package/front_end/third_party/puppeteer/package/lib/puppeteer/api/ElementHandle.js.map +1 -1
  49. package/front_end/third_party/puppeteer/package/lib/puppeteer/cdp/Accessibility.js +8 -1
  50. package/front_end/third_party/puppeteer/package/lib/puppeteer/cdp/Accessibility.js.map +1 -1
  51. package/front_end/third_party/puppeteer/package/lib/puppeteer/common/Debug.d.ts +0 -8
  52. package/front_end/third_party/puppeteer/package/lib/puppeteer/common/Debug.d.ts.map +1 -1
  53. package/front_end/third_party/puppeteer/package/lib/puppeteer/common/Debug.js +0 -24
  54. package/front_end/third_party/puppeteer/package/lib/puppeteer/common/Debug.js.map +1 -1
  55. package/front_end/third_party/puppeteer/package/lib/puppeteer/node/BrowserLauncher.d.ts +12 -0
  56. package/front_end/third_party/puppeteer/package/lib/puppeteer/node/BrowserLauncher.d.ts.map +1 -1
  57. package/front_end/third_party/puppeteer/package/lib/puppeteer/node/BrowserLauncher.js +79 -17
  58. package/front_end/third_party/puppeteer/package/lib/puppeteer/node/BrowserLauncher.js.map +1 -1
  59. package/front_end/third_party/puppeteer/package/lib/puppeteer/node-env-setup.js +1 -1
  60. package/front_end/third_party/puppeteer/package/lib/puppeteer/node-env-setup.js.map +1 -1
  61. package/front_end/third_party/puppeteer/package/lib/puppeteer/revisions.d.ts +3 -3
  62. package/front_end/third_party/puppeteer/package/lib/puppeteer/revisions.js +3 -3
  63. package/front_end/third_party/puppeteer/package/lib/puppeteer/util/version.d.ts +1 -1
  64. package/front_end/third_party/puppeteer/package/lib/puppeteer/util/version.js +1 -1
  65. package/front_end/third_party/puppeteer/package/lib/types.d.ts +5 -0
  66. package/front_end/third_party/puppeteer/package/package.json +4 -4
  67. package/front_end/third_party/puppeteer/package/src/api/ElementHandle.ts +15 -2
  68. package/front_end/third_party/puppeteer/package/src/cdp/Accessibility.ts +10 -1
  69. package/front_end/third_party/puppeteer/package/src/common/Debug.ts +0 -27
  70. package/front_end/third_party/puppeteer/package/src/node/BrowserLauncher.ts +96 -17
  71. package/front_end/third_party/puppeteer/package/src/node-env-setup.ts +1 -1
  72. package/front_end/third_party/puppeteer/package/src/revisions.ts +3 -3
  73. package/front_end/third_party/puppeteer/package/src/util/version.ts +1 -1
  74. package/front_end/ui/comments/CommentAnchorResolver.ts +60 -22
  75. package/front_end/ui/comments/CommentOverlayManager.ts +78 -25
  76. package/front_end/ui/components/markdown_view/MarkdownPlaceholderLitRenderer.ts +7 -65
  77. package/front_end/ui/legacy/TabbedPane.ts +51 -40
  78. package/front_end/ui/legacy/components/data_grid/DataGridExporter.ts +7 -13
  79. package/front_end/ui/legacy/components/inline_editor/PositionAreaEditor.ts +51 -57
  80. package/front_end/ui/legacy/components/object_ui/ObjectPropertiesSection.ts +342 -373
  81. package/front_end/ui/legacy/components/source_frame/ImageView.ts +101 -42
  82. package/front_end/ui/legacy/components/utils/Linkifier.ts +1 -2
  83. package/front_end/ui/visual_logging/KnownContextValues.ts +8 -0
  84. package/package.json +2 -1
@@ -55,59 +55,67 @@ const DEFAULT_VIEW: View = (input: ViewInput, _output: undefined, target: HTMLEl
55
55
  <div
56
56
  class="comment-hover-highlight"
57
57
  style=${styleMap({
58
- top: `${input.hoverHighlight.top}px`,
59
- left: `${input.hoverHighlight.left}px`,
58
+ transform: `translate3d(${input.hoverHighlight.left}px, ${input.hoverHighlight.top}px, 0)`,
60
59
  width: `${input.hoverHighlight.width}px`,
61
60
  height: `${input.hoverHighlight.height}px`,
62
61
  })}>
63
62
  </div>
64
63
  ` : nothing}
65
- ${input.highlights.map(h => h.visible ? html`
66
- <div
67
- class="comment-anchor-highlight"
68
- style=${styleMap({
69
- top: `${h.top}px`,
70
- left: `${h.left}px`,
71
- width: `${h.width}px`,
72
- height: `${h.height}px`,
73
- })}>
74
- </div>
75
- ` : nothing)}
76
- ${input.pins.map(p => p.visible ? html`
77
- <div
78
- class="comment-pin"
79
- style=${styleMap({
80
- top: `${p.top}px`,
81
- left: `${p.left}px`,
82
- })}
83
- @click=${() => input.onPinClick(p.id)}>
84
- <div class="comment-cursor">${p.index}</div>
85
- </div>
86
- ` : nothing)}
87
- ${input.activePin && input.activeThread ? repeat(
88
- [{pin: input.activePin, thread: input.activeThread}],
89
- item => item.thread.id,
90
- item => html`
64
+ ${repeat(
65
+ input.highlights.filter(h => h.visible),
66
+ h => h.id,
67
+ h => html`
91
68
  <div
92
- class="comment-popup-widget"
69
+ class="comment-anchor-highlight"
93
70
  style=${styleMap({
94
- top: `${Math.min(
95
- Math.max(POPUP_MARGIN, item.pin.top + PIN_HEIGHT),
96
- Math.max(POPUP_MARGIN, target.clientHeight - POPUP_HEIGHT),
97
- )}px`,
98
- left: `${Math.min(
99
- Math.max(POPUP_MARGIN, item.pin.left),
100
- Math.max(POPUP_MARGIN, target.clientWidth - POPUP_WIDTH - POPUP_MARGIN),
101
- )}px`,
71
+ transform: `translate3d(${h.left}px, ${h.top}px, 0)`,
72
+ width: `${h.width}px`,
73
+ height: `${h.height}px`,
102
74
  })}>
103
- ${UI.Widget.widget(CommentThreadWidget, {
104
- title: input.title,
105
- comments: [...item.thread.comments],
106
- onAddComment: input.onAddComment,
107
- onClose: input.onCloseCommentThread,
75
+ </div>
76
+ `,
77
+ )}
78
+ ${repeat(
79
+ input.pins.filter(p => p.visible),
80
+ p => p.id,
81
+ p => html`
82
+ <div
83
+ class="comment-pin"
84
+ style=${styleMap({
85
+ transform: `translate3d(${p.left}px, ${p.top}px, 0)`,
108
86
  })}
87
+ @click=${() => input.onPinClick(p.id)}>
88
+ <div class="comment-cursor">${p.index}</div>
109
89
  </div>
110
90
  `,
91
+ )}
92
+ ${input.activePin && input.activeThread ? repeat(
93
+ [{pin: input.activePin, thread: input.activeThread}],
94
+ item => item.thread.id,
95
+ item => {
96
+ const popupTop = Math.min(
97
+ Math.max(POPUP_MARGIN, item.pin.top + PIN_HEIGHT),
98
+ Math.max(POPUP_MARGIN, target.clientHeight - POPUP_HEIGHT),
99
+ );
100
+ const popupLeft = Math.min(
101
+ Math.max(POPUP_MARGIN, item.pin.left),
102
+ Math.max(POPUP_MARGIN, target.clientWidth - POPUP_WIDTH - POPUP_MARGIN),
103
+ );
104
+ return html`
105
+ <div
106
+ class="comment-popup-widget"
107
+ style=${styleMap({
108
+ transform: `translate3d(${popupLeft}px, ${popupTop}px, 0)`,
109
+ })}>
110
+ ${UI.Widget.widget(CommentThreadWidget, {
111
+ title: input.title,
112
+ comments: [...item.thread.comments],
113
+ onAddComment: input.onAddComment,
114
+ onClose: input.onCloseCommentThread,
115
+ })}
116
+ </div>
117
+ `;
118
+ },
111
119
  ) : nothing}
112
120
  </div>
113
121
  `, target);
@@ -160,12 +168,12 @@ export class CommentsOverlayWidget extends UI.Widget.Widget {
160
168
  this.#commentOverlayManager.start();
161
169
  this.#commentOverlayManager.addEventListener(
162
170
  Comments.CommentOverlayManager.Events.POSITIONS_UPDATED,
163
- this.#onStateChanged,
171
+ this.#onPositionsUpdated,
164
172
  this,
165
173
  );
166
174
  this.#commentOverlayManager.addEventListener(
167
175
  Comments.CommentOverlayManager.Events.HOVER_HIGHLIGHT_CHANGED,
168
- this.#onStateChanged,
176
+ this.#onHoverHighlightChanged,
169
177
  this,
170
178
  );
171
179
 
@@ -197,12 +205,12 @@ export class CommentsOverlayWidget extends UI.Widget.Widget {
197
205
  this.#commentOverlayManager.stop();
198
206
  this.#commentOverlayManager.removeEventListener(
199
207
  Comments.CommentOverlayManager.Events.POSITIONS_UPDATED,
200
- this.#onStateChanged,
208
+ this.#onPositionsUpdated,
201
209
  this,
202
210
  );
203
211
  this.#commentOverlayManager.removeEventListener(
204
212
  Comments.CommentOverlayManager.Events.HOVER_HIGHLIGHT_CHANGED,
205
- this.#onStateChanged,
213
+ this.#onHoverHighlightChanged,
206
214
  this,
207
215
  );
208
216
 
@@ -257,16 +265,60 @@ export class CommentsOverlayWidget extends UI.Widget.Widget {
257
265
  this.requestUpdate();
258
266
  }
259
267
 
260
- #onStateChanged(): void {
268
+ #syncActiveThreadFromManager(): void {
261
269
  const draftThread = this.#commentManager.getCommentThreads().find(t => t.status === 'DRAFT');
262
270
  if (draftThread) {
263
271
  this.#setActiveThreadId(draftThread.id);
264
272
  } else if (this.#activeThreadId && !this.#commentManager.getCommentThread(this.#activeThreadId)) {
265
273
  this.#setActiveThreadId(null);
266
274
  }
275
+ }
276
+
277
+ #onPositionsUpdated(
278
+ event: Common.EventTarget.EventTargetEvent<
279
+ Comments.CommentOverlayManager.EventTypes[Comments.CommentOverlayManager.Events.POSITIONS_UPDATED]>,
280
+ ): void {
281
+ this.#syncActiveThreadFromManager();
282
+ if (event.data.isRealtimeSync && this.isShowing() && this.#trySynchronousRender()) {
283
+ return;
284
+ }
285
+ this.requestUpdate();
286
+ }
287
+
288
+ #onHoverHighlightChanged(): void {
289
+ this.#syncActiveThreadFromManager();
290
+ if (this.isShowing() && this.#trySynchronousRender()) {
291
+ return;
292
+ }
293
+ this.requestUpdate();
294
+ }
295
+
296
+ #onStateChanged(): void {
297
+ this.#syncActiveThreadFromManager();
267
298
  this.requestUpdate();
268
299
  }
269
300
 
301
+ #trySynchronousRender(): boolean {
302
+ if (!this.#commentManager.isAgentAttached()) {
303
+ this.#renderDisconnectedView();
304
+ return true;
305
+ }
306
+ const activeThread =
307
+ this.#activeThreadId ? this.#commentManager.getCommentThread(this.#activeThreadId) ?? null : null;
308
+ const anchor = activeThread?.anchor ?? null;
309
+ if (anchor === null) {
310
+ this.#cachedTitleAnchor = null;
311
+ this.#cachedTitle = {text: ''};
312
+ this.#renderWithTitle(activeThread, this.#cachedTitle);
313
+ return true;
314
+ }
315
+ if (anchor === this.#cachedTitleAnchor) {
316
+ this.#renderWithTitle(activeThread, this.#cachedTitle);
317
+ return true;
318
+ }
319
+ return false;
320
+ }
321
+
270
322
  async #getOrComputeTitle(anchor: CommentManager.CommentManager.CommentAnchorSignature|null): Promise<Title> {
271
323
  if (anchor === this.#cachedTitleAnchor) {
272
324
  return this.#cachedTitle;
@@ -333,32 +385,29 @@ export class CommentsOverlayWidget extends UI.Widget.Widget {
333
385
  }
334
386
  };
335
387
 
336
- override async performUpdate(signal?: AbortSignal): Promise<void> {
337
- if (!this.#commentManager.isAgentAttached()) {
338
- this.#view(
339
- {
340
- pins: [],
341
- highlights: [],
342
- hoverHighlight: null,
343
- commentMode: false,
344
- onPinClick: this.#handlePinClick,
345
- activeThread: null,
346
- activePin: null,
347
- title: {text: ''},
348
- onAddComment: () => {},
349
- onCloseCommentThread: this.#handleCloseCommentThread,
350
- },
351
- undefined,
352
- this.contentElement,
353
- );
354
- return;
355
- }
356
-
357
- const activeThread =
358
- this.#activeThreadId ? this.#commentManager.getCommentThread(this.#activeThreadId) ?? null : null;
359
- const title = await this.#getOrComputeTitle(activeThread?.anchor ?? null);
360
- signal?.throwIfAborted();
388
+ #renderDisconnectedView(): void {
389
+ this.#view(
390
+ {
391
+ pins: [],
392
+ highlights: [],
393
+ hoverHighlight: null,
394
+ commentMode: false,
395
+ onPinClick: this.#handlePinClick,
396
+ activeThread: null,
397
+ activePin: null,
398
+ title: {text: ''},
399
+ onAddComment: () => {},
400
+ onCloseCommentThread: this.#handleCloseCommentThread,
401
+ },
402
+ undefined,
403
+ this.contentElement,
404
+ );
405
+ }
361
406
 
407
+ #renderWithTitle(
408
+ activeThread: CommentManager.CommentManager.CommentThread|null,
409
+ title: Title,
410
+ ): void {
362
411
  const pins = this.#commentOverlayManager.getPinPositions();
363
412
  const highlights = this.#commentOverlayManager.getHighlightRects();
364
413
  const activePin = this.#activeThreadId ? pins.find(p => p.id === this.#activeThreadId) ?? null : null;
@@ -391,6 +440,30 @@ export class CommentsOverlayWidget extends UI.Widget.Widget {
391
440
  };
392
441
  this.#view(viewInput, undefined, this.contentElement);
393
442
  }
443
+
444
+ override async performUpdate(signal?: AbortSignal): Promise<void> {
445
+ if (!this.#commentManager.isAgentAttached()) {
446
+ this.#renderDisconnectedView();
447
+ return;
448
+ }
449
+
450
+ const activeThread =
451
+ this.#activeThreadId ? this.#commentManager.getCommentThread(this.#activeThreadId) ?? null : null;
452
+ const anchor = activeThread?.anchor ?? null;
453
+ let title: Title;
454
+ if (anchor === null) {
455
+ this.#cachedTitleAnchor = null;
456
+ this.#cachedTitle = {text: ''};
457
+ title = this.#cachedTitle;
458
+ } else if (anchor === this.#cachedTitleAnchor) {
459
+ title = this.#cachedTitle;
460
+ } else {
461
+ title = await this.#getOrComputeTitle(anchor);
462
+ signal?.throwIfAborted();
463
+ }
464
+
465
+ this.#renderWithTitle(activeThread, title);
466
+ }
394
467
  }
395
468
 
396
469
  let widgetInstance: CommentsOverlayWidget|null = null;
@@ -4,16 +4,24 @@
4
4
 
5
5
  import * as i18n from '../../core/i18n/i18n.js';
6
6
  import * as CommentManager from '../../models/comment_manager/comment_manager.js';
7
+ import * as Buttons from '../../ui/components/buttons/buttons.js';
7
8
  import * as UI from '../../ui/legacy/legacy.js';
8
9
  import * as Lit from '../../ui/lit/lit.js';
9
10
  import * as VisualLogging from '../../ui/visual_logging/visual_logging.js';
10
11
 
12
+ import commentsStatusBarPillStyles from './commentsStatusBarPill.css.js';
13
+
11
14
  const UIStrings = {
12
15
  /**
13
16
  * @description Button text for the comments status bar pill showing the number of open comments.
14
17
  * @example {2} PH1
15
18
  */
16
19
  commentsCount: 'Comments ({PH1})',
20
+ /**
21
+ * @description Button text for sending all draft comments to the agent in the comments status bar pill.
22
+ * @example {2} PH1
23
+ */
24
+ sendToAgentCount: 'Send to agent ({PH1})',
17
25
  } as const;
18
26
  const str_ = i18n.i18n.registerUIStrings('panels/common/CommentsStatusBarPill.ts', UIStrings);
19
27
  const i18nString = i18n.i18n.getLocalizedString.bind(undefined, str_);
@@ -23,22 +31,38 @@ const {html, render} = Lit;
23
31
  export interface ViewInput {
24
32
  threads: CommentManager.CommentManager.CommentThread[];
25
33
  onPillClick: () => void;
34
+ onSendToAgentClick?: () => void;
26
35
  disabled?: boolean;
27
36
  }
28
37
 
29
38
  export type View = (input: ViewInput, output: undefined, target: HTMLElement) => void;
30
39
 
31
40
  export const DEFAULT_VIEW: View = (input: ViewInput, _output: undefined, target: HTMLElement): void => {
41
+ const unsentCount = input.threads.filter(thread => thread.status === 'ACTIVE').length;
42
+ const tooltip = input.threads.flatMap(thread => thread.comments.map(comment => comment.text)).join('\n');
32
43
  // clang-format off
33
44
  render(html`
45
+ <style>${commentsStatusBarPillStyles}</style>
34
46
  ${input.threads.length <= 0 ? Lit.nothing : html`
35
- <button
36
- class="devtools-pill"
37
- ?disabled=${input.disabled}
38
- @click=${input.onPillClick}
39
- jslog=${VisualLogging.action('comments-status-bar-pill').track({click: true})}>
40
- ${i18nString(UIStrings.commentsCount, {PH1: input.threads.length})}
41
- </button>
47
+ <div class="pill-container">
48
+ <button
49
+ class="devtools-pill"
50
+ title=${tooltip}
51
+ ?disabled=${input.disabled}
52
+ @click=${input.onPillClick}
53
+ jslog=${VisualLogging.action('comments-status-bar-pill').track({click: true})}>
54
+ ${i18nString(UIStrings.commentsCount, {PH1: input.threads.length})}
55
+ </button>
56
+ ${unsentCount > 0 ? html`
57
+ <devtools-button
58
+ .variant=${Buttons.Button.Variant.PRIMARY}
59
+ .disabled=${Boolean(input.disabled)}
60
+ .jslogContext=${'comments-send-to-agent'}
61
+ @click=${input.onSendToAgentClick}>
62
+ ${i18nString(UIStrings.sendToAgentCount, {PH1: unsentCount})}
63
+ </devtools-button>
64
+ ` : Lit.nothing}
65
+ </div>
42
66
  `}
43
67
  `, target);
44
68
  // clang-format on
@@ -100,9 +124,18 @@ export class CommentsStatusBarPill extends UI.Widget.Widget {
100
124
  this.#commentManager.getCommentThreads().filter(thread => thread.status !== 'DRAFT') :
101
125
  [],
102
126
  onPillClick: this.#handlePillClick,
127
+ onSendToAgentClick: this.#handleSendToAgentClick,
103
128
  };
104
129
  this.#view(viewInput, undefined, this.contentElement);
105
130
  }
106
131
 
107
132
  #handlePillClick = (): void => {};
133
+
134
+ #handleSendToAgentClick = (): void => {
135
+ for (const thread of this.#commentManager.getCommentThreads()) {
136
+ if (thread.status === 'ACTIVE') {
137
+ thread.sendToAgent();
138
+ }
139
+ }
140
+ };
108
141
  }
@@ -20,57 +20,68 @@
20
20
 
21
21
  .comment-pin {
22
22
  position: absolute;
23
+ top: 0;
24
+ left: 0;
23
25
  pointer-events: auto;
24
26
  cursor: pointer;
25
27
  user-select: none;
26
- transition: transform 0.1s ease;
27
- transform-origin: center center;
28
- will-change: transform;
28
+ will-change: transform;
29
29
  }
30
30
 
31
- .comment-pin:hover {
31
+ .comment-pin:hover .comment-cursor {
32
32
  transform: scale(1.15);
33
33
  }
34
34
 
35
- .comment-cursor {
36
- display: flex;
37
- width: var(--sys-size-9);
38
- height: var(--sys-size-9);
39
- box-sizing: border-box;
40
- padding: 0;
41
- flex-direction: column;
42
- justify-content: center;
43
- align-items: center;
44
- flex-shrink: 0;
45
- box-shadow: var(--sys-elevation-level2);
46
- border-radius: 100px 100px 100px var(--sys-shape-corner-extra-small, 4px);
47
- background: var(--sys-color-primary);
48
- color: var(--sys-color-on-primary);
49
- font-family: var(--default-font-family);
50
- font-size: var(--sys-typescale-body5-size);
51
- font-weight: var(--ref-typeface-weight-bold, 600);
52
- line-height: 1;
53
- }
35
+ .comment-cursor {
36
+ display: flex;
37
+ width: var(--sys-size-9);
38
+ height: var(--sys-size-9);
39
+ box-sizing: border-box;
40
+ padding: 0;
41
+ flex-direction: column;
42
+ justify-content: center;
43
+ align-items: center;
44
+ flex-shrink: 0;
45
+ box-shadow: var(--sys-elevation-level2);
46
+ border-radius: 100px 100px 100px var(--sys-shape-corner-extra-small, 4px);
47
+ background: var(--sys-color-primary);
48
+ color: var(--sys-color-on-primary);
49
+ font-family: var(--default-font-family);
50
+ font-size: var(--sys-typescale-body5-size);
51
+ font-weight: var(--ref-typeface-weight-bold, 600);
52
+ line-height: 1;
53
+ transition: transform 0.1s ease;
54
+ transform-origin: center center;
55
+ }
54
56
 
55
57
  .comment-anchor-highlight {
56
58
  position: absolute;
59
+ top: 0;
60
+ left: 0;
57
61
  pointer-events: none;
58
62
  border: var(--sys-size-2) dashed var(--sys-color-primary);
59
63
  background-color: color-mix(in srgb, var(--sys-color-primary), transparent 90%);
60
64
  box-sizing: border-box;
65
+ will-change: transform;
61
66
  }
62
67
 
63
68
  .comment-hover-highlight {
64
69
  position: absolute;
70
+ top: 0;
71
+ left: 0;
65
72
  pointer-events: none;
66
73
  border: var(--sys-size-2) solid var(--sys-color-primary);
67
74
  background-color: color-mix(in srgb, var(--sys-color-primary), transparent 85%);
68
75
  box-sizing: border-box;
76
+ will-change: transform;
69
77
  }
70
78
 
71
79
  .comment-popup-widget {
72
80
  position: absolute;
81
+ top: 0;
82
+ left: 0;
73
83
  z-index: 2501;
74
84
  pointer-events: auto;
85
+ will-change: transform;
75
86
  }
76
87
  }
@@ -0,0 +1,13 @@
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
+ .pill-container {
9
+ display: flex;
10
+ align-items: center;
11
+ gap: var(--sys-size-3);
12
+ }
13
+ }
@@ -426,6 +426,11 @@ function nodeHasVisibleChildren(node: SDK.DOMModel.DOMNode, rootDOMNode: SDK.DOM
426
426
  return Boolean(node.childNodeCount()) && !ElementsTreeWidget.canShowInlineText(node);
427
427
  }
428
428
 
429
+ function nodeNeedsClosingTag(node: SDK.DOMModel.DOMNode, hasChildren: boolean): boolean {
430
+ return hasChildren && node.nodeType() === Node.ELEMENT_NODE &&
431
+ !ForbiddenClosingTagElements.has(node.nodeName().toLowerCase()) && !node.pseudoType();
432
+ }
433
+
429
434
  function getVisibleChildren(node: SDK.DOMModel.DOMNode, showComments = true): SDK.DOMModel.DOMNode[] {
430
435
  const children: SDK.DOMModel.DOMNode[] = [];
431
436
  children.push(...ElementsTreeWidget.visibleShadowRoots(node));
@@ -778,10 +783,7 @@ export const DECLARATIVE_VIEW: View = (input: ViewInput, _output: ViewOutput, ta
778
783
  if (node instanceof SDK.DOMModel.DOMDocument) {
779
784
  rows += countTopLayerRows(node);
780
785
  }
781
- const tagName = node.nodeName().toLowerCase();
782
- const needsClosingTag = node.nodeType() === Node.ELEMENT_NODE && !ForbiddenClosingTagElements.has(tagName) &&
783
- !node.pseudoType() && (hasChildren || !ElementsTreeWidget.canShowInlineText(node));
784
- if (needsClosingTag) {
786
+ if (nodeNeedsClosingTag(node, hasChildren)) {
785
787
  rows += 1;
786
788
  }
787
789
  }
@@ -827,9 +829,7 @@ export const DECLARATIVE_VIEW: View = (input: ViewInput, _output: ViewOutput, ta
827
829
  const limit = input.expandedChildrenLimit ? input.expandedChildrenLimit(node) : InitialChildrenLimit;
828
830
  const children = allVisibleChildren.slice(0, limit);
829
831
  const remainingChildrenCount = allVisibleChildren.length - children.length;
830
- const tagName = node.nodeName().toLowerCase();
831
- const needsClosingTag = node.nodeType() === Node.ELEMENT_NODE && !ForbiddenClosingTagElements.has(tagName) &&
832
- !node.pseudoType() && (hasChildren || !ElementsTreeWidget.canShowInlineText(node));
832
+ const needsClosingTag = nodeNeedsClosingTag(node, hasChildren);
833
833
 
834
834
  const on = Lit.Directive.directive(Lit.CustomDirectives.InterceptBindingDirective);
835
835
 
@@ -1461,6 +1461,7 @@ export class DOMTreeWidget extends UI.Widget.Widget {
1461
1461
  const domModel = event.data;
1462
1462
  if (this.#view === DECLARATIVE_VIEW) {
1463
1463
  this.#selectedDOMNode = null;
1464
+ this.#selectedClosingTag = false;
1464
1465
  this.#expandedNodes.clear();
1465
1466
  this.#currentHighlightedNode = null;
1466
1467
  this.#updateRecords.clear();
@@ -1679,9 +1680,6 @@ export class DOMTreeWidget extends UI.Widget.Widget {
1679
1680
  !getVisibleChildren(node.parentNode, this.#showComments).includes(node))) {
1680
1681
  node = node.parentNode;
1681
1682
  }
1682
- const isSameNode = this.#selectedDOMNode === node && this.#selectedClosingTag === Boolean(isClosingTag);
1683
- this.#selectedDOMNode = node;
1684
- this.#selectedClosingTag = Boolean(isClosingTag);
1685
1683
  if (node) {
1686
1684
  const ancestors: SDK.DOMModel.DOMNode[] = [];
1687
1685
  for (let current: (SDK.DOMModel.DOMNode|null) = node.parentNode; current; current = current.parentNode) {
@@ -1707,7 +1705,14 @@ export class DOMTreeWidget extends UI.Widget.Widget {
1707
1705
  });
1708
1706
  }
1709
1707
  }
1708
+ if (isClosingTag && nodeHasVisibleChildren(node, this.rootDOMNode, this.maxTreeDepth, this.omitRootDOMNode)) {
1709
+ this.#expandedNodes.add(node);
1710
+ }
1710
1711
  }
1712
+ const selectClosingTag = Boolean(isClosingTag) && Boolean(node && this.#hasClosingTag(node));
1713
+ const isSameNode = this.#selectedDOMNode === node && this.#selectedClosingTag === selectClosingTag;
1714
+ this.#selectedDOMNode = node;
1715
+ this.#selectedClosingTag = selectClosingTag;
1711
1716
  this.#clearHighlightedNode();
1712
1717
  if (!isSameNode) {
1713
1718
  this.onSelectedNodeChanged(
@@ -2014,8 +2019,19 @@ export class DOMTreeWidget extends UI.Widget.Widget {
2014
2019
  return current === this.#rootDOMNode ? selectedNode : (fallback ?? this.#rootDOMNode);
2015
2020
  }
2016
2021
 
2022
+ #hasClosingTag(node: SDK.DOMModel.DOMNode): boolean {
2023
+ const isEditingAsHTML = this.#multilineEditingNode === node ||
2024
+ (this.#nodeToEdit?.node === node && Boolean(this.#nodeToEdit.isEditAsHTML));
2025
+ if (isEditingAsHTML || !this.isNodeExpanded(node)) {
2026
+ return false;
2027
+ }
2028
+ const hasChildren = nodeHasVisibleChildren(node, this.#rootDOMNode, this.#maxTreeDepth, this.omitRootDOMNode);
2029
+ return nodeNeedsClosingTag(node, hasChildren);
2030
+ }
2031
+
2017
2032
  #validateSelectedNode(): void {
2018
2033
  if (!this.#selectedDOMNode) {
2034
+ this.#selectedClosingTag = false;
2019
2035
  return;
2020
2036
  }
2021
2037
  const validNode = this.#findValidSelectedNode(this.#selectedDOMNode);
@@ -2026,6 +2042,8 @@ export class DOMTreeWidget extends UI.Widget.Widget {
2026
2042
  this.onSelectedNodeChanged(
2027
2043
  {data: {node: validNode, focus: false}} as
2028
2044
  Common.EventTarget.EventTargetEvent<{node: SDK.DOMModel.DOMNode | null, focus: boolean}>);
2045
+ } else if (this.#selectedClosingTag && !this.#hasClosingTag(this.#selectedDOMNode)) {
2046
+ this.#selectedClosingTag = false;
2029
2047
  }
2030
2048
  }
2031
2049
 
@@ -2276,6 +2294,7 @@ export class DOMTreeWidget extends UI.Widget.Widget {
2276
2294
  if (this.#rootDOMNode?.domModel() === domModel) {
2277
2295
  this.#rootDOMNode = null;
2278
2296
  this.#selectedDOMNode = null;
2297
+ this.#selectedClosingTag = false;
2279
2298
  this.#expandedNodes.clear();
2280
2299
  this.#updateRecords.clear();
2281
2300
  }
@@ -435,7 +435,11 @@ export class IssueView extends UI.TreeOutline.TreeElement {
435
435
  messageElement.setCollapsible(false);
436
436
  messageElement.selectable = false;
437
437
  const markdownComponent = new MarkdownView.MarkdownView.MarkdownView();
438
- markdownComponent.data = {tokens: this.#description.markdown};
438
+ markdownComponent.data = {
439
+ tokens: this.#description.markdown,
440
+ renderer: new MarkdownView.MarkdownPlaceholderLitRenderer.MarkdownPlaceholderLitRenderer(
441
+ this.#description.substitutions),
442
+ };
439
443
  messageElement.listItemElement.appendChild(markdownComponent);
440
444
  UI.ARIAUtils.setPositionInSet(messageElement.listItemElement, 1);
441
445
  UI.ARIAUtils.setSetSize(messageElement.listItemElement, this.#description.links.length === 0 ? 2 : 3);
@@ -154,11 +154,16 @@ export const DEFAULT_VIEW: View = (input, output, target) => {
154
154
  void contextMenu.show();
155
155
  };
156
156
 
157
- const createSourceText = (text: string): TemplateResult => html`<li role=treeitem
157
+ const createSourceText = (text: string): TemplateResult => {
158
+ const copy = (): void => input.copyValue(text);
159
+ // clang-format off
160
+ return html`<li role=treeitem
158
161
  @contextmenu=${copyValueContextmenu(i18nString(UIStrings.copyPayload), () => text, 'copy-payload')}>
159
- <devtools-widget class='payload-value source-code' ${widget(ShowMoreDetailsWidget, {text})}>
162
+ <devtools-widget class='payload-value source-code' ${widget(ShowMoreDetailsWidget, {text, copy})}>
160
163
  </devtools-widget>
161
164
  </li>`;
165
+ // clang-format on
166
+ };
162
167
 
163
168
  const createParsedParams = (params: SDK.NetworkRequest.NameValue[],
164
169
  decodeParameters: boolean): TemplateResult[] => params.map(param => {
@@ -11,6 +11,10 @@ const UIStrings = {
11
11
  * @description Text to show more content
12
12
  */
13
13
  showMore: 'Show more',
14
+ /**
15
+ * @description Context menu item to copy the shown text to the clipboard
16
+ */
17
+ copy: 'Copy',
14
18
  } as const;
15
19
  const str_ = i18n.i18n.registerUIStrings('panels/network/ShowMoreDetailsWidget.ts', UIStrings);
16
20
  const i18nString = i18n.i18n.getLocalizedString.bind(undefined, str_);
@@ -20,7 +24,7 @@ interface ViewInput {
20
24
  text: string;
21
25
  showMore: boolean;
22
26
  onToggle: () => void;
23
- copy: CopyMenuItem|null;
27
+ copy: (() => void)|null;
24
28
  }
25
29
  type View = (input: ViewInput, output: object, target: HTMLElement) => void;
26
30
  const MAX_LENGTH = 3000;
@@ -29,7 +33,7 @@ export const DEFAULT_VIEW: View = (input, output, target) => {
29
33
  const onContextMenuShowMore = (event: Event): void => {
30
34
  const contextMenu = new UI.ContextMenu.ContextMenu(event);
31
35
  if (input.copy) {
32
- contextMenu.clipboardSection().appendItem(input.copy.menuItem, input.copy.handler);
36
+ contextMenu.clipboardSection().appendItem(i18nString(UIStrings.copy), input.copy, {jslogContext: 'copy'});
33
37
  }
34
38
  if (!input.showMore) {
35
39
  contextMenu.newSection().appendItem(i18nString(UIStrings.showMore), input.onToggle, {jslogContext: 'show-more'});
@@ -51,16 +55,11 @@ export const DEFAULT_VIEW: View = (input, output, target) => {
51
55
  target);
52
56
  };
53
57
 
54
- interface CopyMenuItem {
55
- menuItem: UI.ContextMenu.Item;
56
- handler: () => void;
57
- }
58
-
59
58
  export class ShowMoreDetailsWidget extends UI.Widget.Widget {
60
59
  readonly #view: View;
61
60
  #text = '';
62
61
  #showMore = false;
63
- #copy: CopyMenuItem|null = null;
62
+ #copy: (() => void)|null = null;
64
63
  constructor(target?: HTMLElement, view: View = DEFAULT_VIEW) {
65
64
  super(target);
66
65
  this.#view = view;
@@ -75,7 +74,11 @@ export class ShowMoreDetailsWidget extends UI.Widget.Widget {
75
74
  this.requestUpdate();
76
75
  }
77
76
 
78
- set copy(copy: CopyMenuItem) {
77
+ get copy(): (() => void)|null {
78
+ return this.#copy;
79
+ }
80
+
81
+ set copy(copy: () => void) {
79
82
  this.#copy = copy;
80
83
  this.requestUpdate();
81
84
  }