chrome-devtools-frontend 1.0.1642246 → 1.0.1642899

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 (59) hide show
  1. package/SECURITY.md +1 -0
  2. package/front_end/core/host/UserMetrics.ts +2 -1
  3. package/front_end/core/sdk/CSSMatchedStyles.ts +55 -26
  4. package/front_end/core/sdk/CSSRule.ts +1 -0
  5. package/front_end/core/sdk/DebuggerModel.ts +5 -0
  6. package/front_end/entrypoints/greendev_floaty/FloatyEntrypoint.ts +4 -3
  7. package/front_end/entrypoints/greendev_floaty/greendev_floaty.ts +4 -3
  8. package/front_end/entrypoints/heap_snapshot_worker/HeapSnapshot.ts +11 -0
  9. package/front_end/models/ai_assistance/AiAgent2.ts +80 -16
  10. package/front_end/models/ai_assistance/AiConversation.ts +3 -2
  11. package/front_end/models/ai_assistance/README.md +8 -0
  12. package/front_end/models/ai_assistance/agents/AccessibilityAgent.ts +50 -39
  13. package/front_end/models/ai_assistance/agents/AiAgent.ts +17 -6
  14. package/front_end/models/ai_assistance/agents/ContextSelectionAgent.ts +2 -2
  15. package/front_end/models/ai_assistance/agents/PerformanceAgent.ts +195 -147
  16. package/front_end/models/ai_assistance/agents/StylingAgent.snapshot.txt +1 -26
  17. package/front_end/models/ai_assistance/agents/StylingAgent.ts +24 -309
  18. package/front_end/models/ai_assistance/ai_assistance.ts +8 -0
  19. package/front_end/models/ai_assistance/contexts/DOMNodeContext.snapshot.txt +51 -0
  20. package/front_end/models/ai_assistance/contexts/DOMNodeContext.ts +200 -0
  21. package/front_end/models/ai_assistance/data_formatters/NetworkRequestFormatter.ts +13 -12
  22. package/front_end/models/ai_assistance/skills/styling.md +36 -2
  23. package/front_end/models/ai_assistance/tools/GetStyles.ts +137 -0
  24. package/front_end/models/ai_assistance/tools/Tool.ts +55 -0
  25. package/front_end/models/ai_assistance/tools/ToolRegistry.ts +34 -0
  26. package/front_end/models/heap_snapshot/HeapSnapshotProxy.ts +4 -0
  27. package/front_end/models/javascript_metadata/NativeFunctions.js +16 -0
  28. package/front_end/models/lighthouse/LighthouseReporterTypes.ts +5 -0
  29. package/front_end/models/live-metrics/LiveMetrics.ts +24 -13
  30. package/front_end/models/stack_trace/DetailedErrorStackParser.ts +2 -2
  31. package/front_end/models/stack_trace/StackTrace.ts +4 -1
  32. package/front_end/models/stack_trace/StackTraceImpl.ts +9 -2
  33. package/front_end/models/stack_trace/StackTraceModel.ts +17 -4
  34. package/front_end/models/stack_trace/Trie.ts +1 -1
  35. package/front_end/panels/ai_assistance/AiAssistancePanel.ts +19 -15
  36. package/front_end/panels/ai_assistance/ai_assistance-meta.ts +16 -0
  37. package/front_end/panels/ai_assistance/components/ChatInput.ts +2 -2
  38. package/front_end/panels/application/DOMStorageItemsView.ts +4 -0
  39. package/front_end/panels/application/KeyValueStorageItemsView.ts +39 -7
  40. package/front_end/panels/common/ExtensionServer.ts +26 -15
  41. package/front_end/panels/console/SymbolizedErrorWidget.ts +96 -2
  42. package/front_end/panels/console/symbolizedErrorWidget.css +48 -0
  43. package/front_end/panels/elements/StandaloneStylesContainer.ts +1 -1
  44. package/front_end/panels/elements/StylePropertiesSection.ts +8 -0
  45. package/front_end/panels/elements/StylePropertyHighlighter.ts +4 -2
  46. package/front_end/panels/elements/StylePropertyTreeElement.ts +6 -5
  47. package/front_end/panels/elements/StylesContainer.ts +1 -1
  48. package/front_end/panels/elements/StylesSidebarPane.ts +4 -4
  49. package/front_end/panels/layer_viewer/PaintProfilerView.ts +106 -132
  50. package/front_end/panels/lighthouse/LighthousePanel.ts +4 -3
  51. package/front_end/panels/network/NetworkLogView.ts +3 -0
  52. package/front_end/panels/network/networkLogView.css +0 -15
  53. package/front_end/third_party/chromium/README.chromium +1 -1
  54. package/front_end/ui/legacy/components/cookie_table/CookiesTable.ts +36 -3
  55. package/front_end/ui/legacy/components/data_grid/dataGridAiButton.css +20 -0
  56. package/front_end/ui/legacy/components/utils/Linkifier.ts +19 -4
  57. package/front_end/ui/visual_logging/KnownContextValues.ts +1 -0
  58. package/front_end/ui/visual_logging/LoggingDriver.ts +13 -0
  59. package/package.json +1 -1
@@ -362,23 +362,22 @@ function getMarkdownRenderer(conversation?: AiAssistanceModel.AiConversation.AiC
362
362
  const context = conversation?.selectedContext;
363
363
 
364
364
  if (context instanceof AiAssistanceModel.PerformanceAgent.PerformanceTraceContext) {
365
- if (!context.external) {
366
- const focus = context.getItem();
367
- return new PerformanceAgentMarkdownRenderer(
368
- focus.parsedTrace.data.Meta.mainFrameId, focus.lookupEvent.bind(focus));
369
- }
370
- } else if (conversation?.type === AiAssistanceModel.AiHistoryStorage.ConversationType.PERFORMANCE) {
365
+ const focus = context.getItem();
366
+ return new PerformanceAgentMarkdownRenderer(focus.parsedTrace.data.Meta.mainFrameId, focus.lookupEvent.bind(focus));
367
+ }
368
+ if (conversation?.type === AiAssistanceModel.AiHistoryStorage.ConversationType.PERFORMANCE) {
371
369
  // Handle historical conversations (can't linkify anything).
372
370
  return new PerformanceAgentMarkdownRenderer();
373
- } else if (
374
- Greendev.Prototypes.instance().isEnabled('emulationCapabilities') &&
371
+ }
372
+ if (Greendev.Prototypes.instance().isEnabled('emulationCapabilities') &&
375
373
  conversation?.type === AiAssistanceModel.AiHistoryStorage.ConversationType.STYLING &&
376
374
  SDK.TargetManager.TargetManager.instance().primaryPageTarget()?.model(SDK.DOMModel.DOMModel)) {
377
375
  const domModel = SDK.TargetManager.TargetManager.instance().primaryPageTarget()?.model(SDK.DOMModel.DOMModel);
378
376
  const resourceTreeModel = domModel?.target().model(SDK.ResourceTreeModel.ResourceTreeModel);
379
377
  const mainFrameId = resourceTreeModel?.mainFrame?.id;
380
378
  return new StylingAgentMarkdownRenderer(mainFrameId);
381
- } else if (conversation?.type === AiAssistanceModel.AiHistoryStorage.ConversationType.ACCESSIBILITY) {
379
+ }
380
+ if (conversation?.type === AiAssistanceModel.AiHistoryStorage.ConversationType.ACCESSIBILITY) {
382
381
  const domModel = SDK.TargetManager.TargetManager.instance().primaryPageTarget()?.model(SDK.DOMModel.DOMModel);
383
382
  const mainDocumentURL = domModel?.existingDocument()?.documentURL;
384
383
  return new AccessibilityAgentMarkdownRenderer(mainDocumentURL);
@@ -570,11 +569,11 @@ function defaultView(input: ViewInput, output: PanelViewOutput, target: HTMLElem
570
569
  // clang-format on
571
570
  }
572
571
 
573
- function createNodeContext(node: SDK.DOMModel.DOMNode|null): AiAssistanceModel.StylingAgent.NodeContext|null {
572
+ function createDOMNodeContext(node: SDK.DOMModel.DOMNode|null): AiAssistanceModel.DOMNodeContext.DOMNodeContext|null {
574
573
  if (!node) {
575
574
  return null;
576
575
  }
577
- return new AiAssistanceModel.StylingAgent.NodeContext(node);
576
+ return new AiAssistanceModel.DOMNodeContext.DOMNodeContext(node);
578
577
  }
579
578
 
580
579
  function createFileContext(file: Workspace.UISourceCode.UISourceCode|null): AiAssistanceModel.FileAgent.FileContext|
@@ -668,7 +667,7 @@ export class AiAssistancePanel extends UI.Panel.Panel {
668
667
  #conversation?: AiAssistanceModel.AiConversation.AiConversation;
669
668
 
670
669
  #selectedFile: AiAssistanceModel.FileAgent.FileContext|null = null;
671
- #selectedElement: AiAssistanceModel.StylingAgent.NodeContext|null = null;
670
+ #selectedElement: AiAssistanceModel.DOMNodeContext.DOMNodeContext|null = null;
672
671
  #selectedPerformanceTrace: AiAssistanceModel.PerformanceAgent.PerformanceTraceContext|null = null;
673
672
  #selectedRequest: AiAssistanceModel.NetworkAgent.RequestContext|null = null;
674
673
 
@@ -1117,7 +1116,7 @@ export class AiAssistancePanel extends UI.Panel.Panel {
1117
1116
  this.#viewOutput.chatView?.focusTextInput();
1118
1117
  void this.#handleAidaAvailabilityChange();
1119
1118
  this.#selectedElement =
1120
- createNodeContext(selectedElementFilter(UI.Context.Context.instance().flavor(SDK.DOMModel.DOMNode)));
1119
+ createDOMNodeContext(selectedElementFilter(UI.Context.Context.instance().flavor(SDK.DOMModel.DOMNode)));
1121
1120
  this.#selectedRequest =
1122
1121
  createRequestContext(UI.Context.Context.instance().flavor(SDK.NetworkRequest.NetworkRequest));
1123
1122
  this.#selectedPerformanceTrace =
@@ -1225,7 +1224,7 @@ export class AiAssistancePanel extends UI.Panel.Panel {
1225
1224
  return;
1226
1225
  }
1227
1226
 
1228
- this.#selectedElement = createNodeContext(selectedElementFilter(ev.data));
1227
+ this.#selectedElement = createDOMNodeContext(selectedElementFilter(ev.data));
1229
1228
  this.#updateConversationState(this.#conversation);
1230
1229
  };
1231
1230
 
@@ -1576,6 +1575,11 @@ export class AiAssistancePanel extends UI.Panel.Panel {
1576
1575
  targetConversationType = AiAssistanceModel.AiHistoryStorage.ConversationType.FILE;
1577
1576
  break;
1578
1577
  }
1578
+ case 'ai-assistance.storage-floating-button': {
1579
+ Host.userMetrics.actionTaken(Host.UserMetrics.Action.AiAssistanceOpenedFromStoragePanelFloatingButton);
1580
+ targetConversationType = AiAssistanceModel.AiHistoryStorage.ConversationType.STORAGE;
1581
+ break;
1582
+ }
1579
1583
  }
1580
1584
 
1581
1585
  if (!targetConversationType) {
@@ -1727,7 +1731,7 @@ export class AiAssistancePanel extends UI.Panel.Panel {
1727
1731
  #handleConversationContextChange = (data: unknown): void => {
1728
1732
  if (data instanceof AiAssistanceModel.FileAgent.FileContext) {
1729
1733
  this.#selectedFile = data;
1730
- } else if (data instanceof AiAssistanceModel.StylingAgent.NodeContext) {
1734
+ } else if (data instanceof AiAssistanceModel.DOMNodeContext.DOMNodeContext) {
1731
1735
  this.#selectedElement = data;
1732
1736
  } else if (data instanceof AiAssistanceModel.NetworkAgent.RequestContext) {
1733
1737
  this.#selectedRequest = data;
@@ -297,3 +297,19 @@ UI.ActionRegistration.registerActionExtension({
297
297
  },
298
298
  condition: config => isFileAgentFeatureAvailable(config) && !isPolicyRestricted(config) && !isGeoRestricted(config),
299
299
  });
300
+
301
+ UI.ActionRegistration.registerActionExtension({
302
+ actionId: 'ai-assistance.storage-floating-button',
303
+ contextTypes(): [] {
304
+ return [];
305
+ },
306
+ category: UI.ActionRegistration.ActionCategory.GLOBAL,
307
+ title: i18nAiBrandedString(UIStrings.debugWithGemini, UIStrings.debugWithAi),
308
+ configurableBindings: false,
309
+ async loadActionDelegate() {
310
+ const AiAssistance = await loadAiAssistanceModule();
311
+ return new AiAssistance.ActionDelegate();
312
+ },
313
+ condition: config =>
314
+ isStorageAgentFeatureAvailable(config) && !isPolicyRestricted(config) && !isGeoRestricted(config),
315
+ });
@@ -174,7 +174,7 @@ function getContextRemoveLabel(context: AiAssistanceModel.AiAgent.ConversationCo
174
174
  if (context instanceof AiAssistanceModel.FileAgent.FileContext) {
175
175
  return lockedString(UIStringsNotTranslate.removeContextFile);
176
176
  }
177
- if (context instanceof AiAssistanceModel.StylingAgent.NodeContext) {
177
+ if (context instanceof AiAssistanceModel.DOMNodeContext.DOMNodeContext) {
178
178
  return lockedString(UIStringsNotTranslate.removeContextElement);
179
179
  }
180
180
  if (context instanceof AiAssistanceModel.NetworkAgent.RequestContext) {
@@ -336,7 +336,7 @@ export const DEFAULT_VIEW = (input: ViewInput, _output: ViewOutput, target: HTML
336
336
  })}
337
337
  >
338
338
  ${
339
- input.context instanceof AiAssistanceModel.StylingAgent.NodeContext ?
339
+ input.context instanceof AiAssistanceModel.DOMNodeContext.DOMNodeContext ?
340
340
  html`
341
341
  <devtools-widget
342
342
  class="title"
@@ -205,6 +205,10 @@ export class DOMStorageItemsView extends KeyValueStorageItemsView {
205
205
  UI.Context.Context.instance().setFlavor(AiAssistanceModel.StorageItem.StorageItem, storageItem);
206
206
  }
207
207
 
208
+ protected override isAiButtonEnabled(): boolean {
209
+ return UI.ActionRegistry.ActionRegistry.instance().hasAction('ai-assistance.storage-floating-button');
210
+ }
211
+
208
212
  protected removeItem(key: string): void {
209
213
  this.domStorage?.removeItem(key);
210
214
  }
@@ -28,8 +28,13 @@
28
28
  * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
29
29
  */
30
30
  /* eslint no-return-assign: "off" */
31
+ import '../../ui/components/buttons/buttons.js';
32
+
31
33
  import * as i18n from '../../core/i18n/i18n.js';
34
+ import * as AIAssistance from '../../models/ai_assistance/ai_assistance.js';
32
35
  import * as Geometry from '../../models/geometry/geometry.js';
36
+ // eslint-disable-next-line @devtools/es-modules-import
37
+ import dataGridAiButtonStyles from '../../ui/legacy/components/data_grid/dataGridAiButton.css.js';
33
38
  import * as UI from '../../ui/legacy/legacy.js';
34
39
  import {Directives as LitDirectives, html, nothing, render} from '../../ui/lit/lit.js';
35
40
  import * as VisualLogging from '../../ui/visual_logging/visual_logging.js';
@@ -37,11 +42,13 @@ import * as VisualLogging from '../../ui/visual_logging/visual_logging.js';
37
42
  import * as ApplicationComponents from './components/components.js';
38
43
  import {StorageItemsToolbar} from './StorageItemsToolbar.js';
39
44
 
45
+ const STORAGE_FLOATING_BUTTON_ACTION_ID = 'ai-assistance.storage-floating-button';
46
+
40
47
  const {ARIAUtils} = UI;
41
48
  const {EmptyWidget} = UI.EmptyWidget;
42
49
  const {VBox, widget} = UI.Widget;
43
50
  const {Size} = Geometry;
44
- const {repeat} = LitDirectives;
51
+ const {repeat, ifDefined} = LitDirectives;
45
52
 
46
53
  type Widget = UI.Widget.Widget;
47
54
  type VBox = UI.Widget.VBox;
@@ -87,6 +94,9 @@ export interface ViewInput {
87
94
  onDeleteAll: () => void;
88
95
  jslog?: string;
89
96
  classes?: string[];
97
+ aiButtonTitle?: string;
98
+ showAiButton?: boolean;
99
+ onAiButtonClick?: (item: {key: string, value: string}, event: Event) => void;
90
100
  }
91
101
 
92
102
  interface ViewOutput {
@@ -150,6 +160,7 @@ export abstract class KeyValueStorageItemsView extends UI.Widget.VBox {
150
160
  @deselect=${() => input.onSelect(null)}
151
161
  >
152
162
  <table>
163
+ ${input.showAiButton ? html`<style>${dataGridAiButtonStyles}</style>`: nothing}
153
164
  <tr>
154
165
  <th id="key" sortable ?editable=${input.editable}>
155
166
  ${i18nString(UIStrings.key)}
@@ -165,7 +176,15 @@ export abstract class KeyValueStorageItemsView extends UI.Widget.VBox {
165
176
  input.onEdit(item.key, item.value, e.detail.columnId, e.detail.valueBeforeEditing, e.detail.newText)}
166
177
  @delete=${() => input.onDelete(item.key)}
167
178
  selected=${(input.selectedKey === item.key) || nothing}>
168
- <td>${item.key}</td>
179
+ <td>${input.showAiButton ? html`
180
+ <span class="ai-button-container">
181
+ <devtools-floating-button
182
+ icon-name=${AIAssistance.AiUtils.getIconName()}
183
+ title=${ifDefined(input.aiButtonTitle)}
184
+ @click=${(e: Event) => input.onAiButtonClick?.(item, e)}
185
+ ></devtools-floating-button>
186
+ </span>
187
+ ` : nothing}${item.key}</td>
169
188
  <td>${item.value.substr(0, MAX_VALUE_LENGTH)}</td>
170
189
  </tr>`)}
171
190
  <tr placeholder></tr>
@@ -217,15 +236,24 @@ export abstract class KeyValueStorageItemsView extends UI.Widget.VBox {
217
236
  preview: this.#preview,
218
237
  jslog: this.#jslog,
219
238
  classes: this.#classes,
239
+ showAiButton: this.isAiButtonEnabled(),
240
+ aiButtonTitle: this.isAiButtonEnabled() &&
241
+ UI.ActionRegistry.ActionRegistry.instance().hasAction(STORAGE_FLOATING_BUTTON_ACTION_ID) ?
242
+ UI.ActionRegistry.ActionRegistry.instance().getAction(STORAGE_FLOATING_BUTTON_ACTION_ID).title() :
243
+ undefined,
220
244
  onSelect: (item: {key: string, value: string}|null) => {
221
245
  this.#toolbar?.setCanDeleteSelected(Boolean(item));
222
- if (!item) {
223
- void this.#previewEntry(null);
224
- } else {
225
- void this.#previewEntry(item);
226
- }
246
+ void this.#previewEntry(item);
227
247
  this.selectedItemChanged(item);
228
248
  },
249
+ onAiButtonClick: (item: {key: string, value: string}, event: Event) => {
250
+ event.stopPropagation();
251
+ viewInput.onSelect(item);
252
+ const actionRegistry = UI.ActionRegistry.ActionRegistry.instance();
253
+ if (actionRegistry.hasAction(STORAGE_FLOATING_BUTTON_ACTION_ID)) {
254
+ void actionRegistry.getAction(STORAGE_FLOATING_BUTTON_ACTION_ID).execute();
255
+ }
256
+ },
229
257
 
230
258
  onSort: (ascending: boolean) => {
231
259
  this.#isSortOrderAscending = ascending;
@@ -252,6 +280,10 @@ export abstract class KeyValueStorageItemsView extends UI.Widget.VBox {
252
280
  this.#view(viewInput, viewOutput, this.contentElement);
253
281
  }
254
282
 
283
+ protected isAiButtonEnabled(): boolean {
284
+ return false;
285
+ }
286
+
255
287
  protected get toolbar(): StorageItemsToolbar|undefined {
256
288
  return this.#toolbar;
257
289
  }
@@ -27,7 +27,13 @@ import * as ThemeSupport from '../../ui/legacy/theme_support/theme_support.js';
27
27
  import {ExtensionButton, ExtensionPanel, ExtensionSidebarPane} from './ExtensionPanel.js';
28
28
 
29
29
  const extensionOrigins = new WeakMap<MessagePort, Platform.DevToolsPath.UrlString>();
30
- const kPermittedSchemes = ['http:', 'https:', 'file:', 'data:', 'chrome-extension:', 'about:'];
30
+ const kForbiddenSchemes = [
31
+ 'chrome:',
32
+ 'chrome-untrusted:',
33
+ 'chrome-error:',
34
+ 'chrome-search:',
35
+ 'devtools:',
36
+ ];
31
37
 
32
38
  declare global {
33
39
  interface Window {
@@ -78,7 +84,6 @@ export class HostsPolicy {
78
84
  }
79
85
 
80
86
  class RegisteredExtension {
81
- openResourceScheme: null|string = null;
82
87
  constructor(readonly name: string, readonly hostsPolicy: HostsPolicy, readonly allowFileAccess: boolean) {
83
88
  }
84
89
 
@@ -91,8 +96,11 @@ class RegisteredExtension {
91
96
  return false;
92
97
  }
93
98
 
94
- if (this.openResourceScheme && inspectedURL.startsWith(this.openResourceScheme)) {
95
- return true;
99
+ let parsedURL;
100
+ try {
101
+ parsedURL = new URL(inspectedURL);
102
+ } catch {
103
+ return false;
96
104
  }
97
105
 
98
106
  if (!ExtensionServer.canInspectURL(inspectedURL)) {
@@ -104,12 +112,6 @@ class RegisteredExtension {
104
112
  }
105
113
 
106
114
  if (!this.allowFileAccess) {
107
- let parsedURL;
108
- try {
109
- parsedURL = new URL(inspectedURL);
110
- } catch {
111
- return false;
112
- }
113
115
  return parsedURL.protocol !== 'file:';
114
116
  }
115
117
 
@@ -863,18 +865,27 @@ export class ExtensionServer extends Common.ObjectWrapper.ObjectWrapper<EventTyp
863
865
  if (!extension) {
864
866
  throw new Error('Received a message from an unregistered extension');
865
867
  }
866
- if (message.urlScheme) {
867
- extension.openResourceScheme = message.urlScheme;
868
+ let validScheme = message.urlScheme;
869
+ if (validScheme) {
870
+ try {
871
+ const urlToParse = validScheme.replace(/:?(\/\/)?$/, '') + '://test';
872
+ validScheme = new URL(urlToParse).protocol;
873
+ } catch {
874
+ return this.status.E_BADARG('urlScheme', 'Invalid scheme');
875
+ }
876
+ if (kForbiddenSchemes.includes(validScheme) || validScheme === 'file:') {
877
+ return this.status.E_BADARG('urlScheme', 'Scheme is forbidden');
878
+ }
868
879
  }
869
880
  const extensionOrigin = this.getExtensionOrigin(port);
870
881
  const {name} = extension;
871
882
  const registration = {
872
883
  title: name,
873
884
  origin: extensionOrigin,
874
- scheme: message.urlScheme,
885
+ scheme: validScheme,
875
886
  handler: this.handleOpenURL.bind(this, port),
876
887
  shouldHandleOpenResource: (url: Platform.DevToolsPath.UrlString, schemes: Set<string>) =>
877
- Components.Linkifier.Linkifier.shouldHandleOpenResource(extension.openResourceScheme, url, schemes),
888
+ Components.Linkifier.Linkifier.shouldHandleOpenResource(validScheme || null, url, schemes),
878
889
  };
879
890
  if (message.handlerPresent) {
880
891
  Components.Linkifier.Linkifier.registerLinkHandler(registration);
@@ -1645,7 +1656,7 @@ export class ExtensionServer extends Common.ObjectWrapper.ObjectWrapper<EventTyp
1645
1656
  return false;
1646
1657
  }
1647
1658
 
1648
- if (!kPermittedSchemes.includes(parsedURL.protocol)) {
1659
+ if (kForbiddenSchemes.includes(parsedURL.protocol)) {
1649
1660
  return false;
1650
1661
  }
1651
1662
 
@@ -3,15 +3,108 @@
3
3
  // found in the LICENSE file.
4
4
 
5
5
  import * as Bindings from '../../models/bindings/bindings.js';
6
+ import type * as StackTrace from '../../models/stack_trace/stack_trace.js';
6
7
  import type * as Workspace from '../../models/workspace/workspace.js';
8
+ import * as Components from '../../ui/legacy/components/utils/utils.js';
7
9
  import * as UI from '../../ui/legacy/legacy.js';
10
+ import * as Lit from '../../ui/lit/lit.js';
11
+
12
+ const {html, render} = Lit;
8
13
 
9
14
  export interface ViewInput {
10
15
  error: Bindings.SymbolizedError.SymbolizedError;
11
16
  ignoreListManager?: Workspace.IgnoreListManager.IgnoreListManager;
12
17
  }
13
18
 
14
- const DEFAULT_VIEW = (_input: ViewInput, _output: object, _target: HTMLElement): void => {};
19
+ function renderHeader(content: Lit.LitTemplate|Node|UI.Widget.Widget, isCause: boolean): Lit.LitTemplate {
20
+ if (isCause) {
21
+ return html`<div class="symbolized-error-header"><span>Caused by: </span><span class="error-message-text">${
22
+ content}</span></div>`;
23
+ }
24
+ return html`<span class="error-message-text">${content}</span>`;
25
+ }
26
+
27
+ function renderFramePrefix(
28
+ frame: StackTrace.StackTrace.ParsedErrorStackFrame,
29
+ _options: Components.Linkifier.LinkifyOptions): Lit.LitTemplate {
30
+ const asyncPrefix = frame.isAsync ? 'async ' : '';
31
+ if (frame.promiseIndex !== undefined) {
32
+ const name = frame.name || 'Promise.all';
33
+ return html`${asyncPrefix}${name} (index ${frame.promiseIndex})`;
34
+ }
35
+ const constructorPrefix = frame.isConstructor ? 'new ' : '';
36
+ let name = frame.name || '';
37
+ const isInline = Boolean(frame.rawName) && frame.name !== frame.rawName;
38
+ if (!isInline && frame.methodName && name && name !== frame.methodName && !name.endsWith('.' + frame.methodName) &&
39
+ !name.endsWith(' ' + frame.methodName)) {
40
+ name += ` [as ${frame.methodName}]`;
41
+ }
42
+
43
+ if (name) {
44
+ return html`${asyncPrefix}${constructorPrefix}${name} (`;
45
+ }
46
+ return html`${asyncPrefix}${constructorPrefix}`;
47
+ }
48
+
49
+ function renderFrameSuffix(frame: StackTrace.StackTrace.ParsedErrorStackFrame): Lit.LitTemplate {
50
+ if (frame.promiseIndex !== undefined) {
51
+ return Lit.nothing;
52
+ }
53
+ if (frame.name) {
54
+ return html`)`;
55
+ }
56
+ return Lit.nothing;
57
+ }
58
+
59
+ const DEFAULT_VIEW = (input: ViewInput, _output: object, target: HTMLElement): void => {
60
+ const renderError = (error: Bindings.SymbolizedError.SymbolizedError, isCause: boolean): Lit.LitTemplate => {
61
+ if (!(error instanceof Bindings.SymbolizedError.SymbolizedErrorObject)) {
62
+ console.error('SymbolizedErrorWidget received an unsupported error type:', error);
63
+ return Lit.nothing;
64
+ }
65
+ const linkOptions: Components.Linkifier.LinkifyOptions = {
66
+ showColumnNumber: true,
67
+ inlineFrameIndex: 0,
68
+ maxLength: UI.UIUtils.MaxLengthForDisplayedURLsInConsole,
69
+ ignoreListManager: input.ignoreListManager,
70
+ };
71
+ const headerContent = html`${error.message}`;
72
+ const header = renderHeader(headerContent, isCause);
73
+ const syncFrames = error.stackTrace.syncFragment.frames;
74
+ // clang-format off
75
+ return html`
76
+ <span class=${isCause ? 'console-message-stack-trace-wrapper' : ''}
77
+ >${header}${syncFrames.length > 0 ? '\n' : ''}${syncFrames.map((frame: StackTrace.StackTrace.ParsedErrorStackFrame, i: number) => {
78
+ let linkElement: HTMLElement|Lit.LitTemplate = Lit.nothing;
79
+ let isBuiltin = false;
80
+ if (frame.promiseIndex !== undefined) {
81
+ // Promise.all doesn't have a linkable location.
82
+ isBuiltin = true;
83
+ } else if (frame.url || frame.uiSourceCode) {
84
+ const link = Components.Linkifier.Linkifier.linkifyStackTraceFrame(frame, linkOptions);
85
+ link.tabIndex = -1;
86
+ linkElement = link;
87
+ } else {
88
+ linkElement = html`<span>&lt;anonymous&gt;</span>`;
89
+ isBuiltin = true;
90
+ }
91
+
92
+ const newline = i < error.stackTrace.syncFragment.frames.length - 1 ? '\n' : '';
93
+ const frameClass = isBuiltin ? 'formatted-builtin-stack-frame' : 'formatted-stack-frame';
94
+ return html`
95
+ <span class=${frameClass}>${' at '}${renderFramePrefix(frame, linkOptions)}${linkElement}${renderFrameSuffix(frame)}${newline}</span>
96
+ `;
97
+ })}
98
+ </span>
99
+ ${error.cause ? renderError(error.cause, true) : Lit.nothing}
100
+ `;
101
+ // clang-format on
102
+ };
103
+
104
+ // clang-format off
105
+ render(html`<span class="symbolized-error-widget">${renderError(input.error, false)}</span>`, target);
106
+ // clang-format on
107
+ };
15
108
 
16
109
  export class SymbolizedErrorWidget extends UI.Widget.Widget {
17
110
  #error?: Bindings.SymbolizedError.SymbolizedError;
@@ -19,7 +112,8 @@ export class SymbolizedErrorWidget extends UI.Widget.Widget {
19
112
  #ignoreListManager?: Workspace.IgnoreListManager.IgnoreListManager;
20
113
 
21
114
  constructor(element?: HTMLElement, view: typeof DEFAULT_VIEW = DEFAULT_VIEW) {
22
- super(element);
115
+ const host = element || document.createElement('span');
116
+ super(host, {classes: ['symbolized-error-widget-host']});
23
117
  this.#view = view;
24
118
  }
25
119
 
@@ -0,0 +1,48 @@
1
+ /*
2
+ * Copyright 2026 The Chromium Authors
3
+ * Use of this source code is governed by a BSD-style license that can be
4
+ * found in the LICENSE file.
5
+ */
6
+
7
+ .symbolized-error-widget {
8
+ white-space: pre-wrap;
9
+ word-break: break-all;
10
+
11
+ --display-formatted-stack-frame-default: block;
12
+ --display-ignored-formatted-stack-frame-local: var(--display-ignored-formatted-stack-frame, none);
13
+
14
+ &.show-hidden-rows {
15
+ --display-ignored-formatted-stack-frame-local: var(--display-formatted-stack-frame-default);
16
+ }
17
+ }
18
+
19
+ .symbolized-error-widget .formatted-stack-frame {
20
+ display: var(--display-formatted-stack-frame-default);
21
+
22
+ &:has(.ignore-list-link) {
23
+ display: var(--display-ignored-formatted-stack-frame-local);
24
+ opacity: 60%;
25
+
26
+ /* Subsequent builtin stack frames are also treated as ignored */
27
+ & + .formatted-builtin-stack-frame {
28
+ display: var(--display-ignored-formatted-stack-frame-local);
29
+ opacity: 60%;
30
+ }
31
+ }
32
+ }
33
+
34
+ .symbolized-error-widget .formatted-builtin-stack-frame {
35
+ display: var(--display-formatted-stack-frame-default);
36
+ }
37
+
38
+ .symbolized-error-widget-host {
39
+ display: inline;
40
+ }
41
+
42
+ .symbolized-error-header {
43
+ display: block;
44
+ }
45
+
46
+ .error-message-text {
47
+ display: inline;
48
+ }
@@ -276,7 +276,7 @@ export class StandaloneStylesContainer extends Common.ObjectWrapper.eventMixin<E
276
276
  return null;
277
277
  }
278
278
 
279
- jumpToFunctionDefinition(_functionName: string): void {
279
+ jumpToFunctionDefinition(_functionName: string, _treeScopeDistance: number): void {
280
280
  }
281
281
 
282
282
  continueEditingElement(_sectionIndex: number, _propertyIndex: number): void {
@@ -379,6 +379,14 @@ export class StylePropertiesSection {
379
379
  return this.sectionIdx;
380
380
  }
381
381
 
382
+ treeScopeDistance(): number {
383
+ const treeScope = this.styleInternal.parentRule?.treeScope;
384
+ if (!treeScope) {
385
+ return -1;
386
+ }
387
+ return SDK.CSSMatchedStyles.distanceToTreeScope(this.matchedStyles.node(), treeScope);
388
+ }
389
+
382
390
  static createRuleOriginNode(
383
391
  matchedStyles: SDK.CSSMatchedStyles.CSSMatchedStyles, linkifier: Components.Linkifier.Linkifier,
384
392
  rule: SDK.CSSRule.CSSRule|null): LitTemplate {
@@ -51,9 +51,11 @@ export class StylePropertyHighlighter {
51
51
  PanelUtils.highlightElement(block.titleElement() as HTMLElement);
52
52
  }
53
53
 
54
- findAndHighlightSection(sectionName: string, blockName: string): void {
54
+ findAndHighlightSection(sectionName: string, blockName: string, treeScopeDistance = -1): void {
55
55
  const block = this.styleSidebarPane.getSectionBlockByName(blockName);
56
- const section = block?.sections.find(section => section.headerText() === sectionName);
56
+ const section = block?.sections.find(
57
+ section => section.headerText() === sectionName &&
58
+ (treeScopeDistance === -1 || section.treeScopeDistance() === treeScopeDistance));
57
59
  if (!section || !block) {
58
60
  return;
59
61
  }
@@ -2661,10 +2661,11 @@ export class StylePropertyTreeElement extends UI.TreeOutline.TreeElement {
2661
2661
  return container;
2662
2662
  }
2663
2663
 
2664
- #getLinkableFunction(functionName: string, matchedStyles: SDK.CSSMatchedStyles.CSSMatchedStyles):
2665
- InlineEditor.LinkSwatch.LinkSwatch {
2664
+ #getLinkableFunction(
2665
+ functionName: string, matchedStyles: SDK.CSSMatchedStyles.CSSMatchedStyles,
2666
+ property: SDK.CSSProperty.CSSProperty): InlineEditor.LinkSwatch.LinkSwatch {
2666
2667
  const swatch = new InlineEditor.LinkSwatch.LinkSwatch();
2667
- const registeredFunction = matchedStyles.getRegisteredFunction(functionName);
2668
+ const {registeredFunction, treeScopeDistance} = matchedStyles.getRegisteredFunction(functionName, property);
2668
2669
  const isDefined = Boolean(registeredFunction);
2669
2670
  swatch.data = {
2670
2671
  jslogContext: 'css-function',
@@ -2675,7 +2676,7 @@ export class StylePropertyTreeElement extends UI.TreeOutline.TreeElement {
2675
2676
  if (!registeredFunction) {
2676
2677
  return;
2677
2678
  }
2678
- this.#stylesContainer.jumpToFunctionDefinition(registeredFunction);
2679
+ this.#stylesContainer.jumpToFunctionDefinition(registeredFunction, treeScopeDistance);
2679
2680
  },
2680
2681
  };
2681
2682
  return swatch;
@@ -2696,7 +2697,7 @@ export class StylePropertyTreeElement extends UI.TreeOutline.TreeElement {
2696
2697
  const tooltipId = this.getTooltipId(`${functionName}-trace`);
2697
2698
  // clang-format off
2698
2699
  return html`
2699
- <span tabIndex=-1 class=tracing-anchor aria-details=${tooltipId}>${functionName.startsWith('--') ? this.#getLinkableFunction(functionName, matchedStyles) : functionName}</span>
2700
+ <span tabIndex=-1 class=tracing-anchor aria-details=${tooltipId}>${functionName.startsWith('--') ? this.#getLinkableFunction(functionName, matchedStyles, context.property) : functionName}</span>
2700
2701
  <devtools-tooltip
2701
2702
  id=${tooltipId}
2702
2703
  use-hotkey
@@ -37,7 +37,7 @@ export interface StylesContainer {
37
37
  computedValue: string|null): ElementsComponents.CSSVariableValueView.CSSVariableValueView;
38
38
  getVariableParserError(matchedStyles: SDK.CSSMatchedStyles.CSSMatchedStyles, variableName: string):
39
39
  ElementsComponents.CSSVariableValueView.CSSVariableParserError|null;
40
- jumpToFunctionDefinition(functionName: string): void;
40
+ jumpToFunctionDefinition(functionName: string, treeScopeDistance: number): void;
41
41
  continueEditingElement(sectionIndex: number, propertyIndex: number): void;
42
42
  revealProperty(cssProperty: SDK.CSSProperty.CSSProperty): void;
43
43
  resetFocus(): void;
@@ -344,16 +344,16 @@ export class StylesSidebarPane extends Common.ObjectWrapper.eventMixin<EventType
344
344
  }
345
345
  }
346
346
 
347
- jumpToSection(sectionName: string, blockName: string): void {
348
- this.decorator.findAndHighlightSection(sectionName, blockName);
347
+ jumpToSection(sectionName: string, blockName: string, treeScopeDistance?: number): void {
348
+ this.decorator.findAndHighlightSection(sectionName, blockName, treeScopeDistance);
349
349
  }
350
350
 
351
351
  jumpToSectionBlock(section: string): void {
352
352
  this.decorator.findAndHighlightSectionBlock(section);
353
353
  }
354
354
 
355
- jumpToFunctionDefinition(functionName: string): void {
356
- this.jumpToSection(functionName, FUNCTION_SECTION_NAME);
355
+ jumpToFunctionDefinition(functionName: string, treeScopeDistance: number): void {
356
+ this.jumpToSection(functionName, FUNCTION_SECTION_NAME, treeScopeDistance);
357
357
  }
358
358
 
359
359
  jumpToFontPaletteDefinition(paletteName: string): void {