chrome-devtools-frontend 1.0.1596535 → 1.0.1597448

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 (29) hide show
  1. package/agents/prompts/ui-widgets.md +7 -8
  2. package/docs/ui_engineering.md +10 -11
  3. package/front_end/core/host/InspectorFrontendHostAPI.ts +1 -0
  4. package/front_end/core/host/UserMetrics.ts +12 -0
  5. package/front_end/core/root/Runtime.ts +5 -0
  6. package/front_end/core/sdk/CPUThrottlingManager.ts +9 -12
  7. package/front_end/core/sdk/PageResourceLoader.ts +22 -1
  8. package/front_end/devtools_compatibility.js +2 -1
  9. package/front_end/models/ai_assistance/AiConversation.ts +5 -0
  10. package/front_end/models/ai_assistance/agents/AiAgent.ts +4 -0
  11. package/front_end/models/ai_assistance/agents/StylingAgent.snapshot.txt +24 -0
  12. package/front_end/models/ai_assistance/agents/StylingAgent.ts +289 -12
  13. package/front_end/models/greendev/Prototypes.ts +7 -1
  14. package/front_end/panels/ai_assistance/AiAssistancePanel.ts +35 -33
  15. package/front_end/panels/ai_assistance/PatchWidget.ts +6 -6
  16. package/front_end/panels/ai_assistance/components/ChatMessage.ts +3 -28
  17. package/front_end/panels/ai_assistance/components/ChatView.ts +6 -11
  18. package/front_end/panels/ai_assistance/components/StylingAgentMarkdownRenderer.ts +200 -0
  19. package/front_end/panels/ai_assistance/components/chatMessage.css +0 -8
  20. package/front_end/panels/application/ServiceWorkerCacheViews.ts +1 -1
  21. package/front_end/panels/common/ExtensionServer.ts +15 -0
  22. package/front_end/panels/elements/ElementsTreeElement.ts +55 -47
  23. package/front_end/panels/elements/ElementsTreeOutline.ts +149 -28
  24. package/front_end/panels/lighthouse/LighthousePanel.ts +8 -0
  25. package/front_end/panels/settings/SettingsScreen.ts +3 -2
  26. package/front_end/ui/legacy/UIUtils.ts +5 -5
  27. package/front_end/ui/legacy/Widget.ts +33 -2
  28. package/front_end/ui/visual_logging/KnownContextValues.ts +1 -0
  29. package/package.json +1 -1
@@ -25,6 +25,7 @@ const {
25
25
  repeat,
26
26
  classMap,
27
27
  } = Directives;
28
+ const {widget} = UI.Widget;
28
29
 
29
30
  /*
30
31
  * Strings that don't need to be translated at this time.
@@ -67,9 +68,8 @@ export interface Props {
67
68
  messages: Message[];
68
69
  context: AiAssistanceModel.AiAgent.ConversationContext<unknown>|null;
69
70
  isContextSelected: boolean;
70
- isLoading: boolean;
71
71
  canShowFeedbackForm: boolean;
72
- userInfo: Pick<Host.InspectorFrontendHostAPI.SyncInformation, 'accountImage'|'accountGivenName'>;
72
+ isLoading: boolean;
73
73
  conversationType: AiAssistanceModel.AiHistoryStorage.ConversationType;
74
74
  isReadOnly: boolean;
75
75
  blockedByCrossOrigin: boolean;
@@ -91,7 +91,6 @@ export interface Props {
91
91
  }
92
92
 
93
93
  interface ChatWidgetInput extends Props {
94
- accountGivenName: string;
95
94
  handleScroll: (ev: Event) => void;
96
95
  handleSuggestionClick: (title: string) => void;
97
96
  handleMessageContainerRef: (el: Element|undefined) => void;
@@ -116,12 +115,11 @@ const DEFAULT_VIEW: View = (input, output, target) => {
116
115
  ${input.messages.length > 0 ? html`
117
116
  <div class="messages-container" ${ref(input.handleMessageContainerRef)}>
118
117
  ${repeat(input.messages, message =>
119
- html`<devtools-widget .widgetConfig=${UI.Widget.widgetConfig(ChatMessage, {
118
+ widget(ChatMessage, {
120
119
  message,
121
120
  isLoading: input.isLoading && input.messages.at(-1) === message,
122
121
  isReadOnly: input.isReadOnly,
123
122
  canShowFeedbackForm: input.canShowFeedbackForm,
124
- userInfo: input.userInfo,
125
123
  markdownRenderer: input.markdownRenderer,
126
124
  isLastMessage: input.messages.at(-1) === message,
127
125
  onSuggestionClick: input.handleSuggestionClick,
@@ -130,14 +128,12 @@ const DEFAULT_VIEW: View = (input, output, target) => {
130
128
  walkthrough: {
131
129
  ...input.walkthrough,
132
130
  }
133
- })}></devtools-widget>`
131
+ })
134
132
  )}
135
- ${input.isLoading ? nothing : html`<devtools-widget
136
- .widgetConfig=${UI.Widget.widgetConfig(PatchWidget, {
133
+ ${input.isLoading ? nothing : widget(PatchWidget, {
137
134
  changeSummary: input.changeSummary ?? '',
138
135
  changeManager: input.changeManager,
139
136
  })}
140
- ></devtools-widget>`}
141
137
  </div>
142
138
  ` : html`
143
139
  <div class="empty-state-container">
@@ -149,7 +145,7 @@ const DEFAULT_VIEW: View = (input, output, target) => {
149
145
  </div>
150
146
  ${AiAssistanceModel.AiUtils.isGeminiBranding() ?
151
147
  html`
152
- <h1 class='greeting'>Hello${input.accountGivenName ? `, ${input.accountGivenName}` : ''}</h1>
148
+ <h1 class='greeting'>Hello</h1>
153
149
  <p class='cta'>${lockedString(UIStringsNotTranslate.emptyStateTextGemini)}</p>
154
150
  ` : html`<h1>${lockedString(UIStringsNotTranslate.emptyStateText)}</h1>`
155
151
  }
@@ -354,7 +350,6 @@ export class ChatView extends HTMLElement {
354
350
  this.#view(
355
351
  {
356
352
  ...this.#props,
357
- accountGivenName: this.#props.userInfo.accountGivenName ?? '',
358
353
  handleScroll: this.#handleScroll,
359
354
  handleSuggestionClick: this.#handleSuggestionClick,
360
355
  handleMessageContainerRef: this.#handleMessageContainerRef,
@@ -0,0 +1,200 @@
1
+ // Copyright 2026 The Chromium Authors
2
+ // Use of this source code is governed by a BSD-style license that can be
3
+ // found in the LICENSE file.
4
+
5
+ import * as SDK from '../../../core/sdk/sdk.js';
6
+ import type * as Protocol from '../../../generated/protocol.js';
7
+ import * as Marked from '../../../third_party/marked/marked.js';
8
+ import type * as MarkdownView from '../../../ui/components/markdown_view/MarkdownView.js';
9
+ import * as Lit from '../../../ui/lit/lit.js';
10
+ import * as PanelsCommon from '../../common/common.js';
11
+
12
+ import {MarkdownRendererWithCodeBlock} from './MarkdownRendererWithCodeBlock.js';
13
+
14
+ const {html} = Lit.StaticHtml;
15
+ const {ref, createRef} = Lit.Directives;
16
+
17
+ export class StylingAgentMarkdownRenderer extends MarkdownRendererWithCodeBlock {
18
+ constructor(
19
+ private mainFrameId = '',
20
+ ) {
21
+ super();
22
+ }
23
+
24
+ #renderTableFromJson(data: Array<Record<string, string>>): Lit.LitTemplate|null {
25
+ if (!Array.isArray(data) || data.length === 0 || typeof data[0] !== 'object' || data[0] === null) {
26
+ return null;
27
+ }
28
+
29
+ const headers = Object.keys(data[0]);
30
+ const requiredKeys = ['Problem', 'Element', 'NodeId', 'Details'];
31
+ if (!requiredKeys.every(key => headers.includes(key))) {
32
+ return null; // Not the expected JSON structure
33
+ }
34
+
35
+ const problemIndex = headers.indexOf('Problem');
36
+ if (problemIndex > -1) {
37
+ const problemHeader = headers.splice(problemIndex, 1);
38
+ headers.unshift(...problemHeader);
39
+ }
40
+
41
+ return html`
42
+ <table style="width: 100%;">
43
+ <thead>
44
+ <tr>
45
+ ${headers.map(header => html`<th style="text-align: left;">${header === 'NodeId' ? '' : header}</th>`)}
46
+ </tr>
47
+ </thead>
48
+ <tbody>
49
+ ${data.flatMap(row => {
50
+ return html`
51
+ <tr>
52
+ ${headers.map(header => {
53
+ if (header === 'NodeId') {
54
+ return html`<td>${this.#renderLinkifiedText(row[header])}</td>`;
55
+ }
56
+ if (header === 'Details') {
57
+ // eslint-disable-next-line @devtools/no-a-tags-in-lit
58
+ return html`<td><a href="#" @click=${this.#toggleDetailsRow}>Details</a></td>`;
59
+ }
60
+ return html`<td>${row[header]}</td>`;
61
+ })}
62
+ </tr>
63
+ <tr class="details-row" style="display: none;">
64
+ <td colspan=${headers.length} style="background-color: #f0f0f0; padding: 1em;">
65
+ <devtools-markdown-view .data=${{
66
+ tokens: Marked.Marked.lexer(row['Details']),
67
+ renderer: new StylingAgentMarkdownRenderer(this.mainFrameId),
68
+ } as MarkdownView.MarkdownViewData}></devtools-markdown-view>
69
+ </td>
70
+ </tr>
71
+ `;
72
+ })}
73
+ </tbody>
74
+ </table>
75
+ <br><div>To investigate these problems, please click one of the provided links (above), to set as context, and ask me further questions about the problem.</div>
76
+ `;
77
+ }
78
+
79
+ override templateForToken(token: Marked.Marked.MarkedToken): Lit.LitTemplate|null {
80
+ if (token.type === 'code') {
81
+ try {
82
+ const data = JSON.parse(token.text);
83
+ const table = this.#renderTableFromJson(data);
84
+ if (table) {
85
+ return table;
86
+ }
87
+ } catch {
88
+ // Not a JSON object, fallback to default rendering.
89
+ }
90
+ }
91
+
92
+ if (token.type === 'link' && token.href.startsWith('#')) {
93
+ let nodeId = undefined;
94
+ if (token.href.startsWith('#node-')) {
95
+ nodeId = Number(token.href.replace('#node-', '')) as Protocol.DOM.BackendNodeId;
96
+ } else if (token.href.startsWith('#')) {
97
+ // So often does it ignore requests to prepend nodes with node-, frustratingly.
98
+ nodeId = Number(token.href.replace('#', '')) as Protocol.DOM.BackendNodeId;
99
+ }
100
+
101
+ if (nodeId) {
102
+ const templateRef = createRef();
103
+ void this.#linkifyNode(nodeId, token.text).then(node => {
104
+ if (!templateRef.value || !node) {
105
+ return;
106
+ }
107
+
108
+ templateRef.value.textContent = '';
109
+ templateRef.value.append(node);
110
+ });
111
+ return html`<span ${ref(templateRef)}>${token.text}</span>`;
112
+ }
113
+ }
114
+
115
+ return super.templateForToken(token);
116
+ }
117
+
118
+ #toggleDetailsRow(e: Event): void {
119
+ e.preventDefault();
120
+ const link = e.target as HTMLAnchorElement;
121
+ const currentRow = link.closest('tr');
122
+ if (!currentRow) {
123
+ return;
124
+ }
125
+ const detailsRow = currentRow.nextElementSibling as HTMLTableRowElement | null;
126
+ if (detailsRow?.classList.contains('details-row')) {
127
+ if (detailsRow.style.display === 'none') {
128
+ detailsRow.style.display = 'table-row';
129
+ link.textContent = 'Hide';
130
+ } else {
131
+ detailsRow.style.display = 'none';
132
+ link.textContent = 'Details';
133
+ }
134
+ }
135
+ }
136
+
137
+ #renderLinkifiedText(text: string): Lit.LitTemplate {
138
+ if (text.indexOf(',') === -1) {
139
+ const nodeId = Number(text) as Protocol.DOM.BackendNodeId;
140
+ if (isNaN(nodeId)) {
141
+ // Not a number, return as is.
142
+ return html`${text}`;
143
+ }
144
+ return this.#renderSingleLink(nodeId);
145
+ }
146
+
147
+ // Check for comma separated list.
148
+ const nodeIdsStr = text.split(',').map(s => s.trim()).filter(Boolean);
149
+ return html`${nodeIdsStr.map(idStr => {
150
+ const nodeId = Number(idStr) as Protocol.DOM.BackendNodeId;
151
+ if (isNaN(nodeId)) {
152
+ return html`<div>${idStr}</div>`;
153
+ }
154
+ return html`<div>${this.#renderSingleLink(nodeId)}</div>`;
155
+ })}`;
156
+ }
157
+
158
+ #renderSingleLink(nodeId: Protocol.DOM.BackendNodeId): Lit.LitTemplate {
159
+ const label = `link`;
160
+ const templateRef = createRef();
161
+ void this.#linkifyNode(nodeId, label).then(node => {
162
+ if (!templateRef.value) {
163
+ return;
164
+ }
165
+ templateRef.value.textContent = '';
166
+ if (node) {
167
+ templateRef.value.append(node);
168
+ } else {
169
+ // Fallback to plain text if linkification fails
170
+ templateRef.value.append(document.createTextNode(label));
171
+ }
172
+ });
173
+ // Placeholder for async link
174
+ return html`<span ${ref(templateRef)}>${label}</span>`;
175
+ }
176
+
177
+ async #linkifyNode(backendNodeId: Protocol.DOM.BackendNodeId, label: string): Promise<Node|undefined> {
178
+ if (backendNodeId === undefined) {
179
+ return;
180
+ }
181
+
182
+ const target = SDK.TargetManager.TargetManager.instance().primaryPageTarget();
183
+ const domModel = target?.model(SDK.DOMModel.DOMModel);
184
+ if (!domModel) {
185
+ return undefined;
186
+ }
187
+ const domNodesMap = await domModel.pushNodesByBackendIdsToFrontend(new Set([backendNodeId]));
188
+ const node = domNodesMap?.get(backendNodeId);
189
+ if (!node) {
190
+ return;
191
+ }
192
+
193
+ if (node.frameId() !== this.mainFrameId) {
194
+ return;
195
+ }
196
+
197
+ const linkedNode = PanelsCommon.DOMLinkifier.Linkifier.instance().linkify(node, {textContent: label});
198
+ return linkedNode;
199
+ }
200
+ }
@@ -143,14 +143,6 @@
143
143
  gap: var(--sys-size-4);
144
144
  font: var(--sys-typescale-body4-bold);
145
145
 
146
- img {
147
- border: 0;
148
- border-radius: var(--sys-shape-corner-full);
149
- display: block;
150
- height: var(--sys-size-9);
151
- width: var(--sys-size-9);
152
- }
153
-
154
146
  h2 {
155
147
  font: var(--sys-typescale-body4-bold);
156
148
  }
@@ -178,7 +178,7 @@ export class ServiceWorkerCacheView extends UI.View.SimpleView {
178
178
  this.dataGrid = this.createDataGrid();
179
179
  const dataGridWidget = this.dataGrid.asWidget();
180
180
  this.splitWidget.setSidebarWidget(dataGridWidget);
181
- dataGridWidget.setMinimumSize(0, 250);
181
+ dataGridWidget.setMinimumSize(0, 100);
182
182
  }
183
183
 
184
184
  override wasShown(): void {
@@ -1574,6 +1574,21 @@ export class ExtensionServer extends Common.ObjectWrapper.ObjectWrapper<EventTyp
1574
1574
  return this.status.E_FAILED('Permission denied');
1575
1575
  }
1576
1576
 
1577
+ try {
1578
+ const parsedUrl = new URL(frame.url);
1579
+ let targetType = Host.UserMetrics.ExtensionEvalTarget.WEB_PAGE;
1580
+ if (parsedUrl.protocol === 'chrome-extension:') {
1581
+ if (parsedUrl.origin === securityOrigin) {
1582
+ targetType = Host.UserMetrics.ExtensionEvalTarget.SAME_EXTENSION;
1583
+ } else {
1584
+ targetType = Host.UserMetrics.ExtensionEvalTarget.OTHER_EXTENSION;
1585
+ }
1586
+ }
1587
+ Host.userMetrics.extensionEvalTarget(targetType);
1588
+ } catch {
1589
+ // Ignore invalid URLs.
1590
+ }
1591
+
1577
1592
  void context
1578
1593
  .evaluate(
1579
1594
  {
@@ -284,8 +284,8 @@ const UIStrings = {
284
284
  */
285
285
  startAChat: 'Start a chat',
286
286
  /**
287
-    * @description Label of an adorner next to the html node in the Elements panel.
288
-    */
287
+ * @description Label of an adorner next to the html node in the Elements panel.
288
+ */
289
289
  viewSourceCode: 'View source code',
290
290
  /**
291
291
  * @description Context menu item in Elements panel to assess visibility of an element via AI.
@@ -436,7 +436,6 @@ export interface ViewInput {
436
436
  showAiButton: boolean;
437
437
  aiButtonTitle?: string;
438
438
  onAiButtonClick: (e: Event) => void;
439
-
440
439
  decorations: Decoration[];
441
440
  descendantDecorations: Decoration[];
442
441
  decorationsTooltip: string;
@@ -806,16 +805,13 @@ function renderAttribute(
806
805
 
807
806
  // clang-format off
808
807
  return html`<span class="webkit-html-attribute" jslog=${jslog}><span class="webkit-html-attribute-name"
809
- ${animateOn(Boolean(updateRecord?.isAttributeModified(name) && !hasText), DOM_UPDATE_ANIMATION_CLASS_NAME)} ${
810
- relationRefDirective}>${name}</span>${
811
- hasText ? html`=\u200B"<span class="webkit-html-attribute-value" ${
812
- animateOn(
813
- Boolean(updateRecord?.isAttributeModified(name) && hasText),
814
- DOM_UPDATE_ANIMATION_CLASS_NAME)} ${valueRelationRefDirective} ${withEntitiesRef}>
808
+ ${animateOn(Boolean(updateRecord?.isAttributeModified(name) && !hasText), DOM_UPDATE_ANIMATION_CLASS_NAME)} ${relationRefDirective}>${name}</span>${hasText ? html`=\u200B"<span class="webkit-html-attribute-value" ${animateOn(
809
+ Boolean(updateRecord?.isAttributeModified(name) && hasText),
810
+ DOM_UPDATE_ANIMATION_CLASS_NAME)} ${valueRelationRefDirective} ${withEntitiesRef}>
815
811
  ${valueType === ValueType.SRC ? renderLinkifiedValue(value, node) : nothing}
816
812
  ${valueType === ValueType.SRCSET ? renderLinkifiedSrcset(Common.Srcset.parseSrcset(value), node) : nothing}
817
813
  </span>"` :
818
- nothing}</span>`;
814
+ nothing}</span>`;
819
815
  // clang-format on
820
816
  }
821
817
 
@@ -868,15 +864,15 @@ export const DEFAULT_VIEW = (input: ViewInput, output: ViewOutput, target: HTMLE
868
864
  render(html`
869
865
  <div ${ref(el => { output.contentElement = el as HTMLElement; })}>
870
866
  ${input.node ? html`<span class="highlight">${renderTitle(
871
- input.node,
872
- input.isClosingTag,
873
- input.expanded,
874
- input.isExpandable,
875
- input.isXMLMimeType,
876
- input.updateRecord,
877
- input.onHighlightSearchResults,
878
- input.onExpand,
879
- )}</span>` : nothing}
867
+ input.node,
868
+ input.isClosingTag,
869
+ input.expanded,
870
+ input.isExpandable,
871
+ input.isXMLMimeType,
872
+ input.updateRecord,
873
+ input.onHighlightSearchResults,
874
+ input.onExpand,
875
+ )}</span>` : nothing}
880
876
  ${input.isHovered || input.isSelected ? html`
881
877
  <div class="selection fill" style=${`margin-left: ${-input.indent}px`}></div>
882
878
  ` : nothing}
@@ -913,7 +909,7 @@ export const DEFAULT_VIEW = (input: ViewInput, output: ViewOutput, target: HTMLE
913
909
  toggleable=true
914
910
  tabindex=0
915
911
  .name=${ElementsComponents.AdornerManager.RegisteredAdorners.CONTAINER}
916
- jslog=${VisualLogging.adorner(ElementsComponents.AdornerManager.RegisteredAdorners.CONTAINER).track({click: true})}
912
+ jslog=${VisualLogging.adorner(ElementsComponents.AdornerManager.RegisteredAdorners.CONTAINER).track({ click: true })}
917
913
  active=${input.containerAdornerActive}
918
914
  aria-label=${input.containerAdornerActive ? i18nString(UIStrings.enableContainer) : i18nString(UIStrings.disableContainer)}
919
915
  @click=${input.onContainerAdornerClick}
@@ -930,7 +926,7 @@ export const DEFAULT_VIEW = (input: ViewInput, output: ViewOutput, target: HTMLE
930
926
  toggleable=true
931
927
  tabindex=0
932
928
  .name=${ElementsComponents.AdornerManager.RegisteredAdorners.FLEX}
933
- jslog=${VisualLogging.adorner(ElementsComponents.AdornerManager.RegisteredAdorners.FLEX).track({click: true})}
929
+ jslog=${VisualLogging.adorner(ElementsComponents.AdornerManager.RegisteredAdorners.FLEX).track({ click: true })}
934
930
  active=${input.flexAdornerActive}
935
931
  aria-label=${input.flexAdornerActive ? i18nString(UIStrings.disableFlexMode) : i18nString(UIStrings.enableFlexMode)}
936
932
  @click=${input.onFlexAdornerClick}
@@ -944,7 +940,7 @@ export const DEFAULT_VIEW = (input: ViewInput, output: ViewOutput, target: HTMLE
944
940
  toggleable=true
945
941
  tabindex=0
946
942
  .name=${input.isSubgrid ? ElementsComponents.AdornerManager.RegisteredAdorners.SUBGRID : ElementsComponents.AdornerManager.RegisteredAdorners.GRID}
947
- jslog=${VisualLogging.adorner(input.isSubgrid ? ElementsComponents.AdornerManager.RegisteredAdorners.SUBGRID : ElementsComponents.AdornerManager.RegisteredAdorners.GRID).track({click: true})}
943
+ jslog=${VisualLogging.adorner(input.isSubgrid ? ElementsComponents.AdornerManager.RegisteredAdorners.SUBGRID : ElementsComponents.AdornerManager.RegisteredAdorners.GRID).track({ click: true })}
948
944
  active=${input.gridAdornerActive}
949
945
  aria-label=${input.gridAdornerActive ? i18nString(UIStrings.disableGridMode) : i18nString(UIStrings.enableGridMode)}
950
946
  @click=${input.onGridAdornerClick}
@@ -958,7 +954,7 @@ export const DEFAULT_VIEW = (input: ViewInput, output: ViewOutput, target: HTMLE
958
954
  toggleable=true
959
955
  tabindex=0
960
956
  .name=${ElementsComponents.AdornerManager.RegisteredAdorners.GRID_LANES}
961
- jslog=${VisualLogging.adorner(ElementsComponents.AdornerManager.RegisteredAdorners.GRID_LANES).track({click: true})}
957
+ jslog=${VisualLogging.adorner(ElementsComponents.AdornerManager.RegisteredAdorners.GRID_LANES).track({ click: true })}
962
958
  active=${input.gridAdornerActive}
963
959
  aria-label=${input.gridAdornerActive ? i18nString(UIStrings.disableGridLanesMode) : i18nString(UIStrings.enableGridLanesMode)}
964
960
  @click=${input.onGridAdornerClick}
@@ -971,7 +967,7 @@ export const DEFAULT_VIEW = (input: ViewInput, output: ViewOutput, target: HTMLE
971
967
  role=button
972
968
  tabindex=0
973
969
  .name=${ElementsComponents.AdornerManager.RegisteredAdorners.MEDIA}
974
- jslog=${VisualLogging.adorner(ElementsComponents.AdornerManager.RegisteredAdorners.MEDIA).track({click: true})}
970
+ jslog=${VisualLogging.adorner(ElementsComponents.AdornerManager.RegisteredAdorners.MEDIA).track({ click: true })}
975
971
  aria-label=${i18nString(UIStrings.openMediaPanel)}
976
972
  @click=${input.onMediaAdornerClick}
977
973
  @keydown=${handleAdornerKeydown(input.onMediaAdornerClick)}
@@ -986,7 +982,7 @@ export const DEFAULT_VIEW = (input: ViewInput, output: ViewOutput, target: HTMLE
986
982
  toggleable=true
987
983
  tabindex=0
988
984
  .name=${ElementsComponents.AdornerManager.RegisteredAdorners.POPOVER}
989
- jslog=${VisualLogging.adorner(ElementsComponents.AdornerManager.RegisteredAdorners.POPOVER).track({click: true})}
985
+ jslog=${VisualLogging.adorner(ElementsComponents.AdornerManager.RegisteredAdorners.POPOVER).track({ click: true })}
990
986
  active=${input.popoverAdornerActive}
991
987
  aria-label=${input.popoverAdornerActive ? i18nString(UIStrings.stopForceOpenPopover) : i18nString(UIStrings.forceOpenPopover)}
992
988
  @click=${input.onPopoverAdornerClick}
@@ -999,7 +995,7 @@ export const DEFAULT_VIEW = (input: ViewInput, output: ViewOutput, target: HTMLE
999
995
  role=button
1000
996
  tabindex=0
1001
997
  .name=${ElementsComponents.AdornerManager.RegisteredAdorners.TOP_LAYER}
1002
- jslog=${VisualLogging.adorner(ElementsComponents.AdornerManager.RegisteredAdorners.TOP_LAYER).track({click: true})}
998
+ jslog=${VisualLogging.adorner(ElementsComponents.AdornerManager.RegisteredAdorners.TOP_LAYER).track({ click: true })}
1003
999
  aria-label=${i18nString(UIStrings.reveal)}
1004
1000
  @click=${input.onTopLayerAdornerClick}
1005
1001
  @keydown=${handleAdornerKeydown(input.onTopLayerAdornerClick)}
@@ -1011,7 +1007,7 @@ export const DEFAULT_VIEW = (input: ViewInput, output: ViewOutput, target: HTMLE
1011
1007
  ${input.showStartingStyleAdorner ? html`<devtools-adorner
1012
1008
  class="starting-style"
1013
1009
  .name=${ElementsComponents.AdornerManager.RegisteredAdorners.STARTING_STYLE}
1014
- jslog=${VisualLogging.adorner(ElementsComponents.AdornerManager.RegisteredAdorners.STARTING_STYLE).track({click: true})}
1010
+ jslog=${VisualLogging.adorner(ElementsComponents.AdornerManager.RegisteredAdorners.STARTING_STYLE).track({ click: true })}
1015
1011
  active=${input.startingStyleAdornerActive}
1016
1012
  toggleable=true
1017
1013
  aria-label=${input.startingStyleAdornerActive ? i18nString(UIStrings.disableStartingStyle) : i18nString(UIStrings.enableStartingStyle)}
@@ -1023,7 +1019,7 @@ export const DEFAULT_VIEW = (input: ViewInput, output: ViewOutput, target: HTMLE
1023
1019
  ${input.showScrollAdorner ? html`<devtools-adorner
1024
1020
  class="scroll"
1025
1021
  .name=${ElementsComponents.AdornerManager.RegisteredAdorners.SCROLL}
1026
- jslog=${VisualLogging.adorner(ElementsComponents.AdornerManager.RegisteredAdorners.SCROLL).track({click: true})}
1022
+ jslog=${VisualLogging.adorner(ElementsComponents.AdornerManager.RegisteredAdorners.SCROLL).track({ click: true })}
1027
1023
  aria-label=${i18nString(UIStrings.elementHasScrollableOverflow)}
1028
1024
  ${adornerRef()}>
1029
1025
  <span>${ElementsComponents.AdornerManager.RegisteredAdorners.SCROLL}</span>
@@ -1033,7 +1029,7 @@ export const DEFAULT_VIEW = (input: ViewInput, output: ViewOutput, target: HTMLE
1033
1029
  role=button
1034
1030
  tabindex=0
1035
1031
  .name=${ElementsComponents.AdornerManager.RegisteredAdorners.SLOT}
1036
- jslog=${VisualLogging.adorner(ElementsComponents.AdornerManager.RegisteredAdorners.SLOT).track({click: true})}
1032
+ jslog=${VisualLogging.adorner(ElementsComponents.AdornerManager.RegisteredAdorners.SLOT).track({ click: true })}
1037
1033
  @click=${input.onSlotAdornerClick}
1038
1034
  @mousedown=${(e: Event) => e.stopPropagation()}
1039
1035
  ${adornerRef()}>
@@ -1045,7 +1041,7 @@ export const DEFAULT_VIEW = (input: ViewInput, output: ViewOutput, target: HTMLE
1045
1041
  ${input.showScrollSnapAdorner ? html`<devtools-adorner
1046
1042
  class="scroll-snap"
1047
1043
  .name=${ElementsComponents.AdornerManager.RegisteredAdorners.SCROLL_SNAP}
1048
- jslog=${VisualLogging.adorner(ElementsComponents.AdornerManager.RegisteredAdorners.SCROLL_SNAP).track({click: true})}
1044
+ jslog=${VisualLogging.adorner(ElementsComponents.AdornerManager.RegisteredAdorners.SCROLL_SNAP).track({ click: true })}
1049
1045
  active=${input.scrollSnapAdornerActive}
1050
1046
  toggleable=true
1051
1047
  aria-label=${input.scrollSnapAdornerActive ? i18nString(UIStrings.disableScrollSnap) : i18nString(UIStrings.enableScrollSnap)}
@@ -1056,7 +1052,7 @@ export const DEFAULT_VIEW = (input: ViewInput, output: ViewOutput, target: HTMLE
1056
1052
  </devtools-adorner>` : nothing}
1057
1053
  </div>`: nothing}
1058
1054
  ${input.isSelected ? html`
1059
- <span class="selected-hint" title=${i18nString(UIStrings.useSInTheConsoleToReferToThis, {PH1: '$0'})} aria-hidden="true"></span>
1055
+ <span class="selected-hint" title=${i18nString(UIStrings.useSInTheConsoleToReferToThis, { PH1: '$0' })} aria-hidden="true"></span>
1060
1056
  ` : nothing}
1061
1057
  ${input.showAiButton ? html`
1062
1058
  <span class="ai-button-container">
@@ -1261,7 +1257,8 @@ export class ElementsTreeElement extends UI.TreeOutline.TreeElement {
1261
1257
  showSlotAdorner: Boolean(this.nodeInternal.assignedSlot) && !this.isClosingTag(),
1262
1258
  showStartingStyleAdorner: this.nodeInternal.affectedByStartingStyles() && !this.isClosingTag(),
1263
1259
  startingStyleAdornerActive: this.#startingStyleAdornerActive,
1264
- onStartingStyleAdornerClick: (event: Event) => this.#onStartingStyleAdornerClick(event),
1260
+ onStartingStyleAdornerClick:
1261
+ this.treeOutline?.disableEdits ? () => {} : (event: Event) => this.#onStartingStyleAdornerClick(event),
1265
1262
  onSlotAdornerClick: () => {
1266
1263
  if (this.nodeInternal.assignedSlot) {
1267
1264
  const deferredNode = this.nodeInternal.assignedSlot.deferredNode;
@@ -1271,25 +1268,32 @@ export class ElementsTreeElement extends UI.TreeOutline.TreeElement {
1271
1268
  }
1272
1269
  },
1273
1270
  topLayerIndex: this.node().topLayerIndex(),
1274
- onViewSourceAdornerClick: this.revealHTMLInSources.bind(this),
1271
+ onViewSourceAdornerClick: this.treeOutline?.disableEdits ? () => {} : this.revealHTMLInSources.bind(this),
1275
1272
  onGutterClick: this.showContextMenu.bind(this),
1276
- onContainerAdornerClick: (event: Event) => this.#onContainerAdornerClick(event),
1277
- onFlexAdornerClick: (event: Event) => this.#onFlexAdornerClick(event),
1278
- onGridAdornerClick: (event: Event) => this.#onGridAdornerClick(event),
1279
- onMediaAdornerClick: (event: Event) => this.#onMediaAdornerClick(event),
1280
- onPopoverAdornerClick: (event: Event) => this.#onPopoverAdornerClick(event),
1281
- onScrollSnapAdornerClick: (event: Event) => this.#onScrollSnapAdornerClick(event),
1282
- onTopLayerAdornerClick: () => {
1283
- if (!this.treeOutline) {
1284
- return;
1285
- }
1286
- this.treeOutline.revealInTopLayer(this.node());
1287
- },
1273
+ onContainerAdornerClick:
1274
+ this.treeOutline?.disableEdits ? () => {} : (event: Event) => this.#onContainerAdornerClick(event),
1275
+ onFlexAdornerClick: this.treeOutline?.disableEdits ? () => {} :
1276
+ (event: Event) => this.#onFlexAdornerClick(event),
1277
+ onGridAdornerClick: this.treeOutline?.disableEdits ? () => {} :
1278
+ (event: Event) => this.#onGridAdornerClick(event),
1279
+ onMediaAdornerClick: this.treeOutline?.disableEdits ? () => {} :
1280
+ (event: Event) => this.#onMediaAdornerClick(event),
1281
+ onPopoverAdornerClick: this.treeOutline?.disableEdits ? () => {} :
1282
+ (event: Event) => this.#onPopoverAdornerClick(event),
1283
+ onScrollSnapAdornerClick:
1284
+ this.treeOutline?.disableEdits ? () => {} : (event: Event) => this.#onScrollSnapAdornerClick(event),
1285
+ onTopLayerAdornerClick: this.treeOutline?.disableEdits ? () => {} :
1286
+ () => {
1287
+ if (!this.treeOutline) {
1288
+ return;
1289
+ }
1290
+ this.treeOutline.revealInTopLayer(this.node());
1291
+ },
1288
1292
  isHovered: this.#hovered,
1289
1293
  isSelected: this.selected,
1290
1294
  showAiButton: Boolean(this.#hovered || this.selected) && this.node().nodeType() === Node.ELEMENT_NODE &&
1291
- UI.ActionRegistry.ActionRegistry.instance().hasAction('freestyler.elements-floating-button'),
1292
- aiButtonTitle: UI.ActionRegistry.ActionRegistry.instance().hasAction('freestyler.elements-floating-button') ?
1295
+ this.isAiButtonEnabled() && (this.treeOutline as ElementsTreeOutline)?.showAIButton,
1296
+ aiButtonTitle: this.isAiButtonEnabled() ?
1293
1297
  UI.ActionRegistry.ActionRegistry.instance().getAction('freestyler.elements-floating-button').title() :
1294
1298
  undefined,
1295
1299
  onAiButtonClick: (ev: Event) => {
@@ -1893,6 +1897,10 @@ export class ElementsTreeElement extends UI.TreeOutline.TreeElement {
1893
1897
  {jslogContext: 'scroll-into-view'});
1894
1898
  }
1895
1899
 
1900
+ private isAiButtonEnabled(): boolean {
1901
+ return UI.ActionRegistry.ActionRegistry.instance().hasAction('freestyler.elements-floating-button');
1902
+ }
1903
+
1896
1904
  async populateTextContextMenu(contextMenu: UI.ContextMenu.ContextMenu, textNode: Element): Promise<void> {
1897
1905
  if (!this.editing) {
1898
1906
  contextMenu.editSection().appendItem(