chrome-devtools-frontend 1.0.1578486 → 1.0.1579812

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 (96) hide show
  1. package/docs/cookbook/devtools_on_devtools.md +2 -2
  2. package/docs/feature-specs/elements-gutter-decorators.md +40 -0
  3. package/docs/feature-specs/elements-tree-selection-and-hover.md +31 -0
  4. package/docs/feature-specs/images/elements-gutter-decorators-multiple.png +0 -0
  5. package/docs/get_the_code.md +3 -3
  6. package/front_end/core/common/Revealer.ts +0 -5
  7. package/front_end/core/sdk/ConsoleModel.ts +0 -3
  8. package/front_end/generated/ARIAProperties.js +0 -6
  9. package/front_end/generated/InspectorBackendCommands.ts +1 -1
  10. package/front_end/generated/protocol.ts +1 -0
  11. package/front_end/models/ai_assistance/AiConversation.ts +42 -22
  12. package/front_end/models/ai_assistance/agents/ContextSelectionAgent.snapshot.txt +30 -0
  13. package/front_end/models/ai_assistance/agents/ContextSelectionAgent.ts +79 -0
  14. package/front_end/models/issues_manager/CookieIssue.ts +0 -1
  15. package/front_end/models/issues_manager/IssuesManager.ts +2 -15
  16. package/front_end/models/javascript_metadata/NativeFunctions.js +4 -0
  17. package/front_end/panels/accessibility/AXBreadcrumbsPane.ts +5 -3
  18. package/front_end/panels/ai_assistance/AiAssistancePanel.ts +33 -27
  19. package/front_end/panels/ai_assistance/ai_assistance-meta.ts +0 -1
  20. package/front_end/panels/ai_assistance/components/ChatInput.ts +38 -18
  21. package/front_end/panels/ai_assistance/components/ChatMessage.ts +7 -4
  22. package/front_end/panels/ai_assistance/components/ChatView.ts +2 -0
  23. package/front_end/panels/ai_assistance/components/chatInput.css +10 -3
  24. package/front_end/panels/animation/AnimationGroupPreviewUI.ts +1 -1
  25. package/front_end/panels/application/DeviceBoundSessionsView.ts +51 -76
  26. package/front_end/panels/application/ServiceWorkerUpdateCycleView.ts +1 -0
  27. package/front_end/panels/application/components/BackForwardCacheView.ts +4 -2
  28. package/front_end/panels/application/preloading/components/RuleSetDetailsView.css +0 -5
  29. package/front_end/panels/autofill/AutofillView.ts +1 -1
  30. package/front_end/panels/browser_debugger/XHRBreakpointsSidebarPane.ts +1 -0
  31. package/front_end/panels/common/AiCodeGenerationTeaser.ts +2 -2
  32. package/front_end/panels/common/AiCodeGenerationUpgradeDialog.ts +4 -4
  33. package/front_end/panels/console/ConsoleViewMessage.ts +1 -32
  34. package/front_end/panels/console/consoleView.css +0 -5
  35. package/front_end/panels/elements/AdoptedStyleSheetTreeElement.ts +78 -0
  36. package/front_end/panels/elements/ElementsTreeElement.ts +93 -121
  37. package/front_end/panels/elements/ElementsTreeOutline.ts +1 -0
  38. package/front_end/panels/elements/LayoutPane.ts +8 -7
  39. package/front_end/panels/elements/components/ElementsBreadcrumbs.ts +1 -1
  40. package/front_end/panels/elements/components/StylePropertyEditor.ts +2 -1
  41. package/front_end/panels/elements/elements.ts +3 -0
  42. package/front_end/panels/elements/elementsTreeOutline.css +16 -5
  43. package/front_end/panels/lighthouse/LighthouseReportRenderer.ts +1 -1
  44. package/front_end/panels/lighthouse/LighthouseStartView.ts +1 -1
  45. package/front_end/panels/lighthouse/lighthouseStartView.css +6 -0
  46. package/front_end/panels/linear_memory_inspector/components/LinearMemoryValueInterpreter.ts +1 -1
  47. package/front_end/panels/linear_memory_inspector/components/ValueInterpreterDisplay.ts +1 -1
  48. package/front_end/panels/media/PlayerListView.ts +14 -2
  49. package/front_end/panels/media/playerListView.css +5 -0
  50. package/front_end/panels/network/NetworkItemView.ts +2 -1
  51. package/front_end/panels/network/NetworkLogView.ts +2 -5
  52. package/front_end/panels/network/NetworkLogViewColumns.ts +19 -7
  53. package/front_end/panels/network/RequestInitiatorView.ts +20 -4
  54. package/front_end/panels/network/RequestPayloadView.ts +253 -280
  55. package/front_end/panels/network/ShowMoreDetailsWidget.ts +3 -3
  56. package/front_end/panels/network/components/RequestHeaderSection.ts +1 -1
  57. package/front_end/panels/network/requestPayloadTree.css +6 -3
  58. package/front_end/panels/network/requestPayloadView.css +1 -0
  59. package/front_end/panels/protocol_monitor/JSONEditor.ts +1 -1
  60. package/front_end/panels/recorder/components/RecordingListView.ts +1 -1
  61. package/front_end/panels/recorder/components/StepEditor.ts +3 -3
  62. package/front_end/panels/security/SecurityPanel.ts +0 -16
  63. package/front_end/panels/security/security-meta.ts +1 -15
  64. package/front_end/panels/settings/EditFileSystemView.ts +8 -8
  65. package/front_end/panels/settings/KeybindsSettingsTab.ts +2 -1
  66. package/front_end/panels/settings/SettingsScreen.ts +4 -4
  67. package/front_end/panels/sources/CallStackSidebarPane.ts +1 -1
  68. package/front_end/panels/sources/components/HeadersView.ts +2 -2
  69. package/front_end/panels/timeline/components/BreadcrumbsUI.ts +1 -1
  70. package/front_end/panels/timeline/components/SidebarAnnotationsTab.ts +1 -1
  71. package/front_end/panels/timeline/overlays/OverlaysImpl.ts +47 -26
  72. package/front_end/panels/timeline/overlays/components/TimespanBreakdownOverlay.ts +121 -39
  73. package/front_end/panels/timeline/overlays/components/timespanBreakdownOverlay.css +106 -101
  74. package/front_end/third_party/chromium/README.chromium +1 -1
  75. package/front_end/ui/components/buttons/Button.ts +1 -1
  76. package/front_end/ui/components/suggestion_input/SuggestionInput.ts +7 -12
  77. package/front_end/ui/components/tree_outline/TreeOutline.ts +1 -1
  78. package/front_end/ui/legacy/InplaceEditor.ts +1 -1
  79. package/front_end/ui/legacy/InspectorView.ts +6 -5
  80. package/front_end/ui/legacy/ListControl.ts +2 -1
  81. package/front_end/ui/legacy/ListWidget.ts +1 -1
  82. package/front_end/ui/legacy/SoftContextMenu.ts +2 -1
  83. package/front_end/ui/legacy/Treeoutline.ts +82 -22
  84. package/front_end/ui/legacy/components/data_grid/DataGrid.ts +4 -2
  85. package/front_end/ui/legacy/components/data_grid/SortableDataGrid.ts +59 -9
  86. package/front_end/ui/legacy/components/object_ui/ObjectPropertiesSection.ts +28 -15
  87. package/front_end/ui/legacy/components/object_ui/objectPropertiesSection.css +2 -1
  88. package/front_end/ui/legacy/components/quick_open/CommandMenu.ts +2 -2
  89. package/front_end/ui/legacy/components/quick_open/FilteredListWidget.ts +1 -1
  90. package/front_end/ui/legacy/components/settings_ui/SettingsUI.ts +3 -3
  91. package/front_end/ui/legacy/components/source_frame/JSONView.ts +2 -3
  92. package/front_end/ui/legacy/softDropDown.css +7 -1
  93. package/front_end/ui/visual_logging/Debugging.ts +1 -1
  94. package/front_end/ui/visual_logging/KnownContextValues.ts +3 -0
  95. package/mcp/mcp.ts +2 -0
  96. package/package.json +1 -1
@@ -38,7 +38,6 @@ import * as Common from '../../core/common/common.js';
38
38
  import * as Host from '../../core/host/host.js';
39
39
  import * as i18n from '../../core/i18n/i18n.js';
40
40
  import * as Platform from '../../core/platform/platform.js';
41
- import * as Root from '../../core/root/root.js';
42
41
  import * as SDK from '../../core/sdk/sdk.js';
43
42
  import * as Protocol from '../../generated/protocol.js';
44
43
  import * as AiAssistanceModel from '../../models/ai_assistance/ai_assistance.js';
@@ -47,7 +46,6 @@ import type * as IssuesManager from '../../models/issues_manager/issues_manager.
47
46
  import * as Logs from '../../models/logs/logs.js';
48
47
  import * as TextUtils from '../../models/text_utils/text_utils.js';
49
48
  import * as Workspace from '../../models/workspace/workspace.js';
50
- import * as Buttons from '../../ui/components/buttons/buttons.js';
51
49
  import * as CodeHighlighter from '../../ui/components/code_highlighter/code_highlighter.js';
52
50
  import * as Highlighting from '../../ui/components/highlighting/highlighting.js';
53
51
  import * as IssueCounter from '../../ui/components/issue_counter/issue_counter.js';
@@ -61,7 +59,6 @@ import * as Components from '../../ui/legacy/components/utils/utils.js';
61
59
  import * as UI from '../../ui/legacy/legacy.js';
62
60
  import {render} from '../../ui/lit/lit.js';
63
61
  import * as VisualLogging from '../../ui/visual_logging/visual_logging.js';
64
- import * as Security from '../security/security.js';
65
62
 
66
63
  import {format, updateStyle} from './ConsoleFormat.js';
67
64
  import {ConsoleInsightTeaser} from './ConsoleInsightTeaser.js';
@@ -220,10 +217,6 @@ const UIStrings = {
220
217
  * @description Message to offer insights for a console message
221
218
  */
222
219
  explainThisMessageWithAI: 'Understand this message. Powered by AI',
223
- /**
224
- * @description Tooltip shown when user hovers over the cookie icon to explain that the button will bring the user to the cookie report
225
- */
226
- SeeIssueInCookieReport: 'Click to open privacy and security panel and show third-party cookie report',
227
220
  /**
228
221
  * @description Element text content in Object Properties Section
229
222
  */
@@ -574,23 +567,6 @@ export class ConsoleViewMessage implements ConsoleViewportElement {
574
567
  return elements;
575
568
  }
576
569
 
577
- #appendCookieReportButtonToElem(elem: HTMLElement): void {
578
- const button = new Buttons.Button.Button();
579
- button.data = {
580
- size: Buttons.Button.Size.SMALL,
581
- variant: Buttons.Button.Variant.ICON,
582
- iconName: 'cookie',
583
- jslogContext: 'privacy',
584
- title: i18nString(UIStrings.SeeIssueInCookieReport)
585
- };
586
-
587
- button.addEventListener('click', () => {
588
- void Common.Revealer.reveal(new Security.CookieReportView.CookieReportView());
589
- });
590
-
591
- elem.appendChild(button);
592
- }
593
-
594
570
  #getLinkifierMetric(): Host.UserMetrics.Action|undefined {
595
571
  const request = Logs.NetworkLog.NetworkLog.requestForConsoleMessage(this.message);
596
572
  if (request?.resourceType().isStyleSheet()) {
@@ -631,14 +607,6 @@ export class ConsoleViewMessage implements ConsoleViewportElement {
631
607
  return null;
632
608
  };
633
609
 
634
- if (this.message.isCookieReportIssue && Root.Runtime.hostConfig.devToolsPrivacyUI?.enabled) {
635
- const anchorWrapperElement = document.createElement('span');
636
- anchorWrapperElement.classList.add('console-message-anchor', 'cookie-report-anchor');
637
- this.#appendCookieReportButtonToElem(anchorWrapperElement);
638
- UI.UIUtils.createTextChild(anchorWrapperElement, ' ');
639
- return anchorWrapperElement;
640
- }
641
-
642
610
  const anchorElement = linkify(this.message);
643
611
  // Append a space to prevent the anchor text from being glued to the console message when the user selects and copies the console messages.
644
612
  if (anchorElement) {
@@ -1457,6 +1425,7 @@ export class ConsoleViewMessage implements ConsoleViewportElement {
1457
1425
  this.elementInternal.className = 'console-message-wrapper';
1458
1426
  this.elementInternal.setAttribute('jslog', `${VisualLogging.item('console-message').track({
1459
1427
  click: true,
1428
+ resize: true,
1460
1429
  keydown: 'ArrowUp|ArrowDown|ArrowLeft|ArrowRight|Enter|Space|Home|End',
1461
1430
  })}`);
1462
1431
  this.elementInternal.removeChildren();
@@ -486,11 +486,6 @@
486
486
  margin-left: 4px;
487
487
  }
488
488
 
489
- .cookie-report-anchor {
490
- margin-top: -3px;
491
- margin-bottom: -5px;
492
- }
493
-
494
489
  .console-message-nowrap-below,
495
490
  .console-message-nowrap-below div,
496
491
  .console-message-nowrap-below span {
@@ -11,6 +11,8 @@ import * as UI from '../../ui/legacy/legacy.js';
11
11
  import * as VisualLogging from '../../ui/visual_logging/visual_logging.js';
12
12
  import {PanelUtils} from '../utils/utils.js';
13
13
 
14
+ import type {EditorHandles} from './ElementsTreeElement.js';
15
+
14
16
  export class AdoptedStyleSheetTreeElement extends UI.TreeOutline.TreeElement {
15
17
  private eventListener: Common.EventTarget.EventDescriptor|null = null;
16
18
 
@@ -58,6 +60,8 @@ export class AdoptedStyleSheetTreeElement extends UI.TreeOutline.TreeElement {
58
60
  }
59
61
 
60
62
  export class AdoptedStyleSheetContentsTreeElement extends UI.TreeOutline.TreeElement {
63
+ private editing: EditorHandles|null = null;
64
+
61
65
  constructor(private readonly styleSheetHeader: SDK.CSSStyleSheetHeader.CSSStyleSheetHeader) {
62
66
  super('');
63
67
  }
@@ -69,6 +73,9 @@ export class AdoptedStyleSheetContentsTreeElement extends UI.TreeOutline.TreeEle
69
73
  }
70
74
 
71
75
  override onunbind(): void {
76
+ if (this.editing) {
77
+ this.editing.cancel();
78
+ }
72
79
  this.styleSheetHeader.cssModel().removeEventListener(
73
80
  SDK.CSSModel.Events.StyleSheetChanged, this.onStyleSheetChanged, this);
74
81
  }
@@ -91,4 +98,75 @@ export class AdoptedStyleSheetContentsTreeElement extends UI.TreeOutline.TreeEle
91
98
  void this.onpopulate();
92
99
  }
93
100
  }
101
+
102
+ override ondblclick(event: Event): boolean {
103
+ if (this.editing) {
104
+ return false;
105
+ }
106
+ void this.startEditing(event.target as Element);
107
+ return false;
108
+ }
109
+
110
+ override onenter(): boolean {
111
+ if (this.editing) {
112
+ return false;
113
+ }
114
+ const target = this.listItemElement.querySelector('.webkit-html-text-node');
115
+ if (target) {
116
+ void this.startEditing(target);
117
+ return true;
118
+ }
119
+ return false;
120
+ }
121
+
122
+ private async startEditing(target: Element): Promise<void> {
123
+ if (this.editing || UI.UIUtils.isBeingEdited(target)) {
124
+ return;
125
+ }
126
+
127
+ const textNode = target.enclosingNodeOrSelfWithClass('webkit-html-text-node');
128
+ if (!textNode) {
129
+ return;
130
+ }
131
+
132
+ const data = await this.styleSheetHeader.requestContentData();
133
+ textNode.textContent = (TextUtils.ContentData.ContentData.isError(data) || !data.isTextContent) ? '' : data.text;
134
+
135
+ const config =
136
+ new UI.InplaceEditor.Config(this.editingCommitted.bind(this), () => this.editingCancelled(), undefined);
137
+
138
+ const editorHandles = UI.InplaceEditor.InplaceEditor.startEditing(textNode, config);
139
+ if (!editorHandles) {
140
+ return;
141
+ }
142
+
143
+ this.editing = {
144
+ commit: editorHandles.commit,
145
+ cancel: editorHandles.cancel,
146
+ editor: undefined,
147
+ resize: () => {},
148
+ };
149
+
150
+ const componentSelection = this.listItemElement.getComponentSelection();
151
+ componentSelection?.selectAllChildren(textNode);
152
+ }
153
+
154
+ private async editingCommitted(element: Element, newText: string, oldText: string|null): Promise<void> {
155
+ this.editing = null;
156
+
157
+ if (newText !== oldText) {
158
+ await this.styleSheetHeader.cssModel().setStyleSheetText(this.styleSheetHeader.id, newText, false);
159
+ }
160
+
161
+ this.editingCancelled();
162
+ }
163
+
164
+ private editingCancelled(): void {
165
+ this.editing = null;
166
+ void this.onpopulate();
167
+ }
168
+
169
+ isEditing(): boolean {
170
+ return this.editing !== null;
171
+ }
94
172
  }
@@ -34,6 +34,7 @@
34
34
  */
35
35
 
36
36
  import '../../ui/components/adorners/adorners.js';
37
+ import '../../ui/components/buttons/buttons.js';
37
38
 
38
39
  import * as Common from '../../core/common/common.js';
39
40
  import * as Host from '../../core/host/host.js';
@@ -50,7 +51,6 @@ import * as TextUtils from '../../models/text_utils/text_utils.js';
50
51
  import * as Workspace from '../../models/workspace/workspace.js';
51
52
  import * as CodeMirror from '../../third_party/codemirror.next/codemirror.next.js';
52
53
  import type * as Adorners from '../../ui/components/adorners/adorners.js';
53
- import * as Buttons from '../../ui/components/buttons/buttons.js';
54
54
  import * as CodeHighlighter from '../../ui/components/code_highlighter/code_highlighter.js';
55
55
  import * as Highlighting from '../../ui/components/highlighting/highlighting.js';
56
56
  import * as TextEditor from '../../ui/components/text_editor/text_editor.js';
@@ -417,11 +417,20 @@ export interface ViewInput {
417
417
  showStartingStyleAdorner: boolean;
418
418
  startingStyleAdornerActive: boolean;
419
419
  onStartingStyleAdornerClick: (e: Event) => void;
420
+
421
+ isHovered: boolean;
422
+ isSelected: boolean;
423
+ showAiButton: boolean;
424
+ aiButtonTitle?: string;
425
+ onAiButtonClick: (e: Event) => void;
426
+
427
+ decorations: Decoration[];
428
+ descendantDecorations: Decoration[];
429
+ decorationsTooltip: string;
430
+ indent: number;
420
431
  }
421
432
 
422
433
  export interface ViewOutput {
423
- gutterContainer?: HTMLElement;
424
- decorationsElement?: HTMLElement;
425
434
  contentElement?: HTMLElement;
426
435
  }
427
436
 
@@ -443,6 +452,11 @@ export function adornerRef(): DirectiveResult<typeof Lit.Directives.RefDirective
443
452
  });
444
453
  }
445
454
 
455
+ export interface Decoration {
456
+ title: string;
457
+ color: string;
458
+ }
459
+
446
460
  function handleAdornerKeydown(cb: (event: Event) => void): (event: KeyboardEvent) => void {
447
461
  return (event: KeyboardEvent) => {
448
462
  if (event.code === 'Enter' || event.code === 'Space') {
@@ -458,13 +472,27 @@ export const DEFAULT_VIEW = (input: ViewInput, output: ViewOutput, target: HTMLE
458
472
  input.showGridAdorner || input.showGridLanesAdorner || input.showMediaAdorner || input.showPopoverAdorner ||
459
473
  input.showTopLayerAdorner || input.showViewSourceAdorner || input.showScrollAdorner ||
460
474
  input.showScrollSnapAdorner || input.showSlotAdorner || input.showStartingStyleAdorner;
475
+ const gutterContainerClasses = {
476
+ 'has-decorations': input.decorations.length || input.descendantDecorations.length,
477
+ 'gutter-container': true,
478
+ };
461
479
  // clang-format off
462
480
  render(html`
463
481
  <div ${ref(el => { output.contentElement = el as HTMLElement; })}>
464
482
  ${input.nodeInfo ? html`<span class="highlight">${input.nodeInfo}</span>` : nothing}
465
- <div class="gutter-container" @click=${input.onGutterClick} ${ref(el => { output.gutterContainer = el as HTMLElement; })}>
483
+ ${input.isHovered || input.isSelected ? html`
484
+ <div class="selection fill" style=${`margin-left: ${-input.indent}px`}></div>
485
+ ` : nothing}
486
+ <div class=${Lit.Directives.classMap(gutterContainerClasses)}
487
+ style="left: ${-input.indent}px"
488
+ @click=${input.onGutterClick}>
466
489
  <devtools-icon name="dots-horizontal"></devtools-icon>
467
- <div class="hidden" ${ref(el => { output.decorationsElement = el as HTMLElement; })}></div>
490
+ ${input.decorations.length || input.descendantDecorations.length ? html`
491
+ <div class="elements-gutter-decoration-container"
492
+ title=${input.decorationsTooltip}>
493
+ ${input.decorations.map(d => html`<div class="elements-gutter-decoration" style="--decoration-color: ${d.color}"></div>`)}
494
+ ${input.descendantDecorations.map(d => html`<div class="elements-gutter-decoration elements-has-decorated-children" style="--decoration-color: ${d.color}"></div>`)}
495
+ </div>` : nothing}
468
496
  </div>
469
497
  ${hasAdorners ? html`<div class="adorner-container ${!hasAdorners ? 'hidden' : ''}">
470
498
  ${input.showAdAdorner ? html`<devtools-adorner
@@ -630,6 +658,20 @@ export const DEFAULT_VIEW = (input: ViewInput, output: ViewOutput, target: HTMLE
630
658
  <span>${ElementsComponents.AdornerManager.RegisteredAdorners.SCROLL_SNAP}</span>
631
659
  </devtools-adorner>` : nothing}
632
660
  </div>`: nothing}
661
+ ${input.isSelected ? html`
662
+ <span class="selected-hint" title=${i18nString(UIStrings.useSInTheConsoleToReferToThis, {PH1: '$0'})} aria-hidden="true"></span>
663
+ ` : nothing}
664
+ ${input.showAiButton ? html`
665
+ <span class="ai-button-container">
666
+ <devtools-floating-button
667
+ icon-name=${AIAssistance.AiUtils.getIconName()}
668
+ title=${input.aiButtonTitle || ''}
669
+ jslogcontext="ask-ai"
670
+ @click=${input.onAiButtonClick}
671
+ @mousedown=${(e: Event) => e.stopPropagation()}>
672
+ </devtools-floating-button>
673
+ </span>
674
+ ` : nothing}
633
675
  </div>
634
676
  `, target);
635
677
  // clang-format on
@@ -640,8 +682,6 @@ export class ElementsTreeElement extends UI.TreeOutline.TreeElement {
640
682
  override treeOutline: ElementsTreeOutline|null;
641
683
 
642
684
  // Handled by the view output for now.
643
- gutterContainer!: HTMLElement;
644
- decorationsElement!: HTMLElement;
645
685
  contentElement!: HTMLElement;
646
686
 
647
687
  private searchQuery: string|null;
@@ -652,9 +692,6 @@ export class ElementsTreeElement extends UI.TreeOutline.TreeElement {
652
692
  private editing: EditorHandles|null;
653
693
  private htmlEditElement?: HTMLElement;
654
694
  expandAllButtonElement: UI.TreeOutline.TreeElement|null;
655
- selectionElement?: HTMLDivElement;
656
- private hintElement?: HTMLElement;
657
- private aiButtonContainer?: HTMLElement;
658
695
  #elementIssues = new Map<string, IssuesManager.Issue.Issue>();
659
696
  #nodeElementToIssue = new Map<Element, IssuesManager.Issue.Issue[]>();
660
697
  #highlights: Range[] = [];
@@ -672,6 +709,10 @@ export class ElementsTreeElement extends UI.TreeOutline.TreeElement {
672
709
  #startingStyleAdornerActive = false;
673
710
  #layout: SDK.CSSModel.LayoutProperties|null = null;
674
711
 
712
+ #decorations: Decoration[] = [];
713
+ #descendantDecorations: Decoration[] = [];
714
+ #decorationsTooltip = '';
715
+
675
716
  constructor(node: SDK.DOMModel.DOMNode, isClosingTag?: boolean) {
676
717
  // The title will be updated in onattach.
677
718
  super();
@@ -680,6 +721,7 @@ export class ElementsTreeElement extends UI.TreeOutline.TreeElement {
680
721
  this.listItemElement.setAttribute(
681
722
  'jslog', `${VisualLogging.treeItem().parent('elementsTreeOutline').track({
682
723
  keydown: 'ArrowUp|ArrowDown|ArrowLeft|ArrowRight|Backspace|Delete|Enter|Space|Home|End',
724
+ resize: true,
683
725
  drag: true,
684
726
  click: true,
685
727
  })}`);
@@ -797,6 +839,10 @@ export class ElementsTreeElement extends UI.TreeOutline.TreeElement {
797
839
  showScrollAdorner: ((this.node().nodeName() === 'HTML' && this.node().ownerDocument?.isScrollable()) ||
798
840
  (this.node().nodeName() !== '#document' && this.node().isScrollable())) &&
799
841
  !this.isClosingTag(),
842
+ decorations: this.#decorations,
843
+ descendantDecorations: this.expanded ? [] : this.#descendantDecorations,
844
+ decorationsTooltip: this.#decorationsTooltip,
845
+ indent: this.computeLeftIndent(),
800
846
  showScrollSnapAdorner: Boolean(this.#layout?.hasScroll) && !this.isClosingTag(),
801
847
  scrollSnapAdornerActive: this.#scrollSnapAdornerActive,
802
848
  showSlotAdorner: Boolean(this.nodeInternal.assignedSlot) && !this.isClosingTag(),
@@ -827,6 +873,21 @@ export class ElementsTreeElement extends UI.TreeOutline.TreeElement {
827
873
  }
828
874
  this.treeOutline.revealInTopLayer(this.node());
829
875
  },
876
+ isHovered: this.#hovered,
877
+ isSelected: this.selected,
878
+ showAiButton: Boolean(this.#hovered || this.selected) && this.node().nodeType() === Node.ELEMENT_NODE &&
879
+ UI.ActionRegistry.ActionRegistry.instance().hasAction('freestyler.elements-floating-button'),
880
+ aiButtonTitle: UI.ActionRegistry.ActionRegistry.instance().hasAction('freestyler.elements-floating-button') ?
881
+ UI.ActionRegistry.ActionRegistry.instance().getAction('freestyler.elements-floating-button').title() :
882
+ undefined,
883
+ onAiButtonClick: (ev: Event) => {
884
+ ev.stopPropagation();
885
+ this.select(true, false);
886
+ const action = UI.ActionRegistry.ActionRegistry.instance().getAction('freestyler.elements-floating-button');
887
+ if (action) {
888
+ void action.execute();
889
+ }
890
+ },
830
891
  },
831
892
  this, this.listItemElement);
832
893
  }
@@ -962,22 +1023,15 @@ export class ElementsTreeElement extends UI.TreeOutline.TreeElement {
962
1023
  return;
963
1024
  }
964
1025
 
965
- if (isHovered && !this.aiButtonContainer) {
966
- this.createAiButton();
967
- } else if (!isHovered && this.aiButtonContainer) {
968
- this.aiButtonContainer.remove();
969
- delete this.aiButtonContainer;
970
- }
971
-
972
1026
  this.#hovered = isHovered;
973
1027
 
974
1028
  if (this.listItemElement) {
975
1029
  if (isHovered) {
976
- this.createSelection();
977
1030
  this.listItemElement.classList.add('hovered');
978
1031
  } else {
979
1032
  this.listItemElement.classList.remove('hovered');
980
1033
  }
1034
+ this.performUpdate();
981
1035
  }
982
1036
  }
983
1037
 
@@ -1043,54 +1097,6 @@ export class ElementsTreeElement extends UI.TreeOutline.TreeElement {
1043
1097
  this.#expandedChildrenLimit = expandedChildrenLimit;
1044
1098
  }
1045
1099
 
1046
- private createSelection(): void {
1047
- const contentElement = this.contentElement;
1048
- if (!contentElement) {
1049
- return;
1050
- }
1051
-
1052
- if (!this.selectionElement) {
1053
- this.selectionElement = document.createElement('div');
1054
- this.selectionElement.className = 'selection fill';
1055
- this.selectionElement.style.setProperty('margin-left', (-this.computeLeftIndent()) + 'px');
1056
- contentElement.prepend(this.selectionElement);
1057
- }
1058
- }
1059
-
1060
- private createHint(): void {
1061
- if (this.contentElement && !this.hintElement) {
1062
- this.hintElement = this.contentElement.createChild('span', 'selected-hint');
1063
- const selectedElementCommand = '$0';
1064
- UI.Tooltip.Tooltip.install(
1065
- this.hintElement, i18nString(UIStrings.useSInTheConsoleToReferToThis, {PH1: selectedElementCommand}));
1066
- UI.ARIAUtils.setHidden(this.hintElement, true);
1067
- }
1068
- }
1069
-
1070
- private createAiButton(): void {
1071
- const isElementNode = this.node().nodeType() === Node.ELEMENT_NODE;
1072
- if (!isElementNode ||
1073
- !UI.ActionRegistry.ActionRegistry.instance().hasAction('freestyler.elements-floating-button')) {
1074
- return;
1075
- }
1076
-
1077
- const action = UI.ActionRegistry.ActionRegistry.instance().getAction('freestyler.elements-floating-button');
1078
- if (this.contentElement && !this.aiButtonContainer) {
1079
- this.aiButtonContainer = this.contentElement.createChild('span', 'ai-button-container');
1080
- const floatingButton =
1081
- Buttons.FloatingButton.create(AIAssistance.AiUtils.getIconName(), action.title(), 'ask-ai');
1082
- floatingButton.addEventListener('click', ev => {
1083
- ev.stopPropagation();
1084
- this.select(true, false);
1085
- void action.execute();
1086
- }, {capture: true});
1087
- floatingButton.addEventListener('mousedown', ev => {
1088
- ev.stopPropagation();
1089
- }, {capture: true});
1090
- this.aiButtonContainer.appendChild(floatingButton);
1091
- }
1092
- }
1093
-
1094
1100
  override onbind(): void {
1095
1101
  this.performUpdate();
1096
1102
  if (this.treeOutline && !this.isClosingTag()) {
@@ -1156,6 +1162,14 @@ export class ElementsTreeElement extends UI.TreeOutline.TreeElement {
1156
1162
  onPopoverAdornerClick: () => {},
1157
1163
  onScrollSnapAdornerClick: () => {},
1158
1164
  onTopLayerAdornerClick: () => {},
1165
+ isHovered: false,
1166
+ isSelected: false,
1167
+ showAiButton: false,
1168
+ onAiButtonClick: () => {},
1169
+ decorations: [],
1170
+ descendantDecorations: [],
1171
+ decorationsTooltip: '',
1172
+ indent: 0,
1159
1173
  },
1160
1174
  this, this.listItemElement);
1161
1175
 
@@ -1219,8 +1233,8 @@ export class ElementsTreeElement extends UI.TreeOutline.TreeElement {
1219
1233
 
1220
1234
  override onattach(): void {
1221
1235
  if (this.#hovered) {
1222
- this.createSelection();
1223
1236
  this.listItemElement.classList.add('hovered');
1237
+ this.performUpdate();
1224
1238
  }
1225
1239
 
1226
1240
  this.updateTitle();
@@ -1278,8 +1292,7 @@ export class ElementsTreeElement extends UI.TreeOutline.TreeElement {
1278
1292
  this.nodeInternal.highlight();
1279
1293
  Host.userMetrics.actionTaken(Host.UserMetrics.Action.ChangeInspectedNodeInElementsPanel);
1280
1294
  }
1281
- this.createSelection();
1282
- this.createHint();
1295
+ this.performUpdate();
1283
1296
  this.treeOutline.suppressRevealAndSelect = false;
1284
1297
  return true;
1285
1298
  }
@@ -2252,10 +2265,6 @@ export class ElementsTreeElement extends UI.TreeOutline.TreeElement {
2252
2265
  // fixme: make it clear that `this.title = x` is a setter with significant side effects
2253
2266
  this.title = this.contentElement;
2254
2267
  this.updateDecorations();
2255
- if (this.selected) {
2256
- this.createSelection();
2257
- this.createHint();
2258
- }
2259
2268
 
2260
2269
  // If there is an issue with this node, make sure to update it.
2261
2270
  for (const issue of this.#elementIssues.values()) {
@@ -2278,10 +2287,6 @@ export class ElementsTreeElement extends UI.TreeOutline.TreeElement {
2278
2287
  }
2279
2288
 
2280
2289
  updateDecorations(): void {
2281
- const indent = this.computeLeftIndent();
2282
- this.gutterContainer.style.left = (-indent) + 'px';
2283
- this.listItemElement.style.setProperty('--indent', indent + 'px');
2284
-
2285
2290
  if (this.isClosingTag()) {
2286
2291
  return;
2287
2292
  }
@@ -2339,60 +2344,27 @@ export class ElementsTreeElement extends UI.TreeOutline.TreeElement {
2339
2344
  return Promise.all(promises).then(updateDecorationsUI.bind(this));
2340
2345
 
2341
2346
  function updateDecorationsUI(this: ElementsTreeElement): void {
2342
- this.decorationsElement.removeChildren();
2343
- this.decorationsElement.classList.add('hidden');
2344
- this.gutterContainer.classList.toggle(
2345
- 'has-decorations', Boolean(decorations.length || descendantDecorations.length));
2346
- UI.ARIAUtils.setLabel(this.decorationsElement, '');
2347
+ this.#decorations = decorations;
2348
+ this.#descendantDecorations = descendantDecorations;
2347
2349
 
2348
2350
  if (!decorations.length && !descendantDecorations.length) {
2351
+ this.#decorationsTooltip = '';
2352
+ this.performUpdate();
2349
2353
  return;
2350
2354
  }
2351
2355
 
2352
- const colors = new Set<string>();
2353
- const titles = document.createElement('div');
2354
-
2356
+ const tooltip: string[] = [];
2355
2357
  for (const decoration of decorations) {
2356
- const titleElement = titles.createChild('div');
2357
- titleElement.textContent = decoration.title;
2358
- colors.add(decoration.color);
2358
+ tooltip.push(decoration.title);
2359
2359
  }
2360
- if (this.expanded && !decorations.length) {
2361
- return;
2362
- }
2363
-
2364
- const descendantColors = new Set<string>();
2365
- if (descendantDecorations.length) {
2366
- let element = titles.createChild('div');
2367
- element.textContent = i18nString(UIStrings.children);
2360
+ if (!this.expanded && descendantDecorations.length) {
2361
+ tooltip.push(i18nString(UIStrings.children));
2368
2362
  for (const decoration of descendantDecorations) {
2369
- element = titles.createChild('div');
2370
- element.style.marginLeft = '15px';
2371
- element.textContent = decoration.title;
2372
- descendantColors.add(decoration.color);
2373
- }
2374
- }
2375
-
2376
- let offset = 0;
2377
- processColors.call(this, colors, 'elements-gutter-decoration');
2378
- if (!this.expanded) {
2379
- processColors.call(this, descendantColors, 'elements-gutter-decoration elements-has-decorated-children');
2380
- }
2381
- UI.Tooltip.Tooltip.install(this.decorationsElement, titles.textContent);
2382
- UI.ARIAUtils.setLabel(this.decorationsElement, titles.textContent || '');
2383
-
2384
- function processColors(this: ElementsTreeElement, colors: Set<string>, className: string): void {
2385
- for (const color of colors) {
2386
- const child = this.decorationsElement.createChild('div', className);
2387
- this.decorationsElement.classList.remove('hidden');
2388
- child.style.backgroundColor = color;
2389
- child.style.borderColor = color;
2390
- if (offset) {
2391
- child.style.marginLeft = offset + 'px';
2392
- }
2393
- offset += 3;
2363
+ tooltip.push(decoration.title);
2394
2364
  }
2395
2365
  }
2366
+ this.#decorationsTooltip = tooltip.join('\n');
2367
+ this.performUpdate();
2396
2368
  }
2397
2369
  }
2398
2370
 
@@ -83,6 +83,7 @@ const elementsTreeOutlineByDOMModel = new WeakMap<SDK.DOMModel.DOMModel, Element
83
83
  const populatedTreeElements = new WeakSet<ElementsTreeElement>();
84
84
 
85
85
  export type View = typeof DEFAULT_VIEW;
86
+ export {elementsTreeOutlineStyles};
86
87
 
87
88
  interface ViewInput {
88
89
  omitRootDOMNode: boolean;
@@ -228,26 +228,27 @@ const DEFAULT_VIEW: View = (input, output, target) => {
228
228
  event.preventDefault();
229
229
  }
230
230
  };
231
+ // clang-format off
231
232
  const renderElement = (element: LayoutElement): Lit.LitTemplate => html`<div
232
233
  class="element"
233
- jslog=${VisualLogging.item()}>
234
+ jslog=${VisualLogging.item().track({resize: true})}>
234
235
  <devtools-checkbox
235
236
  data-element="true"
236
237
  class="checkbox-label"
237
238
  .checked=${element.enabled}
238
239
  @change=${(e: Event) => input.onElementToggle(element, e)}
239
- jslog=${VisualLogging.toggle().track({
240
- click: true
241
- })}>
240
+ jslog=${VisualLogging.toggle().track({click: true, resize: true })}>
242
241
  <span
243
242
  class="node-text-container"
244
243
  data-label="true"
245
244
  @mouseenter=${(e: MouseEvent) => input.onMouseEnter(element, e)}
246
245
  @mouseleave=${(e: MouseEvent) => input.onMouseLeave(element, e)}>
247
246
  <devtools-node-text .data=${{
248
- nodeId: element.domId, nodeTitle: element.name, nodeClasses: element.domClasses,
249
- }
250
- }></devtools-node-text>
247
+ nodeId: element.domId,
248
+ nodeTitle: element.name,
249
+ nodeClasses: element.domClasses
250
+ }}>
251
+ </devtools-node-text>
251
252
  </span>
252
253
  </devtools-checkbox>
253
254
  <label
@@ -303,7 +303,7 @@ export class ElementsBreadcrumbs extends HTMLElement {
303
303
  data-crumb="true"
304
304
  >
305
305
  <a href="#" draggable=false class="crumb-link"
306
- jslog=${VisualLogging.item().track({click:true})}
306
+ jslog=${VisualLogging.item().track({click:true, resize:true})}
307
307
  @click=${this.#onCrumbClick(crumb.node)}
308
308
  @mousemove=${this.#onCrumbMouseMove(crumb.node)}
309
309
  @mouseleave=${this.#onCrumbMouseLeave(crumb.node)}
@@ -194,7 +194,8 @@ export class StylePropertyEditor extends HTMLElement {
194
194
  return html`
195
195
  <button title=${title}
196
196
  class=${classes}
197
- jslog=${VisualLogging.item().track({click: true}).context(`${propertyName}-${propertyValue}`)}
197
+ jslog=${
198
+ VisualLogging.item().track({click: true, resize: true}).context(`${propertyName}-${propertyValue}`)}
198
199
  @click=${() => this.#onButtonClick(propertyName, propertyValue, selected)}>
199
200
  <devtools-icon style=${transform} name=${iconInfo.iconName}>
200
201
  </devtools-icon>
@@ -8,6 +8,7 @@ import './ComputedStyleModel.js';
8
8
  import './DOMPath.js';
9
9
  import './ElementsSidebarPane.js';
10
10
  import './ElementsTreeElement.js';
11
+ import './AdoptedStyleSheetTreeElement.js';
11
12
  import './TopLayerContainer.js';
12
13
  import './ElementsTreeOutline.js';
13
14
  import './EventListenersWidget.js';
@@ -30,6 +31,7 @@ import './AccessibilityTreeView.js';
30
31
 
31
32
  import * as AccessibilityTreeUtils from './AccessibilityTreeUtils.js';
32
33
  import * as AccessibilityTreeView from './AccessibilityTreeView.js';
34
+ import * as AdoptedStyleSheetTreeElement from './AdoptedStyleSheetTreeElement.js';
33
35
  import * as ClassesPaneWidget from './ClassesPaneWidget.js';
34
36
  import * as ColorSwatchPopoverIcon from './ColorSwatchPopoverIcon.js';
35
37
  import * as ComputedStyleModel from './ComputedStyleModel.js';
@@ -65,6 +67,7 @@ import * as WebCustomData from './WebCustomData.js';
65
67
  export {
66
68
  AccessibilityTreeUtils,
67
69
  AccessibilityTreeView,
70
+ AdoptedStyleSheetTreeElement,
68
71
  ClassesPaneWidget,
69
72
  ColorSwatchPopoverIcon,
70
73
  ComputedStyleModel,