lighthouse 10.1.1-dev.20230424 → 10.1.1-dev.20230426

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.
Files changed (32) hide show
  1. package/core/audits/predictive-perf.js +9 -2
  2. package/core/audits/prioritize-lcp-image.d.ts +0 -11
  3. package/core/audits/prioritize-lcp-image.js +3 -55
  4. package/core/computed/document-urls.d.ts +32 -0
  5. package/core/computed/document-urls.js +53 -0
  6. package/core/computed/lcp-image-record.d.ts +26 -0
  7. package/core/computed/lcp-image-record.js +74 -0
  8. package/core/computed/metrics/first-contentful-paint.d.ts +6 -0
  9. package/core/computed/metrics/first-meaningful-paint.d.ts +6 -0
  10. package/core/computed/metrics/interactive.d.ts +6 -0
  11. package/core/computed/metrics/largest-contentful-paint.d.ts +6 -0
  12. package/core/computed/metrics/lcp-breakdown.d.ts +23 -0
  13. package/core/computed/metrics/lcp-breakdown.js +58 -0
  14. package/core/computed/metrics/max-potential-fid.d.ts +6 -0
  15. package/core/computed/metrics/metric.d.ts +2 -2
  16. package/core/computed/metrics/metric.js +1 -1
  17. package/core/computed/metrics/navigation-metric.d.ts +2 -2
  18. package/core/computed/metrics/navigation-metric.js +1 -1
  19. package/core/computed/metrics/speed-index.d.ts +6 -0
  20. package/core/computed/metrics/time-to-first-byte.d.ts +16 -0
  21. package/core/computed/metrics/time-to-first-byte.js +60 -0
  22. package/core/computed/metrics/timing-summary.js +10 -0
  23. package/core/computed/metrics/total-blocking-time.d.ts +6 -0
  24. package/core/computed/page-dependency-graph.d.ts +0 -9
  25. package/core/computed/page-dependency-graph.js +2 -33
  26. package/core/lib/url-utils.js +1 -0
  27. package/dist/report/bundle.esm.js +2 -0
  28. package/dist/report/flow.js +1 -1
  29. package/dist/report/standalone.js +1 -1
  30. package/package.json +1 -1
  31. package/shared/util.js +2 -0
  32. 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
- // @ts-expect-error - TODO(bckenny): allow optional `throttling` settings
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 {ProcessedNavigation} from '../computed/processed-navigation.js';
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 lcpRecord = PrioritizeLcpImage.getLcpRecord(trace, processedNavigation, networkRecords);
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, lcpRecord);
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
  }
@@ -22,6 +22,7 @@ const NON_NETWORK_SCHEMES = [
22
22
  'intent', // @see https://developer.chrome.com/docs/multidevice/android/intents/
23
23
  'file', // @see https://en.wikipedia.org/wiki/File_URI_scheme
24
24
  'filesystem', // @see https://developer.mozilla.org/en-US/docs/Web/API/FileSystem
25
+ 'chrome-extension',
25
26
  ];
26
27
 
27
28
  /**
@@ -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
@@ -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.20230424",
4
+ "version": "10.1.1-dev.20230426",
5
5
  "description": "Automated auditing, performance metrics, and best practices for the web.",
6
6
  "main": "./core/index.js",
7
7
  "bin": {
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
@@ -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;