chrome-devtools-frontend 1.0.1592129 → 1.0.1592362
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/front_end/core/host/UserMetrics.ts +2 -1
- package/front_end/core/protocol_client/InspectorBackend.ts +4 -0
- package/front_end/core/root/ExperimentNames.ts +1 -0
- package/front_end/core/sdk/EmulationModel.ts +41 -1
- package/front_end/entrypoints/main/MainImpl.ts +8 -0
- package/front_end/generated/InspectorBackendCommands.ts +1 -0
- package/front_end/generated/protocol-mapping.d.ts +6 -0
- package/front_end/generated/protocol-proxy-api.d.ts +7 -0
- package/front_end/generated/protocol.ts +16 -0
- package/front_end/models/ai_assistance/agents/BreakpointDebuggerAgent.ts +379 -25
- package/front_end/models/ai_assistance/agents/BreakpointDebuggerAgentOverlay.ts +87 -0
- package/front_end/models/ai_assistance/agents/ContextSelectionAgent.snapshot.txt +8 -8
- package/front_end/models/ai_assistance/agents/ContextSelectionAgent.ts +65 -44
- package/front_end/models/computed_style/ComputedStyleModel.ts +40 -6
- package/front_end/models/emulation/DeviceModeModel.ts +47 -0
- package/front_end/panels/ai_assistance/AiAssistancePanel.ts +11 -2
- package/front_end/panels/ai_assistance/components/MarkdownRendererWithCodeBlock.ts +55 -1
- package/front_end/panels/application/CookieItemsView.ts +194 -134
- package/front_end/panels/application/DeviceBoundSessionsTreeElement.ts +1 -1
- package/front_end/panels/application/StorageItemsToolbar.ts +25 -2
- package/front_end/panels/application/cookieItemsView.css +28 -26
- package/front_end/panels/console/ConsoleViewMessage.ts +1 -1
- package/front_end/panels/elements/ComputedStyleWidget.ts +11 -25
- package/front_end/panels/elements/ElementsPanel.ts +26 -3
- package/front_end/panels/elements/StylePropertyTreeElement.ts +33 -0
- package/front_end/panels/elements/StylesAiCodeCompletionProvider.ts +148 -1
- package/front_end/panels/elements/elements-meta.ts +3 -5
- package/front_end/panels/elements/stylePropertiesTreeOutline.css +6 -0
- package/front_end/panels/emulation/DeviceModeToolbar.ts +25 -0
- package/front_end/panels/timeline/components/metricCard.css +0 -4
- package/front_end/ui/components/text_editor/AiCodeCompletionProvider.ts +13 -2
- package/front_end/ui/legacy/components/cookie_table/CookiesTable.ts +44 -24
- package/front_end/ui/visual_logging/Debugging.ts +4 -0
- package/front_end/ui/visual_logging/KnownContextValues.ts +1 -0
- package/package.json +1 -1
|
@@ -0,0 +1,87 @@
|
|
|
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
|
+
/**
|
|
6
|
+
* This file contains the overlay script that is injected into the page when the
|
|
7
|
+
* BreakpointDebuggerAgent is waiting for a user action.
|
|
8
|
+
*
|
|
9
|
+
* This is a temporary solution for the prototype. In the long term, we should
|
|
10
|
+
* use a proper overlay or a different mechanism to communicate with the user,
|
|
11
|
+
* rather than injecting a script into the page. This approach is fine for the
|
|
12
|
+
* prototype but should be replaced before production.
|
|
13
|
+
*/
|
|
14
|
+
|
|
15
|
+
import * as SDK from '../../../core/sdk/sdk.js';
|
|
16
|
+
|
|
17
|
+
export async function injectOverlay(): Promise<void> {
|
|
18
|
+
const targetManager = SDK.TargetManager.TargetManager.instance();
|
|
19
|
+
const primaryTarget = targetManager.primaryPageTarget();
|
|
20
|
+
|
|
21
|
+
await primaryTarget?.runtimeAgent().invoke_evaluate({
|
|
22
|
+
expression: WAIT_FOR_USER_ACTION_OVERLAY_SCRIPT,
|
|
23
|
+
});
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
export async function removeOverlay(): Promise<void> {
|
|
27
|
+
const targetManager = SDK.TargetManager.TargetManager.instance();
|
|
28
|
+
const primaryTarget = targetManager.primaryPageTarget();
|
|
29
|
+
|
|
30
|
+
await primaryTarget?.runtimeAgent().invoke_evaluate({
|
|
31
|
+
expression: REMOVE_OVERLAY_SCRIPT,
|
|
32
|
+
});
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
const WAIT_FOR_USER_ACTION_OVERLAY_SCRIPT = `
|
|
36
|
+
(function() {
|
|
37
|
+
const devtoolsOverlayId = 'devtools-waiting-overlay';
|
|
38
|
+
let overlay = document.getElementById(devtoolsOverlayId);
|
|
39
|
+
if (!overlay) {
|
|
40
|
+
overlay = document.createElement('div');
|
|
41
|
+
overlay.id = devtoolsOverlayId;
|
|
42
|
+
overlay.style.position = 'fixed';
|
|
43
|
+
overlay.style.top = '0';
|
|
44
|
+
overlay.style.left = '0';
|
|
45
|
+
overlay.style.width = '100vw';
|
|
46
|
+
overlay.style.height = '100vh';
|
|
47
|
+
overlay.style.pointerEvents = 'none';
|
|
48
|
+
overlay.style.zIndex = '2147483647';
|
|
49
|
+
overlay.style.boxSizing = 'border-box';
|
|
50
|
+
overlay.style.border = '10px solid red';
|
|
51
|
+
overlay.style.animation = 'devtools-fade 1.5s infinite alternate';
|
|
52
|
+
const text = document.createElement('div');
|
|
53
|
+
text.innerText = 'Trigger the breakpoint again';
|
|
54
|
+
text.style.position = 'absolute';
|
|
55
|
+
text.style.top = '10px';
|
|
56
|
+
text.style.left = '50%';
|
|
57
|
+
text.style.transform = 'translateX(-50%)';
|
|
58
|
+
text.style.backgroundColor = 'red';
|
|
59
|
+
text.style.color = 'white';
|
|
60
|
+
text.style.padding = '10px 20px';
|
|
61
|
+
text.style.borderRadius = '5px';
|
|
62
|
+
text.style.fontFamily = 'system-ui, sans-serif';
|
|
63
|
+
text.style.fontSize = '16px';
|
|
64
|
+
text.style.fontWeight = 'bold';
|
|
65
|
+
text.style.boxShadow = '0 4px 6px rgba(0,0,0,0.1)';
|
|
66
|
+
overlay.appendChild(text);
|
|
67
|
+
|
|
68
|
+
const style = document.createElement('style');
|
|
69
|
+
style.id = devtoolsOverlayId + '-style';
|
|
70
|
+
style.innerText = '@keyframes devtools-fade { from { opacity: 0.5; } to { opacity: 1; } }';
|
|
71
|
+
// Head might not exist immediately on a completely blank page, fallback to documentElement
|
|
72
|
+
(document.head || document.documentElement).appendChild(style);
|
|
73
|
+
|
|
74
|
+
document.documentElement.appendChild(overlay);
|
|
75
|
+
}
|
|
76
|
+
})();
|
|
77
|
+
`;
|
|
78
|
+
|
|
79
|
+
const REMOVE_OVERLAY_SCRIPT = `
|
|
80
|
+
(function() {
|
|
81
|
+
const devtoolsOverlayId = 'devtools-waiting-overlay';
|
|
82
|
+
const overlay = document.getElementById(devtoolsOverlayId);
|
|
83
|
+
if (overlay) overlay.remove();
|
|
84
|
+
const style = document.getElementById(devtoolsOverlayId + '-style');
|
|
85
|
+
if (style) style.remove();
|
|
86
|
+
})();
|
|
87
|
+
`;
|
|
@@ -48,12 +48,12 @@ Content:
|
|
|
48
48
|
"description": "",
|
|
49
49
|
"nullable": true,
|
|
50
50
|
"required": [
|
|
51
|
-
"
|
|
51
|
+
"id"
|
|
52
52
|
],
|
|
53
53
|
"properties": {
|
|
54
|
-
"
|
|
54
|
+
"id": {
|
|
55
55
|
"type": 1,
|
|
56
|
-
"description": "The
|
|
56
|
+
"description": "The id of the network request",
|
|
57
57
|
"nullable": false
|
|
58
58
|
}
|
|
59
59
|
}
|
|
@@ -72,18 +72,18 @@ Content:
|
|
|
72
72
|
},
|
|
73
73
|
{
|
|
74
74
|
"name": "selectSourceFile",
|
|
75
|
-
"description": "Selects a source file. Use this when asked about files on the page. Use listSourceFiles
|
|
75
|
+
"description": "Selects a source file. Use this when asked about files on the page. Use listSourceFiles to find the file ID.",
|
|
76
76
|
"parameters": {
|
|
77
77
|
"type": 6,
|
|
78
78
|
"description": "",
|
|
79
79
|
"nullable": true,
|
|
80
80
|
"required": [
|
|
81
|
-
"
|
|
81
|
+
"id"
|
|
82
82
|
],
|
|
83
83
|
"properties": {
|
|
84
|
-
"
|
|
85
|
-
"type":
|
|
86
|
-
"description": "The
|
|
84
|
+
"id": {
|
|
85
|
+
"type": 3,
|
|
86
|
+
"description": "The id (URL) of the file you want to select.",
|
|
87
87
|
"nullable": false
|
|
88
88
|
}
|
|
89
89
|
}
|
|
@@ -5,7 +5,6 @@
|
|
|
5
5
|
import * as Common from '../../../core/common/common.js';
|
|
6
6
|
import * as Host from '../../../core/host/host.js';
|
|
7
7
|
import * as i18n from '../../../core/i18n/i18n.js';
|
|
8
|
-
import * as Platform from '../../../core/platform/platform.js';
|
|
9
8
|
import * as Root from '../../../core/root/root.js';
|
|
10
9
|
import * as SDK from '../../../core/sdk/sdk.js';
|
|
11
10
|
import * as Logs from '../../logs/logs.js';
|
|
@@ -35,13 +34,12 @@ You are a Web Development Assistant integrated into Chrome DevTools. Your tone i
|
|
|
35
34
|
You aim to help developers of all levels, prioritizing teaching web concepts as the primary entry point for any solution.
|
|
36
35
|
|
|
37
36
|
# Considerations
|
|
38
|
-
* Determine what
|
|
37
|
+
* Determine what is the domain of the question - styling, network, sources, performance or other part of DevTools.
|
|
39
38
|
* Proactively try to gather additional data. If a select specific data can be selected, select one.
|
|
40
39
|
* Always try select single specific context before answering the question.
|
|
41
40
|
* Avoid making assumptions without sufficient evidence, and always seek further clarification if needed.
|
|
42
41
|
* When presenting solutions, clearly distinguish between the primary cause and contributing factors.
|
|
43
42
|
* Please answer only if you are sure about the answer. Otherwise, explain why you're not able to answer.
|
|
44
|
-
* When answering, always consider MULTIPLE possible solutions.
|
|
45
43
|
* If you are unable to gather more information provide a comprehensive guide to how to fix the issue using Chrome DevTools and explain how and why.
|
|
46
44
|
* You can suggest any panel or flow in Chrome DevTools that may help the user out
|
|
47
45
|
|
|
@@ -50,9 +48,13 @@ You aim to help developers of all levels, prioritizing teaching web concepts as
|
|
|
50
48
|
* Always specify the language for code blocks (e.g., \`\`\`css, \`\`\`javascript).
|
|
51
49
|
* Keep text responses concise and scannable.
|
|
52
50
|
|
|
51
|
+
* **CRITICAL** If a tool returns an empty list, immediately pivot to the next logical tool (e.g., from sources to network).
|
|
52
|
+
* **CRITICAL** Always exhaust all possible way to find and select context from different domains.
|
|
53
53
|
* **CRITICAL** NEVER write full Python programs - you should only write individual statements that invoke a single function from the provided library.
|
|
54
54
|
* **CRITICAL** NEVER output text before a function call. Always do a function call first.
|
|
55
55
|
* **CRITICAL** You are a debugging assistant in DevTools. NEVER provide answers to questions of unrelated topics such as legal advice, financial advice, personal opinions, medical advice, religion, race, politics, sexuality, gender, or any other non web-development topics. Answer "Sorry, I can't answer that. I'm best at questions about debugging web pages." to such questions.
|
|
56
|
+
* **CRITICAL** When referring to DevTools resource output a markdown link to the object using the format \`[<text>](#<type>-<ID>)\`.
|
|
57
|
+
* The only available types are \`#req\` for network request and \`#file\` for source files. Only use ID inside the link, never ask about user selecting by ID.
|
|
56
58
|
`;
|
|
57
59
|
|
|
58
60
|
/**
|
|
@@ -117,6 +119,7 @@ export class ContextSelectionAgent extends AiAgent<never> {
|
|
|
117
119
|
}
|
|
118
120
|
|
|
119
121
|
requests.push({
|
|
122
|
+
id: request.requestId(),
|
|
120
123
|
url: request.url(),
|
|
121
124
|
statusCode: request.statusCode,
|
|
122
125
|
duration: i18n.TimeUtilities.secondsToString(request.duration),
|
|
@@ -135,18 +138,18 @@ export class ContextSelectionAgent extends AiAgent<never> {
|
|
|
135
138
|
},
|
|
136
139
|
});
|
|
137
140
|
|
|
138
|
-
this.declareFunction<{
|
|
141
|
+
this.declareFunction<{id: string}>('selectNetworkRequest', {
|
|
139
142
|
description:
|
|
140
143
|
`Selects a specific network request to further provide information about. Use this when asked about network requests issues.`,
|
|
141
144
|
parameters: {
|
|
142
145
|
type: Host.AidaClient.ParametersTypes.OBJECT,
|
|
143
146
|
description: '',
|
|
144
147
|
nullable: true,
|
|
145
|
-
required: ['
|
|
148
|
+
required: ['id'],
|
|
146
149
|
properties: {
|
|
147
|
-
|
|
150
|
+
id: {
|
|
148
151
|
type: Host.AidaClient.ParametersTypes.STRING,
|
|
149
|
-
description: 'The
|
|
152
|
+
description: 'The id of the network request',
|
|
150
153
|
nullable: false,
|
|
151
154
|
},
|
|
152
155
|
},
|
|
@@ -154,15 +157,12 @@ export class ContextSelectionAgent extends AiAgent<never> {
|
|
|
154
157
|
displayInfoFromArgs: args => {
|
|
155
158
|
return {
|
|
156
159
|
title: lockedString('Getting network request…'),
|
|
157
|
-
action: `selectNetworkRequest(${args.
|
|
160
|
+
action: `selectNetworkRequest(${args.id})`,
|
|
158
161
|
};
|
|
159
162
|
},
|
|
160
|
-
handler: async ({
|
|
161
|
-
// TODO: Switch to using IDs to make is easier to link to as well.
|
|
163
|
+
handler: async ({id}) => {
|
|
162
164
|
const request = Logs.NetworkLog.NetworkLog.instance().requests().find(req => {
|
|
163
|
-
return req.
|
|
164
|
-
req.url() === Platform.DevToolsPath.urlString`${url.slice(0, -1)}` ||
|
|
165
|
-
req.url() === Platform.DevToolsPath.urlString`${url}/`;
|
|
165
|
+
return req.requestId() === id;
|
|
166
166
|
});
|
|
167
167
|
|
|
168
168
|
if (request) {
|
|
@@ -195,32 +195,32 @@ export class ContextSelectionAgent extends AiAgent<never> {
|
|
|
195
195
|
};
|
|
196
196
|
},
|
|
197
197
|
handler: async () => {
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
198
|
+
const files: Array<{file: string, id: number | undefined}> = [];
|
|
199
|
+
for (const file of ContextSelectionAgent.getUISourceCodes()) {
|
|
200
|
+
files.push({
|
|
201
|
+
file: file.fullDisplayName(),
|
|
202
|
+
id: ContextSelectionAgent.uiSourceCodeId.get(file),
|
|
203
|
+
});
|
|
204
204
|
}
|
|
205
205
|
|
|
206
206
|
return {
|
|
207
|
-
result:
|
|
207
|
+
result: files,
|
|
208
208
|
};
|
|
209
209
|
},
|
|
210
210
|
});
|
|
211
211
|
|
|
212
|
-
this.declareFunction<{
|
|
212
|
+
this.declareFunction<{id: number}>('selectSourceFile', {
|
|
213
213
|
description:
|
|
214
|
-
`Selects a source file. Use this when asked about files on the page. Use listSourceFiles
|
|
214
|
+
`Selects a source file. Use this when asked about files on the page. Use listSourceFiles to find the file ID.`,
|
|
215
215
|
parameters: {
|
|
216
216
|
type: Host.AidaClient.ParametersTypes.OBJECT,
|
|
217
217
|
description: '',
|
|
218
218
|
nullable: true,
|
|
219
|
-
required: ['
|
|
219
|
+
required: ['id'],
|
|
220
220
|
properties: {
|
|
221
|
-
|
|
222
|
-
type: Host.AidaClient.ParametersTypes.
|
|
223
|
-
description: 'The
|
|
221
|
+
id: {
|
|
222
|
+
type: Host.AidaClient.ParametersTypes.INTEGER,
|
|
223
|
+
description: 'The id (URL) of the file you want to select.',
|
|
224
224
|
nullable: false,
|
|
225
225
|
},
|
|
226
226
|
},
|
|
@@ -228,22 +228,19 @@ export class ContextSelectionAgent extends AiAgent<never> {
|
|
|
228
228
|
displayInfoFromArgs: args => {
|
|
229
229
|
return {
|
|
230
230
|
title: lockedString('Getting source file…'),
|
|
231
|
-
action: `selectSourceFile(${args.
|
|
231
|
+
action: `selectSourceFile(${args.id})`,
|
|
232
232
|
};
|
|
233
233
|
},
|
|
234
234
|
handler: async params => {
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
const files = this.#getUISourceCodes().filter(file => file.fullDisplayName() === params.name);
|
|
235
|
+
const file = ContextSelectionAgent.getUISourceCodes().find(
|
|
236
|
+
file => ContextSelectionAgent.uiSourceCodeId.get(file) === params.id);
|
|
238
237
|
|
|
239
|
-
if (
|
|
238
|
+
if (!file) {
|
|
240
239
|
return {
|
|
241
240
|
error: 'Unable to find file.',
|
|
242
241
|
};
|
|
243
242
|
}
|
|
244
243
|
|
|
245
|
-
// This help us pick the file that is resolved source map.
|
|
246
|
-
const file = files.find(f => f.contentType().isFromSourceMap()) ?? files[0];
|
|
247
244
|
return {
|
|
248
245
|
context: new FileContext(file),
|
|
249
246
|
description: 'User selected a source file',
|
|
@@ -315,27 +312,51 @@ export class ContextSelectionAgent extends AiAgent<never> {
|
|
|
315
312
|
});
|
|
316
313
|
}
|
|
317
314
|
|
|
318
|
-
|
|
315
|
+
async * handleContextDetails(): AsyncGenerator<ContextResponse, void, void> {
|
|
316
|
+
}
|
|
317
|
+
|
|
318
|
+
override async enhanceQuery(query: string): Promise<string> {
|
|
319
|
+
return query;
|
|
320
|
+
}
|
|
321
|
+
|
|
322
|
+
static lastSourceId = 0;
|
|
323
|
+
static uiSourceCodeId = new WeakMap<Workspace.UISourceCode.UISourceCode, number>();
|
|
324
|
+
/**
|
|
325
|
+
* This is a heuristic algorithm that gets all the source files coming from the
|
|
326
|
+
* network and assigns unique ids to be linked from the LLM Markdown response.
|
|
327
|
+
* Steps we do:
|
|
328
|
+
* 1. Get all project that are coming from the Network. This scopes down
|
|
329
|
+
* sources exposed to the LLM
|
|
330
|
+
* 2. Remove all ignore listed source code. We further reduce thing that the
|
|
331
|
+
* user most likely does not have interest in, from global setting.
|
|
332
|
+
* 3.1. Source files don't have an uniqueId so we use the URL to differentiate
|
|
333
|
+
* them.
|
|
334
|
+
* 3.2. In cases where we encounter a duplicated URLs we prefer the latest one
|
|
335
|
+
* coming from SourceMaps (usually only one) as that has simple code and
|
|
336
|
+
* usually is what the user authored.
|
|
337
|
+
*/
|
|
338
|
+
static getUISourceCodes(): Workspace.UISourceCode.UISourceCode[] {
|
|
319
339
|
const workspace = Workspace.Workspace.WorkspaceImpl.instance();
|
|
320
340
|
const projects =
|
|
321
341
|
workspace.projects().filter(project => project.type() === Workspace.Workspace.projectTypes.Network);
|
|
322
|
-
const uiSourceCodes =
|
|
342
|
+
const uiSourceCodes = new Map<string, Workspace.UISourceCode.UISourceCode>();
|
|
343
|
+
|
|
323
344
|
for (const project of projects) {
|
|
324
345
|
for (const uiSourceCode of project.uiSourceCodes()) {
|
|
325
346
|
if (uiSourceCode.isIgnoreListed()) {
|
|
326
347
|
continue;
|
|
327
348
|
}
|
|
328
|
-
|
|
349
|
+
const url = uiSourceCode.url();
|
|
350
|
+
// This helps us pick the file that is a resolved source map.
|
|
351
|
+
if (!uiSourceCodes.get(url) || uiSourceCode.contentType().isFromSourceMap()) {
|
|
352
|
+
uiSourceCodes.set(url, uiSourceCode);
|
|
353
|
+
if (!ContextSelectionAgent.uiSourceCodeId.has(uiSourceCode)) {
|
|
354
|
+
ContextSelectionAgent.uiSourceCodeId.set(uiSourceCode, ++ContextSelectionAgent.lastSourceId);
|
|
355
|
+
}
|
|
356
|
+
}
|
|
329
357
|
}
|
|
330
358
|
}
|
|
331
359
|
|
|
332
|
-
return uiSourceCodes;
|
|
333
|
-
};
|
|
334
|
-
|
|
335
|
-
async * handleContextDetails(): AsyncGenerator<ContextResponse, void, void> {
|
|
336
|
-
}
|
|
337
|
-
|
|
338
|
-
override async enhanceQuery(query: string): Promise<string> {
|
|
339
|
-
return query;
|
|
360
|
+
return [...uiSourceCodes.values()];
|
|
340
361
|
}
|
|
341
362
|
}
|
|
@@ -12,17 +12,18 @@ import * as SDK from '../../core/sdk/sdk.js';
|
|
|
12
12
|
* Model trackComputedStyleUpdatesForNode method.
|
|
13
13
|
*/
|
|
14
14
|
export class ComputedStyleModel extends Common.ObjectWrapper.ObjectWrapper<EventTypes> {
|
|
15
|
-
#node: SDK.DOMModel.DOMNode|null;
|
|
16
|
-
#cssModel: SDK.CSSModel.CSSModel|null;
|
|
17
|
-
private eventListeners: Common.EventTarget.EventDescriptor[];
|
|
15
|
+
#node: SDK.DOMModel.DOMNode|null = null;
|
|
16
|
+
#cssModel: SDK.CSSModel.CSSModel|null = null;
|
|
17
|
+
private eventListeners: Common.EventTarget.EventDescriptor[] = [];
|
|
18
18
|
private frameResizedTimer?: number;
|
|
19
19
|
private computedStylePromise?: Promise<ComputedStyle|null>;
|
|
20
20
|
|
|
21
21
|
constructor(node?: SDK.DOMModel.DOMNode|null) {
|
|
22
22
|
super();
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
23
|
+
if (node) {
|
|
24
|
+
// Call the explicit setter to trigger the setup and event binding.
|
|
25
|
+
this.node = node;
|
|
26
|
+
}
|
|
26
27
|
}
|
|
27
28
|
|
|
28
29
|
get node(): SDK.DOMModel.DOMNode|null {
|
|
@@ -39,6 +40,21 @@ export class ComputedStyleModel extends Common.ObjectWrapper.ObjectWrapper<Event
|
|
|
39
40
|
return this.#cssModel?.isEnabled() ? this.#cssModel : null;
|
|
40
41
|
}
|
|
41
42
|
|
|
43
|
+
/**
|
|
44
|
+
* Clears all event listeners to ensure the instance can be GC'd without leaking memory.
|
|
45
|
+
*/
|
|
46
|
+
dispose(): void {
|
|
47
|
+
Common.EventTarget.removeEventListeners(this.eventListeners);
|
|
48
|
+
this.eventListeners = [];
|
|
49
|
+
this.node = null;
|
|
50
|
+
this.#cssModel = null;
|
|
51
|
+
this.computedStylePromise = undefined;
|
|
52
|
+
if (this.frameResizedTimer) {
|
|
53
|
+
clearTimeout(this.frameResizedTimer);
|
|
54
|
+
this.frameResizedTimer = undefined;
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
|
|
42
58
|
private updateModel(cssModel: SDK.CSSModel.CSSModel|null): void {
|
|
43
59
|
if (this.#cssModel === cssModel) {
|
|
44
60
|
return;
|
|
@@ -168,6 +184,24 @@ export class ComputedStyleModel extends Common.ObjectWrapper.ObjectWrapper<Event
|
|
|
168
184
|
}
|
|
169
185
|
return matchedStyles.node() === this.node ? matchedStyles : null;
|
|
170
186
|
}
|
|
187
|
+
|
|
188
|
+
computePropertyTraces(matchedStyles: SDK.CSSMatchedStyles.CSSMatchedStyles):
|
|
189
|
+
Map<string, SDK.CSSProperty.CSSProperty[]> {
|
|
190
|
+
const result = new Map<string, SDK.CSSProperty.CSSProperty[]>();
|
|
191
|
+
for (const style of matchedStyles.nodeStyles()) {
|
|
192
|
+
const allProperties = style.allProperties();
|
|
193
|
+
for (const property of allProperties) {
|
|
194
|
+
if (!property.activeInStyle() || !matchedStyles.propertyState(property)) {
|
|
195
|
+
continue;
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
const matches = result.get(property.name) ?? [];
|
|
199
|
+
matches.push(property);
|
|
200
|
+
result.set(property.name, matches);
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
return result;
|
|
204
|
+
}
|
|
171
205
|
}
|
|
172
206
|
|
|
173
207
|
export const enum Events {
|
|
@@ -113,6 +113,7 @@ export class DeviceModeModel extends Common.ObjectWrapper.ObjectWrapper<EventTyp
|
|
|
113
113
|
#emulationModel: SDK.EmulationModel.EmulationModel|null;
|
|
114
114
|
#onModelAvailable: (() => void)|null;
|
|
115
115
|
#outlineRect?: Rect;
|
|
116
|
+
#screenOrientationLocked: boolean;
|
|
116
117
|
|
|
117
118
|
private constructor() {
|
|
118
119
|
super();
|
|
@@ -172,6 +173,7 @@ export class DeviceModeModel extends Common.ObjectWrapper.ObjectWrapper<EventTyp
|
|
|
172
173
|
|
|
173
174
|
this.#emulationModel = null;
|
|
174
175
|
this.#onModelAvailable = null;
|
|
176
|
+
this.#screenOrientationLocked = false;
|
|
175
177
|
SDK.TargetManager.TargetManager.instance().observeModels(SDK.EmulationModel.EmulationModel, this);
|
|
176
178
|
}
|
|
177
179
|
|
|
@@ -442,6 +444,9 @@ export class DeviceModeModel extends Common.ObjectWrapper.ObjectWrapper<EventTyp
|
|
|
442
444
|
this.#onModelAvailable = null;
|
|
443
445
|
callback();
|
|
444
446
|
}
|
|
447
|
+
emulationModel.addEventListener(
|
|
448
|
+
SDK.EmulationModel.EmulationModelEvents.SCREEN_ORIENTATION_LOCK_CHANGED, this.onScreenOrientationLockChanged,
|
|
449
|
+
this);
|
|
445
450
|
const resourceTreeModel = emulationModel.target().model(SDK.ResourceTreeModel.ResourceTreeModel);
|
|
446
451
|
if (resourceTreeModel) {
|
|
447
452
|
resourceTreeModel.addEventListener(SDK.ResourceTreeModel.Events.FrameResized, this.onFrameChange, this);
|
|
@@ -454,7 +459,12 @@ export class DeviceModeModel extends Common.ObjectWrapper.ObjectWrapper<EventTyp
|
|
|
454
459
|
|
|
455
460
|
modelRemoved(emulationModel: SDK.EmulationModel.EmulationModel): void {
|
|
456
461
|
if (this.#emulationModel === emulationModel) {
|
|
462
|
+
emulationModel.removeEventListener(
|
|
463
|
+
SDK.EmulationModel.EmulationModelEvents.SCREEN_ORIENTATION_LOCK_CHANGED, this.onScreenOrientationLockChanged,
|
|
464
|
+
this);
|
|
457
465
|
this.#emulationModel = null;
|
|
466
|
+
this.#screenOrientationLocked = false;
|
|
467
|
+
this.dispatchEventToListeners(Events.UPDATED);
|
|
458
468
|
}
|
|
459
469
|
}
|
|
460
470
|
|
|
@@ -471,6 +481,43 @@ export class DeviceModeModel extends Common.ObjectWrapper.ObjectWrapper<EventTyp
|
|
|
471
481
|
this.showHingeIfApplicable(overlayModel);
|
|
472
482
|
}
|
|
473
483
|
|
|
484
|
+
private onScreenOrientationLockChanged(
|
|
485
|
+
event: Common.EventTarget.EventTargetEvent<SDK.EmulationModel.ScreenOrientationLockChangedEvent>): void {
|
|
486
|
+
this.#screenOrientationLocked = event.data.locked;
|
|
487
|
+
if (event.data.locked && event.data.orientation) {
|
|
488
|
+
this.applyOrientationLock(event.data.orientation);
|
|
489
|
+
}
|
|
490
|
+
this.dispatchEventToListeners(Events.UPDATED);
|
|
491
|
+
}
|
|
492
|
+
|
|
493
|
+
private applyOrientationLock(orientation: Protocol.Emulation.ScreenOrientation): void {
|
|
494
|
+
const wantsLandscape = orientation.type === Protocol.Emulation.ScreenOrientationType.LandscapePrimary ||
|
|
495
|
+
orientation.type === Protocol.Emulation.ScreenOrientationType.LandscapeSecondary;
|
|
496
|
+
|
|
497
|
+
if (this.#type === Type.Device && this.#device && this.#mode) {
|
|
498
|
+
// For device emulation, switch to the matching orientation mode.
|
|
499
|
+
const isCurrentlyLandscape =
|
|
500
|
+
this.#mode.orientation === Horizontal || this.#mode.orientation === HorizontalSpanned;
|
|
501
|
+
if (wantsLandscape !== isCurrentlyLandscape) {
|
|
502
|
+
const rotationPartner = this.#device.getRotationPartner(this.#mode);
|
|
503
|
+
if (rotationPartner) {
|
|
504
|
+
this.emulate(this.#type, this.#device, rotationPartner);
|
|
505
|
+
}
|
|
506
|
+
}
|
|
507
|
+
} else if (this.#type === Type.Responsive) {
|
|
508
|
+
// For responsive mode, swap width/height if orientation doesn't match.
|
|
509
|
+
const appliedSize = this.appliedDeviceSize();
|
|
510
|
+
const isCurrentlyLandscape = appliedSize.width > appliedSize.height;
|
|
511
|
+
if (wantsLandscape !== isCurrentlyLandscape) {
|
|
512
|
+
this.setSizeAndScaleToFit(appliedSize.height, appliedSize.width);
|
|
513
|
+
}
|
|
514
|
+
}
|
|
515
|
+
}
|
|
516
|
+
|
|
517
|
+
isScreenOrientationLocked(): boolean {
|
|
518
|
+
return this.#screenOrientationLocked;
|
|
519
|
+
}
|
|
520
|
+
|
|
474
521
|
private scaleSettingChanged(): void {
|
|
475
522
|
this.calculateAndEmulate(false);
|
|
476
523
|
}
|
|
@@ -13,6 +13,7 @@ import * as SDK from '../../core/sdk/sdk.js';
|
|
|
13
13
|
import * as AiAssistanceModel from '../../models/ai_assistance/ai_assistance.js';
|
|
14
14
|
import * as Annotations from '../../models/annotations/annotations.js';
|
|
15
15
|
import * as Badges from '../../models/badges/badges.js';
|
|
16
|
+
import * as Greendev from '../../models/greendev/greendev.js';
|
|
16
17
|
import * as TextUtils from '../../models/text_utils/text_utils.js';
|
|
17
18
|
import type * as Trace from '../../models/trace/trace.js';
|
|
18
19
|
import * as Workspace from '../../models/workspace/workspace.js';
|
|
@@ -461,7 +462,8 @@ function defaultView(input: ViewInput, output: PanelViewOutput, target: HTMLElem
|
|
|
461
462
|
|
|
462
463
|
const shouldShowWalkthrough = input.state === ViewState.CHAT_VIEW && input.walkthrough.isExpanded;
|
|
463
464
|
|
|
464
|
-
if (Root.Runtime.hostConfig.devToolsAiAssistanceV2?.enabled
|
|
465
|
+
if (Root.Runtime.hostConfig.devToolsAiAssistanceV2?.enabled ||
|
|
466
|
+
Greendev.Prototypes.instance().isEnabled('breakpointDebuggerAgent')) {
|
|
465
467
|
Lit.render(html`
|
|
466
468
|
${toolbarView(input)}
|
|
467
469
|
<div class="ai-assistance-view-container">
|
|
@@ -915,7 +917,7 @@ export class AiAssistancePanel extends UI.Panel.Panel {
|
|
|
915
917
|
this.requestUpdate();
|
|
916
918
|
}
|
|
917
919
|
|
|
918
|
-
async handleBreakpointConversation(uiLocation: Workspace.UISourceCode.UILocation): Promise<void> {
|
|
920
|
+
async handleBreakpointConversation(uiLocation: Workspace.UISourceCode.UILocation, errorMsg?: string): Promise<void> {
|
|
919
921
|
const context = new AiAssistanceModel.BreakpointDebuggerAgent.BreakpointContext(uiLocation);
|
|
920
922
|
this.#selectedBreakpoint = context;
|
|
921
923
|
const conversation = new AiAssistanceModel.AiConversation.AiConversation(
|
|
@@ -934,6 +936,9 @@ export class AiAssistancePanel extends UI.Panel.Panel {
|
|
|
934
936
|
this.#conversation?.setContext(context);
|
|
935
937
|
this.requestUpdate();
|
|
936
938
|
await UI.ViewManager.ViewManager.instance().showView(AiAssistancePanel.panelName);
|
|
939
|
+
const prompt = errorMsg ? `debug the error "${errorMsg}" using breakpoint debugging agent` :
|
|
940
|
+
'debug the error using breakpoint debugging agent';
|
|
941
|
+
await this.#startConversation(prompt);
|
|
937
942
|
}
|
|
938
943
|
|
|
939
944
|
override wasShown(): void {
|
|
@@ -1658,6 +1663,10 @@ export class AiAssistancePanel extends UI.Panel.Panel {
|
|
|
1658
1663
|
parts: [],
|
|
1659
1664
|
};
|
|
1660
1665
|
this.#messages.push(systemMessage);
|
|
1666
|
+
if (Greendev.Prototypes.instance().isEnabled('breakpointDebuggerAgent') &&
|
|
1667
|
+
this.#conversation?.type === AiAssistanceModel.AiHistoryStorage.ConversationType.BREAKPOINT) {
|
|
1668
|
+
this.#openWalkthrough(systemMessage);
|
|
1669
|
+
}
|
|
1661
1670
|
break;
|
|
1662
1671
|
}
|
|
1663
1672
|
case AiAssistanceModel.AiAgent.ResponseType.QUERYING: {
|
|
@@ -2,9 +2,15 @@
|
|
|
2
2
|
// Use of this source code is governed by a BSD-style license that can be
|
|
3
3
|
// found in the LICENSE file.
|
|
4
4
|
|
|
5
|
+
import * as Common from '../../../core/common/common.js';
|
|
6
|
+
import * as Platform from '../../../core/platform/platform.js';
|
|
7
|
+
import * as AiAssistanceModel from '../../../models/ai_assistance/ai_assistance.js';
|
|
8
|
+
import * as Logs from '../../../models/logs/logs.js';
|
|
5
9
|
import type * as Marked from '../../../third_party/marked/marked.js';
|
|
6
10
|
import * as MarkdownView from '../../../ui/components/markdown_view/markdown_view.js';
|
|
7
|
-
import
|
|
11
|
+
import * as Lit from '../../../ui/lit/lit.js';
|
|
12
|
+
|
|
13
|
+
const {html} = Lit;
|
|
8
14
|
|
|
9
15
|
/**
|
|
10
16
|
* The model returns multiline code blocks in an erroneous way with the language being in new line.
|
|
@@ -19,7 +25,43 @@ import type * as Lit from '../../../ui/lit/lit.js';
|
|
|
19
25
|
* ```
|
|
20
26
|
**/
|
|
21
27
|
export class MarkdownRendererWithCodeBlock extends MarkdownView.MarkdownView.MarkdownInsightRenderer {
|
|
28
|
+
#revealableLink(revealable: unknown, label: string): Lit.LitTemplate {
|
|
29
|
+
return html`<devtools-link @click=${(e: Event) => {
|
|
30
|
+
e.preventDefault();
|
|
31
|
+
e.stopPropagation();
|
|
32
|
+
void Common.Revealer.reveal(revealable);
|
|
33
|
+
}}>${Platform.StringUtilities.trimEndWithMaxLength(label, 100)}</devtools-link>`;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
#renderLink(href: string): Lit.LitTemplate|null {
|
|
37
|
+
if (href.startsWith('#req-')) {
|
|
38
|
+
const request =
|
|
39
|
+
Logs.NetworkLog.NetworkLog.instance().requests().find(req => req.requestId() === href.substring(5));
|
|
40
|
+
|
|
41
|
+
if (request) {
|
|
42
|
+
return this.#revealableLink(request, request.url());
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
} else if (href.startsWith('#file-')) {
|
|
46
|
+
const file = AiAssistanceModel.ContextSelectionAgent.ContextSelectionAgent.getUISourceCodes().find(
|
|
47
|
+
file => AiAssistanceModel.ContextSelectionAgent.ContextSelectionAgent.uiSourceCodeId.get(file) ===
|
|
48
|
+
Number(href.substring(6)));
|
|
49
|
+
|
|
50
|
+
if (file) {
|
|
51
|
+
return this.#revealableLink(file, file.name());
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
return null;
|
|
55
|
+
}
|
|
56
|
+
|
|
22
57
|
override templateForToken(token: Marked.Marked.MarkedToken): Lit.LitTemplate|null {
|
|
58
|
+
if (token.type === 'link') {
|
|
59
|
+
const link = this.#renderLink(token.href);
|
|
60
|
+
if (link) {
|
|
61
|
+
return link;
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
|
|
23
65
|
if (token.type === 'code') {
|
|
24
66
|
const lines = (token.text).split('\n');
|
|
25
67
|
if (lines[0]?.trim() === 'css') {
|
|
@@ -28,6 +70,18 @@ export class MarkdownRendererWithCodeBlock extends MarkdownView.MarkdownView.Mar
|
|
|
28
70
|
}
|
|
29
71
|
}
|
|
30
72
|
|
|
73
|
+
if (token.type === 'codespan') {
|
|
74
|
+
// LLM likes outputting the link inside a codespan block.
|
|
75
|
+
// Remove the codespan and render the link directly
|
|
76
|
+
const matches = token.text.match(/^\[.*\]\((.+)\)$/);
|
|
77
|
+
if (matches?.[1]) {
|
|
78
|
+
const link = this.#renderLink(matches[1]);
|
|
79
|
+
if (link) {
|
|
80
|
+
return link;
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
|
|
31
85
|
return super.templateForToken(token);
|
|
32
86
|
}
|
|
33
87
|
}
|