function waitForNetworkIdle(session, networkMonitor, networkQuietOptions) {
  let hasDCLFired = false;
  /** @type {NodeJS.Timeout|undefined} */
  let idleTimeout;
  /** @type {(() => void)} */
  let cancel = () => {
    throw new Error('waitForNetworkIdle.cancel() called before it was defined');
  };

  const {networkQuietThresholdMs, busyEvent, idleEvent, isIdle} = networkQuietOptions;

  /** @type {Promise<void>} */
  const promise = new Promise((resolve, reject) => {
    const onIdle = () => {
      networkMonitor.once(busyEvent, onBusy);
      idleTimeout = setTimeout(() => {
        cancel();
        resolve();
      }, networkQuietThresholdMs);
    };

    const onBusy = () => {
      networkMonitor.once(idleEvent, onIdle);
      idleTimeout && clearTimeout(idleTimeout);
    };

    const domContentLoadedListener = () => {
      hasDCLFired = true;
      if (isIdle(networkMonitor)) {
        onIdle();
      } else {
        onBusy();
      }
    };

    // We frequently need to debug why LH is still waiting for the page.
    // This listener is added to all network events to verbosely log what URLs we're waiting on.
    const logStatus = () => {
      if (!hasDCLFired) {
        log.verbose('waitFor', 'Waiting on DomContentLoaded');
        return;
      }

      const inflightRecords = networkMonitor.getInflightRequests();
      // If there are more than 20 inflight requests, load is still in full swing.
      // Wait until it calms down a bit to be a little less spammy.
      if (log.isVerbose() && inflightRecords.length < 20 && inflightRecords.length > 0) {
        log.verbose('waitFor', `=== Waiting on ${inflightRecords.length} requests to finish`);
        for (const record of inflightRecords) {
          log.verbose('waitFor', `Waiting on ${record.url.slice(0, 120)} to finish`);
        }
      }
    };

    networkMonitor.on('requeststarted', logStatus);
    networkMonitor.on('requestfinished', logStatus);
    networkMonitor.on(busyEvent, logStatus);

    if (!networkQuietOptions.pretendDCLAlreadyFired) {
      session.once('Page.domContentEventFired', domContentLoadedListener);
    } else {
      domContentLoadedListener();
    }

    let canceled = false;
    cancel = () => {
      if (canceled) return;
      canceled = true;
      if (idleTimeout) clearTimeout(idleTimeout);
      if (!networkQuietOptions.pretendDCLAlreadyFired) {
        session.off('Page.domContentEventFired', domContentLoadedListener);
      }
      networkMonitor.removeListener(busyEvent, onBusy);
      networkMonitor.removeListener(idleEvent, onIdle);
      networkMonitor.removeListener('requeststarted', logStatus);
      networkMonitor.removeListener('requestfinished', logStatus);
      networkMonitor.removeListener(busyEvent, logStatus);
    };
  });

  return {
    promise,
    cancel,
  };
}
Returns a promise that resolves when the network has been idle (after DCL) for
`networkQuietThresholdMs` ms and a method to cancel internal network listeners/timeout.
@param {LH.Gatherer.ProtocolSession} session
@param {NetworkMonitor} networkMonitor
@param {{networkQuietThresholdMs: number, busyEvent: NetworkMonitorEvent, idleEvent: NetworkMonitorEvent, isIdle(recorder: NetworkMonitor): boolean, pretendDCLAlreadyFired?: boolean}} networkQuietOptions
@return {CancellableWait}