lighthouse 9.2.0-dev.20211217 → 9.2.0-dev.20211221
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/dist/report/bundle.esm.js +6 -6
- package/dist/report/flow.js +5 -5
- package/dist/report/standalone.js +3 -3
- 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/run.js +1 -1
- package/lighthouse-cli/test/smokehouse/lighthouse-runners/cli.js +1 -1
- package/lighthouse-cli/test/smokehouse/report-assert.js +1 -1
- package/lighthouse-cli/test/smokehouse/smokehouse.js +4 -4
- 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 +2 -2
- 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/dobetterweb/inspector-issues.js +3 -3
- 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/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-lcp-image.js +1 -1
- package/lighthouse-core/audits/themed-omnibox.js +1 -1
- 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 +2 -2
- 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 +3 -3
- 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/lib/arbitrary-equality-map.js +1 -1
- 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/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/runner.js +4 -4
- package/package.json +10 -10
- package/report/renderer/category-renderer.js +1 -1
- package/report/renderer/performance-category-renderer.js +4 -4
- package/report/renderer/report-renderer.js +1 -1
- package/report/test-assets/faux-psi.js +2 -3
- package/shared/localization/locales/en-US.json +3 -3
- package/shared/localization/locales/en-XL.json +3 -3
- package/third-party/snyk/snapshot.json +3 -1
|
@@ -161,7 +161,7 @@ class Config {
|
|
|
161
161
|
constructor(configJSON, flags) {
|
|
162
162
|
const status = {msg: 'Create config', id: 'lh:init:config'};
|
|
163
163
|
log.time(status, 'verbose');
|
|
164
|
-
let configPath = flags
|
|
164
|
+
let configPath = flags?.configPath;
|
|
165
165
|
|
|
166
166
|
if (!configJSON) {
|
|
167
167
|
configJSON = defaultConfig;
|
|
@@ -5,6 +5,8 @@
|
|
|
5
5
|
*/
|
|
6
6
|
'use strict';
|
|
7
7
|
|
|
8
|
+
const log = require('lighthouse-logger');
|
|
9
|
+
|
|
8
10
|
const Audit = require('../../audits/audit.js');
|
|
9
11
|
|
|
10
12
|
/** @type {Record<keyof LH.FRBaseArtifacts, string>} */
|
|
@@ -45,7 +47,7 @@ function getAuditIdsInCategories(allCategories, onlyCategories) {
|
|
|
45
47
|
|
|
46
48
|
onlyCategories = onlyCategories || Object.keys(allCategories);
|
|
47
49
|
const categories = onlyCategories.map(categoryId => allCategories[categoryId]);
|
|
48
|
-
const auditRefs = categories.flatMap(category => category
|
|
50
|
+
const auditRefs = categories.flatMap(category => category?.auditRefs || []);
|
|
49
51
|
return new Set(auditRefs.map(auditRef => auditRef.id));
|
|
50
52
|
}
|
|
51
53
|
|
|
@@ -194,6 +196,24 @@ function filterCategoriesByExplicitFilters(categories, onlyCategories) {
|
|
|
194
196
|
return Object.fromEntries(categoriesToKeep);
|
|
195
197
|
}
|
|
196
198
|
|
|
199
|
+
/**
|
|
200
|
+
* Logs a warning if any specified onlyCategory is not a known category that can
|
|
201
|
+
* be included.
|
|
202
|
+
*
|
|
203
|
+
* @param {LH.Config.Config['categories']} allCategories
|
|
204
|
+
* @param {string[] | null} onlyCategories
|
|
205
|
+
* @return {void}
|
|
206
|
+
*/
|
|
207
|
+
function warnOnUnknownOnlyCategories(allCategories, onlyCategories) {
|
|
208
|
+
if (!onlyCategories) return;
|
|
209
|
+
|
|
210
|
+
for (const onlyCategoryId of onlyCategories) {
|
|
211
|
+
if (!allCategories?.[onlyCategoryId]) {
|
|
212
|
+
log.warn('config', `unrecognized category in 'onlyCategories': ${onlyCategoryId}`);
|
|
213
|
+
}
|
|
214
|
+
}
|
|
215
|
+
}
|
|
216
|
+
|
|
197
217
|
/**
|
|
198
218
|
* Filters a categories object and their auditRefs down to the set that can be computed using
|
|
199
219
|
* only the specified audits.
|
|
@@ -266,6 +286,8 @@ function filterConfigByGatherMode(config, mode) {
|
|
|
266
286
|
function filterConfigByExplicitFilters(config, filters) {
|
|
267
287
|
const {onlyAudits, onlyCategories, skipAudits} = filters;
|
|
268
288
|
|
|
289
|
+
warnOnUnknownOnlyCategories(config.categories, onlyCategories);
|
|
290
|
+
|
|
269
291
|
let baseAuditIds = getAuditIdsInCategories(config.categories, undefined);
|
|
270
292
|
if (onlyCategories) {
|
|
271
293
|
baseAuditIds = getAuditIdsInCategories(config.categories, onlyCategories);
|
|
@@ -49,7 +49,7 @@ function assertValidPluginName(configJSON, pluginName) {
|
|
|
49
49
|
throw new Error(`plugin name '${pluginName}' does not start with 'lighthouse-plugin-'`);
|
|
50
50
|
}
|
|
51
51
|
|
|
52
|
-
if (configJSON.categories
|
|
52
|
+
if (configJSON.categories?.[pluginName]) {
|
|
53
53
|
throw new Error(`plugin name '${pluginName}' not allowed because it is the id of a category already found in config`); // eslint-disable-line max-len
|
|
54
54
|
}
|
|
55
55
|
}
|
|
@@ -123,7 +123,7 @@ function assertValidFRNavigations(navigationsDefn) {
|
|
|
123
123
|
function assertValidAudit(auditDefinition) {
|
|
124
124
|
const {implementation, path: auditPath} = auditDefinition;
|
|
125
125
|
const auditName = auditPath ||
|
|
126
|
-
|
|
126
|
+
implementation?.meta?.id ||
|
|
127
127
|
'Unknown audit';
|
|
128
128
|
|
|
129
129
|
if (typeof implementation.audit !== 'function' || implementation.audit === Audit.audit) {
|
|
@@ -136,11 +136,11 @@ async function _collectDebugData(navigationContext, phaseState) {
|
|
|
136
136
|
await collectPhaseArtifacts({...phaseState, phase: 'getArtifact', artifactDefinitions});
|
|
137
137
|
const getArtifactState = phaseState.artifactState.getArtifact;
|
|
138
138
|
|
|
139
|
-
const devtoolsLogArtifactId = devtoolsLogArtifactDefn
|
|
139
|
+
const devtoolsLogArtifactId = devtoolsLogArtifactDefn?.id;
|
|
140
140
|
const devtoolsLog = devtoolsLogArtifactId && await getArtifactState[devtoolsLogArtifactId];
|
|
141
141
|
const records = devtoolsLog && await NetworkRecords.request(devtoolsLog, navigationContext);
|
|
142
142
|
|
|
143
|
-
const traceArtifactId = traceArtifactDefn
|
|
143
|
+
const traceArtifactId = traceArtifactDefn?.id;
|
|
144
144
|
const trace = traceArtifactId && await getArtifactState[traceArtifactId];
|
|
145
145
|
|
|
146
146
|
return {devtoolsLog, records, trace};
|
|
@@ -23,7 +23,7 @@ class UserFlow {
|
|
|
23
23
|
/** @type {FrOptions} */
|
|
24
24
|
this.options = {page, ...options};
|
|
25
25
|
/** @type {string|undefined} */
|
|
26
|
-
this.name = options
|
|
26
|
+
this.name = options?.name;
|
|
27
27
|
/** @type {LH.FlowResult.Step[]} */
|
|
28
28
|
this.steps = [];
|
|
29
29
|
}
|
|
@@ -90,7 +90,7 @@ class UserFlow {
|
|
|
90
90
|
const result = await navigation(this._getNextNavigationOptions(url, stepOptions));
|
|
91
91
|
if (!result) throw Error('Navigation returned undefined');
|
|
92
92
|
|
|
93
|
-
const providedName = stepOptions
|
|
93
|
+
const providedName = stepOptions?.stepName;
|
|
94
94
|
this.steps.push({
|
|
95
95
|
lhr: result.lhr,
|
|
96
96
|
name: providedName || this._getDefaultStepName(result.lhr),
|
|
@@ -118,7 +118,7 @@ class UserFlow {
|
|
|
118
118
|
this.currentTimespan = undefined;
|
|
119
119
|
if (!result) throw Error('Timespan returned undefined');
|
|
120
120
|
|
|
121
|
-
const providedName = options
|
|
121
|
+
const providedName = options?.stepName;
|
|
122
122
|
this.steps.push({
|
|
123
123
|
lhr: result.lhr,
|
|
124
124
|
name: providedName || this._getDefaultStepName(result.lhr),
|
|
@@ -137,7 +137,7 @@ class UserFlow {
|
|
|
137
137
|
const result = await snapshot(options);
|
|
138
138
|
if (!result) throw Error('Snapshot returned undefined');
|
|
139
139
|
|
|
140
|
-
const providedName = stepOptions
|
|
140
|
+
const providedName = stepOptions?.stepName;
|
|
141
141
|
this.steps.push({
|
|
142
142
|
lhr: result.lhr,
|
|
143
143
|
name: providedName || this._getDefaultStepName(result.lhr),
|
|
@@ -148,7 +148,7 @@ class Connection {
|
|
|
148
148
|
} else {
|
|
149
149
|
// In DevTools we receive responses to commands we did not send which we cannot act on, so we
|
|
150
150
|
// just log these occurrences.
|
|
151
|
-
const error = object.error
|
|
151
|
+
const error = object.error?.message;
|
|
152
152
|
log.formatProtocol(`disowned method <= browser ${error ? 'ERR' : 'OK'}`,
|
|
153
153
|
{method: 'UNKNOWN', params: error || object.result}, 'verbose');
|
|
154
154
|
}
|
|
@@ -121,7 +121,7 @@ class ExecutionContext {
|
|
|
121
121
|
new Error('Runtime.evaluate response did not contain a "result" object'));
|
|
122
122
|
}
|
|
123
123
|
const value = response.result.value;
|
|
124
|
-
if (value
|
|
124
|
+
if (value?.__failedInBrowser) {
|
|
125
125
|
return Promise.reject(Object.assign(new Error(), value));
|
|
126
126
|
} else {
|
|
127
127
|
return value;
|
|
@@ -144,7 +144,7 @@ class NetworkMonitor {
|
|
|
144
144
|
const finalNavigation = mainFrameNavigations[mainFrameNavigations.length - 1];
|
|
145
145
|
if (!finalNavigation) log.warn('NetworkMonitor', 'No detected navigations');
|
|
146
146
|
|
|
147
|
-
return finalNavigation
|
|
147
|
+
return finalNavigation?.url;
|
|
148
148
|
}
|
|
149
149
|
|
|
150
150
|
/**
|
|
@@ -172,7 +172,7 @@ class NetworkMonitor {
|
|
|
172
172
|
if (!this._networkRecorder) return false;
|
|
173
173
|
const requests = this._networkRecorder.getRawRecords();
|
|
174
174
|
const rootFrameRequest = requests.find(r => r.resourceType === 'Document');
|
|
175
|
-
const rootFrameId = rootFrameRequest
|
|
175
|
+
const rootFrameId = rootFrameRequest?.frameId;
|
|
176
176
|
|
|
177
177
|
return this._isActiveIdlePeriod(
|
|
178
178
|
0,
|
|
@@ -49,7 +49,7 @@ class TargetManager {
|
|
|
49
49
|
async _onSessionAttached(session) {
|
|
50
50
|
try {
|
|
51
51
|
const target = await session.sendCommand('Target.getTargetInfo').catch(() => null);
|
|
52
|
-
const targetType = target
|
|
52
|
+
const targetType = target?.targetInfo?.type;
|
|
53
53
|
const hasValidTargetType = targetType === 'page' || targetType === 'iframe';
|
|
54
54
|
if (!target || !hasValidTargetType) return;
|
|
55
55
|
|
|
@@ -425,7 +425,7 @@ class Driver {
|
|
|
425
425
|
* @return {Promise<void>}
|
|
426
426
|
*/
|
|
427
427
|
async beginTrace(settings) {
|
|
428
|
-
const additionalCategories = (settings
|
|
428
|
+
const additionalCategories = (settings?.additionalTraceCategories &&
|
|
429
429
|
settings.additionalTraceCategories.split(',')) || [];
|
|
430
430
|
const traceCategories = TraceGatherer.getDefaultTraceCategories().concat(additionalCategories);
|
|
431
431
|
|
|
@@ -66,7 +66,7 @@ class ConsoleMessages extends FRGatherer {
|
|
|
66
66
|
}
|
|
67
67
|
|
|
68
68
|
const {url, lineNumber, columnNumber} =
|
|
69
|
-
event.stackTrace
|
|
69
|
+
event.stackTrace?.callFrames[0] || {};
|
|
70
70
|
/** @type {LH.Artifacts.ConsoleMessage} */
|
|
71
71
|
const consoleMessage = {
|
|
72
72
|
eventType: 'consoleAPI',
|
|
@@ -117,7 +117,7 @@ class ConsoleMessages extends FRGatherer {
|
|
|
117
117
|
|
|
118
118
|
// JS events have a stack trace, which we use to get the column.
|
|
119
119
|
// CSS/HTML events only expose a line number.
|
|
120
|
-
const {columnNumber} = event.entry.stackTrace
|
|
120
|
+
const {columnNumber} = event.entry.stackTrace?.callFrames[0] || {};
|
|
121
121
|
|
|
122
122
|
this._logEntries.push({
|
|
123
123
|
eventType: 'protocolLog',
|
|
@@ -76,7 +76,7 @@ class InspectorIssues extends FRGatherer {
|
|
|
76
76
|
for (const issue of this._issues) {
|
|
77
77
|
if (issue.details.mixedContentIssueDetails) {
|
|
78
78
|
const issueDetails = issue.details.mixedContentIssueDetails;
|
|
79
|
-
const issueReqId = issueDetails.request
|
|
79
|
+
const issueReqId = issueDetails.request?.requestId;
|
|
80
80
|
// Duplicate issues can occur for the same request; only use the one with a matching networkRequest.
|
|
81
81
|
if (issueReqId &&
|
|
82
82
|
networkRecords.find(req => req.requestId === issueReqId)) {
|
|
@@ -85,7 +85,7 @@ class InspectorIssues extends FRGatherer {
|
|
|
85
85
|
}
|
|
86
86
|
if (issue.details.sameSiteCookieIssueDetails) {
|
|
87
87
|
const issueDetails = issue.details.sameSiteCookieIssueDetails;
|
|
88
|
-
const issueReqId = issueDetails.request
|
|
88
|
+
const issueReqId = issueDetails.request?.requestId;
|
|
89
89
|
// Duplicate issues can occur for the same request; only use the one with a matching networkRequest.
|
|
90
90
|
if (issueReqId &&
|
|
91
91
|
networkRecords.find(req => req.requestId === issueReqId)) {
|
|
@@ -94,7 +94,7 @@ class InspectorIssues extends FRGatherer {
|
|
|
94
94
|
}
|
|
95
95
|
if (issue.details.blockedByResponseIssueDetails) {
|
|
96
96
|
const issueDetails = issue.details.blockedByResponseIssueDetails;
|
|
97
|
-
const issueReqId = issueDetails.request
|
|
97
|
+
const issueReqId = issueDetails.request?.requestId;
|
|
98
98
|
// Duplicate issues can occur for the same request; only use the one with a matching networkRequest.
|
|
99
99
|
if (issueReqId &&
|
|
100
100
|
networkRecords.find(req => req.requestId === issueReqId)) {
|
|
@@ -116,7 +116,7 @@ class JsUsage extends FRGatherer {
|
|
|
116
116
|
let url = scriptUsage.url;
|
|
117
117
|
const scriptParsedEvent =
|
|
118
118
|
this._scriptParsedEvents.find(e => e.scriptId === scriptUsage.scriptId);
|
|
119
|
-
if (scriptParsedEvent
|
|
119
|
+
if (scriptParsedEvent?.embedderName) {
|
|
120
120
|
url = scriptParsedEvent.embedderName;
|
|
121
121
|
}
|
|
122
122
|
|
|
@@ -355,7 +355,7 @@ class FontSize extends FRGatherer {
|
|
|
355
355
|
// For the nodes whose computed style we could attribute to a stylesheet, assign
|
|
356
356
|
// the stylsheet to the data.
|
|
357
357
|
analyzedFailingNodesData
|
|
358
|
-
.filter(data => data.cssRule
|
|
358
|
+
.filter(data => data.cssRule?.styleSheetId)
|
|
359
359
|
// @ts-expect-error - guaranteed to exist from the filter immediately above
|
|
360
360
|
.forEach(data => (data.cssRule.stylesheet = stylesheets.get(data.cssRule.styleSheetId)));
|
|
361
361
|
|
|
@@ -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,
|
|
@@ -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
|
|
|
@@ -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,
|
|
@@ -49,7 +49,7 @@ class Runner {
|
|
|
49
49
|
Sentry.captureBreadcrumb({
|
|
50
50
|
message: 'Run started',
|
|
51
51
|
category: 'lifecycle',
|
|
52
|
-
data: sentryContext
|
|
52
|
+
data: sentryContext?.extra,
|
|
53
53
|
});
|
|
54
54
|
|
|
55
55
|
// User can run -G solo, -A solo, or -GA together
|
|
@@ -116,7 +116,7 @@ class Runner {
|
|
|
116
116
|
|
|
117
117
|
// Use version from gathering stage.
|
|
118
118
|
// If accessibility gatherer didn't run or errored, it won't be in credits.
|
|
119
|
-
const axeVersion = artifacts.Accessibility
|
|
119
|
+
const axeVersion = artifacts.Accessibility?.version;
|
|
120
120
|
const credits = {
|
|
121
121
|
'axe-core': axeVersion,
|
|
122
122
|
};
|
|
@@ -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
|
/**
|
|
@@ -367,7 +367,7 @@ class Runner {
|
|
|
367
367
|
return narrowedArtifacts;
|
|
368
368
|
}, /** @type {LH.Artifacts} */ ({}));
|
|
369
369
|
const product = await audit.audit(narrowedArtifacts, auditContext);
|
|
370
|
-
runWarnings.push(...product.runWarnings || []);
|
|
370
|
+
runWarnings.push(...(product.runWarnings || []));
|
|
371
371
|
|
|
372
372
|
auditResult = Audit.generateAuditResult(audit, product);
|
|
373
373
|
} catch (err) {
|