chrome-devtools-frontend 1.0.1574367 → 1.0.1575635

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 (107) hide show
  1. package/.stylelintrc.json +1 -1
  2. package/AUTHORS +1 -0
  3. package/docs/contributing/settings-experiments-features.md +5 -2
  4. package/front_end/core/common/Settings.ts +1 -1
  5. package/front_end/core/host/InspectorFrontendHostAPI.ts +7 -1
  6. package/front_end/core/host/InspectorFrontendHostStub.ts +10 -0
  7. package/front_end/core/host/UserMetrics.ts +15 -0
  8. package/front_end/core/root/Runtime.ts +119 -39
  9. package/front_end/core/sdk/CSSMatchedStyles.ts +27 -0
  10. package/front_end/entrypoints/main/MainImpl.ts +25 -7
  11. package/front_end/entrypoints/node_app/app/NodeConnectionsPanel.ts +3 -2
  12. package/front_end/generated/InspectorBackendCommands.ts +37 -0
  13. package/front_end/generated/protocol-mapping.d.ts +257 -0
  14. package/front_end/generated/protocol-proxy-api.d.ts +256 -0
  15. package/front_end/generated/protocol.ts +359 -0
  16. package/front_end/models/ai_assistance/AiConversation.ts +31 -10
  17. package/front_end/models/ai_assistance/AiHistoryStorage.ts +1 -0
  18. package/front_end/models/ai_assistance/agents/AiAgent.ts +29 -5
  19. package/front_end/models/ai_assistance/agents/ContextSelectionAgent.snapshot.txt +73 -0
  20. package/front_end/models/ai_assistance/agents/ContextSelectionAgent.ts +141 -0
  21. package/front_end/models/ai_assistance/agents/StylingAgent.snapshot.txt +3 -3
  22. package/front_end/models/ai_assistance/agents/StylingAgent.ts +5 -4
  23. package/front_end/models/ai_assistance/ai_assistance.ts +2 -0
  24. package/front_end/models/issues_manager/CorsIssue.ts +0 -3
  25. package/front_end/models/javascript_metadata/NativeFunctions.js +5 -0
  26. package/front_end/models/live-metrics/LiveMetrics.ts +31 -51
  27. package/front_end/models/stack_trace/StackTrace.ts +41 -0
  28. package/front_end/panels/ai_assistance/AiAssistancePanel.ts +72 -6
  29. package/front_end/panels/ai_assistance/ai_assistance-meta.ts +1 -0
  30. package/front_end/panels/ai_assistance/components/ChatInput.ts +24 -2
  31. package/front_end/panels/ai_assistance/components/ChatMessage.ts +6 -4
  32. package/front_end/panels/ai_assistance/components/ChatView.ts +5 -6
  33. package/front_end/panels/ai_assistance/components/chatView.css +10 -0
  34. package/front_end/panels/application/StorageView.ts +3 -3
  35. package/front_end/panels/application/preloading/components/preloadingDisabledInfobar.css +1 -1
  36. package/front_end/panels/common/AiCodeGenerationUpgradeDialog.ts +32 -16
  37. package/front_end/panels/common/aiCodeCompletionSummaryToolbar.css +1 -1
  38. package/front_end/panels/console/ConsoleContextSelector.ts +4 -3
  39. package/front_end/panels/console/ConsoleFormat.ts +31 -2
  40. package/front_end/panels/console/ConsoleInsightTeaser.ts +6 -1
  41. package/front_end/panels/elements/ElementsTreeElement.ts +46 -2
  42. package/front_end/panels/elements/ElementsTreeOutline.ts +1 -1
  43. package/front_end/panels/elements/StylePropertyTreeElement.ts +63 -0
  44. package/front_end/panels/elements/StylesSidebarPane.ts +17 -2
  45. package/front_end/panels/elements/stylePropertiesTreeOutline.css +4 -0
  46. package/front_end/panels/issues/AffectedMetadataAllowedSitesView.ts +3 -3
  47. package/front_end/panels/layer_viewer/Layers3DView.ts +2 -1
  48. package/front_end/panels/lighthouse/LighthouseStartView.ts +7 -6
  49. package/front_end/panels/linear_memory_inspector/LinearMemoryInspectorPane.ts +3 -2
  50. package/front_end/panels/network/RequestConditionsDrawer.ts +2 -3
  51. package/front_end/panels/network/SignedExchangeInfoView.ts +3 -4
  52. package/front_end/panels/network/components/HeaderSectionRow.css +1 -1
  53. package/front_end/panels/recorder/components/RecordingView.ts +2 -2
  54. package/front_end/panels/recorder/components/recordingView.css +5 -0
  55. package/front_end/panels/security/SecurityPanelSidebar.ts +10 -9
  56. package/front_end/panels/settings/AISettingsTab.ts +2 -1
  57. package/front_end/panels/settings/KeybindsSettingsTab.ts +3 -5
  58. package/front_end/panels/settings/SettingsScreen.ts +14 -9
  59. package/front_end/panels/settings/settings-meta.ts +1 -0
  60. package/front_end/panels/sources/CallStackSidebarPane.ts +10 -0
  61. package/front_end/panels/sources/DebuggerPlugin.ts +18 -12
  62. package/front_end/panels/sources/ScopeChainSidebarPane.ts +4 -3
  63. package/front_end/panels/sources/SourcesNavigator.ts +2 -2
  64. package/front_end/panels/sources/SourcesPanel.ts +6 -3
  65. package/front_end/panels/sources/TabbedEditorContainer.ts +2 -2
  66. package/front_end/panels/sources/sources-meta.ts +2 -1
  67. package/front_end/panels/timeline/TimelinePanel.ts +2 -1
  68. package/front_end/panels/timeline/TimelineUIUtils.ts +2 -2
  69. package/front_end/panels/timeline/components/DetailsView.ts +7 -7
  70. package/front_end/panels/timeline/components/insights/BaseInsightComponent.ts +2 -2
  71. package/front_end/panels/timeline/overlays/components/EntryLabelOverlay.ts +4 -4
  72. package/front_end/panels/whats_new/ReleaseNoteText.ts +11 -11
  73. package/front_end/panels/whats_new/releaseNoteView.css +1 -1
  74. package/front_end/panels/whats_new/resources/WNDT.md +6 -6
  75. package/front_end/third_party/chromium/README.chromium +1 -1
  76. package/front_end/third_party/puppeteer/README.chromium +2 -2
  77. package/front_end/third_party/puppeteer/package/lib/cjs/puppeteer/cdp/TargetManager.d.ts.map +1 -1
  78. package/front_end/third_party/puppeteer/package/lib/cjs/puppeteer/cdp/TargetManager.js +30 -25
  79. package/front_end/third_party/puppeteer/package/lib/cjs/puppeteer/cdp/TargetManager.js.map +1 -1
  80. package/front_end/third_party/puppeteer/package/lib/cjs/puppeteer/injected/injected.d.ts +1 -1
  81. package/front_end/third_party/puppeteer/package/lib/cjs/puppeteer/util/Mutex.d.ts +2 -2
  82. package/front_end/third_party/puppeteer/package/lib/cjs/puppeteer/util/version.d.ts +1 -1
  83. package/front_end/third_party/puppeteer/package/lib/cjs/puppeteer/util/version.js +1 -1
  84. package/front_end/third_party/puppeteer/package/lib/es5-iife/puppeteer-core-browser.js +30 -21
  85. package/front_end/third_party/puppeteer/package/lib/esm/puppeteer/cdp/TargetManager.d.ts.map +1 -1
  86. package/front_end/third_party/puppeteer/package/lib/esm/puppeteer/cdp/TargetManager.js +31 -26
  87. package/front_end/third_party/puppeteer/package/lib/esm/puppeteer/cdp/TargetManager.js.map +1 -1
  88. package/front_end/third_party/puppeteer/package/lib/esm/puppeteer/util/version.d.ts +1 -1
  89. package/front_end/third_party/puppeteer/package/lib/esm/puppeteer/util/version.js +1 -1
  90. package/front_end/third_party/puppeteer/package/package.json +3 -3
  91. package/front_end/third_party/puppeteer/package/src/cdp/TargetManager.ts +45 -44
  92. package/front_end/third_party/puppeteer/package/src/util/version.ts +1 -1
  93. package/front_end/ui/components/buttons/Button.ts +2 -2
  94. package/front_end/ui/components/panel_feedback/PreviewToggle.ts +1 -1
  95. package/front_end/ui/components/panel_feedback/panelFeedback.css +1 -1
  96. package/front_end/ui/kit/link/Link.ts +31 -0
  97. package/front_end/ui/legacy/LinkContextMenuProvider.ts +3 -5
  98. package/front_end/ui/legacy/components/object_ui/ObjectPopoverHelper.ts +2 -2
  99. package/front_end/ui/legacy/components/object_ui/ObjectPropertiesSection.ts +336 -250
  100. package/front_end/ui/legacy/inspectorCommon.css +0 -1
  101. package/front_end/ui/legacy/legacy.ts +0 -4
  102. package/front_end/ui/visual_logging/KnownContextValues.ts +4 -0
  103. package/front_end/ui/visual_logging/LoggingDriver.ts +10 -6
  104. package/mcp/HostBindings.ts +6 -0
  105. package/package.json +1 -1
  106. package/front_end/ui/legacy/XElement.ts +0 -41
  107. package/front_end/ui/legacy/XLink.ts +0 -128
@@ -145,26 +145,62 @@ interface NodeChildren {
145
145
  properties?: ObjectTreeNode[];
146
146
  internalProperties?: ObjectTreeNode[];
147
147
  arrayRanges?: ArrayGroupTreeNode[];
148
+ accessors?: ObjectTreeNode[];
148
149
  }
149
150
 
150
- abstract class ObjectTreeNodeBase {
151
+ abstract class ObjectTreeNodeBase extends Common.ObjectWrapper.ObjectWrapper<ObjectTreeNodeBase.EventTypes> {
151
152
  #children?: NodeChildren;
152
153
  protected extraProperties: ObjectTreeNode[] = [];
154
+ expanded = false;
153
155
  constructor(
154
156
  readonly parent?: ObjectTreeNodeBase,
155
157
  readonly propertiesMode: ObjectPropertiesMode = ObjectPropertiesMode.OWN_AND_INTERNAL_AND_INHERITED) {
158
+ super();
159
+ }
160
+
161
+ // Performs a pre-order tree traversal over the populated children. If any children need to be populated, callers must
162
+ // do that while walking (pre-order visitation enables that).
163
+ * #walk(maxDepth = -1): Generator<ObjectTreeNodeBase> {
164
+ function* walkChildren(children: ObjectTreeNodeBase[]|undefined): Generator<ObjectTreeNodeBase> {
165
+ if (children) {
166
+ for (const child of children) {
167
+ yield* child.#walk(Math.max(-1, maxDepth - 1));
168
+ }
169
+ }
170
+ }
171
+ yield this;
172
+ if (maxDepth !== 0) {
173
+ yield* walkChildren(this.#children?.properties);
174
+ yield* walkChildren(this.#children?.arrayRanges);
175
+ yield* walkChildren(this.#children?.internalProperties);
176
+ }
177
+ }
178
+
179
+ async expandRecursively(maxDepth: number): Promise<void> {
180
+ for (const node of this.#walk(maxDepth)) {
181
+ await node.populateChildrenIfNeeded();
182
+ node.expanded = true;
183
+ }
184
+ }
185
+
186
+ collapseRecursively(): void {
187
+ for (const node of this.#walk()) {
188
+ node.expanded = false;
189
+ }
156
190
  }
157
191
 
158
192
  abstract get object(): SDK.RemoteObject.RemoteObject|undefined;
159
193
 
160
194
  removeChildren(): void {
161
195
  this.#children = undefined;
196
+ this.dispatchEventToListeners(ObjectTreeNodeBase.Events.CHILDREN_CHANGED);
162
197
  }
163
198
 
164
199
  removeChild(child: ObjectTreeNodeBase): void {
165
200
  remove(this.#children?.arrayRanges, child);
166
201
  remove(this.#children?.internalProperties, child);
167
202
  remove(this.#children?.properties, child);
203
+ this.dispatchEventToListeners(ObjectTreeNodeBase.Events.CHILDREN_CHANGED);
168
204
 
169
205
  function remove<T>(array: T[]|undefined, element: T): void {
170
206
  if (!array) {
@@ -181,14 +217,14 @@ abstract class ObjectTreeNodeBase {
181
217
  return this;
182
218
  }
183
219
 
184
- async children(): Promise<NodeChildren> {
185
- if (!this.#children) {
186
- this.#children = await this.populateChildren();
187
- }
220
+ get children(): NodeChildren|undefined {
188
221
  return this.#children;
189
222
  }
190
223
 
191
- protected async populateChildren(): Promise<NodeChildren> {
224
+ async populateChildrenIfNeeded(): Promise<NodeChildren> {
225
+ if (this.#children) {
226
+ return this.#children;
227
+ }
192
228
  const object = this.object;
193
229
  if (!object) {
194
230
  return {};
@@ -230,10 +266,12 @@ abstract class ObjectTreeNodeBase {
230
266
 
231
267
  const properties = objectProperties?.map(p => new ObjectTreeNode(p, undefined, effectiveParent, undefined));
232
268
  properties?.push(...this.extraProperties);
269
+ properties?.sort(ObjectPropertiesSection.compareProperties);
270
+ const accessors = properties && ObjectTreeNodeBase.getGettersAndSetters(properties);
233
271
 
234
272
  const internalProperties =
235
273
  objectInternalProperties?.map(p => new ObjectTreeNode(p, undefined, effectiveParent, undefined));
236
- return {properties, internalProperties};
274
+ return {properties, internalProperties, accessors};
237
275
  }
238
276
 
239
277
  get hasChildren(): boolean {
@@ -252,6 +290,36 @@ abstract class ObjectTreeNodeBase {
252
290
  addExtraProperties(...properties: SDK.RemoteObject.RemoteObjectProperty[]): void {
253
291
  this.extraProperties.push(...properties.map(p => new ObjectTreeNode(p, undefined, this, undefined)));
254
292
  }
293
+
294
+ static getGettersAndSetters(properties: ObjectTreeNode[]): ObjectTreeNode[] {
295
+ const gettersAndSetters = [];
296
+ for (const property of properties) {
297
+ if (property.property.isOwn) {
298
+ if (property.property.getter) {
299
+ const getterProperty = new SDK.RemoteObject.RemoteObjectProperty(
300
+ 'get ' + property.property.name, property.property.getter, false);
301
+ gettersAndSetters.push(new ObjectTreeNode(getterProperty, property.propertiesMode, property.parent));
302
+ }
303
+ if (property.property.setter) {
304
+ const setterProperty = new SDK.RemoteObject.RemoteObjectProperty(
305
+ 'set ' + property.property.name, property.property.setter, false);
306
+ gettersAndSetters.push(new ObjectTreeNode(setterProperty, property.propertiesMode, property.parent));
307
+ }
308
+ }
309
+ }
310
+ return gettersAndSetters;
311
+ }
312
+ }
313
+
314
+ namespace ObjectTreeNodeBase {
315
+ export const enum Events {
316
+ VALUE_CHANGED = 'value-changed',
317
+ CHILDREN_CHANGED = 'children-changed',
318
+ }
319
+ export interface EventTypes {
320
+ [Events.VALUE_CHANGED]: void;
321
+ [Events.CHILDREN_CHANGED]: void;
322
+ }
255
323
  }
256
324
 
257
325
  export class ObjectTree extends ObjectTreeNodeBase {
@@ -280,7 +348,10 @@ class ArrayGroupTreeNode extends ObjectTreeNodeBase {
280
348
  this.#range = range;
281
349
  }
282
350
 
283
- protected override async populateChildren(): Promise<NodeChildren> {
351
+ override async populateChildrenIfNeeded(): Promise<NodeChildren> {
352
+ if (this.children) {
353
+ return this.children;
354
+ }
284
355
  if (this.#range.count > ArrayGroupingTreeElement.bucketThreshold) {
285
356
  const ranges = await arrayRangeGroups(this.object, this.#range.fromIndex, this.#range.toIndex);
286
357
  const arrayRanges = ranges?.ranges.map(
@@ -303,7 +374,8 @@ class ArrayGroupTreeNode extends ObjectTreeNodeBase {
303
374
  const properties = allProperties.properties?.map(p => new ObjectTreeNode(p, this.propertiesMode, this, undefined));
304
375
  properties?.push(...this.extraProperties);
305
376
  properties?.sort(ObjectPropertiesSection.compareProperties);
306
- return {properties};
377
+ const accessors = properties && ObjectTreeNodeBase.getGettersAndSetters(properties);
378
+ return {properties, accessors};
307
379
  }
308
380
 
309
381
  get singular(): boolean {
@@ -365,6 +437,57 @@ export class ObjectTreeNode extends ObjectTreeNodeBase {
365
437
  override selfOrParentIfInternal(): ObjectTreeNodeBase {
366
438
  return this.name === '[[Prototype]]' ? (this.parent ?? this) : this;
367
439
  }
440
+
441
+ async setValue(expression: string): Promise<void> {
442
+ const property = SDK.RemoteObject.RemoteObject.toCallArgument(this.property.symbol || this.name);
443
+ expression = JavaScriptREPL.wrapObjectLiteral(expression.trim());
444
+
445
+ if (this.property.synthetic) {
446
+ let invalidate = false;
447
+ if (expression) {
448
+ invalidate = await this.property.setSyntheticValue(expression);
449
+ }
450
+ if (invalidate) {
451
+ this.parent?.removeChildren();
452
+ } else {
453
+ this.dispatchEventToListeners(ObjectTreeNodeBase.Events.VALUE_CHANGED);
454
+ }
455
+ return;
456
+ }
457
+
458
+ const parentObject = this.parent?.object as SDK.RemoteObject.RemoteObject;
459
+ const errorPromise =
460
+ expression ? parentObject.setPropertyValue(property, expression) : parentObject.deleteProperty(property);
461
+ const error = await errorPromise;
462
+ if (error) {
463
+ this.dispatchEventToListeners(ObjectTreeNodeBase.Events.VALUE_CHANGED);
464
+ return;
465
+ }
466
+
467
+ if (!expression) {
468
+ this.parent?.removeChild(this);
469
+ } else {
470
+ this.parent?.removeChildren();
471
+ }
472
+ }
473
+
474
+ async invokeGetter(getter: SDK.RemoteObject.RemoteObject): Promise<void> {
475
+ const invokeGetter = `
476
+ function invokeGetter(getter) {
477
+ return Reflect.apply(getter, this, []);
478
+ }`;
479
+ // Also passing a string instead of a Function to avoid coverage implementation messing with it.
480
+ const result = await this.parent
481
+ ?.object
482
+ // @ts-expect-error No way to teach TypeScript to preserve the Function-ness of `getter`.
483
+ ?.callFunction(invokeGetter, [SDK.RemoteObject.RemoteObject.toCallArgument(getter)]);
484
+ if (!result?.object) {
485
+ return;
486
+ }
487
+ this.property.value = result.object;
488
+ this.property.wasThrown = result.wasThrown || false;
489
+ this.dispatchEventToListeners(ObjectTreeNodeBase.Events.VALUE_CHANGED);
490
+ }
368
491
  }
369
492
 
370
493
  export const getObjectPropertiesSectionFrom = (element: Element): ObjectPropertiesSection|undefined => {
@@ -668,8 +791,7 @@ export class ObjectPropertiesSection extends UI.TreeOutline.TreeOutlineInShadow
668
791
  }
669
792
  if (description.length > maxRenderableStringLength) {
670
793
  return html`<span class="value object-value-${subtype || type}" title=${description}><devtools-widget
671
- .widgetConfig=${
672
- widgetConfig(ExpandableTextPropertyValue, {text: description})}></devtools-widget></span>`;
794
+ .widgetConfig=${widgetConfig(ExpandableTextPropertyValue, {text: description})}></devtools-widget></span>`;
673
795
  }
674
796
  const hasPreview = value.preview && showPreview;
675
797
  return html`<span class="value object-value-${subtype || type}" title=${description}>${
@@ -810,6 +932,7 @@ export class RootElement extends UI.TreeOutline.TreeElement {
810
932
  super(contentElement);
811
933
 
812
934
  this.object = object;
935
+ this.object.addEventListener(ObjectTreeNodeBase.Events.CHILDREN_CHANGED, this.onpopulate, this);
813
936
  this.linkifier = linkifier;
814
937
  this.emptyPlaceholder = emptyPlaceholder;
815
938
 
@@ -820,11 +943,6 @@ export class RootElement extends UI.TreeOutline.TreeElement {
820
943
  this.listItemElement.addEventListener('contextmenu', this.onContextMenu.bind(this), false);
821
944
  }
822
945
 
823
- override invalidateChildren(): void {
824
- super.invalidateChildren();
825
- this.object.removeChildren();
826
- }
827
-
828
946
  override onexpand(): void {
829
947
  if (this.treeOutline) {
830
948
  this.treeOutline.element.classList.add('expanded');
@@ -865,6 +983,7 @@ export class RootElement extends UI.TreeOutline.TreeElement {
865
983
  }
866
984
 
867
985
  override async onpopulate(): Promise<void> {
986
+ this.removeChildren();
868
987
  const treeOutline = (this.treeOutline as ObjectPropertiesSection | null);
869
988
  const skipProto = treeOutline ? Boolean(treeOutline.skipProtoInternal) : false;
870
989
  return await ObjectPropertyTreeElement.populate(
@@ -878,7 +997,7 @@ export class RootElement extends UI.TreeOutline.TreeElement {
878
997
  **/
879
998
  export const InitialVisibleChildrenLimit = 200;
880
999
 
881
- export interface TreeElementViewInput {
1000
+ export interface ObjectPropertyViewInput {
882
1001
  startEditing(): unknown;
883
1002
  invokeGetter(getter: SDK.RemoteObject.RemoteObject): unknown;
884
1003
  onAutoComplete(expression: string, filter: string, force: boolean): unknown;
@@ -890,12 +1009,13 @@ export interface TreeElementViewInput {
890
1009
  editingCommitted(detail: string): unknown;
891
1010
  node: ObjectTreeNode;
892
1011
  }
893
- interface TreeElementViewOutput {
1012
+ interface ObjectPropertyViewOutput {
894
1013
  valueElement: Element|undefined;
895
1014
  nameElement: Element|undefined;
896
1015
  }
897
- type TreeElementView = (input: TreeElementViewInput, output: TreeElementViewOutput, target: HTMLElement) => void;
898
- export const TREE_ELEMENT_DEFAULT_VIEW: TreeElementView = (input, output, target) => {
1016
+ type ObjectPropertyView = (input: ObjectPropertyViewInput, output: ObjectPropertyViewOutput, target: HTMLElement) =>
1017
+ void;
1018
+ export const OBJECT_PROPERTY_DEFAULT_VIEW: ObjectPropertyView = (input, output, target) => {
899
1019
  const {property} = input.node;
900
1020
  const isInternalEntries = property.synthetic && input.node.name === '[[Entries]]';
901
1021
  const completionsId = `completions-${input.node.parent?.object?.objectId?.replaceAll('.', '-')}-${input.node.name}`;
@@ -950,7 +1070,10 @@ export const TREE_ELEMENT_DEFAULT_VIEW: TreeElementView = (input, output, target
950
1070
  i18nString(UIStrings.valueUnavailable)}</span>`;
951
1071
  };
952
1072
 
953
- const onDblClick = (event: Event): void => {
1073
+ const onActivate = (event: MouseEvent|KeyboardEvent): void => {
1074
+ if (event instanceof KeyboardEvent && !Platform.KeyboardUtilities.isEnterOrSpaceKey(event)) {
1075
+ return;
1076
+ }
954
1077
  event.consume(true);
955
1078
  if (property.value && !property.value.customPreview() && (property.writable || property.setter)) {
956
1079
  input.startEditing();
@@ -970,7 +1093,8 @@ export const TREE_ELEMENT_DEFAULT_VIEW: TreeElementView = (input, output, target
970
1093
  @commit=${(e: UI.TextPrompt.TextPromptElement.CommitEvent) => input.editingCommitted(e.detail)}
971
1094
  @cancel=${() => input.editingEnded()}
972
1095
  @beforeautocomplete=${onAutoComplete}
973
- @dblclick=${onDblClick}
1096
+ @dblclick=${onActivate}
1097
+ @keydown=${onActivate}
974
1098
  completions=${completionsId}
975
1099
  placeholder=${i18nString(UIStrings.stringIsTooLargeToEdit)}
976
1100
  ?editing=${input.editing}>
@@ -990,26 +1114,166 @@ export const TREE_ELEMENT_DEFAULT_VIEW: TreeElementView = (input, output, target
990
1114
  target);
991
1115
  // clang-format on
992
1116
  };
1117
+
1118
+ export class ObjectPropertyWidget extends UI.Widget.Widget {
1119
+ #highlightChanges: Highlighting.HighlightChange[] = [];
1120
+ #property?: ObjectTreeNode;
1121
+ #nameElement?: Element;
1122
+ #valueElement?: Element;
1123
+ #completions: string[] = [];
1124
+ #editing = false;
1125
+ readonly #view: ObjectPropertyView;
1126
+ #expanded = false;
1127
+ #linkifier: Components.Linkifier.Linkifier|undefined;
1128
+
1129
+ constructor(target?: HTMLElement, view = OBJECT_PROPERTY_DEFAULT_VIEW) {
1130
+ super(target);
1131
+ this.#view = view;
1132
+ }
1133
+
1134
+ get property(): ObjectTreeNode|undefined {
1135
+ return this.#property;
1136
+ }
1137
+ set property(property: ObjectTreeNode) {
1138
+ if (this.#property) {
1139
+ this.#property.removeEventListener(ObjectTreeNodeBase.Events.VALUE_CHANGED, this.requestUpdate, this);
1140
+ this.#property.removeEventListener(ObjectTreeNodeBase.Events.CHILDREN_CHANGED, this.requestUpdate, this);
1141
+ }
1142
+ this.#property = property;
1143
+ this.#property.addEventListener(ObjectTreeNodeBase.Events.VALUE_CHANGED, this.requestUpdate, this);
1144
+ this.#property.addEventListener(ObjectTreeNodeBase.Events.CHILDREN_CHANGED, this.requestUpdate, this);
1145
+ this.requestUpdate();
1146
+ }
1147
+
1148
+ get expanded(): boolean {
1149
+ return this.#expanded;
1150
+ }
1151
+ set expanded(expanded: boolean) {
1152
+ this.#expanded = expanded;
1153
+ this.requestUpdate();
1154
+ }
1155
+
1156
+ get linkifier(): Components.Linkifier.Linkifier|undefined {
1157
+ return this.#linkifier;
1158
+ }
1159
+ set linkifier(linkifier: Components.Linkifier.Linkifier|undefined) {
1160
+ this.#linkifier = linkifier;
1161
+ this.requestUpdate();
1162
+ }
1163
+
1164
+ override performUpdate(): void {
1165
+ if (!this.#property) {
1166
+ return;
1167
+ }
1168
+ const input: ObjectPropertyViewInput = {
1169
+ expanded: this.#expanded,
1170
+ editing: this.#editing,
1171
+ editingEnded: this.#editingEnded.bind(this),
1172
+ editingCommitted: this.#editingCommitted.bind(this),
1173
+ node: this.#property,
1174
+ linkifier: this.#linkifier,
1175
+ completions: this.#editing ? this.#completions : [],
1176
+ onAutoComplete: this.#updateCompletions.bind(this),
1177
+ invokeGetter: this.#invokeGetter.bind(this),
1178
+ startEditing: this.startEditing.bind(this),
1179
+ };
1180
+ const that = this;
1181
+ const output: ObjectPropertyViewOutput = {
1182
+ set nameElement(e: Element|undefined) {
1183
+ that.#nameElement = e;
1184
+ },
1185
+ set valueElement(e: Element|undefined) {
1186
+ that.#valueElement = e;
1187
+ },
1188
+ };
1189
+ this.#view(input, output, this.element);
1190
+ }
1191
+
1192
+ setSearchRegex(regex: RegExp, additionalCssClassName?: string): boolean {
1193
+ let cssClasses = Highlighting.highlightedSearchResultClassName;
1194
+ if (additionalCssClassName) {
1195
+ cssClasses += ' ' + additionalCssClassName;
1196
+ }
1197
+ this.revertHighlightChanges();
1198
+
1199
+ if (this.#nameElement) {
1200
+ this.#applySearch(regex, this.#nameElement, cssClasses);
1201
+ }
1202
+ if (this.property?.object) {
1203
+ const valueType = this.property?.object.type;
1204
+ if (valueType !== 'object' && this.#valueElement) {
1205
+ this.#applySearch(regex, this.#valueElement, cssClasses);
1206
+ }
1207
+ }
1208
+
1209
+ return Boolean(this.#highlightChanges.length);
1210
+ }
1211
+
1212
+ #applySearch(regex: RegExp, element: Element, cssClassName: string): void {
1213
+ const ranges = [];
1214
+ const content = element.textContent || '';
1215
+ regex.lastIndex = 0;
1216
+ let match = regex.exec(content);
1217
+ while (match) {
1218
+ ranges.push(new TextUtils.TextRange.SourceRange(match.index, match[0].length));
1219
+ match = regex.exec(content);
1220
+ }
1221
+ if (ranges.length) {
1222
+ Highlighting.highlightRangesWithStyleClass(element, ranges, cssClassName, this.#highlightChanges);
1223
+ }
1224
+ }
1225
+
1226
+ revertHighlightChanges(): void {
1227
+ Highlighting.revertDomChanges(this.#highlightChanges);
1228
+ this.#highlightChanges = [];
1229
+ }
1230
+
1231
+ async #updateCompletions(expression: string, filter: string, force: boolean): Promise<void> {
1232
+ const suggestions = await TextEditor.JavaScript.completeInContext(expression, filter, force);
1233
+ this.#completions = suggestions.map(v => v.text);
1234
+ this.requestUpdate();
1235
+ }
1236
+
1237
+ get editing(): boolean {
1238
+ return this.#editing;
1239
+ }
1240
+
1241
+ startEditing(): void {
1242
+ this.#editing = true;
1243
+ this.requestUpdate();
1244
+ }
1245
+
1246
+ #editingEnded(): void {
1247
+ this.#completions = [];
1248
+ this.#editing = false;
1249
+ this.requestUpdate();
1250
+ }
1251
+
1252
+ async #editingCommitted(newContent: string): Promise<void> {
1253
+ this.#editingEnded();
1254
+ await this.#property?.setValue(newContent);
1255
+ }
1256
+
1257
+ #invokeGetter(getter: SDK.RemoteObject.RemoteObject): void {
1258
+ void this.#property?.invokeGetter(getter);
1259
+ }
1260
+ }
1261
+
993
1262
  export class ObjectPropertyTreeElement extends UI.TreeOutline.TreeElement {
994
1263
  property: ObjectTreeNode;
995
1264
  override toggleOnClick: boolean;
996
- private highlightChanges: Highlighting.HighlightChange[];
997
1265
  private linkifier: Components.Linkifier.Linkifier|undefined;
998
1266
  private readonly maxNumPropertiesToShow: number;
999
- readOnly!: boolean;
1000
- #editing = false;
1001
- readonly #view: TreeElementView;
1002
- #completions: string[] = [];
1003
- #nameElement: Element|undefined;
1004
- #valueElement: Element|undefined;
1005
- constructor(property: ObjectTreeNode, linkifier?: Components.Linkifier.Linkifier, view = TREE_ELEMENT_DEFAULT_VIEW) {
1267
+ readonly #widget: ObjectPropertyWidget;
1268
+ constructor(property: ObjectTreeNode, linkifier?: Components.Linkifier.Linkifier) {
1006
1269
  // Pass an empty title, the title gets made later in onattach.
1007
1270
  super();
1008
1271
 
1009
- this.#view = view;
1272
+ this.#widget = new ObjectPropertyWidget();
1010
1273
  this.property = property;
1274
+ this.property.addEventListener(ObjectTreeNodeBase.Events.VALUE_CHANGED, this.#updateValue, this);
1275
+ this.property.addEventListener(ObjectTreeNodeBase.Events.CHILDREN_CHANGED, this.#updateChildren, this);
1011
1276
  this.toggleOnClick = true;
1012
- this.highlightChanges = [];
1013
1277
  this.linkifier = linkifier;
1014
1278
  this.maxNumPropertiesToShow = InitialVisibleChildrenLimit;
1015
1279
  this.listItemElement.addEventListener('contextmenu', this.contextMenuFired.bind(this), false);
@@ -1025,7 +1289,7 @@ export class ObjectPropertyTreeElement extends UI.TreeOutline.TreeElement {
1025
1289
  linkifier?: Components.Linkifier.Linkifier,
1026
1290
  emptyPlaceholder?: string|null,
1027
1291
  ): Promise<void> {
1028
- const properties = await value.children();
1292
+ const properties = await value.populateChildrenIfNeeded();
1029
1293
  if (properties.arrayRanges) {
1030
1294
  await ArrayGroupingTreeElement.populate(treeElement, properties, linkifier);
1031
1295
  } else {
@@ -1035,8 +1299,8 @@ export class ObjectPropertyTreeElement extends UI.TreeOutline.TreeElement {
1035
1299
  }
1036
1300
 
1037
1301
  static populateWithProperties(
1038
- treeNode: UI.TreeOutline.TreeElement, {properties, internalProperties}: NodeChildren, skipProto: boolean,
1039
- skipGettersAndSetters: boolean, linkifier?: Components.Linkifier.Linkifier,
1302
+ treeNode: UI.TreeOutline.TreeElement, {properties, internalProperties, accessors}: NodeChildren,
1303
+ skipProto: boolean, skipGettersAndSetters: boolean, linkifier?: Components.Linkifier.Linkifier,
1040
1304
  emptyPlaceholder?: string|null): void {
1041
1305
  properties?.sort(ObjectPropertiesSection.compareProperties);
1042
1306
 
@@ -1048,27 +1312,12 @@ export class ObjectPropertyTreeElement extends UI.TreeOutline.TreeElement {
1048
1312
  treeNode.appendChild(treeElement);
1049
1313
  }
1050
1314
 
1051
- const tailProperties = [];
1052
1315
  for (const property of properties ?? []) {
1053
1316
  if (treeNode instanceof ObjectPropertyTreeElement &&
1054
1317
  !ObjectPropertiesSection.isDisplayableProperty(property.property, treeNode.property?.property)) {
1055
1318
  continue;
1056
1319
  }
1057
1320
 
1058
- // FIXME move into node
1059
- if (property.property.isOwn && !skipGettersAndSetters) {
1060
- if (property.property.getter) {
1061
- const getterProperty = new SDK.RemoteObject.RemoteObjectProperty(
1062
- 'get ' + property.property.name, property.property.getter, false);
1063
- tailProperties.push(new ObjectTreeNode(getterProperty, property.propertiesMode, property.parent));
1064
- }
1065
- if (property.property.setter) {
1066
- const setterProperty = new SDK.RemoteObject.RemoteObjectProperty(
1067
- 'set ' + property.property.name, property.property.setter, false);
1068
- tailProperties.push(new ObjectTreeNode(setterProperty, property.propertiesMode, property.parent));
1069
- }
1070
- }
1071
-
1072
1321
  const canShowProperty = property.property.getter || !property.property.isAccessorProperty();
1073
1322
  if (canShowProperty) {
1074
1323
  const element = new ObjectPropertyTreeElement(property, linkifier);
@@ -1081,8 +1330,9 @@ export class ObjectPropertyTreeElement extends UI.TreeOutline.TreeElement {
1081
1330
  treeNode.appendChild(element);
1082
1331
  }
1083
1332
  }
1084
- for (let i = 0; i < tailProperties.length; ++i) {
1085
- treeNode.appendChild(new ObjectPropertyTreeElement(tailProperties[i], linkifier));
1333
+
1334
+ for (const accessor of accessors ?? []) {
1335
+ treeNode.appendChild(new ObjectPropertyTreeElement(accessor, linkifier));
1086
1336
  }
1087
1337
 
1088
1338
  for (const property of internalProperties ?? []) {
@@ -1099,6 +1349,28 @@ export class ObjectPropertyTreeElement extends UI.TreeOutline.TreeElement {
1099
1349
  ObjectPropertyTreeElement.appendEmptyPlaceholderIfNeeded(treeNode, emptyPlaceholder);
1100
1350
  }
1101
1351
 
1352
+ revertHighlightChanges(): void {
1353
+ this.#widget.revertHighlightChanges();
1354
+ }
1355
+ setSearchRegex(regex: RegExp, additionalCssClassName?: string): boolean {
1356
+ return this.#widget.setSearchRegex(regex, additionalCssClassName);
1357
+ }
1358
+
1359
+ // This is called by layout tests
1360
+ startEditing(): void {
1361
+ this.#widget.startEditing();
1362
+ }
1363
+
1364
+ // This is called by layout tests
1365
+ get editing(): boolean {
1366
+ return this.#widget.editing;
1367
+ }
1368
+
1369
+ // This is called by layout tests
1370
+ async applyExpression(expression: string): Promise<void> {
1371
+ await this.property.setValue(expression);
1372
+ }
1373
+
1102
1374
  private static appendEmptyPlaceholderIfNeeded(treeNode: UI.TreeOutline.TreeElement, emptyPlaceholder?: string|null):
1103
1375
  void {
1104
1376
  if (treeNode.childCount()) {
@@ -1111,44 +1383,6 @@ export class ObjectPropertyTreeElement extends UI.TreeOutline.TreeElement {
1111
1383
  treeNode.appendChild(infoElement);
1112
1384
  }
1113
1385
 
1114
- get nameElement(): Element|undefined {
1115
- return this.#nameElement;
1116
- }
1117
-
1118
- setSearchRegex(regex: RegExp, additionalCssClassName?: string): boolean {
1119
- let cssClasses = Highlighting.highlightedSearchResultClassName;
1120
- if (additionalCssClassName) {
1121
- cssClasses += ' ' + additionalCssClassName;
1122
- }
1123
- this.revertHighlightChanges();
1124
-
1125
- if (this.#nameElement) {
1126
- this.applySearch(regex, this.#nameElement, cssClasses);
1127
- }
1128
- if (this.property.object) {
1129
- const valueType = this.property.object.type;
1130
- if (valueType !== 'object' && this.#valueElement) {
1131
- this.applySearch(regex, this.#valueElement, cssClasses);
1132
- }
1133
- }
1134
-
1135
- return Boolean(this.highlightChanges.length);
1136
- }
1137
-
1138
- private applySearch(regex: RegExp, element: Element, cssClassName: string): void {
1139
- const ranges = [];
1140
- const content = element.textContent || '';
1141
- regex.lastIndex = 0;
1142
- let match = regex.exec(content);
1143
- while (match) {
1144
- ranges.push(new TextUtils.TextRange.SourceRange(match.index, match[0].length));
1145
- match = regex.exec(content);
1146
- }
1147
- if (ranges.length) {
1148
- Highlighting.highlightRangesWithStyleClass(element, ranges, cssClassName, this.highlightChanges);
1149
- }
1150
- }
1151
-
1152
1386
  private showAllPropertiesElementSelected(element: UI.TreeOutline.TreeElement): boolean {
1153
1387
  this.removeChild(element);
1154
1388
  this.children().forEach(x => {
@@ -1171,16 +1405,10 @@ export class ObjectPropertyTreeElement extends UI.TreeOutline.TreeElement {
1171
1405
  this.appendChild(showAllPropertiesButton);
1172
1406
  }
1173
1407
 
1174
- revertHighlightChanges(): void {
1175
- Highlighting.revertDomChanges(this.highlightChanges);
1176
- this.highlightChanges = [];
1177
- }
1178
-
1179
1408
  override async onpopulate(): Promise<void> {
1180
1409
  const treeOutline = (this.treeOutline as ObjectPropertiesSection | null);
1181
1410
  const skipProto = treeOutline ? Boolean(treeOutline.skipProtoInternal) : false;
1182
1411
  this.removeChildren();
1183
- this.property.removeChildren();
1184
1412
 
1185
1413
  if (this.property.object) {
1186
1414
  await ObjectPropertyTreeElement.populate(this, this.property, skipProto, false, this.linkifier);
@@ -1190,57 +1418,29 @@ export class ObjectPropertyTreeElement extends UI.TreeOutline.TreeElement {
1190
1418
  }
1191
1419
  }
1192
1420
 
1193
- override onenter(): boolean {
1194
- if (this.property.object && !this.property.object.customPreview() &&
1195
- (this.property.property.writable || this.property.property.setter)) {
1196
- this.startEditing();
1197
- return true;
1198
- }
1199
- return false;
1200
- }
1201
-
1202
1421
  override onattach(): void {
1203
- this.performUpdate();
1204
1422
  this.updateExpandable();
1423
+ this.#widget.markAsRoot();
1424
+ this.#widget.show(this.listItemElement);
1425
+ this.#widget.property = this.property;
1426
+ this.#widget.linkifier = this.linkifier;
1205
1427
  }
1206
1428
 
1207
1429
  override onexpand(): void {
1208
- this.performUpdate();
1430
+ this.#widget.expanded = true;
1209
1431
  }
1210
1432
 
1211
1433
  override oncollapse(): void {
1212
- this.performUpdate();
1434
+ this.#widget.expanded = false;
1213
1435
  }
1214
1436
 
1215
- async #updateCompletions(expression: string, filter: string, force: boolean): Promise<void> {
1216
- const suggestions = await TextEditor.JavaScript.completeInContext(expression, filter, force);
1217
- this.#completions = suggestions.map(v => v.text);
1218
- this.performUpdate();
1437
+ #updateValue(): void {
1438
+ this.updateExpandable();
1219
1439
  }
1220
1440
 
1221
- performUpdate(): void {
1222
- const input: TreeElementViewInput = {
1223
- expanded: this.expanded,
1224
- editing: this.#editing,
1225
- editingEnded: this.editingEnded.bind(this),
1226
- editingCommitted: this.editingCommitted.bind(this),
1227
- node: this.property,
1228
- linkifier: this.linkifier,
1229
- completions: this.#editing ? this.#completions : [],
1230
- onAutoComplete: this.#updateCompletions.bind(this),
1231
- invokeGetter: this.onInvokeGetterClick.bind(this),
1232
- startEditing: this.startEditing.bind(this),
1233
- };
1234
- const that = this;
1235
- const output: TreeElementViewOutput = {
1236
- set nameElement(e: Element|undefined) {
1237
- that.#nameElement = e;
1238
- },
1239
- set valueElement(e: Element|undefined) {
1240
- that.#valueElement = e;
1241
- },
1242
- };
1243
- this.#view(input, output, this.listItemElement);
1441
+ #updateChildren(): void {
1442
+ this.removeChildren();
1443
+ void this.onpopulate();
1244
1444
  }
1245
1445
 
1246
1446
  getContextMenu(event: Event): UI.ContextMenu.ContextMenu {
@@ -1284,115 +1484,6 @@ export class ObjectPropertyTreeElement extends UI.TreeOutline.TreeElement {
1284
1484
  void contextMenu.show();
1285
1485
  }
1286
1486
 
1287
- get editing(): boolean {
1288
- return this.#editing;
1289
- }
1290
-
1291
- private startEditing(): void {
1292
- if (!this.readOnly) {
1293
- this.#editing = true;
1294
- this.performUpdate();
1295
- }
1296
- }
1297
-
1298
- private editingEnded(): void {
1299
- this.#completions = [];
1300
- this.#editing = false;
1301
- this.performUpdate();
1302
- this.updateExpandable();
1303
- this.select();
1304
- }
1305
-
1306
- private async editingCommitted(newContent: string): Promise<void> {
1307
- this.editingEnded();
1308
- await this.applyExpression(newContent);
1309
- }
1310
-
1311
- private promptKeyDown(originalContent: string, event: Event): void {
1312
- const keyboardEvent = (event as KeyboardEvent);
1313
- if (keyboardEvent.key === 'Enter') {
1314
- keyboardEvent.consume();
1315
- void this.editingCommitted(originalContent);
1316
- return;
1317
- }
1318
- if (keyboardEvent.key === Platform.KeyboardUtilities.ESCAPE_KEY) {
1319
- keyboardEvent.consume();
1320
- this.editingEnded();
1321
- return;
1322
- }
1323
- }
1324
-
1325
- private async applyExpression(expression: string): Promise<void> {
1326
- const property = SDK.RemoteObject.RemoteObject.toCallArgument(this.property.property.symbol || this.property.name);
1327
- expression = JavaScriptREPL.wrapObjectLiteral(expression.trim());
1328
-
1329
- if (this.property.property.synthetic) {
1330
- let invalidate = false;
1331
- if (expression) {
1332
- invalidate = await this.property.property.setSyntheticValue(expression);
1333
- }
1334
- if (invalidate) {
1335
- const parent = this.parent;
1336
- if (parent) {
1337
- parent.invalidateChildren();
1338
- void parent.onpopulate();
1339
- }
1340
- } else {
1341
- this.performUpdate();
1342
- }
1343
- return;
1344
- }
1345
-
1346
- const parentObject = this.property.parent?.object as SDK.RemoteObject.RemoteObject;
1347
- const errorPromise =
1348
- expression ? parentObject.setPropertyValue(property, expression) : parentObject.deleteProperty(property);
1349
- const error = await errorPromise;
1350
- if (error) {
1351
- this.performUpdate();
1352
- return;
1353
- }
1354
-
1355
- if (!expression) {
1356
- // The property was deleted, so remove this tree element.
1357
- this.parent?.removeChild(this);
1358
- this.property.parent?.removeChild(this.property);
1359
- } else {
1360
- // Call updateSiblings since their value might be based on the value that just changed.
1361
- const parent = this.parent;
1362
- if (parent) {
1363
- parent.invalidateChildren();
1364
- this.property.parent?.removeChildren();
1365
- void parent.onpopulate();
1366
- }
1367
- }
1368
- }
1369
-
1370
- override invalidateChildren(): void {
1371
- super.invalidateChildren();
1372
- this.property.removeChildren();
1373
- }
1374
-
1375
- async onInvokeGetterClick(getter: SDK.RemoteObject.RemoteObject): Promise<void> {
1376
- const invokeGetter = `
1377
- function invokeGetter(getter) {
1378
- return Reflect.apply(getter, this, []);
1379
- }`;
1380
- // Also passing a string instead of a Function to avoid coverage implementation messing with it.
1381
- const result = await this.property.parent
1382
- ?.object
1383
- // @ts-expect-error No way to teach TypeScript to preserve the Function-ness of `getter`.
1384
- ?.callFunction(invokeGetter, [SDK.RemoteObject.RemoteObject.toCallArgument(getter)]);
1385
- if (!result?.object) {
1386
- return;
1387
- }
1388
- this.property.property.value = result.object;
1389
- this.property.property.wasThrown = result.wasThrown || false;
1390
-
1391
- this.performUpdate();
1392
- this.invalidateChildren();
1393
- this.updateExpandable();
1394
- }
1395
-
1396
1487
  private updateExpandable(): void {
1397
1488
  if (this.property.object) {
1398
1489
  this.setExpandable(
@@ -1549,6 +1640,7 @@ export class ArrayGroupingTreeElement extends UI.TreeOutline.TreeElement {
1549
1640
  constructor(child: ArrayGroupTreeNode, linkifier?: Components.Linkifier.Linkifier) {
1550
1641
  super(Platform.StringUtilities.sprintf('[%d … %d]', child.range.fromIndex, child.range.toIndex), true);
1551
1642
  this.#child = child;
1643
+ this.#child.addEventListener(ObjectTreeNodeBase.Events.CHILDREN_CHANGED, this.onpopulate, this);
1552
1644
  this.toggleOnClick = true;
1553
1645
  this.linkifier = linkifier;
1554
1646
  }
@@ -1574,14 +1666,8 @@ export class ArrayGroupingTreeElement extends UI.TreeOutline.TreeElement {
1574
1666
  ObjectPropertyTreeElement.populateWithProperties(treeNode, children, false, false, linkifier);
1575
1667
  }
1576
1668
 
1577
- override invalidateChildren(): void {
1578
- super.invalidateChildren();
1579
- this.#child.removeChildren();
1580
- }
1581
-
1582
1669
  override async onpopulate(): Promise<void> {
1583
1670
  this.removeChildren();
1584
- this.#child.removeChildren();
1585
1671
  await ObjectPropertyTreeElement.populate(this, this.#child, false, false, this.linkifier);
1586
1672
  }
1587
1673