chrome-devtools-frontend 1.0.1703574 → 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 (69) hide show
  1. package/AUTHORS +1 -0
  2. package/front_end/core/common/ParsedURL.ts +54 -0
  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/entrypoints/main/MainImpl.ts +3 -3
  9. package/front_end/models/bindings/CompilerScriptMapping.ts +1 -1
  10. package/front_end/models/har/HARFormat.ts +7 -2
  11. package/front_end/models/source_map_scopes/NamesResolver.ts +13 -5
  12. package/front_end/panels/accessibility/AccessibilityAnnouncementRecordingListView.ts +33 -1
  13. package/front_end/panels/accessibility/AccessibilityAnnouncementRecordingView.ts +4 -12
  14. package/front_end/panels/application/ApplicationPanelSidebar.ts +58 -60
  15. package/front_end/panels/application/BackForwardCacheTreeElement.ts +1 -1
  16. package/front_end/panels/application/FrameDetailsView.ts +64 -82
  17. package/front_end/panels/application/OpenedWindowDetailsView.ts +20 -21
  18. package/front_end/panels/application/PreloadingTreeElement.ts +3 -3
  19. package/front_end/panels/common/CommentsOverlayWidget.ts +145 -72
  20. package/front_end/panels/common/commentsOverlay.css +34 -23
  21. package/front_end/panels/elements/DOMTreeWidget.ts +29 -10
  22. package/front_end/panels/network/RequestPayloadView.ts +7 -2
  23. package/front_end/panels/network/ShowMoreDetailsWidget.ts +12 -9
  24. package/front_end/panels/sources/DebuggerPlugin.ts +49 -16
  25. package/front_end/panels/sources/SourcesPanel.ts +1 -1
  26. package/front_end/panels/sources/SourcesView.ts +3 -12
  27. package/front_end/panels/sources/TabbedEditorContainer.ts +0 -3
  28. package/front_end/panels/sources/components/HeadersView.ts +193 -123
  29. package/front_end/third_party/chromium/README.chromium +1 -1
  30. package/front_end/third_party/puppeteer/README.chromium +2 -2
  31. package/front_end/third_party/puppeteer/package/lib/es5-iife/puppeteer-core-browser.d.ts +5 -0
  32. package/front_end/third_party/puppeteer/package/lib/es5-iife/puppeteer-core-browser.js +24 -32
  33. package/front_end/third_party/puppeteer/package/lib/puppeteer/api/ElementHandle.d.ts +1 -1
  34. package/front_end/third_party/puppeteer/package/lib/puppeteer/api/ElementHandle.d.ts.map +1 -1
  35. package/front_end/third_party/puppeteer/package/lib/puppeteer/api/ElementHandle.js +15 -1
  36. package/front_end/third_party/puppeteer/package/lib/puppeteer/api/ElementHandle.js.map +1 -1
  37. package/front_end/third_party/puppeteer/package/lib/puppeteer/cdp/Accessibility.js +8 -1
  38. package/front_end/third_party/puppeteer/package/lib/puppeteer/cdp/Accessibility.js.map +1 -1
  39. package/front_end/third_party/puppeteer/package/lib/puppeteer/common/Debug.d.ts +0 -8
  40. package/front_end/third_party/puppeteer/package/lib/puppeteer/common/Debug.d.ts.map +1 -1
  41. package/front_end/third_party/puppeteer/package/lib/puppeteer/common/Debug.js +0 -24
  42. package/front_end/third_party/puppeteer/package/lib/puppeteer/common/Debug.js.map +1 -1
  43. package/front_end/third_party/puppeteer/package/lib/puppeteer/node/BrowserLauncher.d.ts +12 -0
  44. package/front_end/third_party/puppeteer/package/lib/puppeteer/node/BrowserLauncher.d.ts.map +1 -1
  45. package/front_end/third_party/puppeteer/package/lib/puppeteer/node/BrowserLauncher.js +79 -17
  46. package/front_end/third_party/puppeteer/package/lib/puppeteer/node/BrowserLauncher.js.map +1 -1
  47. package/front_end/third_party/puppeteer/package/lib/puppeteer/node-env-setup.js +1 -1
  48. package/front_end/third_party/puppeteer/package/lib/puppeteer/node-env-setup.js.map +1 -1
  49. package/front_end/third_party/puppeteer/package/lib/puppeteer/revisions.d.ts +3 -3
  50. package/front_end/third_party/puppeteer/package/lib/puppeteer/revisions.js +3 -3
  51. package/front_end/third_party/puppeteer/package/lib/puppeteer/util/version.d.ts +1 -1
  52. package/front_end/third_party/puppeteer/package/lib/puppeteer/util/version.js +1 -1
  53. package/front_end/third_party/puppeteer/package/lib/types.d.ts +5 -0
  54. package/front_end/third_party/puppeteer/package/package.json +4 -4
  55. package/front_end/third_party/puppeteer/package/src/api/ElementHandle.ts +15 -2
  56. package/front_end/third_party/puppeteer/package/src/cdp/Accessibility.ts +10 -1
  57. package/front_end/third_party/puppeteer/package/src/common/Debug.ts +0 -27
  58. package/front_end/third_party/puppeteer/package/src/node/BrowserLauncher.ts +96 -17
  59. package/front_end/third_party/puppeteer/package/src/node-env-setup.ts +1 -1
  60. package/front_end/third_party/puppeteer/package/src/revisions.ts +3 -3
  61. package/front_end/third_party/puppeteer/package/src/util/version.ts +1 -1
  62. package/front_end/ui/comments/CommentAnchorResolver.ts +60 -22
  63. package/front_end/ui/comments/CommentOverlayManager.ts +78 -25
  64. package/front_end/ui/legacy/components/data_grid/DataGridExporter.ts +7 -13
  65. package/front_end/ui/legacy/components/inline_editor/PositionAreaEditor.ts +51 -57
  66. package/front_end/ui/legacy/components/source_frame/ImageView.ts +101 -42
  67. package/front_end/ui/legacy/components/utils/Linkifier.ts +1 -2
  68. package/front_end/ui/visual_logging/KnownContextValues.ts +7 -0
  69. package/package.json +1 -1
@@ -14,76 +14,75 @@ import openedWindowDetailsViewStyles from './openedWindowDetailsView.css.js';
14
14
 
15
15
  const UIStrings = {
16
16
  /**
17
- * @description Text in Timeline indicating that input has happened recently
17
+ * @description Text indicating that the opened window has access to its opener.
18
18
  */
19
19
  yes: 'Yes',
20
20
  /**
21
- * @description Text in Timeline indicating that input has not happened recently
21
+ * @description Text indicating that the opened window does not have access to its opener.
22
22
  */
23
23
  no: 'No',
24
24
  /**
25
- * @description Title for a link to the Elements panel
25
+ * @description Tooltip for the button to reveal the opener frame DOM node in the Elements panel.
26
26
  */
27
27
  clickToOpenInElementsPanel: 'Click to open in Elements panel',
28
28
  /**
29
- * @description Name of a network resource type
29
+ * @description Section header for document details in the opened window details view and worker details view.
30
30
  */
31
31
  document: 'Document',
32
32
  /**
33
- * @description Text for web URLs
33
+ * @description Field label for the URL in the opened window details view and worker details view.
34
34
  */
35
35
  url: 'URL',
36
36
  /**
37
- * @description Title of the 'Security' tool
37
+ * @description Section header for security information in the opened window details view.
38
38
  */
39
39
  security: 'Security',
40
40
  /**
41
- * @description Label for link to Opener Frame in Detail View for Opened Window
41
+ * @description Field label for the link to the opener frame in the opened window details view.
42
42
  */
43
- openerFrame: 'Opener Frame',
43
+ openerFrame: 'Opener frame',
44
44
  /**
45
- * @description Label in opened window's details view whether window has access to its opener
45
+ * @description Field label indicating whether the opened window has access to its opener in the opened window details view.
46
46
  */
47
47
  accessToOpener: 'Access to opener',
48
48
  /**
49
- * @description Description for the 'Access to Opener' field
49
+ * @description Tooltip explaining whether the opened window has access to its opener in the opened window details view.
50
50
  */
51
51
  showsWhetherTheOpenedWindowIs: 'Shows whether the opened window is able to access its opener and vice versa',
52
52
  /**
53
- * @description Text in Frames View of the Application panel
53
+ * @description Fallback title for an opened window without a title in the opened window details view.
54
54
  */
55
55
  windowWithoutTitle: 'Window without title',
56
56
  /**
57
- * @description Label suffix in the Application Panel Frames section for windows which are already closed
57
+ * @description Suffix for the title of an opened window that has been closed in the opened window details view.
58
58
  */
59
59
  closed: 'closed',
60
60
  /**
61
- * @description Default name for worker
61
+ * @description Fallback title for a worker in the worker details view.
62
62
  */
63
63
  worker: 'worker',
64
64
  /**
65
- * @description Text that refers to some types
65
+ * @description Field label for the worker type in the worker details view.
66
66
  */
67
67
  type: 'Type',
68
68
  /**
69
- * @description Section header in the Frame Details view
69
+ * @description Section header for security and isolation in the worker details view.
70
70
  */
71
- securityIsolation: 'Security & Isolation',
71
+ securityIsolation: 'Security & isolation',
72
72
  /**
73
- * @description Row title in the Frame Details view
73
+ * @description Field label for the Cross-Origin Embedder Policy in the worker details view.
74
74
  */
75
75
  crossoriginEmbedderPolicy: 'Cross-Origin Embedder Policy',
76
76
  /**
77
- * @description Label for worker type: web worker
77
+ * @description Value for dedicated Web Worker type in the worker details view.
78
78
  */
79
79
  webWorker: 'Web Worker',
80
80
  /**
81
- * @description Text for an unspecified service worker response source
81
+ * @description Value for an unknown worker type in the worker details view.
82
82
  */
83
83
  unknown: 'Unknown',
84
84
  /**
85
- * @description This label specifies the server endpoints to which the server is reporting errors
86
- *and warnings through the Report-to API. Following this label will be the URL of the server.
85
+ * @description Prefix for the reporting endpoint in the security & isolation section of the worker details view.
87
86
  */
88
87
  reportingTo: 'reporting to',
89
88
  } as const;
@@ -14,15 +14,15 @@ import type {ResourcesPanel} from './ResourcesPanel.js';
14
14
 
15
15
  const UIStrings = {
16
16
  /**
17
- * @description Text in Application Panel Sidebar of the Application panel
17
+ * @description Label for the speculative loads tree item in the sidebar of the Application panel.
18
18
  */
19
19
  speculativeLoads: 'Speculative loads',
20
20
  /**
21
- * @description Text in Application Panel Sidebar of the Application panel
21
+ * @description Label for the rules child tree item under speculative loads in the sidebar of the Application panel.
22
22
  */
23
23
  rules: 'Rules',
24
24
  /**
25
- * @description Text in Application Panel Sidebar of the Application panel
25
+ * @description Label for the speculations child tree item under speculative loads in the sidebar of the Application panel.
26
26
  */
27
27
  speculations: 'Speculations',
28
28
  } as const;
@@ -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;
@@ -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
  }
@@ -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
  }
@@ -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 => {