lighthouse 11.3.0 → 11.4.0

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 (129) hide show
  1. package/core/audits/accessibility/aria-allowed-role.js +1 -1
  2. package/core/audits/bf-cache.d.ts +1 -0
  3. package/core/audits/bf-cache.js +12 -2
  4. package/core/audits/bootup-time.js +3 -0
  5. package/core/audits/byte-efficiency/byte-efficiency-audit.d.ts +0 -10
  6. package/core/audits/byte-efficiency/byte-efficiency-audit.js +6 -46
  7. package/core/audits/byte-efficiency/duplicated-javascript.d.ts +0 -6
  8. package/core/audits/byte-efficiency/duplicated-javascript.js +9 -38
  9. package/core/audits/byte-efficiency/legacy-javascript.d.ts +0 -11
  10. package/core/audits/byte-efficiency/legacy-javascript.js +5 -36
  11. package/core/audits/byte-efficiency/unminified-css.js +2 -2
  12. package/core/audits/byte-efficiency/unminified-javascript.js +2 -3
  13. package/core/audits/byte-efficiency/unused-javascript.js +10 -11
  14. package/core/audits/dobetterweb/uses-http2.d.ts +1 -1
  15. package/core/audits/dobetterweb/uses-http2.js +8 -5
  16. package/core/audits/layout-shift-elements.js +32 -17
  17. package/core/audits/metrics/cumulative-layout-shift.js +5 -1
  18. package/core/audits/metrics/max-potential-fid.d.ts +20 -0
  19. package/core/audits/metrics/max-potential-fid.js +69 -0
  20. package/core/audits/resource-summary.d.ts +11 -0
  21. package/core/audits/resource-summary.js +86 -0
  22. package/core/audits/third-party-cookies.d.ts +22 -0
  23. package/core/audits/third-party-cookies.js +112 -0
  24. package/core/audits/third-party-summary.d.ts +20 -2
  25. package/core/audits/third-party-summary.js +3 -48
  26. package/core/computed/entity-classification.js +1 -1
  27. package/core/computed/metrics/cumulative-layout-shift.d.ts +13 -1
  28. package/core/computed/metrics/cumulative-layout-shift.js +47 -1
  29. package/core/computed/metrics/lantern-first-contentful-paint.d.ts +50 -16
  30. package/core/computed/metrics/lantern-first-contentful-paint.js +56 -59
  31. package/core/computed/metrics/lantern-first-meaningful-paint.js +10 -12
  32. package/core/computed/metrics/lantern-largest-contentful-paint.js +9 -12
  33. package/core/computed/metrics/lantern-metric.d.ts +2 -2
  34. package/core/computed/metrics/lantern-metric.js +6 -5
  35. package/core/computed/resource-summary.d.ts +0 -1
  36. package/core/computed/resource-summary.js +2 -2
  37. package/core/computed/unused-css.js +2 -2
  38. package/core/config/constants.d.ts +2 -2
  39. package/core/config/constants.js +2 -2
  40. package/core/config/default-config.js +4 -0
  41. package/core/config/filters.js +1 -0
  42. package/core/gather/base-artifacts.js +2 -1
  43. package/core/gather/gatherers/css-usage.js +5 -0
  44. package/core/gather/gatherers/trace-elements.d.ts +6 -15
  45. package/core/gather/gatherers/trace-elements.js +15 -71
  46. package/core/gather/navigation-runner.js +1 -1
  47. package/core/lib/i18n/i18n.d.ts +2 -2
  48. package/core/lib/i18n/i18n.js +2 -2
  49. package/core/lib/minification-estimator.js +1 -1
  50. package/core/lib/network-recorder.d.ts +8 -0
  51. package/core/lib/network-recorder.js +13 -0
  52. package/core/lib/network-request.d.ts +11 -0
  53. package/core/lib/network-request.js +21 -1
  54. package/core/lib/rect-helpers.d.ts +5 -0
  55. package/core/lib/rect-helpers.js +15 -0
  56. package/core/lib/script-helpers.d.ts +34 -5
  57. package/core/lib/script-helpers.js +136 -0
  58. package/core/lib/url-utils.d.ts +6 -0
  59. package/core/lib/url-utils.js +14 -2
  60. package/core/runner.js +5 -1
  61. package/core/scripts/download-chrome.sh +56 -13
  62. package/dist/report/bundle.esm.js +30 -13
  63. package/dist/report/flow.js +32 -15
  64. package/dist/report/standalone.js +32 -15
  65. package/flow-report/src/i18n/i18n.d.ts +4 -0
  66. package/package.json +5 -4
  67. package/report/assets/styles.css +25 -8
  68. package/report/assets/templates.html +1 -1
  69. package/report/clients/standalone.js +1 -0
  70. package/report/renderer/components.js +2 -2
  71. package/report/renderer/explodey-gauge.js +0 -1
  72. package/report/renderer/performance-category-renderer.js +4 -2
  73. package/report/renderer/report-renderer.js +12 -1
  74. package/report/renderer/report-ui-features.js +3 -2
  75. package/report/renderer/report-utils.d.ts +2 -0
  76. package/report/renderer/report-utils.js +8 -4
  77. package/report/types/report-renderer.d.ts +2 -0
  78. package/shared/localization/locales/ar-XB.json +34 -25
  79. package/shared/localization/locales/ar.json +35 -26
  80. package/shared/localization/locales/bg.json +34 -25
  81. package/shared/localization/locales/ca.json +35 -26
  82. package/shared/localization/locales/cs.json +34 -25
  83. package/shared/localization/locales/da.json +51 -42
  84. package/shared/localization/locales/de.json +38 -29
  85. package/shared/localization/locales/el.json +34 -25
  86. package/shared/localization/locales/en-GB.json +35 -26
  87. package/shared/localization/locales/en-US.json +26 -5
  88. package/shared/localization/locales/en-XA.json +34 -25
  89. package/shared/localization/locales/en-XL.json +26 -5
  90. package/shared/localization/locales/es-419.json +34 -25
  91. package/shared/localization/locales/es.json +34 -25
  92. package/shared/localization/locales/fi.json +34 -25
  93. package/shared/localization/locales/fil.json +35 -26
  94. package/shared/localization/locales/fr.json +34 -25
  95. package/shared/localization/locales/he.json +35 -26
  96. package/shared/localization/locales/hi.json +35 -26
  97. package/shared/localization/locales/hr.json +35 -26
  98. package/shared/localization/locales/hu.json +34 -25
  99. package/shared/localization/locales/id.json +34 -25
  100. package/shared/localization/locales/it.json +34 -25
  101. package/shared/localization/locales/ja.json +34 -25
  102. package/shared/localization/locales/ko.json +35 -26
  103. package/shared/localization/locales/lt.json +34 -25
  104. package/shared/localization/locales/lv.json +35 -26
  105. package/shared/localization/locales/nl.json +35 -26
  106. package/shared/localization/locales/no.json +34 -25
  107. package/shared/localization/locales/pl.json +34 -25
  108. package/shared/localization/locales/pt-PT.json +35 -26
  109. package/shared/localization/locales/pt.json +35 -26
  110. package/shared/localization/locales/ro.json +34 -25
  111. package/shared/localization/locales/ru.json +35 -26
  112. package/shared/localization/locales/sk.json +35 -26
  113. package/shared/localization/locales/sl.json +35 -26
  114. package/shared/localization/locales/sr-Latn.json +34 -25
  115. package/shared/localization/locales/sr.json +34 -25
  116. package/shared/localization/locales/sv.json +35 -26
  117. package/shared/localization/locales/ta.json +35 -26
  118. package/shared/localization/locales/te.json +35 -26
  119. package/shared/localization/locales/th.json +34 -25
  120. package/shared/localization/locales/tr.json +35 -26
  121. package/shared/localization/locales/uk.json +34 -25
  122. package/shared/localization/locales/vi.json +34 -25
  123. package/shared/localization/locales/zh-HK.json +35 -26
  124. package/shared/localization/locales/zh-TW.json +38 -29
  125. package/shared/localization/locales/zh.json +34 -25
  126. package/shared/util.d.ts +14 -2
  127. package/shared/util.js +25 -3
  128. package/tsconfig.json +2 -0
  129. package/types/artifacts.d.ts +5 -2
@@ -220,7 +220,7 @@ async function _navigation(navigationContext) {
220
220
 
221
221
  // Every required url is initialized to an empty string in `getBaseArtifacts`.
222
222
  // If we haven't set all the required urls yet, set them here.
223
- if (!Object.values(phaseState.baseArtifacts).every(Boolean)) {
223
+ if (!Object.values(phaseState.baseArtifacts.URL).every(Boolean)) {
224
224
  phaseState.baseArtifacts.URL = {
225
225
  requestedUrl: navigateResult.requestedUrl,
226
226
  mainDocumentUrl: navigateResult.mainDocumentUrl,
@@ -69,9 +69,9 @@ export function lookupLocale(locales?: (string | string[]) | undefined, possible
69
69
  * Returns a function that generates `LH.IcuMessage` objects to localize the
70
70
  * messages in `fileStrings` and the shared `i18n.UIStrings`.
71
71
  * @param {string} filename
72
- * @param {Record<string, string>} fileStrings
72
+ * @param {Record<string, string>=} fileStrings
73
73
  */
74
- export function createIcuMessageFn(filename: string, fileStrings: Record<string, string>): (message: string, values?: Record<string, string | number> | undefined) => LH.IcuMessage;
74
+ export function createIcuMessageFn(filename: string, fileStrings?: Record<string, string> | undefined): (message: string, values?: Record<string, string | number> | undefined) => LH.IcuMessage;
75
75
  /**
76
76
  * Returns true if the given value is a string or an LH.IcuMessage.
77
77
  * @param {unknown} value
@@ -168,9 +168,9 @@ function lookupLocale(locales, possibleLocales) {
168
168
  * Returns a function that generates `LH.IcuMessage` objects to localize the
169
169
  * messages in `fileStrings` and the shared `i18n.UIStrings`.
170
170
  * @param {string} filename
171
- * @param {Record<string, string>} fileStrings
171
+ * @param {Record<string, string>=} fileStrings
172
172
  */
173
- function createIcuMessageFn(filename, fileStrings) {
173
+ function createIcuMessageFn(filename, fileStrings = {}) {
174
174
  if (filename.startsWith('file://')) filename = url.fileURLToPath(filename);
175
175
 
176
176
  // In the common case, `filename` is an absolute path that needs to be transformed
@@ -6,7 +6,7 @@
6
6
 
7
7
  // https://www.ecma-international.org/ecma-262/9.0/index.html#sec-punctuators
8
8
  // eslint-disable-next-line max-len
9
- const PUNCTUATOR_REGEX = /(return|case|{|\(|\[|\.\.\.|;|,|<|>|<=|>=|==|!=|===|!==|\+|-|\*|%|\*\*|\+\+|--|<<|>>|>>>|&|\||\^|!|~|&&|\|\||\?|:|=|\+=|-=|\*=|%=|\*\*=|<<=|>>=|>>>=|&=|\|=|\^=|=>|\/|\/=|\})$/;
9
+ const PUNCTUATOR_REGEX = /(return|case|else|{|\(|\[|\.\.\.|;|,|<|>|<=|>=|==|!=|===|!==|\+|-|\*|%|\*\*|\+\+|--|<<|>>|>>>|&|\||\^|!|~|&&|\|\||\?|:|=|\+=|-=|\*=|%=|\*\*=|<<=|>>=|>>>=|&=|\|=|\^=|=>|\/|\/=|\})$/;
10
10
  const WHITESPACE_REGEX = /( |\n|\t)+$/;
11
11
 
12
12
  /**
@@ -66,6 +66,14 @@ export class NetworkRecorder extends NetworkRecorder_base {
66
66
  targetType: LH.Protocol.TargetType;
67
67
  sessionId?: string;
68
68
  }): void;
69
+ /**
70
+ * @param {{params: LH.Crdp.Network.ResponseReceivedExtraInfoEvent, targetType: LH.Protocol.TargetType, sessionId?: string}} event
71
+ */
72
+ onResponseReceivedExtraInfo(event: {
73
+ params: LH.Crdp.Network.ResponseReceivedExtraInfoEvent;
74
+ targetType: LH.Protocol.TargetType;
75
+ sessionId?: string;
76
+ }): void;
69
77
  /**
70
78
  * @param {{params: LH.Crdp.Network.DataReceivedEvent, targetType: LH.Protocol.TargetType, sessionId?: string}} event
71
79
  */
@@ -138,6 +138,18 @@ class NetworkRecorder extends RequestEventEmitter {
138
138
  request.onResponseReceived(data);
139
139
  }
140
140
 
141
+ /**
142
+ * @param {{params: LH.Crdp.Network.ResponseReceivedExtraInfoEvent, targetType: LH.Protocol.TargetType, sessionId?: string}} event
143
+ */
144
+ onResponseReceivedExtraInfo(event) {
145
+ const data = event.params;
146
+ const request = this._findRealRequestAndSetSession(
147
+ data.requestId, event.targetType, event.sessionId);
148
+ if (!request) return;
149
+ log.verbose('network', `${request.url} response received extra info`);
150
+ request.onResponseReceivedExtraInfo(data);
151
+ }
152
+
141
153
  /**
142
154
  * @param {{params: LH.Crdp.Network.DataReceivedEvent, targetType: LH.Protocol.TargetType, sessionId?: string}} event
143
155
  */
@@ -196,6 +208,7 @@ class NetworkRecorder extends RequestEventEmitter {
196
208
  case 'Network.requestWillBeSent': return this.onRequestWillBeSent(event);
197
209
  case 'Network.requestServedFromCache': return this.onRequestServedFromCache(event);
198
210
  case 'Network.responseReceived': return this.onResponseReceived(event);
211
+ case 'Network.responseReceivedExtraInfo': return this.onResponseReceivedExtraInfo(event);
199
212
  case 'Network.dataReceived': return this.onDataReceived(event);
200
213
  case 'Network.loadingFinished': return this.onLoadingFinished(event);
201
214
  case 'Network.loadingFailed': return this.onLoadingFailed(event);
@@ -66,6 +66,12 @@ export class NetworkRequest {
66
66
  * @return {boolean}
67
67
  */
68
68
  static isHstsRequest(record: NetworkRequest): boolean;
69
+ /**
70
+ * Returns whether the network request was sent encoded.
71
+ * @param {NetworkRequest} record
72
+ * @return {boolean}
73
+ */
74
+ static isContentEncoded(record: NetworkRequest): boolean;
69
75
  /**
70
76
  * Resource size is almost always the right one to be using because of the below:
71
77
  * `transferSize = resourceSize + headers.length`.
@@ -98,6 +104,7 @@ export class NetworkRequest {
98
104
  /** When the last byte of the response body is received, in milliseconds. */
99
105
  networkEndTime: number;
100
106
  transferSize: number;
107
+ responseHeadersTransferSize: number;
101
108
  resourceSize: number;
102
109
  fromDiskCache: boolean;
103
110
  fromMemoryCache: boolean;
@@ -155,6 +162,10 @@ export class NetworkRequest {
155
162
  * @param {LH.Crdp.Network.ResponseReceivedEvent} data
156
163
  */
157
164
  onResponseReceived(data: LH.Crdp.Network.ResponseReceivedEvent): void;
165
+ /**
166
+ * @param {LH.Crdp.Network.ResponseReceivedExtraInfoEvent} data
167
+ */
168
+ onResponseReceivedExtraInfo(data: LH.Crdp.Network.ResponseReceivedExtraInfoEvent): void;
158
169
  /**
159
170
  * @param {LH.Crdp.Network.DataReceivedEvent} data
160
171
  */
@@ -135,6 +135,7 @@ class NetworkRequest {
135
135
 
136
136
  // Go read the comment on _updateTransferSizeForLightrider.
137
137
  this.transferSize = 0;
138
+ this.responseHeadersTransferSize = 0;
138
139
  this.resourceSize = 0;
139
140
  this.fromDiskCache = false;
140
141
  this.fromMemoryCache = false;
@@ -246,6 +247,13 @@ class NetworkRequest {
246
247
  this.frameId = data.frameId;
247
248
  }
248
249
 
250
+ /**
251
+ * @param {LH.Crdp.Network.ResponseReceivedExtraInfoEvent} data
252
+ */
253
+ onResponseReceivedExtraInfo(data) {
254
+ this.responseHeadersText = data.headersText || '';
255
+ }
256
+
249
257
  /**
250
258
  * @param {LH.Crdp.Network.DataReceivedEvent} data
251
259
  */
@@ -344,6 +352,7 @@ class NetworkRequest {
344
352
  this.responseHeadersEndTime = timestamp * 1000;
345
353
 
346
354
  this.transferSize = response.encodedDataLength;
355
+ this.responseHeadersTransferSize = response.encodedDataLength;
347
356
  if (typeof response.fromDiskCache === 'boolean') this.fromDiskCache = response.fromDiskCache;
348
357
  if (typeof response.fromPrefetchCache === 'boolean') {
349
358
  this.fromPrefetchCache = response.fromPrefetchCache;
@@ -354,7 +363,6 @@ class NetworkRequest {
354
363
  this.timing = response.timing;
355
364
  if (resourceType) this.resourceType = RESOURCE_TYPES[resourceType];
356
365
  this.mimeType = response.mimeType;
357
- this.responseHeadersText = response.headersText || '';
358
366
  this.responseHeaders = NetworkRequest._headersDictToHeadersArray(response.headers);
359
367
 
360
368
  this.fetchedViaServiceWorker = !!response.fromServiceWorker;
@@ -600,6 +608,18 @@ class NetworkRequest {
600
608
  return reason === 'HSTS' && NetworkRequest.isSecureRequest(destination);
601
609
  }
602
610
 
611
+ /**
612
+ * Returns whether the network request was sent encoded.
613
+ * @param {NetworkRequest} record
614
+ * @return {boolean}
615
+ */
616
+ static isContentEncoded(record) {
617
+ // FYI: older devtools logs (like our test fixtures) seems to be lower case, while modern logs
618
+ // are Cased-Like-This.
619
+ const pattern = /^content-encoding$/i;
620
+ return record.responseHeaders.some(item => item.name.match(pattern));
621
+ }
622
+
603
623
  /**
604
624
  * Resource size is almost always the right one to be using because of the below:
605
625
  * `transferSize = resourceSize + headers.length`.
@@ -97,4 +97,9 @@ export function filterOutRectsContainedByOthers(rects: LH.Artifacts.Rect[]): LH.
97
97
  * @return {LH.Artifacts.Rect[]}
98
98
  */
99
99
  export function filterOutTinyRects(rects: LH.Artifacts.Rect[]): LH.Artifacts.Rect[];
100
+ /**
101
+ * @param {Array<number>} rect
102
+ * @return {LH.Artifacts.Rect}
103
+ */
104
+ export function traceRectToLHRect(rect: Array<number>): LH.Artifacts.Rect;
100
105
  //# sourceMappingURL=rect-helpers.d.ts.map
@@ -232,6 +232,20 @@ function allRectsContainedWithinEachOther(rectListA, rectListB) {
232
232
  return true;
233
233
  }
234
234
 
235
+ /**
236
+ * @param {Array<number>} rect
237
+ * @return {LH.Artifacts.Rect}
238
+ */
239
+ function traceRectToLHRect(rect) {
240
+ const rectArgs = {
241
+ x: rect[0],
242
+ y: rect[1],
243
+ width: rect[2],
244
+ height: rect[3],
245
+ };
246
+ return addRectTopAndBottom(rectArgs);
247
+ }
248
+
235
249
  export {
236
250
  rectContainsPoint,
237
251
  rectContains,
@@ -248,4 +262,5 @@ export {
248
262
  allRectsContainedWithinEachOther,
249
263
  filterOutRectsContainedByOthers,
250
264
  filterOutTinyRects,
265
+ traceRectToLHRect,
251
266
  };
@@ -4,14 +4,43 @@
4
4
  * @return {LH.Artifacts.NetworkRequest|undefined}
5
5
  */
6
6
  export function getRequestForScript(networkRecords: LH.Artifacts.NetworkRequest[], script: LH.Artifacts.Script): LH.Artifacts.NetworkRequest | undefined;
7
- /**
8
- * @license
9
- * Copyright 2022 Google LLC
10
- * SPDX-License-Identifier: Apache-2.0
11
- */
12
7
  /**
13
8
  * @param {LH.Artifacts.Script} script
14
9
  * @return {boolean}
15
10
  */
16
11
  export function isInline(script: LH.Artifacts.Script): boolean;
12
+ /**
13
+ * Estimates the number of bytes the content of this network record would have consumed on the network based on the
14
+ * uncompressed size (totalBytes). Uses the actual transfer size from the network record if applicable,
15
+ * minus the size of the response headers.
16
+ *
17
+ * This differs from `estimateTransferSize` only in that is subtracts the response headers from the estimate.
18
+ *
19
+ * @param {LH.Artifacts.NetworkRequest|undefined} networkRecord
20
+ * @param {number} totalBytes Uncompressed size of the resource
21
+ * @param {LH.Crdp.Network.ResourceType=} resourceType
22
+ * @return {number}
23
+ */
24
+ export function estimateCompressedContentSize(networkRecord: LH.Artifacts.NetworkRequest | undefined, totalBytes: number, resourceType?: LH.Crdp.Network.ResourceType | undefined): number;
25
+ /**
26
+ * Estimates the number of bytes this network record would have consumed on the network based on the
27
+ * uncompressed size (totalBytes). Uses the actual transfer size from the network record if applicable.
28
+ *
29
+ * @param {LH.Artifacts.NetworkRequest|undefined} networkRecord
30
+ * @param {number} totalBytes Uncompressed size of the resource
31
+ * @param {LH.Crdp.Network.ResourceType=} resourceType
32
+ * @return {number}
33
+ */
34
+ export function estimateTransferSize(networkRecord: LH.Artifacts.NetworkRequest | undefined, totalBytes: number, resourceType?: LH.Crdp.Network.ResourceType | undefined): number;
35
+ /**
36
+ * Utility function to estimate the ratio of the compression on the resource.
37
+ * This excludes the size of the response headers.
38
+ * Also caches the calculation.
39
+ * @param {Map<string, number>} compressionRatioByUrl
40
+ * @param {string} url
41
+ * @param {LH.Artifacts} artifacts
42
+ * @param {Array<LH.Artifacts.NetworkRequest>} networkRecords
43
+ */
44
+ export function estimateCompressionRatioForContent(compressionRatioByUrl: Map<string, number>, url: string, artifacts: LH.Artifacts, networkRecords: Array<LH.Artifacts.NetworkRequest>): number;
45
+ import { NetworkRequest } from './network-request.js';
17
46
  //# sourceMappingURL=script-helpers.d.ts.map
@@ -4,6 +4,8 @@
4
4
  * SPDX-License-Identifier: Apache-2.0
5
5
  */
6
6
 
7
+ import {NetworkRequest} from './network-request.js';
8
+
7
9
  /**
8
10
  * @param {LH.Artifacts.Script} script
9
11
  * @return {boolean}
@@ -25,7 +27,141 @@ function getRequestForScript(networkRecords, script) {
25
27
  return networkRequest;
26
28
  }
27
29
 
30
+ /**
31
+ * Estimates the number of bytes this network record would have consumed on the network based on the
32
+ * uncompressed size (totalBytes). Uses the actual transfer size from the network record if applicable.
33
+ *
34
+ * @param {LH.Artifacts.NetworkRequest|undefined} networkRecord
35
+ * @param {number} totalBytes Uncompressed size of the resource
36
+ * @param {LH.Crdp.Network.ResourceType=} resourceType
37
+ * @return {number}
38
+ */
39
+ function estimateTransferSize(networkRecord, totalBytes, resourceType) {
40
+ if (!networkRecord) {
41
+ // We don't know how many bytes this asset used on the network, but we can guess it was
42
+ // roughly the size of the content gzipped.
43
+ // See https://developers.google.com/web/fundamentals/performance/optimizing-content-efficiency/optimize-encoding-and-transfer for specific CSS/Script examples
44
+ // See https://discuss.httparchive.org/t/file-size-and-compression-savings/145 for fallback multipliers
45
+ switch (resourceType) {
46
+ case 'Stylesheet':
47
+ // Stylesheets tend to compress extremely well.
48
+ return Math.round(totalBytes * 0.2);
49
+ case 'Script':
50
+ case 'Document':
51
+ // Scripts and HTML compress fairly well too.
52
+ return Math.round(totalBytes * 0.33);
53
+ default:
54
+ // Otherwise we'll just fallback to the average savings in HTTPArchive
55
+ return Math.round(totalBytes * 0.5);
56
+ }
57
+ } else if (networkRecord.resourceType === resourceType) {
58
+ // This was a regular standalone asset, just use the transfer size.
59
+ return networkRecord.transferSize || 0;
60
+ } else {
61
+ // This was an asset that was inlined in a different resource type (e.g. HTML document).
62
+ // Use the compression ratio of the resource to estimate the total transferred bytes.
63
+ const transferSize = networkRecord.transferSize || 0;
64
+ const resourceSize = networkRecord.resourceSize || 0;
65
+ // Get the compression ratio, if it's an invalid number, assume no compression.
66
+ const compressionRatio = Number.isFinite(resourceSize) && resourceSize > 0 ?
67
+ (transferSize / resourceSize) : 1;
68
+ return Math.round(totalBytes * compressionRatio);
69
+ }
70
+ }
71
+
72
+ /**
73
+ * Estimates the number of bytes the content of this network record would have consumed on the network based on the
74
+ * uncompressed size (totalBytes). Uses the actual transfer size from the network record if applicable,
75
+ * minus the size of the response headers.
76
+ *
77
+ * This differs from `estimateTransferSize` only in that is subtracts the response headers from the estimate.
78
+ *
79
+ * @param {LH.Artifacts.NetworkRequest|undefined} networkRecord
80
+ * @param {number} totalBytes Uncompressed size of the resource
81
+ * @param {LH.Crdp.Network.ResourceType=} resourceType
82
+ * @return {number}
83
+ */
84
+ function estimateCompressedContentSize(networkRecord, totalBytes, resourceType) {
85
+ if (!networkRecord) {
86
+ // We don't know how many bytes this asset used on the network, but we can guess it was
87
+ // roughly the size of the content gzipped.
88
+ // See https://developers.google.com/web/fundamentals/performance/optimizing-content-efficiency/optimize-encoding-and-transfer for specific CSS/Script examples
89
+ // See https://discuss.httparchive.org/t/file-size-and-compression-savings/145 for fallback multipliers
90
+ switch (resourceType) {
91
+ case 'Stylesheet':
92
+ // Stylesheets tend to compress extremely well.
93
+ return Math.round(totalBytes * 0.2);
94
+ case 'Script':
95
+ case 'Document':
96
+ // Scripts and HTML compress fairly well too.
97
+ return Math.round(totalBytes * 0.33);
98
+ default:
99
+ // Otherwise we'll just fallback to the average savings in HTTPArchive
100
+ return Math.round(totalBytes * 0.5);
101
+ }
102
+ }
103
+
104
+ // Get the size of the response body on the network.
105
+ let contentTransferSize = networkRecord.transferSize || 0;
106
+ if (!NetworkRequest.isContentEncoded(networkRecord)) {
107
+ // This is not encoded, so we can use resourceSize directly.
108
+ // This would be equivalent to transfer size minus headers transfer size, but transfer size
109
+ // may also include bytes for SSL connection etc.
110
+ contentTransferSize = networkRecord.resourceSize;
111
+ } else if (networkRecord.responseHeadersTransferSize) {
112
+ // Subtract the size of the encoded headers.
113
+ contentTransferSize =
114
+ Math.max(0, contentTransferSize - networkRecord.responseHeadersTransferSize);
115
+ }
116
+
117
+ if (networkRecord.resourceType === resourceType) {
118
+ // This was a regular standalone asset, just use the transfer size.
119
+ return contentTransferSize;
120
+ } else {
121
+ // This was an asset that was inlined in a different resource type (e.g. HTML document).
122
+ // Use the compression ratio of the resource to estimate the total transferred bytes.
123
+ const resourceSize = networkRecord.resourceSize || 0;
124
+ // Get the compression ratio, if it's an invalid number, assume no compression.
125
+ const compressionRatio = Number.isFinite(resourceSize) && resourceSize > 0 ?
126
+ (contentTransferSize / resourceSize) : 1;
127
+ return Math.round(totalBytes * compressionRatio);
128
+ }
129
+ }
130
+
131
+ /**
132
+ * Utility function to estimate the ratio of the compression on the resource.
133
+ * This excludes the size of the response headers.
134
+ * Also caches the calculation.
135
+ * @param {Map<string, number>} compressionRatioByUrl
136
+ * @param {string} url
137
+ * @param {LH.Artifacts} artifacts
138
+ * @param {Array<LH.Artifacts.NetworkRequest>} networkRecords
139
+ */
140
+ function estimateCompressionRatioForContent(compressionRatioByUrl, url,
141
+ artifacts, networkRecords) {
142
+ let compressionRatio = compressionRatioByUrl.get(url);
143
+ if (compressionRatio !== undefined) return compressionRatio;
144
+
145
+ const script = artifacts.Scripts.find(script => script.url === url);
146
+
147
+ if (!script) {
148
+ // Can't find content, so just use 1.
149
+ compressionRatio = 1;
150
+ } else {
151
+ const networkRecord = getRequestForScript(networkRecords, script);
152
+ const contentLength = networkRecord?.resourceSize || script.length || 0;
153
+ const compressedSize = estimateCompressedContentSize(networkRecord, contentLength, 'Script');
154
+ compressionRatio = compressedSize / contentLength;
155
+ }
156
+
157
+ compressionRatioByUrl.set(url, compressionRatio);
158
+ return compressionRatio;
159
+ }
160
+
28
161
  export {
29
162
  getRequestForScript,
30
163
  isInline,
164
+ estimateCompressedContentSize,
165
+ estimateTransferSize,
166
+ estimateCompressionRatioForContent,
31
167
  };
@@ -23,6 +23,12 @@ declare class UrlUtils {
23
23
  * @return {?string}
24
24
  */
25
25
  static getOrigin(url: string): string | null;
26
+ /**
27
+ * Returns a primary domain for provided hostname (e.g. www.example.com -> example.com).
28
+ * @param {string|URL} url hostname or URL object
29
+ * @return {string}
30
+ */
31
+ static getRootDomain(url: string | URL): string;
26
32
  /**
27
33
  * Check if rootDomains matches
28
34
  *
@@ -4,6 +4,8 @@
4
4
  * SPDX-License-Identifier: Apache-2.0
5
5
  */
6
6
 
7
+ import {getDomain} from 'tldts-icann';
8
+
7
9
  import {Util} from '../../shared/util.js';
8
10
  import {LighthouseError} from './lh-error.js';
9
11
 
@@ -99,6 +101,16 @@ class UrlUtils {
99
101
  }
100
102
  }
101
103
 
104
+ /**
105
+ * Returns a primary domain for provided hostname (e.g. www.example.com -> example.com).
106
+ * @param {string|URL} url hostname or URL object
107
+ * @return {string}
108
+ */
109
+ static getRootDomain(url) {
110
+ const parsedUrl = Util.createOrReturnURL(url);
111
+ return getDomain(parsedUrl.href) || parsedUrl.hostname;
112
+ }
113
+
102
114
  /**
103
115
  * Check if rootDomains matches
104
116
  *
@@ -120,8 +132,8 @@ class UrlUtils {
120
132
  }
121
133
 
122
134
  // get the string before the tld
123
- const urlARootDomain = Util.getRootDomain(urlAInfo);
124
- const urlBRootDomain = Util.getRootDomain(urlBInfo);
135
+ const urlARootDomain = UrlUtils.getRootDomain(urlAInfo);
136
+ const urlBRootDomain = UrlUtils.getRootDomain(urlBInfo);
125
137
 
126
138
  return urlARootDomain === urlBRootDomain;
127
139
  }
package/core/runner.js CHANGED
@@ -317,7 +317,11 @@ class Runner {
317
317
  for (const k of keys) {
318
318
  if (!isDeepEqual(normalizedGatherSettings[k], normalizedAuditSettings[k])) {
319
319
  throw new Error(
320
- `Cannot change settings between gathering and auditing. Difference found at: ${k}`);
320
+ `Cannot change settings between gathering and auditing
321
+ Difference found at: \`${k}\`
322
+ ${normalizedGatherSettings[k]}
323
+ vs
324
+ ${normalizedAuditSettings[k]}`);
321
325
  }
322
326
  }
323
327
 
@@ -6,9 +6,22 @@
6
6
  ##
7
7
 
8
8
  # Download chrome inside of our CI env.
9
+ # Takes one arg - the location to extract ToT chrome to. Defaults to .tmp/chrome-tot
10
+ # If already exists, this script does nothing.
9
11
 
10
12
  set -euo pipefail
11
13
 
14
+ SCRIPT_DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )"
15
+ LH_ROOT_DIR="$SCRIPT_DIR/../.."
16
+
17
+ chrome_out=${1:-"$LH_ROOT_DIR/.tmp/chrome-tot"}
18
+ mkdir -p "$LH_ROOT_DIR/.tmp"
19
+
20
+ if [ -e "$chrome_out" ]; then
21
+ echo "cached chrome found"
22
+ exit 0
23
+ fi
24
+
12
25
  unameOut="$(uname -s)"
13
26
  case "${unameOut}" in
14
27
  Linux*) machine=Linux;;
@@ -17,19 +30,49 @@ case "${unameOut}" in
17
30
  *) machine="UNKNOWN:${unameOut}"
18
31
  esac
19
32
 
20
- if [ "$machine" == "MinGw" ]; then
21
- url="https://download-chromium.appspot.com/dl/Win?type=snapshots"
22
- elif [ "$machine" == "Linux" ]; then
23
- url="https://download-chromium.appspot.com/dl/Linux_x64?type=snapshots"
24
- elif [ "$machine" == "Mac" ]; then
25
- url="https://download-chromium.appspot.com/dl/Mac?type=snapshots"
26
- else
27
- echo "unsupported platform"
28
- exit 1
29
- fi
33
+ # Only set this to true when actual ToT is broken and we can't fix it yet.
34
+ should_hardcode_ci=false
30
35
 
31
- if [ -e "$CHROME_PATH" ]; then
32
- echo "cached chrome found"
36
+ if [[ "${CI:-}" ]] && [ "$should_hardcode_ci" == true ]; then
37
+ rev=1228630
38
+ if [ "$machine" == "MinGw" ]; then
39
+ url="http://commondatastorage.googleapis.com/chromium-browser-snapshots/Win_x64/$rev/chrome-win.zip"
40
+ elif [ "$machine" == "Linux" ]; then
41
+ url="http://commondatastorage.googleapis.com/chromium-browser-snapshots/Linux_x64/$rev/chrome-linux.zip"
42
+ elif [ "$machine" == "Mac" ]; then
43
+ arch="$(uname -m)"
44
+ if [ "$arch" == "arm64" ]; then
45
+ url="http://commondatastorage.googleapis.com/chromium-browser-snapshots/Mac_Arm/$rev/chrome-mac.zip"
46
+ else
47
+ url="http://commondatastorage.googleapis.com/chromium-browser-snapshots/Mac/$rev/chrome-mac.zip"
48
+ fi
49
+ else
50
+ echo "unsupported platform"
51
+ exit 1
52
+ fi
33
53
  else
34
- curl "$url" -Lo chrome.zip && unzip -q chrome.zip
54
+ if [ "$machine" == "MinGw" ]; then
55
+ url="https://download-chromium.appspot.com/dl/Win?type=snapshots"
56
+ elif [ "$machine" == "Linux" ]; then
57
+ url="https://download-chromium.appspot.com/dl/Linux_x64?type=snapshots"
58
+ elif [ "$machine" == "Mac" ]; then
59
+ arch="$(uname -m)"
60
+ if [ "$arch" == "arm64" ]; then
61
+ url="https://download-chromium.appspot.com/dl/Mac_Arm?type=snapshots"
62
+ else
63
+ url="https://download-chromium.appspot.com/dl/Mac?type=snapshots"
64
+ fi
65
+ else
66
+ echo "unsupported platform"
67
+ exit 1
68
+ fi
35
69
  fi
70
+
71
+ echo "downloading $url"
72
+
73
+ mkdir -p .tmp-download && cd .tmp-download
74
+ curl "$url" -Lo chrome.zip && unzip -q chrome.zip && rm chrome.zip
75
+ mv * "$chrome_out"
76
+ cd - && rm -rf .tmp-download
77
+
78
+ ls "$chrome_out"