chrome-devtools-frontend 1.0.1679704 → 1.0.1681091

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 (54) hide show
  1. package/AUTHORS +1 -0
  2. package/front_end/core/platform/TypescriptUtilities.ts +0 -9
  3. package/front_end/core/platform/platform.ts +1 -1
  4. package/front_end/core/sdk/ConsoleModel.ts +6 -3
  5. package/front_end/core/sdk/DOMModel.ts +102 -0
  6. package/front_end/core/sdk/EmulationModel.ts +4 -2
  7. package/front_end/core/sdk/NetworkManager.ts +7 -6
  8. package/front_end/core/sdk/PageResourceLoader.ts +4 -3
  9. package/front_end/core/sdk/RuntimeModel.ts +5 -3
  10. package/front_end/core/sdk/SDKSettings.ts +47 -0
  11. package/front_end/core/sdk/sdk-meta.ts +0 -138
  12. package/front_end/entrypoints/inspector_main/RenderingOptions.ts +4 -2
  13. package/front_end/entrypoints/inspector_main/inspector_main-meta.ts +32 -0
  14. package/front_end/entrypoints/main/MainImpl.ts +1 -1
  15. package/front_end/generated/InspectorBackendCommands.ts +3 -1
  16. package/front_end/generated/SupportedCSSProperties.ts +20 -0
  17. package/front_end/generated/protocol-mapping.d.ts +7 -0
  18. package/front_end/generated/protocol-proxy-api.d.ts +5 -0
  19. package/front_end/generated/protocol.ts +15 -3
  20. package/front_end/panels/console/ConsoleView.ts +2 -1
  21. package/front_end/panels/console/console-meta.ts +10 -5
  22. package/front_end/panels/elements/DOMTreeContextMenu.ts +541 -0
  23. package/front_end/panels/elements/ElementsTreeElement.ts +27 -494
  24. package/front_end/panels/elements/ElementsTreeOutline.ts +173 -287
  25. package/front_end/panels/elements/StylesSidebarPane.ts +2 -1
  26. package/front_end/panels/elements/elements.ts +3 -0
  27. package/front_end/panels/issues/CorsIssueDetailsView.ts +13 -9
  28. package/front_end/panels/mobile_throttling/NetworkPanelIndicator.ts +3 -1
  29. package/front_end/panels/network/NetworkConfigView.ts +2 -1
  30. package/front_end/panels/network/NetworkPanel.ts +1 -1
  31. package/front_end/panels/network/network-meta.ts +60 -0
  32. package/front_end/panels/profiler/HeapDetachedElementsDataGrid.ts +2 -1
  33. package/front_end/panels/profiler/ProfileLauncherView.ts +19 -16
  34. package/front_end/panels/profiler/ProfileSidebarTreeElement.ts +1 -1
  35. package/front_end/panels/security/security.ts +6 -0
  36. package/front_end/panels/sources/SourcesView.ts +8 -107
  37. package/front_end/panels/sources/TabbedEditorContainer.ts +110 -7
  38. package/front_end/panels/sources/sources-meta.ts +16 -0
  39. package/front_end/panels/timeline/TimelineLoader.ts +1 -1
  40. package/front_end/panels/timeline/TimelineUIUtils.ts +6 -0
  41. package/front_end/panels/timeline/components/LayoutShiftDetails.ts +4 -3
  42. package/front_end/panels/timeline/components/LiveMetricsView.ts +1 -1
  43. package/front_end/panels/timeline/components/MetricCard.ts +4 -3
  44. package/front_end/panels/timeline/components/NetworkRequestDetails.ts +5 -2
  45. package/front_end/panels/timeline/components/TimelineRangeSummaryView.ts +1 -1
  46. package/front_end/panels/timeline/components/TimelineSummary.ts +8 -5
  47. package/front_end/third_party/chromium/README.chromium +1 -1
  48. package/front_end/ui/legacy/TabbedPane.ts +34 -2
  49. package/front_end/ui/legacy/components/quick_open/CommandMenu.ts +5 -5
  50. package/front_end/ui/legacy/components/quick_open/FilteredListWidget.ts +7 -7
  51. package/front_end/ui/legacy/components/quick_open/QuickOpen.ts +1 -1
  52. package/front_end/ui/legacy/components/quick_open/quick_open-meta.ts +2 -2
  53. package/front_end/ui/visual_logging/KnownContextValues.ts +29 -0
  54. package/package.json +1 -1
@@ -0,0 +1,541 @@
1
+ // Copyright 2026 The Chromium Authors
2
+ // Use of this source code is governed by a BSD-style license that can be
3
+ // found in the LICENSE file.
4
+
5
+ import * as Common from '../../core/common/common.js';
6
+ import * as i18n from '../../core/i18n/i18n.js';
7
+ import * as SDK from '../../core/sdk/sdk.js';
8
+ import * as UI from '../../ui/legacy/legacy.js';
9
+ import * as Emulation from '../emulation/emulation.js';
10
+
11
+ import {canGetJSPath} from './DOMPath.js';
12
+ import {ElementsPanel} from './ElementsPanel.js';
13
+ import {ElementsTreeElement} from './ElementsTreeElement.js';
14
+ import type {ElementsTreeOutline} from './ElementsTreeOutline.js';
15
+
16
+ const UIStrings = {
17
+ /**
18
+ * @description A context menu item to store a value as a global variable the Elements Panel
19
+ */
20
+ storeAsGlobalVariable: 'Store as global variable',
21
+ /**
22
+ * @description A context menu item in the Elements Tree Element of the Elements panel
23
+ */
24
+ addAttribute: 'Add attribute',
25
+ /**
26
+ * @description Text to modify the attribute of an item
27
+ */
28
+ editAttribute: 'Edit attribute',
29
+ /**
30
+ * @description Text to focus on something
31
+ */
32
+ focus: 'Focus',
33
+ /**
34
+ * @description Text to scroll the displayed content into view
35
+ */
36
+ scrollIntoView: 'Scroll into view',
37
+ /**
38
+ * @description A context menu item in the Elements panel to switch to Accessibility tree
39
+ */
40
+ switchToAccessibilityTree: 'Switch to accessibility tree',
41
+ /**
42
+ * @description A context menu item in the Elements Tree Element of the Elements panel
43
+ */
44
+ editText: 'Edit text',
45
+ /**
46
+ * @description A context menu item in the Elements Tree Element of the Elements panel
47
+ */
48
+ editAsHtml: 'Edit as HTML',
49
+ /**
50
+ * @description A context menu item in the Elements Tree Element of the Elements panel
51
+ */
52
+ editData: 'Edit data',
53
+ /**
54
+ * @description Text to cut an element, cut should be used as a verb
55
+ */
56
+ cut: 'Cut',
57
+ /**
58
+ * @description Text for copying, copy should be used as a verb
59
+ */
60
+ copy: 'Copy',
61
+ /**
62
+ * @description Text to paste an element, paste should be used as a verb
63
+ */
64
+ paste: 'Paste',
65
+ /**
66
+ * @description Text in Elements Tree Element of the Elements panel, copy should be used as a verb
67
+ */
68
+ copyOuterhtml: 'Copy outerHTML',
69
+ /**
70
+ * @description Text in Elements Tree Element of the Elements panel, copy should be used as a verb
71
+ */
72
+ copySelector: 'Copy `selector`',
73
+ /**
74
+ * @description Text in Elements Tree Element of the Elements panel
75
+ */
76
+ copyJsPath: 'Copy JS path',
77
+ /**
78
+ * @description Text in Elements Tree Element of the Elements panel, copy should be used as a verb
79
+ */
80
+ copyStyles: 'Copy styles',
81
+ /**
82
+ * @description Text in Elements Tree Element of the Elements panel, copy should be used as a verb
83
+ */
84
+ copyXpath: 'Copy XPath',
85
+ /**
86
+ * @description Text in Elements Tree Element of the Elements panel, copy should be used as a verb
87
+ */
88
+ copyFullXpath: 'Copy full XPath',
89
+ /**
90
+ * @description Text in Elements Tree Element of the Elements panel, copy should be used as a verb
91
+ */
92
+ copyElement: 'Copy element',
93
+ /**
94
+ * @description A context menu item in the Elements Tree Element of the Elements panel
95
+ */
96
+ duplicateElement: 'Duplicate element',
97
+ /**
98
+ * @description Text to hide an element
99
+ */
100
+ hideElement: 'Hide element',
101
+ /**
102
+ * @description A context menu item in the Elements Tree Element of the Elements panel
103
+ */
104
+ deleteElement: 'Delete element',
105
+ /**
106
+ * @description Text to expand something recursively
107
+ */
108
+ expandRecursively: 'Expand recursively',
109
+ /**
110
+ * @description Text to collapse children of a parent group
111
+ */
112
+ collapseChildren: 'Collapse children',
113
+ /**
114
+ * @description Title of an action in the emulation tool to capture node screenshot
115
+ */
116
+ captureNodeScreenshot: 'Capture node screenshot',
117
+ /**
118
+ * @description Title of a context menu item. When clicked DevTools goes to the Application panel and shows this specific iframe's details
119
+ */
120
+ showFrameDetails: 'Show `iframe` details',
121
+ /**
122
+ * @description Text of a context menu item to redirect to the AI assistance panel and to start a chat.
123
+ */
124
+ startAChat: 'Start a chat',
125
+ /**
126
+ * @description Context menu item in Elements panel to assess visibility of an element via AI.
127
+ */
128
+ assessVisibility: 'Assess visibility',
129
+ /**
130
+ * @description Context menu item in Elements panel to center an element via AI.
131
+ */
132
+ centerElement: 'Center element',
133
+ /**
134
+ * @description Context menu item in Elements panel to wrap flex items via AI.
135
+ */
136
+ wrapTheseItems: 'Wrap these items',
137
+ /**
138
+ * @description Context menu item in Elements panel to distribute flex items evenly via AI.
139
+ */
140
+ distributeItemsEvenly: 'Distribute items evenly',
141
+ /**
142
+ * @description Context menu item in Elements panel to explain flexbox via AI.
143
+ */
144
+ explainFlexbox: 'Explain flexbox',
145
+ /**
146
+ * @description Context menu item in Elements panel to align grid items via AI.
147
+ */
148
+ alignItems: 'Align items',
149
+ /**
150
+ * @description Context menu item in Elements panel to add padding/gap to grid via AI.
151
+ */
152
+ addPadding: 'Add padding',
153
+ /**
154
+ * @description Context menu item in Elements panel to explain grid layout via AI.
155
+ */
156
+ explainGridLayout: 'Explain grid layout',
157
+ /**
158
+ * @description Context menu item in Elements panel to find grid definition for a subgrid item via AI.
159
+ */
160
+ findGridDefinition: 'Find grid definition',
161
+ /**
162
+ * @description Context menu item in Elements panel to change parent grid properties for a subgrid item via AI.
163
+ */
164
+ changeParentProperties: 'Change parent properties',
165
+ /**
166
+ * @description Context menu item in Elements panel to explain subgrids via AI.
167
+ */
168
+ explainSubgrids: 'Explain subgrids',
169
+ /**
170
+ * @description Context menu item in Elements panel to remove scrollbars via AI.
171
+ */
172
+ removeScrollbars: 'Remove scrollbars',
173
+ /**
174
+ * @description Context menu item in Elements panel to style scrollbars via AI.
175
+ */
176
+ styleScrollbars: 'Style scrollbars',
177
+ /**
178
+ * @description Context menu item in Elements panel to explain scrollbars via AI.
179
+ */
180
+ explainScrollbars: 'Explain scrollbars',
181
+ /**
182
+ * @description Context menu item in Elements panel to explain container queries via AI.
183
+ */
184
+ explainContainerQueries: 'Explain container queries',
185
+ /**
186
+ * @description Context menu item in Elements panel to explain container types via AI.
187
+ */
188
+ explainContainerTypes: 'Explain container types',
189
+ /**
190
+ * @description Context menu item in Elements panel to explain container context via AI.
191
+ */
192
+ explainContainerContext: 'Explain container context',
193
+ } as const;
194
+ const str_ = i18n.i18n.registerUIStrings('panels/elements/DOMTreeContextMenu.ts', UIStrings);
195
+ const i18nString = i18n.i18n.getLocalizedString.bind(undefined, str_);
196
+
197
+ export async function populateNodeContextMenu(contextMenu: UI.ContextMenu.ContextMenu,
198
+ treeElement: ElementsTreeElement): Promise<void> {
199
+ const domNode = treeElement.node();
200
+ const isEditable = treeElement.hasEditableNode();
201
+ if (isEditable && !treeElement.isEditing) {
202
+ contextMenu.editSection().appendItem(i18nString(UIStrings.editAsHtml), () => treeElement.editAsHTML(),
203
+ {jslogContext: 'elements.edit-as-html'});
204
+ }
205
+ const isShadowRoot = domNode.isShadowRoot();
206
+
207
+ const createShortcut = UI.KeyboardShortcut.KeyboardShortcut.shortcutToString.bind(null);
208
+ const modifier = UI.KeyboardShortcut.Modifiers.CtrlOrMeta.value;
209
+ let menuItem;
210
+
211
+ const openAiAssistanceId = 'freestyler.element-panel-context';
212
+ if (UI.ActionRegistry.ActionRegistry.instance().hasAction(openAiAssistanceId)) {
213
+ function appendSubmenuPromptAction(submenu: UI.ContextMenu.SubMenu, action: UI.ActionRegistration.Action,
214
+ label: Common.UIString.LocalizedString, prompt: string,
215
+ jslogContext: string): void {
216
+ submenu.defaultSection().appendItem(label, () => {
217
+ void action.execute({prompt});
218
+ UI.UIUtils.PromotionManager.instance().recordFeatureInteraction(openAiAssistanceId);
219
+ }, {disabled: !action.enabled(), jslogContext});
220
+ }
221
+ UI.Context.Context.instance().setFlavor(SDK.DOMModel.DOMNode, domNode);
222
+ const action = UI.ActionRegistry.ActionRegistry.instance().getAction(openAiAssistanceId);
223
+ const submenu = contextMenu.footerSection().appendSubMenuItem(action.title(), false, openAiAssistanceId);
224
+ submenu.defaultSection().appendAction(openAiAssistanceId, i18nString(UIStrings.startAChat));
225
+
226
+ const submenuConfigs = [
227
+ {
228
+ condition: (props: SDK.CSSModel.LayoutProperties|null): boolean => Boolean(props?.isFlex),
229
+ items: [
230
+ {
231
+ label: i18nString(UIStrings.wrapTheseItems),
232
+ prompt: 'How can I make flex items wrap?',
233
+ jslogContextSuffix: '.flex-wrap',
234
+ },
235
+ {
236
+ label: i18nString(UIStrings.distributeItemsEvenly),
237
+ prompt: 'How do I distribute flex items evenly?',
238
+ jslogContextSuffix: '.flex-distribute',
239
+ },
240
+ {
241
+ label: i18nString(UIStrings.explainFlexbox),
242
+ prompt: 'What is flexbox?',
243
+ jslogContextSuffix: '.flex-what',
244
+ },
245
+ ],
246
+ },
247
+ {
248
+ condition: (props: SDK.CSSModel.LayoutProperties|null): boolean => Boolean(props?.isGrid && !props?.isSubgrid),
249
+ items: [
250
+ {
251
+ label: i18nString(UIStrings.alignItems),
252
+ prompt: 'How do I align items in a grid?',
253
+ jslogContextSuffix: '.grid-align',
254
+ },
255
+ {
256
+ label: i18nString(UIStrings.addPadding),
257
+ prompt: 'How to add spacing between grid items?',
258
+ jslogContextSuffix: '.grid-gap',
259
+ },
260
+ {
261
+ label: i18nString(UIStrings.explainGridLayout),
262
+ prompt: 'How does grid layout work?',
263
+ jslogContextSuffix: '.grid-how',
264
+ },
265
+ ],
266
+ },
267
+ {
268
+ condition: (props: SDK.CSSModel.LayoutProperties|null): boolean => Boolean(props?.isSubgrid),
269
+ items: [
270
+ {
271
+ label: i18nString(UIStrings.findGridDefinition),
272
+ prompt: 'Where is this grid defined?',
273
+ jslogContextSuffix: '.subgrid-where',
274
+ },
275
+ {
276
+ label: i18nString(UIStrings.changeParentProperties),
277
+ prompt: 'How to overwrite parent grid properties?',
278
+ jslogContextSuffix: '.subgrid-override',
279
+ },
280
+ {
281
+ label: i18nString(UIStrings.explainSubgrids),
282
+ prompt: 'How do subgrids work?',
283
+ jslogContextSuffix: '.subgrid-how',
284
+ },
285
+ ],
286
+ },
287
+ {
288
+ condition: (props: SDK.CSSModel.LayoutProperties|null): boolean => Boolean(props?.hasScroll),
289
+ items: [
290
+ {
291
+ label: i18nString(UIStrings.removeScrollbars),
292
+ prompt: 'How do I remove scrollbars for this element?',
293
+ jslogContextSuffix: '.scroll-remove',
294
+ },
295
+ {
296
+ label: i18nString(UIStrings.styleScrollbars),
297
+ prompt: 'How can I style a scrollbar?',
298
+ jslogContextSuffix: '.scroll-style',
299
+ },
300
+ {
301
+ label: i18nString(UIStrings.explainScrollbars),
302
+ prompt: 'Why does this element scroll?',
303
+ jslogContextSuffix: '.scroll-why',
304
+ },
305
+ ],
306
+ },
307
+ {
308
+ condition: (props: SDK.CSSModel.LayoutProperties|null): boolean => Boolean(props?.containerType),
309
+ items: [
310
+ {
311
+ label: i18nString(UIStrings.explainContainerQueries),
312
+ prompt: 'What are container queries?',
313
+ jslogContextSuffix: '.container-what',
314
+ },
315
+ {
316
+ label: i18nString(UIStrings.explainContainerTypes),
317
+ prompt: 'How do I use container-type?',
318
+ jslogContextSuffix: '.container-how',
319
+ },
320
+ {
321
+ label: i18nString(UIStrings.explainContainerContext),
322
+ prompt: 'What\'s the container context for this element?',
323
+ jslogContextSuffix: '.container-context',
324
+ },
325
+ ],
326
+ },
327
+ {
328
+ // Default items
329
+ condition: (): boolean => true,
330
+ items: [
331
+ {
332
+ label: i18nString(UIStrings.assessVisibility),
333
+ prompt: 'Why isn’t this element visible?',
334
+ jslogContextSuffix: '.visibility',
335
+ },
336
+ {
337
+ label: i18nString(UIStrings.centerElement),
338
+ prompt: 'How do I center this element?',
339
+ jslogContextSuffix: '.center',
340
+ },
341
+ ],
342
+ },
343
+ ];
344
+
345
+ const layoutProps = await domNode.domModel().cssModel().getLayoutPropertiesFromComputedStyle(domNode.id);
346
+ const config = submenuConfigs.find(c => c.condition(layoutProps));
347
+ if (config) {
348
+ for (const item of config.items) {
349
+ appendSubmenuPromptAction(submenu, action, item.label, item.prompt,
350
+ openAiAssistanceId + item.jslogContextSuffix);
351
+ }
352
+ }
353
+ }
354
+
355
+ const outline = treeElement.treeOutline as ElementsTreeOutline | null;
356
+
357
+ menuItem = contextMenu.clipboardSection().appendItem(i18nString(UIStrings.cut),
358
+ () => outline?.performCopyOrCut(true, domNode),
359
+ {disabled: !treeElement.hasEditableNode(), jslogContext: 'cut'});
360
+ menuItem.setShortcut(createShortcut('X', modifier));
361
+
362
+ // Place it here so that all "Copy"-ing items stick together.
363
+ const copyMenu = contextMenu.clipboardSection().appendSubMenuItem(i18nString(UIStrings.copy), false, 'copy');
364
+ const section = copyMenu.section();
365
+ if (!isShadowRoot) {
366
+ menuItem = section.appendItem(i18nString(UIStrings.copyOuterhtml), () => outline?.performCopyOrCut(false, domNode),
367
+ {jslogContext: 'copy-outer-html'});
368
+ menuItem.setShortcut(createShortcut('V', modifier));
369
+ }
370
+ if (domNode.nodeType() === Node.ELEMENT_NODE) {
371
+ section.appendItem(i18nString(UIStrings.copySelector), () => treeElement.copyCSSPath(),
372
+ {jslogContext: 'copy-selector'});
373
+ section.appendItem(i18nString(UIStrings.copyJsPath), () => treeElement.copyJSPath(),
374
+ {disabled: !canGetJSPath(domNode), jslogContext: 'copy-js-path'});
375
+ section.appendItem(i18nString(UIStrings.copyStyles), () => void treeElement.copyStyles(),
376
+ {jslogContext: 'elements.copy-styles'});
377
+ }
378
+ if (!isShadowRoot) {
379
+ section.appendItem(i18nString(UIStrings.copyXpath), () => treeElement.copyXPath(), {jslogContext: 'copy-xpath'});
380
+ section.appendItem(i18nString(UIStrings.copyFullXpath), () => treeElement.copyFullXPath(),
381
+ {jslogContext: 'copy-full-xpath'});
382
+ }
383
+
384
+ menuItem = copyMenu.clipboardSection().appendItem(i18nString(UIStrings.copyElement),
385
+ () => outline?.performCopyOrCut(false, domNode, true),
386
+ {jslogContext: 'copy-element'});
387
+ menuItem.setShortcut(createShortcut('C', modifier));
388
+
389
+ if (!isShadowRoot) {
390
+ // Duplicate element, disabled on root element and ShadowDOM.
391
+ const isRootElement = !domNode.parentNode || domNode.parentNode.nodeName() === '#document';
392
+ menuItem = contextMenu.editSection().appendItem(i18nString(UIStrings.duplicateElement),
393
+ () => outline?.duplicateNode(domNode), {
394
+ disabled: (domNode.isInShadowTree() || isRootElement),
395
+ jslogContext: 'elements.duplicate-element',
396
+ });
397
+ }
398
+
399
+ menuItem = contextMenu.clipboardSection().appendItem(i18nString(UIStrings.paste), () => outline?.pasteNode(domNode),
400
+ {disabled: !outline?.canPaste(domNode), jslogContext: 'paste'});
401
+ menuItem.setShortcut(createShortcut('V', modifier));
402
+
403
+ menuItem = contextMenu.debugSection().appendCheckboxItem(i18nString(UIStrings.hideElement),
404
+ () => outline?.toggleHideElement(domNode), {
405
+ checked: Boolean(outline?.isToggledToHidden(domNode)),
406
+ jslogContext: 'elements.hide-element',
407
+ });
408
+ menuItem.setShortcut(
409
+ UI.ShortcutRegistry.ShortcutRegistry.instance().shortcutTitleForAction('elements.hide-element') || '');
410
+
411
+ if (isEditable) {
412
+ contextMenu.editSection().appendItem(i18nString(UIStrings.deleteElement), () => void treeElement.remove(),
413
+ {jslogContext: 'delete-element'});
414
+ }
415
+
416
+ contextMenu.viewSection().appendItem(i18nString(UIStrings.expandRecursively),
417
+ () => void treeElement.expandRecursively(),
418
+ {jslogContext: 'expand-recursively'});
419
+ contextMenu.viewSection().appendItem(i18nString(UIStrings.collapseChildren), () => treeElement.collapseChildren(),
420
+ {jslogContext: 'collapse-children'});
421
+ contextMenu.viewSection().appendItem(i18nString(UIStrings.switchToAccessibilityTree),
422
+ () => ElementsPanel.instance().toggleAccessibilityTree(),
423
+ {jslogContext: 'switch-to-accessibility-tree'});
424
+ const deviceModeWrapperAction = new Emulation.DeviceModeView.ActionDelegate();
425
+ contextMenu.viewSection().appendItem(i18nString(UIStrings.captureNodeScreenshot),
426
+ deviceModeWrapperAction.handleAction.bind(null, UI.Context.Context.instance(),
427
+ 'emulation.capture-node-screenshot'),
428
+ {jslogContext: 'emulation.capture-node-screenshot'});
429
+ if (domNode.frameOwnerFrameId()) {
430
+ contextMenu.viewSection().appendItem(i18nString(UIStrings.showFrameDetails), () => {
431
+ const frameOwnerFrameId = domNode.frameOwnerFrameId();
432
+ if (frameOwnerFrameId) {
433
+ const frame = SDK.FrameManager.FrameManager.instance().getFrame(frameOwnerFrameId);
434
+ void Common.Revealer.reveal(frame);
435
+ }
436
+ }, {jslogContext: 'show-frame-details'});
437
+ }
438
+ }
439
+
440
+ export async function showContextMenu(
441
+ treeElement: ElementsTreeElement,
442
+ event: Event,
443
+ onSaveNodeToTempVariable?: (node: SDK.DOMModel.DOMNode) => void,
444
+ ): Promise<UI.ContextMenu.ContextMenu|undefined> {
445
+ if (UI.UIUtils.isEditing()) {
446
+ return;
447
+ }
448
+
449
+ const outline = treeElement.treeOutline as ElementsTreeOutline | null;
450
+ if (outline && !outline.enableContextMenu) {
451
+ return;
452
+ }
453
+
454
+ const node = (event.target as Node | null);
455
+ if (!node) {
456
+ return;
457
+ }
458
+
459
+ // The context menu construction may be async. In order to
460
+ // make sure that no other (default) context menu shows up, we need
461
+ // to stop propagating and prevent the default action.
462
+ event.stopPropagation();
463
+ event.preventDefault();
464
+
465
+ const contextMenu = new UI.ContextMenu.ContextMenu(event);
466
+ const domNode = treeElement.node();
467
+ const isPseudoElement = Boolean(domNode.pseudoType());
468
+ const isTag = domNode.nodeType() === Node.ELEMENT_NODE && !isPseudoElement;
469
+
470
+ let textNode: Element|null = (node as Element).enclosingNodeOrSelfWithClass?.('webkit-html-text-node');
471
+ if (textNode?.classList.contains('bogus')) {
472
+ textNode = null;
473
+ }
474
+ const commentNode = (node as Element).enclosingNodeOrSelfWithClass?.('webkit-html-comment');
475
+ if (onSaveNodeToTempVariable) {
476
+ contextMenu.saveSection().appendItem(i18nString(UIStrings.storeAsGlobalVariable),
477
+ () => onSaveNodeToTempVariable(domNode),
478
+ {jslogContext: 'store-as-global-variable'});
479
+ }
480
+ if (textNode) {
481
+ if (!treeElement.isEditing) {
482
+ contextMenu.editSection().appendItem(i18nString(UIStrings.editText),
483
+ () => treeElement.startEditingTextNode(textNode as Element),
484
+ {jslogContext: 'edit-text'});
485
+ }
486
+ await populateNodeContextMenu(contextMenu, treeElement);
487
+ } else if (isTag) {
488
+ const targetWidget = treeElement.isClosingTag() ?
489
+ ((treeElement.treeOutline as ElementsTreeOutline | null)?.findTreeElement(domNode) as ElementsTreeElement |
490
+ null) :
491
+ treeElement;
492
+ if (targetWidget) {
493
+ contextMenu.editSection().appendItem(i18nString(UIStrings.addAttribute), () => targetWidget.addNewAttribute(),
494
+ {jslogContext: 'add-attribute'});
495
+ }
496
+
497
+ const target = (event.composedPath()[0] || event.target) as Element;
498
+ const attribute = target.enclosingNodeOrSelfWithClass?.('webkit-html-attribute');
499
+ const newAttribute = target.enclosingNodeOrSelfWithClass?.('add-attribute');
500
+ if (attribute && !newAttribute) {
501
+ contextMenu.editSection().appendItem(i18nString(UIStrings.editAttribute),
502
+ () => treeElement.startEditingAttribute(attribute, target),
503
+ {jslogContext: 'edit-attribute'});
504
+ }
505
+ await populateNodeContextMenu(contextMenu, treeElement);
506
+ ElementsTreeElement.populateForcedPseudoStateItems(contextMenu, domNode);
507
+ contextMenu.viewSection().appendItem(i18nString(UIStrings.scrollIntoView), () => domNode.scrollIntoView(),
508
+ {jslogContext: 'scroll-into-view'});
509
+ contextMenu.viewSection().appendItem(i18nString(UIStrings.focus), async () => {
510
+ await domNode.focus();
511
+ }, {jslogContext: 'focus'});
512
+ } else if (commentNode) {
513
+ await populateNodeContextMenu(contextMenu, treeElement);
514
+ } else if (isPseudoElement) {
515
+ if (treeElement.childCount() !== 0) {
516
+ contextMenu.viewSection().appendItem(i18nString(UIStrings.expandRecursively),
517
+ () => void treeElement.expandRecursively(),
518
+ {jslogContext: 'expand-recursively'});
519
+ }
520
+ contextMenu.viewSection().appendItem(i18nString(UIStrings.scrollIntoView), () => domNode.scrollIntoView(),
521
+ {jslogContext: 'scroll-into-view'});
522
+ } else if (domNode.nodeType() === Node.PROCESSING_INSTRUCTION_NODE) {
523
+ contextMenu.editSection().appendItem(i18nString(UIStrings.editData),
524
+ () => treeElement.startEditingProcessingInstructionValue(),
525
+ {jslogContext: 'elements.edit-data'});
526
+ contextMenu.editSection().appendItem(
527
+ i18nString(UIStrings.duplicateElement),
528
+ () => (treeElement.treeOutline as ElementsTreeOutline | null)?.duplicateNode(domNode), {
529
+ disabled: domNode.isInShadowTree(),
530
+ jslogContext: 'elements.duplicate-element',
531
+ });
532
+ contextMenu.editSection().appendItem(i18nString(UIStrings.deleteElement), () => void treeElement.remove(),
533
+ {jslogContext: 'delete-element'});
534
+ }
535
+
536
+ ElementsPanel.instance().populateAdornerSettingsContextMenu(contextMenu);
537
+
538
+ contextMenu.appendApplicableItems(domNode);
539
+ void contextMenu.show();
540
+ return contextMenu;
541
+ }