lighthouse 11.7.0-dev.20240415 → 11.7.0-dev.20240416
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/cli/test/smokehouse/config/exclusions.js +2 -0
- package/cli/test/smokehouse/core-tests.js +2 -0
- package/core/audits/seo/link-text.js +12 -0
- package/core/computed/page-dependency-graph.js +1 -6
- package/core/gather/driver/navigation.js +4 -1
- package/core/gather/driver/prepare.js +1 -1
- package/core/gather/driver/target-manager.js +3 -2
- package/core/gather/driver.d.ts +12 -0
- package/core/gather/driver.js +26 -0
- package/core/gather/navigation-runner.js +3 -1
- package/core/gather/session.js +2 -1
- package/core/lib/emulation.js +2 -2
- package/core/lib/lh-error.d.ts +9 -0
- package/core/lib/lh-error.js +10 -0
- package/core/lib/navigation-error.js +3 -0
- package/package.json +3 -3
- package/shared/localization/locales/en-US.json +3 -0
- package/shared/localization/locales/en-XL.json +3 -0
- package/types/gatherer.d.ts +2 -0
|
@@ -26,6 +26,8 @@ const exclusions = {
|
|
|
26
26
|
'metrics-tricky-tti', 'metrics-tricky-tti-late-fcp', 'screenshot',
|
|
27
27
|
// Disabled because of differences that need further investigation
|
|
28
28
|
'byte-efficiency', 'byte-gzip', 'perf-preload',
|
|
29
|
+
// Disabled because a renderer crash also breaks devtools frontend
|
|
30
|
+
'crash',
|
|
29
31
|
],
|
|
30
32
|
};
|
|
31
33
|
|
|
@@ -7,6 +7,7 @@
|
|
|
7
7
|
import a11y from './test-definitions/a11y.js';
|
|
8
8
|
import byteEfficiency from './test-definitions/byte-efficiency.js';
|
|
9
9
|
import byteGzip from './test-definitions/byte-gzip.js';
|
|
10
|
+
import crash from './test-definitions/crash.js';
|
|
10
11
|
import cspAllowAll from './test-definitions/csp-allow-all.js';
|
|
11
12
|
import cspBlockAll from './test-definitions/csp-block-all.js';
|
|
12
13
|
import dbw from './test-definitions/dobetterweb.js';
|
|
@@ -65,6 +66,7 @@ const smokeTests = [
|
|
|
65
66
|
a11y,
|
|
66
67
|
byteEfficiency,
|
|
67
68
|
byteGzip,
|
|
69
|
+
crash,
|
|
68
70
|
cspAllowAll,
|
|
69
71
|
cspBlockAll,
|
|
70
72
|
dbw,
|
|
@@ -84,6 +84,18 @@ const BLOCKLIST = new Set([
|
|
|
84
84
|
'மேலும் தரவுகளுக்கு',
|
|
85
85
|
'தயவுசெய்து இங்கே அழுத்தவும்',
|
|
86
86
|
'இங்கே கிளிக் செய்யவும்',
|
|
87
|
+
// Persian
|
|
88
|
+
'اطلاعات بیشتر',
|
|
89
|
+
'اطلاعات',
|
|
90
|
+
'این',
|
|
91
|
+
'اینجا بزنید',
|
|
92
|
+
'اینجا کلیک کنید',
|
|
93
|
+
'اینجا',
|
|
94
|
+
'برو',
|
|
95
|
+
'بیشتر بخوانید',
|
|
96
|
+
'بیشتر بدانید',
|
|
97
|
+
'بیشتر',
|
|
98
|
+
'شروع',
|
|
87
99
|
]);
|
|
88
100
|
|
|
89
101
|
const UIStrings = {
|
|
@@ -9,7 +9,6 @@ import {PageDependencyGraph as LanternPageDependencyGraph} from '../lib/lantern/
|
|
|
9
9
|
import {NetworkRequest} from '../lib/network-request.js';
|
|
10
10
|
import {ProcessedTrace} from './processed-trace.js';
|
|
11
11
|
import {NetworkRecords} from './network-records.js';
|
|
12
|
-
import {DocumentUrls} from './document-urls.js';
|
|
13
12
|
|
|
14
13
|
/** @typedef {import('../lib/lantern/base-node.js').Node<LH.Artifacts.NetworkRequest>} Node */
|
|
15
14
|
|
|
@@ -20,16 +19,12 @@ class PageDependencyGraph {
|
|
|
20
19
|
* @return {Promise<Node>}
|
|
21
20
|
*/
|
|
22
21
|
static async compute_(data, context) {
|
|
23
|
-
const {trace, devtoolsLog} = data;
|
|
22
|
+
const {trace, devtoolsLog, URL} = data;
|
|
24
23
|
const [processedTrace, networkRecords] = await Promise.all([
|
|
25
24
|
ProcessedTrace.request(trace, context),
|
|
26
25
|
NetworkRecords.request(devtoolsLog, context),
|
|
27
26
|
]);
|
|
28
27
|
|
|
29
|
-
// COMPAT: Backport for pre-10.0 clients that don't pass the URL artifact here (e.g. pubads).
|
|
30
|
-
// Calculates the URL artifact from the processed trace and DT log.
|
|
31
|
-
const URL = data.URL || await DocumentUrls.request(data, context);
|
|
32
|
-
|
|
33
28
|
const mainThreadEvents = processedTrace.mainThreadEvents;
|
|
34
29
|
const lanternRequests = networkRecords.map(NetworkRequest.asLanternNetworkRequest);
|
|
35
30
|
return LanternPageDependencyGraph.createGraph(mainThreadEvents, lanternRequests, URL);
|
|
@@ -122,7 +122,10 @@ async function gotoURL(driver, requestor, options) {
|
|
|
122
122
|
throw new Error('Cannot wait for FCP without waiting for page load');
|
|
123
123
|
}
|
|
124
124
|
|
|
125
|
-
const waitConditions = await Promise.
|
|
125
|
+
const waitConditions = await Promise.race([
|
|
126
|
+
driver.fatalRejection.promise,
|
|
127
|
+
Promise.all(waitConditionPromises),
|
|
128
|
+
]);
|
|
126
129
|
const timedOut = waitConditions.some(condition => condition.timedOut);
|
|
127
130
|
const navigationUrls = await networkMonitor.getNavigationUrls();
|
|
128
131
|
|
|
@@ -45,7 +45,7 @@ async function enableAsyncStacks(session) {
|
|
|
45
45
|
await enable();
|
|
46
46
|
|
|
47
47
|
return async () => {
|
|
48
|
-
await session.
|
|
48
|
+
await session.sendCommandAndIgnore('Debugger.disable');
|
|
49
49
|
session.off('Debugger.paused', onDebuggerPaused);
|
|
50
50
|
session.off('Page.frameNavigated', onFrameNavigated);
|
|
51
51
|
};
|
|
@@ -266,8 +266,9 @@ class TargetManager extends ProtocolEventEmitter {
|
|
|
266
266
|
cdpSession.off('sessionattached', this._onSessionAttached);
|
|
267
267
|
}
|
|
268
268
|
|
|
269
|
-
|
|
270
|
-
await this._rootCdpSession.send('
|
|
269
|
+
// Ignore failures on these in case the tab has crashed.
|
|
270
|
+
await this._rootCdpSession.send('Page.disable').catch(_ => {});
|
|
271
|
+
await this._rootCdpSession.send('Runtime.disable').catch(_ => {});
|
|
271
272
|
|
|
272
273
|
this._enabled = false;
|
|
273
274
|
this._targetIdToTargets = new Map();
|
package/core/gather/driver.d.ts
CHANGED
|
@@ -14,6 +14,10 @@ export class Driver implements LH.Gatherer.Driver {
|
|
|
14
14
|
/** @type {Fetcher|undefined} */
|
|
15
15
|
_fetcher: Fetcher | undefined;
|
|
16
16
|
defaultSession: import("../../types/gatherer.js").default.ProtocolSession;
|
|
17
|
+
fatalRejection: {
|
|
18
|
+
promise: Promise<never>;
|
|
19
|
+
rej: (_: Error) => void;
|
|
20
|
+
};
|
|
17
21
|
/** @return {LH.Gatherer.Driver['executionContext']} */
|
|
18
22
|
get executionContext(): ExecutionContext;
|
|
19
23
|
get fetcher(): any;
|
|
@@ -23,6 +27,14 @@ export class Driver implements LH.Gatherer.Driver {
|
|
|
23
27
|
url(): Promise<string>;
|
|
24
28
|
/** @return {Promise<void>} */
|
|
25
29
|
connect(): Promise<void>;
|
|
30
|
+
/**
|
|
31
|
+
* If the target crashes, we can't continue gathering.
|
|
32
|
+
*
|
|
33
|
+
* FWIW, if the target unexpectedly detaches (eg the user closed the tab), pptr will
|
|
34
|
+
* catch that and reject into our this._cdpSession.send, which we'll alrady handle appropriately
|
|
35
|
+
* @return {void}
|
|
36
|
+
*/
|
|
37
|
+
listenForCrashes(): void;
|
|
26
38
|
/** @return {Promise<void>} */
|
|
27
39
|
disconnect(): Promise<void>;
|
|
28
40
|
}
|
package/core/gather/driver.js
CHANGED
|
@@ -8,6 +8,7 @@ import log from 'lighthouse-logger';
|
|
|
8
8
|
|
|
9
9
|
import {ExecutionContext} from './driver/execution-context.js';
|
|
10
10
|
import {TargetManager} from './driver/target-manager.js';
|
|
11
|
+
import {LighthouseError} from '../lib/lh-error.js';
|
|
11
12
|
import {Fetcher} from './fetcher.js';
|
|
12
13
|
import {NetworkMonitor} from './driver/network-monitor.js';
|
|
13
14
|
|
|
@@ -47,6 +48,12 @@ class Driver {
|
|
|
47
48
|
this._fetcher = undefined;
|
|
48
49
|
|
|
49
50
|
this.defaultSession = throwingSession;
|
|
51
|
+
|
|
52
|
+
// Poor man's Promise.withResolvers()
|
|
53
|
+
/** @param {Error} _ */
|
|
54
|
+
let rej = _ => {};
|
|
55
|
+
const promise = /** @type {Promise<never>} */ (new Promise((_, theRej) => rej = theRej));
|
|
56
|
+
this.fatalRejection = {promise, rej};
|
|
50
57
|
}
|
|
51
58
|
|
|
52
59
|
/** @return {LH.Gatherer.Driver['executionContext']} */
|
|
@@ -86,11 +93,30 @@ class Driver {
|
|
|
86
93
|
this._networkMonitor = new NetworkMonitor(this._targetManager);
|
|
87
94
|
await this._networkMonitor.enable();
|
|
88
95
|
this.defaultSession = this._targetManager.rootSession();
|
|
96
|
+
this.listenForCrashes();
|
|
89
97
|
this._executionContext = new ExecutionContext(this.defaultSession);
|
|
90
98
|
this._fetcher = new Fetcher(this.defaultSession);
|
|
91
99
|
log.timeEnd(status);
|
|
92
100
|
}
|
|
93
101
|
|
|
102
|
+
/**
|
|
103
|
+
* If the target crashes, we can't continue gathering.
|
|
104
|
+
*
|
|
105
|
+
* FWIW, if the target unexpectedly detaches (eg the user closed the tab), pptr will
|
|
106
|
+
* catch that and reject into our this._cdpSession.send, which we'll alrady handle appropriately
|
|
107
|
+
* @return {void}
|
|
108
|
+
*/
|
|
109
|
+
listenForCrashes() {
|
|
110
|
+
this.defaultSession.on('Inspector.targetCrashed', async _ => {
|
|
111
|
+
log.error('Driver', 'Inspector.targetCrashed');
|
|
112
|
+
// Manually detach so no more CDP traffic is attempted.
|
|
113
|
+
// Don't await, else our rejection will be a 'Target closed' protocol error on cross-talk CDP calls.
|
|
114
|
+
void this.defaultSession.dispose();
|
|
115
|
+
this.fatalRejection.rej(new LighthouseError(LighthouseError.errors.TARGET_CRASHED));
|
|
116
|
+
});
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
|
|
94
120
|
/** @return {Promise<void>} */
|
|
95
121
|
async disconnect() {
|
|
96
122
|
if (this.defaultSession === throwingSession) return;
|
|
@@ -92,7 +92,9 @@ async function _navigate(navigationContext) {
|
|
|
92
92
|
return {requestedUrl, mainDocumentUrl, navigationError: undefined};
|
|
93
93
|
} catch (err) {
|
|
94
94
|
if (!(err instanceof LighthouseError)) throw err;
|
|
95
|
-
if (err.code !== 'NO_FCP' && err.code !== 'PAGE_HUNG'
|
|
95
|
+
if (err.code !== 'NO_FCP' && err.code !== 'PAGE_HUNG' && err.code !== 'TARGET_CRASHED') {
|
|
96
|
+
throw err;
|
|
97
|
+
}
|
|
96
98
|
if (typeof requestor !== 'string') throw err;
|
|
97
99
|
|
|
98
100
|
// TODO: Make the urls optional here so we don't need to throw an error with a callback requestor.
|
package/core/gather/session.js
CHANGED
|
@@ -100,6 +100,7 @@ class ProtocolSession extends CrdpEventEmitter {
|
|
|
100
100
|
/** @type {NodeJS.Timer|undefined} */
|
|
101
101
|
let timeout;
|
|
102
102
|
const timeoutPromise = new Promise((resolve, reject) => {
|
|
103
|
+
// Unexpected setTimeout invocation to preserve the error stack. https://github.com/GoogleChrome/lighthouse/issues/13332
|
|
103
104
|
// eslint-disable-next-line max-len
|
|
104
105
|
timeout = setTimeout(reject, timeoutMs, new LighthouseError(LighthouseError.errors.PROTOCOL_TIMEOUT, {
|
|
105
106
|
protocolMethod: method,
|
|
@@ -139,7 +140,7 @@ class ProtocolSession extends CrdpEventEmitter {
|
|
|
139
140
|
async dispose() {
|
|
140
141
|
// @ts-expect-error Puppeteer expects the handler params to be type `unknown`
|
|
141
142
|
this._cdpSession.off('*', this._handleProtocolEvent);
|
|
142
|
-
await this._cdpSession.detach();
|
|
143
|
+
await this._cdpSession.detach().catch(e => log.verbose('session', 'detach failed', e.message));
|
|
143
144
|
}
|
|
144
145
|
}
|
|
145
146
|
|
package/core/lib/emulation.js
CHANGED
|
@@ -128,7 +128,7 @@ function enableNetworkThrottling(session, throttlingSettings) {
|
|
|
128
128
|
* @return {Promise<void>}
|
|
129
129
|
*/
|
|
130
130
|
function clearNetworkThrottling(session) {
|
|
131
|
-
return session.
|
|
131
|
+
return session.sendCommandAndIgnore('Network.emulateNetworkConditions', NO_THROTTLING_METRICS);
|
|
132
132
|
}
|
|
133
133
|
|
|
134
134
|
/**
|
|
@@ -146,7 +146,7 @@ function enableCPUThrottling(session, throttlingSettings) {
|
|
|
146
146
|
* @return {Promise<void>}
|
|
147
147
|
*/
|
|
148
148
|
function clearCPUThrottling(session) {
|
|
149
|
-
return session.
|
|
149
|
+
return session.sendCommandAndIgnore('Emulation.setCPUThrottlingRate', NO_CPU_THROTTLE_METRICS);
|
|
150
150
|
}
|
|
151
151
|
|
|
152
152
|
export {
|
package/core/lib/lh-error.d.ts
CHANGED
|
@@ -105,6 +105,7 @@ export namespace UIStrings {
|
|
|
105
105
|
const missingRequiredArtifact: string;
|
|
106
106
|
const erroredRequiredArtifact: string;
|
|
107
107
|
const oldChromeDoesNotSupportFeature: string;
|
|
108
|
+
const targetCrashed: string;
|
|
108
109
|
}
|
|
109
110
|
declare namespace ERRORS {
|
|
110
111
|
namespace NO_SPEEDLINE_FRAMES {
|
|
@@ -340,6 +341,14 @@ declare namespace ERRORS {
|
|
|
340
341
|
import message_30 = UIStrings.erroredRequiredArtifact;
|
|
341
342
|
export { message_30 as message };
|
|
342
343
|
}
|
|
344
|
+
namespace TARGET_CRASHED {
|
|
345
|
+
const code_31: string;
|
|
346
|
+
export { code_31 as code };
|
|
347
|
+
import message_31 = UIStrings.targetCrashed;
|
|
348
|
+
export { message_31 as message };
|
|
349
|
+
const lhrRuntimeError_22: boolean;
|
|
350
|
+
export { lhrRuntimeError_22 as lhrRuntimeError };
|
|
351
|
+
}
|
|
343
352
|
}
|
|
344
353
|
export {};
|
|
345
354
|
//# sourceMappingURL=lh-error.d.ts.map
|
package/core/lib/lh-error.js
CHANGED
|
@@ -91,6 +91,9 @@ const UIStrings = {
|
|
|
91
91
|
* @example {Largest Contentful Paint} featureName
|
|
92
92
|
* */
|
|
93
93
|
oldChromeDoesNotSupportFeature: 'This version of Chrome is too old to support \'{featureName}\'. Use a newer version to see full results.',
|
|
94
|
+
|
|
95
|
+
/** Error message explaining that the browser tab that Lighthouse is inspecting has crashed. */
|
|
96
|
+
targetCrashed: 'Browser tab has unexpectedly crashed.',
|
|
94
97
|
};
|
|
95
98
|
|
|
96
99
|
const str_ = i18n.createIcuMessageFn(import.meta.url, UIStrings);
|
|
@@ -423,6 +426,13 @@ const ERRORS = {
|
|
|
423
426
|
message: UIStrings.erroredRequiredArtifact,
|
|
424
427
|
},
|
|
425
428
|
|
|
429
|
+
/** The page has crashed and will no longer respond to 99% of CDP commmands. */
|
|
430
|
+
TARGET_CRASHED: {
|
|
431
|
+
code: 'TARGET_CRASHED',
|
|
432
|
+
message: UIStrings.targetCrashed,
|
|
433
|
+
lhrRuntimeError: true,
|
|
434
|
+
},
|
|
435
|
+
|
|
426
436
|
// Hey! When adding a new error type, update lighthouse-result.proto too.
|
|
427
437
|
// Only necessary for runtime errors, which come from artifacts or pageLoadErrors.
|
|
428
438
|
};
|
|
@@ -150,6 +150,9 @@ function getPageLoadError(navigationError, context) {
|
|
|
150
150
|
let finalRecord;
|
|
151
151
|
if (mainRecord) {
|
|
152
152
|
finalRecord = NetworkAnalyzer.resolveRedirects(mainRecord);
|
|
153
|
+
} else {
|
|
154
|
+
// We have no network requests to process, use the navError
|
|
155
|
+
return navigationError;
|
|
153
156
|
}
|
|
154
157
|
|
|
155
158
|
if (finalRecord?.mimeType === XHTML_MIME_TYPE) {
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "lighthouse",
|
|
3
3
|
"type": "module",
|
|
4
|
-
"version": "11.7.0-dev.
|
|
4
|
+
"version": "11.7.0-dev.20240416",
|
|
5
5
|
"description": "Automated auditing, performance metrics, and best practices for the web.",
|
|
6
6
|
"main": "./core/index.js",
|
|
7
7
|
"bin": {
|
|
@@ -165,7 +165,7 @@
|
|
|
165
165
|
"pako": "^2.0.3",
|
|
166
166
|
"preact": "^10.7.2",
|
|
167
167
|
"pretty-json-stringify": "^0.0.2",
|
|
168
|
-
"puppeteer": "^22.5
|
|
168
|
+
"puppeteer": "^22.6.5",
|
|
169
169
|
"resolve": "^1.22.1",
|
|
170
170
|
"rollup": "^2.52.7",
|
|
171
171
|
"rollup-plugin-polyfill-node": "^0.12.0",
|
|
@@ -199,7 +199,7 @@
|
|
|
199
199
|
"open": "^8.4.0",
|
|
200
200
|
"parse-cache-control": "1.0.1",
|
|
201
201
|
"ps-list": "^8.0.0",
|
|
202
|
-
"puppeteer-core": "^22.5
|
|
202
|
+
"puppeteer-core": "^22.6.5",
|
|
203
203
|
"robots-parser": "^3.0.1",
|
|
204
204
|
"semver": "^5.3.0",
|
|
205
205
|
"speedline-core": "^1.4.3",
|
|
@@ -2471,6 +2471,9 @@
|
|
|
2471
2471
|
"core/lib/lh-error.js | requestContentTimeout": {
|
|
2472
2472
|
"message": "Fetching resource content has exceeded the allotted time"
|
|
2473
2473
|
},
|
|
2474
|
+
"core/lib/lh-error.js | targetCrashed": {
|
|
2475
|
+
"message": "Browser tab has unexpectedly crashed."
|
|
2476
|
+
},
|
|
2474
2477
|
"core/lib/lh-error.js | urlInvalid": {
|
|
2475
2478
|
"message": "The URL you have provided appears to be invalid."
|
|
2476
2479
|
},
|
|
@@ -2471,6 +2471,9 @@
|
|
|
2471
2471
|
"core/lib/lh-error.js | requestContentTimeout": {
|
|
2472
2472
|
"message": "F̂ét̂ćĥín̂ǵ r̂éŝóûŕĉé ĉón̂t́êńt̂ h́âś êx́ĉéêd́êd́ t̂h́ê ál̂ĺôt́t̂éd̂ t́îḿê"
|
|
2473
2473
|
},
|
|
2474
|
+
"core/lib/lh-error.js | targetCrashed": {
|
|
2475
|
+
"message": "B̂ŕôẃŝér̂ t́âb́ ĥáŝ ún̂éx̂ṕêćt̂éd̂ĺŷ ćr̂áŝh́êd́."
|
|
2476
|
+
},
|
|
2474
2477
|
"core/lib/lh-error.js | urlInvalid": {
|
|
2475
2478
|
"message": "T̂h́ê ÚR̂Ĺ ŷóû h́âv́ê ṕr̂óv̂íd̂éd̂ áp̂ṕêár̂ś t̂ó b̂é îńv̂ál̂íd̂."
|
|
2476
2479
|
},
|
package/types/gatherer.d.ts
CHANGED
|
@@ -49,6 +49,8 @@ declare module Gatherer {
|
|
|
49
49
|
off(event: 'protocolevent', callback: (payload: Protocol.RawEventMessage) => void): void
|
|
50
50
|
};
|
|
51
51
|
networkMonitor: NetworkMonitor;
|
|
52
|
+
listenForCrashes: (() => void);
|
|
53
|
+
fatalRejection: {promise: Promise<never>, rej: (reason: Error) => void}
|
|
52
54
|
}
|
|
53
55
|
|
|
54
56
|
interface Context<TDependencies extends DependencyKey = DefaultDependenciesKey> {
|