lighthouse 9.5.0-dev.20220605 → 9.5.0-dev.20220608

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.
@@ -1,8 +1,6 @@
1
1
  {
2
2
  "extends": "../tsconfig-base.json",
3
3
  "compilerOptions": {
4
- "outDir": "../.tmp/tsbuildinfo/flow-report",
5
-
6
4
  // Limit to base JS and DOM defs.
7
5
  "lib": ["es2020", "dom", "dom.iterable"],
8
6
  // Selectively include types from node_modules/.
@@ -38,6 +38,7 @@ const BASE_ARTIFACT_BLANKS = {
38
38
  HostUserAgent: '',
39
39
  NetworkUserAgent: '',
40
40
  BenchmarkIndex: '',
41
+ BenchmarkIndexes: '',
41
42
  WebAppManifest: '',
42
43
  GatherContext: '',
43
44
  InstallabilityErrors: '',
@@ -14,6 +14,7 @@ const baseArtifactKeySource = {
14
14
  fetchTime: '',
15
15
  LighthouseRunWarnings: '',
16
16
  BenchmarkIndex: '',
17
+ BenchmarkIndexes: '',
17
18
  settings: '',
18
19
  Timing: '',
19
20
  URL: '',
@@ -16,7 +16,7 @@ const throwNotConnectedFn = () => {
16
16
  };
17
17
 
18
18
  /** @type {LH.Gatherer.FRProtocolSession} */
19
- const defaultSession = {
19
+ const throwingSession = {
20
20
  setTargetInfo: throwNotConnectedFn,
21
21
  hasNextProtocolTimeout: throwNotConnectedFn,
22
22
  getNextProtocolTimeout: throwNotConnectedFn,
@@ -39,14 +39,12 @@ class Driver {
39
39
  */
40
40
  constructor(page) {
41
41
  this._page = page;
42
- /** @type {LH.Gatherer.FRProtocolSession|undefined} */
43
- this._session = undefined;
44
42
  /** @type {ExecutionContext|undefined} */
45
43
  this._executionContext = undefined;
46
44
  /** @type {Fetcher|undefined} */
47
45
  this._fetcher = undefined;
48
46
 
49
- this.defaultSession = defaultSession;
47
+ this.defaultSession = throwingSession;
50
48
  }
51
49
 
52
50
  /** @return {LH.Gatherer.FRTransitionalDriver['executionContext']} */
@@ -68,20 +66,20 @@ class Driver {
68
66
 
69
67
  /** @return {Promise<void>} */
70
68
  async connect() {
71
- if (this._session) return;
69
+ if (this.defaultSession !== throwingSession) return;
72
70
  const status = {msg: 'Connecting to browser', id: 'lh:driver:connect'};
73
71
  log.time(status);
74
72
  const session = await this._page.target().createCDPSession();
75
- this._session = this.defaultSession = new ProtocolSession(session);
76
- this._executionContext = new ExecutionContext(this._session);
77
- this._fetcher = new Fetcher(this._session);
73
+ this.defaultSession = new ProtocolSession(session);
74
+ this._executionContext = new ExecutionContext(this.defaultSession);
75
+ this._fetcher = new Fetcher(this.defaultSession);
78
76
  log.timeEnd(status);
79
77
  }
80
78
 
81
79
  /** @return {Promise<void>} */
82
80
  async disconnect() {
83
- if (!this._session) return;
84
- await this._session.dispose();
81
+ if (this.defaultSession === throwingSession) return;
82
+ await this.defaultSession.dispose();
85
83
  }
86
84
  }
87
85
 
@@ -6,7 +6,6 @@
6
6
  'use strict';
7
7
 
8
8
  const LHError = require('../../lib/lh-error.js');
9
- const SessionEmitMonkeypatch = Symbol('monkeypatch');
10
9
 
11
10
  // Controls how long to wait for a response after sending a DevTools protocol command.
12
11
  const DEFAULT_PROTOCOL_TIMEOUT = 30000;
@@ -14,32 +13,22 @@ const DEFAULT_PROTOCOL_TIMEOUT = 30000;
14
13
  /** @implements {LH.Gatherer.FRProtocolSession} */
15
14
  class ProtocolSession {
16
15
  /**
17
- * @param {LH.Puppeteer.CDPSession} session
16
+ * @param {LH.Puppeteer.CDPSession} cdpSession
18
17
  */
19
- constructor(session) {
20
- this._session = session;
18
+ constructor(cdpSession) {
19
+ this._cdpSession = cdpSession;
21
20
  /** @type {LH.Crdp.Target.TargetInfo|undefined} */
22
21
  this._targetInfo = undefined;
23
22
  /** @type {number|undefined} */
24
23
  this._nextProtocolTimeout = undefined;
25
24
  /** @type {WeakMap<any, any>} */
26
25
  this._callbackMap = new WeakMap();
26
+ }
27
27
 
28
- // FIXME: Monkeypatch puppeteer to be able to listen to *all* protocol events.
29
- // This patched method will now emit a copy of every event on `*`.
30
- const originalEmit = session.emit;
31
- // @ts-expect-error - Test for the monkeypatch.
32
- if (originalEmit[SessionEmitMonkeypatch]) return;
33
- session.emit = (method, ...args) => {
34
- // OOPIF sessions need to emit their sessionId so downstream processors can recognize
35
- // the target the event came from.
36
- const sessionId = this._targetInfo && this._targetInfo.type === 'iframe' ?
37
- this._targetInfo.targetId : undefined;
38
- originalEmit.call(session, '*', {method, params: args[0], sessionId});
39
- return originalEmit.call(session, method, ...args);
40
- };
41
- // @ts-expect-error - It's monkeypatching 🤷‍♂️.
42
- session.emit[SessionEmitMonkeypatch] = true;
28
+ sessionId() {
29
+ return this._targetInfo && this._targetInfo.type === 'iframe' ?
30
+ this._targetInfo.targetId :
31
+ undefined;
43
32
  }
44
33
 
45
34
  /** @param {LH.Crdp.Target.TargetInfo} targetInfo */
@@ -75,7 +64,7 @@ class ProtocolSession {
75
64
  * @param {(...args: LH.CrdpEvents[E]) => void} callback
76
65
  */
77
66
  on(eventName, callback) {
78
- this._session.on(eventName, /** @type {*} */ (callback));
67
+ this._cdpSession.on(eventName, /** @type {*} */ (callback));
79
68
  }
80
69
 
81
70
  /**
@@ -85,7 +74,7 @@ class ProtocolSession {
85
74
  * @param {(...args: LH.CrdpEvents[E]) => void} callback
86
75
  */
87
76
  once(eventName, callback) {
88
- this._session.once(eventName, /** @type {*} */ (callback));
77
+ this._cdpSession.once(eventName, /** @type {*} */ (callback));
89
78
  }
90
79
 
91
80
  /**
@@ -110,19 +99,31 @@ class ProtocolSession {
110
99
  }
111
100
 
112
101
  /**
113
- * Bind to our custom event that fires for *any* protocol event.
102
+ * Bind to puppeteer's '*' event that fires for *any* protocol event,
103
+ * and wrap it with data about the protocol message instead of just the event.
114
104
  * @param {(payload: LH.Protocol.RawEventMessage) => void} callback
115
105
  */
116
106
  addProtocolMessageListener(callback) {
117
- this._session.on('*', /** @type {*} */ (callback));
107
+ /**
108
+ * @param {any} method
109
+ * @param {any} event
110
+ */
111
+ const listener = (method, event) => callback({
112
+ method,
113
+ params: event,
114
+ sessionId: this.sessionId(),
115
+ });
116
+ this._callbackMap.set(callback, listener);
117
+ this._cdpSession.on('*', /** @type {*} */ (listener));
118
118
  }
119
119
 
120
120
  /**
121
- * Unbind to our custom event that fires for *any* protocol event.
122
121
  * @param {(payload: LH.Protocol.RawEventMessage) => void} callback
123
122
  */
124
123
  removeProtocolMessageListener(callback) {
125
- this._session.off('*', /** @type {*} */ (callback));
124
+ const listener = this._callbackMap.get(callback);
125
+ if (!listener) return;
126
+ this._cdpSession.off('*', /** @type {*} */ (listener));
126
127
  }
127
128
 
128
129
  /**
@@ -132,7 +133,7 @@ class ProtocolSession {
132
133
  * @param {(...args: LH.CrdpEvents[E]) => void} callback
133
134
  */
134
135
  off(eventName, callback) {
135
- this._session.off(eventName, /** @type {*} */ (callback));
136
+ this._cdpSession.off(eventName, /** @type {*} */ (callback));
136
137
  }
137
138
 
138
139
  /**
@@ -155,7 +156,7 @@ class ProtocolSession {
155
156
  }));
156
157
  });
157
158
 
158
- const resultPromise = this._session.send(method, ...params);
159
+ const resultPromise = this._cdpSession.send(method, ...params);
159
160
  const resultWithTimeoutPromise = Promise.race([resultPromise, timeoutPromise]);
160
161
 
161
162
  return resultWithTimeoutPromise.finally(() => {
@@ -168,12 +169,12 @@ class ProtocolSession {
168
169
  * @return {Promise<void>}
169
170
  */
170
171
  async dispose() {
171
- this._session.removeAllListeners();
172
- await this._session.detach();
172
+ this._cdpSession.removeAllListeners();
173
+ await this._cdpSession.detach();
173
174
  }
174
175
 
175
176
  _getConnection() {
176
- const connection = this._session.connection();
177
+ const connection = this._cdpSession.connection();
177
178
  if (!connection) throw new Error('Connection has been closed.');
178
179
  return connection;
179
180
  }
@@ -82,7 +82,8 @@ class CriConnection extends Connection {
82
82
  */
83
83
  _runJsonCommand(command) {
84
84
  return new Promise((resolve, reject) => {
85
- const request = http.get({
85
+ const request = http.request({
86
+ method: 'PUT', // GET and POST are deprecated: https://crrev.com/c/3595822
86
87
  hostname: this.hostname,
87
88
  port: this.port,
88
89
  path: '/json/' + command,
@@ -109,6 +110,8 @@ class CriConnection extends Connection {
109
110
  });
110
111
  });
111
112
 
113
+ request.end();
114
+
112
115
  // This error handler is critical to ensuring Lighthouse exits cleanly even when Chrome crashes.
113
116
  // See https://github.com/GoogleChrome/lighthouse/pull/8583.
114
117
  request.on('error', reject);
@@ -103,13 +103,20 @@ class NetworkMonitor {
103
103
  };
104
104
 
105
105
  this._networkRecorder.on('requeststarted', reEmit('requeststarted'));
106
- this._networkRecorder.on('requestloaded', reEmit('requestloaded'));
106
+ this._networkRecorder.on('requestfinished', reEmit('requestfinished'));
107
107
 
108
108
  this._session.on('Page.frameNavigated', this._onFrameNavigated);
109
- this._targetManager.addTargetAttachedListener(this._onTargetAttached);
110
-
111
109
  await this._session.sendCommand('Page.enable');
112
- await this._targetManager.enable();
110
+
111
+ // Legacy driver does its own target management.
112
+ // @ts-expect-error
113
+ const isLegacyRunner = Boolean(this._session._domainEnabledCounts);
114
+ if (isLegacyRunner) {
115
+ this._session.addProtocolMessageListener(this._onProtocolMessage);
116
+ } else {
117
+ this._targetManager.addTargetAttachedListener(this._onTargetAttached);
118
+ await this._targetManager.enable();
119
+ }
113
120
  }
114
121
 
115
122
  /**
@@ -119,13 +126,21 @@ class NetworkMonitor {
119
126
  if (!this._targetManager) return;
120
127
 
121
128
  this._session.off('Page.frameNavigated', this._onFrameNavigated);
122
- this._targetManager.removeTargetAttachedListener(this._onTargetAttached);
123
129
 
124
- for (const session of this._sessions.values()) {
125
- session.removeProtocolMessageListener(this._onProtocolMessage);
126
- }
130
+ // Legacy driver does its own target management.
131
+ // @ts-expect-error
132
+ const isLegacyRunner = Boolean(this._session._domainEnabledCounts);
133
+ if (isLegacyRunner) {
134
+ this._session.removeProtocolMessageListener(this._onProtocolMessage);
135
+ } else {
136
+ this._targetManager.removeTargetAttachedListener(this._onTargetAttached);
127
137
 
128
- await this._targetManager.disable();
138
+ for (const session of this._sessions.values()) {
139
+ session.removeProtocolMessageListener(this._onProtocolMessage);
140
+ }
141
+
142
+ await this._targetManager.disable();
143
+ }
129
144
 
130
145
  this._frameNavigations = [];
131
146
  this._networkRecorder = undefined;
@@ -164,7 +164,8 @@ function waitForNetworkIdle(session, networkMonitor, networkQuietOptions) {
164
164
  const inflightRecords = networkMonitor.getInflightRequests();
165
165
  // If there are more than 20 inflight requests, load is still in full swing.
166
166
  // Wait until it calms down a bit to be a little less spammy.
167
- if (inflightRecords.length < 20) {
167
+ if (log.isVerbose() && inflightRecords.length < 20 && inflightRecords.length > 0) {
168
+ log.verbose('waitFor', `=== Waiting on ${inflightRecords.length} requests to finish`);
168
169
  for (const record of inflightRecords) {
169
170
  log.verbose('waitFor', `Waiting on ${record.url.slice(0, 120)} to finish`);
170
171
  }
@@ -172,7 +173,7 @@ function waitForNetworkIdle(session, networkMonitor, networkQuietOptions) {
172
173
  };
173
174
 
174
175
  networkMonitor.on('requeststarted', logStatus);
175
- networkMonitor.on('requestloaded', logStatus);
176
+ networkMonitor.on('requestfinished', logStatus);
176
177
  networkMonitor.on(busyEvent, logStatus);
177
178
 
178
179
  if (!networkQuietOptions.pretendDCLAlreadyFired) {
@@ -192,7 +193,7 @@ function waitForNetworkIdle(session, networkMonitor, networkQuietOptions) {
192
193
  networkMonitor.removeListener(busyEvent, onBusy);
193
194
  networkMonitor.removeListener(idleEvent, onIdle);
194
195
  networkMonitor.removeListener('requeststarted', logStatus);
195
- networkMonitor.removeListener('requestloaded', logStatus);
196
+ networkMonitor.removeListener('requestfinished', logStatus);
196
197
  networkMonitor.removeListener(busyEvent, logStatus);
197
198
  };
198
199
  });
@@ -20,6 +20,7 @@ const WebAppManifest = require('./gatherers/web-app-manifest.js');
20
20
  const InstallabilityErrors = require('./gatherers/installability-errors.js');
21
21
  const NetworkUserAgent = require('./gatherers/network-user-agent.js');
22
22
  const Stacks = require('./gatherers/stacks.js');
23
+ const URL = require('../lib/url-shim.js');
23
24
  const {finalizeArtifacts} = require('../fraggle-rock/gather/base-artifacts.js');
24
25
 
25
26
  /** @typedef {import('../gather/driver.js')} Driver */
@@ -491,6 +492,20 @@ class GatherRunner {
491
492
  const baseArtifacts = await GatherRunner.initializeBaseArtifacts(options);
492
493
  baseArtifacts.BenchmarkIndex = await getBenchmarkIndex(driver.executionContext);
493
494
 
495
+ // Hack for running benchmarkIndex extra times.
496
+ // Add a `bidx=20` query param, eg: https://www.example.com/?bidx=50
497
+ const parsedUrl = URL.isValid(options.requestedUrl) && new URL(options.requestedUrl);
498
+ if (options.settings.channel === 'lr' && parsedUrl && parsedUrl.searchParams.has('bidx')) {
499
+ const bidxRunCount = parsedUrl.searchParams.get('bidx') || 0;
500
+ // Add the first bidx into the new set
501
+ const indexes = [baseArtifacts.BenchmarkIndex];
502
+ for (let i = 0; i < bidxRunCount; i++) {
503
+ const bidx = await getBenchmarkIndex(driver.executionContext);
504
+ indexes.push(bidx);
505
+ }
506
+ baseArtifacts.BenchmarkIndexes = indexes;
507
+ }
508
+
494
509
  await GatherRunner.setupDriver(driver, options);
495
510
 
496
511
  let isFirstPass = true;
@@ -30,7 +30,7 @@ class DevtoolsLog extends FRGatherer {
30
30
  /** @type {NetworkMonitor|undefined} */
31
31
  this._networkMonitor = undefined;
32
32
 
33
- this._messageLog = new MessageLog(/^(Page|Network)\./);
33
+ this._messageLog = new MessageLog(/^(Page|Network|Target|Runtime)\./);
34
34
 
35
35
  /** @param {LH.Protocol.RawEventMessage} e */
36
36
  this._onProtocolMessage = e => this._messageLog.record(e);
@@ -9,7 +9,7 @@ const log = require('lighthouse-logger');
9
9
  const NetworkRequest = require('./network-request.js');
10
10
  const EventEmitter = require('events').EventEmitter;
11
11
 
12
- /** @typedef {'requeststarted'|'requestloaded'} NetworkRecorderEvent */
12
+ /** @typedef {'requeststarted'|'requestfinished'} NetworkRecorderEvent */
13
13
 
14
14
  class NetworkRecorder extends EventEmitter {
15
15
  /**
@@ -71,7 +71,7 @@ class NetworkRecorder extends EventEmitter {
71
71
  * @private
72
72
  */
73
73
  onRequestFinished(request) {
74
- this.emit('requestloaded', request);
74
+ this.emit('requestfinished', request);
75
75
  }
76
76
 
77
77
  // The below methods proxy network data into the NetworkRequest object which mimics the
@@ -95,6 +95,7 @@ class Runner {
95
95
  networkUserAgent: artifacts.NetworkUserAgent,
96
96
  hostUserAgent: artifacts.HostUserAgent,
97
97
  benchmarkIndex: artifacts.BenchmarkIndex,
98
+ benchmarkIndexes: artifacts.BenchmarkIndexes,
98
99
  credits,
99
100
  },
100
101
  audits: auditResultsById,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "lighthouse",
3
- "version": "9.5.0-dev.20220605",
3
+ "version": "9.5.0-dev.20220608",
4
4
  "description": "Automated auditing, performance metrics, and best practices for the web.",
5
5
  "main": "./lighthouse-core/index.js",
6
6
  "bin": {
@@ -125,8 +125,8 @@
125
125
  "@types/ws": "^7.0.0",
126
126
  "@types/yargs": "^17.0.8",
127
127
  "@types/yargs-parser": "^20.2.1",
128
- "@typescript-eslint/eslint-plugin": "^5.6.0",
129
- "@typescript-eslint/parser": "^5.6.0",
128
+ "@typescript-eslint/eslint-plugin": "^5.26.0",
129
+ "@typescript-eslint/parser": "^5.26.0",
130
130
  "acorn": "^8.5.0",
131
131
  "angular": "^1.7.4",
132
132
  "archiver": "^3.0.0",
@@ -175,7 +175,7 @@
175
175
  "terser": "^5.3.8",
176
176
  "ts-jest": "^27.0.4",
177
177
  "typed-query-selector": "^2.6.1",
178
- "typescript": "^4.5.2",
178
+ "typescript": "^4.7.2",
179
179
  "wait-for-expect": "^3.0.2",
180
180
  "webtreemap-cdt": "^3.2.1"
181
181
  },
@@ -1,8 +1,6 @@
1
1
  {
2
2
  "extends": "../../tsconfig-base.json",
3
3
  "compilerOptions": {
4
- "outDir": "../../.tmp/tsbuildinfo/report/generator",
5
-
6
4
  // Limit defs to base JS and DOM (for URL: https://github.com/DefinitelyTyped/DefinitelyTyped/issues/34960).
7
5
  "lib": ["es2020", "dom"],
8
6
  // Only include `@types/node` from node_modules/.
@@ -57,8 +57,8 @@ export class SwapLocaleFeature {
57
57
  const optionLocaleDisplay = new Intl.DisplayNames([locale], {type: 'language'});
58
58
 
59
59
  const optionLocaleName = optionLocaleDisplay.of(locale);
60
- const currentLocaleName = currentLocaleDisplay.of(locale);
61
- if (optionLocaleName !== currentLocaleName) {
60
+ const currentLocaleName = currentLocaleDisplay.of(locale) || locale;
61
+ if (optionLocaleName && optionLocaleName !== currentLocaleName) {
62
62
  optionEl.textContent = `${optionLocaleName} – ${currentLocaleName}`;
63
63
  } else {
64
64
  optionEl.textContent = currentLocaleName;
@@ -1,8 +1,6 @@
1
1
  {
2
2
  "extends": "../tsconfig-base.json",
3
3
  "compilerOptions": {
4
- "outDir": "../.tmp/tsbuildinfo/report",
5
-
6
4
  // Limit to base JS and DOM defs.
7
5
  "lib": ["es2020", "dom", "dom.iterable"],
8
6
  // Don't include any types from node_modules/.
@@ -1,8 +1,6 @@
1
1
  {
2
2
  "extends": "../tsconfig-base.json",
3
3
  "compilerOptions": {
4
- "outDir": "../../.tmp/tsbuildinfo/shared/",
5
-
6
4
  // Limit defs to base JS.
7
5
  "lib": ["es2020"],
8
6
  // Only include `@types/node` from node_modules/.
@@ -3,8 +3,13 @@
3
3
  {
4
4
  "compilerOptions": {
5
5
  "composite": true,
6
+
7
+ // Set up incremental builds. All sub-projects emit under outDir, maintaining
8
+ // directory structure relative to rootDir. No effect on include, exclude, etc.
6
9
  "emitDeclarationOnly": true,
7
10
  "declarationMap": true,
11
+ "outDir": ".tmp/tsbuildinfo/",
12
+ "rootDir": ".",
8
13
 
9
14
  "target": "es2020",
10
15
  "module": "es2020",
package/tsconfig.json CHANGED
@@ -1,8 +1,6 @@
1
1
  {
2
2
  "extends": "./tsconfig-base.json",
3
3
  "compilerOptions": {
4
- "outDir": ".tmp/tsbuildinfo/",
5
-
6
4
  // TODO(esmodules): included to support require('file.json'). Remove on the switch to ES Modules.
7
5
  "resolveJsonModule": true,
8
6
  },
@@ -49,6 +49,8 @@ interface UniversalBaseArtifacts {
49
49
  LighthouseRunWarnings: Array<string | IcuMessage>;
50
50
  /** The benchmark index that indicates rough device class. */
51
51
  BenchmarkIndex: number;
52
+ /** Many benchmark indexes. Many. */
53
+ BenchmarkIndexes?: number[];
52
54
  /** An object containing information about the testing configuration used by Lighthouse. */
53
55
  settings: Config.Settings;
54
56
  /** The timing instrumentation of the gather portion of a run. */
@@ -59,6 +59,8 @@ declare module Result {
59
59
  networkUserAgent: string;
60
60
  /** The benchmark index number that indicates rough device class. */
61
61
  benchmarkIndex: number;
62
+ /** Many benchmark indexes. */
63
+ benchmarkIndexes?: number[];
62
64
  /** The version of libraries with which these results were generated. Ex: axe-core. */
63
65
  credits?: Record<string, string|undefined>,
64
66
  }
@@ -1,8 +1,6 @@
1
1
  {
2
2
  "extends": "../../tsconfig-base.json",
3
3
  "compilerOptions": {
4
- "outDir": "../../.tmp/tsbuildinfo/types/lhr",
5
-
6
4
  // We only need the base JS definitions, no DOM, etc.
7
5
  "lib": ["esnext"],
8
6
  // Don't include any types from node_modules/.
@@ -11,6 +11,7 @@ declare module 'lighthouse-logger' {
11
11
  args?: any[];
12
12
  }
13
13
  export function setLevel(level: string): void;
14
+ export function isVerbose(): boolean;
14
15
  export function formatProtocol(prefix: string, data: Object, level?: string): void;
15
16
  export function log(title: string, ...args: any[]): void;
16
17
  export function warn(title: string, ...args: any[]): void;