lighthouse 11.7.1 → 12.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CONTRIBUTING.md +2 -2
- package/cli/bin.js +0 -7
- package/cli/cli-flags.d.ts +1 -5
- package/cli/cli-flags.js +4 -8
- package/cli/test/smokehouse/__snapshots__/report-assert-test.js.snap +14 -14
- package/cli/test/smokehouse/config/exclusions.js +3 -0
- package/cli/test/smokehouse/core-tests.js +6 -20
- package/cli/test/smokehouse/frontends/smokehouse-bin.js +2 -2
- package/core/audits/accessibility/aria-conditional-attr.d.ts +10 -0
- package/core/audits/accessibility/aria-conditional-attr.js +43 -0
- package/core/audits/accessibility/{duplicate-id-active.d.ts → aria-deprecated-role.d.ts} +3 -3
- package/core/audits/accessibility/aria-deprecated-role.js +43 -0
- package/core/audits/accessibility/aria-prohibited-attr.d.ts +10 -0
- package/core/audits/accessibility/aria-prohibited-attr.js +44 -0
- package/core/audits/accessibility/target-size.js +0 -1
- package/core/audits/byte-efficiency/byte-efficiency-audit.d.ts +0 -16
- package/core/audits/byte-efficiency/byte-efficiency-audit.js +1 -38
- package/core/audits/byte-efficiency/offscreen-images.d.ts +0 -11
- package/core/audits/byte-efficiency/offscreen-images.js +0 -15
- package/core/audits/byte-efficiency/render-blocking-resources.js +27 -26
- package/core/audits/byte-efficiency/unminified-css.js +2 -2
- package/core/audits/byte-efficiency/unused-css-rules.js +3 -1
- package/core/audits/dobetterweb/uses-http2.d.ts +0 -13
- package/core/audits/dobetterweb/uses-http2.js +4 -38
- package/core/audits/font-display.js +2 -2
- package/core/audits/layout-shifts.js +1 -1
- package/core/audits/preload-fonts.d.ts +1 -5
- package/core/audits/preload-fonts.js +1 -10
- package/core/audits/prioritize-lcp-image.js +2 -2
- package/core/audits/redirects-http.d.ts +19 -0
- package/core/audits/redirects-http.js +75 -0
- package/core/audits/resource-summary.d.ts +2 -0
- package/core/audits/resource-summary.js +5 -4
- package/core/audits/seo/is-crawlable.d.ts +2 -1
- package/core/audits/seo/link-text.js +12 -0
- package/core/audits/uses-rel-preload.d.ts +1 -5
- package/core/audits/uses-rel-preload.js +5 -15
- package/core/audits/work-during-interaction.d.ts +2 -2
- package/core/audits/work-during-interaction.js +2 -2
- package/core/computed/js-bundles.d.ts +1 -1
- package/core/computed/metrics/lantern-first-contentful-paint.d.ts +13 -82
- package/core/computed/metrics/lantern-first-contentful-paint.js +16 -178
- package/core/computed/metrics/lantern-first-meaningful-paint.d.ts +13 -12
- package/core/computed/metrics/lantern-first-meaningful-paint.js +11 -49
- package/core/computed/metrics/lantern-interactive.d.ts +13 -21
- package/core/computed/metrics/lantern-interactive.js +12 -84
- package/core/computed/metrics/lantern-largest-contentful-paint.d.ts +13 -25
- package/core/computed/metrics/lantern-largest-contentful-paint.js +13 -80
- package/core/computed/metrics/lantern-max-potential-fid.d.ts +13 -24
- package/core/computed/metrics/lantern-max-potential-fid.js +12 -64
- package/core/computed/metrics/lantern-metric.d.ts +14 -18
- package/core/computed/metrics/lantern-metric.js +34 -30
- package/core/computed/metrics/lantern-speed-index.d.ts +13 -32
- package/core/computed/metrics/lantern-speed-index.js +13 -116
- package/core/computed/metrics/lantern-total-blocking-time.d.ts +13 -25
- package/core/computed/metrics/lantern-total-blocking-time.js +10 -94
- package/core/computed/metrics/metric.js +1 -3
- package/core/computed/module-duplication.d.ts +1 -1
- package/core/computed/navigation-insights.d.ts +36 -0
- package/core/computed/navigation-insights.js +35 -0
- package/core/computed/page-dependency-graph.js +3 -7
- package/core/computed/resource-summary.d.ts +10 -12
- package/core/computed/resource-summary.js +12 -19
- package/core/computed/trace-engine-result.d.ts +16 -3
- package/core/computed/trace-engine-result.js +19 -14
- package/core/computed/unused-css.d.ts +3 -1
- package/core/computed/unused-css.js +6 -5
- package/core/config/config-helpers.js +0 -4
- package/core/config/constants.js +0 -1
- package/core/config/default-config.js +63 -142
- package/core/config/experimental-config.js +11 -0
- package/core/config/filters.js +0 -1
- package/core/gather/driver/navigation.js +4 -1
- package/core/gather/driver/prepare.js +1 -1
- package/core/gather/driver/target-manager.js +4 -3
- package/core/gather/driver/wait-for-condition.js +2 -3
- package/core/gather/driver.d.ts +12 -0
- package/core/gather/driver.js +27 -0
- package/core/gather/gatherers/accessibility.js +4 -6
- package/core/gather/gatherers/css-usage.d.ts +0 -21
- package/core/gather/gatherers/css-usage.js +9 -119
- package/core/gather/gatherers/root-causes.d.ts +2 -2
- package/core/gather/gatherers/root-causes.js +6 -5
- package/core/gather/gatherers/stylesheets.d.ts +26 -0
- package/core/gather/gatherers/stylesheets.js +138 -0
- package/core/gather/gatherers/trace-elements.d.ts +2 -12
- package/core/gather/gatherers/trace-elements.js +2 -21
- package/core/gather/navigation-runner.js +3 -1
- package/core/gather/session.d.ts +8 -0
- package/core/gather/session.js +14 -1
- package/core/lib/emulation.js +2 -2
- package/core/lib/i18n/i18n.d.ts +0 -1
- package/core/lib/i18n/i18n.js +0 -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/metric.js +2 -6
- package/core/lib/lantern/metrics/first-contentful-paint.d.ts +79 -0
- package/core/lib/lantern/metrics/first-contentful-paint.js +200 -0
- package/core/lib/lantern/metrics/first-meaningful-paint.d.ts +6 -0
- package/core/lib/lantern/metrics/first-meaningful-paint.js +64 -0
- package/core/lib/lantern/metrics/interactive.d.ts +26 -0
- package/core/lib/lantern/metrics/interactive.js +112 -0
- package/core/lib/lantern/metrics/largest-contentful-paint.d.ts +19 -0
- package/core/lib/lantern/metrics/largest-contentful-paint.js +108 -0
- package/core/lib/lantern/metrics/max-potential-fid.d.ts +30 -0
- package/core/lib/lantern/metrics/max-potential-fid.js +92 -0
- package/core/lib/lantern/metrics/speed-index.d.ts +38 -0
- package/core/lib/lantern/metrics/speed-index.js +144 -0
- package/core/lib/lantern/metrics/total-blocking-time.d.ts +31 -0
- package/core/lib/lantern/metrics/total-blocking-time.js +128 -0
- package/core/lib/lantern/network-node.js +28 -4
- package/core/lib/lantern/page-dependency-graph.d.ts +4 -4
- package/core/lib/lantern/page-dependency-graph.js +19 -7
- package/core/lib/lantern/simulator/connection-pool.d.ts +2 -12
- package/core/lib/lantern/simulator/connection-pool.js +3 -22
- package/core/lib/lantern/simulator/network-analyzer.js +1 -12
- package/core/lib/lantern/simulator/simulator.d.ts +2 -4
- package/core/lib/lantern/simulator/simulator.js +7 -14
- package/core/lib/lantern/types/lantern.d.ts +5 -3
- package/core/lib/lantern-trace-saver.js +0 -1
- package/core/lib/lh-error.d.ts +9 -0
- package/core/lib/lh-error.js +13 -3
- package/core/lib/lighthouse-compatibility.js +53 -10
- package/core/lib/navigation-error.js +3 -0
- package/core/lib/network-request.d.ts +1 -0
- package/core/lib/network-request.js +28 -0
- package/core/runner.js +18 -25
- package/dist/report/bundle.esm.js +11 -112
- package/dist/report/flow.js +16 -117
- package/dist/report/standalone.js +12 -113
- 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/flow-report/src/summary/category.tsx +10 -12
- package/package.json +5 -5
- package/readme.md +4 -4
- package/report/assets/styles.css +1 -60
- package/report/assets/templates.html +0 -95
- package/report/renderer/category-renderer.js +1 -1
- package/report/renderer/components.d.ts +2 -2
- package/report/renderer/components.js +2 -103
- package/report/renderer/dom.d.ts +1 -1
- package/report/renderer/dom.js +1 -1
- package/report/renderer/performance-category-renderer.d.ts +0 -7
- package/report/renderer/performance-category-renderer.js +1 -32
- package/report/renderer/report-renderer.js +3 -13
- package/report/renderer/report-ui-features.js +1 -1
- package/report/renderer/report-utils.d.ts +0 -1
- package/report/renderer/report-utils.js +13 -13
- package/shared/localization/locales/ar-XB.json +62 -329
- package/shared/localization/locales/ar.json +62 -329
- package/shared/localization/locales/bg.json +62 -329
- package/shared/localization/locales/ca.json +62 -329
- package/shared/localization/locales/cs.json +62 -329
- package/shared/localization/locales/da.json +62 -329
- package/shared/localization/locales/de.json +62 -329
- package/shared/localization/locales/el.json +62 -329
- package/shared/localization/locales/en-GB.json +62 -329
- package/shared/localization/locales/en-US.json +41 -272
- package/shared/localization/locales/en-XA.json +62 -329
- package/shared/localization/locales/en-XL.json +41 -272
- package/shared/localization/locales/es-419.json +62 -329
- package/shared/localization/locales/es.json +62 -329
- package/shared/localization/locales/fi.json +62 -329
- package/shared/localization/locales/fil.json +61 -328
- package/shared/localization/locales/fr.json +62 -329
- package/shared/localization/locales/he.json +62 -329
- package/shared/localization/locales/hi.json +62 -329
- package/shared/localization/locales/hr.json +63 -330
- package/shared/localization/locales/hu.json +62 -329
- package/shared/localization/locales/id.json +62 -329
- package/shared/localization/locales/it.json +62 -329
- package/shared/localization/locales/ja.json +62 -329
- package/shared/localization/locales/ko.json +62 -329
- package/shared/localization/locales/lt.json +62 -329
- package/shared/localization/locales/lv.json +62 -329
- package/shared/localization/locales/nl.json +62 -329
- package/shared/localization/locales/no.json +62 -329
- package/shared/localization/locales/pl.json +61 -328
- package/shared/localization/locales/pt-PT.json +62 -329
- package/shared/localization/locales/pt.json +64 -331
- package/shared/localization/locales/ro.json +62 -329
- package/shared/localization/locales/ru.json +62 -329
- package/shared/localization/locales/sk.json +62 -329
- package/shared/localization/locales/sl.json +62 -329
- package/shared/localization/locales/sr-Latn.json +62 -329
- package/shared/localization/locales/sr.json +62 -329
- package/shared/localization/locales/sv.json +62 -329
- package/shared/localization/locales/ta.json +61 -328
- package/shared/localization/locales/te.json +62 -329
- package/shared/localization/locales/th.json +62 -329
- package/shared/localization/locales/tr.json +62 -329
- package/shared/localization/locales/uk.json +62 -329
- package/shared/localization/locales/vi.json +61 -328
- package/shared/localization/locales/zh-HK.json +62 -329
- package/shared/localization/locales/zh-TW.json +61 -328
- package/shared/localization/locales/zh.json +62 -329
- package/tsconfig.json +1 -3
- package/types/artifacts.d.ts +21 -110
- package/types/audit.d.ts +0 -10
- package/types/config.d.ts +0 -1
- package/types/externs.d.ts +0 -2
- package/types/gatherer.d.ts +3 -0
- package/types/internal/smokehouse.d.ts +1 -1
- package/types/lh.d.ts +0 -2
- package/types/lhr/audit-details.d.ts +9 -4
- package/types/lhr/lhr.d.ts +0 -4
- package/types/lhr/settings.d.ts +0 -4
- package/core/audits/accessibility/duplicate-id-active.js +0 -42
- 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/layout-shift-elements.d.ts +0 -16
- package/core/audits/layout-shift-elements.js +0 -101
- 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/no-unload-listeners.d.ts +0 -16
- package/core/audits/no-unload-listeners.js +0 -86
- package/core/audits/performance-budget.d.ts +0 -39
- package/core/audits/performance-budget.js +0 -154
- package/core/audits/script-elements-test-audit.d.ts +0 -14
- package/core/audits/script-elements-test-audit.js +0 -29
- package/core/audits/seo/plugins.d.ts +0 -15
- package/core/audits/seo/plugins.js +0 -150
- package/core/audits/seo/tap-targets.d.ts +0 -50
- package/core/audits/seo/tap-targets.js +0 -352
- 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/audits/timing-budget.d.ts +0 -43
- package/core/audits/timing-budget.js +0 -175
- package/core/computed/manifest-values.d.ts +0 -24
- package/core/computed/manifest-values.js +0 -136
- package/core/config/budget.d.ts +0 -74
- package/core/config/budget.js +0 -340
- package/core/config/metrics-to-audits.d.ts +0 -19
- package/core/config/metrics-to-audits.js +0 -62
- package/core/gather/driver/service-workers.d.ts +0 -16
- package/core/gather/driver/service-workers.js +0 -52
- 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/global-listeners.d.ts +0 -27
- package/core/gather/gatherers/global-listeners.js +0 -108
- package/core/gather/gatherers/installability-errors.d.ts +0 -17
- package/core/gather/gatherers/installability-errors.js +0 -56
- package/core/gather/gatherers/script-elements.d.ts +0 -21
- package/core/gather/gatherers/script-elements.js +0 -100
- package/core/gather/gatherers/seo/embedded-content.d.ts +0 -10
- package/core/gather/gatherers/seo/embedded-content.js +0 -63
- package/core/gather/gatherers/seo/tap-targets.d.ts +0 -21
- package/core/gather/gatherers/seo/tap-targets.js +0 -389
- package/core/gather/gatherers/service-worker.d.ts +0 -10
- package/core/gather/gatherers/service-worker.js +0 -32
- 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
- package/types/lhr/budget.d.ts +0 -60
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @license
|
|
3
|
+
* Copyright 2024 Google LLC
|
|
4
|
+
* SPDX-License-Identifier: Apache-2.0
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import * as Lantern from '../types/lantern.js';
|
|
8
|
+
import {Metric} from '../metric.js';
|
|
9
|
+
import {BaseNode} from '../base-node.js';
|
|
10
|
+
|
|
11
|
+
/** @typedef {import('../base-node.js').Node} Node */
|
|
12
|
+
|
|
13
|
+
class MaxPotentialFID extends Metric {
|
|
14
|
+
/**
|
|
15
|
+
* @return {LH.Gatherer.Simulation.MetricCoefficients}
|
|
16
|
+
*/
|
|
17
|
+
static get COEFFICIENTS() {
|
|
18
|
+
return {
|
|
19
|
+
intercept: 0,
|
|
20
|
+
optimistic: 0.5,
|
|
21
|
+
pessimistic: 0.5,
|
|
22
|
+
};
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* @param {Node} dependencyGraph
|
|
27
|
+
* @return {Node}
|
|
28
|
+
*/
|
|
29
|
+
static getOptimisticGraph(dependencyGraph) {
|
|
30
|
+
return dependencyGraph;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* @param {Node} dependencyGraph
|
|
35
|
+
* @return {Node}
|
|
36
|
+
*/
|
|
37
|
+
static getPessimisticGraph(dependencyGraph) {
|
|
38
|
+
return dependencyGraph;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* @param {LH.Gatherer.Simulation.Result} simulation
|
|
43
|
+
* @param {import('../metric.js').Extras} extras
|
|
44
|
+
* @return {LH.Gatherer.Simulation.Result}
|
|
45
|
+
*/
|
|
46
|
+
static getEstimateFromSimulation(simulation, extras) {
|
|
47
|
+
if (!extras.fcpResult) throw new Error('missing fcpResult');
|
|
48
|
+
|
|
49
|
+
// Intentionally use the opposite FCP estimate, a more pessimistic FCP means that more tasks
|
|
50
|
+
// are excluded from the FID computation, so a higher FCP means lower FID for same work.
|
|
51
|
+
const fcpTimeInMs = extras.optimistic
|
|
52
|
+
? extras.fcpResult.pessimisticEstimate.timeInMs
|
|
53
|
+
: extras.fcpResult.optimisticEstimate.timeInMs;
|
|
54
|
+
|
|
55
|
+
const timings = MaxPotentialFID.getTimingsAfterFCP(
|
|
56
|
+
simulation.nodeTimings,
|
|
57
|
+
fcpTimeInMs
|
|
58
|
+
);
|
|
59
|
+
|
|
60
|
+
return {
|
|
61
|
+
timeInMs: Math.max(...timings.map(timing => timing.duration), 16),
|
|
62
|
+
nodeTimings: simulation.nodeTimings,
|
|
63
|
+
};
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
/**
|
|
67
|
+
* @param {Lantern.Simulation.MetricComputationDataInput} data
|
|
68
|
+
* @param {Omit<import('../metric.js').Extras, 'optimistic'>=} extras
|
|
69
|
+
* @return {Promise<LH.Artifacts.LanternMetric>}
|
|
70
|
+
*/
|
|
71
|
+
static compute(data, extras) {
|
|
72
|
+
const fcpResult = extras?.fcpResult;
|
|
73
|
+
if (!fcpResult) {
|
|
74
|
+
throw new Error('FCP is required to calculate the Max Potential FID metric');
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
return super.compute(data, extras);
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
/**
|
|
81
|
+
* @param {LH.Gatherer.Simulation.Result['nodeTimings']} nodeTimings
|
|
82
|
+
* @param {number} fcpTimeInMs
|
|
83
|
+
* @return {Array<{duration: number}>}
|
|
84
|
+
*/
|
|
85
|
+
static getTimingsAfterFCP(nodeTimings, fcpTimeInMs) {
|
|
86
|
+
return Array.from(nodeTimings.entries())
|
|
87
|
+
.filter(([node, timing]) => node.type === BaseNode.TYPES.CPU && timing.endTime > fcpTimeInMs)
|
|
88
|
+
.map(([_, timing]) => timing);
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
export {MaxPotentialFID};
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
export type Node = import('../base-node.js').Node;
|
|
2
|
+
/** @typedef {import('../base-node.js').Node} Node */
|
|
3
|
+
export class SpeedIndex extends Metric {
|
|
4
|
+
/**
|
|
5
|
+
* @param {Node} dependencyGraph
|
|
6
|
+
* @return {Node}
|
|
7
|
+
*/
|
|
8
|
+
static getOptimisticGraph(dependencyGraph: Node): Node;
|
|
9
|
+
/**
|
|
10
|
+
* @param {Node} dependencyGraph
|
|
11
|
+
* @return {Node}
|
|
12
|
+
*/
|
|
13
|
+
static getPessimisticGraph(dependencyGraph: Node): Node;
|
|
14
|
+
/**
|
|
15
|
+
* @param {LH.Gatherer.Simulation.Result} simulationResult
|
|
16
|
+
* @param {import('../metric.js').Extras} extras
|
|
17
|
+
* @return {LH.Gatherer.Simulation.Result}
|
|
18
|
+
*/
|
|
19
|
+
static getEstimateFromSimulation(simulationResult: LH.Gatherer.Simulation.Result, extras: import('../metric.js').Extras): LH.Gatherer.Simulation.Result;
|
|
20
|
+
/**
|
|
21
|
+
* Approximate speed index using layout events from the simulated node timings.
|
|
22
|
+
* The layout-based speed index is the weighted average of the endTime of CPU nodes that contained
|
|
23
|
+
* a 'Layout' task. log(duration) is used as the weight to stand for "significance" to the page.
|
|
24
|
+
*
|
|
25
|
+
* If no layout events can be found or the endTime of a CPU task is too early, FCP is used instead.
|
|
26
|
+
*
|
|
27
|
+
* This approach was determined after evaluating the accuracy/complexity tradeoff of many
|
|
28
|
+
* different methods. Read more in the evaluation doc.
|
|
29
|
+
*
|
|
30
|
+
* @see https://docs.google.com/document/d/1qJWXwxoyVLVadezIp_Tgdk867G3tDNkkVRvUJSH3K1E/edit#
|
|
31
|
+
* @param {LH.Gatherer.Simulation.Result['nodeTimings']} nodeTimings
|
|
32
|
+
* @param {number} fcpTimeInMs
|
|
33
|
+
* @return {number}
|
|
34
|
+
*/
|
|
35
|
+
static computeLayoutBasedSpeedIndex(nodeTimings: LH.Gatherer.Simulation.Result['nodeTimings'], fcpTimeInMs: number): number;
|
|
36
|
+
}
|
|
37
|
+
import { Metric } from '../metric.js';
|
|
38
|
+
//# sourceMappingURL=speed-index.d.ts.map
|
|
@@ -0,0 +1,144 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @license
|
|
3
|
+
* Copyright 2024 Google LLC
|
|
4
|
+
* SPDX-License-Identifier: Apache-2.0
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import * as Lantern from '../types/lantern.js';
|
|
8
|
+
import {Metric} from '../metric.js';
|
|
9
|
+
import {BaseNode} from '../base-node.js';
|
|
10
|
+
|
|
11
|
+
const mobileSlow4GRtt = 150;
|
|
12
|
+
|
|
13
|
+
/** @typedef {import('../base-node.js').Node} Node */
|
|
14
|
+
|
|
15
|
+
class SpeedIndex extends Metric {
|
|
16
|
+
/**
|
|
17
|
+
* @return {LH.Gatherer.Simulation.MetricCoefficients}
|
|
18
|
+
*/
|
|
19
|
+
static get COEFFICIENTS() {
|
|
20
|
+
return {
|
|
21
|
+
// Note that the optimistic estimate is based on the real observed speed index rather than a
|
|
22
|
+
// real lantern graph (and the final estimate will be Math.max(FCP, Speed Index)).
|
|
23
|
+
intercept: 0,
|
|
24
|
+
optimistic: 1.4,
|
|
25
|
+
pessimistic: 0.4,
|
|
26
|
+
};
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* @param {number} rttMs
|
|
31
|
+
* @return {LH.Gatherer.Simulation.MetricCoefficients}
|
|
32
|
+
*/
|
|
33
|
+
static getScaledCoefficients(rttMs) { // eslint-disable-line no-unused-vars
|
|
34
|
+
// We want to scale our default coefficients based on the speed of the connection.
|
|
35
|
+
// We will linearly interpolate coefficients for the passed-in rttMs based on two pre-determined points:
|
|
36
|
+
// 1. Baseline point of 30 ms RTT where Speed Index should be a ~50/50 blend of optimistic/pessimistic.
|
|
37
|
+
// 30 ms was based on a typical home WiFi connection's actual RTT.
|
|
38
|
+
// Coefficients here follow from the fact that the optimistic estimate should be very close
|
|
39
|
+
// to reality at this connection speed and the pessimistic estimate compensates for minor
|
|
40
|
+
// connection speed differences.
|
|
41
|
+
// 2. Default throttled point of 150 ms RTT where the default coefficients have been determined to be most accurate.
|
|
42
|
+
// Coefficients here were determined through thorough analysis and linear regression on the
|
|
43
|
+
// lantern test data set. See core/scripts/test-lantern.sh for more detail.
|
|
44
|
+
// While the coefficients haven't been analyzed at the interpolated points, it's our current best effort.
|
|
45
|
+
const defaultCoefficients = this.COEFFICIENTS;
|
|
46
|
+
const defaultRttExcess = mobileSlow4GRtt - 30;
|
|
47
|
+
const multiplier = Math.max((rttMs - 30) / defaultRttExcess, 0);
|
|
48
|
+
|
|
49
|
+
return {
|
|
50
|
+
intercept: defaultCoefficients.intercept * multiplier,
|
|
51
|
+
optimistic: 0.5 + (defaultCoefficients.optimistic - 0.5) * multiplier,
|
|
52
|
+
pessimistic: 0.5 + (defaultCoefficients.pessimistic - 0.5) * multiplier,
|
|
53
|
+
};
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
/**
|
|
57
|
+
* @param {Node} dependencyGraph
|
|
58
|
+
* @return {Node}
|
|
59
|
+
*/
|
|
60
|
+
static getOptimisticGraph(dependencyGraph) {
|
|
61
|
+
return dependencyGraph;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
/**
|
|
65
|
+
* @param {Node} dependencyGraph
|
|
66
|
+
* @return {Node}
|
|
67
|
+
*/
|
|
68
|
+
static getPessimisticGraph(dependencyGraph) {
|
|
69
|
+
return dependencyGraph;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
/**
|
|
73
|
+
* @param {LH.Gatherer.Simulation.Result} simulationResult
|
|
74
|
+
* @param {import('../metric.js').Extras} extras
|
|
75
|
+
* @return {LH.Gatherer.Simulation.Result}
|
|
76
|
+
*/
|
|
77
|
+
static getEstimateFromSimulation(simulationResult, extras) {
|
|
78
|
+
if (!extras.fcpResult) throw new Error('missing fcpResult');
|
|
79
|
+
if (!extras.speedline) throw new Error('missing speedline');
|
|
80
|
+
|
|
81
|
+
const fcpTimeInMs = extras.fcpResult.pessimisticEstimate.timeInMs;
|
|
82
|
+
const estimate = extras.optimistic
|
|
83
|
+
? extras.speedline.speedIndex
|
|
84
|
+
: SpeedIndex.computeLayoutBasedSpeedIndex(simulationResult.nodeTimings, fcpTimeInMs);
|
|
85
|
+
return {
|
|
86
|
+
timeInMs: estimate,
|
|
87
|
+
nodeTimings: simulationResult.nodeTimings,
|
|
88
|
+
};
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
/**
|
|
92
|
+
* @param {Lantern.Simulation.MetricComputationDataInput} data
|
|
93
|
+
* @param {Omit<import('../metric.js').Extras, 'optimistic'>=} extras
|
|
94
|
+
* @return {Promise<LH.Artifacts.LanternMetric>}
|
|
95
|
+
*/
|
|
96
|
+
static async compute(data, extras) {
|
|
97
|
+
const fcpResult = extras?.fcpResult;
|
|
98
|
+
if (!fcpResult) {
|
|
99
|
+
throw new Error('FCP is required to calculate the SpeedIndex metric');
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
const metricResult = await super.compute(data, extras);
|
|
103
|
+
metricResult.timing = Math.max(metricResult.timing, fcpResult.timing);
|
|
104
|
+
return metricResult;
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
/**
|
|
108
|
+
* Approximate speed index using layout events from the simulated node timings.
|
|
109
|
+
* The layout-based speed index is the weighted average of the endTime of CPU nodes that contained
|
|
110
|
+
* a 'Layout' task. log(duration) is used as the weight to stand for "significance" to the page.
|
|
111
|
+
*
|
|
112
|
+
* If no layout events can be found or the endTime of a CPU task is too early, FCP is used instead.
|
|
113
|
+
*
|
|
114
|
+
* This approach was determined after evaluating the accuracy/complexity tradeoff of many
|
|
115
|
+
* different methods. Read more in the evaluation doc.
|
|
116
|
+
*
|
|
117
|
+
* @see https://docs.google.com/document/d/1qJWXwxoyVLVadezIp_Tgdk867G3tDNkkVRvUJSH3K1E/edit#
|
|
118
|
+
* @param {LH.Gatherer.Simulation.Result['nodeTimings']} nodeTimings
|
|
119
|
+
* @param {number} fcpTimeInMs
|
|
120
|
+
* @return {number}
|
|
121
|
+
*/
|
|
122
|
+
static computeLayoutBasedSpeedIndex(nodeTimings, fcpTimeInMs) {
|
|
123
|
+
/** @type {Array<{time: number, weight: number}>} */
|
|
124
|
+
const layoutWeights = [];
|
|
125
|
+
for (const [node, timing] of nodeTimings.entries()) {
|
|
126
|
+
if (node.type !== BaseNode.TYPES.CPU) continue;
|
|
127
|
+
|
|
128
|
+
if (node.childEvents.some(x => x.name === 'Layout')) {
|
|
129
|
+
const timingWeight = Math.max(Math.log2(timing.endTime - timing.startTime), 0);
|
|
130
|
+
layoutWeights.push({time: timing.endTime, weight: timingWeight});
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
const totalWeightedTime = layoutWeights
|
|
135
|
+
.map(evt => evt.weight * Math.max(evt.time, fcpTimeInMs))
|
|
136
|
+
.reduce((a, b) => a + b, 0);
|
|
137
|
+
const totalWeight = layoutWeights.map(evt => evt.weight).reduce((a, b) => a + b, 0);
|
|
138
|
+
|
|
139
|
+
if (!totalWeight) return fcpTimeInMs;
|
|
140
|
+
return totalWeightedTime / totalWeight;
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
export {SpeedIndex};
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
export type Node = import('../base-node.js').Node;
|
|
2
|
+
/** @typedef {import('../base-node.js').Node} Node */
|
|
3
|
+
export class TotalBlockingTime extends Metric {
|
|
4
|
+
/**
|
|
5
|
+
* @param {Node} dependencyGraph
|
|
6
|
+
* @return {Node}
|
|
7
|
+
*/
|
|
8
|
+
static getOptimisticGraph(dependencyGraph: Node): Node;
|
|
9
|
+
/**
|
|
10
|
+
* @param {Node} dependencyGraph
|
|
11
|
+
* @return {Node}
|
|
12
|
+
*/
|
|
13
|
+
static getPessimisticGraph(dependencyGraph: Node): Node;
|
|
14
|
+
/**
|
|
15
|
+
* @param {LH.Gatherer.Simulation.Result} simulation
|
|
16
|
+
* @param {import('../metric.js').Extras} extras
|
|
17
|
+
* @return {LH.Gatherer.Simulation.Result}
|
|
18
|
+
*/
|
|
19
|
+
static getEstimateFromSimulation(simulation: LH.Gatherer.Simulation.Result, extras: import('../metric.js').Extras): LH.Gatherer.Simulation.Result;
|
|
20
|
+
/**
|
|
21
|
+
* @param {LH.Gatherer.Simulation.Result['nodeTimings']} nodeTimings
|
|
22
|
+
* @param {number} minDurationMs
|
|
23
|
+
*/
|
|
24
|
+
static getTopLevelEvents(nodeTimings: LH.Gatherer.Simulation.Result['nodeTimings'], minDurationMs: number): {
|
|
25
|
+
start: number;
|
|
26
|
+
end: number;
|
|
27
|
+
duration: number;
|
|
28
|
+
}[];
|
|
29
|
+
}
|
|
30
|
+
import { Metric } from '../metric.js';
|
|
31
|
+
//# sourceMappingURL=total-blocking-time.d.ts.map
|
|
@@ -0,0 +1,128 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @license
|
|
3
|
+
* Copyright 2024 Google LLC
|
|
4
|
+
* SPDX-License-Identifier: Apache-2.0
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import * as Lantern from '../types/lantern.js';
|
|
8
|
+
import {Metric} from '../metric.js';
|
|
9
|
+
import {BaseNode} from '../base-node.js';
|
|
10
|
+
import {BLOCKING_TIME_THRESHOLD, calculateSumOfBlockingTime} from '../../../computed/metrics/tbt-utils.js';
|
|
11
|
+
|
|
12
|
+
/** @typedef {import('../base-node.js').Node} Node */
|
|
13
|
+
|
|
14
|
+
class TotalBlockingTime extends Metric {
|
|
15
|
+
/**
|
|
16
|
+
* @return {LH.Gatherer.Simulation.MetricCoefficients}
|
|
17
|
+
*/
|
|
18
|
+
static get COEFFICIENTS() {
|
|
19
|
+
return {
|
|
20
|
+
intercept: 0,
|
|
21
|
+
optimistic: 0.5,
|
|
22
|
+
pessimistic: 0.5,
|
|
23
|
+
};
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* @param {Node} dependencyGraph
|
|
28
|
+
* @return {Node}
|
|
29
|
+
*/
|
|
30
|
+
static getOptimisticGraph(dependencyGraph) {
|
|
31
|
+
return dependencyGraph;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* @param {Node} dependencyGraph
|
|
36
|
+
* @return {Node}
|
|
37
|
+
*/
|
|
38
|
+
static getPessimisticGraph(dependencyGraph) {
|
|
39
|
+
return dependencyGraph;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
/**
|
|
43
|
+
* @param {LH.Gatherer.Simulation.Result} simulation
|
|
44
|
+
* @param {import('../metric.js').Extras} extras
|
|
45
|
+
* @return {LH.Gatherer.Simulation.Result}
|
|
46
|
+
*/
|
|
47
|
+
static getEstimateFromSimulation(simulation, extras) {
|
|
48
|
+
if (!extras.fcpResult) throw new Error('missing fcpResult');
|
|
49
|
+
if (!extras.interactiveResult) throw new Error('missing interactiveResult');
|
|
50
|
+
|
|
51
|
+
// Intentionally use the opposite FCP estimate. A pessimistic FCP is higher than equal to an
|
|
52
|
+
// optimistic FCP, which means potentially more tasks are excluded from the Total Blocking Time
|
|
53
|
+
// computation. So a more pessimistic FCP gives a more optimistic Total Blocking Time for the
|
|
54
|
+
// same work.
|
|
55
|
+
const fcpTimeInMs = extras.optimistic
|
|
56
|
+
? extras.fcpResult.pessimisticEstimate.timeInMs
|
|
57
|
+
: extras.fcpResult.optimisticEstimate.timeInMs;
|
|
58
|
+
|
|
59
|
+
// Similarly, we always have pessimistic TTI >= optimistic TTI. Therefore, picking optimistic
|
|
60
|
+
// TTI means our window of interest is smaller and thus potentially more tasks are excluded from
|
|
61
|
+
// Total Blocking Time computation, yielding a lower (more optimistic) Total Blocking Time value
|
|
62
|
+
// for the same work.
|
|
63
|
+
const interactiveTimeMs = extras.optimistic
|
|
64
|
+
? extras.interactiveResult.optimisticEstimate.timeInMs
|
|
65
|
+
: extras.interactiveResult.pessimisticEstimate.timeInMs;
|
|
66
|
+
|
|
67
|
+
const minDurationMs = BLOCKING_TIME_THRESHOLD;
|
|
68
|
+
|
|
69
|
+
const events = TotalBlockingTime.getTopLevelEvents(
|
|
70
|
+
simulation.nodeTimings,
|
|
71
|
+
minDurationMs
|
|
72
|
+
);
|
|
73
|
+
|
|
74
|
+
return {
|
|
75
|
+
timeInMs: calculateSumOfBlockingTime(
|
|
76
|
+
events,
|
|
77
|
+
fcpTimeInMs,
|
|
78
|
+
interactiveTimeMs
|
|
79
|
+
),
|
|
80
|
+
nodeTimings: simulation.nodeTimings,
|
|
81
|
+
};
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
/**
|
|
85
|
+
* @param {Lantern.Simulation.MetricComputationDataInput} data
|
|
86
|
+
* @param {Omit<import('../metric.js').Extras, 'optimistic'>=} extras
|
|
87
|
+
* @return {Promise<LH.Artifacts.LanternMetric>}
|
|
88
|
+
*/
|
|
89
|
+
static async compute(data, extras) {
|
|
90
|
+
const fcpResult = extras?.fcpResult;
|
|
91
|
+
if (!fcpResult) {
|
|
92
|
+
throw new Error('FCP is required to calculate the TBT metric');
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
const interactiveResult = extras?.fcpResult;
|
|
96
|
+
if (!interactiveResult) {
|
|
97
|
+
throw new Error('Interactive is required to calculate the TBT metric');
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
return super.compute(data, extras);
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
/**
|
|
104
|
+
* @param {LH.Gatherer.Simulation.Result['nodeTimings']} nodeTimings
|
|
105
|
+
* @param {number} minDurationMs
|
|
106
|
+
*/
|
|
107
|
+
static getTopLevelEvents(nodeTimings, minDurationMs) {
|
|
108
|
+
/** @type {Array<{start: number, end: number, duration: number}>}
|
|
109
|
+
*/
|
|
110
|
+
const events = [];
|
|
111
|
+
|
|
112
|
+
for (const [node, timing] of nodeTimings.entries()) {
|
|
113
|
+
if (node.type !== BaseNode.TYPES.CPU) continue;
|
|
114
|
+
// Filtering out events below minimum duration.
|
|
115
|
+
if (timing.duration < minDurationMs) continue;
|
|
116
|
+
|
|
117
|
+
events.push({
|
|
118
|
+
start: timing.startTime,
|
|
119
|
+
end: timing.endTime,
|
|
120
|
+
duration: timing.duration,
|
|
121
|
+
});
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
return events;
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
export {TotalBlockingTime};
|
|
@@ -7,8 +7,32 @@
|
|
|
7
7
|
import * as Lantern from './types/lantern.js';
|
|
8
8
|
import {NetworkRequestTypes} from './lantern.js';
|
|
9
9
|
import {BaseNode} from './base-node.js';
|
|
10
|
-
|
|
11
|
-
|
|
10
|
+
|
|
11
|
+
const NON_NETWORK_SCHEMES = [
|
|
12
|
+
'blob', // @see https://developer.mozilla.org/en-US/docs/Web/API/URL/createObjectURL
|
|
13
|
+
'data', // @see https://developer.mozilla.org/en-US/docs/Web/HTTP/Basics_of_HTTP/Data_URIs
|
|
14
|
+
'intent', // @see https://developer.chrome.com/docs/multidevice/android/intents/
|
|
15
|
+
'file', // @see https://en.wikipedia.org/wiki/File_URI_scheme
|
|
16
|
+
'filesystem', // @see https://developer.mozilla.org/en-US/docs/Web/API/FileSystem
|
|
17
|
+
'chrome-extension',
|
|
18
|
+
];
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* Use `NetworkRequest.isNonNetworkRequest(req)` if working with a request.
|
|
22
|
+
* Note: the `protocol` field from CDP can be 'h2', 'http', (not 'https'!) or it'll be url's scheme.
|
|
23
|
+
* https://source.chromium.org/chromium/chromium/src/+/main:content/browser/devtools/protocol/network_handler.cc;l=598-611;drc=56d4a9a9deb30be73adcee8737c73bcb2a5ab64f
|
|
24
|
+
* However, a `new URL(href).protocol` has a colon suffix.
|
|
25
|
+
* https://url.spec.whatwg.org/#dom-url-protocol
|
|
26
|
+
* A URL's `scheme` is specced as the `protocol` sans-colon, but isn't exposed on a URL object.
|
|
27
|
+
* This method can take all 3 of these string types as a parameter.
|
|
28
|
+
* @param {string} protocol Either a networkRequest's `protocol` per CDP or a `new URL(href).protocol`
|
|
29
|
+
* @return {boolean}
|
|
30
|
+
*/
|
|
31
|
+
function isNonNetworkProtocol(protocol) {
|
|
32
|
+
// Strip off any colon
|
|
33
|
+
const urlScheme = protocol.includes(':') ? protocol.slice(0, protocol.indexOf(':')) : protocol;
|
|
34
|
+
return NON_NETWORK_SCHEMES.includes(urlScheme);
|
|
35
|
+
}
|
|
12
36
|
|
|
13
37
|
/**
|
|
14
38
|
* @template [T=any]
|
|
@@ -75,9 +99,9 @@ class NetworkNode extends BaseNode {
|
|
|
75
99
|
*/
|
|
76
100
|
get isNonNetworkProtocol() {
|
|
77
101
|
// The 'protocol' field in devtools a string more like a `scheme`
|
|
78
|
-
return
|
|
102
|
+
return isNonNetworkProtocol(this.request.protocol) ||
|
|
79
103
|
// But `protocol` can fail to be populated if the request fails, so fallback to scheme.
|
|
80
|
-
|
|
104
|
+
isNonNetworkProtocol(this.request.parsedURL.scheme);
|
|
81
105
|
}
|
|
82
106
|
|
|
83
107
|
/**
|
|
@@ -18,10 +18,10 @@ export class PageDependencyGraph {
|
|
|
18
18
|
*/
|
|
19
19
|
static getNetworkNodeOutput(networkRecords: Array<Lantern.NetworkRequest>): NetworkNodeOutput;
|
|
20
20
|
/**
|
|
21
|
-
* @param {LH.
|
|
21
|
+
* @param {LH.TraceEvent[]} mainThreadEvents
|
|
22
22
|
* @return {Array<CPUNode>}
|
|
23
23
|
*/
|
|
24
|
-
static getCPUNodes(
|
|
24
|
+
static getCPUNodes(mainThreadEvents: LH.TraceEvent[]): Array<CPUNode>;
|
|
25
25
|
/**
|
|
26
26
|
* @param {NetworkNode} rootNode
|
|
27
27
|
* @param {NetworkNodeOutput} networkNodeOutput
|
|
@@ -40,12 +40,12 @@ export class PageDependencyGraph {
|
|
|
40
40
|
*/
|
|
41
41
|
static _pruneNode(node: Node): void;
|
|
42
42
|
/**
|
|
43
|
-
* @param {LH.
|
|
43
|
+
* @param {LH.TraceEvent[]} mainThreadEvents
|
|
44
44
|
* @param {Array<Lantern.NetworkRequest>} networkRecords
|
|
45
45
|
* @param {URLArtifact} URL
|
|
46
46
|
* @return {Node}
|
|
47
47
|
*/
|
|
48
|
-
static createGraph(
|
|
48
|
+
static createGraph(mainThreadEvents: LH.TraceEvent[], networkRecords: Array<Lantern.NetworkRequest>, URL: URLArtifact): Node;
|
|
49
49
|
/**
|
|
50
50
|
*
|
|
51
51
|
* @param {Node} rootNode
|
|
@@ -109,10 +109,10 @@ class PageDependencyGraph {
|
|
|
109
109
|
}
|
|
110
110
|
|
|
111
111
|
/**
|
|
112
|
-
* @param {LH.
|
|
112
|
+
* @param {LH.TraceEvent[]} mainThreadEvents
|
|
113
113
|
* @return {Array<CPUNode>}
|
|
114
114
|
*/
|
|
115
|
-
static getCPUNodes(
|
|
115
|
+
static getCPUNodes(mainThreadEvents) {
|
|
116
116
|
/** @type {Array<CPUNode>} */
|
|
117
117
|
const nodes = [];
|
|
118
118
|
let i = 0;
|
|
@@ -128,6 +128,9 @@ class PageDependencyGraph {
|
|
|
128
128
|
continue;
|
|
129
129
|
}
|
|
130
130
|
|
|
131
|
+
/** @type {number|undefined} */
|
|
132
|
+
let correctedEndTs = undefined;
|
|
133
|
+
|
|
131
134
|
// Capture all events that occurred within the task
|
|
132
135
|
/** @type {Array<LH.TraceEvent>} */
|
|
133
136
|
const children = [];
|
|
@@ -136,10 +139,19 @@ class PageDependencyGraph {
|
|
|
136
139
|
i < mainThreadEvents.length && mainThreadEvents[i].ts < endTime;
|
|
137
140
|
i++
|
|
138
141
|
) {
|
|
142
|
+
// Temporary fix for a Chrome bug where some RunTask events can be overlapping.
|
|
143
|
+
// We correct that here be ensuring each RunTask ends at least 1 microsecond before the next
|
|
144
|
+
// https://github.com/GoogleChrome/lighthouse/issues/15896
|
|
145
|
+
// https://issues.chromium.org/issues/329678173
|
|
146
|
+
if (TraceProcessor.isScheduleableTask(mainThreadEvents[i]) && mainThreadEvents[i].dur) {
|
|
147
|
+
correctedEndTs = mainThreadEvents[i].ts - 1;
|
|
148
|
+
break;
|
|
149
|
+
}
|
|
150
|
+
|
|
139
151
|
children.push(mainThreadEvents[i]);
|
|
140
152
|
}
|
|
141
153
|
|
|
142
|
-
nodes.push(new CPUNode(evt, children));
|
|
154
|
+
nodes.push(new CPUNode(evt, children, correctedEndTs));
|
|
143
155
|
}
|
|
144
156
|
|
|
145
157
|
return nodes;
|
|
@@ -359,7 +371,7 @@ class PageDependencyGraph {
|
|
|
359
371
|
isFirst = foundFirstParse = true;
|
|
360
372
|
}
|
|
361
373
|
|
|
362
|
-
if (isFirst || node.
|
|
374
|
+
if (isFirst || node.duration >= minimumEvtDur) {
|
|
363
375
|
// Don't prune this node. The task is long / important so it will impact simulation.
|
|
364
376
|
continue;
|
|
365
377
|
}
|
|
@@ -393,14 +405,14 @@ class PageDependencyGraph {
|
|
|
393
405
|
}
|
|
394
406
|
|
|
395
407
|
/**
|
|
396
|
-
* @param {LH.
|
|
408
|
+
* @param {LH.TraceEvent[]} mainThreadEvents
|
|
397
409
|
* @param {Array<Lantern.NetworkRequest>} networkRecords
|
|
398
410
|
* @param {URLArtifact} URL
|
|
399
411
|
* @return {Node}
|
|
400
412
|
*/
|
|
401
|
-
static createGraph(
|
|
413
|
+
static createGraph(mainThreadEvents, networkRecords, URL) {
|
|
402
414
|
const networkNodeOutput = PageDependencyGraph.getNetworkNodeOutput(networkRecords);
|
|
403
|
-
const cpuNodes = PageDependencyGraph.getCPUNodes(
|
|
415
|
+
const cpuNodes = PageDependencyGraph.getCPUNodes(mainThreadEvents);
|
|
404
416
|
const {requestedUrl, mainDocumentUrl} = URL;
|
|
405
417
|
if (!requestedUrl) throw new Error('requestedUrl is required to get the root request');
|
|
406
418
|
if (!mainDocumentUrl) throw new Error('mainDocumentUrl is required to get the main resource');
|
|
@@ -19,27 +19,17 @@ export class ConnectionPool {
|
|
|
19
19
|
_initializeConnections(): void;
|
|
20
20
|
/**
|
|
21
21
|
* @param {Array<TcpConnection>} connections
|
|
22
|
-
* @param {{ignoreConnectionReused?: boolean, observedConnectionWasReused: boolean}} options
|
|
23
22
|
*/
|
|
24
|
-
_findAvailableConnectionWithLargestCongestionWindow(connections: Array<TcpConnection
|
|
25
|
-
ignoreConnectionReused?: boolean | undefined;
|
|
26
|
-
observedConnectionWasReused: boolean;
|
|
27
|
-
}): TcpConnection | null;
|
|
23
|
+
_findAvailableConnectionWithLargestCongestionWindow(connections: Array<TcpConnection>): TcpConnection | null;
|
|
28
24
|
/**
|
|
29
25
|
* This method finds an available connection to the origin specified by the network record or null
|
|
30
26
|
* if no connection was available. If returned, connection will not be available for other network
|
|
31
27
|
* records until release is called.
|
|
32
28
|
*
|
|
33
|
-
* If ignoreConnectionReused is true, acquire will consider all connections not in use as available.
|
|
34
|
-
* Otherwise, only connections that have matching "warmth" are considered available.
|
|
35
|
-
*
|
|
36
29
|
* @param {Lantern.NetworkRequest} record
|
|
37
|
-
* @param {{ignoreConnectionReused?: boolean}} options
|
|
38
30
|
* @return {?TcpConnection}
|
|
39
31
|
*/
|
|
40
|
-
acquire(record: Lantern.NetworkRequest
|
|
41
|
-
ignoreConnectionReused?: boolean;
|
|
42
|
-
}): TcpConnection | null;
|
|
32
|
+
acquire(record: Lantern.NetworkRequest): TcpConnection | null;
|
|
43
33
|
/**
|
|
44
34
|
* Return the connection currently being used to fetch a record. If no connection
|
|
45
35
|
* currently being used for this record, an error will be thrown.
|
|
@@ -85,24 +85,13 @@ export class ConnectionPool {
|
|
|
85
85
|
|
|
86
86
|
/**
|
|
87
87
|
* @param {Array<TcpConnection>} connections
|
|
88
|
-
* @param {{ignoreConnectionReused?: boolean, observedConnectionWasReused: boolean}} options
|
|
89
88
|
*/
|
|
90
|
-
_findAvailableConnectionWithLargestCongestionWindow(connections
|
|
91
|
-
const {ignoreConnectionReused, observedConnectionWasReused} = options;
|
|
92
|
-
|
|
89
|
+
_findAvailableConnectionWithLargestCongestionWindow(connections) {
|
|
93
90
|
/** @type {TcpConnection|null} */
|
|
94
91
|
let maxConnection = null;
|
|
95
92
|
for (let i = 0; i < connections.length; i++) {
|
|
96
93
|
const connection = connections[i];
|
|
97
94
|
|
|
98
|
-
// Normally, we want to make sure the connection warmth matches the state of the record
|
|
99
|
-
// we're acquiring for. Do this check first since it's the common case and cheaper than our
|
|
100
|
-
// "in use" check below.
|
|
101
|
-
// Use the _warmed property instead of the getter because this is a surprisingly hot code path.
|
|
102
|
-
if (!ignoreConnectionReused && connection._warmed !== observedConnectionWasReused) {
|
|
103
|
-
continue;
|
|
104
|
-
}
|
|
105
|
-
|
|
106
95
|
// Connections that are in use are never available.
|
|
107
96
|
if (this._connectionsInUse.has(connection)) {
|
|
108
97
|
continue;
|
|
@@ -121,23 +110,15 @@ export class ConnectionPool {
|
|
|
121
110
|
* if no connection was available. If returned, connection will not be available for other network
|
|
122
111
|
* records until release is called.
|
|
123
112
|
*
|
|
124
|
-
* If ignoreConnectionReused is true, acquire will consider all connections not in use as available.
|
|
125
|
-
* Otherwise, only connections that have matching "warmth" are considered available.
|
|
126
|
-
*
|
|
127
113
|
* @param {Lantern.NetworkRequest} record
|
|
128
|
-
* @param {{ignoreConnectionReused?: boolean}} options
|
|
129
114
|
* @return {?TcpConnection}
|
|
130
115
|
*/
|
|
131
|
-
acquire(record
|
|
116
|
+
acquire(record) {
|
|
132
117
|
if (this._connectionsByRecord.has(record)) throw new Error('Record already has a connection');
|
|
133
118
|
|
|
134
119
|
const origin = record.parsedURL.securityOrigin;
|
|
135
|
-
const observedConnectionWasReused = !!this._connectionReusedByRequestId.get(record.requestId);
|
|
136
120
|
const connections = this._connectionsByOrigin.get(origin) || [];
|
|
137
|
-
const connectionToUse = this._findAvailableConnectionWithLargestCongestionWindow(connections
|
|
138
|
-
ignoreConnectionReused: options.ignoreConnectionReused,
|
|
139
|
-
observedConnectionWasReused,
|
|
140
|
-
});
|
|
121
|
+
const connectionToUse = this._findAvailableConnectionWithLargestCongestionWindow(connections);
|
|
141
122
|
|
|
142
123
|
if (!connectionToUse) return null;
|
|
143
124
|
|