lighthouse 11.0.0-dev.20230814 → 11.0.0-dev.20230816

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 (65) hide show
  1. package/core/audits/dobetterweb/dom-size.d.ts +8 -2
  2. package/core/audits/dobetterweb/dom-size.js +50 -3
  3. package/core/audits/installable-manifest.d.ts +0 -6
  4. package/core/audits/installable-manifest.js +1 -17
  5. package/core/audits/largest-contentful-paint-element.js +21 -4
  6. package/core/audits/lcp-lazy-loaded.d.ts +3 -2
  7. package/core/audits/lcp-lazy-loaded.js +34 -5
  8. package/core/audits/third-party-facades.js +13 -5
  9. package/core/audits/third-party-summary.d.ts +4 -2
  10. package/core/audits/third-party-summary.js +24 -11
  11. package/core/computed/tbt-impact-tasks.d.ts +8 -13
  12. package/core/computed/tbt-impact-tasks.js +4 -6
  13. package/core/index.cjs +13 -13
  14. package/core/index.d.cts +15 -2
  15. package/package.json +2 -2
  16. package/shared/localization/locales/ar-XB.json +0 -3
  17. package/shared/localization/locales/ar.json +0 -3
  18. package/shared/localization/locales/bg.json +0 -3
  19. package/shared/localization/locales/ca.json +0 -3
  20. package/shared/localization/locales/cs.json +0 -3
  21. package/shared/localization/locales/da.json +0 -3
  22. package/shared/localization/locales/de.json +0 -3
  23. package/shared/localization/locales/el.json +0 -3
  24. package/shared/localization/locales/en-GB.json +0 -3
  25. package/shared/localization/locales/en-US.json +0 -3
  26. package/shared/localization/locales/en-XA.json +0 -3
  27. package/shared/localization/locales/en-XL.json +0 -3
  28. package/shared/localization/locales/es-419.json +0 -3
  29. package/shared/localization/locales/es.json +0 -3
  30. package/shared/localization/locales/fi.json +0 -3
  31. package/shared/localization/locales/fil.json +0 -3
  32. package/shared/localization/locales/fr.json +0 -3
  33. package/shared/localization/locales/he.json +0 -3
  34. package/shared/localization/locales/hi.json +0 -3
  35. package/shared/localization/locales/hr.json +0 -3
  36. package/shared/localization/locales/hu.json +0 -3
  37. package/shared/localization/locales/id.json +0 -3
  38. package/shared/localization/locales/it.json +0 -3
  39. package/shared/localization/locales/ja.json +0 -3
  40. package/shared/localization/locales/ko.json +0 -3
  41. package/shared/localization/locales/lt.json +0 -3
  42. package/shared/localization/locales/lv.json +0 -3
  43. package/shared/localization/locales/nl.json +0 -3
  44. package/shared/localization/locales/no.json +0 -3
  45. package/shared/localization/locales/pl.json +0 -3
  46. package/shared/localization/locales/pt-PT.json +0 -3
  47. package/shared/localization/locales/pt.json +0 -3
  48. package/shared/localization/locales/ro.json +0 -3
  49. package/shared/localization/locales/ru.json +0 -3
  50. package/shared/localization/locales/sk.json +0 -3
  51. package/shared/localization/locales/sl.json +0 -3
  52. package/shared/localization/locales/sr-Latn.json +0 -3
  53. package/shared/localization/locales/sr.json +0 -3
  54. package/shared/localization/locales/sv.json +0 -3
  55. package/shared/localization/locales/ta.json +0 -3
  56. package/shared/localization/locales/te.json +0 -3
  57. package/shared/localization/locales/th.json +0 -3
  58. package/shared/localization/locales/tr.json +0 -3
  59. package/shared/localization/locales/uk.json +0 -3
  60. package/shared/localization/locales/vi.json +0 -3
  61. package/shared/localization/locales/zh-HK.json +0 -3
  62. package/shared/localization/locales/zh-TW.json +0 -3
  63. package/shared/localization/locales/zh.json +0 -3
  64. package/third-party/chromium-synchronization/installability-errors-test.js +0 -1
  65. package/types/artifacts.d.ts +1 -0
@@ -7,9 +7,15 @@ declare class DOMSize extends Audit {
7
7
  /**
8
8
  * @param {LH.Artifacts} artifacts
9
9
  * @param {LH.Audit.Context} context
10
- * @return {LH.Audit.Product}
10
+ * @return {Promise<number|undefined>}
11
11
  */
12
- static audit(artifacts: LH.Artifacts, context: LH.Audit.Context): LH.Audit.Product;
12
+ static computeTbtImpact(artifacts: LH.Artifacts, context: LH.Audit.Context): Promise<number | undefined>;
13
+ /**
14
+ * @param {LH.Artifacts} artifacts
15
+ * @param {LH.Audit.Context} context
16
+ * @return {Promise<LH.Audit.Product>}
17
+ */
18
+ static audit(artifacts: LH.Artifacts, context: LH.Audit.Context): Promise<LH.Audit.Product>;
13
19
  }
14
20
  export namespace UIStrings {
15
21
  const title: string;
@@ -13,6 +13,7 @@
13
13
 
14
14
  import {Audit} from '../audit.js';
15
15
  import * as i18n from '../../lib/i18n/i18n.js';
16
+ import {TBTImpactTasks} from '../../computed/tbt-impact-tasks.js';
16
17
 
17
18
  const UIStrings = {
18
19
  /** Title of a diagnostic audit that provides detail on the size of the web page's DOM. The size of a DOM is characterized by the total number of DOM elements and greatest DOM depth. This descriptive title is shown to users when the amount is acceptable and no user action is required. */
@@ -53,7 +54,8 @@ class DOMSize extends Audit {
53
54
  failureTitle: str_(UIStrings.failureTitle),
54
55
  description: str_(UIStrings.description),
55
56
  scoreDisplayMode: Audit.SCORING_MODES.NUMERIC,
56
- requiredArtifacts: ['DOMStats'],
57
+ requiredArtifacts: ['DOMStats', 'URL', 'GatherContext'],
58
+ __internalOptionalArtifacts: ['traces', 'devtoolsLogs'],
57
59
  };
58
60
  }
59
61
 
@@ -69,13 +71,53 @@ class DOMSize extends Audit {
69
71
  };
70
72
  }
71
73
 
74
+ /**
75
+ * @param {LH.Artifacts} artifacts
76
+ * @param {LH.Audit.Context} context
77
+ * @return {Promise<number|undefined>}
78
+ */
79
+ static async computeTbtImpact(artifacts, context) {
80
+ let tbtImpact = 0;
81
+
82
+ // We still want to surface this audit in snapshot mode, but since we don't compute TBT
83
+ // the impact should always be undefined.
84
+ const {GatherContext, devtoolsLogs, traces} = artifacts;
85
+ if (GatherContext.gatherMode !== 'navigation') {
86
+ return undefined;
87
+ }
88
+
89
+ // Since the artifacts are optional, it's still possible for them to be missing in navigation mode.
90
+ // Navigation mode does compute TBT so we should surface a numerical savings of 0.
91
+ if (!devtoolsLogs?.[Audit.DEFAULT_PASS] || !traces?.[Audit.DEFAULT_PASS]) {
92
+ return 0;
93
+ }
94
+
95
+ const metricComputationData = Audit.makeMetricComputationDataInput(artifacts, context);
96
+
97
+ try {
98
+ // The TBT impact of style/layout tasks is correlated to the DOM size.
99
+ // Even in situations where the page forces a style recalc, the DOM size is partially to blame
100
+ // for any time spent blocking the main thread.
101
+ //
102
+ // `tbtImpact` should be exactly 0 for small DOMs since `selfTbtImpact` accounts for the blocking
103
+ // time and not the main thread time.
104
+ const tbtImpactTasks = await TBTImpactTasks.request(metricComputationData, context);
105
+ for (const task of tbtImpactTasks) {
106
+ if (task.group.id !== 'styleLayout') continue;
107
+ tbtImpact += task.selfTbtImpact;
108
+ }
109
+ } catch {}
110
+
111
+ return Math.round(tbtImpact);
112
+ }
113
+
72
114
 
73
115
  /**
74
116
  * @param {LH.Artifacts} artifacts
75
117
  * @param {LH.Audit.Context} context
76
- * @return {LH.Audit.Product}
118
+ * @return {Promise<LH.Audit.Product>}
77
119
  */
78
- static audit(artifacts, context) {
120
+ static async audit(artifacts, context) {
79
121
  const stats = artifacts.DOMStats;
80
122
 
81
123
  const score = Audit.computeLogNormalScore(
@@ -120,12 +162,17 @@ class DOMSize extends Audit {
120
162
  },
121
163
  ];
122
164
 
165
+ const tbtImpact = await this.computeTbtImpact(artifacts, context);
166
+
123
167
  return {
124
168
  score,
125
169
  numericValue: stats.totalBodyElements,
126
170
  numericUnit: 'element',
127
171
  displayValue: str_(UIStrings.displayValue, {itemCount: stats.totalBodyElements}),
128
172
  details: Audit.makeTableDetails(headings, items),
173
+ metricSavings: {
174
+ TBT: tbtImpact,
175
+ },
129
176
  };
130
177
  }
131
178
  }
@@ -104,12 +104,6 @@ export const UIStrings: {
104
104
  'protocol-timeout': string;
105
105
  /** Message logged when the web app has been uninstalled o desktop, signalling that the install banner state is being reset. */
106
106
  'pipeline-restarted': string;
107
- /**
108
- * TODO: This error was removed in M114, we can remove this message when it hits stable.
109
- * @description Error message explaining that the URL of the manifest uses a scheme that is not supported on Android.
110
- * @example {data:} scheme
111
- */
112
- 'scheme-not-supported-for-webapk': string;
113
107
  };
114
108
  import { Audit } from './audit.js';
115
109
  //# sourceMappingURL=installable-manifest.d.ts.map
@@ -87,17 +87,12 @@ const UIStrings = {
87
87
  /** Error message explaining that the web manifest's URL changed while the manifest was being downloaded by the browser. */
88
88
  'manifest-location-changed': `Manifest URL changed while the manifest was being fetched.`,
89
89
  /** Warning message explaining that the page does not work offline. */
90
+ // TODO(COMPAT): This error was removed in M118, we can remove this message when it hits stable.
90
91
  'warn-not-offline-capable': `Page does not work offline. The page will not be regarded as installable after Chrome 93, stable release August 2021.`,
91
92
  /** Error message explaining that Lighthouse failed while checking if the page is installable, and directing the user to try again in a new Chrome. */
92
93
  'protocol-timeout': `Lighthouse could not determine if the page is installable. Please try with a newer version of Chrome.`,
93
94
  /** Message logged when the web app has been uninstalled o desktop, signalling that the install banner state is being reset. */
94
95
  'pipeline-restarted': 'PWA has been uninstalled and installability checks resetting.',
95
- /**
96
- * TODO: This error was removed in M114, we can remove this message when it hits stable.
97
- * @description Error message explaining that the URL of the manifest uses a scheme that is not supported on Android.
98
- * @example {data:} scheme
99
- */
100
- 'scheme-not-supported-for-webapk': 'The manifest URL scheme ({scheme}) is not supported on Android.',
101
96
  };
102
97
  /* eslint-enable max-len */
103
98
 
@@ -157,17 +152,6 @@ class InstallableManifest extends Audit {
157
152
  // @ts-expect-error errorIds from protocol should match up against the strings dict
158
153
  const matchingString = UIStrings[err.errorId];
159
154
 
160
- if (err.errorId === 'scheme-not-supported-for-webapk') {
161
- // If there was no manifest, then there will be at lest one other installability error.
162
- // We can ignore this error if that's the case.
163
- const manifestUrl = artifacts.WebAppManifest?.url;
164
- if (!manifestUrl) continue;
165
-
166
- const scheme = new URL(manifestUrl).protocol;
167
- i18nErrors.push(str_(matchingString, {scheme}));
168
- continue;
169
- }
170
-
171
155
  // Handle an errorId we don't recognize.
172
156
  if (matchingString === undefined) {
173
157
  i18nErrors.push(str_(UIStrings.noErrorId, {errorId: err.errorId}));
@@ -8,7 +8,8 @@ import log from 'lighthouse-logger';
8
8
 
9
9
  import {Audit} from './audit.js';
10
10
  import * as i18n from '../lib/i18n/i18n.js';
11
- import {LargestContentfulPaint} from '../computed/metrics/largest-contentful-paint.js';
11
+ import {LargestContentfulPaint as LargestContentfulPaintComputed} from '../computed/metrics/largest-contentful-paint.js';
12
+ import LargestContentfulPaint from './metrics/largest-contentful-paint.js';
12
13
  import {LCPBreakdown} from '../computed/metrics/lcp-breakdown.js';
13
14
  import {Sentry} from '../lib/sentry.js';
14
15
 
@@ -124,14 +125,22 @@ class LargestContentfulPaintElement extends Audit {
124
125
  settings: context.settings, URL: artifacts.URL};
125
126
 
126
127
  const elementTable = this.makeElementTable(artifacts);
127
- if (!elementTable) return {score: null, notApplicable: true};
128
+ if (!elementTable) {
129
+ return {
130
+ score: null,
131
+ notApplicable: true,
132
+ metricSavings: {LCP: 0},
133
+ };
134
+ }
128
135
 
129
136
  const items = [elementTable];
130
137
  let displayValue;
138
+ let metricLcp = 0;
131
139
 
132
140
  try {
133
- const {timing: metricLcp} =
134
- await LargestContentfulPaint.request(metricComputationData, context);
141
+ const lcpResult =
142
+ await LargestContentfulPaintComputed.request(metricComputationData, context);
143
+ metricLcp = lcpResult.timing;
135
144
  displayValue = str_(i18n.UIStrings.ms, {timeInMs: metricLcp});
136
145
 
137
146
  const phaseTable = await this.makePhaseTable(metricLcp, metricComputationData, context);
@@ -146,10 +155,18 @@ class LargestContentfulPaintElement extends Audit {
146
155
 
147
156
  const details = Audit.makeListDetails(items);
148
157
 
158
+ // Conceptually, this doesn't make much sense as "savings" for this audit since there isn't anything to "fix".
159
+ // However, this audit will always be useful when improving LCP and that should be reflected in our impact calculations.
160
+ const idealLcp = LargestContentfulPaint.defaultOptions[context.settings.formFactor].scoring.p10;
161
+ const lcpSavings = Math.max(0, metricLcp - idealLcp);
162
+
149
163
  return {
150
164
  score: 1,
151
165
  displayValue,
152
166
  details,
167
+ metricSavings: {
168
+ LCP: lcpSavings,
169
+ },
153
170
  };
154
171
  }
155
172
  }
@@ -8,9 +8,10 @@ declare class LargestContentfulPaintLazyLoaded extends Audit {
8
8
  static isImageInViewport(image: LH.Artifacts.ImageElement, viewportDimensions: LH.Artifacts.ViewportDimensions): boolean;
9
9
  /**
10
10
  * @param {LH.Artifacts} artifacts
11
- * @return {LH.Audit.Product}
11
+ * @param {LH.Audit.Context} context
12
+ * @return {Promise<LH.Audit.Product>}
12
13
  */
13
- static audit(artifacts: LH.Artifacts): LH.Audit.Product;
14
+ static audit(artifacts: LH.Artifacts, context: LH.Audit.Context): Promise<LH.Audit.Product>;
14
15
  }
15
16
  export namespace UIStrings {
16
17
  const title: string;
@@ -6,6 +6,8 @@
6
6
 
7
7
  import {Audit} from './audit.js';
8
8
  import * as i18n from '../lib/i18n/i18n.js';
9
+ import {LCPBreakdown} from '../computed/metrics/lcp-breakdown.js';
10
+ import {LargestContentfulPaint} from '../computed/metrics/largest-contentful-paint.js';
9
11
 
10
12
  const UIStrings = {
11
13
  /** Title of a Lighthouse audit that provides detail on whether the largest above-the-fold image was loaded with sufficient priority. This descriptive title is shown to users when the image was loaded properly. */
@@ -18,6 +20,8 @@ const UIStrings = {
18
20
 
19
21
  const str_ = i18n.createIcuMessageFn(import.meta.url, UIStrings);
20
22
 
23
+ const ESTIMATED_PERCENT_SAVINGS = 0.15;
24
+
21
25
  class LargestContentfulPaintLazyLoaded extends Audit {
22
26
  /**
23
27
  * @return {LH.Audit.Meta}
@@ -29,7 +33,8 @@ class LargestContentfulPaintLazyLoaded extends Audit {
29
33
  failureTitle: str_(UIStrings.failureTitle),
30
34
  description: str_(UIStrings.description),
31
35
  supportedModes: ['navigation'],
32
- requiredArtifacts: ['TraceElements', 'ViewportDimensions', 'ImageElements'],
36
+ requiredArtifacts: ['TraceElements', 'ViewportDimensions', 'ImageElements',
37
+ 'traces', 'devtoolsLogs', 'GatherContext', 'URL'],
33
38
  };
34
39
  }
35
40
 
@@ -46,9 +51,10 @@ class LargestContentfulPaintLazyLoaded extends Audit {
46
51
 
47
52
  /**
48
53
  * @param {LH.Artifacts} artifacts
49
- * @return {LH.Audit.Product}
54
+ * @param {LH.Audit.Context} context
55
+ * @return {Promise<LH.Audit.Product>}
50
56
  */
51
- static audit(artifacts) {
57
+ static async audit(artifacts, context) {
52
58
  const lcpElement = artifacts.TraceElements.find(element => {
53
59
  return element.traceEventType === 'largest-contentful-paint' && element.type === 'image';
54
60
  });
@@ -59,7 +65,11 @@ class LargestContentfulPaintLazyLoaded extends Audit {
59
65
 
60
66
  if (!lcpElementImage ||
61
67
  !this.isImageInViewport(lcpElementImage, artifacts.ViewportDimensions)) {
62
- return {score: null, notApplicable: true};
68
+ return {
69
+ score: null,
70
+ notApplicable: true,
71
+ metricSavings: {LCP: 0},
72
+ };
63
73
  }
64
74
 
65
75
  /** @type {LH.Audit.Details.Table['headings']} */
@@ -73,8 +83,27 @@ class LargestContentfulPaintLazyLoaded extends Audit {
73
83
  },
74
84
  ]);
75
85
 
86
+ const wasLazyLoaded = lcpElementImage.loading === 'lazy';
87
+
88
+ const metricComputationData = Audit.makeMetricComputationDataInput(artifacts, context);
89
+ const {timing: metricLcp} =
90
+ await LargestContentfulPaint.request(metricComputationData, context);
91
+ const lcpBreakdown = await LCPBreakdown.request(metricComputationData, context);
92
+ let lcpSavings = 0;
93
+ if (wasLazyLoaded && lcpBreakdown.loadStart !== undefined) {
94
+ // Estimate the LCP savings using a statistical percentage.
95
+ // https://web.dev/lcp-lazy-loading/#causal-performance
96
+ //
97
+ // LCP savings will be at most the LCP load delay.
98
+ const lcpLoadDelay = lcpBreakdown.loadStart - lcpBreakdown.ttfb;
99
+ lcpSavings = Math.min(metricLcp * ESTIMATED_PERCENT_SAVINGS, lcpLoadDelay);
100
+ }
101
+
76
102
  return {
77
- score: lcpElementImage.loading === 'lazy' ? 0 : 1,
103
+ score: wasLazyLoaded ? 0 : 1,
104
+ metricSavings: {
105
+ LCP: lcpSavings,
106
+ },
78
107
  details,
79
108
  };
80
109
  }
@@ -24,8 +24,8 @@ import * as i18n from '../lib/i18n/i18n.js';
24
24
  import {EntityClassification} from '../computed/entity-classification.js';
25
25
  import thirdPartyWeb from '../lib/third-party-web.js';
26
26
  import {NetworkRecords} from '../computed/network-records.js';
27
- import {MainThreadTasks} from '../computed/main-thread-tasks.js';
28
27
  import ThirdPartySummary from './third-party-summary.js';
28
+ import {TBTImpactTasks} from '../computed/tbt-impact-tasks.js';
29
29
 
30
30
  const UIStrings = {
31
31
  /** Title of a diagnostic audit that provides details about the third-party code on a web page that can be lazy loaded with a facade alternative. This descriptive title is shown to users when no resources have facade alternatives available. A facade is a lightweight component which looks like the desired resource. Lazy loading means resources are deferred until they are needed. Third-party code refers to resources that are not within the control of the site owner. */
@@ -86,7 +86,7 @@ class ThirdPartyFacades extends Audit {
86
86
  failureTitle: str_(UIStrings.failureTitle),
87
87
  description: str_(UIStrings.description),
88
88
  supportedModes: ['navigation'],
89
- requiredArtifacts: ['traces', 'devtoolsLogs', 'URL'],
89
+ requiredArtifacts: ['traces', 'devtoolsLogs', 'URL', 'GatherContext'],
90
90
  };
91
91
  }
92
92
 
@@ -148,19 +148,23 @@ class ThirdPartyFacades extends Audit {
148
148
  */
149
149
  static async audit(artifacts, context) {
150
150
  const settings = context.settings;
151
- const trace = artifacts.traces[Audit.DEFAULT_PASS];
152
151
  const devtoolsLog = artifacts.devtoolsLogs[Audit.DEFAULT_PASS];
153
152
  const networkRecords = await NetworkRecords.request(devtoolsLog, context);
154
153
  const classifiedEntities = await EntityClassification.request(
155
154
  {URL: artifacts.URL, devtoolsLog}, context);
156
- const tasks = await MainThreadTasks.request(trace, context);
155
+
156
+ const metricComputationData = Audit.makeMetricComputationDataInput(artifacts, context);
157
+ const tbtImpactTasks = await TBTImpactTasks.request(metricComputationData, context);
158
+
157
159
  const multiplier = settings.throttlingMethod === 'simulate' ?
158
160
  settings.throttling.cpuSlowdownMultiplier : 1;
159
- const summaries = ThirdPartySummary.getSummaries(networkRecords, tasks, multiplier,
161
+ const summaries = ThirdPartySummary.getSummaries(networkRecords, tbtImpactTasks, multiplier,
160
162
  classifiedEntities);
161
163
  const facadableProducts =
162
164
  ThirdPartyFacades.getProductsWithFacade(summaries.byURL, classifiedEntities);
163
165
 
166
+ let tbtImpact = 0;
167
+
164
168
  /** @type {LH.Audit.Details.TableItem[]} */
165
169
  const results = [];
166
170
  for (const {product, entity} of facadableProducts) {
@@ -179,6 +183,8 @@ class ThirdPartyFacades extends Audit {
179
183
  const entitySummary = summaries.byEntity.get(entity);
180
184
  if (!urls || !entitySummary) continue;
181
185
 
186
+ tbtImpact += entitySummary.tbtImpact;
187
+
182
188
  const items = Array.from(urls).map((url) => {
183
189
  const urlStats = summaries.byURL.get(url);
184
190
  return /** @type {import('./third-party-summary.js').URLSummary} */ ({url, ...urlStats});
@@ -198,6 +204,7 @@ class ThirdPartyFacades extends Audit {
198
204
  return {
199
205
  score: 1,
200
206
  notApplicable: true,
207
+ metricSavings: {TBT: 0},
201
208
  };
202
209
  }
203
210
 
@@ -216,6 +223,7 @@ class ThirdPartyFacades extends Audit {
216
223
  itemCount: results.length,
217
224
  }),
218
225
  details: Audit.makeTableDetails(headings, results),
226
+ metricSavings: {TBT: tbtImpact},
219
227
  };
220
228
  }
221
229
  }
@@ -3,10 +3,12 @@ export type Summary = {
3
3
  mainThreadTime: number;
4
4
  transferSize: number;
5
5
  blockingTime: number;
6
+ tbtImpact: number;
6
7
  };
7
8
  export type URLSummary = {
8
9
  transferSize: number;
9
10
  blockingTime: number;
11
+ tbtImpact: number;
10
12
  url: string | LH.IcuMessage;
11
13
  };
12
14
  export type SummaryMaps = {
@@ -27,12 +29,12 @@ declare class ThirdPartySummary extends Audit {
27
29
  /**
28
30
  *
29
31
  * @param {Array<LH.Artifacts.NetworkRequest>} networkRecords
30
- * @param {Array<LH.Artifacts.TaskNode>} mainThreadTasks
32
+ * @param {Array<LH.Artifacts.TBTImpactTask>} tbtImpactTasks
31
33
  * @param {number} cpuMultiplier
32
34
  * @param {LH.Artifacts.EntityClassification} entityClassification
33
35
  * @return {SummaryMaps}
34
36
  */
35
- static getSummaries(networkRecords: Array<LH.Artifacts.NetworkRequest>, mainThreadTasks: Array<LH.Artifacts.TaskNode>, cpuMultiplier: number, entityClassification: LH.Artifacts.EntityClassification): SummaryMaps;
37
+ static getSummaries(networkRecords: Array<LH.Artifacts.NetworkRequest>, tbtImpactTasks: Array<LH.Artifacts.TBTImpactTask>, cpuMultiplier: number, entityClassification: LH.Artifacts.EntityClassification): SummaryMaps;
36
38
  /**
37
39
  * @param {LH.Artifacts.Entity} entity
38
40
  * @param {SummaryMaps} summaries
@@ -8,8 +8,8 @@ import {Audit} from './audit.js';
8
8
  import {EntityClassification} from '../computed/entity-classification.js';
9
9
  import * as i18n from '../lib/i18n/i18n.js';
10
10
  import {NetworkRecords} from '../computed/network-records.js';
11
- import {MainThreadTasks} from '../computed/main-thread-tasks.js';
12
11
  import {getJavaScriptURLs, getAttributableURLForTask} from '../lib/tracehouse/task-summary.js';
12
+ import {TBTImpactTasks} from '../computed/tbt-impact-tasks.js';
13
13
 
14
14
  const UIStrings = {
15
15
  /** Title of a diagnostic audit that provides details about the code on a web page that the user doesn't control (referred to as "third-party code"). This descriptive title is shown to users when the amount is acceptable and no user action is required. */
@@ -38,12 +38,14 @@ const PASS_THRESHOLD_IN_MS = 250;
38
38
  * @property {number} mainThreadTime
39
39
  * @property {number} transferSize
40
40
  * @property {number} blockingTime
41
+ * @property {number} tbtImpact
41
42
  */
42
43
 
43
44
  /**
44
45
  * @typedef URLSummary
45
46
  * @property {number} transferSize
46
47
  * @property {number} blockingTime
48
+ * @property {number} tbtImpact
47
49
  * @property {string | LH.IcuMessage} url
48
50
  */
49
51
 
@@ -72,24 +74,24 @@ class ThirdPartySummary extends Audit {
72
74
  title: str_(UIStrings.title),
73
75
  failureTitle: str_(UIStrings.failureTitle),
74
76
  description: str_(UIStrings.description),
75
- requiredArtifacts: ['traces', 'devtoolsLogs', 'URL'],
77
+ requiredArtifacts: ['traces', 'devtoolsLogs', 'URL', 'GatherContext'],
76
78
  };
77
79
  }
78
80
 
79
81
  /**
80
82
  *
81
83
  * @param {Array<LH.Artifacts.NetworkRequest>} networkRecords
82
- * @param {Array<LH.Artifacts.TaskNode>} mainThreadTasks
84
+ * @param {Array<LH.Artifacts.TBTImpactTask>} tbtImpactTasks
83
85
  * @param {number} cpuMultiplier
84
86
  * @param {LH.Artifacts.EntityClassification} entityClassification
85
87
  * @return {SummaryMaps}
86
88
  */
87
- static getSummaries(networkRecords, mainThreadTasks, cpuMultiplier, entityClassification) {
89
+ static getSummaries(networkRecords, tbtImpactTasks, cpuMultiplier, entityClassification) {
88
90
  /** @type {Map<string, Summary>} */
89
91
  const byURL = new Map();
90
92
  /** @type {Map<LH.Artifacts.Entity, Summary>} */
91
93
  const byEntity = new Map();
92
- const defaultSummary = {mainThreadTime: 0, blockingTime: 0, transferSize: 0};
94
+ const defaultSummary = {mainThreadTime: 0, blockingTime: 0, transferSize: 0, tbtImpact: 0};
93
95
 
94
96
  for (const request of networkRecords) {
95
97
  const urlSummary = byURL.get(request.url) || {...defaultSummary};
@@ -99,7 +101,7 @@ class ThirdPartySummary extends Audit {
99
101
 
100
102
  const jsURLs = getJavaScriptURLs(networkRecords);
101
103
 
102
- for (const task of mainThreadTasks) {
104
+ for (const task of tbtImpactTasks) {
103
105
  const attributableURL = getAttributableURLForTask(task, jsURLs);
104
106
 
105
107
  const urlSummary = byURL.get(attributableURL) || {...defaultSummary};
@@ -110,6 +112,7 @@ class ThirdPartySummary extends Audit {
110
112
  // Note that this is not totally equivalent to the TBT definition since it fails to account for FCP,
111
113
  // but a majority of third-party work occurs after FCP and should yield largely similar numbers.
112
114
  urlSummary.blockingTime += Math.max(taskDuration - 50, 0);
115
+ urlSummary.tbtImpact += task.selfTbtImpact;
113
116
  byURL.set(attributableURL, urlSummary);
114
117
  }
115
118
 
@@ -127,6 +130,7 @@ class ThirdPartySummary extends Audit {
127
130
  entitySummary.transferSize += urlSummary.transferSize;
128
131
  entitySummary.mainThreadTime += urlSummary.mainThreadTime;
129
132
  entitySummary.blockingTime += urlSummary.blockingTime;
133
+ entitySummary.tbtImpact += urlSummary.tbtImpact;
130
134
  byEntity.set(entity, entitySummary);
131
135
 
132
136
  const entityURLs = urls.get(entity) || [];
@@ -152,7 +156,7 @@ class ThirdPartySummary extends Audit {
152
156
  // Sort by blocking time first, then transfer size to break ties.
153
157
  .sort((a, b) => (b.blockingTime - a.blockingTime) || (b.transferSize - a.transferSize));
154
158
 
155
- const subitemSummary = {transferSize: 0, blockingTime: 0};
159
+ const subitemSummary = {transferSize: 0, blockingTime: 0, tbtImpact: 0};
156
160
  const minTransferSize = Math.max(MIN_TRANSFER_SIZE_FOR_SUBITEMS, stats.transferSize / 20);
157
161
  const maxSubItems = Math.min(MAX_SUBITEMS, items.length);
158
162
  let numSubItems = 0;
@@ -167,6 +171,7 @@ class ThirdPartySummary extends Audit {
167
171
  numSubItems++;
168
172
  subitemSummary.transferSize += nextSubItem.transferSize;
169
173
  subitemSummary.blockingTime += nextSubItem.blockingTime;
174
+ subitemSummary.tbtImpact += nextSubItem.tbtImpact;
170
175
  }
171
176
  if (!subitemSummary.blockingTime && !subitemSummary.transferSize) {
172
177
  // Don't bother breaking down if there are no large resources.
@@ -179,6 +184,7 @@ class ThirdPartySummary extends Audit {
179
184
  url: str_(i18n.UIStrings.otherResourcesLabel),
180
185
  transferSize: stats.transferSize - subitemSummary.transferSize,
181
186
  blockingTime: stats.blockingTime - subitemSummary.blockingTime,
187
+ tbtImpact: stats.tbtImpact - subitemSummary.tbtImpact,
182
188
  };
183
189
  if (remainder.transferSize > minTransferSize) {
184
190
  items.push(remainder);
@@ -193,19 +199,21 @@ class ThirdPartySummary extends Audit {
193
199
  */
194
200
  static async audit(artifacts, context) {
195
201
  const settings = context.settings || {};
196
- const trace = artifacts.traces[Audit.DEFAULT_PASS];
197
202
  const devtoolsLog = artifacts.devtoolsLogs[Audit.DEFAULT_PASS];
198
203
  const networkRecords = await NetworkRecords.request(devtoolsLog, context);
199
204
  const classifiedEntities = await EntityClassification.request(
200
205
  {URL: artifacts.URL, devtoolsLog}, context);
201
206
  const firstPartyEntity = classifiedEntities.firstParty;
202
- const tasks = await MainThreadTasks.request(trace, context);
207
+
208
+ const metricComputationData = Audit.makeMetricComputationDataInput(artifacts, context);
209
+ const tbtImpactTasks = await TBTImpactTasks.request(metricComputationData, context);
210
+
203
211
  const multiplier = settings.throttlingMethod === 'simulate' ?
204
212
  settings.throttling.cpuSlowdownMultiplier : 1;
205
213
 
206
214
  const summaries = ThirdPartySummary.getSummaries(
207
- networkRecords, tasks, multiplier, classifiedEntities);
208
- const overallSummary = {wastedBytes: 0, wastedMs: 0};
215
+ networkRecords, tbtImpactTasks, multiplier, classifiedEntities);
216
+ const overallSummary = {wastedBytes: 0, wastedMs: 0, tbtImpact: 0};
209
217
 
210
218
  const results = Array.from(summaries.byEntity.entries())
211
219
  // Don't consider the page we're on to be third-party.
@@ -214,6 +222,7 @@ class ThirdPartySummary extends Audit {
214
222
  .map(([entity, stats]) => {
215
223
  overallSummary.wastedBytes += stats.transferSize;
216
224
  overallSummary.wastedMs += stats.blockingTime;
225
+ overallSummary.tbtImpact += stats.tbtImpact;
217
226
 
218
227
  return {
219
228
  ...stats,
@@ -240,6 +249,7 @@ class ThirdPartySummary extends Audit {
240
249
  return {
241
250
  score: 1,
242
251
  notApplicable: true,
252
+ metricSavings: {TBT: 0},
243
253
  };
244
254
  }
245
255
 
@@ -252,6 +262,9 @@ class ThirdPartySummary extends Audit {
252
262
  timeInMs: overallSummary.wastedMs,
253
263
  }),
254
264
  details,
265
+ metricSavings: {
266
+ TBT: overallSummary.tbtImpact,
267
+ },
255
268
  };
256
269
  }
257
270
  }
@@ -1,14 +1,9 @@
1
1
  export { TBTImpactTasksComputed as TBTImpactTasks };
2
- export type TBTImpactTask = LH.Artifacts.TaskNode & {
3
- tbtImpact: number;
4
- selfTbtImpact: number;
5
- };
6
2
  declare const TBTImpactTasksComputed: typeof TBTImpactTasks & {
7
3
  request: (dependencies: import("../index.js").Artifacts.MetricComputationDataInput, context: import("../../types/utility-types.js").default.ImmutableObject<{
8
4
  computedCache: Map<string, import("../lib/arbitrary-equality-map.js").ArbitraryEqualityMap>;
9
- }>) => Promise<TBTImpactTask[]>;
5
+ }>) => Promise<import("../index.js").Artifacts.TBTImpactTask[]>;
10
6
  };
11
- /** @typedef {LH.Artifacts.TaskNode & {tbtImpact: number, selfTbtImpact: number}} TBTImpactTask */
12
7
  declare class TBTImpactTasks {
13
8
  /**
14
9
  * @param {LH.Artifacts.TaskNode} task
@@ -28,27 +23,27 @@ declare class TBTImpactTasks {
28
23
  * @param {LH.Artifacts.TaskNode[]} tasks
29
24
  * @param {Map<LH.Artifacts.TaskNode, number>} taskToImpact
30
25
  */
31
- static createImpactTasks(tasks: LH.Artifacts.TaskNode[], taskToImpact: Map<LH.Artifacts.TaskNode, number>): TBTImpactTask[];
26
+ static createImpactTasks(tasks: LH.Artifacts.TaskNode[], taskToImpact: Map<LH.Artifacts.TaskNode, number>): import("../index.js").Artifacts.TBTImpactTask[];
32
27
  /**
33
28
  * @param {LH.Artifacts.TaskNode[]} tasks
34
29
  * @param {number} startTimeMs
35
30
  * @param {number} endTimeMs
36
- * @return {TBTImpactTask[]}
31
+ * @return {LH.Artifacts.TBTImpactTask[]}
37
32
  */
38
- static computeImpactsFromObservedTasks(tasks: LH.Artifacts.TaskNode[], startTimeMs: number, endTimeMs: number): TBTImpactTask[];
33
+ static computeImpactsFromObservedTasks(tasks: LH.Artifacts.TaskNode[], startTimeMs: number, endTimeMs: number): LH.Artifacts.TBTImpactTask[];
39
34
  /**
40
35
  * @param {LH.Artifacts.TaskNode[]} tasks
41
36
  * @param {LH.Gatherer.Simulation.Result['nodeTimings']} tbtNodeTimings
42
37
  * @param {number} startTimeMs
43
38
  * @param {number} endTimeMs
44
- * @return {TBTImpactTask[]}
39
+ * @return {LH.Artifacts.TBTImpactTask[]}
45
40
  */
46
- static computeImpactsFromLantern(tasks: LH.Artifacts.TaskNode[], tbtNodeTimings: LH.Gatherer.Simulation.Result['nodeTimings'], startTimeMs: number, endTimeMs: number): TBTImpactTask[];
41
+ static computeImpactsFromLantern(tasks: LH.Artifacts.TaskNode[], tbtNodeTimings: LH.Gatherer.Simulation.Result['nodeTimings'], startTimeMs: number, endTimeMs: number): LH.Artifacts.TBTImpactTask[];
47
42
  /**
48
43
  * @param {LH.Artifacts.MetricComputationDataInput} metricComputationData
49
44
  * @param {LH.Artifacts.ComputedContext} context
50
- * @return {Promise<TBTImpactTask[]>}
45
+ * @return {Promise<LH.Artifacts.TBTImpactTask[]>}
51
46
  */
52
- static compute_(metricComputationData: LH.Artifacts.MetricComputationDataInput, context: LH.Artifacts.ComputedContext): Promise<TBTImpactTask[]>;
47
+ static compute_(metricComputationData: LH.Artifacts.MetricComputationDataInput, context: LH.Artifacts.ComputedContext): Promise<LH.Artifacts.TBTImpactTask[]>;
53
48
  }
54
49
  //# sourceMappingURL=tbt-impact-tasks.d.ts.map
@@ -12,8 +12,6 @@ import {TotalBlockingTime} from './metrics/total-blocking-time.js';
12
12
  import {ProcessedTrace} from './processed-trace.js';
13
13
  import {calculateTbtImpactForEvent} from './metrics/tbt-utils.js';
14
14
 
15
- /** @typedef {LH.Artifacts.TaskNode & {tbtImpact: number, selfTbtImpact: number}} TBTImpactTask */
16
-
17
15
  class TBTImpactTasks {
18
16
  /**
19
17
  * @param {LH.Artifacts.TaskNode} task
@@ -66,7 +64,7 @@ class TBTImpactTasks {
66
64
  * @param {Map<LH.Artifacts.TaskNode, number>} taskToImpact
67
65
  */
68
66
  static createImpactTasks(tasks, taskToImpact) {
69
- /** @type {TBTImpactTask[]} */
67
+ /** @type {LH.Artifacts.TBTImpactTask[]} */
70
68
  const tbtImpactTasks = [];
71
69
 
72
70
  for (const task of tasks) {
@@ -92,7 +90,7 @@ class TBTImpactTasks {
92
90
  * @param {LH.Artifacts.TaskNode[]} tasks
93
91
  * @param {number} startTimeMs
94
92
  * @param {number} endTimeMs
95
- * @return {TBTImpactTask[]}
93
+ * @return {LH.Artifacts.TBTImpactTask[]}
96
94
  */
97
95
  static computeImpactsFromObservedTasks(tasks, startTimeMs, endTimeMs) {
98
96
  /** @type {Map<LH.Artifacts.TaskNode, number>} */
@@ -125,7 +123,7 @@ class TBTImpactTasks {
125
123
  * @param {LH.Gatherer.Simulation.Result['nodeTimings']} tbtNodeTimings
126
124
  * @param {number} startTimeMs
127
125
  * @param {number} endTimeMs
128
- * @return {TBTImpactTask[]}
126
+ * @return {LH.Artifacts.TBTImpactTask[]}
129
127
  */
130
128
  static computeImpactsFromLantern(tasks, tbtNodeTimings, startTimeMs, endTimeMs) {
131
129
  /** @type {Map<LH.Artifacts.TaskNode, number>} */
@@ -193,7 +191,7 @@ class TBTImpactTasks {
193
191
  /**
194
192
  * @param {LH.Artifacts.MetricComputationDataInput} metricComputationData
195
193
  * @param {LH.Artifacts.ComputedContext} context
196
- * @return {Promise<TBTImpactTask[]>}
194
+ * @return {Promise<LH.Artifacts.TBTImpactTask[]>}
197
195
  */
198
196
  static async compute_(metricComputationData, context) {
199
197
  const tbtResult = await TotalBlockingTime.request(metricComputationData, context);