chrome-devtools-frontend 1.0.1646286 → 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.
- package/SECURITY.md +1 -1
- 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 +1 -0
- package/front_end/core/sdk/CSSPropertyParserMatchers.ts +65 -0
- package/front_end/generated/InspectorBackendCommands.ts +2 -1
- package/front_end/generated/SupportedCSSProperties.js +739 -21
- package/front_end/generated/protocol.ts +26 -0
- package/front_end/models/ai_assistance/AiAgent2.ts +14 -9
- package/front_end/models/ai_assistance/agents/AiAgent.ts +10 -3
- package/front_end/models/ai_assistance/agents/PerformanceAgent.ts +7 -0
- package/front_end/models/ai_assistance/agents/StorageAgent.ts +45 -0
- package/front_end/models/ai_assistance/ai_assistance.ts +4 -0
- package/front_end/models/ai_assistance/skills/Skill.ts +1 -1
- package/front_end/models/ai_assistance/skills/SkillRegistry.ts +2 -0
- package/front_end/models/ai_assistance/skills/network.md +16 -0
- package/front_end/models/ai_assistance/skills/styling.md +1 -1
- package/front_end/models/ai_assistance/tools/GetNetworkRequestDetails.ts +118 -0
- package/front_end/models/ai_assistance/tools/ListNetworkRequests.ts +124 -0
- package/front_end/models/ai_assistance/tools/Tool.ts +2 -0
- package/front_end/models/ai_assistance/tools/ToolRegistry.ts +4 -0
- package/front_end/models/emulation/EmulatedDevices.ts +430 -12
- package/front_end/models/extensions/ExtensionAPI.ts +47 -21
- package/front_end/models/javascript_metadata/NativeFunctions.js +5 -1
- package/front_end/panels/ai_assistance/AiAssistancePanel.ts +40 -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/application/WebMCPTreeElement.ts +8 -0
- 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/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/utils/Linkifier.ts +10 -0
- package/front_end/ui/visual_logging/KnownContextValues.ts +13 -0
- package/package.json +1 -1
|
@@ -18,6 +18,7 @@ import type * as LHModel from '../../models/lighthouse/lighthouse.js';
|
|
|
18
18
|
import type * as Trace from '../../models/trace/trace.js';
|
|
19
19
|
import * as Workspace from '../../models/workspace/workspace.js';
|
|
20
20
|
import * as Buttons from '../../ui/components/buttons/buttons.js';
|
|
21
|
+
import type * as MarkdownView from '../../ui/components/markdown_view/markdown_view.js';
|
|
21
22
|
import * as Snackbars from '../../ui/components/snackbars/snackbars.js';
|
|
22
23
|
import * as UIHelpers from '../../ui/helpers/helpers.js';
|
|
23
24
|
import * as UI from '../../ui/legacy/legacy.js';
|
|
@@ -30,6 +31,10 @@ import * as TimelinePanel from '../timeline/timeline.js';
|
|
|
30
31
|
|
|
31
32
|
import aiAssistancePanelStyles from './aiAssistancePanel.css.js';
|
|
32
33
|
import {AccessibilityAgentMarkdownRenderer} from './components/AccessibilityAgentMarkdownRenderer.js';
|
|
34
|
+
import {
|
|
35
|
+
AIv2MarkdownRenderer,
|
|
36
|
+
type AIv2MarkdownRendererOptions,
|
|
37
|
+
} from './components/AIv2MarkdownRenderer.js';
|
|
33
38
|
import {
|
|
34
39
|
type AnswerPart,
|
|
35
40
|
ChatMessageEntity,
|
|
@@ -349,6 +354,7 @@ async function getEmptyStateSuggestions(conversation?: AiAssistanceModel.AiConve
|
|
|
349
354
|
return [
|
|
350
355
|
{title: 'How is localStorage used on this page?', jslogContext: 'storage-default'},
|
|
351
356
|
{title: 'How is sessionStorage used on this page?', jslogContext: 'storage-default'},
|
|
357
|
+
{title: 'What cookies are stored for this page?', jslogContext: 'storage-default'},
|
|
352
358
|
];
|
|
353
359
|
}
|
|
354
360
|
|
|
@@ -357,8 +363,41 @@ async function getEmptyStateSuggestions(conversation?: AiAssistanceModel.AiConve
|
|
|
357
363
|
}
|
|
358
364
|
}
|
|
359
365
|
|
|
366
|
+
function createV2MarkdownRenderer(conversation?: AiAssistanceModel.AiConversation.AiConversation):
|
|
367
|
+
AIv2MarkdownRenderer {
|
|
368
|
+
const options: AIv2MarkdownRendererOptions = {};
|
|
369
|
+
const primaryTarget = SDK.TargetManager.TargetManager.instance().primaryPageTarget();
|
|
370
|
+
const domModel = primaryTarget?.model(SDK.DOMModel.DOMModel);
|
|
371
|
+
const resourceTreeModel = primaryTarget?.model(SDK.ResourceTreeModel.ResourceTreeModel);
|
|
372
|
+
const context = conversation?.selectedContext;
|
|
373
|
+
|
|
374
|
+
if (context instanceof AiAssistanceModel.PerformanceAgent.PerformanceTraceContext) {
|
|
375
|
+
const focus = context.getItem();
|
|
376
|
+
options.mainFrameId = focus.parsedTrace.data.Meta.mainFrameId;
|
|
377
|
+
options.lookupTraceEvent = focus.lookupEvent.bind(focus);
|
|
378
|
+
} else {
|
|
379
|
+
if (domModel) {
|
|
380
|
+
options.mainDocumentURL = domModel.existingDocument()?.documentURL;
|
|
381
|
+
}
|
|
382
|
+
if (resourceTreeModel) {
|
|
383
|
+
options.mainFrameId = resourceTreeModel.mainFrame?.id;
|
|
384
|
+
}
|
|
385
|
+
}
|
|
386
|
+
return new AIv2MarkdownRenderer(options);
|
|
387
|
+
}
|
|
388
|
+
|
|
360
389
|
function getMarkdownRenderer(conversation?: AiAssistanceModel.AiConversation.AiConversation):
|
|
361
|
-
|
|
390
|
+
MarkdownView.MarkdownView.MarkdownInsightRenderer {
|
|
391
|
+
if (conversation?.type === AiAssistanceModel.AiHistoryStorage.ConversationType.PERFORMANCE &&
|
|
392
|
+
conversation.isReadOnly) {
|
|
393
|
+
// Handle historical conversations (can't linkify anything).
|
|
394
|
+
return new PerformanceAgentMarkdownRenderer();
|
|
395
|
+
}
|
|
396
|
+
|
|
397
|
+
if (Root.Runtime.hostConfig.devToolsAiV2Architecture?.enabled && conversation && !conversation.isReadOnly) {
|
|
398
|
+
return createV2MarkdownRenderer(conversation);
|
|
399
|
+
}
|
|
400
|
+
|
|
362
401
|
const context = conversation?.selectedContext;
|
|
363
402
|
|
|
364
403
|
if (context instanceof AiAssistanceModel.PerformanceAgent.PerformanceTraceContext) {
|
|
@@ -5,6 +5,7 @@
|
|
|
5
5
|
export * from './AiAssistancePanel.js';
|
|
6
6
|
export * from './components/ChatView.js';
|
|
7
7
|
export * from './components/AccessibilityAgentMarkdownRenderer.js';
|
|
8
|
+
export * from './components/AIv2MarkdownRenderer.js';
|
|
8
9
|
export * as ChatInput from './components/ChatInput.js';
|
|
9
10
|
export * from './components/MarkdownRendererWithCodeBlock.js';
|
|
10
11
|
export * from './SelectWorkspaceDialog.js';
|
|
@@ -0,0 +1,228 @@
|
|
|
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 Platform from '../../../core/platform/platform.js';
|
|
7
|
+
import * as SDK from '../../../core/sdk/sdk.js';
|
|
8
|
+
import type * as Protocol from '../../../generated/protocol.js';
|
|
9
|
+
import * as AiAssistanceModel from '../../../models/ai_assistance/ai_assistance.js';
|
|
10
|
+
import * as Logs from '../../../models/logs/logs.js';
|
|
11
|
+
import * as Trace from '../../../models/trace/trace.js';
|
|
12
|
+
import type * as Marked from '../../../third_party/marked/marked.js';
|
|
13
|
+
import * as MarkdownView from '../../../ui/components/markdown_view/markdown_view.js';
|
|
14
|
+
import * as Lit from '../../../ui/lit/lit.js';
|
|
15
|
+
import * as PanelsCommon from '../../common/common.js';
|
|
16
|
+
|
|
17
|
+
const {html} = Lit.StaticHtml;
|
|
18
|
+
const {until} = Lit.Directives;
|
|
19
|
+
|
|
20
|
+
export interface AIv2MarkdownRendererOptions {
|
|
21
|
+
mainFrameId?: string;
|
|
22
|
+
mainDocumentURL?: Platform.DevToolsPath.UrlString;
|
|
23
|
+
lookupTraceEvent?: (key: string) => Trace.Types.Events.Event | null;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
type ParsedLink = {
|
|
27
|
+
type: 'path',
|
|
28
|
+
path: string,
|
|
29
|
+
}|{
|
|
30
|
+
type: 'node',
|
|
31
|
+
nodeId: Protocol.DOM.BackendNodeId,
|
|
32
|
+
};
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* AIv2MarkdownRenderer is currently duplicated from the agent-specific renderers
|
|
36
|
+
* as part of the migration to the V2 architecture. It will eventually become
|
|
37
|
+
* the only markdown renderer used by AI assistance.
|
|
38
|
+
*/
|
|
39
|
+
export class AIv2MarkdownRenderer extends MarkdownView.MarkdownView.MarkdownInsightRenderer {
|
|
40
|
+
constructor(private readonly options: AIv2MarkdownRendererOptions = {}) {
|
|
41
|
+
super();
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
#isSameOrigin(node: SDK.DOMModel.DOMNode): boolean {
|
|
45
|
+
if (!this.options.mainDocumentURL) {
|
|
46
|
+
return true;
|
|
47
|
+
}
|
|
48
|
+
const nodeDocumentURL = node.ownerDocument?.documentURL ?? '' as Platform.DevToolsPath.UrlString;
|
|
49
|
+
return AiAssistanceModel.AiUtils.isSameOrigin(this.options.mainDocumentURL, nodeDocumentURL);
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
#revealableLink(revealable: unknown, label: string): Lit.LitTemplate {
|
|
53
|
+
return html`<devtools-link @click=${(e: Event) => {
|
|
54
|
+
e.preventDefault();
|
|
55
|
+
e.stopPropagation();
|
|
56
|
+
void Common.Revealer.reveal(revealable);
|
|
57
|
+
}}>${Platform.StringUtilities.trimEndWithMaxLength(label, 100)}</devtools-link>`;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
#renderLink(href: string, text: string): Lit.LitTemplate|null {
|
|
61
|
+
const devtoolsLink = this.#renderDevToolsLink(href, text);
|
|
62
|
+
if (devtoolsLink) {
|
|
63
|
+
return devtoolsLink;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
if (href.startsWith('#')) {
|
|
67
|
+
const parsed = this.#parseLink(href);
|
|
68
|
+
if (parsed) {
|
|
69
|
+
const resultPromise =
|
|
70
|
+
parsed.type === 'path' ? this.#linkifyPath(parsed.path, text) : this.#linkifyNode(parsed.nodeId, text);
|
|
71
|
+
|
|
72
|
+
return html`<span>${until(resultPromise.then(node => node || text), text)}</span>`;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
if (this.options.lookupTraceEvent) {
|
|
76
|
+
const event = this.options.lookupTraceEvent(href.slice(1));
|
|
77
|
+
if (event) {
|
|
78
|
+
let label = text;
|
|
79
|
+
let title = '';
|
|
80
|
+
if (Trace.Types.Events.isSyntheticNetworkRequest(event)) {
|
|
81
|
+
title = event.args.data.url;
|
|
82
|
+
} else {
|
|
83
|
+
label += ` (${event.name})`;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
// eslint-disable-next-line @devtools/no-a-tags-in-lit
|
|
87
|
+
return html`<a href="#" draggable=false .title=${title} @click=${(e: Event) => {
|
|
88
|
+
e.stopPropagation();
|
|
89
|
+
void Common.Revealer.reveal(new SDK.TraceObject.RevealableEvent(event));
|
|
90
|
+
}}>${label}</a>`;
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
return null;
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
#renderDevToolsLink(
|
|
99
|
+
href: string,
|
|
100
|
+
fallbackText: string,
|
|
101
|
+
): Lit.LitTemplate|null {
|
|
102
|
+
if (href.startsWith('#req-')) {
|
|
103
|
+
const request = Logs.NetworkLog.NetworkLog.instance().requests().find(
|
|
104
|
+
req => req.requestId() === href.substring(5),
|
|
105
|
+
);
|
|
106
|
+
|
|
107
|
+
if (request) {
|
|
108
|
+
return this.#revealableLink(request, request.url());
|
|
109
|
+
}
|
|
110
|
+
return html`${fallbackText}`;
|
|
111
|
+
}
|
|
112
|
+
if (href.startsWith('#file-')) {
|
|
113
|
+
const file = AiAssistanceModel.ContextSelectionAgent.ContextSelectionAgent.getUISourceCodes().find(
|
|
114
|
+
file => AiAssistanceModel.ContextSelectionAgent.ContextSelectionAgent.uiSourceCodeId.get(file) ===
|
|
115
|
+
Number(href.substring(6)));
|
|
116
|
+
|
|
117
|
+
if (file) {
|
|
118
|
+
return this.#revealableLink(file, file.name());
|
|
119
|
+
}
|
|
120
|
+
return html`${fallbackText}`;
|
|
121
|
+
}
|
|
122
|
+
return null;
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
#parseLink(href: string): ParsedLink|null {
|
|
126
|
+
if (href.startsWith('#path-')) {
|
|
127
|
+
return {type: 'path', path: href.replace('#path-', '')};
|
|
128
|
+
}
|
|
129
|
+
if (href.startsWith('#1,HTML')) {
|
|
130
|
+
return {type: 'path', path: href.slice(1)};
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
let nodeIdStr = '';
|
|
134
|
+
if (href.startsWith('#node-')) {
|
|
135
|
+
nodeIdStr = href.replace('#node-', '');
|
|
136
|
+
} else if (href.startsWith('#')) {
|
|
137
|
+
nodeIdStr = href.slice(1);
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
if (nodeIdStr.trim() !== '') {
|
|
141
|
+
const nodeId = Number(nodeIdStr);
|
|
142
|
+
if (Number.isInteger(nodeId)) {
|
|
143
|
+
return {type: 'node', nodeId: nodeId as Protocol.DOM.BackendNodeId};
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
return null;
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
async #linkifyNode(backendNodeId: Protocol.DOM.BackendNodeId, label: string): Promise<Lit.LitTemplate|undefined> {
|
|
151
|
+
const target = SDK.TargetManager.TargetManager.instance().primaryPageTarget();
|
|
152
|
+
const domModel = target?.model(SDK.DOMModel.DOMModel);
|
|
153
|
+
if (!domModel) {
|
|
154
|
+
return undefined;
|
|
155
|
+
}
|
|
156
|
+
const domNodesMap = await domModel.pushNodesByBackendIdsToFrontend(new Set([backendNodeId]));
|
|
157
|
+
const node = domNodesMap?.get(backendNodeId);
|
|
158
|
+
if (!node) {
|
|
159
|
+
return;
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
if (this.options.mainFrameId && node.frameId() !== this.options.mainFrameId) {
|
|
163
|
+
return;
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
if (!this.#isSameOrigin(node)) {
|
|
167
|
+
return;
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
const linkedNode = PanelsCommon.DOMLinkifier.Linkifier.instance().linkify(node, {textContent: label});
|
|
171
|
+
return linkedNode;
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
async #linkifyPath(path: string, label: string): Promise<Lit.LitTemplate|undefined> {
|
|
175
|
+
const target = SDK.TargetManager.TargetManager.instance().primaryPageTarget();
|
|
176
|
+
const domModel = target?.model(SDK.DOMModel.DOMModel);
|
|
177
|
+
if (!domModel) {
|
|
178
|
+
return undefined;
|
|
179
|
+
}
|
|
180
|
+
const nodeId = await domModel.pushNodeByPathToFrontend(path);
|
|
181
|
+
if (!nodeId) {
|
|
182
|
+
return;
|
|
183
|
+
}
|
|
184
|
+
const node = domModel.nodeForId(nodeId);
|
|
185
|
+
if (!node) {
|
|
186
|
+
return;
|
|
187
|
+
}
|
|
188
|
+
if (!this.#isSameOrigin(node)) {
|
|
189
|
+
return;
|
|
190
|
+
}
|
|
191
|
+
const linkedNode = PanelsCommon.DOMLinkifier.Linkifier.instance().linkify(node, {textContent: label});
|
|
192
|
+
return linkedNode;
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
override templateForToken(token: Marked.Marked.MarkedToken): Lit.LitTemplate|null {
|
|
196
|
+
if (token.type === 'link') {
|
|
197
|
+
const link = this.#renderLink(token.href, token.text);
|
|
198
|
+
if (link) {
|
|
199
|
+
return link;
|
|
200
|
+
}
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
if (token.type === 'code') {
|
|
204
|
+
const lines = (token.text).split('\n');
|
|
205
|
+
if (lines[0]?.trim() === 'css') {
|
|
206
|
+
token.lang = 'css';
|
|
207
|
+
token.text = lines.slice(1).join('\n');
|
|
208
|
+
}
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
if (token.type === 'codespan') {
|
|
212
|
+
// LLM likes outputting the link inside a codespan block.
|
|
213
|
+
// Remove the codespan and render the link directly
|
|
214
|
+
const matches = token.text.match(/^\[(.*)\]\((.+)\)$/);
|
|
215
|
+
if (matches?.[2]) {
|
|
216
|
+
const link = this.#renderLink(
|
|
217
|
+
matches[2],
|
|
218
|
+
matches[1],
|
|
219
|
+
);
|
|
220
|
+
if (link) {
|
|
221
|
+
return link;
|
|
222
|
+
}
|
|
223
|
+
}
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
return super.templateForToken(token);
|
|
227
|
+
}
|
|
228
|
+
}
|
|
@@ -14,8 +14,8 @@ import * as SDK from '../../../core/sdk/sdk.js';
|
|
|
14
14
|
import type * as Protocol from '../../../generated/protocol.js';
|
|
15
15
|
import type {
|
|
16
16
|
AiWidget, BottomUpTreeAiWidget, ComputedStyleAiWidget, CoreVitalsAiWidget, DomTreeAiWidget, LighthouseReportAiWidget,
|
|
17
|
-
NetworkRequestGeneralHeadersAiWidget, NetworkRequestsListAiWidget,
|
|
18
|
-
SourceCodeAiWidget, SourceFileAiWidget, SourceFilesListAiWidget, StylePropertiesAiWidget,
|
|
17
|
+
NetworkRequestGeneralHeadersAiWidget, NetworkRequestsListAiWidget, NetworkTrackAiWidget, PerfInsightAiWidget,
|
|
18
|
+
PerformanceTraceAiWidget, SourceCodeAiWidget, SourceFileAiWidget, SourceFilesListAiWidget, StylePropertiesAiWidget,
|
|
19
19
|
TimelineEventSummaryAiWidget, TimelineRangeSummaryAiWidget} from '../../../models/ai_assistance/agents/AiAgent.js';
|
|
20
20
|
import * as AiAssistanceModel from '../../../models/ai_assistance/ai_assistance.js';
|
|
21
21
|
import * as ComputedStyle from '../../../models/computed_style/computed_style.js';
|
|
@@ -231,6 +231,10 @@ const UIStringsNotTranslate = {
|
|
|
231
231
|
* @description Accessible label for the reveal button in the performance summary widget.
|
|
232
232
|
*/
|
|
233
233
|
revealPerformanceSummary: 'Reveal performance summary',
|
|
234
|
+
/**
|
|
235
|
+
* @description Accessible label for the reveal button in the network track widget.
|
|
236
|
+
*/
|
|
237
|
+
revealNetworkActivity: 'Reveal network activity',
|
|
234
238
|
/**
|
|
235
239
|
* @description Accessible label for the reveal button in the bottom up thread activity widget.
|
|
236
240
|
*/
|
|
@@ -295,6 +299,10 @@ const UIStringsNotTranslate = {
|
|
|
295
299
|
* @description Title for the performance summary widget.
|
|
296
300
|
*/
|
|
297
301
|
performanceSummary: 'Performance summary',
|
|
302
|
+
/**
|
|
303
|
+
* @description Title for the network activity summary widget.
|
|
304
|
+
*/
|
|
305
|
+
networkActivitySummary: 'Network activity',
|
|
298
306
|
/**
|
|
299
307
|
* @description The title of the button that allows exporting the conversation for agents.
|
|
300
308
|
*/
|
|
@@ -1658,6 +1666,8 @@ export function getWidgetSignature(widget: AiWidget): string {
|
|
|
1658
1666
|
return `${widget.name}:${widget.data.track}:${widget.data.bounds.min}-${widget.data.bounds.max}`;
|
|
1659
1667
|
case 'BOTTOM_UP_TREE':
|
|
1660
1668
|
return `${widget.name}:${widget.data.bounds.min}-${widget.data.bounds.max}`;
|
|
1669
|
+
case 'NETWORK_TRACK':
|
|
1670
|
+
return `${widget.name}:${widget.data.bounds.min}-${widget.data.bounds.max}`;
|
|
1661
1671
|
case 'SOURCE_FILE':
|
|
1662
1672
|
return `${widget.name}:${widget.data.uiSourceCode.url()}`;
|
|
1663
1673
|
case 'SOURCE_FILES_LIST':
|
|
@@ -1755,6 +1765,9 @@ async function renderWidgets(
|
|
|
1755
1765
|
case 'BOTTOM_UP_TREE':
|
|
1756
1766
|
response = await makeBottomUpTimelineTreeWidget(widgetData);
|
|
1757
1767
|
break;
|
|
1768
|
+
case 'NETWORK_TRACK':
|
|
1769
|
+
response = await makeNetworkTrackWidget(widgetData);
|
|
1770
|
+
break;
|
|
1758
1771
|
case 'SOURCE_FILE':
|
|
1759
1772
|
response = await makeSourceFileWidget(widgetData);
|
|
1760
1773
|
break;
|
|
@@ -2316,6 +2329,30 @@ async function makeTimelineRangeSummaryWidget(widgetData: TimelineRangeSummaryAi
|
|
|
2316
2329
|
};
|
|
2317
2330
|
}
|
|
2318
2331
|
|
|
2332
|
+
async function makeNetworkTrackWidget(widgetData: NetworkTrackAiWidget): Promise<WidgetMakerResponse|null> {
|
|
2333
|
+
const {parsedTrace, bounds} = widgetData.data;
|
|
2334
|
+
const dataProvider = new Timeline.TimelineFlameChartNetworkDataProvider.TimelineFlameChartNetworkDataProvider();
|
|
2335
|
+
|
|
2336
|
+
// clang-format off
|
|
2337
|
+
const template = html`
|
|
2338
|
+
<devtools-performance-agent-network-track
|
|
2339
|
+
.data=${{
|
|
2340
|
+
parsedTrace,
|
|
2341
|
+
bounds,
|
|
2342
|
+
dataProvider,
|
|
2343
|
+
} as TimelineComponents.NetworkTrackWidget.NetworkTrackWidgetData}
|
|
2344
|
+
></devtools-performance-agent-network-track>`;
|
|
2345
|
+
// clang-format on
|
|
2346
|
+
|
|
2347
|
+
return {
|
|
2348
|
+
renderedWidget: template,
|
|
2349
|
+
revealable: new TimelineUtils.Helpers.RevealableTimeRange(bounds),
|
|
2350
|
+
accessibleRevealLabel: lockedString(UIStringsNotTranslate.revealNetworkActivity),
|
|
2351
|
+
title: lockedString(UIStringsNotTranslate.networkActivitySummary),
|
|
2352
|
+
jslogContext: 'network-track-widget',
|
|
2353
|
+
};
|
|
2354
|
+
}
|
|
2355
|
+
|
|
2319
2356
|
async function makeLighthouseReportWidget(widgetData: LighthouseReportAiWidget): Promise<WidgetMakerResponse|null> {
|
|
2320
2357
|
const reportEl =
|
|
2321
2358
|
Lighthouse.LighthouseReportRenderer.LighthouseReportRenderer.renderLighthouseScores(widgetData.data.report);
|
|
@@ -1797,6 +1797,8 @@ export class DOMStorageTreeElement extends ApplicationPanelTreeElement {
|
|
|
1797
1797
|
super.onselect(selectedByUser);
|
|
1798
1798
|
UI.UIUserMetrics.UIUserMetrics.instance().panelShown('dom-storage');
|
|
1799
1799
|
this.resourcesPanel.showDOMStorage(this.domStorage);
|
|
1800
|
+
const storageItem = this.#getStorageItem();
|
|
1801
|
+
UI.Context.Context.instance().setFlavor(AiAssistance.StorageItem.StorageItem, storageItem);
|
|
1800
1802
|
return false;
|
|
1801
1803
|
}
|
|
1802
1804
|
|
|
@@ -1977,6 +1979,8 @@ export class CookieTreeElement extends ApplicationPanelTreeElement {
|
|
|
1977
1979
|
this.resourcesPanel.showCookies(this.target, this.#cookieDomain);
|
|
1978
1980
|
UI.UIUserMetrics.UIUserMetrics.instance().panelShown(
|
|
1979
1981
|
Host.UserMetrics.PanelCodes[Host.UserMetrics.PanelCodes.cookies]);
|
|
1982
|
+
const storageItem = this.#getStorageItem();
|
|
1983
|
+
UI.Context.Context.instance().setFlavor(AiAssistance.StorageItem.StorageItem, storageItem);
|
|
1980
1984
|
return false;
|
|
1981
1985
|
}
|
|
1982
1986
|
}
|
|
@@ -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
|
}
|