chrome-devtools-frontend 1.0.1539972 → 1.0.1541169

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 (74) hide show
  1. package/eslint.config.mjs +167 -151
  2. package/front_end/core/common/Revealer.ts +5 -0
  3. package/front_end/core/host/InspectorFrontendHost.ts +10 -10
  4. package/front_end/core/sdk/NetworkManager.ts +16 -11
  5. package/front_end/core/sdk/sdk-meta.ts +0 -35
  6. package/front_end/entrypoints/shell/shell.ts +1 -0
  7. package/front_end/entrypoints/trace_app/trace_app.ts +1 -0
  8. package/front_end/generated/InspectorBackendCommands.ts +6 -3
  9. package/front_end/generated/SupportedCSSProperties.js +13 -0
  10. package/front_end/generated/protocol.ts +58 -2
  11. package/front_end/models/ai_assistance/data_formatters/PerformanceTraceFormatter.snapshot.txt +121 -56
  12. package/front_end/models/ai_assistance/data_formatters/PerformanceTraceFormatter.ts +104 -62
  13. package/front_end/models/ai_assistance/performance/AIQueries.ts +56 -2
  14. package/front_end/{panels/issues → models/issues_manager}/IssueAggregator.ts +83 -65
  15. package/front_end/models/issues_manager/issues_manager.ts +2 -0
  16. package/front_end/models/trace/Processor.ts +5 -4
  17. package/front_end/models/trace/insights/types.ts +1 -1
  18. package/front_end/models/trace/types/TraceEvents.ts +1 -1
  19. package/front_end/models/workspace/IgnoreListManager.ts +41 -47
  20. package/front_end/models/workspace/workspace-meta.ts +40 -0
  21. package/front_end/panels/animation/AnimationTimeline.ts +4 -4
  22. package/front_end/panels/animation/AnimationUI.ts +28 -34
  23. package/front_end/panels/elements/ElementsTreeElement.ts +37 -9
  24. package/front_end/panels/elements/LayoutPane.ts +2 -2
  25. package/front_end/panels/elements/components/AdornerManager.ts +9 -9
  26. package/front_end/panels/elements/layoutPane.css +5 -9
  27. package/front_end/panels/event_listeners/EventListenersView.ts +1 -1
  28. package/front_end/panels/explain/components/ConsoleInsight.ts +498 -449
  29. package/front_end/panels/issues/AffectedResourcesView.ts +3 -4
  30. package/front_end/panels/issues/CorsIssueDetailsView.ts +1 -2
  31. package/front_end/panels/issues/IssueView.ts +1 -1
  32. package/front_end/panels/issues/IssuesPane.ts +12 -15
  33. package/front_end/panels/issues/issues.ts +0 -2
  34. package/front_end/panels/network/NetworkDataGridNode.ts +2 -1
  35. package/front_end/panels/network/RequestConditionsDrawer.ts +149 -46
  36. package/front_end/panels/network/RequestTimingView.ts +13 -8
  37. package/front_end/panels/network/network-meta.ts +11 -0
  38. package/front_end/panels/settings/emulation/components/userAgentClientHintsForm.css +1 -1
  39. package/front_end/panels/sources/DebuggerPlugin.ts +1 -1
  40. package/front_end/panels/sources/WatchExpressionsSidebarPane.ts +1 -1
  41. package/front_end/panels/sources/breakpointsView.css +1 -1
  42. package/front_end/panels/sources/sourcesPanel.css +2 -2
  43. package/front_end/panels/timeline/TimelineFlameChartView.ts +3 -3
  44. package/front_end/panels/timeline/TimelinePanel.ts +3 -3
  45. package/front_end/panels/timeline/components/LayoutShiftDetails.ts +16 -10
  46. package/front_end/panels/timeline/components/SidebarSingleInsightSet.ts +2 -0
  47. package/front_end/third_party/chromium/README.chromium +1 -1
  48. package/front_end/ui/components/markdown_view/MarkdownView.ts +1 -0
  49. package/front_end/ui/components/snackbars/Snackbars.docs.ts +46 -0
  50. package/front_end/ui/{components/docs/context_menu/basic.ts → legacy/ContextMenu.docs.ts} +58 -25
  51. package/front_end/ui/legacy/UIUtils.ts +2 -1
  52. package/front_end/ui/legacy/components/inline_editor/BezierEditor.ts +1 -1
  53. package/front_end/ui/legacy/components/object_ui/ObjectPropertiesSection.ts +105 -92
  54. package/front_end/ui/legacy/components/perf_ui/TimelineOverviewPane.ts +3 -3
  55. package/front_end/ui/legacy/components/perf_ui/pieChart.css +1 -1
  56. package/front_end/ui/legacy/components/utils/Linkifier.ts +1 -1
  57. package/front_end/ui/legacy/inspectorCommon.css +3 -2
  58. package/mcp/mcp.ts +15 -1
  59. package/package.json +2 -1
  60. package/front_end/ui/components/docs/context_menu/basic.html +0 -45
  61. package/front_end/ui/components/docs/linkifier/simple-url.html +0 -25
  62. package/front_end/ui/components/docs/linkifier/simple-url.ts +0 -25
  63. package/front_end/ui/components/docs/panel_feedback/basic.html +0 -25
  64. package/front_end/ui/components/docs/panel_feedback/basic.ts +0 -21
  65. package/front_end/ui/components/docs/panel_feedback/button.html +0 -25
  66. package/front_end/ui/components/docs/panel_feedback/button.ts +0 -19
  67. package/front_end/ui/components/docs/panel_introduction_steps/basic.html +0 -25
  68. package/front_end/ui/components/docs/panel_introduction_steps/basic.ts +0 -28
  69. package/front_end/ui/components/docs/perf_piechart/basic-with-legend.html +0 -20
  70. package/front_end/ui/components/docs/perf_piechart/basic-with-legend.ts +0 -20
  71. package/front_end/ui/components/docs/perf_piechart/basic-without-legend.html +0 -20
  72. package/front_end/ui/components/docs/perf_piechart/basic-without-legend.ts +0 -18
  73. package/front_end/ui/components/docs/snackbars/basic.html +0 -17
  74. package/front_end/ui/components/docs/snackbars/basic.ts +0 -50
@@ -190,15 +190,22 @@ export const DEFAULT_VIEW: (input: ViewInput, output: object, target: HTMLElemen
190
190
  // clang-format on
191
191
  };
192
192
 
193
+ function findInsightSet(insightSets: Trace.Insights.Types.TraceInsightSets|null, navigationId: string|undefined):
194
+ Trace.Insights.Types.InsightSet|undefined {
195
+ return insightSets?.values().find(
196
+ insightSet =>
197
+ navigationId ? navigationId === insightSet.navigation?.args.data?.navigationId : !insightSet.navigation);
198
+ }
199
+
193
200
  function renderLayoutShiftDetails(
194
- layoutShift: Trace.Types.Events.SyntheticLayoutShift, traceInsightsSets: Trace.Insights.Types.TraceInsightSets|null,
201
+ layoutShift: Trace.Types.Events.SyntheticLayoutShift, insightSets: Trace.Insights.Types.TraceInsightSets|null,
195
202
  parsedTrace: Trace.TraceModel.ParsedTrace, isFreshRecording: boolean,
196
203
  onEventClick: (e: Trace.Types.Events.Event) => void): Lit.LitTemplate {
197
- if (!traceInsightsSets) {
204
+ if (!insightSets) {
198
205
  return Lit.nothing;
199
206
  }
200
- const insightsId = layoutShift.args.data?.navigationId ?? Trace.Types.Events.NO_NAVIGATION;
201
- const clsInsight = traceInsightsSets.get(insightsId)?.model.CLSCulprits;
207
+
208
+ const clsInsight = findInsightSet(insightSets, layoutShift.args.data?.navigationId)?.model.CLSCulprits;
202
209
  if (!clsInsight || clsInsight instanceof Error) {
203
210
  return Lit.nothing;
204
211
  }
@@ -242,14 +249,13 @@ function renderLayoutShiftDetails(
242
249
  }
243
250
 
244
251
  function renderLayoutShiftClusterDetails(
245
- cluster: Trace.Types.Events.SyntheticLayoutShiftCluster,
246
- traceInsightsSets: Trace.Insights.Types.TraceInsightSets|null, parsedTrace: Trace.TraceModel.ParsedTrace,
247
- onEventClick: (e: Trace.Types.Events.Event) => void): Lit.LitTemplate {
248
- if (!traceInsightsSets) {
252
+ cluster: Trace.Types.Events.SyntheticLayoutShiftCluster, insightSets: Trace.Insights.Types.TraceInsightSets|null,
253
+ parsedTrace: Trace.TraceModel.ParsedTrace, onEventClick: (e: Trace.Types.Events.Event) => void): Lit.LitTemplate {
254
+ if (!insightSets) {
249
255
  return Lit.nothing;
250
256
  }
251
- const insightsId = cluster.navigationId ?? Trace.Types.Events.NO_NAVIGATION;
252
- const clsInsight = traceInsightsSets.get(insightsId)?.model.CLSCulprits;
257
+
258
+ const clsInsight = findInsightSet(insightSets, cluster.navigationId)?.model.CLSCulprits;
253
259
  if (!clsInsight || clsInsight instanceof Error) {
254
260
  return Lit.nothing;
255
261
  }
@@ -437,6 +437,7 @@ export class SidebarSingleInsightSet extends HTMLElement {
437
437
 
438
438
  const agentFocus = AIAssistance.AIContext.AgentFocus.fromInsight(this.#data.parsedTrace, model);
439
439
  // clang-format off
440
+ /* eslint-disable lit/binding-positions,lit/no-invalid-html */
440
441
  return html`<div>
441
442
  <${componentClass.litTagName}
442
443
  .selected=${this.#data.activeInsight?.model === model}
@@ -452,6 +453,7 @@ export class SidebarSingleInsightSet extends HTMLElement {
452
453
  .fieldMetrics=${fieldMetrics}>
453
454
  </${componentClass.litTagName}>
454
455
  </div>`;
456
+ /* eslint-enable lit/binding-positions,lit/no-invalid-html */
455
457
  // clang-format on
456
458
  };
457
459
 
@@ -1,7 +1,7 @@
1
1
  Name: Dependencies sourced from the upstream `chromium` repository
2
2
  URL: https://chromium.googlesource.com/chromium/src
3
3
  Version: N/A
4
- Revision: def2721bfd5abbc4b522b2bca7fd80ec3d55b81a
4
+ Revision: 33096dd334d53eb095e196b5663cc1921f646cae
5
5
  Update Mechanism: Manual (https://crbug.com/428069060)
6
6
  License: BSD-3-Clause
7
7
  License File: LICENSE
@@ -239,6 +239,7 @@ export class MarkdownLitRenderer {
239
239
  case 'code':
240
240
  return this.renderCodeBlock(token);
241
241
  case 'space':
242
+ // eslint-disable-next-line lit/prefer-nothing
242
243
  return html``;
243
244
  case 'link':
244
245
  return html`<devtools-markdown-link
@@ -0,0 +1,46 @@
1
+ // Copyright 2025 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 {Snackbar} from './snackbars.js';
6
+
7
+ export async function render(container: HTMLElement) {
8
+ const onActionClick = (): void => {
9
+ // eslint-disable-next-line no-console
10
+ console.log('Action button clicked!');
11
+ };
12
+
13
+ const showButton1 = document.createElement('button');
14
+ showButton1.textContent = 'Show Long Action Snackbar';
15
+ showButton1.addEventListener('click', () => {
16
+ Snackbar.Snackbar.show(
17
+ {
18
+ message: 'This is a snackbar demonstrating a long action and closable state.',
19
+ closable: true,
20
+ actionProperties: {
21
+ label: 'This is a long action button',
22
+ title: 'Click here to perform the designated action',
23
+ onClick: onActionClick,
24
+ },
25
+ },
26
+ container);
27
+ });
28
+ container.appendChild(showButton1);
29
+
30
+ const showButton2 = document.createElement('button');
31
+ showButton2.textContent = 'Show Action Snackbar';
32
+ showButton2.addEventListener('click', () => {
33
+ Snackbar.Snackbar.show(
34
+ {
35
+ message: 'This is a snackbar demonstrating an action and closable state.',
36
+ closable: true,
37
+ actionProperties: {
38
+ label: 'Action',
39
+ title: 'Click here to perform the designated action',
40
+ onClick: onActionClick,
41
+ },
42
+ },
43
+ container);
44
+ });
45
+ container.appendChild(showButton2);
46
+ }
@@ -2,23 +2,44 @@
2
2
  // Use of this source code is governed by a BSD-style license that can be
3
3
  // found in the LICENSE file.
4
4
 
5
- import * as FrontendHelpers from '../../../../testing/EnvironmentHelpers.js';
6
- import * as UI from '../../../legacy/legacy.js';
7
- import * as Lit from '../../../lit/lit.js';
8
- import * as ComponentHelpers from '../../helpers/helpers.js';
5
+ import * as Lit from '../lit/lit.js';
6
+
7
+ import {ContextMenu} from './legacy.js';
9
8
 
10
9
  const {html} = Lit;
11
10
 
12
- await ComponentHelpers.ComponentServerSetup.setup();
13
- await FrontendHelpers.initializeGlobalVars();
11
+ export async function render(container: HTMLElement) {
12
+ const style = document.createElement('style');
13
+ style.textContent = `
14
+ #container > div {
15
+ width: var(--sys-size-34);
16
+ padding: var(--sys-size-11);
17
+ display: flex;
18
+ align-items: center;
19
+ flex-wrap: wrap;
20
+ gap: var(--sys-size-5);
21
+ background-color: var(--sys-color-neutral-container);
22
+ border-radius: var(--sys-shape-corner-medium);
23
+ text-align: center;
24
+
25
+ p {
26
+ vertical-align: middle;
27
+ }
28
+ }
29
+ `;
30
+ container.appendChild(style);
31
+
32
+ const menuButtonSection = document.createElement('div');
33
+ const menuButtonHeader = document.createElement('header');
34
+ menuButtonHeader.textContent = 'DevTools menu button (lit-html)';
35
+ container.appendChild(menuButtonHeader);
36
+ container.appendChild(menuButtonSection);
14
37
 
15
- {
16
- const menuButtonSection = document.querySelector('#menu-button') as HTMLElement;
17
38
  Lit.render(
18
39
  html`
19
40
  <devtools-menu-button
20
41
  icon-name="bin"
21
- .populateMenuCall=${(menu: UI.ContextMenu.ContextMenu) => {
42
+ .populateMenuCall=${(menu: ContextMenu.ContextMenu) => {
22
43
  menu.defaultSection().appendItem('Item', () => {
23
44
  alert('Item clicked');
24
45
  }, {jslogContext: 'item'});
@@ -27,15 +48,19 @@ await FrontendHelpers.initializeGlobalVars();
27
48
  ></devtools-menu-button>
28
49
  `,
29
50
  menuButtonSection);
30
- }
31
51
 
32
- {
52
+ const simpleItemsSection = document.createElement('div');
53
+ simpleItemsSection.innerHTML = '<p>Right-click here</p>';
54
+ const simpleItemsHeader = document.createElement('header');
55
+ simpleItemsHeader.textContent = 'Various simple menu items (imperative API)';
56
+ container.appendChild(simpleItemsHeader);
57
+ container.appendChild(simpleItemsSection);
58
+
33
59
  let checked = true;
34
- const simpleItemMenuSection = document.querySelector('#simple-items');
35
- simpleItemMenuSection?.addEventListener('contextmenu', onSimpleMenu.bind(this));
60
+ simpleItemsSection.addEventListener('contextmenu', onSimpleMenu);
36
61
 
37
62
  function onSimpleMenu(event: Event) {
38
- const simpleMenu = new UI.ContextMenu.ContextMenu(event);
63
+ const simpleMenu = new ContextMenu.ContextMenu(event);
39
64
 
40
65
  // Regular item
41
66
  simpleMenu.defaultSection().appendItem('Regular item', () => {
@@ -63,14 +88,18 @@ await FrontendHelpers.initializeGlobalVars();
63
88
 
64
89
  void simpleMenu.show();
65
90
  }
66
- }
67
91
 
68
- {
69
- const customSectionMenuSection = document.querySelector('#custom-section');
70
- customSectionMenuSection?.addEventListener('contextmenu', onCustomSectionMenu.bind(this));
92
+ const customSection = document.createElement('div');
93
+ customSection.innerHTML = '<p>Right-click here</p>';
94
+ const customSectionHeader = document.createElement('header');
95
+ customSectionHeader.textContent = 'Custom sections (imperative API)';
96
+ container.appendChild(customSectionHeader);
97
+ container.appendChild(customSection);
98
+
99
+ customSection.addEventListener('contextmenu', onCustomSectionMenu);
71
100
 
72
101
  function onCustomSectionMenu(event: Event) {
73
- const customSectionMenu = new UI.ContextMenu.ContextMenu(event);
102
+ const customSectionMenu = new ContextMenu.ContextMenu(event);
74
103
 
75
104
  // First custom section
76
105
  const customSection = customSectionMenu.section('Custom section');
@@ -83,14 +112,18 @@ await FrontendHelpers.initializeGlobalVars();
83
112
 
84
113
  void customSectionMenu.show();
85
114
  }
86
- }
87
115
 
88
- {
89
- const subMenuSection = document.querySelector('#sub-menu');
90
- subMenuSection?.addEventListener('contextmenu', onCustomSectionMenu.bind(this));
116
+ const subMenuSection = document.createElement('div');
117
+ subMenuSection.innerHTML = '<p>Right-click here</p>';
118
+ const subMenuHeader = document.createElement('header');
119
+ subMenuHeader.textContent = 'Sub menu (imperative API)';
120
+ container.appendChild(subMenuHeader);
121
+ container.appendChild(subMenuSection);
91
122
 
92
- function onCustomSectionMenu(event: Event) {
93
- const subMenuMenu = new UI.ContextMenu.ContextMenu(event);
123
+ subMenuSection.addEventListener('contextmenu', onSubMenu);
124
+
125
+ function onSubMenu(event: Event) {
126
+ const subMenuMenu = new ContextMenu.ContextMenu(event);
94
127
 
95
128
  const subMenu =
96
129
  subMenuMenu.defaultSection().appendSubMenuItem('Item to open sub menu', /* disabled */ false, 'my-sub-menu');
@@ -1820,7 +1820,8 @@ export const isScrolledToBottom = (element: Element): boolean => {
1820
1820
  return Math.abs(element.scrollTop + element.clientHeight - element.scrollHeight) <= 2;
1821
1821
  };
1822
1822
 
1823
- export function createSVGChild(element: Element, childType: string, className?: string): Element {
1823
+ export function createSVGChild<K extends keyof SVGElementTagNameMap>(
1824
+ element: Element, childType: K, className?: string): SVGElementTagNameMap[K] {
1824
1825
  const child = element.ownerDocument.createElementNS('http://www.w3.org/2000/svg', childType);
1825
1826
  if (className) {
1826
1827
  child.setAttribute('class', className);
@@ -147,7 +147,7 @@ export class BezierEditor extends Common.ObjectWrapper.eventMixin<EventTypes, ty
147
147
  return category;
148
148
  }
149
149
 
150
- private createPresetModifyIcon(parentElement: Element, className: string, drawPath: string): Element {
150
+ private createPresetModifyIcon(parentElement: Element, className: string, drawPath: string): SVGElement {
151
151
  const icon = UI.UIUtils.createSVGChild(parentElement, 'svg', 'bezier-preset-modify ' + className);
152
152
  icon.setAttribute('width', '20');
153
153
  icon.setAttribute('height', '20');
@@ -39,6 +39,7 @@ import * as TextUtils from '../../../../models/text_utils/text_utils.js';
39
39
  import * as uiI18n from '../../../../ui/i18n/i18n.js';
40
40
  import * as IconButton from '../../../components/icon_button/icon_button.js';
41
41
  import * as TextEditor from '../../../components/text_editor/text_editor.js';
42
+ import {Directives, html, render} from '../../../lit/lit.js';
42
43
  import * as VisualLogging from '../../../visual_logging/visual_logging.js';
43
44
  import * as UI from '../../legacy.js';
44
45
  import type * as Components from '../utils/utils.js';
@@ -49,6 +50,7 @@ import objectPropertiesSectionStyles from './objectPropertiesSection.css.js';
49
50
  import objectValueStyles from './objectValue.css.js';
50
51
  import {createSpansForNodeTitle, RemoteObjectPreviewFormatter} from './RemoteObjectPreviewFormatter.js';
51
52
 
53
+ const {ifDefined} = Directives;
52
54
  const UIStrings = {
53
55
  /**
54
56
  * @description Text in Object Properties Section
@@ -397,7 +399,7 @@ export class ObjectPropertiesSection extends UI.TreeOutline.TreeOutlineInShadow
397
399
  const shadowRoot = UI.UIUtils.createShadowRootWithCoreStyles(titleElement, {cssFile: objectValueStyles});
398
400
  const propertyValue =
399
401
  ObjectPropertiesSection.createPropertyValue(object, /* wasThrown */ false, /* showPreview */ true);
400
- shadowRoot.appendChild(propertyValue.element);
402
+ shadowRoot.appendChild(propertyValue);
401
403
  const objectPropertiesSection = new ObjectPropertiesSection(object, titleElement, linkifier);
402
404
  objectPropertiesSection.editable = false;
403
405
  if (skipProto) {
@@ -477,7 +479,7 @@ export class ObjectPropertiesSection extends UI.TreeOutline.TreeOutlineInShadow
477
479
  }
478
480
 
479
481
  static valueElementForFunctionDescription(description?: string, includePreview?: boolean, defaultName?: string):
480
- Element {
482
+ HTMLElement {
481
483
  const valueElement = document.createElement('span');
482
484
  valueElement.classList.add('object-value-function');
483
485
  description = description || '';
@@ -560,12 +562,11 @@ export class ObjectPropertiesSection extends UI.TreeOutline.TreeOutlineInShadow
560
562
 
561
563
  static createPropertyValueWithCustomSupport(
562
564
  value: SDK.RemoteObject.RemoteObject, wasThrown: boolean, showPreview: boolean,
563
- linkifier?: Components.Linkifier.Linkifier, isSyntheticProperty?: boolean,
564
- variableName?: string): ObjectPropertyValue {
565
+ linkifier?: Components.Linkifier.Linkifier, isSyntheticProperty?: boolean, variableName?: string): HTMLElement {
565
566
  if (value.customPreview()) {
566
567
  const result = (new CustomPreviewComponent(value)).element;
567
568
  result.classList.add('object-properties-section-custom-section');
568
- return new ObjectPropertyValue(result);
569
+ return result;
569
570
  }
570
571
  return ObjectPropertiesSection.createPropertyValue(
571
572
  value, wasThrown, showPreview, linkifier, isSyntheticProperty, variableName);
@@ -600,9 +601,8 @@ export class ObjectPropertiesSection extends UI.TreeOutline.TreeOutlineInShadow
600
601
 
601
602
  static createPropertyValue(
602
603
  value: SDK.RemoteObject.RemoteObject, wasThrown: boolean, showPreview: boolean,
603
- linkifier?: Components.Linkifier.Linkifier, isSyntheticProperty = false,
604
- variableName?: string): ObjectPropertyValue {
605
- let propertyValue;
604
+ linkifier?: Components.Linkifier.Linkifier, isSyntheticProperty = false, variableName?: string): HTMLElement {
605
+ let propertyValue: HTMLElement;
606
606
  const type = value.type;
607
607
  const subtype = value.subtype;
608
608
  const description = value.description || '';
@@ -611,31 +611,31 @@ export class ObjectPropertiesSection extends UI.TreeOutline.TreeOutlineInShadow
611
611
  const rawLocation = value.debuggerModel().createRawLocationByScriptId(
612
612
  value.value.scriptId, value.value.lineNumber, value.value.columnNumber);
613
613
  if (rawLocation && linkifier) {
614
- return new ObjectPropertyValue(linkifier.linkifyRawLocation(rawLocation, Platform.DevToolsPath.EmptyUrlString));
614
+ return linkifier.linkifyRawLocation(rawLocation, Platform.DevToolsPath.EmptyUrlString);
615
615
  }
616
- propertyValue = new ObjectPropertyValue(createUnknownInternalLocationElement());
616
+ propertyValue = createUnknownInternalLocationElement();
617
617
  } else if (type === 'string' && typeof description === 'string') {
618
618
  propertyValue = createStringElement();
619
619
  } else if (type === 'object' && subtype === 'trustedtype') {
620
620
  propertyValue = createTrustedTypeElement();
621
621
  } else if (type === 'function') {
622
- propertyValue = new ObjectPropertyValue(ObjectPropertiesSection.valueElementForFunctionDescription(description));
622
+ propertyValue = ObjectPropertiesSection.valueElementForFunctionDescription(description);
623
623
  } else if (type === 'object' && subtype === 'node' && description) {
624
- propertyValue = new ObjectPropertyValue(createNodeElement());
624
+ propertyValue = createNodeElement();
625
625
  } else {
626
626
  const valueElement = document.createElement('span');
627
627
  valueElement.classList.add('object-value-' + (subtype || type));
628
628
  if (value.preview && showPreview) {
629
629
  const previewFormatter = new RemoteObjectPreviewFormatter();
630
630
  previewFormatter.appendObjectPreview(valueElement, value.preview, false /* isEntry */);
631
- propertyValue = new ObjectPropertyValue(valueElement);
632
- UI.Tooltip.Tooltip.install(propertyValue.element as HTMLElement, description || '');
631
+ propertyValue = valueElement;
632
+ UI.Tooltip.Tooltip.install(propertyValue as HTMLElement, description || '');
633
633
  } else if (description.length > maxRenderableStringLength) {
634
- propertyValue = new ExpandableTextPropertyValue(valueElement, description, EXPANDABLE_MAX_LENGTH);
634
+ propertyValue = new ExpandableTextPropertyValue(valueElement, description, EXPANDABLE_MAX_LENGTH).element;
635
635
  } else {
636
- propertyValue = new ObjectPropertyValue(valueElement);
637
- propertyValue.element.textContent = description;
638
- UI.Tooltip.Tooltip.install(propertyValue.element as HTMLElement, description);
636
+ propertyValue = valueElement;
637
+ propertyValue.textContent = description;
638
+ UI.Tooltip.Tooltip.install(propertyValue as HTMLElement, description);
639
639
  }
640
640
  if (!isSyntheticProperty) {
641
641
  this.appendMemoryIcon(valueElement, value, variableName);
@@ -646,54 +646,53 @@ export class ObjectPropertiesSection extends UI.TreeOutline.TreeOutlineInShadow
646
646
  const wrapperElement = document.createElement('span');
647
647
  wrapperElement.classList.add('error');
648
648
  wrapperElement.classList.add('value');
649
- wrapperElement.appendChild(
650
- uiI18n.getFormatLocalizedString(str_, UIStrings.exceptionS, {PH1: propertyValue.element}));
651
- propertyValue.element = wrapperElement;
649
+ wrapperElement.appendChild(uiI18n.getFormatLocalizedString(str_, UIStrings.exceptionS, {PH1: propertyValue}));
650
+ propertyValue = wrapperElement;
652
651
  }
653
- propertyValue.element.classList.add('value');
652
+ propertyValue.classList.add('value');
654
653
  return propertyValue;
655
654
 
656
- function createUnknownInternalLocationElement(): Element {
655
+ function createUnknownInternalLocationElement(): HTMLElement {
657
656
  const valueElement = document.createElement('span');
658
657
  valueElement.textContent = '<' + i18nString(UIStrings.unknown) + '>';
659
658
  UI.Tooltip.Tooltip.install(valueElement, description || '');
660
659
  return valueElement;
661
660
  }
662
661
 
663
- function createStringElement(): ObjectPropertyValue {
662
+ function createStringElement(): HTMLElement {
664
663
  const valueElement = document.createElement('span');
665
664
  valueElement.classList.add('object-value-string');
666
665
  const text = JSON.stringify(description);
667
- let propertyValue;
666
+ let propertyValue: HTMLElement;
668
667
  if (description.length > maxRenderableStringLength) {
669
- propertyValue = new ExpandableTextPropertyValue(valueElement, text, EXPANDABLE_MAX_LENGTH);
668
+ propertyValue = new ExpandableTextPropertyValue(valueElement, text, EXPANDABLE_MAX_LENGTH).element;
670
669
  } else {
671
670
  UI.UIUtils.createTextChild(valueElement, text);
672
- propertyValue = new ObjectPropertyValue(valueElement);
671
+ propertyValue = valueElement;
673
672
  UI.Tooltip.Tooltip.install(valueElement, description);
674
673
  }
675
674
  return propertyValue;
676
675
  }
677
676
 
678
- function createTrustedTypeElement(): ObjectPropertyValue {
677
+ function createTrustedTypeElement(): HTMLElement {
679
678
  const valueElement = document.createElement('span');
680
679
  valueElement.classList.add('object-value-trustedtype');
681
680
  const text = `${className} "${description}"`;
682
681
  let propertyValue;
683
682
  if (text.length > maxRenderableStringLength) {
684
- propertyValue = new ExpandableTextPropertyValue(valueElement, text, EXPANDABLE_MAX_LENGTH);
683
+ propertyValue = new ExpandableTextPropertyValue(valueElement, text, EXPANDABLE_MAX_LENGTH).element;
685
684
  } else {
686
685
  const contentString = createStringElement();
687
686
  UI.UIUtils.createTextChild(valueElement, `${className} `);
688
- valueElement.appendChild(contentString.element);
689
- propertyValue = new ObjectPropertyValue(valueElement);
687
+ valueElement.appendChild(contentString);
688
+ propertyValue = valueElement;
690
689
  UI.Tooltip.Tooltip.install(valueElement, text);
691
690
  }
692
691
 
693
692
  return propertyValue;
694
693
  }
695
694
 
696
- function createNodeElement(): Element {
695
+ function createNodeElement(): HTMLElement {
697
696
  const valueElement = document.createElement('span');
698
697
  valueElement.classList.add('object-value-node');
699
698
  createSpansForNodeTitle(valueElement, (description));
@@ -896,7 +895,7 @@ export class ObjectPropertyTreeElement extends UI.TreeOutline.TreeElement {
896
895
  readOnly!: boolean;
897
896
  private prompt!: ObjectPropertyPrompt|undefined;
898
897
  private editableDiv!: HTMLElement;
899
- propertyValue?: ObjectPropertyValue;
898
+ propertyValue?: HTMLElement;
900
899
  expandedValueElement?: Element|null;
901
900
  constructor(property: ObjectTreeNode, linkifier?: Components.Linkifier.Linkifier) {
902
901
  // Pass an empty title, the title gets made later in onattach.
@@ -1202,7 +1201,7 @@ export class ObjectPropertyTreeElement extends UI.TreeOutline.TreeElement {
1202
1201
  this.propertyValue = ObjectPropertiesSection.createPropertyValueWithCustomSupport(
1203
1202
  this.property.object, this.property.property.wasThrown, showPreview, this.linkifier,
1204
1203
  this.property.property.synthetic, this.path() /* variableName */);
1205
- this.valueElement = (this.propertyValue.element as HTMLElement);
1204
+ this.valueElement = this.propertyValue;
1206
1205
  } else if (this.property.property.getter) {
1207
1206
  this.valueElement = document.createElement('span');
1208
1207
  const element = this.valueElement.createChild('span');
@@ -1285,7 +1284,7 @@ export class ObjectPropertyTreeElement extends UI.TreeOutline.TreeElement {
1285
1284
  }
1286
1285
  }
1287
1286
 
1288
- private contextMenuFired(event: Event): void {
1287
+ getContextMenu(event: Event): UI.ContextMenu.ContextMenu {
1289
1288
  const contextMenu = new UI.ContextMenu.ContextMenu(event);
1290
1289
  contextMenu.appendApplicableItems(this);
1291
1290
  if (this.property.property.symbol) {
@@ -1318,9 +1317,11 @@ export class ObjectPropertyTreeElement extends UI.TreeOutline.TreeElement {
1318
1317
  i18nString(UIStrings.collapseChildren), this.collapseChildren.bind(this),
1319
1318
  {jslogContext: 'collapse-children'});
1320
1319
  }
1321
- if (this.propertyValue) {
1322
- this.propertyValue.appendApplicableItems(event, contextMenu, {});
1323
- }
1320
+ return contextMenu;
1321
+ }
1322
+
1323
+ private contextMenuFired(event: Event): void {
1324
+ const contextMenu = this.getContextMenu(event);
1324
1325
  void contextMenu.show();
1325
1326
  }
1326
1327
 
@@ -1773,74 +1774,86 @@ export class Renderer implements UI.UIUtils.Renderer {
1773
1774
  }
1774
1775
  }
1775
1776
 
1776
- export class ObjectPropertyValue implements UI.ContextMenu.Provider<Object> {
1777
- element: Element;
1778
- constructor(element: Element) {
1779
- this.element = element;
1780
- }
1781
-
1782
- appendApplicableItems(_event: Event, _contextMenu: UI.ContextMenu.ContextMenu, _object: Object): void {
1783
- }
1784
- }
1785
-
1786
- export class ExpandableTextPropertyValue extends ObjectPropertyValue {
1777
+ export class ExpandableTextPropertyValue {
1787
1778
  private readonly text: string;
1788
1779
  private readonly maxLength: number;
1789
- private expandElement: Element|null;
1790
1780
  private readonly maxDisplayableTextLength: number;
1791
- private readonly expandElementText: Common.UIString.LocalizedString|undefined;
1792
- private readonly copyButtonText: Common.UIString.LocalizedString;
1793
- constructor(element: Element, text: string, maxLength: number) {
1794
- // abbreviated text and expandable text controls are added as children to element
1795
- super(element);
1796
- const container = element.createChild('span');
1781
+ readonly #byteCount: number;
1782
+ #expanded = false;
1783
+ #element: HTMLElement;
1784
+
1785
+ constructor(element: HTMLElement, text: string, maxLength: number) {
1786
+ this.#element = element;
1797
1787
  this.text = text;
1798
1788
  this.maxLength = maxLength;
1799
- container.textContent = text.slice(0, maxLength);
1800
- UI.Tooltip.Tooltip.install(container as HTMLElement, `${text.slice(0, maxLength)}…`);
1801
-
1802
- this.expandElement = container.createChild('button');
1803
1789
  this.maxDisplayableTextLength = 10000000;
1790
+ this.#byteCount = Platform.StringUtilities.countWtf8Bytes(text);
1791
+ this.#render();
1792
+ }
1804
1793
 
1805
- const byteCount = Platform.StringUtilities.countWtf8Bytes(text);
1806
- const totalBytesText = i18n.ByteUtilities.bytesToString(byteCount);
1807
- if (this.text.length < this.maxDisplayableTextLength) {
1808
- this.expandElementText = i18nString(UIStrings.showMoreS, {PH1: totalBytesText});
1809
- this.expandElement.setAttribute('data-text', this.expandElementText);
1810
- this.expandElement.setAttribute('jslog', `${VisualLogging.action('expand').track({click: true})}`);
1811
- this.expandElement.classList.add('expandable-inline-button');
1812
- this.expandElement.addEventListener('click', this.expandText.bind(this));
1813
- } else {
1814
- this.expandElement.setAttribute('data-text', i18nString(UIStrings.longTextWasTruncatedS, {PH1: totalBytesText}));
1815
- this.expandElement.classList.add('undisplayable-text');
1816
- }
1817
-
1818
- this.copyButtonText = i18nString(UIStrings.copy);
1819
- const copyButton = container.createChild('button', 'expandable-inline-button');
1820
- copyButton.setAttribute('data-text', this.copyButtonText);
1821
- copyButton.setAttribute('jslog', `${VisualLogging.action('copy').track({click: true})}`);
1822
- copyButton.addEventListener('click', this.copyText.bind(this));
1794
+ get element(): HTMLElement {
1795
+ return this.#element;
1823
1796
  }
1824
1797
 
1825
- override appendApplicableItems(_event: Event, contextMenu: UI.ContextMenu.ContextMenu, _object: Object): void {
1826
- if (this.text.length < this.maxDisplayableTextLength && this.expandElement) {
1798
+ #render(): void {
1799
+ const totalBytesText = i18n.ByteUtilities.bytesToString(this.#byteCount);
1800
+ const onContextMenu = (e: Event): void => {
1801
+ const {target} = e;
1802
+ if (!(target instanceof Element)) {
1803
+ return;
1804
+ }
1805
+ const listItem = target.closest('li');
1806
+ const element = listItem && UI.TreeOutline.TreeElement.getTreeElementBylistItemNode(listItem);
1807
+ if (!(element instanceof ObjectPropertyTreeElement)) {
1808
+ return;
1809
+ }
1810
+ const contextMenu = element.getContextMenu(e);
1811
+ if (this.text.length < this.maxDisplayableTextLength && !this.#expanded) {
1812
+ contextMenu.clipboardSection().appendItem(
1813
+ i18nString(UIStrings.showMoreS, {PH1: totalBytesText}), this.expandText.bind(this),
1814
+ {jslogContext: 'show-more'});
1815
+ }
1827
1816
  contextMenu.clipboardSection().appendItem(
1828
- this.expandElementText || '', this.expandText.bind(this), {jslogContext: 'show-more'});
1829
- }
1830
- contextMenu.clipboardSection().appendItem(this.copyButtonText, this.copyText.bind(this), {jslogContext: 'copy'});
1817
+ i18nString(UIStrings.copy), this.copyText.bind(this), {jslogContext: 'copy'});
1818
+ void contextMenu.show();
1819
+ e.consume(true);
1820
+ };
1821
+
1822
+ const croppedText = this.text.slice(0, this.maxLength);
1823
+
1824
+ // eslint-disable-next-line @devtools/no-lit-render-outside-of-view
1825
+ render(
1826
+ // clang-format off
1827
+ html`<span title=${croppedText + '…'} @contextmenu=${onContextMenu}>
1828
+ ${this.#expanded ? this.text : croppedText}
1829
+ <button
1830
+ ?hidden=${this.#expanded}
1831
+ @click=${this.#canExpand ? this.expandText.bind(this) : undefined}
1832
+ jslog=${ifDefined(this.#canExpand ? VisualLogging.action('expand').track({click: true}) : undefined)}
1833
+ class=${this.#canExpand ? 'expandable-inline-button' : 'undisplayable-text'}
1834
+ data-text=${this.#canExpand ? i18nString(UIStrings.showMoreS, {PH1: totalBytesText}) :
1835
+ i18nString(UIStrings.longTextWasTruncatedS, {PH1: totalBytesText})}
1836
+ ></button>
1837
+ <button
1838
+ class=expandable-inline-button
1839
+ @click=${this.copyText.bind(this)}
1840
+ data-text=${i18nString(UIStrings.copy)}
1841
+ jslog=${VisualLogging.action('copy').track({click: true})}
1842
+ ></button>
1843
+ </span>`,
1844
+ // clang-format on
1845
+ this.#element);
1846
+ }
1847
+
1848
+ get #canExpand(): boolean {
1849
+ return this.text.length < this.maxDisplayableTextLength;
1831
1850
  }
1832
1851
 
1833
1852
  private expandText(): void {
1834
- if (!this.expandElement) {
1835
- return;
1836
- }
1837
-
1838
- if (this.expandElement.parentElement) {
1839
- this.expandElement.parentElement.insertBefore(
1840
- document.createTextNode(this.text.slice(this.maxLength)), this.expandElement);
1853
+ if (!this.#expanded) {
1854
+ this.#expanded = true;
1855
+ this.#render();
1841
1856
  }
1842
- this.expandElement.remove();
1843
- this.expandElement = null;
1844
1857
  }
1845
1858
 
1846
1859
  private copyText(): void {
@@ -337,7 +337,7 @@ export class TimelineOverviewPane extends Common.ObjectWrapper.eventMixin<EventT
337
337
  #initializeDimHighlightSVG(): void {
338
338
  // Set up the desaturation mask
339
339
  const defs = UI.UIUtils.createSVGChild(this.#dimHighlightSVG, 'defs');
340
- const mask = UI.UIUtils.createSVGChild(defs, 'mask') as SVGMaskElement;
340
+ const mask = UI.UIUtils.createSVGChild(defs, 'mask');
341
341
  mask.id = 'dim-highlight-cutouts';
342
342
  /* Within the mask...
343
343
  - black fill = punch, fully transparently, through to the next thing. these are the cutouts to the color.
@@ -357,7 +357,7 @@ export class TimelineOverviewPane extends Common.ObjectWrapper.eventMixin<EventT
357
357
  // `mask` element.
358
358
  // The `mixBlendMode` is set to 'saturation', so this rectangle will completely desaturate the area it covers
359
359
  // within the mask.
360
- const desaturateRect = UI.UIUtils.createSVGChild(this.#dimHighlightSVG, 'rect', 'background') as SVGRectElement;
360
+ const desaturateRect = UI.UIUtils.createSVGChild(this.#dimHighlightSVG, 'rect', 'background');
361
361
  desaturateRect.setAttribute('width', '100%');
362
362
  desaturateRect.setAttribute('height', '100%');
363
363
  desaturateRect.setAttribute('fill', ThemeSupport.ThemeSupport.instance().getComputedValue('--color-background'));
@@ -374,7 +374,7 @@ export class TimelineOverviewPane extends Common.ObjectWrapper.eventMixin<EventT
374
374
 
375
375
  // This polygon is for the bracket beyond the not desaturated area.
376
376
  const bracketColor = ThemeSupport.ThemeSupport.instance().getComputedValue('--sys-color-state-on-header-hover');
377
- const bracket = UI.UIUtils.createSVGChild(this.#dimHighlightSVG, 'polygon') as SVGRectElement;
377
+ const bracket = UI.UIUtils.createSVGChild(this.#dimHighlightSVG, 'polygon');
378
378
  bracket.setAttribute('fill', bracketColor);
379
379
 
380
380
  ThemeSupport.ThemeSupport.instance().addEventListener(ThemeSupport.ThemeChangeEvent.eventName, () => {