lighthouse 11.0.0-dev.20230814 → 11.0.0-dev.20230816
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/core/audits/dobetterweb/dom-size.d.ts +8 -2
- package/core/audits/dobetterweb/dom-size.js +50 -3
- package/core/audits/installable-manifest.d.ts +0 -6
- package/core/audits/installable-manifest.js +1 -17
- package/core/audits/largest-contentful-paint-element.js +21 -4
- package/core/audits/lcp-lazy-loaded.d.ts +3 -2
- package/core/audits/lcp-lazy-loaded.js +34 -5
- package/core/audits/third-party-facades.js +13 -5
- package/core/audits/third-party-summary.d.ts +4 -2
- package/core/audits/third-party-summary.js +24 -11
- package/core/computed/tbt-impact-tasks.d.ts +8 -13
- package/core/computed/tbt-impact-tasks.js +4 -6
- package/core/index.cjs +13 -13
- package/core/index.d.cts +15 -2
- package/package.json +2 -2
- package/shared/localization/locales/ar-XB.json +0 -3
- package/shared/localization/locales/ar.json +0 -3
- package/shared/localization/locales/bg.json +0 -3
- package/shared/localization/locales/ca.json +0 -3
- package/shared/localization/locales/cs.json +0 -3
- package/shared/localization/locales/da.json +0 -3
- package/shared/localization/locales/de.json +0 -3
- package/shared/localization/locales/el.json +0 -3
- package/shared/localization/locales/en-GB.json +0 -3
- package/shared/localization/locales/en-US.json +0 -3
- package/shared/localization/locales/en-XA.json +0 -3
- package/shared/localization/locales/en-XL.json +0 -3
- package/shared/localization/locales/es-419.json +0 -3
- package/shared/localization/locales/es.json +0 -3
- package/shared/localization/locales/fi.json +0 -3
- package/shared/localization/locales/fil.json +0 -3
- package/shared/localization/locales/fr.json +0 -3
- package/shared/localization/locales/he.json +0 -3
- package/shared/localization/locales/hi.json +0 -3
- package/shared/localization/locales/hr.json +0 -3
- package/shared/localization/locales/hu.json +0 -3
- package/shared/localization/locales/id.json +0 -3
- package/shared/localization/locales/it.json +0 -3
- package/shared/localization/locales/ja.json +0 -3
- package/shared/localization/locales/ko.json +0 -3
- package/shared/localization/locales/lt.json +0 -3
- package/shared/localization/locales/lv.json +0 -3
- package/shared/localization/locales/nl.json +0 -3
- package/shared/localization/locales/no.json +0 -3
- package/shared/localization/locales/pl.json +0 -3
- package/shared/localization/locales/pt-PT.json +0 -3
- package/shared/localization/locales/pt.json +0 -3
- package/shared/localization/locales/ro.json +0 -3
- package/shared/localization/locales/ru.json +0 -3
- package/shared/localization/locales/sk.json +0 -3
- package/shared/localization/locales/sl.json +0 -3
- package/shared/localization/locales/sr-Latn.json +0 -3
- package/shared/localization/locales/sr.json +0 -3
- package/shared/localization/locales/sv.json +0 -3
- 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 +0 -3
- 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 +0 -1
- package/types/artifacts.d.ts +1 -0
|
@@ -7,9 +7,15 @@ declare class DOMSize extends Audit {
|
|
|
7
7
|
/**
|
|
8
8
|
* @param {LH.Artifacts} artifacts
|
|
9
9
|
* @param {LH.Audit.Context} context
|
|
10
|
-
* @return {
|
|
10
|
+
* @return {Promise<number|undefined>}
|
|
11
11
|
*/
|
|
12
|
-
static
|
|
12
|
+
static computeTbtImpact(artifacts: LH.Artifacts, context: LH.Audit.Context): Promise<number | undefined>;
|
|
13
|
+
/**
|
|
14
|
+
* @param {LH.Artifacts} artifacts
|
|
15
|
+
* @param {LH.Audit.Context} context
|
|
16
|
+
* @return {Promise<LH.Audit.Product>}
|
|
17
|
+
*/
|
|
18
|
+
static audit(artifacts: LH.Artifacts, context: LH.Audit.Context): Promise<LH.Audit.Product>;
|
|
13
19
|
}
|
|
14
20
|
export namespace UIStrings {
|
|
15
21
|
const title: string;
|
|
@@ -13,6 +13,7 @@
|
|
|
13
13
|
|
|
14
14
|
import {Audit} from '../audit.js';
|
|
15
15
|
import * as i18n from '../../lib/i18n/i18n.js';
|
|
16
|
+
import {TBTImpactTasks} from '../../computed/tbt-impact-tasks.js';
|
|
16
17
|
|
|
17
18
|
const UIStrings = {
|
|
18
19
|
/** Title of a diagnostic audit that provides detail on the size of the web page's DOM. The size of a DOM is characterized by the total number of DOM elements and greatest DOM depth. This descriptive title is shown to users when the amount is acceptable and no user action is required. */
|
|
@@ -53,7 +54,8 @@ class DOMSize extends Audit {
|
|
|
53
54
|
failureTitle: str_(UIStrings.failureTitle),
|
|
54
55
|
description: str_(UIStrings.description),
|
|
55
56
|
scoreDisplayMode: Audit.SCORING_MODES.NUMERIC,
|
|
56
|
-
requiredArtifacts: ['DOMStats'],
|
|
57
|
+
requiredArtifacts: ['DOMStats', 'URL', 'GatherContext'],
|
|
58
|
+
__internalOptionalArtifacts: ['traces', 'devtoolsLogs'],
|
|
57
59
|
};
|
|
58
60
|
}
|
|
59
61
|
|
|
@@ -69,13 +71,53 @@ class DOMSize extends Audit {
|
|
|
69
71
|
};
|
|
70
72
|
}
|
|
71
73
|
|
|
74
|
+
/**
|
|
75
|
+
* @param {LH.Artifacts} artifacts
|
|
76
|
+
* @param {LH.Audit.Context} context
|
|
77
|
+
* @return {Promise<number|undefined>}
|
|
78
|
+
*/
|
|
79
|
+
static async computeTbtImpact(artifacts, context) {
|
|
80
|
+
let tbtImpact = 0;
|
|
81
|
+
|
|
82
|
+
// We still want to surface this audit in snapshot mode, but since we don't compute TBT
|
|
83
|
+
// the impact should always be undefined.
|
|
84
|
+
const {GatherContext, devtoolsLogs, traces} = artifacts;
|
|
85
|
+
if (GatherContext.gatherMode !== 'navigation') {
|
|
86
|
+
return undefined;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
// Since the artifacts are optional, it's still possible for them to be missing in navigation mode.
|
|
90
|
+
// Navigation mode does compute TBT so we should surface a numerical savings of 0.
|
|
91
|
+
if (!devtoolsLogs?.[Audit.DEFAULT_PASS] || !traces?.[Audit.DEFAULT_PASS]) {
|
|
92
|
+
return 0;
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
const metricComputationData = Audit.makeMetricComputationDataInput(artifacts, context);
|
|
96
|
+
|
|
97
|
+
try {
|
|
98
|
+
// The TBT impact of style/layout tasks is correlated to the DOM size.
|
|
99
|
+
// Even in situations where the page forces a style recalc, the DOM size is partially to blame
|
|
100
|
+
// for any time spent blocking the main thread.
|
|
101
|
+
//
|
|
102
|
+
// `tbtImpact` should be exactly 0 for small DOMs since `selfTbtImpact` accounts for the blocking
|
|
103
|
+
// time and not the main thread time.
|
|
104
|
+
const tbtImpactTasks = await TBTImpactTasks.request(metricComputationData, context);
|
|
105
|
+
for (const task of tbtImpactTasks) {
|
|
106
|
+
if (task.group.id !== 'styleLayout') continue;
|
|
107
|
+
tbtImpact += task.selfTbtImpact;
|
|
108
|
+
}
|
|
109
|
+
} catch {}
|
|
110
|
+
|
|
111
|
+
return Math.round(tbtImpact);
|
|
112
|
+
}
|
|
113
|
+
|
|
72
114
|
|
|
73
115
|
/**
|
|
74
116
|
* @param {LH.Artifacts} artifacts
|
|
75
117
|
* @param {LH.Audit.Context} context
|
|
76
|
-
* @return {LH.Audit.Product}
|
|
118
|
+
* @return {Promise<LH.Audit.Product>}
|
|
77
119
|
*/
|
|
78
|
-
static audit(artifacts, context) {
|
|
120
|
+
static async audit(artifacts, context) {
|
|
79
121
|
const stats = artifacts.DOMStats;
|
|
80
122
|
|
|
81
123
|
const score = Audit.computeLogNormalScore(
|
|
@@ -120,12 +162,17 @@ class DOMSize extends Audit {
|
|
|
120
162
|
},
|
|
121
163
|
];
|
|
122
164
|
|
|
165
|
+
const tbtImpact = await this.computeTbtImpact(artifacts, context);
|
|
166
|
+
|
|
123
167
|
return {
|
|
124
168
|
score,
|
|
125
169
|
numericValue: stats.totalBodyElements,
|
|
126
170
|
numericUnit: 'element',
|
|
127
171
|
displayValue: str_(UIStrings.displayValue, {itemCount: stats.totalBodyElements}),
|
|
128
172
|
details: Audit.makeTableDetails(headings, items),
|
|
173
|
+
metricSavings: {
|
|
174
|
+
TBT: tbtImpact,
|
|
175
|
+
},
|
|
129
176
|
};
|
|
130
177
|
}
|
|
131
178
|
}
|
|
@@ -104,12 +104,6 @@ export const UIStrings: {
|
|
|
104
104
|
'protocol-timeout': string;
|
|
105
105
|
/** Message logged when the web app has been uninstalled o desktop, signalling that the install banner state is being reset. */
|
|
106
106
|
'pipeline-restarted': string;
|
|
107
|
-
/**
|
|
108
|
-
* TODO: This error was removed in M114, we can remove this message when it hits stable.
|
|
109
|
-
* @description Error message explaining that the URL of the manifest uses a scheme that is not supported on Android.
|
|
110
|
-
* @example {data:} scheme
|
|
111
|
-
*/
|
|
112
|
-
'scheme-not-supported-for-webapk': string;
|
|
113
107
|
};
|
|
114
108
|
import { Audit } from './audit.js';
|
|
115
109
|
//# sourceMappingURL=installable-manifest.d.ts.map
|
|
@@ -87,17 +87,12 @@ const UIStrings = {
|
|
|
87
87
|
/** Error message explaining that the web manifest's URL changed while the manifest was being downloaded by the browser. */
|
|
88
88
|
'manifest-location-changed': `Manifest URL changed while the manifest was being fetched.`,
|
|
89
89
|
/** Warning message explaining that the page does not work offline. */
|
|
90
|
+
// TODO(COMPAT): This error was removed in M118, we can remove this message when it hits stable.
|
|
90
91
|
'warn-not-offline-capable': `Page does not work offline. The page will not be regarded as installable after Chrome 93, stable release August 2021.`,
|
|
91
92
|
/** Error message explaining that Lighthouse failed while checking if the page is installable, and directing the user to try again in a new Chrome. */
|
|
92
93
|
'protocol-timeout': `Lighthouse could not determine if the page is installable. Please try with a newer version of Chrome.`,
|
|
93
94
|
/** Message logged when the web app has been uninstalled o desktop, signalling that the install banner state is being reset. */
|
|
94
95
|
'pipeline-restarted': 'PWA has been uninstalled and installability checks resetting.',
|
|
95
|
-
/**
|
|
96
|
-
* TODO: This error was removed in M114, we can remove this message when it hits stable.
|
|
97
|
-
* @description Error message explaining that the URL of the manifest uses a scheme that is not supported on Android.
|
|
98
|
-
* @example {data:} scheme
|
|
99
|
-
*/
|
|
100
|
-
'scheme-not-supported-for-webapk': 'The manifest URL scheme ({scheme}) is not supported on Android.',
|
|
101
96
|
};
|
|
102
97
|
/* eslint-enable max-len */
|
|
103
98
|
|
|
@@ -157,17 +152,6 @@ class InstallableManifest extends Audit {
|
|
|
157
152
|
// @ts-expect-error errorIds from protocol should match up against the strings dict
|
|
158
153
|
const matchingString = UIStrings[err.errorId];
|
|
159
154
|
|
|
160
|
-
if (err.errorId === 'scheme-not-supported-for-webapk') {
|
|
161
|
-
// If there was no manifest, then there will be at lest one other installability error.
|
|
162
|
-
// We can ignore this error if that's the case.
|
|
163
|
-
const manifestUrl = artifacts.WebAppManifest?.url;
|
|
164
|
-
if (!manifestUrl) continue;
|
|
165
|
-
|
|
166
|
-
const scheme = new URL(manifestUrl).protocol;
|
|
167
|
-
i18nErrors.push(str_(matchingString, {scheme}));
|
|
168
|
-
continue;
|
|
169
|
-
}
|
|
170
|
-
|
|
171
155
|
// Handle an errorId we don't recognize.
|
|
172
156
|
if (matchingString === undefined) {
|
|
173
157
|
i18nErrors.push(str_(UIStrings.noErrorId, {errorId: err.errorId}));
|
|
@@ -8,7 +8,8 @@ import log from 'lighthouse-logger';
|
|
|
8
8
|
|
|
9
9
|
import {Audit} from './audit.js';
|
|
10
10
|
import * as i18n from '../lib/i18n/i18n.js';
|
|
11
|
-
import {LargestContentfulPaint} from '../computed/metrics/largest-contentful-paint.js';
|
|
11
|
+
import {LargestContentfulPaint as LargestContentfulPaintComputed} from '../computed/metrics/largest-contentful-paint.js';
|
|
12
|
+
import LargestContentfulPaint from './metrics/largest-contentful-paint.js';
|
|
12
13
|
import {LCPBreakdown} from '../computed/metrics/lcp-breakdown.js';
|
|
13
14
|
import {Sentry} from '../lib/sentry.js';
|
|
14
15
|
|
|
@@ -124,14 +125,22 @@ class LargestContentfulPaintElement extends Audit {
|
|
|
124
125
|
settings: context.settings, URL: artifacts.URL};
|
|
125
126
|
|
|
126
127
|
const elementTable = this.makeElementTable(artifacts);
|
|
127
|
-
if (!elementTable)
|
|
128
|
+
if (!elementTable) {
|
|
129
|
+
return {
|
|
130
|
+
score: null,
|
|
131
|
+
notApplicable: true,
|
|
132
|
+
metricSavings: {LCP: 0},
|
|
133
|
+
};
|
|
134
|
+
}
|
|
128
135
|
|
|
129
136
|
const items = [elementTable];
|
|
130
137
|
let displayValue;
|
|
138
|
+
let metricLcp = 0;
|
|
131
139
|
|
|
132
140
|
try {
|
|
133
|
-
const
|
|
134
|
-
await
|
|
141
|
+
const lcpResult =
|
|
142
|
+
await LargestContentfulPaintComputed.request(metricComputationData, context);
|
|
143
|
+
metricLcp = lcpResult.timing;
|
|
135
144
|
displayValue = str_(i18n.UIStrings.ms, {timeInMs: metricLcp});
|
|
136
145
|
|
|
137
146
|
const phaseTable = await this.makePhaseTable(metricLcp, metricComputationData, context);
|
|
@@ -146,10 +155,18 @@ class LargestContentfulPaintElement extends Audit {
|
|
|
146
155
|
|
|
147
156
|
const details = Audit.makeListDetails(items);
|
|
148
157
|
|
|
158
|
+
// Conceptually, this doesn't make much sense as "savings" for this audit since there isn't anything to "fix".
|
|
159
|
+
// However, this audit will always be useful when improving LCP and that should be reflected in our impact calculations.
|
|
160
|
+
const idealLcp = LargestContentfulPaint.defaultOptions[context.settings.formFactor].scoring.p10;
|
|
161
|
+
const lcpSavings = Math.max(0, metricLcp - idealLcp);
|
|
162
|
+
|
|
149
163
|
return {
|
|
150
164
|
score: 1,
|
|
151
165
|
displayValue,
|
|
152
166
|
details,
|
|
167
|
+
metricSavings: {
|
|
168
|
+
LCP: lcpSavings,
|
|
169
|
+
},
|
|
153
170
|
};
|
|
154
171
|
}
|
|
155
172
|
}
|
|
@@ -8,9 +8,10 @@ declare class LargestContentfulPaintLazyLoaded extends Audit {
|
|
|
8
8
|
static isImageInViewport(image: LH.Artifacts.ImageElement, viewportDimensions: LH.Artifacts.ViewportDimensions): boolean;
|
|
9
9
|
/**
|
|
10
10
|
* @param {LH.Artifacts} artifacts
|
|
11
|
-
* @
|
|
11
|
+
* @param {LH.Audit.Context} context
|
|
12
|
+
* @return {Promise<LH.Audit.Product>}
|
|
12
13
|
*/
|
|
13
|
-
static audit(artifacts: LH.Artifacts): LH.Audit.Product
|
|
14
|
+
static audit(artifacts: LH.Artifacts, context: LH.Audit.Context): Promise<LH.Audit.Product>;
|
|
14
15
|
}
|
|
15
16
|
export namespace UIStrings {
|
|
16
17
|
const title: string;
|
|
@@ -6,6 +6,8 @@
|
|
|
6
6
|
|
|
7
7
|
import {Audit} from './audit.js';
|
|
8
8
|
import * as i18n from '../lib/i18n/i18n.js';
|
|
9
|
+
import {LCPBreakdown} from '../computed/metrics/lcp-breakdown.js';
|
|
10
|
+
import {LargestContentfulPaint} from '../computed/metrics/largest-contentful-paint.js';
|
|
9
11
|
|
|
10
12
|
const UIStrings = {
|
|
11
13
|
/** Title of a Lighthouse audit that provides detail on whether the largest above-the-fold image was loaded with sufficient priority. This descriptive title is shown to users when the image was loaded properly. */
|
|
@@ -18,6 +20,8 @@ const UIStrings = {
|
|
|
18
20
|
|
|
19
21
|
const str_ = i18n.createIcuMessageFn(import.meta.url, UIStrings);
|
|
20
22
|
|
|
23
|
+
const ESTIMATED_PERCENT_SAVINGS = 0.15;
|
|
24
|
+
|
|
21
25
|
class LargestContentfulPaintLazyLoaded extends Audit {
|
|
22
26
|
/**
|
|
23
27
|
* @return {LH.Audit.Meta}
|
|
@@ -29,7 +33,8 @@ class LargestContentfulPaintLazyLoaded extends Audit {
|
|
|
29
33
|
failureTitle: str_(UIStrings.failureTitle),
|
|
30
34
|
description: str_(UIStrings.description),
|
|
31
35
|
supportedModes: ['navigation'],
|
|
32
|
-
requiredArtifacts: ['TraceElements', 'ViewportDimensions', 'ImageElements'
|
|
36
|
+
requiredArtifacts: ['TraceElements', 'ViewportDimensions', 'ImageElements',
|
|
37
|
+
'traces', 'devtoolsLogs', 'GatherContext', 'URL'],
|
|
33
38
|
};
|
|
34
39
|
}
|
|
35
40
|
|
|
@@ -46,9 +51,10 @@ class LargestContentfulPaintLazyLoaded extends Audit {
|
|
|
46
51
|
|
|
47
52
|
/**
|
|
48
53
|
* @param {LH.Artifacts} artifacts
|
|
49
|
-
* @
|
|
54
|
+
* @param {LH.Audit.Context} context
|
|
55
|
+
* @return {Promise<LH.Audit.Product>}
|
|
50
56
|
*/
|
|
51
|
-
static audit(artifacts) {
|
|
57
|
+
static async audit(artifacts, context) {
|
|
52
58
|
const lcpElement = artifacts.TraceElements.find(element => {
|
|
53
59
|
return element.traceEventType === 'largest-contentful-paint' && element.type === 'image';
|
|
54
60
|
});
|
|
@@ -59,7 +65,11 @@ class LargestContentfulPaintLazyLoaded extends Audit {
|
|
|
59
65
|
|
|
60
66
|
if (!lcpElementImage ||
|
|
61
67
|
!this.isImageInViewport(lcpElementImage, artifacts.ViewportDimensions)) {
|
|
62
|
-
return {
|
|
68
|
+
return {
|
|
69
|
+
score: null,
|
|
70
|
+
notApplicable: true,
|
|
71
|
+
metricSavings: {LCP: 0},
|
|
72
|
+
};
|
|
63
73
|
}
|
|
64
74
|
|
|
65
75
|
/** @type {LH.Audit.Details.Table['headings']} */
|
|
@@ -73,8 +83,27 @@ class LargestContentfulPaintLazyLoaded extends Audit {
|
|
|
73
83
|
},
|
|
74
84
|
]);
|
|
75
85
|
|
|
86
|
+
const wasLazyLoaded = lcpElementImage.loading === 'lazy';
|
|
87
|
+
|
|
88
|
+
const metricComputationData = Audit.makeMetricComputationDataInput(artifacts, context);
|
|
89
|
+
const {timing: metricLcp} =
|
|
90
|
+
await LargestContentfulPaint.request(metricComputationData, context);
|
|
91
|
+
const lcpBreakdown = await LCPBreakdown.request(metricComputationData, context);
|
|
92
|
+
let lcpSavings = 0;
|
|
93
|
+
if (wasLazyLoaded && lcpBreakdown.loadStart !== undefined) {
|
|
94
|
+
// Estimate the LCP savings using a statistical percentage.
|
|
95
|
+
// https://web.dev/lcp-lazy-loading/#causal-performance
|
|
96
|
+
//
|
|
97
|
+
// LCP savings will be at most the LCP load delay.
|
|
98
|
+
const lcpLoadDelay = lcpBreakdown.loadStart - lcpBreakdown.ttfb;
|
|
99
|
+
lcpSavings = Math.min(metricLcp * ESTIMATED_PERCENT_SAVINGS, lcpLoadDelay);
|
|
100
|
+
}
|
|
101
|
+
|
|
76
102
|
return {
|
|
77
|
-
score:
|
|
103
|
+
score: wasLazyLoaded ? 0 : 1,
|
|
104
|
+
metricSavings: {
|
|
105
|
+
LCP: lcpSavings,
|
|
106
|
+
},
|
|
78
107
|
details,
|
|
79
108
|
};
|
|
80
109
|
}
|
|
@@ -24,8 +24,8 @@ import * as i18n from '../lib/i18n/i18n.js';
|
|
|
24
24
|
import {EntityClassification} from '../computed/entity-classification.js';
|
|
25
25
|
import thirdPartyWeb from '../lib/third-party-web.js';
|
|
26
26
|
import {NetworkRecords} from '../computed/network-records.js';
|
|
27
|
-
import {MainThreadTasks} from '../computed/main-thread-tasks.js';
|
|
28
27
|
import ThirdPartySummary from './third-party-summary.js';
|
|
28
|
+
import {TBTImpactTasks} from '../computed/tbt-impact-tasks.js';
|
|
29
29
|
|
|
30
30
|
const UIStrings = {
|
|
31
31
|
/** Title of a diagnostic audit that provides details about the third-party code on a web page that can be lazy loaded with a facade alternative. This descriptive title is shown to users when no resources have facade alternatives available. A facade is a lightweight component which looks like the desired resource. Lazy loading means resources are deferred until they are needed. Third-party code refers to resources that are not within the control of the site owner. */
|
|
@@ -86,7 +86,7 @@ class ThirdPartyFacades extends Audit {
|
|
|
86
86
|
failureTitle: str_(UIStrings.failureTitle),
|
|
87
87
|
description: str_(UIStrings.description),
|
|
88
88
|
supportedModes: ['navigation'],
|
|
89
|
-
requiredArtifacts: ['traces', 'devtoolsLogs', 'URL'],
|
|
89
|
+
requiredArtifacts: ['traces', 'devtoolsLogs', 'URL', 'GatherContext'],
|
|
90
90
|
};
|
|
91
91
|
}
|
|
92
92
|
|
|
@@ -148,19 +148,23 @@ class ThirdPartyFacades extends Audit {
|
|
|
148
148
|
*/
|
|
149
149
|
static async audit(artifacts, context) {
|
|
150
150
|
const settings = context.settings;
|
|
151
|
-
const trace = artifacts.traces[Audit.DEFAULT_PASS];
|
|
152
151
|
const devtoolsLog = artifacts.devtoolsLogs[Audit.DEFAULT_PASS];
|
|
153
152
|
const networkRecords = await NetworkRecords.request(devtoolsLog, context);
|
|
154
153
|
const classifiedEntities = await EntityClassification.request(
|
|
155
154
|
{URL: artifacts.URL, devtoolsLog}, context);
|
|
156
|
-
|
|
155
|
+
|
|
156
|
+
const metricComputationData = Audit.makeMetricComputationDataInput(artifacts, context);
|
|
157
|
+
const tbtImpactTasks = await TBTImpactTasks.request(metricComputationData, context);
|
|
158
|
+
|
|
157
159
|
const multiplier = settings.throttlingMethod === 'simulate' ?
|
|
158
160
|
settings.throttling.cpuSlowdownMultiplier : 1;
|
|
159
|
-
const summaries = ThirdPartySummary.getSummaries(networkRecords,
|
|
161
|
+
const summaries = ThirdPartySummary.getSummaries(networkRecords, tbtImpactTasks, multiplier,
|
|
160
162
|
classifiedEntities);
|
|
161
163
|
const facadableProducts =
|
|
162
164
|
ThirdPartyFacades.getProductsWithFacade(summaries.byURL, classifiedEntities);
|
|
163
165
|
|
|
166
|
+
let tbtImpact = 0;
|
|
167
|
+
|
|
164
168
|
/** @type {LH.Audit.Details.TableItem[]} */
|
|
165
169
|
const results = [];
|
|
166
170
|
for (const {product, entity} of facadableProducts) {
|
|
@@ -179,6 +183,8 @@ class ThirdPartyFacades extends Audit {
|
|
|
179
183
|
const entitySummary = summaries.byEntity.get(entity);
|
|
180
184
|
if (!urls || !entitySummary) continue;
|
|
181
185
|
|
|
186
|
+
tbtImpact += entitySummary.tbtImpact;
|
|
187
|
+
|
|
182
188
|
const items = Array.from(urls).map((url) => {
|
|
183
189
|
const urlStats = summaries.byURL.get(url);
|
|
184
190
|
return /** @type {import('./third-party-summary.js').URLSummary} */ ({url, ...urlStats});
|
|
@@ -198,6 +204,7 @@ class ThirdPartyFacades extends Audit {
|
|
|
198
204
|
return {
|
|
199
205
|
score: 1,
|
|
200
206
|
notApplicable: true,
|
|
207
|
+
metricSavings: {TBT: 0},
|
|
201
208
|
};
|
|
202
209
|
}
|
|
203
210
|
|
|
@@ -216,6 +223,7 @@ class ThirdPartyFacades extends Audit {
|
|
|
216
223
|
itemCount: results.length,
|
|
217
224
|
}),
|
|
218
225
|
details: Audit.makeTableDetails(headings, results),
|
|
226
|
+
metricSavings: {TBT: tbtImpact},
|
|
219
227
|
};
|
|
220
228
|
}
|
|
221
229
|
}
|
|
@@ -3,10 +3,12 @@ export type Summary = {
|
|
|
3
3
|
mainThreadTime: number;
|
|
4
4
|
transferSize: number;
|
|
5
5
|
blockingTime: number;
|
|
6
|
+
tbtImpact: number;
|
|
6
7
|
};
|
|
7
8
|
export type URLSummary = {
|
|
8
9
|
transferSize: number;
|
|
9
10
|
blockingTime: number;
|
|
11
|
+
tbtImpact: number;
|
|
10
12
|
url: string | LH.IcuMessage;
|
|
11
13
|
};
|
|
12
14
|
export type SummaryMaps = {
|
|
@@ -27,12 +29,12 @@ declare class ThirdPartySummary extends Audit {
|
|
|
27
29
|
/**
|
|
28
30
|
*
|
|
29
31
|
* @param {Array<LH.Artifacts.NetworkRequest>} networkRecords
|
|
30
|
-
* @param {Array<LH.Artifacts.
|
|
32
|
+
* @param {Array<LH.Artifacts.TBTImpactTask>} tbtImpactTasks
|
|
31
33
|
* @param {number} cpuMultiplier
|
|
32
34
|
* @param {LH.Artifacts.EntityClassification} entityClassification
|
|
33
35
|
* @return {SummaryMaps}
|
|
34
36
|
*/
|
|
35
|
-
static getSummaries(networkRecords: Array<LH.Artifacts.NetworkRequest>,
|
|
37
|
+
static getSummaries(networkRecords: Array<LH.Artifacts.NetworkRequest>, tbtImpactTasks: Array<LH.Artifacts.TBTImpactTask>, cpuMultiplier: number, entityClassification: LH.Artifacts.EntityClassification): SummaryMaps;
|
|
36
38
|
/**
|
|
37
39
|
* @param {LH.Artifacts.Entity} entity
|
|
38
40
|
* @param {SummaryMaps} summaries
|
|
@@ -8,8 +8,8 @@ import {Audit} from './audit.js';
|
|
|
8
8
|
import {EntityClassification} from '../computed/entity-classification.js';
|
|
9
9
|
import * as i18n from '../lib/i18n/i18n.js';
|
|
10
10
|
import {NetworkRecords} from '../computed/network-records.js';
|
|
11
|
-
import {MainThreadTasks} from '../computed/main-thread-tasks.js';
|
|
12
11
|
import {getJavaScriptURLs, getAttributableURLForTask} from '../lib/tracehouse/task-summary.js';
|
|
12
|
+
import {TBTImpactTasks} from '../computed/tbt-impact-tasks.js';
|
|
13
13
|
|
|
14
14
|
const UIStrings = {
|
|
15
15
|
/** Title of a diagnostic audit that provides details about the code on a web page that the user doesn't control (referred to as "third-party code"). This descriptive title is shown to users when the amount is acceptable and no user action is required. */
|
|
@@ -38,12 +38,14 @@ const PASS_THRESHOLD_IN_MS = 250;
|
|
|
38
38
|
* @property {number} mainThreadTime
|
|
39
39
|
* @property {number} transferSize
|
|
40
40
|
* @property {number} blockingTime
|
|
41
|
+
* @property {number} tbtImpact
|
|
41
42
|
*/
|
|
42
43
|
|
|
43
44
|
/**
|
|
44
45
|
* @typedef URLSummary
|
|
45
46
|
* @property {number} transferSize
|
|
46
47
|
* @property {number} blockingTime
|
|
48
|
+
* @property {number} tbtImpact
|
|
47
49
|
* @property {string | LH.IcuMessage} url
|
|
48
50
|
*/
|
|
49
51
|
|
|
@@ -72,24 +74,24 @@ class ThirdPartySummary extends Audit {
|
|
|
72
74
|
title: str_(UIStrings.title),
|
|
73
75
|
failureTitle: str_(UIStrings.failureTitle),
|
|
74
76
|
description: str_(UIStrings.description),
|
|
75
|
-
requiredArtifacts: ['traces', 'devtoolsLogs', 'URL'],
|
|
77
|
+
requiredArtifacts: ['traces', 'devtoolsLogs', 'URL', 'GatherContext'],
|
|
76
78
|
};
|
|
77
79
|
}
|
|
78
80
|
|
|
79
81
|
/**
|
|
80
82
|
*
|
|
81
83
|
* @param {Array<LH.Artifacts.NetworkRequest>} networkRecords
|
|
82
|
-
* @param {Array<LH.Artifacts.
|
|
84
|
+
* @param {Array<LH.Artifacts.TBTImpactTask>} tbtImpactTasks
|
|
83
85
|
* @param {number} cpuMultiplier
|
|
84
86
|
* @param {LH.Artifacts.EntityClassification} entityClassification
|
|
85
87
|
* @return {SummaryMaps}
|
|
86
88
|
*/
|
|
87
|
-
static getSummaries(networkRecords,
|
|
89
|
+
static getSummaries(networkRecords, tbtImpactTasks, cpuMultiplier, entityClassification) {
|
|
88
90
|
/** @type {Map<string, Summary>} */
|
|
89
91
|
const byURL = new Map();
|
|
90
92
|
/** @type {Map<LH.Artifacts.Entity, Summary>} */
|
|
91
93
|
const byEntity = new Map();
|
|
92
|
-
const defaultSummary = {mainThreadTime: 0, blockingTime: 0, transferSize: 0};
|
|
94
|
+
const defaultSummary = {mainThreadTime: 0, blockingTime: 0, transferSize: 0, tbtImpact: 0};
|
|
93
95
|
|
|
94
96
|
for (const request of networkRecords) {
|
|
95
97
|
const urlSummary = byURL.get(request.url) || {...defaultSummary};
|
|
@@ -99,7 +101,7 @@ class ThirdPartySummary extends Audit {
|
|
|
99
101
|
|
|
100
102
|
const jsURLs = getJavaScriptURLs(networkRecords);
|
|
101
103
|
|
|
102
|
-
for (const task of
|
|
104
|
+
for (const task of tbtImpactTasks) {
|
|
103
105
|
const attributableURL = getAttributableURLForTask(task, jsURLs);
|
|
104
106
|
|
|
105
107
|
const urlSummary = byURL.get(attributableURL) || {...defaultSummary};
|
|
@@ -110,6 +112,7 @@ class ThirdPartySummary extends Audit {
|
|
|
110
112
|
// Note that this is not totally equivalent to the TBT definition since it fails to account for FCP,
|
|
111
113
|
// but a majority of third-party work occurs after FCP and should yield largely similar numbers.
|
|
112
114
|
urlSummary.blockingTime += Math.max(taskDuration - 50, 0);
|
|
115
|
+
urlSummary.tbtImpact += task.selfTbtImpact;
|
|
113
116
|
byURL.set(attributableURL, urlSummary);
|
|
114
117
|
}
|
|
115
118
|
|
|
@@ -127,6 +130,7 @@ class ThirdPartySummary extends Audit {
|
|
|
127
130
|
entitySummary.transferSize += urlSummary.transferSize;
|
|
128
131
|
entitySummary.mainThreadTime += urlSummary.mainThreadTime;
|
|
129
132
|
entitySummary.blockingTime += urlSummary.blockingTime;
|
|
133
|
+
entitySummary.tbtImpact += urlSummary.tbtImpact;
|
|
130
134
|
byEntity.set(entity, entitySummary);
|
|
131
135
|
|
|
132
136
|
const entityURLs = urls.get(entity) || [];
|
|
@@ -152,7 +156,7 @@ class ThirdPartySummary extends Audit {
|
|
|
152
156
|
// Sort by blocking time first, then transfer size to break ties.
|
|
153
157
|
.sort((a, b) => (b.blockingTime - a.blockingTime) || (b.transferSize - a.transferSize));
|
|
154
158
|
|
|
155
|
-
const subitemSummary = {transferSize: 0, blockingTime: 0};
|
|
159
|
+
const subitemSummary = {transferSize: 0, blockingTime: 0, tbtImpact: 0};
|
|
156
160
|
const minTransferSize = Math.max(MIN_TRANSFER_SIZE_FOR_SUBITEMS, stats.transferSize / 20);
|
|
157
161
|
const maxSubItems = Math.min(MAX_SUBITEMS, items.length);
|
|
158
162
|
let numSubItems = 0;
|
|
@@ -167,6 +171,7 @@ class ThirdPartySummary extends Audit {
|
|
|
167
171
|
numSubItems++;
|
|
168
172
|
subitemSummary.transferSize += nextSubItem.transferSize;
|
|
169
173
|
subitemSummary.blockingTime += nextSubItem.blockingTime;
|
|
174
|
+
subitemSummary.tbtImpact += nextSubItem.tbtImpact;
|
|
170
175
|
}
|
|
171
176
|
if (!subitemSummary.blockingTime && !subitemSummary.transferSize) {
|
|
172
177
|
// Don't bother breaking down if there are no large resources.
|
|
@@ -179,6 +184,7 @@ class ThirdPartySummary extends Audit {
|
|
|
179
184
|
url: str_(i18n.UIStrings.otherResourcesLabel),
|
|
180
185
|
transferSize: stats.transferSize - subitemSummary.transferSize,
|
|
181
186
|
blockingTime: stats.blockingTime - subitemSummary.blockingTime,
|
|
187
|
+
tbtImpact: stats.tbtImpact - subitemSummary.tbtImpact,
|
|
182
188
|
};
|
|
183
189
|
if (remainder.transferSize > minTransferSize) {
|
|
184
190
|
items.push(remainder);
|
|
@@ -193,19 +199,21 @@ class ThirdPartySummary extends Audit {
|
|
|
193
199
|
*/
|
|
194
200
|
static async audit(artifacts, context) {
|
|
195
201
|
const settings = context.settings || {};
|
|
196
|
-
const trace = artifacts.traces[Audit.DEFAULT_PASS];
|
|
197
202
|
const devtoolsLog = artifacts.devtoolsLogs[Audit.DEFAULT_PASS];
|
|
198
203
|
const networkRecords = await NetworkRecords.request(devtoolsLog, context);
|
|
199
204
|
const classifiedEntities = await EntityClassification.request(
|
|
200
205
|
{URL: artifacts.URL, devtoolsLog}, context);
|
|
201
206
|
const firstPartyEntity = classifiedEntities.firstParty;
|
|
202
|
-
|
|
207
|
+
|
|
208
|
+
const metricComputationData = Audit.makeMetricComputationDataInput(artifacts, context);
|
|
209
|
+
const tbtImpactTasks = await TBTImpactTasks.request(metricComputationData, context);
|
|
210
|
+
|
|
203
211
|
const multiplier = settings.throttlingMethod === 'simulate' ?
|
|
204
212
|
settings.throttling.cpuSlowdownMultiplier : 1;
|
|
205
213
|
|
|
206
214
|
const summaries = ThirdPartySummary.getSummaries(
|
|
207
|
-
networkRecords,
|
|
208
|
-
const overallSummary = {wastedBytes: 0, wastedMs: 0};
|
|
215
|
+
networkRecords, tbtImpactTasks, multiplier, classifiedEntities);
|
|
216
|
+
const overallSummary = {wastedBytes: 0, wastedMs: 0, tbtImpact: 0};
|
|
209
217
|
|
|
210
218
|
const results = Array.from(summaries.byEntity.entries())
|
|
211
219
|
// Don't consider the page we're on to be third-party.
|
|
@@ -214,6 +222,7 @@ class ThirdPartySummary extends Audit {
|
|
|
214
222
|
.map(([entity, stats]) => {
|
|
215
223
|
overallSummary.wastedBytes += stats.transferSize;
|
|
216
224
|
overallSummary.wastedMs += stats.blockingTime;
|
|
225
|
+
overallSummary.tbtImpact += stats.tbtImpact;
|
|
217
226
|
|
|
218
227
|
return {
|
|
219
228
|
...stats,
|
|
@@ -240,6 +249,7 @@ class ThirdPartySummary extends Audit {
|
|
|
240
249
|
return {
|
|
241
250
|
score: 1,
|
|
242
251
|
notApplicable: true,
|
|
252
|
+
metricSavings: {TBT: 0},
|
|
243
253
|
};
|
|
244
254
|
}
|
|
245
255
|
|
|
@@ -252,6 +262,9 @@ class ThirdPartySummary extends Audit {
|
|
|
252
262
|
timeInMs: overallSummary.wastedMs,
|
|
253
263
|
}),
|
|
254
264
|
details,
|
|
265
|
+
metricSavings: {
|
|
266
|
+
TBT: overallSummary.tbtImpact,
|
|
267
|
+
},
|
|
255
268
|
};
|
|
256
269
|
}
|
|
257
270
|
}
|
|
@@ -1,14 +1,9 @@
|
|
|
1
1
|
export { TBTImpactTasksComputed as TBTImpactTasks };
|
|
2
|
-
export type TBTImpactTask = LH.Artifacts.TaskNode & {
|
|
3
|
-
tbtImpact: number;
|
|
4
|
-
selfTbtImpact: number;
|
|
5
|
-
};
|
|
6
2
|
declare const TBTImpactTasksComputed: typeof TBTImpactTasks & {
|
|
7
3
|
request: (dependencies: import("../index.js").Artifacts.MetricComputationDataInput, context: import("../../types/utility-types.js").default.ImmutableObject<{
|
|
8
4
|
computedCache: Map<string, import("../lib/arbitrary-equality-map.js").ArbitraryEqualityMap>;
|
|
9
|
-
}>) => Promise<TBTImpactTask[]>;
|
|
5
|
+
}>) => Promise<import("../index.js").Artifacts.TBTImpactTask[]>;
|
|
10
6
|
};
|
|
11
|
-
/** @typedef {LH.Artifacts.TaskNode & {tbtImpact: number, selfTbtImpact: number}} TBTImpactTask */
|
|
12
7
|
declare class TBTImpactTasks {
|
|
13
8
|
/**
|
|
14
9
|
* @param {LH.Artifacts.TaskNode} task
|
|
@@ -28,27 +23,27 @@ declare class TBTImpactTasks {
|
|
|
28
23
|
* @param {LH.Artifacts.TaskNode[]} tasks
|
|
29
24
|
* @param {Map<LH.Artifacts.TaskNode, number>} taskToImpact
|
|
30
25
|
*/
|
|
31
|
-
static createImpactTasks(tasks: LH.Artifacts.TaskNode[], taskToImpact: Map<LH.Artifacts.TaskNode, number>): TBTImpactTask[];
|
|
26
|
+
static createImpactTasks(tasks: LH.Artifacts.TaskNode[], taskToImpact: Map<LH.Artifacts.TaskNode, number>): import("../index.js").Artifacts.TBTImpactTask[];
|
|
32
27
|
/**
|
|
33
28
|
* @param {LH.Artifacts.TaskNode[]} tasks
|
|
34
29
|
* @param {number} startTimeMs
|
|
35
30
|
* @param {number} endTimeMs
|
|
36
|
-
* @return {TBTImpactTask[]}
|
|
31
|
+
* @return {LH.Artifacts.TBTImpactTask[]}
|
|
37
32
|
*/
|
|
38
|
-
static computeImpactsFromObservedTasks(tasks: LH.Artifacts.TaskNode[], startTimeMs: number, endTimeMs: number): TBTImpactTask[];
|
|
33
|
+
static computeImpactsFromObservedTasks(tasks: LH.Artifacts.TaskNode[], startTimeMs: number, endTimeMs: number): LH.Artifacts.TBTImpactTask[];
|
|
39
34
|
/**
|
|
40
35
|
* @param {LH.Artifacts.TaskNode[]} tasks
|
|
41
36
|
* @param {LH.Gatherer.Simulation.Result['nodeTimings']} tbtNodeTimings
|
|
42
37
|
* @param {number} startTimeMs
|
|
43
38
|
* @param {number} endTimeMs
|
|
44
|
-
* @return {TBTImpactTask[]}
|
|
39
|
+
* @return {LH.Artifacts.TBTImpactTask[]}
|
|
45
40
|
*/
|
|
46
|
-
static computeImpactsFromLantern(tasks: LH.Artifacts.TaskNode[], tbtNodeTimings: LH.Gatherer.Simulation.Result['nodeTimings'], startTimeMs: number, endTimeMs: number): TBTImpactTask[];
|
|
41
|
+
static computeImpactsFromLantern(tasks: LH.Artifacts.TaskNode[], tbtNodeTimings: LH.Gatherer.Simulation.Result['nodeTimings'], startTimeMs: number, endTimeMs: number): LH.Artifacts.TBTImpactTask[];
|
|
47
42
|
/**
|
|
48
43
|
* @param {LH.Artifacts.MetricComputationDataInput} metricComputationData
|
|
49
44
|
* @param {LH.Artifacts.ComputedContext} context
|
|
50
|
-
* @return {Promise<TBTImpactTask[]>}
|
|
45
|
+
* @return {Promise<LH.Artifacts.TBTImpactTask[]>}
|
|
51
46
|
*/
|
|
52
|
-
static compute_(metricComputationData: LH.Artifacts.MetricComputationDataInput, context: LH.Artifacts.ComputedContext): Promise<TBTImpactTask[]>;
|
|
47
|
+
static compute_(metricComputationData: LH.Artifacts.MetricComputationDataInput, context: LH.Artifacts.ComputedContext): Promise<LH.Artifacts.TBTImpactTask[]>;
|
|
53
48
|
}
|
|
54
49
|
//# sourceMappingURL=tbt-impact-tasks.d.ts.map
|
|
@@ -12,8 +12,6 @@ import {TotalBlockingTime} from './metrics/total-blocking-time.js';
|
|
|
12
12
|
import {ProcessedTrace} from './processed-trace.js';
|
|
13
13
|
import {calculateTbtImpactForEvent} from './metrics/tbt-utils.js';
|
|
14
14
|
|
|
15
|
-
/** @typedef {LH.Artifacts.TaskNode & {tbtImpact: number, selfTbtImpact: number}} TBTImpactTask */
|
|
16
|
-
|
|
17
15
|
class TBTImpactTasks {
|
|
18
16
|
/**
|
|
19
17
|
* @param {LH.Artifacts.TaskNode} task
|
|
@@ -66,7 +64,7 @@ class TBTImpactTasks {
|
|
|
66
64
|
* @param {Map<LH.Artifacts.TaskNode, number>} taskToImpact
|
|
67
65
|
*/
|
|
68
66
|
static createImpactTasks(tasks, taskToImpact) {
|
|
69
|
-
/** @type {TBTImpactTask[]} */
|
|
67
|
+
/** @type {LH.Artifacts.TBTImpactTask[]} */
|
|
70
68
|
const tbtImpactTasks = [];
|
|
71
69
|
|
|
72
70
|
for (const task of tasks) {
|
|
@@ -92,7 +90,7 @@ class TBTImpactTasks {
|
|
|
92
90
|
* @param {LH.Artifacts.TaskNode[]} tasks
|
|
93
91
|
* @param {number} startTimeMs
|
|
94
92
|
* @param {number} endTimeMs
|
|
95
|
-
* @return {TBTImpactTask[]}
|
|
93
|
+
* @return {LH.Artifacts.TBTImpactTask[]}
|
|
96
94
|
*/
|
|
97
95
|
static computeImpactsFromObservedTasks(tasks, startTimeMs, endTimeMs) {
|
|
98
96
|
/** @type {Map<LH.Artifacts.TaskNode, number>} */
|
|
@@ -125,7 +123,7 @@ class TBTImpactTasks {
|
|
|
125
123
|
* @param {LH.Gatherer.Simulation.Result['nodeTimings']} tbtNodeTimings
|
|
126
124
|
* @param {number} startTimeMs
|
|
127
125
|
* @param {number} endTimeMs
|
|
128
|
-
* @return {TBTImpactTask[]}
|
|
126
|
+
* @return {LH.Artifacts.TBTImpactTask[]}
|
|
129
127
|
*/
|
|
130
128
|
static computeImpactsFromLantern(tasks, tbtNodeTimings, startTimeMs, endTimeMs) {
|
|
131
129
|
/** @type {Map<LH.Artifacts.TaskNode, number>} */
|
|
@@ -193,7 +191,7 @@ class TBTImpactTasks {
|
|
|
193
191
|
/**
|
|
194
192
|
* @param {LH.Artifacts.MetricComputationDataInput} metricComputationData
|
|
195
193
|
* @param {LH.Artifacts.ComputedContext} context
|
|
196
|
-
* @return {Promise<TBTImpactTask[]>}
|
|
194
|
+
* @return {Promise<LH.Artifacts.TBTImpactTask[]>}
|
|
197
195
|
*/
|
|
198
196
|
static async compute_(metricComputationData, context) {
|
|
199
197
|
const tbtResult = await TotalBlockingTime.request(metricComputationData, context);
|