chrome-devtools-frontend 1.0.1646714 → 1.0.1650035
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/extension-api/ExtensionAPI.d.ts +26 -0
- package/front_end/core/common/Debouncer.ts +9 -1
- package/front_end/core/sdk/CSSMatchedStyles.ts +3 -1
- package/front_end/core/sdk/CSSMetadata.ts +20 -1
- package/front_end/core/sdk/CSSPropertyParserMatchers.ts +65 -0
- package/front_end/entrypoints/heap_snapshot_worker/HeapSnapshot.ts +15 -0
- package/front_end/generated/Deprecation.ts +8 -0
- package/front_end/generated/InspectorBackendCommands.ts +3 -2
- package/front_end/generated/SupportedCSSProperties.js +533 -19
- package/front_end/generated/protocol.ts +26 -4
- package/front_end/models/ai_assistance/agents/AiAgent.ts +8 -1
- package/front_end/models/ai_assistance/agents/PerformanceAgent.ts +7 -0
- package/front_end/models/extensions/ExtensionAPI.ts +47 -21
- package/front_end/models/javascript_metadata/NativeFunctions.js +7 -3
- package/front_end/panels/ai_assistance/AiAssistancePanel.ts +39 -1
- package/front_end/panels/ai_assistance/ai_assistance.ts +1 -0
- package/front_end/panels/ai_assistance/components/AIv2MarkdownRenderer.ts +228 -0
- package/front_end/panels/ai_assistance/components/ChatMessage.ts +39 -2
- package/front_end/panels/application/ApplicationPanelSidebar.ts +4 -0
- package/front_end/panels/application/CookieItemsView.ts +55 -6
- package/front_end/panels/application/DOMStorageItemsView.ts +43 -8
- package/front_end/panels/application/KeyValueStorageItemsView.ts +17 -9
- package/front_end/panels/console/ConsolePinPane.ts +1 -0
- package/front_end/panels/elements/ElementsTreeElement.ts +97 -57
- package/front_end/panels/elements/StylePropertyTreeElement.ts +73 -16
- package/front_end/panels/elements/StylesAiCodeCompletionProvider.ts +9 -1
- package/front_end/panels/elements/StylesSidebarPane.ts +22 -8
- package/front_end/panels/layer_viewer/PaintProfilerView.ts +309 -182
- package/front_end/panels/layer_viewer/paintProfiler.css +27 -23
- package/front_end/panels/layers/LayerPaintProfilerView.ts +86 -29
- package/front_end/panels/profiler/HeapSnapshotView.ts +6 -0
- package/front_end/panels/sources/WatchExpressionsSidebarPane.ts +210 -356
- package/front_end/panels/sources/watchExpressionsSidebarPane.css +7 -0
- package/front_end/panels/timeline/TimelinePaintProfilerView.ts +5 -5
- package/front_end/panels/timeline/components/NetworkTrackWidget.ts +122 -0
- package/front_end/panels/timeline/components/components.ts +2 -0
- package/front_end/panels/timeline/components/networkTrackWidget.css +31 -0
- package/front_end/panels/timeline/timeline.ts +2 -0
- package/front_end/third_party/chromium/README.chromium +1 -1
- package/front_end/ui/components/buttons/FloatingButton.ts +17 -2
- package/front_end/ui/components/buttons/floatingButton.css +2 -2
- package/front_end/ui/components/text_editor/AiCodeCompletionProvider.ts +1 -1
- package/front_end/ui/legacy/TextPrompt.ts +24 -18
- package/front_end/ui/legacy/Treeoutline.ts +31 -4
- package/front_end/ui/legacy/components/cookie_table/CookiesTable.ts +50 -24
- package/front_end/ui/legacy/components/source_frame/SourceFrame.ts +10 -10
- package/front_end/ui/visual_logging/KnownContextValues.ts +5 -0
- package/package.json +1 -1
|
@@ -50,6 +50,14 @@ const UIStrings = {
|
|
|
50
50
|
* @description Label for checkbox to show URL-decoded cookie values
|
|
51
51
|
*/
|
|
52
52
|
showUrlDecoded: 'Show URL-decoded',
|
|
53
|
+
/**
|
|
54
|
+
* @description Text of a context menu item to start a chat with AI
|
|
55
|
+
*/
|
|
56
|
+
startAChat: 'Start a chat',
|
|
57
|
+
/**
|
|
58
|
+
* @description Text of a context menu item to explain a web cookie with AI
|
|
59
|
+
*/
|
|
60
|
+
explainCookie: 'Explain this cookie',
|
|
53
61
|
/**
|
|
54
62
|
* @description Text in Cookie Items View of the Application panel to indicate that no cookie has been selected for preview
|
|
55
63
|
*/
|
|
@@ -174,6 +182,10 @@ interface CookieItemsViewInput {
|
|
|
174
182
|
onDeleteSelectedItems: () => void;
|
|
175
183
|
onDeleteAllItems: () => void;
|
|
176
184
|
onRefreshItems: () => void;
|
|
185
|
+
aiButtonIsEnabled: boolean;
|
|
186
|
+
onPopulateAiContextMenu: (cookie: SDK.Cookie.Cookie, contextMenu: UI.ContextMenu.ContextMenu) => void;
|
|
187
|
+
onAiButtonClick: (cookie: SDK.Cookie.Cookie, event: Event) => void;
|
|
188
|
+
aiButtonTitle?: string;
|
|
177
189
|
selectedCookie: SDK.Cookie.Cookie|null;
|
|
178
190
|
}
|
|
179
191
|
|
|
@@ -203,6 +215,10 @@ export const DEFAULT_VIEW: View = (input, output, target) => {
|
|
|
203
215
|
refreshCallback: input.onRefresh,
|
|
204
216
|
selectedCallback: input.onSelect,
|
|
205
217
|
deleteCallback: input.onDelete,
|
|
218
|
+
aiButtonIsEnabled: input.aiButtonIsEnabled,
|
|
219
|
+
onAiButtonClick: input.onAiButtonClick,
|
|
220
|
+
onPopulateAiContextMenu: input.onPopulateAiContextMenu,
|
|
221
|
+
aiButtonTitle: input.aiButtonTitle,
|
|
206
222
|
editable: true,
|
|
207
223
|
})}
|
|
208
224
|
></devtools-widget>
|
|
@@ -220,8 +236,8 @@ export const DEFAULT_VIEW: View = (input, output, target) => {
|
|
|
220
236
|
</devtools-split-view>
|
|
221
237
|
</devtools-widget>
|
|
222
238
|
`,
|
|
223
|
-
|
|
224
|
-
|
|
239
|
+
// clang-format on
|
|
240
|
+
target, {container: {attributes: {jslog: `${VisualLogging.pane('cookies-data')}`}}});
|
|
225
241
|
};
|
|
226
242
|
|
|
227
243
|
export class CookieItemsView extends UI.Widget.VBox {
|
|
@@ -261,7 +277,6 @@ export class CookieItemsView extends UI.Widget.VBox {
|
|
|
261
277
|
this.cookieDomain = domain;
|
|
262
278
|
this.refreshItems();
|
|
263
279
|
this.model.addEventListener(SDK.CookieModel.Events.COOKIE_LIST_UPDATED, this.onCookieListUpdate, this);
|
|
264
|
-
this.updateAiAssistanceContext(null);
|
|
265
280
|
}
|
|
266
281
|
|
|
267
282
|
override performUpdate(): void {
|
|
@@ -296,6 +311,12 @@ export class CookieItemsView extends UI.Widget.VBox {
|
|
|
296
311
|
onDeleteAllItems: this.deleteAllItems.bind(this),
|
|
297
312
|
onRefreshItems: this.refreshItems.bind(this),
|
|
298
313
|
selectedCookie: this.selectedCookie,
|
|
314
|
+
aiButtonIsEnabled: this.isAiButtonEnabled(),
|
|
315
|
+
onPopulateAiContextMenu: this.#onPopulateAiContextMenu.bind(this),
|
|
316
|
+
onAiButtonClick: this.#onAiButtonClick.bind(this),
|
|
317
|
+
aiButtonTitle: this.isAiButtonEnabled() ?
|
|
318
|
+
UI.ActionRegistry.ActionRegistry.instance().getAction('ai-assistance.storage-floating-button').title() :
|
|
319
|
+
undefined,
|
|
299
320
|
};
|
|
300
321
|
|
|
301
322
|
this.view(input, output, this.contentElement);
|
|
@@ -304,7 +325,6 @@ export class CookieItemsView extends UI.Widget.VBox {
|
|
|
304
325
|
override wasShown(): void {
|
|
305
326
|
super.wasShown();
|
|
306
327
|
this.refreshItems();
|
|
307
|
-
this.updateAiAssistanceContext(this.selectedCookie);
|
|
308
328
|
}
|
|
309
329
|
|
|
310
330
|
private showPreview(cookie: SDK.Cookie.Cookie|null): void {
|
|
@@ -313,10 +333,9 @@ export class CookieItemsView extends UI.Widget.VBox {
|
|
|
313
333
|
}
|
|
314
334
|
this.selectedCookie = cookie;
|
|
315
335
|
this.requestUpdate();
|
|
316
|
-
this.updateAiAssistanceContext(cookie);
|
|
317
336
|
}
|
|
318
337
|
|
|
319
|
-
|
|
338
|
+
#updateAiAssistanceContext(cookie: SDK.Cookie.Cookie|null): void {
|
|
320
339
|
if (cookie && cookie.httpOnly()) {
|
|
321
340
|
UI.Context.Context.instance().setFlavor(AiAssistanceModel.StorageItem.StorageItem, null);
|
|
322
341
|
return;
|
|
@@ -342,6 +361,7 @@ export class CookieItemsView extends UI.Widget.VBox {
|
|
|
342
361
|
}
|
|
343
362
|
this.#toolbar.setCanDeleteSelected(Boolean(selectedCookie));
|
|
344
363
|
this.showPreview(selectedCookie);
|
|
364
|
+
this.#updateAiAssistanceContext(selectedCookie);
|
|
345
365
|
}
|
|
346
366
|
|
|
347
367
|
private async saveCookie(newCookie: SDK.Cookie.Cookie, oldCookie: SDK.Cookie.Cookie|null): Promise<boolean> {
|
|
@@ -393,6 +413,7 @@ export class CookieItemsView extends UI.Widget.VBox {
|
|
|
393
413
|
* This will only delete the currently visible cookies.
|
|
394
414
|
*/
|
|
395
415
|
deleteAllItems(): void {
|
|
416
|
+
UI.Context.Context.instance().setFlavor(AiAssistanceModel.StorageItem.StorageItem, null);
|
|
396
417
|
this.showPreview(null);
|
|
397
418
|
void this.model.deleteCookies(this.shownCookies);
|
|
398
419
|
}
|
|
@@ -412,4 +433,32 @@ export class CookieItemsView extends UI.Widget.VBox {
|
|
|
412
433
|
refreshItems(): void {
|
|
413
434
|
void this.model.getCookiesForDomain(this.cookieDomain, true).then(this.updateWithCookies.bind(this));
|
|
414
435
|
}
|
|
436
|
+
|
|
437
|
+
private isAiButtonEnabled(): boolean {
|
|
438
|
+
return UI.ActionRegistry.ActionRegistry.instance().hasAction('ai-assistance.storage-floating-button');
|
|
439
|
+
}
|
|
440
|
+
|
|
441
|
+
#onPopulateAiContextMenu(cookie: SDK.Cookie.Cookie, contextMenu: UI.ContextMenu.ContextMenu): void {
|
|
442
|
+
const openAiAssistanceId = 'ai-assistance.application-panel-context';
|
|
443
|
+
if (this.isAiButtonEnabled() && UI.ActionRegistry.ActionRegistry.instance().hasAction(openAiAssistanceId)) {
|
|
444
|
+
this.#updateAiAssistanceContext(cookie);
|
|
445
|
+
if (UI.Context.Context.instance().flavor(AiAssistanceModel.StorageItem.StorageItem)) {
|
|
446
|
+
const action = UI.ActionRegistry.ActionRegistry.instance().getAction(openAiAssistanceId);
|
|
447
|
+
const submenu = contextMenu.footerSection().appendSubMenuItem(action.title(), false, openAiAssistanceId);
|
|
448
|
+
submenu.defaultSection().appendAction(openAiAssistanceId, i18nString(UIStrings.startAChat));
|
|
449
|
+
submenu.defaultSection().appendItem(
|
|
450
|
+
i18nString(UIStrings.explainCookie), () => action.execute({prompt: 'What is the purpose of this cookie?'}),
|
|
451
|
+
{disabled: !action.enabled(), jslogContext: openAiAssistanceId + '.cookies'});
|
|
452
|
+
}
|
|
453
|
+
}
|
|
454
|
+
}
|
|
455
|
+
|
|
456
|
+
#onAiButtonClick(cookie: SDK.Cookie.Cookie, _event: Event): void {
|
|
457
|
+
this.#updateAiAssistanceContext(cookie);
|
|
458
|
+
const actionRegistry = UI.ActionRegistry.ActionRegistry.instance();
|
|
459
|
+
const storageFloatingButtonId = 'ai-assistance.storage-floating-button';
|
|
460
|
+
if (actionRegistry.hasAction(storageFloatingButtonId)) {
|
|
461
|
+
void actionRegistry.getAction(storageFloatingButtonId).execute();
|
|
462
|
+
}
|
|
463
|
+
}
|
|
415
464
|
}
|
|
@@ -55,6 +55,14 @@ const UIStrings = {
|
|
|
55
55
|
* @description Text for announcing a DOM Storage key/value item has been deleted
|
|
56
56
|
*/
|
|
57
57
|
domStorageItemDeleted: 'The storage item was deleted.',
|
|
58
|
+
/**
|
|
59
|
+
* @description Text of a context menu item to start a chat with AI
|
|
60
|
+
*/
|
|
61
|
+
startAChat: 'Start a chat',
|
|
62
|
+
/**
|
|
63
|
+
* @description Text of a context menu item to explain a storage item of a storage bucket with AI
|
|
64
|
+
*/
|
|
65
|
+
explainItem: 'Explain this item',
|
|
58
66
|
} as const;
|
|
59
67
|
const str_ = i18n.i18n.registerUIStrings('panels/application/DOMStorageItemsView.ts', UIStrings);
|
|
60
68
|
const i18nString = i18n.i18n.getLocalizedString.bind(undefined, str_);
|
|
@@ -111,7 +119,6 @@ export class DOMStorageItemsView extends KeyValueStorageItemsView {
|
|
|
111
119
|
SDK.DOMStorageModel.DOMStorage.Events.DOM_STORAGE_ITEM_UPDATED, this.domStorageItemUpdated, this),
|
|
112
120
|
];
|
|
113
121
|
this.refreshItems();
|
|
114
|
-
this.selectedItemChanged(null);
|
|
115
122
|
}
|
|
116
123
|
|
|
117
124
|
private domStorageItemsCleared(): void {
|
|
@@ -174,13 +181,7 @@ export class DOMStorageItemsView extends KeyValueStorageItemsView {
|
|
|
174
181
|
this.showItems(filteredItems);
|
|
175
182
|
}
|
|
176
183
|
|
|
177
|
-
|
|
178
|
-
this.domStorage.clear();
|
|
179
|
-
// explicitly clear the view because the event won't be fired when it has no items
|
|
180
|
-
this.domStorageItemsCleared();
|
|
181
|
-
}
|
|
182
|
-
|
|
183
|
-
protected override selectedItemChanged(item: {key: string, value: string}|null): void {
|
|
184
|
+
#setAiStorageContext(item: {key: string, value: string}|null): void {
|
|
184
185
|
const storageKey = this.domStorage.storageKey;
|
|
185
186
|
if (!storageKey) {
|
|
186
187
|
return;
|
|
@@ -205,10 +206,44 @@ export class DOMStorageItemsView extends KeyValueStorageItemsView {
|
|
|
205
206
|
UI.Context.Context.instance().setFlavor(AiAssistanceModel.StorageItem.StorageItem, storageItem);
|
|
206
207
|
}
|
|
207
208
|
|
|
209
|
+
override deleteAllItems(): void {
|
|
210
|
+
this.domStorage.clear();
|
|
211
|
+
// explicitly clear the view because the event won't be fired when it has no items
|
|
212
|
+
this.domStorageItemsCleared();
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
protected override selectedItemChanged(item: {key: string, value: string}|null): void {
|
|
216
|
+
this.#setAiStorageContext(item);
|
|
217
|
+
}
|
|
218
|
+
|
|
208
219
|
protected override isAiButtonEnabled(): boolean {
|
|
209
220
|
return UI.ActionRegistry.ActionRegistry.instance().hasAction('ai-assistance.storage-floating-button');
|
|
210
221
|
}
|
|
211
222
|
|
|
223
|
+
protected override populateContextMenu(item: {key: string, value: string},
|
|
224
|
+
contextMenu: UI.ContextMenu.ContextMenu): void {
|
|
225
|
+
const openAiAssistanceId = 'ai-assistance.application-panel-context';
|
|
226
|
+
const actionRegistry = UI.ActionRegistry.ActionRegistry.instance();
|
|
227
|
+
if (actionRegistry.hasAction(openAiAssistanceId)) {
|
|
228
|
+
this.#setAiStorageContext(item);
|
|
229
|
+
const action = actionRegistry.getAction(openAiAssistanceId);
|
|
230
|
+
const submenu = contextMenu.footerSection().appendSubMenuItem(action.title(), false, openAiAssistanceId);
|
|
231
|
+
submenu.defaultSection().appendAction(openAiAssistanceId, i18nString(UIStrings.startAChat));
|
|
232
|
+
submenu.defaultSection().appendItem(i18nString(UIStrings.explainItem),
|
|
233
|
+
() => action.execute({prompt: 'Explain this storage item.'}),
|
|
234
|
+
{disabled: !action.enabled(), jslogContext: openAiAssistanceId + '.storage'});
|
|
235
|
+
}
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
protected override onAiButtonClick(item: {key: string, value: string}, _event: Event): void {
|
|
239
|
+
this.#setAiStorageContext(item);
|
|
240
|
+
const aiFloatingActionId = 'ai-assistance.storage-floating-button';
|
|
241
|
+
const actionRegistry = UI.ActionRegistry.ActionRegistry.instance();
|
|
242
|
+
if (actionRegistry.hasAction(aiFloatingActionId)) {
|
|
243
|
+
void actionRegistry.getAction(aiFloatingActionId).execute();
|
|
244
|
+
}
|
|
245
|
+
}
|
|
246
|
+
|
|
212
247
|
protected removeItem(key: string): void {
|
|
213
248
|
this.domStorage?.removeItem(key);
|
|
214
249
|
}
|
|
@@ -92,6 +92,7 @@ export interface ViewInput {
|
|
|
92
92
|
onDelete: (key: string) => void;
|
|
93
93
|
onDeleteSelected: () => void;
|
|
94
94
|
onDeleteAll: () => void;
|
|
95
|
+
onContextMenu?: (item: {key: string, value: string}, contextMenu: UI.ContextMenu.ContextMenu) => void;
|
|
95
96
|
jslog?: string;
|
|
96
97
|
classes?: string[];
|
|
97
98
|
aiButtonTitle?: string;
|
|
@@ -175,6 +176,7 @@ export abstract class KeyValueStorageItemsView extends UI.Widget.VBox {
|
|
|
175
176
|
@edit=${(e: CustomEvent<{columnId: string, valueBeforeEditing: string, newText: string}>) =>
|
|
176
177
|
input.onEdit(item.key, item.value, e.detail.columnId, e.detail.valueBeforeEditing, e.detail.newText)}
|
|
177
178
|
@delete=${() => input.onDelete(item.key)}
|
|
179
|
+
@contextmenu=${(e: CustomEvent<UI.ContextMenu.ContextMenu>) => input.onContextMenu?.(item, e.detail)}
|
|
178
180
|
selected=${(input.selectedKey === item.key) || nothing}>
|
|
179
181
|
<td>${input.showAiButton ? html`
|
|
180
182
|
<span class="ai-button-container">
|
|
@@ -198,8 +200,8 @@ export abstract class KeyValueStorageItemsView extends UI.Widget.VBox {
|
|
|
198
200
|
${input.preview?.element}
|
|
199
201
|
</devtools-widget>
|
|
200
202
|
</devtools-split-view>`,
|
|
201
|
-
|
|
202
|
-
|
|
203
|
+
// clang-format on
|
|
204
|
+
target, {container: {attributes: {jslog: input.jslog}, classes: input.classes}});
|
|
203
205
|
};
|
|
204
206
|
}
|
|
205
207
|
super();
|
|
@@ -246,13 +248,13 @@ export abstract class KeyValueStorageItemsView extends UI.Widget.VBox {
|
|
|
246
248
|
void this.#previewEntry(item);
|
|
247
249
|
this.selectedItemChanged(item);
|
|
248
250
|
},
|
|
249
|
-
onAiButtonClick: (
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
251
|
+
onAiButtonClick: this.isAiButtonEnabled() ?
|
|
252
|
+
(item: {key: string, value: string}, event: Event) => {
|
|
253
|
+
this.onAiButtonClick(item, event);
|
|
254
|
+
} :
|
|
255
|
+
undefined,
|
|
256
|
+
onContextMenu: (item: {key: string, value: string}, contextMenu: UI.ContextMenu.ContextMenu) => {
|
|
257
|
+
this.populateContextMenu(item, contextMenu);
|
|
256
258
|
},
|
|
257
259
|
|
|
258
260
|
onSort: (ascending: boolean) => {
|
|
@@ -284,6 +286,12 @@ export abstract class KeyValueStorageItemsView extends UI.Widget.VBox {
|
|
|
284
286
|
return false;
|
|
285
287
|
}
|
|
286
288
|
|
|
289
|
+
protected populateContextMenu(_item: {key: string, value: string}, _contextMenu: UI.ContextMenu.ContextMenu): void {
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
protected onAiButtonClick(_item: {key: string, value: string}, _event: Event): void {
|
|
293
|
+
}
|
|
294
|
+
|
|
287
295
|
protected get toolbar(): StorageItemsToolbar|undefined {
|
|
288
296
|
return this.#toolbar;
|
|
289
297
|
}
|
|
@@ -403,6 +403,7 @@ export class ConsolePinPresenter extends UI.Widget.Widget {
|
|
|
403
403
|
CodeMirror.EditorView.domEventHandlers({
|
|
404
404
|
blur: (_e, view) => this.#onBlur(view),
|
|
405
405
|
paste: () => this.#onPaste(),
|
|
406
|
+
drop: event => event.preventDefault(),
|
|
406
407
|
}),
|
|
407
408
|
TextEditor.Config.baseConfiguration(doc),
|
|
408
409
|
TextEditor.Config.closeBrackets.instance(),
|
|
@@ -738,65 +738,86 @@ function renderLinkifiedValue(value: string, node: SDK.DOMModel.DOMNode): Lit.Te
|
|
|
738
738
|
});
|
|
739
739
|
}
|
|
740
740
|
|
|
741
|
+
const relationPromisesCache = new WeakMap<SDK.DOMModel.DOMNode, Map<string, Promise<string|Lit.LitTemplate>>>();
|
|
742
|
+
const relatedElementsCache = new WeakMap<SDK.DOMModel.DOMNode, Map<string, SDK.DOMModel.DOMNode|null>>();
|
|
743
|
+
|
|
741
744
|
function renderAttribute(
|
|
742
745
|
attr: {name: string, value?: string}, updateRecord: Elements.ElementUpdateRecord.ElementUpdateRecord|null,
|
|
743
746
|
isDiff: boolean, node: SDK.DOMModel.DOMNode): Lit.LitTemplate {
|
|
744
747
|
const name = attr.name;
|
|
745
748
|
const value = attr.value || '';
|
|
746
749
|
const forceValue = isDiff;
|
|
750
|
+
const isRelation = name === 'popovertarget' || name === 'interesttarget' || name === 'commandfor';
|
|
747
751
|
const hasText = (forceValue || value.length > 0);
|
|
748
|
-
const
|
|
749
|
-
|
|
750
|
-
dblclick: true,
|
|
751
|
-
});
|
|
752
|
-
|
|
753
|
-
const relationRef =
|
|
754
|
-
(relation: Protocol.DOM.GetElementByRelationRequestRelation, tooltip: string): ReturnType<typeof ref> =>
|
|
755
|
-
ref((el): void => {
|
|
756
|
-
if (!el) {
|
|
757
|
-
return;
|
|
758
|
-
}
|
|
759
|
-
void (async(): Promise<void> => {
|
|
760
|
-
const relatedElementId = await node.domModel().getElementByRelation(node.id, relation);
|
|
761
|
-
const relatedElement = node.domModel().nodeForId(relatedElementId);
|
|
762
|
-
if (!relatedElement) {
|
|
763
|
-
return;
|
|
764
|
-
}
|
|
765
|
-
const link = PanelsCommon.DOMLinkifier.Linkifier.instance().linkify(relatedElement, {
|
|
766
|
-
preventKeyboardFocus: true,
|
|
767
|
-
tooltip,
|
|
768
|
-
textContent: el.textContent || undefined,
|
|
769
|
-
isDynamicLink: true,
|
|
770
|
-
});
|
|
771
|
-
render(link, el as HTMLElement);
|
|
772
|
-
})();
|
|
773
|
-
});
|
|
752
|
+
const linkifyName = isRelation && value.length === 0;
|
|
753
|
+
const linkifyValue = isRelation && value.length > 0;
|
|
774
754
|
|
|
775
|
-
let
|
|
776
|
-
|
|
755
|
+
let relation: Protocol.DOM.GetElementByRelationRequestRelation|undefined = undefined;
|
|
756
|
+
let tooltip = '';
|
|
757
|
+
if (isRelation) {
|
|
777
758
|
if (name === 'popovertarget') {
|
|
778
|
-
|
|
779
|
-
|
|
759
|
+
relation = Protocol.DOM.GetElementByRelationRequestRelation.PopoverTarget;
|
|
760
|
+
tooltip = i18nString(UIStrings.showPopoverTarget);
|
|
780
761
|
} else if (name === 'interesttarget') {
|
|
781
|
-
|
|
782
|
-
|
|
762
|
+
relation = Protocol.DOM.GetElementByRelationRequestRelation.InterestTarget;
|
|
763
|
+
tooltip = i18nString(UIStrings.showInterestTarget);
|
|
783
764
|
} else if (name === 'commandfor') {
|
|
784
|
-
|
|
785
|
-
|
|
786
|
-
}
|
|
787
|
-
}
|
|
765
|
+
relation = Protocol.DOM.GetElementByRelationRequestRelation.CommandFor;
|
|
766
|
+
tooltip = i18nString(UIStrings.showCommandForTarget);
|
|
767
|
+
}
|
|
768
|
+
}
|
|
769
|
+
|
|
770
|
+
let relationPromise: Promise<string|Lit.LitTemplate>|undefined = undefined;
|
|
771
|
+
if (isRelation && relation) {
|
|
772
|
+
let nodeCache = relationPromisesCache.get(node);
|
|
773
|
+
if (!nodeCache) {
|
|
774
|
+
nodeCache = new Map();
|
|
775
|
+
relationPromisesCache.set(node, nodeCache);
|
|
776
|
+
}
|
|
777
|
+
const cacheKey = `${relation}:${value}`;
|
|
778
|
+
relationPromise = nodeCache.get(cacheKey);
|
|
779
|
+
const relationType = relation;
|
|
780
|
+
if (!relationPromise) {
|
|
781
|
+
relationPromise = (async () => {
|
|
782
|
+
try {
|
|
783
|
+
const relatedElementId = await node.domModel().getElementByRelation(node.id, relationType);
|
|
784
|
+
const relatedElement = node.domModel().nodeForId(relatedElementId);
|
|
785
|
+
|
|
786
|
+
let elemCache = relatedElementsCache.get(node);
|
|
787
|
+
if (!elemCache) {
|
|
788
|
+
elemCache = new Map();
|
|
789
|
+
relatedElementsCache.set(node, elemCache);
|
|
790
|
+
}
|
|
791
|
+
elemCache.set(`${name}:${value}`, relatedElement || null);
|
|
788
792
|
|
|
789
|
-
|
|
790
|
-
|
|
791
|
-
|
|
792
|
-
|
|
793
|
-
|
|
794
|
-
|
|
795
|
-
|
|
796
|
-
|
|
797
|
-
|
|
798
|
-
|
|
799
|
-
|
|
793
|
+
const isNameLinking = value.length === 0;
|
|
794
|
+
const fallback = isNameLinking ? name : value;
|
|
795
|
+
|
|
796
|
+
if (!relatedElement) {
|
|
797
|
+
return fallback;
|
|
798
|
+
}
|
|
799
|
+
|
|
800
|
+
const linkOptions: PanelsCommon.DOMLinkifier.Options = {
|
|
801
|
+
preventKeyboardFocus: true,
|
|
802
|
+
tooltip,
|
|
803
|
+
isDynamicLink: true,
|
|
804
|
+
};
|
|
805
|
+
|
|
806
|
+
if (isNameLinking) {
|
|
807
|
+
linkOptions.textContent = name;
|
|
808
|
+
} else {
|
|
809
|
+
const targetId = relatedElement.getAttribute('id');
|
|
810
|
+
if (targetId) {
|
|
811
|
+
linkOptions.textContent = targetId;
|
|
812
|
+
}
|
|
813
|
+
}
|
|
814
|
+
|
|
815
|
+
return PanelsCommon.DOMLinkifier.Linkifier.instance().linkify(relatedElement, linkOptions);
|
|
816
|
+
} catch {
|
|
817
|
+
return value.length === 0 ? name : value;
|
|
818
|
+
}
|
|
819
|
+
})();
|
|
820
|
+
nodeCache.set(cacheKey, relationPromise);
|
|
800
821
|
}
|
|
801
822
|
}
|
|
802
823
|
|
|
@@ -815,23 +836,31 @@ function renderAttribute(
|
|
|
815
836
|
valueType = ValueType.SRCSET;
|
|
816
837
|
}
|
|
817
838
|
|
|
818
|
-
const withEntitiesRef = valueType === ValueType.UNKNOWN ? ref(el => {
|
|
839
|
+
const withEntitiesRef = (valueType === ValueType.UNKNOWN && !isRelation) ? ref(el => {
|
|
819
840
|
if (el) {
|
|
820
841
|
setValueWithEntities(el, value);
|
|
821
842
|
}
|
|
822
843
|
}) :
|
|
823
|
-
|
|
844
|
+
nothing;
|
|
845
|
+
|
|
846
|
+
const jslog = VisualLogging.value(name === 'style' ? 'style-attribute' : 'attribute').track({
|
|
847
|
+
change: true,
|
|
848
|
+
dblclick: true,
|
|
849
|
+
});
|
|
824
850
|
|
|
825
|
-
// clang-format off
|
|
826
851
|
return html`<span class="webkit-html-attribute" jslog=${jslog}><span class="webkit-html-attribute-name"
|
|
827
|
-
${animateOn(Boolean(updateRecord?.isAttributeModified(name) && !hasText), DOM_UPDATE_ANIMATION_CLASS_NAME)}
|
|
828
|
-
|
|
829
|
-
|
|
852
|
+
${animateOn(Boolean(updateRecord?.isAttributeModified(name) && !hasText), DOM_UPDATE_ANIMATION_CLASS_NAME)}>${
|
|
853
|
+
linkifyName && relationPromise ? Lit.Directives.until(relationPromise, name) : name}</span>${
|
|
854
|
+
hasText ?
|
|
855
|
+
html`=\u200B"<span class="webkit-html-attribute-value" ${
|
|
856
|
+
animateOn(Boolean(updateRecord?.isAttributeModified(name) && hasText),
|
|
857
|
+
DOM_UPDATE_ANIMATION_CLASS_NAME)} ${withEntitiesRef}>
|
|
830
858
|
${valueType === ValueType.SRC ? renderLinkifiedValue(value, node) : nothing}
|
|
831
|
-
${
|
|
859
|
+
${
|
|
860
|
+
valueType === ValueType.SRCSET ? renderLinkifiedSrcset(Common.Srcset.parseSrcset(value), node) : nothing}
|
|
861
|
+
${linkifyValue && relationPromise ? Lit.Directives.until(relationPromise, value) : nothing}
|
|
832
862
|
</span>"` :
|
|
833
|
-
|
|
834
|
-
// clang-format on
|
|
863
|
+
nothing}</span>`;
|
|
835
864
|
}
|
|
836
865
|
|
|
837
866
|
function renderTag(
|
|
@@ -2440,9 +2469,20 @@ export class ElementsTreeElement extends UI.TreeOutline.TreeElement {
|
|
|
2440
2469
|
}
|
|
2441
2470
|
}
|
|
2442
2471
|
|
|
2443
|
-
|
|
2472
|
+
let attributeValue = attributeName && attributeValueElement ?
|
|
2444
2473
|
this.nodeInternal.getAttribute(attributeName)?.replaceAll('"', '"') :
|
|
2445
2474
|
undefined;
|
|
2475
|
+
|
|
2476
|
+
const isRelation =
|
|
2477
|
+
attributeName === 'popovertarget' || attributeName === 'interesttarget' || attributeName === 'commandfor';
|
|
2478
|
+
if (isRelation && attributeName && attributeValueElement) {
|
|
2479
|
+
const rawValue = this.nodeInternal.getAttribute(attributeName) || '';
|
|
2480
|
+
const relatedElement = relatedElementsCache.get(this.nodeInternal)?.get(`${attributeName}:${rawValue}`);
|
|
2481
|
+
if (relatedElement) {
|
|
2482
|
+
attributeValue = relatedElement.getAttribute('id') || '';
|
|
2483
|
+
}
|
|
2484
|
+
}
|
|
2485
|
+
|
|
2446
2486
|
if (attributeValue !== undefined) {
|
|
2447
2487
|
attributeValueElement.setTextContentTruncatedIfNeeded(
|
|
2448
2488
|
attributeValue, i18nString(UIStrings.valueIsTooLargeToEdit));
|
|
@@ -316,6 +316,21 @@ export class CSSWideKeywordRenderer extends rendererBase(SDK.CSSPropertyParserMa
|
|
|
316
316
|
}
|
|
317
317
|
}
|
|
318
318
|
|
|
319
|
+
function handleVarDefinitionActivate(variable: string|SDK.CSSMatchedStyles.CSSValueSource,
|
|
320
|
+
stylesContainer: StylesContainer): void {
|
|
321
|
+
Host.userMetrics.actionTaken(Host.UserMetrics.Action.CustomPropertyLinkClicked);
|
|
322
|
+
Host.userMetrics.swatchActivated(Host.UserMetrics.SwatchType.VAR_LINK);
|
|
323
|
+
|
|
324
|
+
if (typeof variable === 'string') {
|
|
325
|
+
stylesContainer.jumpToProperty(variable) ||
|
|
326
|
+
stylesContainer.jumpToProperty('initial-value', variable, REGISTERED_PROPERTY_SECTION_NAME);
|
|
327
|
+
} else if (variable.declaration instanceof SDK.CSSProperty.CSSProperty) {
|
|
328
|
+
stylesContainer.revealProperty(variable.declaration);
|
|
329
|
+
} else if (variable.declaration instanceof SDK.CSSMatchedStyles.CSSRegisteredProperty) {
|
|
330
|
+
stylesContainer.jumpToProperty('initial-value', variable.name, REGISTERED_PROPERTY_SECTION_NAME);
|
|
331
|
+
}
|
|
332
|
+
}
|
|
333
|
+
|
|
319
334
|
// clang-format off
|
|
320
335
|
export class VariableRenderer extends rendererBase(SDK.CSSPropertyParserMatchers.VariableMatch) {
|
|
321
336
|
// clang-format on
|
|
@@ -344,7 +359,8 @@ export class VariableRenderer extends rendererBase(SDK.CSSPropertyParserMatchers
|
|
|
344
359
|
const {declaration, value: variableValue} = match.resolveVariable() ?? {};
|
|
345
360
|
const fromFallback = variableValue === undefined;
|
|
346
361
|
const computedValue = variableValue ?? match.fallbackValue();
|
|
347
|
-
const onLinkActivate = (name: string): void =>
|
|
362
|
+
const onLinkActivate = (name: string): void =>
|
|
363
|
+
handleVarDefinitionActivate(declaration ?? name, this.#stylesContainer);
|
|
348
364
|
const varSwatch = document.createElement('span');
|
|
349
365
|
|
|
350
366
|
const substitution = context.tracing?.substitution({match, context});
|
|
@@ -416,19 +432,59 @@ export class VariableRenderer extends rendererBase(SDK.CSSPropertyParserMatchers
|
|
|
416
432
|
|
|
417
433
|
return [colorSwatch, varSwatch];
|
|
418
434
|
}
|
|
435
|
+
}
|
|
419
436
|
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
437
|
+
// clang-format off
|
|
438
|
+
export class VariableNameRenderer extends rendererBase(SDK.CSSPropertyParserMatchers.VariableNameMatch) {
|
|
439
|
+
// clang-format on
|
|
440
|
+
readonly #stylesContainer: StylesContainer;
|
|
441
|
+
readonly #treeElement: StylePropertyTreeElement|null;
|
|
442
|
+
readonly #matchedStyles: SDK.CSSMatchedStyles.CSSMatchedStyles;
|
|
443
|
+
constructor(stylesContainer: StylesContainer, treeElement: StylePropertyTreeElement|null,
|
|
444
|
+
matchedStyles: SDK.CSSMatchedStyles.CSSMatchedStyles) {
|
|
445
|
+
super();
|
|
446
|
+
this.#treeElement = treeElement;
|
|
447
|
+
this.#stylesContainer = stylesContainer;
|
|
448
|
+
this.#matchedStyles = matchedStyles;
|
|
449
|
+
}
|
|
423
450
|
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
} else if (variable.declaration instanceof SDK.CSSProperty.CSSProperty) {
|
|
428
|
-
this.#stylesContainer.revealProperty(variable.declaration);
|
|
429
|
-
} else if (variable.declaration instanceof SDK.CSSMatchedStyles.CSSRegisteredProperty) {
|
|
430
|
-
this.#stylesContainer.jumpToProperty('initial-value', variable.name, REGISTERED_PROPERTY_SECTION_NAME);
|
|
451
|
+
override render(match: SDK.CSSPropertyParserMatchers.VariableNameMatch, context: RenderingContext): Node[] {
|
|
452
|
+
if (this.#treeElement?.property.ownerStyle.parentRule instanceof SDK.CSSRule.CSSFunctionRule) {
|
|
453
|
+
return Renderer.render(ASTUtils.children(match.node), context).nodes;
|
|
431
454
|
}
|
|
455
|
+
|
|
456
|
+
const {declaration, value: variableValue} = match.resolveVariable() ?? {};
|
|
457
|
+
const isDefined = variableValue !== undefined;
|
|
458
|
+
const onLinkActivate = (name: string): void =>
|
|
459
|
+
handleVarDefinitionActivate(declaration ?? name, this.#stylesContainer);
|
|
460
|
+
const varSwatch = document.createElement('span');
|
|
461
|
+
|
|
462
|
+
const tooltipContents =
|
|
463
|
+
this.#stylesContainer.getVariablePopoverContents(this.#matchedStyles, match.text, variableValue ?? null);
|
|
464
|
+
const tooltipId = this.#treeElement?.getTooltipId('custom-property-var');
|
|
465
|
+
const tooltip = tooltipId ? {tooltipId} : undefined;
|
|
466
|
+
// clang-format off
|
|
467
|
+
render(html`
|
|
468
|
+
<devtools-link-swatch class=css-var-link .data=${{
|
|
469
|
+
tooltip,
|
|
470
|
+
text: match.text,
|
|
471
|
+
isDefined,
|
|
472
|
+
onLinkActivate,
|
|
473
|
+
}}>
|
|
474
|
+
</devtools-link-swatch>
|
|
475
|
+
${tooltipId ? html`
|
|
476
|
+
<devtools-tooltip
|
|
477
|
+
id=${tooltipId}
|
|
478
|
+
variant=rich
|
|
479
|
+
jslogContext=elements.css-var
|
|
480
|
+
>
|
|
481
|
+
${tooltipContents}
|
|
482
|
+
</devtools-tooltip>
|
|
483
|
+
` : ''}
|
|
484
|
+
`, varSwatch);
|
|
485
|
+
// clang-format on
|
|
486
|
+
|
|
487
|
+
return [varSwatch];
|
|
432
488
|
}
|
|
433
489
|
}
|
|
434
490
|
|
|
@@ -2007,6 +2063,7 @@ export function getPropertyRenderers(
|
|
|
2007
2063
|
Array<MatchRenderer<SDK.CSSPropertyParser.Match>> {
|
|
2008
2064
|
return [
|
|
2009
2065
|
new VariableRenderer(stylesContainer, treeElement, matchedStyles, computedStyles, computedStyleExtraFields),
|
|
2066
|
+
new VariableNameRenderer(stylesContainer, treeElement, matchedStyles),
|
|
2010
2067
|
new ColorRenderer(stylesContainer, treeElement),
|
|
2011
2068
|
new ColorMixRenderer(stylesContainer, matchedStyles, computedStyles, computedStyleExtraFields, treeElement),
|
|
2012
2069
|
new ContrastColorRenderer(stylesContainer, treeElement),
|
|
@@ -2026,10 +2083,10 @@ export function getPropertyRenderers(
|
|
|
2026
2083
|
new EnvFunctionRenderer(treeElement, matchedStyles, computedStyles, computedStyleExtraFields),
|
|
2027
2084
|
new PositionTryRenderer(matchedStyles),
|
|
2028
2085
|
new LengthRenderer(stylesContainer, propertyName, treeElement),
|
|
2029
|
-
new MathFunctionRenderer(
|
|
2030
|
-
|
|
2031
|
-
new CustomFunctionRenderer(
|
|
2032
|
-
|
|
2086
|
+
new MathFunctionRenderer(stylesContainer, matchedStyles, computedStyles, computedStyleExtraFields, propertyName,
|
|
2087
|
+
treeElement),
|
|
2088
|
+
new CustomFunctionRenderer(stylesContainer, matchedStyles, computedStyles, computedStyleExtraFields, propertyName,
|
|
2089
|
+
treeElement),
|
|
2033
2090
|
new AutoBaseRenderer(computedStyles, computedStyleExtraFields),
|
|
2034
2091
|
new BinOpRenderer(),
|
|
2035
2092
|
new RelativeColorChannelRenderer(treeElement),
|
|
@@ -2082,7 +2139,7 @@ export class StylePropertyTreeElement extends UI.TreeOutline.TreeElement {
|
|
|
2082
2139
|
this.#parentSection = section;
|
|
2083
2140
|
this.isShorthand = isShorthand;
|
|
2084
2141
|
this.newProperty = newProperty;
|
|
2085
|
-
this.#lazyRender = stylesContainer.shouldRenderLazily();
|
|
2142
|
+
this.#lazyRender = !this.newProperty && stylesContainer.shouldRenderLazily();
|
|
2086
2143
|
if (this.newProperty) {
|
|
2087
2144
|
this.listItemElement.textContent = '';
|
|
2088
2145
|
}
|
|
@@ -137,7 +137,15 @@ export class StylesAiCodeCompletionProvider {
|
|
|
137
137
|
}
|
|
138
138
|
currentPropertyString = currentPropertyString + text;
|
|
139
139
|
prefix = prefix + text;
|
|
140
|
-
|
|
140
|
+
let suffix = content.substring(propertyEndOffset);
|
|
141
|
+
|
|
142
|
+
const maxLength = TextEditor.AiCodeCompletionProvider.MAX_PREFIX_SUFFIX_LENGTH;
|
|
143
|
+
if (prefix.length > maxLength) {
|
|
144
|
+
prefix = prefix.substring(prefix.length - maxLength);
|
|
145
|
+
}
|
|
146
|
+
if (suffix.length > maxLength) {
|
|
147
|
+
suffix = suffix.substring(0, maxLength);
|
|
148
|
+
}
|
|
141
149
|
|
|
142
150
|
const startTime = performance.now();
|
|
143
151
|
// TODO(b/476098133): Consider adjusting cursor position
|