[go: nahoru, domu]

Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

core(gather-runner): detect Chrome interstitials #9176

Merged
merged 7 commits into from
Jun 12, 2019
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Next Next commit
core(gather-runner): detect Chrome interstitials
  • Loading branch information
patrickhulce committed Jun 10, 2019
commit 4d258c7f52a5d83ebad60438c04ead48e4386775
34 changes: 33 additions & 1 deletion lighthouse-core/gather/gather-runner.js
Original file line number Diff line number Diff line change
Expand Up @@ -167,6 +167,34 @@ class GatherRunner {
}
}

/**
* Returns an error if we ended up on the `chrome-error` page and all other requests failed.
* @param {Array<LH.Artifacts.NetworkRequest>} networkRecords
* @return {LH.LighthouseError|undefined}
*/
static getInterstitialError(networkRecords) {
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;

const pageNetworkRecords = networkRecords
.filter(record => !URL.NON_NETWORK_PROTOCOLS.includes(record.protocol) &&
!record.documentURL.startsWith('chrome-error://'));
// If at least one request didn't fail, there's no error here.
patrickhulce marked this conversation as resolved.
Show resolved Hide resolved
if (!pageNetworkRecords.some(record => record.failed)) return undefined;
brendankenny marked this conversation as resolved.
Show resolved Hide resolved

// If a request failed with the `net::ERR_CERT_*` collection of errors, then it's a security issue.
const insecureRequest = pageNetworkRecords.find(record =>
record.failed && record.localizedFailDescription.startsWith('net::ERR_CERT'));
if (insecureRequest) {
return new LHError(LHError.errors.INSECURE_DOCUMENT_REQUEST, {securityMessages:
patrickhulce marked this conversation as resolved.
Show resolved Hide resolved
insecureRequest.localizedFailDescription});
}

return new LHError(LHError.errors.CHROME_INTERSTITIAL_ERROR);
}

/**
* Returns an error if the page load should be considered failed, e.g. from a
* main document request failure, a security issue, etc.
Expand All @@ -177,10 +205,14 @@ class GatherRunner {
*/
static getPageLoadError(passContext, loadData, navigationError) {
const networkError = GatherRunner.getNetworkError(passContext.url, loadData.networkRecords);
const interstitialError = GatherRunner.getInterstitialError(loadData.networkRecords);

// If the driver was offline, the load will fail without offline support. Ignore this case.
// If the driver was offline, the load will fail without offline support. Ignore this case.
if (!passContext.driver.online) return;

// Interstitials
if (interstitialError) return interstitialError;
patrickhulce marked this conversation as resolved.
Show resolved Hide resolved

// 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`.
Expand Down
9 changes: 9 additions & 0 deletions lighthouse-core/lib/lh-error.js
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,8 @@ const UIStrings = {
pageLoadFailedWithDetails: '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. (Details: {errorDetails})',
/** Error message explaining that the security certificate of the page Lighthouse observed was invalid, so the URL cannot be accessed. securityMessages will be replaced with one or more strings from the browser explaining what was insecure about the page load. */
pageLoadFailedInsecure: 'The URL you have provided does not have a valid security certificate. {securityMessages}',
/** Error message explaining that Chrome prevented the page from loading and displayed an interstitial screen instead, so the URL cannot be accessed. */
pageLoadFailedInterstitial: 'Chrome prevented page load with an interstitial. Make sure you are testing the correct URL and that the server is properly responding to all requests.',
/** Error message explaining that Chrome has encountered an error during the Lighthouse run, and that Chrome should be restarted. */
internalChromeError: 'An internal Chrome error occurred. Please restart Chrome and try re-running Lighthouse.',
/** Error message explaining that fetching the resources of the webpage has taken longer than the maximum time. */
Expand Down Expand Up @@ -180,6 +182,13 @@ const ERRORS = {
message: UIStrings.pageLoadFailedInsecure,
lhrRuntimeError: true,
},
/* Used when any Chrome interstitial error prevents page load.
*/
CHROME_INTERSTITIAL_ERROR: {
code: 'CHROME_INTERSTITIAL_ERROR',
message: UIStrings.pageLoadFailedInterstitial,
lhrRuntimeError: true,
},
/* Used when the page stopped responding and did not finish loading. */
PAGE_HUNG: {
code: 'PAGE_HUNG',
Expand Down
63 changes: 63 additions & 0 deletions lighthouse-core/test/gather/gather-runner-test.js
Original file line number Diff line number Diff line change
Expand Up @@ -820,6 +820,69 @@ describe('GatherRunner', function() {
});
});

describe('#getInterstitialError', () => {
it('passes when the page is loaded', () => {
const url = 'http://the-page.com';
const mainRecord = new NetworkRequest();
mainRecord.url = url;
expect(GatherRunner.getInterstitialError([mainRecord])).toBeUndefined();
});

it('passes when page fails to load normally', () => {
const url = 'http://the-page.com';
const mainRecord = new NetworkRequest();
mainRecord.url = url;
mainRecord.failed = true;
mainRecord.localizedFailDescription = 'foobar';
expect(GatherRunner.getInterstitialError([mainRecord])).toBeUndefined();
});

it('passes when page gets a generic interstitial but somehow also loads everything', () => {
// This case, AFAIK, is impossible, but we'll err on the side of not tanking the run.
const url = 'http://the-page.com';
const mainRecord = new NetworkRequest();
mainRecord.url = url;
const interstitialRecord = new NetworkRequest();
mainRecord.url = 'chrome-error://chromewebdata/';
mainRecord.documentURL = 'chrome-error://chromewebdata/';
const records = [mainRecord, interstitialRecord];
expect(GatherRunner.getInterstitialError(records)).toBeUndefined();
});

it('fails when page gets a generic interstitial', () => {
const url = 'http://the-page.com';
const mainRecord = new NetworkRequest();
mainRecord.url = url;
mainRecord.failed = true;
mainRecord.localizedFailDescription = 'foobar';
patrickhulce marked this conversation as resolved.
Show resolved Hide resolved
const interstitialRecord = new NetworkRequest();
interstitialRecord.url = 'chrome-error://chromewebdata/';
interstitialRecord.documentURL = 'chrome-error://chromewebdata/';
const records = [mainRecord, interstitialRecord];
const error = GatherRunner.getInterstitialError(records);
expect(error.message).toEqual('CHROME_INTERSTITIAL_ERROR');
expect(error.code).toEqual('CHROME_INTERSTITIAL_ERROR');
expect(error.friendlyMessage).toBeDisplayString(/^Chrome prevented/);
});

it('fails when page gets a security interstitial', () => {
const url = 'http://the-page.com';
const mainRecord = new NetworkRequest();
mainRecord.url = url;
mainRecord.failed = true;
mainRecord.localizedFailDescription = 'net::ERR_CERT_COMMON_NAME_INVALID';
const interstitialRecord = new NetworkRequest();
interstitialRecord.url = 'chrome-error://chromewebdata/';
interstitialRecord.documentURL = 'chrome-error://chromewebdata/';
const records = [mainRecord, interstitialRecord];
const error = GatherRunner.getInterstitialError(records);
expect(error.message).toEqual('INSECURE_DOCUMENT_REQUEST');
expect(error.code).toEqual('INSECURE_DOCUMENT_REQUEST');
expect(error.friendlyMessage).toBeDisplayString(/valid security certificate/);
expect(error.friendlyMessage).toBeDisplayString(/net::ERR_CERT_COMMON_NAME_INVALID/);
});
});

describe('artifact collection', () => {
// Make sure our gatherers never execute in parallel
it('runs gatherer lifecycle methods strictly in sequence', async () => {
Expand Down