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.
- package/AUTHORS +1 -0
- package/front_end/core/platform/TypescriptUtilities.ts +0 -9
- package/front_end/core/platform/platform.ts +1 -1
- package/front_end/core/sdk/ConsoleModel.ts +6 -3
- package/front_end/core/sdk/DOMModel.ts +102 -0
- package/front_end/core/sdk/EmulationModel.ts +4 -2
- package/front_end/core/sdk/NetworkManager.ts +7 -6
- package/front_end/core/sdk/PageResourceLoader.ts +4 -3
- package/front_end/core/sdk/RuntimeModel.ts +5 -3
- package/front_end/core/sdk/SDKSettings.ts +47 -0
- package/front_end/core/sdk/sdk-meta.ts +0 -138
- package/front_end/entrypoints/inspector_main/RenderingOptions.ts +4 -2
- package/front_end/entrypoints/inspector_main/inspector_main-meta.ts +32 -0
- package/front_end/entrypoints/main/MainImpl.ts +1 -1
- package/front_end/generated/InspectorBackendCommands.ts +3 -1
- package/front_end/generated/SupportedCSSProperties.ts +20 -0
- package/front_end/generated/protocol-mapping.d.ts +7 -0
- package/front_end/generated/protocol-proxy-api.d.ts +5 -0
- package/front_end/generated/protocol.ts +15 -3
- package/front_end/panels/console/ConsoleView.ts +2 -1
- package/front_end/panels/console/console-meta.ts +10 -5
- package/front_end/panels/elements/DOMTreeContextMenu.ts +541 -0
- package/front_end/panels/elements/ElementsTreeElement.ts +27 -494
- package/front_end/panels/elements/ElementsTreeOutline.ts +173 -287
- package/front_end/panels/elements/StylesSidebarPane.ts +2 -1
- package/front_end/panels/elements/elements.ts +3 -0
- package/front_end/panels/issues/CorsIssueDetailsView.ts +13 -9
- package/front_end/panels/mobile_throttling/NetworkPanelIndicator.ts +3 -1
- package/front_end/panels/network/NetworkConfigView.ts +2 -1
- package/front_end/panels/network/NetworkPanel.ts +1 -1
- package/front_end/panels/network/network-meta.ts +60 -0
- package/front_end/panels/profiler/HeapDetachedElementsDataGrid.ts +2 -1
- package/front_end/panels/profiler/ProfileLauncherView.ts +19 -16
- package/front_end/panels/profiler/ProfileSidebarTreeElement.ts +1 -1
- package/front_end/panels/security/security.ts +6 -0
- package/front_end/panels/sources/SourcesView.ts +8 -107
- package/front_end/panels/sources/TabbedEditorContainer.ts +110 -7
- package/front_end/panels/sources/sources-meta.ts +16 -0
- package/front_end/panels/timeline/TimelineLoader.ts +1 -1
- package/front_end/panels/timeline/TimelineUIUtils.ts +6 -0
- package/front_end/panels/timeline/components/LayoutShiftDetails.ts +4 -3
- package/front_end/panels/timeline/components/LiveMetricsView.ts +1 -1
- package/front_end/panels/timeline/components/MetricCard.ts +4 -3
- package/front_end/panels/timeline/components/NetworkRequestDetails.ts +5 -2
- package/front_end/panels/timeline/components/TimelineRangeSummaryView.ts +1 -1
- package/front_end/panels/timeline/components/TimelineSummary.ts +8 -5
- package/front_end/third_party/chromium/README.chromium +1 -1
- package/front_end/ui/legacy/TabbedPane.ts +34 -2
- package/front_end/ui/legacy/components/quick_open/CommandMenu.ts +5 -5
- package/front_end/ui/legacy/components/quick_open/FilteredListWidget.ts +7 -7
- package/front_end/ui/legacy/components/quick_open/QuickOpen.ts +1 -1
- package/front_end/ui/legacy/components/quick_open/quick_open-meta.ts +2 -2
- package/front_end/ui/visual_logging/KnownContextValues.ts +29 -0
- 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
|
+
}
|