lighthouse 9.5.0 → 9.6.3

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 (69) hide show
  1. package/dist/report/bundle.esm.js +67 -384
  2. package/dist/report/flow.js +20 -25
  3. package/dist/report/standalone.js +18 -23
  4. package/lighthouse-cli/test/smokehouse/core-tests.js +2 -0
  5. package/lighthouse-cli/test/smokehouse/frontends/lib.js +1 -3
  6. package/lighthouse-cli/test/smokehouse/frontends/smokehouse-bin.js +1 -3
  7. package/lighthouse-cli/test/smokehouse/lighthouse-runners/cli.js +1 -0
  8. package/lighthouse-cli/test/smokehouse/readme.md +5 -4
  9. package/lighthouse-cli/test/smokehouse/report-assert.js +16 -13
  10. package/lighthouse-cli/test/smokehouse/version-check-test.js +46 -0
  11. package/lighthouse-cli/test/smokehouse/version-check.js +49 -0
  12. package/lighthouse-core/audits/bootup-time.js +2 -70
  13. package/lighthouse-core/audits/byte-efficiency/duplicated-javascript.js +3 -5
  14. package/lighthouse-core/audits/byte-efficiency/legacy-javascript.js +2 -5
  15. package/lighthouse-core/audits/byte-efficiency/unminified-javascript.js +4 -2
  16. package/lighthouse-core/audits/byte-efficiency/unused-javascript.js +3 -1
  17. package/lighthouse-core/audits/deprecations.js +603 -28
  18. package/lighthouse-core/audits/installable-manifest.js +27 -6
  19. package/lighthouse-core/audits/long-tasks.js +3 -3
  20. package/lighthouse-core/audits/metrics/experimental-interaction-to-next-paint.js +88 -0
  21. package/lighthouse-core/audits/network-requests.js +79 -62
  22. package/lighthouse-core/audits/preload-lcp-image.js +33 -7
  23. package/lighthouse-core/audits/third-party-summary.js +3 -3
  24. package/lighthouse-core/audits/work-during-interaction.js +280 -0
  25. package/lighthouse-core/computed/metrics/responsiveness.js +157 -0
  26. package/lighthouse-core/config/config-helpers.js +1 -1
  27. package/lighthouse-core/config/metrics-to-audits.js +5 -0
  28. package/lighthouse-core/fraggle-rock/config/default-config.js +9 -1
  29. package/lighthouse-core/fraggle-rock/gather/base-artifacts.js +2 -2
  30. package/lighthouse-core/fraggle-rock/gather/session.js +8 -2
  31. package/lighthouse-core/gather/driver.js +21 -14
  32. package/lighthouse-core/gather/gather-runner.js +2 -4
  33. package/lighthouse-core/gather/gatherers/css-usage.js +59 -34
  34. package/lighthouse-core/gather/gatherers/trace-elements.js +21 -2
  35. package/lighthouse-core/lib/arbitrary-equality-map.js +2 -2
  36. package/lighthouse-core/lib/dependency-graph/network-node.js +1 -1
  37. package/lighthouse-core/lib/i18n/i18n.js +2 -0
  38. package/lighthouse-core/lib/minify-trace.js +2 -0
  39. package/lighthouse-core/lib/script-helpers.js +24 -0
  40. package/lighthouse-core/lib/stack-packs.js +8 -5
  41. package/lighthouse-core/lib/tracehouse/task-groups.js +1 -0
  42. package/lighthouse-core/lib/tracehouse/task-summary.js +87 -0
  43. package/lighthouse-core/lib/tracehouse/trace-processor.js +39 -14
  44. package/lighthouse-core/runner.js +1 -1
  45. package/lighthouse-core/util-commonjs.js +20 -18
  46. package/package.json +10 -8
  47. package/report/assets/styles.css +5 -2
  48. package/report/clients/bundle.js +1 -0
  49. package/report/renderer/components.js +1 -1
  50. package/report/renderer/details-renderer.js +4 -4
  51. package/report/renderer/performance-category-renderer.js +15 -11
  52. package/report/renderer/util.js +20 -18
  53. package/report/test/renderer/__snapshots__/report-renderer-axe-test.js.snap +6 -98
  54. package/report/test/renderer/details-renderer-test.js +9 -9
  55. package/report/test/renderer/performance-category-renderer-test.js +31 -0
  56. package/report/test/renderer/report-renderer-axe-test.js +3 -15
  57. package/report/test-assets/faux-psi-template.html +3 -2
  58. package/report/test-assets/faux-psi.js +22 -1
  59. package/shared/localization/locales/en-US.json +244 -4
  60. package/shared/localization/locales/en-XL.json +244 -4
  61. package/shared/localization/swap-locale.js +2 -1
  62. package/third-party/chromium-synchronization/installability-errors-test.js +1 -0
  63. package/third-party/snyk/snapshot.json +2 -3
  64. package/tsconfig.json +1 -0
  65. package/types/artifacts.d.ts +40 -1
  66. package/types/lhr/audit-details.d.ts +3 -1
  67. package/types/smokehouse.d.ts +1 -1
  68. package/changelog.md +0 -5902
  69. package/lighthouse-core/scripts/package.json +0 -4
@@ -5,7 +5,9 @@
5
5
  */
6
6
  'use strict';
7
7
 
8
+ const log = require('lighthouse-logger');
8
9
  const FRGatherer = require('../../fraggle-rock/gather/base-gatherer.js');
10
+ const Sentry = require('../../lib/sentry.js');
9
11
 
10
12
  /**
11
13
  * @fileoverview Tracks unused CSS rules.
@@ -13,21 +15,16 @@ const FRGatherer = require('../../fraggle-rock/gather/base-gatherer.js');
13
15
  class CSSUsage extends FRGatherer {
14
16
  constructor() {
15
17
  super();
16
- /** @type {Array<LH.Crdp.CSS.StyleSheetAddedEvent>} */
17
- this._stylesheets = [];
18
- /** @param {LH.Crdp.CSS.StyleSheetAddedEvent} sheet */
19
- this._onStylesheetAdded = sheet => this._stylesheets.push(sheet);
20
- /** @param {LH.Crdp.CSS.StyleSheetRemovedEvent} sheet */
21
- this._onStylesheetRemoved = sheet => {
22
- // We can't fetch the content of removed stylesheets, so we ignore them completely.
23
- const styleSheetId = sheet.styleSheetId;
24
- this._stylesheets = this._stylesheets.filter(s => s.header.styleSheetId !== styleSheetId);
25
- };
18
+ /** @type {LH.Gatherer.FRProtocolSession|undefined} */
19
+ this._session = undefined;
20
+ /** @type {Map<string, Promise<LH.Artifacts.CSSStyleSheetInfo|Error>>} */
21
+ this._sheetPromises = new Map();
26
22
  /**
27
23
  * Initialize as undefined so we can assert results are fetched.
28
24
  * @type {LH.Crdp.CSS.RuleUsage[]|undefined}
29
25
  */
30
26
  this._ruleUsage = undefined;
27
+ this._onStylesheetAdded = this._onStylesheetAdded.bind(this);
31
28
  }
32
29
 
33
30
  /** @type {LH.Gatherer.GathererMeta} */
@@ -35,26 +32,49 @@ class CSSUsage extends FRGatherer {
35
32
  supportedModes: ['snapshot', 'timespan', 'navigation'],
36
33
  };
37
34
 
35
+ /**
36
+ * @param {LH.Crdp.CSS.StyleSheetAddedEvent} event
37
+ */
38
+ async _onStylesheetAdded(event) {
39
+ if (!this._session) throw new Error('Session not initialized');
40
+ const styleSheetId = event.header.styleSheetId;
41
+ const sheetPromise = this._session.sendCommand('CSS.getStyleSheetText', {styleSheetId})
42
+ .then(content => ({
43
+ header: event.header,
44
+ content: content.text,
45
+ }))
46
+ .catch(/** @param {Error} err */ (err) => {
47
+ log.warn(
48
+ 'CSSUsage',
49
+ `Error fetching content of stylesheet with URL "${event.header.sourceURL}"`
50
+ );
51
+ Sentry.captureException(err, {
52
+ tags: {
53
+ gatherer: this.name,
54
+ },
55
+ extra: {
56
+ url: event.header.sourceURL,
57
+ },
58
+ level: 'error',
59
+ });
60
+ return err;
61
+ });
62
+ this._sheetPromises.set(styleSheetId, sheetPromise);
63
+ }
64
+
38
65
  /**
39
66
  * @param {LH.Gatherer.FRTransitionalContext} context
40
67
  */
41
68
  async startCSSUsageTracking(context) {
42
69
  const session = context.driver.defaultSession;
70
+ this._session = session;
43
71
  session.on('CSS.styleSheetAdded', this._onStylesheetAdded);
44
- session.on('CSS.styleSheetRemoved', this._onStylesheetRemoved);
45
72
 
46
73
  await session.sendCommand('DOM.enable');
47
74
  await session.sendCommand('CSS.enable');
48
75
  await session.sendCommand('CSS.startRuleUsageTracking');
49
76
  }
50
77
 
51
- /**
52
- * @param {LH.Gatherer.FRTransitionalContext} context
53
- */
54
- async startInstrumentation(context) {
55
- if (context.gatherMode !== 'timespan') return;
56
- await this.startCSSUsageTracking(context);
57
- }
58
78
 
59
79
  /**
60
80
  * @param {LH.Gatherer.FRTransitionalContext} context
@@ -64,7 +84,14 @@ class CSSUsage extends FRGatherer {
64
84
  const coverageResponse = await session.sendCommand('CSS.stopRuleUsageTracking');
65
85
  this._ruleUsage = coverageResponse.ruleUsage;
66
86
  session.off('CSS.styleSheetAdded', this._onStylesheetAdded);
67
- session.off('CSS.styleSheetRemoved', this._onStylesheetRemoved);
87
+ }
88
+
89
+ /**
90
+ * @param {LH.Gatherer.FRTransitionalContext} context
91
+ */
92
+ async startInstrumentation(context) {
93
+ if (context.gatherMode !== 'timespan') return;
94
+ await this.startCSSUsageTracking(context);
68
95
  }
69
96
 
70
97
  /**
@@ -93,25 +120,23 @@ class CSSUsage extends FRGatherer {
93
120
  await this.stopCSSUsageTracking(context);
94
121
  }
95
122
 
96
- // Fetch style sheet content in parallel.
97
- const promises = this._stylesheets.map(sheet => {
98
- const styleSheetId = sheet.header.styleSheetId;
99
- return session.sendCommand('CSS.getStyleSheetText', {styleSheetId}).then(content => {
100
- return {
101
- header: sheet.header,
102
- content: content.text,
103
- };
104
- });
105
- });
106
- const styleSheetInfo = await Promise.all(promises);
123
+ /** @type {Map<string, LH.Artifacts.CSSStyleSheetInfo>} */
124
+ const dedupedStylesheets = new Map();
125
+ const sheets = await Promise.all(this._sheetPromises.values());
126
+
127
+ for (const sheet of sheets) {
128
+ // Erroneous sheets will be reported via sentry and the log.
129
+ // We can ignore them here without throwing a fatal error.
130
+ if (sheet instanceof Error) {
131
+ continue;
132
+ }
133
+
134
+ dedupedStylesheets.set(sheet.content, sheet);
135
+ }
107
136
 
108
137
  await session.sendCommand('CSS.disable');
109
138
  await session.sendCommand('DOM.disable');
110
139
 
111
- const dedupedStylesheets = new Map(styleSheetInfo.map(sheet => {
112
- return [sheet.content, sheet];
113
- }));
114
-
115
140
  if (!this._ruleUsage) throw new Error('Issue collecting rule usages');
116
141
 
117
142
  return {
@@ -22,6 +22,7 @@ const Trace = require('./trace.js');
22
22
  const ProcessedTrace = require('../../computed/processed-trace.js');
23
23
  const ProcessedNavigation = require('../../computed/processed-navigation.js');
24
24
  const LighthouseError = require('../../lib/lh-error.js');
25
+ const ComputedResponsivenes = require('../../computed/metrics/responsiveness.js');
25
26
 
26
27
  /** @typedef {{nodeId: number, score?: number, animations?: {name?: string, failureReasonsMask?: number, unsupportedProperties?: string[]}[]}} TraceElementData */
27
28
 
@@ -142,6 +143,23 @@ class TraceElements extends FRGatherer {
142
143
  return topFive;
143
144
  }
144
145
 
146
+ /**
147
+ * @param {LH.Trace} trace
148
+ * @param {LH.Gatherer.FRTransitionalContext} context
149
+ * @return {Promise<TraceElementData|undefined>}
150
+ */
151
+ static async getResponsivenessElement(trace, context) {
152
+ const {settings} = context;
153
+ try {
154
+ const responsivenessEvent = await ComputedResponsivenes.request({trace, settings}, context);
155
+ if (!responsivenessEvent || responsivenessEvent.name === 'FallbackTiming') return;
156
+ return {nodeId: responsivenessEvent.args.data.nodeId};
157
+ } catch {
158
+ // Don't let responsiveness errors sink the rest of the gatherer.
159
+ return;
160
+ }
161
+ }
162
+
145
163
  /**
146
164
  * Find the node ids of elements which are animated using the Animation trace events.
147
165
  * @param {Array<LH.TraceEvent>} mainThreadEvents
@@ -237,14 +255,15 @@ class TraceElements extends FRGatherer {
237
255
 
238
256
  const lcpNodeId = largestContentfulPaintEvt?.args?.data?.nodeId;
239
257
  const clsNodeData = TraceElements.getTopLayoutShiftElements(mainThreadEvents);
240
- const animatedElementData =
241
- await this.getAnimatedElements(mainThreadEvents);
258
+ const animatedElementData = await this.getAnimatedElements(mainThreadEvents);
259
+ const responsivenessElementData = await TraceElements.getResponsivenessElement(trace, context);
242
260
 
243
261
  /** @type {Map<string, TraceElementData[]>} */
244
262
  const backendNodeDataMap = new Map([
245
263
  ['largest-contentful-paint', lcpNodeId ? [{nodeId: lcpNodeId}] : []],
246
264
  ['layout-shift', clsNodeData],
247
265
  ['animation', animatedElementData],
266
+ ['responsiveness', responsivenessElementData ? [responsivenessElementData] : []],
248
267
  ]);
249
268
 
250
269
  const traceElements = [];
@@ -5,7 +5,7 @@
5
5
  */
6
6
  'use strict';
7
7
 
8
- const {isEqual} = require('lodash');
8
+ const isDeepEqual = require('lodash/isEqual.js');
9
9
 
10
10
  /**
11
11
  * @fileoverview This class is designed to allow maps with arbitrary equality functions.
@@ -74,7 +74,7 @@ class ArbitraryEqualityMap {
74
74
  * @return {boolean}
75
75
  */
76
76
  static deepEquals(objA, objB) {
77
- return isEqual(objA, objB);
77
+ return isDeepEqual(objA, objB);
78
78
  }
79
79
  }
80
80
 
@@ -44,7 +44,7 @@ class NetworkNode extends BaseNode {
44
44
  }
45
45
 
46
46
  /**
47
- * @return {?string}
47
+ * @return {string}
48
48
  */
49
49
  get initiatorType() {
50
50
  return this._record.initiator && this._record.initiator.type;
@@ -105,6 +105,8 @@ const UIStrings = {
105
105
  largestContentfulPaintMetric: 'Largest Contentful Paint',
106
106
  /** The name of the metric "Cumulative Layout Shift" that indicates how much the page changes its layout while it loads. If big segments of the page shift their location during load, the Cumulative Layout Shift will be higher. Shown to users as the label for the numeric metric value. Ideally fits within a ~40 character limit. */
107
107
  cumulativeLayoutShiftMetric: 'Cumulative Layout Shift',
108
+ /** The name of the "Interaction to Next Paint" metric that measures the time between a user interaction and when the browser displays a response on screen. Shown to users as the label for the numeric metric value. Ideally fits within a ~40 character limit. */
109
+ interactionToNextPaint: 'Interaction to Next Paint',
108
110
  /** Table item value for the severity of a small, or low impact vulnerability. Part of a ranking scale in the form: low, medium, high. */
109
111
  itemSeverityLow: 'Low',
110
112
  /** Table item value for the severity of a vulnerability. Part of a ranking scale in the form: low, medium, high. */
@@ -44,6 +44,8 @@ const traceEventsToAlwaysKeep = new Set([
44
44
  'EventDispatch',
45
45
  'LayoutShift',
46
46
  'FrameCommittedInBrowser',
47
+ 'EventTiming',
48
+ 'Responsiveness.Renderer.UserInteraction',
47
49
  // Not currently used by Lighthouse but might be used in the future for cross-frame LCP
48
50
  'NavStartToLargestContentfulPaint::Invalidate::AllFrames::UKM',
49
51
  'NavStartToLargestContentfulPaint::Candidate::AllFrames::UKM',
@@ -0,0 +1,24 @@
1
+ /**
2
+ * @license Copyright 2022 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
+ 'use strict';
7
+
8
+ /**
9
+ * @param {LH.Artifacts.NetworkRequest[]} networkRecords
10
+ * @param {LH.Artifacts.ScriptElement|undefined} script
11
+ * @return {LH.Artifacts.NetworkRequest|undefined}
12
+ */
13
+ function getRequestForScript(networkRecords, script) {
14
+ if (!script) return;
15
+ let networkRequest = script.requestId &&
16
+ networkRecords.find(request => request.requestId === script.requestId);
17
+ if (!networkRequest) networkRequest = networkRecords.find(request => request.url === script.src);
18
+ while (networkRequest?.redirectDestination) {
19
+ networkRequest = networkRequest.redirectDestination;
20
+ }
21
+ return networkRequest;
22
+ }
23
+
24
+ module.exports = {getRequestForScript};
@@ -19,11 +19,10 @@ const stackPacksToInclude = [
19
19
  packId: 'wordpress',
20
20
  requiredStacks: ['js:wordpress'],
21
21
  },
22
- // waiting for https://github.com/johnmichel/Library-Detector-for-Chrome/pull/193
23
- // {
24
- // packId: 'ezoic',
25
- // requiredStacks: ['js:ezoic'],
26
- // },
22
+ {
23
+ packId: 'ezoic',
24
+ requiredStacks: ['js:ezoic'],
25
+ },
27
26
  {
28
27
  packId: 'drupal',
29
28
  requiredStacks: ['js:drupal'],
@@ -48,6 +47,10 @@ const stackPacksToInclude = [
48
47
  packId: 'next.js',
49
48
  requiredStacks: ['js:next'],
50
49
  },
50
+ {
51
+ packId: 'nuxt',
52
+ requiredStacks: ['js:nuxt'],
53
+ },
51
54
  {
52
55
  packId: 'angular',
53
56
  requiredStacks: ['js:@angular/core'],
@@ -50,6 +50,7 @@ const taskGroups = {
50
50
  'UpdateLayer',
51
51
  'UpdateLayerTree',
52
52
  'CompositeLayers',
53
+ 'PrePaint', // New name for UpdateLayerTree: https://crrev.com/c/3519012
53
54
  ],
54
55
  },
55
56
  scriptParseCompile: {
@@ -0,0 +1,87 @@
1
+ /**
2
+ * @license Copyright 2022 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
+ 'use strict';
7
+
8
+ /**
9
+ * @fileoverview Utility functions for grouping and summarizing tasks.
10
+ */
11
+
12
+ const NetworkRequest = require('../network-request.js');
13
+
14
+ // These trace events, when not triggered by a script inside a particular task, are just general Chrome overhead.
15
+ const BROWSER_TASK_NAMES_SET = new Set([
16
+ 'CpuProfiler::StartProfiling',
17
+ ]);
18
+
19
+ // These trace events, when not triggered by a script inside a particular task, are GC Chrome overhead.
20
+ const BROWSER_GC_TASK_NAMES_SET = new Set([
21
+ 'V8.GCCompactor',
22
+ 'MajorGC',
23
+ 'MinorGC',
24
+ ]);
25
+
26
+ /**
27
+ * @param {LH.Artifacts.NetworkRequest[]} records
28
+ */
29
+ function getJavaScriptURLs(records) {
30
+ /** @type {Set<string>} */
31
+ const urls = new Set();
32
+ for (const record of records) {
33
+ if (record.resourceType === NetworkRequest.TYPES.Script) {
34
+ urls.add(record.url);
35
+ }
36
+ }
37
+
38
+ return urls;
39
+ }
40
+
41
+ /**
42
+ * @param {LH.Artifacts.TaskNode} task
43
+ * @param {Set<string>} jsURLs
44
+ * @return {string}
45
+ */
46
+ function getAttributableURLForTask(task, jsURLs) {
47
+ const jsURL = task.attributableURLs.find(url => jsURLs.has(url));
48
+ const fallbackURL = task.attributableURLs[0];
49
+ let attributableURL = jsURL || fallbackURL;
50
+ // If we can't find what URL was responsible for this execution, attribute it to the root page
51
+ // or Chrome depending on the type of work.
52
+ if (!attributableURL || attributableURL === 'about:blank') {
53
+ if (BROWSER_TASK_NAMES_SET.has(task.event.name)) attributableURL = 'Browser';
54
+ else if (BROWSER_GC_TASK_NAMES_SET.has(task.event.name)) attributableURL = 'Browser GC';
55
+ else attributableURL = 'Unattributable';
56
+ }
57
+
58
+ return attributableURL;
59
+ }
60
+
61
+ /**
62
+ * @param {LH.Artifacts.TaskNode[]} tasks
63
+ * @param {Array<LH.Artifacts.NetworkRequest>} networkRecords
64
+ * @return {Map<string, Record<string, number>>}
65
+ */
66
+ function getExecutionTimingsByURL(tasks, networkRecords) {
67
+ const jsURLs = getJavaScriptURLs(networkRecords);
68
+
69
+ /** @type {Map<string, Record<string, number>>} */
70
+ const result = new Map();
71
+
72
+ for (const task of tasks) {
73
+ const attributableURL = getAttributableURLForTask(task, jsURLs);
74
+ const timingByGroupId = result.get(attributableURL) || {};
75
+ const originalTime = timingByGroupId[task.group.id] || 0;
76
+ timingByGroupId[task.group.id] = originalTime + task.selfTime;
77
+ result.set(attributableURL, timingByGroupId);
78
+ }
79
+
80
+ return result;
81
+ }
82
+
83
+ module.exports = {
84
+ getJavaScriptURLs,
85
+ getAttributableURLForTask,
86
+ getExecutionTimingsByURL,
87
+ };
@@ -82,11 +82,15 @@ class TraceProcessor {
82
82
  * Returns true if the event is a navigation start event of a document whose URL seems valid.
83
83
  *
84
84
  * @param {LH.TraceEvent} event
85
+ * @return {boolean}
85
86
  */
86
87
  static _isNavigationStartOfInterest(event) {
87
- return event.name === 'navigationStart' &&
88
- (!event.args.data || !event.args.data.documentLoaderURL ||
89
- ACCEPTABLE_NAVIGATION_URL_REGEX.test(event.args.data.documentLoaderURL));
88
+ if (event.name !== 'navigationStart') return false;
89
+ // COMPAT: support pre-m67 test traces before `args.data` added to all navStart events.
90
+ // TODO: remove next line when old test traces (e.g. progressive-app-m60.json) are updated.
91
+ if (event.args.data?.documentLoaderURL === undefined) return true;
92
+ if (!event.args.data?.documentLoaderURL) return false;
93
+ return ACCEPTABLE_NAVIGATION_URL_REGEX.test(event.args.data.documentLoaderURL);
90
94
  }
91
95
 
92
96
  /**
@@ -462,8 +466,9 @@ class TraceProcessor {
462
466
  // If we can't find either TracingStarted event, then we'll fallback to the first navStart that
463
467
  // looks like it was loading the main frame with a real URL. Because the schema for this event
464
468
  // has changed across Chrome versions, we'll be extra defensive about finding this case.
465
- const navStartEvt = events.find(e => Boolean(e.name === 'navigationStart' &&
466
- e.args?.data?.isLoadingMainFrame && e.args.data.documentLoaderURL));
469
+ const navStartEvt = events.find(e =>
470
+ this._isNavigationStartOfInterest(e) && e.args.data?.isLoadingMainFrame
471
+ );
467
472
  // Find the first resource that was requested and make sure it agrees on the id.
468
473
  const firstResourceSendEvt = events.find(e => e.name === 'ResourceSendRequest');
469
474
  // We know that these properties exist if we found the events, but TSC doesn't.
@@ -607,39 +612,59 @@ class TraceProcessor {
607
612
  // Find the inspected frame
608
613
  const mainFrameIds = this.findMainFrameIds(keyEvents);
609
614
 
610
- const frames = keyEvents
615
+ /** @type {Map<string, {id: string, url: string, parent?: string}>} */
616
+ const framesById = new Map();
617
+
618
+ // Begin collection of frame tree information with TracingStartedInBrowser,
619
+ // which should be present even without navigations.
620
+ const tracingStartedFrames = keyEvents
621
+ .find(e => e.name === 'TracingStartedInBrowser')?.args?.data?.frames;
622
+ if (tracingStartedFrames) {
623
+ for (const frame of tracingStartedFrames) {
624
+ framesById.set(frame.frame, {
625
+ id: frame.frame,
626
+ url: frame.url,
627
+ parent: frame.parent,
628
+ });
629
+ }
630
+ }
631
+
632
+ // Update known frames if FrameCommittedInBrowser events come in, typically
633
+ // with updated `url`, as well as pid, etc. Some traces (like timespans) may
634
+ // not have any committed frames.
635
+ keyEvents
611
636
  .filter(/** @return {evt is FrameCommittedEvent} */ evt => {
612
637
  return Boolean(
613
638
  evt.name === 'FrameCommittedInBrowser' &&
614
639
  evt.args.data?.frame &&
615
640
  evt.args.data.url
616
641
  );
617
- })
618
- .map(evt => {
619
- return {
642
+ }).forEach(evt => {
643
+ framesById.set(evt.args.data.frame, {
620
644
  id: evt.args.data.frame,
621
645
  url: evt.args.data.url,
622
646
  parent: evt.args.data.parent,
623
- };
647
+ });
624
648
  });
649
+ const frames = [...framesById.values()];
625
650
  const frameIdToRootFrameId = this.resolveRootFrames(frames);
626
651
 
627
652
  // Filter to just events matching the main frame ID, just to make sure.
628
653
  const frameEvents = keyEvents.filter(e => e.args.frame === mainFrameIds.frameId);
629
654
 
630
655
  // Filter to just events matching the main frame ID or any child frame IDs.
631
- // In practice, there should always be FrameCommittedInBrowser events to define the frame tree.
632
- // Unfortunately, many test traces do not include FrameCommittedInBrowser events due to minification.
633
- // This ensures there is always a minimal frame tree and events so those tests don't fail.
634
656
  let frameTreeEvents = [];
635
657
  if (frameIdToRootFrameId.has(mainFrameIds.frameId)) {
636
658
  frameTreeEvents = keyEvents.filter(e => {
637
659
  return e.args.frame && frameIdToRootFrameId.get(e.args.frame) === mainFrameIds.frameId;
638
660
  });
639
661
  } else {
662
+ // In practice, there should always be TracingStartedInBrowser/FrameCommittedInBrowser events to
663
+ // define the frame tree. Unfortunately, many test traces do not that frame info due to minification.
664
+ // This ensures there is always a minimal frame tree and events so those tests don't fail.
640
665
  log.warn(
641
666
  'trace-of-tab',
642
- 'frameTreeEvents may be incomplete, make sure the trace has FrameCommittedInBrowser events'
667
+ 'frameTreeEvents may be incomplete, make sure the trace has frame events'
643
668
  );
644
669
  frameIdToRootFrameId.set(mainFrameIds.frameId, mainFrameIds.frameId);
645
670
  frameTreeEvents = frameEvents;
@@ -5,7 +5,7 @@
5
5
  */
6
6
  'use strict';
7
7
 
8
- const {isEqual: isDeepEqual} = require('lodash');
8
+ const isDeepEqual = require('lodash/isEqual.js');
9
9
  const Driver = require('./gather/driver.js');
10
10
  const GatherRunner = require('./gather/gather-runner.js');
11
11
  const ReportScoring = require('./scoring.js');
@@ -318,24 +318,26 @@ class Util {
318
318
  }
319
319
 
320
320
  const MAX_LENGTH = 64;
321
- // Always elide hexadecimal hash
322
- name = name.replace(/([a-f0-9]{7})[a-f0-9]{13}[a-f0-9]*/g, `$1${ELLIPSIS}`);
323
- // Also elide other hash-like mixed-case strings
324
- name = name.replace(/([a-zA-Z0-9-_]{9})(?=.*[a-z])(?=.*[A-Z])(?=.*[0-9])[a-zA-Z0-9-_]{10,}/g,
325
- `$1${ELLIPSIS}`);
326
- // Also elide long number sequences
327
- name = name.replace(/(\d{3})\d{6,}/g, `$1${ELLIPSIS}`);
328
- // Merge any adjacent ellipses
329
- name = name.replace(/\u2026+/g, ELLIPSIS);
330
-
331
- // Elide query params first
332
- if (name.length > MAX_LENGTH && name.includes('?')) {
333
- // Try to leave the first query parameter intact
334
- name = name.replace(/\?([^=]*)(=)?.*/, `?$1$2${ELLIPSIS}`);
335
-
336
- // Remove it all if it's still too long
337
- if (name.length > MAX_LENGTH) {
338
- name = name.replace(/\?.*/, `?${ELLIPSIS}`);
321
+ if (parsedUrl.protocol !== 'data:') {
322
+ // Always elide hexadecimal hash
323
+ name = name.replace(/([a-f0-9]{7})[a-f0-9]{13}[a-f0-9]*/g, `$1${ELLIPSIS}`);
324
+ // Also elide other hash-like mixed-case strings
325
+ name = name.replace(/([a-zA-Z0-9-_]{9})(?=.*[a-z])(?=.*[A-Z])(?=.*[0-9])[a-zA-Z0-9-_]{10,}/g,
326
+ `$1${ELLIPSIS}`);
327
+ // Also elide long number sequences
328
+ name = name.replace(/(\d{3})\d{6,}/g, `$1${ELLIPSIS}`);
329
+ // Merge any adjacent ellipses
330
+ name = name.replace(/\u2026+/g, ELLIPSIS);
331
+
332
+ // Elide query params first
333
+ if (name.length > MAX_LENGTH && name.includes('?')) {
334
+ // Try to leave the first query parameter intact
335
+ name = name.replace(/\?([^=]*)(=)?.*/, `?$1$2${ELLIPSIS}`);
336
+
337
+ // Remove it all if it's still too long
338
+ if (name.length > MAX_LENGTH) {
339
+ name = name.replace(/\?.*/, `?${ELLIPSIS}`);
340
+ }
339
341
  }
340
342
  }
341
343
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "lighthouse",
3
- "version": "9.5.0",
3
+ "version": "9.6.3",
4
4
  "description": "Automated auditing, performance metrics, and best practices for the web.",
5
5
  "main": "./lighthouse-core/index.js",
6
6
  "bin": {
@@ -138,7 +138,7 @@
138
138
  "cpy": "^8.1.2",
139
139
  "cross-env": "^7.0.2",
140
140
  "csv-validator": "^0.0.3",
141
- "devtools-protocol": "0.0.975298",
141
+ "devtools-protocol": "0.0.999451",
142
142
  "es-main": "^1.0.2",
143
143
  "eslint": "^8.4.1",
144
144
  "eslint-config-google": "^0.14.0",
@@ -163,7 +163,7 @@
163
163
  "pako": "^2.0.3",
164
164
  "preact": "^10.5.14",
165
165
  "pretty-json-stringify": "^0.0.2",
166
- "puppeteer": "^10.2.0",
166
+ "puppeteer": "13.7.0",
167
167
  "resolve": "^1.20.0",
168
168
  "rollup": "^2.52.7",
169
169
  "rollup-plugin-node-resolve": "^5.2.0",
@@ -181,7 +181,7 @@
181
181
  },
182
182
  "dependencies": {
183
183
  "@sentry/node": "^6.17.4",
184
- "axe-core": "4.3.5",
184
+ "axe-core": "4.4.1",
185
185
  "chrome-launcher": "^0.15.0",
186
186
  "configstore": "^5.0.1",
187
187
  "csp_evaluator": "1.1.0",
@@ -190,25 +190,27 @@
190
190
  "http-link-header": "^0.8.0",
191
191
  "intl-messageformat": "^4.4.0",
192
192
  "jpeg-js": "^0.4.3",
193
- "js-library-detector": "^6.4.0",
193
+ "js-library-detector": "^6.5.0",
194
194
  "lighthouse-logger": "^1.3.0",
195
- "lighthouse-stack-packs": "^1.7.0",
195
+ "lighthouse-stack-packs": "^1.8.1",
196
196
  "lodash": "^4.17.21",
197
197
  "lookup-closest-locale": "6.2.0",
198
198
  "metaviewport-parser": "0.2.0",
199
199
  "open": "^8.4.0",
200
200
  "parse-cache-control": "1.0.1",
201
201
  "ps-list": "^8.0.0",
202
+ "puppeteer-core": "^13.7.0",
202
203
  "robots-parser": "^3.0.0",
203
204
  "semver": "^5.3.0",
204
205
  "speedline-core": "^1.4.3",
205
- "third-party-web": "^0.12.7",
206
+ "third-party-web": "^0.17.1",
206
207
  "ws": "^7.0.0",
207
208
  "yargs": "^17.3.1",
208
209
  "yargs-parser": "^21.0.0"
209
210
  },
210
211
  "resolutions": {
211
- "puppeteer/**/devtools-protocol": "0.0.975298"
212
+ "puppeteer/**/devtools-protocol": "0.0.999451",
213
+ "puppeteer-core/**/devtools-protocol": "0.0.999451"
212
214
  },
213
215
  "repository": "GoogleChrome/lighthouse",
214
216
  "keywords": [
@@ -300,6 +300,9 @@
300
300
  /* This is normally the height of the topbar, but we want it to stick to the top of our scroll container .lh-container` */
301
301
  top: 0;
302
302
  }
303
+ .lh-devtools .lh-element-screenshot__overlay {
304
+ position: absolute;
305
+ }
303
306
 
304
307
  @keyframes fadeIn {
305
308
  0% { opacity: 0;}
@@ -1062,8 +1065,8 @@
1062
1065
  }
1063
1066
 
1064
1067
  /* Report */
1065
- .lh-list > div:not(:last-child) {
1066
- padding-bottom: 20px;
1068
+ .lh-list > :not(:last-child) {
1069
+ margin-bottom: calc(var(--default-padding) * 2);
1067
1070
  }
1068
1071
 
1069
1072
  .lh-header-container {
@@ -16,3 +16,4 @@ export {DOM} from '../renderer/dom.js';
16
16
  export {ReportRenderer} from '../renderer/report-renderer.js';
17
17
  export {ReportUIFeatures} from '../renderer/report-ui-features.js';
18
18
  export {renderReport} from '../renderer/api.js';
19
+ export {swapLocale, format} from '../../shared/localization/i18n-module.js';