lighthouse 9.5.0-dev.20230130 → 9.5.0-dev.20230201
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/core/audits/byte-efficiency/offscreen-images.js +1 -1
- package/core/audits/critical-request-chains.d.ts +6 -6
- package/core/audits/critical-request-chains.js +15 -15
- package/core/audits/dobetterweb/doctype.js +4 -4
- package/core/audits/dobetterweb/{password-inputs-can-be-pasted-into.d.ts → paste-preventing-inputs.d.ts} +3 -3
- package/core/audits/dobetterweb/{password-inputs-can-be-pasted-into.js → paste-preventing-inputs.js} +17 -15
- package/core/audits/font-display.js +1 -1
- package/core/audits/network-requests.js +4 -4
- package/core/audits/redirects.js +2 -1
- package/core/audits/uses-rel-preconnect.js +5 -4
- package/core/config/default-config.js +2 -4
- package/core/gather/driver/network-monitor.js +2 -2
- package/core/gather/gatherers/dobetterweb/tags-blocking-first-paint.js +4 -4
- package/core/gather/gatherers/inputs.js +6 -0
- package/core/legacy/config/legacy-default-config.js +0 -1
- package/core/lib/bf-cache-strings.js +1 -1
- package/core/lib/dependency-graph/network-node.js +2 -2
- package/core/lib/dependency-graph/simulator/network-analyzer.js +8 -6
- package/core/lib/lighthouse-compatibility.d.ts +8 -0
- package/core/lib/lighthouse-compatibility.js +139 -0
- package/core/lib/navigation-error.js +3 -1
- package/core/lib/network-recorder.js +1 -1
- package/core/lib/network-request.d.ts +7 -7
- package/core/lib/network-request.js +26 -27
- package/core/runner.d.ts +1 -6
- package/core/runner.js +9 -20
- package/dist/report/bundle.esm.js +145 -129
- package/dist/report/flow.js +19 -13
- package/dist/report/standalone.js +10 -4
- package/package.json +1 -1
- package/report/assets/styles.css +1 -1
- package/report/renderer/components.js +1 -1
- package/report/renderer/i18n-formatter.js +1 -1
- package/report/renderer/report-ui-features.js +1 -1
- package/report/renderer/report-utils.d.ts +4 -4
- package/report/renderer/report-utils.js +13 -130
- package/report/test/renderer/report-ui-features-test.js +20 -31
- package/report/test/renderer/report-utils-test.js +0 -170
- package/report/tsconfig.json +1 -0
- package/shared/localization/locales/ar-XB.json +0 -9
- package/shared/localization/locales/ar.json +0 -9
- package/shared/localization/locales/bg.json +0 -9
- package/shared/localization/locales/ca.json +0 -9
- package/shared/localization/locales/cs.json +0 -9
- package/shared/localization/locales/da.json +0 -9
- package/shared/localization/locales/de.json +0 -9
- package/shared/localization/locales/el.json +0 -9
- package/shared/localization/locales/en-GB.json +0 -9
- package/shared/localization/locales/en-US.json +9 -9
- package/shared/localization/locales/en-XA.json +0 -9
- package/shared/localization/locales/en-XL.json +9 -9
- package/shared/localization/locales/es-419.json +0 -9
- package/shared/localization/locales/es.json +0 -9
- package/shared/localization/locales/fi.json +0 -9
- package/shared/localization/locales/fil.json +0 -9
- package/shared/localization/locales/fr.json +0 -9
- package/shared/localization/locales/he.json +0 -9
- package/shared/localization/locales/hi.json +0 -9
- package/shared/localization/locales/hr.json +0 -9
- package/shared/localization/locales/hu.json +0 -9
- package/shared/localization/locales/id.json +0 -9
- package/shared/localization/locales/it.json +0 -9
- package/shared/localization/locales/ja.json +0 -9
- package/shared/localization/locales/ko.json +0 -9
- package/shared/localization/locales/lt.json +0 -9
- package/shared/localization/locales/lv.json +0 -9
- package/shared/localization/locales/nl.json +0 -9
- package/shared/localization/locales/no.json +0 -9
- package/shared/localization/locales/pl.json +0 -9
- package/shared/localization/locales/pt-PT.json +0 -9
- package/shared/localization/locales/pt.json +0 -9
- package/shared/localization/locales/ro.json +0 -9
- package/shared/localization/locales/ru.json +0 -9
- package/shared/localization/locales/sk.json +0 -9
- package/shared/localization/locales/sl.json +0 -9
- package/shared/localization/locales/sr-Latn.json +0 -9
- package/shared/localization/locales/sr.json +0 -9
- package/shared/localization/locales/sv.json +0 -9
- package/shared/localization/locales/ta.json +0 -9
- package/shared/localization/locales/te.json +0 -9
- package/shared/localization/locales/th.json +0 -9
- package/shared/localization/locales/tr.json +0 -9
- package/shared/localization/locales/uk.json +0 -9
- package/shared/localization/locales/vi.json +0 -9
- package/shared/localization/locales/zh-HK.json +0 -9
- package/shared/localization/locales/zh-TW.json +0 -9
- package/shared/localization/locales/zh.json +0 -9
- package/tsconfig.json +1 -1
- package/types/artifacts.d.ts +2 -5
- package/types/lhr/lhr.d.ts +3 -11
- package/core/gather/gatherers/dobetterweb/password-inputs-with-prevented-paste.d.ts +0 -10
- package/core/gather/gatherers/dobetterweb/password-inputs-with-prevented-paste.js +0 -49
|
@@ -102,7 +102,7 @@ class OffscreenImages extends ByteEfficiencyAudit {
|
|
|
102
102
|
return {
|
|
103
103
|
node: ByteEfficiencyAudit.makeNodeItem(image.node),
|
|
104
104
|
url,
|
|
105
|
-
requestStartTime: networkRecord.
|
|
105
|
+
requestStartTime: networkRecord.networkRequestTime,
|
|
106
106
|
totalBytes,
|
|
107
107
|
wastedBytes,
|
|
108
108
|
wastedPercent: 100 * wastedRatio,
|
|
@@ -1,23 +1,23 @@
|
|
|
1
1
|
export default CriticalRequestChains;
|
|
2
2
|
declare class CriticalRequestChains extends Audit {
|
|
3
|
-
/** @typedef {{depth: number, id: string, chainDuration: number, chainTransferSize: number, node: LH.
|
|
3
|
+
/** @typedef {{depth: number, id: string, chainDuration: number, chainTransferSize: number, node: LH.Artifacts.CriticalRequestNode[string]}} CrcNodeInfo */
|
|
4
4
|
/**
|
|
5
|
-
* @param {LH.
|
|
5
|
+
* @param {LH.Artifacts.CriticalRequestNode} tree
|
|
6
6
|
* @param {function(CrcNodeInfo): void} cb
|
|
7
7
|
*/
|
|
8
|
-
static _traverse(tree: LH.
|
|
8
|
+
static _traverse(tree: LH.Artifacts.CriticalRequestNode, cb: (arg0: {
|
|
9
9
|
depth: number;
|
|
10
10
|
id: string;
|
|
11
11
|
chainDuration: number;
|
|
12
12
|
chainTransferSize: number;
|
|
13
|
-
node: LH.
|
|
13
|
+
node: LH.Artifacts.CriticalRequestNode[string];
|
|
14
14
|
}) => void): void;
|
|
15
15
|
/**
|
|
16
16
|
* Get stats about the longest initiator chain (as determined by time duration)
|
|
17
|
-
* @param {LH.
|
|
17
|
+
* @param {LH.Artifacts.CriticalRequestNode} tree
|
|
18
18
|
* @return {{duration: number, length: number, transferSize: number}}
|
|
19
19
|
*/
|
|
20
|
-
static _getLongestChain(tree: LH.
|
|
20
|
+
static _getLongestChain(tree: LH.Artifacts.CriticalRequestNode): {
|
|
21
21
|
duration: number;
|
|
22
22
|
length: number;
|
|
23
23
|
transferSize: number;
|
|
@@ -41,28 +41,28 @@ class CriticalRequestChains extends Audit {
|
|
|
41
41
|
};
|
|
42
42
|
}
|
|
43
43
|
|
|
44
|
-
/** @typedef {{depth: number, id: string, chainDuration: number, chainTransferSize: number, node: LH.
|
|
44
|
+
/** @typedef {{depth: number, id: string, chainDuration: number, chainTransferSize: number, node: LH.Artifacts.CriticalRequestNode[string]}} CrcNodeInfo */
|
|
45
45
|
|
|
46
46
|
/**
|
|
47
|
-
* @param {LH.
|
|
47
|
+
* @param {LH.Artifacts.CriticalRequestNode} tree
|
|
48
48
|
* @param {function(CrcNodeInfo): void} cb
|
|
49
49
|
*/
|
|
50
50
|
static _traverse(tree, cb) {
|
|
51
51
|
/**
|
|
52
|
-
* @param {LH.
|
|
52
|
+
* @param {LH.Artifacts.CriticalRequestNode} node
|
|
53
53
|
* @param {number} depth
|
|
54
|
-
* @param {number=}
|
|
54
|
+
* @param {number=} networkRequestTime
|
|
55
55
|
* @param {number=} transferSize
|
|
56
56
|
*/
|
|
57
|
-
function walk(node, depth,
|
|
57
|
+
function walk(node, depth, networkRequestTime, transferSize = 0) {
|
|
58
58
|
const children = Object.keys(node);
|
|
59
59
|
if (children.length === 0) {
|
|
60
60
|
return;
|
|
61
61
|
}
|
|
62
62
|
children.forEach(id => {
|
|
63
63
|
const child = node[id];
|
|
64
|
-
if (!
|
|
65
|
-
|
|
64
|
+
if (!networkRequestTime) {
|
|
65
|
+
networkRequestTime = child.request.networkRequestTime;
|
|
66
66
|
}
|
|
67
67
|
|
|
68
68
|
// Call the callback with the info for this child.
|
|
@@ -70,13 +70,13 @@ class CriticalRequestChains extends Audit {
|
|
|
70
70
|
depth,
|
|
71
71
|
id,
|
|
72
72
|
node: child,
|
|
73
|
-
chainDuration: child.request.
|
|
73
|
+
chainDuration: child.request.networkEndTime - networkRequestTime,
|
|
74
74
|
chainTransferSize: transferSize + child.request.transferSize,
|
|
75
75
|
});
|
|
76
76
|
|
|
77
77
|
// Carry on walking.
|
|
78
78
|
if (child.children) {
|
|
79
|
-
walk(child.children, depth + 1,
|
|
79
|
+
walk(child.children, depth + 1, networkRequestTime);
|
|
80
80
|
}
|
|
81
81
|
}, '');
|
|
82
82
|
}
|
|
@@ -86,7 +86,7 @@ class CriticalRequestChains extends Audit {
|
|
|
86
86
|
|
|
87
87
|
/**
|
|
88
88
|
* Get stats about the longest initiator chain (as determined by time duration)
|
|
89
|
-
* @param {LH.
|
|
89
|
+
* @param {LH.Artifacts.CriticalRequestNode} tree
|
|
90
90
|
* @return {{duration: number, length: number, transferSize: number}}
|
|
91
91
|
*/
|
|
92
92
|
static _getLongestChain(tree) {
|
|
@@ -96,7 +96,7 @@ class CriticalRequestChains extends Audit {
|
|
|
96
96
|
transferSize: 0,
|
|
97
97
|
};
|
|
98
98
|
CriticalRequestChains._traverse(tree, opts => {
|
|
99
|
-
const duration = opts.chainDuration
|
|
99
|
+
const duration = opts.chainDuration;
|
|
100
100
|
if (duration > longest.duration) {
|
|
101
101
|
longest.duration = duration;
|
|
102
102
|
longest.transferSize = opts.chainTransferSize;
|
|
@@ -123,9 +123,9 @@ class CriticalRequestChains extends Audit {
|
|
|
123
123
|
const request = opts.node.request;
|
|
124
124
|
const simpleRequest = {
|
|
125
125
|
url: request.url,
|
|
126
|
-
startTime: request.
|
|
127
|
-
endTime: request.
|
|
128
|
-
responseReceivedTime: request.
|
|
126
|
+
startTime: request.networkRequestTime / 1000,
|
|
127
|
+
endTime: request.networkEndTime / 1000,
|
|
128
|
+
responseReceivedTime: request.responseHeadersEndTime / 1000,
|
|
129
129
|
transferSize: request.transferSize,
|
|
130
130
|
};
|
|
131
131
|
|
|
@@ -199,7 +199,7 @@ class CriticalRequestChains extends Audit {
|
|
|
199
199
|
walk(initialNavChildren, 0);
|
|
200
200
|
}
|
|
201
201
|
|
|
202
|
-
const longestChain = CriticalRequestChains._getLongestChain(
|
|
202
|
+
const longestChain = CriticalRequestChains._getLongestChain(chains);
|
|
203
203
|
|
|
204
204
|
return {
|
|
205
205
|
score: Number(chainCount === 0),
|
|
@@ -18,10 +18,10 @@ const UIStrings = {
|
|
|
18
18
|
'[Learn more about the doctype declaration](https://developer.chrome.com/docs/lighthouse/best-practices/doctype/).',
|
|
19
19
|
/** Explanatory message stating that the document has no doctype. */
|
|
20
20
|
explanationNoDoctype: 'Document must contain a doctype',
|
|
21
|
-
/** Explanatory message stating that the document has wrong doctype */
|
|
22
|
-
explanationWrongDoctype: 'Document contains a doctype that triggers quirks-mode',
|
|
23
|
-
/** Explanatory message stating that the document has wrong doctype */
|
|
24
|
-
explanationLimitedQuirks: 'Document contains a doctype that triggers limited-quirks-mode',
|
|
21
|
+
/** Explanatory message stating that the document has wrong doctype that triggers `quirks-mode` */
|
|
22
|
+
explanationWrongDoctype: 'Document contains a `doctype` that triggers `quirks-mode`',
|
|
23
|
+
/** Explanatory message stating that the document has wrong doctype that triggers `limited-quirks-mode` */
|
|
24
|
+
explanationLimitedQuirks: 'Document contains a `doctype` that triggers `limited-quirks-mode`',
|
|
25
25
|
/** Explanatory message stating that the publicId field is not empty. */
|
|
26
26
|
explanationPublicId: 'Expected publicId to be an empty string',
|
|
27
27
|
/** Explanatory message stating that the systemId field is not empty. */
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
export default
|
|
2
|
-
declare class
|
|
1
|
+
export default PastePreventingInputsAudit;
|
|
2
|
+
declare class PastePreventingInputsAudit extends Audit {
|
|
3
3
|
/**
|
|
4
4
|
* @param {LH.Artifacts} artifacts
|
|
5
5
|
* @return {LH.Audit.Product}
|
|
@@ -12,4 +12,4 @@ export namespace UIStrings {
|
|
|
12
12
|
const description: string;
|
|
13
13
|
}
|
|
14
14
|
import { Audit } from "../audit.js";
|
|
15
|
-
//# sourceMappingURL=
|
|
15
|
+
//# sourceMappingURL=paste-preventing-inputs.d.ts.map
|
package/core/audits/dobetterweb/{password-inputs-can-be-pasted-into.js → paste-preventing-inputs.js}
RENAMED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* @license Copyright
|
|
2
|
+
* @license Copyright 2023 The Lighthouse Authors. All Rights Reserved.
|
|
3
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
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
5
|
*/
|
|
@@ -8,28 +8,29 @@ import {Audit} from '../audit.js';
|
|
|
8
8
|
import * as i18n from '../../lib/i18n/i18n.js';
|
|
9
9
|
|
|
10
10
|
const UIStrings = {
|
|
11
|
-
/** Title of a Lighthouse audit that provides detail on the ability to paste into
|
|
12
|
-
title: 'Allows users to paste into
|
|
13
|
-
/** Title of a Lighthouse audit that provides detail on the ability to paste into
|
|
14
|
-
failureTitle: 'Prevents users
|
|
15
|
-
/** Description of a Lighthouse audit that tells the user why they should allow pasting of content into
|
|
16
|
-
description: 'Preventing
|
|
17
|
-
'
|
|
11
|
+
/** Title of a Lighthouse audit that provides detail on the ability to paste into input fields. This descriptive title is shown to users when the page allows pasting of content into input fields. */
|
|
12
|
+
title: 'Allows users to paste into input fields',
|
|
13
|
+
/** Title of a Lighthouse audit that provides detail on the ability to paste into input fields. This descriptive title is shown to users when the page does not allow pasting of content into input fields. */
|
|
14
|
+
failureTitle: 'Prevents users from pasting into input fields',
|
|
15
|
+
/** Description of a Lighthouse audit that tells the user why they should allow pasting of content into input fields. This is displayed after a user expands the section to see more. No character length limits. 'Learn More' becomes link text to additional documentation. */
|
|
16
|
+
description: 'Preventing input pasting is a UX anti-pattern, ' +
|
|
17
|
+
'and undermines good security policy. ' +
|
|
18
|
+
'[Learn more about user-friendly input fields](https://developer.chrome.com/docs/lighthouse/best-practices/paste-preventing-inputs/).',
|
|
18
19
|
};
|
|
19
20
|
|
|
20
21
|
const str_ = i18n.createIcuMessageFn(import.meta.url, UIStrings);
|
|
21
22
|
|
|
22
|
-
class
|
|
23
|
+
class PastePreventingInputsAudit extends Audit {
|
|
23
24
|
/**
|
|
24
25
|
* @return {LH.Audit.Meta}
|
|
25
26
|
*/
|
|
26
27
|
static get meta() {
|
|
27
28
|
return {
|
|
28
|
-
id: '
|
|
29
|
+
id: 'paste-preventing-inputs',
|
|
29
30
|
title: str_(UIStrings.title),
|
|
30
31
|
failureTitle: str_(UIStrings.failureTitle),
|
|
31
32
|
description: str_(UIStrings.description),
|
|
32
|
-
requiredArtifacts: ['
|
|
33
|
+
requiredArtifacts: ['Inputs'],
|
|
33
34
|
};
|
|
34
35
|
}
|
|
35
36
|
|
|
@@ -38,14 +39,15 @@ class PasswordInputsCanBePastedIntoAudit extends Audit {
|
|
|
38
39
|
* @return {LH.Audit.Product}
|
|
39
40
|
*/
|
|
40
41
|
static audit(artifacts) {
|
|
41
|
-
const
|
|
42
|
+
const inputsWithPreventsPaste = artifacts.Inputs.inputs.filter(input => input.preventsPaste);
|
|
42
43
|
|
|
43
44
|
/** @type {LH.Audit.Details.Table['items']} */
|
|
44
45
|
const items = [];
|
|
45
46
|
|
|
46
|
-
|
|
47
|
+
inputsWithPreventsPaste.forEach(input => {
|
|
47
48
|
items.push({
|
|
48
49
|
node: Audit.makeNodeItem(input.node),
|
|
50
|
+
type: input.type,
|
|
49
51
|
});
|
|
50
52
|
});
|
|
51
53
|
|
|
@@ -55,11 +57,11 @@ class PasswordInputsCanBePastedIntoAudit extends Audit {
|
|
|
55
57
|
];
|
|
56
58
|
|
|
57
59
|
return {
|
|
58
|
-
score: Number(
|
|
60
|
+
score: Number(inputsWithPreventsPaste.length === 0),
|
|
59
61
|
details: Audit.makeTableDetails(headings, items),
|
|
60
62
|
};
|
|
61
63
|
}
|
|
62
64
|
}
|
|
63
65
|
|
|
64
|
-
export default
|
|
66
|
+
export default PastePreventingInputsAudit;
|
|
65
67
|
export {UIStrings};
|
|
@@ -165,7 +165,7 @@ class FontDisplay extends Audit {
|
|
|
165
165
|
.map(record => {
|
|
166
166
|
// In reality the end time should be calculated with paint time included
|
|
167
167
|
// all browsers wait 3000ms to block text so we make sure 3000 is our max wasted time
|
|
168
|
-
const wastedMs = Math.min(record.
|
|
168
|
+
const wastedMs = Math.min(record.networkEndTime - record.networkRequestTime, 3000);
|
|
169
169
|
|
|
170
170
|
return {
|
|
171
171
|
url: record.url,
|
|
@@ -65,8 +65,8 @@ class NetworkRequests extends Audit {
|
|
|
65
65
|
url: UrlUtils.elideDataURI(record.url),
|
|
66
66
|
protocol: record.protocol,
|
|
67
67
|
rendererStartTime: normalizeTime(record.rendererStartTime),
|
|
68
|
-
|
|
69
|
-
|
|
68
|
+
networkRequestTime: normalizeTime(record.networkRequestTime),
|
|
69
|
+
networkEndTime: normalizeTime(record.networkEndTime),
|
|
70
70
|
finished: record.finished,
|
|
71
71
|
transferSize: record.transferSize,
|
|
72
72
|
resourceSize: record.resourceSize,
|
|
@@ -88,8 +88,8 @@ class NetworkRequests extends Audit {
|
|
|
88
88
|
const headings = [
|
|
89
89
|
{key: 'url', valueType: 'url', label: 'URL'},
|
|
90
90
|
{key: 'protocol', valueType: 'text', label: 'Protocol'},
|
|
91
|
-
{key: '
|
|
92
|
-
{key: '
|
|
91
|
+
{key: 'networkRequestTime', valueType: 'ms', granularity: 1, label: 'Network Request Time'},
|
|
92
|
+
{key: 'networkEndTime', valueType: 'ms', granularity: 1, label: 'Network End Time'},
|
|
93
93
|
{
|
|
94
94
|
key: 'transferSize',
|
|
95
95
|
valueType: 'bytes',
|
package/core/audits/redirects.js
CHANGED
|
@@ -125,7 +125,8 @@ class Redirects extends Audit {
|
|
|
125
125
|
}
|
|
126
126
|
|
|
127
127
|
const lanternTimingDeltaMs = redirectedTiming.startTime - initialTiming.startTime;
|
|
128
|
-
const observedTimingDeltaMs = redirectedRequest.
|
|
128
|
+
const observedTimingDeltaMs = redirectedRequest.networkRequestTime -
|
|
129
|
+
initialRequest.networkRequestTime;
|
|
129
130
|
const wastedMs = settings.throttlingMethod === 'simulate' ?
|
|
130
131
|
lanternTimingDeltaMs : observedTimingDeltaMs;
|
|
131
132
|
totalWastedMs += wastedMs;
|
|
@@ -95,7 +95,8 @@ class UsesRelPreconnectAudit extends Audit {
|
|
|
95
95
|
* @return {boolean}
|
|
96
96
|
*/
|
|
97
97
|
static socketStartTimeIsBelowThreshold(record, mainResource) {
|
|
98
|
-
|
|
98
|
+
const timeSinceMainEnd = Math.max(0, record.networkRequestTime - mainResource.networkEndTime);
|
|
99
|
+
return timeSinceMainEnd < PRECONNECT_SOCKET_MAX_IDLE_IN_MS;
|
|
99
100
|
}
|
|
100
101
|
|
|
101
102
|
/**
|
|
@@ -169,7 +170,7 @@ class UsesRelPreconnectAudit extends Audit {
|
|
|
169
170
|
// Sometimes requests are done simultaneous and the connection has not been made
|
|
170
171
|
// chrome will try to connect for each network record, we get the first record
|
|
171
172
|
const firstRecordOfOrigin = records.reduce((firstRecord, record) => {
|
|
172
|
-
return (record.
|
|
173
|
+
return (record.networkRequestTime < firstRecord.networkRequestTime) ? record : firstRecord;
|
|
173
174
|
});
|
|
174
175
|
|
|
175
176
|
// Skip the origin if we don't have timing information
|
|
@@ -186,8 +187,8 @@ class UsesRelPreconnectAudit extends Audit {
|
|
|
186
187
|
if (firstRecordOfOrigin.parsedURL.scheme === 'https') connectionTime = connectionTime * 2;
|
|
187
188
|
|
|
188
189
|
const timeBetweenMainResourceAndDnsStart =
|
|
189
|
-
firstRecordOfOrigin.
|
|
190
|
-
mainResource.
|
|
190
|
+
firstRecordOfOrigin.networkRequestTime -
|
|
191
|
+
mainResource.networkEndTime +
|
|
191
192
|
firstRecordOfOrigin.timing.dnsStart;
|
|
192
193
|
|
|
193
194
|
const wastedMs = Math.min(connectionTime, timeBetweenMainResourceAndDnsStart);
|
|
@@ -150,7 +150,6 @@ const artifacts = {
|
|
|
150
150
|
MetaElements: '',
|
|
151
151
|
NetworkUserAgent: '',
|
|
152
152
|
OptimizedImages: '',
|
|
153
|
-
PasswordInputsWithPreventedPaste: '',
|
|
154
153
|
ResponseCompression: '',
|
|
155
154
|
RobotsTxt: '',
|
|
156
155
|
ServiceWorker: '',
|
|
@@ -200,7 +199,6 @@ const defaultConfig = {
|
|
|
200
199
|
{id: artifacts.MetaElements, gatherer: 'meta-elements'},
|
|
201
200
|
{id: artifacts.NetworkUserAgent, gatherer: 'network-user-agent'},
|
|
202
201
|
{id: artifacts.OptimizedImages, gatherer: 'dobetterweb/optimized-images'},
|
|
203
|
-
{id: artifacts.PasswordInputsWithPreventedPaste, gatherer: 'dobetterweb/password-inputs-with-prevented-paste'},
|
|
204
202
|
{id: artifacts.ResponseCompression, gatherer: 'dobetterweb/response-compression'},
|
|
205
203
|
{id: artifacts.RobotsTxt, gatherer: 'seo/robots-txt'},
|
|
206
204
|
{id: artifacts.ServiceWorker, gatherer: 'service-worker'},
|
|
@@ -361,7 +359,7 @@ const defaultConfig = {
|
|
|
361
359
|
'dobetterweb/no-document-write',
|
|
362
360
|
'dobetterweb/js-libraries',
|
|
363
361
|
'dobetterweb/notification-on-start',
|
|
364
|
-
'dobetterweb/
|
|
362
|
+
'dobetterweb/paste-preventing-inputs',
|
|
365
363
|
'dobetterweb/uses-http2',
|
|
366
364
|
'dobetterweb/uses-passive-event-listeners',
|
|
367
365
|
'seo/meta-description',
|
|
@@ -614,7 +612,7 @@ const defaultConfig = {
|
|
|
614
612
|
{id: 'notification-on-start', weight: 1, group: 'best-practices-trust-safety'},
|
|
615
613
|
{id: 'csp-xss', weight: 0, group: 'best-practices-trust-safety'},
|
|
616
614
|
// User Experience
|
|
617
|
-
{id: '
|
|
615
|
+
{id: 'paste-preventing-inputs', weight: 1, group: 'best-practices-ux'},
|
|
618
616
|
{id: 'image-aspect-ratio', weight: 1, group: 'best-practices-ux'},
|
|
619
617
|
{id: 'image-size-responsive', weight: 1, group: 'best-practices-ux'},
|
|
620
618
|
{id: 'preload-fonts', weight: 1, group: 'best-practices-ux'},
|
|
@@ -218,9 +218,9 @@ class NetworkMonitor extends NetworkMonitorEventEmitter {
|
|
|
218
218
|
if (request.protocol === 'ws' || request.protocol === 'wss') return;
|
|
219
219
|
|
|
220
220
|
// convert the network timestamp to ms
|
|
221
|
-
timeBoundaries.push({time: request.
|
|
221
|
+
timeBoundaries.push({time: request.networkRequestTime * 1000, isStart: true});
|
|
222
222
|
if (request.finished) {
|
|
223
|
-
timeBoundaries.push({time: request.
|
|
223
|
+
timeBoundaries.push({time: request.networkEndTime * 1000, isStart: false});
|
|
224
224
|
}
|
|
225
225
|
});
|
|
226
226
|
|
|
@@ -155,7 +155,7 @@ class TagsBlockingFirstPaint extends FRGatherer {
|
|
|
155
155
|
*/
|
|
156
156
|
static async findBlockingTags(driver, networkRecords) {
|
|
157
157
|
const firstRequestEndTime = networkRecords.reduce(
|
|
158
|
-
(min, record) => Math.min(min, record.
|
|
158
|
+
(min, record) => Math.min(min, record.networkEndTime),
|
|
159
159
|
Infinity
|
|
160
160
|
);
|
|
161
161
|
const tags = await driver.executionContext.evaluate(collectTagsThatBlockFirstPaint, {args: []});
|
|
@@ -167,7 +167,7 @@ class TagsBlockingFirstPaint extends FRGatherer {
|
|
|
167
167
|
const request = requests.get(tag.url);
|
|
168
168
|
if (!request || request.isLinkPreload) continue;
|
|
169
169
|
|
|
170
|
-
let endTime = request.
|
|
170
|
+
let endTime = request.networkEndTime;
|
|
171
171
|
let mediaChanges;
|
|
172
172
|
|
|
173
173
|
if (tag.tagName === 'LINK') {
|
|
@@ -180,7 +180,7 @@ class TagsBlockingFirstPaint extends FRGatherer {
|
|
|
180
180
|
if (timesResourceBecameNonBlocking.length > 0) {
|
|
181
181
|
const earliestNonBlockingTime = Math.min(...timesResourceBecameNonBlocking);
|
|
182
182
|
const lastTimeResourceWasBlocking = Math.max(
|
|
183
|
-
request.
|
|
183
|
+
request.networkRequestTime,
|
|
184
184
|
firstRequestEndTime + earliestNonBlockingTime / 1000
|
|
185
185
|
);
|
|
186
186
|
endTime = Math.min(endTime, lastTimeResourceWasBlocking);
|
|
@@ -194,7 +194,7 @@ class TagsBlockingFirstPaint extends FRGatherer {
|
|
|
194
194
|
result.push({
|
|
195
195
|
tag: {tagName, url, mediaChanges},
|
|
196
196
|
transferSize: request.transferSize,
|
|
197
|
-
startTime: request.
|
|
197
|
+
startTime: request.networkRequestTime,
|
|
198
198
|
endTime,
|
|
199
199
|
});
|
|
200
200
|
|
|
@@ -61,6 +61,11 @@ function collectElements() {
|
|
|
61
61
|
return [...labelElToArtifact.keys()].indexOf(labelEl);
|
|
62
62
|
});
|
|
63
63
|
|
|
64
|
+
let preventsPaste;
|
|
65
|
+
if (!inputEl.readOnly) {
|
|
66
|
+
preventsPaste = !inputEl.dispatchEvent(new ClipboardEvent('paste', {cancelable: true}));
|
|
67
|
+
}
|
|
68
|
+
|
|
64
69
|
inputArtifacts.push({
|
|
65
70
|
parentFormIndex,
|
|
66
71
|
labelIndices,
|
|
@@ -74,6 +79,7 @@ function collectElements() {
|
|
|
74
79
|
// Requires `--enable-features=AutofillShowTypePredictions`.
|
|
75
80
|
prediction: inputEl.getAttribute('autofill-prediction'),
|
|
76
81
|
},
|
|
82
|
+
preventsPaste,
|
|
77
83
|
// @ts-expect-error - getNodeDetails put into scope via stringification
|
|
78
84
|
node: getNodeDetails(inputEl),
|
|
79
85
|
});
|
|
@@ -59,7 +59,6 @@ legacyDefaultConfig.passes = [{
|
|
|
59
59
|
'dobetterweb/doctype',
|
|
60
60
|
'dobetterweb/domstats',
|
|
61
61
|
'dobetterweb/optimized-images',
|
|
62
|
-
'dobetterweb/password-inputs-with-prevented-paste',
|
|
63
62
|
'dobetterweb/response-compression',
|
|
64
63
|
'dobetterweb/tags-blocking-first-paint',
|
|
65
64
|
'seo/font-size',
|
|
@@ -367,7 +367,7 @@ const UIStrings = {
|
|
|
367
367
|
* @description Description text for not restored reason InjectedJavascript.
|
|
368
368
|
*/
|
|
369
369
|
injectedJavascript:
|
|
370
|
-
'
|
|
370
|
+
'Pages that JavaScript is injected into by extensions are not currently eligible for back/forward cache.',
|
|
371
371
|
/**
|
|
372
372
|
* @description Description text for not restored reason InjectedStyleSheet.
|
|
373
373
|
*/
|
|
@@ -26,14 +26,14 @@ class NetworkNode extends BaseNode {
|
|
|
26
26
|
* @return {number}
|
|
27
27
|
*/
|
|
28
28
|
get startTime() {
|
|
29
|
-
return this._record.
|
|
29
|
+
return this._record.networkRequestTime * 1000;
|
|
30
30
|
}
|
|
31
31
|
|
|
32
32
|
/**
|
|
33
33
|
* @return {number}
|
|
34
34
|
*/
|
|
35
35
|
get endTime() {
|
|
36
|
-
return this._record.
|
|
36
|
+
return this._record.networkEndTime * 1000;
|
|
37
37
|
}
|
|
38
38
|
|
|
39
39
|
/**
|
|
@@ -150,7 +150,7 @@ class NetworkAnalyzer {
|
|
|
150
150
|
if (!Number.isFinite(timing.receiveHeadersEnd) || timing.receiveHeadersEnd < 0) return;
|
|
151
151
|
|
|
152
152
|
// Compute the amount of time downloading everything after the first congestion window took
|
|
153
|
-
const totalTime = record.
|
|
153
|
+
const totalTime = record.networkEndTime - record.networkRequestTime;
|
|
154
154
|
const downloadTimeAfterFirstByte = totalTime - timing.receiveHeadersEnd;
|
|
155
155
|
const numberOfRoundTrips = Math.log2(record.transferSize / INITIAL_CWD);
|
|
156
156
|
|
|
@@ -279,17 +279,19 @@ class NetworkAnalyzer {
|
|
|
279
279
|
const groupedByOrigin = NetworkAnalyzer.groupByOrigin(records);
|
|
280
280
|
for (const [_, originRecords] of groupedByOrigin.entries()) {
|
|
281
281
|
const earliestReusePossible = originRecords
|
|
282
|
-
.map(record => record.
|
|
282
|
+
.map(record => record.networkEndTime)
|
|
283
283
|
.reduce((a, b) => Math.min(a, b), Infinity);
|
|
284
284
|
|
|
285
285
|
for (const record of originRecords) {
|
|
286
286
|
connectionWasReused.set(
|
|
287
287
|
record.requestId,
|
|
288
|
-
record.
|
|
288
|
+
record.networkRequestTime >= earliestReusePossible || record.protocol === 'h2'
|
|
289
289
|
);
|
|
290
290
|
}
|
|
291
291
|
|
|
292
|
-
const firstRecord = originRecords.reduce((a, b) =>
|
|
292
|
+
const firstRecord = originRecords.reduce((a, b) => {
|
|
293
|
+
return a.networkRequestTime > b.networkRequestTime ? b : a;
|
|
294
|
+
});
|
|
293
295
|
connectionWasReused.set(firstRecord.requestId, false);
|
|
294
296
|
}
|
|
295
297
|
|
|
@@ -399,8 +401,8 @@ class NetworkAnalyzer {
|
|
|
399
401
|
|
|
400
402
|
// If we've made it this far, all the times we need should be valid (i.e. not undefined/-1).
|
|
401
403
|
totalBytes += record.transferSize;
|
|
402
|
-
boundaries.push({time: record.
|
|
403
|
-
boundaries.push({time: record.
|
|
404
|
+
boundaries.push({time: record.responseHeadersEndTime / 1000, isStart: true});
|
|
405
|
+
boundaries.push({time: record.networkEndTime / 1000, isStart: false});
|
|
404
406
|
return boundaries;
|
|
405
407
|
}, /** @type {Array<{time: number, isStart: boolean}>} */([])).sort((a, b) => a.time - b.time);
|
|
406
408
|
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
export type ItemValueType = import('../../types/lhr/audit-details').default.ItemValueType;
|
|
2
|
+
/** @typedef {import('../../types/lhr/audit-details').default.ItemValueType} ItemValueType */
|
|
3
|
+
/**
|
|
4
|
+
* Upgrades an lhr object in-place to account for changes in the data structure over major versions.
|
|
5
|
+
* @param {LH.Result} lhr
|
|
6
|
+
*/
|
|
7
|
+
export function upgradeLhrForCompatibility(lhr: LH.Result): void;
|
|
8
|
+
//# sourceMappingURL=lighthouse-compatibility.d.ts.map
|
|
@@ -0,0 +1,139 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @license Copyright 2023 The Lighthouse Authors. All Rights Reserved.
|
|
3
|
+
* Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0
|
|
4
|
+
* Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License.
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import {Util} from '../../shared/util.js';
|
|
8
|
+
|
|
9
|
+
const SCREENSHOT_PREFIX = 'data:image/jpeg;base64,';
|
|
10
|
+
|
|
11
|
+
/** @typedef {import('../../types/lhr/audit-details').default.ItemValueType} ItemValueType */
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* Upgrades an lhr object in-place to account for changes in the data structure over major versions.
|
|
15
|
+
* @param {LH.Result} lhr
|
|
16
|
+
*/
|
|
17
|
+
function upgradeLhrForCompatibility(lhr) {
|
|
18
|
+
// If LHR is older (≤3.0.3), it has no locale setting. Set default.
|
|
19
|
+
if (!lhr.configSettings.locale) {
|
|
20
|
+
lhr.configSettings.locale = 'en';
|
|
21
|
+
}
|
|
22
|
+
if (!lhr.configSettings.formFactor) {
|
|
23
|
+
// @ts-expect-error fallback handling for emulatedFormFactor
|
|
24
|
+
lhr.configSettings.formFactor = lhr.configSettings.emulatedFormFactor;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
lhr.finalDisplayedUrl = Util.getFinalDisplayedUrl(lhr);
|
|
28
|
+
lhr.mainDocumentUrl = Util.getMainDocumentUrl(lhr);
|
|
29
|
+
|
|
30
|
+
for (const audit of Object.values(lhr.audits)) {
|
|
31
|
+
// Turn 'not-applicable' (LHR <4.0) and 'not_applicable' (older proto versions)
|
|
32
|
+
// into 'notApplicable' (LHR ≥4.0).
|
|
33
|
+
// @ts-expect-error tsc rightly flags that these values shouldn't occur.
|
|
34
|
+
// eslint-disable-next-line max-len
|
|
35
|
+
if (audit.scoreDisplayMode === 'not_applicable' || audit.scoreDisplayMode === 'not-applicable') {
|
|
36
|
+
audit.scoreDisplayMode = 'notApplicable';
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
if (audit.details) {
|
|
40
|
+
// Turn `auditDetails.type` of undefined (LHR <4.2) and 'diagnostic' (LHR <5.0)
|
|
41
|
+
// into 'debugdata' (LHR ≥5.0).
|
|
42
|
+
// @ts-expect-error tsc rightly flags that these values shouldn't occur.
|
|
43
|
+
if (audit.details.type === undefined || audit.details.type === 'diagnostic') {
|
|
44
|
+
// @ts-expect-error details is of type never.
|
|
45
|
+
audit.details.type = 'debugdata';
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
// Add the jpg data URL prefix to filmstrip screenshots without them (LHR <5.0).
|
|
49
|
+
if (audit.details.type === 'filmstrip') {
|
|
50
|
+
for (const screenshot of audit.details.items) {
|
|
51
|
+
if (!screenshot.data.startsWith(SCREENSHOT_PREFIX)) {
|
|
52
|
+
screenshot.data = SCREENSHOT_PREFIX + screenshot.data;
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
// Circa 10.0, table items were refactored.
|
|
58
|
+
if (audit.details.type === 'table') {
|
|
59
|
+
for (const heading of audit.details.headings) {
|
|
60
|
+
/** @type {{itemType: ItemValueType|undefined, text: string|undefined}} */
|
|
61
|
+
// @ts-expect-error
|
|
62
|
+
const {itemType, text} = heading;
|
|
63
|
+
if (itemType !== undefined) {
|
|
64
|
+
heading.valueType = itemType;
|
|
65
|
+
// @ts-expect-error
|
|
66
|
+
delete heading.itemType;
|
|
67
|
+
}
|
|
68
|
+
if (text !== undefined) {
|
|
69
|
+
heading.label = text;
|
|
70
|
+
// @ts-expect-error
|
|
71
|
+
delete heading.text;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
// @ts-expect-error
|
|
75
|
+
const subItemsItemType = heading.subItemsHeading?.itemType;
|
|
76
|
+
if (heading.subItemsHeading && subItemsItemType !== undefined) {
|
|
77
|
+
heading.subItemsHeading.valueType = subItemsItemType;
|
|
78
|
+
// @ts-expect-error
|
|
79
|
+
delete heading.subItemsHeading.itemType;
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
// TODO: convert printf-style displayValue.
|
|
85
|
+
// Added: #5099, v3
|
|
86
|
+
// Removed: #6767, v4
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
// This backcompat converts old LHRs (<9.0.0) to use the new "hidden" group.
|
|
91
|
+
// Old LHRs used "no group" to identify audits that should be hidden in performance instead of the "hidden" group.
|
|
92
|
+
// Newer LHRs use "no group" to identify opportunities and diagnostics whose groups are assigned by details type.
|
|
93
|
+
const [majorVersion] = lhr.lighthouseVersion.split('.').map(Number);
|
|
94
|
+
const perfCategory = lhr.categories['performance'];
|
|
95
|
+
if (majorVersion < 9 && perfCategory) {
|
|
96
|
+
if (!lhr.categoryGroups) lhr.categoryGroups = {};
|
|
97
|
+
lhr.categoryGroups['hidden'] = {title: ''};
|
|
98
|
+
for (const auditRef of perfCategory.auditRefs) {
|
|
99
|
+
if (!auditRef.group) {
|
|
100
|
+
auditRef.group = 'hidden';
|
|
101
|
+
} else if (['load-opportunities', 'diagnostics'].includes(auditRef.group)) {
|
|
102
|
+
delete auditRef.group;
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
// Add some minimal stuff so older reports still work.
|
|
108
|
+
if (!lhr.environment) {
|
|
109
|
+
// @ts-expect-error
|
|
110
|
+
lhr.environment = {benchmarkIndex: 0};
|
|
111
|
+
}
|
|
112
|
+
if (!lhr.configSettings.screenEmulation) {
|
|
113
|
+
// @ts-expect-error
|
|
114
|
+
lhr.configSettings.screenEmulation = {};
|
|
115
|
+
}
|
|
116
|
+
if (!lhr.i18n) {
|
|
117
|
+
// @ts-expect-error
|
|
118
|
+
lhr.i18n = {};
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
// In 10.0, full-page-screenshot became a top-level property on the LHR.
|
|
122
|
+
if (lhr.audits['full-page-screenshot']) {
|
|
123
|
+
const details = /** @type {LH.Result.FullPageScreenshot=} */ (
|
|
124
|
+
lhr.audits['full-page-screenshot'].details);
|
|
125
|
+
if (details) {
|
|
126
|
+
lhr.fullPageScreenshot = {
|
|
127
|
+
screenshot: details.screenshot,
|
|
128
|
+
nodes: details.nodes,
|
|
129
|
+
};
|
|
130
|
+
} else {
|
|
131
|
+
lhr.fullPageScreenshot = null;
|
|
132
|
+
}
|
|
133
|
+
delete lhr.audits['full-page-screenshot'];
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
export {
|
|
138
|
+
upgradeLhrForCompatibility,
|
|
139
|
+
};
|