lighthouse 11.0.0 → 11.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/cli/bin.js +1 -1
- package/cli/cli-flags.js +1 -1
- package/cli/test/smokehouse/core-tests.js +3 -1
- package/cli/test/smokehouse/frontends/smokehouse-bin.js +1 -1
- package/cli/test/smokehouse/lighthouse-runners/bundle.js +1 -1
- package/cli/test/smokehouse/lighthouse-runners/cli.js +1 -1
- package/cli/test/smokehouse/lighthouse-runners/devtools.js +1 -1
- 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-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-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-active.js +1 -1
- package/core/audits/accessibility/duplicate-id-aria.js +2 -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 -1
- 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/bf-cache.js +1 -0
- package/core/audits/bootup-time.d.ts +6 -0
- package/core/audits/bootup-time.js +37 -1
- package/core/audits/byte-efficiency/duplicated-javascript.js +1 -0
- package/core/audits/byte-efficiency/efficient-animated-content.js +1 -0
- package/core/audits/byte-efficiency/legacy-javascript.js +2 -1
- package/core/audits/byte-efficiency/modern-image-formats.js +1 -0
- package/core/audits/byte-efficiency/offscreen-images.js +1 -0
- package/core/audits/byte-efficiency/render-blocking-resources.js +1 -0
- package/core/audits/byte-efficiency/total-byte-weight.js +1 -0
- package/core/audits/byte-efficiency/unminified-css.js +1 -0
- package/core/audits/byte-efficiency/unminified-javascript.js +1 -0
- package/core/audits/byte-efficiency/unused-css-rules.js +1 -0
- package/core/audits/byte-efficiency/unused-javascript.js +1 -0
- package/core/audits/byte-efficiency/uses-long-cache-ttl.js +1 -0
- package/core/audits/byte-efficiency/uses-optimized-images.js +1 -0
- package/core/audits/byte-efficiency/uses-responsive-images-snapshot.js +1 -0
- package/core/audits/byte-efficiency/uses-responsive-images.js +1 -0
- package/core/audits/byte-efficiency/uses-text-compression.js +1 -0
- package/core/audits/critical-request-chains.js +1 -0
- package/core/audits/dobetterweb/dom-size.d.ts +8 -2
- package/core/audits/dobetterweb/dom-size.js +51 -3
- package/core/audits/dobetterweb/no-document-write.js +1 -0
- package/core/audits/dobetterweb/uses-http2.js +1 -0
- package/core/audits/dobetterweb/uses-passive-event-listeners.js +1 -0
- package/core/audits/font-display.js +1 -0
- package/core/audits/image-aspect-ratio.js +10 -1
- package/core/audits/installable-manifest.d.ts +0 -6
- package/core/audits/installable-manifest.js +1 -17
- package/core/audits/largest-contentful-paint-element.js +22 -4
- package/core/audits/layout-shift-elements.js +1 -0
- package/core/audits/lcp-lazy-loaded.d.ts +3 -2
- package/core/audits/lcp-lazy-loaded.js +35 -5
- package/core/audits/long-tasks.js +1 -0
- package/core/audits/mainthread-work-breakdown.js +1 -0
- package/core/audits/non-composited-animations.js +1 -0
- package/core/audits/prioritize-lcp-image.js +1 -0
- package/core/audits/redirects.js +2 -4
- package/core/audits/seo/crawlable-anchors.js +2 -1
- package/core/audits/server-response-time.js +1 -0
- package/core/audits/third-party-facades.js +14 -5
- package/core/audits/third-party-summary.d.ts +4 -2
- package/core/audits/third-party-summary.js +25 -11
- package/core/audits/unsized-images.js +1 -0
- package/core/audits/user-timings.js +1 -0
- package/core/audits/uses-rel-preconnect.js +1 -0
- package/core/audits/uses-rel-preload.js +1 -0
- package/core/audits/viewport.js +1 -0
- package/core/audits/work-during-interaction.js +1 -0
- package/core/computed/main-resource.d.ts +1 -1
- package/core/computed/main-resource.js +10 -4
- package/core/computed/page-dependency-graph.js +2 -1
- package/core/computed/tbt-impact-tasks.d.ts +8 -13
- package/core/computed/tbt-impact-tasks.js +4 -6
- package/core/computed/viewport-meta.js +11 -1
- package/core/config/config-helpers.js +1 -1
- package/core/config/config.js +1 -1
- package/core/gather/gatherers/accessibility.js +5 -0
- package/core/gather/timespan-runner.d.ts +3 -0
- package/core/gather/timespan-runner.js +23 -0
- package/core/index.cjs +13 -13
- package/core/index.d.cts +15 -2
- package/core/lib/asset-saver.d.ts +0 -3
- package/core/lib/asset-saver.js +68 -42
- package/core/lib/dependency-graph/simulator/network-analyzer.d.ts +7 -1
- package/core/lib/dependency-graph/simulator/network-analyzer.js +17 -1
- package/core/lib/emulation.js +1 -1
- package/core/lib/i18n/i18n.js +2 -2
- package/core/runner.js +5 -4
- package/dist/report/bundle.esm.js +3 -1
- package/dist/report/flow.js +3 -1
- package/dist/report/standalone.js +3 -1
- package/flow-report/tsconfig.json +0 -2
- package/package.json +8 -7
- package/readme.md +4 -1
- package/report/assets/templates.html +2 -0
- package/report/generator/flow-report-assets.js +1 -1
- package/report/generator/report-assets.js +1 -1
- package/report/generator/tsconfig.json +1 -1
- package/report/renderer/components.js +1 -1
- package/report/renderer/i18n-formatter.js +1 -1
- package/shared/localization/format.js +1 -1
- package/shared/localization/locales/ar-XB.json +36 -39
- package/shared/localization/locales/ar.json +36 -39
- package/shared/localization/locales/bg.json +36 -39
- package/shared/localization/locales/ca.json +36 -39
- package/shared/localization/locales/cs.json +36 -39
- package/shared/localization/locales/da.json +36 -39
- package/shared/localization/locales/de.json +36 -39
- package/shared/localization/locales/el.json +36 -39
- package/shared/localization/locales/en-GB.json +7 -10
- package/shared/localization/locales/en-US.json +64 -64
- package/shared/localization/locales/en-XA.json +36 -39
- package/shared/localization/locales/en-XL.json +64 -64
- package/shared/localization/locales/es-419.json +36 -39
- package/shared/localization/locales/es.json +36 -39
- package/shared/localization/locales/fi.json +36 -39
- package/shared/localization/locales/fil.json +36 -39
- package/shared/localization/locales/fr.json +36 -39
- package/shared/localization/locales/he.json +36 -39
- package/shared/localization/locales/hi.json +36 -39
- package/shared/localization/locales/hr.json +36 -39
- package/shared/localization/locales/hu.json +36 -39
- package/shared/localization/locales/id.json +36 -39
- package/shared/localization/locales/it.json +36 -39
- package/shared/localization/locales/ja.json +36 -39
- package/shared/localization/locales/ko.json +36 -39
- package/shared/localization/locales/lt.json +36 -39
- package/shared/localization/locales/lv.json +36 -39
- package/shared/localization/locales/nl.json +36 -39
- package/shared/localization/locales/no.json +36 -39
- package/shared/localization/locales/pl.json +36 -39
- package/shared/localization/locales/pt-PT.json +36 -39
- package/shared/localization/locales/pt.json +36 -39
- package/shared/localization/locales/ro.json +36 -39
- package/shared/localization/locales/ru.json +36 -39
- package/shared/localization/locales/sk.json +36 -39
- package/shared/localization/locales/sl.json +36 -39
- package/shared/localization/locales/sr-Latn.json +36 -39
- package/shared/localization/locales/sr.json +36 -39
- package/shared/localization/locales/sv.json +36 -39
- package/shared/localization/locales/ta.json +36 -39
- package/shared/localization/locales/te.json +36 -39
- package/shared/localization/locales/th.json +36 -39
- package/shared/localization/locales/tr.json +36 -39
- package/shared/localization/locales/uk.json +36 -39
- package/shared/localization/locales/vi.json +36 -39
- package/shared/localization/locales/zh-HK.json +36 -39
- package/shared/localization/locales/zh-TW.json +36 -39
- package/shared/localization/locales/zh.json +36 -39
- package/shared/localization/locales.js +1 -1
- package/{root.js → shared/root.js} +2 -1
- package/shared/tsconfig.json +0 -1
- package/third-party/chromium-synchronization/inspector-issueAdded-types-test.js +1 -1
- package/third-party/chromium-synchronization/installability-errors-test.js +0 -1
- package/third-party/download-content-shell/download-content-shell.js +1 -1
- package/tsconfig.json +0 -2
- package/types/artifacts.d.ts +2 -1
- package/types/audit.d.ts +2 -0
- /package/{esm-utils.d.ts → shared/esm-utils.d.ts} +0 -0
- /package/{esm-utils.js → shared/esm-utils.js} +0 -0
- /package/{root.d.ts → shared/root.d.ts} +0 -0
|
@@ -87,17 +87,12 @@ const UIStrings = {
|
|
|
87
87
|
/** Error message explaining that the web manifest's URL changed while the manifest was being downloaded by the browser. */
|
|
88
88
|
'manifest-location-changed': `Manifest URL changed while the manifest was being fetched.`,
|
|
89
89
|
/** Warning message explaining that the page does not work offline. */
|
|
90
|
+
// TODO(COMPAT): This error was removed in M118, we can remove this message when it hits stable.
|
|
90
91
|
'warn-not-offline-capable': `Page does not work offline. The page will not be regarded as installable after Chrome 93, stable release August 2021.`,
|
|
91
92
|
/** Error message explaining that Lighthouse failed while checking if the page is installable, and directing the user to try again in a new Chrome. */
|
|
92
93
|
'protocol-timeout': `Lighthouse could not determine if the page is installable. Please try with a newer version of Chrome.`,
|
|
93
94
|
/** Message logged when the web app has been uninstalled o desktop, signalling that the install banner state is being reset. */
|
|
94
95
|
'pipeline-restarted': 'PWA has been uninstalled and installability checks resetting.',
|
|
95
|
-
/**
|
|
96
|
-
* TODO: This error was removed in M114, we can remove this message when it hits stable.
|
|
97
|
-
* @description Error message explaining that the URL of the manifest uses a scheme that is not supported on Android.
|
|
98
|
-
* @example {data:} scheme
|
|
99
|
-
*/
|
|
100
|
-
'scheme-not-supported-for-webapk': 'The manifest URL scheme ({scheme}) is not supported on Android.',
|
|
101
96
|
};
|
|
102
97
|
/* eslint-enable max-len */
|
|
103
98
|
|
|
@@ -157,17 +152,6 @@ class InstallableManifest extends Audit {
|
|
|
157
152
|
// @ts-expect-error errorIds from protocol should match up against the strings dict
|
|
158
153
|
const matchingString = UIStrings[err.errorId];
|
|
159
154
|
|
|
160
|
-
if (err.errorId === 'scheme-not-supported-for-webapk') {
|
|
161
|
-
// If there was no manifest, then there will be at lest one other installability error.
|
|
162
|
-
// We can ignore this error if that's the case.
|
|
163
|
-
const manifestUrl = artifacts.WebAppManifest?.url;
|
|
164
|
-
if (!manifestUrl) continue;
|
|
165
|
-
|
|
166
|
-
const scheme = new URL(manifestUrl).protocol;
|
|
167
|
-
i18nErrors.push(str_(matchingString, {scheme}));
|
|
168
|
-
continue;
|
|
169
|
-
}
|
|
170
|
-
|
|
171
155
|
// Handle an errorId we don't recognize.
|
|
172
156
|
if (matchingString === undefined) {
|
|
173
157
|
i18nErrors.push(str_(UIStrings.noErrorId, {errorId: err.errorId}));
|
|
@@ -8,7 +8,8 @@ import log from 'lighthouse-logger';
|
|
|
8
8
|
|
|
9
9
|
import {Audit} from './audit.js';
|
|
10
10
|
import * as i18n from '../lib/i18n/i18n.js';
|
|
11
|
-
import {LargestContentfulPaint} from '../computed/metrics/largest-contentful-paint.js';
|
|
11
|
+
import {LargestContentfulPaint as LargestContentfulPaintComputed} from '../computed/metrics/largest-contentful-paint.js';
|
|
12
|
+
import LargestContentfulPaint from './metrics/largest-contentful-paint.js';
|
|
12
13
|
import {LCPBreakdown} from '../computed/metrics/lcp-breakdown.js';
|
|
13
14
|
import {Sentry} from '../lib/sentry.js';
|
|
14
15
|
|
|
@@ -46,6 +47,7 @@ class LargestContentfulPaintElement extends Audit {
|
|
|
46
47
|
title: str_(UIStrings.title),
|
|
47
48
|
description: str_(UIStrings.description),
|
|
48
49
|
scoreDisplayMode: Audit.SCORING_MODES.INFORMATIVE,
|
|
50
|
+
guidanceLevel: 1,
|
|
49
51
|
supportedModes: ['navigation'],
|
|
50
52
|
requiredArtifacts:
|
|
51
53
|
['traces', 'TraceElements', 'devtoolsLogs', 'GatherContext', 'settings', 'URL'],
|
|
@@ -124,14 +126,22 @@ class LargestContentfulPaintElement extends Audit {
|
|
|
124
126
|
settings: context.settings, URL: artifacts.URL};
|
|
125
127
|
|
|
126
128
|
const elementTable = this.makeElementTable(artifacts);
|
|
127
|
-
if (!elementTable)
|
|
129
|
+
if (!elementTable) {
|
|
130
|
+
return {
|
|
131
|
+
score: null,
|
|
132
|
+
notApplicable: true,
|
|
133
|
+
metricSavings: {LCP: 0},
|
|
134
|
+
};
|
|
135
|
+
}
|
|
128
136
|
|
|
129
137
|
const items = [elementTable];
|
|
130
138
|
let displayValue;
|
|
139
|
+
let metricLcp = 0;
|
|
131
140
|
|
|
132
141
|
try {
|
|
133
|
-
const
|
|
134
|
-
await
|
|
142
|
+
const lcpResult =
|
|
143
|
+
await LargestContentfulPaintComputed.request(metricComputationData, context);
|
|
144
|
+
metricLcp = lcpResult.timing;
|
|
135
145
|
displayValue = str_(i18n.UIStrings.ms, {timeInMs: metricLcp});
|
|
136
146
|
|
|
137
147
|
const phaseTable = await this.makePhaseTable(metricLcp, metricComputationData, context);
|
|
@@ -146,10 +156,18 @@ class LargestContentfulPaintElement extends Audit {
|
|
|
146
156
|
|
|
147
157
|
const details = Audit.makeListDetails(items);
|
|
148
158
|
|
|
159
|
+
// Conceptually, this doesn't make much sense as "savings" for this audit since there isn't anything to "fix".
|
|
160
|
+
// However, this audit will always be useful when improving LCP and that should be reflected in our impact calculations.
|
|
161
|
+
const idealLcp = LargestContentfulPaint.defaultOptions[context.settings.formFactor].scoring.p10;
|
|
162
|
+
const lcpSavings = Math.max(0, metricLcp - idealLcp);
|
|
163
|
+
|
|
149
164
|
return {
|
|
150
165
|
score: 1,
|
|
151
166
|
displayValue,
|
|
152
167
|
details,
|
|
168
|
+
metricSavings: {
|
|
169
|
+
LCP: lcpSavings,
|
|
170
|
+
},
|
|
153
171
|
};
|
|
154
172
|
}
|
|
155
173
|
}
|
|
@@ -8,9 +8,10 @@ declare class LargestContentfulPaintLazyLoaded extends Audit {
|
|
|
8
8
|
static isImageInViewport(image: LH.Artifacts.ImageElement, viewportDimensions: LH.Artifacts.ViewportDimensions): boolean;
|
|
9
9
|
/**
|
|
10
10
|
* @param {LH.Artifacts} artifacts
|
|
11
|
-
* @
|
|
11
|
+
* @param {LH.Audit.Context} context
|
|
12
|
+
* @return {Promise<LH.Audit.Product>}
|
|
12
13
|
*/
|
|
13
|
-
static audit(artifacts: LH.Artifacts): LH.Audit.Product
|
|
14
|
+
static audit(artifacts: LH.Artifacts, context: LH.Audit.Context): Promise<LH.Audit.Product>;
|
|
14
15
|
}
|
|
15
16
|
export namespace UIStrings {
|
|
16
17
|
const title: string;
|
|
@@ -6,6 +6,8 @@
|
|
|
6
6
|
|
|
7
7
|
import {Audit} from './audit.js';
|
|
8
8
|
import * as i18n from '../lib/i18n/i18n.js';
|
|
9
|
+
import {LCPBreakdown} from '../computed/metrics/lcp-breakdown.js';
|
|
10
|
+
import {LargestContentfulPaint} from '../computed/metrics/largest-contentful-paint.js';
|
|
9
11
|
|
|
10
12
|
const UIStrings = {
|
|
11
13
|
/** Title of a Lighthouse audit that provides detail on whether the largest above-the-fold image was loaded with sufficient priority. This descriptive title is shown to users when the image was loaded properly. */
|
|
@@ -18,6 +20,8 @@ const UIStrings = {
|
|
|
18
20
|
|
|
19
21
|
const str_ = i18n.createIcuMessageFn(import.meta.url, UIStrings);
|
|
20
22
|
|
|
23
|
+
const ESTIMATED_PERCENT_SAVINGS = 0.15;
|
|
24
|
+
|
|
21
25
|
class LargestContentfulPaintLazyLoaded extends Audit {
|
|
22
26
|
/**
|
|
23
27
|
* @return {LH.Audit.Meta}
|
|
@@ -29,7 +33,9 @@ class LargestContentfulPaintLazyLoaded extends Audit {
|
|
|
29
33
|
failureTitle: str_(UIStrings.failureTitle),
|
|
30
34
|
description: str_(UIStrings.description),
|
|
31
35
|
supportedModes: ['navigation'],
|
|
32
|
-
|
|
36
|
+
guidanceLevel: 3,
|
|
37
|
+
requiredArtifacts: ['TraceElements', 'ViewportDimensions', 'ImageElements',
|
|
38
|
+
'traces', 'devtoolsLogs', 'GatherContext', 'URL'],
|
|
33
39
|
};
|
|
34
40
|
}
|
|
35
41
|
|
|
@@ -46,9 +52,10 @@ class LargestContentfulPaintLazyLoaded extends Audit {
|
|
|
46
52
|
|
|
47
53
|
/**
|
|
48
54
|
* @param {LH.Artifacts} artifacts
|
|
49
|
-
* @
|
|
55
|
+
* @param {LH.Audit.Context} context
|
|
56
|
+
* @return {Promise<LH.Audit.Product>}
|
|
50
57
|
*/
|
|
51
|
-
static audit(artifacts) {
|
|
58
|
+
static async audit(artifacts, context) {
|
|
52
59
|
const lcpElement = artifacts.TraceElements.find(element => {
|
|
53
60
|
return element.traceEventType === 'largest-contentful-paint' && element.type === 'image';
|
|
54
61
|
});
|
|
@@ -59,7 +66,11 @@ class LargestContentfulPaintLazyLoaded extends Audit {
|
|
|
59
66
|
|
|
60
67
|
if (!lcpElementImage ||
|
|
61
68
|
!this.isImageInViewport(lcpElementImage, artifacts.ViewportDimensions)) {
|
|
62
|
-
return {
|
|
69
|
+
return {
|
|
70
|
+
score: null,
|
|
71
|
+
notApplicable: true,
|
|
72
|
+
metricSavings: {LCP: 0},
|
|
73
|
+
};
|
|
63
74
|
}
|
|
64
75
|
|
|
65
76
|
/** @type {LH.Audit.Details.Table['headings']} */
|
|
@@ -73,8 +84,27 @@ class LargestContentfulPaintLazyLoaded extends Audit {
|
|
|
73
84
|
},
|
|
74
85
|
]);
|
|
75
86
|
|
|
87
|
+
const wasLazyLoaded = lcpElementImage.loading === 'lazy';
|
|
88
|
+
|
|
89
|
+
const metricComputationData = Audit.makeMetricComputationDataInput(artifacts, context);
|
|
90
|
+
const {timing: metricLcp} =
|
|
91
|
+
await LargestContentfulPaint.request(metricComputationData, context);
|
|
92
|
+
const lcpBreakdown = await LCPBreakdown.request(metricComputationData, context);
|
|
93
|
+
let lcpSavings = 0;
|
|
94
|
+
if (wasLazyLoaded && lcpBreakdown.loadStart !== undefined) {
|
|
95
|
+
// Estimate the LCP savings using a statistical percentage.
|
|
96
|
+
// https://web.dev/lcp-lazy-loading/#causal-performance
|
|
97
|
+
//
|
|
98
|
+
// LCP savings will be at most the LCP load delay.
|
|
99
|
+
const lcpLoadDelay = lcpBreakdown.loadStart - lcpBreakdown.ttfb;
|
|
100
|
+
lcpSavings = Math.min(metricLcp * ESTIMATED_PERCENT_SAVINGS, lcpLoadDelay);
|
|
101
|
+
}
|
|
102
|
+
|
|
76
103
|
return {
|
|
77
|
-
score:
|
|
104
|
+
score: wasLazyLoaded ? 0 : 1,
|
|
105
|
+
metricSavings: {
|
|
106
|
+
LCP: lcpSavings,
|
|
107
|
+
},
|
|
78
108
|
details,
|
|
79
109
|
};
|
|
80
110
|
}
|
|
@@ -110,6 +110,7 @@ class NonCompositedAnimations extends Audit {
|
|
|
110
110
|
scoreDisplayMode: Audit.SCORING_MODES.INFORMATIVE,
|
|
111
111
|
title: str_(UIStrings.title),
|
|
112
112
|
description: str_(UIStrings.description),
|
|
113
|
+
guidanceLevel: 2,
|
|
113
114
|
requiredArtifacts: ['TraceElements', 'HostUserAgent'],
|
|
114
115
|
};
|
|
115
116
|
}
|
|
@@ -38,6 +38,7 @@ class PrioritizeLcpImage extends Audit {
|
|
|
38
38
|
title: str_(UIStrings.title),
|
|
39
39
|
description: str_(UIStrings.description),
|
|
40
40
|
supportedModes: ['navigation'],
|
|
41
|
+
guidanceLevel: 3,
|
|
41
42
|
requiredArtifacts: ['traces', 'devtoolsLogs', 'GatherContext', 'URL', 'TraceElements'],
|
|
42
43
|
scoreDisplayMode: Audit.SCORING_MODES.NUMERIC,
|
|
43
44
|
};
|
package/core/audits/redirects.js
CHANGED
|
@@ -31,6 +31,7 @@ class Redirects extends Audit {
|
|
|
31
31
|
description: str_(UIStrings.description),
|
|
32
32
|
scoreDisplayMode: Audit.SCORING_MODES.NUMERIC,
|
|
33
33
|
supportedModes: ['navigation'],
|
|
34
|
+
guidanceLevel: 2,
|
|
34
35
|
requiredArtifacts: ['URL', 'GatherContext', 'devtoolsLogs', 'traces'],
|
|
35
36
|
};
|
|
36
37
|
}
|
|
@@ -144,10 +145,7 @@ class Redirects extends Audit {
|
|
|
144
145
|
{overallSavingsMs: totalWastedMs});
|
|
145
146
|
|
|
146
147
|
return {
|
|
147
|
-
|
|
148
|
-
// TODO(phulce): reconsider if cases like the example in https://github.com/GoogleChrome/lighthouse/issues/8984
|
|
149
|
-
// should fail this audit.
|
|
150
|
-
score: documentRequests.length <= 2 ? 1 : ByteEfficiencyAudit.scoreForWastedMs(totalWastedMs),
|
|
148
|
+
score: ByteEfficiencyAudit.scoreForWastedMs(totalWastedMs),
|
|
151
149
|
numericValue: totalWastedMs,
|
|
152
150
|
numericUnit: 'millisecond',
|
|
153
151
|
displayValue: totalWastedMs ?
|
|
@@ -44,6 +44,7 @@ class CrawlableAnchors extends Audit {
|
|
|
44
44
|
name = '',
|
|
45
45
|
role = '',
|
|
46
46
|
id,
|
|
47
|
+
href,
|
|
47
48
|
}) => {
|
|
48
49
|
rawHref = rawHref.replace( /\s/g, '');
|
|
49
50
|
name = name.trim();
|
|
@@ -61,7 +62,7 @@ class CrawlableAnchors extends Audit {
|
|
|
61
62
|
if (rawHref.startsWith('file:')) return true;
|
|
62
63
|
if (name.length > 0) return;
|
|
63
64
|
|
|
64
|
-
if (
|
|
65
|
+
if (href === '') return true;
|
|
65
66
|
if (javaScriptVoidRegExp.test(rawHref)) return true;
|
|
66
67
|
|
|
67
68
|
// checking if rawHref is a valid
|
|
@@ -37,6 +37,7 @@ class ServerResponseTime extends Audit {
|
|
|
37
37
|
failureTitle: str_(UIStrings.failureTitle),
|
|
38
38
|
description: str_(UIStrings.description),
|
|
39
39
|
supportedModes: ['navigation'],
|
|
40
|
+
guidanceLevel: 1,
|
|
40
41
|
requiredArtifacts: ['devtoolsLogs', 'URL', 'GatherContext'],
|
|
41
42
|
};
|
|
42
43
|
}
|
|
@@ -24,8 +24,8 @@ import * as i18n from '../lib/i18n/i18n.js';
|
|
|
24
24
|
import {EntityClassification} from '../computed/entity-classification.js';
|
|
25
25
|
import thirdPartyWeb from '../lib/third-party-web.js';
|
|
26
26
|
import {NetworkRecords} from '../computed/network-records.js';
|
|
27
|
-
import {MainThreadTasks} from '../computed/main-thread-tasks.js';
|
|
28
27
|
import ThirdPartySummary from './third-party-summary.js';
|
|
28
|
+
import {TBTImpactTasks} from '../computed/tbt-impact-tasks.js';
|
|
29
29
|
|
|
30
30
|
const UIStrings = {
|
|
31
31
|
/** Title of a diagnostic audit that provides details about the third-party code on a web page that can be lazy loaded with a facade alternative. This descriptive title is shown to users when no resources have facade alternatives available. A facade is a lightweight component which looks like the desired resource. Lazy loading means resources are deferred until they are needed. Third-party code refers to resources that are not within the control of the site owner. */
|
|
@@ -86,7 +86,8 @@ class ThirdPartyFacades extends Audit {
|
|
|
86
86
|
failureTitle: str_(UIStrings.failureTitle),
|
|
87
87
|
description: str_(UIStrings.description),
|
|
88
88
|
supportedModes: ['navigation'],
|
|
89
|
-
|
|
89
|
+
guidanceLevel: 3,
|
|
90
|
+
requiredArtifacts: ['traces', 'devtoolsLogs', 'URL', 'GatherContext'],
|
|
90
91
|
};
|
|
91
92
|
}
|
|
92
93
|
|
|
@@ -148,19 +149,23 @@ class ThirdPartyFacades extends Audit {
|
|
|
148
149
|
*/
|
|
149
150
|
static async audit(artifacts, context) {
|
|
150
151
|
const settings = context.settings;
|
|
151
|
-
const trace = artifacts.traces[Audit.DEFAULT_PASS];
|
|
152
152
|
const devtoolsLog = artifacts.devtoolsLogs[Audit.DEFAULT_PASS];
|
|
153
153
|
const networkRecords = await NetworkRecords.request(devtoolsLog, context);
|
|
154
154
|
const classifiedEntities = await EntityClassification.request(
|
|
155
155
|
{URL: artifacts.URL, devtoolsLog}, context);
|
|
156
|
-
|
|
156
|
+
|
|
157
|
+
const metricComputationData = Audit.makeMetricComputationDataInput(artifacts, context);
|
|
158
|
+
const tbtImpactTasks = await TBTImpactTasks.request(metricComputationData, context);
|
|
159
|
+
|
|
157
160
|
const multiplier = settings.throttlingMethod === 'simulate' ?
|
|
158
161
|
settings.throttling.cpuSlowdownMultiplier : 1;
|
|
159
|
-
const summaries = ThirdPartySummary.getSummaries(networkRecords,
|
|
162
|
+
const summaries = ThirdPartySummary.getSummaries(networkRecords, tbtImpactTasks, multiplier,
|
|
160
163
|
classifiedEntities);
|
|
161
164
|
const facadableProducts =
|
|
162
165
|
ThirdPartyFacades.getProductsWithFacade(summaries.byURL, classifiedEntities);
|
|
163
166
|
|
|
167
|
+
let tbtImpact = 0;
|
|
168
|
+
|
|
164
169
|
/** @type {LH.Audit.Details.TableItem[]} */
|
|
165
170
|
const results = [];
|
|
166
171
|
for (const {product, entity} of facadableProducts) {
|
|
@@ -179,6 +184,8 @@ class ThirdPartyFacades extends Audit {
|
|
|
179
184
|
const entitySummary = summaries.byEntity.get(entity);
|
|
180
185
|
if (!urls || !entitySummary) continue;
|
|
181
186
|
|
|
187
|
+
tbtImpact += entitySummary.tbtImpact;
|
|
188
|
+
|
|
182
189
|
const items = Array.from(urls).map((url) => {
|
|
183
190
|
const urlStats = summaries.byURL.get(url);
|
|
184
191
|
return /** @type {import('./third-party-summary.js').URLSummary} */ ({url, ...urlStats});
|
|
@@ -198,6 +205,7 @@ class ThirdPartyFacades extends Audit {
|
|
|
198
205
|
return {
|
|
199
206
|
score: 1,
|
|
200
207
|
notApplicable: true,
|
|
208
|
+
metricSavings: {TBT: 0},
|
|
201
209
|
};
|
|
202
210
|
}
|
|
203
211
|
|
|
@@ -216,6 +224,7 @@ class ThirdPartyFacades extends Audit {
|
|
|
216
224
|
itemCount: results.length,
|
|
217
225
|
}),
|
|
218
226
|
details: Audit.makeTableDetails(headings, results),
|
|
227
|
+
metricSavings: {TBT: tbtImpact},
|
|
219
228
|
};
|
|
220
229
|
}
|
|
221
230
|
}
|
|
@@ -3,10 +3,12 @@ export type Summary = {
|
|
|
3
3
|
mainThreadTime: number;
|
|
4
4
|
transferSize: number;
|
|
5
5
|
blockingTime: number;
|
|
6
|
+
tbtImpact: number;
|
|
6
7
|
};
|
|
7
8
|
export type URLSummary = {
|
|
8
9
|
transferSize: number;
|
|
9
10
|
blockingTime: number;
|
|
11
|
+
tbtImpact: number;
|
|
10
12
|
url: string | LH.IcuMessage;
|
|
11
13
|
};
|
|
12
14
|
export type SummaryMaps = {
|
|
@@ -27,12 +29,12 @@ declare class ThirdPartySummary extends Audit {
|
|
|
27
29
|
/**
|
|
28
30
|
*
|
|
29
31
|
* @param {Array<LH.Artifacts.NetworkRequest>} networkRecords
|
|
30
|
-
* @param {Array<LH.Artifacts.
|
|
32
|
+
* @param {Array<LH.Artifacts.TBTImpactTask>} tbtImpactTasks
|
|
31
33
|
* @param {number} cpuMultiplier
|
|
32
34
|
* @param {LH.Artifacts.EntityClassification} entityClassification
|
|
33
35
|
* @return {SummaryMaps}
|
|
34
36
|
*/
|
|
35
|
-
static getSummaries(networkRecords: Array<LH.Artifacts.NetworkRequest>,
|
|
37
|
+
static getSummaries(networkRecords: Array<LH.Artifacts.NetworkRequest>, tbtImpactTasks: Array<LH.Artifacts.TBTImpactTask>, cpuMultiplier: number, entityClassification: LH.Artifacts.EntityClassification): SummaryMaps;
|
|
36
38
|
/**
|
|
37
39
|
* @param {LH.Artifacts.Entity} entity
|
|
38
40
|
* @param {SummaryMaps} summaries
|
|
@@ -8,8 +8,8 @@ import {Audit} from './audit.js';
|
|
|
8
8
|
import {EntityClassification} from '../computed/entity-classification.js';
|
|
9
9
|
import * as i18n from '../lib/i18n/i18n.js';
|
|
10
10
|
import {NetworkRecords} from '../computed/network-records.js';
|
|
11
|
-
import {MainThreadTasks} from '../computed/main-thread-tasks.js';
|
|
12
11
|
import {getJavaScriptURLs, getAttributableURLForTask} from '../lib/tracehouse/task-summary.js';
|
|
12
|
+
import {TBTImpactTasks} from '../computed/tbt-impact-tasks.js';
|
|
13
13
|
|
|
14
14
|
const UIStrings = {
|
|
15
15
|
/** Title of a diagnostic audit that provides details about the code on a web page that the user doesn't control (referred to as "third-party code"). This descriptive title is shown to users when the amount is acceptable and no user action is required. */
|
|
@@ -38,12 +38,14 @@ const PASS_THRESHOLD_IN_MS = 250;
|
|
|
38
38
|
* @property {number} mainThreadTime
|
|
39
39
|
* @property {number} transferSize
|
|
40
40
|
* @property {number} blockingTime
|
|
41
|
+
* @property {number} tbtImpact
|
|
41
42
|
*/
|
|
42
43
|
|
|
43
44
|
/**
|
|
44
45
|
* @typedef URLSummary
|
|
45
46
|
* @property {number} transferSize
|
|
46
47
|
* @property {number} blockingTime
|
|
48
|
+
* @property {number} tbtImpact
|
|
47
49
|
* @property {string | LH.IcuMessage} url
|
|
48
50
|
*/
|
|
49
51
|
|
|
@@ -72,24 +74,25 @@ class ThirdPartySummary extends Audit {
|
|
|
72
74
|
title: str_(UIStrings.title),
|
|
73
75
|
failureTitle: str_(UIStrings.failureTitle),
|
|
74
76
|
description: str_(UIStrings.description),
|
|
75
|
-
|
|
77
|
+
guidanceLevel: 1,
|
|
78
|
+
requiredArtifacts: ['traces', 'devtoolsLogs', 'URL', 'GatherContext'],
|
|
76
79
|
};
|
|
77
80
|
}
|
|
78
81
|
|
|
79
82
|
/**
|
|
80
83
|
*
|
|
81
84
|
* @param {Array<LH.Artifacts.NetworkRequest>} networkRecords
|
|
82
|
-
* @param {Array<LH.Artifacts.
|
|
85
|
+
* @param {Array<LH.Artifacts.TBTImpactTask>} tbtImpactTasks
|
|
83
86
|
* @param {number} cpuMultiplier
|
|
84
87
|
* @param {LH.Artifacts.EntityClassification} entityClassification
|
|
85
88
|
* @return {SummaryMaps}
|
|
86
89
|
*/
|
|
87
|
-
static getSummaries(networkRecords,
|
|
90
|
+
static getSummaries(networkRecords, tbtImpactTasks, cpuMultiplier, entityClassification) {
|
|
88
91
|
/** @type {Map<string, Summary>} */
|
|
89
92
|
const byURL = new Map();
|
|
90
93
|
/** @type {Map<LH.Artifacts.Entity, Summary>} */
|
|
91
94
|
const byEntity = new Map();
|
|
92
|
-
const defaultSummary = {mainThreadTime: 0, blockingTime: 0, transferSize: 0};
|
|
95
|
+
const defaultSummary = {mainThreadTime: 0, blockingTime: 0, transferSize: 0, tbtImpact: 0};
|
|
93
96
|
|
|
94
97
|
for (const request of networkRecords) {
|
|
95
98
|
const urlSummary = byURL.get(request.url) || {...defaultSummary};
|
|
@@ -99,7 +102,7 @@ class ThirdPartySummary extends Audit {
|
|
|
99
102
|
|
|
100
103
|
const jsURLs = getJavaScriptURLs(networkRecords);
|
|
101
104
|
|
|
102
|
-
for (const task of
|
|
105
|
+
for (const task of tbtImpactTasks) {
|
|
103
106
|
const attributableURL = getAttributableURLForTask(task, jsURLs);
|
|
104
107
|
|
|
105
108
|
const urlSummary = byURL.get(attributableURL) || {...defaultSummary};
|
|
@@ -110,6 +113,7 @@ class ThirdPartySummary extends Audit {
|
|
|
110
113
|
// Note that this is not totally equivalent to the TBT definition since it fails to account for FCP,
|
|
111
114
|
// but a majority of third-party work occurs after FCP and should yield largely similar numbers.
|
|
112
115
|
urlSummary.blockingTime += Math.max(taskDuration - 50, 0);
|
|
116
|
+
urlSummary.tbtImpact += task.selfTbtImpact;
|
|
113
117
|
byURL.set(attributableURL, urlSummary);
|
|
114
118
|
}
|
|
115
119
|
|
|
@@ -127,6 +131,7 @@ class ThirdPartySummary extends Audit {
|
|
|
127
131
|
entitySummary.transferSize += urlSummary.transferSize;
|
|
128
132
|
entitySummary.mainThreadTime += urlSummary.mainThreadTime;
|
|
129
133
|
entitySummary.blockingTime += urlSummary.blockingTime;
|
|
134
|
+
entitySummary.tbtImpact += urlSummary.tbtImpact;
|
|
130
135
|
byEntity.set(entity, entitySummary);
|
|
131
136
|
|
|
132
137
|
const entityURLs = urls.get(entity) || [];
|
|
@@ -152,7 +157,7 @@ class ThirdPartySummary extends Audit {
|
|
|
152
157
|
// Sort by blocking time first, then transfer size to break ties.
|
|
153
158
|
.sort((a, b) => (b.blockingTime - a.blockingTime) || (b.transferSize - a.transferSize));
|
|
154
159
|
|
|
155
|
-
const subitemSummary = {transferSize: 0, blockingTime: 0};
|
|
160
|
+
const subitemSummary = {transferSize: 0, blockingTime: 0, tbtImpact: 0};
|
|
156
161
|
const minTransferSize = Math.max(MIN_TRANSFER_SIZE_FOR_SUBITEMS, stats.transferSize / 20);
|
|
157
162
|
const maxSubItems = Math.min(MAX_SUBITEMS, items.length);
|
|
158
163
|
let numSubItems = 0;
|
|
@@ -167,6 +172,7 @@ class ThirdPartySummary extends Audit {
|
|
|
167
172
|
numSubItems++;
|
|
168
173
|
subitemSummary.transferSize += nextSubItem.transferSize;
|
|
169
174
|
subitemSummary.blockingTime += nextSubItem.blockingTime;
|
|
175
|
+
subitemSummary.tbtImpact += nextSubItem.tbtImpact;
|
|
170
176
|
}
|
|
171
177
|
if (!subitemSummary.blockingTime && !subitemSummary.transferSize) {
|
|
172
178
|
// Don't bother breaking down if there are no large resources.
|
|
@@ -179,6 +185,7 @@ class ThirdPartySummary extends Audit {
|
|
|
179
185
|
url: str_(i18n.UIStrings.otherResourcesLabel),
|
|
180
186
|
transferSize: stats.transferSize - subitemSummary.transferSize,
|
|
181
187
|
blockingTime: stats.blockingTime - subitemSummary.blockingTime,
|
|
188
|
+
tbtImpact: stats.tbtImpact - subitemSummary.tbtImpact,
|
|
182
189
|
};
|
|
183
190
|
if (remainder.transferSize > minTransferSize) {
|
|
184
191
|
items.push(remainder);
|
|
@@ -193,19 +200,21 @@ class ThirdPartySummary extends Audit {
|
|
|
193
200
|
*/
|
|
194
201
|
static async audit(artifacts, context) {
|
|
195
202
|
const settings = context.settings || {};
|
|
196
|
-
const trace = artifacts.traces[Audit.DEFAULT_PASS];
|
|
197
203
|
const devtoolsLog = artifacts.devtoolsLogs[Audit.DEFAULT_PASS];
|
|
198
204
|
const networkRecords = await NetworkRecords.request(devtoolsLog, context);
|
|
199
205
|
const classifiedEntities = await EntityClassification.request(
|
|
200
206
|
{URL: artifacts.URL, devtoolsLog}, context);
|
|
201
207
|
const firstPartyEntity = classifiedEntities.firstParty;
|
|
202
|
-
|
|
208
|
+
|
|
209
|
+
const metricComputationData = Audit.makeMetricComputationDataInput(artifacts, context);
|
|
210
|
+
const tbtImpactTasks = await TBTImpactTasks.request(metricComputationData, context);
|
|
211
|
+
|
|
203
212
|
const multiplier = settings.throttlingMethod === 'simulate' ?
|
|
204
213
|
settings.throttling.cpuSlowdownMultiplier : 1;
|
|
205
214
|
|
|
206
215
|
const summaries = ThirdPartySummary.getSummaries(
|
|
207
|
-
networkRecords,
|
|
208
|
-
const overallSummary = {wastedBytes: 0, wastedMs: 0};
|
|
216
|
+
networkRecords, tbtImpactTasks, multiplier, classifiedEntities);
|
|
217
|
+
const overallSummary = {wastedBytes: 0, wastedMs: 0, tbtImpact: 0};
|
|
209
218
|
|
|
210
219
|
const results = Array.from(summaries.byEntity.entries())
|
|
211
220
|
// Don't consider the page we're on to be third-party.
|
|
@@ -214,6 +223,7 @@ class ThirdPartySummary extends Audit {
|
|
|
214
223
|
.map(([entity, stats]) => {
|
|
215
224
|
overallSummary.wastedBytes += stats.transferSize;
|
|
216
225
|
overallSummary.wastedMs += stats.blockingTime;
|
|
226
|
+
overallSummary.tbtImpact += stats.tbtImpact;
|
|
217
227
|
|
|
218
228
|
return {
|
|
219
229
|
...stats,
|
|
@@ -240,6 +250,7 @@ class ThirdPartySummary extends Audit {
|
|
|
240
250
|
return {
|
|
241
251
|
score: 1,
|
|
242
252
|
notApplicable: true,
|
|
253
|
+
metricSavings: {TBT: 0},
|
|
243
254
|
};
|
|
244
255
|
}
|
|
245
256
|
|
|
@@ -252,6 +263,9 @@ class ThirdPartySummary extends Audit {
|
|
|
252
263
|
timeInMs: overallSummary.wastedMs,
|
|
253
264
|
}),
|
|
254
265
|
details,
|
|
266
|
+
metricSavings: {
|
|
267
|
+
TBT: overallSummary.tbtImpact,
|
|
268
|
+
},
|
|
255
269
|
};
|
|
256
270
|
}
|
|
257
271
|
}
|
|
@@ -62,6 +62,7 @@ class UsesRelPreconnectAudit extends Audit {
|
|
|
62
62
|
title: str_(UIStrings.title),
|
|
63
63
|
description: str_(UIStrings.description),
|
|
64
64
|
supportedModes: ['navigation'],
|
|
65
|
+
guidanceLevel: 3,
|
|
65
66
|
requiredArtifacts: ['traces', 'devtoolsLogs', 'URL', 'LinkElements'],
|
|
66
67
|
scoreDisplayMode: Audit.SCORING_MODES.NUMERIC,
|
|
67
68
|
};
|
|
@@ -43,6 +43,7 @@ class UsesRelPreloadAudit extends Audit {
|
|
|
43
43
|
title: str_(UIStrings.title),
|
|
44
44
|
description: str_(UIStrings.description),
|
|
45
45
|
supportedModes: ['navigation'],
|
|
46
|
+
guidanceLevel: 3,
|
|
46
47
|
requiredArtifacts: ['devtoolsLogs', 'traces', 'URL'],
|
|
47
48
|
scoreDisplayMode: Audit.SCORING_MODES.NUMERIC,
|
|
48
49
|
};
|
package/core/audits/viewport.js
CHANGED
|
@@ -60,6 +60,7 @@ class WorkDuringInteraction extends Audit {
|
|
|
60
60
|
description: str_(UIStrings.description),
|
|
61
61
|
scoreDisplayMode: Audit.SCORING_MODES.NUMERIC,
|
|
62
62
|
supportedModes: ['timespan'],
|
|
63
|
+
guidanceLevel: 1,
|
|
63
64
|
requiredArtifacts: ['traces', 'devtoolsLogs', 'TraceElements'],
|
|
64
65
|
};
|
|
65
66
|
}
|
|
@@ -9,7 +9,7 @@ declare const MainResourceComputed: typeof MainResource & {
|
|
|
9
9
|
};
|
|
10
10
|
/**
|
|
11
11
|
* @fileoverview This artifact identifies the main resource on the page. Current solution assumes
|
|
12
|
-
* that the main resource is the first non-
|
|
12
|
+
* that the main resource is the first non-redirected one.
|
|
13
13
|
*/
|
|
14
14
|
declare class MainResource {
|
|
15
15
|
/**
|
|
@@ -5,12 +5,12 @@
|
|
|
5
5
|
*/
|
|
6
6
|
|
|
7
7
|
import {makeComputedArtifact} from './computed-artifact.js';
|
|
8
|
-
import {NetworkAnalyzer} from '../lib/dependency-graph/simulator/network-analyzer.js';
|
|
9
8
|
import {NetworkRecords} from './network-records.js';
|
|
9
|
+
import {NetworkAnalyzer} from '../lib/dependency-graph/simulator/network-analyzer.js';
|
|
10
10
|
|
|
11
11
|
/**
|
|
12
12
|
* @fileoverview This artifact identifies the main resource on the page. Current solution assumes
|
|
13
|
-
* that the main resource is the first non-
|
|
13
|
+
* that the main resource is the first non-redirected one.
|
|
14
14
|
*/
|
|
15
15
|
class MainResource {
|
|
16
16
|
/**
|
|
@@ -21,8 +21,14 @@ class MainResource {
|
|
|
21
21
|
static async compute_(data, context) {
|
|
22
22
|
const {mainDocumentUrl} = data.URL;
|
|
23
23
|
if (!mainDocumentUrl) throw new Error('mainDocumentUrl must exist to get the main resource');
|
|
24
|
-
const
|
|
25
|
-
|
|
24
|
+
const records = await NetworkRecords.request(data.devtoolsLog, context);
|
|
25
|
+
|
|
26
|
+
// We could have more than one record matching the main doucment url,
|
|
27
|
+
// if the page did `location.reload()`. Since `mainDocumentUrl` refers to the _last_
|
|
28
|
+
// document request, we should return the last candidate here. Besides, the browser
|
|
29
|
+
// would have evicted the first request by the time `MainDocumentRequest` (a consumer
|
|
30
|
+
// of this computed artifact) attempts to fetch the contents, resulting in a protocol error.
|
|
31
|
+
const mainResource = NetworkAnalyzer.findLastDocumentForUrl(records, mainDocumentUrl);
|
|
26
32
|
if (!mainResource) {
|
|
27
33
|
throw new Error('Unable to identify the main resource');
|
|
28
34
|
}
|
|
@@ -412,7 +412,8 @@ class PageDependencyGraph {
|
|
|
412
412
|
const rootNode = networkNodeOutput.idToNodeMap.get(rootRequest.requestId);
|
|
413
413
|
if (!rootNode) throw new Error('rootNode not found');
|
|
414
414
|
|
|
415
|
-
const mainDocumentRequest =
|
|
415
|
+
const mainDocumentRequest =
|
|
416
|
+
NetworkAnalyzer.findLastDocumentForUrl(networkRecords, mainDocumentUrl);
|
|
416
417
|
if (!mainDocumentRequest) throw new Error('mainDocumentRequest not found');
|
|
417
418
|
const mainDocumentNode = networkNodeOutput.idToNodeMap.get(mainDocumentRequest.requestId);
|
|
418
419
|
if (!mainDocumentNode) throw new Error('mainDocumentNode not found');
|