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
|
@@ -28,7 +28,6 @@ import {TraceEngineResult} from '../../computed/trace-engine-result.js';
|
|
|
28
28
|
|
|
29
29
|
/** @typedef {{nodeId: number, animations?: {name?: string, failureReasonsMask?: number, unsupportedProperties?: string[]}[], type?: string}} TraceElementData */
|
|
30
30
|
|
|
31
|
-
const MAX_LAYOUT_SHIFT_ELEMENTS = 15;
|
|
32
31
|
const MAX_LAYOUT_SHIFTS = 15;
|
|
33
32
|
|
|
34
33
|
/**
|
|
@@ -66,41 +65,55 @@ class TraceElements extends BaseGatherer {
|
|
|
66
65
|
if (name) this.animationIdToName.set(id, name);
|
|
67
66
|
}
|
|
68
67
|
|
|
69
|
-
/**
|
|
70
|
-
* This function finds the top (up to 15) elements that shift on the page.
|
|
71
|
-
*
|
|
72
|
-
* @param {LH.Trace} trace
|
|
73
|
-
* @param {LH.Gatherer.Context} context
|
|
74
|
-
* @return {Promise<Array<{nodeId: number}>>}
|
|
75
|
-
*/
|
|
76
|
-
static async getTopLayoutShiftElements(trace, context) {
|
|
77
|
-
const {impactByNodeId} = await CumulativeLayoutShift.request(trace, context);
|
|
78
|
-
|
|
79
|
-
return [...impactByNodeId.entries()]
|
|
80
|
-
.sort((a, b) => b[1] - a[1])
|
|
81
|
-
.slice(0, MAX_LAYOUT_SHIFT_ELEMENTS)
|
|
82
|
-
.map(([nodeId]) => ({nodeId}));
|
|
83
|
-
}
|
|
84
|
-
|
|
85
68
|
/**
|
|
86
69
|
* We want to a single representative node to represent the shift, so let's pick
|
|
87
70
|
* the one with the largest impact (size x distance moved).
|
|
88
71
|
*
|
|
89
72
|
* @param {LH.Artifacts.TraceImpactedNode[]} impactedNodes
|
|
90
73
|
* @param {Map<number, number>} impactByNodeId
|
|
74
|
+
* @param {import('../../lib/trace-engine.js').SaneSyntheticLayoutShift} event Only for debugging
|
|
91
75
|
* @return {number|undefined}
|
|
92
76
|
*/
|
|
93
|
-
static getBiggestImpactNodeForShiftEvent(impactedNodes, impactByNodeId) {
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
const
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
77
|
+
static getBiggestImpactNodeForShiftEvent(impactedNodes, impactByNodeId, event) {
|
|
78
|
+
try {
|
|
79
|
+
let biggestImpactNodeId;
|
|
80
|
+
let biggestImpactNodeScore = Number.NEGATIVE_INFINITY;
|
|
81
|
+
for (const node of impactedNodes) {
|
|
82
|
+
const impactScore = impactByNodeId.get(node.node_id);
|
|
83
|
+
if (impactScore !== undefined && impactScore > biggestImpactNodeScore) {
|
|
84
|
+
biggestImpactNodeId = node.node_id;
|
|
85
|
+
biggestImpactNodeScore = impactScore;
|
|
86
|
+
}
|
|
101
87
|
}
|
|
88
|
+
return biggestImpactNodeId;
|
|
89
|
+
} catch (err) {
|
|
90
|
+
// See https://github.com/GoogleChrome/lighthouse/issues/15870
|
|
91
|
+
// `impactedNodes` should always be an array here, but it can randomly be something else for
|
|
92
|
+
// currently unknown reasons. This exception handling will help us identify what
|
|
93
|
+
// `impactedNodes` really is and also prevent the error from being fatal.
|
|
94
|
+
|
|
95
|
+
// It's possible `impactedNodes` is not JSON serializable, so let's add more supplemental
|
|
96
|
+
// fields just in case.
|
|
97
|
+
const impactedNodesType = typeof impactedNodes;
|
|
98
|
+
const impactedNodesClassName = impactedNodes?.constructor?.name;
|
|
99
|
+
|
|
100
|
+
let impactedNodesJson;
|
|
101
|
+
let eventJson;
|
|
102
|
+
try {
|
|
103
|
+
impactedNodesJson = JSON.parse(JSON.stringify(impactedNodes));
|
|
104
|
+
eventJson = JSON.parse(JSON.stringify(event));
|
|
105
|
+
} catch {}
|
|
106
|
+
|
|
107
|
+
Sentry.captureException(err, {
|
|
108
|
+
extra: {
|
|
109
|
+
impactedNodes: impactedNodesJson,
|
|
110
|
+
event: eventJson,
|
|
111
|
+
impactedNodesType,
|
|
112
|
+
impactedNodesClassName,
|
|
113
|
+
},
|
|
114
|
+
});
|
|
115
|
+
return;
|
|
102
116
|
}
|
|
103
|
-
return biggestImpactNodeId;
|
|
104
117
|
}
|
|
105
118
|
|
|
106
119
|
/**
|
|
@@ -109,7 +122,7 @@ class TraceElements extends BaseGatherer {
|
|
|
109
122
|
* that may have caused the shift.
|
|
110
123
|
*
|
|
111
124
|
* @param {LH.Trace} trace
|
|
112
|
-
* @param {LH.Artifacts.TraceEngineResult} traceEngineResult
|
|
125
|
+
* @param {LH.Artifacts.TraceEngineResult['data']} traceEngineResult
|
|
113
126
|
* @param {LH.Artifacts.TraceEngineRootCauses} rootCauses
|
|
114
127
|
* @param {LH.Gatherer.Context} context
|
|
115
128
|
* @return {Promise<Array<{nodeId: number}>>}
|
|
@@ -129,7 +142,7 @@ class TraceElements extends BaseGatherer {
|
|
|
129
142
|
const nodeIds = [];
|
|
130
143
|
const impactedNodes = event.args.data.impacted_nodes || [];
|
|
131
144
|
const biggestImpactedNodeId =
|
|
132
|
-
this.getBiggestImpactNodeForShiftEvent(impactedNodes, impactByNodeId);
|
|
145
|
+
this.getBiggestImpactNodeForShiftEvent(impactedNodes, impactByNodeId, event);
|
|
133
146
|
if (biggestImpactedNodeId !== undefined) {
|
|
134
147
|
nodeIds.push(biggestImpactedNodeId);
|
|
135
148
|
}
|
|
@@ -308,16 +321,14 @@ class TraceElements extends BaseGatherer {
|
|
|
308
321
|
const {mainThreadEvents} = processedTrace;
|
|
309
322
|
|
|
310
323
|
const lcpNodeData = await TraceElements.getLcpElement(trace, context);
|
|
311
|
-
const shiftElementsNodeData = await TraceElements.getTopLayoutShiftElements(trace, context);
|
|
312
324
|
const shiftsData = await TraceElements.getTopLayoutShifts(
|
|
313
|
-
trace, traceEngineResult, rootCauses, context);
|
|
325
|
+
trace, traceEngineResult.data, rootCauses, context);
|
|
314
326
|
const animatedElementData = await this.getAnimatedElements(mainThreadEvents);
|
|
315
327
|
const responsivenessElementData = await TraceElements.getResponsivenessElement(trace, context);
|
|
316
328
|
|
|
317
329
|
/** @type {Map<string, TraceElementData[]>} */
|
|
318
330
|
const backendNodeDataMap = new Map([
|
|
319
331
|
['largest-contentful-paint', lcpNodeData ? [lcpNodeData] : []],
|
|
320
|
-
['layout-shift-element', shiftElementsNodeData],
|
|
321
332
|
['layout-shift', shiftsData],
|
|
322
333
|
['animation', animatedElementData],
|
|
323
334
|
['responsiveness', responsivenessElementData ? [responsivenessElementData] : []],
|
|
@@ -92,7 +92,9 @@ async function _navigate(navigationContext) {
|
|
|
92
92
|
return {requestedUrl, mainDocumentUrl, navigationError: undefined};
|
|
93
93
|
} catch (err) {
|
|
94
94
|
if (!(err instanceof LighthouseError)) throw err;
|
|
95
|
-
if (err.code !== 'NO_FCP' && err.code !== 'PAGE_HUNG'
|
|
95
|
+
if (err.code !== 'NO_FCP' && err.code !== 'PAGE_HUNG' && err.code !== 'TARGET_CRASHED') {
|
|
96
|
+
throw err;
|
|
97
|
+
}
|
|
96
98
|
if (typeof requestor !== 'string') throw err;
|
|
97
99
|
|
|
98
100
|
// TODO: Make the urls optional here so we don't need to throw an error with a callback requestor.
|
package/core/gather/session.d.ts
CHANGED
|
@@ -40,6 +40,14 @@ export class ProtocolSession extends ProtocolSession_base implements LH.Gatherer
|
|
|
40
40
|
* @return {Promise<LH.CrdpCommands[C]['returnType']>}
|
|
41
41
|
*/
|
|
42
42
|
sendCommand<C extends keyof import("puppeteer-core").ProtocolMapping.Commands>(method: C, ...params: import("puppeteer-core").ProtocolMapping.Commands[C]["paramsType"]): Promise<import("puppeteer-core").ProtocolMapping.Commands[C]["returnType"]>;
|
|
43
|
+
/**
|
|
44
|
+
* Send and if there's an error response, do not reject.
|
|
45
|
+
* @template {keyof LH.CrdpCommands} C
|
|
46
|
+
* @param {C} method
|
|
47
|
+
* @param {LH.CrdpCommands[C]['paramsType']} params
|
|
48
|
+
* @return {Promise<void>}
|
|
49
|
+
*/
|
|
50
|
+
sendCommandAndIgnore<C_1 extends keyof import("puppeteer-core").ProtocolMapping.Commands>(method: C_1, ...params: import("puppeteer-core").ProtocolMapping.Commands[C_1]["paramsType"]): Promise<void>;
|
|
43
51
|
/**
|
|
44
52
|
* Disposes of a session so that it can no longer talk to Chrome.
|
|
45
53
|
* @return {Promise<void>}
|
package/core/gather/session.js
CHANGED
|
@@ -100,6 +100,7 @@ class ProtocolSession extends CrdpEventEmitter {
|
|
|
100
100
|
/** @type {NodeJS.Timer|undefined} */
|
|
101
101
|
let timeout;
|
|
102
102
|
const timeoutPromise = new Promise((resolve, reject) => {
|
|
103
|
+
// Unexpected setTimeout invocation to preserve the error stack. https://github.com/GoogleChrome/lighthouse/issues/13332
|
|
103
104
|
// eslint-disable-next-line max-len
|
|
104
105
|
timeout = setTimeout(reject, timeoutMs, new LighthouseError(LighthouseError.errors.PROTOCOL_TIMEOUT, {
|
|
105
106
|
protocolMethod: method,
|
|
@@ -120,6 +121,18 @@ class ProtocolSession extends CrdpEventEmitter {
|
|
|
120
121
|
});
|
|
121
122
|
}
|
|
122
123
|
|
|
124
|
+
/**
|
|
125
|
+
* Send and if there's an error response, do not reject.
|
|
126
|
+
* @template {keyof LH.CrdpCommands} C
|
|
127
|
+
* @param {C} method
|
|
128
|
+
* @param {LH.CrdpCommands[C]['paramsType']} params
|
|
129
|
+
* @return {Promise<void>}
|
|
130
|
+
*/
|
|
131
|
+
sendCommandAndIgnore(method, ...params) {
|
|
132
|
+
return this.sendCommand(method, ...params)
|
|
133
|
+
.catch(e => log.verbose('session', method, e.message)).then(_ => void 0);
|
|
134
|
+
}
|
|
135
|
+
|
|
123
136
|
/**
|
|
124
137
|
* Disposes of a session so that it can no longer talk to Chrome.
|
|
125
138
|
* @return {Promise<void>}
|
|
@@ -127,7 +140,7 @@ class ProtocolSession extends CrdpEventEmitter {
|
|
|
127
140
|
async dispose() {
|
|
128
141
|
// @ts-expect-error Puppeteer expects the handler params to be type `unknown`
|
|
129
142
|
this._cdpSession.off('*', this._handleProtocolEvent);
|
|
130
|
-
await this._cdpSession.detach();
|
|
143
|
+
await this._cdpSession.detach().catch(e => log.verbose('session', 'detach failed', e.message));
|
|
131
144
|
}
|
|
132
145
|
}
|
|
133
146
|
|
package/core/lib/emulation.js
CHANGED
|
@@ -128,7 +128,7 @@ function enableNetworkThrottling(session, throttlingSettings) {
|
|
|
128
128
|
* @return {Promise<void>}
|
|
129
129
|
*/
|
|
130
130
|
function clearNetworkThrottling(session) {
|
|
131
|
-
return session.
|
|
131
|
+
return session.sendCommandAndIgnore('Network.emulateNetworkConditions', NO_THROTTLING_METRICS);
|
|
132
132
|
}
|
|
133
133
|
|
|
134
134
|
/**
|
|
@@ -146,7 +146,7 @@ function enableCPUThrottling(session, throttlingSettings) {
|
|
|
146
146
|
* @return {Promise<void>}
|
|
147
147
|
*/
|
|
148
148
|
function clearCPUThrottling(session) {
|
|
149
|
-
return session.
|
|
149
|
+
return session.sendCommandAndIgnore('Emulation.setCPUThrottlingRate', NO_CPU_THROTTLE_METRICS);
|
|
150
150
|
}
|
|
151
151
|
|
|
152
152
|
export {
|
package/core/lib/i18n/i18n.d.ts
CHANGED
package/core/lib/i18n/i18n.js
CHANGED
|
@@ -57,8 +57,6 @@ const UIStrings = {
|
|
|
57
57
|
columnName: 'Name',
|
|
58
58
|
/** Label for a column in a data table; entries will be the locations of JavaScript or CSS code, e.g. the name of a Javascript package or module. */
|
|
59
59
|
columnSource: 'Source',
|
|
60
|
-
/** Label for a column in a data table; entries will be how much a predetermined budget has been exeeded by. Depending on the context, this number could represent an excess in quantity or size of network requests, or, an excess in the duration of time that it takes for the page to load.*/
|
|
61
|
-
columnOverBudget: 'Over Budget',
|
|
62
60
|
/** Label for a column in a data table; entries will be a representation of a DOM element. */
|
|
63
61
|
columnElement: 'Element',
|
|
64
62
|
/** Label for a column in a data table; entries will be the number of milliseconds since the page started loading. */
|
|
@@ -6,11 +6,17 @@ export class CPUNode<T = any> extends BaseNode<T> {
|
|
|
6
6
|
/**
|
|
7
7
|
* @param {LH.TraceEvent} parentEvent
|
|
8
8
|
* @param {LH.TraceEvent[]=} childEvents
|
|
9
|
+
* @param {number=} correctedEndTs
|
|
9
10
|
*/
|
|
10
|
-
constructor(parentEvent: LH.TraceEvent, childEvents?: LH.TraceEvent[] | undefined);
|
|
11
|
+
constructor(parentEvent: LH.TraceEvent, childEvents?: LH.TraceEvent[] | undefined, correctedEndTs?: number | undefined);
|
|
11
12
|
_event: LH.TraceEvent;
|
|
12
13
|
_childEvents: LH.TraceEvent[];
|
|
14
|
+
_correctedEndTs: number | undefined;
|
|
13
15
|
get type(): "cpu";
|
|
16
|
+
/**
|
|
17
|
+
* @return {number}
|
|
18
|
+
*/
|
|
19
|
+
get duration(): number;
|
|
14
20
|
/**
|
|
15
21
|
* @return {LH.TraceEvent}
|
|
16
22
|
*/
|
|
@@ -15,13 +15,15 @@ class CPUNode extends BaseNode {
|
|
|
15
15
|
/**
|
|
16
16
|
* @param {LH.TraceEvent} parentEvent
|
|
17
17
|
* @param {LH.TraceEvent[]=} childEvents
|
|
18
|
+
* @param {number=} correctedEndTs
|
|
18
19
|
*/
|
|
19
|
-
constructor(parentEvent, childEvents = []) {
|
|
20
|
+
constructor(parentEvent, childEvents = [], correctedEndTs) {
|
|
20
21
|
const nodeId = `${parentEvent.tid}.${parentEvent.ts}`;
|
|
21
22
|
super(nodeId);
|
|
22
23
|
|
|
23
24
|
this._event = parentEvent;
|
|
24
25
|
this._childEvents = childEvents;
|
|
26
|
+
this._correctedEndTs = correctedEndTs;
|
|
25
27
|
}
|
|
26
28
|
|
|
27
29
|
get type() {
|
|
@@ -39,9 +41,17 @@ class CPUNode extends BaseNode {
|
|
|
39
41
|
* @return {number}
|
|
40
42
|
*/
|
|
41
43
|
get endTime() {
|
|
44
|
+
if (this._correctedEndTs) return this._correctedEndTs;
|
|
42
45
|
return this._event.ts + this._event.dur;
|
|
43
46
|
}
|
|
44
47
|
|
|
48
|
+
/**
|
|
49
|
+
* @return {number}
|
|
50
|
+
*/
|
|
51
|
+
get duration() {
|
|
52
|
+
return this.endTime - this.startTime;
|
|
53
|
+
}
|
|
54
|
+
|
|
45
55
|
/**
|
|
46
56
|
* @return {LH.TraceEvent}
|
|
47
57
|
*/
|
|
@@ -83,7 +93,7 @@ class CPUNode extends BaseNode {
|
|
|
83
93
|
* @return {CPUNode}
|
|
84
94
|
*/
|
|
85
95
|
cloneWithoutRelationships() {
|
|
86
|
-
return new CPUNode(this._event, this._childEvents);
|
|
96
|
+
return new CPUNode(this._event, this._childEvents, this._correctedEndTs);
|
|
87
97
|
}
|
|
88
98
|
}
|
|
89
99
|
|
|
@@ -101,19 +101,15 @@ class Metric {
|
|
|
101
101
|
const optimisticGraph = this.getOptimisticGraph(graph, processedNavigation);
|
|
102
102
|
const pessimisticGraph = this.getPessimisticGraph(graph, processedNavigation);
|
|
103
103
|
|
|
104
|
-
/** @type {{flexibleOrdering?: boolean, label?: string}} */
|
|
105
104
|
let simulateOptions = {label: `optimistic${metricName}`};
|
|
106
105
|
const optimisticSimulation = simulator.simulate(optimisticGraph, simulateOptions);
|
|
107
106
|
|
|
108
|
-
simulateOptions = {label: `optimisticFlex${metricName}`, flexibleOrdering: true};
|
|
109
|
-
const optimisticFlexSimulation = simulator.simulate(optimisticGraph, simulateOptions);
|
|
110
|
-
|
|
111
107
|
simulateOptions = {label: `pessimistic${metricName}`};
|
|
112
108
|
const pessimisticSimulation = simulator.simulate(pessimisticGraph, simulateOptions);
|
|
113
109
|
|
|
114
110
|
const optimisticEstimate = this.getEstimateFromSimulation(
|
|
115
|
-
optimisticSimulation
|
|
116
|
-
|
|
111
|
+
optimisticSimulation,
|
|
112
|
+
{...extras, optimistic: true}
|
|
117
113
|
);
|
|
118
114
|
|
|
119
115
|
const pessimisticEstimate = this.getEstimateFromSimulation(
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
export type Node = import('../base-node.js').Node;
|
|
2
|
+
export type NetworkNode<T> = import('../network-node.js').NetworkNode<T>;
|
|
3
|
+
export type CpuNode = import('../cpu-node.js').CPUNode;
|
|
4
|
+
/** @typedef {import('../base-node.js').Node} Node */
|
|
5
|
+
/** @template T @typedef {import('../network-node.js').NetworkNode<T>} NetworkNode */
|
|
6
|
+
/** @typedef {import('../cpu-node.js').CPUNode} CpuNode */
|
|
7
|
+
export class FirstContentfulPaint extends Metric {
|
|
8
|
+
/**
|
|
9
|
+
* @template T
|
|
10
|
+
* @typedef FirstPaintBasedGraphOpts
|
|
11
|
+
* @property {number} cutoffTimestamp The timestamp used to filter out tasks that occured after
|
|
12
|
+
* our paint of interest. Typically this is First Contentful Paint or First Meaningful Paint.
|
|
13
|
+
* @property {function(NetworkNode<T>):boolean} treatNodeAsRenderBlocking The function that determines
|
|
14
|
+
* which resources should be considered *possibly* render-blocking.
|
|
15
|
+
* @property {(function(CpuNode):boolean)=} additionalCpuNodesToTreatAsRenderBlocking The function that
|
|
16
|
+
* determines which CPU nodes should also be included in our blocking node IDs set,
|
|
17
|
+
* beyond what getRenderBlockingNodeData() already includes.
|
|
18
|
+
*/
|
|
19
|
+
/**
|
|
20
|
+
* This function computes the set of URLs that *appeared* to be render-blocking based on our filter,
|
|
21
|
+
* *but definitely were not* render-blocking based on the timing of their EvaluateScript task.
|
|
22
|
+
* It also computes the set of corresponding CPU node ids that were needed for the paint at the
|
|
23
|
+
* given timestamp.
|
|
24
|
+
*
|
|
25
|
+
* @template [T=unknown]
|
|
26
|
+
* @param {Node} graph
|
|
27
|
+
* @param {FirstPaintBasedGraphOpts<T>} opts
|
|
28
|
+
* @return {{definitelyNotRenderBlockingScriptUrls: Set<string>, renderBlockingCpuNodeIds: Set<string>}}
|
|
29
|
+
*/
|
|
30
|
+
static getRenderBlockingNodeData<T = unknown>(graph: Node, { cutoffTimestamp, treatNodeAsRenderBlocking, additionalCpuNodesToTreatAsRenderBlocking }: {
|
|
31
|
+
/**
|
|
32
|
+
* The timestamp used to filter out tasks that occured after
|
|
33
|
+
* our paint of interest. Typically this is First Contentful Paint or First Meaningful Paint.
|
|
34
|
+
*/
|
|
35
|
+
cutoffTimestamp: number;
|
|
36
|
+
/**
|
|
37
|
+
* The function that determines
|
|
38
|
+
* which resources should be considered *possibly* render-blocking.
|
|
39
|
+
*/
|
|
40
|
+
treatNodeAsRenderBlocking: (arg0: import("../network-node.js").NetworkNode<T>) => boolean;
|
|
41
|
+
/**
|
|
42
|
+
* The function that
|
|
43
|
+
* determines which CPU nodes should also be included in our blocking node IDs set,
|
|
44
|
+
* beyond what getRenderBlockingNodeData() already includes.
|
|
45
|
+
*/
|
|
46
|
+
additionalCpuNodesToTreatAsRenderBlocking?: ((arg0: CpuNode) => boolean) | undefined;
|
|
47
|
+
}): {
|
|
48
|
+
definitelyNotRenderBlockingScriptUrls: Set<string>;
|
|
49
|
+
renderBlockingCpuNodeIds: Set<string>;
|
|
50
|
+
};
|
|
51
|
+
/**
|
|
52
|
+
* This function computes the graph required for the first paint of interest.
|
|
53
|
+
*
|
|
54
|
+
* @template [T=unknown]
|
|
55
|
+
* @param {Node} dependencyGraph
|
|
56
|
+
* @param {FirstPaintBasedGraphOpts<T>} opts
|
|
57
|
+
* @return {Node}
|
|
58
|
+
*/
|
|
59
|
+
static getFirstPaintBasedGraph<T_1 = unknown>(dependencyGraph: Node, { cutoffTimestamp, treatNodeAsRenderBlocking, additionalCpuNodesToTreatAsRenderBlocking }: {
|
|
60
|
+
/**
|
|
61
|
+
* The timestamp used to filter out tasks that occured after
|
|
62
|
+
* our paint of interest. Typically this is First Contentful Paint or First Meaningful Paint.
|
|
63
|
+
*/
|
|
64
|
+
cutoffTimestamp: number;
|
|
65
|
+
/**
|
|
66
|
+
* The function that determines
|
|
67
|
+
* which resources should be considered *possibly* render-blocking.
|
|
68
|
+
*/
|
|
69
|
+
treatNodeAsRenderBlocking: (arg0: import("../network-node.js").NetworkNode<T_1>) => boolean;
|
|
70
|
+
/**
|
|
71
|
+
* The function that
|
|
72
|
+
* determines which CPU nodes should also be included in our blocking node IDs set,
|
|
73
|
+
* beyond what getRenderBlockingNodeData() already includes.
|
|
74
|
+
*/
|
|
75
|
+
additionalCpuNodesToTreatAsRenderBlocking?: ((arg0: CpuNode) => boolean) | undefined;
|
|
76
|
+
}): Node;
|
|
77
|
+
}
|
|
78
|
+
import { Metric } from '../metric.js';
|
|
79
|
+
//# sourceMappingURL=first-contentful-paint.d.ts.map
|
|
@@ -0,0 +1,200 @@
|
|
|
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
|
+
/** @template T @typedef {import('../network-node.js').NetworkNode<T>} NetworkNode */
|
|
13
|
+
/** @typedef {import('../cpu-node.js').CPUNode} CpuNode */
|
|
14
|
+
|
|
15
|
+
class FirstContentfulPaint extends Metric {
|
|
16
|
+
/**
|
|
17
|
+
* @return {Lantern.Simulation.MetricCoefficients}
|
|
18
|
+
*/
|
|
19
|
+
static get COEFFICIENTS() {
|
|
20
|
+
return {
|
|
21
|
+
intercept: 0,
|
|
22
|
+
optimistic: 0.5,
|
|
23
|
+
pessimistic: 0.5,
|
|
24
|
+
};
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* @template T
|
|
29
|
+
* @typedef FirstPaintBasedGraphOpts
|
|
30
|
+
* @property {number} cutoffTimestamp The timestamp used to filter out tasks that occured after
|
|
31
|
+
* our paint of interest. Typically this is First Contentful Paint or First Meaningful Paint.
|
|
32
|
+
* @property {function(NetworkNode<T>):boolean} treatNodeAsRenderBlocking The function that determines
|
|
33
|
+
* which resources should be considered *possibly* render-blocking.
|
|
34
|
+
* @property {(function(CpuNode):boolean)=} additionalCpuNodesToTreatAsRenderBlocking The function that
|
|
35
|
+
* determines which CPU nodes should also be included in our blocking node IDs set,
|
|
36
|
+
* beyond what getRenderBlockingNodeData() already includes.
|
|
37
|
+
*/
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* This function computes the set of URLs that *appeared* to be render-blocking based on our filter,
|
|
41
|
+
* *but definitely were not* render-blocking based on the timing of their EvaluateScript task.
|
|
42
|
+
* It also computes the set of corresponding CPU node ids that were needed for the paint at the
|
|
43
|
+
* given timestamp.
|
|
44
|
+
*
|
|
45
|
+
* @template [T=unknown]
|
|
46
|
+
* @param {Node} graph
|
|
47
|
+
* @param {FirstPaintBasedGraphOpts<T>} opts
|
|
48
|
+
* @return {{definitelyNotRenderBlockingScriptUrls: Set<string>, renderBlockingCpuNodeIds: Set<string>}}
|
|
49
|
+
*/
|
|
50
|
+
static getRenderBlockingNodeData(
|
|
51
|
+
graph,
|
|
52
|
+
{cutoffTimestamp, treatNodeAsRenderBlocking, additionalCpuNodesToTreatAsRenderBlocking}
|
|
53
|
+
) {
|
|
54
|
+
/** @type {Map<string, CpuNode>} A map of blocking script URLs to the earliest EvaluateScript task node that executed them. */
|
|
55
|
+
const scriptUrlToNodeMap = new Map();
|
|
56
|
+
|
|
57
|
+
/** @type {Array<CpuNode>} */
|
|
58
|
+
const cpuNodes = [];
|
|
59
|
+
graph.traverse(node => {
|
|
60
|
+
if (node.type === BaseNode.TYPES.CPU) {
|
|
61
|
+
// A task is *possibly* render blocking if it *started* before cutoffTimestamp.
|
|
62
|
+
// We use startTime here because the paint event can be *inside* the task that was render blocking.
|
|
63
|
+
if (node.startTime <= cutoffTimestamp) cpuNodes.push(node);
|
|
64
|
+
|
|
65
|
+
// Build our script URL map to find the earliest EvaluateScript task node.
|
|
66
|
+
const scriptUrls = node.getEvaluateScriptURLs();
|
|
67
|
+
for (const url of scriptUrls) {
|
|
68
|
+
// Use the earliest CPU node we find.
|
|
69
|
+
const existing = scriptUrlToNodeMap.get(url) || node;
|
|
70
|
+
scriptUrlToNodeMap.set(url, node.startTime < existing.startTime ? node : existing);
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
});
|
|
74
|
+
|
|
75
|
+
cpuNodes.sort((a, b) => a.startTime - b.startTime);
|
|
76
|
+
|
|
77
|
+
// A script is *possibly* render blocking if it finished loading before cutoffTimestamp.
|
|
78
|
+
const possiblyRenderBlockingScriptUrls = Metric.getScriptUrls(graph, node => {
|
|
79
|
+
// The optimistic LCP treatNodeAsRenderBlocking fn wants to exclude some images in the graph,
|
|
80
|
+
// but here it only receives scripts to evaluate. It's a no-op in this case, but it will
|
|
81
|
+
// matter below in the getFirstPaintBasedGraph clone operation.
|
|
82
|
+
return node.endTime <= cutoffTimestamp && treatNodeAsRenderBlocking(node);
|
|
83
|
+
});
|
|
84
|
+
|
|
85
|
+
// A script is *definitely not* render blocking if its EvaluateScript task started after cutoffTimestamp.
|
|
86
|
+
/** @type {Set<string>} */
|
|
87
|
+
const definitelyNotRenderBlockingScriptUrls = new Set();
|
|
88
|
+
/** @type {Set<string>} */
|
|
89
|
+
const renderBlockingCpuNodeIds = new Set();
|
|
90
|
+
for (const url of possiblyRenderBlockingScriptUrls) {
|
|
91
|
+
// Lookup the CPU node that had the earliest EvaluateScript for this URL.
|
|
92
|
+
const cpuNodeForUrl = scriptUrlToNodeMap.get(url);
|
|
93
|
+
|
|
94
|
+
// If we can't find it at all, we can't conclude anything, so just skip it.
|
|
95
|
+
if (!cpuNodeForUrl) continue;
|
|
96
|
+
|
|
97
|
+
// If we found it and it was in our `cpuNodes` set that means it finished before cutoffTimestamp, so it really is render-blocking.
|
|
98
|
+
if (cpuNodes.includes(cpuNodeForUrl)) {
|
|
99
|
+
renderBlockingCpuNodeIds.add(cpuNodeForUrl.id);
|
|
100
|
+
continue;
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
// We couldn't find the evaluate script in the set of CPU nodes that ran before our paint, so
|
|
104
|
+
// it must not have been necessary for the paint.
|
|
105
|
+
definitelyNotRenderBlockingScriptUrls.add(url);
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
// The first layout, first paint, and first ParseHTML are almost always necessary for first paint,
|
|
109
|
+
// so we always include those CPU nodes.
|
|
110
|
+
const firstLayout = cpuNodes.find(node => node.didPerformLayout());
|
|
111
|
+
if (firstLayout) renderBlockingCpuNodeIds.add(firstLayout.id);
|
|
112
|
+
const firstPaint = cpuNodes.find(node => node.childEvents.some(e => e.name === 'Paint'));
|
|
113
|
+
if (firstPaint) renderBlockingCpuNodeIds.add(firstPaint.id);
|
|
114
|
+
const firstParse = cpuNodes.find(node => node.childEvents.some(e => e.name === 'ParseHTML'));
|
|
115
|
+
if (firstParse) renderBlockingCpuNodeIds.add(firstParse.id);
|
|
116
|
+
|
|
117
|
+
// If a CPU filter was passed in, we also want to include those extra nodes.
|
|
118
|
+
if (additionalCpuNodesToTreatAsRenderBlocking) {
|
|
119
|
+
cpuNodes
|
|
120
|
+
.filter(additionalCpuNodesToTreatAsRenderBlocking)
|
|
121
|
+
.forEach(node => renderBlockingCpuNodeIds.add(node.id));
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
return {
|
|
125
|
+
definitelyNotRenderBlockingScriptUrls,
|
|
126
|
+
renderBlockingCpuNodeIds,
|
|
127
|
+
};
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
/**
|
|
131
|
+
* This function computes the graph required for the first paint of interest.
|
|
132
|
+
*
|
|
133
|
+
* @template [T=unknown]
|
|
134
|
+
* @param {Node} dependencyGraph
|
|
135
|
+
* @param {FirstPaintBasedGraphOpts<T>} opts
|
|
136
|
+
* @return {Node}
|
|
137
|
+
*/
|
|
138
|
+
static getFirstPaintBasedGraph(
|
|
139
|
+
dependencyGraph,
|
|
140
|
+
{cutoffTimestamp, treatNodeAsRenderBlocking, additionalCpuNodesToTreatAsRenderBlocking}
|
|
141
|
+
) {
|
|
142
|
+
const rbData = this.getRenderBlockingNodeData(dependencyGraph, {
|
|
143
|
+
cutoffTimestamp,
|
|
144
|
+
treatNodeAsRenderBlocking,
|
|
145
|
+
additionalCpuNodesToTreatAsRenderBlocking,
|
|
146
|
+
});
|
|
147
|
+
const {definitelyNotRenderBlockingScriptUrls, renderBlockingCpuNodeIds} = rbData;
|
|
148
|
+
|
|
149
|
+
return dependencyGraph.cloneWithRelationships(node => {
|
|
150
|
+
if (node.type === BaseNode.TYPES.NETWORK) {
|
|
151
|
+
// Exclude all nodes that ended after cutoffTimestamp (except for the main document which we always consider necessary)
|
|
152
|
+
// endTime is negative if request does not finish, make sure startTime isn't after cutoffTimestamp in this case.
|
|
153
|
+
const endedAfterPaint = node.endTime > cutoffTimestamp || node.startTime > cutoffTimestamp;
|
|
154
|
+
if (endedAfterPaint && !node.isMainDocument()) return false;
|
|
155
|
+
|
|
156
|
+
const url = node.record.url;
|
|
157
|
+
// If the URL definitely wasn't render-blocking then we filter it out.
|
|
158
|
+
if (definitelyNotRenderBlockingScriptUrls.has(url)) {
|
|
159
|
+
return false;
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
// Lastly, build up the FCP graph of all nodes we consider render blocking
|
|
163
|
+
return treatNodeAsRenderBlocking(node);
|
|
164
|
+
} else {
|
|
165
|
+
// If it's a CPU node, just check if it was blocking.
|
|
166
|
+
return renderBlockingCpuNodeIds.has(node.id);
|
|
167
|
+
}
|
|
168
|
+
});
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
/**
|
|
172
|
+
* @param {Node} dependencyGraph
|
|
173
|
+
* @param {LH.Artifacts.ProcessedNavigation} processedNavigation
|
|
174
|
+
* @return {Node}
|
|
175
|
+
*/
|
|
176
|
+
static getOptimisticGraph(dependencyGraph, processedNavigation) {
|
|
177
|
+
return this.getFirstPaintBasedGraph(dependencyGraph, {
|
|
178
|
+
cutoffTimestamp: processedNavigation.timestamps.firstContentfulPaint,
|
|
179
|
+
// In the optimistic graph we exclude resources that appeared to be render blocking but were
|
|
180
|
+
// initiated by a script. While they typically have a very high importance and tend to have a
|
|
181
|
+
// significant impact on the page's content, these resources don't technically block rendering.
|
|
182
|
+
treatNodeAsRenderBlocking: node =>
|
|
183
|
+
node.hasRenderBlockingPriority() && node.initiatorType !== 'script',
|
|
184
|
+
});
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
/**
|
|
188
|
+
* @param {Node} dependencyGraph
|
|
189
|
+
* @param {LH.Artifacts.ProcessedNavigation} processedNavigation
|
|
190
|
+
* @return {Node}
|
|
191
|
+
*/
|
|
192
|
+
static getPessimisticGraph(dependencyGraph, processedNavigation) {
|
|
193
|
+
return this.getFirstPaintBasedGraph(dependencyGraph, {
|
|
194
|
+
cutoffTimestamp: processedNavigation.timestamps.firstContentfulPaint,
|
|
195
|
+
treatNodeAsRenderBlocking: node => node.hasRenderBlockingPriority(),
|
|
196
|
+
});
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
export {FirstContentfulPaint};
|