lighthouse 9.5.0-dev.20230131 → 9.5.0-dev.20230202

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/dobetterweb/{password-inputs-can-be-pasted-into.d.ts → paste-preventing-inputs.d.ts} +3 -3
  2. package/core/audits/dobetterweb/{password-inputs-can-be-pasted-into.js → paste-preventing-inputs.js} +17 -15
  3. package/core/config/default-config.js +2 -4
  4. package/core/gather/driver/prepare.d.ts +6 -0
  5. package/core/gather/driver/prepare.js +26 -12
  6. package/core/gather/gatherers/bf-cache-failures.js +4 -1
  7. package/core/gather/gatherers/inputs.js +6 -0
  8. package/core/gather/navigation-runner.js +10 -0
  9. package/core/gather/timespan-runner.js +10 -1
  10. package/core/legacy/config/legacy-default-config.js +0 -1
  11. package/core/legacy/gather/driver.d.ts +4 -3
  12. package/core/legacy/gather/driver.js +6 -3
  13. package/core/legacy/gather/gather-runner.js +1 -1
  14. package/core/runner.d.ts +1 -6
  15. package/core/runner.js +9 -20
  16. package/dist/report/bundle.esm.js +7 -9
  17. package/dist/report/flow.js +2 -2
  18. package/dist/report/standalone.js +2 -2
  19. package/package.json +1 -1
  20. package/report/renderer/report-ui-features.js +1 -1
  21. package/report/renderer/report-utils.d.ts +2 -2
  22. package/report/renderer/report-utils.js +6 -8
  23. package/report/test/renderer/report-ui-features-test.js +20 -31
  24. package/shared/localization/locales/ar-XB.json +0 -9
  25. package/shared/localization/locales/ar.json +0 -9
  26. package/shared/localization/locales/bg.json +0 -9
  27. package/shared/localization/locales/ca.json +0 -9
  28. package/shared/localization/locales/cs.json +0 -9
  29. package/shared/localization/locales/da.json +0 -9
  30. package/shared/localization/locales/de.json +0 -9
  31. package/shared/localization/locales/el.json +0 -9
  32. package/shared/localization/locales/en-GB.json +0 -9
  33. package/shared/localization/locales/en-US.json +6 -6
  34. package/shared/localization/locales/en-XA.json +0 -9
  35. package/shared/localization/locales/en-XL.json +6 -6
  36. package/shared/localization/locales/es-419.json +0 -9
  37. package/shared/localization/locales/es.json +0 -9
  38. package/shared/localization/locales/fi.json +0 -9
  39. package/shared/localization/locales/fil.json +0 -9
  40. package/shared/localization/locales/fr.json +0 -9
  41. package/shared/localization/locales/he.json +0 -9
  42. package/shared/localization/locales/hi.json +0 -9
  43. package/shared/localization/locales/hr.json +0 -9
  44. package/shared/localization/locales/hu.json +0 -9
  45. package/shared/localization/locales/id.json +0 -9
  46. package/shared/localization/locales/it.json +0 -9
  47. package/shared/localization/locales/ja.json +0 -9
  48. package/shared/localization/locales/ko.json +0 -9
  49. package/shared/localization/locales/lt.json +0 -9
  50. package/shared/localization/locales/lv.json +0 -9
  51. package/shared/localization/locales/nl.json +0 -9
  52. package/shared/localization/locales/no.json +0 -9
  53. package/shared/localization/locales/pl.json +0 -9
  54. package/shared/localization/locales/pt-PT.json +0 -9
  55. package/shared/localization/locales/pt.json +0 -9
  56. package/shared/localization/locales/ro.json +0 -9
  57. package/shared/localization/locales/ru.json +0 -9
  58. package/shared/localization/locales/sk.json +0 -9
  59. package/shared/localization/locales/sl.json +0 -9
  60. package/shared/localization/locales/sr-Latn.json +0 -9
  61. package/shared/localization/locales/sr.json +0 -9
  62. package/shared/localization/locales/sv.json +0 -9
  63. package/shared/localization/locales/ta.json +0 -9
  64. package/shared/localization/locales/te.json +0 -9
  65. package/shared/localization/locales/th.json +0 -9
  66. package/shared/localization/locales/tr.json +0 -9
  67. package/shared/localization/locales/uk.json +0 -9
  68. package/shared/localization/locales/vi.json +0 -9
  69. package/shared/localization/locales/zh-HK.json +0 -9
  70. package/shared/localization/locales/zh-TW.json +0 -9
  71. package/shared/localization/locales/zh.json +0 -9
  72. package/tsconfig.json +0 -1
  73. package/types/artifacts.d.ts +2 -5
  74. package/types/lhr/lhr.d.ts +3 -11
  75. package/core/gather/gatherers/dobetterweb/password-inputs-with-prevented-paste.d.ts +0 -10
  76. package/core/gather/gatherers/dobetterweb/password-inputs-with-prevented-paste.js +0 -49
@@ -1,5 +1,5 @@
1
- export default PasswordInputsCanBePastedIntoAudit;
2
- declare class PasswordInputsCanBePastedIntoAudit extends Audit {
1
+ export default PastePreventingInputsAudit;
2
+ declare class PastePreventingInputsAudit extends Audit {
3
3
  /**
4
4
  * @param {LH.Artifacts} artifacts
5
5
  * @return {LH.Audit.Product}
@@ -12,4 +12,4 @@ export namespace UIStrings {
12
12
  const description: string;
13
13
  }
14
14
  import { Audit } from "../audit.js";
15
- //# sourceMappingURL=password-inputs-can-be-pasted-into.d.ts.map
15
+ //# sourceMappingURL=paste-preventing-inputs.d.ts.map
@@ -1,5 +1,5 @@
1
1
  /**
2
- * @license Copyright 2016 The Lighthouse Authors. All Rights Reserved.
2
+ * @license Copyright 2023 The Lighthouse Authors. All Rights Reserved.
3
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
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
5
  */
@@ -8,28 +8,29 @@ import {Audit} from '../audit.js';
8
8
  import * as i18n from '../../lib/i18n/i18n.js';
9
9
 
10
10
  const UIStrings = {
11
- /** Title of a Lighthouse audit that provides detail on the ability to paste into password fields. This descriptive title is shown to users when the page allows pasting of content into password fields. */
12
- title: 'Allows users to paste into password fields',
13
- /** Title of a Lighthouse audit that provides detail on the ability to paste into password fields. This descriptive title is shown to users when the page does not allow pasting of content into password fields. */
14
- failureTitle: 'Prevents users to paste into password fields',
15
- /** Description of a Lighthouse audit that tells the user why they should allow pasting of content into password fields. 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. */
16
- description: 'Preventing password pasting undermines good security policy. ' +
17
- '[Learn more about user-friendly password fields](https://developer.chrome.com/docs/lighthouse/best-practices/password-inputs-can-be-pasted-into/).',
11
+ /** Title of a Lighthouse audit that provides detail on the ability to paste into input fields. This descriptive title is shown to users when the page allows pasting of content into input fields. */
12
+ title: 'Allows users to paste into input fields',
13
+ /** Title of a Lighthouse audit that provides detail on the ability to paste into input fields. This descriptive title is shown to users when the page does not allow pasting of content into input fields. */
14
+ failureTitle: 'Prevents users from pasting into input fields',
15
+ /** Description of a Lighthouse audit that tells the user why they should allow pasting of content into input fields. This is displayed after a user expands the section to see more. No character length limits. 'Learn More' becomes link text to additional documentation. */
16
+ description: 'Preventing input pasting is a UX anti-pattern, ' +
17
+ 'and undermines good security policy. ' +
18
+ '[Learn more about user-friendly input fields](https://developer.chrome.com/docs/lighthouse/best-practices/paste-preventing-inputs/).',
18
19
  };
19
20
 
20
21
  const str_ = i18n.createIcuMessageFn(import.meta.url, UIStrings);
21
22
 
22
- class PasswordInputsCanBePastedIntoAudit extends Audit {
23
+ class PastePreventingInputsAudit extends Audit {
23
24
  /**
24
25
  * @return {LH.Audit.Meta}
25
26
  */
26
27
  static get meta() {
27
28
  return {
28
- id: 'password-inputs-can-be-pasted-into',
29
+ id: 'paste-preventing-inputs',
29
30
  title: str_(UIStrings.title),
30
31
  failureTitle: str_(UIStrings.failureTitle),
31
32
  description: str_(UIStrings.description),
32
- requiredArtifacts: ['PasswordInputsWithPreventedPaste'],
33
+ requiredArtifacts: ['Inputs'],
33
34
  };
34
35
  }
35
36
 
@@ -38,14 +39,15 @@ class PasswordInputsCanBePastedIntoAudit extends Audit {
38
39
  * @return {LH.Audit.Product}
39
40
  */
40
41
  static audit(artifacts) {
41
- const passwordInputsWithPreventedPaste = artifacts.PasswordInputsWithPreventedPaste;
42
+ const inputsWithPreventsPaste = artifacts.Inputs.inputs.filter(input => input.preventsPaste);
42
43
 
43
44
  /** @type {LH.Audit.Details.Table['items']} */
44
45
  const items = [];
45
46
 
46
- passwordInputsWithPreventedPaste.forEach(input => {
47
+ inputsWithPreventsPaste.forEach(input => {
47
48
  items.push({
48
49
  node: Audit.makeNodeItem(input.node),
50
+ type: input.type,
49
51
  });
50
52
  });
51
53
 
@@ -55,11 +57,11 @@ class PasswordInputsCanBePastedIntoAudit extends Audit {
55
57
  ];
56
58
 
57
59
  return {
58
- score: Number(passwordInputsWithPreventedPaste.length === 0),
60
+ score: Number(inputsWithPreventsPaste.length === 0),
59
61
  details: Audit.makeTableDetails(headings, items),
60
62
  };
61
63
  }
62
64
  }
63
65
 
64
- export default PasswordInputsCanBePastedIntoAudit;
66
+ export default PastePreventingInputsAudit;
65
67
  export {UIStrings};
@@ -150,7 +150,6 @@ const artifacts = {
150
150
  MetaElements: '',
151
151
  NetworkUserAgent: '',
152
152
  OptimizedImages: '',
153
- PasswordInputsWithPreventedPaste: '',
154
153
  ResponseCompression: '',
155
154
  RobotsTxt: '',
156
155
  ServiceWorker: '',
@@ -200,7 +199,6 @@ const defaultConfig = {
200
199
  {id: artifacts.MetaElements, gatherer: 'meta-elements'},
201
200
  {id: artifacts.NetworkUserAgent, gatherer: 'network-user-agent'},
202
201
  {id: artifacts.OptimizedImages, gatherer: 'dobetterweb/optimized-images'},
203
- {id: artifacts.PasswordInputsWithPreventedPaste, gatherer: 'dobetterweb/password-inputs-with-prevented-paste'},
204
202
  {id: artifacts.ResponseCompression, gatherer: 'dobetterweb/response-compression'},
205
203
  {id: artifacts.RobotsTxt, gatherer: 'seo/robots-txt'},
206
204
  {id: artifacts.ServiceWorker, gatherer: 'service-worker'},
@@ -361,7 +359,7 @@ const defaultConfig = {
361
359
  'dobetterweb/no-document-write',
362
360
  'dobetterweb/js-libraries',
363
361
  'dobetterweb/notification-on-start',
364
- 'dobetterweb/password-inputs-can-be-pasted-into',
362
+ 'dobetterweb/paste-preventing-inputs',
365
363
  'dobetterweb/uses-http2',
366
364
  'dobetterweb/uses-passive-event-listeners',
367
365
  'seo/meta-description',
@@ -614,7 +612,7 @@ const defaultConfig = {
614
612
  {id: 'notification-on-start', weight: 1, group: 'best-practices-trust-safety'},
615
613
  {id: 'csp-xss', weight: 0, group: 'best-practices-trust-safety'},
616
614
  // User Experience
617
- {id: 'password-inputs-can-be-pasted-into', weight: 1, group: 'best-practices-ux'},
615
+ {id: 'paste-preventing-inputs', weight: 1, group: 'best-practices-ux'},
618
616
  {id: 'image-aspect-ratio', weight: 1, group: 'best-practices-ux'},
619
617
  {id: 'image-size-responsive', weight: 1, group: 'best-practices-ux'},
620
618
  {id: 'preload-fonts', weight: 1, group: 'best-practices-ux'},
@@ -43,4 +43,10 @@ export function prepareTargetForIndividualNavigation(session: LH.Gatherer.FRProt
43
43
  }): Promise<{
44
44
  warnings: Array<LH.IcuMessage>;
45
45
  }>;
46
+ /**
47
+ * Enables `Debugger` domain to receive async stacktrace information on network request initiators.
48
+ * This is critical for tracking attribution of tasks and performance simulation accuracy.
49
+ * @param {LH.Gatherer.FRProtocolSession} session
50
+ */
51
+ export function enableAsyncStacks(session: LH.Gatherer.FRProtocolSession): Promise<() => Promise<void>>;
46
52
  //# sourceMappingURL=prepare.d.ts.map
@@ -22,17 +22,33 @@ async function enableAsyncStacks(session) {
22
22
  await session.sendCommand('Debugger.setAsyncCallStackDepth', {maxDepth: 8});
23
23
  };
24
24
 
25
- // Resume any pauses that make it through `setSkipAllPauses`
26
- session.on('Debugger.paused', () => session.sendCommand('Debugger.resume'));
25
+ /**
26
+ * Resume any pauses that make it through `setSkipAllPauses`
27
+ */
28
+ function onDebuggerPaused() {
29
+ session.sendCommand('Debugger.resume');
30
+ }
27
31
 
28
- // `Debugger.setSkipAllPauses` is reset after every navigation, so retrigger it on main frame navigations.
29
- // See https://bugs.chromium.org/p/chromium/issues/detail?id=990945&q=setSkipAllPauses&can=2
30
- session.on('Page.frameNavigated', event => {
32
+ /**
33
+ * `Debugger.setSkipAllPauses` is reset after every navigation, so retrigger it on main frame navigations.
34
+ * See https://bugs.chromium.org/p/chromium/issues/detail?id=990945&q=setSkipAllPauses&can=2
35
+ * @param {LH.Crdp.Page.FrameNavigatedEvent} event
36
+ */
37
+ function onFrameNavigated(event) {
31
38
  if (event.frame.parentId) return;
32
39
  enable().catch(err => log.error('Driver', err));
33
- });
40
+ }
41
+
42
+ session.on('Debugger.paused', onDebuggerPaused);
43
+ session.on('Page.frameNavigated', onFrameNavigated);
34
44
 
35
45
  await enable();
46
+
47
+ return async () => {
48
+ await session.sendCommand('Debugger.disable');
49
+ session.off('Debugger.paused', onDebuggerPaused);
50
+ session.off('Page.frameNavigated', onFrameNavigated);
51
+ };
36
52
  }
37
53
 
38
54
  /**
@@ -128,15 +144,12 @@ async function prepareThrottlingAndNetwork(session, settings, options) {
128
144
  * @param {LH.Gatherer.FRTransitionalDriver} driver
129
145
  * @param {LH.Config.Settings} settings
130
146
  */
131
- async function prepareDeviceEmulationAndAsyncStacks(driver, settings) {
147
+ async function prepareDeviceEmulation(driver, settings) {
132
148
  // Enable network domain here so future calls to `emulate()` don't clear cache (https://github.com/GoogleChrome/lighthouse/issues/12631)
133
149
  await driver.defaultSession.sendCommand('Network.enable');
134
150
 
135
151
  // Emulate our target device screen and user agent.
136
152
  await emulation.emulate(driver.defaultSession, settings);
137
-
138
- // Enable better stacks on network requests.
139
- await enableAsyncStacks(driver.defaultSession);
140
153
  }
141
154
 
142
155
  /**
@@ -149,7 +162,7 @@ async function prepareTargetForTimespanMode(driver, settings) {
149
162
  const status = {msg: 'Preparing target for timespan mode', id: 'lh:prepare:timespanMode'};
150
163
  log.time(status);
151
164
 
152
- await prepareDeviceEmulationAndAsyncStacks(driver, settings);
165
+ await prepareDeviceEmulation(driver, settings);
153
166
  await prepareThrottlingAndNetwork(driver.defaultSession, settings, {
154
167
  disableThrottling: false,
155
168
  blockedUrlPatterns: undefined,
@@ -171,7 +184,7 @@ async function prepareTargetForNavigationMode(driver, settings) {
171
184
  const status = {msg: 'Preparing target for navigation mode', id: 'lh:prepare:navigationMode'};
172
185
  log.time(status);
173
186
 
174
- await prepareDeviceEmulationAndAsyncStacks(driver, settings);
187
+ await prepareDeviceEmulation(driver, settings);
175
188
 
176
189
  // Automatically handle any JavaScript dialogs to prevent a hung renderer.
177
190
  await dismissJavaScriptDialogs(driver.defaultSession);
@@ -227,4 +240,5 @@ export {
227
240
  prepareTargetForTimespanMode,
228
241
  prepareTargetForNavigationMode,
229
242
  prepareTargetForIndividualNavigation,
243
+ enableAsyncStacks,
230
244
  };
@@ -9,6 +9,7 @@ import {waitForFrameNavigated, waitForLoadEvent} from '../driver/wait-for-condit
9
9
  import DevtoolsLog from './devtools-log.js';
10
10
 
11
11
  const FAILURE_EVENT_TIMEOUT = 100;
12
+ const TEMP_PAGE_PAUSE_TIMEOUT = 100;
12
13
 
13
14
  class BFCacheFailures extends FRGatherer {
14
15
  /** @type {LH.Gatherer.GathererMeta<'DevtoolsLog'>} */
@@ -109,7 +110,9 @@ class BFCacheFailures extends FRGatherer {
109
110
  // https://github.com/GoogleChrome/lighthouse/issues/14665
110
111
  await Promise.all([
111
112
  session.sendCommand('Page.navigate', {url: 'chrome://terms'}),
112
- waitForLoadEvent(session, 0).promise,
113
+ // DevTools e2e tests can sometimes fail on the next command if we progress too fast.
114
+ // The only reliable way to prevent this is to wait for an arbitrary period of time after load.
115
+ waitForLoadEvent(session, TEMP_PAGE_PAUSE_TIMEOUT).promise,
113
116
  ]);
114
117
 
115
118
  const [, frameNavigatedEvent] = await Promise.all([
@@ -61,6 +61,11 @@ function collectElements() {
61
61
  return [...labelElToArtifact.keys()].indexOf(labelEl);
62
62
  });
63
63
 
64
+ let preventsPaste;
65
+ if (!inputEl.readOnly) {
66
+ preventsPaste = !inputEl.dispatchEvent(new ClipboardEvent('paste', {cancelable: true}));
67
+ }
68
+
64
69
  inputArtifacts.push({
65
70
  parentFormIndex,
66
71
  labelIndices,
@@ -74,6 +79,7 @@ function collectElements() {
74
79
  // Requires `--enable-features=AutofillShowTypePredictions`.
75
80
  prediction: inputEl.getAttribute('autofill-prediction'),
76
81
  },
82
+ preventsPaste,
77
83
  // @ts-expect-error - getNodeDetails put into scope via stringification
78
84
  node: getNodeDetails(inputEl),
79
85
  });
@@ -227,6 +227,10 @@ async function _navigation(navigationContext) {
227
227
  };
228
228
 
229
229
  const setupResult = await _setupNavigation(navigationContext);
230
+
231
+ const disableAsyncStacks =
232
+ await prepare.enableAsyncStacks(navigationContext.driver.defaultSession);
233
+
230
234
  await collectPhaseArtifacts({phase: 'startInstrumentation', ...phaseState});
231
235
  await collectPhaseArtifacts({phase: 'startSensitiveInstrumentation', ...phaseState});
232
236
  const navigateResult = await _navigate(navigationContext);
@@ -244,6 +248,12 @@ async function _navigation(navigationContext) {
244
248
 
245
249
  await collectPhaseArtifacts({phase: 'stopSensitiveInstrumentation', ...phaseState});
246
250
  await collectPhaseArtifacts({phase: 'stopInstrumentation', ...phaseState});
251
+
252
+ // bf-cache-failures can emit `Page.frameNavigated` at the end of the run.
253
+ // This can cause us to issue protocol commands after the target closes.
254
+ // We should disable our `Page.frameNavigated` handlers before that.
255
+ await disableAsyncStacks();
256
+
247
257
  await _cleanupNavigation(navigationContext);
248
258
 
249
259
  return _computeNavigationResult(navigationContext, phaseState, setupResult, navigateResult);
@@ -9,7 +9,7 @@ import log from 'lighthouse-logger';
9
9
  import {Driver} from './driver.js';
10
10
  import {Runner} from '../runner.js';
11
11
  import {getEmptyArtifactState, collectPhaseArtifacts, awaitArtifacts} from './runner-helpers.js';
12
- import {prepareTargetForTimespanMode} from './driver/prepare.js';
12
+ import {enableAsyncStacks, prepareTargetForTimespanMode} from './driver/prepare.js';
13
13
  import {initializeConfig} from '../config/config.js';
14
14
  import {getBaseArtifacts, finalizeArtifacts} from './base-artifacts.js';
15
15
 
@@ -44,6 +44,9 @@ async function startTimespanGather(page, options = {}) {
44
44
  };
45
45
 
46
46
  await prepareTargetForTimespanMode(driver, resolvedConfig.settings);
47
+
48
+ const disableAsyncStacks = await enableAsyncStacks(driver.defaultSession);
49
+
47
50
  await collectPhaseArtifacts({phase: 'startInstrumentation', ...phaseOptions});
48
51
  await collectPhaseArtifacts({phase: 'startSensitiveInstrumentation', ...phaseOptions});
49
52
 
@@ -58,6 +61,12 @@ async function startTimespanGather(page, options = {}) {
58
61
 
59
62
  await collectPhaseArtifacts({phase: 'stopSensitiveInstrumentation', ...phaseOptions});
60
63
  await collectPhaseArtifacts({phase: 'stopInstrumentation', ...phaseOptions});
64
+
65
+ // bf-cache-failures can emit `Page.frameNavigated` at the end of the run.
66
+ // This can cause us to issue protocol commands after the target closes.
67
+ // We should disable our `Page.frameNavigated` handlers before that.
68
+ await disableAsyncStacks();
69
+
61
70
  await collectPhaseArtifacts({phase: 'getArtifact', ...phaseOptions});
62
71
  await driver.disconnect();
63
72
 
@@ -59,7 +59,6 @@ legacyDefaultConfig.passes = [{
59
59
  'dobetterweb/doctype',
60
60
  'dobetterweb/domstats',
61
61
  'dobetterweb/optimized-images',
62
- 'dobetterweb/password-inputs-with-prevented-paste',
63
62
  'dobetterweb/response-compression',
64
63
  'dobetterweb/tags-blocking-first-paint',
65
64
  'seo/font-size',
@@ -200,12 +200,13 @@ export class Driver implements LH.Gatherer.FRTransitionalDriver {
200
200
  /**
201
201
  * Begin recording devtools protocol messages.
202
202
  */
203
- beginDevtoolsLog(): void;
203
+ beginDevtoolsLog(): Promise<void>;
204
+ _disableAsyncStacks: (() => Promise<void>) | undefined;
204
205
  /**
205
206
  * Stop recording to devtoolsLog and return log contents.
206
- * @return {LH.DevtoolsLog}
207
+ * @return {Promise<LH.DevtoolsLog>}
207
208
  */
208
- endDevtoolsLog(): LH.DevtoolsLog;
209
+ endDevtoolsLog(): Promise<LH.DevtoolsLog>;
209
210
  url(): Promise<string>;
210
211
  }
211
212
  import * as LH from "../../../types/lh.js";
@@ -16,6 +16,7 @@ import {fetchResponseBodyFromCache} from '../../gather/driver/network.js';
16
16
  import {DevtoolsMessageLog} from '../../gather/gatherers/devtools-log.js';
17
17
  import TraceGatherer from '../../gather/gatherers/trace.js';
18
18
  import {getBrowserVersion} from '../../gather/driver/environment.js';
19
+ import {enableAsyncStacks} from '../../gather/driver/prepare.js';
19
20
 
20
21
  // Controls how long to wait for a response after sending a DevTools protocol command.
21
22
  const DEFAULT_PROTOCOL_TIMEOUT = 30000;
@@ -443,17 +444,19 @@ class Driver {
443
444
  /**
444
445
  * Begin recording devtools protocol messages.
445
446
  */
446
- beginDevtoolsLog() {
447
+ async beginDevtoolsLog() {
448
+ this._disableAsyncStacks = await enableAsyncStacks(this);
447
449
  this._devtoolsLog.reset();
448
450
  this._devtoolsLog.beginRecording();
449
451
  }
450
452
 
451
453
  /**
452
454
  * Stop recording to devtoolsLog and return log contents.
453
- * @return {LH.DevtoolsLog}
455
+ * @return {Promise<LH.DevtoolsLog>}
454
456
  */
455
- endDevtoolsLog() {
457
+ async endDevtoolsLog() {
456
458
  this._devtoolsLog.endRecording();
459
+ await this._disableAsyncStacks?.();
457
460
  return this._devtoolsLog.messages;
458
461
  }
459
462
 
@@ -235,7 +235,7 @@ class GatherRunner {
235
235
  id: `lh:gather:getDevtoolsLog`,
236
236
  };
237
237
  log.time(status);
238
- const devtoolsLog = driver.endDevtoolsLog();
238
+ const devtoolsLog = await driver.endDevtoolsLog();
239
239
  const networkRecords = await NetworkRecords.request(devtoolsLog, passContext);
240
240
  log.timeEnd(status);
241
241
 
package/core/runner.d.ts CHANGED
@@ -20,12 +20,7 @@ export class Runner {
20
20
  * @param {LH.Artifacts} artifacts
21
21
  * @param {LH.Artifacts.ComputedContext} context
22
22
  */
23
- static getEntityClassification(artifacts: LH.Artifacts, context: LH.Artifacts.ComputedContext): Promise<{
24
- list: import("../types/lhr/lhr.js").default.LhrEntity[];
25
- firstParty: string | undefined;
26
- entityIndexByOrigin: Record<string, number>;
27
- entityIndexByName: Record<string, number>;
28
- } | undefined>;
23
+ static getEntityClassification(artifacts: LH.Artifacts, context: LH.Artifacts.ComputedContext): Promise<import("../types/lhr/lhr.js").default.LhrEntity[] | undefined>;
29
24
  /**
30
25
  * User can run -G solo, -A solo, or -GA together
31
26
  * -G and -A will run partial lighthouse pipelines,
package/core/runner.js CHANGED
@@ -154,37 +154,26 @@ class Runner {
154
154
 
155
155
  /** @type {Array<LH.Result.LhrEntity>} */
156
156
  const entities = [];
157
- /** @type {Record<string, number>} */
158
- const entityIndexByOrigin = {};
159
- /** @type {Record<string, number>} */
160
- const entityIndexByName = {};
161
-
162
157
  for (const [entity, entityUrls] of classifiedEntities.urlsByEntity) {
158
+ const uniqueOrigins = new Set();
159
+ for (const url of entityUrls) {
160
+ const origin = UrlUtils.getOrigin(url);
161
+ if (origin) uniqueOrigins.add(origin);
162
+ }
163
+
163
164
  /** @type {LH.Result.LhrEntity} */
164
165
  const shortEntity = {
165
166
  name: entity.name,
166
167
  homepage: entity.homepage,
168
+ origins: [...uniqueOrigins],
167
169
  };
168
-
169
170
  // Reduce payload size in LHR JSON by omitting whats falsy.
170
171
  if (entity === classifiedEntities.firstParty) shortEntity.isFirstParty = true;
171
172
  if (entity.isUnrecognized) shortEntity.isUnrecognized = true;
172
-
173
- const id = entities.push(shortEntity) - 1;
174
- for (const url of entityUrls) {
175
- const origin = UrlUtils.getOrigin(url);
176
- if (!origin) continue;
177
- entityIndexByOrigin[origin] = id;
178
- }
179
- entityIndexByName[shortEntity.name] = id;
173
+ entities.push(shortEntity);
180
174
  }
181
175
 
182
- return {
183
- list: entities,
184
- firstParty: classifiedEntities.firstParty?.name,
185
- entityIndexByOrigin,
186
- entityIndexByName,
187
- };
176
+ return entities;
188
177
  }
189
178
 
190
179
  /**
@@ -1626,11 +1626,11 @@ class ReportUtils {
1626
1626
 
1627
1627
  /**
1628
1628
  * Mark TableItems/OpportunityItems with entity names.
1629
- * @param {LH.Result.Entities|undefined} entityClassification
1629
+ * @param {LH.Result.Entities|undefined} entities
1630
1630
  * @param {import('../../types/lhr/audit-result').Result} audit
1631
1631
  */
1632
- static classifyEntities(entityClassification, audit) {
1633
- if (!entityClassification) return;
1632
+ static classifyEntities(entities, audit) {
1633
+ if (!entities) return;
1634
1634
  if (audit.details?.type !== 'opportunity' && audit.details?.type !== 'table') {
1635
1635
  return;
1636
1636
  }
@@ -1647,17 +1647,15 @@ class ReportUtils {
1647
1647
  const url = urlLocatorFn(item);
1648
1648
  if (!url) continue;
1649
1649
 
1650
- let origin;
1650
+ let origin = '';
1651
1651
  try {
1652
1652
  // Non-URLs can appear in valueType: url columns, like 'Unattributable'
1653
1653
  origin = Util.parseURL(url).origin;
1654
1654
  } catch {}
1655
1655
  if (!origin) continue;
1656
1656
 
1657
- const entityIndex = entityClassification.entityIndexByOrigin[origin];
1658
- if (entityIndex === undefined) return;
1659
- const entity = entityClassification.list[entityIndex];
1660
- item.entity = entity.name;
1657
+ const entity = entities.find(e => e.origins.includes(origin));
1658
+ if (entity) item.entity = entity.name;
1661
1659
  }
1662
1660
  }
1663
1661
 
@@ -5832,7 +5830,7 @@ class ReportUIFeatures {
5832
5830
  */
5833
5831
  _getThirdPartyRows(rowEls, finalDisplayedUrl) {
5834
5832
  const finalDisplayedUrlRootDomain = Util.getRootDomain(finalDisplayedUrl);
5835
- const firstPartyEntityName = this.json.entities?.firstParty;
5833
+ const firstPartyEntityName = this.json.entities?.find(e => e.isFirstParty === true)?.name;
5836
5834
 
5837
5835
  /** @type {Array<HTMLElement>} */
5838
5836
  const thirdPartyRowEls = [];