-
Notifications
You must be signed in to change notification settings - Fork 9.4k
/
navigation-error.js
191 lines (167 loc) · 7.85 KB
/
navigation-error.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
/**
* @license
* Copyright 2021 Google LLC
* SPDX-License-Identifier: Apache-2.0
*/
import * as Lantern from './lantern/lantern.js';
import {LighthouseError} from './lh-error.js';
import {NetworkRequest} from './network-request.js';
import * as i18n from '../lib/i18n/i18n.js';
const UIStrings = {
/**
* Warning shown in report when the page under test is an XHTML document, which Lighthouse does not directly support
* so we display a warning.
*/
warningXhtml:
'The page MIME type is XHTML: Lighthouse does not explicitly support this document type',
/**
* @description Warning shown in report when the page under test returns an error code, which Lighthouse is not able to reliably load so we display a warning.
* @example {404} errorCode
*/
warningStatusCode: 'Lighthouse was unable to reliably load the page you requested. Make sure' +
' you are testing the correct URL and that the server is properly responding' +
' to all requests. (Status code: {errorCode})',
};
const str_ = i18n.createIcuMessageFn(import.meta.url, UIStrings);
// MIME types are case-insensitive but Chrome normalizes MIME types to be lowercase.
const HTML_MIME_TYPE = 'text/html';
const XHTML_MIME_TYPE = 'application/xhtml+xml';
/**
* Returns an error if the original network request failed or wasn't found.
* @param {LH.Artifacts.NetworkRequest|undefined} mainRecord
* @param {{warnings: Array<string | LH.IcuMessage>, ignoreStatusCode?: LH.Config.Settings['ignoreStatusCode']}} context
* @return {LH.LighthouseError|undefined}
*/
function getNetworkError(mainRecord, context) {
if (!mainRecord) {
return new LighthouseError(LighthouseError.errors.NO_DOCUMENT_REQUEST);
} else if (mainRecord.failed) {
const netErr = mainRecord.localizedFailDescription;
// Match all resolution and DNS failures
// https://cs.chromium.org/chromium/src/net/base/net_error_list.h?rcl=cd62979b
if (
netErr === 'net::ERR_NAME_NOT_RESOLVED' ||
netErr === 'net::ERR_NAME_RESOLUTION_FAILED' ||
netErr.startsWith('net::ERR_DNS_')
) {
return new LighthouseError(LighthouseError.errors.DNS_FAILURE);
} else {
return new LighthouseError(
LighthouseError.errors.FAILED_DOCUMENT_REQUEST, {errorDetails: netErr});
}
} else if (mainRecord.hasErrorStatusCode()) {
if (context.ignoreStatusCode) {
context.warnings.push(str_(UIStrings.warningStatusCode, {errorCode: mainRecord.statusCode}));
} else {
return new LighthouseError(LighthouseError.errors.ERRORED_DOCUMENT_REQUEST, {
statusCode: `${mainRecord.statusCode}`,
});
}
}
}
/**
* Returns an error if we ended up on the `chrome-error` page and all other requests failed.
* @param {LH.Artifacts.NetworkRequest|undefined} mainRecord
* @param {Array<LH.Artifacts.NetworkRequest>} networkRecords
* @return {LH.LighthouseError|undefined}
*/
function getInterstitialError(mainRecord, networkRecords) {
// If we never requested a document, there's no interstitial error, let other cases handle it.
if (!mainRecord) return undefined;
const interstitialRequest = networkRecords.find(record =>
record.documentURL.startsWith('chrome-error://')
);
// If the page didn't end up on a chrome interstitial, there's no error here.
if (!interstitialRequest) return undefined;
// If the main document didn't fail, we didn't end up on an interstitial.
// FIXME: This doesn't handle client-side redirects.
// None of our error-handling deals with this case either because passContext.url doesn't handle non-network redirects.
if (!mainRecord.failed) return undefined;
// If a request failed with the `net::ERR_CERT_*` collection of errors, then it's a security issue.
if (mainRecord.localizedFailDescription.startsWith('net::ERR_CERT')) {
return new LighthouseError(LighthouseError.errors.INSECURE_DOCUMENT_REQUEST, {
securityMessages: mainRecord.localizedFailDescription,
});
}
// If we made it this far, it's a generic Chrome interstitial error.
return new LighthouseError(LighthouseError.errors.CHROME_INTERSTITIAL_ERROR);
}
/**
* Returns an error if we try to load a non-HTML page.
* Expects a network request with all redirects resolved, otherwise the MIME type may be incorrect.
* @param {LH.Artifacts.NetworkRequest|undefined} finalRecord
* @return {LH.LighthouseError|undefined}
*/
function getNonHtmlError(finalRecord) {
// If we never requested a document, there's no doctype error, let other cases handle it.
if (!finalRecord) return undefined;
// If the document request error'd, we should not complain about a bad mimeType.
if (!finalRecord.mimeType || finalRecord.statusCode === -1) return undefined;
// mimeType is determined by the browser, we assume Chrome is determining mimeType correctly,
// independently of 'Content-Type' response headers, and always sending mimeType if well-formed.
if (finalRecord.mimeType !== HTML_MIME_TYPE && finalRecord.mimeType !== XHTML_MIME_TYPE) {
return new LighthouseError(LighthouseError.errors.NOT_HTML, {
mimeType: finalRecord.mimeType,
});
}
return undefined;
}
/**
* Returns an error if the page load should be considered failed, e.g. from a
* main document request failure, a security issue, etc.
* @param {LH.LighthouseError|undefined} navigationError
* @param {{url: string, ignoreStatusCode?: LH.Config.Settings['ignoreStatusCode'], networkRecords: Array<LH.Artifacts.NetworkRequest>, warnings: Array<string | LH.IcuMessage>}} context
* @return {LH.LighthouseError|undefined}
*/
function getPageLoadError(navigationError, context) {
const {url, networkRecords} = context;
/** @type {LH.Artifacts.NetworkRequest|undefined} */
let mainRecord = Lantern.Core.NetworkAnalyzer.findResourceForUrl(networkRecords, url);
// If the url doesn't give us a network request, it's possible we landed on a chrome-error:// page
// In this case, just get the first document request.
if (!mainRecord) {
const documentRequests = networkRecords.filter(record =>
record.resourceType === NetworkRequest.TYPES.Document
);
if (documentRequests.length) {
mainRecord = documentRequests.reduce((min, r) => {
return r.networkRequestTime < min.networkRequestTime ? r : min;
});
}
}
// MIME Type is only set on the final redirected document request. Use this for the HTML check instead of root.
let finalRecord;
if (mainRecord) {
finalRecord = Lantern.Core.NetworkAnalyzer.resolveRedirects(mainRecord);
} else {
// We have no network requests to process, use the navError
return navigationError;
}
if (finalRecord?.mimeType === XHTML_MIME_TYPE) {
context.warnings.push(str_(UIStrings.warningXhtml));
}
const networkError = getNetworkError(mainRecord, context);
const interstitialError = getInterstitialError(mainRecord, networkRecords);
const nonHtmlError = getNonHtmlError(finalRecord);
// We want to special-case the interstitial beyond FAILED_DOCUMENT_REQUEST. See https://github.com/GoogleChrome/lighthouse/pull/8865#issuecomment-497507618
if (interstitialError) return interstitialError;
// Network errors are usually the most specific and provide the best reason for why the page failed to load.
// Prefer networkError over navigationError.
// Example: `DNS_FAILURE` is better than `NO_FCP`.
if (networkError) return networkError;
// Error if page is not HTML.
if (nonHtmlError) return nonHtmlError;
// Navigation errors are rather generic and express some failure of the page to render properly.
// Use `navigationError` as the last resort.
// Example: `NO_FCP`, the page never painted content for some unknown reason.
// Note that the caller possibly gave this to us as undefined, in which case we have determined
// there to be no error with this page load - but there was perhaps a warning.
return navigationError;
}
export {
getNetworkError,
getInterstitialError,
getPageLoadError,
getNonHtmlError,
UIStrings,
};