lighthouse 11.7.0 → 12.0.0-dev.20240422
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/accesskeys.js +1 -1
- package/core/audits/accessibility/aria-allowed-attr.js +1 -1
- package/core/audits/accessibility/aria-allowed-role.js +1 -1
- package/core/audits/accessibility/aria-command-name.js +1 -1
- 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-dialog-name.js +1 -1
- package/core/audits/accessibility/aria-hidden-body.js +1 -1
- package/core/audits/accessibility/aria-hidden-focus.js +1 -1
- package/core/audits/accessibility/aria-input-field-name.js +1 -1
- package/core/audits/accessibility/aria-meter-name.js +1 -1
- package/core/audits/accessibility/aria-progressbar-name.js +1 -1
- 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/aria-required-attr.js +1 -1
- package/core/audits/accessibility/aria-required-children.js +1 -1
- package/core/audits/accessibility/aria-required-parent.js +1 -1
- package/core/audits/accessibility/aria-roles.js +1 -1
- package/core/audits/accessibility/aria-text.js +1 -1
- package/core/audits/accessibility/aria-toggle-field-name.js +1 -1
- package/core/audits/accessibility/aria-tooltip-name.js +1 -1
- package/core/audits/accessibility/aria-treeitem-name.js +1 -1
- package/core/audits/accessibility/aria-valid-attr-value.js +1 -1
- package/core/audits/accessibility/aria-valid-attr.js +1 -1
- package/core/audits/accessibility/button-name.js +1 -1
- package/core/audits/accessibility/bypass.js +1 -1
- package/core/audits/accessibility/color-contrast.js +1 -1
- package/core/audits/accessibility/definition-list.js +1 -1
- package/core/audits/accessibility/dlitem.js +1 -1
- package/core/audits/accessibility/document-title.js +1 -1
- package/core/audits/accessibility/duplicate-id-aria.js +1 -1
- package/core/audits/accessibility/empty-heading.js +1 -1
- package/core/audits/accessibility/form-field-multiple-labels.js +1 -1
- package/core/audits/accessibility/frame-title.js +1 -1
- package/core/audits/accessibility/heading-order.js +1 -1
- package/core/audits/accessibility/html-has-lang.js +1 -1
- package/core/audits/accessibility/html-lang-valid.js +1 -1
- package/core/audits/accessibility/html-xml-lang-mismatch.js +1 -1
- package/core/audits/accessibility/identical-links-same-purpose.js +1 -1
- package/core/audits/accessibility/image-alt.js +1 -1
- package/core/audits/accessibility/image-redundant-alt.js +1 -1
- package/core/audits/accessibility/input-button-name.js +1 -1
- package/core/audits/accessibility/input-image-alt.js +1 -1
- package/core/audits/accessibility/label-content-name-mismatch.js +1 -1
- package/core/audits/accessibility/label.js +1 -1
- package/core/audits/accessibility/landmark-one-main.js +1 -1
- package/core/audits/accessibility/link-in-text-block.js +1 -1
- package/core/audits/accessibility/link-name.js +1 -1
- package/core/audits/accessibility/list.js +1 -1
- package/core/audits/accessibility/listitem.js +1 -1
- package/core/audits/accessibility/meta-refresh.js +1 -1
- package/core/audits/accessibility/meta-viewport.js +1 -1
- package/core/audits/accessibility/object-alt.js +1 -1
- package/core/audits/accessibility/select-name.js +1 -1
- package/core/audits/accessibility/skip-link.js +1 -1
- package/core/audits/accessibility/tabindex.js +1 -1
- package/core/audits/accessibility/table-duplicate-name.js +1 -1
- package/core/audits/accessibility/table-fake-caption.js +1 -1
- package/core/audits/accessibility/target-size.js +1 -2
- package/core/audits/accessibility/td-has-header.js +1 -1
- package/core/audits/accessibility/td-headers-attr.js +1 -1
- package/core/audits/accessibility/th-has-data-cells.js +1 -1
- package/core/audits/accessibility/valid-lang.js +1 -1
- package/core/audits/accessibility/video-caption.js +1 -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 +2 -2
- 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/cumulative-layout-shift.js +2 -2
- 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 +4 -13
- package/core/gather/gatherers/trace-elements.js +42 -31
- 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 +29 -1
- package/core/lib/proto-preprocessor.js +24 -8
- package/core/lib/tracehouse/trace-processor.js +15 -2
- 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 +7 -7
- 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 +101 -332
- package/shared/localization/locales/en-XA.json +62 -329
- package/shared/localization/locales/en-XL.json +101 -332
- 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,64 @@
|
|
|
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 {FirstContentfulPaint} from './first-contentful-paint.js';
|
|
10
|
+
|
|
11
|
+
/** @typedef {import('../base-node.js').Node} Node */
|
|
12
|
+
|
|
13
|
+
class FirstMeaningfulPaint extends Metric {
|
|
14
|
+
/**
|
|
15
|
+
* @return {Lantern.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
|
+
* @param {LH.Artifacts.ProcessedNavigation} processedNavigation
|
|
28
|
+
* @return {Node}
|
|
29
|
+
*/
|
|
30
|
+
static getOptimisticGraph(dependencyGraph, processedNavigation) {
|
|
31
|
+
const fmp = processedNavigation.timestamps.firstMeaningfulPaint;
|
|
32
|
+
if (!fmp) {
|
|
33
|
+
throw new Error('NO_FMP');
|
|
34
|
+
}
|
|
35
|
+
return FirstContentfulPaint.getFirstPaintBasedGraph(dependencyGraph, {
|
|
36
|
+
cutoffTimestamp: fmp,
|
|
37
|
+
// See FirstContentfulPaint's getOptimisticGraph implementation for a longer description
|
|
38
|
+
// of why we exclude script initiated resources here.
|
|
39
|
+
treatNodeAsRenderBlocking: node =>
|
|
40
|
+
node.hasRenderBlockingPriority() && node.initiatorType !== 'script',
|
|
41
|
+
});
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
/**
|
|
45
|
+
* @param {Node} dependencyGraph
|
|
46
|
+
* @param {LH.Artifacts.ProcessedNavigation} processedNavigation
|
|
47
|
+
* @return {Node}
|
|
48
|
+
*/
|
|
49
|
+
static getPessimisticGraph(dependencyGraph, processedNavigation) {
|
|
50
|
+
const fmp = processedNavigation.timestamps.firstMeaningfulPaint;
|
|
51
|
+
if (!fmp) {
|
|
52
|
+
throw new Error('NO_FMP');
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
return FirstContentfulPaint.getFirstPaintBasedGraph(dependencyGraph, {
|
|
56
|
+
cutoffTimestamp: fmp,
|
|
57
|
+
treatNodeAsRenderBlocking: node => node.hasRenderBlockingPriority(),
|
|
58
|
+
// For pessimistic FMP we'll include *all* layout nodes
|
|
59
|
+
additionalCpuNodesToTreatAsRenderBlocking: node => node.didPerformLayout(),
|
|
60
|
+
});
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
export {FirstMeaningfulPaint};
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
export type Node = import('../base-node.js').Node;
|
|
2
|
+
export class Interactive extends Metric {
|
|
3
|
+
/**
|
|
4
|
+
* @param {Node} dependencyGraph
|
|
5
|
+
* @return {Node}
|
|
6
|
+
*/
|
|
7
|
+
static getOptimisticGraph(dependencyGraph: Node): Node;
|
|
8
|
+
/**
|
|
9
|
+
* @param {Node} dependencyGraph
|
|
10
|
+
* @return {Node}
|
|
11
|
+
*/
|
|
12
|
+
static getPessimisticGraph(dependencyGraph: Node): Node;
|
|
13
|
+
/**
|
|
14
|
+
* @param {LH.Gatherer.Simulation.Result} simulationResult
|
|
15
|
+
* @param {import('../metric.js').Extras} extras
|
|
16
|
+
* @return {LH.Gatherer.Simulation.Result}
|
|
17
|
+
*/
|
|
18
|
+
static getEstimateFromSimulation(simulationResult: LH.Gatherer.Simulation.Result, extras: import('../metric.js').Extras): LH.Gatherer.Simulation.Result;
|
|
19
|
+
/**
|
|
20
|
+
* @param {LH.Gatherer.Simulation.Result['nodeTimings']} nodeTimings
|
|
21
|
+
* @return {number}
|
|
22
|
+
*/
|
|
23
|
+
static getLastLongTaskEndTime(nodeTimings: LH.Gatherer.Simulation.Result['nodeTimings'], duration?: number): number;
|
|
24
|
+
}
|
|
25
|
+
import { Metric } from '../metric.js';
|
|
26
|
+
//# sourceMappingURL=interactive.d.ts.map
|
|
@@ -0,0 +1,112 @@
|
|
|
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 {NetworkRequestTypes} from '../lantern.js';
|
|
11
|
+
|
|
12
|
+
/** @typedef {import('../base-node.js').Node} Node */
|
|
13
|
+
|
|
14
|
+
// Any CPU task of 20 ms or more will end up being a critical long task on mobile
|
|
15
|
+
const CRITICAL_LONG_TASK_THRESHOLD = 20;
|
|
16
|
+
|
|
17
|
+
class Interactive extends Metric {
|
|
18
|
+
/**
|
|
19
|
+
* @return {LH.Gatherer.Simulation.MetricCoefficients}
|
|
20
|
+
*/
|
|
21
|
+
static get COEFFICIENTS() {
|
|
22
|
+
return {
|
|
23
|
+
intercept: 0,
|
|
24
|
+
optimistic: 0.45,
|
|
25
|
+
pessimistic: 0.55,
|
|
26
|
+
};
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* @param {Node} dependencyGraph
|
|
31
|
+
* @return {Node}
|
|
32
|
+
*/
|
|
33
|
+
static getOptimisticGraph(dependencyGraph) {
|
|
34
|
+
// Adjust the critical long task threshold for microseconds
|
|
35
|
+
const minimumCpuTaskDuration = CRITICAL_LONG_TASK_THRESHOLD * 1000;
|
|
36
|
+
|
|
37
|
+
return dependencyGraph.cloneWithRelationships(node => {
|
|
38
|
+
// Include everything that might be a long task
|
|
39
|
+
if (node.type === BaseNode.TYPES.CPU) {
|
|
40
|
+
return node.duration > minimumCpuTaskDuration;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
// Include all scripts and high priority requests, exclude all images
|
|
44
|
+
const isImage = node.record.resourceType === NetworkRequestTypes.Image;
|
|
45
|
+
const isScript = node.record.resourceType === NetworkRequestTypes.Script;
|
|
46
|
+
return (
|
|
47
|
+
!isImage &&
|
|
48
|
+
(isScript ||
|
|
49
|
+
node.record.priority === 'High' ||
|
|
50
|
+
node.record.priority === 'VeryHigh')
|
|
51
|
+
);
|
|
52
|
+
});
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
/**
|
|
56
|
+
* @param {Node} dependencyGraph
|
|
57
|
+
* @return {Node}
|
|
58
|
+
*/
|
|
59
|
+
static getPessimisticGraph(dependencyGraph) {
|
|
60
|
+
return dependencyGraph;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
/**
|
|
64
|
+
* @param {LH.Gatherer.Simulation.Result} simulationResult
|
|
65
|
+
* @param {import('../metric.js').Extras} extras
|
|
66
|
+
* @return {LH.Gatherer.Simulation.Result}
|
|
67
|
+
*/
|
|
68
|
+
static getEstimateFromSimulation(simulationResult, extras) {
|
|
69
|
+
if (!extras.fmpResult) throw new Error('missing fmpResult');
|
|
70
|
+
|
|
71
|
+
const lastTaskAt = Interactive.getLastLongTaskEndTime(simulationResult.nodeTimings);
|
|
72
|
+
const minimumTime = extras.optimistic
|
|
73
|
+
? extras.fmpResult.optimisticEstimate.timeInMs
|
|
74
|
+
: extras.fmpResult.pessimisticEstimate.timeInMs;
|
|
75
|
+
return {
|
|
76
|
+
timeInMs: Math.max(minimumTime, lastTaskAt),
|
|
77
|
+
nodeTimings: simulationResult.nodeTimings,
|
|
78
|
+
};
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
/**
|
|
82
|
+
* @param {Lantern.Simulation.MetricComputationDataInput} data
|
|
83
|
+
* @param {Omit<import('../metric.js').Extras, 'optimistic'>=} extras
|
|
84
|
+
* @return {Promise<LH.Artifacts.LanternMetric>}
|
|
85
|
+
*/
|
|
86
|
+
static async compute(data, extras) {
|
|
87
|
+
const fmpResult = extras?.fmpResult;
|
|
88
|
+
if (!fmpResult) {
|
|
89
|
+
throw new Error('FMP is required to calculate the Interactive metric');
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
const metricResult = await super.compute(data, extras);
|
|
93
|
+
metricResult.timing = Math.max(metricResult.timing, fmpResult.timing);
|
|
94
|
+
return metricResult;
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
/**
|
|
98
|
+
* @param {LH.Gatherer.Simulation.Result['nodeTimings']} nodeTimings
|
|
99
|
+
* @return {number}
|
|
100
|
+
*/
|
|
101
|
+
static getLastLongTaskEndTime(nodeTimings, duration = 50) {
|
|
102
|
+
return Array.from(nodeTimings.entries())
|
|
103
|
+
.filter(([node, timing]) => {
|
|
104
|
+
if (node.type !== BaseNode.TYPES.CPU) return false;
|
|
105
|
+
return timing.duration > duration;
|
|
106
|
+
})
|
|
107
|
+
.map(([_, timing]) => timing.endTime)
|
|
108
|
+
.reduce((max, x) => Math.max(max || 0, x || 0), 0);
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
export {Interactive};
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
export type Node = import('../base-node.js').Node;
|
|
2
|
+
/** @typedef {import('../base-node.js').Node} Node */
|
|
3
|
+
export class LargestContentfulPaint extends Metric {
|
|
4
|
+
/**
|
|
5
|
+
* Low priority image nodes are usually offscreen and very unlikely to be the
|
|
6
|
+
* resource that is required for LCP. Our LCP graphs include everything except for these images.
|
|
7
|
+
*
|
|
8
|
+
* @param {Node} node
|
|
9
|
+
* @return {boolean}
|
|
10
|
+
*/
|
|
11
|
+
static isNotLowPriorityImageNode(node: Node): boolean;
|
|
12
|
+
/**
|
|
13
|
+
* @param {LH.Gatherer.Simulation.Result} simulationResult
|
|
14
|
+
* @return {LH.Gatherer.Simulation.Result}
|
|
15
|
+
*/
|
|
16
|
+
static getEstimateFromSimulation(simulationResult: LH.Gatherer.Simulation.Result): LH.Gatherer.Simulation.Result;
|
|
17
|
+
}
|
|
18
|
+
import { Metric } from '../metric.js';
|
|
19
|
+
//# sourceMappingURL=largest-contentful-paint.d.ts.map
|
|
@@ -0,0 +1,108 @@
|
|
|
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 {FirstContentfulPaint} from './first-contentful-paint.js';
|
|
10
|
+
import {LanternError} from '../lantern-error.js';
|
|
11
|
+
|
|
12
|
+
/** @typedef {import('../base-node.js').Node} Node */
|
|
13
|
+
|
|
14
|
+
class LargestContentfulPaint 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
|
+
* Low priority image nodes are usually offscreen and very unlikely to be the
|
|
28
|
+
* resource that is required for LCP. Our LCP graphs include everything except for these images.
|
|
29
|
+
*
|
|
30
|
+
* @param {Node} node
|
|
31
|
+
* @return {boolean}
|
|
32
|
+
*/
|
|
33
|
+
static isNotLowPriorityImageNode(node) {
|
|
34
|
+
if (node.type !== 'network') return true;
|
|
35
|
+
const isImage = node.record.resourceType === 'Image';
|
|
36
|
+
const isLowPriority = node.record.priority === 'Low' || node.record.priority === 'VeryLow';
|
|
37
|
+
return !isImage || !isLowPriority;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* @param {Node} dependencyGraph
|
|
42
|
+
* @param {LH.Artifacts.ProcessedNavigation} processedNavigation
|
|
43
|
+
* @return {Node}
|
|
44
|
+
*/
|
|
45
|
+
static getOptimisticGraph(dependencyGraph, processedNavigation) {
|
|
46
|
+
const lcp = processedNavigation.timestamps.largestContentfulPaint;
|
|
47
|
+
if (!lcp) {
|
|
48
|
+
throw new LanternError('NO_LCP');
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
return FirstContentfulPaint.getFirstPaintBasedGraph(dependencyGraph, {
|
|
52
|
+
cutoffTimestamp: lcp,
|
|
53
|
+
treatNodeAsRenderBlocking: LargestContentfulPaint.isNotLowPriorityImageNode,
|
|
54
|
+
});
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
/**
|
|
58
|
+
* @param {Node} dependencyGraph
|
|
59
|
+
* @param {LH.Artifacts.ProcessedNavigation} processedNavigation
|
|
60
|
+
* @return {Node}
|
|
61
|
+
*/
|
|
62
|
+
static getPessimisticGraph(dependencyGraph, processedNavigation) {
|
|
63
|
+
const lcp = processedNavigation.timestamps.largestContentfulPaint;
|
|
64
|
+
if (!lcp) {
|
|
65
|
+
throw new LanternError('NO_LCP');
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
return FirstContentfulPaint.getFirstPaintBasedGraph(dependencyGraph, {
|
|
69
|
+
cutoffTimestamp: lcp,
|
|
70
|
+
treatNodeAsRenderBlocking: _ => true,
|
|
71
|
+
// For pessimistic LCP we'll include *all* layout nodes
|
|
72
|
+
additionalCpuNodesToTreatAsRenderBlocking: node => node.didPerformLayout(),
|
|
73
|
+
});
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
/**
|
|
77
|
+
* @param {LH.Gatherer.Simulation.Result} simulationResult
|
|
78
|
+
* @return {LH.Gatherer.Simulation.Result}
|
|
79
|
+
*/
|
|
80
|
+
static getEstimateFromSimulation(simulationResult) {
|
|
81
|
+
const nodeTimesNotOffscreenImages = Array.from(simulationResult.nodeTimings.entries())
|
|
82
|
+
.filter(entry => LargestContentfulPaint.isNotLowPriorityImageNode(entry[0]))
|
|
83
|
+
.map(entry => entry[1].endTime);
|
|
84
|
+
|
|
85
|
+
return {
|
|
86
|
+
timeInMs: Math.max(...nodeTimesNotOffscreenImages),
|
|
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 LCP 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
|
+
export {LargestContentfulPaint};
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
export type Node = import('../base-node.js').Node;
|
|
2
|
+
/** @typedef {import('../base-node.js').Node} Node */
|
|
3
|
+
export class MaxPotentialFID 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} fcpTimeInMs
|
|
23
|
+
* @return {Array<{duration: number}>}
|
|
24
|
+
*/
|
|
25
|
+
static getTimingsAfterFCP(nodeTimings: LH.Gatherer.Simulation.Result['nodeTimings'], fcpTimeInMs: number): Array<{
|
|
26
|
+
duration: number;
|
|
27
|
+
}>;
|
|
28
|
+
}
|
|
29
|
+
import { Metric } from '../metric.js';
|
|
30
|
+
//# sourceMappingURL=max-potential-fid.d.ts.map
|
|
@@ -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
|