lighthouse 12.5.1-dev.20250424 → 12.5.1-dev.20250426

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.
Files changed (76) hide show
  1. package/core/audits/insights/cache-insight.js +5 -6
  2. package/core/audits/insights/cls-culprits-insight.js +2 -2
  3. package/core/audits/insights/duplicated-javascript-insight.d.ts +13 -0
  4. package/core/audits/insights/duplicated-javascript-insight.js +35 -10
  5. package/core/audits/insights/insight-audit.d.ts +11 -2
  6. package/core/audits/insights/insight-audit.js +26 -5
  7. package/core/audits/insights/slow-css-selector-insight.js +2 -0
  8. package/core/audits/insights/third-parties-insight.d.ts +3 -2
  9. package/core/audits/insights/third-parties-insight.js +23 -22
  10. package/core/audits/layout-shifts.js +2 -2
  11. package/core/audits/third-party-cookies.js +1 -1
  12. package/core/config/default-config.js +0 -2
  13. package/core/config/experimental-config.js +0 -1
  14. package/core/gather/gatherers/trace-elements.js +3 -3
  15. package/dist/report/bundle.esm.js +50 -10
  16. package/dist/report/flow.js +53 -13
  17. package/dist/report/standalone.js +51 -11
  18. package/flow-report/src/i18n/i18n.d.ts +6 -2
  19. package/package.json +5 -3
  20. package/report/assets/styles.css +40 -0
  21. package/report/assets/templates.html +0 -1
  22. package/report/clients/standalone.js +6 -4
  23. package/report/renderer/components.js +2 -8
  24. package/report/renderer/performance-category-renderer.d.ts +26 -0
  25. package/report/renderer/performance-category-renderer.js +107 -0
  26. package/report/renderer/report-utils.d.ts +3 -1
  27. package/report/renderer/report-utils.js +7 -2
  28. package/report/renderer/topbar-features.js +1 -9
  29. package/shared/localization/locales/ar-XB.json +0 -3
  30. package/shared/localization/locales/ar.json +0 -3
  31. package/shared/localization/locales/bg.json +0 -3
  32. package/shared/localization/locales/ca.json +0 -3
  33. package/shared/localization/locales/cs.json +0 -3
  34. package/shared/localization/locales/da.json +0 -3
  35. package/shared/localization/locales/de.json +0 -3
  36. package/shared/localization/locales/el.json +0 -3
  37. package/shared/localization/locales/en-GB.json +0 -3
  38. package/shared/localization/locales/en-US.json +13 -4
  39. package/shared/localization/locales/en-XA.json +0 -3
  40. package/shared/localization/locales/en-XL.json +13 -4
  41. package/shared/localization/locales/es-419.json +0 -3
  42. package/shared/localization/locales/es.json +0 -3
  43. package/shared/localization/locales/fi.json +0 -3
  44. package/shared/localization/locales/fil.json +0 -3
  45. package/shared/localization/locales/fr.json +0 -3
  46. package/shared/localization/locales/he.json +0 -3
  47. package/shared/localization/locales/hi.json +0 -3
  48. package/shared/localization/locales/hr.json +0 -3
  49. package/shared/localization/locales/hu.json +0 -3
  50. package/shared/localization/locales/id.json +0 -3
  51. package/shared/localization/locales/it.json +0 -3
  52. package/shared/localization/locales/ja.json +0 -3
  53. package/shared/localization/locales/ko.json +0 -3
  54. package/shared/localization/locales/lt.json +0 -3
  55. package/shared/localization/locales/lv.json +0 -3
  56. package/shared/localization/locales/nl.json +0 -3
  57. package/shared/localization/locales/no.json +0 -3
  58. package/shared/localization/locales/pl.json +0 -3
  59. package/shared/localization/locales/pt-PT.json +0 -3
  60. package/shared/localization/locales/pt.json +0 -3
  61. package/shared/localization/locales/ro.json +0 -3
  62. package/shared/localization/locales/ru.json +0 -3
  63. package/shared/localization/locales/sk.json +0 -3
  64. package/shared/localization/locales/sl.json +0 -3
  65. package/shared/localization/locales/sr-Latn.json +0 -3
  66. package/shared/localization/locales/sr.json +0 -3
  67. package/shared/localization/locales/sv.json +0 -3
  68. package/shared/localization/locales/ta.json +0 -3
  69. package/shared/localization/locales/te.json +0 -3
  70. package/shared/localization/locales/th.json +0 -3
  71. package/shared/localization/locales/tr.json +0 -3
  72. package/shared/localization/locales/uk.json +0 -3
  73. package/shared/localization/locales/vi.json +0 -3
  74. package/shared/localization/locales/zh-HK.json +0 -3
  75. package/shared/localization/locales/zh-TW.json +0 -3
  76. package/shared/localization/locales/zh.json +0 -3
@@ -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: 'totalBytes', valueType: 'bytes', label: str_(i18n.UIStrings.columnTransferSize), displayUnit: 'kb', granularity: 1},
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
- totalBytes: value.request.args.data.encodedDataLength,
53
+ wastedBytes: value.wastedBytes,
55
54
  }));
56
55
  return Audit.makeTableDetails(headings, items, {
57
- sortedBy: ['totalBytes'],
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 backendNodeId of culprits.unsizedImages) {
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, makeNodeItemForNodeId} from './insight-audit.js';
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', 'TraceElements', 'SourceMaps'],
30
- // TODO: enable when implemented.
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
- /** @type {LH.Audit.Details.Table['items']} */
49
- const items = [
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.insights.get(key);
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
  }
@@ -1,5 +1,7 @@
1
1
  /* eslint-disable no-unused-vars */ // TODO: remove once implemented.
2
2
 
3
+ // TODO: currently disabled via core/scripts/generate-insight-audits.js. Ignore for now.
4
+
3
5
  /**
4
6
  * @license
5
7
  * Copyright 2025 Google LLC
@@ -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/insights/ThirdParties.js').ThirdPartiesInsightModel} insight
17
- * @return {Array<URLSummary>}
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/insights/ThirdParties.js').ThirdPartiesInsightModel} insight
42
- * @return {Array<URLSummary>}
42
+ * @param {import('@paulirish/trace_engine/models/trace/extras/ThirdParties.js').URLSummary[]} urlSummaries
43
+ * @return {URLSummary[]}
43
44
  */
44
- // static makeSubItems(entity, insight) {
45
- // const urls = [...insight.urlsByEntity.get(entity) ?? []];
46
- // return urls
47
- // .map(url => ({
48
- // url,
49
- // mainThreadTime: 0,
50
- // transferSize: 0,
51
- // ...insight.summaryByUrl.get(url),
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
- // }
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 thirdPartySummaries = insight.summaries
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
- // TODO: fix this! we lost all the data ... https://chromium-review.googlesource.com/c/devtools/devtools-frontend/+/6341914
83
- // subItems: {
84
- // type: /** @type {const} */ ('subitems'),
85
- // items: ThirdPartiesInsight.makeSubItems(summary.entity, insight),
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 backendNodeId of rootCauses.unsizedImages) {
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),
@@ -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: 'Chrome is moving towards a new experience that allows users to choose to browse without third-party cookies. [Learn more about third-party cookies](https://developers.google.com/privacy-sandbox/cookies).',
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: would be better if unsizedImages was `Array<{nodeId}>`.
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}));