lighthouse 12.5.1-dev.20250423 → 12.5.1-dev.20250425
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/insights/cache-insight.js +5 -6
- package/core/audits/insights/cls-culprits-insight.js +2 -2
- package/core/audits/insights/duplicated-javascript-insight.d.ts +13 -0
- package/core/audits/insights/duplicated-javascript-insight.js +35 -10
- package/core/audits/insights/insight-audit.d.ts +11 -2
- package/core/audits/insights/insight-audit.js +26 -5
- package/core/audits/insights/slow-css-selector-insight.js +2 -0
- package/core/audits/insights/third-parties-insight.d.ts +3 -2
- package/core/audits/insights/third-parties-insight.js +23 -22
- package/core/audits/layout-shifts.js +2 -2
- package/core/audits/script-treemap-data.js +31 -1
- package/core/audits/third-party-cookies.js +1 -1
- package/core/config/default-config.js +0 -2
- package/core/config/experimental-config.js +0 -1
- package/core/gather/gatherers/trace-elements.js +3 -3
- package/core/scoring.d.ts +1 -0
- package/dist/report/bundle.esm.js +50 -10
- package/dist/report/flow.js +53 -13
- package/dist/report/standalone.js +51 -11
- package/flow-report/src/i18n/i18n.d.ts +6 -2
- package/package.json +5 -3
- package/report/assets/styles.css +40 -0
- package/report/assets/templates.html +0 -1
- package/report/clients/standalone.js +6 -4
- package/report/renderer/components.js +2 -8
- package/report/renderer/performance-category-renderer.d.ts +26 -0
- package/report/renderer/performance-category-renderer.js +107 -0
- package/report/renderer/report-utils.d.ts +3 -1
- package/report/renderer/report-utils.js +7 -2
- package/report/renderer/topbar-features.js +1 -9
- package/shared/localization/locales/ar-XB.json +0 -3
- package/shared/localization/locales/ar.json +0 -3
- package/shared/localization/locales/bg.json +0 -3
- package/shared/localization/locales/ca.json +0 -3
- package/shared/localization/locales/cs.json +0 -3
- package/shared/localization/locales/da.json +0 -3
- package/shared/localization/locales/de.json +0 -3
- package/shared/localization/locales/el.json +0 -3
- package/shared/localization/locales/en-GB.json +0 -3
- package/shared/localization/locales/en-US.json +16 -4
- package/shared/localization/locales/en-XA.json +0 -3
- package/shared/localization/locales/en-XL.json +16 -4
- package/shared/localization/locales/es-419.json +0 -3
- package/shared/localization/locales/es.json +0 -3
- package/shared/localization/locales/fi.json +0 -3
- package/shared/localization/locales/fil.json +0 -3
- package/shared/localization/locales/fr.json +0 -3
- package/shared/localization/locales/he.json +0 -3
- package/shared/localization/locales/hi.json +0 -3
- package/shared/localization/locales/hr.json +0 -3
- package/shared/localization/locales/hu.json +0 -3
- package/shared/localization/locales/id.json +0 -3
- package/shared/localization/locales/it.json +0 -3
- package/shared/localization/locales/ja.json +0 -3
- package/shared/localization/locales/ko.json +0 -3
- package/shared/localization/locales/lt.json +0 -3
- package/shared/localization/locales/lv.json +0 -3
- package/shared/localization/locales/nl.json +0 -3
- package/shared/localization/locales/no.json +0 -3
- package/shared/localization/locales/pl.json +0 -3
- package/shared/localization/locales/pt-PT.json +0 -3
- package/shared/localization/locales/pt.json +0 -3
- package/shared/localization/locales/ro.json +0 -3
- package/shared/localization/locales/ru.json +0 -3
- package/shared/localization/locales/sk.json +0 -3
- package/shared/localization/locales/sl.json +0 -3
- package/shared/localization/locales/sr-Latn.json +0 -3
- package/shared/localization/locales/sr.json +0 -3
- package/shared/localization/locales/sv.json +0 -3
- package/shared/localization/locales/ta.json +0 -3
- package/shared/localization/locales/te.json +0 -3
- package/shared/localization/locales/th.json +0 -3
- package/shared/localization/locales/tr.json +0 -3
- package/shared/localization/locales/uk.json +0 -3
- package/shared/localization/locales/vi.json +0 -3
- package/shared/localization/locales/zh-HK.json +0 -3
- package/shared/localization/locales/zh-TW.json +0 -3
- package/shared/localization/locales/zh.json +0 -3
- package/types/lhr/treemap.d.ts +3 -0
|
@@ -41,20 +41,19 @@ class CacheInsight extends Audit {
|
|
|
41
41
|
/* eslint-disable max-len */
|
|
42
42
|
{key: 'url', valueType: 'url', label: str_(UIStrings.requestColumn)},
|
|
43
43
|
{key: 'cacheLifetimeMs', valueType: 'ms', label: str_(UIStrings.cacheTTL), displayUnit: 'duration'},
|
|
44
|
-
{key: '
|
|
44
|
+
{key: 'wastedBytes', valueType: 'bytes', label: str_(i18n.UIStrings.columnTransferSize), displayUnit: 'kb', granularity: 1},
|
|
45
45
|
/* eslint-enable max-len */
|
|
46
46
|
];
|
|
47
|
-
// TODO: this should be sorting in the model
|
|
48
|
-
const values = insight.requests.sort((a, b) =>
|
|
49
|
-
b.request.args.data.decodedBodyLength - a.request.args.data.decodedBodyLength);
|
|
47
|
+
// TODO: this should be the sorting in the model (instead it sorts by transfer size...)
|
|
48
|
+
const values = insight.requests.sort((a, b) => b.wastedBytes - a.wastedBytes);
|
|
50
49
|
/** @type {LH.Audit.Details.Table['items']} */
|
|
51
50
|
const items = values.map(value => ({
|
|
52
51
|
url: value.request.args.data.url,
|
|
53
52
|
cacheLifetimeMs: value.ttl * 1000,
|
|
54
|
-
|
|
53
|
+
wastedBytes: value.wastedBytes,
|
|
55
54
|
}));
|
|
56
55
|
return Audit.makeTableDetails(headings, items, {
|
|
57
|
-
sortedBy: ['
|
|
56
|
+
sortedBy: ['wastedBytes'],
|
|
58
57
|
skipSumming: ['cacheLifetimeMs'],
|
|
59
58
|
});
|
|
60
59
|
});
|
|
@@ -58,9 +58,9 @@ class CLSCulpritsInsight extends Audit {
|
|
|
58
58
|
|
|
59
59
|
/** @type {SubItem[]} */
|
|
60
60
|
const subItems = [];
|
|
61
|
-
for (const
|
|
61
|
+
for (const unsizedImage of culprits.unsizedImages) {
|
|
62
62
|
subItems.push({
|
|
63
|
-
extra: makeNodeItemForNodeId(TraceElements, backendNodeId),
|
|
63
|
+
extra: makeNodeItemForNodeId(TraceElements, unsizedImage.backendNodeId),
|
|
64
64
|
cause: insightStr_(InsightUIStrings.unsizedImages),
|
|
65
65
|
});
|
|
66
66
|
}
|
|
@@ -1,4 +1,17 @@
|
|
|
1
1
|
export default DuplicatedJavaScriptInsight;
|
|
2
|
+
export type Item = LH.Audit.Details.TableItem & {
|
|
3
|
+
source: string;
|
|
4
|
+
subItems: {
|
|
5
|
+
type: "subitems";
|
|
6
|
+
items: SubItem[];
|
|
7
|
+
};
|
|
8
|
+
};
|
|
9
|
+
export type SubItem = {
|
|
10
|
+
url: string;
|
|
11
|
+
sourceTransferBytes: number | LH.Audit.Details.TextValue;
|
|
12
|
+
};
|
|
13
|
+
/** @typedef {LH.Audit.Details.TableItem & {source: string, subItems: {type: 'subitems', items: SubItem[]}}} Item */
|
|
14
|
+
/** @typedef {{url: string, sourceTransferBytes: number|LH.Audit.Details.TextValue}} SubItem */
|
|
2
15
|
declare class DuplicatedJavaScriptInsight extends Audit {
|
|
3
16
|
/**
|
|
4
17
|
* @param {LH.Artifacts} artifacts
|
|
@@ -1,5 +1,3 @@
|
|
|
1
|
-
/* eslint-disable no-unused-vars */ // TODO: remove once implemented.
|
|
2
|
-
|
|
3
1
|
/**
|
|
4
2
|
* @license
|
|
5
3
|
* Copyright 2025 Google LLC
|
|
@@ -10,11 +8,14 @@ import {UIStrings} from '@paulirish/trace_engine/models/trace/insights/Duplicate
|
|
|
10
8
|
|
|
11
9
|
import {Audit} from '../audit.js';
|
|
12
10
|
import * as i18n from '../../lib/i18n/i18n.js';
|
|
13
|
-
import {adaptInsightToAuditProduct
|
|
11
|
+
import {adaptInsightToAuditProduct} from './insight-audit.js';
|
|
14
12
|
|
|
15
13
|
// eslint-disable-next-line max-len
|
|
16
14
|
const str_ = i18n.createIcuMessageFn('node_modules/@paulirish/trace_engine/models/trace/insights/DuplicatedJavaScript.js', UIStrings);
|
|
17
15
|
|
|
16
|
+
/** @typedef {LH.Audit.Details.TableItem & {source: string, subItems: {type: 'subitems', items: SubItem[]}}} Item */
|
|
17
|
+
/** @typedef {{url: string, sourceTransferBytes: number|LH.Audit.Details.TextValue}} SubItem */
|
|
18
|
+
|
|
18
19
|
class DuplicatedJavaScriptInsight extends Audit {
|
|
19
20
|
/**
|
|
20
21
|
* @return {LH.Audit.Meta}
|
|
@@ -26,9 +27,8 @@ class DuplicatedJavaScriptInsight extends Audit {
|
|
|
26
27
|
failureTitle: str_(UIStrings.title),
|
|
27
28
|
description: str_(UIStrings.description),
|
|
28
29
|
guidanceLevel: 2,
|
|
29
|
-
requiredArtifacts: ['Trace', '
|
|
30
|
-
|
|
31
|
-
// replacesAudits: ['duplicated-javascript'],
|
|
30
|
+
requiredArtifacts: ['Trace', 'SourceMaps'],
|
|
31
|
+
replacesAudits: ['duplicated-javascript'],
|
|
32
32
|
};
|
|
33
33
|
}
|
|
34
34
|
|
|
@@ -38,16 +38,41 @@ class DuplicatedJavaScriptInsight extends Audit {
|
|
|
38
38
|
* @return {Promise<LH.Audit.Product>}
|
|
39
39
|
*/
|
|
40
40
|
static async audit(artifacts, context) {
|
|
41
|
-
// TODO: implement.
|
|
42
41
|
return adaptInsightToAuditProduct(artifacts, context, 'DuplicatedJavaScript', (insight) => {
|
|
43
42
|
/** @type {LH.Audit.Details.Table['headings']} */
|
|
44
43
|
const headings = [
|
|
45
44
|
/* eslint-disable max-len */
|
|
45
|
+
{key: 'source', valueType: 'code', subItemsHeading: {key: 'url', valueType: 'url'}, label: str_(i18n.UIStrings.columnSource)},
|
|
46
|
+
{key: 'wastedBytes', valueType: 'bytes', subItemsHeading: {key: 'sourceTransferBytes'}, granularity: 10, label: str_(UIStrings.columnDuplicatedBytes)},
|
|
46
47
|
/* eslint-enable max-len */
|
|
47
48
|
];
|
|
48
|
-
|
|
49
|
-
const
|
|
50
|
-
|
|
49
|
+
|
|
50
|
+
const entries = [...insight.duplicationGroupedByNodeModules.entries()].slice(0, 10);
|
|
51
|
+
|
|
52
|
+
/** @type {Item[]} */
|
|
53
|
+
const items = entries.map(([source, data]) => {
|
|
54
|
+
/** @type {Item} */
|
|
55
|
+
const item = {
|
|
56
|
+
source,
|
|
57
|
+
wastedBytes: data.estimatedDuplicateBytes,
|
|
58
|
+
subItems: {
|
|
59
|
+
type: 'subitems',
|
|
60
|
+
items: [],
|
|
61
|
+
},
|
|
62
|
+
};
|
|
63
|
+
|
|
64
|
+
for (const [index, {script, attributedSize}] of data.duplicates.entries()) {
|
|
65
|
+
/** @type {SubItem} */
|
|
66
|
+
const subItem = {
|
|
67
|
+
url: script.url ?? '',
|
|
68
|
+
sourceTransferBytes: index === 0 ? {type: 'text', value: '--'} : attributedSize,
|
|
69
|
+
};
|
|
70
|
+
item.subItems.items.push(subItem);
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
return item;
|
|
74
|
+
});
|
|
75
|
+
|
|
51
76
|
return Audit.makeTableDetails(headings, items);
|
|
52
77
|
});
|
|
53
78
|
}
|
|
@@ -1,12 +1,21 @@
|
|
|
1
|
+
export type CreateDetailsExtras = {
|
|
2
|
+
insights: import("@paulirish/trace_engine/models/trace/insights/types.js").InsightSet;
|
|
3
|
+
parsedTrace: LH.Artifacts.TraceEngineResult["data"];
|
|
4
|
+
};
|
|
5
|
+
/**
|
|
6
|
+
* @typedef CreateDetailsExtras
|
|
7
|
+
* @property {import('@paulirish/trace_engine/models/trace/insights/types.js').InsightSet} insights
|
|
8
|
+
* @property {LH.Artifacts.TraceEngineResult['data']} parsedTrace
|
|
9
|
+
*/
|
|
1
10
|
/**
|
|
2
11
|
* @param {LH.Artifacts} artifacts
|
|
3
12
|
* @param {LH.Audit.Context} context
|
|
4
13
|
* @param {T} insightName
|
|
5
|
-
* @param {(insight: import('@paulirish/trace_engine/models/trace/insights/types.js').InsightModels[T]) => LH.Audit.Details|undefined} createDetails
|
|
14
|
+
* @param {(insight: import('@paulirish/trace_engine/models/trace/insights/types.js').InsightModels[T], extras: CreateDetailsExtras) => LH.Audit.Details|undefined} createDetails
|
|
6
15
|
* @template {keyof import('@paulirish/trace_engine/models/trace/insights/types.js').InsightModelsType} T
|
|
7
16
|
* @return {Promise<LH.Audit.Product>}
|
|
8
17
|
*/
|
|
9
|
-
export function adaptInsightToAuditProduct<T extends keyof import("@paulirish/trace_engine/models/trace/insights/types.js").InsightModelsType>(artifacts: LH.Artifacts, context: LH.Audit.Context, insightName: T, createDetails: (insight: import("@paulirish/trace_engine/models/trace/insights/types.js").InsightModels[T]) => LH.Audit.Details | undefined): Promise<LH.Audit.Product>;
|
|
18
|
+
export function adaptInsightToAuditProduct<T extends keyof import("@paulirish/trace_engine/models/trace/insights/types.js").InsightModelsType>(artifacts: LH.Artifacts, context: LH.Audit.Context, insightName: T, createDetails: (insight: import("@paulirish/trace_engine/models/trace/insights/types.js").InsightModels[T], extras: CreateDetailsExtras) => LH.Audit.Details | undefined): Promise<LH.Audit.Product>;
|
|
10
19
|
/**
|
|
11
20
|
* @param {LH.Artifacts.TraceElement[]} traceElements
|
|
12
21
|
* @param {number|null|undefined} nodeId
|
|
@@ -9,11 +9,14 @@ import {NO_NAVIGATION} from '@paulirish/trace_engine/models/trace/types/TraceEve
|
|
|
9
9
|
import {ProcessedTrace} from '../../computed/processed-trace.js';
|
|
10
10
|
import {TraceEngineResult} from '../../computed/trace-engine-result.js';
|
|
11
11
|
import {Audit} from '../audit.js';
|
|
12
|
+
import * as i18n from '../../lib/i18n/i18n.js';
|
|
13
|
+
|
|
14
|
+
const str_ = i18n.createIcuMessageFn(import.meta.url, {});
|
|
12
15
|
|
|
13
16
|
/**
|
|
14
17
|
* @param {LH.Artifacts} artifacts
|
|
15
18
|
* @param {LH.Audit.Context} context
|
|
16
|
-
* @return {Promise<import('@paulirish/trace_engine/models/trace/insights/types.js').InsightSet|undefined>}
|
|
19
|
+
* @return {Promise<{insights: import('@paulirish/trace_engine/models/trace/insights/types.js').InsightSet|undefined, parsedTrace: LH.Artifacts.TraceEngineResult['data']}>}
|
|
17
20
|
*/
|
|
18
21
|
async function getInsightSet(artifacts, context) {
|
|
19
22
|
const settings = context.settings;
|
|
@@ -24,20 +27,27 @@ async function getInsightSet(artifacts, context) {
|
|
|
24
27
|
|
|
25
28
|
const navigationId = processedTrace.timeOriginEvt.args.data?.navigationId;
|
|
26
29
|
const key = navigationId ?? NO_NAVIGATION;
|
|
30
|
+
const insights = traceEngineResult.insights.get(key);
|
|
27
31
|
|
|
28
|
-
return traceEngineResult.
|
|
32
|
+
return {insights, parsedTrace: traceEngineResult.data};
|
|
29
33
|
}
|
|
30
34
|
|
|
35
|
+
/**
|
|
36
|
+
* @typedef CreateDetailsExtras
|
|
37
|
+
* @property {import('@paulirish/trace_engine/models/trace/insights/types.js').InsightSet} insights
|
|
38
|
+
* @property {LH.Artifacts.TraceEngineResult['data']} parsedTrace
|
|
39
|
+
*/
|
|
40
|
+
|
|
31
41
|
/**
|
|
32
42
|
* @param {LH.Artifacts} artifacts
|
|
33
43
|
* @param {LH.Audit.Context} context
|
|
34
44
|
* @param {T} insightName
|
|
35
|
-
* @param {(insight: import('@paulirish/trace_engine/models/trace/insights/types.js').InsightModels[T]) => LH.Audit.Details|undefined} createDetails
|
|
45
|
+
* @param {(insight: import('@paulirish/trace_engine/models/trace/insights/types.js').InsightModels[T], extras: CreateDetailsExtras) => LH.Audit.Details|undefined} createDetails
|
|
36
46
|
* @template {keyof import('@paulirish/trace_engine/models/trace/insights/types.js').InsightModelsType} T
|
|
37
47
|
* @return {Promise<LH.Audit.Product>}
|
|
38
48
|
*/
|
|
39
49
|
async function adaptInsightToAuditProduct(artifacts, context, insightName, createDetails) {
|
|
40
|
-
const insights = await getInsightSet(artifacts, context);
|
|
50
|
+
const {insights, parsedTrace} = await getInsightSet(artifacts, context);
|
|
41
51
|
if (!insights) {
|
|
42
52
|
return {
|
|
43
53
|
scoreDisplayMode: Audit.SCORING_MODES.NOT_APPLICABLE,
|
|
@@ -54,7 +64,10 @@ async function adaptInsightToAuditProduct(artifacts, context, insightName, creat
|
|
|
54
64
|
};
|
|
55
65
|
}
|
|
56
66
|
|
|
57
|
-
const details = createDetails(insight
|
|
67
|
+
const details = createDetails(insight, {
|
|
68
|
+
parsedTrace,
|
|
69
|
+
insights,
|
|
70
|
+
});
|
|
58
71
|
if (!details || (details.type === 'table' && details.headings.length === 0)) {
|
|
59
72
|
return {
|
|
60
73
|
scoreDisplayMode: Audit.SCORING_MODES.NOT_APPLICABLE,
|
|
@@ -73,6 +86,13 @@ async function adaptInsightToAuditProduct(artifacts, context, insightName, creat
|
|
|
73
86
|
metricSavings = {...metricSavings, LCP: /** @type {any} */ (0)};
|
|
74
87
|
}
|
|
75
88
|
|
|
89
|
+
// TODO: consider adding a `estimatedSavingsText` to InsightModel, which can capture
|
|
90
|
+
// the exact i18n string used by RPP; and include the same est. timing savings.
|
|
91
|
+
let displayValue;
|
|
92
|
+
if (insight.wastedBytes) {
|
|
93
|
+
displayValue = str_(i18n.UIStrings.displayValueByteSavings, {wastedBytes: insight.wastedBytes});
|
|
94
|
+
}
|
|
95
|
+
|
|
76
96
|
let score;
|
|
77
97
|
let scoreDisplayMode;
|
|
78
98
|
if (insight.state === 'fail' || insight.state === 'pass') {
|
|
@@ -89,6 +109,7 @@ async function adaptInsightToAuditProduct(artifacts, context, insightName, creat
|
|
|
89
109
|
score,
|
|
90
110
|
metricSavings,
|
|
91
111
|
warnings: insight.warnings,
|
|
112
|
+
displayValue,
|
|
92
113
|
details,
|
|
93
114
|
};
|
|
94
115
|
}
|
|
@@ -13,9 +13,10 @@ export type URLSummary = {
|
|
|
13
13
|
declare class ThirdPartiesInsight extends Audit {
|
|
14
14
|
/**
|
|
15
15
|
* @param {LH.Artifacts.Entity} entity
|
|
16
|
-
* @param {import('@paulirish/trace_engine/models/trace/
|
|
17
|
-
* @return {
|
|
16
|
+
* @param {import('@paulirish/trace_engine/models/trace/extras/ThirdParties.js').URLSummary[]} urlSummaries
|
|
17
|
+
* @return {URLSummary[]}
|
|
18
18
|
*/
|
|
19
|
+
static makeSubItems(entity: LH.Artifacts.Entity, urlSummaries: import("@paulirish/trace_engine/models/trace/extras/ThirdParties.js").URLSummary[]): URLSummary[];
|
|
19
20
|
/**
|
|
20
21
|
* @param {LH.Artifacts} artifacts
|
|
21
22
|
* @param {LH.Audit.Context} context
|
|
@@ -5,6 +5,7 @@
|
|
|
5
5
|
*/
|
|
6
6
|
|
|
7
7
|
import {UIStrings} from '@paulirish/trace_engine/models/trace/insights/ThirdParties.js';
|
|
8
|
+
import {summarizeByURL} from '@paulirish/trace_engine/models/trace/extras/ThirdParties.js';
|
|
8
9
|
|
|
9
10
|
import {Audit} from '../audit.js';
|
|
10
11
|
import * as i18n from '../../lib/i18n/i18n.js';
|
|
@@ -38,21 +39,20 @@ class ThirdPartiesInsight extends Audit {
|
|
|
38
39
|
|
|
39
40
|
/**
|
|
40
41
|
* @param {LH.Artifacts.Entity} entity
|
|
41
|
-
* @param {import('@paulirish/trace_engine/models/trace/
|
|
42
|
-
* @return {
|
|
42
|
+
* @param {import('@paulirish/trace_engine/models/trace/extras/ThirdParties.js').URLSummary[]} urlSummaries
|
|
43
|
+
* @return {URLSummary[]}
|
|
43
44
|
*/
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
// }
|
|
45
|
+
static makeSubItems(entity, urlSummaries) {
|
|
46
|
+
urlSummaries = urlSummaries.filter(s => s.entity === entity);
|
|
47
|
+
return urlSummaries.filter(s => s.entity === entity)
|
|
48
|
+
.map(s => ({
|
|
49
|
+
url: s.url,
|
|
50
|
+
mainThreadTime: s.mainThreadTime,
|
|
51
|
+
transferSize: s.transferSize,
|
|
52
|
+
}))
|
|
53
|
+
// Sort by main thread time first, then transfer size to break ties.
|
|
54
|
+
.sort((a, b) => (b.mainThreadTime - a.mainThreadTime) || (b.transferSize - a.transferSize));
|
|
55
|
+
}
|
|
56
56
|
|
|
57
57
|
/**
|
|
58
58
|
* @param {LH.Artifacts} artifacts
|
|
@@ -60,8 +60,10 @@ class ThirdPartiesInsight extends Audit {
|
|
|
60
60
|
* @return {Promise<LH.Audit.Product>}
|
|
61
61
|
*/
|
|
62
62
|
static async audit(artifacts, context) {
|
|
63
|
-
return adaptInsightToAuditProduct(artifacts, context, 'ThirdParties', (insight) => {
|
|
64
|
-
const
|
|
63
|
+
return adaptInsightToAuditProduct(artifacts, context, 'ThirdParties', (insight, extras) => {
|
|
64
|
+
const urlSummaries = summarizeByURL(extras.parsedTrace, extras.insights.bounds);
|
|
65
|
+
|
|
66
|
+
const thirdPartySummaries = insight.entitySummaries
|
|
65
67
|
.filter(summary => summary.entity !== insight.firstPartyEntity || null)
|
|
66
68
|
.sort((a, b) => b.mainThreadTime - a.mainThreadTime);
|
|
67
69
|
|
|
@@ -77,13 +79,12 @@ class ThirdPartiesInsight extends Audit {
|
|
|
77
79
|
const items = thirdPartySummaries.map((summary) => {
|
|
78
80
|
return {
|
|
79
81
|
entity: summary.entity.name,
|
|
80
|
-
transferSize: summary.transferSize,
|
|
81
82
|
mainThreadTime: summary.mainThreadTime,
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
83
|
+
transferSize: summary.transferSize,
|
|
84
|
+
subItems: {
|
|
85
|
+
type: /** @type {const} */ ('subitems'),
|
|
86
|
+
items: ThirdPartiesInsight.makeSubItems(summary.entity, urlSummaries),
|
|
87
|
+
},
|
|
87
88
|
};
|
|
88
89
|
});
|
|
89
90
|
return Audit.makeTableDetails(headings, items, {isEntityGrouped: true});
|
|
@@ -97,9 +97,9 @@ class LayoutShifts extends Audit {
|
|
|
97
97
|
/** @type {SubItem[]} */
|
|
98
98
|
const subItems = [];
|
|
99
99
|
if (rootCauses) {
|
|
100
|
-
for (const
|
|
100
|
+
for (const unsizedImage of rootCauses.unsizedImages) {
|
|
101
101
|
const element = artifacts.TraceElements.find(
|
|
102
|
-
t => t.traceEventType === 'trace-engine' && t.nodeId === backendNodeId);
|
|
102
|
+
t => t.traceEventType === 'trace-engine' && t.nodeId === unsizedImage.backendNodeId);
|
|
103
103
|
subItems.push({
|
|
104
104
|
extra: element ? Audit.makeNodeItem(element.node) : undefined,
|
|
105
105
|
cause: str_(UIStrings.rootCauseUnsizedMedia),
|
|
@@ -16,9 +16,10 @@
|
|
|
16
16
|
|
|
17
17
|
import {Audit} from './audit.js';
|
|
18
18
|
import {JSBundles} from '../computed/js-bundles.js';
|
|
19
|
+
import {NetworkRecords} from '../computed/network-records.js';
|
|
19
20
|
import {UnusedJavascriptSummary} from '../computed/unused-javascript-summary.js';
|
|
20
21
|
import {ModuleDuplication} from '../computed/module-duplication.js';
|
|
21
|
-
import {isInline} from '../lib/script-helpers.js';
|
|
22
|
+
import {getRequestForScript, isInline} from '../lib/script-helpers.js';
|
|
22
23
|
|
|
23
24
|
class ScriptTreemapDataAudit extends Audit {
|
|
24
25
|
/**
|
|
@@ -54,6 +55,7 @@ class ScriptTreemapDataAudit extends Audit {
|
|
|
54
55
|
return {
|
|
55
56
|
name,
|
|
56
57
|
resourceBytes: 0,
|
|
58
|
+
encodedBytes: undefined,
|
|
57
59
|
};
|
|
58
60
|
}
|
|
59
61
|
|
|
@@ -167,6 +169,9 @@ class ScriptTreemapDataAudit extends Audit {
|
|
|
167
169
|
* @return {Promise<LH.Treemap.Node[]>}
|
|
168
170
|
*/
|
|
169
171
|
static async makeNodes(artifacts, context) {
|
|
172
|
+
const devtoolsLog = artifacts.DevtoolsLog;
|
|
173
|
+
const networkRecords = await NetworkRecords.request(devtoolsLog, context);
|
|
174
|
+
|
|
170
175
|
/** @type {LH.Treemap.Node[]} */
|
|
171
176
|
const nodes = [];
|
|
172
177
|
/** @type {Map<string, LH.Treemap.Node>} */
|
|
@@ -234,6 +239,7 @@ class ScriptTreemapDataAudit extends Audit {
|
|
|
234
239
|
node = {
|
|
235
240
|
name,
|
|
236
241
|
resourceBytes: unusedJavascriptSummary?.totalBytes ?? script.length ?? 0,
|
|
242
|
+
encodedBytes: undefined,
|
|
237
243
|
unusedBytes: unusedJavascriptSummary?.wastedBytes,
|
|
238
244
|
};
|
|
239
245
|
}
|
|
@@ -246,6 +252,7 @@ class ScriptTreemapDataAudit extends Audit {
|
|
|
246
252
|
htmlNode = {
|
|
247
253
|
name,
|
|
248
254
|
resourceBytes: 0,
|
|
255
|
+
encodedBytes: undefined,
|
|
249
256
|
unusedBytes: undefined,
|
|
250
257
|
children: [],
|
|
251
258
|
};
|
|
@@ -261,6 +268,29 @@ class ScriptTreemapDataAudit extends Audit {
|
|
|
261
268
|
} else {
|
|
262
269
|
// Non-inline scripts each have their own top-level node.
|
|
263
270
|
nodes.push(node);
|
|
271
|
+
|
|
272
|
+
const networkRecord = getRequestForScript(networkRecords, script);
|
|
273
|
+
if (networkRecord) {
|
|
274
|
+
const bodyTransferSize =
|
|
275
|
+
networkRecord.transferSize - networkRecord.responseHeadersTransferSize;
|
|
276
|
+
node.encodedBytes = bodyTransferSize;
|
|
277
|
+
} else {
|
|
278
|
+
node.encodedBytes = node.resourceBytes;
|
|
279
|
+
}
|
|
280
|
+
}
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
// For the HTML nodes, set encodedBytes to be the size of all the inline
|
|
284
|
+
// scripts multiplied by the average compression ratio of the HTML document.
|
|
285
|
+
for (const [frameId, node] of htmlNodesByFrameId) {
|
|
286
|
+
const record =
|
|
287
|
+
networkRecords.find(r => r.resourceType === 'Document' && r.frameId === frameId);
|
|
288
|
+
if (record) {
|
|
289
|
+
const inlineScriptsPct = node.resourceBytes / record.resourceSize;
|
|
290
|
+
const bodyTransferSize = record.transferSize - record.responseHeadersTransferSize;
|
|
291
|
+
node.encodedBytes = Math.floor(bodyTransferSize * inlineScriptsPct);
|
|
292
|
+
} else {
|
|
293
|
+
node.encodedBytes = node.resourceBytes;
|
|
264
294
|
}
|
|
265
295
|
}
|
|
266
296
|
|
|
@@ -18,7 +18,7 @@ const UIStrings = {
|
|
|
18
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
19
|
failureTitle: 'Uses third-party cookies',
|
|
20
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: '
|
|
21
|
+
description: 'Third-party cookies may be blocked in some contexts. [Learn more about preparing for third-party cookie restrictions](https://privacysandbox.google.com/cookies/prepare/overview).',
|
|
22
22
|
/** [ICU Syntax] Label for the audit identifying the number of third-party cookies. */
|
|
23
23
|
displayValue: `{itemCount, plural,
|
|
24
24
|
=1 {1 cookie found}
|
|
@@ -326,7 +326,6 @@ const defaultConfig = {
|
|
|
326
326
|
'insights/modern-http-insight',
|
|
327
327
|
'insights/network-dependency-tree-insight',
|
|
328
328
|
'insights/render-blocking-insight',
|
|
329
|
-
'insights/slow-css-selector-insight',
|
|
330
329
|
'insights/third-parties-insight',
|
|
331
330
|
'insights/viewport-insight',
|
|
332
331
|
],
|
|
@@ -429,7 +428,6 @@ const defaultConfig = {
|
|
|
429
428
|
{id: 'modern-http-insight', weight: 0, group: 'hidden'},
|
|
430
429
|
{id: 'network-dependency-tree-insight', weight: 0, group: 'hidden'},
|
|
431
430
|
{id: 'render-blocking-insight', weight: 0, group: 'hidden'},
|
|
432
|
-
{id: 'slow-css-selector-insight', weight: 0, group: 'hidden'},
|
|
433
431
|
{id: 'third-parties-insight', weight: 0, group: 'hidden'},
|
|
434
432
|
{id: 'viewport-insight', weight: 0, group: 'hidden'},
|
|
435
433
|
|
|
@@ -42,7 +42,6 @@ const config = {
|
|
|
42
42
|
{id: 'modern-http-insight', weight: 0, group: 'insights'},
|
|
43
43
|
{id: 'network-dependency-tree-insight', weight: 0, group: 'insights'},
|
|
44
44
|
{id: 'render-blocking-insight', weight: 0, group: 'insights'},
|
|
45
|
-
{id: 'slow-css-selector-insight', weight: 0, group: 'insights'},
|
|
46
45
|
{id: 'third-parties-insight', weight: 0, group: 'insights'},
|
|
47
46
|
{id: 'viewport-insight', weight: 0, group: 'insights'},
|
|
48
47
|
],
|
|
@@ -132,15 +132,15 @@ class TraceElements extends BaseGatherer {
|
|
|
132
132
|
/** @type {number[]} */
|
|
133
133
|
const nodeIds = [];
|
|
134
134
|
recursiveObjectEnumerate(insightSet.model, (val, key) => {
|
|
135
|
-
const keys = ['nodeId', 'node_id'];
|
|
135
|
+
const keys = ['nodeId', 'node_id', 'backendNodeId'];
|
|
136
136
|
if (typeof val === 'number' && keys.includes(key)) {
|
|
137
137
|
nodeIds.push(val);
|
|
138
138
|
}
|
|
139
139
|
}, new Set());
|
|
140
140
|
|
|
141
|
-
// TODO:
|
|
141
|
+
// TODO: handle digging into Map in recursiveObjectEnumerate.
|
|
142
142
|
for (const shift of insightSet.model.CLSCulprits.shifts.values()) {
|
|
143
|
-
nodeIds.push(...shift.unsizedImages);
|
|
143
|
+
nodeIds.push(...shift.unsizedImages.map(s => s.backendNodeId));
|
|
144
144
|
}
|
|
145
145
|
|
|
146
146
|
return [...new Set(nodeIds)].map(id => ({nodeId: id}));
|
package/core/scoring.d.ts
CHANGED
|
@@ -79,6 +79,7 @@ export class ReportScoring {
|
|
|
79
79
|
nodes: {
|
|
80
80
|
name: string | import("./index.js").IcuMessage;
|
|
81
81
|
resourceBytes: number;
|
|
82
|
+
encodedBytes?: number | undefined;
|
|
82
83
|
unusedBytes?: number | undefined;
|
|
83
84
|
duplicatedNormalizedModuleName?: string | import("./index.js").IcuMessage | undefined;
|
|
84
85
|
children?: /*elided*/ any[] | undefined;
|