lighthouse 9.2.0 → 9.3.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.
- package/changelog.md +67 -0
- package/dist/report/bundle.esm.js +23 -11
- package/dist/report/flow.js +11 -11
- package/dist/report/standalone.js +8 -8
- package/flow-report/src/common.tsx +1 -1
- package/flow-report/src/sidebar/flow.tsx +1 -1
- package/flow-report/src/util.ts +1 -2
- package/lighthouse-cli/bin.js +0 -4
- package/lighthouse-cli/cli-flags.js +22 -8
- package/lighthouse-cli/run.js +5 -1
- package/lighthouse-cli/test/smokehouse/frontends/smokehouse-bin.js +4 -2
- package/lighthouse-cli/test/smokehouse/lighthouse-runners/cli.js +1 -1
- package/lighthouse-cli/test/smokehouse/lighthouse-runners/devtools.js +103 -0
- package/lighthouse-cli/test/smokehouse/readme.md +10 -8
- package/lighthouse-cli/test/smokehouse/report-assert.js +1 -1
- package/lighthouse-cli/test/smokehouse/smokehouse.js +4 -5
- package/lighthouse-core/audits/accessibility/axe-audit.js +4 -4
- package/lighthouse-core/audits/accessibility/object-alt.js +3 -3
- package/lighthouse-core/audits/audit.js +19 -4
- package/lighthouse-core/audits/byte-efficiency/byte-efficiency-audit.js +1 -1
- package/lighthouse-core/audits/byte-efficiency/duplicated-javascript.js +1 -1
- package/lighthouse-core/audits/deprecations.js +2 -2
- package/lighthouse-core/audits/dobetterweb/inspector-issues.js +11 -11
- package/lighthouse-core/audits/dobetterweb/uses-http2.js +1 -1
- package/lighthouse-core/audits/font-display.js +1 -1
- package/lighthouse-core/audits/installable-manifest.js +1 -1
- package/lighthouse-core/audits/is-on-https.js +1 -1
- package/lighthouse-core/audits/maskable-icon.js +1 -1
- package/lighthouse-core/audits/multi-check-audit.js +1 -1
- package/lighthouse-core/audits/network-requests.js +4 -4
- package/lighthouse-core/audits/preload-fonts.js +2 -3
- package/lighthouse-core/audits/preload-lcp-image.js +2 -12
- package/lighthouse-core/audits/seo/is-crawlable.js +2 -1
- package/lighthouse-core/audits/themed-omnibox.js +1 -1
- package/lighthouse-core/audits/uses-rel-preload.js +2 -3
- package/lighthouse-core/audits/valid-source-maps.js +2 -2
- package/lighthouse-core/computed/image-records.js +1 -1
- package/lighthouse-core/computed/js-bundles.js +1 -1
- package/lighthouse-core/computed/metrics/lantern-metric.js +1 -1
- package/lighthouse-core/computed/metrics/timing-summary.js +35 -35
- package/lighthouse-core/computed/page-dependency-graph.js +2 -2
- package/lighthouse-core/computed/resource-summary.js +1 -1
- package/lighthouse-core/config/config-helpers.js +2 -2
- package/lighthouse-core/config/config.js +1 -1
- package/lighthouse-core/fraggle-rock/config/filters.js +23 -1
- package/lighthouse-core/fraggle-rock/config/validation.js +2 -2
- package/lighthouse-core/fraggle-rock/gather/navigation-runner.js +5 -4
- package/lighthouse-core/fraggle-rock/gather/snapshot-runner.js +0 -1
- package/lighthouse-core/fraggle-rock/gather/timespan-runner.js +0 -1
- package/lighthouse-core/fraggle-rock/user-flow.js +4 -4
- package/lighthouse-core/gather/connections/connection.js +1 -1
- package/lighthouse-core/gather/driver/execution-context.js +1 -1
- package/lighthouse-core/gather/driver/network-monitor.js +2 -2
- package/lighthouse-core/gather/driver/target-manager.js +1 -1
- package/lighthouse-core/gather/driver.js +1 -1
- package/lighthouse-core/gather/gatherers/console-messages.js +2 -2
- package/lighthouse-core/gather/gatherers/inspector-issues.js +34 -46
- package/lighthouse-core/gather/gatherers/js-usage.js +1 -1
- package/lighthouse-core/gather/gatherers/seo/font-size.js +1 -1
- package/lighthouse-core/gather/gatherers/trace-elements.js +8 -43
- package/lighthouse-core/index.js +4 -3
- package/lighthouse-core/lib/arbitrary-equality-map.js +1 -1
- package/lighthouse-core/lib/asset-saver.js +6 -4
- package/lighthouse-core/lib/dependency-graph/simulator/connection-pool.js +1 -1
- package/lighthouse-core/lib/dependency-graph/simulator/network-analyzer.js +1 -1
- package/lighthouse-core/lib/emulation.js +1 -1
- package/lighthouse-core/lib/icons.js +1 -2
- package/lighthouse-core/lib/median-run.js +1 -2
- package/lighthouse-core/lib/network-recorder.js +2 -2
- package/lighthouse-core/lib/network-request.js +1 -1
- package/lighthouse-core/lib/stack-packs.js +5 -0
- package/lighthouse-core/lib/tracehouse/cpu-profile-model.js +4 -6
- package/lighthouse-core/lib/tracehouse/main-thread-tasks.js +1 -1
- package/lighthouse-core/lib/tracehouse/trace-processor.js +14 -17
- package/lighthouse-core/lib/url-shim.js +15 -0
- package/lighthouse-core/runner.js +62 -49
- package/lighthouse-core/scripts/download-chrome.sh +15 -2
- package/package.json +21 -25
- package/report/assets/templates.html +2 -1
- package/report/clients/standalone.js +5 -1
- package/report/renderer/category-renderer.js +1 -1
- package/report/renderer/components.js +2 -2
- package/report/renderer/performance-category-renderer.js +4 -4
- package/report/renderer/report-renderer.js +1 -1
- package/report/renderer/report-ui-features.js +15 -3
- package/report/test/generator/report-generator-test.js +1 -1
- package/report/test/renderer/report-ui-features-test.js +22 -4
- package/report/test-assets/faux-psi.js +2 -3
- package/report/types/report-renderer.d.ts +2 -0
- package/shared/localization/locales/ar-XB.json +40 -4
- package/shared/localization/locales/ar.json +59 -23
- package/shared/localization/locales/bg.json +40 -4
- package/shared/localization/locales/ca.json +45 -9
- package/shared/localization/locales/cs.json +39 -3
- package/shared/localization/locales/da.json +43 -7
- package/shared/localization/locales/de.json +42 -6
- package/shared/localization/locales/el.json +59 -23
- package/shared/localization/locales/en-GB.json +43 -7
- package/shared/localization/locales/en-US.json +48 -6
- package/shared/localization/locales/en-XA.json +40 -4
- package/shared/localization/locales/en-XL.json +48 -6
- package/shared/localization/locales/es-419.json +39 -3
- package/shared/localization/locales/es.json +46 -10
- package/shared/localization/locales/fi.json +57 -21
- package/shared/localization/locales/fil.json +40 -4
- package/shared/localization/locales/fr.json +45 -9
- package/shared/localization/locales/he.json +41 -5
- package/shared/localization/locales/hi.json +59 -23
- package/shared/localization/locales/hr.json +57 -21
- package/shared/localization/locales/hu.json +39 -3
- package/shared/localization/locales/id.json +40 -4
- package/shared/localization/locales/it.json +40 -4
- package/shared/localization/locales/ja.json +39 -3
- package/shared/localization/locales/ko.json +40 -4
- package/shared/localization/locales/lt.json +40 -4
- package/shared/localization/locales/lv.json +56 -20
- package/shared/localization/locales/nl.json +40 -4
- package/shared/localization/locales/no.json +56 -20
- package/shared/localization/locales/pl.json +56 -20
- package/shared/localization/locales/pt-PT.json +57 -21
- package/shared/localization/locales/pt.json +41 -5
- package/shared/localization/locales/ro.json +39 -3
- package/shared/localization/locales/ru.json +40 -4
- package/shared/localization/locales/sk.json +39 -3
- package/shared/localization/locales/sl.json +39 -3
- package/shared/localization/locales/sr-Latn.json +40 -4
- package/shared/localization/locales/sr.json +40 -4
- package/shared/localization/locales/sv.json +39 -3
- package/shared/localization/locales/ta.json +47 -11
- package/shared/localization/locales/te.json +61 -25
- package/shared/localization/locales/th.json +39 -3
- package/shared/localization/locales/tr.json +40 -4
- package/shared/localization/locales/uk.json +40 -4
- package/shared/localization/locales/vi.json +63 -27
- package/shared/localization/locales/zh-HK.json +40 -4
- package/shared/localization/locales/zh-TW.json +45 -9
- package/shared/localization/locales/zh.json +41 -5
- package/third-party/chromium-synchronization/inspector-issueAdded-types-test.js +1 -1
- package/third-party/snyk/snapshot.json +4 -1
- package/types/artifacts.d.ts +16 -6
- package/types/robots-parser/index.d.ts +0 -20
|
@@ -60,42 +60,6 @@ class TraceElements extends FRGatherer {
|
|
|
60
60
|
if (name) this.animationIdToName.set(id, name);
|
|
61
61
|
}
|
|
62
62
|
|
|
63
|
-
/**
|
|
64
|
-
* @param {LH.TraceEvent | undefined} event
|
|
65
|
-
* @return {number | undefined}
|
|
66
|
-
*/
|
|
67
|
-
static getNodeIDFromTraceEvent(event) {
|
|
68
|
-
return event && event.args &&
|
|
69
|
-
event.args.data && event.args.data.nodeId;
|
|
70
|
-
}
|
|
71
|
-
|
|
72
|
-
/**
|
|
73
|
-
* @param {LH.TraceEvent | undefined} event
|
|
74
|
-
* @return {string | undefined}
|
|
75
|
-
*/
|
|
76
|
-
static getAnimationIDFromTraceEvent(event) {
|
|
77
|
-
return event && event.args &&
|
|
78
|
-
event.args.data && event.args.data.id;
|
|
79
|
-
}
|
|
80
|
-
|
|
81
|
-
/**
|
|
82
|
-
* @param {LH.TraceEvent | undefined} event
|
|
83
|
-
* @return {number | undefined}
|
|
84
|
-
*/
|
|
85
|
-
static getFailureReasonsFromTraceEvent(event) {
|
|
86
|
-
return event && event.args &&
|
|
87
|
-
event.args.data && event.args.data.compositeFailed;
|
|
88
|
-
}
|
|
89
|
-
|
|
90
|
-
/**
|
|
91
|
-
* @param {LH.TraceEvent | undefined} event
|
|
92
|
-
* @return {string[] | undefined}
|
|
93
|
-
*/
|
|
94
|
-
static getUnsupportedPropertiesFromTraceEvent(event) {
|
|
95
|
-
return event && event.args &&
|
|
96
|
-
event.args.data && event.args.data.unsupportedProperties;
|
|
97
|
-
}
|
|
98
|
-
|
|
99
63
|
/**
|
|
100
64
|
* @param {Array<number>} rect
|
|
101
65
|
* @return {LH.Artifacts.Rect}
|
|
@@ -124,7 +88,7 @@ class TraceElements extends FRGatherer {
|
|
|
124
88
|
const clsPerNode = new Map();
|
|
125
89
|
const shiftEvents = mainThreadEvents
|
|
126
90
|
.filter(e => e.name === 'LayoutShift')
|
|
127
|
-
.map(e => e.args
|
|
91
|
+
.map(e => e.args?.data);
|
|
128
92
|
const indexFirstEventWithoutInput =
|
|
129
93
|
shiftEvents.findIndex(event => event && !event.had_recent_input);
|
|
130
94
|
|
|
@@ -207,11 +171,12 @@ class TraceElements extends FRGatherer {
|
|
|
207
171
|
/** @type {Map<number, Set<{animationId: string, failureReasonsMask?: number, unsupportedProperties?: string[]}>>} */
|
|
208
172
|
const elementAnimations = new Map();
|
|
209
173
|
for (const {begin, status} of animationPairs.values()) {
|
|
210
|
-
const nodeId =
|
|
211
|
-
const animationId =
|
|
212
|
-
const failureReasonsMask =
|
|
213
|
-
const unsupportedProperties =
|
|
174
|
+
const nodeId = begin?.args?.data?.nodeId;
|
|
175
|
+
const animationId = begin?.args?.data?.id;
|
|
176
|
+
const failureReasonsMask = status?.args?.data?.compositeFailed;
|
|
177
|
+
const unsupportedProperties = status?.args?.data?.unsupportedProperties;
|
|
214
178
|
if (!nodeId || !animationId) continue;
|
|
179
|
+
|
|
215
180
|
const animationIds = elementAnimations.get(nodeId) || new Set();
|
|
216
181
|
animationIds.add({animationId, failureReasonsMask, unsupportedProperties});
|
|
217
182
|
elementAnimations.set(nodeId, animationIds);
|
|
@@ -270,7 +235,7 @@ class TraceElements extends FRGatherer {
|
|
|
270
235
|
});
|
|
271
236
|
const {mainThreadEvents} = processedTrace;
|
|
272
237
|
|
|
273
|
-
const lcpNodeId =
|
|
238
|
+
const lcpNodeId = largestContentfulPaintEvt?.args?.data?.nodeId;
|
|
274
239
|
const clsNodeData = TraceElements.getTopLayoutShiftElements(mainThreadEvents);
|
|
275
240
|
const animatedElementData =
|
|
276
241
|
await this.getAnimatedElements(mainThreadEvents);
|
|
@@ -308,7 +273,7 @@ class TraceElements extends FRGatherer {
|
|
|
308
273
|
continue;
|
|
309
274
|
}
|
|
310
275
|
|
|
311
|
-
if (response
|
|
276
|
+
if (response?.result?.value) {
|
|
312
277
|
traceElements.push({
|
|
313
278
|
traceEventType,
|
|
314
279
|
...response.result.value,
|
package/lighthouse-core/index.js
CHANGED
|
@@ -9,6 +9,7 @@ const Runner = require('./runner.js');
|
|
|
9
9
|
const log = require('lighthouse-logger');
|
|
10
10
|
const ChromeProtocol = require('./gather/connections/cri.js');
|
|
11
11
|
const Config = require('./config/config.js');
|
|
12
|
+
const URL = require('./lib/url-shim.js');
|
|
12
13
|
|
|
13
14
|
/** @typedef {import('./gather/connections/connection.js')} Connection */
|
|
14
15
|
|
|
@@ -42,12 +43,12 @@ async function lighthouse(url, flags = {}, configJSON, userConnection) {
|
|
|
42
43
|
|
|
43
44
|
const config = generateConfig(configJSON, flags);
|
|
44
45
|
const computedCache = new Map();
|
|
45
|
-
const options = {
|
|
46
|
+
const options = {config, computedCache};
|
|
46
47
|
const connection = userConnection || new ChromeProtocol(flags.port, flags.hostname);
|
|
47
48
|
|
|
48
49
|
// kick off a lighthouse run
|
|
49
|
-
|
|
50
|
-
|
|
50
|
+
const gatherFn = () => {
|
|
51
|
+
const requestedUrl = URL.normalizeUrl(url);
|
|
51
52
|
return Runner._gatherArtifactsFromBrowser(requestedUrl, options, connection);
|
|
52
53
|
};
|
|
53
54
|
return Runner.run(gatherFn, options);
|
|
@@ -19,6 +19,8 @@ const LHError = require('../lib/lh-error.js');
|
|
|
19
19
|
// TODO(esmodules): Rollup does not support `promisfy` or `stream.pipeline`. Bundled files
|
|
20
20
|
// don't need anything in this file except for `stringifyReplacer`, so a check for
|
|
21
21
|
// truthiness before using is enough.
|
|
22
|
+
// TODO: Can remove promisify(pipeline) in Node 15.
|
|
23
|
+
// https://nodejs.org/api/stream.html#streams-promises-api
|
|
22
24
|
const pipeline = promisify && promisify(stream.pipeline);
|
|
23
25
|
|
|
24
26
|
const artifactsFilename = 'artifacts.json';
|
|
@@ -238,8 +240,6 @@ async function saveTrace(traceData, traceFilename) {
|
|
|
238
240
|
const traceIter = traceJsonGenerator(traceData);
|
|
239
241
|
const writeStream = fs.createWriteStream(traceFilename);
|
|
240
242
|
|
|
241
|
-
// TODO: Can remove promisify(pipeline) in Node 15.
|
|
242
|
-
// https://nodejs.org/api/stream.html#stream_stream_pipeline_streams_callback
|
|
243
243
|
return pipeline(traceIter, writeStream);
|
|
244
244
|
}
|
|
245
245
|
|
|
@@ -250,10 +250,12 @@ async function saveTrace(traceData, traceFilename) {
|
|
|
250
250
|
* @return {Promise<void>}
|
|
251
251
|
*/
|
|
252
252
|
function saveDevtoolsLog(devtoolsLog, devtoolLogFilename) {
|
|
253
|
-
const logIter = arrayOfObjectsJsonGenerator(devtoolsLog);
|
|
254
253
|
const writeStream = fs.createWriteStream(devtoolLogFilename);
|
|
255
254
|
|
|
256
|
-
return pipeline(
|
|
255
|
+
return pipeline(function* () {
|
|
256
|
+
yield* arrayOfObjectsJsonGenerator(devtoolsLog);
|
|
257
|
+
yield '\n';
|
|
258
|
+
}, writeStream);
|
|
257
259
|
}
|
|
258
260
|
|
|
259
261
|
/**
|
|
@@ -109,7 +109,7 @@ module.exports = class ConnectionPool {
|
|
|
109
109
|
}
|
|
110
110
|
|
|
111
111
|
// This connection is a match and is available! Update our max if it has a larger congestionWindow
|
|
112
|
-
const currentMax = (maxConnection
|
|
112
|
+
const currentMax = (maxConnection?.congestionWindow) || -Infinity;
|
|
113
113
|
if (connection.congestionWindow > currentMax) maxConnection = connection;
|
|
114
114
|
}
|
|
115
115
|
|
|
@@ -390,7 +390,7 @@ class NetworkAnalyzer {
|
|
|
390
390
|
// downloading those bytes. We slice up all the network records into start/end boundaries, so
|
|
391
391
|
// it's easier to deal with the gaps in downloading.
|
|
392
392
|
const timeBoundaries = networkRecords.reduce((boundaries, record) => {
|
|
393
|
-
const scheme = record.parsedURL
|
|
393
|
+
const scheme = record.parsedURL?.scheme;
|
|
394
394
|
// Requests whose bodies didn't come over the network or didn't completely finish will mess
|
|
395
395
|
// with the computation, just skip over them.
|
|
396
396
|
if (scheme === 'data' || record.failed || !record.finished ||
|
|
@@ -25,7 +25,7 @@ const NO_CPU_THROTTLE_METRICS = {
|
|
|
25
25
|
*/
|
|
26
26
|
function parseUseragentIntoMetadata(userAgent, formFactor) {
|
|
27
27
|
const match = userAgent.match(/Chrome\/([\d.]+)/); // eg 'Chrome/(71.0.3577.0)'
|
|
28
|
-
const fullVersion =
|
|
28
|
+
const fullVersion = match?.[1] || '99.0.1234.0';
|
|
29
29
|
const [version] = fullVersion.split('.', 1);
|
|
30
30
|
const brands = [
|
|
31
31
|
{brand: 'Chromium', version},
|
|
@@ -73,8 +73,7 @@ function pngSizedAtLeast(sizeRequirement, manifest) {
|
|
|
73
73
|
function containsMaskableIcon(manifest) {
|
|
74
74
|
const iconValues = manifest.icons.value;
|
|
75
75
|
return iconValues.some(icon => {
|
|
76
|
-
return icon.value.purpose &&
|
|
77
|
-
icon.value.purpose.value &&
|
|
76
|
+
return icon.value.purpose?.value &&
|
|
78
77
|
icon.value.purpose.value.includes('maskable');
|
|
79
78
|
});
|
|
80
79
|
}
|
|
@@ -6,8 +6,7 @@
|
|
|
6
6
|
'use strict';
|
|
7
7
|
|
|
8
8
|
/** @param {LH.Result} lhr @param {string} auditName */
|
|
9
|
-
const getNumericValue = (lhr, auditName) =>
|
|
10
|
-
(lhr.audits[auditName] && lhr.audits[auditName].numericValue) || NaN;
|
|
9
|
+
const getNumericValue = (lhr, auditName) => lhr.audits[auditName]?.numericValue || NaN;
|
|
11
10
|
|
|
12
11
|
/**
|
|
13
12
|
* @param {Array<number>} numbers
|
|
@@ -251,8 +251,8 @@ class NetworkRecorder extends EventEmitter {
|
|
|
251
251
|
if (record.redirectSource) {
|
|
252
252
|
return record.redirectSource;
|
|
253
253
|
}
|
|
254
|
-
const stackFrames =
|
|
255
|
-
const initiatorURL = record.initiator.url ||
|
|
254
|
+
const stackFrames = record.initiator.stack?.callFrames || [];
|
|
255
|
+
const initiatorURL = record.initiator.url || stackFrames[0]?.url;
|
|
256
256
|
|
|
257
257
|
let candidates = recordsByURL.get(initiatorURL) || [];
|
|
258
258
|
// The initiator must come before the initiated request.
|
|
@@ -510,7 +510,7 @@ class NetworkRequest {
|
|
|
510
510
|
|
|
511
511
|
const reasonHeader = record.responseHeaders
|
|
512
512
|
.find(header => header.name === 'Non-Authoritative-Reason');
|
|
513
|
-
const reason = reasonHeader
|
|
513
|
+
const reason = reasonHeader?.value;
|
|
514
514
|
return reason === 'HSTS' && NetworkRequest.isSecureRequest(destination);
|
|
515
515
|
}
|
|
516
516
|
|
|
@@ -19,6 +19,11 @@ const stackPacksToInclude = [
|
|
|
19
19
|
packId: 'wordpress',
|
|
20
20
|
requiredStacks: ['js:wordpress'],
|
|
21
21
|
},
|
|
22
|
+
// waiting for https://github.com/johnmichel/Library-Detector-for-Chrome/pull/193
|
|
23
|
+
// {
|
|
24
|
+
// packId: 'ezoic',
|
|
25
|
+
// requiredStacks: ['js:ezoic'],
|
|
26
|
+
// },
|
|
22
27
|
{
|
|
23
28
|
packId: 'drupal',
|
|
24
29
|
requiredStacks: ['js:drupal'],
|
|
@@ -184,8 +184,7 @@ class CpuProfilerModel {
|
|
|
184
184
|
if (!event) return false;
|
|
185
185
|
return Boolean(
|
|
186
186
|
event.name === SAMPLER_TRACE_EVENT_NAME &&
|
|
187
|
-
event.args.data
|
|
188
|
-
event.args.data._syntheticProfilerRange
|
|
187
|
+
event.args.data?._syntheticProfilerRange
|
|
189
188
|
);
|
|
190
189
|
}
|
|
191
190
|
|
|
@@ -562,9 +561,8 @@ class CpuProfilerModel {
|
|
|
562
561
|
|
|
563
562
|
// `Profile` or `ProfileChunk` can partially define these across multiple events.
|
|
564
563
|
// We'll fallback to empty values and worry about validation in the `synthesizeTraceEvents` phase.
|
|
565
|
-
const cpuProfileArg =
|
|
566
|
-
const timeDeltas =
|
|
567
|
-
(event.args.data && event.args.data.timeDeltas) || cpuProfileArg.timeDeltas;
|
|
564
|
+
const cpuProfileArg = event.args.data?.cpuProfile || {};
|
|
565
|
+
const timeDeltas = event.args.data?.timeDeltas || cpuProfileArg.timeDeltas;
|
|
568
566
|
let profile = profiles.get(event.id);
|
|
569
567
|
|
|
570
568
|
if (event.name === 'Profile') {
|
|
@@ -572,7 +570,7 @@ class CpuProfilerModel {
|
|
|
572
570
|
id: event.id,
|
|
573
571
|
pid: event.pid,
|
|
574
572
|
tid: event.tid,
|
|
575
|
-
startTime:
|
|
573
|
+
startTime: event.args.data?.startTime || event.ts,
|
|
576
574
|
nodes: cpuProfileArg.nodes || [],
|
|
577
575
|
samples: cpuProfileArg.samples || [],
|
|
578
576
|
timeDeltas: timeDeltas || [],
|
|
@@ -234,7 +234,7 @@ class MainThreadTasks {
|
|
|
234
234
|
// Do bookkeeping on XHR requester data.
|
|
235
235
|
if (nextTask.event.name === 'XHRReadyStateChange') {
|
|
236
236
|
const data = nextTask.event.args.data;
|
|
237
|
-
const url = data
|
|
237
|
+
const url = data?.url;
|
|
238
238
|
if (data && url && data.readyState === 1) priorTaskData.xhrs.set(url, nextTask);
|
|
239
239
|
}
|
|
240
240
|
|
|
@@ -425,15 +425,14 @@ class TraceProcessor {
|
|
|
425
425
|
static findMainFrameIds(events) {
|
|
426
426
|
// Prefer the newer TracingStartedInBrowser event first, if it exists
|
|
427
427
|
const startedInBrowserEvt = events.find(e => e.name === 'TracingStartedInBrowser');
|
|
428
|
-
if (startedInBrowserEvt
|
|
429
|
-
startedInBrowserEvt.args.data.frames) {
|
|
428
|
+
if (startedInBrowserEvt?.args.data?.frames) {
|
|
430
429
|
const mainFrame = startedInBrowserEvt.args.data.frames.find(frame => !frame.parent);
|
|
431
|
-
const frameId = mainFrame
|
|
432
|
-
const pid = mainFrame
|
|
430
|
+
const frameId = mainFrame?.frame;
|
|
431
|
+
const pid = mainFrame?.processId;
|
|
433
432
|
|
|
434
433
|
const threadNameEvt = events.find(e => e.pid === pid && e.ph === 'M' &&
|
|
435
434
|
e.cat === '__metadata' && e.name === 'thread_name' && e.args.name === 'CrRendererMain');
|
|
436
|
-
const tid = threadNameEvt
|
|
435
|
+
const tid = threadNameEvt?.tid;
|
|
437
436
|
|
|
438
437
|
if (pid && tid && frameId) {
|
|
439
438
|
return {
|
|
@@ -448,7 +447,7 @@ class TraceProcessor {
|
|
|
448
447
|
// The first TracingStartedInPage in the trace is definitely our renderer thread of interest
|
|
449
448
|
// Beware: the tracingStartedInPage event can appear slightly after a navigationStart
|
|
450
449
|
const startedInPageEvt = events.find(e => e.name === 'TracingStartedInPage');
|
|
451
|
-
if (startedInPageEvt
|
|
450
|
+
if (startedInPageEvt?.args?.data) {
|
|
452
451
|
const frameId = startedInPageEvt.args.data.page;
|
|
453
452
|
if (frameId) {
|
|
454
453
|
return {
|
|
@@ -463,12 +462,12 @@ class TraceProcessor {
|
|
|
463
462
|
// If we can't find either TracingStarted event, then we'll fallback to the first navStart that
|
|
464
463
|
// looks like it was loading the main frame with a real URL. Because the schema for this event
|
|
465
464
|
// has changed across Chrome versions, we'll be extra defensive about finding this case.
|
|
466
|
-
const navStartEvt = events.find(e => Boolean(e.name === 'navigationStart' &&
|
|
467
|
-
e.args
|
|
465
|
+
const navStartEvt = events.find(e => Boolean(e.name === 'navigationStart' &&
|
|
466
|
+
e.args?.data?.isLoadingMainFrame && e.args.data.documentLoaderURL));
|
|
468
467
|
// Find the first resource that was requested and make sure it agrees on the id.
|
|
469
468
|
const firstResourceSendEvt = events.find(e => e.name === 'ResourceSendRequest');
|
|
470
469
|
// We know that these properties exist if we found the events, but TSC doesn't.
|
|
471
|
-
if (navStartEvt
|
|
470
|
+
if (navStartEvt?.args?.data &&
|
|
472
471
|
firstResourceSendEvt &&
|
|
473
472
|
firstResourceSendEvt.pid === navStartEvt.pid &&
|
|
474
473
|
firstResourceSendEvt.tid === navStartEvt.tid) {
|
|
@@ -503,7 +502,7 @@ class TraceProcessor {
|
|
|
503
502
|
static isLCPEvent(evt) {
|
|
504
503
|
if (evt.name !== 'largestContentfulPaint::Invalidate' &&
|
|
505
504
|
evt.name !== 'largestContentfulPaint::Candidate') return false;
|
|
506
|
-
return Boolean(evt.args
|
|
505
|
+
return Boolean(evt.args?.frame);
|
|
507
506
|
}
|
|
508
507
|
|
|
509
508
|
/**
|
|
@@ -513,8 +512,7 @@ class TraceProcessor {
|
|
|
513
512
|
static isLCPCandidateEvent(evt) {
|
|
514
513
|
return Boolean(
|
|
515
514
|
evt.name === 'largestContentfulPaint::Candidate' &&
|
|
516
|
-
evt.args &&
|
|
517
|
-
evt.args.frame &&
|
|
515
|
+
evt.args?.frame &&
|
|
518
516
|
evt.args.data &&
|
|
519
517
|
evt.args.data.size !== undefined
|
|
520
518
|
);
|
|
@@ -613,8 +611,7 @@ class TraceProcessor {
|
|
|
613
611
|
.filter(/** @return {evt is FrameCommittedEvent} */ evt => {
|
|
614
612
|
return Boolean(
|
|
615
613
|
evt.name === 'FrameCommittedInBrowser' &&
|
|
616
|
-
evt.args.data &&
|
|
617
|
-
evt.args.data.frame &&
|
|
614
|
+
evt.args.data?.frame &&
|
|
618
615
|
evt.args.data.url
|
|
619
616
|
);
|
|
620
617
|
})
|
|
@@ -723,7 +720,7 @@ class TraceProcessor {
|
|
|
723
720
|
firstContentfulPaintAllFrames: getTiming(fcpAllFramesEvt.ts),
|
|
724
721
|
firstMeaningfulPaint: frameTimings.timings.firstMeaningfulPaint,
|
|
725
722
|
largestContentfulPaint: frameTimings.timings.largestContentfulPaint,
|
|
726
|
-
largestContentfulPaintAllFrames: maybeGetTiming(lcpAllFramesEvt
|
|
723
|
+
largestContentfulPaintAllFrames: maybeGetTiming(lcpAllFramesEvt?.ts),
|
|
727
724
|
load: frameTimings.timings.load,
|
|
728
725
|
domContentLoaded: frameTimings.timings.domContentLoaded,
|
|
729
726
|
traceEnd: timings.traceEnd,
|
|
@@ -735,7 +732,7 @@ class TraceProcessor {
|
|
|
735
732
|
firstContentfulPaintAllFrames: fcpAllFramesEvt.ts,
|
|
736
733
|
firstMeaningfulPaint: frameTimings.timestamps.firstMeaningfulPaint,
|
|
737
734
|
largestContentfulPaint: frameTimings.timestamps.largestContentfulPaint,
|
|
738
|
-
largestContentfulPaintAllFrames: lcpAllFramesEvt
|
|
735
|
+
largestContentfulPaintAllFrames: lcpAllFramesEvt?.ts,
|
|
739
736
|
load: frameTimings.timestamps.load,
|
|
740
737
|
domContentLoaded: frameTimings.timestamps.domContentLoaded,
|
|
741
738
|
traceEnd: timestamps.traceEnd,
|
|
@@ -886,7 +883,7 @@ class TraceProcessor {
|
|
|
886
883
|
);
|
|
887
884
|
|
|
888
885
|
/** @param {{ts: number}=} event */
|
|
889
|
-
const getTimestamp = (event) => event
|
|
886
|
+
const getTimestamp = (event) => event?.ts;
|
|
890
887
|
/** @type {TraceNavigationTimesForFrame} */
|
|
891
888
|
const timestamps = {
|
|
892
889
|
timeOrigin: timeOriginEvt.ts,
|
|
@@ -10,6 +10,7 @@
|
|
|
10
10
|
*/
|
|
11
11
|
|
|
12
12
|
const {Util} = require('../util-commonjs.js');
|
|
13
|
+
const LHError = require('../lib/lh-error.js');
|
|
13
14
|
|
|
14
15
|
/** @typedef {import('./network-request.js')} NetworkRequest */
|
|
15
16
|
|
|
@@ -248,6 +249,20 @@ class URLShim extends URL {
|
|
|
248
249
|
if (ext === 'jpg') return 'image/jpeg';
|
|
249
250
|
return `image/${ext}`;
|
|
250
251
|
}
|
|
252
|
+
|
|
253
|
+
/**
|
|
254
|
+
* @param {string|undefined} url
|
|
255
|
+
* @return {string}
|
|
256
|
+
*/
|
|
257
|
+
static normalizeUrl(url) {
|
|
258
|
+
// Verify the url is valid and that protocol is allowed
|
|
259
|
+
if (url && this.isValid(url) && this.isProtocolAllowed(url)) {
|
|
260
|
+
// Use canonicalized URL (with trailing slashes and such)
|
|
261
|
+
return new URL(url).href;
|
|
262
|
+
} else {
|
|
263
|
+
throw new LHError(LHError.errors.INVALID_URL);
|
|
264
|
+
}
|
|
265
|
+
}
|
|
251
266
|
}
|
|
252
267
|
|
|
253
268
|
URLShim.URL = URL;
|
|
@@ -16,7 +16,6 @@ const stackPacks = require('./lib/stack-packs.js');
|
|
|
16
16
|
const assetSaver = require('./lib/asset-saver.js');
|
|
17
17
|
const fs = require('fs');
|
|
18
18
|
const path = require('path');
|
|
19
|
-
const URL = require('./lib/url-shim.js');
|
|
20
19
|
const Sentry = require('./lib/sentry.js');
|
|
21
20
|
const generateReport = require('../report/generator/report-generator.js').generateReport;
|
|
22
21
|
const LHError = require('./lib/lh-error.js');
|
|
@@ -29,8 +28,8 @@ const {version: lighthouseVersion} = require('../package.json');
|
|
|
29
28
|
class Runner {
|
|
30
29
|
/**
|
|
31
30
|
* @template {LH.Config.Config | LH.Config.FRConfig} TConfig
|
|
32
|
-
* @param {(runnerData: {
|
|
33
|
-
* @param {{config: TConfig, computedCache: Map<string, ArbitraryEqualityMap>,
|
|
31
|
+
* @param {(runnerData: {config: TConfig, driverMock?: Driver}) => Promise<LH.Artifacts>} gatherFn
|
|
32
|
+
* @param {{config: TConfig, computedCache: Map<string, ArbitraryEqualityMap>, driverMock?: Driver}} runOpts
|
|
34
33
|
* @return {Promise<LH.RunnerResult|undefined>}
|
|
35
34
|
*/
|
|
36
35
|
static async run(gatherFn, runOpts) {
|
|
@@ -49,50 +48,10 @@ class Runner {
|
|
|
49
48
|
Sentry.captureBreadcrumb({
|
|
50
49
|
message: 'Run started',
|
|
51
50
|
category: 'lifecycle',
|
|
52
|
-
data: sentryContext
|
|
51
|
+
data: sentryContext?.extra,
|
|
53
52
|
});
|
|
54
53
|
|
|
55
|
-
|
|
56
|
-
// -G and -A will run partial lighthouse pipelines,
|
|
57
|
-
// and -GA will run everything plus save artifacts and lhr to disk.
|
|
58
|
-
|
|
59
|
-
// Gather phase
|
|
60
|
-
// Either load saved artifacts off disk or from the browser
|
|
61
|
-
let artifacts;
|
|
62
|
-
let requestedUrl;
|
|
63
|
-
if (settings.auditMode && !settings.gatherMode) {
|
|
64
|
-
// No browser required, just load the artifacts from disk.
|
|
65
|
-
const path = Runner._getDataSavePath(settings);
|
|
66
|
-
artifacts = assetSaver.loadArtifacts(path);
|
|
67
|
-
requestedUrl = artifacts.URL.requestedUrl;
|
|
68
|
-
|
|
69
|
-
if (!requestedUrl) {
|
|
70
|
-
throw new Error('Cannot run audit mode on empty URL');
|
|
71
|
-
}
|
|
72
|
-
if (runOpts.url && !URL.equalWithExcludedFragments(runOpts.url, requestedUrl)) {
|
|
73
|
-
throw new Error('Cannot run audit mode on different URL');
|
|
74
|
-
}
|
|
75
|
-
} else {
|
|
76
|
-
// verify the url is valid and that protocol is allowed
|
|
77
|
-
if (runOpts.url && URL.isValid(runOpts.url) && URL.isProtocolAllowed(runOpts.url)) {
|
|
78
|
-
// Use canonicalized URL (with trailing slashes and such)
|
|
79
|
-
requestedUrl = new URL(runOpts.url).href;
|
|
80
|
-
} else {
|
|
81
|
-
throw new LHError(LHError.errors.INVALID_URL);
|
|
82
|
-
}
|
|
83
|
-
|
|
84
|
-
artifacts = await gatherFn({
|
|
85
|
-
requestedUrl,
|
|
86
|
-
config: runOpts.config,
|
|
87
|
-
driverMock: runOpts.driverMock,
|
|
88
|
-
});
|
|
89
|
-
|
|
90
|
-
// -G means save these to ./latest-run, etc.
|
|
91
|
-
if (settings.gatherMode) {
|
|
92
|
-
const path = Runner._getDataSavePath(settings);
|
|
93
|
-
await assetSaver.saveArtifacts(artifacts, path);
|
|
94
|
-
}
|
|
95
|
-
}
|
|
54
|
+
const artifacts = await this.gatherAndManageArtifacts(gatherFn, runOpts);
|
|
96
55
|
|
|
97
56
|
// Potentially quit early
|
|
98
57
|
if (settings.gatherMode && !settings.auditMode) return;
|
|
@@ -116,7 +75,7 @@ class Runner {
|
|
|
116
75
|
|
|
117
76
|
// Use version from gathering stage.
|
|
118
77
|
// If accessibility gatherer didn't run or errored, it won't be in credits.
|
|
119
|
-
const axeVersion = artifacts.Accessibility
|
|
78
|
+
const axeVersion = artifacts.Accessibility?.version;
|
|
120
79
|
const credits = {
|
|
121
80
|
'axe-core': axeVersion,
|
|
122
81
|
};
|
|
@@ -133,7 +92,7 @@ class Runner {
|
|
|
133
92
|
/** @type {LH.RawIcu<LH.Result>} */
|
|
134
93
|
const i18nLhr = {
|
|
135
94
|
lighthouseVersion,
|
|
136
|
-
requestedUrl,
|
|
95
|
+
requestedUrl: artifacts.URL.requestedUrl,
|
|
137
96
|
finalUrl: artifacts.URL.finalUrl,
|
|
138
97
|
fetchTime: artifacts.fetchTime,
|
|
139
98
|
gatherMode: artifacts.GatherContext.gatherMode,
|
|
@@ -184,6 +143,47 @@ class Runner {
|
|
|
184
143
|
}
|
|
185
144
|
}
|
|
186
145
|
|
|
146
|
+
/**
|
|
147
|
+
* User can run -G solo, -A solo, or -GA together
|
|
148
|
+
* -G and -A will run partial lighthouse pipelines,
|
|
149
|
+
* and -GA will run everything plus save artifacts and lhr to disk.
|
|
150
|
+
*
|
|
151
|
+
* @template {LH.Config.Config | LH.Config.FRConfig} TConfig
|
|
152
|
+
* @param {(runnerData: {config: TConfig, driverMock?: Driver}) => Promise<LH.Artifacts>} gatherFn
|
|
153
|
+
* @param {{config: TConfig, driverMock?: Driver}} options
|
|
154
|
+
* @return {Promise<LH.Artifacts>}
|
|
155
|
+
*/
|
|
156
|
+
static async gatherAndManageArtifacts(gatherFn, options) {
|
|
157
|
+
const settings = options.config.settings;
|
|
158
|
+
|
|
159
|
+
// Gather phase
|
|
160
|
+
// Either load saved artifacts from disk or from the browser.
|
|
161
|
+
let artifacts;
|
|
162
|
+
if (settings.auditMode && !settings.gatherMode) {
|
|
163
|
+
// No browser required, just load the artifacts from disk.
|
|
164
|
+
const path = this._getDataSavePath(settings);
|
|
165
|
+
artifacts = assetSaver.loadArtifacts(path);
|
|
166
|
+
const requestedUrl = artifacts.URL.requestedUrl;
|
|
167
|
+
|
|
168
|
+
if (!requestedUrl) {
|
|
169
|
+
throw new Error('Cannot run audit mode on empty URL');
|
|
170
|
+
}
|
|
171
|
+
} else {
|
|
172
|
+
artifacts = await gatherFn({
|
|
173
|
+
config: options.config,
|
|
174
|
+
driverMock: options.driverMock,
|
|
175
|
+
});
|
|
176
|
+
|
|
177
|
+
// -G means save these to disk (e.g. ./latest-run).
|
|
178
|
+
if (settings.gatherMode) {
|
|
179
|
+
const path = this._getDataSavePath(settings);
|
|
180
|
+
await assetSaver.saveArtifacts(artifacts, path);
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
return artifacts;
|
|
185
|
+
}
|
|
186
|
+
|
|
187
187
|
/**
|
|
188
188
|
* This handles both the auditMode case where gatherer entries need to be merged in and
|
|
189
189
|
* the gather/audit case where timingEntriesFromRunner contains all entries from this run,
|
|
@@ -213,7 +213,7 @@ class Runner {
|
|
|
213
213
|
};
|
|
214
214
|
}).sort((a, b) => a.startTime - b.startTime);
|
|
215
215
|
const runnerEntry = timingEntries.find(e => e.name === 'lh:runner:run');
|
|
216
|
-
return {entries: timingEntries, total: runnerEntry
|
|
216
|
+
return {entries: timingEntries, total: runnerEntry?.duration || 0};
|
|
217
217
|
}
|
|
218
218
|
|
|
219
219
|
/**
|
|
@@ -263,6 +263,19 @@ class Runner {
|
|
|
263
263
|
const normalizedGatherSettings = Object.assign({}, artifacts.settings, overrides);
|
|
264
264
|
const normalizedAuditSettings = Object.assign({}, settings, overrides);
|
|
265
265
|
|
|
266
|
+
// First, try each key individually so we can print which key differed.
|
|
267
|
+
const keys = new Set([
|
|
268
|
+
...Object.keys(normalizedGatherSettings),
|
|
269
|
+
...Object.keys(normalizedAuditSettings),
|
|
270
|
+
]);
|
|
271
|
+
for (const k of keys) {
|
|
272
|
+
if (!isDeepEqual(normalizedGatherSettings[k], normalizedAuditSettings[k])) {
|
|
273
|
+
throw new Error(
|
|
274
|
+
`Cannot change settings between gathering and auditing. Difference found at: ${k}`);
|
|
275
|
+
}
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
// Call `isDeepEqual` on the entire thing, just in case something was missed.
|
|
266
279
|
if (!isDeepEqual(normalizedGatherSettings, normalizedAuditSettings)) {
|
|
267
280
|
throw new Error('Cannot change settings between gathering and auditing');
|
|
268
281
|
}
|
|
@@ -367,7 +380,7 @@ class Runner {
|
|
|
367
380
|
return narrowedArtifacts;
|
|
368
381
|
}, /** @type {LH.Artifacts} */ ({}));
|
|
369
382
|
const product = await audit.audit(narrowedArtifacts, auditContext);
|
|
370
|
-
runWarnings.push(...product.runWarnings || []);
|
|
383
|
+
runWarnings.push(...(product.runWarnings || []));
|
|
371
384
|
|
|
372
385
|
auditResult = Audit.generateAuditResult(audit, product);
|
|
373
386
|
} catch (err) {
|
|
@@ -10,10 +10,23 @@
|
|
|
10
10
|
|
|
11
11
|
set -euo pipefail
|
|
12
12
|
|
|
13
|
-
|
|
13
|
+
unameOut="$(uname -s)"
|
|
14
|
+
case "${unameOut}" in
|
|
15
|
+
Linux*) machine=Linux;;
|
|
16
|
+
Darwin*) machine=Mac;;
|
|
17
|
+
MINGW*) machine=MinGw;;
|
|
18
|
+
*) machine="UNKNOWN:${unameOut}"
|
|
19
|
+
esac
|
|
20
|
+
|
|
21
|
+
if [ "$machine" == "MinGw" ]; then
|
|
14
22
|
url="https://download-chromium.appspot.com/dl/Win?type=snapshots"
|
|
15
|
-
|
|
23
|
+
elif [ "$machine" == "Linux" ]; then
|
|
16
24
|
url="https://download-chromium.appspot.com/dl/Linux_x64?type=snapshots"
|
|
25
|
+
elif [ "$machine" == "Mac" ]; then
|
|
26
|
+
url="https://download-chromium.appspot.com/dl/Mac?type=snapshots"
|
|
27
|
+
else
|
|
28
|
+
echo "unsupported platform"
|
|
29
|
+
exit 1
|
|
17
30
|
fi
|
|
18
31
|
|
|
19
32
|
if [ -e "$CHROME_PATH" ]; then
|