lighthouse 11.4.0-dev.20240103 → 11.4.0-dev.20240104

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 (41) hide show
  1. package/cli/test/smokehouse/core-tests.js +2 -0
  2. package/core/audits/audit.d.ts +5 -0
  3. package/core/audits/audit.js +42 -2
  4. package/core/audits/byte-efficiency/byte-efficiency-audit.js +1 -1
  5. package/core/audits/layout-shift-elements.js +1 -1
  6. package/core/audits/layout-shifts.d.ts +33 -0
  7. package/core/audits/layout-shifts.js +158 -0
  8. package/core/audits/viewport.js +10 -0
  9. package/core/computed/metrics/cumulative-layout-shift.d.ts +2 -2
  10. package/core/computed/trace-engine-result.d.ts +40 -0
  11. package/core/computed/trace-engine-result.js +69 -0
  12. package/core/computed/viewport-meta.d.ts +4 -0
  13. package/core/computed/viewport-meta.js +6 -1
  14. package/core/config/default-config.js +3 -0
  15. package/core/config/metrics-to-audits.js +1 -0
  16. package/core/gather/gatherers/root-causes.d.ts +20 -0
  17. package/core/gather/gatherers/root-causes.js +133 -0
  18. package/core/gather/gatherers/trace-elements.d.ts +38 -7
  19. package/core/gather/gatherers/trace-elements.js +113 -34
  20. package/core/gather/gatherers/trace.js +3 -0
  21. package/core/lib/i18n/i18n.js +2 -0
  22. package/core/lib/trace-engine.d.ts +5 -2
  23. package/core/lib/trace-engine.js +3 -0
  24. package/dist/report/bundle.esm.js +9 -5
  25. package/dist/report/flow.js +6 -2
  26. package/dist/report/standalone.js +11 -7
  27. package/package.json +2 -2
  28. package/report/assets/styles.css +4 -0
  29. package/report/renderer/category-renderer.d.ts +0 -7
  30. package/report/renderer/category-renderer.js +2 -12
  31. package/report/renderer/components.js +1 -1
  32. package/report/renderer/performance-category-renderer.d.ts +2 -3
  33. package/report/renderer/performance-category-renderer.js +3 -4
  34. package/shared/localization/locales/en-US.json +24 -0
  35. package/shared/localization/locales/en-XL.json +24 -0
  36. package/types/artifacts.d.ts +10 -1
  37. package/types/audit.d.ts +9 -1
  38. package/types/config.d.ts +1 -1
  39. package/types/lhr/audit-result.d.ts +1 -4
  40. package/types/lhr/lhr.d.ts +1 -3
  41. package/types/trace-engine.d.ts +1516 -0
@@ -63,6 +63,7 @@ import seoPassing from './test-definitions/seo-passing.js';
63
63
  import seoStatus403 from './test-definitions/seo-status-403.js';
64
64
  import seoTapTargets from './test-definitions/seo-tap-targets.js';
65
65
  import serviceWorkerReloaded from './test-definitions/service-worker-reloaded.js';
66
+ import shiftAttribution from './test-definitions/shift-attribution.js';
66
67
  import sourceMaps from './test-definitions/source-maps.js';
67
68
  import timing from './test-definitions/timing.js';
68
69
 
@@ -127,6 +128,7 @@ const smokeTests = [
127
128
  seoStatus403,
128
129
  seoTapTargets,
129
130
  serviceWorkerReloaded,
131
+ shiftAttribution,
130
132
  sourceMaps,
131
133
  timing,
132
134
  ];
@@ -146,6 +146,11 @@ export class Audit {
146
146
  * @return {number|null}
147
147
  */
148
148
  static _normalizeAuditScore(score: number | null, scoreDisplayMode: LH.Audit.ScoreDisplayMode, auditId: string): number | null;
149
+ /**
150
+ * @param {LH.Audit.ProductMetricSavings|undefined} metricSavings
151
+ * @return {LH.Audit.ProductMetricSavings|undefined}
152
+ */
153
+ static _quantizeMetricSavings(metricSavings: LH.Audit.ProductMetricSavings | undefined): LH.Audit.ProductMetricSavings | undefined;
149
154
  /**
150
155
  * @param {typeof Audit} audit
151
156
  * @param {string | LH.IcuMessage} errorMessage
@@ -10,6 +10,15 @@ import {Util} from '../../shared/util.js';
10
10
 
11
11
  const DEFAULT_PASS = 'defaultPass';
12
12
 
13
+ /** @type {Record<keyof LH.Audit.ProductMetricSavings, number>} */
14
+ const METRIC_SAVINGS_PRECISION = {
15
+ FCP: 50,
16
+ LCP: 50,
17
+ INP: 50,
18
+ TBT: 50,
19
+ CLS: 0.001,
20
+ };
21
+
13
22
  /**
14
23
  * @typedef TableOptions
15
24
  * @property {number=} wastedMs
@@ -339,6 +348,34 @@ class Audit {
339
348
  return score;
340
349
  }
341
350
 
351
+ /**
352
+ * @param {LH.Audit.ProductMetricSavings|undefined} metricSavings
353
+ * @return {LH.Audit.ProductMetricSavings|undefined}
354
+ */
355
+ static _quantizeMetricSavings(metricSavings) {
356
+ if (!metricSavings) return;
357
+
358
+ /** @type {LH.Audit.ProductMetricSavings} */
359
+ const normalizedMetricSavings = {...metricSavings};
360
+
361
+ // eslint-disable-next-line max-len
362
+ for (const key of /** @type {Array<keyof LH.Audit.ProductMetricSavings>} */ (Object.keys(metricSavings))) {
363
+ let value = metricSavings[key];
364
+ if (value === undefined) continue;
365
+
366
+ value = Math.max(value, 0);
367
+
368
+ const precision = METRIC_SAVINGS_PRECISION[key];
369
+ if (precision !== undefined) {
370
+ value = Math.round(value / precision) * precision;
371
+ }
372
+
373
+ normalizedMetricSavings[key] = value;
374
+ }
375
+
376
+ return normalizedMetricSavings;
377
+ }
378
+
342
379
  /**
343
380
  * @param {typeof Audit} audit
344
381
  * @param {string | LH.IcuMessage} errorMessage
@@ -378,10 +415,13 @@ class Audit {
378
415
  scoreDisplayMode = product.scoreDisplayMode;
379
416
  }
380
417
 
418
+ const metricSavings = Audit._quantizeMetricSavings(product.metricSavings);
419
+ const hasSomeSavings = Object.values(metricSavings || {}).some(v => v);
420
+
381
421
  if (scoreDisplayMode === Audit.SCORING_MODES.METRIC_SAVINGS) {
382
422
  if (score && score >= Util.PASS_THRESHOLD) {
383
423
  score = 1;
384
- } else if (Object.values(product.metricSavings || {}).some(v => v)) {
424
+ } else if (hasSomeSavings) {
385
425
  score = 0;
386
426
  } else {
387
427
  score = 0.5;
@@ -419,7 +459,7 @@ class Audit {
419
459
  errorStack: product.errorStack,
420
460
  warnings: product.warnings,
421
461
  scoringOptions: product.scoringOptions,
422
- metricSavings: product.metricSavings,
462
+ metricSavings,
423
463
 
424
464
  details: product.details,
425
465
  guidanceLevel: audit.meta.guidanceLevel,
@@ -194,7 +194,7 @@ class ByteEfficiencyAudit extends Audit {
194
194
 
195
195
  const wastedBytes = results.reduce((sum, item) => sum + item.wastedBytes, 0);
196
196
 
197
- /** @type {LH.Audit.MetricSavings} */
197
+ /** @type {LH.Audit.ProductMetricSavings} */
198
198
  const metricSavings = {
199
199
  FCP: 0,
200
200
  LCP: 0,
@@ -45,7 +45,7 @@ class LayoutShiftElements extends Audit {
45
45
 
46
46
  /** @type {Array<{node: LH.Audit.Details.ItemValue, score: number}>} */
47
47
  const clsElementData = artifacts.TraceElements
48
- .filter(element => element.traceEventType === 'layout-shift')
48
+ .filter(element => element.traceEventType === 'layout-shift-element')
49
49
  .map(element => ({
50
50
  node: Audit.makeNodeItem(element.node),
51
51
  score: impactByNodeId.get(element.nodeId) || 0,
@@ -0,0 +1,33 @@
1
+ export default LayoutShifts;
2
+ export type Item = LH.Audit.Details.TableItem & {
3
+ node?: LH.Audit.Details.NodeValue;
4
+ score: number;
5
+ subItems?: {
6
+ type: 'subitems';
7
+ items: SubItem[];
8
+ };
9
+ };
10
+ export type SubItem = {
11
+ extra?: LH.Audit.Details.NodeValue | LH.Audit.Details.UrlValue;
12
+ cause: LH.IcuMessage;
13
+ };
14
+ declare class LayoutShifts extends Audit {
15
+ /**
16
+ * @param {LH.Artifacts} artifacts
17
+ * @param {LH.Audit.Context} context
18
+ * @return {Promise<LH.Audit.Product>}
19
+ */
20
+ static audit(artifacts: LH.Artifacts, context: LH.Audit.Context): Promise<LH.Audit.Product>;
21
+ }
22
+ export namespace UIStrings {
23
+ const title: string;
24
+ const description: string;
25
+ const columnScore: string;
26
+ const rootCauseUnsizedMedia: string;
27
+ const rootCauseFontChanges: string;
28
+ const rootCauseInjectedIframe: string;
29
+ const rootCauseRenderBlockingRequest: string;
30
+ const displayValueShiftsFound: string;
31
+ }
32
+ import { Audit } from './audit.js';
33
+ //# sourceMappingURL=layout-shifts.d.ts.map
@@ -0,0 +1,158 @@
1
+ /**
2
+ * @license Copyright 2023 Google LLC
3
+ * SPDX-License-Identifier: Apache-2.0
4
+ */
5
+
6
+ import {Audit} from './audit.js';
7
+ import * as i18n from '../lib/i18n/i18n.js';
8
+ import {CumulativeLayoutShift as CumulativeLayoutShiftComputed} from '../computed/metrics/cumulative-layout-shift.js';
9
+ import CumulativeLayoutShift from './metrics/cumulative-layout-shift.js';
10
+ import TraceElements from '../gather/gatherers/trace-elements.js';
11
+ import {TraceEngineResult} from '../computed/trace-engine-result.js';
12
+
13
+ const MAX_LAYOUT_SHIFTS = 15;
14
+
15
+ /** @typedef {LH.Audit.Details.TableItem & {node?: LH.Audit.Details.NodeValue, score: number, subItems?: {type: 'subitems', items: SubItem[]}}} Item */
16
+ /** @typedef {{extra?: LH.Audit.Details.NodeValue | LH.Audit.Details.UrlValue, cause: LH.IcuMessage}} SubItem */
17
+
18
+ /* eslint-disable max-len */
19
+ const UIStrings = {
20
+ /** Descriptive title of a diagnostic audit that provides the top elements affected by layout shifts. */
21
+ title: 'Avoid large layout shifts',
22
+ /** Description of a diagnostic audit that provides the top elements affected by layout shifts. "windowing" means the metric value is calculated using the subset of events in a small window of time during the run. "normalization" is a good substitute for "windowing". The last sentence starting with 'Learn' becomes link text to additional documentation. */
23
+ description: 'These are the largest layout shifts observed on the page. Each table item represents a single layout shift, and shows the element that shifted the most. Below each item are possible root causes that led to the layout shift. Some of these layout shifts may not be included in the CLS metric value due to [windowing](https://web.dev/articles/cls#what_is_cls). [Learn how to improve CLS](https://web.dev/articles/optimize-cls)',
24
+ /** Label for a column in a data table; entries in this column will be a number representing how large the layout shift was. */
25
+ columnScore: 'Layout shift score',
26
+ /** A possible reason why that the layout shift occured. */
27
+ rootCauseUnsizedMedia: 'Media element lacking an explicit size',
28
+ /** A possible reason why that the layout shift occured. */
29
+ rootCauseFontChanges: 'Web font loaded',
30
+ /** A possible reason why that the layout shift occured. */
31
+ rootCauseInjectedIframe: 'Injected iframe',
32
+ /** A possible reason why that the layout shift occured. */
33
+ rootCauseRenderBlockingRequest: 'A late network request adjusted the page layout',
34
+ /** Label shown per-audit to show how many layout shifts are present. The `{# shifts found}` placeholder will be replaced with the number of layout shifts. */
35
+ displayValueShiftsFound: `{shiftCount, plural, =1 {1 layout shift found} other {# layout shifts found}}`,
36
+ };
37
+ /* eslint-enable max-len */
38
+
39
+ const str_ = i18n.createIcuMessageFn(import.meta.url, UIStrings);
40
+
41
+ class LayoutShifts extends Audit {
42
+ /**
43
+ * @return {LH.Audit.Meta}
44
+ */
45
+ static get meta() {
46
+ return {
47
+ id: 'layout-shifts',
48
+ title: str_(UIStrings.title),
49
+ description: str_(UIStrings.description),
50
+ scoreDisplayMode: Audit.SCORING_MODES.METRIC_SAVINGS,
51
+ guidanceLevel: 2,
52
+ requiredArtifacts: ['traces', 'RootCauses', 'TraceElements'],
53
+ };
54
+ }
55
+
56
+ /**
57
+ * @param {LH.Artifacts} artifacts
58
+ * @param {LH.Audit.Context} context
59
+ * @return {Promise<LH.Audit.Product>}
60
+ */
61
+ static async audit(artifacts, context) {
62
+ const trace = artifacts.traces[Audit.DEFAULT_PASS];
63
+ const traceEngineResult = await TraceEngineResult.request({trace}, context);
64
+ const clusters = traceEngineResult.LayoutShifts.clusters ?? [];
65
+ const {cumulativeLayoutShift: clsSavings, impactByNodeId} =
66
+ await CumulativeLayoutShiftComputed.request(trace, context);
67
+ const traceElements = artifacts.TraceElements
68
+ .filter(element => element.traceEventType === 'layout-shift');
69
+
70
+ /** @type {Item[]} */
71
+ const items = [];
72
+ const layoutShiftEvents = clusters.flatMap(c => c.events);
73
+ const topLayoutShiftEvents = layoutShiftEvents
74
+ .sort((a, b) => b.args.data.weighted_score_delta - a.args.data.weighted_score_delta)
75
+ .slice(0, MAX_LAYOUT_SHIFTS);
76
+ for (const event of topLayoutShiftEvents) {
77
+ const biggestImpactNodeId = TraceElements.getBiggestImpactNodeForShiftEvent(
78
+ event.args.data.impacted_nodes, impactByNodeId);
79
+ const biggestImpactElement = traceElements.find(t => t.nodeId === biggestImpactNodeId);
80
+
81
+ // Turn root causes into sub-items.
82
+ const index = layoutShiftEvents.indexOf(event);
83
+ const rootCauses = artifacts.RootCauses.layoutShifts[index];
84
+ /** @type {SubItem[]} */
85
+ const subItems = [];
86
+ if (rootCauses) {
87
+ for (const cause of rootCauses.unsizedMedia) {
88
+ const element = artifacts.TraceElements.find(
89
+ t => t.traceEventType === 'layout-shift' && t.nodeId === cause.node.backendNodeId);
90
+ subItems.push({
91
+ extra: element ? Audit.makeNodeItem(element.node) : undefined,
92
+ cause: str_(UIStrings.rootCauseUnsizedMedia),
93
+ });
94
+ }
95
+ for (const cause of rootCauses.fontChanges) {
96
+ const url = cause.request.args.data.url;
97
+ subItems.push({
98
+ extra: {type: 'url', value: url},
99
+ cause: str_(UIStrings.rootCauseFontChanges),
100
+ });
101
+ }
102
+ for (const cause of rootCauses.iframes) {
103
+ const element = artifacts.TraceElements.find(
104
+ t => t.traceEventType === 'layout-shift' && t.nodeId === cause.iframe.backendNodeId);
105
+ subItems.push({
106
+ extra: element ? Audit.makeNodeItem(element.node) : undefined,
107
+ cause: str_(UIStrings.rootCauseInjectedIframe),
108
+ });
109
+ }
110
+ for (const cause of rootCauses.renderBlockingRequests) {
111
+ const url = cause.request.args.data.url;
112
+ subItems.push({
113
+ extra: {type: 'url', value: url},
114
+ cause: str_(UIStrings.rootCauseRenderBlockingRequest),
115
+ });
116
+ }
117
+ }
118
+
119
+ items.push({
120
+ node: biggestImpactElement ? Audit.makeNodeItem(biggestImpactElement.node) : undefined,
121
+ score: event.args.data.weighted_score_delta,
122
+ subItems: subItems.length ? {type: 'subitems', items: subItems} : undefined,
123
+ });
124
+ }
125
+
126
+ /** @type {LH.Audit.Details.Table['headings']} */
127
+ const headings = [
128
+ /* eslint-disable max-len */
129
+ {key: 'node', valueType: 'node', subItemsHeading: {key: 'extra'}, label: str_(i18n.UIStrings.columnElement)},
130
+ {key: 'score', valueType: 'numeric', subItemsHeading: {key: 'cause', valueType: 'text'}, granularity: 0.001, label: str_(UIStrings.columnScore)},
131
+ /* eslint-enable max-len */
132
+ ];
133
+
134
+ const details = Audit.makeTableDetails(headings, items);
135
+
136
+ let displayValue;
137
+ if (items.length > 0) {
138
+ displayValue = str_(UIStrings.displayValueShiftsFound,
139
+ {shiftCount: items.length});
140
+ }
141
+
142
+ const passed = clsSavings <= CumulativeLayoutShift.defaultOptions.p10;
143
+
144
+ return {
145
+ score: passed ? 1 : 0,
146
+ scoreDisplayMode: passed ? Audit.SCORING_MODES.INFORMATIVE : undefined,
147
+ metricSavings: {
148
+ CLS: clsSavings,
149
+ },
150
+ notApplicable: details.items.length === 0,
151
+ displayValue,
152
+ details,
153
+ };
154
+ }
155
+ }
156
+
157
+ export default LayoutShifts;
158
+ export {UIStrings};
@@ -63,12 +63,22 @@ class Viewport extends Audit {
63
63
  inpSavings = 0;
64
64
  }
65
65
 
66
+ /** @type {LH.Audit.Details.DebugData|undefined} */
67
+ let details;
68
+ if (viewportMeta.rawContentString !== undefined) {
69
+ details = {
70
+ type: 'debugdata',
71
+ viewportContent: viewportMeta.rawContentString,
72
+ };
73
+ }
74
+
66
75
  return {
67
76
  score: Number(viewportMeta.isMobileOptimized),
68
77
  metricSavings: {
69
78
  INP: inpSavings,
70
79
  },
71
80
  warnings: viewportMeta.parserWarnings,
81
+ details,
72
82
  };
73
83
  }
74
84
  }
@@ -54,8 +54,8 @@ declare class CumulativeLayoutShift {
54
54
  * @param {LayoutShiftEvent[]} mainFrameShiftEvents
55
55
  */
56
56
  static computeWithSharedTraceEngine(allFrameShiftEvents: LayoutShiftEvent[], mainFrameShiftEvents: LayoutShiftEvent[]): Promise<{
57
- cumulativeLayoutShift: any;
58
- cumulativeLayoutShiftMainFrame: any;
57
+ cumulativeLayoutShift: number;
58
+ cumulativeLayoutShiftMainFrame: number;
59
59
  }>;
60
60
  /**
61
61
  * @param {LH.Trace} trace
@@ -0,0 +1,40 @@
1
+ export { TraceEngineResultComputed as TraceEngineResult };
2
+ declare const TraceEngineResultComputed: typeof TraceEngineResult & {
3
+ request: (dependencies: {
4
+ trace: LH.Trace;
5
+ }, context: import("../../types/utility-types.js").default.ImmutableObject<{
6
+ computedCache: Map<string, import("../lib/arbitrary-equality-map.js").ArbitraryEqualityMap>;
7
+ }>) => Promise<{
8
+ LayoutShifts: {
9
+ clusters: {
10
+ events: import("../../types/trace-engine.js").LayoutShiftTraceEvent[];
11
+ }[];
12
+ sessionMaxScore: number;
13
+ };
14
+ }>;
15
+ };
16
+ /**
17
+ * @fileoverview Processes trace with the shared trace engine.
18
+ */
19
+ declare class TraceEngineResult {
20
+ /**
21
+ * @param {LH.TraceEvent[]} traceEvents
22
+ */
23
+ static runTraceEngine(traceEvents: LH.TraceEvent[]): Promise<{
24
+ LayoutShifts: {
25
+ clusters: {
26
+ events: import("../../types/trace-engine.js").LayoutShiftTraceEvent[];
27
+ }[];
28
+ sessionMaxScore: number;
29
+ };
30
+ }>;
31
+ /**
32
+ * @param {{trace: LH.Trace}} data
33
+ * @param {LH.Artifacts.ComputedContext} context
34
+ * @return {Promise<LH.Artifacts.TraceEngineResult>}
35
+ */
36
+ static compute_(data: {
37
+ trace: LH.Trace;
38
+ }, context: LH.Artifacts.ComputedContext): Promise<LH.Artifacts.TraceEngineResult>;
39
+ }
40
+ //# sourceMappingURL=trace-engine-result.d.ts.map
@@ -0,0 +1,69 @@
1
+ /**
2
+ * @license
3
+ * Copyright 2023 Google LLC
4
+ * SPDX-License-Identifier: Apache-2.0
5
+ */
6
+
7
+ import * as TraceEngine from '../lib/trace-engine.js';
8
+ import {makeComputedArtifact} from './computed-artifact.js';
9
+ import {CumulativeLayoutShift} from './metrics/cumulative-layout-shift.js';
10
+ import {ProcessedTrace} from './processed-trace.js';
11
+
12
+ /**
13
+ * @fileoverview Processes trace with the shared trace engine.
14
+ */
15
+ class TraceEngineResult {
16
+ /**
17
+ * @param {LH.TraceEvent[]} traceEvents
18
+ */
19
+ static async runTraceEngine(traceEvents) {
20
+ const engine = new TraceEngine.TraceProcessor({
21
+ AuctionWorklets: TraceEngine.TraceHandlers.AuctionWorklets,
22
+ Initiators: TraceEngine.TraceHandlers.Initiators,
23
+ LayoutShifts: TraceEngine.TraceHandlers.LayoutShifts,
24
+ NetworkRequests: TraceEngine.TraceHandlers.NetworkRequests,
25
+ Renderer: TraceEngine.TraceHandlers.Renderer,
26
+ Samples: TraceEngine.TraceHandlers.Samples,
27
+ Screenshots: TraceEngine.TraceHandlers.Screenshots,
28
+ });
29
+ await engine.parse(traceEvents);
30
+ return engine.data;
31
+ }
32
+
33
+ /**
34
+ * @param {{trace: LH.Trace}} data
35
+ * @param {LH.Artifacts.ComputedContext} context
36
+ * @return {Promise<LH.Artifacts.TraceEngineResult>}
37
+ */
38
+ static async compute_(data, context) {
39
+ // In CumulativeLayoutShift.getLayoutShiftEvents we handle a bug in Chrome layout shift
40
+ // trace events re: changing the viewport emulation resulting in incorrectly set `had_recent_input`.
41
+ // Below, the same logic is applied to set those problem events' `had_recent_input` to false, so that
42
+ // the trace engine will count them.
43
+ // The trace events are copied-on-write, so the original trace remains unmodified.
44
+ const processedTrace = await ProcessedTrace.request(data.trace, context);
45
+ const layoutShiftEvents = new Set(
46
+ CumulativeLayoutShift.getLayoutShiftEvents(processedTrace).map(e => e.event));
47
+
48
+ // Avoid modifying the input array.
49
+ const traceEvents = [...data.trace.traceEvents];
50
+ for (let i = 0; i < traceEvents.length; i++) {
51
+ let event = traceEvents[i];
52
+ if (event.name !== 'LayoutShift') continue;
53
+ if (!event.args.data) continue;
54
+
55
+ const isConsidered = layoutShiftEvents.has(event);
56
+ if (event.args.data.had_recent_input && isConsidered) {
57
+ event = JSON.parse(JSON.stringify(event));
58
+ // @ts-expect-error impossible for data to be missing.
59
+ event.args.data.had_recent_input = false;
60
+ traceEvents[i] = event;
61
+ }
62
+ }
63
+
64
+ return TraceEngineResult.runTraceEngine(traceEvents);
65
+ }
66
+ }
67
+
68
+ const TraceEngineResultComputed = makeComputedArtifact(TraceEngineResult, ['trace']);
69
+ export {TraceEngineResultComputed as TraceEngineResult};
@@ -12,6 +12,10 @@ export type ViewportMetaResult = {
12
12
  * Warnings if the parser encountered invalid content in the viewport tag.
13
13
  */
14
14
  parserWarnings: Array<string>;
15
+ /**
16
+ * The `content` attribute value, if a viewport was defined.
17
+ */
18
+ rawContentString: string | undefined;
15
19
  };
16
20
  declare const ViewportMetaComputed: typeof ViewportMeta & {
17
21
  request: (dependencies: {
@@ -21,11 +21,13 @@ class ViewportMeta {
21
21
  hasViewportTag: false,
22
22
  isMobileOptimized: false,
23
23
  parserWarnings: [],
24
+ rawContentString: undefined,
24
25
  };
25
26
  }
26
27
 
27
28
  const warnings = [];
28
- const parsedProps = Parser.parseMetaViewPortContent(viewportMeta.content || '');
29
+ const rawContentString = viewportMeta.content || '';
30
+ const parsedProps = Parser.parseMetaViewPortContent(rawContentString);
29
31
 
30
32
  if (Object.keys(parsedProps.unknownProperties).length) {
31
33
  warnings.push(`Invalid properties found: ${JSON.stringify(parsedProps.unknownProperties)}`);
@@ -42,6 +44,7 @@ class ViewportMeta {
42
44
  hasViewportTag: true,
43
45
  isMobileOptimized: false,
44
46
  parserWarnings: warnings,
47
+ rawContentString,
45
48
  };
46
49
  }
47
50
 
@@ -51,6 +54,7 @@ class ViewportMeta {
51
54
  hasViewportTag: true,
52
55
  isMobileOptimized,
53
56
  parserWarnings: warnings,
57
+ rawContentString,
54
58
  };
55
59
  }
56
60
  }
@@ -63,4 +67,5 @@ export {ViewportMetaComputed as ViewportMeta};
63
67
  * @property {boolean} hasViewportTag Whether the page has any viewport tag.
64
68
  * @property {boolean} isMobileOptimized Whether the viewport tag is optimized for mobile screens.
65
69
  * @property {Array<string>} parserWarnings Warnings if the parser encountered invalid content in the viewport tag.
70
+ * @property {string|undefined} rawContentString The `content` attribute value, if a viewport was defined.
66
71
  */
@@ -129,6 +129,7 @@ const defaultConfig = {
129
129
  // Artifacts which can be depended on come first.
130
130
  {id: 'DevtoolsLog', gatherer: 'devtools-log'},
131
131
  {id: 'Trace', gatherer: 'trace'},
132
+ {id: 'RootCauses', gatherer: 'root-causes'},
132
133
 
133
134
  {id: 'Accessibility', gatherer: 'accessibility'},
134
135
  {id: 'AnchorElements', gatherer: 'anchor-elements'},
@@ -222,6 +223,7 @@ const defaultConfig = {
222
223
  'largest-contentful-paint-element',
223
224
  'lcp-lazy-loaded',
224
225
  'layout-shift-elements',
226
+ 'layout-shifts',
225
227
  'long-tasks',
226
228
  'no-unload-listeners',
227
229
  'non-composited-animations',
@@ -477,6 +479,7 @@ const defaultConfig = {
477
479
  {id: 'largest-contentful-paint-element', weight: 0},
478
480
  {id: 'lcp-lazy-loaded', weight: 0},
479
481
  {id: 'layout-shift-elements', weight: 0},
482
+ {id: 'layout-shifts', weight: 0},
480
483
  {id: 'uses-passive-event-listeners', weight: 0},
481
484
  {id: 'no-document-write', weight: 0},
482
485
  {id: 'long-tasks', weight: 0},
@@ -43,6 +43,7 @@ const tbtRelevantAudits = [
43
43
 
44
44
  const clsRelevantAudits = [
45
45
  'layout-shift-elements',
46
+ 'layout-shifts',
46
47
  'non-composited-animations',
47
48
  'unsized-images',
48
49
  // 'preload-fonts', // actually in BP, rather than perf
@@ -0,0 +1,20 @@
1
+ export default RootCauses;
2
+ declare class RootCauses extends BaseGatherer {
3
+ static symbol: symbol;
4
+ /**
5
+ * @param {LH.Gatherer.Driver} driver
6
+ * @param {LH.Artifacts.TraceEngineResult} traceEngineResult
7
+ * @return {Promise<LH.Artifacts.TraceEngineRootCauses>}
8
+ */
9
+ static runRootCauseAnalysis(driver: LH.Gatherer.Driver, traceEngineResult: LH.Artifacts.TraceEngineResult): Promise<LH.Artifacts.TraceEngineRootCauses>;
10
+ /** @type {LH.Gatherer.GathererMeta<'Trace'>} */
11
+ meta: LH.Gatherer.GathererMeta<'Trace'>;
12
+ /**
13
+ * @param {LH.Gatherer.Context<'Trace'>} context
14
+ * @return {Promise<LH.Artifacts.TraceEngineRootCauses>}
15
+ */
16
+ getArtifact(context: LH.Gatherer.Context<'Trace'>): Promise<LH.Artifacts.TraceEngineRootCauses>;
17
+ }
18
+ import BaseGatherer from '../base-gatherer.js';
19
+ import { TraceEngineResult } from '../../computed/trace-engine-result.js';
20
+ //# sourceMappingURL=root-causes.d.ts.map