lighthouse 9.0.0 → 9.1.0
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/changelog.md +5681 -0
- package/dist/report/bundle.esm.js +36 -7
- package/dist/report/flow.js +97 -30
- package/dist/report/standalone.js +5 -5
- package/flow-report/api.ts +18 -0
- package/flow-report/assets/standalone-flow-template.html +2 -5
- package/flow-report/{standalone-flow.tsx → clients/standalone.ts} +5 -6
- package/flow-report/src/app.tsx +13 -8
- package/flow-report/src/sidebar/flow.tsx +1 -3
- package/flow-report/src/sidebar/sidebar.tsx +1 -3
- package/flow-report/src/topbar.tsx +24 -18
- package/flow-report/src/util.ts +35 -1
- package/flow-report/src/wrappers/category-score.tsx +9 -24
- package/flow-report/src/wrappers/markdown.tsx +4 -13
- package/flow-report/src/wrappers/report.tsx +26 -57
- package/flow-report/src/wrappers/styles.tsx +16 -0
- package/flow-report/test/setup/env-setup.ts +11 -5
- package/flow-report/test/summary/category-test.tsx +3 -6
- package/flow-report/test/summary/summary-test.tsx +3 -6
- package/flow-report/test/topbar-test.tsx +30 -14
- package/flow-report/test/util-test.tsx +38 -1
- package/flow-report/test/wrappers/category-score-test.tsx +3 -6
- package/flow-report/test/wrappers/markdown-test.tsx +1 -13
- package/flow-report/types/flow-report.d.ts +4 -0
- package/lighthouse-cli/cli-flags.js +4 -4
- package/lighthouse-cli/test/smokehouse/frontends/smokehouse-bin.js +0 -0
- package/lighthouse-core/audits/audit.js +39 -7
- package/lighthouse-core/audits/byte-efficiency/legacy-javascript.js +1 -26
- package/lighthouse-core/audits/deprecations.js +10 -14
- package/lighthouse-core/audits/dobetterweb/geolocation-on-start.js +5 -4
- package/lighthouse-core/audits/dobetterweb/js-libraries.js +1 -1
- package/lighthouse-core/audits/dobetterweb/no-document-write.js +6 -4
- package/lighthouse-core/audits/dobetterweb/notification-on-start.js +6 -4
- package/lighthouse-core/audits/dobetterweb/uses-passive-event-listeners.js +6 -4
- package/lighthouse-core/audits/errors-in-console.js +7 -4
- package/lighthouse-core/audits/installable-manifest.js +11 -4
- package/lighthouse-core/audits/no-unload-listeners.js +9 -10
- package/lighthouse-core/audits/seo/canonical.js +0 -14
- package/lighthouse-core/audits/seo/font-size.js +4 -2
- package/lighthouse-core/audits/seo/is-crawlable.js +2 -2
- package/lighthouse-core/audits/third-party-summary.js +2 -2
- package/lighthouse-core/audits/valid-source-maps.js +1 -1
- package/lighthouse-core/audits/violation-audit.js +10 -3
- package/lighthouse-core/computed/js-bundles.js +1 -3
- package/lighthouse-core/fraggle-rock/gather/navigation-runner.js +3 -3
- package/lighthouse-core/gather/driver/execution-context.js +1 -1
- package/lighthouse-core/gather/gatherers/full-page-screenshot.js +7 -7
- package/lighthouse-core/gather/gatherers/stacks.js +0 -3
- package/lighthouse-core/lib/page-functions.js +2 -1
- package/lighthouse-core/lib/stack-packs.js +12 -8
- package/lighthouse-core/lib/statistics.js +26 -39
- package/lighthouse-core/runner.js +7 -7
- package/lighthouse-core/scripts/package.json +4 -0
- package/package.json +4 -6
- package/report/assets/styles.css +2 -2
- package/report/generator/file-namer.js +12 -1
- package/report/generator/flow-report-assets.js +2 -1
- package/report/generator/report-assets.js +2 -5
- package/report/generator/report-generator.js +0 -1
- package/report/renderer/api.js +49 -1
- package/report/renderer/category-renderer.js +17 -3
- package/report/renderer/components.js +1 -1
- package/report/renderer/report-renderer.js +5 -1
- package/report/renderer/report-ui-features.js +1 -1
- package/report/renderer/swap-locale-feature.js +0 -5
- package/report/test/clients/bundle-test.js +2 -2
- package/report/test/renderer/category-renderer-test.js +28 -0
- package/report/test/renderer/report-renderer-axe-test.js +2 -2
- package/report/test/renderer/report-renderer-test.js +29 -0
- package/report/test/renderer/report-ui-features-test.js +0 -1
- package/report/types/report-renderer.d.ts +7 -0
- package/shared/localization/locales/ar-XB.json +0 -3
- package/shared/localization/locales/ar.json +0 -3
- package/shared/localization/locales/bg.json +17 -20
- package/shared/localization/locales/ca.json +0 -3
- package/shared/localization/locales/cs.json +0 -3
- package/shared/localization/locales/da.json +16 -19
- package/shared/localization/locales/de.json +0 -3
- package/shared/localization/locales/el.json +0 -3
- package/shared/localization/locales/en-GB.json +19 -22
- package/shared/localization/locales/en-US.json +33 -3
- package/shared/localization/locales/en-XA.json +0 -3
- package/shared/localization/locales/en-XL.json +33 -3
- package/shared/localization/locales/es-419.json +17 -20
- package/shared/localization/locales/es.json +0 -3
- package/shared/localization/locales/fi.json +16 -19
- package/shared/localization/locales/fil.json +33 -36
- package/shared/localization/locales/fr.json +16 -19
- package/shared/localization/locales/he.json +16 -19
- package/shared/localization/locales/hi.json +0 -3
- package/shared/localization/locales/hr.json +16 -19
- package/shared/localization/locales/hu.json +16 -19
- package/shared/localization/locales/id.json +16 -19
- package/shared/localization/locales/it.json +37 -40
- package/shared/localization/locales/ja.json +16 -19
- package/shared/localization/locales/ko.json +16 -19
- package/shared/localization/locales/lt.json +17 -20
- package/shared/localization/locales/lv.json +0 -3
- package/shared/localization/locales/nl.json +16 -19
- package/shared/localization/locales/no.json +17 -20
- package/shared/localization/locales/pl.json +16 -19
- package/shared/localization/locales/pt-PT.json +37 -40
- package/shared/localization/locales/pt.json +16 -19
- package/shared/localization/locales/ro.json +0 -3
- package/shared/localization/locales/ru.json +16 -19
- package/shared/localization/locales/sk.json +0 -3
- package/shared/localization/locales/sl.json +0 -3
- package/shared/localization/locales/sr-Latn.json +16 -19
- package/shared/localization/locales/sr.json +16 -19
- package/shared/localization/locales/sv.json +17 -20
- package/shared/localization/locales/ta.json +0 -3
- package/shared/localization/locales/te.json +0 -3
- package/shared/localization/locales/th.json +0 -3
- package/shared/localization/locales/tr.json +0 -3
- package/shared/localization/locales/uk.json +17 -20
- package/shared/localization/locales/vi.json +0 -3
- package/shared/localization/locales/zh-HK.json +0 -3
- package/shared/localization/locales/zh-TW.json +0 -3
- package/shared/localization/locales/zh.json +0 -3
- package/third-party/chromium-synchronization/installability-errors-test.js +1 -2
- package/third-party/snyk/snapshot.json +4 -1
- package/types/externs.d.ts +5 -24
- package/flow-report/src/wrappers/report-renderer.tsx +0 -53
|
@@ -5,11 +5,12 @@
|
|
|
5
5
|
*/
|
|
6
6
|
|
|
7
7
|
import {jest} from '@jest/globals';
|
|
8
|
+
import {render} from '@testing-library/preact';
|
|
8
9
|
import {renderHook} from '@testing-library/preact-hooks';
|
|
9
10
|
import {FunctionComponent} from 'preact';
|
|
10
11
|
import {act} from 'preact/test-utils';
|
|
11
12
|
|
|
12
|
-
import {FlowResultContext, useHashState} from '../src/util';
|
|
13
|
+
import {FlowResultContext, useExternalRenderer, useHashState} from '../src/util';
|
|
13
14
|
import {flowResult} from './sample-flow';
|
|
14
15
|
|
|
15
16
|
let wrapper: FunctionComponent;
|
|
@@ -94,3 +95,39 @@ describe('useHashState', () => {
|
|
|
94
95
|
expect(result.current).toBeNull();
|
|
95
96
|
});
|
|
96
97
|
});
|
|
98
|
+
|
|
99
|
+
describe('useExternalRenderer', () => {
|
|
100
|
+
it('attaches DOM subtree of render callback', () => {
|
|
101
|
+
const Container: FunctionComponent = () => {
|
|
102
|
+
const ref = useExternalRenderer<HTMLDivElement>(() => {
|
|
103
|
+
const el = document.createElement('div');
|
|
104
|
+
el.textContent = 'Some text';
|
|
105
|
+
return el;
|
|
106
|
+
});
|
|
107
|
+
return <div ref={ref}/>;
|
|
108
|
+
};
|
|
109
|
+
|
|
110
|
+
const root = render(<Container/>);
|
|
111
|
+
|
|
112
|
+
expect(root.getByText('Some text')).toBeTruthy();
|
|
113
|
+
});
|
|
114
|
+
|
|
115
|
+
it('re-renders DOM subtree when input changes', () => {
|
|
116
|
+
const Container: FunctionComponent<{text: string}> = ({text}) => {
|
|
117
|
+
const ref = useExternalRenderer<HTMLDivElement>(() => {
|
|
118
|
+
const el = document.createElement('div');
|
|
119
|
+
el.textContent = text;
|
|
120
|
+
return el;
|
|
121
|
+
}, [text]);
|
|
122
|
+
return <div ref={ref}/>;
|
|
123
|
+
};
|
|
124
|
+
|
|
125
|
+
const root = render(<Container text="Some text"/>);
|
|
126
|
+
|
|
127
|
+
expect(root.getByText('Some text')).toBeTruthy();
|
|
128
|
+
|
|
129
|
+
root.rerender(<Container text="New text"/>);
|
|
130
|
+
|
|
131
|
+
expect(root.getByText('New text')).toBeTruthy();
|
|
132
|
+
});
|
|
133
|
+
});
|
|
@@ -9,7 +9,6 @@ import {render} from '@testing-library/preact';
|
|
|
9
9
|
|
|
10
10
|
import {CategoryScore} from '../../src/wrappers/category-score';
|
|
11
11
|
import {FlowResultContext} from '../../src/util';
|
|
12
|
-
import {ReportRendererProvider} from '../../src/wrappers/report-renderer';
|
|
13
12
|
import {I18nProvider} from '../../src/i18n/i18n';
|
|
14
13
|
import {flowResult} from '../sample-flow';
|
|
15
14
|
|
|
@@ -18,11 +17,9 @@ let wrapper: FunctionComponent;
|
|
|
18
17
|
beforeEach(() => {
|
|
19
18
|
wrapper = ({children}) => (
|
|
20
19
|
<FlowResultContext.Provider value={flowResult}>
|
|
21
|
-
<
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
</I18nProvider>
|
|
25
|
-
</ReportRendererProvider>
|
|
20
|
+
<I18nProvider>
|
|
21
|
+
{children}
|
|
22
|
+
</I18nProvider>
|
|
26
23
|
</FlowResultContext.Provider>
|
|
27
24
|
);
|
|
28
25
|
});
|
|
@@ -5,24 +5,12 @@
|
|
|
5
5
|
*/
|
|
6
6
|
|
|
7
7
|
import {render} from '@testing-library/preact';
|
|
8
|
-
import {FunctionComponent} from 'preact';
|
|
9
8
|
|
|
10
9
|
import {Markdown} from '../../src/wrappers/markdown';
|
|
11
|
-
import {ReportRendererProvider} from '../../src/wrappers/report-renderer';
|
|
12
|
-
|
|
13
|
-
let wrapper: FunctionComponent;
|
|
14
|
-
|
|
15
|
-
beforeEach(() => {
|
|
16
|
-
wrapper = ({children}) => (
|
|
17
|
-
<ReportRendererProvider>
|
|
18
|
-
{children}
|
|
19
|
-
</ReportRendererProvider>
|
|
20
|
-
);
|
|
21
|
-
});
|
|
22
10
|
|
|
23
11
|
describe('Markdown', () => {
|
|
24
12
|
it('renders markdown text', () => {
|
|
25
|
-
const root = render(<Markdown text="Some `fancy` text"
|
|
13
|
+
const root = render(<Markdown text="Some `fancy` text"/>);
|
|
26
14
|
const text = root.getByText(/^Some.*text$/);
|
|
27
15
|
expect(text.innerHTML).toEqual('Some <code>fancy</code> text');
|
|
28
16
|
});
|
|
@@ -20,6 +20,10 @@ declare global {
|
|
|
20
20
|
// Expose global types in LH namespace.
|
|
21
21
|
module LH {
|
|
22
22
|
export type ConfigSettings = Settings.ConfigSettings;
|
|
23
|
+
export interface FlowReportOptions {
|
|
24
|
+
getReportHtml?: (flowResult: FlowResult_) => string;
|
|
25
|
+
saveAsGist?: (flowResult: FlowResult_) => void;
|
|
26
|
+
}
|
|
23
27
|
|
|
24
28
|
export import FlowResult = FlowResult_;
|
|
25
29
|
}
|
|
@@ -225,7 +225,7 @@ function getFlags(manualArgv, options = {}) {
|
|
|
225
225
|
.options({
|
|
226
226
|
'output': {
|
|
227
227
|
type: 'array',
|
|
228
|
-
default: /** @type {
|
|
228
|
+
default: /** @type {const} */ (['html']),
|
|
229
229
|
coerce: coerceOutput,
|
|
230
230
|
describe: 'Reporter for the results, supports multiple values. choices: "json", "html", "csv"',
|
|
231
231
|
},
|
|
@@ -309,9 +309,9 @@ function getFlags(manualArgv, options = {}) {
|
|
|
309
309
|
})
|
|
310
310
|
|
|
311
311
|
// Choices added outside of `options()` and cast so tsc picks them up.
|
|
312
|
-
.choices('form-factor', /** @type {
|
|
313
|
-
.choices('throttling-method', /** @type {
|
|
314
|
-
.choices('preset', /** @type {
|
|
312
|
+
.choices('form-factor', /** @type {const} */ (['mobile', 'desktop']))
|
|
313
|
+
.choices('throttling-method', /** @type {const} */ (['devtools', 'provided', 'simulate']))
|
|
314
|
+
.choices('preset', /** @type {const} */ (['perf', 'experimental', 'desktop']))
|
|
315
315
|
|
|
316
316
|
.check(argv => {
|
|
317
317
|
// Lighthouse doesn't need a URL if...
|
|
File without changes
|
|
@@ -233,21 +233,53 @@ class Audit {
|
|
|
233
233
|
}
|
|
234
234
|
|
|
235
235
|
/**
|
|
236
|
-
* @param {LH.Artifacts.
|
|
237
|
-
* @
|
|
236
|
+
* @param {LH.Artifacts.Bundle} bundle
|
|
237
|
+
* @param {number} generatedLine
|
|
238
|
+
* @param {number} generatedColumn
|
|
239
|
+
* @return {LH.Audit.Details.SourceLocationValue['original']}
|
|
238
240
|
*/
|
|
239
|
-
static
|
|
240
|
-
|
|
241
|
+
static _findOriginalLocation(bundle, generatedLine, generatedColumn) {
|
|
242
|
+
const entry = bundle?.map.findEntry(generatedLine, generatedColumn);
|
|
243
|
+
if (!entry) return;
|
|
241
244
|
|
|
245
|
+
return {
|
|
246
|
+
file: entry.sourceURL || '',
|
|
247
|
+
line: entry.sourceLineNumber || 0,
|
|
248
|
+
column: entry.sourceColumnNumber || 0,
|
|
249
|
+
};
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
/**
|
|
253
|
+
* @param {string} url
|
|
254
|
+
* @param {number} line 0-indexed
|
|
255
|
+
* @param {number} column 0-indexed
|
|
256
|
+
* @param {LH.Artifacts.Bundle=} bundle
|
|
257
|
+
* @return {LH.Audit.Details.SourceLocationValue}
|
|
258
|
+
*/
|
|
259
|
+
static makeSourceLocation(url, line, column, bundle) {
|
|
242
260
|
return {
|
|
243
261
|
type: 'source-location',
|
|
244
|
-
url
|
|
262
|
+
url,
|
|
245
263
|
urlProvider: 'network',
|
|
246
|
-
line
|
|
247
|
-
column
|
|
264
|
+
line,
|
|
265
|
+
column,
|
|
266
|
+
original: bundle && this._findOriginalLocation(bundle, line, column),
|
|
248
267
|
};
|
|
249
268
|
}
|
|
250
269
|
|
|
270
|
+
/**
|
|
271
|
+
* @param {LH.Artifacts.ConsoleMessage} entry
|
|
272
|
+
* @param {LH.Artifacts.Bundle=} bundle
|
|
273
|
+
* @return {LH.Audit.Details.SourceLocationValue | undefined}
|
|
274
|
+
*/
|
|
275
|
+
static makeSourceLocationFromConsoleMessage(entry, bundle) {
|
|
276
|
+
if (!entry.url) return;
|
|
277
|
+
|
|
278
|
+
const line = entry.lineNumber || 0;
|
|
279
|
+
const column = entry.columnNumber || 0;
|
|
280
|
+
return this.makeSourceLocation(entry.url, line, column, bundle);
|
|
281
|
+
}
|
|
282
|
+
|
|
251
283
|
/**
|
|
252
284
|
* @param {number|null} score
|
|
253
285
|
* @param {LH.Audit.ScoreDisplayMode} scoreDisplayMode
|
|
@@ -397,23 +397,6 @@ class LegacyJavascript extends ByteEfficiencyAudit {
|
|
|
397
397
|
return transferRatio;
|
|
398
398
|
}
|
|
399
399
|
|
|
400
|
-
/**
|
|
401
|
-
* @param {LH.Artifacts.Bundle} bundle
|
|
402
|
-
* @param {number} generatedLine
|
|
403
|
-
* @param {number} generatedColumn
|
|
404
|
-
* @return {LH.Audit.Details.SourceLocationValue['original']}
|
|
405
|
-
*/
|
|
406
|
-
static _findOriginalLocation(bundle, generatedLine, generatedColumn) {
|
|
407
|
-
const entry = bundle && bundle.map.findEntry(generatedLine, generatedColumn);
|
|
408
|
-
if (!entry) return;
|
|
409
|
-
|
|
410
|
-
return {
|
|
411
|
-
file: entry.sourceURL || '',
|
|
412
|
-
line: entry.sourceLineNumber || 0,
|
|
413
|
-
column: entry.sourceColumnNumber || 0,
|
|
414
|
-
};
|
|
415
|
-
}
|
|
416
|
-
|
|
417
400
|
/**
|
|
418
401
|
* @param {LH.Artifacts} artifacts
|
|
419
402
|
* @param {Array<LH.Artifacts.NetworkRequest>} networkRecords
|
|
@@ -456,18 +439,10 @@ class LegacyJavascript extends ByteEfficiencyAudit {
|
|
|
456
439
|
const bundle = bundles.find(bundle => bundle.script.src === url);
|
|
457
440
|
for (const match of matches) {
|
|
458
441
|
const {name, line, column} = match;
|
|
459
|
-
|
|
460
442
|
/** @type {SubItem} */
|
|
461
443
|
const subItem = {
|
|
462
444
|
signal: name,
|
|
463
|
-
location:
|
|
464
|
-
type: 'source-location',
|
|
465
|
-
url,
|
|
466
|
-
line,
|
|
467
|
-
column,
|
|
468
|
-
original: bundle && this._findOriginalLocation(bundle, line, column),
|
|
469
|
-
urlProvider: 'network',
|
|
470
|
-
},
|
|
445
|
+
location: ByteEfficiencyAudit.makeSourceLocation(url, line, column, bundle),
|
|
471
446
|
};
|
|
472
447
|
item.subItems.items.push(subItem);
|
|
473
448
|
}
|
|
@@ -14,6 +14,7 @@
|
|
|
14
14
|
// TODO: when M97 is sufficiently old, drop support for console messages
|
|
15
15
|
|
|
16
16
|
const Audit = require('./audit.js');
|
|
17
|
+
const JsBundles = require('../computed/js-bundles.js');
|
|
17
18
|
const i18n = require('../lib/i18n/i18n.js');
|
|
18
19
|
|
|
19
20
|
const UIStrings = {
|
|
@@ -47,34 +48,29 @@ class Deprecations extends Audit {
|
|
|
47
48
|
title: str_(UIStrings.title),
|
|
48
49
|
failureTitle: str_(UIStrings.failureTitle),
|
|
49
50
|
description: str_(UIStrings.description),
|
|
50
|
-
requiredArtifacts: ['ConsoleMessages', 'InspectorIssues'],
|
|
51
|
+
requiredArtifacts: ['ConsoleMessages', 'InspectorIssues', 'SourceMaps', 'ScriptElements'],
|
|
51
52
|
};
|
|
52
53
|
}
|
|
53
54
|
|
|
54
55
|
/**
|
|
55
56
|
* @param {LH.Artifacts} artifacts
|
|
56
|
-
* @
|
|
57
|
+
* @param {LH.Audit.Context} context
|
|
58
|
+
* @return {Promise<LH.Audit.Product>}
|
|
57
59
|
*/
|
|
58
|
-
static audit(artifacts) {
|
|
60
|
+
static async audit(artifacts, context) {
|
|
59
61
|
const entries = artifacts.ConsoleMessages;
|
|
62
|
+
const bundles = await JsBundles.request(artifacts, context);
|
|
60
63
|
|
|
61
64
|
let deprecations;
|
|
62
|
-
|
|
63
65
|
if (artifacts.InspectorIssues.deprecations.length) {
|
|
64
66
|
deprecations = artifacts.InspectorIssues.deprecations
|
|
65
67
|
.map(deprecation => {
|
|
68
|
+
const {url, lineNumber, columnNumber} = deprecation.sourceCodeLocation;
|
|
69
|
+
const bundle = bundles.find(bundle => bundle.script.src === url);
|
|
66
70
|
return {
|
|
67
71
|
value: deprecation.message || '',
|
|
68
|
-
|
|
69
|
-
source:
|
|
70
|
-
type: 'source-location',
|
|
71
|
-
url: deprecation.sourceCodeLocation.url,
|
|
72
|
-
urlProvider: 'network',
|
|
73
|
-
line: deprecation.sourceCodeLocation.lineNumber,
|
|
74
|
-
// Protocol.Audits.SourceCodeLocation.columnNumber is 1-indexed,
|
|
75
|
-
// but we use 0-indexed.
|
|
76
|
-
column: deprecation.sourceCodeLocation.columnNumber - 1,
|
|
77
|
-
},
|
|
72
|
+
// Protocol.Audits.SourceCodeLocation.columnNumber is 1-indexed, but we use 0-indexed.
|
|
73
|
+
source: Audit.makeSourceLocation(url, lineNumber, columnNumber - 1, bundle),
|
|
78
74
|
};
|
|
79
75
|
});
|
|
80
76
|
} else {
|
|
@@ -38,17 +38,18 @@ class GeolocationOnStart extends ViolationAudit {
|
|
|
38
38
|
failureTitle: str_(UIStrings.failureTitle),
|
|
39
39
|
description: str_(UIStrings.description),
|
|
40
40
|
supportedModes: ['navigation'],
|
|
41
|
-
requiredArtifacts: ['ConsoleMessages'],
|
|
41
|
+
requiredArtifacts: ['ConsoleMessages', 'SourceMaps', 'ScriptElements'],
|
|
42
42
|
};
|
|
43
43
|
}
|
|
44
44
|
|
|
45
45
|
/**
|
|
46
46
|
* @param {LH.Artifacts} artifacts
|
|
47
|
-
* @
|
|
47
|
+
* @param {LH.Audit.Context} context
|
|
48
|
+
* @return {Promise<LH.Audit.Product>}
|
|
48
49
|
*/
|
|
49
|
-
static audit(artifacts) {
|
|
50
|
+
static async audit(artifacts, context) {
|
|
50
51
|
// 'Only request geolocation information in response to a user gesture.'
|
|
51
|
-
const results = ViolationAudit.getViolationResults(artifacts, /geolocation/);
|
|
52
|
+
const results = await ViolationAudit.getViolationResults(artifacts, context, /geolocation/);
|
|
52
53
|
|
|
53
54
|
/** @type {LH.Audit.Details.Table['headings']} */
|
|
54
55
|
const headings = [
|
|
@@ -61,7 +61,7 @@ class JsLibrariesAudit extends Audit {
|
|
|
61
61
|
const details = Audit.makeTableDetails(headings, libDetails, {});
|
|
62
62
|
|
|
63
63
|
const debugData = {
|
|
64
|
-
type: /** @type {
|
|
64
|
+
type: /** @type {const} */ ('debugdata'),
|
|
65
65
|
stacks: artifacts.Stacks.map(stack => {
|
|
66
66
|
return {
|
|
67
67
|
id: stack.id,
|
|
@@ -54,16 +54,18 @@ class NoDocWriteAudit extends ViolationAudit {
|
|
|
54
54
|
title: str_(UIStrings.title),
|
|
55
55
|
failureTitle: str_(UIStrings.failureTitle),
|
|
56
56
|
description: str_(UIStrings.description),
|
|
57
|
-
requiredArtifacts: ['ConsoleMessages'],
|
|
57
|
+
requiredArtifacts: ['ConsoleMessages', 'SourceMaps', 'ScriptElements'],
|
|
58
58
|
};
|
|
59
59
|
}
|
|
60
60
|
|
|
61
61
|
/**
|
|
62
62
|
* @param {LH.Artifacts} artifacts
|
|
63
|
-
* @
|
|
63
|
+
* @param {LH.Audit.Context} context
|
|
64
|
+
* @return {Promise<LH.Audit.Product>}
|
|
64
65
|
*/
|
|
65
|
-
static audit(artifacts) {
|
|
66
|
-
const results =
|
|
66
|
+
static async audit(artifacts, context) {
|
|
67
|
+
const results =
|
|
68
|
+
await ViolationAudit.getViolationResults(artifacts, context, /document\.write/);
|
|
67
69
|
|
|
68
70
|
/** @type {LH.Audit.Details.Table['headings']} */
|
|
69
71
|
const headings = [
|
|
@@ -38,16 +38,18 @@ class NotificationOnStart extends ViolationAudit {
|
|
|
38
38
|
failureTitle: str_(UIStrings.failureTitle),
|
|
39
39
|
description: str_(UIStrings.description),
|
|
40
40
|
supportedModes: ['navigation'],
|
|
41
|
-
requiredArtifacts: ['ConsoleMessages'],
|
|
41
|
+
requiredArtifacts: ['ConsoleMessages', 'SourceMaps', 'ScriptElements'],
|
|
42
42
|
};
|
|
43
43
|
}
|
|
44
44
|
|
|
45
45
|
/**
|
|
46
46
|
* @param {LH.Artifacts} artifacts
|
|
47
|
-
* @
|
|
47
|
+
* @param {LH.Audit.Context} context
|
|
48
|
+
* @return {Promise<LH.Audit.Product>}
|
|
48
49
|
*/
|
|
49
|
-
static audit(artifacts) {
|
|
50
|
-
const results =
|
|
50
|
+
static async audit(artifacts, context) {
|
|
51
|
+
const results =
|
|
52
|
+
await ViolationAudit.getViolationResults(artifacts, context, /notification permission/);
|
|
51
53
|
|
|
52
54
|
/** @type {LH.Audit.Details.Table['headings']} */
|
|
53
55
|
const headings = [
|
|
@@ -37,16 +37,18 @@ class PassiveEventsAudit extends ViolationAudit {
|
|
|
37
37
|
title: str_(UIStrings.title),
|
|
38
38
|
failureTitle: str_(UIStrings.failureTitle),
|
|
39
39
|
description: str_(UIStrings.description),
|
|
40
|
-
requiredArtifacts: ['ConsoleMessages'],
|
|
40
|
+
requiredArtifacts: ['ConsoleMessages', 'SourceMaps', 'ScriptElements'],
|
|
41
41
|
};
|
|
42
42
|
}
|
|
43
43
|
|
|
44
44
|
/**
|
|
45
45
|
* @param {LH.Artifacts} artifacts
|
|
46
|
-
* @
|
|
46
|
+
* @param {LH.Audit.Context} context
|
|
47
|
+
* @return {Promise<LH.Audit.Product>}
|
|
47
48
|
*/
|
|
48
|
-
static audit(artifacts) {
|
|
49
|
-
const results =
|
|
49
|
+
static async audit(artifacts, context) {
|
|
50
|
+
const results =
|
|
51
|
+
await ViolationAudit.getViolationResults(artifacts, context, /passive event listener/);
|
|
50
52
|
|
|
51
53
|
/** @type {LH.Audit.Details.Table['headings']} */
|
|
52
54
|
const headings = [
|
|
@@ -12,6 +12,7 @@
|
|
|
12
12
|
|
|
13
13
|
const log = require('lighthouse-logger');
|
|
14
14
|
const Audit = require('./audit.js');
|
|
15
|
+
const JsBundles = require('../computed/js-bundles.js');
|
|
15
16
|
const i18n = require('../lib/i18n/i18n.js');
|
|
16
17
|
|
|
17
18
|
const UIStrings = {
|
|
@@ -39,7 +40,7 @@ class ErrorLogs extends Audit {
|
|
|
39
40
|
title: str_(UIStrings.title),
|
|
40
41
|
failureTitle: str_(UIStrings.failureTitle),
|
|
41
42
|
description: str_(UIStrings.description),
|
|
42
|
-
requiredArtifacts: ['ConsoleMessages'],
|
|
43
|
+
requiredArtifacts: ['ConsoleMessages', 'SourceMaps', 'ScriptElements'],
|
|
43
44
|
};
|
|
44
45
|
}
|
|
45
46
|
|
|
@@ -75,20 +76,22 @@ class ErrorLogs extends Audit {
|
|
|
75
76
|
/**
|
|
76
77
|
* @param {LH.Artifacts} artifacts
|
|
77
78
|
* @param {LH.Audit.Context} context
|
|
78
|
-
* @return {LH.Audit.Product}
|
|
79
|
+
* @return {Promise<LH.Audit.Product>}
|
|
79
80
|
*/
|
|
80
|
-
static audit(artifacts, context) {
|
|
81
|
+
static async audit(artifacts, context) {
|
|
81
82
|
/** @type {AuditOptions} */
|
|
82
83
|
const auditOptions = context.options;
|
|
84
|
+
const bundles = await JsBundles.request(artifacts, context);
|
|
83
85
|
|
|
84
86
|
/** @type {Array<{source: string, description: string|undefined, sourceLocation: LH.Audit.Details.SourceLocationValue|undefined}>} */
|
|
85
87
|
const consoleRows = artifacts.ConsoleMessages
|
|
86
88
|
.filter(item => item.level === 'error')
|
|
87
89
|
.map(item => {
|
|
90
|
+
const bundle = bundles.find(bundle => bundle.script.src === item.url);
|
|
88
91
|
return {
|
|
89
92
|
source: item.source,
|
|
90
93
|
description: item.text,
|
|
91
|
-
sourceLocation: Audit.makeSourceLocationFromConsoleMessage(item),
|
|
94
|
+
sourceLocation: Audit.makeSourceLocationFromConsoleMessage(item, bundle),
|
|
92
95
|
};
|
|
93
96
|
});
|
|
94
97
|
|
|
@@ -21,7 +21,7 @@ const UIStrings = {
|
|
|
21
21
|
'columnValue': 'Failure reason',
|
|
22
22
|
/**
|
|
23
23
|
* @description [ICU Syntax] Label for an audit identifying the number of installability errors found in the page.
|
|
24
|
-
|
|
24
|
+
*/
|
|
25
25
|
'displayValue': `{itemCount, plural,
|
|
26
26
|
=1 {1 reason}
|
|
27
27
|
other {# reasons}
|
|
@@ -56,9 +56,9 @@ const UIStrings = {
|
|
|
56
56
|
'manifest-missing-suitable-icon': `Manifest does not contain a suitable icon - PNG, SVG or WebP format of at least {value0}\xa0px is required, the sizes attribute must be set, and the purpose attribute, if set, must include "any".`,
|
|
57
57
|
|
|
58
58
|
/**
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
59
|
+
* @description Error message explaining that the manifest does not supply an icon of the correct format.
|
|
60
|
+
* @example {192} value0
|
|
61
|
+
*/
|
|
62
62
|
'no-acceptable-icon': `No supplied icon is at least {value0}\xa0px square in PNG, SVG or WebP format, with the purpose attribute unset or set to "any"`,
|
|
63
63
|
|
|
64
64
|
/** Error message explaining that the icon could not be downloaded. */
|
|
@@ -95,6 +95,8 @@ const UIStrings = {
|
|
|
95
95
|
'warn-not-offline-capable': `Page does not work offline. The page will not be regarded as installable after Chrome 93, stable release August 2021.`,
|
|
96
96
|
/** Error message explaining that Lighthouse failed while detecting a service worker, and directing the user to try again in a new Chrome. */
|
|
97
97
|
'protocol-timeout': `Lighthouse could not determine if there was a service worker. Please try with a newer version of Chrome.`,
|
|
98
|
+
/** Message logged when the web app has been uninstalled o desktop, signalling that the install banner state is being reset. */
|
|
99
|
+
'pipeline-restarted': 'PWA has been uninstalled and installability checks resetting.',
|
|
98
100
|
};
|
|
99
101
|
/* eslint-enable max-len */
|
|
100
102
|
|
|
@@ -146,6 +148,11 @@ class InstallableManifest extends Audit {
|
|
|
146
148
|
continue;
|
|
147
149
|
}
|
|
148
150
|
|
|
151
|
+
// Filter out errorId 'pipeline-restarted' since it only applies when the PWA is uninstalled.
|
|
152
|
+
if (err.errorId === 'pipeline-restarted') {
|
|
153
|
+
continue;
|
|
154
|
+
}
|
|
155
|
+
|
|
149
156
|
// @ts-expect-error errorIds from protocol should match up against the strings dict
|
|
150
157
|
const matchingString = UIStrings[err.errorId];
|
|
151
158
|
|
|
@@ -6,6 +6,7 @@
|
|
|
6
6
|
'use strict';
|
|
7
7
|
|
|
8
8
|
const Audit = require('./audit.js');
|
|
9
|
+
const JsBundles = require('../computed/js-bundles.js');
|
|
9
10
|
const i18n = require('./../lib/i18n/i18n.js');
|
|
10
11
|
|
|
11
12
|
const UIStrings = {
|
|
@@ -29,15 +30,16 @@ class NoUnloadListeners extends Audit {
|
|
|
29
30
|
title: str_(UIStrings.title),
|
|
30
31
|
failureTitle: str_(UIStrings.failureTitle),
|
|
31
32
|
description: str_(UIStrings.description),
|
|
32
|
-
requiredArtifacts: ['GlobalListeners', 'JsUsage'],
|
|
33
|
+
requiredArtifacts: ['GlobalListeners', 'JsUsage', 'SourceMaps', 'ScriptElements'],
|
|
33
34
|
};
|
|
34
35
|
}
|
|
35
36
|
|
|
36
37
|
/**
|
|
37
38
|
* @param {LH.Artifacts} artifacts
|
|
38
|
-
* @
|
|
39
|
+
* @param {LH.Audit.Context} context
|
|
40
|
+
* @return {Promise<LH.Audit.Product>}
|
|
39
41
|
*/
|
|
40
|
-
static audit(artifacts) {
|
|
42
|
+
static async audit(artifacts, context) {
|
|
41
43
|
const unloadListeners = artifacts.GlobalListeners.filter(l => l.type === 'unload');
|
|
42
44
|
if (!unloadListeners.length) {
|
|
43
45
|
return {
|
|
@@ -45,6 +47,8 @@ class NoUnloadListeners extends Audit {
|
|
|
45
47
|
};
|
|
46
48
|
}
|
|
47
49
|
|
|
50
|
+
const bundles = await JsBundles.request(artifacts, context);
|
|
51
|
+
|
|
48
52
|
/** @type {LH.Audit.Details.Table['headings']} */
|
|
49
53
|
const headings = [
|
|
50
54
|
{key: 'source', itemType: 'source-location', text: str_(i18n.UIStrings.columnSource)},
|
|
@@ -74,14 +78,9 @@ class NoUnloadListeners extends Audit {
|
|
|
74
78
|
};
|
|
75
79
|
}
|
|
76
80
|
|
|
81
|
+
const bundle = bundles.find(bundle => bundle.script.src === url);
|
|
77
82
|
return {
|
|
78
|
-
source:
|
|
79
|
-
type: 'source-location',
|
|
80
|
-
url,
|
|
81
|
-
urlProvider: 'network',
|
|
82
|
-
line: listener.lineNumber,
|
|
83
|
-
column: listener.columnNumber,
|
|
84
|
-
},
|
|
83
|
+
source: Audit.makeSourceLocation(url, listener.lineNumber, listener.columnNumber, bundle),
|
|
85
84
|
};
|
|
86
85
|
});
|
|
87
86
|
|
|
@@ -38,11 +38,6 @@ const UIStrings = {
|
|
|
38
38
|
* @example {https://example.com/} url
|
|
39
39
|
*/
|
|
40
40
|
explanationPointsElsewhere: 'Points to another `hreflang` location ({url})',
|
|
41
|
-
/**
|
|
42
|
-
* @description Explanatory message stating that there was a failure in an audit caused by a URL pointing to a different domain.
|
|
43
|
-
* @example {https://example.com/} url
|
|
44
|
-
* */
|
|
45
|
-
explanationDifferentDomain: 'Points to a different domain ({url})',
|
|
46
41
|
/** Explanatory message stating that the page's canonical URL was pointing to the domain's root URL, which is a common mistake. "points" refers to the action of the 'rel=canonical' referencing another link. "root" refers to the starting/home page of the website. "domain" refers to the registered domain name of the website. */
|
|
47
42
|
explanationRoot: 'Points to the domain\'s root URL (the homepage), ' +
|
|
48
43
|
'instead of an equivalent page of content',
|
|
@@ -174,15 +169,6 @@ class Canonical extends Audit {
|
|
|
174
169
|
};
|
|
175
170
|
}
|
|
176
171
|
|
|
177
|
-
// bing and yahoo don't allow canonical URLs pointing to different domains, it's also
|
|
178
|
-
// a common mistake to publish a page with canonical pointing to e.g. a test domain or localhost
|
|
179
|
-
if (!URL.rootDomainsMatch(canonicalURL, baseURL)) {
|
|
180
|
-
return {
|
|
181
|
-
score: 0,
|
|
182
|
-
explanation: str_(UIStrings.explanationDifferentDomain, {url: canonicalURL.href}),
|
|
183
|
-
};
|
|
184
|
-
}
|
|
185
|
-
|
|
186
172
|
// another common mistake is to have canonical pointing from all pages of the website to its root
|
|
187
173
|
if (
|
|
188
174
|
canonicalURL.origin === baseURL.origin &&
|
|
@@ -196,9 +196,11 @@ function findStyleRuleSource(baseURL, styleDeclaration, parentNode) {
|
|
|
196
196
|
}
|
|
197
197
|
}
|
|
198
198
|
|
|
199
|
-
const
|
|
199
|
+
const source = Audit.makeSourceLocation(stylesheet.sourceURL, line, column);
|
|
200
|
+
source.urlProvider = urlProvider;
|
|
201
|
+
|
|
200
202
|
return {
|
|
201
|
-
source
|
|
203
|
+
source,
|
|
202
204
|
selector,
|
|
203
205
|
};
|
|
204
206
|
}
|
|
@@ -126,9 +126,9 @@ class IsCrawlable extends Audit {
|
|
|
126
126
|
const line = robotsTxt.getMatchingLineNumber(mainResource.url) || 1;
|
|
127
127
|
blockingDirectives.push({
|
|
128
128
|
source: {
|
|
129
|
-
type: /** @type {
|
|
129
|
+
type: /** @type {const} */ ('source-location'),
|
|
130
130
|
url: robotsFileUrl.href,
|
|
131
|
-
urlProvider: /** @type {
|
|
131
|
+
urlProvider: /** @type {const} */ ('network'),
|
|
132
132
|
line: line - 1,
|
|
133
133
|
column: 0,
|
|
134
134
|
},
|
|
@@ -216,12 +216,12 @@ class ThirdPartySummary extends Audit {
|
|
|
216
216
|
return {
|
|
217
217
|
...stats,
|
|
218
218
|
entity: {
|
|
219
|
-
type: /** @type {
|
|
219
|
+
type: /** @type {const} */ ('link'),
|
|
220
220
|
text: entity.name,
|
|
221
221
|
url: entity.homepage || '',
|
|
222
222
|
},
|
|
223
223
|
subItems: {
|
|
224
|
-
type: /** @type {
|
|
224
|
+
type: /** @type {const} */ ('subitems'),
|
|
225
225
|
items: ThirdPartySummary.makeSubItems(entity, summaries, stats),
|
|
226
226
|
},
|
|
227
227
|
};
|
|
@@ -110,7 +110,7 @@ class ValidSourceMaps extends Audit {
|
|
|
110
110
|
scriptUrl: scriptElement.src,
|
|
111
111
|
sourceMapUrl: sourceMap && sourceMap.sourceMapUrl,
|
|
112
112
|
subItems: {
|
|
113
|
-
type: /** @type {
|
|
113
|
+
type: /** @type {const} */ ('subitems'),
|
|
114
114
|
items: errors,
|
|
115
115
|
},
|
|
116
116
|
});
|
|
@@ -6,14 +6,18 @@
|
|
|
6
6
|
'use strict';
|
|
7
7
|
|
|
8
8
|
const Audit = require('./audit.js');
|
|
9
|
+
const JsBundles = require('../computed/js-bundles.js');
|
|
9
10
|
|
|
10
11
|
class ViolationAudit extends Audit {
|
|
11
12
|
/**
|
|
12
13
|
* @param {LH.Artifacts} artifacts
|
|
14
|
+
* @param {LH.Audit.Context} context
|
|
13
15
|
* @param {RegExp} pattern
|
|
14
|
-
* @return {Array<{source: LH.Audit.Details.SourceLocationValue}
|
|
16
|
+
* @return {Promise<Array<{source: LH.Audit.Details.SourceLocationValue}>>}
|
|
15
17
|
*/
|
|
16
|
-
static getViolationResults(artifacts, pattern) {
|
|
18
|
+
static async getViolationResults(artifacts, context, pattern) {
|
|
19
|
+
const bundles = await JsBundles.request(artifacts, context);
|
|
20
|
+
|
|
17
21
|
/**
|
|
18
22
|
* @template T
|
|
19
23
|
* @param {T} value
|
|
@@ -26,7 +30,10 @@ class ViolationAudit extends Audit {
|
|
|
26
30
|
const seen = new Set();
|
|
27
31
|
return artifacts.ConsoleMessages
|
|
28
32
|
.filter(entry => entry.url && entry.source === 'violation' && pattern.test(entry.text))
|
|
29
|
-
.map(
|
|
33
|
+
.map(entry => {
|
|
34
|
+
const bundle = bundles.find(bundle => bundle.script.src === entry.url);
|
|
35
|
+
return Audit.makeSourceLocationFromConsoleMessage(entry, bundle);
|
|
36
|
+
})
|
|
30
37
|
.filter(filterUndefined)
|
|
31
38
|
.filter(source => {
|
|
32
39
|
// Filter out duplicate entries since they are not differentiable to the user
|