lighthouse 9.5.0 → 9.6.3
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 +67 -384
- package/dist/report/flow.js +20 -25
- package/dist/report/standalone.js +18 -23
- package/lighthouse-cli/test/smokehouse/core-tests.js +2 -0
- 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/lighthouse-runners/cli.js +1 -0
- package/lighthouse-cli/test/smokehouse/readme.md +5 -4
- package/lighthouse-cli/test/smokehouse/report-assert.js +16 -13
- package/lighthouse-cli/test/smokehouse/version-check-test.js +46 -0
- package/lighthouse-cli/test/smokehouse/version-check.js +49 -0
- package/lighthouse-core/audits/bootup-time.js +2 -70
- package/lighthouse-core/audits/byte-efficiency/duplicated-javascript.js +3 -5
- package/lighthouse-core/audits/byte-efficiency/legacy-javascript.js +2 -5
- package/lighthouse-core/audits/byte-efficiency/unminified-javascript.js +4 -2
- package/lighthouse-core/audits/byte-efficiency/unused-javascript.js +3 -1
- package/lighthouse-core/audits/deprecations.js +603 -28
- package/lighthouse-core/audits/installable-manifest.js +27 -6
- package/lighthouse-core/audits/long-tasks.js +3 -3
- package/lighthouse-core/audits/metrics/experimental-interaction-to-next-paint.js +88 -0
- package/lighthouse-core/audits/network-requests.js +79 -62
- package/lighthouse-core/audits/preload-lcp-image.js +33 -7
- package/lighthouse-core/audits/third-party-summary.js +3 -3
- package/lighthouse-core/audits/work-during-interaction.js +280 -0
- package/lighthouse-core/computed/metrics/responsiveness.js +157 -0
- package/lighthouse-core/config/config-helpers.js +1 -1
- package/lighthouse-core/config/metrics-to-audits.js +5 -0
- package/lighthouse-core/fraggle-rock/config/default-config.js +9 -1
- package/lighthouse-core/fraggle-rock/gather/base-artifacts.js +2 -2
- package/lighthouse-core/fraggle-rock/gather/session.js +8 -2
- package/lighthouse-core/gather/driver.js +21 -14
- package/lighthouse-core/gather/gather-runner.js +2 -4
- package/lighthouse-core/gather/gatherers/css-usage.js +59 -34
- package/lighthouse-core/gather/gatherers/trace-elements.js +21 -2
- package/lighthouse-core/lib/arbitrary-equality-map.js +2 -2
- package/lighthouse-core/lib/dependency-graph/network-node.js +1 -1
- package/lighthouse-core/lib/i18n/i18n.js +2 -0
- package/lighthouse-core/lib/minify-trace.js +2 -0
- package/lighthouse-core/lib/script-helpers.js +24 -0
- package/lighthouse-core/lib/stack-packs.js +8 -5
- package/lighthouse-core/lib/tracehouse/task-groups.js +1 -0
- package/lighthouse-core/lib/tracehouse/task-summary.js +87 -0
- package/lighthouse-core/lib/tracehouse/trace-processor.js +39 -14
- package/lighthouse-core/runner.js +1 -1
- package/lighthouse-core/util-commonjs.js +20 -18
- package/package.json +10 -8
- package/report/assets/styles.css +5 -2
- package/report/clients/bundle.js +1 -0
- package/report/renderer/components.js +1 -1
- package/report/renderer/details-renderer.js +4 -4
- package/report/renderer/performance-category-renderer.js +15 -11
- package/report/renderer/util.js +20 -18
- package/report/test/renderer/__snapshots__/report-renderer-axe-test.js.snap +6 -98
- package/report/test/renderer/details-renderer-test.js +9 -9
- package/report/test/renderer/performance-category-renderer-test.js +31 -0
- package/report/test/renderer/report-renderer-axe-test.js +3 -15
- package/report/test-assets/faux-psi-template.html +3 -2
- package/report/test-assets/faux-psi.js +22 -1
- package/shared/localization/locales/en-US.json +244 -4
- package/shared/localization/locales/en-XL.json +244 -4
- package/shared/localization/swap-locale.js +2 -1
- package/third-party/chromium-synchronization/installability-errors-test.js +1 -0
- package/third-party/snyk/snapshot.json +2 -3
- package/tsconfig.json +1 -0
- package/types/artifacts.d.ts +40 -1
- package/types/lhr/audit-details.d.ts +3 -1
- package/types/smokehouse.d.ts +1 -1
- package/changelog.md +0 -5902
- package/lighthouse-core/scripts/package.json +0 -4
|
@@ -5,7 +5,9 @@
|
|
|
5
5
|
*/
|
|
6
6
|
'use strict';
|
|
7
7
|
|
|
8
|
+
const log = require('lighthouse-logger');
|
|
8
9
|
const FRGatherer = require('../../fraggle-rock/gather/base-gatherer.js');
|
|
10
|
+
const Sentry = require('../../lib/sentry.js');
|
|
9
11
|
|
|
10
12
|
/**
|
|
11
13
|
* @fileoverview Tracks unused CSS rules.
|
|
@@ -13,21 +15,16 @@ const FRGatherer = require('../../fraggle-rock/gather/base-gatherer.js');
|
|
|
13
15
|
class CSSUsage extends FRGatherer {
|
|
14
16
|
constructor() {
|
|
15
17
|
super();
|
|
16
|
-
/** @type {
|
|
17
|
-
this.
|
|
18
|
-
/** @
|
|
19
|
-
this.
|
|
20
|
-
/** @param {LH.Crdp.CSS.StyleSheetRemovedEvent} sheet */
|
|
21
|
-
this._onStylesheetRemoved = sheet => {
|
|
22
|
-
// We can't fetch the content of removed stylesheets, so we ignore them completely.
|
|
23
|
-
const styleSheetId = sheet.styleSheetId;
|
|
24
|
-
this._stylesheets = this._stylesheets.filter(s => s.header.styleSheetId !== styleSheetId);
|
|
25
|
-
};
|
|
18
|
+
/** @type {LH.Gatherer.FRProtocolSession|undefined} */
|
|
19
|
+
this._session = undefined;
|
|
20
|
+
/** @type {Map<string, Promise<LH.Artifacts.CSSStyleSheetInfo|Error>>} */
|
|
21
|
+
this._sheetPromises = new Map();
|
|
26
22
|
/**
|
|
27
23
|
* Initialize as undefined so we can assert results are fetched.
|
|
28
24
|
* @type {LH.Crdp.CSS.RuleUsage[]|undefined}
|
|
29
25
|
*/
|
|
30
26
|
this._ruleUsage = undefined;
|
|
27
|
+
this._onStylesheetAdded = this._onStylesheetAdded.bind(this);
|
|
31
28
|
}
|
|
32
29
|
|
|
33
30
|
/** @type {LH.Gatherer.GathererMeta} */
|
|
@@ -35,26 +32,49 @@ class CSSUsage extends FRGatherer {
|
|
|
35
32
|
supportedModes: ['snapshot', 'timespan', 'navigation'],
|
|
36
33
|
};
|
|
37
34
|
|
|
35
|
+
/**
|
|
36
|
+
* @param {LH.Crdp.CSS.StyleSheetAddedEvent} event
|
|
37
|
+
*/
|
|
38
|
+
async _onStylesheetAdded(event) {
|
|
39
|
+
if (!this._session) throw new Error('Session not initialized');
|
|
40
|
+
const styleSheetId = event.header.styleSheetId;
|
|
41
|
+
const sheetPromise = this._session.sendCommand('CSS.getStyleSheetText', {styleSheetId})
|
|
42
|
+
.then(content => ({
|
|
43
|
+
header: event.header,
|
|
44
|
+
content: content.text,
|
|
45
|
+
}))
|
|
46
|
+
.catch(/** @param {Error} err */ (err) => {
|
|
47
|
+
log.warn(
|
|
48
|
+
'CSSUsage',
|
|
49
|
+
`Error fetching content of stylesheet with URL "${event.header.sourceURL}"`
|
|
50
|
+
);
|
|
51
|
+
Sentry.captureException(err, {
|
|
52
|
+
tags: {
|
|
53
|
+
gatherer: this.name,
|
|
54
|
+
},
|
|
55
|
+
extra: {
|
|
56
|
+
url: event.header.sourceURL,
|
|
57
|
+
},
|
|
58
|
+
level: 'error',
|
|
59
|
+
});
|
|
60
|
+
return err;
|
|
61
|
+
});
|
|
62
|
+
this._sheetPromises.set(styleSheetId, sheetPromise);
|
|
63
|
+
}
|
|
64
|
+
|
|
38
65
|
/**
|
|
39
66
|
* @param {LH.Gatherer.FRTransitionalContext} context
|
|
40
67
|
*/
|
|
41
68
|
async startCSSUsageTracking(context) {
|
|
42
69
|
const session = context.driver.defaultSession;
|
|
70
|
+
this._session = session;
|
|
43
71
|
session.on('CSS.styleSheetAdded', this._onStylesheetAdded);
|
|
44
|
-
session.on('CSS.styleSheetRemoved', this._onStylesheetRemoved);
|
|
45
72
|
|
|
46
73
|
await session.sendCommand('DOM.enable');
|
|
47
74
|
await session.sendCommand('CSS.enable');
|
|
48
75
|
await session.sendCommand('CSS.startRuleUsageTracking');
|
|
49
76
|
}
|
|
50
77
|
|
|
51
|
-
/**
|
|
52
|
-
* @param {LH.Gatherer.FRTransitionalContext} context
|
|
53
|
-
*/
|
|
54
|
-
async startInstrumentation(context) {
|
|
55
|
-
if (context.gatherMode !== 'timespan') return;
|
|
56
|
-
await this.startCSSUsageTracking(context);
|
|
57
|
-
}
|
|
58
78
|
|
|
59
79
|
/**
|
|
60
80
|
* @param {LH.Gatherer.FRTransitionalContext} context
|
|
@@ -64,7 +84,14 @@ class CSSUsage extends FRGatherer {
|
|
|
64
84
|
const coverageResponse = await session.sendCommand('CSS.stopRuleUsageTracking');
|
|
65
85
|
this._ruleUsage = coverageResponse.ruleUsage;
|
|
66
86
|
session.off('CSS.styleSheetAdded', this._onStylesheetAdded);
|
|
67
|
-
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
/**
|
|
90
|
+
* @param {LH.Gatherer.FRTransitionalContext} context
|
|
91
|
+
*/
|
|
92
|
+
async startInstrumentation(context) {
|
|
93
|
+
if (context.gatherMode !== 'timespan') return;
|
|
94
|
+
await this.startCSSUsageTracking(context);
|
|
68
95
|
}
|
|
69
96
|
|
|
70
97
|
/**
|
|
@@ -93,25 +120,23 @@ class CSSUsage extends FRGatherer {
|
|
|
93
120
|
await this.stopCSSUsageTracking(context);
|
|
94
121
|
}
|
|
95
122
|
|
|
96
|
-
|
|
97
|
-
const
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
123
|
+
/** @type {Map<string, LH.Artifacts.CSSStyleSheetInfo>} */
|
|
124
|
+
const dedupedStylesheets = new Map();
|
|
125
|
+
const sheets = await Promise.all(this._sheetPromises.values());
|
|
126
|
+
|
|
127
|
+
for (const sheet of sheets) {
|
|
128
|
+
// Erroneous sheets will be reported via sentry and the log.
|
|
129
|
+
// We can ignore them here without throwing a fatal error.
|
|
130
|
+
if (sheet instanceof Error) {
|
|
131
|
+
continue;
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
dedupedStylesheets.set(sheet.content, sheet);
|
|
135
|
+
}
|
|
107
136
|
|
|
108
137
|
await session.sendCommand('CSS.disable');
|
|
109
138
|
await session.sendCommand('DOM.disable');
|
|
110
139
|
|
|
111
|
-
const dedupedStylesheets = new Map(styleSheetInfo.map(sheet => {
|
|
112
|
-
return [sheet.content, sheet];
|
|
113
|
-
}));
|
|
114
|
-
|
|
115
140
|
if (!this._ruleUsage) throw new Error('Issue collecting rule usages');
|
|
116
141
|
|
|
117
142
|
return {
|
|
@@ -22,6 +22,7 @@ const Trace = require('./trace.js');
|
|
|
22
22
|
const ProcessedTrace = require('../../computed/processed-trace.js');
|
|
23
23
|
const ProcessedNavigation = require('../../computed/processed-navigation.js');
|
|
24
24
|
const LighthouseError = require('../../lib/lh-error.js');
|
|
25
|
+
const ComputedResponsivenes = require('../../computed/metrics/responsiveness.js');
|
|
25
26
|
|
|
26
27
|
/** @typedef {{nodeId: number, score?: number, animations?: {name?: string, failureReasonsMask?: number, unsupportedProperties?: string[]}[]}} TraceElementData */
|
|
27
28
|
|
|
@@ -142,6 +143,23 @@ class TraceElements extends FRGatherer {
|
|
|
142
143
|
return topFive;
|
|
143
144
|
}
|
|
144
145
|
|
|
146
|
+
/**
|
|
147
|
+
* @param {LH.Trace} trace
|
|
148
|
+
* @param {LH.Gatherer.FRTransitionalContext} context
|
|
149
|
+
* @return {Promise<TraceElementData|undefined>}
|
|
150
|
+
*/
|
|
151
|
+
static async getResponsivenessElement(trace, context) {
|
|
152
|
+
const {settings} = context;
|
|
153
|
+
try {
|
|
154
|
+
const responsivenessEvent = await ComputedResponsivenes.request({trace, settings}, context);
|
|
155
|
+
if (!responsivenessEvent || responsivenessEvent.name === 'FallbackTiming') return;
|
|
156
|
+
return {nodeId: responsivenessEvent.args.data.nodeId};
|
|
157
|
+
} catch {
|
|
158
|
+
// Don't let responsiveness errors sink the rest of the gatherer.
|
|
159
|
+
return;
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
|
|
145
163
|
/**
|
|
146
164
|
* Find the node ids of elements which are animated using the Animation trace events.
|
|
147
165
|
* @param {Array<LH.TraceEvent>} mainThreadEvents
|
|
@@ -237,14 +255,15 @@ class TraceElements extends FRGatherer {
|
|
|
237
255
|
|
|
238
256
|
const lcpNodeId = largestContentfulPaintEvt?.args?.data?.nodeId;
|
|
239
257
|
const clsNodeData = TraceElements.getTopLayoutShiftElements(mainThreadEvents);
|
|
240
|
-
const animatedElementData =
|
|
241
|
-
|
|
258
|
+
const animatedElementData = await this.getAnimatedElements(mainThreadEvents);
|
|
259
|
+
const responsivenessElementData = await TraceElements.getResponsivenessElement(trace, context);
|
|
242
260
|
|
|
243
261
|
/** @type {Map<string, TraceElementData[]>} */
|
|
244
262
|
const backendNodeDataMap = new Map([
|
|
245
263
|
['largest-contentful-paint', lcpNodeId ? [{nodeId: lcpNodeId}] : []],
|
|
246
264
|
['layout-shift', clsNodeData],
|
|
247
265
|
['animation', animatedElementData],
|
|
266
|
+
['responsiveness', responsivenessElementData ? [responsivenessElementData] : []],
|
|
248
267
|
]);
|
|
249
268
|
|
|
250
269
|
const traceElements = [];
|
|
@@ -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',
|
|
@@ -0,0 +1,24 @@
|
|
|
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
|
+
* @param {LH.Artifacts.NetworkRequest[]} networkRecords
|
|
10
|
+
* @param {LH.Artifacts.ScriptElement|undefined} script
|
|
11
|
+
* @return {LH.Artifacts.NetworkRequest|undefined}
|
|
12
|
+
*/
|
|
13
|
+
function getRequestForScript(networkRecords, script) {
|
|
14
|
+
if (!script) return;
|
|
15
|
+
let networkRequest = script.requestId &&
|
|
16
|
+
networkRecords.find(request => request.requestId === script.requestId);
|
|
17
|
+
if (!networkRequest) networkRequest = networkRecords.find(request => request.url === script.src);
|
|
18
|
+
while (networkRequest?.redirectDestination) {
|
|
19
|
+
networkRequest = networkRequest.redirectDestination;
|
|
20
|
+
}
|
|
21
|
+
return networkRequest;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
module.exports = {getRequestForScript};
|
|
@@ -19,11 +19,10 @@ const stackPacksToInclude = [
|
|
|
19
19
|
packId: 'wordpress',
|
|
20
20
|
requiredStacks: ['js:wordpress'],
|
|
21
21
|
},
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
// },
|
|
22
|
+
{
|
|
23
|
+
packId: 'ezoic',
|
|
24
|
+
requiredStacks: ['js:ezoic'],
|
|
25
|
+
},
|
|
27
26
|
{
|
|
28
27
|
packId: 'drupal',
|
|
29
28
|
requiredStacks: ['js:drupal'],
|
|
@@ -48,6 +47,10 @@ const stackPacksToInclude = [
|
|
|
48
47
|
packId: 'next.js',
|
|
49
48
|
requiredStacks: ['js:next'],
|
|
50
49
|
},
|
|
50
|
+
{
|
|
51
|
+
packId: 'nuxt',
|
|
52
|
+
requiredStacks: ['js:nuxt'],
|
|
53
|
+
},
|
|
51
54
|
{
|
|
52
55
|
packId: 'angular',
|
|
53
56
|
requiredStacks: ['js:@angular/core'],
|
|
@@ -0,0 +1,87 @@
|
|
|
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 Utility functions for grouping and summarizing tasks.
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
const NetworkRequest = require('../network-request.js');
|
|
13
|
+
|
|
14
|
+
// These trace events, when not triggered by a script inside a particular task, are just general Chrome overhead.
|
|
15
|
+
const BROWSER_TASK_NAMES_SET = new Set([
|
|
16
|
+
'CpuProfiler::StartProfiling',
|
|
17
|
+
]);
|
|
18
|
+
|
|
19
|
+
// These trace events, when not triggered by a script inside a particular task, are GC Chrome overhead.
|
|
20
|
+
const BROWSER_GC_TASK_NAMES_SET = new Set([
|
|
21
|
+
'V8.GCCompactor',
|
|
22
|
+
'MajorGC',
|
|
23
|
+
'MinorGC',
|
|
24
|
+
]);
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* @param {LH.Artifacts.NetworkRequest[]} records
|
|
28
|
+
*/
|
|
29
|
+
function getJavaScriptURLs(records) {
|
|
30
|
+
/** @type {Set<string>} */
|
|
31
|
+
const urls = new Set();
|
|
32
|
+
for (const record of records) {
|
|
33
|
+
if (record.resourceType === NetworkRequest.TYPES.Script) {
|
|
34
|
+
urls.add(record.url);
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
return urls;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* @param {LH.Artifacts.TaskNode} task
|
|
43
|
+
* @param {Set<string>} jsURLs
|
|
44
|
+
* @return {string}
|
|
45
|
+
*/
|
|
46
|
+
function getAttributableURLForTask(task, jsURLs) {
|
|
47
|
+
const jsURL = task.attributableURLs.find(url => jsURLs.has(url));
|
|
48
|
+
const fallbackURL = task.attributableURLs[0];
|
|
49
|
+
let attributableURL = jsURL || fallbackURL;
|
|
50
|
+
// If we can't find what URL was responsible for this execution, attribute it to the root page
|
|
51
|
+
// or Chrome depending on the type of work.
|
|
52
|
+
if (!attributableURL || attributableURL === 'about:blank') {
|
|
53
|
+
if (BROWSER_TASK_NAMES_SET.has(task.event.name)) attributableURL = 'Browser';
|
|
54
|
+
else if (BROWSER_GC_TASK_NAMES_SET.has(task.event.name)) attributableURL = 'Browser GC';
|
|
55
|
+
else attributableURL = 'Unattributable';
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
return attributableURL;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
/**
|
|
62
|
+
* @param {LH.Artifacts.TaskNode[]} tasks
|
|
63
|
+
* @param {Array<LH.Artifacts.NetworkRequest>} networkRecords
|
|
64
|
+
* @return {Map<string, Record<string, number>>}
|
|
65
|
+
*/
|
|
66
|
+
function getExecutionTimingsByURL(tasks, networkRecords) {
|
|
67
|
+
const jsURLs = getJavaScriptURLs(networkRecords);
|
|
68
|
+
|
|
69
|
+
/** @type {Map<string, Record<string, number>>} */
|
|
70
|
+
const result = new Map();
|
|
71
|
+
|
|
72
|
+
for (const task of tasks) {
|
|
73
|
+
const attributableURL = getAttributableURLForTask(task, jsURLs);
|
|
74
|
+
const timingByGroupId = result.get(attributableURL) || {};
|
|
75
|
+
const originalTime = timingByGroupId[task.group.id] || 0;
|
|
76
|
+
timingByGroupId[task.group.id] = originalTime + task.selfTime;
|
|
77
|
+
result.set(attributableURL, timingByGroupId);
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
return result;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
module.exports = {
|
|
84
|
+
getJavaScriptURLs,
|
|
85
|
+
getAttributableURLForTask,
|
|
86
|
+
getExecutionTimingsByURL,
|
|
87
|
+
};
|
|
@@ -82,11 +82,15 @@ class TraceProcessor {
|
|
|
82
82
|
* Returns true if the event is a navigation start event of a document whose URL seems valid.
|
|
83
83
|
*
|
|
84
84
|
* @param {LH.TraceEvent} event
|
|
85
|
+
* @return {boolean}
|
|
85
86
|
*/
|
|
86
87
|
static _isNavigationStartOfInterest(event) {
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
88
|
+
if (event.name !== 'navigationStart') return false;
|
|
89
|
+
// COMPAT: support pre-m67 test traces before `args.data` added to all navStart events.
|
|
90
|
+
// TODO: remove next line when old test traces (e.g. progressive-app-m60.json) are updated.
|
|
91
|
+
if (event.args.data?.documentLoaderURL === undefined) return true;
|
|
92
|
+
if (!event.args.data?.documentLoaderURL) return false;
|
|
93
|
+
return ACCEPTABLE_NAVIGATION_URL_REGEX.test(event.args.data.documentLoaderURL);
|
|
90
94
|
}
|
|
91
95
|
|
|
92
96
|
/**
|
|
@@ -462,8 +466,9 @@ class TraceProcessor {
|
|
|
462
466
|
// If we can't find either TracingStarted event, then we'll fallback to the first navStart that
|
|
463
467
|
// looks like it was loading the main frame with a real URL. Because the schema for this event
|
|
464
468
|
// has changed across Chrome versions, we'll be extra defensive about finding this case.
|
|
465
|
-
const navStartEvt = events.find(e =>
|
|
466
|
-
e
|
|
469
|
+
const navStartEvt = events.find(e =>
|
|
470
|
+
this._isNavigationStartOfInterest(e) && e.args.data?.isLoadingMainFrame
|
|
471
|
+
);
|
|
467
472
|
// Find the first resource that was requested and make sure it agrees on the id.
|
|
468
473
|
const firstResourceSendEvt = events.find(e => e.name === 'ResourceSendRequest');
|
|
469
474
|
// We know that these properties exist if we found the events, but TSC doesn't.
|
|
@@ -607,39 +612,59 @@ class TraceProcessor {
|
|
|
607
612
|
// Find the inspected frame
|
|
608
613
|
const mainFrameIds = this.findMainFrameIds(keyEvents);
|
|
609
614
|
|
|
610
|
-
|
|
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
|
|
611
636
|
.filter(/** @return {evt is FrameCommittedEvent} */ evt => {
|
|
612
637
|
return Boolean(
|
|
613
638
|
evt.name === 'FrameCommittedInBrowser' &&
|
|
614
639
|
evt.args.data?.frame &&
|
|
615
640
|
evt.args.data.url
|
|
616
641
|
);
|
|
617
|
-
})
|
|
618
|
-
|
|
619
|
-
return {
|
|
642
|
+
}).forEach(evt => {
|
|
643
|
+
framesById.set(evt.args.data.frame, {
|
|
620
644
|
id: evt.args.data.frame,
|
|
621
645
|
url: evt.args.data.url,
|
|
622
646
|
parent: evt.args.data.parent,
|
|
623
|
-
};
|
|
647
|
+
});
|
|
624
648
|
});
|
|
649
|
+
const frames = [...framesById.values()];
|
|
625
650
|
const frameIdToRootFrameId = this.resolveRootFrames(frames);
|
|
626
651
|
|
|
627
652
|
// Filter to just events matching the main frame ID, just to make sure.
|
|
628
653
|
const frameEvents = keyEvents.filter(e => e.args.frame === mainFrameIds.frameId);
|
|
629
654
|
|
|
630
655
|
// Filter to just events matching the main frame ID or any child frame IDs.
|
|
631
|
-
// In practice, there should always be FrameCommittedInBrowser events to define the frame tree.
|
|
632
|
-
// Unfortunately, many test traces do not include FrameCommittedInBrowser events due to minification.
|
|
633
|
-
// This ensures there is always a minimal frame tree and events so those tests don't fail.
|
|
634
656
|
let frameTreeEvents = [];
|
|
635
657
|
if (frameIdToRootFrameId.has(mainFrameIds.frameId)) {
|
|
636
658
|
frameTreeEvents = keyEvents.filter(e => {
|
|
637
659
|
return e.args.frame && frameIdToRootFrameId.get(e.args.frame) === mainFrameIds.frameId;
|
|
638
660
|
});
|
|
639
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.
|
|
640
665
|
log.warn(
|
|
641
666
|
'trace-of-tab',
|
|
642
|
-
'frameTreeEvents may be incomplete, make sure the trace has
|
|
667
|
+
'frameTreeEvents may be incomplete, make sure the trace has frame events'
|
|
643
668
|
);
|
|
644
669
|
frameIdToRootFrameId.set(mainFrameIds.frameId, mainFrameIds.frameId);
|
|
645
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');
|
|
@@ -318,24 +318,26 @@ class Util {
|
|
|
318
318
|
}
|
|
319
319
|
|
|
320
320
|
const MAX_LENGTH = 64;
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
321
|
+
if (parsedUrl.protocol !== 'data:') {
|
|
322
|
+
// Always elide hexadecimal hash
|
|
323
|
+
name = name.replace(/([a-f0-9]{7})[a-f0-9]{13}[a-f0-9]*/g, `$1${ELLIPSIS}`);
|
|
324
|
+
// Also elide other hash-like mixed-case strings
|
|
325
|
+
name = name.replace(/([a-zA-Z0-9-_]{9})(?=.*[a-z])(?=.*[A-Z])(?=.*[0-9])[a-zA-Z0-9-_]{10,}/g,
|
|
326
|
+
`$1${ELLIPSIS}`);
|
|
327
|
+
// Also elide long number sequences
|
|
328
|
+
name = name.replace(/(\d{3})\d{6,}/g, `$1${ELLIPSIS}`);
|
|
329
|
+
// Merge any adjacent ellipses
|
|
330
|
+
name = name.replace(/\u2026+/g, ELLIPSIS);
|
|
331
|
+
|
|
332
|
+
// Elide query params first
|
|
333
|
+
if (name.length > MAX_LENGTH && name.includes('?')) {
|
|
334
|
+
// Try to leave the first query parameter intact
|
|
335
|
+
name = name.replace(/\?([^=]*)(=)?.*/, `?$1$2${ELLIPSIS}`);
|
|
336
|
+
|
|
337
|
+
// Remove it all if it's still too long
|
|
338
|
+
if (name.length > MAX_LENGTH) {
|
|
339
|
+
name = name.replace(/\?.*/, `?${ELLIPSIS}`);
|
|
340
|
+
}
|
|
339
341
|
}
|
|
340
342
|
}
|
|
341
343
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "lighthouse",
|
|
3
|
-
"version": "9.
|
|
3
|
+
"version": "9.6.3",
|
|
4
4
|
"description": "Automated auditing, performance metrics, and best practices for the web.",
|
|
5
5
|
"main": "./lighthouse-core/index.js",
|
|
6
6
|
"bin": {
|
|
@@ -138,7 +138,7 @@
|
|
|
138
138
|
"cpy": "^8.1.2",
|
|
139
139
|
"cross-env": "^7.0.2",
|
|
140
140
|
"csv-validator": "^0.0.3",
|
|
141
|
-
"devtools-protocol": "0.0.
|
|
141
|
+
"devtools-protocol": "0.0.999451",
|
|
142
142
|
"es-main": "^1.0.2",
|
|
143
143
|
"eslint": "^8.4.1",
|
|
144
144
|
"eslint-config-google": "^0.14.0",
|
|
@@ -163,7 +163,7 @@
|
|
|
163
163
|
"pako": "^2.0.3",
|
|
164
164
|
"preact": "^10.5.14",
|
|
165
165
|
"pretty-json-stringify": "^0.0.2",
|
|
166
|
-
"puppeteer": "
|
|
166
|
+
"puppeteer": "13.7.0",
|
|
167
167
|
"resolve": "^1.20.0",
|
|
168
168
|
"rollup": "^2.52.7",
|
|
169
169
|
"rollup-plugin-node-resolve": "^5.2.0",
|
|
@@ -181,7 +181,7 @@
|
|
|
181
181
|
},
|
|
182
182
|
"dependencies": {
|
|
183
183
|
"@sentry/node": "^6.17.4",
|
|
184
|
-
"axe-core": "4.
|
|
184
|
+
"axe-core": "4.4.1",
|
|
185
185
|
"chrome-launcher": "^0.15.0",
|
|
186
186
|
"configstore": "^5.0.1",
|
|
187
187
|
"csp_evaluator": "1.1.0",
|
|
@@ -190,25 +190,27 @@
|
|
|
190
190
|
"http-link-header": "^0.8.0",
|
|
191
191
|
"intl-messageformat": "^4.4.0",
|
|
192
192
|
"jpeg-js": "^0.4.3",
|
|
193
|
-
"js-library-detector": "^6.
|
|
193
|
+
"js-library-detector": "^6.5.0",
|
|
194
194
|
"lighthouse-logger": "^1.3.0",
|
|
195
|
-
"lighthouse-stack-packs": "^1.
|
|
195
|
+
"lighthouse-stack-packs": "^1.8.1",
|
|
196
196
|
"lodash": "^4.17.21",
|
|
197
197
|
"lookup-closest-locale": "6.2.0",
|
|
198
198
|
"metaviewport-parser": "0.2.0",
|
|
199
199
|
"open": "^8.4.0",
|
|
200
200
|
"parse-cache-control": "1.0.1",
|
|
201
201
|
"ps-list": "^8.0.0",
|
|
202
|
+
"puppeteer-core": "^13.7.0",
|
|
202
203
|
"robots-parser": "^3.0.0",
|
|
203
204
|
"semver": "^5.3.0",
|
|
204
205
|
"speedline-core": "^1.4.3",
|
|
205
|
-
"third-party-web": "^0.
|
|
206
|
+
"third-party-web": "^0.17.1",
|
|
206
207
|
"ws": "^7.0.0",
|
|
207
208
|
"yargs": "^17.3.1",
|
|
208
209
|
"yargs-parser": "^21.0.0"
|
|
209
210
|
},
|
|
210
211
|
"resolutions": {
|
|
211
|
-
"puppeteer/**/devtools-protocol": "0.0.
|
|
212
|
+
"puppeteer/**/devtools-protocol": "0.0.999451",
|
|
213
|
+
"puppeteer-core/**/devtools-protocol": "0.0.999451"
|
|
212
214
|
},
|
|
213
215
|
"repository": "GoogleChrome/lighthouse",
|
|
214
216
|
"keywords": [
|
package/report/assets/styles.css
CHANGED
|
@@ -300,6 +300,9 @@
|
|
|
300
300
|
/* This is normally the height of the topbar, but we want it to stick to the top of our scroll container .lh-container` */
|
|
301
301
|
top: 0;
|
|
302
302
|
}
|
|
303
|
+
.lh-devtools .lh-element-screenshot__overlay {
|
|
304
|
+
position: absolute;
|
|
305
|
+
}
|
|
303
306
|
|
|
304
307
|
@keyframes fadeIn {
|
|
305
308
|
0% { opacity: 0;}
|
|
@@ -1062,8 +1065,8 @@
|
|
|
1062
1065
|
}
|
|
1063
1066
|
|
|
1064
1067
|
/* Report */
|
|
1065
|
-
.lh-list >
|
|
1066
|
-
|
|
1068
|
+
.lh-list > :not(:last-child) {
|
|
1069
|
+
margin-bottom: calc(var(--default-padding) * 2);
|
|
1067
1070
|
}
|
|
1068
1071
|
|
|
1069
1072
|
.lh-header-container {
|
package/report/clients/bundle.js
CHANGED
|
@@ -16,3 +16,4 @@ export {DOM} from '../renderer/dom.js';
|
|
|
16
16
|
export {ReportRenderer} from '../renderer/report-renderer.js';
|
|
17
17
|
export {ReportUIFeatures} from '../renderer/report-ui-features.js';
|
|
18
18
|
export {renderReport} from '../renderer/api.js';
|
|
19
|
+
export {swapLocale, format} from '../../shared/localization/i18n-module.js';
|