lighthouse 9.5.0-dev.20220424 → 9.5.0-dev.20220427
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 +52 -41
- package/dist/report/flow.js +3 -3
- package/dist/report/standalone.js +8 -8
- package/lighthouse-cli/cli-flags.js +1 -1
- package/lighthouse-cli/test/smokehouse/frontends/lib.js +1 -3
- package/lighthouse-cli/test/smokehouse/frontends/smokehouse-bin.js +1 -3
- package/lighthouse-cli/test/smokehouse/report-assert.js +1 -3
- package/lighthouse-core/audits/deprecations.js +14 -33
- package/lighthouse-core/audits/dobetterweb/doctype.js +20 -9
- package/lighthouse-core/audits/metrics/experimental-interaction-to-next-paint.js +84 -0
- package/lighthouse-core/computed/metrics/responsiveness.js +64 -0
- package/lighthouse-core/config/config-helpers.js +1 -1
- package/lighthouse-core/fraggle-rock/config/default-config.js +2 -0
- package/lighthouse-core/fraggle-rock/gather/base-artifacts.js +2 -2
- package/lighthouse-core/gather/gatherers/accessibility.js +16 -4
- package/lighthouse-core/gather/gatherers/dobetterweb/doctype.js +4 -2
- package/lighthouse-core/lib/arbitrary-equality-map.js +2 -2
- package/lighthouse-core/lib/i18n/i18n.js +2 -0
- package/lighthouse-core/lib/minify-trace.js +2 -0
- package/lighthouse-core/lib/tracehouse/trace-processor.js +29 -9
- package/lighthouse-core/runner.js +1 -1
- package/lighthouse-core/util-commonjs.js +3 -7
- package/package.json +4 -4
- package/readme.md +1 -1
- package/report/renderer/details-renderer.js +6 -5
- package/report/renderer/i18n.js +43 -29
- package/report/renderer/util.js +3 -7
- package/report/test/renderer/details-renderer-test.js +49 -0
- package/report/test/renderer/i18n-test.js +49 -20
- package/report/test/renderer/performance-category-renderer-test.js +11 -1
- package/shared/localization/locales/en-US.json +10 -1
- package/shared/localization/locales/en-XL.json +10 -1
- package/shared/localization/swap-locale.js +2 -1
- package/types/artifacts.d.ts +6 -0
|
@@ -177,7 +177,7 @@ function getFlags(manualArgv, options = {}) {
|
|
|
177
177
|
},
|
|
178
178
|
'enable-error-reporting': {
|
|
179
179
|
type: 'boolean',
|
|
180
|
-
describe: 'Enables error reporting, overriding any saved preference. --no-enable-error-reporting will do the opposite. More: https://
|
|
180
|
+
describe: 'Enables error reporting, overriding any saved preference. --no-enable-error-reporting will do the opposite. More: https://github.com/GoogleChrome/lighthouse/blob/master/docs/error-reporting.md',
|
|
181
181
|
},
|
|
182
182
|
'gather-mode': {
|
|
183
183
|
alias: 'G',
|
|
@@ -12,13 +12,11 @@
|
|
|
12
12
|
|
|
13
13
|
/* eslint-disable no-console */
|
|
14
14
|
|
|
15
|
-
import
|
|
15
|
+
import cloneDeep from 'lodash/cloneDeep.js';
|
|
16
16
|
|
|
17
17
|
import smokeTests from '../core-tests.js';
|
|
18
18
|
import {runSmokehouse, getShardedDefinitions} from '../smokehouse.js';
|
|
19
19
|
|
|
20
|
-
const {cloneDeep} = _;
|
|
21
|
-
|
|
22
20
|
/**
|
|
23
21
|
* @param {Smokehouse.SmokehouseLibOptions} options
|
|
24
22
|
*/
|
|
@@ -16,7 +16,7 @@ import path from 'path';
|
|
|
16
16
|
import fs from 'fs';
|
|
17
17
|
import url from 'url';
|
|
18
18
|
|
|
19
|
-
import
|
|
19
|
+
import cloneDeep from 'lodash/cloneDeep.js';
|
|
20
20
|
import yargs from 'yargs';
|
|
21
21
|
import * as yargsHelpers from 'yargs/helpers';
|
|
22
22
|
import log from 'lighthouse-logger';
|
|
@@ -25,8 +25,6 @@ import {runSmokehouse, getShardedDefinitions} from '../smokehouse.js';
|
|
|
25
25
|
import {updateTestDefnFormat} from './back-compat-util.js';
|
|
26
26
|
import {LH_ROOT} from '../../../../root.js';
|
|
27
27
|
|
|
28
|
-
const {cloneDeep} = _;
|
|
29
|
-
|
|
30
28
|
const coreTestDefnsPath =
|
|
31
29
|
path.join(LH_ROOT, 'lighthouse-cli/test/smokehouse/core-tests.js');
|
|
32
30
|
|
|
@@ -9,14 +9,12 @@
|
|
|
9
9
|
* against the results actually collected from Lighthouse.
|
|
10
10
|
*/
|
|
11
11
|
|
|
12
|
-
import
|
|
12
|
+
import cloneDeep from 'lodash/cloneDeep.js';
|
|
13
13
|
import log from 'lighthouse-logger';
|
|
14
14
|
|
|
15
15
|
import {LocalConsole} from './lib/local-console.js';
|
|
16
16
|
import {chromiumVersionCheck} from './version-check.js';
|
|
17
17
|
|
|
18
|
-
const {cloneDeep} = _;
|
|
19
|
-
|
|
20
18
|
/**
|
|
21
19
|
* @typedef Difference
|
|
22
20
|
* @property {string} path
|
|
@@ -7,12 +7,8 @@
|
|
|
7
7
|
|
|
8
8
|
/**
|
|
9
9
|
* @fileoverview Audits a page to determine if it is calling deprecated APIs.
|
|
10
|
-
* This is done by collecting console log messages and filtering them by ones
|
|
11
|
-
* that contain deprecated API warnings sent by Chrome.
|
|
12
10
|
*/
|
|
13
11
|
|
|
14
|
-
// TODO: when M97 is sufficiently old, drop support for console messages
|
|
15
|
-
|
|
16
12
|
const Audit = require('./audit.js');
|
|
17
13
|
const JsBundles = require('../computed/js-bundles.js');
|
|
18
14
|
const i18n = require('../lib/i18n/i18n.js');
|
|
@@ -48,7 +44,7 @@ class Deprecations extends Audit {
|
|
|
48
44
|
title: str_(UIStrings.title),
|
|
49
45
|
failureTitle: str_(UIStrings.failureTitle),
|
|
50
46
|
description: str_(UIStrings.description),
|
|
51
|
-
requiredArtifacts: ['
|
|
47
|
+
requiredArtifacts: ['InspectorIssues', 'SourceMaps', 'Scripts'],
|
|
52
48
|
};
|
|
53
49
|
}
|
|
54
50
|
|
|
@@ -58,36 +54,21 @@ class Deprecations extends Audit {
|
|
|
58
54
|
* @return {Promise<LH.Audit.Product>}
|
|
59
55
|
*/
|
|
60
56
|
static async audit(artifacts, context) {
|
|
61
|
-
const entries = artifacts.ConsoleMessages;
|
|
62
57
|
const bundles = await JsBundles.request(artifacts, context);
|
|
63
58
|
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
.
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
source: Audit.makeSourceLocation(url, lineNumber, columnNumber - 1, bundle),
|
|
78
|
-
};
|
|
79
|
-
});
|
|
80
|
-
} else {
|
|
81
|
-
// Backcompat for <M97.
|
|
82
|
-
// https://bugs.chromium.org/p/chromium/issues/detail?id=1248484
|
|
83
|
-
deprecations = entries.filter(log => log.source === 'deprecation')
|
|
84
|
-
.map(log => {
|
|
85
|
-
return {
|
|
86
|
-
value: log.text,
|
|
87
|
-
source: Audit.makeSourceLocationFromConsoleMessage(log),
|
|
88
|
-
};
|
|
89
|
-
});
|
|
90
|
-
}
|
|
59
|
+
const deprecations = artifacts.InspectorIssues.deprecationIssue
|
|
60
|
+
// TODO: translate these strings.
|
|
61
|
+
// see https://github.com/GoogleChrome/lighthouse/issues/13895
|
|
62
|
+
.filter(deprecation => !deprecation.type || deprecation.type === 'Untranslated')
|
|
63
|
+
.map(deprecation => {
|
|
64
|
+
const {scriptId, url, lineNumber, columnNumber} = deprecation.sourceCodeLocation;
|
|
65
|
+
const bundle = bundles.find(bundle => bundle.script.scriptId === scriptId);
|
|
66
|
+
return {
|
|
67
|
+
value: deprecation.message || '',
|
|
68
|
+
// Protocol.Audits.SourceCodeLocation.columnNumber is 1-indexed, but we use 0-indexed.
|
|
69
|
+
source: Audit.makeSourceLocation(url, lineNumber, columnNumber - 1, bundle),
|
|
70
|
+
};
|
|
71
|
+
});
|
|
91
72
|
|
|
92
73
|
/** @type {LH.Audit.Details.Table['headings']} */
|
|
93
74
|
const headings = [
|
|
@@ -19,12 +19,14 @@ const UIStrings = {
|
|
|
19
19
|
'[Learn more](https://web.dev/doctype/).',
|
|
20
20
|
/** Explanatory message stating that the document has no doctype. */
|
|
21
21
|
explanationNoDoctype: 'Document must contain a doctype',
|
|
22
|
+
/** Explanatory message stating that the document has wrong doctype */
|
|
23
|
+
explanationWrongDoctype: 'Document contains a doctype that triggers quirks-mode',
|
|
22
24
|
/** Explanatory message stating that the publicId field is not empty. */
|
|
23
25
|
explanationPublicId: 'Expected publicId to be an empty string',
|
|
24
26
|
/** Explanatory message stating that the systemId field is not empty. */
|
|
25
27
|
explanationSystemId: 'Expected systemId to be an empty string',
|
|
26
28
|
/** Explanatory message stating that the doctype is set, but is not "html" and is therefore invalid. */
|
|
27
|
-
explanationBadDoctype: 'Doctype name must be the
|
|
29
|
+
explanationBadDoctype: 'Doctype name must be the string `html`',
|
|
28
30
|
};
|
|
29
31
|
|
|
30
32
|
const str_ = i18n.createMessageInstanceIdFn(__filename, UIStrings);
|
|
@@ -56,9 +58,10 @@ class Doctype extends Audit {
|
|
|
56
58
|
}
|
|
57
59
|
|
|
58
60
|
// only set constants once we know there is a doctype
|
|
59
|
-
const doctypeName = artifacts.Doctype.name
|
|
61
|
+
const doctypeName = artifacts.Doctype.name;
|
|
60
62
|
const doctypePublicId = artifacts.Doctype.publicId;
|
|
61
63
|
const doctypeSystemId = artifacts.Doctype.systemId;
|
|
64
|
+
const compatMode = artifacts.Doctype.documentCompatMode;
|
|
62
65
|
|
|
63
66
|
if (doctypePublicId !== '') {
|
|
64
67
|
return {
|
|
@@ -74,17 +77,25 @@ class Doctype extends Audit {
|
|
|
74
77
|
};
|
|
75
78
|
}
|
|
76
79
|
|
|
77
|
-
/* Note that the
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
if (doctypeName === 'html') {
|
|
80
|
+
/* Note that the casing of this property is normalized to be lowercase.
|
|
81
|
+
see: https://html.spec.whatwg.org/#doctype-name-state */
|
|
82
|
+
if (doctypeName !== 'html') {
|
|
81
83
|
return {
|
|
82
|
-
score:
|
|
84
|
+
score: 0,
|
|
85
|
+
explanation: str_(UIStrings.explanationBadDoctype),
|
|
83
86
|
};
|
|
84
|
-
}
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
// Catch-all for any quirks-mode situations the above checks didn't get.
|
|
90
|
+
// https://github.com/GoogleChrome/lighthouse/issues/10030
|
|
91
|
+
if (compatMode === 'BackCompat') {
|
|
85
92
|
return {
|
|
86
93
|
score: 0,
|
|
87
|
-
explanation: str_(UIStrings.
|
|
94
|
+
explanation: str_(UIStrings.explanationWrongDoctype),
|
|
95
|
+
};
|
|
96
|
+
} else {
|
|
97
|
+
return {
|
|
98
|
+
score: 1,
|
|
88
99
|
};
|
|
89
100
|
}
|
|
90
101
|
}
|
|
@@ -0,0 +1,84 @@
|
|
|
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
|
+
'use strict';
|
|
7
|
+
|
|
8
|
+
const Audit = require('../audit.js');
|
|
9
|
+
const ComputedResponsivenes = require('../../computed/metrics/responsiveness.js');
|
|
10
|
+
const i18n = require('../../lib/i18n/i18n.js');
|
|
11
|
+
|
|
12
|
+
const UIStrings = {
|
|
13
|
+
/** Description of the Interaction to Next Paint metric. This description is displayed within a tooltip when the user hovers on the metric name to see more. No character length limits. 'Learn More' becomes link text to additional documentation. */
|
|
14
|
+
description: 'Interaction to Next Paint measures page responsiveness, how long it ' +
|
|
15
|
+
'takes the page to visibly respond to user input. [Learn more](https://web.dev/inp/).',
|
|
16
|
+
};
|
|
17
|
+
|
|
18
|
+
const str_ = i18n.createMessageInstanceIdFn(__filename, UIStrings);
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* @fileoverview This metric gives a high-percentile measure of responsiveness to input.
|
|
22
|
+
*/
|
|
23
|
+
class ExperimentalInteractionToNextPaint extends Audit {
|
|
24
|
+
/**
|
|
25
|
+
* @return {LH.Audit.Meta}
|
|
26
|
+
*/
|
|
27
|
+
static get meta() {
|
|
28
|
+
return {
|
|
29
|
+
id: 'experimental-interaction-to-next-paint',
|
|
30
|
+
title: str_(i18n.UIStrings.interactionToNextPaint),
|
|
31
|
+
description: str_(UIStrings.description),
|
|
32
|
+
scoreDisplayMode: Audit.SCORING_MODES.NUMERIC,
|
|
33
|
+
supportedModes: ['timespan'],
|
|
34
|
+
requiredArtifacts: ['traces'],
|
|
35
|
+
};
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* @return {LH.Audit.ScoreOptions}
|
|
40
|
+
*/
|
|
41
|
+
static get defaultOptions() {
|
|
42
|
+
return {
|
|
43
|
+
// https://web.dev/inp/
|
|
44
|
+
// This is using the same threshold as field tools since only supported in
|
|
45
|
+
// unsimulated user flows for now.
|
|
46
|
+
// see https://www.desmos.com/calculator/4xtrhg51th
|
|
47
|
+
p10: 200,
|
|
48
|
+
median: 500,
|
|
49
|
+
};
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
/**
|
|
53
|
+
* @param {LH.Artifacts} artifacts
|
|
54
|
+
* @param {LH.Audit.Context} context
|
|
55
|
+
* @return {Promise<LH.Audit.Product>}
|
|
56
|
+
*/
|
|
57
|
+
static async audit(artifacts, context) {
|
|
58
|
+
const {settings} = context;
|
|
59
|
+
// TODO: responsiveness isn't yet supported by lantern.
|
|
60
|
+
if (settings.throttlingMethod === 'simulate') {
|
|
61
|
+
return {score: null, notApplicable: true};
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
const trace = artifacts.traces[Audit.DEFAULT_PASS];
|
|
65
|
+
const metricData = {trace, settings};
|
|
66
|
+
const metricResult = await ComputedResponsivenes.request(metricData, context);
|
|
67
|
+
|
|
68
|
+
// TODO: include the no-interaction state in the report instead of using n/a.
|
|
69
|
+
if (metricResult === null) {
|
|
70
|
+
return {score: null, notApplicable: true};
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
return {
|
|
74
|
+
score: Audit.computeLogNormalScore({p10: context.options.p10, median: context.options.median},
|
|
75
|
+
metricResult.timing),
|
|
76
|
+
numericValue: metricResult.timing,
|
|
77
|
+
numericUnit: 'millisecond',
|
|
78
|
+
displayValue: str_(i18n.UIStrings.ms, {timeInMs: metricResult.timing}),
|
|
79
|
+
};
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
module.exports = ExperimentalInteractionToNextPaint;
|
|
84
|
+
module.exports.UIStrings = UIStrings;
|
|
@@ -0,0 +1,64 @@
|
|
|
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
|
+
'use strict';
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* @fileoverview Returns a high-percentle (usually 98th) measure of how long it
|
|
10
|
+
* takes the page to visibly respond to user input (or null, if there was no
|
|
11
|
+
* user input in the provided trace).
|
|
12
|
+
*/
|
|
13
|
+
|
|
14
|
+
const makeComputedArtifact = require('../computed-artifact.js');
|
|
15
|
+
const ProcessedTrace = require('../processed-trace.js');
|
|
16
|
+
|
|
17
|
+
class Responsiveness {
|
|
18
|
+
/**
|
|
19
|
+
* @param {LH.Artifacts.ProcessedTrace} processedTrace
|
|
20
|
+
* @return {{timing: number}|null}
|
|
21
|
+
*/
|
|
22
|
+
static getHighPercentileResponsiveness(processedTrace) {
|
|
23
|
+
const durations = processedTrace.frameTreeEvents
|
|
24
|
+
// https://source.chromium.org/chromium/chromium/src/+/main:third_party/blink/renderer/core/timing/responsiveness_metrics.cc;l=146-150;drc=a1a2302f30b0a58f7669a41c80acdf1fa11958dd
|
|
25
|
+
.filter(e => e.name === 'Responsiveness.Renderer.UserInteraction')
|
|
26
|
+
.map(evt => evt.args.data?.maxDuration)
|
|
27
|
+
.filter(/** @return {duration is number} */duration => duration !== undefined)
|
|
28
|
+
.sort((a, b) => b - a);
|
|
29
|
+
|
|
30
|
+
// If there were no interactions with the page, the metric is N/A.
|
|
31
|
+
if (durations.length === 0) {
|
|
32
|
+
return null;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
// INP is the "nearest-rank"/inverted_cdf 98th percentile, except Chrome only
|
|
36
|
+
// keeps the 10 worst events around, so it can never be more than the 10th from
|
|
37
|
+
// last array element. To keep things simpler, sort desc and pick from front.
|
|
38
|
+
// See https://source.chromium.org/chromium/chromium/src/+/main:components/page_load_metrics/browser/responsiveness_metrics_normalization.cc;l=45-59;drc=cb0f9c8b559d9c7c3cb4ca94fc1118cc015d38ad
|
|
39
|
+
const index = Math.min(9, Math.floor(durations.length / 50));
|
|
40
|
+
|
|
41
|
+
return {
|
|
42
|
+
timing: durations[index],
|
|
43
|
+
};
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
/**
|
|
47
|
+
* @param {{trace: LH.Trace, settings: Immutable<LH.Config.Settings>}} data
|
|
48
|
+
* @param {LH.Artifacts.ComputedContext} context
|
|
49
|
+
* @return {Promise<LH.Artifacts.Metric|null>}
|
|
50
|
+
*/
|
|
51
|
+
static async compute_(data, context) {
|
|
52
|
+
if (data.settings.throttlingMethod === 'simulate') {
|
|
53
|
+
throw new Error('Responsiveness currently unsupported by simulated throttling');
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
const processedTrace = await ProcessedTrace.request(data.trace, context);
|
|
57
|
+
return Responsiveness.getHighPercentileResponsiveness(processedTrace);
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
module.exports = makeComputedArtifact(Responsiveness, [
|
|
62
|
+
'trace',
|
|
63
|
+
'settings',
|
|
64
|
+
]);
|
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
'use strict';
|
|
7
7
|
|
|
8
8
|
const path = require('path');
|
|
9
|
-
const
|
|
9
|
+
const isDeepEqual = require('lodash/isEqual.js');
|
|
10
10
|
const constants = require('./constants.js');
|
|
11
11
|
const Budget = require('./budget.js');
|
|
12
12
|
const ConfigPlugin = require('./config-plugin.js');
|
|
@@ -11,12 +11,14 @@ const {deepClone} = require('../../config/config-helpers.js');
|
|
|
11
11
|
/** @type {LH.Config.AuditJson[]} */
|
|
12
12
|
const frAudits = [
|
|
13
13
|
'byte-efficiency/uses-responsive-images-snapshot',
|
|
14
|
+
'metrics/experimental-interaction-to-next-paint',
|
|
14
15
|
];
|
|
15
16
|
|
|
16
17
|
/** @type {Record<string, LH.Config.AuditRefJson[]>} */
|
|
17
18
|
const frCategoryAuditRefExtensions = {
|
|
18
19
|
'performance': [
|
|
19
20
|
{id: 'uses-responsive-images-snapshot', weight: 0},
|
|
21
|
+
{id: 'experimental-interaction-to-next-paint', weight: 0, group: 'metrics', acronym: 'INP'},
|
|
20
22
|
],
|
|
21
23
|
};
|
|
22
24
|
|
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
'use strict';
|
|
7
7
|
|
|
8
8
|
const log = require('lighthouse-logger');
|
|
9
|
-
const
|
|
9
|
+
const isDeepEqual = require('lodash/isEqual.js');
|
|
10
10
|
const {
|
|
11
11
|
getBrowserVersion,
|
|
12
12
|
getBenchmarkIndex,
|
|
@@ -61,7 +61,7 @@ function deduplicateWarnings(warnings) {
|
|
|
61
61
|
const unique = [];
|
|
62
62
|
|
|
63
63
|
for (const warning of warnings) {
|
|
64
|
-
if (unique.some(existing =>
|
|
64
|
+
if (unique.some(existing => isDeepEqual(warning, existing))) continue;
|
|
65
65
|
unique.push(warning);
|
|
66
66
|
}
|
|
67
67
|
|
|
@@ -12,9 +12,6 @@ const axeLibSource = require('../../lib/axe.js').source;
|
|
|
12
12
|
const pageFunctions = require('../../lib/page-functions.js');
|
|
13
13
|
|
|
14
14
|
/**
|
|
15
|
-
* This is run in the page, not Lighthouse itself.
|
|
16
|
-
* axe.run returns a promise which fulfills with a results object
|
|
17
|
-
* containing any violations.
|
|
18
15
|
* @return {Promise<LH.Artifacts.Accessibility>}
|
|
19
16
|
*/
|
|
20
17
|
/* c8 ignore start */
|
|
@@ -38,8 +35,11 @@ async function runA11yChecks() {
|
|
|
38
35
|
'wcag2aa',
|
|
39
36
|
],
|
|
40
37
|
},
|
|
38
|
+
// resultTypes doesn't limit the output of the axeResults object. Instead, if it's defined,
|
|
39
|
+
// some expensive element identification is done only for the respective types. https://github.com/dequelabs/axe-core/blob/f62f0cf18f7b69b247b0b6362cf1ae71ffbf3a1b/lib/core/reporters/helpers/process-aggregate.js#L61-L97
|
|
41
40
|
resultTypes: ['violations', 'inapplicable'],
|
|
42
41
|
rules: {
|
|
42
|
+
// Consider http://go/prcpg for expert review of the aXe rules.
|
|
43
43
|
'tabindex': {enabled: true},
|
|
44
44
|
'accesskeys': {enabled: true},
|
|
45
45
|
'heading-order': {enabled: true},
|
|
@@ -60,6 +60,12 @@ async function runA11yChecks() {
|
|
|
60
60
|
// https://github.com/dequelabs/axe-core/issues/2958
|
|
61
61
|
'nested-interactive': {enabled: false},
|
|
62
62
|
'frame-focusable-content': {enabled: false},
|
|
63
|
+
'aria-roledescription': {enabled: false},
|
|
64
|
+
'scrollable-region-focusable': {enabled: false},
|
|
65
|
+
// TODO(paulirish): create audits and enable these 3.
|
|
66
|
+
'input-button-name': {enabled: false},
|
|
67
|
+
'role-img-alt': {enabled: false},
|
|
68
|
+
'select-name': {enabled: false},
|
|
63
69
|
},
|
|
64
70
|
});
|
|
65
71
|
|
|
@@ -70,7 +76,8 @@ async function runA11yChecks() {
|
|
|
70
76
|
return {
|
|
71
77
|
violations: axeResults.violations.map(createAxeRuleResultArtifact),
|
|
72
78
|
incomplete: axeResults.incomplete.map(createAxeRuleResultArtifact),
|
|
73
|
-
notApplicable: axeResults.inapplicable.map(result => ({id: result.id})),
|
|
79
|
+
notApplicable: axeResults.inapplicable.map(result => ({id: result.id})), // FYI: inapplicable => notApplicable!
|
|
80
|
+
passes: axeResults.passes.map(result => ({id: result.id})),
|
|
74
81
|
version: axeResults.testEngine.version,
|
|
75
82
|
};
|
|
76
83
|
}
|
|
@@ -150,6 +157,11 @@ class Accessibility extends FRGatherer {
|
|
|
150
157
|
supportedModes: ['snapshot', 'navigation'],
|
|
151
158
|
};
|
|
152
159
|
|
|
160
|
+
static pageFns = {
|
|
161
|
+
runA11yChecks,
|
|
162
|
+
createAxeRuleResultArtifact,
|
|
163
|
+
};
|
|
164
|
+
|
|
153
165
|
/**
|
|
154
166
|
* @param {LH.Gatherer.FRTransitionalContext} passContext
|
|
155
167
|
* @return {Promise<LH.Artifacts.Accessibility>}
|
|
@@ -12,7 +12,8 @@ const FRGatherer = require('../../../fraggle-rock/gather/base-gatherer.js');
|
|
|
12
12
|
/**
|
|
13
13
|
* Get and return `name`, `publicId`, `systemId` from
|
|
14
14
|
* `document.doctype`
|
|
15
|
-
*
|
|
15
|
+
* and `compatMode` from `document` to check `quirks-mode`
|
|
16
|
+
* @return {{name: string, publicId: string, systemId: string, documentCompatMode: string} | null}
|
|
16
17
|
*/
|
|
17
18
|
function getDoctype() {
|
|
18
19
|
// An example of this is warnerbros.com/archive/spacejam/movie/jam.htm
|
|
@@ -20,8 +21,9 @@ function getDoctype() {
|
|
|
20
21
|
return null;
|
|
21
22
|
}
|
|
22
23
|
|
|
24
|
+
const documentCompatMode = document.compatMode;
|
|
23
25
|
const {name, publicId, systemId} = document.doctype;
|
|
24
|
-
return {name, publicId, systemId};
|
|
26
|
+
return {name, publicId, systemId, documentCompatMode};
|
|
25
27
|
}
|
|
26
28
|
|
|
27
29
|
class Doctype extends FRGatherer {
|
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
*/
|
|
6
6
|
'use strict';
|
|
7
7
|
|
|
8
|
-
const
|
|
8
|
+
const isDeepEqual = require('lodash/isEqual.js');
|
|
9
9
|
|
|
10
10
|
/**
|
|
11
11
|
* @fileoverview This class is designed to allow maps with arbitrary equality functions.
|
|
@@ -74,7 +74,7 @@ class ArbitraryEqualityMap {
|
|
|
74
74
|
* @return {boolean}
|
|
75
75
|
*/
|
|
76
76
|
static deepEquals(objA, objB) {
|
|
77
|
-
return
|
|
77
|
+
return isDeepEqual(objA, objB);
|
|
78
78
|
}
|
|
79
79
|
}
|
|
80
80
|
|
|
@@ -105,6 +105,8 @@ const UIStrings = {
|
|
|
105
105
|
largestContentfulPaintMetric: 'Largest Contentful Paint',
|
|
106
106
|
/** The name of the metric "Cumulative Layout Shift" that indicates how much the page changes its layout while it loads. If big segments of the page shift their location during load, the Cumulative Layout Shift will be higher. Shown to users as the label for the numeric metric value. Ideally fits within a ~40 character limit. */
|
|
107
107
|
cumulativeLayoutShiftMetric: 'Cumulative Layout Shift',
|
|
108
|
+
/** The name of the "Interaction to Next Paint" metric that measures the time between a user interaction and when the browser displays a response on screen. Shown to users as the label for the numeric metric value. Ideally fits within a ~40 character limit. */
|
|
109
|
+
interactionToNextPaint: 'Interaction to Next Paint',
|
|
108
110
|
/** Table item value for the severity of a small, or low impact vulnerability. Part of a ranking scale in the form: low, medium, high. */
|
|
109
111
|
itemSeverityLow: 'Low',
|
|
110
112
|
/** Table item value for the severity of a vulnerability. Part of a ranking scale in the form: low, medium, high. */
|
|
@@ -44,6 +44,8 @@ const traceEventsToAlwaysKeep = new Set([
|
|
|
44
44
|
'EventDispatch',
|
|
45
45
|
'LayoutShift',
|
|
46
46
|
'FrameCommittedInBrowser',
|
|
47
|
+
'EventTiming',
|
|
48
|
+
'Responsiveness.Renderer.UserInteraction',
|
|
47
49
|
// Not currently used by Lighthouse but might be used in the future for cross-frame LCP
|
|
48
50
|
'NavStartToLargestContentfulPaint::Invalidate::AllFrames::UKM',
|
|
49
51
|
'NavStartToLargestContentfulPaint::Candidate::AllFrames::UKM',
|
|
@@ -612,39 +612,59 @@ class TraceProcessor {
|
|
|
612
612
|
// Find the inspected frame
|
|
613
613
|
const mainFrameIds = this.findMainFrameIds(keyEvents);
|
|
614
614
|
|
|
615
|
-
|
|
615
|
+
/** @type {Map<string, {id: string, url: string, parent?: string}>} */
|
|
616
|
+
const framesById = new Map();
|
|
617
|
+
|
|
618
|
+
// Begin collection of frame tree information with TracingStartedInBrowser,
|
|
619
|
+
// which should be present even without navigations.
|
|
620
|
+
const tracingStartedFrames = keyEvents
|
|
621
|
+
.find(e => e.name === 'TracingStartedInBrowser')?.args?.data?.frames;
|
|
622
|
+
if (tracingStartedFrames) {
|
|
623
|
+
for (const frame of tracingStartedFrames) {
|
|
624
|
+
framesById.set(frame.frame, {
|
|
625
|
+
id: frame.frame,
|
|
626
|
+
url: frame.url,
|
|
627
|
+
parent: frame.parent,
|
|
628
|
+
});
|
|
629
|
+
}
|
|
630
|
+
}
|
|
631
|
+
|
|
632
|
+
// Update known frames if FrameCommittedInBrowser events come in, typically
|
|
633
|
+
// with updated `url`, as well as pid, etc. Some traces (like timespans) may
|
|
634
|
+
// not have any committed frames.
|
|
635
|
+
keyEvents
|
|
616
636
|
.filter(/** @return {evt is FrameCommittedEvent} */ evt => {
|
|
617
637
|
return Boolean(
|
|
618
638
|
evt.name === 'FrameCommittedInBrowser' &&
|
|
619
639
|
evt.args.data?.frame &&
|
|
620
640
|
evt.args.data.url
|
|
621
641
|
);
|
|
622
|
-
})
|
|
623
|
-
|
|
624
|
-
return {
|
|
642
|
+
}).forEach(evt => {
|
|
643
|
+
framesById.set(evt.args.data.frame, {
|
|
625
644
|
id: evt.args.data.frame,
|
|
626
645
|
url: evt.args.data.url,
|
|
627
646
|
parent: evt.args.data.parent,
|
|
628
|
-
};
|
|
647
|
+
});
|
|
629
648
|
});
|
|
649
|
+
const frames = [...framesById.values()];
|
|
630
650
|
const frameIdToRootFrameId = this.resolveRootFrames(frames);
|
|
631
651
|
|
|
632
652
|
// Filter to just events matching the main frame ID, just to make sure.
|
|
633
653
|
const frameEvents = keyEvents.filter(e => e.args.frame === mainFrameIds.frameId);
|
|
634
654
|
|
|
635
655
|
// Filter to just events matching the main frame ID or any child frame IDs.
|
|
636
|
-
// In practice, there should always be FrameCommittedInBrowser events to define the frame tree.
|
|
637
|
-
// Unfortunately, many test traces do not include FrameCommittedInBrowser events due to minification.
|
|
638
|
-
// This ensures there is always a minimal frame tree and events so those tests don't fail.
|
|
639
656
|
let frameTreeEvents = [];
|
|
640
657
|
if (frameIdToRootFrameId.has(mainFrameIds.frameId)) {
|
|
641
658
|
frameTreeEvents = keyEvents.filter(e => {
|
|
642
659
|
return e.args.frame && frameIdToRootFrameId.get(e.args.frame) === mainFrameIds.frameId;
|
|
643
660
|
});
|
|
644
661
|
} else {
|
|
662
|
+
// In practice, there should always be TracingStartedInBrowser/FrameCommittedInBrowser events to
|
|
663
|
+
// define the frame tree. Unfortunately, many test traces do not that frame info due to minification.
|
|
664
|
+
// This ensures there is always a minimal frame tree and events so those tests don't fail.
|
|
645
665
|
log.warn(
|
|
646
666
|
'trace-of-tab',
|
|
647
|
-
'frameTreeEvents may be incomplete, make sure the trace has
|
|
667
|
+
'frameTreeEvents may be incomplete, make sure the trace has frame events'
|
|
648
668
|
);
|
|
649
669
|
frameIdToRootFrameId.set(mainFrameIds.frameId, mainFrameIds.frameId);
|
|
650
670
|
frameTreeEvents = frameEvents;
|
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
*/
|
|
6
6
|
'use strict';
|
|
7
7
|
|
|
8
|
-
const
|
|
8
|
+
const isDeepEqual = require('lodash/isEqual.js');
|
|
9
9
|
const Driver = require('./gather/driver.js');
|
|
10
10
|
const GatherRunner = require('./gather/gather-runner.js');
|
|
11
11
|
const ReportScoring = require('./scoring.js');
|
|
@@ -433,11 +433,9 @@ class Util {
|
|
|
433
433
|
break;
|
|
434
434
|
case 'devtools': {
|
|
435
435
|
const {cpuSlowdownMultiplier, requestLatencyMs} = throttling;
|
|
436
|
-
// TODO: better api in i18n formatter such that this isn't needed.
|
|
437
|
-
const cpuGranularity = Number.isInteger(cpuSlowdownMultiplier) ? 1 : 0.1;
|
|
438
436
|
// eslint-disable-next-line max-len
|
|
439
|
-
cpuThrottling = `${Util.i18n.formatNumber(cpuSlowdownMultiplier
|
|
440
|
-
networkThrottling = `${Util.i18n.formatMilliseconds(requestLatencyMs
|
|
437
|
+
cpuThrottling = `${Util.i18n.formatNumber(cpuSlowdownMultiplier)}x slowdown (DevTools)`;
|
|
438
|
+
networkThrottling = `${Util.i18n.formatMilliseconds(requestLatencyMs)} HTTP RTT, ` +
|
|
441
439
|
`${Util.i18n.formatKbps(throttling.downloadThroughputKbps)} down, ` +
|
|
442
440
|
`${Util.i18n.formatKbps(throttling.uploadThroughputKbps)} up (DevTools)`;
|
|
443
441
|
|
|
@@ -451,10 +449,8 @@ class Util {
|
|
|
451
449
|
}
|
|
452
450
|
case 'simulate': {
|
|
453
451
|
const {cpuSlowdownMultiplier, rttMs, throughputKbps} = throttling;
|
|
454
|
-
// TODO: better api in i18n formatter such that this isn't needed.
|
|
455
|
-
const cpuGranularity = Number.isInteger(cpuSlowdownMultiplier) ? 1 : 0.1;
|
|
456
452
|
// eslint-disable-next-line max-len
|
|
457
|
-
cpuThrottling = `${Util.i18n.formatNumber(cpuSlowdownMultiplier
|
|
453
|
+
cpuThrottling = `${Util.i18n.formatNumber(cpuSlowdownMultiplier)}x slowdown (Simulated)`;
|
|
458
454
|
networkThrottling = `${Util.i18n.formatMilliseconds(rttMs)} TCP RTT, ` +
|
|
459
455
|
`${Util.i18n.formatKbps(throughputKbps)} throughput (Simulated)`;
|
|
460
456
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "lighthouse",
|
|
3
|
-
"version": "9.5.0-dev.
|
|
3
|
+
"version": "9.5.0-dev.20220427",
|
|
4
4
|
"description": "Automated auditing, performance metrics, and best practices for the web.",
|
|
5
5
|
"main": "./lighthouse-core/index.js",
|
|
6
6
|
"bin": {
|
|
@@ -139,7 +139,7 @@
|
|
|
139
139
|
"cpy": "^8.1.2",
|
|
140
140
|
"cross-env": "^7.0.2",
|
|
141
141
|
"csv-validator": "^0.0.3",
|
|
142
|
-
"devtools-protocol": "0.0.
|
|
142
|
+
"devtools-protocol": "0.0.995287",
|
|
143
143
|
"es-main": "^1.0.2",
|
|
144
144
|
"eslint": "^8.4.1",
|
|
145
145
|
"eslint-config-google": "^0.14.0",
|
|
@@ -210,8 +210,8 @@
|
|
|
210
210
|
"yargs-parser": "^21.0.0"
|
|
211
211
|
},
|
|
212
212
|
"resolutions": {
|
|
213
|
-
"puppeteer/**/devtools-protocol": "0.0.
|
|
214
|
-
"puppeteer-core/**/devtools-protocol": "0.0.
|
|
213
|
+
"puppeteer/**/devtools-protocol": "0.0.995287",
|
|
214
|
+
"puppeteer-core/**/devtools-protocol": "0.0.995287"
|
|
215
215
|
},
|
|
216
216
|
"repository": "GoogleChrome/lighthouse",
|
|
217
217
|
"keywords": [
|
package/readme.md
CHANGED
|
@@ -99,7 +99,7 @@ Configuration:
|
|
|
99
99
|
--screenEmulation Sets screen emulation parameters. See also --preset. Use --screenEmulation.disabled to disable. Otherwise set these 4 parameters individually: --screenEmulation.mobile --screenEmulation.width=360 --screenEmulation.height=640 --screenEmulation.deviceScaleFactor=2
|
|
100
100
|
--emulatedUserAgent Sets useragent emulation [string]
|
|
101
101
|
--max-wait-for-load The timeout (in milliseconds) to wait before the page is considered done loading and the run should continue. WARNING: Very high values can lead to large traces and instability [number]
|
|
102
|
-
--enable-error-reporting Enables error reporting, overriding any saved preference. --no-enable-error-reporting will do the opposite. More: https://
|
|
102
|
+
--enable-error-reporting Enables error reporting, overriding any saved preference. --no-enable-error-reporting will do the opposite. More: https://github.com/GoogleChrome/lighthouse/blob/master/docs/error-reporting.md [boolean]
|
|
103
103
|
--gather-mode, -G Collect artifacts from a connected browser and save to disk. (Artifacts folder path may optionally be provided). If audit-mode is not also enabled, the run will quit early.
|
|
104
104
|
--audit-mode, -A Process saved artifacts from disk. (Artifacts folder path may be provided, otherwise defaults to ./latest-run/)
|
|
105
105
|
--only-audits Only run the specified audits [array]
|