lighthouse 9.5.0-dev.20230123 → 9.5.0-dev.20230124

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 (109) hide show
  1. package/.codecov.yml +0 -5
  2. package/core/audits/audit.js +1 -1
  3. package/core/audits/byte-efficiency/legacy-javascript.d.ts +1 -0
  4. package/core/audits/byte-efficiency/legacy-javascript.js +7 -3
  5. package/core/audits/dobetterweb/uses-http2.d.ts +5 -2
  6. package/core/audits/dobetterweb/uses-http2.js +12 -7
  7. package/core/audits/third-party-facades.d.ts +3 -2
  8. package/core/audits/third-party-facades.js +12 -9
  9. package/core/audits/third-party-summary.d.ts +7 -6
  10. package/core/audits/third-party-summary.js +16 -14
  11. package/core/audits/valid-source-maps.d.ts +5 -3
  12. package/core/audits/valid-source-maps.js +11 -9
  13. package/core/computed/entity-classification.d.ts +29 -0
  14. package/core/computed/entity-classification.js +102 -0
  15. package/core/computed/resource-summary.d.ts +4 -2
  16. package/core/computed/resource-summary.js +8 -5
  17. package/core/lib/url-utils.js +1 -1
  18. package/core/runner.d.ts +10 -0
  19. package/core/runner.js +48 -0
  20. package/core/util.cjs +1 -1
  21. package/dist/report/bundle.esm.js +724 -687
  22. package/dist/report/flow.js +25 -15
  23. package/dist/report/standalone.js +25 -15
  24. package/flow-report/src/common.tsx +1 -1
  25. package/flow-report/src/i18n/i18n.tsx +17 -16
  26. package/flow-report/src/sidebar/sidebar.tsx +2 -2
  27. package/flow-report/src/summary/category.tsx +6 -6
  28. package/flow-report/src/summary/summary.tsx +2 -2
  29. package/package.json +4 -4
  30. package/report/renderer/category-renderer.js +19 -17
  31. package/report/renderer/crc-details-renderer.js +6 -6
  32. package/report/renderer/details-renderer.js +7 -6
  33. package/report/renderer/dom.js +1 -1
  34. package/report/renderer/element-screenshot-renderer.js +3 -3
  35. package/report/renderer/performance-category-renderer.js +17 -15
  36. package/report/renderer/pwa-category-renderer.js +5 -4
  37. package/report/renderer/report-globals.d.ts +21 -0
  38. package/report/renderer/report-globals.js +49 -0
  39. package/report/renderer/report-renderer.js +22 -20
  40. package/report/renderer/report-ui-features.js +9 -8
  41. package/report/renderer/report-utils.d.ts +116 -0
  42. package/report/renderer/{util.js → report-utils.js} +81 -429
  43. package/report/renderer/snippet-renderer.js +3 -2
  44. package/report/test/renderer/category-renderer-test.js +9 -4
  45. package/report/test/renderer/crc-details-renderer-test.js +7 -3
  46. package/report/test/renderer/details-renderer-test.js +7 -3
  47. package/report/test/renderer/dom-test.js +7 -3
  48. package/report/test/renderer/element-screenshot-renderer-test.js +8 -4
  49. package/report/test/renderer/performance-category-renderer-test.js +14 -9
  50. package/report/test/renderer/pwa-category-renderer-test.js +10 -5
  51. package/report/test/renderer/report-renderer-test.js +2 -2
  52. package/report/test/renderer/report-ui-features-test.js +3 -3
  53. package/report/test/renderer/{util-test.js → report-utils-test.js} +36 -234
  54. package/report/test/renderer/snippet-renderer-test.js +7 -3
  55. package/shared/localization/format.js +5 -2
  56. package/shared/localization/locales/ar-XB.json +50 -50
  57. package/shared/localization/locales/ar.json +50 -50
  58. package/shared/localization/locales/bg.json +50 -50
  59. package/shared/localization/locales/ca.json +50 -50
  60. package/shared/localization/locales/cs.json +50 -50
  61. package/shared/localization/locales/da.json +50 -50
  62. package/shared/localization/locales/de.json +50 -50
  63. package/shared/localization/locales/el.json +50 -50
  64. package/shared/localization/locales/en-GB.json +50 -50
  65. package/shared/localization/locales/en-US.json +54 -54
  66. package/shared/localization/locales/en-XA.json +50 -50
  67. package/shared/localization/locales/en-XL.json +54 -54
  68. package/shared/localization/locales/es-419.json +50 -50
  69. package/shared/localization/locales/es.json +50 -50
  70. package/shared/localization/locales/fi.json +50 -50
  71. package/shared/localization/locales/fil.json +50 -50
  72. package/shared/localization/locales/fr.json +50 -50
  73. package/shared/localization/locales/he.json +50 -50
  74. package/shared/localization/locales/hi.json +50 -50
  75. package/shared/localization/locales/hr.json +50 -50
  76. package/shared/localization/locales/hu.json +50 -50
  77. package/shared/localization/locales/id.json +50 -50
  78. package/shared/localization/locales/it.json +50 -50
  79. package/shared/localization/locales/ja.json +50 -50
  80. package/shared/localization/locales/ko.json +50 -50
  81. package/shared/localization/locales/lt.json +50 -50
  82. package/shared/localization/locales/lv.json +50 -50
  83. package/shared/localization/locales/nl.json +50 -50
  84. package/shared/localization/locales/no.json +50 -50
  85. package/shared/localization/locales/pl.json +50 -50
  86. package/shared/localization/locales/pt-PT.json +50 -50
  87. package/shared/localization/locales/pt.json +50 -50
  88. package/shared/localization/locales/ro.json +50 -50
  89. package/shared/localization/locales/ru.json +50 -50
  90. package/shared/localization/locales/sk.json +50 -50
  91. package/shared/localization/locales/sl.json +50 -50
  92. package/shared/localization/locales/sr-Latn.json +50 -50
  93. package/shared/localization/locales/sr.json +50 -50
  94. package/shared/localization/locales/sv.json +50 -50
  95. package/shared/localization/locales/ta.json +50 -50
  96. package/shared/localization/locales/te.json +50 -50
  97. package/shared/localization/locales/th.json +50 -50
  98. package/shared/localization/locales/tr.json +50 -50
  99. package/shared/localization/locales/uk.json +50 -50
  100. package/shared/localization/locales/vi.json +50 -50
  101. package/shared/localization/locales/zh-HK.json +50 -50
  102. package/shared/localization/locales/zh-TW.json +50 -50
  103. package/shared/localization/locales/zh.json +50 -50
  104. package/shared/test/util-test.js +214 -0
  105. package/shared/util.d.ts +122 -0
  106. package/shared/util.js +332 -0
  107. package/types/artifacts.d.ts +14 -0
  108. package/types/lhr/lhr.d.ts +33 -0
  109. package/report/renderer/util.d.ts +0 -342
package/.codecov.yml CHANGED
@@ -20,8 +20,3 @@ coverage:
20
20
  patch:
21
21
  default:
22
22
  if_not_found: success
23
-
24
- # docs:
25
- # https://docs.codecov.com/docs/ignoring-paths
26
- ignore:
27
- - "core/util.cjs" # file is a copy of report/renderer/util.js
@@ -7,7 +7,7 @@
7
7
  import * as LH from '../../types/lh.js';
8
8
  import {isUnderTest} from '../lib/lh-env.js';
9
9
  import * as statistics from '../lib/statistics.js';
10
- import {Util} from '../util.cjs';
10
+ import {Util} from '../../shared/util.js';
11
11
 
12
12
  const DEFAULT_PASS = 'defaultPass';
13
13
 
@@ -78,6 +78,7 @@ export namespace UIStrings {
78
78
  const title: string;
79
79
  const description: string;
80
80
  }
81
+ import { Audit } from "../audit.js";
81
82
  import { ByteEfficiencyAudit } from "./byte-efficiency-audit.js";
82
83
  /**
83
84
  * Takes a list of patterns (consisting of a name identifier and a RegExp expression string)
@@ -19,10 +19,11 @@
19
19
 
20
20
  import fs from 'fs';
21
21
 
22
+ import {Audit} from '../audit.js';
22
23
  import {ByteEfficiencyAudit} from './byte-efficiency-audit.js';
24
+ import {EntityClassification} from '../../computed/entity-classification.js';
23
25
  import {JSBundles} from '../../computed/js-bundles.js';
24
26
  import * as i18n from '../../lib/i18n/i18n.js';
25
- import thirdPartyWeb from '../../lib/third-party-web.js';
26
27
  import {getRequestForScript} from '../../lib/script-helpers.js';
27
28
  import {LH_ROOT} from '../../../root.js';
28
29
 
@@ -401,7 +402,10 @@ class LegacyJavascript extends ByteEfficiencyAudit {
401
402
  * @return {Promise<ByteEfficiencyProduct>}
402
403
  */
403
404
  static async audit_(artifacts, networkRecords, context) {
404
- const mainDocumentEntity = thirdPartyWeb.getEntity(artifacts.URL.finalDisplayedUrl);
405
+ const devtoolsLog = artifacts.devtoolsLogs[Audit.DEFAULT_PASS];
406
+ const classifiedEntities = await EntityClassification.request(
407
+ {URL: artifacts.URL, devtoolsLog}, context);
408
+
405
409
  const bundles = await JSBundles.request(artifacts, context);
406
410
 
407
411
  /** @type {Item[]} */
@@ -450,7 +454,7 @@ class LegacyJavascript extends ByteEfficiencyAudit {
450
454
  const wastedBytesByUrl = new Map();
451
455
  for (const item of items) {
452
456
  // Only estimate savings if first party code has legacy code.
453
- if (thirdPartyWeb.isFirstParty(item.url, mainDocumentEntity)) {
457
+ if (classifiedEntities.isFirstParty(item.url)) {
454
458
  wastedBytesByUrl.set(item.url, item.wastedBytes);
455
459
  }
456
460
  }
@@ -22,9 +22,10 @@ declare class UsesHTTP2Audit extends Audit {
22
22
  * for the same origin at the exact same time.
23
23
  *
24
24
  * @param {LH.Artifacts.NetworkRequest} networkRequest
25
+ * @param {LH.Artifacts.EntityClassification} classifiedEntities
25
26
  * @return {boolean}
26
27
  */
27
- static isStaticAsset(networkRequest: LH.Artifacts.NetworkRequest): boolean;
28
+ static isStaticAsset(networkRequest: LH.Artifacts.NetworkRequest, classifiedEntities: LH.Artifacts.EntityClassification): boolean;
28
29
  /**
29
30
  * Determine the set of resources that aren't HTTP/2 but should be.
30
31
  * We're a little conservative about what we surface for a few reasons:
@@ -45,9 +46,10 @@ declare class UsesHTTP2Audit extends Audit {
45
46
  * https://www.cachefly.com/http-2-is-not-a-magic-bullet/
46
47
  *
47
48
  * @param {Array<LH.Artifacts.NetworkRequest>} networkRecords
49
+ * @param {LH.Artifacts.EntityClassification} classifiedEntities
48
50
  * @return {Array<{url: string, protocol: string}>}
49
51
  */
50
- static determineNonHttp2Resources(networkRecords: Array<LH.Artifacts.NetworkRequest>): Array<{
52
+ static determineNonHttp2Resources(networkRecords: Array<LH.Artifacts.NetworkRequest>, classifiedEntities: LH.Artifacts.EntityClassification): Array<{
51
53
  url: string;
52
54
  protocol: string;
53
55
  }>;
@@ -66,4 +68,5 @@ export namespace UIStrings {
66
68
  }
67
69
  import { Audit } from "../audit.js";
68
70
  import { NetworkRequest } from "../../lib/network-request.js";
71
+ import { EntityClassification } from "../../computed/entity-classification.js";
69
72
  //# sourceMappingURL=uses-http2.d.ts.map
@@ -13,7 +13,7 @@
13
13
  /** @typedef {import('../../lib/dependency-graph/base-node.js').Node} Node */
14
14
 
15
15
  import {Audit} from '../audit.js';
16
- import ThirdParty from '../../lib/third-party-web.js';
16
+ import {EntityClassification} from '../../computed/entity-classification.js';
17
17
  import UrlUtils from '../../lib/url-utils.js';
18
18
  import {ByteEfficiencyAudit} from '../byte-efficiency/byte-efficiency-audit.js';
19
19
  import {LanternInteractive} from '../../computed/metrics/lantern-interactive.js';
@@ -120,16 +120,19 @@ class UsesHTTP2Audit extends Audit {
120
120
  * for the same origin at the exact same time.
121
121
  *
122
122
  * @param {LH.Artifacts.NetworkRequest} networkRequest
123
+ * @param {LH.Artifacts.EntityClassification} classifiedEntities
123
124
  * @return {boolean}
124
125
  */
125
- static isStaticAsset(networkRequest) {
126
+ static isStaticAsset(networkRequest, classifiedEntities) {
126
127
  if (!STATIC_RESOURCE_TYPES.has(networkRequest.resourceType)) return false;
127
128
 
128
129
  // Resources from third-parties that are less than 100 bytes are usually tracking pixels, not actual resources.
129
130
  // They can masquerade as static types though (gifs, documents, etc)
130
131
  if (networkRequest.resourceSize < 100) {
131
- const entity = ThirdParty.getEntity(networkRequest.url);
132
- if (entity) return false;
132
+ // This logic needs to be revisited.
133
+ // See https://github.com/GoogleChrome/lighthouse/issues/14661
134
+ const entity = classifiedEntities.entityByUrl.get(networkRequest.url);
135
+ if (entity && !entity.isUnrecognized) return false;
133
136
  }
134
137
 
135
138
  return true;
@@ -155,9 +158,10 @@ class UsesHTTP2Audit extends Audit {
155
158
  * https://www.cachefly.com/http-2-is-not-a-magic-bullet/
156
159
  *
157
160
  * @param {Array<LH.Artifacts.NetworkRequest>} networkRecords
161
+ * @param {LH.Artifacts.EntityClassification} classifiedEntities
158
162
  * @return {Array<{url: string, protocol: string}>}
159
163
  */
160
- static determineNonHttp2Resources(networkRecords) {
164
+ static determineNonHttp2Resources(networkRecords, classifiedEntities) {
161
165
  /** @type {Array<{url: string, protocol: string}>} */
162
166
  const nonHttp2Resources = [];
163
167
 
@@ -166,7 +170,7 @@ class UsesHTTP2Audit extends Audit {
166
170
  /** @type {Map<string, Array<LH.Artifacts.NetworkRequest>>} */
167
171
  const groupedByOrigin = new Map();
168
172
  for (const record of networkRecords) {
169
- if (!UsesHTTP2Audit.isStaticAsset(record)) continue;
173
+ if (!UsesHTTP2Audit.isStaticAsset(record, classifiedEntities)) continue;
170
174
  if (UrlUtils.isLikeLocalhost(record.parsedURL.host)) continue;
171
175
  const existing = groupedByOrigin.get(record.parsedURL.securityOrigin) || [];
172
176
  existing.push(record);
@@ -203,7 +207,8 @@ class UsesHTTP2Audit extends Audit {
203
207
  const devtoolsLog = artifacts.devtoolsLogs[Audit.DEFAULT_PASS];
204
208
  const URL = artifacts.URL;
205
209
  const networkRecords = await NetworkRecords.request(devtoolsLog, context);
206
- const resources = UsesHTTP2Audit.determineNonHttp2Resources(networkRecords);
210
+ const classifiedEntities = await EntityClassification.request({URL, devtoolsLog}, context);
211
+ const resources = UsesHTTP2Audit.determineNonHttp2Resources(networkRecords, classifiedEntities);
207
212
 
208
213
  let displayValue;
209
214
  if (resources.length > 0) {
@@ -15,10 +15,10 @@ declare class ThirdPartyFacades extends Audit {
15
15
  static condenseItems(items: import('./third-party-summary.js').URLSummary[]): void;
16
16
  /**
17
17
  * @param {Map<string, import('./third-party-summary.js').Summary>} byURL
18
- * @param {ThirdPartyEntity | undefined} mainEntity
18
+ * @param {LH.Artifacts.EntityClassification} classifiedEntities
19
19
  * @return {FacadableProduct[]}
20
20
  */
21
- static getProductsWithFacade(byURL: Map<string, import('./third-party-summary.js').Summary>, mainEntity: ThirdPartyEntity | undefined): FacadableProduct[];
21
+ static getProductsWithFacade(byURL: Map<string, import('./third-party-summary.js').Summary>, classifiedEntities: LH.Artifacts.EntityClassification): FacadableProduct[];
22
22
  /**
23
23
  * @param {LH.Artifacts} artifacts
24
24
  * @param {LH.Audit.Context} context
@@ -38,4 +38,5 @@ export namespace UIStrings {
38
38
  const categorySocial: string;
39
39
  }
40
40
  import { Audit } from "./audit.js";
41
+ import { EntityClassification } from "../computed/entity-classification.js";
41
42
  //# sourceMappingURL=third-party-facades.d.ts.map
@@ -21,9 +21,9 @@
21
21
 
22
22
  import {Audit} from './audit.js';
23
23
  import * as i18n from '../lib/i18n/i18n.js';
24
+ import {EntityClassification} from '../computed/entity-classification.js';
24
25
  import thirdPartyWeb from '../lib/third-party-web.js';
25
26
  import {NetworkRecords} from '../computed/network-records.js';
26
- import {MainResource} from '../computed/main-resource.js';
27
27
  import {MainThreadTasks} from '../computed/main-thread-tasks.js';
28
28
  import ThirdPartySummary from './third-party-summary.js';
29
29
 
@@ -121,15 +121,15 @@ class ThirdPartyFacades extends Audit {
121
121
 
122
122
  /**
123
123
  * @param {Map<string, import('./third-party-summary.js').Summary>} byURL
124
- * @param {ThirdPartyEntity | undefined} mainEntity
124
+ * @param {LH.Artifacts.EntityClassification} classifiedEntities
125
125
  * @return {FacadableProduct[]}
126
126
  */
127
- static getProductsWithFacade(byURL, mainEntity) {
127
+ static getProductsWithFacade(byURL, classifiedEntities) {
128
128
  /** @type {Map<string, FacadableProduct>} */
129
129
  const facadableProductMap = new Map();
130
130
  for (const url of byURL.keys()) {
131
- const entity = thirdPartyWeb.getEntity(url);
132
- if (!entity || thirdPartyWeb.isFirstParty(url, mainEntity)) continue;
131
+ const entity = classifiedEntities.entityByUrl.get(url);
132
+ if (!entity || classifiedEntities.isFirstParty(url)) continue;
133
133
 
134
134
  const product = thirdPartyWeb.getProduct(url);
135
135
  if (!product || !product.facades || !product.facades.length) continue;
@@ -151,14 +151,15 @@ class ThirdPartyFacades extends Audit {
151
151
  const trace = artifacts.traces[Audit.DEFAULT_PASS];
152
152
  const devtoolsLog = artifacts.devtoolsLogs[Audit.DEFAULT_PASS];
153
153
  const networkRecords = await NetworkRecords.request(devtoolsLog, context);
154
- const mainResource = await MainResource.request({devtoolsLog, URL: artifacts.URL}, context);
155
- const mainEntity = thirdPartyWeb.getEntity(mainResource.url);
154
+ const classifiedEntities = await EntityClassification.request(
155
+ {URL: artifacts.URL, devtoolsLog}, context);
156
156
  const tasks = await MainThreadTasks.request(trace, context);
157
157
  const multiplier = settings.throttlingMethod === 'simulate' ?
158
158
  settings.throttling.cpuSlowdownMultiplier : 1;
159
- const summaries = ThirdPartySummary.getSummaries(networkRecords, tasks, multiplier);
159
+ const summaries = ThirdPartySummary.getSummaries(networkRecords, tasks, multiplier,
160
+ classifiedEntities);
160
161
  const facadableProducts =
161
- ThirdPartyFacades.getProductsWithFacade(summaries.byURL, mainEntity);
162
+ ThirdPartyFacades.getProductsWithFacade(summaries.byURL, classifiedEntities);
162
163
 
163
164
  /** @type {LH.Audit.Details.TableItem[]} */
164
165
  const results = [];
@@ -188,6 +189,8 @@ class ThirdPartyFacades extends Audit {
188
189
  transferSize: entitySummary.transferSize,
189
190
  blockingTime: entitySummary.blockingTime,
190
191
  subItems: {type: 'subitems', items},
192
+ // Add entity manually since facades don't have a single `url`.
193
+ entity: entity.name,
191
194
  });
192
195
  }
193
196
 
@@ -1,5 +1,4 @@
1
1
  export default ThirdPartySummary;
2
- export type ThirdPartyEntity = import("third-party-web").IEntity;
3
2
  export type Summary = {
4
3
  mainThreadTime: number;
5
4
  transferSize: number;
@@ -14,7 +13,7 @@ export type SummaryMaps = {
14
13
  /**
15
14
  * Map of impact summaries for each entity.
16
15
  */
17
- byEntity: Map<ThirdPartyEntity, Summary>;
16
+ byEntity: Map<LH.Artifacts.Entity, Summary>;
18
17
  /**
19
18
  * Map of impact summaries for each URL.
20
19
  */
@@ -22,7 +21,7 @@ export type SummaryMaps = {
22
21
  /**
23
22
  * Map of URLs under each entity.
24
23
  */
25
- urls: Map<ThirdPartyEntity, string[]>;
24
+ urls: Map<LH.Artifacts.Entity, string[]>;
26
25
  };
27
26
  declare class ThirdPartySummary extends Audit {
28
27
  /**
@@ -30,16 +29,17 @@ declare class ThirdPartySummary extends Audit {
30
29
  * @param {Array<LH.Artifacts.NetworkRequest>} networkRecords
31
30
  * @param {Array<LH.Artifacts.TaskNode>} mainThreadTasks
32
31
  * @param {number} cpuMultiplier
32
+ * @param {LH.Artifacts.EntityClassification} entityClassification
33
33
  * @return {SummaryMaps}
34
34
  */
35
- static getSummaries(networkRecords: Array<LH.Artifacts.NetworkRequest>, mainThreadTasks: Array<LH.Artifacts.TaskNode>, cpuMultiplier: number): SummaryMaps;
35
+ static getSummaries(networkRecords: Array<LH.Artifacts.NetworkRequest>, mainThreadTasks: Array<LH.Artifacts.TaskNode>, cpuMultiplier: number, entityClassification: LH.Artifacts.EntityClassification): SummaryMaps;
36
36
  /**
37
- * @param {ThirdPartyEntity} entity
37
+ * @param {LH.Artifacts.Entity} entity
38
38
  * @param {SummaryMaps} summaries
39
39
  * @param {Summary} stats
40
40
  * @return {Array<URLSummary>}
41
41
  */
42
- static makeSubItems(entity: ThirdPartyEntity, summaries: SummaryMaps, stats: Summary): Array<URLSummary>;
42
+ static makeSubItems(entity: LH.Artifacts.Entity, summaries: SummaryMaps, stats: Summary): Array<URLSummary>;
43
43
  /**
44
44
  * @param {LH.Artifacts} artifacts
45
45
  * @param {LH.Audit.Context} context
@@ -55,4 +55,5 @@ export namespace UIStrings {
55
55
  const displayValue: string;
56
56
  }
57
57
  import { Audit } from "./audit.js";
58
+ import { EntityClassification } from "../computed/entity-classification.js";
58
59
  //# sourceMappingURL=third-party-summary.d.ts.map
@@ -5,8 +5,8 @@
5
5
  */
6
6
 
7
7
  import {Audit} from './audit.js';
8
+ import {EntityClassification} from '../computed/entity-classification.js';
8
9
  import * as i18n from '../lib/i18n/i18n.js';
9
- import thirdPartyWeb from '../lib/third-party-web.js';
10
10
  import {NetworkRecords} from '../computed/network-records.js';
11
11
  import {MainThreadTasks} from '../computed/main-thread-tasks.js';
12
12
  import {getJavaScriptURLs, getAttributableURLForTask} from '../lib/tracehouse/task-summary.js';
@@ -33,8 +33,6 @@ const str_ = i18n.createIcuMessageFn(import.meta.url, UIStrings);
33
33
  // A page passes when all third-party code blocks for less than 250 ms.
34
34
  const PASS_THRESHOLD_IN_MS = 250;
35
35
 
36
- /** @typedef {import("third-party-web").IEntity} ThirdPartyEntity */
37
-
38
36
  /**
39
37
  * @typedef Summary
40
38
  * @property {number} mainThreadTime
@@ -50,9 +48,9 @@ const PASS_THRESHOLD_IN_MS = 250;
50
48
  */
51
49
 
52
50
  /** @typedef SummaryMaps
53
- * @property {Map<ThirdPartyEntity, Summary>} byEntity Map of impact summaries for each entity.
51
+ * @property {Map<LH.Artifacts.Entity, Summary>} byEntity Map of impact summaries for each entity.
54
52
  * @property {Map<string, Summary>} byURL Map of impact summaries for each URL.
55
- * @property {Map<ThirdPartyEntity, string[]>} urls Map of URLs under each entity.
53
+ * @property {Map<LH.Artifacts.Entity, string[]>} urls Map of URLs under each entity.
56
54
  */
57
55
 
58
56
  /**
@@ -83,12 +81,13 @@ class ThirdPartySummary extends Audit {
83
81
  * @param {Array<LH.Artifacts.NetworkRequest>} networkRecords
84
82
  * @param {Array<LH.Artifacts.TaskNode>} mainThreadTasks
85
83
  * @param {number} cpuMultiplier
84
+ * @param {LH.Artifacts.EntityClassification} entityClassification
86
85
  * @return {SummaryMaps}
87
86
  */
88
- static getSummaries(networkRecords, mainThreadTasks, cpuMultiplier) {
87
+ static getSummaries(networkRecords, mainThreadTasks, cpuMultiplier, entityClassification) {
89
88
  /** @type {Map<string, Summary>} */
90
89
  const byURL = new Map();
91
- /** @type {Map<ThirdPartyEntity, Summary>} */
90
+ /** @type {Map<LH.Artifacts.Entity, Summary>} */
92
91
  const byEntity = new Map();
93
92
  const defaultSummary = {mainThreadTime: 0, blockingTime: 0, transferSize: 0};
94
93
 
@@ -114,11 +113,11 @@ class ThirdPartySummary extends Audit {
114
113
  byURL.set(attributableURL, urlSummary);
115
114
  }
116
115
 
117
- // Map each URL's stat to a particular third party entity.
118
- /** @type {Map<ThirdPartyEntity, string[]>} */
116
+ // Map each URL's stat to a particular entity.
117
+ /** @type {Map<LH.Artifacts.Entity, string[]>} */
119
118
  const urls = new Map();
120
119
  for (const [url, urlSummary] of byURL.entries()) {
121
- const entity = thirdPartyWeb.getEntity(url);
120
+ const entity = entityClassification.entityByUrl.get(url);
122
121
  if (!entity) {
123
122
  byURL.delete(url);
124
123
  continue;
@@ -139,7 +138,7 @@ class ThirdPartySummary extends Audit {
139
138
  }
140
139
 
141
140
  /**
142
- * @param {ThirdPartyEntity} entity
141
+ * @param {LH.Artifacts.Entity} entity
143
142
  * @param {SummaryMaps} summaries
144
143
  * @param {Summary} stats
145
144
  * @return {Array<URLSummary>}
@@ -197,18 +196,21 @@ class ThirdPartySummary extends Audit {
197
196
  const trace = artifacts.traces[Audit.DEFAULT_PASS];
198
197
  const devtoolsLog = artifacts.devtoolsLogs[Audit.DEFAULT_PASS];
199
198
  const networkRecords = await NetworkRecords.request(devtoolsLog, context);
200
- const mainEntity = thirdPartyWeb.getEntity(artifacts.URL.finalDisplayedUrl);
199
+ const classifiedEntities = await EntityClassification.request(
200
+ {URL: artifacts.URL, devtoolsLog}, context);
201
+ const firstPartyEntity = classifiedEntities.firstParty;
201
202
  const tasks = await MainThreadTasks.request(trace, context);
202
203
  const multiplier = settings.throttlingMethod === 'simulate' ?
203
204
  settings.throttling.cpuSlowdownMultiplier : 1;
204
205
 
205
- const summaries = ThirdPartySummary.getSummaries(networkRecords, tasks, multiplier);
206
+ const summaries = ThirdPartySummary.getSummaries(
207
+ networkRecords, tasks, multiplier, classifiedEntities);
206
208
  const overallSummary = {wastedBytes: 0, wastedMs: 0};
207
209
 
208
210
  const results = Array.from(summaries.byEntity.entries())
209
211
  // Don't consider the page we're on to be third-party.
210
212
  // e.g. Facebook SDK isn't a third-party script on facebook.com
211
- .filter(([entity]) => !(mainEntity && mainEntity.name === entity.name))
213
+ .filter(([entity]) => !(firstPartyEntity && firstPartyEntity === entity))
212
214
  .map(([entity, stats]) => {
213
215
  overallSummary.wastedBytes += stats.transferSize;
214
216
  overallSummary.wastedMs += stats.blockingTime;
@@ -3,14 +3,15 @@ declare class ValidSourceMaps extends Audit {
3
3
  /**
4
4
  * Returns true if the size of the script exceeds a static threshold.
5
5
  * @param {LH.Artifacts.Script} script
6
- * @param {string} finalURL
6
+ * @param {LH.Artifacts.EntityClassification} classifiedEntities
7
7
  * @return {boolean}
8
8
  */
9
- static isLargeFirstPartyJS(script: LH.Artifacts.Script, finalURL: string): boolean;
9
+ static isLargeFirstPartyJS(script: LH.Artifacts.Script, classifiedEntities: LH.Artifacts.EntityClassification): boolean;
10
10
  /**
11
11
  * @param {LH.Artifacts} artifacts
12
+ * @param {LH.Audit.Context} context
12
13
  */
13
- static audit(artifacts: LH.Artifacts): Promise<{
14
+ static audit(artifacts: LH.Artifacts, context: LH.Audit.Context): Promise<{
14
15
  score: number;
15
16
  details: import("../../types/lhr/audit-details.js").default.Table;
16
17
  }>;
@@ -24,4 +25,5 @@ export namespace UIStrings {
24
25
  const missingSourceMapItemsWarningMesssage: string;
25
26
  }
26
27
  import { Audit } from "./audit.js";
28
+ import { EntityClassification } from "../computed/entity-classification.js";
27
29
  //# sourceMappingURL=valid-source-maps.d.ts.map
@@ -4,8 +4,8 @@
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 thirdPartyWeb from '../lib/third-party-web.js';
8
7
  import {Audit} from './audit.js';
8
+ import {EntityClassification} from '../computed/entity-classification.js';
9
9
  import * as i18n from '../lib/i18n/i18n.js';
10
10
 
11
11
  const UIStrings = {
@@ -43,31 +43,33 @@ class ValidSourceMaps extends Audit {
43
43
  title: str_(UIStrings.title),
44
44
  failureTitle: str_(UIStrings.failureTitle),
45
45
  description: str_(UIStrings.description),
46
- requiredArtifacts: ['Scripts', 'SourceMaps', 'URL'],
46
+ requiredArtifacts: ['Scripts', 'SourceMaps', 'URL', 'devtoolsLogs'],
47
47
  };
48
48
  }
49
49
 
50
50
  /**
51
51
  * Returns true if the size of the script exceeds a static threshold.
52
52
  * @param {LH.Artifacts.Script} script
53
- * @param {string} finalURL
53
+ * @param {LH.Artifacts.EntityClassification} classifiedEntities
54
54
  * @return {boolean}
55
55
  */
56
- static isLargeFirstPartyJS(script, finalURL) {
56
+ static isLargeFirstPartyJS(script, classifiedEntities) {
57
57
  if (!script.length) return false;
58
58
 
59
59
  const isLargeJS = script.length >= LARGE_JS_BYTE_THRESHOLD;
60
- const isFirstPartyJS = script.url ?
61
- thirdPartyWeb.isFirstParty(script.url, thirdPartyWeb.getEntity(finalURL)) : false;
62
-
60
+ const isFirstPartyJS = script.url ? classifiedEntities.isFirstParty(script.url) : false;
63
61
  return isLargeJS && isFirstPartyJS;
64
62
  }
65
63
 
66
64
  /**
67
65
  * @param {LH.Artifacts} artifacts
66
+ * @param {LH.Audit.Context} context
68
67
  */
69
- static async audit(artifacts) {
68
+ static async audit(artifacts, context) {
70
69
  const {SourceMaps} = artifacts;
70
+ const devtoolsLog = artifacts.devtoolsLogs[Audit.DEFAULT_PASS];
71
+ const classifiedEntities = await EntityClassification.request(
72
+ {URL: artifacts.URL, devtoolsLog}, context);
71
73
 
72
74
  /** @type {Set<string>} */
73
75
  const isMissingMapForLargeFirstPartyScriptUrl = new Set();
@@ -77,7 +79,7 @@ class ValidSourceMaps extends Audit {
77
79
  for (const script of artifacts.Scripts) {
78
80
  const sourceMap = SourceMaps.find(m => m.scriptId === script.scriptId);
79
81
  const errors = [];
80
- const isLargeFirstParty = this.isLargeFirstPartyJS(script, artifacts.URL.finalDisplayedUrl);
82
+ const isLargeFirstParty = this.isLargeFirstPartyJS(script, classifiedEntities);
81
83
 
82
84
  if (isLargeFirstParty && (!sourceMap || !sourceMap.map)) {
83
85
  missingMapsForLargeFirstPartyFile = true;
@@ -0,0 +1,29 @@
1
+ export { EntityClassificationComputed as EntityClassification };
2
+ export type EntityCache = Map<string, LH.Artifacts.Entity>;
3
+ declare const EntityClassificationComputed: typeof EntityClassification & {
4
+ request: (dependencies: {
5
+ URL: LH.Artifacts['URL'];
6
+ devtoolsLog: import("../index.js").DevtoolsLog;
7
+ }, context: import("../../types/utility-types.js").default.ImmutableObject<{
8
+ computedCache: Map<string, import("../lib/arbitrary-equality-map.js").ArbitraryEqualityMap>;
9
+ }>) => Promise<import("../index.js").Artifacts.EntityClassification>;
10
+ };
11
+ /** @typedef {Map<string, LH.Artifacts.Entity>} EntityCache */
12
+ declare class EntityClassification {
13
+ /**
14
+ * @param {EntityCache} entityCache
15
+ * @param {string} url
16
+ * @return {LH.Artifacts.Entity | undefined}
17
+ */
18
+ static makeUpAnEntity(entityCache: EntityCache, url: string): LH.Artifacts.Entity | undefined;
19
+ /**
20
+ * @param {{URL: LH.Artifacts['URL'], devtoolsLog: LH.DevtoolsLog}} data
21
+ * @param {LH.Artifacts.ComputedContext} context
22
+ * @return {Promise<LH.Artifacts.EntityClassification>}
23
+ */
24
+ static compute_(data: {
25
+ URL: LH.Artifacts['URL'];
26
+ devtoolsLog: import("../index.js").DevtoolsLog;
27
+ }, context: LH.Artifacts.ComputedContext): Promise<LH.Artifacts.EntityClassification>;
28
+ }
29
+ //# sourceMappingURL=entity-classification.d.ts.map
@@ -0,0 +1,102 @@
1
+ /**
2
+ * @license Copyright 2022 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 {Util} from '../util.cjs';
10
+ import UrlUtils from '../lib/url-utils.js';
11
+ import thirdPartyWeb from '../lib/third-party-web.js';
12
+
13
+ /** @typedef {Map<string, LH.Artifacts.Entity>} EntityCache */
14
+
15
+ class EntityClassification {
16
+ /**
17
+ * @param {EntityCache} entityCache
18
+ * @param {string} url
19
+ * @return {LH.Artifacts.Entity | undefined}
20
+ */
21
+ static makeUpAnEntity(entityCache, url) {
22
+ if (!UrlUtils.isValid(url)) return;
23
+ // We can make up an entity only for those URLs with a valid domain attached.
24
+ // So we further restrict from allowed URLs to (http/https).
25
+ if (!Util.createOrReturnURL(url).protocol.startsWith('http')) return;
26
+
27
+ const rootDomain = Util.getRootDomain(url);
28
+ if (!rootDomain) return;
29
+ if (entityCache.has(rootDomain)) return entityCache.get(rootDomain);
30
+
31
+ const unrecognizedEntity = {
32
+ name: rootDomain,
33
+ company: rootDomain,
34
+ category: '',
35
+ categories: [],
36
+ domains: [rootDomain],
37
+ averageExecutionTime: 0,
38
+ totalExecutionTime: 0,
39
+ totalOccurrences: 0,
40
+ isUnrecognized: true,
41
+ };
42
+ entityCache.set(rootDomain, unrecognizedEntity);
43
+ return unrecognizedEntity;
44
+ }
45
+
46
+ /**
47
+ * @param {{URL: LH.Artifacts['URL'], devtoolsLog: LH.DevtoolsLog}} data
48
+ * @param {LH.Artifacts.ComputedContext} context
49
+ * @return {Promise<LH.Artifacts.EntityClassification>}
50
+ */
51
+ static async compute_(data, context) {
52
+ const networkRecords = await NetworkRecords.request(data.devtoolsLog, context);
53
+ /** @type {EntityCache} */
54
+ const madeUpEntityCache = new Map();
55
+ /** @type {Map<string, LH.Artifacts.Entity>} */
56
+ const entityByUrl = new Map();
57
+ /** @type {Map<LH.Artifacts.Entity, Set<string>>} */
58
+ const urlsByEntity = new Map();
59
+
60
+ for (const record of networkRecords) {
61
+ const {url} = record;
62
+ if (entityByUrl.has(url)) continue;
63
+
64
+ const entity = thirdPartyWeb.getEntity(url) ||
65
+ EntityClassification.makeUpAnEntity(madeUpEntityCache, url);
66
+ if (!entity) continue;
67
+
68
+ const entityURLs = urlsByEntity.get(entity) || new Set();
69
+ entityURLs.add(url);
70
+ urlsByEntity.set(entity, entityURLs);
71
+ entityByUrl.set(url, entity);
72
+ }
73
+
74
+ // When available, first party identification will be done via
75
+ // `mainDocumentUrl` (for navigations), and falls back to `finalDisplayedUrl` (for timespan/snapshot).
76
+ // See https://github.com/GoogleChrome/lighthouse/issues/13706
77
+ const firstPartyUrl = data.URL.mainDocumentUrl || data.URL.finalDisplayedUrl;
78
+ const firstParty = thirdPartyWeb.getEntity(firstPartyUrl) ||
79
+ EntityClassification.makeUpAnEntity(madeUpEntityCache, firstPartyUrl);
80
+
81
+ /**
82
+ * Convenience function to check if a URL belongs to first party.
83
+ * @param {string} url
84
+ * @return {boolean}
85
+ */
86
+ function isFirstParty(url) {
87
+ const entityUrl = entityByUrl.get(url);
88
+ if (!entityUrl) throw new Error('A url not in devtoolsLog was used for first-party check.');
89
+ return entityUrl === firstParty;
90
+ }
91
+
92
+ return {
93
+ entityByUrl,
94
+ urlsByEntity,
95
+ firstParty,
96
+ isFirstParty,
97
+ };
98
+ }
99
+ }
100
+
101
+ const EntityClassificationComputed = makeComputedArtifact(EntityClassification, null);
102
+ export {EntityClassificationComputed as EntityClassification};
@@ -24,9 +24,10 @@ declare class ResourceSummary {
24
24
  * @param {Array<LH.Artifacts.NetworkRequest>} networkRecords
25
25
  * @param {LH.Artifacts.URL} URLArtifact
26
26
  * @param {LH.Util.ImmutableObject<LH.Budget[]|null>} budgets
27
+ * @param {LH.Artifacts.EntityClassification} classifiedEntities
27
28
  * @return {Record<LH.Budget.ResourceType, ResourceEntry>}
28
29
  */
29
- static summarize(networkRecords: Array<LH.Artifacts.NetworkRequest>, URLArtifact: LH.Artifacts.URL, budgets: LH.Util.ImmutableObject<LH.Budget[] | null>): Record<LH.Budget.ResourceType, ResourceEntry>;
30
+ static summarize(networkRecords: Array<LH.Artifacts.NetworkRequest>, URLArtifact: LH.Artifacts.URL, budgets: LH.Util.ImmutableObject<LH.Budget[] | null>, classifiedEntities: LH.Artifacts.EntityClassification): Record<LH.Budget.ResourceType, ResourceEntry>;
30
31
  /**
31
32
  * @param {{URL: LH.Artifacts['URL'], devtoolsLog: LH.DevtoolsLog, budgets: LH.Util.ImmutableObject<LH.Budget[]|null>}} data
32
33
  * @param {LH.Artifacts.ComputedContext} context
@@ -38,7 +39,8 @@ declare class ResourceSummary {
38
39
  budgets: LH.Util.ImmutableObject<LH.Budget[] | null>;
39
40
  }, context: LH.Artifacts.ComputedContext): Promise<Record<LH.Budget.ResourceType, ResourceEntry>>;
40
41
  }
41
- import { Util } from "../util.cjs";
42
+ import { Util } from "../../shared/util.js";
42
43
  import { Budget } from "../config/budget.js";
43
44
  import { NetworkRequest } from "../lib/network-request.js";
45
+ import { EntityClassification } from "./entity-classification.js";
44
46
  //# sourceMappingURL=resource-summary.d.ts.map