lighthouse 10.1.1-dev.20230423 → 10.1.1-dev.20230425
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/core/audits/predictive-perf.js +9 -2
- package/core/audits/prioritize-lcp-image.d.ts +0 -11
- package/core/audits/prioritize-lcp-image.js +3 -55
- package/core/computed/document-urls.d.ts +32 -0
- package/core/computed/document-urls.js +53 -0
- package/core/computed/entity-classification.js +0 -1
- package/core/computed/lcp-image-record.d.ts +26 -0
- package/core/computed/lcp-image-record.js +74 -0
- package/core/computed/metrics/first-contentful-paint.d.ts +6 -0
- package/core/computed/metrics/first-meaningful-paint.d.ts +6 -0
- package/core/computed/metrics/interactive.d.ts +6 -0
- package/core/computed/metrics/largest-contentful-paint.d.ts +6 -0
- package/core/computed/metrics/lcp-breakdown.d.ts +23 -0
- package/core/computed/metrics/lcp-breakdown.js +58 -0
- package/core/computed/metrics/max-potential-fid.d.ts +6 -0
- package/core/computed/metrics/metric.d.ts +2 -2
- package/core/computed/metrics/metric.js +1 -1
- package/core/computed/metrics/navigation-metric.d.ts +2 -2
- package/core/computed/metrics/navigation-metric.js +1 -1
- package/core/computed/metrics/speed-index.d.ts +6 -0
- package/core/computed/metrics/time-to-first-byte.d.ts +16 -0
- package/core/computed/metrics/time-to-first-byte.js +60 -0
- package/core/computed/metrics/timing-summary.js +10 -0
- package/core/computed/metrics/total-blocking-time.d.ts +6 -0
- package/core/computed/page-dependency-graph.d.ts +0 -9
- package/core/computed/page-dependency-graph.js +2 -33
- package/core/gather/navigation-runner.js +1 -1
- package/dist/report/bundle.esm.js +2 -0
- package/dist/report/flow.js +1 -1
- package/dist/report/standalone.js +1 -1
- package/package.json +4 -3
- package/shared/util.js +2 -0
- package/types/artifacts.d.ts +4 -0
|
@@ -11,6 +11,8 @@ import {LanternFirstMeaningfulPaint} from '../computed/metrics/lantern-first-mea
|
|
|
11
11
|
import {LanternInteractive} from '../computed/metrics/lantern-interactive.js';
|
|
12
12
|
import {LanternSpeedIndex} from '../computed/metrics/lantern-speed-index.js';
|
|
13
13
|
import {LanternLargestContentfulPaint} from '../computed/metrics/lantern-largest-contentful-paint.js';
|
|
14
|
+
import {TimingSummary} from '../computed/metrics/timing-summary.js';
|
|
15
|
+
import {defaultSettings} from '../config/constants.js';
|
|
14
16
|
|
|
15
17
|
// Parameters (in ms) for log-normal CDF scoring. To see the curve:
|
|
16
18
|
// https://www.desmos.com/calculator/bksgkihhj8
|
|
@@ -47,8 +49,7 @@ class PredictivePerf extends Audit {
|
|
|
47
49
|
const devtoolsLog = artifacts.devtoolsLogs[Audit.DEFAULT_PASS];
|
|
48
50
|
const URL = artifacts.URL;
|
|
49
51
|
/** @type {LH.Config.Settings} */
|
|
50
|
-
|
|
51
|
-
const settings = {}; // Use default settings.
|
|
52
|
+
const settings = JSON.parse(JSON.stringify(defaultSettings)); // Use default settings.
|
|
52
53
|
const computationData = {trace, devtoolsLog, gatherContext, settings, URL};
|
|
53
54
|
const fcp = await LanternFirstContentfulPaint.request(computationData, context);
|
|
54
55
|
const fmp = await LanternFirstMeaningfulPaint.request(computationData, context);
|
|
@@ -56,6 +57,8 @@ class PredictivePerf extends Audit {
|
|
|
56
57
|
const si = await LanternSpeedIndex.request(computationData, context);
|
|
57
58
|
const lcp = await LanternLargestContentfulPaint.request(computationData, context);
|
|
58
59
|
|
|
60
|
+
const timingSummary = await TimingSummary.request(computationData, context);
|
|
61
|
+
|
|
59
62
|
const values = {
|
|
60
63
|
roughEstimateOfFCP: fcp.timing,
|
|
61
64
|
optimisticFCP: fcp.optimisticEstimate.timeInMs,
|
|
@@ -76,6 +79,10 @@ class PredictivePerf extends Audit {
|
|
|
76
79
|
roughEstimateOfLCP: lcp.timing,
|
|
77
80
|
optimisticLCP: lcp.optimisticEstimate.timeInMs,
|
|
78
81
|
pessimisticLCP: lcp.pessimisticEstimate.timeInMs,
|
|
82
|
+
|
|
83
|
+
roughEstimateOfTTFB: timingSummary.metrics.timeToFirstByte,
|
|
84
|
+
roughEstimateOfLCPLoadStart: timingSummary.metrics.lcpLoadStart,
|
|
85
|
+
roughEstimateOfLCPLoadEnd: timingSummary.metrics.lcpLoadEnd,
|
|
79
86
|
};
|
|
80
87
|
|
|
81
88
|
const score = Audit.computeLogNormalScore(
|
|
@@ -42,16 +42,6 @@ declare class PrioritizeLcpImage extends Audit {
|
|
|
42
42
|
lcpNodeToPreload?: import("../lib/dependency-graph/network-node.js").NetworkNode | undefined;
|
|
43
43
|
initiatorPath?: InitiatorPath | undefined;
|
|
44
44
|
};
|
|
45
|
-
/**
|
|
46
|
-
* Match the LCP event with the paint event to get the request of the image actually painted.
|
|
47
|
-
* This could differ from the `ImageElement` associated with the nodeId if e.g. the LCP
|
|
48
|
-
* was a pseudo-element associated with a node containing a smaller background-image.
|
|
49
|
-
* @param {LH.Trace} trace
|
|
50
|
-
* @param {LH.Artifacts.ProcessedNavigation} processedNavigation
|
|
51
|
-
* @param {Array<NetworkRequest>} networkRecords
|
|
52
|
-
* @return {NetworkRequest|undefined}
|
|
53
|
-
*/
|
|
54
|
-
static getLcpRecord(trace: LH.Trace, processedNavigation: LH.Artifacts.ProcessedNavigation, networkRecords: Array<NetworkRequest>): NetworkRequest | undefined;
|
|
55
45
|
/**
|
|
56
46
|
* Computes the estimated effect of preloading the LCP image.
|
|
57
47
|
* @param {LH.Artifacts.TraceElement} lcpElement
|
|
@@ -81,5 +71,4 @@ export namespace UIStrings {
|
|
|
81
71
|
}
|
|
82
72
|
import { Audit } from "./audit.js";
|
|
83
73
|
import { NetworkRequest } from "../lib/network-request.js";
|
|
84
|
-
import { ProcessedNavigation } from "../computed/processed-navigation.js";
|
|
85
74
|
//# sourceMappingURL=prioritize-lcp-image.d.ts.map
|
|
@@ -11,8 +11,7 @@ import {MainResource} from '../computed/main-resource.js';
|
|
|
11
11
|
import {LanternLargestContentfulPaint} from '../computed/metrics/lantern-largest-contentful-paint.js';
|
|
12
12
|
import {LoadSimulator} from '../computed/load-simulator.js';
|
|
13
13
|
import {ByteEfficiencyAudit} from './byte-efficiency/byte-efficiency-audit.js';
|
|
14
|
-
import {
|
|
15
|
-
import {NetworkRecords} from '../computed/network-records.js';
|
|
14
|
+
import {LCPImageRecord} from '../computed/lcp-image-record.js';
|
|
16
15
|
|
|
17
16
|
const UIStrings = {
|
|
18
17
|
/** Title of a lighthouse audit that tells a user to preload an image in order to improve their LCP time. */
|
|
@@ -140,55 +139,6 @@ class PrioritizeLcpImage extends Audit {
|
|
|
140
139
|
};
|
|
141
140
|
}
|
|
142
141
|
|
|
143
|
-
/**
|
|
144
|
-
* Match the LCP event with the paint event to get the request of the image actually painted.
|
|
145
|
-
* This could differ from the `ImageElement` associated with the nodeId if e.g. the LCP
|
|
146
|
-
* was a pseudo-element associated with a node containing a smaller background-image.
|
|
147
|
-
* @param {LH.Trace} trace
|
|
148
|
-
* @param {LH.Artifacts.ProcessedNavigation} processedNavigation
|
|
149
|
-
* @param {Array<NetworkRequest>} networkRecords
|
|
150
|
-
* @return {NetworkRequest|undefined}
|
|
151
|
-
*/
|
|
152
|
-
static getLcpRecord(trace, processedNavigation, networkRecords) {
|
|
153
|
-
// Use main-frame-only LCP to match the metric value.
|
|
154
|
-
const lcpEvent = processedNavigation.largestContentfulPaintEvt;
|
|
155
|
-
if (!lcpEvent) return;
|
|
156
|
-
|
|
157
|
-
const lcpImagePaintEvent = trace.traceEvents.filter(e => {
|
|
158
|
-
return e.name === 'LargestImagePaint::Candidate' &&
|
|
159
|
-
e.args.frame === lcpEvent.args.frame &&
|
|
160
|
-
e.args.data?.DOMNodeId === lcpEvent.args.data?.nodeId &&
|
|
161
|
-
e.args.data?.size === lcpEvent.args.data?.size;
|
|
162
|
-
// Get last candidate, in case there was more than one.
|
|
163
|
-
}).sort((a, b) => b.ts - a.ts)[0];
|
|
164
|
-
|
|
165
|
-
const lcpUrl = lcpImagePaintEvent?.args.data?.imageUrl;
|
|
166
|
-
if (!lcpUrl) return;
|
|
167
|
-
|
|
168
|
-
const candidates = networkRecords.filter(record => {
|
|
169
|
-
return record.url === lcpUrl &&
|
|
170
|
-
record.finished &&
|
|
171
|
-
// Same frame as LCP trace event.
|
|
172
|
-
record.frameId === lcpImagePaintEvent.args.frame &&
|
|
173
|
-
record.networkRequestTime < (processedNavigation.timestamps.largestContentfulPaint || 0);
|
|
174
|
-
}).map(record => {
|
|
175
|
-
// Follow any redirects to find the real image request.
|
|
176
|
-
while (record.redirectDestination) {
|
|
177
|
-
record = record.redirectDestination;
|
|
178
|
-
}
|
|
179
|
-
return record;
|
|
180
|
-
}).filter(record => {
|
|
181
|
-
// Don't select if also loaded by some other means (xhr, etc). `resourceType`
|
|
182
|
-
// isn't set on redirect _sources_, so have to check after following redirects.
|
|
183
|
-
return record.resourceType === 'Image';
|
|
184
|
-
});
|
|
185
|
-
|
|
186
|
-
// If there are still multiple candidates, at this point it appears the page
|
|
187
|
-
// simply made multiple requests for the image. The first loaded is the best
|
|
188
|
-
// guess of the request that made the image available for use.
|
|
189
|
-
return candidates.sort((a, b) => a.networkEndTime - b.networkEndTime)[0];
|
|
190
|
-
}
|
|
191
|
-
|
|
192
142
|
/**
|
|
193
143
|
* Computes the estimated effect of preloading the LCP image.
|
|
194
144
|
* @param {LH.Artifacts.TraceElement} lcpElement
|
|
@@ -297,17 +247,15 @@ class PrioritizeLcpImage extends Audit {
|
|
|
297
247
|
return {score: null, notApplicable: true};
|
|
298
248
|
}
|
|
299
249
|
|
|
300
|
-
const networkRecords = await NetworkRecords.request(devtoolsLog, context);
|
|
301
|
-
const processedNavigation = await ProcessedNavigation.request(trace, context);
|
|
302
250
|
const mainResource = await MainResource.request({devtoolsLog, URL}, context);
|
|
303
251
|
const lanternLCP = await LanternLargestContentfulPaint.request(metricData, context);
|
|
304
252
|
const simulator = await LoadSimulator.request({devtoolsLog, settings}, context);
|
|
305
253
|
|
|
306
|
-
const
|
|
254
|
+
const lcpImageRecord = await LCPImageRecord.request({trace, devtoolsLog}, context);
|
|
307
255
|
const graph = lanternLCP.pessimisticGraph;
|
|
308
256
|
// Note: if moving to LCPAllFrames, mainResource would need to be the LCP frame's main resource.
|
|
309
257
|
const {lcpNodeToPreload, initiatorPath} = PrioritizeLcpImage.getLCPNodeToPreload(mainResource,
|
|
310
|
-
graph,
|
|
258
|
+
graph, lcpImageRecord);
|
|
311
259
|
|
|
312
260
|
const {results, wastedMs} =
|
|
313
261
|
PrioritizeLcpImage.computeWasteWithGraph(lcpElement, lcpNodeToPreload, graph, simulator);
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
export { DocumentUrlsComputed as DocumentUrls };
|
|
2
|
+
declare const DocumentUrlsComputed: typeof DocumentUrls & {
|
|
3
|
+
request: (dependencies: {
|
|
4
|
+
trace: LH.Trace;
|
|
5
|
+
devtoolsLog: import("../index.js").DevtoolsLog;
|
|
6
|
+
}, context: import("../../types/utility-types.js").default.ImmutableObject<{
|
|
7
|
+
computedCache: Map<string, import("../lib/arbitrary-equality-map.js").ArbitraryEqualityMap>;
|
|
8
|
+
}>) => Promise<{
|
|
9
|
+
requestedUrl: string;
|
|
10
|
+
mainDocumentUrl: string;
|
|
11
|
+
}>;
|
|
12
|
+
};
|
|
13
|
+
/**
|
|
14
|
+
* @fileoverview Compute the navigation specific URLs `requestedUrl` and `mainDocumentUrl` in situations where
|
|
15
|
+
* the `URL` artifact is not present. This is not a drop-in replacement for `URL` but can be helpful in situations
|
|
16
|
+
* where getting the `URL` artifact is difficult.
|
|
17
|
+
*/
|
|
18
|
+
declare class DocumentUrls {
|
|
19
|
+
/**
|
|
20
|
+
* @param {{trace: LH.Trace, devtoolsLog: LH.DevtoolsLog}} data
|
|
21
|
+
* @param {LH.Artifacts.ComputedContext} context
|
|
22
|
+
* @return {Promise<{requestedUrl: string, mainDocumentUrl: string}>}
|
|
23
|
+
*/
|
|
24
|
+
static compute_(data: {
|
|
25
|
+
trace: LH.Trace;
|
|
26
|
+
devtoolsLog: import("../index.js").DevtoolsLog;
|
|
27
|
+
}, context: LH.Artifacts.ComputedContext): Promise<{
|
|
28
|
+
requestedUrl: string;
|
|
29
|
+
mainDocumentUrl: string;
|
|
30
|
+
}>;
|
|
31
|
+
}
|
|
32
|
+
//# sourceMappingURL=document-urls.d.ts.map
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @license Copyright 2023 The Lighthouse Authors. All Rights Reserved.
|
|
3
|
+
* Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0
|
|
4
|
+
* Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License.
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import {NetworkAnalyzer} from '../lib/dependency-graph/simulator/network-analyzer.js';
|
|
8
|
+
import {makeComputedArtifact} from './computed-artifact.js';
|
|
9
|
+
import {NetworkRecords} from './network-records.js';
|
|
10
|
+
import {ProcessedTrace} from './processed-trace.js';
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* @fileoverview Compute the navigation specific URLs `requestedUrl` and `mainDocumentUrl` in situations where
|
|
14
|
+
* the `URL` artifact is not present. This is not a drop-in replacement for `URL` but can be helpful in situations
|
|
15
|
+
* where getting the `URL` artifact is difficult.
|
|
16
|
+
*/
|
|
17
|
+
|
|
18
|
+
class DocumentUrls {
|
|
19
|
+
/**
|
|
20
|
+
* @param {{trace: LH.Trace, devtoolsLog: LH.DevtoolsLog}} data
|
|
21
|
+
* @param {LH.Artifacts.ComputedContext} context
|
|
22
|
+
* @return {Promise<{requestedUrl: string, mainDocumentUrl: string}>}
|
|
23
|
+
*/
|
|
24
|
+
static async compute_(data, context) {
|
|
25
|
+
const processedTrace = await ProcessedTrace.request(data.trace, context);
|
|
26
|
+
const networkRecords = await NetworkRecords.request(data.devtoolsLog, context);
|
|
27
|
+
|
|
28
|
+
const mainFrameId = processedTrace.mainFrameInfo.frameId;
|
|
29
|
+
|
|
30
|
+
/** @type {string|undefined} */
|
|
31
|
+
let requestedUrl;
|
|
32
|
+
/** @type {string|undefined} */
|
|
33
|
+
let mainDocumentUrl;
|
|
34
|
+
for (const event of data.devtoolsLog) {
|
|
35
|
+
if (event.method === 'Page.frameNavigated' && event.params.frame.id === mainFrameId) {
|
|
36
|
+
const {url} = event.params.frame;
|
|
37
|
+
// Only set requestedUrl on the first main frame navigation.
|
|
38
|
+
if (!requestedUrl) requestedUrl = url;
|
|
39
|
+
mainDocumentUrl = url;
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
if (!requestedUrl || !mainDocumentUrl) throw new Error('No main frame navigations found');
|
|
43
|
+
|
|
44
|
+
const initialRequest = NetworkAnalyzer.findResourceForUrl(networkRecords, requestedUrl);
|
|
45
|
+
if (initialRequest?.redirects?.length) requestedUrl = initialRequest.redirects[0].url;
|
|
46
|
+
|
|
47
|
+
return {requestedUrl, mainDocumentUrl};
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
const DocumentUrlsComputed = makeComputedArtifact(DocumentUrls, ['devtoolsLog', 'trace']);
|
|
52
|
+
export {DocumentUrlsComputed as DocumentUrls};
|
|
53
|
+
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
export { LCPImageRecordComputed as LCPImageRecord };
|
|
2
|
+
declare const LCPImageRecordComputed: typeof LCPImageRecord & {
|
|
3
|
+
request: (dependencies: {
|
|
4
|
+
trace: LH.Trace;
|
|
5
|
+
devtoolsLog: import("../index.js").DevtoolsLog;
|
|
6
|
+
}, context: import("../../types/utility-types.js").default.ImmutableObject<{
|
|
7
|
+
computedCache: Map<string, import("../lib/arbitrary-equality-map.js").ArbitraryEqualityMap>;
|
|
8
|
+
}>) => Promise<import("../lib/network-request.js").NetworkRequest | undefined>;
|
|
9
|
+
};
|
|
10
|
+
/**
|
|
11
|
+
* @fileoverview Match the LCP event with the paint event to get the request of the image actually painted.
|
|
12
|
+
* This could differ from the `ImageElement` associated with the nodeId if e.g. the LCP
|
|
13
|
+
* was a pseudo-element associated with a node containing a smaller background-image.
|
|
14
|
+
*/
|
|
15
|
+
declare class LCPImageRecord {
|
|
16
|
+
/**
|
|
17
|
+
* @param {{trace: LH.Trace, devtoolsLog: LH.DevtoolsLog}} data
|
|
18
|
+
* @param {LH.Artifacts.ComputedContext} context
|
|
19
|
+
* @return {Promise<LH.Artifacts.NetworkRequest|undefined>}
|
|
20
|
+
*/
|
|
21
|
+
static compute_(data: {
|
|
22
|
+
trace: LH.Trace;
|
|
23
|
+
devtoolsLog: import("../index.js").DevtoolsLog;
|
|
24
|
+
}, context: LH.Artifacts.ComputedContext): Promise<LH.Artifacts.NetworkRequest | undefined>;
|
|
25
|
+
}
|
|
26
|
+
//# sourceMappingURL=lcp-image-record.d.ts.map
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @license Copyright 2023 The Lighthouse Authors. All Rights Reserved.
|
|
3
|
+
* Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0
|
|
4
|
+
* Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License.
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import {makeComputedArtifact} from './computed-artifact.js';
|
|
8
|
+
import {NetworkRecords} from './network-records.js';
|
|
9
|
+
import {ProcessedNavigation} from './processed-navigation.js';
|
|
10
|
+
import {LighthouseError} from '../lib/lh-error.js';
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* @fileoverview Match the LCP event with the paint event to get the request of the image actually painted.
|
|
14
|
+
* This could differ from the `ImageElement` associated with the nodeId if e.g. the LCP
|
|
15
|
+
* was a pseudo-element associated with a node containing a smaller background-image.
|
|
16
|
+
*/
|
|
17
|
+
|
|
18
|
+
class LCPImageRecord {
|
|
19
|
+
/**
|
|
20
|
+
* @param {{trace: LH.Trace, devtoolsLog: LH.DevtoolsLog}} data
|
|
21
|
+
* @param {LH.Artifacts.ComputedContext} context
|
|
22
|
+
* @return {Promise<LH.Artifacts.NetworkRequest|undefined>}
|
|
23
|
+
*/
|
|
24
|
+
static async compute_(data, context) {
|
|
25
|
+
const {trace, devtoolsLog} = data;
|
|
26
|
+
const networkRecords = await NetworkRecords.request(devtoolsLog, context);
|
|
27
|
+
const processedNavigation = await ProcessedNavigation.request(trace, context);
|
|
28
|
+
if (processedNavigation.timings.largestContentfulPaint === undefined) {
|
|
29
|
+
throw new LighthouseError(LighthouseError.errors.NO_LCP);
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
// Use main-frame-only LCP to match the metric value.
|
|
33
|
+
const lcpEvent = processedNavigation.largestContentfulPaintEvt;
|
|
34
|
+
if (!lcpEvent) return;
|
|
35
|
+
|
|
36
|
+
const lcpImagePaintEvent = trace.traceEvents.filter(e => {
|
|
37
|
+
return e.name === 'LargestImagePaint::Candidate' &&
|
|
38
|
+
e.args.frame === lcpEvent.args.frame &&
|
|
39
|
+
e.args.data?.DOMNodeId === lcpEvent.args.data?.nodeId &&
|
|
40
|
+
e.args.data?.size === lcpEvent.args.data?.size;
|
|
41
|
+
// Get last candidate, in case there was more than one.
|
|
42
|
+
}).sort((a, b) => b.ts - a.ts)[0];
|
|
43
|
+
|
|
44
|
+
const lcpUrl = lcpImagePaintEvent?.args.data?.imageUrl;
|
|
45
|
+
if (!lcpUrl) return;
|
|
46
|
+
|
|
47
|
+
const candidates = networkRecords.filter(record => {
|
|
48
|
+
return record.url === lcpUrl &&
|
|
49
|
+
record.finished &&
|
|
50
|
+
// Same frame as LCP trace event.
|
|
51
|
+
record.frameId === lcpImagePaintEvent.args.frame &&
|
|
52
|
+
record.networkRequestTime < (processedNavigation.timestamps.largestContentfulPaint || 0);
|
|
53
|
+
}).map(record => {
|
|
54
|
+
// Follow any redirects to find the real image request.
|
|
55
|
+
while (record.redirectDestination) {
|
|
56
|
+
record = record.redirectDestination;
|
|
57
|
+
}
|
|
58
|
+
return record;
|
|
59
|
+
}).filter(record => {
|
|
60
|
+
// Don't select if also loaded by some other means (xhr, etc). `resourceType`
|
|
61
|
+
// isn't set on redirect _sources_, so have to check after following redirects.
|
|
62
|
+
return record.resourceType === 'Image';
|
|
63
|
+
});
|
|
64
|
+
|
|
65
|
+
// If there are still multiple candidates, at this point it appears the page
|
|
66
|
+
// simply made multiple requests for the image. The first loaded is the best
|
|
67
|
+
// guess of the request that made the image available for use.
|
|
68
|
+
return candidates.sort((a, b) => a.networkEndTime - b.networkEndTime)[0];
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
const LCPImageRecordComputed = makeComputedArtifact(LCPImageRecord, ['devtoolsLog', 'trace']);
|
|
73
|
+
export {LCPImageRecordComputed as LCPImageRecord};
|
|
74
|
+
|
|
@@ -5,6 +5,12 @@ declare const FirstContentfulPaintComputed: typeof FirstContentfulPaint & {
|
|
|
5
5
|
}>) => Promise<import("../../index.js").Artifacts.Metric | import("../../index.js").Artifacts.LanternMetric>;
|
|
6
6
|
};
|
|
7
7
|
declare class FirstContentfulPaint extends NavigationMetric {
|
|
8
|
+
/**
|
|
9
|
+
* @param {LH.Artifacts.NavigationMetricComputationData} data
|
|
10
|
+
* @param {LH.Artifacts.ComputedContext} context
|
|
11
|
+
* @return {Promise<LH.Artifacts.LanternMetric>}
|
|
12
|
+
*/
|
|
13
|
+
static computeSimulatedMetric(data: LH.Artifacts.NavigationMetricComputationData, context: LH.Artifacts.ComputedContext): Promise<LH.Artifacts.LanternMetric>;
|
|
8
14
|
/**
|
|
9
15
|
* @param {LH.Artifacts.NavigationMetricComputationData} data
|
|
10
16
|
* @return {Promise<LH.Artifacts.Metric>}
|
|
@@ -5,6 +5,12 @@ declare const FirstMeaningfulPaintComputed: typeof FirstMeaningfulPaint & {
|
|
|
5
5
|
}>) => Promise<import("../../index.js").Artifacts.Metric | import("../../index.js").Artifacts.LanternMetric>;
|
|
6
6
|
};
|
|
7
7
|
declare class FirstMeaningfulPaint extends NavigationMetric {
|
|
8
|
+
/**
|
|
9
|
+
* @param {LH.Artifacts.NavigationMetricComputationData} data
|
|
10
|
+
* @param {LH.Artifacts.ComputedContext} context
|
|
11
|
+
* @return {Promise<LH.Artifacts.LanternMetric>}
|
|
12
|
+
*/
|
|
13
|
+
static computeSimulatedMetric(data: LH.Artifacts.NavigationMetricComputationData, context: LH.Artifacts.ComputedContext): Promise<LH.Artifacts.LanternMetric>;
|
|
8
14
|
/**
|
|
9
15
|
* @param {LH.Artifacts.NavigationMetricComputationData} data
|
|
10
16
|
* @return {Promise<LH.Artifacts.Metric>}
|
|
@@ -51,6 +51,12 @@ declare class Interactive extends NavigationMetric {
|
|
|
51
51
|
cpuQuietPeriods: Array<TimePeriod>;
|
|
52
52
|
networkQuietPeriods: Array<TimePeriod>;
|
|
53
53
|
};
|
|
54
|
+
/**
|
|
55
|
+
* @param {LH.Artifacts.NavigationMetricComputationData} data
|
|
56
|
+
* @param {LH.Artifacts.ComputedContext} context
|
|
57
|
+
* @return {Promise<LH.Artifacts.LanternMetric>}
|
|
58
|
+
*/
|
|
59
|
+
static computeSimulatedMetric(data: LH.Artifacts.NavigationMetricComputationData, context: LH.Artifacts.ComputedContext): Promise<LH.Artifacts.LanternMetric>;
|
|
54
60
|
/**
|
|
55
61
|
* @param {LH.Artifacts.NavigationMetricComputationData} data
|
|
56
62
|
* @return {Promise<LH.Artifacts.Metric>}
|
|
@@ -5,6 +5,12 @@ declare const LargestContentfulPaintComputed: typeof LargestContentfulPaint & {
|
|
|
5
5
|
}>) => Promise<import("../../index.js").Artifacts.Metric | import("../../index.js").Artifacts.LanternMetric>;
|
|
6
6
|
};
|
|
7
7
|
declare class LargestContentfulPaint extends NavigationMetric {
|
|
8
|
+
/**
|
|
9
|
+
* @param {LH.Artifacts.NavigationMetricComputationData} data
|
|
10
|
+
* @param {LH.Artifacts.ComputedContext} context
|
|
11
|
+
* @return {Promise<LH.Artifacts.LanternMetric>}
|
|
12
|
+
*/
|
|
13
|
+
static computeSimulatedMetric(data: LH.Artifacts.NavigationMetricComputationData, context: LH.Artifacts.ComputedContext): Promise<LH.Artifacts.LanternMetric>;
|
|
8
14
|
/**
|
|
9
15
|
* @param {LH.Artifacts.NavigationMetricComputationData} data
|
|
10
16
|
* @return {Promise<LH.Artifacts.Metric>}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
export { LCPBreakdownComputed as LCPBreakdown };
|
|
2
|
+
declare const LCPBreakdownComputed: typeof LCPBreakdown & {
|
|
3
|
+
request: (dependencies: import("../../index.js").Artifacts.MetricComputationDataInput, context: import("../../../types/utility-types.js").default.ImmutableObject<{
|
|
4
|
+
computedCache: Map<string, import("../../lib/arbitrary-equality-map.js").ArbitraryEqualityMap>;
|
|
5
|
+
}>) => Promise<{
|
|
6
|
+
ttfb: number;
|
|
7
|
+
loadStart?: number | undefined;
|
|
8
|
+
loadEnd?: number | undefined;
|
|
9
|
+
}>;
|
|
10
|
+
};
|
|
11
|
+
declare class LCPBreakdown {
|
|
12
|
+
/**
|
|
13
|
+
* @param {LH.Artifacts.MetricComputationDataInput} data
|
|
14
|
+
* @param {LH.Artifacts.ComputedContext} context
|
|
15
|
+
* @return {Promise<{ttfb: number, loadStart?: number, loadEnd?: number}>}
|
|
16
|
+
*/
|
|
17
|
+
static compute_(data: LH.Artifacts.MetricComputationDataInput, context: LH.Artifacts.ComputedContext): Promise<{
|
|
18
|
+
ttfb: number;
|
|
19
|
+
loadStart?: number | undefined;
|
|
20
|
+
loadEnd?: number | undefined;
|
|
21
|
+
}>;
|
|
22
|
+
}
|
|
23
|
+
//# sourceMappingURL=lcp-breakdown.d.ts.map
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @license Copyright 2023 The Lighthouse Authors. All Rights Reserved.
|
|
3
|
+
* Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0
|
|
4
|
+
* Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License.
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import {makeComputedArtifact} from '../computed-artifact.js';
|
|
8
|
+
import {LighthouseError} from '../../lib/lh-error.js';
|
|
9
|
+
import {LargestContentfulPaint} from './largest-contentful-paint.js';
|
|
10
|
+
import {ProcessedNavigation} from '../processed-navigation.js';
|
|
11
|
+
import {TimeToFirstByte} from './time-to-first-byte.js';
|
|
12
|
+
import {LCPImageRecord} from '../lcp-image-record.js';
|
|
13
|
+
|
|
14
|
+
class LCPBreakdown {
|
|
15
|
+
/**
|
|
16
|
+
* @param {LH.Artifacts.MetricComputationDataInput} data
|
|
17
|
+
* @param {LH.Artifacts.ComputedContext} context
|
|
18
|
+
* @return {Promise<{ttfb: number, loadStart?: number, loadEnd?: number}>}
|
|
19
|
+
*/
|
|
20
|
+
static async compute_(data, context) {
|
|
21
|
+
const processedNavigation = await ProcessedNavigation.request(data.trace, context);
|
|
22
|
+
const observedLcp = processedNavigation.timings.largestContentfulPaint;
|
|
23
|
+
if (observedLcp === undefined) {
|
|
24
|
+
throw new LighthouseError(LighthouseError.errors.NO_LCP);
|
|
25
|
+
}
|
|
26
|
+
const timeOrigin = processedNavigation.timestamps.timeOrigin / 1000;
|
|
27
|
+
|
|
28
|
+
const {timing: ttfb} = await TimeToFirstByte.request(data, context);
|
|
29
|
+
|
|
30
|
+
const lcpRecord = await LCPImageRecord.request(data, context);
|
|
31
|
+
if (!lcpRecord) {
|
|
32
|
+
return {ttfb};
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
// Official LCP^tm. Will be lantern result if simulated, otherwise same as observedLcp.
|
|
36
|
+
const {timing: metricLcp} = await LargestContentfulPaint.request(data, context);
|
|
37
|
+
const throttleRatio = metricLcp / observedLcp;
|
|
38
|
+
|
|
39
|
+
const unclampedLoadStart = (lcpRecord.networkRequestTime - timeOrigin) * throttleRatio;
|
|
40
|
+
const loadStart = Math.max(ttfb, Math.min(unclampedLoadStart, metricLcp));
|
|
41
|
+
|
|
42
|
+
const unclampedLoadEnd = (lcpRecord.networkEndTime - timeOrigin) * throttleRatio;
|
|
43
|
+
const loadEnd = Math.max(loadStart, Math.min(unclampedLoadEnd, metricLcp));
|
|
44
|
+
|
|
45
|
+
return {
|
|
46
|
+
ttfb,
|
|
47
|
+
loadStart,
|
|
48
|
+
loadEnd,
|
|
49
|
+
};
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
const LCPBreakdownComputed = makeComputedArtifact(
|
|
54
|
+
LCPBreakdown,
|
|
55
|
+
['devtoolsLog', 'gatherContext', 'settings', 'simulator', 'trace', 'URL']
|
|
56
|
+
);
|
|
57
|
+
export {LCPBreakdownComputed as LCPBreakdown};
|
|
58
|
+
|
|
@@ -5,6 +5,12 @@ declare const MaxPotentialFIDComputed: typeof MaxPotentialFID & {
|
|
|
5
5
|
}>) => Promise<import("../../index.js").Artifacts.Metric | import("../../index.js").Artifacts.LanternMetric>;
|
|
6
6
|
};
|
|
7
7
|
declare class MaxPotentialFID extends NavigationMetric {
|
|
8
|
+
/**
|
|
9
|
+
* @param {LH.Artifacts.NavigationMetricComputationData} data
|
|
10
|
+
* @param {LH.Artifacts.ComputedContext} context
|
|
11
|
+
* @return {Promise<LH.Artifacts.LanternMetric>}
|
|
12
|
+
*/
|
|
13
|
+
static computeSimulatedMetric(data: LH.Artifacts.NavigationMetricComputationData, context: LH.Artifacts.ComputedContext): Promise<LH.Artifacts.LanternMetric>;
|
|
8
14
|
/**
|
|
9
15
|
* @param {LH.Artifacts.NavigationMetricComputationData} data
|
|
10
16
|
* @return {Promise<LH.Artifacts.Metric>}
|
|
@@ -19,9 +19,9 @@ declare class Metric {
|
|
|
19
19
|
/**
|
|
20
20
|
* @param {LH.Artifacts.MetricComputationData} data
|
|
21
21
|
* @param {LH.Artifacts.ComputedContext} context
|
|
22
|
-
* @return {Promise<LH.Artifacts.LanternMetric>}
|
|
22
|
+
* @return {Promise<LH.Artifacts.LanternMetric|LH.Artifacts.Metric>}
|
|
23
23
|
*/
|
|
24
|
-
static computeSimulatedMetric(data: LH.Artifacts.MetricComputationData, context: LH.Artifacts.ComputedContext): Promise<LH.Artifacts.LanternMetric>;
|
|
24
|
+
static computeSimulatedMetric(data: LH.Artifacts.MetricComputationData, context: LH.Artifacts.ComputedContext): Promise<LH.Artifacts.LanternMetric | LH.Artifacts.Metric>;
|
|
25
25
|
/**
|
|
26
26
|
* @param {LH.Artifacts.MetricComputationData} data
|
|
27
27
|
* @param {LH.Artifacts.ComputedContext} context
|
|
@@ -40,7 +40,7 @@ class Metric {
|
|
|
40
40
|
/**
|
|
41
41
|
* @param {LH.Artifacts.MetricComputationData} data
|
|
42
42
|
* @param {LH.Artifacts.ComputedContext} context
|
|
43
|
-
* @return {Promise<LH.Artifacts.LanternMetric>}
|
|
43
|
+
* @return {Promise<LH.Artifacts.LanternMetric|LH.Artifacts.Metric>}
|
|
44
44
|
*/
|
|
45
45
|
static computeSimulatedMetric(data, context) { // eslint-disable-line no-unused-vars
|
|
46
46
|
throw new Error('Unimplemented');
|
|
@@ -2,9 +2,9 @@ export class NavigationMetric extends Metric {
|
|
|
2
2
|
/**
|
|
3
3
|
* @param {LH.Artifacts.NavigationMetricComputationData} data
|
|
4
4
|
* @param {LH.Artifacts.ComputedContext} context
|
|
5
|
-
* @return {Promise<LH.Artifacts.LanternMetric>}
|
|
5
|
+
* @return {Promise<LH.Artifacts.LanternMetric|LH.Artifacts.Metric>}
|
|
6
6
|
*/
|
|
7
|
-
static computeSimulatedMetric(data: LH.Artifacts.NavigationMetricComputationData, context: LH.Artifacts.ComputedContext): Promise<LH.Artifacts.LanternMetric>;
|
|
7
|
+
static computeSimulatedMetric(data: LH.Artifacts.NavigationMetricComputationData, context: LH.Artifacts.ComputedContext): Promise<LH.Artifacts.LanternMetric | LH.Artifacts.Metric>;
|
|
8
8
|
/**
|
|
9
9
|
* @param {LH.Artifacts.NavigationMetricComputationData} data
|
|
10
10
|
* @param {LH.Artifacts.ComputedContext} context
|
|
@@ -14,7 +14,7 @@ class NavigationMetric extends Metric {
|
|
|
14
14
|
/**
|
|
15
15
|
* @param {LH.Artifacts.NavigationMetricComputationData} data
|
|
16
16
|
* @param {LH.Artifacts.ComputedContext} context
|
|
17
|
-
* @return {Promise<LH.Artifacts.LanternMetric>}
|
|
17
|
+
* @return {Promise<LH.Artifacts.LanternMetric|LH.Artifacts.Metric>}
|
|
18
18
|
*/
|
|
19
19
|
static computeSimulatedMetric(data, context) { // eslint-disable-line no-unused-vars
|
|
20
20
|
throw new Error('Unimplemented');
|
|
@@ -5,6 +5,12 @@ declare const SpeedIndexComputed: typeof SpeedIndex & {
|
|
|
5
5
|
}>) => Promise<import("../../index.js").Artifacts.Metric | import("../../index.js").Artifacts.LanternMetric>;
|
|
6
6
|
};
|
|
7
7
|
declare class SpeedIndex extends NavigationMetric {
|
|
8
|
+
/**
|
|
9
|
+
* @param {LH.Artifacts.NavigationMetricComputationData} data
|
|
10
|
+
* @param {LH.Artifacts.ComputedContext} context
|
|
11
|
+
* @return {Promise<LH.Artifacts.LanternMetric>}
|
|
12
|
+
*/
|
|
13
|
+
static computeSimulatedMetric(data: LH.Artifacts.NavigationMetricComputationData, context: LH.Artifacts.ComputedContext): Promise<LH.Artifacts.LanternMetric>;
|
|
8
14
|
}
|
|
9
15
|
import { NavigationMetric } from "./navigation-metric.js";
|
|
10
16
|
//# sourceMappingURL=speed-index.d.ts.map
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
export { TimeToFirstByteComputed as TimeToFirstByte };
|
|
2
|
+
declare const TimeToFirstByteComputed: typeof TimeToFirstByte & {
|
|
3
|
+
request: (dependencies: import("../../index.js").Artifacts.MetricComputationDataInput, context: import("../../../types/utility-types.js").default.ImmutableObject<{
|
|
4
|
+
computedCache: Map<string, import("../../lib/arbitrary-equality-map.js").ArbitraryEqualityMap>;
|
|
5
|
+
}>) => Promise<import("../../index.js").Artifacts.Metric | import("../../index.js").Artifacts.LanternMetric>;
|
|
6
|
+
};
|
|
7
|
+
declare class TimeToFirstByte extends NavigationMetric {
|
|
8
|
+
/**
|
|
9
|
+
* @param {LH.Artifacts.NavigationMetricComputationData} data
|
|
10
|
+
* @param {LH.Artifacts.ComputedContext} context
|
|
11
|
+
* @return {Promise<LH.Artifacts.Metric>}
|
|
12
|
+
*/
|
|
13
|
+
static computeSimulatedMetric(data: LH.Artifacts.NavigationMetricComputationData, context: LH.Artifacts.ComputedContext): Promise<LH.Artifacts.Metric>;
|
|
14
|
+
}
|
|
15
|
+
import { NavigationMetric } from "./navigation-metric.js";
|
|
16
|
+
//# sourceMappingURL=time-to-first-byte.d.ts.map
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @license Copyright 2023 The Lighthouse Authors. All Rights Reserved.
|
|
3
|
+
* Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0
|
|
4
|
+
* Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License.
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import {makeComputedArtifact} from '../computed-artifact.js';
|
|
8
|
+
import {NavigationMetric} from './navigation-metric.js';
|
|
9
|
+
import {MainResource} from '../main-resource.js';
|
|
10
|
+
import {NetworkAnalysis} from '../network-analysis.js';
|
|
11
|
+
|
|
12
|
+
class TimeToFirstByte extends NavigationMetric {
|
|
13
|
+
/**
|
|
14
|
+
* @param {LH.Artifacts.NavigationMetricComputationData} data
|
|
15
|
+
* @param {LH.Artifacts.ComputedContext} context
|
|
16
|
+
* @return {Promise<LH.Artifacts.Metric>}
|
|
17
|
+
*/
|
|
18
|
+
static async computeSimulatedMetric(data, context) {
|
|
19
|
+
const mainResource = await MainResource.request(data, context);
|
|
20
|
+
const networkAnalysis = await NetworkAnalysis.request(data.devtoolsLog, context);
|
|
21
|
+
|
|
22
|
+
const observedTTFB = (await this.computeObservedMetric(data, context)).timing;
|
|
23
|
+
const observedResponseTime =
|
|
24
|
+
networkAnalysis.serverResponseTimeByOrigin.get(mainResource.parsedURL.securityOrigin);
|
|
25
|
+
if (observedResponseTime === undefined) throw new Error('No response time for origin');
|
|
26
|
+
|
|
27
|
+
// Estimate when the connection is not warm.
|
|
28
|
+
// TTFB = DNS + (SSL)? + TCP handshake + 1 RT for request + server response time
|
|
29
|
+
let roundTrips = 3;
|
|
30
|
+
if (mainResource.parsedURL.scheme === 'https') roundTrips += 1;
|
|
31
|
+
const estimatedTTFB = data.settings.throttling.rttMs * roundTrips + observedResponseTime;
|
|
32
|
+
|
|
33
|
+
const timing = Math.max(observedTTFB, estimatedTTFB);
|
|
34
|
+
return {timing};
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* @param {LH.Artifacts.NavigationMetricComputationData} data
|
|
39
|
+
* @param {LH.Artifacts.ComputedContext} context
|
|
40
|
+
* @return {Promise<LH.Artifacts.Metric>}
|
|
41
|
+
*/
|
|
42
|
+
static async computeObservedMetric(data, context) {
|
|
43
|
+
const {processedNavigation} = data;
|
|
44
|
+
const timeOriginTs = processedNavigation.timestamps.timeOrigin;
|
|
45
|
+
const mainResource = await MainResource.request(data, context);
|
|
46
|
+
|
|
47
|
+
// Technically TTFB is the start of the response headers not the end.
|
|
48
|
+
// That signal isn't available to us so we use header end time as a best guess.
|
|
49
|
+
const timestamp = mainResource.responseHeadersEndTime * 1000;
|
|
50
|
+
const timing = (timestamp - timeOriginTs) / 1000;
|
|
51
|
+
|
|
52
|
+
return {timing, timestamp};
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
const TimeToFirstByteComputed = makeComputedArtifact(
|
|
57
|
+
TimeToFirstByte,
|
|
58
|
+
['devtoolsLog', 'gatherContext', 'settings', 'simulator', 'trace', 'URL']
|
|
59
|
+
);
|
|
60
|
+
export {TimeToFirstByteComputed as TimeToFirstByte};
|
|
@@ -18,6 +18,8 @@ import {SpeedIndex} from './speed-index.js';
|
|
|
18
18
|
import {MaxPotentialFID} from './max-potential-fid.js';
|
|
19
19
|
import {TotalBlockingTime} from './total-blocking-time.js';
|
|
20
20
|
import {makeComputedArtifact} from '../computed-artifact.js';
|
|
21
|
+
import {TimeToFirstByte} from './time-to-first-byte.js';
|
|
22
|
+
import {LCPBreakdown} from './lcp-breakdown.js';
|
|
21
23
|
|
|
22
24
|
class TimingSummary {
|
|
23
25
|
/**
|
|
@@ -57,6 +59,8 @@ class TimingSummary {
|
|
|
57
59
|
const maxPotentialFID = await requestOrUndefined(MaxPotentialFID, metricComputationData);
|
|
58
60
|
const speedIndex = await requestOrUndefined(SpeedIndex, metricComputationData);
|
|
59
61
|
const totalBlockingTime = await requestOrUndefined(TotalBlockingTime, metricComputationData);
|
|
62
|
+
const lcpBreakdown = await requestOrUndefined(LCPBreakdown, metricComputationData);
|
|
63
|
+
const ttfb = await requestOrUndefined(TimeToFirstByte, metricComputationData);
|
|
60
64
|
|
|
61
65
|
const {
|
|
62
66
|
cumulativeLayoutShift,
|
|
@@ -87,6 +91,12 @@ class TimingSummary {
|
|
|
87
91
|
cumulativeLayoutShiftMainFrame,
|
|
88
92
|
totalCumulativeLayoutShift,
|
|
89
93
|
|
|
94
|
+
lcpLoadStart: lcpBreakdown?.loadStart,
|
|
95
|
+
lcpLoadEnd: lcpBreakdown?.loadEnd,
|
|
96
|
+
|
|
97
|
+
timeToFirstByte: ttfb?.timing,
|
|
98
|
+
timeToFirstByteTs: ttfb?.timestamp,
|
|
99
|
+
|
|
90
100
|
// Include all timestamps of interest from the processed trace
|
|
91
101
|
observedTimeOrigin: processedTrace.timings.timeOrigin,
|
|
92
102
|
observedTimeOriginTs: processedTrace.timestamps.timeOrigin,
|
|
@@ -18,6 +18,12 @@ declare const TotalBlockingTimeComputed: typeof TotalBlockingTime & {
|
|
|
18
18
|
* to smaller improvements to main thread responsiveness.
|
|
19
19
|
*/
|
|
20
20
|
declare class TotalBlockingTime extends ComputedMetric {
|
|
21
|
+
/**
|
|
22
|
+
* @param {LH.Artifacts.MetricComputationData} data
|
|
23
|
+
* @param {LH.Artifacts.ComputedContext} context
|
|
24
|
+
* @return {Promise<LH.Artifacts.LanternMetric>}
|
|
25
|
+
*/
|
|
26
|
+
static computeSimulatedMetric(data: LH.Artifacts.MetricComputationData, context: LH.Artifacts.ComputedContext): Promise<LH.Artifacts.LanternMetric>;
|
|
21
27
|
}
|
|
22
28
|
import ComputedMetric from "./metric.js";
|
|
23
29
|
//# sourceMappingURL=total-blocking-time.d.ts.map
|
|
@@ -62,15 +62,6 @@ declare class PageDependencyGraph {
|
|
|
62
62
|
* @param {Node} rootNode
|
|
63
63
|
*/
|
|
64
64
|
static printGraph(rootNode: Node, widthInCharacters?: number): void;
|
|
65
|
-
/**
|
|
66
|
-
* Recalculate `artifacts.URL` for clients that don't provide it.
|
|
67
|
-
*
|
|
68
|
-
* @param {LH.DevtoolsLog} devtoolsLog
|
|
69
|
-
* @param {LH.Artifacts.NetworkRequest[]} networkRecords
|
|
70
|
-
* @param {LH.Artifacts.ProcessedTrace} processedTrace
|
|
71
|
-
* @return {URLArtifact}
|
|
72
|
-
*/
|
|
73
|
-
static getDocumentUrls(devtoolsLog: import("../index.js").DevtoolsLog, networkRecords: LH.Artifacts.NetworkRequest[], processedTrace: LH.Artifacts.ProcessedTrace): URLArtifact;
|
|
74
65
|
/**
|
|
75
66
|
* @param {{trace: LH.Trace, devtoolsLog: LH.DevtoolsLog, URL: LH.Artifacts['URL']}} data
|
|
76
67
|
* @param {LH.Artifacts.ComputedContext} context
|
|
@@ -12,6 +12,7 @@ import {NetworkRequest} from '../lib/network-request.js';
|
|
|
12
12
|
import {ProcessedTrace} from './processed-trace.js';
|
|
13
13
|
import {NetworkRecords} from './network-records.js';
|
|
14
14
|
import {NetworkAnalyzer} from '../lib/dependency-graph/simulator/network-analyzer.js';
|
|
15
|
+
import {DocumentUrls} from './document-urls.js';
|
|
15
16
|
|
|
16
17
|
/** @typedef {import('../lib/dependency-graph/base-node.js').Node} Node */
|
|
17
18
|
/** @typedef {Omit<LH.Artifacts['URL'], 'finalDisplayedUrl'>} URLArtifact */
|
|
@@ -459,37 +460,6 @@ class PageDependencyGraph {
|
|
|
459
460
|
});
|
|
460
461
|
}
|
|
461
462
|
|
|
462
|
-
/**
|
|
463
|
-
* Recalculate `artifacts.URL` for clients that don't provide it.
|
|
464
|
-
*
|
|
465
|
-
* @param {LH.DevtoolsLog} devtoolsLog
|
|
466
|
-
* @param {LH.Artifacts.NetworkRequest[]} networkRecords
|
|
467
|
-
* @param {LH.Artifacts.ProcessedTrace} processedTrace
|
|
468
|
-
* @return {URLArtifact}
|
|
469
|
-
*/
|
|
470
|
-
static getDocumentUrls(devtoolsLog, networkRecords, processedTrace) {
|
|
471
|
-
const mainFrameId = processedTrace.mainFrameInfo.frameId;
|
|
472
|
-
|
|
473
|
-
/** @type {string|undefined} */
|
|
474
|
-
let requestedUrl;
|
|
475
|
-
/** @type {string|undefined} */
|
|
476
|
-
let mainDocumentUrl;
|
|
477
|
-
for (const event of devtoolsLog) {
|
|
478
|
-
if (event.method === 'Page.frameNavigated' && event.params.frame.id === mainFrameId) {
|
|
479
|
-
const {url} = event.params.frame;
|
|
480
|
-
// Only set requestedUrl on the first main frame navigation.
|
|
481
|
-
if (!requestedUrl) requestedUrl = url;
|
|
482
|
-
mainDocumentUrl = url;
|
|
483
|
-
}
|
|
484
|
-
}
|
|
485
|
-
if (!requestedUrl || !mainDocumentUrl) throw new Error('No main frame navigations found');
|
|
486
|
-
|
|
487
|
-
const initialRequest = NetworkAnalyzer.findResourceForUrl(networkRecords, requestedUrl);
|
|
488
|
-
if (initialRequest?.redirects?.length) requestedUrl = initialRequest.redirects[0].url;
|
|
489
|
-
|
|
490
|
-
return {requestedUrl, mainDocumentUrl};
|
|
491
|
-
}
|
|
492
|
-
|
|
493
463
|
/**
|
|
494
464
|
* @param {{trace: LH.Trace, devtoolsLog: LH.DevtoolsLog, URL: LH.Artifacts['URL']}} data
|
|
495
465
|
* @param {LH.Artifacts.ComputedContext} context
|
|
@@ -504,8 +474,7 @@ class PageDependencyGraph {
|
|
|
504
474
|
|
|
505
475
|
// COMPAT: Backport for pre-10.0 clients that don't pass the URL artifact here (e.g. pubads).
|
|
506
476
|
// Calculates the URL artifact from the processed trace and DT log.
|
|
507
|
-
const URL = data.URL ||
|
|
508
|
-
PageDependencyGraph.getDocumentUrls(devtoolsLog, networkRecords, processedTrace);
|
|
477
|
+
const URL = data.URL || await DocumentUrls.request(data, context);
|
|
509
478
|
|
|
510
479
|
return PageDependencyGraph.createGraph(processedTrace, networkRecords, URL);
|
|
511
480
|
}
|
|
@@ -353,7 +353,7 @@ async function navigationGather(page, requestor, options = {}) {
|
|
|
353
353
|
// therefore we connect to the browser in the gatherFn callback.
|
|
354
354
|
if (!page) {
|
|
355
355
|
const {hostname = DEFAULT_HOSTNAME, port = DEFAULT_PORT} = flags;
|
|
356
|
-
lhBrowser = await puppeteer.connect({browserURL: `http://${hostname}:${port}
|
|
356
|
+
lhBrowser = await puppeteer.connect({browserURL: `http://${hostname}:${port}`, defaultViewport: null});
|
|
357
357
|
lhPage = await lhBrowser.newPage();
|
|
358
358
|
page = lhPage;
|
|
359
359
|
}
|
|
@@ -221,6 +221,8 @@ class Util {
|
|
|
221
221
|
|
|
222
222
|
const MAX_LENGTH = 64;
|
|
223
223
|
if (parsedUrl.protocol !== 'data:') {
|
|
224
|
+
// Even non-data uris can be 10k characters long.
|
|
225
|
+
name = name.slice(0, 200);
|
|
224
226
|
// Always elide hexadecimal hash
|
|
225
227
|
name = name.replace(/([a-f0-9]{7})[a-f0-9]{13}[a-f0-9]*/g, `$1${ELLIPSIS}`);
|
|
226
228
|
// Also elide other hash-like mixed-case strings
|
package/dist/report/flow.js
CHANGED
|
@@ -14,7 +14,7 @@
|
|
|
14
14
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
15
15
|
* See the License for the specific language governing permissions and
|
|
16
16
|
* limitations under the License.
|
|
17
|
-
*/const oe="…",re={PASS:{label:"pass",minScore:.9},AVERAGE:{label:"average",minScore:.5},FAIL:{label:"fail"},ERROR:{label:"error"}},se=["com","co","gov","edu","ac","org","go","gob","or","net","in","ne","nic","gouv","web","spb","blog","jus","kiev","mil","wi","qc","ca","bel","on"];class le{static get RATINGS(){return re}static get PASS_THRESHOLD(){return.9}static get MS_DISPLAY_VALUE(){return"%10d ms"}static getFinalDisplayedUrl(e){if(e.finalDisplayedUrl)return e.finalDisplayedUrl;if(e.finalUrl)return e.finalUrl;throw new Error("Could not determine final displayed URL")}static getMainDocumentUrl(e){return e.mainDocumentUrl||e.finalUrl}static getFullPageScreenshot(e){if(e.fullPageScreenshot)return e.fullPageScreenshot;return e.audits["full-page-screenshot"]?.details}static splitMarkdownCodeSpans(e){const a=[],n=e.split(/`(.*?)`/g);for(let e=0;e<n.length;e++){const t=n[e];if(!t)continue;const i=e%2!=0;a.push({isCode:i,text:t})}return a}static splitMarkdownLink(e){const a=[],n=e.split(/\[([^\]]+?)\]\((https?:\/\/.*?)\)/g);for(;n.length;){const[e,t,i]=n.splice(0,3);e&&a.push({isLink:!1,text:e}),t&&i&&a.push({isLink:!0,text:t,linkHref:i})}return a}static truncate(e,a,n="…"){if(e.length<=a)return e;const t=new Intl.Segmenter(void 0,{granularity:"grapheme"}).segment(e)[Symbol.iterator]();let i=0;for(let o=0;o<=a-n.length;o++){const a=t.next();if(a.done)return e;i=a.value.index}for(let a=0;a<n.length;a++)if(t.next().done)return e;return e.slice(0,i)+n}static getURLDisplayName(e,a){const n=void 0!==(a=a||{numPathParts:void 0,preserveQuery:void 0,preserveHost:void 0}).numPathParts?a.numPathParts:2,t=void 0===a.preserveQuery||a.preserveQuery,i=a.preserveHost||!1;let o;if("about:"===e.protocol||"data:"===e.protocol)o=e.href;else{o=e.pathname;const a=o.split("/").filter((e=>e.length));n&&a.length>n&&(o=oe+a.slice(-1*n).join("/")),i&&(o=`${e.host}/${o.replace(/^\//,"")}`),t&&(o=`${o}${e.search}`)}if("data:"!==e.protocol&&(o=o.replace(/([a-f0-9]{7})[a-f0-9]{13}[a-f0-9]*/g,"$1…"),o=o.replace(/([a-zA-Z0-9-_]{9})(?=.*[a-z])(?=.*[A-Z])(?=.*[0-9])[a-zA-Z0-9-_]{10,}/g,"$1…"),o=o.replace(/(\d{3})\d{6,}/g,"$1…"),o=o.replace(/\u2026+/g,oe),o.length>64&&o.includes("?")&&(o=o.replace(/\?([^=]*)(=)?.*/,"?$1$2…"),o.length>64&&(o=o.replace(/\?.*/,"?…")))),o.length>64){const e=o.lastIndexOf(".");o=e>=0?o.slice(0,63-(o.length-e))+`…${o.slice(e)}`:o.slice(0,63)+oe}return o}static parseURL(e){const a=new URL(e);return{file:le.getURLDisplayName(a),hostname:a.hostname,origin:a.origin}}static createOrReturnURL(e){return e instanceof URL?e:new URL(e)}static getTld(e){const a=e.split(".").slice(-2);return se.includes(a[0])?`.${a.join(".")}`:`.${a[a.length-1]}`}static getRootDomain(e){const a=le.createOrReturnURL(e).hostname,n=le.getTld(a).split(".");return a.split(".").slice(-n.length).join(".")}static filterRelevantLines(e,a,n){if(0===a.length)return e.slice(0,2*n+1);const t=new Set;return(a=a.sort(((e,a)=>(e.lineNumber||0)-(a.lineNumber||0)))).forEach((({lineNumber:e})=>{let a=e-n,i=e+n;for(;a<1;)a++,i++;t.has(a-3-1)&&(a-=3);for(let e=a;e<=i;e++){const a=e;t.add(a)}})),e.filter((e=>t.has(e.lineNumber)))}}
|
|
17
|
+
*/const oe="…",re={PASS:{label:"pass",minScore:.9},AVERAGE:{label:"average",minScore:.5},FAIL:{label:"fail"},ERROR:{label:"error"}},se=["com","co","gov","edu","ac","org","go","gob","or","net","in","ne","nic","gouv","web","spb","blog","jus","kiev","mil","wi","qc","ca","bel","on"];class le{static get RATINGS(){return re}static get PASS_THRESHOLD(){return.9}static get MS_DISPLAY_VALUE(){return"%10d ms"}static getFinalDisplayedUrl(e){if(e.finalDisplayedUrl)return e.finalDisplayedUrl;if(e.finalUrl)return e.finalUrl;throw new Error("Could not determine final displayed URL")}static getMainDocumentUrl(e){return e.mainDocumentUrl||e.finalUrl}static getFullPageScreenshot(e){if(e.fullPageScreenshot)return e.fullPageScreenshot;return e.audits["full-page-screenshot"]?.details}static splitMarkdownCodeSpans(e){const a=[],n=e.split(/`(.*?)`/g);for(let e=0;e<n.length;e++){const t=n[e];if(!t)continue;const i=e%2!=0;a.push({isCode:i,text:t})}return a}static splitMarkdownLink(e){const a=[],n=e.split(/\[([^\]]+?)\]\((https?:\/\/.*?)\)/g);for(;n.length;){const[e,t,i]=n.splice(0,3);e&&a.push({isLink:!1,text:e}),t&&i&&a.push({isLink:!0,text:t,linkHref:i})}return a}static truncate(e,a,n="…"){if(e.length<=a)return e;const t=new Intl.Segmenter(void 0,{granularity:"grapheme"}).segment(e)[Symbol.iterator]();let i=0;for(let o=0;o<=a-n.length;o++){const a=t.next();if(a.done)return e;i=a.value.index}for(let a=0;a<n.length;a++)if(t.next().done)return e;return e.slice(0,i)+n}static getURLDisplayName(e,a){const n=void 0!==(a=a||{numPathParts:void 0,preserveQuery:void 0,preserveHost:void 0}).numPathParts?a.numPathParts:2,t=void 0===a.preserveQuery||a.preserveQuery,i=a.preserveHost||!1;let o;if("about:"===e.protocol||"data:"===e.protocol)o=e.href;else{o=e.pathname;const a=o.split("/").filter((e=>e.length));n&&a.length>n&&(o=oe+a.slice(-1*n).join("/")),i&&(o=`${e.host}/${o.replace(/^\//,"")}`),t&&(o=`${o}${e.search}`)}if("data:"!==e.protocol&&(o=o.slice(0,200),o=o.replace(/([a-f0-9]{7})[a-f0-9]{13}[a-f0-9]*/g,"$1…"),o=o.replace(/([a-zA-Z0-9-_]{9})(?=.*[a-z])(?=.*[A-Z])(?=.*[0-9])[a-zA-Z0-9-_]{10,}/g,"$1…"),o=o.replace(/(\d{3})\d{6,}/g,"$1…"),o=o.replace(/\u2026+/g,oe),o.length>64&&o.includes("?")&&(o=o.replace(/\?([^=]*)(=)?.*/,"?$1$2…"),o.length>64&&(o=o.replace(/\?.*/,"?…")))),o.length>64){const e=o.lastIndexOf(".");o=e>=0?o.slice(0,63-(o.length-e))+`…${o.slice(e)}`:o.slice(0,63)+oe}return o}static parseURL(e){const a=new URL(e);return{file:le.getURLDisplayName(a),hostname:a.hostname,origin:a.origin}}static createOrReturnURL(e){return e instanceof URL?e:new URL(e)}static getTld(e){const a=e.split(".").slice(-2);return se.includes(a[0])?`.${a.join(".")}`:`.${a[a.length-1]}`}static getRootDomain(e){const a=le.createOrReturnURL(e).hostname,n=le.getTld(a).split(".");return a.split(".").slice(-n.length).join(".")}static filterRelevantLines(e,a,n){if(0===a.length)return e.slice(0,2*n+1);const t=new Set;return(a=a.sort(((e,a)=>(e.lineNumber||0)-(a.lineNumber||0)))).forEach((({lineNumber:e})=>{let a=e-n,i=e+n;for(;a<1;)a++,i++;t.has(a-3-1)&&(a-=3);for(let e=a;e<=i;e++){const a=e;t.add(a)}})),e.filter((e=>t.has(e.lineNumber)))}}
|
|
18
18
|
/**
|
|
19
19
|
* @license Copyright 2023 The Lighthouse Authors. All Rights Reserved.
|
|
20
20
|
* Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0
|
|
@@ -14,7 +14,7 @@
|
|
|
14
14
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
15
15
|
* See the License for the specific language governing permissions and
|
|
16
16
|
* limitations under the License.
|
|
17
|
-
*/const e="…",t={PASS:{label:"pass",minScore:.9},AVERAGE:{label:"average",minScore:.5},FAIL:{label:"fail"},ERROR:{label:"error"}},n=["com","co","gov","edu","ac","org","go","gob","or","net","in","ne","nic","gouv","web","spb","blog","jus","kiev","mil","wi","qc","ca","bel","on"];class r{static get RATINGS(){return t}static get PASS_THRESHOLD(){return.9}static get MS_DISPLAY_VALUE(){return"%10d ms"}static getFinalDisplayedUrl(e){if(e.finalDisplayedUrl)return e.finalDisplayedUrl;if(e.finalUrl)return e.finalUrl;throw new Error("Could not determine final displayed URL")}static getMainDocumentUrl(e){return e.mainDocumentUrl||e.finalUrl}static getFullPageScreenshot(e){if(e.fullPageScreenshot)return e.fullPageScreenshot;return e.audits["full-page-screenshot"]?.details}static splitMarkdownCodeSpans(e){const t=[],n=e.split(/`(.*?)`/g);for(let e=0;e<n.length;e++){const r=n[e];if(!r)continue;const o=e%2!=0;t.push({isCode:o,text:r})}return t}static splitMarkdownLink(e){const t=[],n=e.split(/\[([^\]]+?)\]\((https?:\/\/.*?)\)/g);for(;n.length;){const[e,r,o]=n.splice(0,3);e&&t.push({isLink:!1,text:e}),r&&o&&t.push({isLink:!0,text:r,linkHref:o})}return t}static truncate(e,t,n="…"){if(e.length<=t)return e;const r=new Intl.Segmenter(void 0,{granularity:"grapheme"}).segment(e)[Symbol.iterator]();let o=0;for(let i=0;i<=t-n.length;i++){const t=r.next();if(t.done)return e;o=t.value.index}for(let t=0;t<n.length;t++)if(r.next().done)return e;return e.slice(0,o)+n}static getURLDisplayName(t,n){const r=void 0!==(n=n||{numPathParts:void 0,preserveQuery:void 0,preserveHost:void 0}).numPathParts?n.numPathParts:2,o=void 0===n.preserveQuery||n.preserveQuery,i=n.preserveHost||!1;let a;if("about:"===t.protocol||"data:"===t.protocol)a=t.href;else{a=t.pathname;const n=a.split("/").filter((e=>e.length));r&&n.length>r&&(a=e+n.slice(-1*r).join("/")),i&&(a=`${t.host}/${a.replace(/^\//,"")}`),o&&(a=`${a}${t.search}`)}if("data:"!==t.protocol&&(a=a.replace(/([a-f0-9]{7})[a-f0-9]{13}[a-f0-9]*/g,"$1…"),a=a.replace(/([a-zA-Z0-9-_]{9})(?=.*[a-z])(?=.*[A-Z])(?=.*[0-9])[a-zA-Z0-9-_]{10,}/g,"$1…"),a=a.replace(/(\d{3})\d{6,}/g,"$1…"),a=a.replace(/\u2026+/g,e),a.length>64&&a.includes("?")&&(a=a.replace(/\?([^=]*)(=)?.*/,"?$1$2…"),a.length>64&&(a=a.replace(/\?.*/,"?…")))),a.length>64){const t=a.lastIndexOf(".");a=t>=0?a.slice(0,63-(a.length-t))+`…${a.slice(t)}`:a.slice(0,63)+e}return a}static parseURL(e){const t=new URL(e);return{file:r.getURLDisplayName(t),hostname:t.hostname,origin:t.origin}}static createOrReturnURL(e){return e instanceof URL?e:new URL(e)}static getTld(e){const t=e.split(".").slice(-2);return n.includes(t[0])?`.${t.join(".")}`:`.${t[t.length-1]}`}static getRootDomain(e){const t=r.createOrReturnURL(e).hostname,n=r.getTld(t).split(".");return t.split(".").slice(-n.length).join(".")}static filterRelevantLines(e,t,n){if(0===t.length)return e.slice(0,2*n+1);const r=new Set;return(t=t.sort(((e,t)=>(e.lineNumber||0)-(t.lineNumber||0)))).forEach((({lineNumber:e})=>{let t=e-n,o=e+n;for(;t<1;)t++,o++;r.has(t-3-1)&&(t-=3);for(let e=t;e<=o;e++){const t=e;r.add(t)}})),e.filter((e=>r.has(e.lineNumber)))}}
|
|
17
|
+
*/const e="…",t={PASS:{label:"pass",minScore:.9},AVERAGE:{label:"average",minScore:.5},FAIL:{label:"fail"},ERROR:{label:"error"}},n=["com","co","gov","edu","ac","org","go","gob","or","net","in","ne","nic","gouv","web","spb","blog","jus","kiev","mil","wi","qc","ca","bel","on"];class r{static get RATINGS(){return t}static get PASS_THRESHOLD(){return.9}static get MS_DISPLAY_VALUE(){return"%10d ms"}static getFinalDisplayedUrl(e){if(e.finalDisplayedUrl)return e.finalDisplayedUrl;if(e.finalUrl)return e.finalUrl;throw new Error("Could not determine final displayed URL")}static getMainDocumentUrl(e){return e.mainDocumentUrl||e.finalUrl}static getFullPageScreenshot(e){if(e.fullPageScreenshot)return e.fullPageScreenshot;return e.audits["full-page-screenshot"]?.details}static splitMarkdownCodeSpans(e){const t=[],n=e.split(/`(.*?)`/g);for(let e=0;e<n.length;e++){const r=n[e];if(!r)continue;const o=e%2!=0;t.push({isCode:o,text:r})}return t}static splitMarkdownLink(e){const t=[],n=e.split(/\[([^\]]+?)\]\((https?:\/\/.*?)\)/g);for(;n.length;){const[e,r,o]=n.splice(0,3);e&&t.push({isLink:!1,text:e}),r&&o&&t.push({isLink:!0,text:r,linkHref:o})}return t}static truncate(e,t,n="…"){if(e.length<=t)return e;const r=new Intl.Segmenter(void 0,{granularity:"grapheme"}).segment(e)[Symbol.iterator]();let o=0;for(let i=0;i<=t-n.length;i++){const t=r.next();if(t.done)return e;o=t.value.index}for(let t=0;t<n.length;t++)if(r.next().done)return e;return e.slice(0,o)+n}static getURLDisplayName(t,n){const r=void 0!==(n=n||{numPathParts:void 0,preserveQuery:void 0,preserveHost:void 0}).numPathParts?n.numPathParts:2,o=void 0===n.preserveQuery||n.preserveQuery,i=n.preserveHost||!1;let a;if("about:"===t.protocol||"data:"===t.protocol)a=t.href;else{a=t.pathname;const n=a.split("/").filter((e=>e.length));r&&n.length>r&&(a=e+n.slice(-1*r).join("/")),i&&(a=`${t.host}/${a.replace(/^\//,"")}`),o&&(a=`${a}${t.search}`)}if("data:"!==t.protocol&&(a=a.slice(0,200),a=a.replace(/([a-f0-9]{7})[a-f0-9]{13}[a-f0-9]*/g,"$1…"),a=a.replace(/([a-zA-Z0-9-_]{9})(?=.*[a-z])(?=.*[A-Z])(?=.*[0-9])[a-zA-Z0-9-_]{10,}/g,"$1…"),a=a.replace(/(\d{3})\d{6,}/g,"$1…"),a=a.replace(/\u2026+/g,e),a.length>64&&a.includes("?")&&(a=a.replace(/\?([^=]*)(=)?.*/,"?$1$2…"),a.length>64&&(a=a.replace(/\?.*/,"?…")))),a.length>64){const t=a.lastIndexOf(".");a=t>=0?a.slice(0,63-(a.length-t))+`…${a.slice(t)}`:a.slice(0,63)+e}return a}static parseURL(e){const t=new URL(e);return{file:r.getURLDisplayName(t),hostname:t.hostname,origin:t.origin}}static createOrReturnURL(e){return e instanceof URL?e:new URL(e)}static getTld(e){const t=e.split(".").slice(-2);return n.includes(t[0])?`.${t.join(".")}`:`.${t[t.length-1]}`}static getRootDomain(e){const t=r.createOrReturnURL(e).hostname,n=r.getTld(t).split(".");return t.split(".").slice(-n.length).join(".")}static filterRelevantLines(e,t,n){if(0===t.length)return e.slice(0,2*n+1);const r=new Set;return(t=t.sort(((e,t)=>(e.lineNumber||0)-(t.lineNumber||0)))).forEach((({lineNumber:e})=>{let t=e-n,o=e+n;for(;t<1;)t++,o++;r.has(t-3-1)&&(t-=3);for(let e=t;e<=o;e++){const t=e;r.add(t)}})),e.filter((e=>r.has(e.lineNumber)))}}
|
|
18
18
|
/**
|
|
19
19
|
* @license
|
|
20
20
|
* Copyright 2017 The Lighthouse Authors. All Rights Reserved.
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "lighthouse",
|
|
3
3
|
"type": "module",
|
|
4
|
-
"version": "10.1.1-dev.
|
|
4
|
+
"version": "10.1.1-dev.20230425",
|
|
5
5
|
"description": "Automated auditing, performance metrics, and best practices for the web.",
|
|
6
6
|
"main": "./core/index.js",
|
|
7
7
|
"bin": {
|
|
@@ -113,6 +113,7 @@
|
|
|
113
113
|
"@types/chrome": "^0.0.154",
|
|
114
114
|
"@types/configstore": "^4.0.0",
|
|
115
115
|
"@types/cpy": "^5.1.0",
|
|
116
|
+
"@types/debug": "^4.1.7",
|
|
116
117
|
"@types/eslint": "^8.2.1",
|
|
117
118
|
"@types/estree": "^0.0.50",
|
|
118
119
|
"@types/gh-pages": "^2.0.0",
|
|
@@ -168,7 +169,7 @@
|
|
|
168
169
|
"pako": "^2.0.3",
|
|
169
170
|
"preact": "^10.7.2",
|
|
170
171
|
"pretty-json-stringify": "^0.0.2",
|
|
171
|
-
"puppeteer": "^19.
|
|
172
|
+
"puppeteer": "^19.11.0",
|
|
172
173
|
"resolve": "^1.20.0",
|
|
173
174
|
"rollup": "^2.52.7",
|
|
174
175
|
"rollup-plugin-node-resolve": "^5.2.0",
|
|
@@ -204,7 +205,7 @@
|
|
|
204
205
|
"open": "^8.4.0",
|
|
205
206
|
"parse-cache-control": "1.0.1",
|
|
206
207
|
"ps-list": "^8.0.0",
|
|
207
|
-
"puppeteer-core": "^19.
|
|
208
|
+
"puppeteer-core": "^19.11.0",
|
|
208
209
|
"robots-parser": "^3.0.0",
|
|
209
210
|
"semver": "^5.3.0",
|
|
210
211
|
"speedline-core": "^1.4.3",
|
package/shared/util.js
CHANGED
|
@@ -221,6 +221,8 @@ class Util {
|
|
|
221
221
|
|
|
222
222
|
const MAX_LENGTH = 64;
|
|
223
223
|
if (parsedUrl.protocol !== 'data:') {
|
|
224
|
+
// Even non-data uris can be 10k characters long.
|
|
225
|
+
name = name.slice(0, 200);
|
|
224
226
|
// Always elide hexadecimal hash
|
|
225
227
|
name = name.replace(/([a-f0-9]{7})[a-f0-9]{13}[a-f0-9]*/g, `$1${ELLIPSIS}`);
|
|
226
228
|
// Also elide other hash-like mixed-case strings
|
package/types/artifacts.d.ts
CHANGED
|
@@ -789,6 +789,10 @@ declare module Artifacts {
|
|
|
789
789
|
largestContentfulPaintTs: number | undefined;
|
|
790
790
|
largestContentfulPaintAllFrames: number | undefined;
|
|
791
791
|
largestContentfulPaintAllFramesTs: number | undefined;
|
|
792
|
+
timeToFirstByte: number | undefined;
|
|
793
|
+
timeToFirstByteTs: number | undefined;
|
|
794
|
+
lcpLoadStart: number | undefined;
|
|
795
|
+
lcpLoadEnd: number | undefined;
|
|
792
796
|
interactive: number | undefined;
|
|
793
797
|
interactiveTs: number | undefined;
|
|
794
798
|
speedIndex: number | undefined;
|