lighthouse 11.7.0-dev.20240409 → 11.7.0-dev.20240411
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/cli-flags.js +3 -3
- package/cli/test/smokehouse/core-tests.js +0 -16
- package/cli/test/smokehouse/frontends/smokehouse-bin.js +2 -2
- package/core/audits/byte-efficiency/render-blocking-resources.js +25 -26
- package/core/audits/layout-shifts.js +1 -1
- package/core/computed/metrics/lantern-first-contentful-paint.js +3 -2
- package/core/computed/metrics/lantern-first-meaningful-paint.js +3 -2
- package/core/computed/metrics/lantern-interactive.js +3 -2
- package/core/computed/metrics/lantern-largest-contentful-paint.js +3 -2
- package/core/computed/metrics/lantern-max-potential-fid.js +3 -2
- package/core/computed/metrics/lantern-metric.d.ts +5 -0
- package/core/computed/metrics/lantern-metric.js +23 -1
- package/core/computed/metrics/lantern-speed-index.js +3 -2
- package/core/computed/navigation-insights.d.ts +36 -0
- package/core/computed/navigation-insights.js +35 -0
- package/core/computed/trace-engine-result.d.ts +16 -3
- package/core/computed/trace-engine-result.js +19 -14
- package/core/config/default-config.js +6 -51
- package/core/config/filters.js +0 -1
- package/core/gather/gatherers/root-causes.d.ts +2 -2
- package/core/gather/gatherers/root-causes.js +6 -5
- package/core/gather/gatherers/trace-elements.d.ts +2 -2
- package/core/gather/gatherers/trace-elements.js +2 -2
- package/core/lib/lantern/cpu-node.d.ts +7 -1
- package/core/lib/lantern/cpu-node.js +12 -2
- package/core/lib/lantern/lantern-error.d.ts +8 -0
- package/core/lib/lantern/lantern-error.js +9 -0
- package/core/lib/lantern/metrics/first-meaningful-paint.js +2 -4
- package/core/lib/lantern/metrics/interactive.js +1 -1
- package/core/lib/lantern/metrics/largest-contentful-paint.js +3 -3
- package/core/lib/lantern/network-node.js +28 -4
- package/core/lib/lantern/page-dependency-graph.js +14 -2
- package/core/lib/lantern/simulator/simulator.js +1 -1
- package/core/lib/lh-error.js +3 -3
- package/core/runner.js +0 -1
- package/dist/report/bundle.esm.js +11 -95
- package/dist/report/flow.js +16 -100
- package/dist/report/standalone.js +12 -96
- package/flow-report/src/help-dialog.tsx +0 -1
- package/flow-report/src/i18n/i18n.d.ts +0 -4
- package/flow-report/src/i18n/ui-strings.d.ts +0 -1
- package/flow-report/src/i18n/ui-strings.js +0 -2
- package/package.json +1 -1
- package/readme.md +2 -2
- package/report/assets/styles.css +1 -43
- package/report/assets/templates.html +0 -95
- package/report/renderer/components.d.ts +2 -2
- package/report/renderer/components.js +2 -103
- package/report/renderer/report-renderer.js +2 -12
- package/report/renderer/report-ui-features.js +1 -1
- package/report/renderer/report-utils.d.ts +0 -1
- package/report/renderer/report-utils.js +0 -3
- package/shared/localization/locales/ar-XB.json +0 -174
- package/shared/localization/locales/ar.json +0 -174
- package/shared/localization/locales/bg.json +0 -174
- package/shared/localization/locales/ca.json +0 -174
- package/shared/localization/locales/cs.json +0 -174
- package/shared/localization/locales/da.json +0 -174
- package/shared/localization/locales/de.json +0 -174
- package/shared/localization/locales/el.json +0 -174
- package/shared/localization/locales/en-GB.json +0 -174
- package/shared/localization/locales/en-US.json +0 -177
- package/shared/localization/locales/en-XA.json +0 -174
- package/shared/localization/locales/en-XL.json +0 -177
- package/shared/localization/locales/es-419.json +0 -174
- package/shared/localization/locales/es.json +0 -174
- package/shared/localization/locales/fi.json +0 -174
- package/shared/localization/locales/fil.json +0 -174
- package/shared/localization/locales/fr.json +0 -174
- package/shared/localization/locales/he.json +0 -174
- package/shared/localization/locales/hi.json +0 -174
- package/shared/localization/locales/hr.json +0 -174
- package/shared/localization/locales/hu.json +0 -174
- package/shared/localization/locales/id.json +0 -174
- package/shared/localization/locales/it.json +0 -174
- package/shared/localization/locales/ja.json +0 -174
- package/shared/localization/locales/ko.json +0 -174
- package/shared/localization/locales/lt.json +0 -174
- package/shared/localization/locales/lv.json +0 -174
- package/shared/localization/locales/nl.json +0 -174
- package/shared/localization/locales/no.json +0 -174
- package/shared/localization/locales/pl.json +0 -174
- package/shared/localization/locales/pt-PT.json +0 -174
- package/shared/localization/locales/pt.json +0 -174
- package/shared/localization/locales/ro.json +0 -174
- package/shared/localization/locales/ru.json +0 -174
- package/shared/localization/locales/sk.json +0 -174
- package/shared/localization/locales/sl.json +0 -174
- package/shared/localization/locales/sr-Latn.json +0 -174
- package/shared/localization/locales/sr.json +0 -174
- package/shared/localization/locales/sv.json +0 -174
- package/shared/localization/locales/ta.json +0 -174
- package/shared/localization/locales/te.json +0 -174
- package/shared/localization/locales/th.json +0 -174
- package/shared/localization/locales/tr.json +0 -174
- package/shared/localization/locales/uk.json +0 -174
- package/shared/localization/locales/vi.json +0 -174
- package/shared/localization/locales/zh-HK.json +0 -174
- package/shared/localization/locales/zh-TW.json +0 -174
- package/shared/localization/locales/zh.json +0 -174
- package/tsconfig.json +0 -1
- package/types/artifacts.d.ts +15 -90
- package/types/audit.d.ts +0 -10
- package/types/internal/smokehouse.d.ts +1 -1
- package/types/lhr/lhr.d.ts +0 -2
- package/core/audits/content-width.d.ts +0 -17
- package/core/audits/content-width.js +0 -76
- package/core/audits/installable-manifest.d.ts +0 -111
- package/core/audits/installable-manifest.js +0 -248
- package/core/audits/manual/pwa-cross-browser.d.ts +0 -12
- package/core/audits/manual/pwa-cross-browser.js +0 -40
- package/core/audits/manual/pwa-each-page-has-url.d.ts +0 -12
- package/core/audits/manual/pwa-each-page-has-url.js +0 -38
- package/core/audits/manual/pwa-page-transitions.d.ts +0 -12
- package/core/audits/manual/pwa-page-transitions.js +0 -38
- package/core/audits/maskable-icon.d.ts +0 -25
- package/core/audits/maskable-icon.js +0 -70
- package/core/audits/multi-check-audit.d.ts +0 -28
- package/core/audits/multi-check-audit.js +0 -82
- package/core/audits/splash-screen.d.ts +0 -37
- package/core/audits/splash-screen.js +0 -98
- package/core/audits/themed-omnibox.d.ts +0 -42
- package/core/audits/themed-omnibox.js +0 -102
- package/core/computed/manifest-values.d.ts +0 -24
- package/core/computed/manifest-values.js +0 -136
- package/core/gather/gatherers/dobetterweb/tags-blocking-first-paint.d.ts +0 -47
- package/core/gather/gatherers/dobetterweb/tags-blocking-first-paint.js +0 -233
- package/core/gather/gatherers/installability-errors.d.ts +0 -17
- package/core/gather/gatherers/installability-errors.js +0 -56
- package/core/gather/gatherers/web-app-manifest.d.ts +0 -32
- package/core/gather/gatherers/web-app-manifest.js +0 -106
- package/report/renderer/pwa-category-renderer.d.ts +0 -55
- package/report/renderer/pwa-category-renderer.js +0 -184
- package/third-party/chromium-synchronization/installability-errors-test.js +0 -79
|
@@ -1,98 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* @license
|
|
3
|
-
* Copyright 2017 Google LLC
|
|
4
|
-
* SPDX-License-Identifier: Apache-2.0
|
|
5
|
-
*/
|
|
6
|
-
|
|
7
|
-
import MultiCheckAudit from './multi-check-audit.js';
|
|
8
|
-
import {ManifestValues} from '../computed/manifest-values.js';
|
|
9
|
-
import * as i18n from '../lib/i18n/i18n.js';
|
|
10
|
-
|
|
11
|
-
const UIStrings = {
|
|
12
|
-
/** Title of a Lighthouse audit that provides detail on splash screens. This descriptive title is shown to users when the site has a custom splash screen. */
|
|
13
|
-
title: 'Configured for a custom splash screen',
|
|
14
|
-
/** Title of a Lighthouse audit that provides detail on splash screens. This descriptive title is shown to users when the site does not have a custom splash screen. */
|
|
15
|
-
failureTitle: 'Is not configured for a custom splash screen',
|
|
16
|
-
/** Description of a Lighthouse audit that tells the user why they should configure a custom splash screen. This is displayed after a user expands the section to see more. No character length limits. The last sentence starting with 'Learn' becomes link text to additional documentation. */
|
|
17
|
-
description: 'A themed splash screen ensures a high-quality experience when ' +
|
|
18
|
-
'users launch your app from their homescreens. ' +
|
|
19
|
-
'[Learn more about splash screens](https://developer.chrome.com/docs/lighthouse/pwa/splash-screen/).',
|
|
20
|
-
};
|
|
21
|
-
|
|
22
|
-
const str_ = i18n.createIcuMessageFn(import.meta.url, UIStrings);
|
|
23
|
-
|
|
24
|
-
/**
|
|
25
|
-
* @fileoverview
|
|
26
|
-
* Audits if a page is configured for a custom splash screen when launched
|
|
27
|
-
* https://github.com/GoogleChrome/lighthouse/issues/24
|
|
28
|
-
*
|
|
29
|
-
* Requirements:
|
|
30
|
-
* * manifest is not empty
|
|
31
|
-
* * manifest has a valid name
|
|
32
|
-
* * manifest has a valid background_color
|
|
33
|
-
* * manifest has a valid theme_color
|
|
34
|
-
* * manifest contains icon that's a png and size >= 512px
|
|
35
|
-
*/
|
|
36
|
-
|
|
37
|
-
class SplashScreen extends MultiCheckAudit {
|
|
38
|
-
/**
|
|
39
|
-
* @return {LH.Audit.Meta}
|
|
40
|
-
*/
|
|
41
|
-
static get meta() {
|
|
42
|
-
return {
|
|
43
|
-
id: 'splash-screen',
|
|
44
|
-
title: str_(UIStrings.title),
|
|
45
|
-
failureTitle: str_(UIStrings.failureTitle),
|
|
46
|
-
description: str_(UIStrings.description),
|
|
47
|
-
supportedModes: ['navigation'],
|
|
48
|
-
requiredArtifacts: ['WebAppManifest', 'InstallabilityErrors'],
|
|
49
|
-
};
|
|
50
|
-
}
|
|
51
|
-
|
|
52
|
-
/**
|
|
53
|
-
* @param {LH.Artifacts.ManifestValues} manifestValues
|
|
54
|
-
* @param {Array<string>} failures
|
|
55
|
-
*/
|
|
56
|
-
static assessManifest(manifestValues, failures) {
|
|
57
|
-
if (manifestValues.isParseFailure && manifestValues.parseFailureReason) {
|
|
58
|
-
failures.push(manifestValues.parseFailureReason);
|
|
59
|
-
return;
|
|
60
|
-
}
|
|
61
|
-
|
|
62
|
-
const splashScreenCheckIds = [
|
|
63
|
-
'hasName',
|
|
64
|
-
'hasBackgroundColor',
|
|
65
|
-
'hasThemeColor',
|
|
66
|
-
'hasIconsAtLeast512px',
|
|
67
|
-
];
|
|
68
|
-
|
|
69
|
-
manifestValues.allChecks
|
|
70
|
-
.filter(item => splashScreenCheckIds.includes(item.id))
|
|
71
|
-
.forEach(item => {
|
|
72
|
-
if (!item.passing) {
|
|
73
|
-
failures.push(item.failureText);
|
|
74
|
-
}
|
|
75
|
-
});
|
|
76
|
-
}
|
|
77
|
-
|
|
78
|
-
/**
|
|
79
|
-
* @param {LH.Artifacts} artifacts
|
|
80
|
-
* @param {LH.Audit.Context} context
|
|
81
|
-
* @return {Promise<{failures: Array<string>, manifestValues: LH.Artifacts.ManifestValues}>}
|
|
82
|
-
*/
|
|
83
|
-
static async audit_(artifacts, context) {
|
|
84
|
-
/** @type {Array<string>} */
|
|
85
|
-
const failures = [];
|
|
86
|
-
|
|
87
|
-
const manifestValues = await ManifestValues.request(artifacts, context);
|
|
88
|
-
SplashScreen.assessManifest(manifestValues, failures);
|
|
89
|
-
|
|
90
|
-
return {
|
|
91
|
-
failures,
|
|
92
|
-
manifestValues,
|
|
93
|
-
};
|
|
94
|
-
}
|
|
95
|
-
}
|
|
96
|
-
|
|
97
|
-
export default SplashScreen;
|
|
98
|
-
export {UIStrings};
|
|
@@ -1,42 +0,0 @@
|
|
|
1
|
-
export default ThemedOmnibox;
|
|
2
|
-
/**
|
|
3
|
-
* @fileoverview
|
|
4
|
-
* Audits if a page is configured for a themed address bar
|
|
5
|
-
*
|
|
6
|
-
* Requirements:
|
|
7
|
-
* * manifest is not empty
|
|
8
|
-
* * manifest has a theme_color
|
|
9
|
-
* * HTML has a theme-color meta
|
|
10
|
-
*
|
|
11
|
-
* Color validity is explicitly not checked.
|
|
12
|
-
*/
|
|
13
|
-
declare class ThemedOmnibox extends MultiCheckAudit {
|
|
14
|
-
/**
|
|
15
|
-
* @param {LH.Artifacts.MetaElement|undefined} themeColorMeta
|
|
16
|
-
* @param {Array<string>} failures
|
|
17
|
-
*/
|
|
18
|
-
static assessMetaThemecolor(themeColorMeta: LH.Artifacts.MetaElement | undefined, failures: Array<string>): void;
|
|
19
|
-
/**
|
|
20
|
-
* @param {LH.Artifacts.ManifestValues} manifestValues
|
|
21
|
-
* @param {Array<string>} failures
|
|
22
|
-
*/
|
|
23
|
-
static assessManifest(manifestValues: LH.Artifacts.ManifestValues, failures: Array<string>): void;
|
|
24
|
-
/**
|
|
25
|
-
* @param {LH.Artifacts} artifacts
|
|
26
|
-
* @param {LH.Audit.Context} context
|
|
27
|
-
* @return {Promise<{failures: Array<string>, manifestValues: LH.Artifacts.ManifestValues, themeColor: ?string}>}
|
|
28
|
-
*/
|
|
29
|
-
static audit_(artifacts: LH.Artifacts, context: LH.Audit.Context): Promise<{
|
|
30
|
-
failures: Array<string>;
|
|
31
|
-
manifestValues: LH.Artifacts.ManifestValues;
|
|
32
|
-
themeColor: string | null;
|
|
33
|
-
}>;
|
|
34
|
-
}
|
|
35
|
-
export namespace UIStrings {
|
|
36
|
-
const title: string;
|
|
37
|
-
const failureTitle: string;
|
|
38
|
-
const description: string;
|
|
39
|
-
}
|
|
40
|
-
import MultiCheckAudit from './multi-check-audit.js';
|
|
41
|
-
import { ManifestValues } from '../computed/manifest-values.js';
|
|
42
|
-
//# sourceMappingURL=themed-omnibox.d.ts.map
|
|
@@ -1,102 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* @license
|
|
3
|
-
* Copyright 2017 Google LLC
|
|
4
|
-
* SPDX-License-Identifier: Apache-2.0
|
|
5
|
-
*/
|
|
6
|
-
|
|
7
|
-
import MultiCheckAudit from './multi-check-audit.js';
|
|
8
|
-
import {ManifestValues} from '../computed/manifest-values.js';
|
|
9
|
-
import * as i18n from '../lib/i18n/i18n.js';
|
|
10
|
-
|
|
11
|
-
const UIStrings = {
|
|
12
|
-
/** Title of a Lighthouse audit that provides detail on the theme color the web page has set for the browser's address bar. This descriptive title is shown to users when an address-bar theme color has been set. */
|
|
13
|
-
title: 'Sets a theme color for the address bar.',
|
|
14
|
-
/** Title of a Lighthouse audit that provides detail on the theme color the web page has set for the browser's address bar. This descriptive title is shown to users when an address-bar theme color has not been set. */
|
|
15
|
-
failureTitle: 'Does not set a theme color for the address bar.',
|
|
16
|
-
/** Description of a Lighthouse audit that tells the user why they should set a theme color for the browser's address bar. This is displayed after a user expands the section to see more. No character length limits. The last sentence starting with 'Learn' becomes link text to additional documentation. */
|
|
17
|
-
description: 'The browser address bar can be themed to match your site. ' +
|
|
18
|
-
'[Learn more about theming the address bar](https://developer.chrome.com/docs/lighthouse/pwa/themed-omnibox/).',
|
|
19
|
-
};
|
|
20
|
-
|
|
21
|
-
const str_ = i18n.createIcuMessageFn(import.meta.url, UIStrings);
|
|
22
|
-
|
|
23
|
-
/**
|
|
24
|
-
* @fileoverview
|
|
25
|
-
* Audits if a page is configured for a themed address bar
|
|
26
|
-
*
|
|
27
|
-
* Requirements:
|
|
28
|
-
* * manifest is not empty
|
|
29
|
-
* * manifest has a theme_color
|
|
30
|
-
* * HTML has a theme-color meta
|
|
31
|
-
*
|
|
32
|
-
* Color validity is explicitly not checked.
|
|
33
|
-
*/
|
|
34
|
-
|
|
35
|
-
class ThemedOmnibox extends MultiCheckAudit {
|
|
36
|
-
/**
|
|
37
|
-
* @return {LH.Audit.Meta}
|
|
38
|
-
*/
|
|
39
|
-
static get meta() {
|
|
40
|
-
return {
|
|
41
|
-
id: 'themed-omnibox',
|
|
42
|
-
title: str_(UIStrings.title),
|
|
43
|
-
failureTitle: str_(UIStrings.failureTitle),
|
|
44
|
-
description: str_(UIStrings.description),
|
|
45
|
-
supportedModes: ['navigation'],
|
|
46
|
-
requiredArtifacts: ['WebAppManifest', 'InstallabilityErrors', 'MetaElements'],
|
|
47
|
-
};
|
|
48
|
-
}
|
|
49
|
-
|
|
50
|
-
/**
|
|
51
|
-
* @param {LH.Artifacts.MetaElement|undefined} themeColorMeta
|
|
52
|
-
* @param {Array<string>} failures
|
|
53
|
-
*/
|
|
54
|
-
static assessMetaThemecolor(themeColorMeta, failures) {
|
|
55
|
-
if (!themeColorMeta) {
|
|
56
|
-
// TODO(#7238): i18n
|
|
57
|
-
failures.push('No `<meta name="theme-color">` tag found');
|
|
58
|
-
} else if (!themeColorMeta.content) {
|
|
59
|
-
failures.push('The theme-color meta tag did not contain a content value');
|
|
60
|
-
}
|
|
61
|
-
}
|
|
62
|
-
|
|
63
|
-
/**
|
|
64
|
-
* @param {LH.Artifacts.ManifestValues} manifestValues
|
|
65
|
-
* @param {Array<string>} failures
|
|
66
|
-
*/
|
|
67
|
-
static assessManifest(manifestValues, failures) {
|
|
68
|
-
if (manifestValues.isParseFailure && manifestValues.parseFailureReason) {
|
|
69
|
-
failures.push(manifestValues.parseFailureReason);
|
|
70
|
-
return;
|
|
71
|
-
}
|
|
72
|
-
|
|
73
|
-
const themeColorCheck = manifestValues.allChecks.find(i => i.id === 'hasThemeColor');
|
|
74
|
-
if (themeColorCheck && !themeColorCheck.passing) {
|
|
75
|
-
failures.push(themeColorCheck.failureText);
|
|
76
|
-
}
|
|
77
|
-
}
|
|
78
|
-
|
|
79
|
-
/**
|
|
80
|
-
* @param {LH.Artifacts} artifacts
|
|
81
|
-
* @param {LH.Audit.Context} context
|
|
82
|
-
* @return {Promise<{failures: Array<string>, manifestValues: LH.Artifacts.ManifestValues, themeColor: ?string}>}
|
|
83
|
-
*/
|
|
84
|
-
static async audit_(artifacts, context) {
|
|
85
|
-
/** @type {Array<string>} */
|
|
86
|
-
const failures = [];
|
|
87
|
-
|
|
88
|
-
const themeColorMeta = artifacts.MetaElements.find(meta => meta.name === 'theme-color');
|
|
89
|
-
const manifestValues = await ManifestValues.request(artifacts, context);
|
|
90
|
-
ThemedOmnibox.assessManifest(manifestValues, failures);
|
|
91
|
-
ThemedOmnibox.assessMetaThemecolor(themeColorMeta, failures);
|
|
92
|
-
|
|
93
|
-
return {
|
|
94
|
-
failures,
|
|
95
|
-
manifestValues,
|
|
96
|
-
themeColor: themeColorMeta?.content || null,
|
|
97
|
-
};
|
|
98
|
-
}
|
|
99
|
-
}
|
|
100
|
-
|
|
101
|
-
export default ThemedOmnibox;
|
|
102
|
-
export {UIStrings};
|
|
@@ -1,24 +0,0 @@
|
|
|
1
|
-
export { ManifestValuesComputed as ManifestValues };
|
|
2
|
-
declare const ManifestValuesComputed: typeof ManifestValues & {
|
|
3
|
-
request: (dependencies: Pick<import("../index.js").Artifacts, "InstallabilityErrors" | "WebAppManifest">, context: import("../../types/utility-types.js").default.ImmutableObject<{
|
|
4
|
-
computedCache: Map<string, import("../lib/arbitrary-equality-map.js").ArbitraryEqualityMap>;
|
|
5
|
-
}>) => Promise<import("../index.js").Artifacts.ManifestValues>;
|
|
6
|
-
};
|
|
7
|
-
declare class ManifestValues {
|
|
8
|
-
/** @typedef {(val: NonNullable<LH.Artifacts.Manifest['value']>, errors: LH.Artifacts.InstallabilityErrors['errors']) => boolean} Validator */
|
|
9
|
-
/**
|
|
10
|
-
* @return {Array<{id: LH.Artifacts.ManifestValueCheckID, failureText: string, validate: Validator}>}
|
|
11
|
-
*/
|
|
12
|
-
static get manifestChecks(): {
|
|
13
|
-
id: LH.Artifacts.ManifestValueCheckID;
|
|
14
|
-
failureText: string;
|
|
15
|
-
validate: (val: NonNullable<LH.Artifacts.Manifest['value']>, errors: LH.Artifacts.InstallabilityErrors['errors']) => boolean;
|
|
16
|
-
}[];
|
|
17
|
-
/**
|
|
18
|
-
* Returns results of all manifest checks
|
|
19
|
-
* @param {Pick<LH.Artifacts, 'WebAppManifest'|'InstallabilityErrors'>} Manifest
|
|
20
|
-
* @return {Promise<LH.Artifacts.ManifestValues>}
|
|
21
|
-
*/
|
|
22
|
-
static compute_({ WebAppManifest, InstallabilityErrors }: Pick<LH.Artifacts, 'WebAppManifest' | 'InstallabilityErrors'>): Promise<LH.Artifacts.ManifestValues>;
|
|
23
|
-
}
|
|
24
|
-
//# sourceMappingURL=manifest-values.d.ts.map
|
|
@@ -1,136 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* @license
|
|
3
|
-
* Copyright 2016 Google LLC
|
|
4
|
-
* SPDX-License-Identifier: Apache-2.0
|
|
5
|
-
*/
|
|
6
|
-
|
|
7
|
-
import {makeComputedArtifact} from './computed-artifact.js';
|
|
8
|
-
import * as icons from '../lib/icons.js';
|
|
9
|
-
|
|
10
|
-
const PWA_DISPLAY_VALUES = ['minimal-ui', 'fullscreen', 'standalone'];
|
|
11
|
-
|
|
12
|
-
// Historically, Chrome recommended 12 chars as the maximum short_name length to prevent truncation.
|
|
13
|
-
// For more discussion, see https://github.com/GoogleChrome/lighthouse/issues/69 and https://developer.chrome.com/apps/manifest/name#short_name
|
|
14
|
-
const SUGGESTED_SHORTNAME_LENGTH = 12;
|
|
15
|
-
|
|
16
|
-
class ManifestValues {
|
|
17
|
-
/** @typedef {(val: NonNullable<LH.Artifacts.Manifest['value']>, errors: LH.Artifacts.InstallabilityErrors['errors']) => boolean} Validator */
|
|
18
|
-
|
|
19
|
-
/**
|
|
20
|
-
* @return {Array<{id: LH.Artifacts.ManifestValueCheckID, failureText: string, validate: Validator}>}
|
|
21
|
-
*/
|
|
22
|
-
static get manifestChecks() {
|
|
23
|
-
return [
|
|
24
|
-
{
|
|
25
|
-
id: 'hasStartUrl',
|
|
26
|
-
failureText: 'Manifest does not contain a `start_url`',
|
|
27
|
-
validate: manifestValue => !!manifestValue.start_url.value,
|
|
28
|
-
},
|
|
29
|
-
{
|
|
30
|
-
id: 'hasIconsAtLeast144px',
|
|
31
|
-
failureText: 'Manifest does not have a PNG icon of at least 144px',
|
|
32
|
-
validate: manifestValue => icons.doExist(manifestValue) &&
|
|
33
|
-
icons.pngSizedAtLeast(144, manifestValue).length > 0,
|
|
34
|
-
},
|
|
35
|
-
{
|
|
36
|
-
id: 'hasIconsAtLeast512px',
|
|
37
|
-
failureText: 'Manifest does not have a PNG icon of at least 512px',
|
|
38
|
-
validate: manifestValue => icons.doExist(manifestValue) &&
|
|
39
|
-
icons.pngSizedAtLeast(512, manifestValue).length > 0,
|
|
40
|
-
},
|
|
41
|
-
{
|
|
42
|
-
id: 'fetchesIcon',
|
|
43
|
-
failureText: 'Manifest icon failed to be fetched',
|
|
44
|
-
validate: (manifestValue, errors) => {
|
|
45
|
-
const failedToFetchIconErrorIds = [
|
|
46
|
-
'cannot-download-icon',
|
|
47
|
-
'no-icon-available',
|
|
48
|
-
];
|
|
49
|
-
return icons.doExist(manifestValue) &&
|
|
50
|
-
!errors.some(error => failedToFetchIconErrorIds.includes(error.errorId));
|
|
51
|
-
},
|
|
52
|
-
},
|
|
53
|
-
{
|
|
54
|
-
id: 'hasPWADisplayValue',
|
|
55
|
-
failureText: 'Manifest\'s `display` value is not one of: ' + PWA_DISPLAY_VALUES.join(' | '),
|
|
56
|
-
validate: manifestValue => PWA_DISPLAY_VALUES.includes(manifestValue.display.value),
|
|
57
|
-
},
|
|
58
|
-
{
|
|
59
|
-
id: 'hasBackgroundColor',
|
|
60
|
-
failureText: 'Manifest does not have `background_color`',
|
|
61
|
-
validate: manifestValue => !!manifestValue.background_color.value,
|
|
62
|
-
},
|
|
63
|
-
{
|
|
64
|
-
id: 'hasThemeColor',
|
|
65
|
-
failureText: 'Manifest does not have `theme_color`',
|
|
66
|
-
validate: manifestValue => !!manifestValue.theme_color.value,
|
|
67
|
-
},
|
|
68
|
-
{
|
|
69
|
-
id: 'hasShortName',
|
|
70
|
-
failureText: 'Manifest does not have `short_name`',
|
|
71
|
-
validate: manifestValue => !!manifestValue.short_name.value,
|
|
72
|
-
},
|
|
73
|
-
{
|
|
74
|
-
id: 'shortNameLength',
|
|
75
|
-
failureText: `Manifest's \`short_name\` is too long (>${SUGGESTED_SHORTNAME_LENGTH} ` +
|
|
76
|
-
`characters) to be displayed on a homescreen without truncation`,
|
|
77
|
-
// Pass if there's no short_name. Don't want to report a non-existent string is too long
|
|
78
|
-
validate: manifestValue => !!manifestValue.short_name.value &&
|
|
79
|
-
manifestValue.short_name.value.length <= SUGGESTED_SHORTNAME_LENGTH,
|
|
80
|
-
},
|
|
81
|
-
{
|
|
82
|
-
id: 'hasName',
|
|
83
|
-
failureText: 'Manifest does not have `name`',
|
|
84
|
-
validate: manifestValue => !!manifestValue.name.value,
|
|
85
|
-
},
|
|
86
|
-
{
|
|
87
|
-
id: 'hasMaskableIcon',
|
|
88
|
-
failureText: 'Manifest does not have at least one icon that is maskable',
|
|
89
|
-
validate: ManifestValue => icons.doExist(ManifestValue) &&
|
|
90
|
-
icons.containsMaskableIcon(ManifestValue),
|
|
91
|
-
},
|
|
92
|
-
];
|
|
93
|
-
}
|
|
94
|
-
|
|
95
|
-
/**
|
|
96
|
-
* Returns results of all manifest checks
|
|
97
|
-
* @param {Pick<LH.Artifacts, 'WebAppManifest'|'InstallabilityErrors'>} Manifest
|
|
98
|
-
* @return {Promise<LH.Artifacts.ManifestValues>}
|
|
99
|
-
*/
|
|
100
|
-
static async compute_({WebAppManifest, InstallabilityErrors}) {
|
|
101
|
-
// if the manifest isn't there or is invalid json, we report that and bail
|
|
102
|
-
if (WebAppManifest === null) {
|
|
103
|
-
return {
|
|
104
|
-
isParseFailure: true,
|
|
105
|
-
parseFailureReason: 'No manifest was fetched',
|
|
106
|
-
allChecks: [],
|
|
107
|
-
};
|
|
108
|
-
}
|
|
109
|
-
const manifestValue = WebAppManifest.value;
|
|
110
|
-
if (manifestValue === undefined) {
|
|
111
|
-
return {
|
|
112
|
-
isParseFailure: true,
|
|
113
|
-
parseFailureReason: 'Manifest failed to parse as valid JSON',
|
|
114
|
-
allChecks: [],
|
|
115
|
-
};
|
|
116
|
-
}
|
|
117
|
-
|
|
118
|
-
// manifest is valid, so do the rest of the checks
|
|
119
|
-
const remainingChecks = ManifestValues.manifestChecks.map(item => {
|
|
120
|
-
return {
|
|
121
|
-
id: item.id,
|
|
122
|
-
failureText: item.failureText,
|
|
123
|
-
passing: item.validate(manifestValue, InstallabilityErrors.errors),
|
|
124
|
-
};
|
|
125
|
-
});
|
|
126
|
-
|
|
127
|
-
return {
|
|
128
|
-
isParseFailure: false,
|
|
129
|
-
allChecks: remainingChecks,
|
|
130
|
-
};
|
|
131
|
-
}
|
|
132
|
-
}
|
|
133
|
-
|
|
134
|
-
const ManifestValuesComputed =
|
|
135
|
-
makeComputedArtifact(ManifestValues, ['InstallabilityErrors', 'WebAppManifest']);
|
|
136
|
-
export {ManifestValuesComputed as ManifestValues};
|
|
@@ -1,47 +0,0 @@
|
|
|
1
|
-
export default TagsBlockingFirstPaint;
|
|
2
|
-
export type MediaChange = {
|
|
3
|
-
href: string;
|
|
4
|
-
media: string;
|
|
5
|
-
msSinceHTMLEnd: number;
|
|
6
|
-
matches: boolean;
|
|
7
|
-
};
|
|
8
|
-
export type LinkTag = {
|
|
9
|
-
tagName: 'LINK';
|
|
10
|
-
url: string;
|
|
11
|
-
href: string;
|
|
12
|
-
rel: string;
|
|
13
|
-
media: string;
|
|
14
|
-
disabled: boolean;
|
|
15
|
-
mediaChanges: Array<MediaChange>;
|
|
16
|
-
};
|
|
17
|
-
export type ScriptTag = {
|
|
18
|
-
tagName: 'SCRIPT';
|
|
19
|
-
url: string;
|
|
20
|
-
src: string;
|
|
21
|
-
};
|
|
22
|
-
declare class TagsBlockingFirstPaint extends BaseGatherer {
|
|
23
|
-
/**
|
|
24
|
-
* @param {Array<LH.Artifacts.NetworkRequest>} networkRecords
|
|
25
|
-
* @return {Map<string, LH.Artifacts.NetworkRequest>}
|
|
26
|
-
*/
|
|
27
|
-
static _filteredAndIndexedByUrl(networkRecords: Array<LH.Artifacts.NetworkRequest>): Map<string, LH.Artifacts.NetworkRequest>;
|
|
28
|
-
/**
|
|
29
|
-
* @param {LH.Gatherer.Driver} driver
|
|
30
|
-
* @param {Array<LH.Artifacts.NetworkRequest>} networkRecords
|
|
31
|
-
* @return {Promise<Array<LH.Artifacts.TagBlockingFirstPaint>>}
|
|
32
|
-
*/
|
|
33
|
-
static findBlockingTags(driver: LH.Gatherer.Driver, networkRecords: Array<LH.Artifacts.NetworkRequest>): Promise<Array<LH.Artifacts.TagBlockingFirstPaint>>;
|
|
34
|
-
/** @type {LH.Gatherer.GathererMeta<'DevtoolsLog'>} */
|
|
35
|
-
meta: LH.Gatherer.GathererMeta<'DevtoolsLog'>;
|
|
36
|
-
/**
|
|
37
|
-
* @param {LH.Gatherer.Context} context
|
|
38
|
-
*/
|
|
39
|
-
startSensitiveInstrumentation(context: LH.Gatherer.Context): Promise<void>;
|
|
40
|
-
/**
|
|
41
|
-
* @param {LH.Gatherer.Context<'DevtoolsLog'>} context
|
|
42
|
-
* @return {Promise<LH.Artifacts['TagsBlockingFirstPaint']>}
|
|
43
|
-
*/
|
|
44
|
-
getArtifact(context: LH.Gatherer.Context<'DevtoolsLog'>): Promise<LH.Artifacts['TagsBlockingFirstPaint']>;
|
|
45
|
-
}
|
|
46
|
-
import BaseGatherer from '../../base-gatherer.js';
|
|
47
|
-
//# sourceMappingURL=tags-blocking-first-paint.d.ts.map
|
|
@@ -1,233 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* @license
|
|
3
|
-
* Copyright 2016 Google LLC
|
|
4
|
-
* SPDX-License-Identifier: Apache-2.0
|
|
5
|
-
*/
|
|
6
|
-
/**
|
|
7
|
-
* @fileoverview
|
|
8
|
-
* Identifies stylesheets, HTML Imports, and scripts that potentially block
|
|
9
|
-
* the first paint of the page by running several scripts in the page context.
|
|
10
|
-
* Candidate blocking tags are collected by querying for all script tags in
|
|
11
|
-
* the head of the page and all link tags that are either matching media
|
|
12
|
-
* stylesheets or non-async HTML imports. These are then compared to the
|
|
13
|
-
* network requests to ensure they were initiated by the parser and not
|
|
14
|
-
* injected with script. To avoid false positives from strategies like
|
|
15
|
-
* (http://filamentgroup.github.io/loadCSS/test/preload.html), a separate
|
|
16
|
-
* script is run to flag all links that at one point were rel=preload.
|
|
17
|
-
*/
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
import {NetworkRecords} from '../../../computed/network-records.js';
|
|
21
|
-
import DevtoolsLog from '../devtools-log.js';
|
|
22
|
-
import BaseGatherer from '../../base-gatherer.js';
|
|
23
|
-
|
|
24
|
-
/* global document, window, HTMLLinkElement, SVGScriptElement */
|
|
25
|
-
|
|
26
|
-
/** @typedef {{href: string, media: string, msSinceHTMLEnd: number, matches: boolean}} MediaChange */
|
|
27
|
-
/** @typedef {{tagName: 'LINK', url: string, href: string, rel: string, media: string, disabled: boolean, mediaChanges: Array<MediaChange>}} LinkTag */
|
|
28
|
-
/** @typedef {{tagName: 'SCRIPT', url: string, src: string}} ScriptTag */
|
|
29
|
-
|
|
30
|
-
/* c8 ignore start */
|
|
31
|
-
function installMediaListener() {
|
|
32
|
-
// @ts-expect-error - inserted in page to track media changes.
|
|
33
|
-
window.___linkMediaChanges = [];
|
|
34
|
-
Object.defineProperty(HTMLLinkElement.prototype, 'media', {
|
|
35
|
-
set: function(val) {
|
|
36
|
-
/** @type {MediaChange} */
|
|
37
|
-
const mediaChange = {
|
|
38
|
-
href: this.href,
|
|
39
|
-
media: val,
|
|
40
|
-
msSinceHTMLEnd: Date.now() - performance.timing.responseEnd,
|
|
41
|
-
matches: window.matchMedia(val).matches,
|
|
42
|
-
};
|
|
43
|
-
// @ts-expect-error - `___linkMediaChanges` created above.
|
|
44
|
-
window.___linkMediaChanges.push(mediaChange);
|
|
45
|
-
|
|
46
|
-
this.setAttribute('media', val);
|
|
47
|
-
},
|
|
48
|
-
});
|
|
49
|
-
}
|
|
50
|
-
/* c8 ignore stop */
|
|
51
|
-
|
|
52
|
-
/**
|
|
53
|
-
* @return {Promise<Array<LinkTag | ScriptTag>>}
|
|
54
|
-
*/
|
|
55
|
-
/* c8 ignore start */
|
|
56
|
-
async function collectTagsThatBlockFirstPaint() {
|
|
57
|
-
/** @type {Array<MediaChange>} */
|
|
58
|
-
// @ts-expect-error - `___linkMediaChanges` created in `installMediaListener`.
|
|
59
|
-
const linkMediaChanges = window.___linkMediaChanges;
|
|
60
|
-
|
|
61
|
-
try {
|
|
62
|
-
/** @type {Array<LinkTag>} */
|
|
63
|
-
const linkTags = [...document.querySelectorAll('link')]
|
|
64
|
-
.filter(linkTag => {
|
|
65
|
-
// Ignore malformed links with no href (e.g. `<link rel="stylesheet" href="">`)
|
|
66
|
-
// The resolved `linkTag.href` will be the main document, but the main document
|
|
67
|
-
// should never be render blocking.
|
|
68
|
-
if (!linkTag.getAttribute('href')) return false;
|
|
69
|
-
|
|
70
|
-
// Filter stylesheet/HTML imports that block rendering.
|
|
71
|
-
// https://www.igvita.com/2012/06/14/debunking-responsive-css-performance-myths/
|
|
72
|
-
// https://www.w3.org/TR/html-imports/#dfn-import-async-attribute
|
|
73
|
-
const blockingStylesheet = linkTag.rel === 'stylesheet' &&
|
|
74
|
-
window.matchMedia(linkTag.media).matches && !linkTag.disabled;
|
|
75
|
-
const blockingImport = linkTag.rel === 'import' && !linkTag.hasAttribute('async');
|
|
76
|
-
return blockingStylesheet || blockingImport;
|
|
77
|
-
})
|
|
78
|
-
.map(tag => {
|
|
79
|
-
return {
|
|
80
|
-
tagName: 'LINK',
|
|
81
|
-
url: tag.href,
|
|
82
|
-
href: tag.href,
|
|
83
|
-
rel: tag.rel,
|
|
84
|
-
media: tag.media,
|
|
85
|
-
disabled: tag.disabled,
|
|
86
|
-
mediaChanges: linkMediaChanges.filter(item => item.href === tag.href),
|
|
87
|
-
};
|
|
88
|
-
});
|
|
89
|
-
|
|
90
|
-
/** @type {Array<ScriptTag>} */
|
|
91
|
-
const scriptTags = [...document.querySelectorAll('head script[src]')]
|
|
92
|
-
.filter(/** @return {scriptTag is HTMLScriptElement} */ scriptTag => {
|
|
93
|
-
// SVGScriptElement can't appear in <head> (it'll be kicked to <body>), but keep tsc happy.
|
|
94
|
-
// https://html.spec.whatwg.org/multipage/semantics.html#the-head-element
|
|
95
|
-
if (scriptTag instanceof SVGScriptElement) return false;
|
|
96
|
-
|
|
97
|
-
return (
|
|
98
|
-
!scriptTag.hasAttribute('async') &&
|
|
99
|
-
!scriptTag.hasAttribute('defer') &&
|
|
100
|
-
!/^data:/.test(scriptTag.src) &&
|
|
101
|
-
!/^blob:/.test(scriptTag.src) &&
|
|
102
|
-
scriptTag.getAttribute('type') !== 'module'
|
|
103
|
-
);
|
|
104
|
-
})
|
|
105
|
-
.map(tag => {
|
|
106
|
-
return {
|
|
107
|
-
tagName: 'SCRIPT',
|
|
108
|
-
url: tag.src,
|
|
109
|
-
src: tag.src,
|
|
110
|
-
};
|
|
111
|
-
});
|
|
112
|
-
|
|
113
|
-
return [...linkTags, ...scriptTags];
|
|
114
|
-
} catch (e) {
|
|
115
|
-
const friendly = 'Unable to gather Scripts/Stylesheets/HTML Imports on the page';
|
|
116
|
-
throw new Error(`${friendly}: ${e.message}`);
|
|
117
|
-
}
|
|
118
|
-
}
|
|
119
|
-
/* c8 ignore stop */
|
|
120
|
-
|
|
121
|
-
class TagsBlockingFirstPaint extends BaseGatherer {
|
|
122
|
-
/** @type {LH.Gatherer.GathererMeta<'DevtoolsLog'>} */
|
|
123
|
-
meta = {
|
|
124
|
-
supportedModes: ['navigation'],
|
|
125
|
-
dependencies: {DevtoolsLog: DevtoolsLog.symbol},
|
|
126
|
-
};
|
|
127
|
-
|
|
128
|
-
/**
|
|
129
|
-
* @param {Array<LH.Artifacts.NetworkRequest>} networkRecords
|
|
130
|
-
* @return {Map<string, LH.Artifacts.NetworkRequest>}
|
|
131
|
-
*/
|
|
132
|
-
static _filteredAndIndexedByUrl(networkRecords) {
|
|
133
|
-
/** @type {Map<string, LH.Artifacts.NetworkRequest>} */
|
|
134
|
-
const result = new Map();
|
|
135
|
-
|
|
136
|
-
for (const record of networkRecords) {
|
|
137
|
-
if (!record.finished) continue;
|
|
138
|
-
|
|
139
|
-
const isParserGenerated = record.initiator.type === 'parser';
|
|
140
|
-
// A stylesheet only blocks script if it was initiated by the parser
|
|
141
|
-
// https://html.spec.whatwg.org/multipage/semantics.html#interactions-of-styling-and-scripting
|
|
142
|
-
const isParserScriptOrStyle = /(css|script)/.test(record.mimeType) && isParserGenerated;
|
|
143
|
-
const isFailedRequest = record.failed;
|
|
144
|
-
const isHtml = record.mimeType && record.mimeType.includes('html');
|
|
145
|
-
|
|
146
|
-
// Filter stylesheet, javascript, and html import mimetypes.
|
|
147
|
-
// Include 404 scripts/links generated by the parser because they are likely blocking.
|
|
148
|
-
if (isHtml || isParserScriptOrStyle || (isFailedRequest && isParserGenerated)) {
|
|
149
|
-
result.set(record.url, record);
|
|
150
|
-
}
|
|
151
|
-
}
|
|
152
|
-
|
|
153
|
-
return result;
|
|
154
|
-
}
|
|
155
|
-
|
|
156
|
-
/**
|
|
157
|
-
* @param {LH.Gatherer.Driver} driver
|
|
158
|
-
* @param {Array<LH.Artifacts.NetworkRequest>} networkRecords
|
|
159
|
-
* @return {Promise<Array<LH.Artifacts.TagBlockingFirstPaint>>}
|
|
160
|
-
*/
|
|
161
|
-
static async findBlockingTags(driver, networkRecords) {
|
|
162
|
-
const firstRequestEndTime = networkRecords.reduce(
|
|
163
|
-
(min, record) => Math.min(min, record.networkEndTime),
|
|
164
|
-
Infinity
|
|
165
|
-
);
|
|
166
|
-
const tags = await driver.executionContext.evaluate(collectTagsThatBlockFirstPaint, {args: []});
|
|
167
|
-
const requests = TagsBlockingFirstPaint._filteredAndIndexedByUrl(networkRecords);
|
|
168
|
-
|
|
169
|
-
/** @type {Array<LH.Artifacts.TagBlockingFirstPaint>} */
|
|
170
|
-
const result = [];
|
|
171
|
-
for (const tag of tags) {
|
|
172
|
-
const request = requests.get(tag.url);
|
|
173
|
-
if (!request || request.isLinkPreload) continue;
|
|
174
|
-
|
|
175
|
-
let endTime = request.networkEndTime;
|
|
176
|
-
let mediaChanges;
|
|
177
|
-
|
|
178
|
-
if (tag.tagName === 'LINK') {
|
|
179
|
-
// Even if the request was initially blocking or appeared to be blocking once the
|
|
180
|
-
// page was loaded, the media attribute could have been changed during load, capping the
|
|
181
|
-
// amount of time it was render blocking. See https://github.com/GoogleChrome/lighthouse/issues/2832.
|
|
182
|
-
const timesResourceBecameNonBlocking = tag.mediaChanges
|
|
183
|
-
.filter(change => !change.matches)
|
|
184
|
-
.map(change => change.msSinceHTMLEnd);
|
|
185
|
-
if (timesResourceBecameNonBlocking.length > 0) {
|
|
186
|
-
const earliestNonBlockingTime = Math.min(...timesResourceBecameNonBlocking);
|
|
187
|
-
const lastTimeResourceWasBlocking = Math.max(
|
|
188
|
-
request.networkRequestTime,
|
|
189
|
-
firstRequestEndTime + earliestNonBlockingTime / 1000
|
|
190
|
-
);
|
|
191
|
-
endTime = Math.min(endTime, lastTimeResourceWasBlocking);
|
|
192
|
-
}
|
|
193
|
-
|
|
194
|
-
mediaChanges = tag.mediaChanges;
|
|
195
|
-
}
|
|
196
|
-
|
|
197
|
-
const {tagName, url} = tag;
|
|
198
|
-
|
|
199
|
-
result.push({
|
|
200
|
-
tag: {tagName, url, mediaChanges},
|
|
201
|
-
transferSize: request.transferSize,
|
|
202
|
-
startTime: request.networkRequestTime,
|
|
203
|
-
endTime,
|
|
204
|
-
});
|
|
205
|
-
|
|
206
|
-
// Prevent duplicates from showing up again
|
|
207
|
-
requests.delete(tag.url);
|
|
208
|
-
}
|
|
209
|
-
|
|
210
|
-
return result;
|
|
211
|
-
}
|
|
212
|
-
|
|
213
|
-
/**
|
|
214
|
-
* @param {LH.Gatherer.Context} context
|
|
215
|
-
*/
|
|
216
|
-
async startSensitiveInstrumentation(context) {
|
|
217
|
-
const {executionContext} = context.driver;
|
|
218
|
-
// Don't return return value of `evaluateOnNewDocument`.
|
|
219
|
-
await executionContext.evaluateOnNewDocument(installMediaListener, {args: []});
|
|
220
|
-
}
|
|
221
|
-
|
|
222
|
-
/**
|
|
223
|
-
* @param {LH.Gatherer.Context<'DevtoolsLog'>} context
|
|
224
|
-
* @return {Promise<LH.Artifacts['TagsBlockingFirstPaint']>}
|
|
225
|
-
*/
|
|
226
|
-
async getArtifact(context) {
|
|
227
|
-
const devtoolsLog = context.dependencies.DevtoolsLog;
|
|
228
|
-
const networkRecords = await NetworkRecords.request(devtoolsLog, context);
|
|
229
|
-
return TagsBlockingFirstPaint.findBlockingTags(context.driver, networkRecords);
|
|
230
|
-
}
|
|
231
|
-
}
|
|
232
|
-
|
|
233
|
-
export default TagsBlockingFirstPaint;
|