chrome-devtools-mcp 0.6.0 → 0.7.0
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/README.md +13 -6
- package/build/node_modules/chrome-devtools-frontend/front_end/core/common/Color.js +13 -9
- package/build/node_modules/chrome-devtools-frontend/front_end/core/common/ColorConverter.js +9 -7
- package/build/node_modules/chrome-devtools-frontend/front_end/core/common/Gzip.js +1 -1
- package/build/node_modules/chrome-devtools-frontend/front_end/core/common/MapWithDefault.js +5 -3
- package/build/node_modules/chrome-devtools-frontend/front_end/core/common/ResourceType.js +0 -11
- package/build/node_modules/chrome-devtools-frontend/front_end/core/common/ReturnToPanel.js +6 -4
- package/build/node_modules/chrome-devtools-frontend/front_end/core/host/AidaClient.js +1 -1
- package/build/node_modules/chrome-devtools-frontend/front_end/core/host/GdpClient.js +116 -59
- package/build/node_modules/chrome-devtools-frontend/front_end/core/host/Platform.js +5 -3
- package/build/node_modules/chrome-devtools-frontend/front_end/core/host/UserMetrics.js +6 -4
- package/build/node_modules/chrome-devtools-frontend/front_end/core/platform/ArrayUtilities.js +1 -1
- package/build/node_modules/chrome-devtools-frontend/front_end/core/platform/StringUtilities.js +33 -31
- package/build/node_modules/chrome-devtools-frontend/front_end/core/sdk/CSSMetadata.js +4 -2
- package/build/node_modules/chrome-devtools-frontend/front_end/core/sdk/CSSPropertyParser.js +11 -9
- package/build/node_modules/chrome-devtools-frontend/front_end/core/sdk/CSSPropertyParserMatchers.js +19 -13
- package/build/node_modules/chrome-devtools-frontend/front_end/core/sdk/ChildTargetManager.js +30 -0
- package/build/node_modules/chrome-devtools-frontend/front_end/core/sdk/DOMModel.js +1 -1
- package/build/node_modules/chrome-devtools-frontend/front_end/core/sdk/EventBreakpointsModel.js +4 -2
- package/build/node_modules/chrome-devtools-frontend/front_end/core/sdk/HttpReasonPhraseStrings.js +4 -2
- package/build/node_modules/chrome-devtools-frontend/front_end/core/sdk/NetworkManager.js +9 -41
- package/build/node_modules/chrome-devtools-frontend/front_end/core/sdk/NetworkRequest.js +0 -14
- package/build/node_modules/chrome-devtools-frontend/front_end/core/sdk/PageResourceLoader.js +1 -1
- package/build/node_modules/chrome-devtools-frontend/front_end/core/sdk/PreloadingModel.js +7 -5
- package/build/node_modules/chrome-devtools-frontend/front_end/core/sdk/RehydratingConnection.js +1 -1
- package/build/node_modules/chrome-devtools-frontend/front_end/core/sdk/RemoteObject.js +1 -1
- package/build/node_modules/chrome-devtools-frontend/front_end/core/sdk/ResourceTreeModel.js +1 -0
- package/build/node_modules/chrome-devtools-frontend/front_end/core/sdk/ScreenCaptureModel.js +20 -18
- package/build/node_modules/chrome-devtools-frontend/front_end/core/sdk/Target.js +7 -1
- package/build/node_modules/chrome-devtools-frontend/front_end/core/sdk/TraceObject.js +2 -2
- package/build/node_modules/chrome-devtools-frontend/front_end/generated/Deprecation.js +4 -4
- package/build/node_modules/chrome-devtools-frontend/front_end/generated/InspectorBackendCommands.js +2 -2
- package/build/node_modules/chrome-devtools-frontend/front_end/models/ai_assistance/data_formatters/NetworkRequestFormatter.js +30 -3
- package/build/node_modules/chrome-devtools-frontend/front_end/models/ai_assistance/performance/AIContext.js +18 -4
- package/build/node_modules/chrome-devtools-frontend/front_end/models/crux-manager/CrUXManager.js +1 -1
- package/build/node_modules/chrome-devtools-frontend/front_end/models/network_time_calculator/RequestTimeRanges.js +6 -4
- package/build/node_modules/chrome-devtools-frontend/front_end/models/source_map_scopes/NamesResolver.js +7 -5
- package/build/node_modules/chrome-devtools-frontend/front_end/models/trace/LanternComputationData.js +1 -0
- package/build/node_modules/chrome-devtools-frontend/front_end/models/trace/extras/TraceTree.js +1 -1
- package/build/node_modules/chrome-devtools-frontend/front_end/models/trace/handlers/FramesHandler.js +7 -5
- package/build/node_modules/chrome-devtools-frontend/front_end/models/trace/handlers/LayoutShiftsHandler.js +8 -4
- package/build/node_modules/chrome-devtools-frontend/front_end/models/trace/handlers/NetworkRequestsHandler.js +17 -0
- package/build/node_modules/chrome-devtools-frontend/front_end/models/trace/handlers/helpers.js +1 -1
- package/build/node_modules/chrome-devtools-frontend/front_end/models/trace/helpers/Timing.js +4 -2
- package/build/node_modules/chrome-devtools-frontend/front_end/models/trace/helpers/Trace.js +8 -4
- package/build/node_modules/chrome-devtools-frontend/front_end/models/trace/insights/DocumentLatency.js +10 -10
- package/build/node_modules/chrome-devtools-frontend/front_end/models/trace/insights/INPBreakdown.js +12 -1
- package/build/node_modules/chrome-devtools-frontend/front_end/models/trace/insights/LCPBreakdown.js +11 -1
- package/build/node_modules/chrome-devtools-frontend/front_end/models/trace/insights/NetworkDependencyTree.js +2 -2
- package/build/node_modules/chrome-devtools-frontend/front_end/models/trace/types/TraceEvents.js +5 -3
- package/build/node_modules/chrome-devtools-frontend/front_end/models/trace_source_maps_resolver/SourceMapsResolver.js +1 -1
- package/build/src/McpContext.js +24 -7
- package/build/src/McpResponse.js +49 -20
- package/build/src/Mutex.js +3 -6
- package/build/src/browser.js +6 -5
- package/build/src/cli.js +10 -2
- package/build/src/formatters/consoleFormatter.js +1 -1
- package/build/src/formatters/networkFormatter.js +44 -0
- package/build/src/tools/emulation.js +13 -2
- package/build/src/tools/performance.js +3 -4
- package/build/src/tools/screenshot.js +2 -3
- package/build/src/trace-processing/parse.js +7 -6
- package/package.json +7 -6
|
@@ -3,11 +3,25 @@
|
|
|
3
3
|
// found in the LICENSE file.
|
|
4
4
|
import * as Trace from '../../../models/trace/trace.js';
|
|
5
5
|
import { AICallTree } from './AICallTree.js';
|
|
6
|
+
/**
|
|
7
|
+
* Gets the first, most relevant InsightSet to use, following the logic of:
|
|
8
|
+
* 1. If there is only one InsightSet, use that.
|
|
9
|
+
* 2. If there are more, prefer the first we find that has a navigation associated with it.
|
|
10
|
+
* 3. If none with a navigation are found, fallback to the first one.
|
|
11
|
+
* 4. Otherwise, return null.
|
|
12
|
+
*
|
|
13
|
+
* TODO(cjamcl): we should just give the agent the entire insight set, and give
|
|
14
|
+
* summary detail about all of them + the ability to query each.
|
|
15
|
+
*/
|
|
6
16
|
function getFirstInsightSet(insights) {
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
17
|
+
const insightSets = Array.from(insights.values());
|
|
18
|
+
if (insightSets.length === 0) {
|
|
19
|
+
return null;
|
|
20
|
+
}
|
|
21
|
+
if (insightSets.length === 1) {
|
|
22
|
+
return insightSets[0];
|
|
23
|
+
}
|
|
24
|
+
return insightSets.filter(set => set.navigation).at(0) ?? insightSets.at(0) ?? null;
|
|
11
25
|
}
|
|
12
26
|
export class AgentFocus {
|
|
13
27
|
static fromParsedTrace(parsedTrace) {
|
package/build/node_modules/chrome-devtools-frontend/front_end/models/crux-manager/CrUXManager.js
CHANGED
|
@@ -19,7 +19,7 @@ const i18nString = i18n.i18n.getLocalizedString.bind(undefined, str_);
|
|
|
19
19
|
const CRUX_API_KEY = 'AIzaSyCCSOx25vrb5z0tbedCB3_JRzzbVW6Uwgw';
|
|
20
20
|
const DEFAULT_ENDPOINT = `https://chromeuxreport.googleapis.com/v1/records:queryRecord?key=${CRUX_API_KEY}`;
|
|
21
21
|
let cruxManagerInstance;
|
|
22
|
-
|
|
22
|
+
/** TODO: Potentially support `TABLET`. Tablet field data will always be `null` until then. **/
|
|
23
23
|
export const DEVICE_SCOPE_LIST = ['ALL', 'DESKTOP', 'PHONE'];
|
|
24
24
|
const pageScopeList = ['origin', 'url'];
|
|
25
25
|
const metrics = [
|
|
@@ -36,10 +36,12 @@ export function calculateRequestTimeRanges(request, navigationStart) {
|
|
|
36
36
|
addRange(name, startTime + (start / 1000), startTime + (end / 1000));
|
|
37
37
|
}
|
|
38
38
|
}
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
39
|
+
/**
|
|
40
|
+
* In some situations, argument `start` may come before `startTime` (`timing.requestStart`). This is especially true
|
|
41
|
+
* in cases such as SW static routing API where fields like `workerRouterEvaluationStart` or `workerCacheLookupStart`
|
|
42
|
+
* is set before setting `timing.requestStart`. If the `start` and `end` is known to be a valid value (i.e. not default
|
|
43
|
+
* invalid value -1 or undefined), we allow adding the range.
|
|
44
|
+
**/
|
|
43
45
|
function addMaybeNegativeOffsetRange(name, start, end) {
|
|
44
46
|
addRange(name, startTime + (start / 1000), startTime + (end / 1000));
|
|
45
47
|
}
|
|
@@ -182,7 +182,7 @@ const resolveScope = async (script, scopeChain) => {
|
|
|
182
182
|
return;
|
|
183
183
|
}
|
|
184
184
|
}
|
|
185
|
-
|
|
185
|
+
/** If there is no entry with the name field, try to infer the name from the source positions. **/
|
|
186
186
|
async function resolvePosition() {
|
|
187
187
|
if (!sourceMap) {
|
|
188
188
|
return;
|
|
@@ -556,10 +556,12 @@ export class RemoteObject extends SDK.RemoteObject.RemoteObject {
|
|
|
556
556
|
return this.object.isNode();
|
|
557
557
|
}
|
|
558
558
|
}
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
559
|
+
/**
|
|
560
|
+
* Resolve the frame's function name using the name associated with the opening
|
|
561
|
+
* paren that starts the scope. If there is no name associated with the scope
|
|
562
|
+
* start or if the function scope does not start with a left paren (e.g., arrow
|
|
563
|
+
* function with one parameter), the resolution returns null.
|
|
564
|
+
**/
|
|
563
565
|
async function getFunctionNameFromScopeStart(script, lineNumber, columnNumber) {
|
|
564
566
|
// To reduce the overhead of resolving function names,
|
|
565
567
|
// we check for source maps first and immediately leave
|
package/build/node_modules/chrome-devtools-frontend/front_end/models/trace/LanternComputationData.js
CHANGED
|
@@ -167,6 +167,7 @@ function createLanternRequest(parsedTrace, workerThreads, request) {
|
|
|
167
167
|
priority: request.args.data.priority,
|
|
168
168
|
frameId: request.args.data.frame,
|
|
169
169
|
fromWorker,
|
|
170
|
+
serverResponseTime: request.args.data.lrServerResponseTime ?? undefined,
|
|
170
171
|
// Set later.
|
|
171
172
|
redirects: undefined,
|
|
172
173
|
redirectSource: undefined,
|
package/build/node_modules/chrome-devtools-frontend/front_end/models/trace/extras/TraceTree.js
CHANGED
|
@@ -573,7 +573,7 @@ export function eventStackFrame(event) {
|
|
|
573
573
|
}
|
|
574
574
|
return { ...topFrame, scriptId: String(topFrame.scriptId) };
|
|
575
575
|
}
|
|
576
|
-
|
|
576
|
+
/** TODO(paulirish): rename to generateNodeId **/
|
|
577
577
|
export function generateEventID(event) {
|
|
578
578
|
if (Types.Events.isProfileCall(event)) {
|
|
579
579
|
const name = SamplesIntegrator.isNativeRuntimeFrame(event.callFrame) ?
|
package/build/node_modules/chrome-devtools-frontend/front_end/models/trace/handlers/FramesHandler.js
CHANGED
|
@@ -410,7 +410,7 @@ export class PendingFrame {
|
|
|
410
410
|
this.triggerTime = triggerTime;
|
|
411
411
|
}
|
|
412
412
|
}
|
|
413
|
-
|
|
413
|
+
/** The parameters of an impl-side BeginFrame. **/
|
|
414
414
|
class BeginFrameInfo {
|
|
415
415
|
seqId;
|
|
416
416
|
startTime;
|
|
@@ -423,10 +423,12 @@ class BeginFrameInfo {
|
|
|
423
423
|
this.isPartial = isPartial;
|
|
424
424
|
}
|
|
425
425
|
}
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
426
|
+
/**
|
|
427
|
+
* A queue of BeginFrames pending visualization.
|
|
428
|
+
* BeginFrames are added into this queue as they occur; later when their
|
|
429
|
+
* corresponding DrawFrames occur (or lack thereof), the BeginFrames are removed
|
|
430
|
+
* from the queue and their timestamps are used for visualization.
|
|
431
|
+
**/
|
|
430
432
|
export class TimelineFrameBeginFrameQueue {
|
|
431
433
|
queueFrames = [];
|
|
432
434
|
// Maps frameSeqId to BeginFrameInfo.
|
|
@@ -6,11 +6,15 @@ import * as Helpers from '../helpers/helpers.js';
|
|
|
6
6
|
import * as Types from '../types/types.js';
|
|
7
7
|
import { data as metaHandlerData } from './MetaHandler.js';
|
|
8
8
|
import { data as screenshotsHandlerData } from './ScreenshotsHandler.js';
|
|
9
|
-
|
|
10
|
-
|
|
9
|
+
/**
|
|
10
|
+
* This represents the maximum #time we will allow a cluster to go before we
|
|
11
|
+
* reset it.
|
|
12
|
+
**/
|
|
11
13
|
export const MAX_CLUSTER_DURATION = Helpers.Timing.milliToMicro(Types.Timing.Milli(5000));
|
|
12
|
-
|
|
13
|
-
|
|
14
|
+
/**
|
|
15
|
+
* This represents the maximum #time we will allow between layout shift events
|
|
16
|
+
* before considering it to be the start of a new cluster.
|
|
17
|
+
**/
|
|
14
18
|
export const MAX_SHIFT_TIME_DELTA = Helpers.Timing.milliToMicro(Types.Timing.Milli(1000));
|
|
15
19
|
// Layout shifts are reported globally to the developer, irrespective of which
|
|
16
20
|
// frame they originated in. However, each process does have its own individual
|
|
@@ -221,6 +221,7 @@ export async function finalize() {
|
|
|
221
221
|
*
|
|
222
222
|
* See `_updateTimingsForLightrider` in Lighthouse for more detail.
|
|
223
223
|
*/
|
|
224
|
+
let lrServerResponseTime;
|
|
224
225
|
if (isLightrider && request.receiveResponse?.args.data.headers) {
|
|
225
226
|
timing = {
|
|
226
227
|
requestTime: Helpers.Timing.microToSeconds(request.sendRequests.at(0)?.ts ?? 0),
|
|
@@ -254,6 +255,13 @@ export async function finalize() {
|
|
|
254
255
|
timing.connectEnd = TCPMs;
|
|
255
256
|
timing.sslEnd = TCPMs;
|
|
256
257
|
}
|
|
258
|
+
// Lightrider does not have any equivalent for `sendEnd` timing values. The
|
|
259
|
+
// closest we can get to the server response time is from a header that
|
|
260
|
+
// Lightrider sets.
|
|
261
|
+
const ResponseMsHeader = request.receiveResponse.args.data.headers.find(h => h.name === 'X-ResponseMs');
|
|
262
|
+
if (ResponseMsHeader) {
|
|
263
|
+
lrServerResponseTime = Math.max(0, parseInt(ResponseMsHeader.value, 10));
|
|
264
|
+
}
|
|
257
265
|
}
|
|
258
266
|
// TODO: consider allowing chrome / about.
|
|
259
267
|
const allowedProtocols = [
|
|
@@ -346,6 +354,13 @@ export async function finalize() {
|
|
|
346
354
|
const waiting = timing ?
|
|
347
355
|
Types.Timing.Micro((timing.receiveHeadersEnd - timing.sendEnd) * MILLISECONDS_TO_MICROSECONDS) :
|
|
348
356
|
Types.Timing.Micro(0);
|
|
357
|
+
// Server Response Time
|
|
358
|
+
// =======================
|
|
359
|
+
// Time from when the send finished going to when the first byte of headers were received.
|
|
360
|
+
const serverResponseTime = timing ?
|
|
361
|
+
Types.Timing.Micro(((timing.receiveHeadersStart ?? timing.receiveHeadersEnd) - timing.sendEnd) *
|
|
362
|
+
MILLISECONDS_TO_MICROSECONDS) :
|
|
363
|
+
Types.Timing.Micro(0);
|
|
349
364
|
// Download
|
|
350
365
|
// =======================
|
|
351
366
|
// Time from receipt of headers to the finish time.
|
|
@@ -404,6 +419,7 @@ export async function finalize() {
|
|
|
404
419
|
stalled,
|
|
405
420
|
totalTime,
|
|
406
421
|
waiting,
|
|
422
|
+
serverResponseTime,
|
|
407
423
|
},
|
|
408
424
|
// All fields below are from TraceEventsForNetworkRequest.
|
|
409
425
|
decodedBodyLength,
|
|
@@ -428,6 +444,7 @@ export async function finalize() {
|
|
|
428
444
|
initiator: finalSendRequest.args.data.initiator,
|
|
429
445
|
stackTrace: finalSendRequest.args.data.stackTrace,
|
|
430
446
|
timing,
|
|
447
|
+
lrServerResponseTime,
|
|
431
448
|
url,
|
|
432
449
|
failed: request.resourceFinish?.args.data.didFail ?? false,
|
|
433
450
|
finished: Boolean(request.resourceFinish),
|
package/build/node_modules/chrome-devtools-frontend/front_end/models/trace/handlers/helpers.js
CHANGED
|
@@ -142,7 +142,7 @@ export function addEventToEntityMapping(event, entityMappings) {
|
|
|
142
142
|
}
|
|
143
143
|
entityMappings.entityByEvent.set(event, entity);
|
|
144
144
|
}
|
|
145
|
-
|
|
145
|
+
/** A slight upgrade of addEventToEntityMapping to handle the sub-events of a network request. **/
|
|
146
146
|
export function addNetworkRequestToEntityMapping(networkRequest, entityMappings, requestTraceEvents) {
|
|
147
147
|
const entity = getEntityForEvent(networkRequest, entityMappings);
|
|
148
148
|
if (!entity) {
|
package/build/node_modules/chrome-devtools-frontend/front_end/models/trace/helpers/Timing.js
CHANGED
|
@@ -25,8 +25,10 @@ export function timeStampForEventAdjustedByClosestNavigation(event, traceBounds,
|
|
|
25
25
|
}
|
|
26
26
|
return Types.Timing.Micro(eventTimeStamp);
|
|
27
27
|
}
|
|
28
|
-
|
|
29
|
-
|
|
28
|
+
/**
|
|
29
|
+
* Expands the trace window by a provided percentage or, if it the expanded window is smaller than 1 millisecond, expands it to 1 millisecond.
|
|
30
|
+
* If the expanded window is outside of the max trace window, cut the overflowing bound to the max trace window bound.
|
|
31
|
+
**/
|
|
30
32
|
export function expandWindowByPercentOrToOneMillisecond(annotationWindow, maxTraceWindow, percentage) {
|
|
31
33
|
// Expand min and max of the window by half of the provided percentage. That way, in total, the window will be expanded by the provided percentage.
|
|
32
34
|
let newMin = annotationWindow.min - annotationWindow.range * (percentage / 100) / 2;
|
|
@@ -70,8 +70,10 @@ export function extractOriginFromTrace(firstNavigationURL) {
|
|
|
70
70
|
}
|
|
71
71
|
return null;
|
|
72
72
|
}
|
|
73
|
-
|
|
74
|
-
|
|
73
|
+
/**
|
|
74
|
+
* Each thread contains events. Events indicate the thread and process IDs, which are
|
|
75
|
+
* used to store the event in the correct process thread entry below.
|
|
76
|
+
**/
|
|
75
77
|
export function addEventToProcessThread(event, eventsInProcessThread) {
|
|
76
78
|
const { tid, pid } = event;
|
|
77
79
|
let eventsInThread = eventsInProcessThread.get(pid);
|
|
@@ -704,8 +706,10 @@ export function extractSampleTraceId(event) {
|
|
|
704
706
|
}
|
|
705
707
|
return event.args?.sampleTraceId ?? event.args?.data?.sampleTraceId ?? null;
|
|
706
708
|
}
|
|
707
|
-
|
|
708
|
-
|
|
709
|
+
/**
|
|
710
|
+
* This exactly matches Trace.Styles.visibleTypes. See the runtime verification in maybeInitStylesMap.
|
|
711
|
+
* TODO(crbug.com/410884528)
|
|
712
|
+
**/
|
|
709
713
|
export const VISIBLE_TRACE_EVENT_TYPES = new Set([
|
|
710
714
|
"AbortPostTaskCallback" /* Types.Events.Name.ABORT_POST_TASK_CALLBACK */,
|
|
711
715
|
"Animation" /* Types.Events.Name.ANIMATION */,
|
|
@@ -67,20 +67,20 @@ const IGNORE_THRESHOLD_IN_BYTES = 1400;
|
|
|
67
67
|
export function isDocumentLatencyInsight(x) {
|
|
68
68
|
return x.insightKey === 'DocumentLatency';
|
|
69
69
|
}
|
|
70
|
-
function getServerResponseTime(request
|
|
71
|
-
//
|
|
72
|
-
//
|
|
73
|
-
//
|
|
74
|
-
//
|
|
75
|
-
const
|
|
76
|
-
if (
|
|
77
|
-
return
|
|
70
|
+
function getServerResponseTime(request) {
|
|
71
|
+
// For technical reasons, Lightrider does not have `sendEnd` timing values. The
|
|
72
|
+
// closest we can get to the server response time is from a header that Lightrider
|
|
73
|
+
// sets.
|
|
74
|
+
// @ts-expect-error
|
|
75
|
+
const isLightrider = globalThis.isLightrider;
|
|
76
|
+
if (isLightrider) {
|
|
77
|
+
return request.args.data.lrServerResponseTime ?? null;
|
|
78
78
|
}
|
|
79
79
|
const timing = request.args.data.timing;
|
|
80
80
|
if (!timing) {
|
|
81
81
|
return null;
|
|
82
82
|
}
|
|
83
|
-
const ms = Helpers.Timing.microToMilli(request.args.data.syntheticData.
|
|
83
|
+
const ms = Helpers.Timing.microToMilli(request.args.data.syntheticData.serverResponseTime);
|
|
84
84
|
return Math.round(ms);
|
|
85
85
|
}
|
|
86
86
|
function getCompressionSavings(request) {
|
|
@@ -162,7 +162,7 @@ export function generateInsight(data, context) {
|
|
|
162
162
|
if (!documentRequest) {
|
|
163
163
|
return finalize({ warnings: [InsightWarning.NO_DOCUMENT_REQUEST] });
|
|
164
164
|
}
|
|
165
|
-
const serverResponseTime = getServerResponseTime(documentRequest
|
|
165
|
+
const serverResponseTime = getServerResponseTime(documentRequest);
|
|
166
166
|
if (serverResponseTime === null) {
|
|
167
167
|
throw new Error('missing document request timing');
|
|
168
168
|
}
|
package/build/node_modules/chrome-devtools-frontend/front_end/models/trace/insights/INPBreakdown.js
CHANGED
|
@@ -2,6 +2,7 @@
|
|
|
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
|
import * as i18n from '../../../core/i18n/i18n.js';
|
|
5
|
+
import * as Handlers from '../handlers/handlers.js';
|
|
5
6
|
import * as Helpers from '../helpers/helpers.js';
|
|
6
7
|
import { InsightCategory, } from './types.js';
|
|
7
8
|
export const UIStrings = {
|
|
@@ -45,13 +46,23 @@ export function isINPBreakdownInsight(insight) {
|
|
|
45
46
|
return insight.insightKey === "INPBreakdown" /* InsightKeys.INP_BREAKDOWN */;
|
|
46
47
|
}
|
|
47
48
|
function finalize(partialModel) {
|
|
49
|
+
let state = 'pass';
|
|
50
|
+
if (partialModel.longestInteractionEvent) {
|
|
51
|
+
const classification = Handlers.ModelHandlers.UserInteractions.scoreClassificationForInteractionToNextPaint(partialModel.longestInteractionEvent.dur);
|
|
52
|
+
if (classification === "good" /* Handlers.ModelHandlers.PageLoadMetrics.ScoreClassification.GOOD */) {
|
|
53
|
+
state = 'informative';
|
|
54
|
+
}
|
|
55
|
+
else {
|
|
56
|
+
state = 'fail';
|
|
57
|
+
}
|
|
58
|
+
}
|
|
48
59
|
return {
|
|
49
60
|
insightKey: "INPBreakdown" /* InsightKeys.INP_BREAKDOWN */,
|
|
50
61
|
strings: UIStrings,
|
|
51
62
|
title: i18nString(UIStrings.title),
|
|
52
63
|
description: i18nString(UIStrings.description),
|
|
53
64
|
category: InsightCategory.INP,
|
|
54
|
-
state
|
|
65
|
+
state,
|
|
55
66
|
...partialModel,
|
|
56
67
|
};
|
|
57
68
|
}
|
package/build/node_modules/chrome-devtools-frontend/front_end/models/trace/insights/LCPBreakdown.js
CHANGED
|
@@ -124,13 +124,23 @@ function finalize(partialModel) {
|
|
|
124
124
|
if (partialModel.lcpRequest) {
|
|
125
125
|
relatedEvents.push(partialModel.lcpRequest);
|
|
126
126
|
}
|
|
127
|
+
let state = 'pass';
|
|
128
|
+
if (partialModel.lcpMs !== undefined) {
|
|
129
|
+
const classification = Handlers.ModelHandlers.PageLoadMetrics.scoreClassificationForLargestContentfulPaint(Helpers.Timing.milliToMicro(partialModel.lcpMs));
|
|
130
|
+
if (classification === "good" /* Handlers.ModelHandlers.PageLoadMetrics.ScoreClassification.GOOD */) {
|
|
131
|
+
state = 'informative';
|
|
132
|
+
}
|
|
133
|
+
else {
|
|
134
|
+
state = 'fail';
|
|
135
|
+
}
|
|
136
|
+
}
|
|
127
137
|
return {
|
|
128
138
|
insightKey: "LCPBreakdown" /* InsightKeys.LCP_BREAKDOWN */,
|
|
129
139
|
strings: UIStrings,
|
|
130
140
|
title: i18nString(UIStrings.title),
|
|
131
141
|
description: i18nString(UIStrings.description),
|
|
132
142
|
category: InsightCategory.LCP,
|
|
133
|
-
state
|
|
143
|
+
state,
|
|
134
144
|
...partialModel,
|
|
135
145
|
relatedEvents,
|
|
136
146
|
};
|
|
@@ -337,7 +337,7 @@ export function handleLinkResponseHeader(linkHeaderValue) {
|
|
|
337
337
|
}
|
|
338
338
|
return preconnectedOrigins;
|
|
339
339
|
}
|
|
340
|
-
|
|
340
|
+
/** Export the function for test purpose. **/
|
|
341
341
|
export function generatePreconnectedOrigins(data, context, contextRequests, preconnectCandidates) {
|
|
342
342
|
const preconnectedOrigins = [];
|
|
343
343
|
for (const event of data.NetworkRequests.linkPreconnectEvents) {
|
|
@@ -436,7 +436,7 @@ function candidateRequestsByOrigin(data, mainResource, contextRequests, lcpGraph
|
|
|
436
436
|
});
|
|
437
437
|
return origins;
|
|
438
438
|
}
|
|
439
|
-
|
|
439
|
+
/** Export the function for test purpose. **/
|
|
440
440
|
export function generatePreconnectCandidates(data, context, contextRequests) {
|
|
441
441
|
if (!context.lantern) {
|
|
442
442
|
return [];
|
package/build/node_modules/chrome-devtools-frontend/front_end/models/trace/types/TraceEvents.js
CHANGED
|
@@ -349,7 +349,7 @@ export function isResourceWillSendRequest(event) {
|
|
|
349
349
|
export function isResourceReceivedData(event) {
|
|
350
350
|
return event.name === 'ResourceReceivedData';
|
|
351
351
|
}
|
|
352
|
-
|
|
352
|
+
/** Any event where we receive data (and get an encodedDataLength) **/
|
|
353
353
|
export function isReceivedDataEvent(event) {
|
|
354
354
|
return event.name === 'ResourceReceivedData' || event.name === 'ResourceFinish' ||
|
|
355
355
|
event.name === 'ResourceReceiveResponse';
|
|
@@ -547,8 +547,10 @@ export function isFlowPhaseEvent(event) {
|
|
|
547
547
|
export function isParseAuthorStyleSheetEvent(event) {
|
|
548
548
|
return event.name === "ParseAuthorStyleSheet" /* Name.PARSE_AUTHOR_STYLE_SHEET */ && event.ph === "X" /* Phase.COMPLETE */;
|
|
549
549
|
}
|
|
550
|
-
|
|
551
|
-
|
|
550
|
+
/**
|
|
551
|
+
* NOT AN EXHAUSTIVE LIST: just some categories we use and refer
|
|
552
|
+
* to in multiple places.
|
|
553
|
+
**/
|
|
552
554
|
export const Categories = {
|
|
553
555
|
Console: 'blink.console',
|
|
554
556
|
UserTiming: 'blink.user_timing',
|
|
@@ -13,7 +13,7 @@ export class SourceMappingsUpdated extends Event {
|
|
|
13
13
|
super(SourceMappingsUpdated.eventName, { composed: true, bubbles: true });
|
|
14
14
|
}
|
|
15
15
|
}
|
|
16
|
-
|
|
16
|
+
/** The code location key is created as a concatenation of its fields. **/
|
|
17
17
|
export const resolvedCodeLocationDataNames = new Map();
|
|
18
18
|
export class SourceMapsResolver extends EventTarget {
|
|
19
19
|
executionContextNamesByOrigin = new Map();
|
package/build/src/McpContext.js
CHANGED
|
@@ -27,6 +27,17 @@ function getNetworkMultiplierFromString(condition) {
|
|
|
27
27
|
}
|
|
28
28
|
return 1;
|
|
29
29
|
}
|
|
30
|
+
function getExtensionFromMimeType(mimeType) {
|
|
31
|
+
switch (mimeType) {
|
|
32
|
+
case 'image/png':
|
|
33
|
+
return 'png';
|
|
34
|
+
case 'image/jpeg':
|
|
35
|
+
return 'jpeg';
|
|
36
|
+
case 'image/webp':
|
|
37
|
+
return 'webp';
|
|
38
|
+
}
|
|
39
|
+
throw new Error(`No mapping for Mime type ${mimeType}.`);
|
|
40
|
+
}
|
|
30
41
|
export class McpContext {
|
|
31
42
|
browser;
|
|
32
43
|
logger;
|
|
@@ -258,18 +269,24 @@ export class McpContext {
|
|
|
258
269
|
async saveTemporaryFile(data, mimeType) {
|
|
259
270
|
try {
|
|
260
271
|
const dir = await fs.mkdtemp(path.join(os.tmpdir(), 'chrome-devtools-mcp-'));
|
|
261
|
-
const
|
|
262
|
-
? 'png'
|
|
263
|
-
: mimeType === 'image/jpeg'
|
|
264
|
-
? 'jpg'
|
|
265
|
-
: 'webp';
|
|
266
|
-
const filename = path.join(dir, `screenshot.${ext}`);
|
|
272
|
+
const filename = path.join(dir, `screenshot.${getExtensionFromMimeType(mimeType)}`);
|
|
267
273
|
await fs.writeFile(filename, data);
|
|
268
274
|
return { filename };
|
|
269
275
|
}
|
|
270
276
|
catch (err) {
|
|
271
277
|
this.logger(err);
|
|
272
|
-
throw new Error('Could not save a screenshot to a file');
|
|
278
|
+
throw new Error('Could not save a screenshot to a file', { cause: err });
|
|
279
|
+
}
|
|
280
|
+
}
|
|
281
|
+
async saveFile(data, filename) {
|
|
282
|
+
try {
|
|
283
|
+
const filePath = path.resolve(filename);
|
|
284
|
+
await fs.writeFile(filePath, data);
|
|
285
|
+
return { filename };
|
|
286
|
+
}
|
|
287
|
+
catch (err) {
|
|
288
|
+
this.logger(err);
|
|
289
|
+
throw new Error('Could not save a screenshot to a file', { cause: err });
|
|
273
290
|
}
|
|
274
291
|
}
|
|
275
292
|
storeTraceRecording(result) {
|
package/build/src/McpResponse.js
CHANGED
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
import { formatConsoleEvent } from './formatters/consoleFormatter.js';
|
|
2
|
-
import { getFormattedHeaderValue, getShortDescriptionForRequest, getStatusFromRequest, } from './formatters/networkFormatter.js';
|
|
2
|
+
import { getFormattedHeaderValue, getFormattedResponseBody, getFormattedRequestBody, getShortDescriptionForRequest, getStatusFromRequest, } from './formatters/networkFormatter.js';
|
|
3
3
|
import { formatA11ySnapshot } from './formatters/snapshotFormatter.js';
|
|
4
4
|
import { handleDialog } from './tools/pages.js';
|
|
5
5
|
import { paginate } from './utils/pagination.js';
|
|
6
6
|
export class McpResponse {
|
|
7
7
|
#includePages = false;
|
|
8
8
|
#includeSnapshot = false;
|
|
9
|
-
#
|
|
9
|
+
#attachedNetworkRequestData;
|
|
10
10
|
#includeConsoleData = false;
|
|
11
11
|
#textResponseLines = [];
|
|
12
12
|
#formattedConsoleData;
|
|
@@ -38,7 +38,9 @@ export class McpResponse {
|
|
|
38
38
|
this.#includeConsoleData = value;
|
|
39
39
|
}
|
|
40
40
|
attachNetworkRequest(url) {
|
|
41
|
-
this.#
|
|
41
|
+
this.#attachedNetworkRequestData = {
|
|
42
|
+
networkRequestUrl: url,
|
|
43
|
+
};
|
|
42
44
|
}
|
|
43
45
|
get includePages() {
|
|
44
46
|
return this.#includePages;
|
|
@@ -50,7 +52,7 @@ export class McpResponse {
|
|
|
50
52
|
return this.#includeConsoleData;
|
|
51
53
|
}
|
|
52
54
|
get attachedNetworkRequestUrl() {
|
|
53
|
-
return this.#
|
|
55
|
+
return this.#attachedNetworkRequestData?.networkRequestUrl;
|
|
54
56
|
}
|
|
55
57
|
get networkRequestsPageIdx() {
|
|
56
58
|
return this.#networkRequestsOptions?.pagination?.pageIdx;
|
|
@@ -78,6 +80,16 @@ export class McpResponse {
|
|
|
78
80
|
await context.createTextSnapshot();
|
|
79
81
|
}
|
|
80
82
|
let formattedConsoleMessages;
|
|
83
|
+
if (this.#attachedNetworkRequestData?.networkRequestUrl) {
|
|
84
|
+
const request = context.getNetworkRequestByUrl(this.#attachedNetworkRequestData.networkRequestUrl);
|
|
85
|
+
this.#attachedNetworkRequestData.requestBody =
|
|
86
|
+
await getFormattedRequestBody(request);
|
|
87
|
+
const response = request.response();
|
|
88
|
+
if (response) {
|
|
89
|
+
this.#attachedNetworkRequestData.responseBody =
|
|
90
|
+
await getFormattedResponseBody(response);
|
|
91
|
+
}
|
|
92
|
+
}
|
|
81
93
|
if (this.#includeConsoleData) {
|
|
82
94
|
const consoleMessages = context.getConsoleData();
|
|
83
95
|
if (consoleMessages) {
|
|
@@ -139,21 +151,9 @@ Call ${handleDialog.name} to handle it before continuing.`);
|
|
|
139
151
|
}
|
|
140
152
|
response.push('## Network requests');
|
|
141
153
|
if (requests.length) {
|
|
142
|
-
const
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
}
|
|
146
|
-
const { startIndex, endIndex, currentPage, totalPages } = paginationResult;
|
|
147
|
-
response.push(`Showing ${startIndex + 1}-${endIndex} of ${requests.length} (Page ${currentPage + 1} of ${totalPages}).`);
|
|
148
|
-
if (this.#networkRequestsOptions.pagination) {
|
|
149
|
-
if (paginationResult.hasNextPage) {
|
|
150
|
-
response.push(`Next page: ${currentPage + 1}`);
|
|
151
|
-
}
|
|
152
|
-
if (paginationResult.hasPreviousPage) {
|
|
153
|
-
response.push(`Previous page: ${currentPage - 1}`);
|
|
154
|
-
}
|
|
155
|
-
}
|
|
156
|
-
for (const request of paginationResult.items) {
|
|
154
|
+
const data = this.#dataWithPagination(requests, this.#networkRequestsOptions.pagination);
|
|
155
|
+
response.push(...data.info);
|
|
156
|
+
for (const request of data.items) {
|
|
157
157
|
response.push(getShortDescriptionForRequest(request));
|
|
158
158
|
}
|
|
159
159
|
}
|
|
@@ -182,9 +182,30 @@ Call ${handleDialog.name} to handle it before continuing.`);
|
|
|
182
182
|
});
|
|
183
183
|
return [text, ...images];
|
|
184
184
|
}
|
|
185
|
+
#dataWithPagination(data, pagination) {
|
|
186
|
+
const response = [];
|
|
187
|
+
const paginationResult = paginate(data, pagination);
|
|
188
|
+
if (paginationResult.invalidPage) {
|
|
189
|
+
response.push('Invalid page number provided. Showing first page.');
|
|
190
|
+
}
|
|
191
|
+
const { startIndex, endIndex, currentPage, totalPages } = paginationResult;
|
|
192
|
+
response.push(`Showing ${startIndex + 1}-${endIndex} of ${data.length} (Page ${currentPage + 1} of ${totalPages}).`);
|
|
193
|
+
if (pagination) {
|
|
194
|
+
if (paginationResult.hasNextPage) {
|
|
195
|
+
response.push(`Next page: ${currentPage + 1}`);
|
|
196
|
+
}
|
|
197
|
+
if (paginationResult.hasPreviousPage) {
|
|
198
|
+
response.push(`Previous page: ${currentPage - 1}`);
|
|
199
|
+
}
|
|
200
|
+
}
|
|
201
|
+
return {
|
|
202
|
+
info: response,
|
|
203
|
+
items: paginationResult.items,
|
|
204
|
+
};
|
|
205
|
+
}
|
|
185
206
|
#getIncludeNetworkRequestsData(context) {
|
|
186
207
|
const response = [];
|
|
187
|
-
const url = this.#
|
|
208
|
+
const url = this.#attachedNetworkRequestData?.networkRequestUrl;
|
|
188
209
|
if (!url) {
|
|
189
210
|
return response;
|
|
190
211
|
}
|
|
@@ -195,6 +216,10 @@ Call ${handleDialog.name} to handle it before continuing.`);
|
|
|
195
216
|
for (const line of getFormattedHeaderValue(httpRequest.headers())) {
|
|
196
217
|
response.push(line);
|
|
197
218
|
}
|
|
219
|
+
if (this.#attachedNetworkRequestData?.requestBody) {
|
|
220
|
+
response.push(`### Request Body`);
|
|
221
|
+
response.push(this.#attachedNetworkRequestData.requestBody);
|
|
222
|
+
}
|
|
198
223
|
const httpResponse = httpRequest.response();
|
|
199
224
|
if (httpResponse) {
|
|
200
225
|
response.push(`### Response Headers`);
|
|
@@ -202,6 +227,10 @@ Call ${handleDialog.name} to handle it before continuing.`);
|
|
|
202
227
|
response.push(line);
|
|
203
228
|
}
|
|
204
229
|
}
|
|
230
|
+
if (this.#attachedNetworkRequestData?.responseBody) {
|
|
231
|
+
response.push(`### Response Body`);
|
|
232
|
+
response.push(this.#attachedNetworkRequestData.responseBody);
|
|
233
|
+
}
|
|
205
234
|
const httpFailure = httpRequest.failure();
|
|
206
235
|
if (httpFailure) {
|
|
207
236
|
response.push(`### Request failed with`);
|
package/build/src/Mutex.js
CHANGED
|
@@ -6,20 +6,17 @@
|
|
|
6
6
|
export class Mutex {
|
|
7
7
|
static Guard = class Guard {
|
|
8
8
|
#mutex;
|
|
9
|
-
|
|
10
|
-
constructor(mutex, onRelease) {
|
|
9
|
+
constructor(mutex) {
|
|
11
10
|
this.#mutex = mutex;
|
|
12
|
-
this.#onRelease = onRelease;
|
|
13
11
|
}
|
|
14
12
|
dispose() {
|
|
15
|
-
this.#onRelease?.();
|
|
16
13
|
return this.#mutex.release();
|
|
17
14
|
}
|
|
18
15
|
};
|
|
19
16
|
#locked = false;
|
|
20
17
|
#acquirers = [];
|
|
21
18
|
// This is FIFO.
|
|
22
|
-
async acquire(
|
|
19
|
+
async acquire() {
|
|
23
20
|
if (!this.#locked) {
|
|
24
21
|
this.#locked = true;
|
|
25
22
|
return new Mutex.Guard(this);
|
|
@@ -27,7 +24,7 @@ export class Mutex {
|
|
|
27
24
|
const { resolve, promise } = Promise.withResolvers();
|
|
28
25
|
this.#acquirers.push(resolve);
|
|
29
26
|
await promise;
|
|
30
|
-
return new Mutex.Guard(this
|
|
27
|
+
return new Mutex.Guard(this);
|
|
31
28
|
}
|
|
32
29
|
release() {
|
|
33
30
|
const resolve = this.#acquirers.shift();
|