lighthouse 9.5.0-dev.20220503 → 9.5.0-dev.20220506
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/lighthouse-cli/test/smokehouse/lighthouse-runners/bundle.js +97 -22
- package/lighthouse-cli/test/smokehouse/version-check-test.js +0 -2
- package/lighthouse-core/audits/bootup-time.js +2 -70
- package/lighthouse-core/audits/long-tasks.js +3 -3
- package/lighthouse-core/audits/metrics/experimental-interaction-to-next-paint.js +7 -5
- package/lighthouse-core/audits/third-party-summary.js +3 -3
- package/lighthouse-core/computed/metrics/responsiveness.js +14 -13
- package/lighthouse-core/fraggle-rock/gather/driver.js +1 -1
- package/lighthouse-core/gather/driver.js +1 -1
- package/lighthouse-core/gather/fetcher.js +34 -210
- package/lighthouse-core/gather/gather-runner.js +0 -11
- package/lighthouse-core/gather/gatherers/seo/robots-txt.js +0 -29
- package/lighthouse-core/gather/gatherers/source-maps.js +0 -1
- package/lighthouse-core/lib/cdt/generated/SourceMap.js +1 -1
- package/lighthouse-core/lib/stack-packs.js +4 -5
- package/lighthouse-core/lib/tracehouse/task-groups.js +1 -0
- package/lighthouse-core/lib/tracehouse/task-summary.js +87 -0
- package/package.json +5 -5
- package/report/test/.eslintrc.cjs +11 -0
- package/report/test/clients/bundle-test.js +0 -3
- package/report/test/generator/file-namer-test.js +0 -1
- package/report/test/generator/report-generator-test.js +0 -2
- package/report/test/renderer/category-renderer-test.js +0 -2
- package/report/test/renderer/components-test.js +0 -2
- package/report/test/renderer/crc-details-renderer-test.js +0 -2
- package/report/test/renderer/details-renderer-test.js +0 -2
- package/report/test/renderer/dom-test.js +0 -2
- package/report/test/renderer/element-screenshot-renderer-test.js +0 -2
- package/report/test/renderer/i18n-test.js +0 -2
- package/report/test/renderer/performance-category-renderer-test.js +0 -2
- package/report/test/renderer/pwa-category-renderer-test.js +0 -2
- package/report/test/renderer/report-renderer-axe-test.js +0 -2
- package/report/test/renderer/report-renderer-test.js +0 -2
- package/report/test/renderer/report-ui-features-test.js +0 -2
- package/report/test/renderer/snippet-renderer-test.js +0 -2
- package/report/test/renderer/text-encoding-test.js +0 -2
- package/report/test/renderer/util-test.js +0 -2
- package/shared/test/localization/.eslintrc.cjs +11 -0
- package/shared/test/localization/format-test.js +0 -2
- package/shared/test/localization/locales-test.js +0 -2
- package/shared/test/localization/swap-locale-test.js +0 -2
- package/third-party/chromium-synchronization/inspector-issueAdded-types-test.js +0 -2
- package/third-party/chromium-synchronization/installability-errors-test.js +0 -2
- package/tsconfig.json +2 -0
- package/types/artifacts.d.ts +17 -0
|
@@ -6,124 +6,55 @@
|
|
|
6
6
|
'use strict';
|
|
7
7
|
|
|
8
8
|
/**
|
|
9
|
-
* @fileoverview Fetcher is a utility for making requests
|
|
10
|
-
*
|
|
11
|
-
* on a different origin.
|
|
9
|
+
* @fileoverview Fetcher is a utility for making requests to any arbitrary resource,
|
|
10
|
+
* ignoring normal browser constraints such as CORS.
|
|
12
11
|
*/
|
|
13
12
|
|
|
14
|
-
/* global
|
|
13
|
+
/* global fetch */
|
|
15
14
|
|
|
16
15
|
/** @typedef {{content: string|null, status: number|null}} FetchResponse */
|
|
17
16
|
|
|
18
|
-
const log = require('lighthouse-logger');
|
|
19
|
-
const {getBrowserVersion} = require('./driver/environment.js');
|
|
20
|
-
|
|
21
17
|
class Fetcher {
|
|
22
18
|
/**
|
|
23
19
|
* @param {LH.Gatherer.FRProtocolSession} session
|
|
24
|
-
* @param {import('./driver/execution-context.js')} executionContext
|
|
25
20
|
*/
|
|
26
|
-
constructor(session
|
|
21
|
+
constructor(session) {
|
|
27
22
|
this.session = session;
|
|
28
|
-
this.executionContext = executionContext;
|
|
29
|
-
/** @type {Map<string, (event: LH.Crdp.Fetch.RequestPausedEvent) => void>} */
|
|
30
|
-
this._onRequestPausedHandlers = new Map();
|
|
31
|
-
this._onRequestPaused = this._onRequestPaused.bind(this);
|
|
32
|
-
this._enabled = false;
|
|
33
23
|
}
|
|
34
24
|
|
|
35
25
|
/**
|
|
36
|
-
*
|
|
37
|
-
* We use `Network.loadNetworkResource` to fetch each resource.
|
|
38
|
-
*
|
|
39
|
-
* Chrome <M92:
|
|
40
|
-
* The Fetch domain accepts patterns for controlling what requests are intercepted, but we
|
|
41
|
-
* enable the domain for all patterns and filter events at a lower level to support multiple
|
|
42
|
-
* concurrent usages. Reasons for this:
|
|
43
|
-
*
|
|
44
|
-
* 1) only one set of patterns may be applied for the entire domain.
|
|
45
|
-
* 2) every request that matches the patterns are paused and only resumes when certain Fetch
|
|
46
|
-
* commands are sent. So a listener of the `Fetch.requestPaused` event must either handle
|
|
47
|
-
* the requests it cares about, or explicitly allow them to continue.
|
|
48
|
-
* 3) if multiple commands to continue the same request are sent, protocol errors occur.
|
|
26
|
+
* Fetches any resource using the network directly.
|
|
49
27
|
*
|
|
50
|
-
* So instead we have one global `Fetch.enable` / `Fetch.requestPaused` pair, and allow specific
|
|
51
|
-
* urls to be intercepted via `fetcher._setOnRequestPausedHandler`.
|
|
52
|
-
*/
|
|
53
|
-
async enable() {
|
|
54
|
-
if (this._enabled) return;
|
|
55
|
-
|
|
56
|
-
this._enabled = true;
|
|
57
|
-
await this.session.sendCommand('Fetch.enable', {
|
|
58
|
-
patterns: [{requestStage: 'Request'}, {requestStage: 'Response'}],
|
|
59
|
-
});
|
|
60
|
-
await this.session.on('Fetch.requestPaused', this._onRequestPaused);
|
|
61
|
-
}
|
|
62
|
-
|
|
63
|
-
async disable() {
|
|
64
|
-
if (!this._enabled) return;
|
|
65
|
-
|
|
66
|
-
this._enabled = false;
|
|
67
|
-
await this.session.off('Fetch.requestPaused', this._onRequestPaused);
|
|
68
|
-
await this.session.sendCommand('Fetch.disable');
|
|
69
|
-
this._onRequestPausedHandlers.clear();
|
|
70
|
-
}
|
|
71
|
-
|
|
72
|
-
/**
|
|
73
28
|
* @param {string} url
|
|
74
|
-
* @param {
|
|
75
|
-
|
|
76
|
-
async _setOnRequestPausedHandler(url, handler) {
|
|
77
|
-
this._onRequestPausedHandlers.set(url, handler);
|
|
78
|
-
}
|
|
79
|
-
|
|
80
|
-
/**
|
|
81
|
-
* @param {LH.Crdp.Fetch.RequestPausedEvent} event
|
|
29
|
+
* @param {{timeout: number}=} options timeout is in ms
|
|
30
|
+
* @return {Promise<FetchResponse>}
|
|
82
31
|
*/
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
// Nothing cares about this URL, so continue.
|
|
89
|
-
this.session.sendCommand('Fetch.continueRequest', {requestId: event.requestId}).catch(err => {
|
|
90
|
-
log.error('Fetcher', `Failed to continueRequest: ${err.message}`);
|
|
91
|
-
});
|
|
32
|
+
async fetchResource(url, options = {timeout: 2_000}) {
|
|
33
|
+
// In Lightrider, `Network.loadNetworkResource` is not implemented, but fetch
|
|
34
|
+
// is configured to work for any resource.
|
|
35
|
+
if (global.isLightrider) {
|
|
36
|
+
return this._wrapWithTimeout(this._fetchWithFetchApi(url), options.timeout);
|
|
92
37
|
}
|
|
93
|
-
}
|
|
94
38
|
|
|
95
|
-
|
|
96
|
-
* `Network.loadNetworkResource` was introduced in M88.
|
|
97
|
-
* The long timeout bug with `IO.read` was fixed in M92:
|
|
98
|
-
* https://bugs.chromium.org/p/chromium/issues/detail?id=1191757
|
|
99
|
-
* Lightrider has a bug forcing us to use the old version for now:
|
|
100
|
-
* https://docs.google.com/document/d/1V-DxgsOFMPxUuFrdGPQpyiCqSljvgNlOqXCtqDtd0b8/edit?usp=sharing&resourcekey=0-aIaIqcHFKG-0dX4MAudBEw
|
|
101
|
-
* @return {Promise<boolean>}
|
|
102
|
-
*/
|
|
103
|
-
async shouldUseLegacyFetcher() {
|
|
104
|
-
const {milestone} = await getBrowserVersion(this.session);
|
|
105
|
-
return milestone < 92 || Boolean(global.isLightrider);
|
|
39
|
+
return this._fetchResourceOverProtocol(url, options);
|
|
106
40
|
}
|
|
107
41
|
|
|
108
42
|
/**
|
|
109
|
-
* Requires that `fetcher.enable` has been called.
|
|
110
|
-
*
|
|
111
|
-
* Fetches any resource in a way that circumvents CORS.
|
|
112
|
-
*
|
|
113
43
|
* @param {string} url
|
|
114
|
-
* @param {{timeout: number}=} options timeout is in ms
|
|
115
44
|
* @return {Promise<FetchResponse>}
|
|
116
45
|
*/
|
|
117
|
-
async
|
|
118
|
-
|
|
119
|
-
throw new Error('Must call `enable` before using fetchResource');
|
|
120
|
-
}
|
|
46
|
+
async _fetchWithFetchApi(url) {
|
|
47
|
+
const response = await fetch(url);
|
|
121
48
|
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
49
|
+
let content = null;
|
|
50
|
+
try {
|
|
51
|
+
content = await response.text();
|
|
52
|
+
} catch {}
|
|
125
53
|
|
|
126
|
-
return
|
|
54
|
+
return {
|
|
55
|
+
content,
|
|
56
|
+
status: response.status,
|
|
57
|
+
};
|
|
127
58
|
}
|
|
128
59
|
|
|
129
60
|
/**
|
|
@@ -172,18 +103,6 @@ class Fetcher {
|
|
|
172
103
|
};
|
|
173
104
|
}
|
|
174
105
|
|
|
175
|
-
/**
|
|
176
|
-
* @param {string} requestId
|
|
177
|
-
* @return {Promise<string>}
|
|
178
|
-
*/
|
|
179
|
-
async _resolveResponseBody(requestId) {
|
|
180
|
-
const responseBody = await this.session.sendCommand('Fetch.getResponseBody', {requestId});
|
|
181
|
-
if (responseBody.base64Encoded) {
|
|
182
|
-
return Buffer.from(responseBody.body, 'base64').toString();
|
|
183
|
-
}
|
|
184
|
-
return responseBody.body;
|
|
185
|
-
}
|
|
186
|
-
|
|
187
106
|
/**
|
|
188
107
|
* @param {string} url
|
|
189
108
|
* @param {{timeout: number}} options timeout is in ms
|
|
@@ -191,18 +110,7 @@ class Fetcher {
|
|
|
191
110
|
*/
|
|
192
111
|
async _fetchResourceOverProtocol(url, options) {
|
|
193
112
|
const startTime = Date.now();
|
|
194
|
-
|
|
195
|
-
/** @type {NodeJS.Timeout} */
|
|
196
|
-
let timeoutHandle;
|
|
197
|
-
const timeoutPromise = new Promise((_, reject) => {
|
|
198
|
-
timeoutHandle = setTimeout(reject, options.timeout, new Error('Timed out fetching resource'));
|
|
199
|
-
});
|
|
200
|
-
|
|
201
|
-
const responsePromise = this._loadNetworkResource(url);
|
|
202
|
-
|
|
203
|
-
/** @type {{stream: LH.Crdp.IO.StreamHandle|null, status: number|null}} */
|
|
204
|
-
const response = await Promise.race([responsePromise, timeoutPromise])
|
|
205
|
-
.finally(() => clearTimeout(timeoutHandle));
|
|
113
|
+
const response = await this._wrapWithTimeout(this._loadNetworkResource(url), options.timeout);
|
|
206
114
|
|
|
207
115
|
const isOk = response.status && response.status >= 200 && response.status <= 299;
|
|
208
116
|
if (!response.stream || !isOk) return {status: response.status, content: null};
|
|
@@ -213,105 +121,21 @@ class Fetcher {
|
|
|
213
121
|
}
|
|
214
122
|
|
|
215
123
|
/**
|
|
216
|
-
*
|
|
217
|
-
* @param {
|
|
218
|
-
* @param {
|
|
219
|
-
* @return {Promise<FetchResponse>}
|
|
124
|
+
* @template T
|
|
125
|
+
* @param {Promise<T>} promise
|
|
126
|
+
* @param {number} ms
|
|
220
127
|
*/
|
|
221
|
-
async
|
|
222
|
-
/** @type {Promise<FetchResponse>} */
|
|
223
|
-
const requestInterceptionPromise = new Promise((resolve, reject) => {
|
|
224
|
-
/** @param {LH.Crdp.Fetch.RequestPausedEvent} event */
|
|
225
|
-
const handlerAsync = async event => {
|
|
226
|
-
const {requestId, responseStatusCode} = event;
|
|
227
|
-
|
|
228
|
-
// The first requestPaused event is for the request stage. Continue it.
|
|
229
|
-
if (!responseStatusCode) {
|
|
230
|
-
// Remove cookies so we aren't buying stuff on Amazon.
|
|
231
|
-
const headers = Object.entries(event.request.headers)
|
|
232
|
-
.filter(([name]) => name !== 'Cookie')
|
|
233
|
-
.map(([name, value]) => {
|
|
234
|
-
return {name, value};
|
|
235
|
-
});
|
|
236
|
-
|
|
237
|
-
await this.session.sendCommand('Fetch.continueRequest', {
|
|
238
|
-
requestId,
|
|
239
|
-
headers,
|
|
240
|
-
});
|
|
241
|
-
return;
|
|
242
|
-
}
|
|
243
|
-
|
|
244
|
-
if (responseStatusCode >= 200 && responseStatusCode <= 299) {
|
|
245
|
-
resolve({
|
|
246
|
-
status: responseStatusCode,
|
|
247
|
-
content: await this._resolveResponseBody(requestId),
|
|
248
|
-
});
|
|
249
|
-
} else {
|
|
250
|
-
resolve({status: responseStatusCode, content: null});
|
|
251
|
-
}
|
|
252
|
-
|
|
253
|
-
// Fail the request (from the page's perspective) so that the iframe never loads.
|
|
254
|
-
await this.session.sendCommand('Fetch.failRequest', {requestId, errorReason: 'Aborted'});
|
|
255
|
-
};
|
|
256
|
-
this._setOnRequestPausedHandler(url, event => handlerAsync(event).catch(reject));
|
|
257
|
-
});
|
|
258
|
-
|
|
259
|
-
/**
|
|
260
|
-
* @param {string} src
|
|
261
|
-
*/
|
|
262
|
-
/* c8 ignore start */
|
|
263
|
-
function injectIframe(src) {
|
|
264
|
-
const iframe = document.createElement('iframe');
|
|
265
|
-
// Try really hard not to affect the page.
|
|
266
|
-
iframe.style.display = 'none';
|
|
267
|
-
iframe.style.visibility = 'hidden';
|
|
268
|
-
iframe.style.position = 'absolute';
|
|
269
|
-
iframe.style.top = '-1000px';
|
|
270
|
-
iframe.style.left = '-1000px';
|
|
271
|
-
iframe.style.width = '1px';
|
|
272
|
-
iframe.style.height = '1px';
|
|
273
|
-
iframe.src = src;
|
|
274
|
-
iframe.onload = iframe.onerror = () => {
|
|
275
|
-
iframe.remove();
|
|
276
|
-
iframe.onload = null;
|
|
277
|
-
iframe.onerror = null;
|
|
278
|
-
};
|
|
279
|
-
document.body.appendChild(iframe);
|
|
280
|
-
}
|
|
281
|
-
/* c8 ignore stop */
|
|
282
|
-
|
|
128
|
+
async _wrapWithTimeout(promise, ms) {
|
|
283
129
|
/** @type {NodeJS.Timeout} */
|
|
284
|
-
let
|
|
285
|
-
/** @type {Promise<never>} */
|
|
130
|
+
let timeoutHandle;
|
|
286
131
|
const timeoutPromise = new Promise((_, reject) => {
|
|
287
|
-
|
|
288
|
-
});
|
|
289
|
-
|
|
290
|
-
const racePromise = Promise.race([
|
|
291
|
-
timeoutPromise,
|
|
292
|
-
requestInterceptionPromise,
|
|
293
|
-
]).finally(() => clearTimeout(asyncTimeout));
|
|
294
|
-
|
|
295
|
-
// Temporarily disable auto-attaching for this iframe.
|
|
296
|
-
await this.session.sendCommand('Target.setAutoAttach', {
|
|
297
|
-
autoAttach: false,
|
|
298
|
-
waitForDebuggerOnStart: false,
|
|
299
|
-
});
|
|
300
|
-
|
|
301
|
-
const injectionPromise = this.executionContext.evaluate(injectIframe, {
|
|
302
|
-
args: [url],
|
|
303
|
-
useIsolation: true,
|
|
132
|
+
timeoutHandle = setTimeout(reject, ms, new Error('Timed out fetching resource'));
|
|
304
133
|
});
|
|
305
134
|
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
autoAttach: true,
|
|
311
|
-
waitForDebuggerOnStart: true,
|
|
312
|
-
});
|
|
313
|
-
|
|
314
|
-
return fetchResult;
|
|
135
|
+
/** @type {Promise<T>} */
|
|
136
|
+
const wrappedPromise = await Promise.race([promise, timeoutPromise])
|
|
137
|
+
.finally(() => clearTimeout(timeoutHandle));
|
|
138
|
+
return wrappedPromise;
|
|
315
139
|
}
|
|
316
140
|
}
|
|
317
141
|
|
|
@@ -183,11 +183,6 @@ class GatherRunner {
|
|
|
183
183
|
const resetStorage = !options.settings.disableStorageReset;
|
|
184
184
|
if (resetStorage) await storage.clearDataForOrigin(session, options.requestedUrl);
|
|
185
185
|
|
|
186
|
-
// Disable fetcher, in case a gatherer enabled it.
|
|
187
|
-
// This cleanup should be removed once the only usage of
|
|
188
|
-
// fetcher (fetching arbitrary URLs) is replaced by new protocol support.
|
|
189
|
-
await driver.fetcher.disable();
|
|
190
|
-
|
|
191
186
|
await driver.disconnect();
|
|
192
187
|
} catch (err) {
|
|
193
188
|
// Ignore disconnecting error if browser was already closed.
|
|
@@ -524,12 +519,6 @@ class GatherRunner {
|
|
|
524
519
|
await GatherRunner.populateBaseArtifacts(passContext);
|
|
525
520
|
isFirstPass = false;
|
|
526
521
|
}
|
|
527
|
-
|
|
528
|
-
// Disable fetcher for every pass, in case a gatherer enabled it.
|
|
529
|
-
// Noop if fetcher was never enabled.
|
|
530
|
-
// This cleanup should be removed once the only usage of
|
|
531
|
-
// fetcher (fetching arbitrary URLs) is replaced by new protocol support.
|
|
532
|
-
await driver.fetcher.disable();
|
|
533
522
|
}
|
|
534
523
|
|
|
535
524
|
await GatherRunner.disposeDriver(driver, options);
|
|
@@ -7,25 +7,6 @@
|
|
|
7
7
|
|
|
8
8
|
const FRGatherer = require('../../../fraggle-rock/gather/base-gatherer.js');
|
|
9
9
|
|
|
10
|
-
/* global fetch, location */
|
|
11
|
-
|
|
12
|
-
/** @return {Promise<LH.Artifacts['RobotsTxt']>} */
|
|
13
|
-
/* c8 ignore start */
|
|
14
|
-
async function getRobotsTxtContent() {
|
|
15
|
-
try {
|
|
16
|
-
const response = await fetch(new URL('/robots.txt', location.href).href);
|
|
17
|
-
if (!response.ok) {
|
|
18
|
-
return {status: response.status, content: null};
|
|
19
|
-
}
|
|
20
|
-
|
|
21
|
-
const content = await response.text();
|
|
22
|
-
return {status: response.status, content};
|
|
23
|
-
} catch (err) {
|
|
24
|
-
return {status: null, content: null, errorMessage: err.message};
|
|
25
|
-
}
|
|
26
|
-
}
|
|
27
|
-
/* c8 ignore stop */
|
|
28
|
-
|
|
29
10
|
class RobotsTxt extends FRGatherer {
|
|
30
11
|
/** @type {LH.Gatherer.GathererMeta} */
|
|
31
12
|
meta = {
|
|
@@ -37,18 +18,8 @@ class RobotsTxt extends FRGatherer {
|
|
|
37
18
|
* @return {Promise<LH.Artifacts['RobotsTxt']>}
|
|
38
19
|
*/
|
|
39
20
|
async getArtifact(passContext) {
|
|
40
|
-
// Iframe fetcher still has issues with CSPs.
|
|
41
|
-
// Only use the fetcher if we are fetching over the protocol.
|
|
42
|
-
if (await passContext.driver.fetcher.shouldUseLegacyFetcher()) {
|
|
43
|
-
return passContext.driver.executionContext.evaluate(getRobotsTxtContent, {
|
|
44
|
-
args: [],
|
|
45
|
-
useIsolation: true,
|
|
46
|
-
});
|
|
47
|
-
}
|
|
48
|
-
|
|
49
21
|
const {finalUrl} = passContext.baseArtifacts.URL;
|
|
50
22
|
const robotsUrl = new URL('/robots.txt', finalUrl).href;
|
|
51
|
-
await passContext.driver.fetcher.enable();
|
|
52
23
|
return passContext.driver.fetcher.fetchResource(robotsUrl)
|
|
53
24
|
.catch(err => ({status: null, content: null, errorMessage: err.message}));
|
|
54
25
|
}
|
|
@@ -143,7 +143,6 @@ class SourceMaps extends FRGatherer {
|
|
|
143
143
|
* @return {Promise<LH.Artifacts['SourceMaps']>}
|
|
144
144
|
*/
|
|
145
145
|
async getArtifact(context) {
|
|
146
|
-
await context.driver.fetcher.enable();
|
|
147
146
|
const eventProcessPromises = this._scriptParsedEvents
|
|
148
147
|
.map((event) => this._retrieveMapFromScriptParsedEvent(context.driver, event));
|
|
149
148
|
return Promise.all(eventProcessPromises);
|
|
@@ -149,7 +149,7 @@ class TextSourceMap {
|
|
|
149
149
|
}
|
|
150
150
|
return mappings.slice(startIndex, endIndex);
|
|
151
151
|
}
|
|
152
|
-
/** @return {Array<{lineNumber: number, columnNumber: number, sourceURL?: string, sourceLineNumber, sourceColumnNumber: number, name?: string, lastColumnNumber?: number}>} */
|
|
152
|
+
/** @return {Array<{lineNumber: number, columnNumber: number, sourceURL?: string, sourceLineNumber: number, sourceColumnNumber: number, name?: string, lastColumnNumber?: number}>} */
|
|
153
153
|
mappings() {
|
|
154
154
|
if (this.mappingsInternal === null) {
|
|
155
155
|
this.mappingsInternal = [];
|
|
@@ -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'],
|
|
@@ -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
|
+
};
|
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.20220506",
|
|
4
4
|
"description": "Automated auditing, performance metrics, and best practices for the web.",
|
|
5
5
|
"main": "./lighthouse-core/index.js",
|
|
6
6
|
"bin": {
|
|
@@ -37,7 +37,7 @@
|
|
|
37
37
|
"start": "yarn build-report --standalone && node ./lighthouse-cli/index.js",
|
|
38
38
|
"jest": "node --experimental-vm-modules ./node_modules/jest/bin/jest.js",
|
|
39
39
|
"test": "yarn diff:sample-json && yarn lint --quiet && yarn unit && yarn type-check",
|
|
40
|
-
"test-bundle": "yarn smoke --runner bundle
|
|
40
|
+
"test-bundle": "yarn smoke --runner bundle --retries=2",
|
|
41
41
|
"test-clients": "yarn jest \"$PWD/clients/\" && yarn jest --testMatch=\"**/clients/test/**/*-test-pptr.js\"",
|
|
42
42
|
"test-viewer": "yarn unit-viewer && yarn jest --testMatch=\"**/viewer/**/*-test-pptr.js\"",
|
|
43
43
|
"test-treemap": "yarn unit-treemap && yarn jest --testMatch=\"**/treemap/**/*-test-pptr.js\"",
|
|
@@ -68,13 +68,13 @@
|
|
|
68
68
|
"vercel-build": "yarn build-sample-reports && yarn build-viewer && yarn build-treemap",
|
|
69
69
|
"dogfood-lhci": "./lighthouse-core/scripts/dogfood-lhci.sh",
|
|
70
70
|
"timing-trace": "node lighthouse-core/scripts/generate-timing-trace.js",
|
|
71
|
-
"changelog": "conventional-changelog --config ./build/changelog-generator/index.
|
|
71
|
+
"changelog": "conventional-changelog --config ./build/changelog-generator/index.cjs --infile changelog.md --same-file",
|
|
72
72
|
"type-check": "tsc --build ./tsconfig-all.json",
|
|
73
73
|
"i18n:checks": "./lighthouse-core/scripts/i18n/assert-strings-collected.sh",
|
|
74
74
|
"i18n:collect-strings": "node lighthouse-core/scripts/i18n/collect-strings.js",
|
|
75
75
|
"update:lantern-baseline": "node lighthouse-core/scripts/lantern/update-baseline-lantern-values.js",
|
|
76
76
|
"update:sample-artifacts": "node lighthouse-core/scripts/update-report-fixtures.js",
|
|
77
|
-
"update:sample-json": "yarn i18n:collect-strings && node ./lighthouse-cli -A=./lighthouse-core/test/results/artifacts --config-path=./lighthouse-core/test/results/sample-config.js --output=json --output-path=./lighthouse-core/test/results/sample_v2.json && node lighthouse-core/scripts/cleanup-LHR-for-diff.js ./lighthouse-core/test/results/sample_v2.json --only-remove-timing",
|
|
77
|
+
"update:sample-json": "yarn i18n:collect-strings && node ./lighthouse-cli -A=./lighthouse-core/test/results/artifacts --config-path=./lighthouse-core/test/results/sample-config.js --output=json --output-path=./lighthouse-core/test/results/sample_v2.json && node lighthouse-core/scripts/cleanup-LHR-for-diff.js ./lighthouse-core/test/results/sample_v2.json --only-remove-timing && node ./lighthouse-core/scripts/update-flow-fixtures.js",
|
|
78
78
|
"update:flow-sample-json": "yarn i18n:collect-strings && node ./lighthouse-core/scripts/update-flow-fixtures.js",
|
|
79
79
|
"update:test-devtools": "bash lighthouse-core/test/chromium-web-tests/test-locally.sh --reset-results",
|
|
80
80
|
"test-devtools": "bash lighthouse-core/test/chromium-web-tests/test-locally.sh",
|
|
@@ -190,7 +190,7 @@
|
|
|
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
195
|
"lighthouse-stack-packs": "^1.8.1",
|
|
196
196
|
"lodash": "^4.17.21",
|
|
@@ -0,0 +1,11 @@
|
|
|
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
|
+
|
|
7
|
+
module.exports = {
|
|
8
|
+
env: {
|
|
9
|
+
jest: true,
|
|
10
|
+
},
|
|
11
|
+
};
|
|
@@ -16,9 +16,6 @@ import {LH_ROOT} from '../../../root.js';
|
|
|
16
16
|
const sampleResultsStr =
|
|
17
17
|
fs.readFileSync(LH_ROOT + '/lighthouse-core/test/results/sample_v2.json', 'utf-8');
|
|
18
18
|
|
|
19
|
-
|
|
20
|
-
/* eslint-env jest */
|
|
21
|
-
|
|
22
19
|
describe('lighthouseRenderer bundle', () => {
|
|
23
20
|
let document;
|
|
24
21
|
beforeAll(() => {
|
|
@@ -9,7 +9,6 @@ const assert = require('assert').strict;
|
|
|
9
9
|
|
|
10
10
|
const getLhrFilenamePrefix = require('../../generator/file-namer.js').getLhrFilenamePrefix;
|
|
11
11
|
|
|
12
|
-
/* eslint-env jest */
|
|
13
12
|
describe('file-namer helper', () => {
|
|
14
13
|
it('generates filename prefixes', () => {
|
|
15
14
|
const results = {
|
|
@@ -15,8 +15,6 @@ const csvValidator = require('csv-validator');
|
|
|
15
15
|
const ReportGenerator = require('../../generator/report-generator.js');
|
|
16
16
|
const sampleResults = require('../../../lighthouse-core/test/results/sample_v2.json');
|
|
17
17
|
|
|
18
|
-
/* eslint-env jest */
|
|
19
|
-
|
|
20
18
|
describe('ReportGenerator', () => {
|
|
21
19
|
describe('#replaceStrings', () => {
|
|
22
20
|
it('should replace all occurrences', () => {
|
|
@@ -4,8 +4,6 @@
|
|
|
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
|
*/
|
|
6
6
|
|
|
7
|
-
/* eslint-env jest */
|
|
8
|
-
|
|
9
7
|
import {strict as assert} from 'assert';
|
|
10
8
|
|
|
11
9
|
import jsdom from 'jsdom';
|
|
@@ -4,8 +4,6 @@
|
|
|
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
|
*/
|
|
6
6
|
|
|
7
|
-
/* eslint-env jest */
|
|
8
|
-
|
|
9
7
|
import fs from 'fs';
|
|
10
8
|
|
|
11
9
|
import jsdom from 'jsdom';
|
|
@@ -4,8 +4,6 @@
|
|
|
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
|
*/
|
|
6
6
|
|
|
7
|
-
/* eslint-env jest */
|
|
8
|
-
|
|
9
7
|
import {strict as assert} from 'assert';
|
|
10
8
|
|
|
11
9
|
import jsdom from 'jsdom';
|
|
@@ -4,8 +4,6 @@
|
|
|
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
|
*/
|
|
6
6
|
|
|
7
|
-
/* eslint-env jest */
|
|
8
|
-
|
|
9
7
|
import jsdom from 'jsdom';
|
|
10
8
|
|
|
11
9
|
import {ElementScreenshotRenderer} from '../../renderer/element-screenshot-renderer.js';
|