lighthouse 10.3.0 → 10.4.0-dev.20230711
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/test/smokehouse/frontends/smokehouse-bin.js +38 -22
- package/cli/test/smokehouse/lighthouse-runners/bundle.d.ts +1 -2
- package/cli/test/smokehouse/lighthouse-runners/bundle.js +11 -24
- package/cli/test/smokehouse/lighthouse-runners/cli.d.ts +1 -2
- package/cli/test/smokehouse/lighthouse-runners/cli.js +3 -7
- package/cli/test/smokehouse/lighthouse-runners/devtools.d.ts +1 -2
- package/cli/test/smokehouse/lighthouse-runners/devtools.js +1 -11
- package/cli/test/smokehouse/readme.md +0 -2
- package/cli/test/smokehouse/report-assert.d.ts +1 -2
- package/cli/test/smokehouse/report-assert.js +2 -19
- package/cli/test/smokehouse/smokehouse.js +3 -32
- package/core/audits/accessibility/aria-dialog-name.d.ts +10 -0
- package/core/audits/accessibility/aria-dialog-name.js +45 -0
- package/core/audits/accessibility/aria-text.d.ts +10 -0
- package/core/audits/accessibility/aria-text.js +44 -0
- package/core/audits/accessibility/empty-heading.d.ts +10 -0
- package/core/audits/accessibility/empty-heading.js +45 -0
- package/core/audits/accessibility/identical-links-same-purpose.d.ts +10 -0
- package/core/audits/accessibility/identical-links-same-purpose.js +45 -0
- package/core/audits/accessibility/landmark-one-main.d.ts +10 -0
- package/core/audits/accessibility/landmark-one-main.js +44 -0
- package/core/audits/accessibility/link-in-text-block.d.ts +10 -0
- package/core/audits/accessibility/link-in-text-block.js +44 -0
- package/core/audits/accessibility/select-name.d.ts +10 -0
- package/core/audits/accessibility/select-name.js +44 -0
- package/core/audits/accessibility/target-size.d.ts +10 -0
- package/core/audits/accessibility/target-size.js +45 -0
- package/core/audits/audit.d.ts +6 -0
- package/core/audits/audit.js +12 -0
- package/core/audits/byte-efficiency/byte-efficiency-audit.d.ts +22 -6
- package/core/audits/byte-efficiency/byte-efficiency-audit.js +97 -25
- package/core/audits/byte-efficiency/render-blocking-resources.js +1 -0
- package/core/audits/long-tasks.d.ts +48 -0
- package/core/audits/long-tasks.js +146 -19
- package/core/audits/non-composited-animations.js +7 -0
- package/core/audits/prioritize-lcp-image.js +2 -1
- package/core/audits/redirects.js +4 -0
- package/core/audits/seo/link-text.js +10 -0
- package/core/audits/unsized-images.js +3 -0
- package/core/audits/viewport.js +11 -0
- package/core/audits/work-during-interaction.js +13 -2
- package/core/computed/js-bundles.js +1 -1
- package/core/computed/metrics/tbt-utils.d.ts +26 -0
- package/core/computed/metrics/tbt-utils.js +48 -28
- package/core/computed/metrics/total-blocking-time.js +1 -1
- package/core/computed/tbt-impact-tasks.d.ts +54 -0
- package/core/computed/tbt-impact-tasks.js +221 -0
- package/core/config/default-config.js +17 -0
- package/core/config/filters.d.ts +9 -9
- package/core/config/filters.js +7 -7
- package/core/config/validation.js +12 -0
- package/core/gather/base-gatherer.d.ts +1 -3
- package/core/gather/base-gatherer.js +1 -3
- package/core/gather/gatherers/accessibility.js +8 -3
- package/core/gather/gatherers/seo/font-size.d.ts +0 -1
- package/core/gather/gatherers/seo/font-size.js +0 -1
- package/core/gather/gatherers/source-maps.js +3 -2
- package/core/lib/cdt/SDK.d.ts +1 -1
- package/core/lib/cdt/SDK.js +2 -2
- package/core/lib/dependency-graph/simulator/simulator.js +3 -1
- package/core/lib/lh-error.d.ts +14 -2
- package/core/lib/lh-error.js +7 -1
- package/core/lib/navigation-error.d.ts +2 -2
- package/core/lib/navigation-error.js +1 -1
- package/core/lib/network-recorder.js +1 -0
- package/core/lib/stack-packs.js +4 -0
- package/dist/report/bundle.esm.js +2 -1
- package/dist/report/flow.js +9 -9
- package/dist/report/standalone.js +2 -2
- package/package.json +5 -5
- package/report/assets/styles.css +5 -1
- package/report/renderer/category-renderer.js +1 -0
- package/report/renderer/components.js +1 -1
- package/shared/localization/locales/ar-XB.json +233 -2
- package/shared/localization/locales/ar.json +233 -2
- package/shared/localization/locales/bg.json +233 -2
- package/shared/localization/locales/ca.json +233 -2
- package/shared/localization/locales/cs.json +246 -15
- package/shared/localization/locales/da.json +239 -8
- package/shared/localization/locales/de.json +238 -7
- package/shared/localization/locales/el.json +234 -3
- package/shared/localization/locales/en-GB.json +233 -2
- package/shared/localization/locales/en-US.json +87 -0
- package/shared/localization/locales/en-XA.json +233 -2
- package/shared/localization/locales/en-XL.json +87 -0
- package/shared/localization/locales/es-419.json +233 -2
- package/shared/localization/locales/es.json +233 -2
- package/shared/localization/locales/fi.json +233 -2
- package/shared/localization/locales/fil.json +236 -5
- package/shared/localization/locales/fr.json +235 -4
- package/shared/localization/locales/he.json +233 -2
- package/shared/localization/locales/hi.json +236 -5
- package/shared/localization/locales/hr.json +234 -3
- package/shared/localization/locales/hu.json +233 -2
- package/shared/localization/locales/id.json +233 -2
- package/shared/localization/locales/it.json +234 -3
- package/shared/localization/locales/ja.json +233 -2
- package/shared/localization/locales/ko.json +233 -2
- package/shared/localization/locales/lt.json +233 -2
- package/shared/localization/locales/lv.json +250 -19
- package/shared/localization/locales/nl.json +233 -2
- package/shared/localization/locales/no.json +235 -4
- package/shared/localization/locales/pl.json +233 -2
- package/shared/localization/locales/pt-PT.json +233 -2
- package/shared/localization/locales/pt.json +234 -3
- package/shared/localization/locales/ro.json +233 -2
- package/shared/localization/locales/ru.json +233 -2
- package/shared/localization/locales/sk.json +233 -2
- package/shared/localization/locales/sl.json +233 -2
- package/shared/localization/locales/sr-Latn.json +233 -2
- package/shared/localization/locales/sr.json +233 -2
- package/shared/localization/locales/sv.json +233 -2
- package/shared/localization/locales/ta.json +233 -2
- package/shared/localization/locales/te.json +233 -2
- package/shared/localization/locales/th.json +233 -2
- package/shared/localization/locales/tr.json +233 -2
- package/shared/localization/locales/uk.json +233 -2
- package/shared/localization/locales/vi.json +233 -2
- package/shared/localization/locales/zh-HK.json +233 -2
- package/shared/localization/locales/zh-TW.json +235 -4
- package/shared/localization/locales/zh.json +239 -8
- package/tsconfig.json +1 -1
- package/types/artifacts.d.ts +9 -2
- package/types/internal/smokehouse.d.ts +1 -3
- package/core/audits/metrics/first-contentful-paint-3g.d.ts +0 -15
- package/core/audits/metrics/first-contentful-paint-3g.js +0 -69
|
@@ -14,6 +14,7 @@ import {getJavaScriptURLs, getAttributableURLForTask} from '../lib/tracehouse/ta
|
|
|
14
14
|
|
|
15
15
|
/** We don't always have timing data for short tasks, if we're missing timing data. Treat it as though it were 0ms. */
|
|
16
16
|
const DEFAULT_TIMING = {startTime: 0, endTime: 0, duration: 0};
|
|
17
|
+
const DISPLAYED_TASK_COUNT = 20;
|
|
17
18
|
|
|
18
19
|
const UIStrings = {
|
|
19
20
|
/** Title of a diagnostic LH audit that provides details on the longest running tasks that occur when the page loads. */
|
|
@@ -31,6 +32,30 @@ const UIStrings = {
|
|
|
31
32
|
|
|
32
33
|
const str_ = i18n.createIcuMessageFn(import.meta.url, UIStrings);
|
|
33
34
|
|
|
35
|
+
/**
|
|
36
|
+
* Insert `url` into `urls` array if not already present. Returns
|
|
37
|
+
* the index of `url` in `urls` for later lookup.
|
|
38
|
+
* @param {Array<string>} urls
|
|
39
|
+
* @param {string} url
|
|
40
|
+
*/
|
|
41
|
+
function insertUrl(urls, url) {
|
|
42
|
+
const index = urls.indexOf(url);
|
|
43
|
+
if (index > -1) return index;
|
|
44
|
+
return urls.push(url) - 1;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
/**
|
|
48
|
+
* @param {number} value
|
|
49
|
+
* @return {number}
|
|
50
|
+
*/
|
|
51
|
+
function roundTenths(value) {
|
|
52
|
+
return Math.round(value * 10) / 10;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
/** @typedef {import('../lib/tracehouse/task-groups.js').TaskGroupIds} TaskGroupIds */
|
|
56
|
+
/** @typedef {{startTime: number, duration: number}} Timing */
|
|
57
|
+
/** @typedef {Timing & {urlIndex: number, [p: string]: number}} DebugTask */
|
|
58
|
+
|
|
34
59
|
class LongTasks extends Audit {
|
|
35
60
|
/**
|
|
36
61
|
* @return {LH.Audit.Meta}
|
|
@@ -45,6 +70,102 @@ class LongTasks extends Audit {
|
|
|
45
70
|
};
|
|
46
71
|
}
|
|
47
72
|
|
|
73
|
+
/**
|
|
74
|
+
* Returns the timing information for the given task, recursively walking the
|
|
75
|
+
* task's children and adding up time spent in each type of task activity.
|
|
76
|
+
* If `taskTimingsByEvent` is present, it will be used for task timing instead
|
|
77
|
+
* of the timings on the tasks themselves.
|
|
78
|
+
* If `timeByTaskGroup` is not provided, a new Map will be populated with
|
|
79
|
+
* timing breakdown; if one is provided, timing breakdown will be added to the
|
|
80
|
+
* existing breakdown.
|
|
81
|
+
*
|
|
82
|
+
* TODO: when simulated, a significant number of child tasks are dropped, so
|
|
83
|
+
* most time will be attributed to 'other' (the category of the top-level
|
|
84
|
+
* RunTask). See pruning in `PageDependencyGraph.linkCPUNodes`.
|
|
85
|
+
* @param {LH.Artifacts.TaskNode} task
|
|
86
|
+
* @param {Map<LH.TraceEvent, LH.Gatherer.Simulation.NodeTiming>|undefined} taskTimingsByEvent
|
|
87
|
+
* @param {Map<TaskGroupIds, number>} [timeByTaskGroup]
|
|
88
|
+
* @return {{startTime: number, duration: number, timeByTaskGroup: Map<TaskGroupIds, number>}}
|
|
89
|
+
*/
|
|
90
|
+
static getTimingBreakdown(task, taskTimingsByEvent, timeByTaskGroup = new Map()) {
|
|
91
|
+
const taskTiming = LongTasks.getTiming(task, taskTimingsByEvent);
|
|
92
|
+
|
|
93
|
+
// Add up child time, while recursively stepping in to accumulate group times.
|
|
94
|
+
let childrenTime = 0;
|
|
95
|
+
if (taskTiming.duration > 0) {
|
|
96
|
+
for (const child of task.children) {
|
|
97
|
+
const {duration} = LongTasks.getTimingBreakdown(child, taskTimingsByEvent, timeByTaskGroup);
|
|
98
|
+
childrenTime += duration;
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
// Add this task's selfTime to its group's total time.
|
|
103
|
+
const selfTime = taskTiming.duration - childrenTime;
|
|
104
|
+
const taskGroupTime = timeByTaskGroup.get(task.group.id) || 0;
|
|
105
|
+
timeByTaskGroup.set(task.group.id, taskGroupTime + selfTime);
|
|
106
|
+
|
|
107
|
+
return {
|
|
108
|
+
startTime: taskTiming.startTime,
|
|
109
|
+
duration: taskTiming.duration,
|
|
110
|
+
timeByTaskGroup,
|
|
111
|
+
};
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
/**
|
|
115
|
+
* @param {Array<LH.Artifacts.TaskNode>} longTasks
|
|
116
|
+
* @param {Set<string>} jsUrls
|
|
117
|
+
* @param {Map<LH.TraceEvent, LH.Gatherer.Simulation.NodeTiming>|undefined} taskTimingsByEvent
|
|
118
|
+
* @return {LH.Audit.Details.DebugData}
|
|
119
|
+
*/
|
|
120
|
+
static makeDebugData(longTasks, jsUrls, taskTimingsByEvent) {
|
|
121
|
+
/** @type {Array<string>} */
|
|
122
|
+
const urls = [];
|
|
123
|
+
/** @type {Array<DebugTask>} */
|
|
124
|
+
const tasks = [];
|
|
125
|
+
|
|
126
|
+
for (const longTask of longTasks) {
|
|
127
|
+
const attributableUrl = getAttributableURLForTask(longTask, jsUrls);
|
|
128
|
+
|
|
129
|
+
const {startTime, duration, timeByTaskGroup} =
|
|
130
|
+
LongTasks.getTimingBreakdown(longTask, taskTimingsByEvent);
|
|
131
|
+
|
|
132
|
+
// Round time per group and sort entries so order is consistent.
|
|
133
|
+
const timeByTaskGroupEntries = [...timeByTaskGroup]
|
|
134
|
+
.map(/** @return {[TaskGroupIds, number]} */ ([group, time]) => [group, roundTenths(time)])
|
|
135
|
+
.sort((a, b) => a[0].localeCompare(b[0]));
|
|
136
|
+
|
|
137
|
+
tasks.push({
|
|
138
|
+
urlIndex: insertUrl(urls, attributableUrl),
|
|
139
|
+
startTime: roundTenths(startTime),
|
|
140
|
+
duration: roundTenths(duration),
|
|
141
|
+
...Object.fromEntries(timeByTaskGroupEntries),
|
|
142
|
+
});
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
return {
|
|
146
|
+
type: 'debugdata',
|
|
147
|
+
urls,
|
|
148
|
+
tasks,
|
|
149
|
+
};
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
/**
|
|
153
|
+
* Get timing from task, overridden by taskTimingsByEvent if provided.
|
|
154
|
+
* @param {LH.Artifacts.TaskNode} task
|
|
155
|
+
* @param {Map<LH.TraceEvent, LH.Gatherer.Simulation.NodeTiming>|undefined} taskTimingsByEvent
|
|
156
|
+
* @return {Timing}
|
|
157
|
+
*/
|
|
158
|
+
static getTiming(task, taskTimingsByEvent) {
|
|
159
|
+
/** @type {Timing} */
|
|
160
|
+
let timing = task;
|
|
161
|
+
if (taskTimingsByEvent) {
|
|
162
|
+
timing = taskTimingsByEvent.get(task.event) || DEFAULT_TIMING;
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
const {duration, startTime} = timing;
|
|
166
|
+
return {duration, startTime};
|
|
167
|
+
}
|
|
168
|
+
|
|
48
169
|
/**
|
|
49
170
|
* @param {LH.Artifacts} artifacts
|
|
50
171
|
* @param {LH.Audit.Context} context
|
|
@@ -58,10 +179,12 @@ class LongTasks extends Audit {
|
|
|
58
179
|
const devtoolsLog = artifacts.devtoolsLogs[LongTasks.DEFAULT_PASS];
|
|
59
180
|
const networkRecords = await NetworkRecords.request(devtoolsLog, context);
|
|
60
181
|
|
|
61
|
-
/** @type {Map<LH.TraceEvent, LH.Gatherer.Simulation.NodeTiming
|
|
62
|
-
|
|
182
|
+
/** @type {Map<LH.TraceEvent, LH.Gatherer.Simulation.NodeTiming>|undefined} */
|
|
183
|
+
let taskTimingsByEvent;
|
|
63
184
|
|
|
64
185
|
if (settings.throttlingMethod === 'simulate') {
|
|
186
|
+
taskTimingsByEvent = new Map();
|
|
187
|
+
|
|
65
188
|
const simulatorOptions = {devtoolsLog, settings: context.settings};
|
|
66
189
|
const pageGraph = await PageDependencyGraph.request({trace, devtoolsLog, URL}, context);
|
|
67
190
|
const simulator = await LoadSimulator.request(simulatorOptions, context);
|
|
@@ -70,30 +193,32 @@ class LongTasks extends Audit {
|
|
|
70
193
|
if (node.type !== 'cpu') continue;
|
|
71
194
|
taskTimingsByEvent.set(node.event, timing);
|
|
72
195
|
}
|
|
73
|
-
} else {
|
|
74
|
-
for (const task of tasks) {
|
|
75
|
-
if (task.unbounded || task.parent) continue;
|
|
76
|
-
taskTimingsByEvent.set(task.event, task);
|
|
77
|
-
}
|
|
78
196
|
}
|
|
79
197
|
|
|
80
198
|
const jsURLs = getJavaScriptURLs(networkRecords);
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
199
|
+
|
|
200
|
+
// Only consider top-level (no parent) long tasks that have an explicit endTime.
|
|
201
|
+
const longTasks = tasks
|
|
202
|
+
.map(task => {
|
|
203
|
+
// Use duration from simulation, if available.
|
|
204
|
+
const {duration} = LongTasks.getTiming(task, taskTimingsByEvent);
|
|
205
|
+
return {task, duration};
|
|
206
|
+
})
|
|
207
|
+
.filter(({task, duration}) => {
|
|
208
|
+
return duration >= 50 && !task.unbounded && !task.parent;
|
|
86
209
|
})
|
|
87
|
-
.filter(t => t.duration >= 50 && !t.unbounded && !t.parent)
|
|
88
210
|
.sort((a, b) => b.duration - a.duration)
|
|
89
|
-
.
|
|
211
|
+
.map(({task}) => task);
|
|
90
212
|
|
|
91
213
|
// TODO(beytoven): Add start time that matches with the simulated throttling
|
|
92
|
-
const results =
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
214
|
+
const results = longTasks.map(task => {
|
|
215
|
+
const timing = LongTasks.getTiming(task, taskTimingsByEvent);
|
|
216
|
+
return {
|
|
217
|
+
url: getAttributableURLForTask(task, jsURLs),
|
|
218
|
+
duration: timing.duration,
|
|
219
|
+
startTime: timing.startTime,
|
|
220
|
+
};
|
|
221
|
+
}).slice(0, DISPLAYED_TASK_COUNT);
|
|
97
222
|
|
|
98
223
|
/** @type {LH.Audit.Details.Table['headings']} */
|
|
99
224
|
const headings = [
|
|
@@ -107,6 +232,8 @@ class LongTasks extends Audit {
|
|
|
107
232
|
const tableDetails = Audit.makeTableDetails(headings, results,
|
|
108
233
|
{sortedBy: ['duration'], skipSumming: ['startTime']});
|
|
109
234
|
|
|
235
|
+
tableDetails.debugData = LongTasks.makeDebugData(longTasks, jsURLs, taskTimingsByEvent);
|
|
236
|
+
|
|
110
237
|
let displayValue;
|
|
111
238
|
if (results.length > 0) {
|
|
112
239
|
displayValue = str_(UIStrings.displayValue, {itemCount: results.length});
|
|
@@ -125,6 +125,7 @@ class NonCompositedAnimations extends Audit {
|
|
|
125
125
|
return {
|
|
126
126
|
score: 1,
|
|
127
127
|
notApplicable: true,
|
|
128
|
+
metricSavings: {CLS: 0},
|
|
128
129
|
};
|
|
129
130
|
}
|
|
130
131
|
|
|
@@ -195,6 +196,12 @@ class NonCompositedAnimations extends Audit {
|
|
|
195
196
|
return {
|
|
196
197
|
score: results.length === 0 ? 1 : 0,
|
|
197
198
|
notApplicable: results.length === 0,
|
|
199
|
+
metricSavings: {
|
|
200
|
+
// We do not have enough information to accurately predict the impact of individual animations on CLS.
|
|
201
|
+
// It is also not worth the effort since only a small percentage of sites have their CLS affected by non-composited animations.
|
|
202
|
+
// https://github.com/GoogleChrome/lighthouse/pull/15099#issuecomment-1558107906
|
|
203
|
+
CLS: 0,
|
|
204
|
+
},
|
|
198
205
|
details,
|
|
199
206
|
displayValue,
|
|
200
207
|
};
|
|
@@ -244,7 +244,7 @@ class PrioritizeLcpImage extends Audit {
|
|
|
244
244
|
.find(element => element.traceEventType === 'largest-contentful-paint');
|
|
245
245
|
|
|
246
246
|
if (!lcpElement || lcpElement.type !== 'image') {
|
|
247
|
-
return {score: null, notApplicable: true};
|
|
247
|
+
return {score: null, notApplicable: true, metricSavings: {LCP: 0}};
|
|
248
248
|
}
|
|
249
249
|
|
|
250
250
|
const mainResource = await MainResource.request({devtoolsLog, URL}, context);
|
|
@@ -286,6 +286,7 @@ class PrioritizeLcpImage extends Audit {
|
|
|
286
286
|
numericUnit: 'millisecond',
|
|
287
287
|
displayValue: wastedMs ? str_(i18n.UIStrings.displayValueMsSavings, {wastedMs}) : '',
|
|
288
288
|
details,
|
|
289
|
+
metricSavings: {LCP: wastedMs},
|
|
289
290
|
};
|
|
290
291
|
}
|
|
291
292
|
}
|
package/core/audits/redirects.js
CHANGED
|
@@ -74,6 +74,16 @@ const BLOCKLIST = new Set([
|
|
|
74
74
|
'mer',
|
|
75
75
|
'mer info',
|
|
76
76
|
'mer information',
|
|
77
|
+
// Tamil
|
|
78
|
+
'அடுத்த பக்கம்',
|
|
79
|
+
'மறுபக்கம்',
|
|
80
|
+
'முந்தைய பக்கம்',
|
|
81
|
+
'முன்பக்கம்',
|
|
82
|
+
'மேலும் அறிக',
|
|
83
|
+
'மேலும் தகவலுக்கு',
|
|
84
|
+
'மேலும் தரவுகளுக்கு',
|
|
85
|
+
'தயவுசெய்து இங்கே அழுத்தவும்',
|
|
86
|
+
'இங்கே கிளிக் செய்யவும்',
|
|
77
87
|
]);
|
|
78
88
|
|
|
79
89
|
const UIStrings = {
|
package/core/audits/viewport.js
CHANGED
|
@@ -46,15 +46,26 @@ class Viewport extends Audit {
|
|
|
46
46
|
static async audit(artifacts, context) {
|
|
47
47
|
const viewportMeta = await ViewportMeta.request(artifacts.MetaElements, context);
|
|
48
48
|
|
|
49
|
+
let inpSavings = 300;
|
|
49
50
|
if (!viewportMeta.hasViewportTag) {
|
|
50
51
|
return {
|
|
51
52
|
score: 0,
|
|
52
53
|
explanation: str_(UIStrings.explanationNoTag),
|
|
54
|
+
metricSavings: {
|
|
55
|
+
INP: inpSavings,
|
|
56
|
+
},
|
|
53
57
|
};
|
|
54
58
|
}
|
|
55
59
|
|
|
60
|
+
if (viewportMeta.isMobileOptimized) {
|
|
61
|
+
inpSavings = 0;
|
|
62
|
+
}
|
|
63
|
+
|
|
56
64
|
return {
|
|
57
65
|
score: Number(viewportMeta.isMobileOptimized),
|
|
66
|
+
metricSavings: {
|
|
67
|
+
INP: inpSavings,
|
|
68
|
+
},
|
|
58
69
|
warnings: viewportMeta.parserWarnings,
|
|
59
70
|
};
|
|
60
71
|
}
|
|
@@ -224,7 +224,11 @@ class WorkDuringInteraction extends Audit {
|
|
|
224
224
|
const {settings} = context;
|
|
225
225
|
// TODO: responsiveness isn't yet supported by lantern.
|
|
226
226
|
if (settings.throttlingMethod === 'simulate') {
|
|
227
|
-
return {
|
|
227
|
+
return {
|
|
228
|
+
score: null,
|
|
229
|
+
notApplicable: true,
|
|
230
|
+
metricSavings: {INP: 0},
|
|
231
|
+
};
|
|
228
232
|
}
|
|
229
233
|
|
|
230
234
|
const trace = artifacts.traces[WorkDuringInteraction.DEFAULT_PASS];
|
|
@@ -232,7 +236,11 @@ class WorkDuringInteraction extends Audit {
|
|
|
232
236
|
const interactionEvent = await Responsiveness.request(metricData, context);
|
|
233
237
|
// If no interaction, diagnostic audit is n/a.
|
|
234
238
|
if (interactionEvent === null) {
|
|
235
|
-
return {
|
|
239
|
+
return {
|
|
240
|
+
score: null,
|
|
241
|
+
notApplicable: true,
|
|
242
|
+
metricSavings: {INP: 0},
|
|
243
|
+
};
|
|
236
244
|
}
|
|
237
245
|
// TODO: remove workaround once 103.0.5052.0 is sufficiently released.
|
|
238
246
|
if (interactionEvent.name === 'FallbackTiming') {
|
|
@@ -271,6 +279,9 @@ class WorkDuringInteraction extends Audit {
|
|
|
271
279
|
type: 'list',
|
|
272
280
|
items: auditDetailsItems,
|
|
273
281
|
},
|
|
282
|
+
metricSavings: {
|
|
283
|
+
INP: duration,
|
|
284
|
+
},
|
|
274
285
|
};
|
|
275
286
|
}
|
|
276
287
|
}
|
|
@@ -99,7 +99,7 @@ class JSBundles {
|
|
|
99
99
|
|
|
100
100
|
const compiledUrl = SourceMap.scriptUrl || 'compiled.js';
|
|
101
101
|
const mapUrl = SourceMap.sourceMapUrl || 'compiled.js.map';
|
|
102
|
-
const map = new SDK.
|
|
102
|
+
const map = new SDK.SourceMap(compiledUrl, mapUrl, rawMap);
|
|
103
103
|
|
|
104
104
|
const sizes = computeGeneratedFileSizes(map, script.length || 0, script.content || '');
|
|
105
105
|
|
|
@@ -15,4 +15,30 @@ export function calculateSumOfBlockingTime(topLevelEvents: Array<{
|
|
|
15
15
|
end: number;
|
|
16
16
|
duration: number;
|
|
17
17
|
}>, startTimeMs: number, endTimeMs: number): number;
|
|
18
|
+
/**
|
|
19
|
+
* For TBT, We only want to consider tasks that fall in our time range
|
|
20
|
+
* - FCP and TTI for navigation mode
|
|
21
|
+
* - Trace start and trace end for timespan mode
|
|
22
|
+
*
|
|
23
|
+
* FCP is picked as `startTimeMs` because there is little risk of user input happening
|
|
24
|
+
* before FCP so Long Queuing Qelay regions do not harm user experience. Developers should be
|
|
25
|
+
* optimizing to reach FCP as fast as possible without having to worry about task lengths.
|
|
26
|
+
*
|
|
27
|
+
* TTI is picked as `endTimeMs` because we want a well defined end point for page load.
|
|
28
|
+
*
|
|
29
|
+
* @param {{start: number, end: number, duration: number}} event
|
|
30
|
+
* @param {number} startTimeMs Should be FCP in navigation mode and the trace start time in timespan mode
|
|
31
|
+
* @param {number} endTimeMs Should be TTI in navigation mode and the trace end time in timespan mode
|
|
32
|
+
* @param {{start: number, end: number, duration: number}} [topLevelEvent] Leave unset if `event` is top level. Has no effect if `event` has the same duration as `topLevelEvent`.
|
|
33
|
+
* @return {number}
|
|
34
|
+
*/
|
|
35
|
+
export function calculateTbtImpactForEvent(event: {
|
|
36
|
+
start: number;
|
|
37
|
+
end: number;
|
|
38
|
+
duration: number;
|
|
39
|
+
}, startTimeMs: number, endTimeMs: number, topLevelEvent?: {
|
|
40
|
+
start: number;
|
|
41
|
+
end: number;
|
|
42
|
+
duration: number;
|
|
43
|
+
} | undefined): number;
|
|
18
44
|
//# sourceMappingURL=tbt-utils.d.ts.map
|
|
@@ -6,6 +6,52 @@
|
|
|
6
6
|
|
|
7
7
|
const BLOCKING_TIME_THRESHOLD = 50;
|
|
8
8
|
|
|
9
|
+
/**
|
|
10
|
+
* For TBT, We only want to consider tasks that fall in our time range
|
|
11
|
+
* - FCP and TTI for navigation mode
|
|
12
|
+
* - Trace start and trace end for timespan mode
|
|
13
|
+
*
|
|
14
|
+
* FCP is picked as `startTimeMs` because there is little risk of user input happening
|
|
15
|
+
* before FCP so Long Queuing Qelay regions do not harm user experience. Developers should be
|
|
16
|
+
* optimizing to reach FCP as fast as possible without having to worry about task lengths.
|
|
17
|
+
*
|
|
18
|
+
* TTI is picked as `endTimeMs` because we want a well defined end point for page load.
|
|
19
|
+
*
|
|
20
|
+
* @param {{start: number, end: number, duration: number}} event
|
|
21
|
+
* @param {number} startTimeMs Should be FCP in navigation mode and the trace start time in timespan mode
|
|
22
|
+
* @param {number} endTimeMs Should be TTI in navigation mode and the trace end time in timespan mode
|
|
23
|
+
* @param {{start: number, end: number, duration: number}} [topLevelEvent] Leave unset if `event` is top level. Has no effect if `event` has the same duration as `topLevelEvent`.
|
|
24
|
+
* @return {number}
|
|
25
|
+
*/
|
|
26
|
+
function calculateTbtImpactForEvent(event, startTimeMs, endTimeMs, topLevelEvent) {
|
|
27
|
+
let threshold = BLOCKING_TIME_THRESHOLD;
|
|
28
|
+
|
|
29
|
+
// If a task is not top level, it doesn't make sense to subtract the entire 50ms
|
|
30
|
+
// blocking threshold from the event.
|
|
31
|
+
//
|
|
32
|
+
// e.g. A 80ms top level task with two 40ms children should attribute some blocking
|
|
33
|
+
// time to the 40ms tasks even though they do not meet the 50ms threshold.
|
|
34
|
+
//
|
|
35
|
+
// The solution is to scale the threshold for child events to be considered blocking.
|
|
36
|
+
if (topLevelEvent) threshold *= (event.duration / topLevelEvent.duration);
|
|
37
|
+
|
|
38
|
+
if (event.duration < threshold) return 0;
|
|
39
|
+
if (event.end < startTimeMs) return 0;
|
|
40
|
+
if (event.start > endTimeMs) return 0;
|
|
41
|
+
|
|
42
|
+
// Perform the clipping and then calculate Blocking Region. So if we have a 150ms task
|
|
43
|
+
// [0, 150] and `startTimeMs` is at 50ms, we first clip the task to [50, 150], and then
|
|
44
|
+
// calculate the Blocking Region to be [100, 150]. The rational here is that tasks before
|
|
45
|
+
// the start time are unimportant, so we care whether the main thread is busy more than
|
|
46
|
+
// 50ms at a time only after the start time.
|
|
47
|
+
const clippedStart = Math.max(event.start, startTimeMs);
|
|
48
|
+
const clippedEnd = Math.min(event.end, endTimeMs);
|
|
49
|
+
const clippedDuration = clippedEnd - clippedStart;
|
|
50
|
+
if (clippedDuration < threshold) return 0;
|
|
51
|
+
|
|
52
|
+
return clippedDuration - threshold;
|
|
53
|
+
}
|
|
54
|
+
|
|
9
55
|
/**
|
|
10
56
|
* @param {Array<{start: number, end: number, duration: number}>} topLevelEvents
|
|
11
57
|
* @param {number} startTimeMs
|
|
@@ -15,36 +61,9 @@ const BLOCKING_TIME_THRESHOLD = 50;
|
|
|
15
61
|
function calculateSumOfBlockingTime(topLevelEvents, startTimeMs, endTimeMs) {
|
|
16
62
|
if (endTimeMs <= startTimeMs) return 0;
|
|
17
63
|
|
|
18
|
-
const threshold = BLOCKING_TIME_THRESHOLD;
|
|
19
64
|
let sumBlockingTime = 0;
|
|
20
65
|
for (const event of topLevelEvents) {
|
|
21
|
-
|
|
22
|
-
if (event.duration < threshold) continue;
|
|
23
|
-
|
|
24
|
-
// We only want to consider tasks that fall in our time range (FCP and TTI for navigations).
|
|
25
|
-
// FCP is picked as the lower bound because there is little risk of user input happening
|
|
26
|
-
// before FCP so Long Queuing Qelay regions do not harm user experience. Developers should be
|
|
27
|
-
// optimizing to reach FCP as fast as possible without having to worry about task lengths.
|
|
28
|
-
if (event.end < startTimeMs) continue;
|
|
29
|
-
|
|
30
|
-
// TTI is picked as the upper bound because we want a well defined end point for page load.
|
|
31
|
-
if (event.start > endTimeMs) continue;
|
|
32
|
-
|
|
33
|
-
// We first perform the clipping, and then calculate Blocking Region. So if we have a 150ms
|
|
34
|
-
// task [0, 150] and FCP happens midway at 50ms, we first clip the task to [50, 150], and then
|
|
35
|
-
// calculate the Blocking Region to be [100, 150]. The rational here is that tasks before FCP
|
|
36
|
-
// are unimportant, so we care whether the main thread is busy more than 50ms at a time only
|
|
37
|
-
// after FCP.
|
|
38
|
-
const clippedStart = Math.max(event.start, startTimeMs);
|
|
39
|
-
const clippedEnd = Math.min(event.end, endTimeMs);
|
|
40
|
-
const clippedDuration = clippedEnd - clippedStart;
|
|
41
|
-
if (clippedDuration < threshold) continue;
|
|
42
|
-
|
|
43
|
-
// The duration of the task beyond 50ms at the beginning is considered the Blocking Region.
|
|
44
|
-
// Example:
|
|
45
|
-
// [ 250ms Task ]
|
|
46
|
-
// | First 50ms | Blocking Region (200ms) |
|
|
47
|
-
sumBlockingTime += clippedDuration - threshold;
|
|
66
|
+
sumBlockingTime += calculateTbtImpactForEvent(event, startTimeMs, endTimeMs);
|
|
48
67
|
}
|
|
49
68
|
|
|
50
69
|
return sumBlockingTime;
|
|
@@ -53,4 +72,5 @@ function calculateSumOfBlockingTime(topLevelEvents, startTimeMs, endTimeMs) {
|
|
|
53
72
|
export {
|
|
54
73
|
BLOCKING_TIME_THRESHOLD,
|
|
55
74
|
calculateSumOfBlockingTime,
|
|
75
|
+
calculateTbtImpactForEvent,
|
|
56
76
|
};
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
export { TBTImpactTasksComputed as TBTImpactTasks };
|
|
2
|
+
export type TBTImpactTask = LH.Artifacts.TaskNode & {
|
|
3
|
+
tbtImpact: number;
|
|
4
|
+
selfTbtImpact: number;
|
|
5
|
+
};
|
|
6
|
+
declare const TBTImpactTasksComputed: typeof TBTImpactTasks & {
|
|
7
|
+
request: (dependencies: import("../index.js").Artifacts.MetricComputationDataInput, context: import("../../types/utility-types.js").default.ImmutableObject<{
|
|
8
|
+
computedCache: Map<string, import("../lib/arbitrary-equality-map.js").ArbitraryEqualityMap>;
|
|
9
|
+
}>) => Promise<TBTImpactTask[]>;
|
|
10
|
+
};
|
|
11
|
+
/** @typedef {LH.Artifacts.TaskNode & {tbtImpact: number, selfTbtImpact: number}} TBTImpactTask */
|
|
12
|
+
declare class TBTImpactTasks {
|
|
13
|
+
/**
|
|
14
|
+
* @param {LH.Artifacts.TaskNode} task
|
|
15
|
+
* @return {LH.Artifacts.TaskNode}
|
|
16
|
+
*/
|
|
17
|
+
static getTopLevelTask(task: LH.Artifacts.TaskNode): LH.Artifacts.TaskNode;
|
|
18
|
+
/**
|
|
19
|
+
* @param {LH.Artifacts.MetricComputationDataInput} metricComputationData
|
|
20
|
+
* @param {LH.Artifacts.ComputedContext} context
|
|
21
|
+
* @return {Promise<{startTimeMs: number, endTimeMs: number}>}
|
|
22
|
+
*/
|
|
23
|
+
static getTbtBounds(metricComputationData: LH.Artifacts.MetricComputationDataInput, context: LH.Artifacts.ComputedContext): Promise<{
|
|
24
|
+
startTimeMs: number;
|
|
25
|
+
endTimeMs: number;
|
|
26
|
+
}>;
|
|
27
|
+
/**
|
|
28
|
+
* @param {LH.Artifacts.TaskNode[]} tasks
|
|
29
|
+
* @param {Map<LH.Artifacts.TaskNode, number>} taskToImpact
|
|
30
|
+
*/
|
|
31
|
+
static createImpactTasks(tasks: LH.Artifacts.TaskNode[], taskToImpact: Map<LH.Artifacts.TaskNode, number>): TBTImpactTask[];
|
|
32
|
+
/**
|
|
33
|
+
* @param {LH.Artifacts.TaskNode[]} tasks
|
|
34
|
+
* @param {number} startTimeMs
|
|
35
|
+
* @param {number} endTimeMs
|
|
36
|
+
* @return {TBTImpactTask[]}
|
|
37
|
+
*/
|
|
38
|
+
static computeImpactsFromObservedTasks(tasks: LH.Artifacts.TaskNode[], startTimeMs: number, endTimeMs: number): TBTImpactTask[];
|
|
39
|
+
/**
|
|
40
|
+
* @param {LH.Artifacts.TaskNode[]} tasks
|
|
41
|
+
* @param {LH.Gatherer.Simulation.Result['nodeTimings']} tbtNodeTimings
|
|
42
|
+
* @param {number} startTimeMs
|
|
43
|
+
* @param {number} endTimeMs
|
|
44
|
+
* @return {TBTImpactTask[]}
|
|
45
|
+
*/
|
|
46
|
+
static computeImpactsFromLantern(tasks: LH.Artifacts.TaskNode[], tbtNodeTimings: LH.Gatherer.Simulation.Result['nodeTimings'], startTimeMs: number, endTimeMs: number): TBTImpactTask[];
|
|
47
|
+
/**
|
|
48
|
+
* @param {LH.Artifacts.MetricComputationDataInput} metricComputationData
|
|
49
|
+
* @param {LH.Artifacts.ComputedContext} context
|
|
50
|
+
* @return {Promise<TBTImpactTask[]>}
|
|
51
|
+
*/
|
|
52
|
+
static compute_(metricComputationData: LH.Artifacts.MetricComputationDataInput, context: LH.Artifacts.ComputedContext): Promise<TBTImpactTask[]>;
|
|
53
|
+
}
|
|
54
|
+
//# sourceMappingURL=tbt-impact-tasks.d.ts.map
|