chrome-devtools-frontend 1.0.1646714 → 1.0.1649421

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 (42) hide show
  1. package/extension-api/ExtensionAPI.d.ts +26 -0
  2. package/front_end/core/common/Debouncer.ts +9 -1
  3. package/front_end/core/sdk/CSSMatchedStyles.ts +3 -1
  4. package/front_end/core/sdk/CSSMetadata.ts +1 -0
  5. package/front_end/core/sdk/CSSPropertyParserMatchers.ts +65 -0
  6. package/front_end/generated/InspectorBackendCommands.ts +3 -2
  7. package/front_end/generated/SupportedCSSProperties.js +533 -19
  8. package/front_end/generated/protocol.ts +26 -4
  9. package/front_end/models/ai_assistance/agents/AiAgent.ts +8 -1
  10. package/front_end/models/ai_assistance/agents/PerformanceAgent.ts +7 -0
  11. package/front_end/models/extensions/ExtensionAPI.ts +47 -21
  12. package/front_end/models/javascript_metadata/NativeFunctions.js +5 -1
  13. package/front_end/panels/ai_assistance/AiAssistancePanel.ts +39 -1
  14. package/front_end/panels/ai_assistance/ai_assistance.ts +1 -0
  15. package/front_end/panels/ai_assistance/components/AIv2MarkdownRenderer.ts +228 -0
  16. package/front_end/panels/ai_assistance/components/ChatMessage.ts +39 -2
  17. package/front_end/panels/application/ApplicationPanelSidebar.ts +4 -0
  18. package/front_end/panels/application/CookieItemsView.ts +55 -6
  19. package/front_end/panels/application/DOMStorageItemsView.ts +43 -8
  20. package/front_end/panels/application/KeyValueStorageItemsView.ts +17 -9
  21. package/front_end/panels/elements/StylePropertyTreeElement.ts +73 -16
  22. package/front_end/panels/elements/StylesAiCodeCompletionProvider.ts +9 -1
  23. package/front_end/panels/elements/StylesSidebarPane.ts +22 -8
  24. package/front_end/panels/layer_viewer/PaintProfilerView.ts +309 -182
  25. package/front_end/panels/layer_viewer/paintProfiler.css +27 -23
  26. package/front_end/panels/layers/LayerPaintProfilerView.ts +86 -29
  27. package/front_end/panels/sources/WatchExpressionsSidebarPane.ts +210 -356
  28. package/front_end/panels/sources/watchExpressionsSidebarPane.css +7 -0
  29. package/front_end/panels/timeline/TimelinePaintProfilerView.ts +5 -5
  30. package/front_end/panels/timeline/components/NetworkTrackWidget.ts +122 -0
  31. package/front_end/panels/timeline/components/components.ts +2 -0
  32. package/front_end/panels/timeline/components/networkTrackWidget.css +31 -0
  33. package/front_end/panels/timeline/timeline.ts +2 -0
  34. package/front_end/third_party/chromium/README.chromium +1 -1
  35. package/front_end/ui/components/buttons/FloatingButton.ts +17 -2
  36. package/front_end/ui/components/buttons/floatingButton.css +2 -2
  37. package/front_end/ui/components/text_editor/AiCodeCompletionProvider.ts +1 -1
  38. package/front_end/ui/legacy/TextPrompt.ts +24 -18
  39. package/front_end/ui/legacy/Treeoutline.ts +31 -4
  40. package/front_end/ui/legacy/components/cookie_table/CookiesTable.ts +50 -24
  41. package/front_end/ui/visual_logging/KnownContextValues.ts +5 -0
  42. package/package.json +1 -1
@@ -1,7 +1,6 @@
1
1
  // Copyright 2021 The Chromium Authors
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
- /* eslint-disable @devtools/no-imperative-dom-api */
5
4
 
6
5
  /*
7
6
  * Copyright (C) IBM Corp. 2009 All rights reserved.
@@ -43,13 +42,12 @@ import * as Formatter from '../../models/formatter/formatter.js';
43
42
  import * as SourceMapScopes from '../../models/source_map_scopes/source_map_scopes.js';
44
43
  import * as StackTrace from '../../models/stack_trace/stack_trace.js';
45
44
  import * as Buttons from '../../ui/components/buttons/buttons.js';
46
- import * as TextEditor from '../../ui/components/text_editor/text_editor.js';
47
45
  import * as ObjectUI from '../../ui/legacy/components/object_ui/object_ui.js';
48
46
  // eslint-disable-next-line @devtools/es-modules-import
49
47
  import objectValueStyles from '../../ui/legacy/components/object_ui/objectValue.css.js';
50
48
  import * as Components from '../../ui/legacy/components/utils/utils.js';
51
49
  import * as UI from '../../ui/legacy/legacy.js';
52
- import {Directives, html, render, type TemplateResult} from '../../ui/lit/lit.js';
50
+ import {Directives, html, nothing, render, type TemplateResult} from '../../ui/lit/lit.js';
53
51
  import * as VisualLogging from '../../ui/visual_logging/visual_logging.js';
54
52
 
55
53
  import {UISourceCodeFrame} from './UISourceCodeFrame.js';
@@ -88,26 +86,27 @@ const UIStrings = {
88
86
  * @description A context menu item in the Watch Expressions Sidebar Pane of the Sources panel and Network pane request.
89
87
  */
90
88
  copyValue: 'Copy value',
91
- /**
92
- * @description announcement for when watch expression is deleted
93
- */
94
- watchExpressionDeleted: 'Watch expression deleted',
95
89
  } as const;
96
90
  const str_ = i18n.i18n.registerUIStrings('panels/sources/WatchExpressionsSidebarPane.ts', UIStrings);
97
91
  const i18nString = i18n.i18n.getLocalizedString.bind(undefined, str_);
98
92
  let watchExpressionsSidebarPaneInstance: WatchExpressionsSidebarPane;
99
- const {repeat} = Directives;
93
+ const {classMap, ifDefined} = Directives;
100
94
  interface ViewInput {
95
+ linkifier: Components.Linkifier.Linkifier;
96
+ onFinishEditing(watchExpression: WatchExpression, detail: string|null): void;
97
+ onStartEditing(watchExpression: WatchExpression): void;
98
+ onDelete(watchExpression: WatchExpression): void;
101
99
  onCopyWatchExpression(watchExpression: WatchExpression): void;
102
100
  onDeleteAll(): unknown;
103
101
  onAddExpression(): unknown;
102
+ onExpand(e: WatchExpression, expanded: boolean): unknown;
104
103
  watchExpressions: WatchExpression[];
105
104
  }
106
105
  type View = (input: ViewInput, output: object, target: HTMLElement) => void;
107
106
  export const DEFAULT_VIEW: View = (input, output, target) => {
108
107
  const onContextMenu = (watchExpression: WatchExpression|undefined, event: Event): void => {
109
108
  const contextMenu = new UI.ContextMenu.ContextMenu(event);
110
- const isEditing = input.watchExpressions.some(e => e.isEditing());
109
+ const isEditing = input.watchExpressions.some(e => e.editing);
111
110
 
112
111
  if (!isEditing) {
113
112
  contextMenu.debugSection().appendItem(
@@ -121,14 +120,14 @@ export const DEFAULT_VIEW: View = (input, output, target) => {
121
120
  }
122
121
 
123
122
  if (watchExpression) {
124
- if (!watchExpression.isEditing()) {
125
- contextMenu.editSection().appendItem(
126
- i18nString(UIStrings.deleteWatchExpression), watchExpression.updateExpression.bind(watchExpression, null),
127
- {jslogContext: 'delete-watch-expression'});
123
+ if (!watchExpression.editing) {
124
+ contextMenu.editSection().appendItem(i18nString(UIStrings.deleteWatchExpression),
125
+ () => input.onDelete(watchExpression),
126
+ {jslogContext: 'delete-watch-expression'});
128
127
  }
129
128
 
130
- if (!watchExpression.isEditing() && watchExpression.result &&
131
- (watchExpression.result.type === 'number' || watchExpression.result.type === 'string')) {
129
+ if (!watchExpression.editing && watchExpression.result &&
130
+ (watchExpression.result.object.type === 'number' || watchExpression.result.object.type === 'string')) {
132
131
  contextMenu.clipboardSection().appendItem(
133
132
  i18nString(UIStrings.copyValue), () => input.onCopyWatchExpression(watchExpression),
134
133
  {jslogContext: 'copy-watch-expression-value'});
@@ -140,10 +139,78 @@ export const DEFAULT_VIEW: View = (input, output, target) => {
140
139
  event.consume();
141
140
  };
142
141
 
143
- for (const watchExpression of input.watchExpressions) {
144
- watchExpression.treeElement().listItemElement.oncontextmenu = onContextMenu.bind(undefined, watchExpression);
145
- watchExpression.treeElement().childrenListElement.oncontextmenu = onContextMenu.bind(undefined, watchExpression);
146
- }
142
+ const onExpressionKeydown = (expression: WatchExpression, event: KeyboardEvent): void => {
143
+ if (event.key === 'Enter' && !expression.editing) {
144
+ event.consume(true);
145
+ input.onStartEditing(expression);
146
+ } else if (event.key === 'Delete' && !expression.editing) {
147
+ event.consume(true);
148
+ input.onDelete(expression);
149
+ }
150
+ };
151
+
152
+ const renderNameElement = (e: WatchExpression): Element => {
153
+ const nameElement = ObjectUI.ObjectPropertiesSection.ObjectPropertiesSection.createNameElement(e.expression);
154
+ UI.Tooltip.Tooltip.install(nameElement as HTMLElement, e.expression);
155
+ return nameElement;
156
+ };
157
+
158
+ const renderTreeElement = (e: WatchExpression): TemplateResult =>
159
+ // clang-format off
160
+ html`<li
161
+ class=${classMap({'watch-expression-tree-item': true, 'watch-expression-editing': e.editing })}
162
+ @keydown=${onExpressionKeydown.bind(undefined, e)}
163
+ @expand=${(event: UI.TreeOutline.TreeViewElement.ExpandEvent) => input.onExpand(e, event.detail.expanded)}
164
+ role=treeitem>
165
+ <devtools-prompt
166
+ value=${e.expression ?? ''}
167
+ @commit=${(event: UI.TextPrompt.TextPromptElement.CommitEvent) => input.onFinishEditing(e, event.detail)}
168
+ @cancel=${() => input.onFinishEditing(e, null)}
169
+ ?editing=${e.editing}
170
+ @mousedown=${(event: Event) => e.editing && event.stopPropagation()}
171
+ @click=${(event: Event) => e.editing && event.stopPropagation()}
172
+ @dblclick=${(event: Event) => e.editing && event.stopPropagation()}
173
+ class=${classMap({monospace: true, 'watch-expression': true,
174
+ 'watch-expression-text-prompt-proxy': e.editing})}>
175
+ <div class=${classMap({
176
+ 'watch-expression-header': true,
177
+ 'watch-expression-object-header': !e.exceptionDetails && e.result !== undefined &&
178
+ e.result.hasChildren && !e.result.object.customPreview(),
179
+ })}
180
+ @contextmenu=${onContextMenu.bind(undefined, e)}
181
+ @dblclick=${() => input.onStartEditing(e)}>
182
+ <div class=${classMap({'watch-expression-title': true,
183
+ 'tree-element-title': true,
184
+ dimmed: Boolean(e.exceptionDetails) && !e.result})}>
185
+ <devtools-button
186
+ .data=${{
187
+ variant: Buttons.Button.Variant.ICON,
188
+ iconName: 'bin',
189
+ size: Buttons.Button.Size.SMALL,
190
+ jslogContext: 'delete-watch-expression',
191
+ } as Buttons.Button.ButtonData}
192
+ class=watch-expression-delete-button
193
+ title=${i18nString(UIStrings.deleteWatchExpression)}
194
+ @click=${() => input.onDelete(e)}></devtools-button>
195
+ ${renderNameElement(e)}<span class=watch-expressions-separator>: </span>${e.exceptionDetails || !e.result
196
+ ? html`<span
197
+ class="watch-expression-error value"
198
+ title=${ifDefined(e.exceptionDetails?.exception?.description)}
199
+ >${i18nString(UIStrings.notAvailable)}</span>`
200
+ : ObjectUI.ObjectPropertiesSection.ObjectPropertiesSection.createPropertyValueWithCustomSupport(
201
+ e.result.object, Boolean(e.exceptionDetails), false /* showPreview */, input.linkifier)}
202
+ </div>
203
+ </div>
204
+ </devtools-prompt>
205
+ ${e.editing || !e.result || e.exceptionDetails ||
206
+ !e.result.hasChildren || e.result.object.customPreview() ? nothing : html`
207
+ <ul role=group>
208
+ ${ObjectUI.ObjectPropertiesSection.ObjectPropertyTreeElement.createPropertyNodes(
209
+ e.result.children ?? {}, true /* skipProto */, true /* skipGettersAndSetters */).map(
210
+ node => html`<devtools-tree-wrapper .treeElement=${node}></devtools-tree-wrapper>`)}
211
+ </ul>`}
212
+ </li>`;
213
+ // clang-format on
147
214
 
148
215
  render(
149
216
  // clang-format off
@@ -152,13 +219,12 @@ export const DEFAULT_VIEW: View = (input, output, target) => {
152
219
  ? html`<div class=gray-info-message tabindex=-1 >
153
220
  ${i18nString(UIStrings.noWatchExpressions)}
154
221
  </div>`
155
- : html`<devtools-tree hide-overflow show-selection-on-keyboard-focus .template=${html`
222
+ : html`<devtools-tree autofocus hide-overflow show-selection-on-keyboard-focus toggle-on-click .template=${html`
156
223
  <ul role=tree class="source-code object-properties-section">
157
224
  <style>${ObjectUI.ObjectPropertiesSection.objectValueStyles}</style>
158
225
  <style>${ObjectUI.ObjectPropertiesSection.objectPropertiesSectionStyles}</style>
159
226
  <style>${watchExpressionsSidebarPaneStyles}</style>
160
- ${repeat(input.watchExpressions,
161
- e => html`<devtools-tree-wrapper .treeElement=${e.treeElement()}></devtools-tree-wrapper>`)}
227
+ ${input.watchExpressions.map(renderTreeElement)}
162
228
  </ul>`}>
163
229
  </devtools-tree>`
164
230
  }`,
@@ -181,7 +247,7 @@ export class WatchExpressionsSidebarPane extends UI.Widget.VBox implements UI.Ac
181
247
  #watchExpressionsSetting: Common.Settings.Setting<string[]>;
182
248
  private readonly linkifier: Components.Linkifier.Linkifier;
183
249
  #view: View;
184
- #expandControllers = new Map<string, ObjectUI.ObjectPropertiesSection.ObjectTreeExpansionTracker>();
250
+ #expansionTrackers = new Map<string, ObjectUI.ObjectPropertiesSection.ObjectTreeExpansionTracker>();
185
251
  constructor() {
186
252
  super({useShadowDom: true});
187
253
  this.registerRequiredCSS(watchExpressionsSidebarPaneStyles, objectValueStyles);
@@ -196,7 +262,7 @@ export class WatchExpressionsSidebarPane extends UI.Widget.VBox implements UI.Ac
196
262
  StackTrace.StackTrace.DebuggableFrameFlavor, this.#refreshExpressions, this);
197
263
  this.linkifier = new Components.Linkifier.Linkifier();
198
264
  this.#view = DEFAULT_VIEW;
199
- this.#refreshExpressions();
265
+ void this.#refreshExpressions();
200
266
  }
201
267
 
202
268
  static instance(): WatchExpressionsSidebarPane {
@@ -236,7 +302,7 @@ export class WatchExpressionsSidebarPane extends UI.Widget.VBox implements UI.Ac
236
302
  private saveExpressions(): void {
237
303
  const toSave = [];
238
304
  for (let i = 0; i < this.#watchExpressions.length; i++) {
239
- const expression = this.#watchExpressions[i].expression();
305
+ const expression = this.#watchExpressions[i].expression;
240
306
  if (expression) {
241
307
  toSave.push(expression);
242
308
  }
@@ -248,104 +314,117 @@ export class WatchExpressionsSidebarPane extends UI.Widget.VBox implements UI.Ac
248
314
  private async addButtonClicked(event?: Event): Promise<void> {
249
315
  event?.consume(true);
250
316
  await UI.ViewManager.ViewManager.instance().showView('sources.watch');
251
- const watchExpression = this.createWatchExpression(null);
317
+ const watchExpression = new WatchExpression();
318
+ this.#watchExpressions.push(watchExpression);
319
+ watchExpression.editing = true;
252
320
  this.requestUpdate();
253
- // This synchronization is only needed because startEditing is imperative and
254
- // requires the tree element to be in the DOM. This can be removed once the
255
- // prompt is migrated to Lit.
256
- await this.updateComplete;
257
- watchExpression.startEditing();
258
321
  }
259
322
 
260
323
  private refreshButtonClicked(event: Event): void {
261
324
  event.consume(true);
262
- this.#refreshExpressions();
325
+ void this.#refreshExpressions();
263
326
  }
264
-
265
- #refreshExpressions(): void {
327
+ async #refreshExpressions(): Promise<void> {
266
328
  this.linkifier.reset();
267
- for (const expression of this.#watchExpressions) {
268
- expression.removeEventListener(Events.EXPRESSION_UPDATED, this.watchExpressionUpdated, this);
269
- }
270
329
  this.#watchExpressions = [];
271
330
  const watchExpressionStrings = this.#watchExpressionsSetting.get();
272
331
 
273
- const oldExpandControllers = this.#expandControllers;
274
- this.#expandControllers = new Map();
332
+ const oldExpansionTrackers = this.#expansionTrackers;
333
+ this.#expansionTrackers = new Map();
275
334
 
335
+ const promises = [];
276
336
  for (let i = 0; i < watchExpressionStrings.length; ++i) {
277
337
  const expression = watchExpressionStrings[i];
278
338
  if (!expression) {
279
339
  continue;
280
340
  }
281
341
 
282
- const controller = oldExpandControllers.get(expression);
283
- if (controller) {
284
- this.#expandControllers.set(expression, controller);
342
+ const tracker = oldExpansionTrackers.get(expression);
343
+ if (tracker) {
344
+ this.#expansionTrackers.set(expression, tracker);
285
345
  }
286
346
 
287
- this.createWatchExpression(expression);
347
+ const watchExpression = new WatchExpression();
348
+ this.#watchExpressions.push(watchExpression);
349
+ promises.push(watchExpression.setExpression(expression, this.#getExpansionTracker(expression)));
288
350
  }
351
+ await Promise.all(promises);
289
352
  this.requestUpdate();
290
353
  }
291
354
 
292
355
  override async performUpdate(): Promise<void> {
293
- await Promise.all(this.#watchExpressions.map(we => we.updateComplete));
294
- this.#view(
295
- {
296
- watchExpressions: this.watchExpressions,
297
- onDeleteAll: this.deleteAllButtonClicked.bind(this),
298
- onAddExpression: this.addButtonClicked.bind(this),
299
- onCopyWatchExpression: watchExpression => {
300
- if (watchExpression.result?.description) {
301
- Host.InspectorFrontendHost.InspectorFrontendHostInstance.copyText(watchExpression.result.description);
302
- }
356
+ this.#view({
357
+ watchExpressions: this.watchExpressions,
358
+ linkifier: this.linkifier,
359
+ onDeleteAll: this.#onDeleteAll.bind(this),
360
+ onAddExpression: this.addButtonClicked.bind(this),
361
+ onCopyWatchExpression: watchExpression => {
362
+ if (watchExpression.result?.object.description) {
363
+ Host.InspectorFrontendHost.InspectorFrontendHostInstance.copyText(watchExpression.result.object.description);
364
+ }
365
+ },
366
+ onFinishEditing: async (watchExpression: WatchExpression, expression: string|null) => {
367
+ if (!watchExpression.editing) {
368
+ return;
369
+ }
370
+ watchExpression.editing = false;
371
+ this.requestUpdate();
372
+ if (expression === '' || (expression === null && !watchExpression.expression)) {
373
+ Platform.ArrayUtilities.removeElement(this.#watchExpressions, watchExpression);
374
+ } else if (expression) {
375
+ if (watchExpression.expression && watchExpression.expression !== expression) {
376
+ this.#getExpansionTracker(watchExpression.expression).clear();
377
+ this.#getExpansionTracker(expression).clear();
303
378
  }
304
- },
305
- {}, this.contentElement);
379
+ await watchExpression.setExpression(expression, this.#getExpansionTracker(expression));
380
+ }
381
+ this.saveExpressions();
382
+ this.requestUpdate();
383
+ },
384
+ onStartEditing: (watchExpression: WatchExpression): void => {
385
+ watchExpression.editing = true;
386
+ this.requestUpdate();
387
+ },
388
+ onDelete: (watchExpression: WatchExpression): void => {
389
+ Platform.ArrayUtilities.removeElement(this.#watchExpressions, watchExpression);
390
+ this.saveExpressions();
391
+ this.requestUpdate();
392
+ },
393
+ onExpand: async(e: WatchExpression, expanded: boolean): Promise<void> => {
394
+ if (expanded) {
395
+ await e.result?.populateChildrenIfNeeded();
396
+ this.requestUpdate();
397
+ }
398
+ }
399
+ },
400
+ {}, this.contentElement);
306
401
  }
307
402
 
308
- #getExpandController(expression: string|null): ObjectUI.ObjectPropertiesSection.ObjectTreeExpansionTracker {
403
+ #getExpansionTracker(expression: string): ObjectUI.ObjectPropertiesSection.ObjectTreeExpansionTracker {
309
404
  if (expression === null) {
310
405
  return new ObjectUI.ObjectPropertiesSection.ObjectTreeExpansionTracker();
311
406
  }
312
- let expandController = this.#expandControllers.get(expression);
313
- if (!expandController) {
314
- expandController = new ObjectUI.ObjectPropertiesSection.ObjectTreeExpansionTracker();
315
- this.#expandControllers.set(expression, expandController);
407
+ let expansionTracker = this.#expansionTrackers.get(expression);
408
+ if (!expansionTracker) {
409
+ expansionTracker = new ObjectUI.ObjectPropertiesSection.ObjectTreeExpansionTracker();
410
+ this.#expansionTrackers.set(expression, expansionTracker);
316
411
  }
317
- return expandController;
412
+ return expansionTracker;
318
413
  }
319
414
 
320
- private createWatchExpression(expression: string|null): WatchExpression {
321
- const expandController = this.#getExpandController(expression);
322
- const watchExpression = new WatchExpression(expression, expandController, this.linkifier);
323
- watchExpression.addEventListener(Events.EXPRESSION_UPDATED, this.watchExpressionUpdated, this);
324
- this.#watchExpressions.push(watchExpression);
325
- return watchExpression;
326
- }
327
-
328
- private watchExpressionUpdated({data: watchExpression}: Common.EventTarget.EventTargetEvent<WatchExpression>): void {
329
- const expression = watchExpression.expression();
330
- if (!expression) {
331
- Platform.ArrayUtilities.removeElement(this.#watchExpressions, watchExpression);
332
- }
333
-
334
- this.saveExpressions();
335
- this.requestUpdate();
336
- }
337
-
338
- private deleteAllButtonClicked(): void {
415
+ #onDeleteAll(): void {
339
416
  this.#watchExpressions = [];
340
417
  this.saveExpressions();
341
- this.#refreshExpressions();
418
+ void this.#refreshExpressions();
342
419
  }
343
420
 
344
- private async focusAndAddExpressionToWatch(expression: string): Promise<void> {
421
+ async #focusAndAddExpressionToWatch(expression: string): Promise<void> {
345
422
  await UI.ViewManager.ViewManager.instance().showView('sources.watch');
346
- this.createWatchExpression(expression);
423
+ const watchExpression = new WatchExpression();
424
+ await watchExpression.setExpression(expression, this.#getExpansionTracker(expression));
425
+ this.watchExpressions.push(watchExpression);
347
426
  this.saveExpressions();
348
- this.#refreshExpressions();
427
+ await this.#refreshExpressions();
349
428
  }
350
429
 
351
430
  handleAction(_context: UI.Context.Context, _actionId: string): boolean {
@@ -355,7 +434,7 @@ export class WatchExpressionsSidebarPane extends UI.Widget.VBox implements UI.Ac
355
434
  }
356
435
  const {state} = frame.textEditor;
357
436
  const text = state.sliceDoc(state.selection.main.from, state.selection.main.to);
358
- void this.focusAndAddExpressionToWatch(text);
437
+ void this.#focusAndAddExpressionToWatch(text);
359
438
  return true;
360
439
  }
361
440
 
@@ -364,9 +443,9 @@ export class WatchExpressionsSidebarPane extends UI.Widget.VBox implements UI.Ac
364
443
  target: ObjectUI.ObjectPropertiesSection.ObjectPropertyTreeElement|UISourceCodeFrame): void {
365
444
  if (target instanceof ObjectUI.ObjectPropertiesSection.ObjectPropertyTreeElement) {
366
445
  if (!target.property.property.synthetic) {
367
- contextMenu.debugSection().appendItem(
368
- i18nString(UIStrings.addPropertyPathToWatch), () => this.focusAndAddExpressionToWatch(target.path()),
369
- {jslogContext: 'add-property-path-to-watch'});
446
+ contextMenu.debugSection().appendItem(i18nString(UIStrings.addPropertyPathToWatch),
447
+ () => this.#focusAndAddExpressionToWatch(target.path()),
448
+ {jslogContext: 'add-property-path-to-watch'});
370
449
  }
371
450
  return;
372
451
  }
@@ -379,60 +458,36 @@ export class WatchExpressionsSidebarPane extends UI.Widget.VBox implements UI.Ac
379
458
  }
380
459
  }
381
460
 
382
- class ObjectPropertyPrompt extends UI.TextPrompt.TextPrompt {
383
- constructor() {
384
- super();
385
- this.initialize(TextEditor.JavaScript.completeInContext);
386
- }
387
- }
461
+ export class WatchExpression {
462
+ editing = false;
463
+ #exceptionDetails: Protocol.Runtime.ExceptionDetails|undefined = undefined;
464
+ #result: ObjectUI.ObjectPropertiesSection.ObjectTree|undefined = undefined;
465
+ #expression: string|null = null;
388
466
 
389
- export class WatchExpression extends Common.ObjectWrapper.ObjectWrapper<EventTypes> {
390
- #treeElement!: UI.TreeOutline.TreeElement;
391
- private nameElement!: Element;
392
- private valueElement!: Element;
393
- #expression: string|null;
394
- readonly #expandController: ObjectUI.ObjectPropertiesSection.ObjectTreeExpansionTracker;
395
- private element: HTMLDivElement;
396
- private editing: boolean;
397
- private linkifier: Components.Linkifier.Linkifier;
398
- private textPrompt?: ObjectPropertyPrompt;
399
- #result?: SDK.RemoteObject.RemoteObject|null;
400
- private preventClickTimeout?: number;
401
- #updateComplete: Promise<void> = Promise.resolve();
402
- constructor(
403
- expression: string|null, expandController: ObjectUI.ObjectPropertiesSection.ObjectTreeExpansionTracker,
404
- linkifier: Components.Linkifier.Linkifier) {
405
- super();
406
-
407
- this.#expression = expression;
408
- this.#expandController = expandController;
409
- this.element = document.createElement('div');
410
- this.element.classList.add('watch-expression');
411
- this.element.classList.add('monospace');
412
- this.editing = false;
413
- this.linkifier = linkifier;
414
-
415
- void this.createWatchExpression();
416
- this.update();
417
- }
418
-
419
- get result(): SDK.RemoteObject.RemoteObject|null {
420
- return this.#result ?? null;
421
- }
422
- get updateComplete(): Promise<void> {
423
- return this.#updateComplete;
467
+ get exceptionDetails(): Protocol.Runtime.ExceptionDetails|undefined {
468
+ return this.#exceptionDetails;
424
469
  }
425
470
 
426
- treeElement(): UI.TreeOutline.TreeElement {
427
- return this.#treeElement;
471
+ get result(): ObjectUI.ObjectPropertiesSection.ObjectTree|undefined {
472
+ return this.#result;
428
473
  }
429
474
 
430
- expression(): string|null {
475
+ get expression(): string|null {
431
476
  return this.#expression;
432
477
  }
433
478
 
434
- async #evaluateExpression(executionContext: SDK.RuntimeModel.ExecutionContext, expression: string):
435
- Promise<SDK.RuntimeModel.EvaluationResult> {
479
+ async setExpression(
480
+ expression: string,
481
+ expandController: ObjectUI.ObjectPropertiesSection.ObjectTreeExpansionTracker,
482
+ ): Promise<void> {
483
+ this.#exceptionDetails = this.#result = undefined;
484
+ this.#expression = expression;
485
+
486
+ const executionContext = UI.Context.Context.instance().flavor(SDK.RuntimeModel.ExecutionContext);
487
+ if (!expression || !executionContext) {
488
+ return;
489
+ }
490
+
436
491
  const callFrame = executionContext.debuggerModel.selectedCallFrame();
437
492
  if (callFrame?.script.isJavaScript()) {
438
493
  const nameMap = await SourceMapScopes.NamesResolver.allVariablesInCallFrame(callFrame);
@@ -443,231 +498,30 @@ export class WatchExpression extends Common.ObjectWrapper.ObjectWrapper<EventTyp
443
498
  }
444
499
  }
445
500
 
446
- return await executionContext.evaluate(
447
- {
448
- expression,
449
- objectGroup: WatchExpression.watchObjectGroupId,
450
- includeCommandLineAPI: false,
451
- silent: true,
452
- returnByValue: false,
453
- generatePreview: false,
454
- },
455
- /* userGesture */ false,
456
- /* awaitPromise */ false);
457
- }
458
-
459
- update(): void {
460
- const currentExecutionContext = UI.Context.Context.instance().flavor(SDK.RuntimeModel.ExecutionContext);
461
- if (currentExecutionContext && this.#expression) {
462
- this.#updateComplete = this.#evaluateExpression(currentExecutionContext, this.#expression)
463
- .then(async result => {
464
- if ('object' in result) {
465
- await this.createWatchExpression(result.object, result.exceptionDetails);
466
- } else {
467
- await this.createWatchExpression();
468
- }
469
- })
470
- .catch(() => {});
471
- } else {
472
- this.#updateComplete = this.createWatchExpression();
473
- }
474
- }
475
-
476
- startEditing(): void {
477
- this.editing = true;
478
- this.#treeElement.setDisableSelectFocus(true);
479
- this.element.removeChildren();
480
- const newDiv = this.element.createChild('div');
481
- newDiv.textContent = this.nameElement.textContent;
482
- this.textPrompt = new ObjectPropertyPrompt();
483
- this.textPrompt.renderAsBlock();
484
- const proxyElement = (this.textPrompt.attachAndStartEditing(newDiv, this.finishEditing.bind(this)) as HTMLElement);
485
- this.#treeElement.listItemElement.classList.add('watch-expression-editing');
486
- this.#treeElement.collapse();
487
- proxyElement.classList.add('watch-expression-text-prompt-proxy');
488
- proxyElement.addEventListener('keydown', this.promptKeyDown.bind(this), false);
489
- const selection = this.element.getComponentSelection();
490
- if (selection) {
491
- selection.selectAllChildren(newDiv);
492
- }
493
- }
494
-
495
- isEditing(): boolean {
496
- return Boolean(this.editing);
497
- }
498
-
499
- private finishEditing(event: Event, canceled?: boolean): void {
500
- if (event) {
501
- event.consume(canceled);
502
- }
503
-
504
- this.editing = false;
505
- this.#treeElement.setDisableSelectFocus(false);
506
- this.#treeElement.listItemElement.classList.remove('watch-expression-editing');
507
- if (this.textPrompt) {
508
- const text = this.textPrompt.text();
509
- this.textPrompt.detach();
510
- const newExpression = canceled ? this.#expression : text;
511
- this.textPrompt = undefined;
512
- this.element.removeChildren();
513
- this.updateExpression(newExpression);
514
- }
515
- }
516
-
517
- private dblClickOnWatchExpression(event: Event): void {
518
- event.consume();
519
- if (!this.isEditing()) {
520
- this.startEditing();
521
- }
522
- }
523
-
524
- updateExpression(newExpression: string|null): void {
525
- if (this.#expression !== newExpression && newExpression) {
526
- this.#expandController.clear();
527
- }
528
- this.#expression = newExpression;
529
- this.update();
530
- this.dispatchEventToListeners(Events.EXPRESSION_UPDATED, this);
531
- }
532
-
533
- private deleteWatchExpression(event: Event): void {
534
- event.consume(true);
535
- UI.ARIAUtils.LiveAnnouncer.alert(i18nString(UIStrings.watchExpressionDeleted));
536
- this.updateExpression(null);
537
- }
538
-
539
- async createWatchExpression(
540
- result?: SDK.RemoteObject.RemoteObject, exceptionDetails?: Protocol.Runtime.ExceptionDetails): Promise<void> {
541
- this.#result = result || null;
542
-
543
- this.element.removeChildren();
544
- const oldTreeElement = this.#treeElement;
545
- await this.#createWatchExpressionTreeElement(result, exceptionDetails);
546
- if (oldTreeElement?.parent) {
547
- const root = oldTreeElement.parent;
548
- const index = root.indexOfChild(oldTreeElement);
549
- root.removeChild(oldTreeElement);
550
- root.insertChild(this.#treeElement, index);
551
- }
552
- this.#treeElement.select();
553
- }
554
-
555
- private createWatchExpressionHeader(
556
- expressionValue?: SDK.RemoteObject.RemoteObject, exceptionDetails?: Protocol.Runtime.ExceptionDetails): Element {
557
- const headerElement = this.element.createChild('div', 'watch-expression-header');
558
- const deleteButton = new Buttons.Button.Button();
559
- deleteButton.data = {
560
- variant: Buttons.Button.Variant.ICON,
561
- iconName: 'bin',
562
- size: Buttons.Button.Size.SMALL,
563
- jslogContext: 'delete-watch-expression',
564
- };
565
- deleteButton.className = 'watch-expression-delete-button';
566
- UI.Tooltip.Tooltip.install(deleteButton, i18nString(UIStrings.deleteWatchExpression));
567
- deleteButton.addEventListener('click', this.deleteWatchExpression.bind(this), false);
568
- deleteButton.addEventListener('keydown', event => {
569
- if (event.key === 'Enter') {
570
- this.deleteWatchExpression(event);
571
- }
572
- });
573
-
574
- const titleElement = headerElement.createChild('div', 'watch-expression-title tree-element-title');
575
- titleElement.appendChild(deleteButton);
576
- this.nameElement = ObjectUI.ObjectPropertiesSection.ObjectPropertiesSection.createNameElement(this.#expression);
577
- UI.Tooltip.Tooltip.install(this.nameElement as HTMLElement, this.#expression);
578
- if (Boolean(exceptionDetails) || !expressionValue) {
579
- this.valueElement = document.createElement('span');
580
- this.valueElement.classList.add('watch-expression-error');
581
- this.valueElement.classList.add('value');
582
- titleElement.classList.add('dimmed');
583
- this.valueElement.textContent = i18nString(UIStrings.notAvailable);
584
- if (exceptionDetails?.exception?.description !== undefined) {
585
- UI.Tooltip.Tooltip.install(this.valueElement as HTMLElement, exceptionDetails.exception.description);
586
- }
587
- } else {
588
- const propertyValue =
589
- ObjectUI.ObjectPropertiesSection.ObjectPropertiesSection.createPropertyValueWithCustomSupport(
590
- expressionValue, Boolean(exceptionDetails), false /* showPreview */, this.linkifier);
591
- this.valueElement = propertyValue;
592
- }
593
- const separatorElement = document.createElement('span');
594
- separatorElement.classList.add('watch-expressions-separator');
595
- separatorElement.textContent = ': ';
596
- titleElement.append(this.nameElement, separatorElement, this.valueElement);
597
-
598
- return headerElement;
599
- }
600
-
601
- async #createWatchExpressionTreeElement(
602
- expressionValue?: SDK.RemoteObject.RemoteObject,
603
- exceptionDetails?: Protocol.Runtime.ExceptionDetails): Promise<void> {
604
- const headerElement = this.createWatchExpressionHeader(expressionValue, exceptionDetails);
605
-
606
- if (!exceptionDetails && expressionValue && expressionValue.hasChildren && !expressionValue.customPreview()) {
607
- headerElement.classList.add('watch-expression-object-header');
608
- const objectTree = new ObjectUI.ObjectPropertiesSection.ObjectTree(expressionValue, {
609
- readOnly: true,
610
- propertiesMode: ObjectUI.ObjectPropertiesSection.ObjectPropertiesMode.OWN_AND_INTERNAL_AND_INHERITED,
611
- expansionTracker: this.#expandController,
612
- });
613
- await this.#expandController.apply(objectTree);
614
- this.#treeElement = new ObjectUI.ObjectPropertiesSection.RootElement(objectTree, this.linkifier);
615
- this.#treeElement.toggleOnClick = false;
616
- this.#treeElement.listItemElement.addEventListener('click', this.onSectionClick.bind(this), false);
617
- this.#treeElement.listItemElement.addEventListener('dblclick', this.dblClickOnWatchExpression.bind(this));
618
- } else {
619
- headerElement.addEventListener('dblclick', this.dblClickOnWatchExpression.bind(this));
620
- this.#treeElement = new UI.TreeOutline.TreeElement();
621
- }
622
- this.#treeElement.title = this.element;
623
- this.#treeElement.listItemElement.classList.add('watch-expression-tree-item');
624
- this.#treeElement.listItemElement.addEventListener('keydown', event => {
625
- if (event.key === 'Enter' && !this.isEditing()) {
626
- this.startEditing();
627
- event.consume(true);
628
- } else if (event.key === 'Delete' && !this.isEditing()) {
629
- this.deleteWatchExpression(event);
630
- }
631
- });
632
- }
633
-
634
- private onSectionClick(event: MouseEvent): void {
635
- event.consume(true);
636
- const mouseEvent = event;
637
- if (mouseEvent.detail === 1) {
638
- this.preventClickTimeout = window.setTimeout(handleClick.bind(this), 333);
639
- } else if (this.preventClickTimeout !== undefined) {
640
- window.clearTimeout(this.preventClickTimeout);
641
- this.preventClickTimeout = undefined;
642
- }
643
-
644
- function handleClick(this: WatchExpression): void {
645
- if (!this.#treeElement) {
646
- return;
647
- }
648
-
649
- if (this.#treeElement.expanded) {
650
- this.#treeElement.collapse();
651
- } else if (!this.editing) {
652
- this.#treeElement.expand();
501
+ try {
502
+ const result = await executionContext.evaluate({
503
+ expression,
504
+ objectGroup: WatchExpression.watchObjectGroupId,
505
+ includeCommandLineAPI: false,
506
+ silent: true,
507
+ returnByValue: false,
508
+ generatePreview: false,
509
+ },
510
+ /* userGesture */ false,
511
+ /* awaitPromise */ false);
512
+ if ('object' in result) {
513
+ const objectTree = new ObjectUI.ObjectPropertiesSection.ObjectTree(result.object, {
514
+ readOnly: true,
515
+ propertiesMode: ObjectUI.ObjectPropertiesSection.ObjectPropertiesMode.OWN_AND_INTERNAL_AND_INHERITED,
516
+ expansionTracker: expandController,
517
+ });
518
+ await expandController.apply(objectTree);
519
+ this.#result = objectTree;
520
+ this.#exceptionDetails = result.exceptionDetails;
653
521
  }
654
- }
655
- }
656
-
657
- private promptKeyDown(event: KeyboardEvent): void {
658
- const isEscapeKey = Platform.KeyboardUtilities.isEscKey(event);
659
- if (event.key === 'Enter' || isEscapeKey) {
660
- this.finishEditing(event, isEscapeKey);
522
+ } catch {
661
523
  }
662
524
  }
663
525
 
664
526
  private static readonly watchObjectGroupId = 'watch-group';
665
527
  }
666
-
667
- const enum Events {
668
- EXPRESSION_UPDATED = 'ExpressionUpdated',
669
- }
670
-
671
- interface EventTypes {
672
- [Events.EXPRESSION_UPDATED]: WatchExpression;
673
- }