lighthouse 11.3.0 → 11.4.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/core/audits/accessibility/aria-allowed-role.js +1 -1
- package/core/audits/bf-cache.d.ts +1 -0
- package/core/audits/bf-cache.js +12 -2
- package/core/audits/bootup-time.js +3 -0
- package/core/audits/byte-efficiency/byte-efficiency-audit.d.ts +0 -10
- package/core/audits/byte-efficiency/byte-efficiency-audit.js +6 -46
- package/core/audits/byte-efficiency/duplicated-javascript.d.ts +0 -6
- package/core/audits/byte-efficiency/duplicated-javascript.js +9 -38
- package/core/audits/byte-efficiency/legacy-javascript.d.ts +0 -11
- package/core/audits/byte-efficiency/legacy-javascript.js +5 -36
- package/core/audits/byte-efficiency/unminified-css.js +2 -2
- package/core/audits/byte-efficiency/unminified-javascript.js +2 -3
- package/core/audits/byte-efficiency/unused-javascript.js +10 -11
- package/core/audits/dobetterweb/uses-http2.d.ts +1 -1
- package/core/audits/dobetterweb/uses-http2.js +8 -5
- package/core/audits/layout-shift-elements.js +32 -17
- package/core/audits/metrics/cumulative-layout-shift.js +5 -1
- package/core/audits/metrics/max-potential-fid.d.ts +20 -0
- package/core/audits/metrics/max-potential-fid.js +69 -0
- package/core/audits/resource-summary.d.ts +11 -0
- package/core/audits/resource-summary.js +86 -0
- package/core/audits/third-party-cookies.d.ts +22 -0
- package/core/audits/third-party-cookies.js +112 -0
- package/core/audits/third-party-summary.d.ts +20 -2
- package/core/audits/third-party-summary.js +3 -48
- package/core/computed/entity-classification.js +1 -1
- package/core/computed/metrics/cumulative-layout-shift.d.ts +13 -1
- package/core/computed/metrics/cumulative-layout-shift.js +47 -1
- package/core/computed/metrics/lantern-first-contentful-paint.d.ts +50 -16
- package/core/computed/metrics/lantern-first-contentful-paint.js +56 -59
- package/core/computed/metrics/lantern-first-meaningful-paint.js +10 -12
- package/core/computed/metrics/lantern-largest-contentful-paint.js +9 -12
- package/core/computed/metrics/lantern-metric.d.ts +2 -2
- package/core/computed/metrics/lantern-metric.js +6 -5
- package/core/computed/resource-summary.d.ts +0 -1
- package/core/computed/resource-summary.js +2 -2
- package/core/computed/unused-css.js +2 -2
- package/core/config/constants.d.ts +2 -2
- package/core/config/constants.js +2 -2
- package/core/config/default-config.js +4 -0
- package/core/config/filters.js +1 -0
- package/core/gather/base-artifacts.js +2 -1
- package/core/gather/gatherers/css-usage.js +5 -0
- package/core/gather/gatherers/trace-elements.d.ts +6 -15
- package/core/gather/gatherers/trace-elements.js +15 -71
- package/core/gather/navigation-runner.js +1 -1
- package/core/lib/i18n/i18n.d.ts +2 -2
- package/core/lib/i18n/i18n.js +2 -2
- package/core/lib/minification-estimator.js +1 -1
- package/core/lib/network-recorder.d.ts +8 -0
- package/core/lib/network-recorder.js +13 -0
- package/core/lib/network-request.d.ts +11 -0
- package/core/lib/network-request.js +21 -1
- package/core/lib/rect-helpers.d.ts +5 -0
- package/core/lib/rect-helpers.js +15 -0
- package/core/lib/script-helpers.d.ts +34 -5
- package/core/lib/script-helpers.js +136 -0
- package/core/lib/url-utils.d.ts +6 -0
- package/core/lib/url-utils.js +14 -2
- package/core/runner.js +5 -1
- package/core/scripts/download-chrome.sh +56 -13
- package/dist/report/bundle.esm.js +30 -13
- package/dist/report/flow.js +32 -15
- package/dist/report/standalone.js +32 -15
- package/flow-report/src/i18n/i18n.d.ts +4 -0
- package/package.json +5 -4
- package/report/assets/styles.css +25 -8
- package/report/assets/templates.html +1 -1
- package/report/clients/standalone.js +1 -0
- package/report/renderer/components.js +2 -2
- package/report/renderer/explodey-gauge.js +0 -1
- package/report/renderer/performance-category-renderer.js +4 -2
- package/report/renderer/report-renderer.js +12 -1
- package/report/renderer/report-ui-features.js +3 -2
- package/report/renderer/report-utils.d.ts +2 -0
- package/report/renderer/report-utils.js +8 -4
- package/report/types/report-renderer.d.ts +2 -0
- package/shared/localization/locales/ar-XB.json +34 -25
- package/shared/localization/locales/ar.json +35 -26
- package/shared/localization/locales/bg.json +34 -25
- package/shared/localization/locales/ca.json +35 -26
- package/shared/localization/locales/cs.json +34 -25
- package/shared/localization/locales/da.json +51 -42
- package/shared/localization/locales/de.json +38 -29
- package/shared/localization/locales/el.json +34 -25
- package/shared/localization/locales/en-GB.json +35 -26
- package/shared/localization/locales/en-US.json +26 -5
- package/shared/localization/locales/en-XA.json +34 -25
- package/shared/localization/locales/en-XL.json +26 -5
- package/shared/localization/locales/es-419.json +34 -25
- package/shared/localization/locales/es.json +34 -25
- package/shared/localization/locales/fi.json +34 -25
- package/shared/localization/locales/fil.json +35 -26
- package/shared/localization/locales/fr.json +34 -25
- package/shared/localization/locales/he.json +35 -26
- package/shared/localization/locales/hi.json +35 -26
- package/shared/localization/locales/hr.json +35 -26
- package/shared/localization/locales/hu.json +34 -25
- package/shared/localization/locales/id.json +34 -25
- package/shared/localization/locales/it.json +34 -25
- package/shared/localization/locales/ja.json +34 -25
- package/shared/localization/locales/ko.json +35 -26
- package/shared/localization/locales/lt.json +34 -25
- package/shared/localization/locales/lv.json +35 -26
- package/shared/localization/locales/nl.json +35 -26
- package/shared/localization/locales/no.json +34 -25
- package/shared/localization/locales/pl.json +34 -25
- package/shared/localization/locales/pt-PT.json +35 -26
- package/shared/localization/locales/pt.json +35 -26
- package/shared/localization/locales/ro.json +34 -25
- package/shared/localization/locales/ru.json +35 -26
- package/shared/localization/locales/sk.json +35 -26
- package/shared/localization/locales/sl.json +35 -26
- package/shared/localization/locales/sr-Latn.json +34 -25
- package/shared/localization/locales/sr.json +34 -25
- package/shared/localization/locales/sv.json +35 -26
- package/shared/localization/locales/ta.json +35 -26
- package/shared/localization/locales/te.json +35 -26
- package/shared/localization/locales/th.json +34 -25
- package/shared/localization/locales/tr.json +35 -26
- package/shared/localization/locales/uk.json +34 -25
- package/shared/localization/locales/vi.json +34 -25
- package/shared/localization/locales/zh-HK.json +35 -26
- package/shared/localization/locales/zh-TW.json +38 -29
- package/shared/localization/locales/zh.json +34 -25
- package/shared/util.d.ts +14 -2
- package/shared/util.js +25 -3
- package/tsconfig.json +2 -0
- package/types/artifacts.d.ts +5 -2
|
@@ -6,8 +6,18 @@
|
|
|
6
6
|
|
|
7
7
|
import {Audit} from '../audit.js';
|
|
8
8
|
import {MaxPotentialFID as ComputedFid} from '../../computed/metrics/max-potential-fid.js';
|
|
9
|
+
import {ProcessedTrace} from '../../computed/processed-trace.js';
|
|
10
|
+
import {ProcessedNavigation} from '../../computed/processed-navigation.js';
|
|
9
11
|
import * as i18n from '../../lib/i18n/i18n.js';
|
|
10
12
|
|
|
13
|
+
/**
|
|
14
|
+
* @typedef LoafDebugDetails
|
|
15
|
+
* @property {'debugdata'} type
|
|
16
|
+
* @property {LH.TraceEvent=} observedMaxDurationLoaf
|
|
17
|
+
* @property {LH.TraceEvent=} observedMaxBlockingLoaf
|
|
18
|
+
* @property {Array<{startTime: number, duration: number, blockingDuration: number}>} observedLoafs
|
|
19
|
+
*/
|
|
20
|
+
|
|
11
21
|
const UIStrings = {
|
|
12
22
|
/** Description of the Maximum Potential First Input Delay metric that marks the maximum estimated time between the page receiving input (a user clicking, tapping, or typing) and the page responding. This description is displayed within a tooltip when the user hovers on the metric name to see more. No character length limits. The last sentence starting with 'Learn' becomes link text to additional documentation. */
|
|
13
23
|
description: 'The maximum potential First Input Delay that your users could experience is the ' +
|
|
@@ -48,6 +58,59 @@ class MaxPotentialFID extends Audit {
|
|
|
48
58
|
};
|
|
49
59
|
}
|
|
50
60
|
|
|
61
|
+
/**
|
|
62
|
+
* Extract potential LoAF replacements for MPFID from the trace to log in
|
|
63
|
+
* debugdata details.
|
|
64
|
+
* @param {LH.Artifacts.ProcessedTrace} processedTrace
|
|
65
|
+
* @param {LH.Artifacts.ProcessedNavigation} processedNavigation
|
|
66
|
+
* @return {LoafDebugDetails|undefined}
|
|
67
|
+
*/
|
|
68
|
+
static getLongAnimationFrameDetails(processedTrace, processedNavigation) {
|
|
69
|
+
const {firstContentfulPaint, timeOrigin} = processedNavigation.timestamps;
|
|
70
|
+
|
|
71
|
+
const loafEvents = processedTrace.mainThreadEvents.filter(evt => {
|
|
72
|
+
return evt.name === 'LongAnimationFrame' && evt.ph === 'b';
|
|
73
|
+
});
|
|
74
|
+
if (loafEvents.length === 0) return;
|
|
75
|
+
|
|
76
|
+
let currentMaxDuration = -Infinity;
|
|
77
|
+
let currentMaxDurationLoaf;
|
|
78
|
+
let currentMaxBlocking = -Infinity;
|
|
79
|
+
let currentMaxBlockingLoaf;
|
|
80
|
+
const observedLoafs = [];
|
|
81
|
+
for (const loafEvent of loafEvents) {
|
|
82
|
+
const loafDuration = loafEvent.args?.data?.duration;
|
|
83
|
+
const loafBlocking = loafEvent.args?.data?.blockingDuration;
|
|
84
|
+
// Should never happen, so mostly keeping the type checker happy.
|
|
85
|
+
if (loafDuration === undefined || loafBlocking === undefined) continue;
|
|
86
|
+
|
|
87
|
+
observedLoafs.push({
|
|
88
|
+
startTime: (loafEvent.ts - timeOrigin) / 1000,
|
|
89
|
+
duration: loafDuration,
|
|
90
|
+
blockingDuration: loafBlocking,
|
|
91
|
+
});
|
|
92
|
+
|
|
93
|
+
// Max LoAFs are only considered after FCP.
|
|
94
|
+
if (loafEvent.ts < firstContentfulPaint) continue;
|
|
95
|
+
|
|
96
|
+
if (loafDuration > currentMaxDuration) {
|
|
97
|
+
currentMaxDuration = loafDuration;
|
|
98
|
+
currentMaxDurationLoaf = loafEvent;
|
|
99
|
+
}
|
|
100
|
+
if (loafBlocking > currentMaxBlocking) {
|
|
101
|
+
currentMaxBlocking = loafBlocking;
|
|
102
|
+
currentMaxBlockingLoaf = loafEvent;
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
return {
|
|
107
|
+
type: 'debugdata',
|
|
108
|
+
observedMaxDurationLoaf: currentMaxDurationLoaf,
|
|
109
|
+
observedMaxBlockingLoaf: currentMaxBlockingLoaf,
|
|
110
|
+
observedLoafs,
|
|
111
|
+
};
|
|
112
|
+
}
|
|
113
|
+
|
|
51
114
|
/**
|
|
52
115
|
* @param {LH.Artifacts} artifacts
|
|
53
116
|
* @param {LH.Audit.Context} context
|
|
@@ -61,6 +124,11 @@ class MaxPotentialFID extends Audit {
|
|
|
61
124
|
settings: context.settings, URL: artifacts.URL};
|
|
62
125
|
const metricResult = await ComputedFid.request(metricComputationData, context);
|
|
63
126
|
|
|
127
|
+
const processedTrace = await ProcessedTrace.request(trace, context);
|
|
128
|
+
const processedNavigation = await ProcessedNavigation.request(trace, context);
|
|
129
|
+
const details = MaxPotentialFID.getLongAnimationFrameDetails(processedTrace,
|
|
130
|
+
processedNavigation);
|
|
131
|
+
|
|
64
132
|
return {
|
|
65
133
|
score: Audit.computeLogNormalScore(
|
|
66
134
|
{p10: context.options.p10, median: context.options.median},
|
|
@@ -69,6 +137,7 @@ class MaxPotentialFID extends Audit {
|
|
|
69
137
|
numericValue: metricResult.timing,
|
|
70
138
|
numericUnit: 'millisecond',
|
|
71
139
|
displayValue: str_(i18n.UIStrings.ms, {timeInMs: metricResult.timing}),
|
|
140
|
+
details,
|
|
72
141
|
};
|
|
73
142
|
}
|
|
74
143
|
}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
export default ResourceSummary;
|
|
2
|
+
declare class ResourceSummary extends Audit {
|
|
3
|
+
/**
|
|
4
|
+
* @param {LH.Artifacts} artifacts
|
|
5
|
+
* @param {LH.Audit.Context} context
|
|
6
|
+
* @return {Promise<LH.Audit.Product>}
|
|
7
|
+
*/
|
|
8
|
+
static audit(artifacts: LH.Artifacts, context: LH.Audit.Context): Promise<LH.Audit.Product>;
|
|
9
|
+
}
|
|
10
|
+
import { Audit } from './audit.js';
|
|
11
|
+
//# sourceMappingURL=resource-summary.d.ts.map
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @license Copyright 2019 The Lighthouse Authors. All Rights Reserved.
|
|
3
|
+
* Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0
|
|
4
|
+
* Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License.
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import {Audit} from './audit.js';
|
|
8
|
+
import {ResourceSummary as ComputedResourceSummary} from '../computed/resource-summary.js';
|
|
9
|
+
import * as i18n from '../lib/i18n/i18n.js';
|
|
10
|
+
|
|
11
|
+
const str_ = i18n.createIcuMessageFn(import.meta.url);
|
|
12
|
+
|
|
13
|
+
class ResourceSummary extends Audit {
|
|
14
|
+
/**
|
|
15
|
+
* @return {LH.Audit.Meta}
|
|
16
|
+
*/
|
|
17
|
+
static get meta() {
|
|
18
|
+
return {
|
|
19
|
+
id: 'resource-summary',
|
|
20
|
+
title: 'Resources Summary',
|
|
21
|
+
description: 'Aggregates all network requests and groups them by type',
|
|
22
|
+
scoreDisplayMode: Audit.SCORING_MODES.INFORMATIVE,
|
|
23
|
+
requiredArtifacts: ['devtoolsLogs', 'URL'],
|
|
24
|
+
};
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* @param {LH.Artifacts} artifacts
|
|
29
|
+
* @param {LH.Audit.Context} context
|
|
30
|
+
* @return {Promise<LH.Audit.Product>}
|
|
31
|
+
*/
|
|
32
|
+
static async audit(artifacts, context) {
|
|
33
|
+
const devtoolsLog = artifacts.devtoolsLogs[Audit.DEFAULT_PASS];
|
|
34
|
+
const summary = await ComputedResourceSummary
|
|
35
|
+
.request({devtoolsLog, URL: artifacts.URL, budgets: context.settings.budgets}, context);
|
|
36
|
+
|
|
37
|
+
/** @type {LH.Audit.Details.Table['headings']} */
|
|
38
|
+
const headings = [
|
|
39
|
+
{key: 'label', valueType: 'text', label: str_(i18n.UIStrings.columnResourceType)},
|
|
40
|
+
{key: 'requestCount', valueType: 'numeric', label: str_(i18n.UIStrings.columnRequests)},
|
|
41
|
+
{key: 'transferSize', valueType: 'bytes', label: str_(i18n.UIStrings.columnTransferSize)},
|
|
42
|
+
];
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
/** @type {Record<LH.Budget.ResourceType, LH.IcuMessage>} */
|
|
46
|
+
const strMappings = {
|
|
47
|
+
'total': str_(i18n.UIStrings.totalResourceType),
|
|
48
|
+
'document': str_(i18n.UIStrings.documentResourceType),
|
|
49
|
+
'script': str_(i18n.UIStrings.scriptResourceType),
|
|
50
|
+
'stylesheet': str_(i18n.UIStrings.stylesheetResourceType),
|
|
51
|
+
'image': str_(i18n.UIStrings.imageResourceType),
|
|
52
|
+
'media': str_(i18n.UIStrings.mediaResourceType),
|
|
53
|
+
'font': str_(i18n.UIStrings.fontResourceType),
|
|
54
|
+
'other': str_(i18n.UIStrings.otherResourceType),
|
|
55
|
+
'third-party': str_(i18n.UIStrings.thirdPartyResourceType),
|
|
56
|
+
};
|
|
57
|
+
|
|
58
|
+
const types = /** @type {Array<LH.Budget.ResourceType>} */ (Object.keys(summary));
|
|
59
|
+
const rows = types.map(type => {
|
|
60
|
+
return {
|
|
61
|
+
// ResourceType is included as an "id" for ease of use.
|
|
62
|
+
// It does not appear directly in the table.
|
|
63
|
+
resourceType: type,
|
|
64
|
+
label: strMappings[type],
|
|
65
|
+
requestCount: summary[type].count,
|
|
66
|
+
transferSize: summary[type].transferSize,
|
|
67
|
+
};
|
|
68
|
+
});
|
|
69
|
+
// Force third-party to be last, descending by size otherwise
|
|
70
|
+
const thirdPartyRow = rows.find(r => r.resourceType === 'third-party') || [];
|
|
71
|
+
const otherRows = rows.filter(r => r.resourceType !== 'third-party')
|
|
72
|
+
.sort((a, b) => {
|
|
73
|
+
return b.transferSize - a.transferSize;
|
|
74
|
+
});
|
|
75
|
+
const tableItems = otherRows.concat(thirdPartyRow);
|
|
76
|
+
|
|
77
|
+
const tableDetails = Audit.makeTableDetails(headings, tableItems);
|
|
78
|
+
|
|
79
|
+
return {
|
|
80
|
+
details: tableDetails,
|
|
81
|
+
score: null,
|
|
82
|
+
};
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
export default ResourceSummary;
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
export default ThirdPartyCookies;
|
|
2
|
+
declare class ThirdPartyCookies extends Audit {
|
|
3
|
+
/**
|
|
4
|
+
* https://source.chromium.org/chromium/chromium/src/+/d2fcd4ba302baeabf4b96d8fa9fdb7a215736c31:third_party/devtools-frontend/src/front_end/models/issues_manager/CookieIssue.ts;l=62-69
|
|
5
|
+
* @param {LH.Crdp.Audits.CookieIssueDetails} cookieIssue
|
|
6
|
+
* @return {string}
|
|
7
|
+
*/
|
|
8
|
+
static getCookieId(cookieIssue: LH.Crdp.Audits.CookieIssueDetails): string;
|
|
9
|
+
/**
|
|
10
|
+
* @param {LH.Artifacts} artifacts
|
|
11
|
+
* @return {Promise<LH.Audit.Product>}
|
|
12
|
+
*/
|
|
13
|
+
static audit(artifacts: LH.Artifacts): Promise<LH.Audit.Product>;
|
|
14
|
+
}
|
|
15
|
+
export namespace UIStrings {
|
|
16
|
+
const title: string;
|
|
17
|
+
const failureTitle: string;
|
|
18
|
+
const description: string;
|
|
19
|
+
const displayValue: string;
|
|
20
|
+
}
|
|
21
|
+
import { Audit } from './audit.js';
|
|
22
|
+
//# sourceMappingURL=third-party-cookies.d.ts.map
|
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @license
|
|
3
|
+
* Copyright 2023 Google LLC
|
|
4
|
+
* SPDX-License-Identifier: Apache-2.0
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* @fileoverview Audits a page to determine if it is using third party cookies.
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
import {Audit} from './audit.js';
|
|
12
|
+
import * as i18n from '../lib/i18n/i18n.js';
|
|
13
|
+
|
|
14
|
+
/* eslint-disable max-len */
|
|
15
|
+
const UIStrings = {
|
|
16
|
+
/** Title of a Lighthouse audit that provides detail on the use of third party cookies. This descriptive title is shown to users when the page does not use third party cookies. */
|
|
17
|
+
title: 'Avoids third-party cookies',
|
|
18
|
+
/** Title of a Lighthouse audit that provides detail on the use of third party cookies. This descriptive title is shown to users when the page uses third party cookies. */
|
|
19
|
+
failureTitle: 'Uses third-party cookies',
|
|
20
|
+
/** Description of a Lighthouse audit that tells the user why they should not use third party cookies on their page. This is displayed after a user expands the section to see more. No character length limits. The last sentence starting with 'Learn' becomes link text to additional documentation. */
|
|
21
|
+
description: 'Support for third-party cookies will be removed in a future version of Chrome. [Learn more about phasing out third-party cookies](https://developer.chrome.com/en/docs/privacy-sandbox/third-party-cookie-phase-out/).',
|
|
22
|
+
/** [ICU Syntax] Label for the audit identifying the number of third-party cookies. */
|
|
23
|
+
displayValue: `{itemCount, plural,
|
|
24
|
+
=1 {1 cookie found}
|
|
25
|
+
other {# cookies found}
|
|
26
|
+
}`,
|
|
27
|
+
};
|
|
28
|
+
/* eslint-enable max-len */
|
|
29
|
+
|
|
30
|
+
const str_ = i18n.createIcuMessageFn(import.meta.url, UIStrings);
|
|
31
|
+
|
|
32
|
+
class ThirdPartyCookies extends Audit {
|
|
33
|
+
/**
|
|
34
|
+
* @return {LH.Audit.Meta}
|
|
35
|
+
*/
|
|
36
|
+
static get meta() {
|
|
37
|
+
return {
|
|
38
|
+
id: 'third-party-cookies',
|
|
39
|
+
title: str_(UIStrings.title),
|
|
40
|
+
failureTitle: str_(UIStrings.failureTitle),
|
|
41
|
+
description: str_(UIStrings.description),
|
|
42
|
+
requiredArtifacts: ['InspectorIssues'],
|
|
43
|
+
};
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
/**
|
|
47
|
+
* https://source.chromium.org/chromium/chromium/src/+/d2fcd4ba302baeabf4b96d8fa9fdb7a215736c31:third_party/devtools-frontend/src/front_end/models/issues_manager/CookieIssue.ts;l=62-69
|
|
48
|
+
* @param {LH.Crdp.Audits.CookieIssueDetails} cookieIssue
|
|
49
|
+
* @return {string}
|
|
50
|
+
*/
|
|
51
|
+
static getCookieId(cookieIssue) {
|
|
52
|
+
if (!cookieIssue.cookie) {
|
|
53
|
+
return cookieIssue.rawCookieLine ?? 'no-cookie-info';
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
const {domain, path, name} = cookieIssue.cookie;
|
|
57
|
+
const cookieId = `${domain};${path};${name}`;
|
|
58
|
+
return cookieId;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
/**
|
|
62
|
+
* @param {LH.Artifacts} artifacts
|
|
63
|
+
* @return {Promise<LH.Audit.Product>}
|
|
64
|
+
*/
|
|
65
|
+
static async audit(artifacts) {
|
|
66
|
+
/** @type {Set<string>} */
|
|
67
|
+
const seenCookies = new Set();
|
|
68
|
+
|
|
69
|
+
/** @type {LH.Audit.Details.TableItem[]} */
|
|
70
|
+
const items = [];
|
|
71
|
+
for (const issue of artifacts.InspectorIssues.cookieIssue) {
|
|
72
|
+
const isPhaseoutWarn = issue.cookieWarningReasons.includes('WarnThirdPartyPhaseout');
|
|
73
|
+
const isPhaseoutExclude = issue.cookieExclusionReasons.includes('ExcludeThirdPartyPhaseout');
|
|
74
|
+
if (!isPhaseoutWarn && !isPhaseoutExclude) continue;
|
|
75
|
+
|
|
76
|
+
// According to JSDOC for `issue.cookie`, if `cookie` is undefined then `rawCookieLine`
|
|
77
|
+
// should be set and no valid cookie could be created. It should be safe to skip in this case.
|
|
78
|
+
const name = issue.cookie?.name || issue.rawCookieLine;
|
|
79
|
+
if (!name) continue;
|
|
80
|
+
|
|
81
|
+
const cookieId = this.getCookieId(issue);
|
|
82
|
+
if (seenCookies.has(cookieId)) continue;
|
|
83
|
+
seenCookies.add(cookieId);
|
|
84
|
+
|
|
85
|
+
items.push({
|
|
86
|
+
name,
|
|
87
|
+
url: issue.cookieUrl,
|
|
88
|
+
});
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
/** @type {LH.Audit.Details.Table['headings']} */
|
|
92
|
+
const headings = [
|
|
93
|
+
{key: 'name', valueType: 'text', label: str_(i18n.UIStrings.columnName)},
|
|
94
|
+
{key: 'url', valueType: 'url', label: str_(i18n.UIStrings.columnURL)},
|
|
95
|
+
];
|
|
96
|
+
const details = Audit.makeTableDetails(headings, items);
|
|
97
|
+
|
|
98
|
+
let displayValue;
|
|
99
|
+
if (items.length > 0) {
|
|
100
|
+
displayValue = str_(UIStrings.displayValue, {itemCount: items.length});
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
return {
|
|
104
|
+
score: items.length ? 0 : 1,
|
|
105
|
+
displayValue,
|
|
106
|
+
details,
|
|
107
|
+
};
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
export default ThirdPartyCookies;
|
|
112
|
+
export {UIStrings};
|
|
@@ -25,6 +25,25 @@ export type SummaryMaps = {
|
|
|
25
25
|
*/
|
|
26
26
|
urls: Map<LH.Artifacts.Entity, string[]>;
|
|
27
27
|
};
|
|
28
|
+
/**
|
|
29
|
+
* @typedef Summary
|
|
30
|
+
* @property {number} mainThreadTime
|
|
31
|
+
* @property {number} transferSize
|
|
32
|
+
* @property {number} blockingTime
|
|
33
|
+
* @property {number} tbtImpact
|
|
34
|
+
*/
|
|
35
|
+
/**
|
|
36
|
+
* @typedef URLSummary
|
|
37
|
+
* @property {number} transferSize
|
|
38
|
+
* @property {number} blockingTime
|
|
39
|
+
* @property {number} tbtImpact
|
|
40
|
+
* @property {string | LH.IcuMessage} url
|
|
41
|
+
*/
|
|
42
|
+
/** @typedef SummaryMaps
|
|
43
|
+
* @property {Map<LH.Artifacts.Entity, Summary>} byEntity Map of impact summaries for each entity.
|
|
44
|
+
* @property {Map<string, Summary>} byURL Map of impact summaries for each URL.
|
|
45
|
+
* @property {Map<LH.Artifacts.Entity, string[]>} urls Map of URLs under each entity.
|
|
46
|
+
*/
|
|
28
47
|
declare class ThirdPartySummary extends Audit {
|
|
29
48
|
/**
|
|
30
49
|
*
|
|
@@ -38,10 +57,9 @@ declare class ThirdPartySummary extends Audit {
|
|
|
38
57
|
/**
|
|
39
58
|
* @param {LH.Artifacts.Entity} entity
|
|
40
59
|
* @param {SummaryMaps} summaries
|
|
41
|
-
* @param {Summary} stats
|
|
42
60
|
* @return {Array<URLSummary>}
|
|
43
61
|
*/
|
|
44
|
-
static makeSubItems(entity: LH.Artifacts.Entity, summaries: SummaryMaps
|
|
62
|
+
static makeSubItems(entity: LH.Artifacts.Entity, summaries: SummaryMaps): Array<URLSummary>;
|
|
45
63
|
/**
|
|
46
64
|
* @param {LH.Artifacts} artifacts
|
|
47
65
|
* @param {LH.Audit.Context} context
|
|
@@ -55,15 +55,6 @@ const PASS_THRESHOLD_IN_MS = 250;
|
|
|
55
55
|
* @property {Map<LH.Artifacts.Entity, string[]>} urls Map of URLs under each entity.
|
|
56
56
|
*/
|
|
57
57
|
|
|
58
|
-
/**
|
|
59
|
-
* Don't bother showing resources smaller than 4KiB since they're likely to be pixels, which isn't
|
|
60
|
-
* too actionable.
|
|
61
|
-
*/
|
|
62
|
-
const MIN_TRANSFER_SIZE_FOR_SUBITEMS = 4096;
|
|
63
|
-
|
|
64
|
-
/** Show at most 5 sub items in the resource breakdown. */
|
|
65
|
-
const MAX_SUBITEMS = 5;
|
|
66
|
-
|
|
67
58
|
class ThirdPartySummary extends Audit {
|
|
68
59
|
/**
|
|
69
60
|
* @return {LH.Audit.Meta}
|
|
@@ -146,51 +137,15 @@ class ThirdPartySummary extends Audit {
|
|
|
146
137
|
/**
|
|
147
138
|
* @param {LH.Artifacts.Entity} entity
|
|
148
139
|
* @param {SummaryMaps} summaries
|
|
149
|
-
* @param {Summary} stats
|
|
150
140
|
* @return {Array<URLSummary>}
|
|
151
141
|
*/
|
|
152
|
-
static makeSubItems(entity, summaries
|
|
142
|
+
static makeSubItems(entity, summaries) {
|
|
153
143
|
const entityURLs = summaries.urls.get(entity) || [];
|
|
154
|
-
|
|
144
|
+
const items = entityURLs
|
|
155
145
|
.map(url => /** @type {URLSummary} */ ({url, ...summaries.byURL.get(url)}))
|
|
156
|
-
// Filter out any cases where byURL was missing entries.
|
|
157
|
-
.filter((stat) => stat.transferSize > 0)
|
|
158
146
|
// Sort by blocking time first, then transfer size to break ties.
|
|
159
147
|
.sort((a, b) => (b.blockingTime - a.blockingTime) || (b.transferSize - a.transferSize));
|
|
160
148
|
|
|
161
|
-
const subitemSummary = {transferSize: 0, blockingTime: 0, tbtImpact: 0};
|
|
162
|
-
const minTransferSize = Math.max(MIN_TRANSFER_SIZE_FOR_SUBITEMS, stats.transferSize / 20);
|
|
163
|
-
const maxSubItems = Math.min(MAX_SUBITEMS, items.length);
|
|
164
|
-
let numSubItems = 0;
|
|
165
|
-
while (numSubItems < maxSubItems) {
|
|
166
|
-
const nextSubItem = items[numSubItems];
|
|
167
|
-
if (nextSubItem.blockingTime === 0 && nextSubItem.transferSize < minTransferSize) {
|
|
168
|
-
// Don't include the resource in the sub-item breakdown because it didn't have a big
|
|
169
|
-
// enough impact on its own.
|
|
170
|
-
break;
|
|
171
|
-
}
|
|
172
|
-
|
|
173
|
-
numSubItems++;
|
|
174
|
-
subitemSummary.transferSize += nextSubItem.transferSize;
|
|
175
|
-
subitemSummary.blockingTime += nextSubItem.blockingTime;
|
|
176
|
-
subitemSummary.tbtImpact += nextSubItem.tbtImpact;
|
|
177
|
-
}
|
|
178
|
-
if (!subitemSummary.blockingTime && !subitemSummary.transferSize) {
|
|
179
|
-
// Don't bother breaking down if there are no large resources.
|
|
180
|
-
return [];
|
|
181
|
-
}
|
|
182
|
-
// Only show the top N entries for brevity. If there is more than one remaining entry
|
|
183
|
-
// we'll replace the tail entries with single remainder entry.
|
|
184
|
-
items = items.slice(0, numSubItems);
|
|
185
|
-
const remainder = {
|
|
186
|
-
url: str_(i18n.UIStrings.otherResourcesLabel),
|
|
187
|
-
transferSize: stats.transferSize - subitemSummary.transferSize,
|
|
188
|
-
blockingTime: stats.blockingTime - subitemSummary.blockingTime,
|
|
189
|
-
tbtImpact: stats.tbtImpact - subitemSummary.tbtImpact,
|
|
190
|
-
};
|
|
191
|
-
if (remainder.transferSize > minTransferSize) {
|
|
192
|
-
items.push(remainder);
|
|
193
|
-
}
|
|
194
149
|
return items;
|
|
195
150
|
}
|
|
196
151
|
|
|
@@ -231,7 +186,7 @@ class ThirdPartySummary extends Audit {
|
|
|
231
186
|
entity: entity.name,
|
|
232
187
|
subItems: {
|
|
233
188
|
type: /** @type {const} */ ('subitems'),
|
|
234
|
-
items: ThirdPartySummary.makeSubItems(entity, summaries
|
|
189
|
+
items: ThirdPartySummary.makeSubItems(entity, summaries),
|
|
235
190
|
},
|
|
236
191
|
};
|
|
237
192
|
})
|
|
@@ -59,7 +59,7 @@ class EntityClassification {
|
|
|
59
59
|
// Make up an entity only for valid http/https URLs.
|
|
60
60
|
if (!parsedUrl.protocol.startsWith('http')) return;
|
|
61
61
|
|
|
62
|
-
const rootDomain =
|
|
62
|
+
const rootDomain = UrlUtils.getRootDomain(url);
|
|
63
63
|
if (!rootDomain) return;
|
|
64
64
|
if (entityCache.has(rootDomain)) return entityCache.get(rootDomain);
|
|
65
65
|
|
|
@@ -11,6 +11,7 @@ declare const CumulativeLayoutShiftComputed: typeof CumulativeLayoutShift & {
|
|
|
11
11
|
}>) => Promise<{
|
|
12
12
|
cumulativeLayoutShift: number;
|
|
13
13
|
cumulativeLayoutShiftMainFrame: number;
|
|
14
|
+
impactByNodeId: Map<number, number>;
|
|
14
15
|
}>;
|
|
15
16
|
};
|
|
16
17
|
declare class CumulativeLayoutShift {
|
|
@@ -23,6 +24,16 @@ declare class CumulativeLayoutShift {
|
|
|
23
24
|
* @return {Array<LayoutShiftEvent>}
|
|
24
25
|
*/
|
|
25
26
|
static getLayoutShiftEvents(processedTrace: LH.Artifacts.ProcessedTrace): Array<LayoutShiftEvent>;
|
|
27
|
+
/**
|
|
28
|
+
* Each layout shift event has a 'score' which is the amount added to the CLS as a result of the given shift(s).
|
|
29
|
+
* We calculate the score per element by taking the 'score' of each layout shift event and
|
|
30
|
+
* distributing it between all the nodes that were shifted, proportianal to the impact region of
|
|
31
|
+
* each shifted element.
|
|
32
|
+
*
|
|
33
|
+
* @param {LayoutShiftEvent[]} layoutShiftEvents
|
|
34
|
+
* @return {Map<number, number>}
|
|
35
|
+
*/
|
|
36
|
+
static getImpactByNodeId(layoutShiftEvents: LayoutShiftEvent[]): Map<number, number>;
|
|
26
37
|
/**
|
|
27
38
|
* Calculates cumulative layout shifts per cluster (session) of LayoutShift
|
|
28
39
|
* events -- where a new cluster is created when there's a gap of more than
|
|
@@ -35,11 +46,12 @@ declare class CumulativeLayoutShift {
|
|
|
35
46
|
/**
|
|
36
47
|
* @param {LH.Trace} trace
|
|
37
48
|
* @param {LH.Artifacts.ComputedContext} context
|
|
38
|
-
* @return {Promise<{cumulativeLayoutShift: number, cumulativeLayoutShiftMainFrame: number}>}
|
|
49
|
+
* @return {Promise<{cumulativeLayoutShift: number, cumulativeLayoutShiftMainFrame: number, impactByNodeId: Map<number, number>}>}
|
|
39
50
|
*/
|
|
40
51
|
static compute_(trace: LH.Trace, context: LH.Artifacts.ComputedContext): Promise<{
|
|
41
52
|
cumulativeLayoutShift: number;
|
|
42
53
|
cumulativeLayoutShiftMainFrame: number;
|
|
54
|
+
impactByNodeId: Map<number, number>;
|
|
43
55
|
}>;
|
|
44
56
|
}
|
|
45
57
|
import { ProcessedTrace } from '../processed-trace.js';
|
|
@@ -6,6 +6,7 @@
|
|
|
6
6
|
|
|
7
7
|
import {makeComputedArtifact} from '../computed-artifact.js';
|
|
8
8
|
import {ProcessedTrace} from '../processed-trace.js';
|
|
9
|
+
import * as RectHelpers from '../../lib/rect-helpers.js';
|
|
9
10
|
|
|
10
11
|
/** @typedef {{ts: number, isMainFrame: boolean, weightedScore: number, impactedNodes?: LH.Artifacts.TraceImpactedNode[]}} LayoutShiftEvent */
|
|
11
12
|
|
|
@@ -72,6 +73,49 @@ class CumulativeLayoutShift {
|
|
|
72
73
|
return layoutShiftEvents;
|
|
73
74
|
}
|
|
74
75
|
|
|
76
|
+
/**
|
|
77
|
+
* Each layout shift event has a 'score' which is the amount added to the CLS as a result of the given shift(s).
|
|
78
|
+
* We calculate the score per element by taking the 'score' of each layout shift event and
|
|
79
|
+
* distributing it between all the nodes that were shifted, proportianal to the impact region of
|
|
80
|
+
* each shifted element.
|
|
81
|
+
*
|
|
82
|
+
* @param {LayoutShiftEvent[]} layoutShiftEvents
|
|
83
|
+
* @return {Map<number, number>}
|
|
84
|
+
*/
|
|
85
|
+
static getImpactByNodeId(layoutShiftEvents) {
|
|
86
|
+
/** @type {Map<number, number>} */
|
|
87
|
+
const impactByNodeId = new Map();
|
|
88
|
+
|
|
89
|
+
for (const event of layoutShiftEvents) {
|
|
90
|
+
if (!event.impactedNodes) continue;
|
|
91
|
+
|
|
92
|
+
let totalAreaOfImpact = 0;
|
|
93
|
+
/** @type {Map<number, number>} */
|
|
94
|
+
const pixelsMovedPerNode = new Map();
|
|
95
|
+
|
|
96
|
+
for (const node of event.impactedNodes) {
|
|
97
|
+
if (!node.node_id || !node.old_rect || !node.new_rect) continue;
|
|
98
|
+
|
|
99
|
+
const oldRect = RectHelpers.traceRectToLHRect(node.old_rect);
|
|
100
|
+
const newRect = RectHelpers.traceRectToLHRect(node.new_rect);
|
|
101
|
+
const areaOfImpact = RectHelpers.getRectArea(oldRect) +
|
|
102
|
+
RectHelpers.getRectArea(newRect) -
|
|
103
|
+
RectHelpers.getRectOverlapArea(oldRect, newRect);
|
|
104
|
+
|
|
105
|
+
pixelsMovedPerNode.set(node.node_id, areaOfImpact);
|
|
106
|
+
totalAreaOfImpact += areaOfImpact;
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
for (const [nodeId, pixelsMoved] of pixelsMovedPerNode.entries()) {
|
|
110
|
+
let clsContribution = impactByNodeId.get(nodeId) || 0;
|
|
111
|
+
clsContribution += (pixelsMoved / totalAreaOfImpact) * event.weightedScore;
|
|
112
|
+
impactByNodeId.set(nodeId, clsContribution);
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
return impactByNodeId;
|
|
117
|
+
}
|
|
118
|
+
|
|
75
119
|
/**
|
|
76
120
|
* Calculates cumulative layout shifts per cluster (session) of LayoutShift
|
|
77
121
|
* events -- where a new cluster is created when there's a gap of more than
|
|
@@ -104,18 +148,20 @@ class CumulativeLayoutShift {
|
|
|
104
148
|
/**
|
|
105
149
|
* @param {LH.Trace} trace
|
|
106
150
|
* @param {LH.Artifacts.ComputedContext} context
|
|
107
|
-
* @return {Promise<{cumulativeLayoutShift: number, cumulativeLayoutShiftMainFrame: number}>}
|
|
151
|
+
* @return {Promise<{cumulativeLayoutShift: number, cumulativeLayoutShiftMainFrame: number, impactByNodeId: Map<number, number>}>}
|
|
108
152
|
*/
|
|
109
153
|
static async compute_(trace, context) {
|
|
110
154
|
const processedTrace = await ProcessedTrace.request(trace, context);
|
|
111
155
|
|
|
112
156
|
const allFrameShiftEvents =
|
|
113
157
|
CumulativeLayoutShift.getLayoutShiftEvents(processedTrace);
|
|
158
|
+
const impactByNodeId = CumulativeLayoutShift.getImpactByNodeId(allFrameShiftEvents);
|
|
114
159
|
const mainFrameShiftEvents = allFrameShiftEvents.filter(e => e.isMainFrame);
|
|
115
160
|
|
|
116
161
|
return {
|
|
117
162
|
cumulativeLayoutShift: CumulativeLayoutShift.calculate(allFrameShiftEvents),
|
|
118
163
|
cumulativeLayoutShiftMainFrame: CumulativeLayoutShift.calculate(mainFrameShiftEvents),
|
|
164
|
+
impactByNodeId,
|
|
119
165
|
};
|
|
120
166
|
}
|
|
121
167
|
}
|
|
@@ -11,6 +11,16 @@ declare const LanternFirstContentfulPaintComputed: typeof LanternFirstContentful
|
|
|
11
11
|
/** @typedef {import('../../lib/dependency-graph/cpu-node').CPUNode} CPUNode */
|
|
12
12
|
/** @typedef {import('../../lib/dependency-graph/network-node').NetworkNode} NetworkNode */
|
|
13
13
|
declare class LanternFirstContentfulPaint extends LanternMetric {
|
|
14
|
+
/**
|
|
15
|
+
* @typedef FirstPaintBasedGraphOpts
|
|
16
|
+
* @property {number} cutoffTimestamp The timestamp used to filter out tasks that occured after
|
|
17
|
+
* our paint of interest. Typically this is First Contentful Paint or First Meaningful Paint.
|
|
18
|
+
* @property {function(NetworkNode):boolean} treatNodeAsRenderBlocking The function that determines
|
|
19
|
+
* which resources should be considered *possibly* render-blocking.
|
|
20
|
+
* @property {(function(CPUNode):boolean)=} additionalCpuNodesToTreatAsRenderBlocking The function that
|
|
21
|
+
* determines which CPU nodes should also be included in our blocking node IDs set,
|
|
22
|
+
* beyond what getRenderBlockingNodeData() already includes.
|
|
23
|
+
*/
|
|
14
24
|
/**
|
|
15
25
|
* This function computes the set of URLs that *appeared* to be render-blocking based on our filter,
|
|
16
26
|
* *but definitely were not* render-blocking based on the timing of their EvaluateScript task.
|
|
@@ -18,31 +28,55 @@ declare class LanternFirstContentfulPaint extends LanternMetric {
|
|
|
18
28
|
* given timestamp.
|
|
19
29
|
*
|
|
20
30
|
* @param {Node} graph
|
|
21
|
-
* @param {
|
|
22
|
-
*
|
|
23
|
-
* @param {function(NetworkNode):boolean} blockingScriptFilter The function that determines which scripts
|
|
24
|
-
* should be considered *possibly* render-blocking.
|
|
25
|
-
* @param {(function(CPUNode):boolean)=} extraBlockingCpuNodesToIncludeFilter The function that determines which CPU nodes
|
|
26
|
-
* should also be included in our blocking node IDs set.
|
|
27
|
-
* @return {{definitelyNotRenderBlockingScriptUrls: Set<string>, blockingCpuNodeIds: Set<string>}}
|
|
31
|
+
* @param {FirstPaintBasedGraphOpts} opts
|
|
32
|
+
* @return {{definitelyNotRenderBlockingScriptUrls: Set<string>, renderBlockingCpuNodeIds: Set<string>}}
|
|
28
33
|
*/
|
|
29
|
-
static
|
|
34
|
+
static getRenderBlockingNodeData(graph: Node, { cutoffTimestamp, treatNodeAsRenderBlocking, additionalCpuNodesToTreatAsRenderBlocking }: {
|
|
35
|
+
/**
|
|
36
|
+
* The timestamp used to filter out tasks that occured after
|
|
37
|
+
* our paint of interest. Typically this is First Contentful Paint or First Meaningful Paint.
|
|
38
|
+
*/
|
|
39
|
+
cutoffTimestamp: number;
|
|
40
|
+
/**
|
|
41
|
+
* The function that determines
|
|
42
|
+
* which resources should be considered *possibly* render-blocking.
|
|
43
|
+
*/
|
|
44
|
+
treatNodeAsRenderBlocking: (arg0: NetworkNode) => boolean;
|
|
45
|
+
/**
|
|
46
|
+
* The function that
|
|
47
|
+
* determines which CPU nodes should also be included in our blocking node IDs set,
|
|
48
|
+
* beyond what getRenderBlockingNodeData() already includes.
|
|
49
|
+
*/
|
|
50
|
+
additionalCpuNodesToTreatAsRenderBlocking?: ((arg0: CPUNode) => boolean) | undefined;
|
|
51
|
+
}): {
|
|
30
52
|
definitelyNotRenderBlockingScriptUrls: Set<string>;
|
|
31
|
-
|
|
53
|
+
renderBlockingCpuNodeIds: Set<string>;
|
|
32
54
|
};
|
|
33
55
|
/**
|
|
34
56
|
* This function computes the graph required for the first paint of interest.
|
|
35
57
|
*
|
|
36
58
|
* @param {Node} dependencyGraph
|
|
37
|
-
* @param {
|
|
38
|
-
* paint of interest. Typically this is First Contentful Paint or First Meaningful Paint.
|
|
39
|
-
* @param {function(NetworkNode):boolean} blockingResourcesFilter The function that determines which resources
|
|
40
|
-
* should be considered *possibly* render-blocking.
|
|
41
|
-
* @param {(function(CPUNode):boolean)=} extraBlockingCpuNodesToIncludeFilter The function that determines which CPU nodes
|
|
42
|
-
* should also be included in our blocking node IDs set.
|
|
59
|
+
* @param {FirstPaintBasedGraphOpts} opts
|
|
43
60
|
* @return {Node}
|
|
44
61
|
*/
|
|
45
|
-
static getFirstPaintBasedGraph(dependencyGraph: Node,
|
|
62
|
+
static getFirstPaintBasedGraph(dependencyGraph: Node, { cutoffTimestamp, treatNodeAsRenderBlocking, additionalCpuNodesToTreatAsRenderBlocking }: {
|
|
63
|
+
/**
|
|
64
|
+
* The timestamp used to filter out tasks that occured after
|
|
65
|
+
* our paint of interest. Typically this is First Contentful Paint or First Meaningful Paint.
|
|
66
|
+
*/
|
|
67
|
+
cutoffTimestamp: number;
|
|
68
|
+
/**
|
|
69
|
+
* The function that determines
|
|
70
|
+
* which resources should be considered *possibly* render-blocking.
|
|
71
|
+
*/
|
|
72
|
+
treatNodeAsRenderBlocking: (arg0: NetworkNode) => boolean;
|
|
73
|
+
/**
|
|
74
|
+
* The function that
|
|
75
|
+
* determines which CPU nodes should also be included in our blocking node IDs set,
|
|
76
|
+
* beyond what getRenderBlockingNodeData() already includes.
|
|
77
|
+
*/
|
|
78
|
+
additionalCpuNodesToTreatAsRenderBlocking?: ((arg0: CPUNode) => boolean) | undefined;
|
|
79
|
+
}): Node;
|
|
46
80
|
}
|
|
47
81
|
import { LanternMetric } from './lantern-metric.js';
|
|
48
82
|
//# sourceMappingURL=lantern-first-contentful-paint.d.ts.map
|