diff --git a/core/gather/driver.js b/core/gather/driver.js index fd25c98e21d7..074ddb97c5ed 100644 --- a/core/gather/driver.js +++ b/core/gather/driver.js @@ -8,7 +8,6 @@ import log from 'lighthouse-logger'; import {ExecutionContext} from './driver/execution-context.js'; import {TargetManager} from './driver/target-manager.js'; -import {LighthouseError} from '../lib/lh-error.js'; import {Fetcher} from './fetcher.js'; import {NetworkMonitor} from './driver/network-monitor.js'; @@ -29,6 +28,7 @@ const throwingSession = { sendCommand: throwNotConnectedFn, sendCommandAndIgnore: throwNotConnectedFn, dispose: throwNotConnectedFn, + onCrashPromise: throwNotConnectedFn, }; /** @implements {LH.Gatherer.Driver} */ @@ -48,12 +48,6 @@ class Driver { this._fetcher = undefined; this.defaultSession = throwingSession; - - // Poor man's Promise.withResolvers() - /** @param {Error} _ */ - let rej = _ => {}; - const promise = /** @type {Promise} */ (new Promise((_, theRej) => rej = theRej)); - this.fatalRejection = {promise, rej}; } /** @return {LH.Gatherer.Driver['executionContext']} */ @@ -93,30 +87,11 @@ class Driver { this._networkMonitor = new NetworkMonitor(this._targetManager); await this._networkMonitor.enable(); this.defaultSession = this._targetManager.rootSession(); - this.listenForCrashes(); this._executionContext = new ExecutionContext(this.defaultSession); this._fetcher = new Fetcher(this.defaultSession); log.timeEnd(status); } - /** - * If the target crashes, we can't continue gathering. - * - * FWIW, if the target unexpectedly detaches (eg the user closed the tab), pptr will - * catch that and reject into our this._cdpSession.send, which we'll alrady handle appropriately - * @return {void} - */ - listenForCrashes() { - this.defaultSession.on('Inspector.targetCrashed', async _ => { - log.error('Driver', 'Inspector.targetCrashed'); - // Manually detach so no more CDP traffic is attempted. - // Don't await, else our rejection will be a 'Target closed' protocol error on cross-talk CDP calls. - void this.defaultSession.dispose(); - this.fatalRejection.rej(new LighthouseError(LighthouseError.errors.TARGET_CRASHED)); - }); - } - - /** @return {Promise} */ async disconnect() { if (this.defaultSession === throwingSession) return; diff --git a/core/gather/driver/navigation.js b/core/gather/driver/navigation.js index 9518246c38b6..fea75ef5761a 100644 --- a/core/gather/driver/navigation.js +++ b/core/gather/driver/navigation.js @@ -123,7 +123,7 @@ async function gotoURL(driver, requestor, options) { } const waitConditions = await Promise.race([ - driver.fatalRejection.promise, + session.onCrashPromise(), Promise.all(waitConditionPromises), ]); const timedOut = waitConditions.some(condition => condition.timedOut); diff --git a/core/gather/session.js b/core/gather/session.js index 25f3c830b25b..b15938c9bd14 100644 --- a/core/gather/session.js +++ b/core/gather/session.js @@ -44,6 +44,22 @@ class ProtocolSession extends CrdpEventEmitter { this._handleProtocolEvent = this._handleProtocolEvent.bind(this); // @ts-expect-error Puppeteer expects the handler params to be type `unknown` this._cdpSession.on('*', this._handleProtocolEvent); + + // If the target crashes, we can't continue gathering. + // FWIW, if the target unexpectedly detaches (eg the user closed the tab), pptr will + // catch that and reject in this._cdpSession.send, which is caught by us. + /** @param {Error} _ */ + let rej = _ => {}; // Poor man's Promise.withResolvers() + this._targetCrashedPromise = /** @type {Promise} */ ( + new Promise((_, theRej) => rej = theRej)); + this.on('Inspector.targetCrashed', async () => { + log.error('TargetManager', 'Inspector.targetCrashed'); + // Manually detach so no more CDP traffic is attempted. + // Don't await, else our rejection will be a 'Target closed' protocol error on cross-talk + // CDP calls. + void this.dispose(); + rej(new LighthouseError(LighthouseError.errors.TARGET_CRASHED)); + }); } id() { @@ -114,7 +130,8 @@ class ProtocolSession extends CrdpEventEmitter { log.formatProtocol('method <= browser ERR', {method}, 'error'); throw LighthouseError.fromProtocolMessage(method, error); }); - const resultWithTimeoutPromise = Promise.race([resultPromise, timeoutPromise]); + const resultWithTimeoutPromise = + Promise.race([resultPromise, timeoutPromise, this._targetCrashedPromise]); return resultWithTimeoutPromise.finally(() => { if (timeout) clearTimeout(timeout); @@ -142,6 +159,10 @@ class ProtocolSession extends CrdpEventEmitter { this._cdpSession.off('*', this._handleProtocolEvent); await this._cdpSession.detach().catch(e => log.verbose('session', 'detach failed', e.message)); } + + onCrashPromise() { + return this._targetCrashedPromise; + } } export {ProtocolSession}; diff --git a/core/test/gather/mock-driver.js b/core/test/gather/mock-driver.js index 1290ff09b34d..dca57e8e0713 100644 --- a/core/test/gather/mock-driver.js +++ b/core/test/gather/mock-driver.js @@ -38,6 +38,7 @@ function createMockSession() { addProtocolMessageListener: createMockOnFn(), removeProtocolMessageListener: fnAny(), dispose: fnAny(), + onCrashPromise: fnAny().mockReturnValue(new Promise(() => {})), /** @return {LH.Gatherer.ProtocolSession} */ asSession() { @@ -158,13 +159,6 @@ function createMockDriver() { const context = createMockExecutionContext(); const targetManager = createMockTargetManager(session); - // The `fatalRejection` - /** @param {Error} _ */ - let rej = _ => {}; - const promise = new Promise((_, theRej) => { - rej = theRej; - }); - return { _page: page, _executionContext: context, @@ -179,8 +173,6 @@ function createMockDriver() { fetchResource: fnAny(), }, networkMonitor: new NetworkMonitor(targetManager.asTargetManager()), - listenForCrashes: fnAny(), - fatalRejection: {promise, rej}, /** @return {Driver} */ asDriver() { diff --git a/types/gatherer.d.ts b/types/gatherer.d.ts index 39fde8e239be..2d30439cc07c 100644 --- a/types/gatherer.d.ts +++ b/types/gatherer.d.ts @@ -35,6 +35,7 @@ declare module Gatherer { sendCommand(method: TMethod, ...params: CrdpCommands[TMethod]['paramsType']): Promise; sendCommandAndIgnore(method: TMethod, ...params: CrdpCommands[TMethod]['paramsType']): Promise; dispose(): Promise; + onCrashPromise(): Promise; } interface Driver { @@ -49,8 +50,6 @@ declare module Gatherer { off(event: 'protocolevent', callback: (payload: Protocol.RawEventMessage) => void): void }; networkMonitor: NetworkMonitor; - listenForCrashes: (() => void); - fatalRejection: {promise: Promise, rej: (reason: Error) => void} } interface Context {