chrome-devtools-frontend 1.0.1632065 → 1.0.1635648
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 +5 -2
- package/front_end/core/root/ExperimentNames.ts +1 -0
- package/front_end/core/root/Runtime.ts +5 -0
- package/front_end/entrypoints/main/MainImpl.ts +8 -0
- package/front_end/generated/InspectorBackendCommands.ts +3 -4
- package/front_end/generated/SupportedCSSProperties.js +272 -2
- package/front_end/generated/protocol-mapping.d.ts +0 -10
- package/front_end/generated/protocol-proxy-api.d.ts +0 -8
- package/front_end/generated/protocol.ts +5 -7
- package/front_end/models/ai_assistance/AiConversation.ts +16 -11
- package/front_end/models/ai_assistance/agents/AccessibilityAgent.ts +13 -2
- package/front_end/models/ai_assistance/agents/AiAgent.ts +65 -11
- package/front_end/models/ai_assistance/agents/ContextSelectionAgent.ts +42 -2
- package/front_end/models/ai_assistance/agents/PerformanceAgent.ts +30 -1
- package/front_end/models/ai_assistance/data_formatters/PerformanceInsightFormatter.ts +4 -2
- package/front_end/models/ai_assistance/data_formatters/PerformanceTraceFormatter.ts +5 -2
- package/front_end/models/extensions/RecorderExtensionEndpoint.ts +9 -1
- package/front_end/models/extensions/RecorderPluginManager.ts +1 -0
- package/front_end/models/trace/handlers/FramesHandler.ts +19 -13
- package/front_end/panels/ai_assistance/components/AccessibilityAgentMarkdownRenderer.ts +3 -0
- package/front_end/panels/ai_assistance/components/ChatInput.ts +4 -2
- package/front_end/panels/ai_assistance/components/ChatMessage.ts +3 -1
- package/front_end/panels/application/preloading/components/PreloadingString.ts +0 -8
- package/front_end/panels/common/ExtensionServer.ts +34 -11
- package/front_end/panels/elements/CSSRuleValidator.ts +37 -34
- package/front_end/panels/elements/CSSRuleValidatorHelper.ts +8 -6
- package/front_end/panels/elements/ElementsTreeElement.ts +8 -2
- package/front_end/panels/elements/components/CSSHintDetailsView.ts +5 -5
- package/front_end/panels/js_timeline/js_timeline-meta.ts +30 -0
- package/front_end/panels/protocol_monitor/JSONEditor.ts +4 -4
- package/front_end/panels/protocol_monitor/ProtocolMonitor.ts +2 -2
- package/front_end/panels/recorder/RecorderController.ts +50 -1
- package/front_end/panels/recorder/extensions/ExtensionManager.ts +1 -0
- package/front_end/panels/recorder/models/RecordingPlayer.ts +12 -3
- package/front_end/panels/recorder/testing/RecorderHelpers.ts +2 -0
- package/front_end/panels/sources/FilteredUISourceCodeListProvider.ts +5 -2
- package/front_end/panels/timeline/timeline-meta.ts +10 -6
- package/front_end/third_party/chromium/README.chromium +1 -1
- package/front_end/ui/legacy/InspectorDrawerView.ts +2 -1
- package/front_end/ui/legacy/InspectorView.ts +1 -1
- package/front_end/ui/legacy/PlusButton.ts +269 -0
- package/front_end/ui/legacy/ViewManager.ts +38 -11
- package/front_end/ui/legacy/components/source_frame/ImageView.ts +16 -0
- package/front_end/ui/legacy/components/source_frame/imageView.css +13 -0
- package/front_end/ui/legacy/components/utils/Linkifier.ts +1 -1
- package/front_end/ui/legacy/legacy.ts +2 -0
- package/front_end/ui/visual_logging/KnownContextValues.ts +19 -0
- package/mcp/mcp.ts +1 -1
- package/package.json +1 -1
|
@@ -429,16 +429,7 @@ export class AiConversation {
|
|
|
429
429
|
throw new Error('cross-origin context data should not be included');
|
|
430
430
|
}
|
|
431
431
|
|
|
432
|
-
|
|
433
|
-
type: ResponseType.USER_QUERY,
|
|
434
|
-
query: initialQuery,
|
|
435
|
-
imageInput: options.multimodalInput?.input,
|
|
436
|
-
imageId: options.multimodalInput?.id,
|
|
437
|
-
};
|
|
438
|
-
void this.addHistoryItem(userQuery);
|
|
439
|
-
yield userQuery;
|
|
440
|
-
|
|
441
|
-
yield* this.#runAgent(initialQuery, options);
|
|
432
|
+
yield* this.#runAgent(initialQuery, options, {isInitialCall: true});
|
|
442
433
|
} finally {
|
|
443
434
|
targetManager.removeModelListener(
|
|
444
435
|
SDK.ResourceTreeModel.ResourceTreeModel, SDK.ResourceTreeModel.Events.PrimaryPageChanged, listener, this);
|
|
@@ -456,6 +447,7 @@ export class AiConversation {
|
|
|
456
447
|
signal?: AbortSignal,
|
|
457
448
|
multimodalInput?: MultimodalInput,
|
|
458
449
|
} = {},
|
|
450
|
+
runOptions: {isInitialCall?: boolean} = {},
|
|
459
451
|
): AsyncGenerator<ResponseData, void, void> {
|
|
460
452
|
this.#setOriginIfEmpty(this.selectedContext?.getOrigin());
|
|
461
453
|
if (this.isBlockedByOrigin) {
|
|
@@ -466,6 +458,17 @@ export class AiConversation {
|
|
|
466
458
|
return;
|
|
467
459
|
}
|
|
468
460
|
|
|
461
|
+
if (runOptions.isInitialCall) {
|
|
462
|
+
const userQuery: UserQuery = {
|
|
463
|
+
type: ResponseType.USER_QUERY,
|
|
464
|
+
query: initialQuery,
|
|
465
|
+
imageInput: options.multimodalInput?.input,
|
|
466
|
+
imageId: options.multimodalInput?.id,
|
|
467
|
+
};
|
|
468
|
+
void this.addHistoryItem(userQuery);
|
|
469
|
+
yield userQuery;
|
|
470
|
+
}
|
|
471
|
+
|
|
469
472
|
function shouldAddToHistory(data: ResponseData): boolean {
|
|
470
473
|
if (data.type === ResponseType.CONTEXT_CHANGE) {
|
|
471
474
|
return false;
|
|
@@ -499,7 +502,9 @@ export class AiConversation {
|
|
|
499
502
|
// requery with the specialized agent.
|
|
500
503
|
if (data.type === ResponseType.CONTEXT_CHANGE) {
|
|
501
504
|
this.setContext(data.context);
|
|
502
|
-
yield*
|
|
505
|
+
yield*
|
|
506
|
+
this.#runAgent(
|
|
507
|
+
this.#getQueryAfterSelection(initialQuery, data.description), options, {isInitialCall: false});
|
|
503
508
|
return;
|
|
504
509
|
}
|
|
505
510
|
}
|
|
@@ -236,7 +236,18 @@ export class AccessibilityAgent extends AiAgent<LHModel.ReporterTypes.ReportJSON
|
|
|
236
236
|
if (!nodeId) {
|
|
237
237
|
return null;
|
|
238
238
|
}
|
|
239
|
-
|
|
239
|
+
const node = domModel.nodeForId(nodeId);
|
|
240
|
+
if (!node) {
|
|
241
|
+
return null;
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
const resourceTreeModel = target.model(SDK.ResourceTreeModel.ResourceTreeModel);
|
|
245
|
+
const mainFrameId = resourceTreeModel?.mainFrame?.id;
|
|
246
|
+
if (node.frameId() !== mainFrameId) {
|
|
247
|
+
return null;
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
return node;
|
|
240
251
|
}
|
|
241
252
|
|
|
242
253
|
#declareFunctions(): void {
|
|
@@ -281,7 +292,7 @@ export class AccessibilityAgent extends AiAgent<LHModel.ReporterTypes.ReportJSON
|
|
|
281
292
|
const audits = new LighthouseFormatter().audits(report, 'accessibility');
|
|
282
293
|
return {
|
|
283
294
|
result: {audits},
|
|
284
|
-
widgets: [{name: 'LIGHTHOUSE_REPORT', data: {report}}],
|
|
295
|
+
widgets: [{name: 'LIGHTHOUSE_REPORT', data: {report, snapshotReport: true}}],
|
|
285
296
|
};
|
|
286
297
|
}
|
|
287
298
|
});
|
|
@@ -13,6 +13,8 @@ import type * as Trace from '../../trace/trace.js';
|
|
|
13
13
|
import type * as Workspace from '../../workspace/workspace.js';
|
|
14
14
|
import {debugLog, isStructuredLogEnabled} from '../debug.js';
|
|
15
15
|
|
|
16
|
+
const MAX_SUGGESTION_LENGTH = 200;
|
|
17
|
+
|
|
16
18
|
export const enum ResponseType {
|
|
17
19
|
CONTEXT = 'context',
|
|
18
20
|
TITLE = 'title',
|
|
@@ -35,6 +37,21 @@ export const enum ErrorType {
|
|
|
35
37
|
CROSS_ORIGIN = 'cross-origin'
|
|
36
38
|
}
|
|
37
39
|
|
|
40
|
+
/**
|
|
41
|
+
* Returns true if the origin is considered opaque and should be blocked from
|
|
42
|
+
* AI assistance to prevent potential data leakage.
|
|
43
|
+
*
|
|
44
|
+
* @see https://crbug.com/513732588
|
|
45
|
+
*/
|
|
46
|
+
export function isOpaqueOrigin(origin: string): boolean {
|
|
47
|
+
/**
|
|
48
|
+
* Origins starting with 'about' (like about:blank or about:srcdoc) are
|
|
49
|
+
* considered opaque. 'about://' is the sentinel used by DevTools
|
|
50
|
+
* ParsedURL.securityOrigin() for these.
|
|
51
|
+
*/
|
|
52
|
+
return origin === 'null' || origin === 'data:' || origin.startsWith('about') || origin.startsWith('detached');
|
|
53
|
+
}
|
|
54
|
+
|
|
38
55
|
export const enum MultimodalInputType {
|
|
39
56
|
SCREENSHOT = 'screenshot',
|
|
40
57
|
UPLOADED_IMAGE = 'uploaded-image',
|
|
@@ -185,15 +202,30 @@ export abstract class ConversationContext<T> {
|
|
|
185
202
|
abstract getItem(): T;
|
|
186
203
|
abstract getTitle(): string;
|
|
187
204
|
|
|
188
|
-
|
|
189
|
-
|
|
205
|
+
/**
|
|
206
|
+
* Returns true if this data context (e.g., a DOM node or Network Request) is
|
|
207
|
+
* allowed to be included in a conversation that is locked to the provided
|
|
208
|
+
* `establishedOrigin`.
|
|
209
|
+
*
|
|
210
|
+
* A conversation is "locked" to an origin once the first query is made.
|
|
211
|
+
* This method ensures that we don't mix data from different origins in the
|
|
212
|
+
* same conversation.
|
|
213
|
+
*
|
|
214
|
+
* @param establishedOrigin The origin that the current conversation is locked to.
|
|
215
|
+
* If undefined, the conversation has not yet been locked to an origin.
|
|
216
|
+
*/
|
|
217
|
+
isOriginAllowed(establishedOrigin: string|undefined): boolean {
|
|
218
|
+
const dataOrigin = this.getOrigin();
|
|
219
|
+
// Opaque origins are never allowed to be used as context.
|
|
220
|
+
if (isOpaqueOrigin(dataOrigin)) {
|
|
221
|
+
return false;
|
|
222
|
+
}
|
|
223
|
+
// If no origin is established yet, this context will be the one to lock the conversation.
|
|
224
|
+
if (!establishedOrigin) {
|
|
190
225
|
return true;
|
|
191
226
|
}
|
|
192
|
-
//
|
|
193
|
-
|
|
194
|
-
// that serialization of the origin is the same
|
|
195
|
-
// https://html.spec.whatwg.org/#ascii-serialisation-of-an-origin.
|
|
196
|
-
return this.getOrigin() === agentOrigin;
|
|
227
|
+
// Only allow data that matches the origin the conversation is already locked to.
|
|
228
|
+
return dataOrigin === establishedOrigin;
|
|
197
229
|
}
|
|
198
230
|
|
|
199
231
|
/**
|
|
@@ -294,6 +326,7 @@ export interface LighthouseReportAiWidget {
|
|
|
294
326
|
name: 'LIGHTHOUSE_REPORT';
|
|
295
327
|
data: {
|
|
296
328
|
report: LHModel.ReporterTypes.ReportJSON,
|
|
329
|
+
snapshotReport?: boolean,
|
|
297
330
|
};
|
|
298
331
|
}
|
|
299
332
|
|
|
@@ -545,8 +578,7 @@ export abstract class AiAgent<T> {
|
|
|
545
578
|
const trimmed = line.trim();
|
|
546
579
|
if (trimmed.startsWith('SUGGESTIONS:')) {
|
|
547
580
|
try {
|
|
548
|
-
|
|
549
|
-
suggestions = JSON.parse(trimmed.substring('SUGGESTIONS:'.length).trim());
|
|
581
|
+
suggestions = sanitizeSuggestions(trimmed.substring('SUGGESTIONS:'.length).trim());
|
|
550
582
|
} catch {
|
|
551
583
|
}
|
|
552
584
|
} else {
|
|
@@ -559,8 +591,7 @@ export abstract class AiAgent<T> {
|
|
|
559
591
|
if (!suggestions && answerLines.at(-1)?.includes('SUGGESTIONS:')) {
|
|
560
592
|
const [answer, suggestionsText] = answerLines[answerLines.length - 1].split('SUGGESTIONS:', 2);
|
|
561
593
|
try {
|
|
562
|
-
|
|
563
|
-
suggestions = JSON.parse(suggestionsText.trim().substring('SUGGESTIONS:'.length).trim());
|
|
594
|
+
suggestions = sanitizeSuggestions(suggestionsText.trim());
|
|
564
595
|
} catch {
|
|
565
596
|
}
|
|
566
597
|
answerLines[answerLines.length - 1] = answer;
|
|
@@ -967,3 +998,26 @@ export abstract class AiAgent<T> {
|
|
|
967
998
|
};
|
|
968
999
|
}
|
|
969
1000
|
}
|
|
1001
|
+
|
|
1002
|
+
function sanitizeSuggestions(suggestions: string): [string, ...string[]]|undefined {
|
|
1003
|
+
const parsed = JSON.parse(suggestions);
|
|
1004
|
+
if (!Array.isArray(parsed)) {
|
|
1005
|
+
return undefined;
|
|
1006
|
+
}
|
|
1007
|
+
const sanitized: string[] = [];
|
|
1008
|
+
for (const item of parsed) {
|
|
1009
|
+
if (typeof item !== 'string') {
|
|
1010
|
+
continue;
|
|
1011
|
+
}
|
|
1012
|
+
// Collapse multiple whitespace/newlines into a single space.
|
|
1013
|
+
const noExtraWhitespace = item.replace(/\s+/g, ' ').trim();
|
|
1014
|
+
if (noExtraWhitespace.length === 0) {
|
|
1015
|
+
continue;
|
|
1016
|
+
}
|
|
1017
|
+
sanitized.push(noExtraWhitespace.substring(0, MAX_SUGGESTION_LENGTH));
|
|
1018
|
+
}
|
|
1019
|
+
if (sanitized.length === 0) {
|
|
1020
|
+
return undefined;
|
|
1021
|
+
}
|
|
1022
|
+
return sanitized as [string, ...string[]];
|
|
1023
|
+
}
|
|
@@ -20,6 +20,7 @@ import {
|
|
|
20
20
|
AiAgent,
|
|
21
21
|
type AllowedOriginResult,
|
|
22
22
|
type ContextResponse,
|
|
23
|
+
isOpaqueOrigin,
|
|
23
24
|
type RequestOptions,
|
|
24
25
|
} from './AiAgent.js';
|
|
25
26
|
import {FileContext} from './FileAgent.js';
|
|
@@ -126,6 +127,11 @@ export class ContextSelectionAgent extends AiAgent<never> {
|
|
|
126
127
|
};
|
|
127
128
|
}
|
|
128
129
|
const origin = allowedOriginResult.origin;
|
|
130
|
+
if (origin && isOpaqueOrigin(origin)) {
|
|
131
|
+
return {
|
|
132
|
+
error: 'No requests recorded by DevTools',
|
|
133
|
+
};
|
|
134
|
+
}
|
|
129
135
|
|
|
130
136
|
let hasCrossOriginRequest = false;
|
|
131
137
|
for (const request of Logs.NetworkLog.NetworkLog.instance().requests()) {
|
|
@@ -196,6 +202,11 @@ export class ContextSelectionAgent extends AiAgent<never> {
|
|
|
196
202
|
};
|
|
197
203
|
}
|
|
198
204
|
const origin = allowedOriginResult.origin;
|
|
205
|
+
if (origin && isOpaqueOrigin(origin)) {
|
|
206
|
+
return {
|
|
207
|
+
error: 'No request found',
|
|
208
|
+
};
|
|
209
|
+
}
|
|
199
210
|
const request = Logs.NetworkLog.NetworkLog.instance().requests().find(req => {
|
|
200
211
|
if (req.requestId() !== id) {
|
|
201
212
|
return false;
|
|
@@ -235,8 +246,23 @@ export class ContextSelectionAgent extends AiAgent<never> {
|
|
|
235
246
|
};
|
|
236
247
|
},
|
|
237
248
|
handler: async () => {
|
|
249
|
+
const allowedOriginResult = this.#allowedOrigin();
|
|
250
|
+
if ('blocked' in allowedOriginResult) {
|
|
251
|
+
return {
|
|
252
|
+
error: 'Cross-origin access blocked due to navigation. Please start a new chat.',
|
|
253
|
+
};
|
|
254
|
+
}
|
|
255
|
+
const origin = allowedOriginResult.origin;
|
|
256
|
+
|
|
238
257
|
const files: Array<{file: string, id: number | undefined}> = [];
|
|
239
258
|
for (const file of ContextSelectionAgent.getUISourceCodes()) {
|
|
259
|
+
const fileUrl = file.url();
|
|
260
|
+
const fileOrigin = Common.ParsedURL.ParsedURL.extractOrigin(fileUrl);
|
|
261
|
+
|
|
262
|
+
if (origin && fileOrigin !== origin) {
|
|
263
|
+
continue;
|
|
264
|
+
}
|
|
265
|
+
|
|
240
266
|
files.push({
|
|
241
267
|
file: file.fullDisplayName(),
|
|
242
268
|
id: ContextSelectionAgent.uiSourceCodeId.get(file),
|
|
@@ -272,8 +298,22 @@ export class ContextSelectionAgent extends AiAgent<never> {
|
|
|
272
298
|
};
|
|
273
299
|
},
|
|
274
300
|
handler: async params => {
|
|
275
|
-
const
|
|
276
|
-
|
|
301
|
+
const allowedOriginResult = this.#allowedOrigin();
|
|
302
|
+
if ('blocked' in allowedOriginResult) {
|
|
303
|
+
return {
|
|
304
|
+
error: 'Cross-origin access blocked due to navigation. Please start a new chat.',
|
|
305
|
+
};
|
|
306
|
+
}
|
|
307
|
+
const origin = allowedOriginResult.origin;
|
|
308
|
+
|
|
309
|
+
const file = ContextSelectionAgent.getUISourceCodes().find(file => {
|
|
310
|
+
if (ContextSelectionAgent.uiSourceCodeId.get(file) !== params.id) {
|
|
311
|
+
return false;
|
|
312
|
+
}
|
|
313
|
+
const fileUrl = file.url();
|
|
314
|
+
const fileOrigin = Common.ParsedURL.ParsedURL.extractOrigin(fileUrl);
|
|
315
|
+
return !origin || fileOrigin === origin;
|
|
316
|
+
});
|
|
277
317
|
|
|
278
318
|
if (!file) {
|
|
279
319
|
return {
|
|
@@ -14,6 +14,7 @@ import * as Logs from '../../logs/logs.js';
|
|
|
14
14
|
import * as SourceMapScopes from '../../source_map_scopes/source_map_scopes.js';
|
|
15
15
|
import * as TextUtils from '../../text_utils/text_utils.js';
|
|
16
16
|
import * as Trace from '../../trace/trace.js';
|
|
17
|
+
import {sanitizeHeaders} from '../data_formatters/NetworkRequestFormatter.js';
|
|
17
18
|
import {
|
|
18
19
|
PerformanceInsightFormatter,
|
|
19
20
|
} from '../data_formatters/PerformanceInsightFormatter.js';
|
|
@@ -1060,7 +1061,35 @@ export class PerformanceAgent extends AiAgent<AgentFocus> {
|
|
|
1060
1061
|
}
|
|
1061
1062
|
|
|
1062
1063
|
// TODO(b/425270067): Format in the same way that "Summary" detail tab does.
|
|
1063
|
-
|
|
1064
|
+
let details;
|
|
1065
|
+
if (Trace.Types.Events.isSyntheticNetworkRequest(event)) {
|
|
1066
|
+
const eventToSerialize = {
|
|
1067
|
+
...event,
|
|
1068
|
+
args: {
|
|
1069
|
+
...event.args,
|
|
1070
|
+
data: {
|
|
1071
|
+
...event.args.data,
|
|
1072
|
+
responseHeaders: event.args.data.responseHeaders ? sanitizeHeaders(event.args.data.responseHeaders) :
|
|
1073
|
+
null,
|
|
1074
|
+
},
|
|
1075
|
+
},
|
|
1076
|
+
};
|
|
1077
|
+
details = JSON.stringify(eventToSerialize);
|
|
1078
|
+
} else if (Trace.Types.Events.isResourceReceiveResponse(event)) {
|
|
1079
|
+
const eventToSerialize = {
|
|
1080
|
+
...event,
|
|
1081
|
+
args: {
|
|
1082
|
+
...event.args,
|
|
1083
|
+
data: {
|
|
1084
|
+
...event.args.data,
|
|
1085
|
+
headers: event.args.data.headers ? sanitizeHeaders(event.args.data.headers) : undefined,
|
|
1086
|
+
},
|
|
1087
|
+
},
|
|
1088
|
+
};
|
|
1089
|
+
details = JSON.stringify(eventToSerialize);
|
|
1090
|
+
} else {
|
|
1091
|
+
details = JSON.stringify(event);
|
|
1092
|
+
}
|
|
1064
1093
|
|
|
1065
1094
|
const key = `getEventByKey('${params.eventKey}')`;
|
|
1066
1095
|
this.#cacheFunctionResult(focus, key, details);
|
|
@@ -3,6 +3,7 @@
|
|
|
3
3
|
// found in the LICENSE file.
|
|
4
4
|
|
|
5
5
|
import * as Common from '../../../core/common/common.js';
|
|
6
|
+
import type * as CrUXManager from '../../crux-manager/crux-manager.js';
|
|
6
7
|
import * as Trace from '../../trace/trace.js';
|
|
7
8
|
import type {ConversationSuggestions} from '../agents/AiAgent.js';
|
|
8
9
|
import type {AgentFocus} from '../performance/AIContext.js';
|
|
@@ -48,8 +49,9 @@ export class PerformanceInsightFormatter {
|
|
|
48
49
|
#insight: Trace.Insights.Types.InsightModel;
|
|
49
50
|
#parsedTrace: Trace.TraceModel.ParsedTrace;
|
|
50
51
|
|
|
51
|
-
constructor(
|
|
52
|
-
|
|
52
|
+
constructor(
|
|
53
|
+
focus: AgentFocus, insight: Trace.Insights.Types.InsightModel, deviceScope: CrUXManager.DeviceScope|null = null) {
|
|
54
|
+
this.#traceFormatter = new PerformanceTraceFormatter(focus, deviceScope);
|
|
53
55
|
this.#insight = insight;
|
|
54
56
|
this.#parsedTrace = focus.parsedTrace;
|
|
55
57
|
}
|
|
@@ -35,15 +35,17 @@ export class PerformanceTraceFormatter {
|
|
|
35
35
|
#insightSet: Trace.Insights.Types.InsightSet|null;
|
|
36
36
|
#eventsSerializer: Trace.EventsSerializer.EventsSerializer;
|
|
37
37
|
#formattedFunctionCodes = new Set<string>();
|
|
38
|
+
#deviceScope: CrUXManager.DeviceScope|null;
|
|
38
39
|
resolveFunctionCode?:
|
|
39
40
|
(url: Platform.DevToolsPath.UrlString, line: number,
|
|
40
41
|
column: number) => Promise<SourceMapScopes.FunctionCodeResolver.FunctionCode|null>;
|
|
41
42
|
|
|
42
|
-
constructor(focus: AgentFocus) {
|
|
43
|
+
constructor(focus: AgentFocus, deviceScope: CrUXManager.DeviceScope|null = null) {
|
|
43
44
|
this.#focus = focus;
|
|
44
45
|
this.#parsedTrace = focus.parsedTrace;
|
|
45
46
|
this.#insightSet = focus.primaryInsightSet;
|
|
46
47
|
this.#eventsSerializer = focus.eventsSerializer;
|
|
48
|
+
this.#deviceScope = deviceScope;
|
|
47
49
|
}
|
|
48
50
|
|
|
49
51
|
serializeEvent(event: Trace.Types.Events.Event): string {
|
|
@@ -64,7 +66,8 @@ export class PerformanceTraceFormatter {
|
|
|
64
66
|
return [];
|
|
65
67
|
}
|
|
66
68
|
try {
|
|
67
|
-
const cruxScope =
|
|
69
|
+
const cruxScope: CrUXManager.Scope = this.#deviceScope ? {pageScope: 'url', deviceScope: this.#deviceScope} :
|
|
70
|
+
CrUXManager.CrUXManager.instance().getSelectedScope();
|
|
68
71
|
const parts: string[] = [];
|
|
69
72
|
const fieldMetrics =
|
|
70
73
|
Trace.Insights.Common.getFieldMetricsForInsightSet(insightSet, this.#parsedTrace.metadata, cruxScope);
|
|
@@ -2,6 +2,8 @@
|
|
|
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 type * as Platform from '../../core/platform/platform.js';
|
|
6
|
+
|
|
5
7
|
import {PrivateAPI} from './ExtensionAPI.js';
|
|
6
8
|
import {ExtensionEndpoint} from './ExtensionEndpoint.js';
|
|
7
9
|
import {RecorderPluginManager} from './RecorderPluginManager.js';
|
|
@@ -10,20 +12,26 @@ export class RecorderExtensionEndpoint extends ExtensionEndpoint {
|
|
|
10
12
|
private readonly name: string;
|
|
11
13
|
private readonly mediaType?: string;
|
|
12
14
|
private readonly capabilities: PrivateAPI.RecordingExtensionPluginCapability[];
|
|
15
|
+
readonly #extensionOrigin: Platform.DevToolsPath.UrlString;
|
|
13
16
|
|
|
14
17
|
constructor(
|
|
15
18
|
name: string, port: MessagePort, capabilities: PrivateAPI.RecordingExtensionPluginCapability[],
|
|
16
|
-
mediaType?: string) {
|
|
19
|
+
extensionOrigin: Platform.DevToolsPath.UrlString, mediaType?: string) {
|
|
17
20
|
super(port);
|
|
18
21
|
this.name = name;
|
|
19
22
|
this.mediaType = mediaType;
|
|
20
23
|
this.capabilities = capabilities;
|
|
24
|
+
this.#extensionOrigin = extensionOrigin;
|
|
21
25
|
}
|
|
22
26
|
|
|
23
27
|
getName(): string {
|
|
24
28
|
return this.name;
|
|
25
29
|
}
|
|
26
30
|
|
|
31
|
+
getOrigin(): Platform.DevToolsPath.UrlString {
|
|
32
|
+
return this.#extensionOrigin;
|
|
33
|
+
}
|
|
34
|
+
|
|
27
35
|
getCapabilities(): PrivateAPI.RecordingExtensionPluginCapability[] {
|
|
28
36
|
return this.capabilities;
|
|
29
37
|
}
|
|
@@ -507,26 +507,28 @@ export class TimelineFrameBeginFrameQueue {
|
|
|
507
507
|
private queueFrames: number[] = [];
|
|
508
508
|
|
|
509
509
|
// Maps frameSeqId to BeginFrameInfo.
|
|
510
|
-
private mapFrames
|
|
510
|
+
private mapFrames = new Map<number, BeginFrameInfo>();
|
|
511
511
|
|
|
512
512
|
// Add a BeginFrame to the queue, if it does not already exit.
|
|
513
513
|
addFrameIfNotExists(seqId: number, startTime: Types.Timing.Micro, isDropped: boolean, isPartial: boolean): void {
|
|
514
|
-
if (!
|
|
515
|
-
this.mapFrames
|
|
514
|
+
if (!this.mapFrames.has(seqId)) {
|
|
515
|
+
this.mapFrames.set(seqId, new BeginFrameInfo(seqId, startTime, isDropped, isPartial));
|
|
516
516
|
this.queueFrames.push(seqId);
|
|
517
517
|
}
|
|
518
518
|
}
|
|
519
519
|
|
|
520
520
|
// Set a BeginFrame in queue as dropped.
|
|
521
521
|
setDropped(seqId: number, isDropped: boolean): void {
|
|
522
|
-
|
|
523
|
-
|
|
522
|
+
const frame = this.mapFrames.get(seqId);
|
|
523
|
+
if (frame) {
|
|
524
|
+
frame.isDropped = isDropped;
|
|
524
525
|
}
|
|
525
526
|
}
|
|
526
527
|
|
|
527
528
|
setPartial(seqId: number, isPartial: boolean): void {
|
|
528
|
-
|
|
529
|
-
|
|
529
|
+
const frame = this.mapFrames.get(seqId);
|
|
530
|
+
if (frame) {
|
|
531
|
+
frame.isPartial = isPartial;
|
|
530
532
|
}
|
|
531
533
|
}
|
|
532
534
|
|
|
@@ -535,7 +537,7 @@ export class TimelineFrameBeginFrameQueue {
|
|
|
535
537
|
|
|
536
538
|
// Do not visualize this frame in the rare case where the current DrawFrame
|
|
537
539
|
// does not have a corresponding BeginFrame.
|
|
538
|
-
if (
|
|
540
|
+
if (this.mapFrames.has(seqId)) {
|
|
539
541
|
// Pop all BeginFrames before the current frame, and add only the dropped
|
|
540
542
|
// ones in |frames_to_visualize|.
|
|
541
543
|
// Non-dropped frames popped here are BeginFrames that are never
|
|
@@ -544,17 +546,21 @@ export class TimelineFrameBeginFrameQueue {
|
|
|
544
546
|
// be naturally presented as continuationss of other frames.
|
|
545
547
|
while (this.queueFrames[0] !== seqId) {
|
|
546
548
|
const currentSeqId = this.queueFrames[0];
|
|
547
|
-
|
|
548
|
-
|
|
549
|
+
const currentFrame = this.mapFrames.get(currentSeqId);
|
|
550
|
+
if (currentFrame && currentFrame.isDropped) {
|
|
551
|
+
framesToVisualize.push(currentFrame);
|
|
549
552
|
}
|
|
550
553
|
|
|
551
|
-
|
|
554
|
+
this.mapFrames.delete(currentSeqId);
|
|
552
555
|
this.queueFrames.shift();
|
|
553
556
|
}
|
|
554
557
|
|
|
555
558
|
// Pop the BeginFrame associated with the current DrawFrame.
|
|
556
|
-
|
|
557
|
-
|
|
559
|
+
const frame = this.mapFrames.get(seqId);
|
|
560
|
+
if (frame) {
|
|
561
|
+
framesToVisualize.push(frame);
|
|
562
|
+
}
|
|
563
|
+
this.mapFrames.delete(seqId);
|
|
558
564
|
this.queueFrames.shift();
|
|
559
565
|
}
|
|
560
566
|
return framesToVisualize;
|
|
@@ -123,6 +123,9 @@ export class AccessibilityAgentMarkdownRenderer extends MarkdownRendererWithCode
|
|
|
123
123
|
if (!node) {
|
|
124
124
|
return;
|
|
125
125
|
}
|
|
126
|
+
if (node.frameId() !== this.mainFrameId) {
|
|
127
|
+
return;
|
|
128
|
+
}
|
|
126
129
|
const linkedNode = PanelsCommon.DOMLinkifier.Linkifier.instance().linkify(node, {textContent: label});
|
|
127
130
|
return linkedNode;
|
|
128
131
|
}
|
|
@@ -530,9 +530,11 @@ export class ChatInput extends UI.Widget.Widget implements SDK.TargetManager.Obs
|
|
|
530
530
|
|
|
531
531
|
setInputValue(text: string): void {
|
|
532
532
|
if (this.#textAreaRef.value) {
|
|
533
|
-
this.#textAreaRef.value.
|
|
533
|
+
const maxLength = this.#textAreaRef.value.maxLength;
|
|
534
|
+
const truncatedText = (maxLength >= 0) ? text.substring(0, maxLength) : text;
|
|
535
|
+
this.#textAreaRef.value.value = truncatedText;
|
|
534
536
|
// Place the cursor at the end of the new value.
|
|
535
|
-
this.#textAreaRef.value.setSelectionRange(
|
|
537
|
+
this.#textAreaRef.value.setSelectionRange(truncatedText.length, truncatedText.length);
|
|
536
538
|
}
|
|
537
539
|
this.performUpdate();
|
|
538
540
|
}
|
|
@@ -2090,11 +2090,13 @@ async function makeLighthouseReportWidget(widgetData: LighthouseReportAiWidget):
|
|
|
2090
2090
|
return null;
|
|
2091
2091
|
}
|
|
2092
2092
|
|
|
2093
|
+
const snapshotReport = widgetData.data.snapshotReport;
|
|
2094
|
+
|
|
2093
2095
|
return {
|
|
2094
2096
|
renderedWidget: html`<div class="lighthouse-report-widget">${reportEl}</div>`,
|
|
2095
2097
|
revealable: new Lighthouse.LighthousePanel.ActiveLighthouseReport(widgetData.data.report),
|
|
2096
2098
|
accessibleRevealLabel: lockedString(UIStringsNotTranslate.revealLighthouse),
|
|
2097
2099
|
title: lockedString(UIStringsNotTranslate.lighthouseReport),
|
|
2098
|
-
jslogContext: 'lighthouse-report-widget',
|
|
2100
|
+
jslogContext: snapshotReport ? 'lighthouse-snapshot-report-widget' : 'lighthouse-report-widget',
|
|
2099
2101
|
};
|
|
2100
2102
|
}
|
|
@@ -11,11 +11,6 @@ import * as Protocol from '../../../../generated/protocol.js';
|
|
|
11
11
|
import * as Bindings from '../../../../models/bindings/bindings.js';
|
|
12
12
|
|
|
13
13
|
const UIStrings = {
|
|
14
|
-
/**
|
|
15
|
-
* @description Descrption text for Prefetch status PrefetchCancelledOnUserNavigation.
|
|
16
|
-
*/
|
|
17
|
-
PrefetchCancelledOnUserNavigation:
|
|
18
|
-
'The prefetch was cancelled because the user navigated the page before the prefetch finished',
|
|
19
14
|
/**
|
|
20
15
|
* @description Description text for Prefetch status PrefetchFailedIneligibleRedirect.
|
|
21
16
|
*/
|
|
@@ -459,7 +454,6 @@ export const PrefetchReasonDescription: Record<string, {name: () => Platform.UIS
|
|
|
459
454
|
PrefetchNotEligibleRedirectFromServiceWorker: {name: () => i18n.i18n.lockedString('Unknown')},
|
|
460
455
|
PrefetchNotEligibleRedirectToServiceWorker: {name: () => i18n.i18n.lockedString('Unknown')},
|
|
461
456
|
PrefetchEvictedAfterBrowsingDataRemoved: {name: i18nLazyString(UIStrings.PrefetchEvictedAfterBrowsingDataRemoved)},
|
|
462
|
-
PrefetchCancelledOnUserNavigation: {name: i18nLazyString(UIStrings.PrefetchCancelledOnUserNavigation)},
|
|
463
457
|
};
|
|
464
458
|
|
|
465
459
|
/** Decoding PrefetchFinalStatus prefetchAttempt to failure description. **/
|
|
@@ -546,8 +540,6 @@ export function prefetchFailureReason(
|
|
|
546
540
|
return PrefetchReasonDescription['PrefetchNotEligibleRedirectToServiceWorker'].name();
|
|
547
541
|
case Protocol.Preload.PrefetchStatus.PrefetchEvictedAfterBrowsingDataRemoved:
|
|
548
542
|
return PrefetchReasonDescription['PrefetchEvictedAfterBrowsingDataRemoved'].name();
|
|
549
|
-
case Protocol.Preload.PrefetchStatus.PrefetchCancelledOnUserNavigation:
|
|
550
|
-
return PrefetchReasonDescription['PrefetchCancelledOnUserNavigation'].name();
|
|
551
543
|
default:
|
|
552
544
|
// Note that we use switch and exhaustiveness check to prevent to
|
|
553
545
|
// forget updating these strings, but allow to handle unknown
|