lighthouse 9.5.0-dev.20221115 → 9.5.0-dev.20221117
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.
- package/cli/cli-flags.js +1 -1
- package/cli/printer.js +1 -1
- package/cli/test/smokehouse/frontends/smokehouse-bin.js +1 -1
- package/core/audits/byte-efficiency/render-blocking-resources.js +1 -1
- package/core/audits/byte-efficiency/uses-long-cache-ttl.js +87 -88
- package/core/audits/critical-request-chains.js +41 -42
- package/core/audits/is-on-https.js +38 -39
- package/core/audits/metrics/first-contentful-paint-3g.js +1 -1
- package/core/audits/performance-budget.js +1 -1
- package/core/audits/seo/is-crawlable.js +51 -54
- package/core/audits/timing-budget.js +1 -1
- package/core/audits/user-timings.js +42 -43
- package/core/computed/computed-artifact.js +3 -3
- package/core/computed/load-simulator.js +1 -1
- package/core/computed/metrics/responsiveness.js +1 -1
- package/core/computed/metrics/timing-summary.js +2 -2
- package/core/computed/network-analysis.js +1 -1
- package/core/computed/resource-summary.js +2 -2
- package/core/config/budget.js +2 -2
- package/core/config/config-helpers.js +2 -2
- package/core/gather/gatherers/full-page-screenshot.js +3 -1
- package/core/lib/lantern-trace-saver.js +1 -1
- package/core/lib/network-request.js +1 -1
- package/package.json +1 -1
- package/types/artifacts.d.ts +5 -3
- package/types/audit.d.ts +2 -1
- package/types/externs.d.ts +0 -95
- package/types/global-lh.d.ts +3 -0
- package/types/node.d.ts +32 -0
- package/types/utility-types.d.ts +72 -0
package/cli/cli-flags.js
CHANGED
|
@@ -351,7 +351,7 @@ function getFlags(manualArgv, options = {}) {
|
|
|
351
351
|
// Augmenting yargs type with auto-camelCasing breaks in tsc@4.1.2 and @types/yargs@15.0.11,
|
|
352
352
|
// so for now cast to add yarg's camelCase properties to type.
|
|
353
353
|
const argv = /** @type {Awaited<typeof parser.argv>} */ (parser.argv);
|
|
354
|
-
const cliFlags = /** @type {typeof argv & CamelCasify<typeof argv>} */ (argv);
|
|
354
|
+
const cliFlags = /** @type {typeof argv & LH.Util.CamelCasify<typeof argv>} */ (argv);
|
|
355
355
|
|
|
356
356
|
// yargs will return `undefined` for options that have a `coerce` function but
|
|
357
357
|
// are not actually present in the user input. Instead of passing properties
|
package/cli/printer.js
CHANGED
|
@@ -13,7 +13,7 @@ import log from 'lighthouse-logger';
|
|
|
13
13
|
* 'json': JSON formatted results
|
|
14
14
|
* 'html': An HTML report
|
|
15
15
|
* 'csv': CSV formatted results
|
|
16
|
-
* @type {SelfMap<LH.OutputMode>}
|
|
16
|
+
* @type {LH.Util.SelfMap<LH.OutputMode>}
|
|
17
17
|
*/
|
|
18
18
|
const OutputMode = {
|
|
19
19
|
json: 'json',
|
|
@@ -174,7 +174,7 @@ async function begin() {
|
|
|
174
174
|
// Augmenting yargs type with auto-camelCasing breaks in tsc@4.1.2 and @types/yargs@15.0.11,
|
|
175
175
|
// so for now cast to add yarg's camelCase properties to type.
|
|
176
176
|
const argv =
|
|
177
|
-
/** @type {Awaited<typeof rawArgv> & CamelCasify<Awaited<typeof rawArgv>>} */ (rawArgv);
|
|
177
|
+
/** @type {Awaited<typeof rawArgv> & LH.Util.CamelCasify<Awaited<typeof rawArgv>>} */ (rawArgv);
|
|
178
178
|
|
|
179
179
|
const jobs = Number.isFinite(argv.jobs) ? argv.jobs : undefined;
|
|
180
180
|
const retries = Number.isFinite(argv.retries) ? argv.retries : undefined;
|
|
@@ -138,7 +138,7 @@ class RenderBlockingResources extends Audit {
|
|
|
138
138
|
const simulator = await LoadSimulator.request(simulatorData, context);
|
|
139
139
|
const wastedCssBytes = await RenderBlockingResources.computeWastedCSSBytes(artifacts, context);
|
|
140
140
|
|
|
141
|
-
/** @type {
|
|
141
|
+
/** @type {LH.Audit.Context['settings']} */
|
|
142
142
|
const metricSettings = {
|
|
143
143
|
...context.settings,
|
|
144
144
|
throttlingMethod: 'simulate',
|
|
@@ -194,103 +194,102 @@ class CacheHeaders extends Audit {
|
|
|
194
194
|
* @param {LH.Audit.Context} context
|
|
195
195
|
* @return {Promise<LH.Audit.Product>}
|
|
196
196
|
*/
|
|
197
|
-
static audit(artifacts, context) {
|
|
197
|
+
static async audit(artifacts, context) {
|
|
198
198
|
const devtoolsLogs = artifacts.devtoolsLogs[Audit.DEFAULT_PASS];
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
}
|
|
216
|
-
}
|
|
217
|
-
|
|
218
|
-
const cacheControl = parseCacheControl(headers.get('cache-control'));
|
|
219
|
-
if (this.shouldSkipRecord(headers, cacheControl)) {
|
|
220
|
-
continue;
|
|
199
|
+
const records = await NetworkRecords.request(devtoolsLogs, context);
|
|
200
|
+
const results = [];
|
|
201
|
+
let totalWastedBytes = 0;
|
|
202
|
+
|
|
203
|
+
for (const record of records) {
|
|
204
|
+
if (!CacheHeaders.isCacheableAsset(record)) continue;
|
|
205
|
+
|
|
206
|
+
/** @type {Map<string, string>} */
|
|
207
|
+
const headers = new Map();
|
|
208
|
+
for (const header of record.responseHeaders || []) {
|
|
209
|
+
if (headers.has(header.name.toLowerCase())) {
|
|
210
|
+
const previousHeaderValue = headers.get(header.name.toLowerCase());
|
|
211
|
+
headers.set(header.name.toLowerCase(),
|
|
212
|
+
`${previousHeaderValue}, ${header.value}`);
|
|
213
|
+
} else {
|
|
214
|
+
headers.set(header.name.toLowerCase(), header.value);
|
|
221
215
|
}
|
|
216
|
+
}
|
|
222
217
|
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
(!Number.isFinite(cacheLifetimeInSeconds) || cacheLifetimeInSeconds <= 0)) {
|
|
228
|
-
continue;
|
|
229
|
-
}
|
|
230
|
-
cacheLifetimeInSeconds = cacheLifetimeInSeconds || 0;
|
|
231
|
-
|
|
232
|
-
// Ignore assets whose cache lifetime is already high enough
|
|
233
|
-
const cacheHitProbability = CacheHeaders.getCacheHitProbability(cacheLifetimeInSeconds);
|
|
234
|
-
if (cacheHitProbability > IGNORE_THRESHOLD_IN_PERCENT) continue;
|
|
235
|
-
|
|
236
|
-
const url = UrlUtils.elideDataURI(record.url);
|
|
237
|
-
const totalBytes = record.transferSize || 0;
|
|
238
|
-
const wastedBytes = (1 - cacheHitProbability) * totalBytes;
|
|
239
|
-
|
|
240
|
-
totalWastedBytes += wastedBytes;
|
|
241
|
-
|
|
242
|
-
// Include cacheControl info (if it exists) per url as a diagnostic.
|
|
243
|
-
/** @type {LH.Audit.Details.DebugData|undefined} */
|
|
244
|
-
let debugData;
|
|
245
|
-
if (cacheControl) {
|
|
246
|
-
debugData = {
|
|
247
|
-
type: 'debugdata',
|
|
248
|
-
...cacheControl,
|
|
249
|
-
};
|
|
250
|
-
}
|
|
218
|
+
const cacheControl = parseCacheControl(headers.get('cache-control'));
|
|
219
|
+
if (this.shouldSkipRecord(headers, cacheControl)) {
|
|
220
|
+
continue;
|
|
221
|
+
}
|
|
251
222
|
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
223
|
+
// Ignore if cacheLifetimeInSeconds is a nonpositive number.
|
|
224
|
+
let cacheLifetimeInSeconds = CacheHeaders.computeCacheLifetimeInSeconds(
|
|
225
|
+
headers, cacheControl);
|
|
226
|
+
if (cacheLifetimeInSeconds !== null &&
|
|
227
|
+
(!Number.isFinite(cacheLifetimeInSeconds) || cacheLifetimeInSeconds <= 0)) {
|
|
228
|
+
continue;
|
|
229
|
+
}
|
|
230
|
+
cacheLifetimeInSeconds = cacheLifetimeInSeconds || 0;
|
|
231
|
+
|
|
232
|
+
// Ignore assets whose cache lifetime is already high enough
|
|
233
|
+
const cacheHitProbability = CacheHeaders.getCacheHitProbability(cacheLifetimeInSeconds);
|
|
234
|
+
if (cacheHitProbability > IGNORE_THRESHOLD_IN_PERCENT) continue;
|
|
235
|
+
|
|
236
|
+
const url = UrlUtils.elideDataURI(record.url);
|
|
237
|
+
const totalBytes = record.transferSize || 0;
|
|
238
|
+
const wastedBytes = (1 - cacheHitProbability) * totalBytes;
|
|
239
|
+
|
|
240
|
+
totalWastedBytes += wastedBytes;
|
|
241
|
+
|
|
242
|
+
// Include cacheControl info (if it exists) per url as a diagnostic.
|
|
243
|
+
/** @type {LH.Audit.Details.DebugData|undefined} */
|
|
244
|
+
let debugData;
|
|
245
|
+
if (cacheControl) {
|
|
246
|
+
debugData = {
|
|
247
|
+
type: 'debugdata',
|
|
248
|
+
...cacheControl,
|
|
249
|
+
};
|
|
260
250
|
}
|
|
261
251
|
|
|
262
|
-
results.
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
252
|
+
results.push({
|
|
253
|
+
url,
|
|
254
|
+
debugData,
|
|
255
|
+
cacheLifetimeMs: cacheLifetimeInSeconds * 1000,
|
|
256
|
+
cacheHitProbability,
|
|
257
|
+
totalBytes,
|
|
258
|
+
wastedBytes,
|
|
266
259
|
});
|
|
260
|
+
}
|
|
267
261
|
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
/** @type {LH.Audit.Details.Table['headings']} */
|
|
274
|
-
const headings = [
|
|
275
|
-
{key: 'url', valueType: 'url', label: str_(i18n.UIStrings.columnURL)},
|
|
276
|
-
// TODO(i18n): pre-compute localized duration
|
|
277
|
-
{key: 'cacheLifetimeMs', valueType: 'ms', label: str_(i18n.UIStrings.columnCacheTTL),
|
|
278
|
-
displayUnit: 'duration'},
|
|
279
|
-
{key: 'totalBytes', valueType: 'bytes', label: str_(i18n.UIStrings.columnTransferSize),
|
|
280
|
-
displayUnit: 'kb', granularity: 1},
|
|
281
|
-
];
|
|
282
|
-
|
|
283
|
-
const summary = {wastedBytes: totalWastedBytes};
|
|
284
|
-
const details = Audit.makeTableDetails(headings, results, summary);
|
|
285
|
-
|
|
286
|
-
return {
|
|
287
|
-
score,
|
|
288
|
-
numericValue: totalWastedBytes,
|
|
289
|
-
numericUnit: 'byte',
|
|
290
|
-
displayValue: str_(UIStrings.displayValue, {itemCount: results.length}),
|
|
291
|
-
details,
|
|
292
|
-
};
|
|
262
|
+
results.sort((a, b) => {
|
|
263
|
+
return a.cacheLifetimeMs - b.cacheLifetimeMs ||
|
|
264
|
+
b.totalBytes - a.totalBytes ||
|
|
265
|
+
a.url.localeCompare(b.url);
|
|
293
266
|
});
|
|
267
|
+
|
|
268
|
+
const score = Audit.computeLogNormalScore(
|
|
269
|
+
{p10: context.options.p10, median: context.options.median},
|
|
270
|
+
totalWastedBytes
|
|
271
|
+
);
|
|
272
|
+
|
|
273
|
+
/** @type {LH.Audit.Details.Table['headings']} */
|
|
274
|
+
const headings = [
|
|
275
|
+
{key: 'url', valueType: 'url', label: str_(i18n.UIStrings.columnURL)},
|
|
276
|
+
// TODO(i18n): pre-compute localized duration
|
|
277
|
+
{key: 'cacheLifetimeMs', valueType: 'ms', label: str_(i18n.UIStrings.columnCacheTTL),
|
|
278
|
+
displayUnit: 'duration'},
|
|
279
|
+
{key: 'totalBytes', valueType: 'bytes', label: str_(i18n.UIStrings.columnTransferSize),
|
|
280
|
+
displayUnit: 'kb', granularity: 1},
|
|
281
|
+
];
|
|
282
|
+
|
|
283
|
+
const summary = {wastedBytes: totalWastedBytes};
|
|
284
|
+
const details = Audit.makeTableDetails(headings, results, summary);
|
|
285
|
+
|
|
286
|
+
return {
|
|
287
|
+
score,
|
|
288
|
+
numericValue: totalWastedBytes,
|
|
289
|
+
numericUnit: 'byte',
|
|
290
|
+
displayValue: str_(UIStrings.displayValue, {itemCount: results.length}),
|
|
291
|
+
details,
|
|
292
|
+
};
|
|
294
293
|
}
|
|
295
294
|
}
|
|
296
295
|
|
|
@@ -166,52 +166,51 @@ class CriticalRequestChains extends Audit {
|
|
|
166
166
|
* @param {LH.Audit.Context} context
|
|
167
167
|
* @return {Promise<LH.Audit.Product>}
|
|
168
168
|
*/
|
|
169
|
-
static audit(artifacts, context) {
|
|
169
|
+
static async audit(artifacts, context) {
|
|
170
170
|
const trace = artifacts.traces[Audit.DEFAULT_PASS];
|
|
171
171
|
const devtoolsLog = artifacts.devtoolsLogs[Audit.DEFAULT_PASS];
|
|
172
172
|
const URL = artifacts.URL;
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
childIds.forEach(id => {
|
|
183
|
-
const child = node[id];
|
|
184
|
-
if (child.children) {
|
|
185
|
-
walk(child.children, depth + 1);
|
|
186
|
-
} else {
|
|
187
|
-
// if the node doesn't have a children field, then it is a leaf, so +1
|
|
188
|
-
chainCount++;
|
|
189
|
-
}
|
|
190
|
-
}, '');
|
|
191
|
-
}
|
|
192
|
-
// Convert
|
|
193
|
-
const flattenedChains = CriticalRequestChains.flattenRequests(chains);
|
|
194
|
-
|
|
195
|
-
// Account for initial navigation
|
|
196
|
-
const initialNavKey = Object.keys(flattenedChains)[0];
|
|
197
|
-
const initialNavChildren = initialNavKey && flattenedChains[initialNavKey].children;
|
|
198
|
-
if (initialNavChildren && Object.keys(initialNavChildren).length > 0) {
|
|
199
|
-
walk(initialNavChildren, 0);
|
|
200
|
-
}
|
|
173
|
+
const chains = await ComputedChains.request({devtoolsLog, trace, URL}, context);
|
|
174
|
+
let chainCount = 0;
|
|
175
|
+
/**
|
|
176
|
+
* @param {LH.Audit.Details.SimpleCriticalRequestNode} node
|
|
177
|
+
* @param {number} depth
|
|
178
|
+
*/
|
|
179
|
+
function walk(node, depth) {
|
|
180
|
+
const childIds = Object.keys(node);
|
|
201
181
|
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
182
|
+
childIds.forEach(id => {
|
|
183
|
+
const child = node[id];
|
|
184
|
+
if (child.children) {
|
|
185
|
+
walk(child.children, depth + 1);
|
|
186
|
+
} else {
|
|
187
|
+
// if the node doesn't have a children field, then it is a leaf, so +1
|
|
188
|
+
chainCount++;
|
|
189
|
+
}
|
|
190
|
+
}, '');
|
|
191
|
+
}
|
|
192
|
+
// Convert
|
|
193
|
+
const flattenedChains = CriticalRequestChains.flattenRequests(chains);
|
|
194
|
+
|
|
195
|
+
// Account for initial navigation
|
|
196
|
+
const initialNavKey = Object.keys(flattenedChains)[0];
|
|
197
|
+
const initialNavChildren = initialNavKey && flattenedChains[initialNavKey].children;
|
|
198
|
+
if (initialNavChildren && Object.keys(initialNavChildren).length > 0) {
|
|
199
|
+
walk(initialNavChildren, 0);
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
const longestChain = CriticalRequestChains._getLongestChain(flattenedChains);
|
|
203
|
+
|
|
204
|
+
return {
|
|
205
|
+
score: Number(chainCount === 0),
|
|
206
|
+
notApplicable: chainCount === 0,
|
|
207
|
+
displayValue: chainCount ? str_(UIStrings.displayValue, {itemCount: chainCount}) : '',
|
|
208
|
+
details: {
|
|
209
|
+
type: 'criticalrequestchain',
|
|
210
|
+
chains: flattenedChains,
|
|
211
|
+
longestChain,
|
|
212
|
+
},
|
|
213
|
+
};
|
|
215
214
|
}
|
|
216
215
|
}
|
|
217
216
|
|
|
@@ -70,53 +70,52 @@ class HTTPS extends Audit {
|
|
|
70
70
|
* @param {LH.Audit.Context} context
|
|
71
71
|
* @return {Promise<LH.Audit.Product>}
|
|
72
72
|
*/
|
|
73
|
-
static audit(artifacts, context) {
|
|
73
|
+
static async audit(artifacts, context) {
|
|
74
74
|
const devtoolsLogs = artifacts.devtoolsLogs[Audit.DEFAULT_PASS];
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
75
|
+
const networkRecords = await NetworkRecords.request(devtoolsLogs, context);
|
|
76
|
+
const insecureURLs = networkRecords
|
|
77
|
+
.filter(record => !NetworkRequest.isSecureRequest(record))
|
|
78
|
+
.map(record => UrlUtils.elideDataURI(record.url));
|
|
79
79
|
|
|
80
|
-
|
|
81
|
-
|
|
80
|
+
/** @type {Array<{url: string, resolution?: LH.IcuMessage|string}>} */
|
|
81
|
+
const items = Array.from(new Set(insecureURLs)).map(url => ({url, resolution: undefined}));
|
|
82
82
|
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
83
|
+
/** @type {LH.Audit.Details.Table['headings']} */
|
|
84
|
+
const headings = [
|
|
85
|
+
{key: 'url', valueType: 'url', label: str_(UIStrings.columnInsecureURL)},
|
|
86
|
+
{key: 'resolution', valueType: 'text', label: str_(UIStrings.columnResolution)},
|
|
87
|
+
];
|
|
88
88
|
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
}
|
|
95
|
-
item.resolution = resolutionToString[details.resolutionStatus] ?
|
|
96
|
-
str_(resolutionToString[details.resolutionStatus]) :
|
|
97
|
-
details.resolutionStatus;
|
|
89
|
+
for (const details of artifacts.InspectorIssues.mixedContentIssue) {
|
|
90
|
+
let item = items.find(item => item.url === details.insecureURL);
|
|
91
|
+
if (!item) {
|
|
92
|
+
item = {url: details.insecureURL};
|
|
93
|
+
items.push(item);
|
|
98
94
|
}
|
|
95
|
+
item.resolution = resolutionToString[details.resolutionStatus] ?
|
|
96
|
+
str_(resolutionToString[details.resolutionStatus]) :
|
|
97
|
+
details.resolutionStatus;
|
|
98
|
+
}
|
|
99
99
|
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
100
|
+
// If a resolution wasn't assigned from an InspectorIssue, then the item
|
|
101
|
+
// is not blocked by the browser but we've determined it is insecure anyhow.
|
|
102
|
+
// For example, if the URL is localhost, all `http` requests are valid
|
|
103
|
+
// (localhost is a secure context), but we still identify `http` requests
|
|
104
|
+
// as an "Allowed" insecure URL.
|
|
105
|
+
for (const item of items) {
|
|
106
|
+
if (!item.resolution) item.resolution = str_(UIStrings.allowed);
|
|
107
|
+
}
|
|
108
108
|
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
109
|
+
let displayValue;
|
|
110
|
+
if (items.length > 0) {
|
|
111
|
+
displayValue = str_(UIStrings.displayValue, {itemCount: items.length});
|
|
112
|
+
}
|
|
113
113
|
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
});
|
|
114
|
+
return {
|
|
115
|
+
score: Number(items.length === 0),
|
|
116
|
+
displayValue,
|
|
117
|
+
details: Audit.makeTableDetails(headings, items),
|
|
118
|
+
};
|
|
120
119
|
}
|
|
121
120
|
}
|
|
122
121
|
|
|
@@ -49,7 +49,7 @@ class FirstContentfulPaint3G extends Audit {
|
|
|
49
49
|
const gatherContext = artifacts.GatherContext;
|
|
50
50
|
const trace = artifacts.traces[Audit.DEFAULT_PASS];
|
|
51
51
|
const devtoolsLog = artifacts.devtoolsLogs[Audit.DEFAULT_PASS];
|
|
52
|
-
/** @type {
|
|
52
|
+
/** @type {LH.Audit.Context['settings']} */
|
|
53
53
|
const settings = {...context.settings, throttlingMethod: 'simulate', throttling: regular3G};
|
|
54
54
|
const metricComputationData = {trace, devtoolsLog, gatherContext, settings, URL: artifacts.URL};
|
|
55
55
|
const metricResult = await ComputedFcp.request(metricComputationData, context);
|
|
@@ -90,63 +90,60 @@ class IsCrawlable extends Audit {
|
|
|
90
90
|
* @param {LH.Audit.Context} context
|
|
91
91
|
* @return {Promise<LH.Audit.Product>}
|
|
92
92
|
*/
|
|
93
|
-
static audit(artifacts, context) {
|
|
93
|
+
static async audit(artifacts, context) {
|
|
94
94
|
const devtoolsLog = artifacts.devtoolsLogs[Audit.DEFAULT_PASS];
|
|
95
95
|
const metaRobots = artifacts.MetaElements.find(meta => meta.name === 'robots');
|
|
96
|
+
const mainResource = await MainResource.request({devtoolsLog, URL: artifacts.URL}, context);
|
|
97
|
+
/** @type {LH.Audit.Details.Table['items']} */
|
|
98
|
+
const blockingDirectives = [];
|
|
99
|
+
|
|
100
|
+
if (metaRobots) {
|
|
101
|
+
const metaRobotsContent = metaRobots.content || '';
|
|
102
|
+
const isBlocking = hasBlockingDirective(metaRobotsContent);
|
|
103
|
+
|
|
104
|
+
if (isBlocking) {
|
|
105
|
+
blockingDirectives.push({
|
|
106
|
+
source: {
|
|
107
|
+
...Audit.makeNodeItem(metaRobots.node),
|
|
108
|
+
snippet: `<meta name="robots" content="${metaRobotsContent}" />`,
|
|
109
|
+
},
|
|
110
|
+
});
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
mainResource.responseHeaders && mainResource.responseHeaders
|
|
115
|
+
.filter(h => h.name.toLowerCase() === ROBOTS_HEADER && !hasUserAgent(h.value) &&
|
|
116
|
+
hasBlockingDirective(h.value))
|
|
117
|
+
.forEach(h => blockingDirectives.push({source: `${h.name}: ${h.value}`}));
|
|
118
|
+
|
|
119
|
+
if (artifacts.RobotsTxt.content) {
|
|
120
|
+
const robotsFileUrl = new URL('/robots.txt', mainResource.url);
|
|
121
|
+
const robotsTxt = robotsParser(robotsFileUrl.href, artifacts.RobotsTxt.content);
|
|
122
|
+
|
|
123
|
+
if (!robotsTxt.isAllowed(mainResource.url)) {
|
|
124
|
+
const line = robotsTxt.getMatchingLineNumber(mainResource.url) || 1;
|
|
125
|
+
blockingDirectives.push({
|
|
126
|
+
source: {
|
|
127
|
+
type: /** @type {const} */ ('source-location'),
|
|
128
|
+
url: robotsFileUrl.href,
|
|
129
|
+
urlProvider: /** @type {const} */ ('network'),
|
|
130
|
+
line: line - 1,
|
|
131
|
+
column: 0,
|
|
132
|
+
},
|
|
133
|
+
});
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
/** @type {LH.Audit.Details.Table['headings']} */
|
|
138
|
+
const headings = [
|
|
139
|
+
{key: 'source', valueType: 'code', label: 'Blocking Directive Source'},
|
|
140
|
+
];
|
|
141
|
+
const details = Audit.makeTableDetails(headings, blockingDirectives);
|
|
96
142
|
|
|
97
|
-
return
|
|
98
|
-
.
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
if (metaRobots) {
|
|
103
|
-
const metaRobotsContent = metaRobots.content || '';
|
|
104
|
-
const isBlocking = hasBlockingDirective(metaRobotsContent);
|
|
105
|
-
|
|
106
|
-
if (isBlocking) {
|
|
107
|
-
blockingDirectives.push({
|
|
108
|
-
source: {
|
|
109
|
-
...Audit.makeNodeItem(metaRobots.node),
|
|
110
|
-
snippet: `<meta name="robots" content="${metaRobotsContent}" />`,
|
|
111
|
-
},
|
|
112
|
-
});
|
|
113
|
-
}
|
|
114
|
-
}
|
|
115
|
-
|
|
116
|
-
mainResource.responseHeaders && mainResource.responseHeaders
|
|
117
|
-
.filter(h => h.name.toLowerCase() === ROBOTS_HEADER && !hasUserAgent(h.value) &&
|
|
118
|
-
hasBlockingDirective(h.value))
|
|
119
|
-
.forEach(h => blockingDirectives.push({source: `${h.name}: ${h.value}`}));
|
|
120
|
-
|
|
121
|
-
if (artifacts.RobotsTxt.content) {
|
|
122
|
-
const robotsFileUrl = new URL('/robots.txt', mainResource.url);
|
|
123
|
-
const robotsTxt = robotsParser(robotsFileUrl.href, artifacts.RobotsTxt.content);
|
|
124
|
-
|
|
125
|
-
if (!robotsTxt.isAllowed(mainResource.url)) {
|
|
126
|
-
const line = robotsTxt.getMatchingLineNumber(mainResource.url) || 1;
|
|
127
|
-
blockingDirectives.push({
|
|
128
|
-
source: {
|
|
129
|
-
type: /** @type {const} */ ('source-location'),
|
|
130
|
-
url: robotsFileUrl.href,
|
|
131
|
-
urlProvider: /** @type {const} */ ('network'),
|
|
132
|
-
line: line - 1,
|
|
133
|
-
column: 0,
|
|
134
|
-
},
|
|
135
|
-
});
|
|
136
|
-
}
|
|
137
|
-
}
|
|
138
|
-
|
|
139
|
-
/** @type {LH.Audit.Details.Table['headings']} */
|
|
140
|
-
const headings = [
|
|
141
|
-
{key: 'source', valueType: 'code', label: 'Blocking Directive Source'},
|
|
142
|
-
];
|
|
143
|
-
const details = Audit.makeTableDetails(headings, blockingDirectives);
|
|
144
|
-
|
|
145
|
-
return {
|
|
146
|
-
score: Number(blockingDirectives.length === 0),
|
|
147
|
-
details,
|
|
148
|
-
};
|
|
149
|
-
});
|
|
143
|
+
return {
|
|
144
|
+
score: Number(blockingDirectives.length === 0),
|
|
145
|
+
details,
|
|
146
|
+
};
|
|
150
147
|
}
|
|
151
148
|
}
|
|
152
149
|
|
|
@@ -64,54 +64,53 @@ class UserTimings extends Audit {
|
|
|
64
64
|
* @param {LH.Audit.Context} context
|
|
65
65
|
* @return {Promise<LH.Audit.Product>}
|
|
66
66
|
*/
|
|
67
|
-
static audit(artifacts, context) {
|
|
67
|
+
static async audit(artifacts, context) {
|
|
68
68
|
const trace = artifacts.traces[Audit.DEFAULT_PASS];
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
69
|
+
const computedUserTimings = await ComputedUserTimings.request(trace, context);
|
|
70
|
+
const userTimings = computedUserTimings.filter(UserTimings.excludeEvent);
|
|
71
|
+
const tableRows = userTimings.map(item => {
|
|
72
|
+
return {
|
|
73
|
+
name: item.name,
|
|
74
|
+
startTime: item.startTime,
|
|
75
|
+
duration: item.isMark ? undefined : item.duration,
|
|
76
|
+
timingType: item.isMark ? 'Mark' : 'Measure',
|
|
77
|
+
};
|
|
78
|
+
}).sort((itemA, itemB) => {
|
|
79
|
+
if (itemA.timingType === itemB.timingType) {
|
|
80
|
+
// If both items are the same type, sort in ascending order by time
|
|
81
|
+
return itemA.startTime - itemB.startTime;
|
|
82
|
+
} else if (itemA.timingType === 'Measure') {
|
|
83
|
+
// Put measures before marks
|
|
84
|
+
return -1;
|
|
85
|
+
} else {
|
|
86
|
+
return 1;
|
|
87
|
+
}
|
|
88
|
+
});
|
|
89
89
|
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
90
|
+
/** @type {LH.Audit.Details.Table['headings']} */
|
|
91
|
+
const headings = [
|
|
92
|
+
{key: 'name', valueType: 'text', label: str_(i18n.UIStrings.columnName)},
|
|
93
|
+
{key: 'timingType', valueType: 'text', label: str_(UIStrings.columnType)},
|
|
94
|
+
{key: 'startTime', valueType: 'ms', granularity: 0.01,
|
|
95
|
+
label: str_(i18n.UIStrings.columnStartTime)},
|
|
96
|
+
{key: 'duration', valueType: 'ms', granularity: 0.01,
|
|
97
|
+
label: str_(i18n.UIStrings.columnDuration)},
|
|
98
|
+
];
|
|
99
99
|
|
|
100
|
-
|
|
100
|
+
const details = Audit.makeTableDetails(headings, tableRows);
|
|
101
101
|
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
102
|
+
let displayValue;
|
|
103
|
+
if (userTimings.length) {
|
|
104
|
+
displayValue = str_(UIStrings.displayValue, {itemCount: userTimings.length});
|
|
105
|
+
}
|
|
106
106
|
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
});
|
|
107
|
+
return {
|
|
108
|
+
// mark the audit as notApplicable if there were no user timings
|
|
109
|
+
score: Number(userTimings.length === 0),
|
|
110
|
+
notApplicable: userTimings.length === 0,
|
|
111
|
+
displayValue,
|
|
112
|
+
details,
|
|
113
|
+
};
|
|
115
114
|
}
|
|
116
115
|
}
|
|
117
116
|
|
|
@@ -12,9 +12,9 @@ import {ArbitraryEqualityMap} from '../lib/arbitrary-equality-map.js';
|
|
|
12
12
|
* Decorate computableArtifact with a caching `request()` method which will
|
|
13
13
|
* automatically call `computableArtifact.compute_()` under the hood.
|
|
14
14
|
* @template {{name: string, compute_(dependencies: unknown, context: LH.Artifacts.ComputedContext): Promise<unknown>}} C
|
|
15
|
-
* @template {Array<keyof FirstParamType<C['compute_']>>} K
|
|
15
|
+
* @template {Array<keyof LH.Util.FirstParamType<C['compute_']>>} K
|
|
16
16
|
* @param {C} computableArtifact
|
|
17
|
-
* @param {(K & ([keyof FirstParamType<C['compute_']>] extends [K[number]] ? unknown : never)) | null} keys List of properties of `dependencies` used by `compute_`; other properties are filtered out. Use `null` to allow all properties. Ensures that only required properties are used for caching result.
|
|
17
|
+
* @param {(K & ([keyof LH.Util.FirstParamType<C['compute_']>] extends [K[number]] ? unknown : never)) | null} keys List of properties of `dependencies` used by `compute_`; other properties are filtered out. Use `null` to allow all properties. Ensures that only required properties are used for caching result.
|
|
18
18
|
*/
|
|
19
19
|
function makeComputedArtifact(computableArtifact, keys) {
|
|
20
20
|
// tsc (3.1) has more difficulty with template inter-references in jsdoc, so
|
|
@@ -22,7 +22,7 @@ function makeComputedArtifact(computableArtifact, keys) {
|
|
|
22
22
|
// polymorphic-this behavior for C.
|
|
23
23
|
/**
|
|
24
24
|
* Return an automatically cached result from the computed artifact.
|
|
25
|
-
* @param {FirstParamType<C['compute_']>} dependencies
|
|
25
|
+
* @param {LH.Util.FirstParamType<C['compute_']>} dependencies
|
|
26
26
|
* @param {LH.Artifacts.ComputedContext} context
|
|
27
27
|
* @return {ReturnType<C['compute_']>}
|
|
28
28
|
*/
|
|
@@ -11,7 +11,7 @@ import {NetworkAnalysis} from './network-analysis.js';
|
|
|
11
11
|
|
|
12
12
|
class LoadSimulator {
|
|
13
13
|
/**
|
|
14
|
-
* @param {{devtoolsLog: LH.DevtoolsLog, settings:
|
|
14
|
+
* @param {{devtoolsLog: LH.DevtoolsLog, settings: LH.Audit.Context['settings']}} data
|
|
15
15
|
* @param {LH.Artifacts.ComputedContext} context
|
|
16
16
|
* @return {Promise<Simulator>}
|
|
17
17
|
*/
|
|
@@ -131,7 +131,7 @@ class Responsiveness {
|
|
|
131
131
|
}
|
|
132
132
|
|
|
133
133
|
/**
|
|
134
|
-
* @param {{trace: LH.Trace, settings:
|
|
134
|
+
* @param {{trace: LH.Trace, settings: LH.Audit.Context['settings']}} data
|
|
135
135
|
* @param {LH.Artifacts.ComputedContext} context
|
|
136
136
|
* @return {Promise<EventTimingEvent|FallbackTimingEvent|null>}
|
|
137
137
|
*/
|
|
@@ -24,7 +24,7 @@ class TimingSummary {
|
|
|
24
24
|
* @param {LH.Trace} trace
|
|
25
25
|
* @param {LH.DevtoolsLog} devtoolsLog
|
|
26
26
|
* @param {LH.Artifacts['GatherContext']} gatherContext
|
|
27
|
-
* @param {ImmutableObject<LH.Config.Settings>} settings
|
|
27
|
+
* @param {LH.Util.ImmutableObject<LH.Config.Settings>} settings
|
|
28
28
|
* @param {LH.Artifacts['URL']} URL
|
|
29
29
|
* @param {LH.Artifacts.ComputedContext} context
|
|
30
30
|
* @return {Promise<{metrics: LH.Artifacts.TimingSummary, debugInfo: Record<string,boolean>}>}
|
|
@@ -134,7 +134,7 @@ class TimingSummary {
|
|
|
134
134
|
return {metrics, debugInfo};
|
|
135
135
|
}
|
|
136
136
|
/**
|
|
137
|
-
* @param {{trace: LH.Trace, devtoolsLog: LH.DevtoolsLog, gatherContext: LH.Artifacts['GatherContext']; settings: ImmutableObject<LH.Config.Settings>, URL: LH.Artifacts['URL']}} data
|
|
137
|
+
* @param {{trace: LH.Trace, devtoolsLog: LH.DevtoolsLog, gatherContext: LH.Artifacts['GatherContext']; settings: LH.Util.ImmutableObject<LH.Config.Settings>, URL: LH.Artifacts['URL']}} data
|
|
138
138
|
* @param {LH.Artifacts.ComputedContext} context
|
|
139
139
|
* @return {Promise<{metrics: LH.Artifacts.TimingSummary, debugInfo: Record<string,boolean>}>}
|
|
140
140
|
*/
|
|
@@ -11,7 +11,7 @@ import {NetworkRecords} from './network-records.js';
|
|
|
11
11
|
class NetworkAnalysis {
|
|
12
12
|
/**
|
|
13
13
|
* @param {Array<LH.Artifacts.NetworkRequest>} records
|
|
14
|
-
* @return {StrictOmit<LH.Artifacts.NetworkAnalysis, 'throughput'>}
|
|
14
|
+
* @return {LH.Util.StrictOmit<LH.Artifacts.NetworkAnalysis, 'throughput'>}
|
|
15
15
|
*/
|
|
16
16
|
static computeRTTAndServerResponseTime(records) {
|
|
17
17
|
// First pass compute the estimated observed RTT to each origin's servers.
|
|
@@ -34,7 +34,7 @@ class ResourceSummary {
|
|
|
34
34
|
/**
|
|
35
35
|
* @param {Array<LH.Artifacts.NetworkRequest>} networkRecords
|
|
36
36
|
* @param {LH.Artifacts.URL} URLArtifact
|
|
37
|
-
* @param {ImmutableObject<LH.Budget[]|null>} budgets
|
|
37
|
+
* @param {LH.Util.ImmutableObject<LH.Budget[]|null>} budgets
|
|
38
38
|
* @return {Record<LH.Budget.ResourceType, ResourceEntry>}
|
|
39
39
|
*/
|
|
40
40
|
static summarize(networkRecords, URLArtifact, budgets) {
|
|
@@ -99,7 +99,7 @@ class ResourceSummary {
|
|
|
99
99
|
}
|
|
100
100
|
|
|
101
101
|
/**
|
|
102
|
-
* @param {{URL: LH.Artifacts['URL'], devtoolsLog: LH.DevtoolsLog, budgets: ImmutableObject<LH.Budget[]|null>}} data
|
|
102
|
+
* @param {{URL: LH.Artifacts['URL'], devtoolsLog: LH.DevtoolsLog, budgets: LH.Util.ImmutableObject<LH.Budget[]|null>}} data
|
|
103
103
|
* @param {LH.Artifacts.ComputedContext} context
|
|
104
104
|
* @return {Promise<Record<LH.Budget.ResourceType,ResourceEntry>>}
|
|
105
105
|
*/
|
package/core/config/budget.js
CHANGED
|
@@ -135,9 +135,9 @@ class Budget {
|
|
|
135
135
|
* Returns the budget that applies to a given URL.
|
|
136
136
|
* If multiple budgets match based on thier 'path' property,
|
|
137
137
|
* then the last-listed of those budgets is returned.
|
|
138
|
-
* @param {Immutable<Array<LH.Budget>>|null} budgets
|
|
138
|
+
* @param {LH.Util.Immutable<Array<LH.Budget>>|null} budgets
|
|
139
139
|
* @param {string|undefined} url
|
|
140
|
-
* @return {Immutable<LH.Budget> | undefined} budget
|
|
140
|
+
* @return {LH.Util.Immutable<LH.Budget> | undefined} budget
|
|
141
141
|
*/
|
|
142
142
|
static getMatchingBudget(budgets, url) {
|
|
143
143
|
if (budgets === null || url === undefined) return;
|
|
@@ -299,10 +299,10 @@ function requireAudit(auditPath, coreAuditList, configDir) {
|
|
|
299
299
|
* Creates a settings object from potential flags object by dropping all the properties
|
|
300
300
|
* that don't exist on Config.Settings.
|
|
301
301
|
* @param {Partial<LH.Flags>=} flags
|
|
302
|
-
* @return {RecursivePartial<LH.Config.Settings>}
|
|
302
|
+
* @return {LH.Util.RecursivePartial<LH.Config.Settings>}
|
|
303
303
|
*/
|
|
304
304
|
function cleanFlagsForSettings(flags = {}) {
|
|
305
|
-
/** @type {RecursivePartial<LH.Config.Settings>} */
|
|
305
|
+
/** @type {LH.Util.RecursivePartial<LH.Config.Settings>} */
|
|
306
306
|
const settings = {};
|
|
307
307
|
|
|
308
308
|
for (const key of Object.keys(flags)) {
|
|
@@ -25,7 +25,9 @@ const MAX_WEBP_SIZE = 16383;
|
|
|
25
25
|
* @param {S} str
|
|
26
26
|
*/
|
|
27
27
|
function kebabCaseToCamelCase(str) {
|
|
28
|
-
return /** @type {KebabToCamelCase<S>} */ (
|
|
28
|
+
return /** @type {LH.Util.KebabToCamelCase<S>} */ (
|
|
29
|
+
str.replace(/(-\w)/g, m => m[1].toUpperCase())
|
|
30
|
+
);
|
|
29
31
|
}
|
|
30
32
|
|
|
31
33
|
/* c8 ignore start */
|
|
@@ -130,7 +130,7 @@ function convertNodeTimingsToTrace(nodeTimings) {
|
|
|
130
130
|
if (startTime === endTime) endTime += 0.3;
|
|
131
131
|
|
|
132
132
|
const requestData = {requestId: requestId.toString(), frame};
|
|
133
|
-
/** @type {StrictOmit<LH.TraceEvent, 'name'|'ts'|'args'>} */
|
|
133
|
+
/** @type {LH.Util.StrictOmit<LH.TraceEvent, 'name'|'ts'|'args'>} */
|
|
134
134
|
const baseRequestEvent = {...baseEvent, ph: 'I', s: 't', dur: 0};
|
|
135
135
|
|
|
136
136
|
const sendRequestData = {
|
|
@@ -89,7 +89,7 @@ const HEADER_PROTOCOL_IS_H2 = 'X-ProtocolIsH2';
|
|
|
89
89
|
* @property {number} responseMs
|
|
90
90
|
*/
|
|
91
91
|
|
|
92
|
-
/** @type {SelfMap<LH.Crdp.Network.ResourceType>} */
|
|
92
|
+
/** @type {LH.Util.SelfMap<LH.Crdp.Network.ResourceType>} */
|
|
93
93
|
const RESOURCE_TYPES = {
|
|
94
94
|
XHR: 'XHR',
|
|
95
95
|
Fetch: 'Fetch',
|
package/package.json
CHANGED
package/types/artifacts.d.ts
CHANGED
|
@@ -18,10 +18,12 @@ import Gatherer from './gatherer';
|
|
|
18
18
|
import {IcuMessage} from './lhr/i18n';
|
|
19
19
|
import LHResult from './lhr/lhr'
|
|
20
20
|
import Protocol from './protocol';
|
|
21
|
+
import Util from './utility-types.js';
|
|
22
|
+
import Audit from './audit.js';
|
|
21
23
|
|
|
22
24
|
export interface Artifacts extends BaseArtifacts, GathererArtifacts {}
|
|
23
25
|
|
|
24
|
-
export type FRArtifacts = StrictOmit<Artifacts,
|
|
26
|
+
export type FRArtifacts = Util.StrictOmit<Artifacts,
|
|
25
27
|
| 'Fonts'
|
|
26
28
|
| 'Manifest'
|
|
27
29
|
| 'MixedContent'
|
|
@@ -179,7 +181,7 @@ export interface GathererArtifacts extends PublicGathererArtifacts,LegacyBaseArt
|
|
|
179
181
|
}
|
|
180
182
|
|
|
181
183
|
declare module Artifacts {
|
|
182
|
-
type ComputedContext = Immutable<{
|
|
184
|
+
type ComputedContext = Util.Immutable<{
|
|
183
185
|
computedCache: Map<string, ArbitraryEqualityMap>;
|
|
184
186
|
}>;
|
|
185
187
|
|
|
@@ -634,7 +636,7 @@ declare module Artifacts {
|
|
|
634
636
|
interface MetricComputationDataInput {
|
|
635
637
|
devtoolsLog: DevtoolsLog;
|
|
636
638
|
trace: Trace;
|
|
637
|
-
settings:
|
|
639
|
+
settings: Audit.Context['settings'];
|
|
638
640
|
gatherContext: Artifacts['GatherContext'];
|
|
639
641
|
simulator?: InstanceType<typeof Simulator>;
|
|
640
642
|
URL: Artifacts['URL'];
|
package/types/audit.d.ts
CHANGED
|
@@ -11,6 +11,7 @@ import Config from './config';
|
|
|
11
11
|
import Gatherer from './gatherer';
|
|
12
12
|
import {IcuMessage} from './lhr/i18n';
|
|
13
13
|
import * as AuditResult from './lhr/audit-result';
|
|
14
|
+
import Util from './utility-types.js';
|
|
14
15
|
|
|
15
16
|
declare module Audit {
|
|
16
17
|
export import Details = AuditDetails;
|
|
@@ -18,7 +19,7 @@ declare module Audit {
|
|
|
18
19
|
export type ScoreDisplayMode = AuditResult.ScoreDisplayMode;
|
|
19
20
|
export type ScoreDisplayModes = AuditResult.ScoreDisplayModes;
|
|
20
21
|
|
|
21
|
-
type Context = Immutable<{
|
|
22
|
+
type Context = Util.Immutable<{
|
|
22
23
|
/** audit options */
|
|
23
24
|
options: Record<string, any>;
|
|
24
25
|
settings: Config.Settings;
|
package/types/externs.d.ts
CHANGED
|
@@ -8,101 +8,6 @@ import {Artifacts} from './artifacts';
|
|
|
8
8
|
import LHResult from './lhr/lhr';
|
|
9
9
|
import {SharedFlagsSettings, OutputMode} from './lhr/settings';
|
|
10
10
|
|
|
11
|
-
declare global {
|
|
12
|
-
// Augment Intl to include
|
|
13
|
-
// https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Intl/getCanonicalLocales
|
|
14
|
-
namespace Intl {
|
|
15
|
-
var getCanonicalLocales: (locales?: string | Array<string>) => Array<string>;
|
|
16
|
-
}
|
|
17
|
-
|
|
18
|
-
interface Window {
|
|
19
|
-
// Cached native functions/objects for use in case the page overwrites them.
|
|
20
|
-
// See: `executionContext.cacheNativesOnNewDocument`.
|
|
21
|
-
__nativePromise: PromiseConstructor;
|
|
22
|
-
__nativePerformance: Performance;
|
|
23
|
-
__nativeFetch: typeof fetch,
|
|
24
|
-
__nativeURL: typeof URL;
|
|
25
|
-
__ElementMatches: Element['matches'];
|
|
26
|
-
__HTMLElementBoundingClientRect: HTMLElement['getBoundingClientRect'];
|
|
27
|
-
|
|
28
|
-
/** Used for monitoring long tasks in the test page. */
|
|
29
|
-
____lastLongTask?: number;
|
|
30
|
-
|
|
31
|
-
/** Used by FullPageScreenshot gatherer. */
|
|
32
|
-
__lighthouseNodesDontTouchOrAllVarianceGoesAway: Map<Element, string>;
|
|
33
|
-
__lighthouseExecutionContextUniqueIdentifier?: number;
|
|
34
|
-
|
|
35
|
-
/** Injected into the page when the `--debug` flag is used. */
|
|
36
|
-
continueLighthouseRun(): void;
|
|
37
|
-
|
|
38
|
-
// Not defined in tsc yet: https://github.com/microsoft/TypeScript/issues/40807
|
|
39
|
-
requestIdleCallback(callback: (deadline: {didTimeout: boolean, timeRemaining: () => DOMHighResTimeStamp}) => void, options?: {timeout: number}): number;
|
|
40
|
-
}
|
|
41
|
-
|
|
42
|
-
/** Make properties K in T optional. */
|
|
43
|
-
type MakeOptional<T, K extends keyof T> = {
|
|
44
|
-
[P in Exclude<keyof T, K>]: T[P]
|
|
45
|
-
} & {
|
|
46
|
-
[P in K]+?: T[P]
|
|
47
|
-
}
|
|
48
|
-
|
|
49
|
-
/** An object with the keys in the union K mapped to themselves as values. */
|
|
50
|
-
type SelfMap<K extends string> = {
|
|
51
|
-
[P in K]: P;
|
|
52
|
-
};
|
|
53
|
-
|
|
54
|
-
/** Make optional all properties on T and any properties on object properties of T. */
|
|
55
|
-
type RecursivePartial<T> =
|
|
56
|
-
// Recurse into arrays and tuples: elements aren't (newly) optional, but any properties they have are.
|
|
57
|
-
T extends (infer U)[] ? RecursivePartial<U>[] :
|
|
58
|
-
// Recurse into objects: properties and any of their properties are optional.
|
|
59
|
-
T extends object ? {[P in keyof T]?: RecursivePartial<T[P]>} :
|
|
60
|
-
// Strings, numbers, etc. (terminal types) end here.
|
|
61
|
-
T;
|
|
62
|
-
|
|
63
|
-
/** Recursively makes all properties of T read-only. */
|
|
64
|
-
type Immutable<T> =
|
|
65
|
-
T extends Function ? T :
|
|
66
|
-
T extends Array<infer R> ? ImmutableArray<R> :
|
|
67
|
-
T extends Map<infer K, infer V> ? ImmutableMap<K, V> :
|
|
68
|
-
T extends Set<infer M> ? ImmutableSet<M> :
|
|
69
|
-
T extends object ? ImmutableObject<T> :
|
|
70
|
-
T
|
|
71
|
-
|
|
72
|
-
// Intermediate immutable types. Prefer e.g. Immutable<Set<T>> over direct use.
|
|
73
|
-
type ImmutableArray<T> = ReadonlyArray<Immutable<T>>;
|
|
74
|
-
type ImmutableMap<K, V> = ReadonlyMap<Immutable<K>, Immutable<V>>;
|
|
75
|
-
type ImmutableSet<T> = ReadonlySet<Immutable<T>>;
|
|
76
|
-
type ImmutableObject<T> = {
|
|
77
|
-
readonly [K in keyof T]: Immutable<T[K]>;
|
|
78
|
-
};
|
|
79
|
-
|
|
80
|
-
/**
|
|
81
|
-
* Exclude void from T
|
|
82
|
-
*/
|
|
83
|
-
type NonVoid<T> = T extends void ? never : T;
|
|
84
|
-
|
|
85
|
-
/** Remove properties K from T. */
|
|
86
|
-
type StrictOmit<T, K extends keyof T> = Pick<T, Exclude<keyof T, K>>;
|
|
87
|
-
|
|
88
|
-
/** Obtain the type of the first parameter of a function. */
|
|
89
|
-
type FirstParamType<T extends (arg1: any, ...args: any[]) => any> =
|
|
90
|
-
T extends (arg1: infer P, ...args: any[]) => any ? P : never;
|
|
91
|
-
|
|
92
|
-
/**
|
|
93
|
-
* If `S` is a kebab-style string `S`, convert to camelCase.
|
|
94
|
-
*/
|
|
95
|
-
type KebabToCamelCase<S> =
|
|
96
|
-
S extends `${infer T}-${infer U}` ?
|
|
97
|
-
`${T}${Capitalize<KebabToCamelCase<U>>}` :
|
|
98
|
-
S
|
|
99
|
-
|
|
100
|
-
/** Returns T with any kebab-style property names rewritten as camelCase. */
|
|
101
|
-
type CamelCasify<T> = {
|
|
102
|
-
[K in keyof T as KebabToCamelCase<K>]: T[K];
|
|
103
|
-
}
|
|
104
|
-
}
|
|
105
|
-
|
|
106
11
|
/**
|
|
107
12
|
* Extends the flags in SharedFlagsSettings with flags used to configure the
|
|
108
13
|
* Lighthouse module but will not end up in the Config settings.
|
package/types/global-lh.d.ts
CHANGED
|
@@ -22,6 +22,7 @@ import * as Settings from './lhr/settings';
|
|
|
22
22
|
import Treemap_ from './lhr/treemap';
|
|
23
23
|
import UserFlow_ from './user-flow';
|
|
24
24
|
import Puppeteer_ from './puppeteer';
|
|
25
|
+
import Util_ from './utility-types.js';
|
|
25
26
|
|
|
26
27
|
// Construct hierarchy of global types under the LH namespace.
|
|
27
28
|
declare global {
|
|
@@ -29,6 +30,8 @@ declare global {
|
|
|
29
30
|
export type ArbitraryEqualityMap = ArbitraryEqualityMap_;
|
|
30
31
|
export type NavigationRequestor = string | (() => Promise<void> | void);
|
|
31
32
|
|
|
33
|
+
export import Util = Util_;
|
|
34
|
+
|
|
32
35
|
export import Puppeteer = Puppeteer_;
|
|
33
36
|
|
|
34
37
|
// artifacts.d.ts
|
package/types/node.d.ts
CHANGED
|
@@ -7,6 +7,38 @@
|
|
|
7
7
|
declare global {
|
|
8
8
|
var isDevtools: boolean | undefined;
|
|
9
9
|
var isLightrider: boolean | undefined;
|
|
10
|
+
|
|
11
|
+
// Augment Intl to include
|
|
12
|
+
// https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Intl/getCanonicalLocales
|
|
13
|
+
namespace Intl {
|
|
14
|
+
var getCanonicalLocales: (locales?: string | Array<string>) => Array<string>;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
// Some functions defined in node are stringified and run in the browser.
|
|
18
|
+
// Ensure those functions are working with the correct browser environment.
|
|
19
|
+
interface Window {
|
|
20
|
+
// Cached native functions/objects for use in case the page overwrites them.
|
|
21
|
+
// See: `executionContext.cacheNativesOnNewDocument`.
|
|
22
|
+
__nativePromise: PromiseConstructor;
|
|
23
|
+
__nativePerformance: Performance;
|
|
24
|
+
__nativeFetch: typeof fetch,
|
|
25
|
+
__nativeURL: typeof URL;
|
|
26
|
+
__ElementMatches: Element['matches'];
|
|
27
|
+
__HTMLElementBoundingClientRect: HTMLElement['getBoundingClientRect'];
|
|
28
|
+
|
|
29
|
+
/** Used for monitoring long tasks in the test page. */
|
|
30
|
+
____lastLongTask?: number;
|
|
31
|
+
|
|
32
|
+
/** Used by FullPageScreenshot gatherer. */
|
|
33
|
+
__lighthouseNodesDontTouchOrAllVarianceGoesAway: Map<Element, string>;
|
|
34
|
+
__lighthouseExecutionContextUniqueIdentifier?: number;
|
|
35
|
+
|
|
36
|
+
/** Injected into the page when the `--debug` flag is used. */
|
|
37
|
+
continueLighthouseRun(): void;
|
|
38
|
+
|
|
39
|
+
// Not defined in tsc yet: https://github.com/microsoft/TypeScript/issues/40807
|
|
40
|
+
requestIdleCallback(callback: (deadline: {didTimeout: boolean, timeRemaining: () => DOMHighResTimeStamp}) => void, options?: {timeout: number}): number;
|
|
41
|
+
}
|
|
10
42
|
}
|
|
11
43
|
|
|
12
44
|
export {};
|
|
@@ -0,0 +1,72 @@
|
|
|
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
|
+
declare module Util {
|
|
8
|
+
/** Make properties K in T optional. */
|
|
9
|
+
type MakeOptional<T, K extends keyof T> = {
|
|
10
|
+
[P in Exclude<keyof T, K>]: T[P]
|
|
11
|
+
} & {
|
|
12
|
+
[P in K]+?: T[P]
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
/** An object with the keys in the union K mapped to themselves as values. */
|
|
16
|
+
type SelfMap<K extends string> = {
|
|
17
|
+
[P in K]: P;
|
|
18
|
+
};
|
|
19
|
+
|
|
20
|
+
/** Make optional all properties on T and any properties on object properties of T. */
|
|
21
|
+
type RecursivePartial<T> =
|
|
22
|
+
// Recurse into arrays and tuples: elements aren't (newly) optional, but any properties they have are.
|
|
23
|
+
T extends (infer U)[] ? RecursivePartial<U>[] :
|
|
24
|
+
// Recurse into objects: properties and any of their properties are optional.
|
|
25
|
+
T extends object ? {[P in keyof T]?: RecursivePartial<T[P]>} :
|
|
26
|
+
// Strings, numbers, etc. (terminal types) end here.
|
|
27
|
+
T;
|
|
28
|
+
|
|
29
|
+
/** Recursively makes all properties of T read-only. */
|
|
30
|
+
type Immutable<T> =
|
|
31
|
+
T extends Function ? T :
|
|
32
|
+
T extends Array<infer R> ? ImmutableArray<R> :
|
|
33
|
+
T extends Map<infer K, infer V> ? ImmutableMap<K, V> :
|
|
34
|
+
T extends Set<infer M> ? ImmutableSet<M> :
|
|
35
|
+
T extends object ? ImmutableObject<T> :
|
|
36
|
+
T
|
|
37
|
+
|
|
38
|
+
// Intermediate immutable types. Prefer e.g. Immutable<Set<T>> over direct use.
|
|
39
|
+
type ImmutableArray<T> = ReadonlyArray<Immutable<T>>;
|
|
40
|
+
type ImmutableMap<K, V> = ReadonlyMap<Immutable<K>, Immutable<V>>;
|
|
41
|
+
type ImmutableSet<T> = ReadonlySet<Immutable<T>>;
|
|
42
|
+
type ImmutableObject<T> = {
|
|
43
|
+
readonly [K in keyof T]: Immutable<T[K]>;
|
|
44
|
+
};
|
|
45
|
+
|
|
46
|
+
/**
|
|
47
|
+
* Exclude void from T
|
|
48
|
+
*/
|
|
49
|
+
type NonVoid<T> = T extends void ? never : T;
|
|
50
|
+
|
|
51
|
+
/** Remove properties K from T. */
|
|
52
|
+
type StrictOmit<T, K extends keyof T> = Pick<T, Exclude<keyof T, K>>;
|
|
53
|
+
|
|
54
|
+
/** Obtain the type of the first parameter of a function. */
|
|
55
|
+
type FirstParamType<T extends (arg1: any, ...args: any[]) => any> =
|
|
56
|
+
T extends (arg1: infer P, ...args: any[]) => any ? P : never;
|
|
57
|
+
|
|
58
|
+
/**
|
|
59
|
+
* If `S` is a kebab-style string `S`, convert to camelCase.
|
|
60
|
+
*/
|
|
61
|
+
type KebabToCamelCase<S> =
|
|
62
|
+
S extends `${infer T}-${infer U}` ?
|
|
63
|
+
`${T}${Capitalize<KebabToCamelCase<U>>}` :
|
|
64
|
+
S
|
|
65
|
+
|
|
66
|
+
/** Returns T with any kebab-style property names rewritten as camelCase. */
|
|
67
|
+
type CamelCasify<T> = {
|
|
68
|
+
[K in keyof T as KebabToCamelCase<K>]: T[K];
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
export default Util;
|