lighthouse 10.4.0-dev.20230801 → 10.4.0-dev.20230802

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.
@@ -2,12 +2,29 @@ export default UsesHTTP2Audit;
2
2
  export type Simulator = import('../../lib/dependency-graph/simulator/simulator').Simulator;
3
3
  export type Node = import('../../lib/dependency-graph/base-node.js').Node;
4
4
  declare class UsesHTTP2Audit extends Audit {
5
+ /**
6
+ * Computes the estimated effect of all results being converted to http/2 on the provided graph.
7
+ *
8
+ * @param {Array<{url: string}>} results
9
+ * @param {Node} graph
10
+ * @param {Simulator} simulator
11
+ * @param {{label?: string}=} options
12
+ * @return {{savings: number, simulationBefore: LH.Gatherer.Simulation.Result, simulationAfter: LH.Gatherer.Simulation.Result}}
13
+ */
14
+ static computeWasteWithGraph(results: Array<{
15
+ url: string;
16
+ }>, graph: Node, simulator: Simulator, options?: {
17
+ label?: string;
18
+ } | undefined): {
19
+ savings: number;
20
+ simulationBefore: LH.Gatherer.Simulation.Result;
21
+ simulationAfter: LH.Gatherer.Simulation.Result;
22
+ };
5
23
  /**
6
24
  * Computes the estimated effect all results being converted to use http/2, the max of:
7
25
  *
8
26
  * - end time of the last long task in the provided graph
9
27
  * - end time of the last node in the graph
10
- *
11
28
  * @param {Array<{url: string}>} results
12
29
  * @param {Node} graph
13
30
  * @param {Simulator} simulator
@@ -21,6 +21,8 @@ import {NetworkRequest} from '../../lib/network-request.js';
21
21
  import {NetworkRecords} from '../../computed/network-records.js';
22
22
  import {LoadSimulator} from '../../computed/load-simulator.js';
23
23
  import {PageDependencyGraph} from '../../computed/page-dependency-graph.js';
24
+ import {LanternLargestContentfulPaint} from '../../computed/metrics/lantern-largest-contentful-paint.js';
25
+ import {LanternFirstContentfulPaint} from '../../computed/metrics/lantern-first-contentful-paint.js';
24
26
  import * as i18n from '../../lib/i18n/i18n.js';
25
27
 
26
28
  const UIStrings = {
@@ -66,23 +68,23 @@ class UsesHTTP2Audit extends Audit {
66
68
  }
67
69
 
68
70
  /**
69
- * Computes the estimated effect all results being converted to use http/2, the max of:
70
- *
71
- * - end time of the last long task in the provided graph
72
- * - end time of the last node in the graph
71
+ * Computes the estimated effect of all results being converted to http/2 on the provided graph.
73
72
  *
74
73
  * @param {Array<{url: string}>} results
75
74
  * @param {Node} graph
76
75
  * @param {Simulator} simulator
77
- * @return {number}
76
+ * @param {{label?: string}=} options
77
+ * @return {{savings: number, simulationBefore: LH.Gatherer.Simulation.Result, simulationAfter: LH.Gatherer.Simulation.Result}}
78
78
  */
79
- static computeWasteWithTTIGraph(results, graph, simulator) {
80
- const beforeLabel = `uses-http2-before`;
81
- const afterLabel = `uses-http2-after`;
79
+ static computeWasteWithGraph(results, graph, simulator, options) {
80
+ options = Object.assign({label: ''}, options);
81
+ const beforeLabel = `${this.meta.id}-${options.label}-before`;
82
+ const afterLabel = `${this.meta.id}-${options.label}-after`;
82
83
  const flexibleOrdering = true;
83
84
 
84
85
  const urlsToChange = new Set(results.map(result => result.url));
85
- const simulationBefore = simulator.simulate(graph, {label: beforeLabel, flexibleOrdering});
86
+ const simulationBefore =
87
+ simulator.simulate(graph, {label: beforeLabel, flexibleOrdering});
86
88
 
87
89
  // Update all the protocols to reflect implementing our recommendations
88
90
  /** @type {Map<string, string>} */
@@ -105,9 +107,36 @@ class UsesHTTP2Audit extends Audit {
105
107
  node.record.protocol = originalProtocol;
106
108
  });
107
109
 
108
- const savingsOnOverallLoad = simulationBefore.timeInMs - simulationAfter.timeInMs;
109
- const savingsOnTTI = LanternInteractive.getLastLongTaskEndTime(simulationBefore.nodeTimings) -
110
+ const savings = simulationBefore.timeInMs - simulationAfter.timeInMs;
111
+
112
+ return {
113
+ // Round waste to nearest 10ms
114
+ savings: Math.round(Math.max(savings, 0) / 10) * 10,
115
+ simulationBefore,
116
+ simulationAfter,
117
+ };
118
+ }
119
+
120
+ /**
121
+ * Computes the estimated effect all results being converted to use http/2, the max of:
122
+ *
123
+ * - end time of the last long task in the provided graph
124
+ * - end time of the last node in the graph
125
+ * @param {Array<{url: string}>} results
126
+ * @param {Node} graph
127
+ * @param {Simulator} simulator
128
+ * @return {number}
129
+ */
130
+ static computeWasteWithTTIGraph(results, graph, simulator) {
131
+ const {savings: savingsOnOverallLoad, simulationBefore, simulationAfter} =
132
+ this.computeWasteWithGraph(results, graph, simulator, {
133
+ label: 'tti',
134
+ });
135
+
136
+ const savingsOnTTI =
137
+ LanternInteractive.getLastLongTaskEndTime(simulationBefore.nodeTimings) -
110
138
  LanternInteractive.getLastLongTaskEndTime(simulationAfter.nodeTimings);
139
+
111
140
  const savings = Math.max(savingsOnTTI, savingsOnOverallLoad);
112
141
 
113
142
  // Round waste to nearest 10ms
@@ -237,9 +266,25 @@ class UsesHTTP2Audit extends Audit {
237
266
  devtoolsLog,
238
267
  settings,
239
268
  };
240
- const graph = await PageDependencyGraph.request({trace, devtoolsLog, URL}, context);
269
+ const pageGraph = await PageDependencyGraph.request({trace, devtoolsLog, URL}, context);
241
270
  const simulator = await LoadSimulator.request(simulatorOptions, context);
242
- const wastedMs = UsesHTTP2Audit.computeWasteWithTTIGraph(resources, graph, simulator);
271
+ const metricComputationInput = Audit.makeMetricComputationDataInput(artifacts, context);
272
+
273
+ const {
274
+ pessimisticGraph: fcpGraph,
275
+ } = await LanternFirstContentfulPaint.request(metricComputationInput, context);
276
+ const {
277
+ pessimisticGraph: lcpGraph,
278
+ } = await LanternLargestContentfulPaint.request(metricComputationInput, context);
279
+
280
+ const wastedMsTti = UsesHTTP2Audit.computeWasteWithTTIGraph(
281
+ resources, pageGraph, simulator);
282
+ const wasteFcp =
283
+ UsesHTTP2Audit.computeWasteWithGraph(resources,
284
+ fcpGraph, simulator, {label: 'fcp'});
285
+ const wasteLcp =
286
+ UsesHTTP2Audit.computeWasteWithGraph(resources,
287
+ lcpGraph, simulator, {label: 'lcp'});
243
288
 
244
289
  /** @type {LH.Audit.Details.Opportunity['headings']} */
245
290
  const headings = [
@@ -248,14 +293,15 @@ class UsesHTTP2Audit extends Audit {
248
293
  ];
249
294
 
250
295
  const details = Audit.makeOpportunityDetails(headings, resources,
251
- {overallSavingsMs: wastedMs});
296
+ {overallSavingsMs: wastedMsTti});
252
297
 
253
298
  return {
254
299
  displayValue,
255
- numericValue: wastedMs,
300
+ numericValue: wastedMsTti,
256
301
  numericUnit: 'millisecond',
257
- score: ByteEfficiencyAudit.scoreForWastedMs(wastedMs),
302
+ score: ByteEfficiencyAudit.scoreForWastedMs(wastedMsTti),
258
303
  details,
304
+ metricSavings: {LCP: wasteLcp.savings, FCP: wasteFcp.savings},
259
305
  };
260
306
  }
261
307
  }
@@ -1,11 +1,5 @@
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>;
9
3
  /**
10
4
  * @param {LH.Artifacts} artifacts
11
5
  * @return {LH.Audit.Details.Table|undefined}
@@ -4,10 +4,13 @@
4
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
5
  */
6
6
 
7
+ import log from 'lighthouse-logger';
8
+
7
9
  import {Audit} from './audit.js';
8
10
  import * as i18n from '../lib/i18n/i18n.js';
9
11
  import {LargestContentfulPaint} from '../computed/metrics/largest-contentful-paint.js';
10
12
  import {LCPBreakdown} from '../computed/metrics/lcp-breakdown.js';
13
+ import {Sentry} from '../lib/sentry.js';
11
14
 
12
15
  const UIStrings = {
13
16
  /** Descriptive title of a diagnostic audit that provides the element that was determined to be the Largest Contentful Paint. */
@@ -49,19 +52,6 @@ class LargestContentfulPaintElement extends Audit {
49
52
  };
50
53
  }
51
54
 
52
- /**
53
- * @param {LH.Artifacts.MetricComputationDataInput} metricComputationData
54
- * @param {LH.Audit.Context} context
55
- * @return {Promise<number|undefined>}
56
- */
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
55
  /**
66
56
  * @param {LH.Artifacts} artifacts
67
57
  * @return {LH.Audit.Details.Table|undefined}
@@ -139,11 +129,19 @@ class LargestContentfulPaintElement extends Audit {
139
129
  const items = [elementTable];
140
130
  let displayValue;
141
131
 
142
- const metricLcp = await this.getOptionalLCPMetric(metricComputationData, context);
143
- if (metricLcp) {
132
+ try {
133
+ const {timing: metricLcp} =
134
+ await LargestContentfulPaint.request(metricComputationData, context);
144
135
  displayValue = str_(i18n.UIStrings.ms, {timeInMs: metricLcp});
136
+
145
137
  const phaseTable = await this.makePhaseTable(metricLcp, metricComputationData, context);
146
138
  items.push(phaseTable);
139
+ } catch (err) {
140
+ Sentry.captureException(err, {
141
+ tags: {audit: this.meta.id},
142
+ level: 'error',
143
+ });
144
+ log.error(this.meta.id, err.message);
147
145
  }
148
146
 
149
147
  const details = Audit.makeListDetails(items);
@@ -129,7 +129,7 @@ const defaultSettings = {
129
129
 
130
130
  /** @type {Required<LH.Config.NavigationJson>} */
131
131
  const defaultNavigationConfig = {
132
- id: 'default',
132
+ id: 'defaultPass',
133
133
  loadFailureMode: 'fatal',
134
134
  disableThrottling: false,
135
135
  disableStorageReset: false,
@@ -593,12 +593,12 @@ const defaultConfig = {
593
593
  supportedModes: ['navigation', 'timespan', 'snapshot'],
594
594
  auditRefs: [
595
595
  // Trust & Safety
596
- {id: 'is-on-https', weight: 1, group: 'best-practices-trust-safety'},
596
+ {id: 'is-on-https', weight: 5, group: 'best-practices-trust-safety'},
597
597
  {id: 'geolocation-on-start', weight: 1, group: 'best-practices-trust-safety'},
598
598
  {id: 'notification-on-start', weight: 1, group: 'best-practices-trust-safety'},
599
599
  {id: 'csp-xss', weight: 0, group: 'best-practices-trust-safety'},
600
600
  // User Experience
601
- {id: 'paste-preventing-inputs', weight: 1, group: 'best-practices-ux'},
601
+ {id: 'paste-preventing-inputs', weight: 3, group: 'best-practices-ux'},
602
602
  {id: 'image-aspect-ratio', weight: 1, group: 'best-practices-ux'},
603
603
  {id: 'image-size-responsive', weight: 1, group: 'best-practices-ux'},
604
604
  {id: 'preload-fonts', weight: 1, group: 'best-practices-ux'},
@@ -608,7 +608,7 @@ const defaultConfig = {
608
608
  // General Group
609
609
  {id: 'no-unload-listeners', weight: 1, group: 'best-practices-general'},
610
610
  {id: 'js-libraries', weight: 0, group: 'best-practices-general'},
611
- {id: 'deprecations', weight: 1, group: 'best-practices-general'},
611
+ {id: 'deprecations', weight: 5, group: 'best-practices-general'},
612
612
  {id: 'errors-in-console', weight: 1, group: 'best-practices-general'},
613
613
  {id: 'valid-source-maps', weight: 0, group: 'best-practices-general'},
614
614
  {id: 'inspector-issues', weight: 1, group: 'best-practices-general'},
@@ -188,8 +188,14 @@ async function _computeNavigationResult(
188
188
  /** @type {Partial<LH.GathererArtifacts>} */
189
189
  const artifacts = {};
190
190
  const pageLoadErrorId = `pageLoadError-${navigationContext.navigation.id}`;
191
- if (debugData.devtoolsLog) artifacts.devtoolsLogs = {[pageLoadErrorId]: debugData.devtoolsLog};
192
- if (debugData.trace) artifacts.traces = {[pageLoadErrorId]: debugData.trace};
191
+ if (debugData.devtoolsLog) {
192
+ artifacts.DevtoolsLogError = debugData.devtoolsLog;
193
+ artifacts.devtoolsLogs = {[pageLoadErrorId]: debugData.devtoolsLog};
194
+ }
195
+ if (debugData.trace) {
196
+ artifacts.TraceError = debugData.trace;
197
+ artifacts.traces = {[pageLoadErrorId]: debugData.trace};
198
+ }
193
199
 
194
200
  return {
195
201
  pageLoadError,
@@ -62,6 +62,9 @@ function loadArtifacts(basePath) {
62
62
  if (passName === 'defaultPass') {
63
63
  artifacts.DevtoolsLog = devtoolsLog;
64
64
  }
65
+ if (passName === 'pageLoadError-defaultPass') {
66
+ artifacts.DevtoolsLogError = devtoolsLog;
67
+ }
65
68
  });
66
69
 
67
70
  // load traces
@@ -74,6 +77,9 @@ function loadArtifacts(basePath) {
74
77
  if (passName === 'defaultPass') {
75
78
  artifacts.Trace = artifacts.traces[passName];
76
79
  }
80
+ if (passName === 'pageLoadError-defaultPass') {
81
+ artifacts.TraceError = artifacts.traces[passName];
82
+ }
77
83
  });
78
84
 
79
85
  if (Array.isArray(artifacts.Timing)) {
@@ -489,7 +489,7 @@ class NetworkRequest {
489
489
  // Bail if there was no totalTime.
490
490
  if (!totalHeader) return;
491
491
 
492
- const totalMs = parseInt(totalHeader.value);
492
+ let totalMs = parseInt(totalHeader.value);
493
493
  const TCPMsHeader = this.responseHeaders.find(item => item.name === HEADER_TCP);
494
494
  const SSLMsHeader = this.responseHeaders.find(item => item.name === HEADER_SSL);
495
495
  const requestMsHeader = this.responseHeaders.find(item => item.name === HEADER_REQ);
@@ -502,11 +502,20 @@ class NetworkRequest {
502
502
  const requestMs = requestMsHeader ? Math.max(0, parseInt(requestMsHeader.value)) : 0;
503
503
  const responseMs = responseMsHeader ? Math.max(0, parseInt(responseMsHeader.value)) : 0;
504
504
 
505
- // Bail if the timings don't add up.
506
- if (TCPMs + requestMs + responseMs !== totalMs) {
505
+ if (Number.isNaN(TCPMs + requestMs + responseMs + totalMs)) {
507
506
  return;
508
507
  }
509
508
 
509
+ // If things don't add up, tweak the total a bit.
510
+ if (TCPMs + requestMs + responseMs !== totalMs) {
511
+ const delta = Math.abs(TCPMs + requestMs + responseMs - totalMs);
512
+ // We didn't see total being more than 5ms less than the total of the components.
513
+ // Allow some discrepancy in the timing, but not too much.
514
+ if (delta >= 25) return;
515
+
516
+ totalMs = TCPMs + requestMs + responseMs;
517
+ }
518
+
510
519
  // Bail if SSL time is > TCP time.
511
520
  if (SSLMs > TCPMs) {
512
521
  return;
package/core/runner.js CHANGED
@@ -367,6 +367,8 @@ class Runner {
367
367
 
368
368
  let auditResult;
369
369
  try {
370
+ if (artifacts.PageLoadError) throw artifacts.PageLoadError;
371
+
370
372
  // Return an early error if an artifact required for the audit is missing or an error.
371
373
  for (const artifactName of audit.meta.requiredArtifacts) {
372
374
  const noArtifact = artifacts[artifactName] === undefined;
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "lighthouse",
3
3
  "type": "module",
4
- "version": "10.4.0-dev.20230801",
4
+ "version": "10.4.0-dev.20230802",
5
5
  "description": "Automated auditing, performance metrics, and best practices for the web.",
6
6
  "main": "./core/index.js",
7
7
  "bin": {
@@ -108,6 +108,8 @@ export interface GathererArtifacts extends PublicGathererArtifacts {
108
108
  CSSUsage: {rules: Crdp.CSS.RuleUsage[], stylesheets: Artifacts.CSSStyleSheetInfo[]};
109
109
  /** The primary log of devtools protocol activity. */
110
110
  DevtoolsLog: DevtoolsLog;
111
+ /** The log of devtools protocol activity if there was a page load error and Chrome navigated to a `chrome-error://` page. */
112
+ DevtoolsLogError: DevtoolsLog;
111
113
  /** Information on the document's doctype(or null if not present), specifically the name, publicId, and systemId.
112
114
  All properties default to an empty string if not present */
113
115
  Doctype: Artifacts.Doctype | null;
@@ -152,6 +154,8 @@ export interface GathererArtifacts extends PublicGathererArtifacts {
152
154
  TapTargets: Artifacts.TapTarget[];
153
155
  /** The primary trace taken over the entire run. */
154
156
  Trace: Trace;
157
+ /** The trace if there was a page load error and Chrome navigated to a `chrome-error://` page. */
158
+ TraceError: Trace;
155
159
  /** Elements associated with metrics (ie: Largest Contentful Paint element). */
156
160
  TraceElements: Artifacts.TraceElement[];
157
161
  /** Parsed version of the page's Web App Manifest, or null if none found. */