lighthouse 10.2.0-dev.20230610 → 10.2.0-dev.20230612

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.
@@ -3,7 +3,7 @@
3
3
  exports[`getAssertionReport works (multiple failing) 1`] = `
4
4
  "X difference at cumulative-layout-shift audit.details.items.length
5
5
  expected: []
6
- found: [{\\"cumulativeLayoutShiftMainFrame\\":0.13570762803819444,\\"totalCumulativeLayoutShift\\":0.13570762803819444}]
6
+ found: [{\\"cumulativeLayoutShiftMainFrame\\":0.13570762803819444}]
7
7
 
8
8
 
9
9
  X difference at cumulative-layout-shift audit.details.blah
@@ -24,8 +24,7 @@ exports[`getAssertionReport works (multiple failing) 1`] = `
24
24
  \\"type\\": \\"debugdata\\",
25
25
  \\"items\\": [
26
26
  {
27
- \\"cumulativeLayoutShiftMainFrame\\": 0.13570762803819444,
28
- \\"totalCumulativeLayoutShift\\": 0.13570762803819444
27
+ \\"cumulativeLayoutShiftMainFrame\\": 0.13570762803819444
29
28
  }
30
29
  ]
31
30
  }
@@ -35,7 +34,7 @@ exports[`getAssertionReport works (multiple failing) 1`] = `
35
34
  exports[`getAssertionReport works (trivial failing) 1`] = `
36
35
  "X difference at cumulative-layout-shift audit.details.items.length
37
36
  expected: []
38
- found: [{\\"cumulativeLayoutShiftMainFrame\\":0.13570762803819444,\\"totalCumulativeLayoutShift\\":0.13570762803819444}]
37
+ found: [{\\"cumulativeLayoutShiftMainFrame\\":0.13570762803819444}]
39
38
 
40
39
  found result:
41
40
  {
@@ -51,8 +50,7 @@ exports[`getAssertionReport works (trivial failing) 1`] = `
51
50
  \\"type\\": \\"debugdata\\",
52
51
  \\"items\\": [
53
52
  {
54
- \\"cumulativeLayoutShiftMainFrame\\": 0.13570762803819444,
55
- \\"totalCumulativeLayoutShift\\": 0.13570762803819444
53
+ \\"cumulativeLayoutShiftMainFrame\\": 0.13570762803819444
56
54
  }
57
55
  ]
58
56
  }
@@ -1,11 +1,23 @@
1
1
  export default LargestContentfulPaintElement;
2
2
  declare class LargestContentfulPaintElement extends Audit {
3
+ /**
4
+ * @param {LH.Artifacts.MetricComputationDataInput} metricComputationData
5
+ * @param {LH.Audit.Context} context
6
+ * @return {Promise<number|undefined>}
7
+ */
8
+ static getOptionalLCPMetric(metricComputationData: LH.Artifacts.MetricComputationDataInput, context: LH.Audit.Context): Promise<number | undefined>;
3
9
  /**
4
10
  * @param {LH.Artifacts} artifacts
11
+ * @return {LH.Audit.Details.Table|undefined}
12
+ */
13
+ static makeElementTable(artifacts: LH.Artifacts): LH.Audit.Details.Table | undefined;
14
+ /**
15
+ * @param {number} metricLcp
16
+ * @param {LH.Artifacts.MetricComputationDataInput} metricComputationData
5
17
  * @param {LH.Audit.Context} context
6
- * @return {Promise<LH.Audit.Details.Table|undefined>}
18
+ * @return {Promise<LH.Audit.Details.Table>}
7
19
  */
8
- static makePhaseTable(artifacts: LH.Artifacts, context: LH.Audit.Context): Promise<LH.Audit.Details.Table | undefined>;
20
+ static makePhaseTable(metricLcp: number, metricComputationData: LH.Artifacts.MetricComputationDataInput, context: LH.Audit.Context): Promise<LH.Audit.Details.Table>;
9
21
  /**
10
22
  * @param {LH.Artifacts} artifacts
11
23
  * @param {LH.Audit.Context} context
@@ -50,19 +50,44 @@ class LargestContentfulPaintElement extends Audit {
50
50
  }
51
51
 
52
52
  /**
53
- * @param {LH.Artifacts} artifacts
53
+ * @param {LH.Artifacts.MetricComputationDataInput} metricComputationData
54
54
  * @param {LH.Audit.Context} context
55
- * @return {Promise<LH.Audit.Details.Table|undefined>}
55
+ * @return {Promise<number|undefined>}
56
56
  */
57
- static async makePhaseTable(artifacts, context) {
58
- const trace = artifacts.traces[Audit.DEFAULT_PASS];
59
- const devtoolsLog = artifacts.devtoolsLogs[Audit.DEFAULT_PASS];
60
- const gatherContext = artifacts.GatherContext;
61
- const metricComputationData = {trace, devtoolsLog, gatherContext,
62
- settings: context.settings, URL: artifacts.URL};
57
+ static async getOptionalLCPMetric(metricComputationData, context) {
58
+ try {
59
+ const {timing: metricLcp} =
60
+ await LargestContentfulPaint.request(metricComputationData, context);
61
+ return metricLcp;
62
+ } catch {}
63
+ }
64
+
65
+ /**
66
+ * @param {LH.Artifacts} artifacts
67
+ * @return {LH.Audit.Details.Table|undefined}
68
+ */
69
+ static makeElementTable(artifacts) {
70
+ const lcpElement = artifacts.TraceElements
71
+ .find(element => element.traceEventType === 'largest-contentful-paint');
72
+ if (!lcpElement) return;
73
+
74
+ /** @type {LH.Audit.Details.Table['headings']} */
75
+ const headings = [
76
+ {key: 'node', valueType: 'node', label: str_(i18n.UIStrings.columnElement)},
77
+ ];
63
78
 
64
- const {timing: metricLcp} =
65
- await LargestContentfulPaint.request(metricComputationData, context);
79
+ const lcpElementDetails = [{node: Audit.makeNodeItem(lcpElement.node)}];
80
+
81
+ return Audit.makeTableDetails(headings, lcpElementDetails);
82
+ }
83
+
84
+ /**
85
+ * @param {number} metricLcp
86
+ * @param {LH.Artifacts.MetricComputationDataInput} metricComputationData
87
+ * @param {LH.Audit.Context} context
88
+ * @return {Promise<LH.Audit.Details.Table>}
89
+ */
90
+ static async makePhaseTable(metricLcp, metricComputationData, context) {
66
91
  const {ttfb, loadStart, loadEnd} = await LCPBreakdown.request(metricComputationData, context);
67
92
 
68
93
  let loadDelay = 0;
@@ -102,36 +127,29 @@ class LargestContentfulPaintElement extends Audit {
102
127
  * @return {Promise<LH.Audit.Product>}
103
128
  */
104
129
  static async audit(artifacts, context) {
105
- const lcpElement = artifacts.TraceElements
106
- .find(element => element.traceEventType === 'largest-contentful-paint');
107
- const lcpElementDetails = [];
108
- if (lcpElement) {
109
- lcpElementDetails.push({
110
- node: Audit.makeNodeItem(lcpElement.node),
111
- });
112
- }
113
-
114
- /** @type {LH.Audit.Details.Table['headings']} */
115
- const headings = [
116
- {key: 'node', valueType: 'node', label: str_(i18n.UIStrings.columnElement)},
117
- ];
130
+ const trace = artifacts.traces[Audit.DEFAULT_PASS];
131
+ const devtoolsLog = artifacts.devtoolsLogs[Audit.DEFAULT_PASS];
132
+ const gatherContext = artifacts.GatherContext;
133
+ const metricComputationData = {trace, devtoolsLog, gatherContext,
134
+ settings: context.settings, URL: artifacts.URL};
118
135
 
119
- const elementTable = Audit.makeTableDetails(headings, lcpElementDetails);
136
+ const elementTable = this.makeElementTable(artifacts);
137
+ if (!elementTable) return {score: null, notApplicable: true};
120
138
 
121
139
  const items = [elementTable];
122
- if (elementTable.items.length) {
123
- const phaseTable = await this.makePhaseTable(artifacts, context);
124
- if (phaseTable) items.push(phaseTable);
140
+ let displayValue;
141
+
142
+ const metricLcp = await this.getOptionalLCPMetric(metricComputationData, context);
143
+ if (metricLcp) {
144
+ displayValue = str_(i18n.UIStrings.ms, {timeInMs: metricLcp});
145
+ const phaseTable = await this.makePhaseTable(metricLcp, metricComputationData, context);
146
+ items.push(phaseTable);
125
147
  }
126
148
 
127
149
  const details = Audit.makeListDetails(items);
128
150
 
129
- const displayValue = str_(i18n.UIStrings.displayValueElementsFound,
130
- {nodeCount: lcpElementDetails.length});
131
-
132
151
  return {
133
152
  score: 1,
134
- notApplicable: lcpElementDetails.length === 0,
135
153
  displayValue,
136
154
  details,
137
155
  };
@@ -11,10 +11,8 @@ import {TimingSummary} from '../computed/metrics/timing-summary.js';
11
11
  const DECIMAL_METRIC_KEYS = new Set([
12
12
  'cumulativeLayoutShift',
13
13
  'cumulativeLayoutShiftMainFrame',
14
- 'totalCumulativeLayoutShift',
15
14
  'observedCumulativeLayoutShift',
16
15
  'observedCumulativeLayoutShiftMainFrame',
17
- 'observedTotalCumulativeLayoutShift',
18
16
  ]);
19
17
 
20
18
  class Metrics extends Audit {
@@ -46,7 +46,7 @@ class ServerResponseTime extends Audit {
46
46
  */
47
47
  static calculateResponseTime(record) {
48
48
  const timing = record.timing;
49
- return timing ? timing.receiveHeadersEnd - timing.sendEnd : 0;
49
+ return timing ? timing.receiveHeadersStart - timing.sendEnd : 0;
50
50
  }
51
51
 
52
52
  /**
@@ -11,7 +11,6 @@ declare const CumulativeLayoutShiftComputed: typeof CumulativeLayoutShift & {
11
11
  }>) => Promise<{
12
12
  cumulativeLayoutShift: number;
13
13
  cumulativeLayoutShiftMainFrame: number;
14
- totalCumulativeLayoutShift: number;
15
14
  }>;
16
15
  };
17
16
  declare class CumulativeLayoutShift {
@@ -33,21 +32,14 @@ declare class CumulativeLayoutShift {
33
32
  * @return {number}
34
33
  */
35
34
  static calculate(layoutShiftEvents: Array<LayoutShiftEvent>): number;
36
- /**
37
- * Sum all layout shift events from the entire trace.
38
- * @param {Array<LayoutShiftEvent>} layoutShiftEvents
39
- * @return {number}
40
- */
41
- static calculateTotalCumulativeLayoutShift(layoutShiftEvents: Array<LayoutShiftEvent>): number;
42
35
  /**
43
36
  * @param {LH.Trace} trace
44
37
  * @param {LH.Artifacts.ComputedContext} context
45
- * @return {Promise<{cumulativeLayoutShift: number, cumulativeLayoutShiftMainFrame: number, totalCumulativeLayoutShift: number}>}
38
+ * @return {Promise<{cumulativeLayoutShift: number, cumulativeLayoutShiftMainFrame: number}>}
46
39
  */
47
40
  static compute_(trace: LH.Trace, context: LH.Artifacts.ComputedContext): Promise<{
48
41
  cumulativeLayoutShift: number;
49
42
  cumulativeLayoutShiftMainFrame: number;
50
- totalCumulativeLayoutShift: number;
51
43
  }>;
52
44
  }
53
45
  import { ProcessedTrace } from '../processed-trace.js';
@@ -101,19 +101,10 @@ class CumulativeLayoutShift {
101
101
  return maxScore;
102
102
  }
103
103
 
104
- /**
105
- * Sum all layout shift events from the entire trace.
106
- * @param {Array<LayoutShiftEvent>} layoutShiftEvents
107
- * @return {number}
108
- */
109
- static calculateTotalCumulativeLayoutShift(layoutShiftEvents) {
110
- return layoutShiftEvents.reduce((sum, e) => sum += e.weightedScore, 0);
111
- }
112
-
113
104
  /**
114
105
  * @param {LH.Trace} trace
115
106
  * @param {LH.Artifacts.ComputedContext} context
116
- * @return {Promise<{cumulativeLayoutShift: number, cumulativeLayoutShiftMainFrame: number, totalCumulativeLayoutShift: number}>}
107
+ * @return {Promise<{cumulativeLayoutShift: number, cumulativeLayoutShiftMainFrame: number}>}
117
108
  */
118
109
  static async compute_(trace, context) {
119
110
  const processedTrace = await ProcessedTrace.request(trace, context);
@@ -122,14 +113,9 @@ class CumulativeLayoutShift {
122
113
  CumulativeLayoutShift.getLayoutShiftEvents(processedTrace);
123
114
  const mainFrameShiftEvents = allFrameShiftEvents.filter(e => e.isMainFrame);
124
115
 
125
- // The original Cumulative Layout Shift metric, the sum of all main-frame shift events.
126
- const totalCumulativeLayoutShift =
127
- CumulativeLayoutShift.calculateTotalCumulativeLayoutShift(mainFrameShiftEvents);
128
-
129
116
  return {
130
117
  cumulativeLayoutShift: CumulativeLayoutShift.calculate(allFrameShiftEvents),
131
118
  cumulativeLayoutShiftMainFrame: CumulativeLayoutShift.calculate(mainFrameShiftEvents),
132
- totalCumulativeLayoutShift,
133
119
  };
134
120
  }
135
121
  }
@@ -41,15 +41,17 @@ class TimeToFirstByte extends NavigationMetric {
41
41
  * @return {Promise<LH.Artifacts.Metric>}
42
42
  */
43
43
  static async computeObservedMetric(data, context) {
44
- const {processedNavigation} = data;
45
- const timeOriginTs = processedNavigation.timestamps.timeOrigin;
46
44
  const mainResource = await MainResource.request(data, context);
45
+ if (!mainResource.timing) {
46
+ throw new Error('missing timing for main resource');
47
+ }
47
48
 
48
- // Technically TTFB is the start of the response headers not the end.
49
- // That signal isn't available to us so we use header end time as a best guess.
50
- const timestamp = mainResource.responseHeadersEndTime * 1000;
49
+ const {processedNavigation} = data;
50
+ const timeOriginTs = processedNavigation.timestamps.timeOrigin;
51
+ const timestampMs =
52
+ mainResource.timing.requestTime * 1000 + mainResource.timing.receiveHeadersStart;
53
+ const timestamp = timestampMs * 1000;
51
54
  const timing = (timestamp - timeOriginTs) / 1000;
52
-
53
55
  return {timing, timestamp};
54
56
  }
55
57
  }
@@ -65,7 +65,6 @@ class TimingSummary {
65
65
  const {
66
66
  cumulativeLayoutShift,
67
67
  cumulativeLayoutShiftMainFrame,
68
- totalCumulativeLayoutShift,
69
68
  } = cumulativeLayoutShiftValues || {};
70
69
 
71
70
  /** @type {LH.Artifacts.TimingSummary} */
@@ -89,7 +88,6 @@ class TimingSummary {
89
88
  maxPotentialFID: maxPotentialFID?.timing,
90
89
  cumulativeLayoutShift,
91
90
  cumulativeLayoutShiftMainFrame,
92
- totalCumulativeLayoutShift,
93
91
 
94
92
  lcpLoadStart: lcpBreakdown?.loadStart,
95
93
  lcpLoadEnd: lcpBreakdown?.loadEnd,
@@ -123,7 +121,6 @@ class TimingSummary {
123
121
  observedDomContentLoadedTs: processedNavigation?.timestamps.domContentLoaded,
124
122
  observedCumulativeLayoutShift: cumulativeLayoutShift,
125
123
  observedCumulativeLayoutShiftMainFrame: cumulativeLayoutShiftMainFrame,
126
- observedTotalCumulativeLayoutShift: totalCumulativeLayoutShift,
127
124
 
128
125
  // Include some visual metrics from speedline
129
126
  observedFirstVisualChange: speedline.first,
@@ -139,14 +139,15 @@ class NetworkAnalyzer {
139
139
  return NetworkAnalyzer._estimateValueByOrigin(records, ({timing, connectionReused, record}) => {
140
140
  if (connectionReused) return;
141
141
 
142
- if (timing.connectEnd > 0 && timing.connectStart > 0 && record.protocol.startsWith('h3')) {
142
+ const {connectStart, sslStart, sslEnd, connectEnd} = timing;
143
+ if (connectEnd >= 0 && connectStart >= 0 && record.protocol.startsWith('h3')) {
143
144
  // These values are equal to sslStart and sslEnd for h3.
144
- return timing.connectEnd - timing.connectStart;
145
- } else if (timing.sslStart > 0 && timing.sslEnd > 0) {
145
+ return connectEnd - connectStart;
146
+ } else if (sslStart >= 0 && sslEnd >= 0 && sslStart !== connectStart) {
146
147
  // SSL can also be more than 1 RT but assume False Start was used.
147
- return [timing.connectEnd - timing.sslStart, timing.sslStart - timing.connectStart];
148
- } else if (timing.connectStart > 0 && timing.connectEnd > 0) {
149
- return timing.connectEnd - timing.connectStart;
148
+ return [connectEnd - sslStart, sslStart - connectStart];
149
+ } else if (connectStart >= 0 && connectEnd >= 0) {
150
+ return connectEnd - connectStart;
150
151
  }
151
152
  });
152
153
  }
@@ -219,6 +219,13 @@ export class NetworkRequest {
219
219
  * LR loses protocol information.
220
220
  */
221
221
  _updateProtocolForLightrider(): void;
222
+ /**
223
+ * TODO(compat): remove M116.
224
+ * `timing.receiveHeadersStart` was added recently, and will be in M116. Until then,
225
+ * set it to receiveHeadersEnd, which is close enough, to allow consumers of NetworkRequest
226
+ * to use the new field without accounting for this backcompat.
227
+ */
228
+ _backfillReceiveHeaderStartTiming(): void;
222
229
  /**
223
230
  * LR gets additional, accurate timing information from its underlying fetch infrastructure. This
224
231
  * is passed in via X-Headers similar to 'X-TotalFetchedSize'.
@@ -274,6 +274,7 @@ class NetworkRequest {
274
274
  }
275
275
 
276
276
  this._updateResponseHeadersEndTimeIfNecessary();
277
+ this._backfillReceiveHeaderStartTiming();
277
278
  this._updateTransferSizeForLightrider();
278
279
  this._updateTimingsForLightrider();
279
280
  }
@@ -293,6 +294,7 @@ class NetworkRequest {
293
294
  this.localizedFailDescription = data.errorText;
294
295
 
295
296
  this._updateResponseHeadersEndTimeIfNecessary();
297
+ this._backfillReceiveHeaderStartTiming();
296
298
  this._updateTransferSizeForLightrider();
297
299
  this._updateTimingsForLightrider();
298
300
  }
@@ -315,6 +317,7 @@ class NetworkRequest {
315
317
  this.networkEndTime = data.timestamp * 1000;
316
318
 
317
319
  this._updateResponseHeadersEndTimeIfNecessary();
320
+ this._backfillReceiveHeaderStartTiming();
318
321
  }
319
322
 
320
323
  /**
@@ -449,6 +452,19 @@ class NetworkRequest {
449
452
  }
450
453
  }
451
454
 
455
+ /**
456
+ * TODO(compat): remove M116.
457
+ * `timing.receiveHeadersStart` was added recently, and will be in M116. Until then,
458
+ * set it to receiveHeadersEnd, which is close enough, to allow consumers of NetworkRequest
459
+ * to use the new field without accounting for this backcompat.
460
+ */
461
+ _backfillReceiveHeaderStartTiming() {
462
+ // Do nothing if a value is already present!
463
+ if (!this.timing || this.timing.receiveHeadersStart !== undefined) return;
464
+
465
+ this.timing.receiveHeadersStart = this.timing.receiveHeadersEnd;
466
+ }
467
+
452
468
  /**
453
469
  * LR gets additional, accurate timing information from its underlying fetch infrastructure. This
454
470
  * is passed in via X-Headers similar to 'X-TotalFetchedSize'.
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "lighthouse",
3
3
  "type": "module",
4
- "version": "10.2.0-dev.20230610",
4
+ "version": "10.2.0-dev.20230612",
5
5
  "description": "Automated auditing, performance metrics, and best practices for the web.",
6
6
  "main": "./core/index.js",
7
7
  "bin": {
@@ -802,7 +802,6 @@ declare module Artifacts {
802
802
  maxPotentialFID: number | undefined;
803
803
  cumulativeLayoutShift: number | undefined;
804
804
  cumulativeLayoutShiftMainFrame: number | undefined;
805
- totalCumulativeLayoutShift: number | undefined;
806
805
  totalBlockingTime: number | undefined;
807
806
  observedTimeOrigin: number;
808
807
  observedTimeOriginTs: number;
@@ -810,7 +809,6 @@ declare module Artifacts {
810
809
  observedNavigationStartTs: number | undefined;
811
810
  observedCumulativeLayoutShift: number | undefined;
812
811
  observedCumulativeLayoutShiftMainFrame: number | undefined;
813
- observedTotalCumulativeLayoutShift: number | undefined;
814
812
  observedFirstPaint: number | undefined;
815
813
  observedFirstPaintTs: number | undefined;
816
814
  observedFirstContentfulPaint: number | undefined;