/**
   * @returns {!Array} User timing raw trace event pairs
   */
  generateFakeEvents() {
    const fakeEvents = [];
    const metrics = this.gatherMetrics();
    if (metrics.length === 0) {
      log.error('metrics-events', 'Metrics collection had errors, not synthetizing trace events');
      return [];
    }

    this.identifyNavigationStartEvt(metrics);
    if (!this._navigationStartEvt) {
      log.error('pwmetrics-events', 'Reference navigationStart not found');
      return [];
    }

    metrics.forEach(metric => {
      if (metric.id === 'navstart') {
        return;
      }
      if (!metric.ts) {
        log.error('pwmetrics-events', `(${metric.name}) missing timestamp. Skipping…`);
        return;
      }
      log.verbose('pwmetrics-events', `Sythesizing trace events for ${metric.name}`);
      fakeEvents.push(...this.synthesizeEventPair(metric));
    });
    return fakeEvents;
  }
Esempio n. 2
0
 return driver.disconnect().catch(err => {
   // Ignore disconnecting error if browser was already closed.
   // See https://github.com/GoogleChrome/lighthouse/issues/1583
   if (!(/close\/.*status: 500$/.test(err.message))) {
     log.error('GatherRunner disconnect', err.message);
   }
 });
Esempio n. 3
0
 return this._queryCurrentTab().then(tab => {
   if (!tab.url) {
     log.error('ExtensionConnection', 'getCurrentTabURL returned empty string', tab);
     throw new Error('getCurrentTabURL returned empty string');
   }
   return tab.url;
 });
Esempio n. 4
0
    return new Promise((resolve, reject) => {
      log.formatProtocol('method => browser', {method, params}, 'verbose');
      if (!this._tabId) {
        log.error('ExtensionConnection', 'No tabId set for sendCommand');
        return reject(new Error('No tabId set for sendCommand'));
      }

      chrome.debugger.sendCommand({tabId: this._tabId}, method, params, result => {
        if (chrome.runtime.lastError) {
          // The error from the extension has a `message` property that is the
          // stringified version of the actual protocol error object.
          const message = chrome.runtime.lastError.message || '';
          let errorMessage;
          try {
            errorMessage = JSON.parse(message).message;
          } catch (e) {}
          errorMessage = errorMessage || message || 'Unknown debugger protocol error.';

          log.formatProtocol('method <= browser ERR', {method}, 'error');
          return reject(new Error(`Protocol error (${method}): ${errorMessage}`));
        }

        log.formatProtocol('method <= browser OK', {method, params: result}, 'verbose');
        resolve(result);
      });
    });
Esempio n. 5
0
 /**
  * @override
  * @param {string} message
  * @protected
  */
 sendRawMessage(message) {
   if (!this._ws) {
     log.error('CriConnection', 'sendRawMessage() was called without an established connection.');
     throw new Error('sendRawMessage() was called without an established connection.');
   }
   this._ws.send(message);
 }
 metrics.forEach(metric => {
   if (metric.id === 'navstart') {
     return;
   }
   if (!metric.ts) {
     log.error('pwmetrics-events', `(${metric.name}) missing timestamp. Skipping…`);
     return;
   }
   log.verbose('pwmetrics-events', `Sythesizing trace events for ${metric.name}`);
   fakeEvents.push(...this.synthesizeEventPair(metric));
 });
 metricDfns.forEach(metric => {
   // try/catch in case auditResults is missing a particular audit result
   try {
     resolvedMetrics.push({
       id: metric.id,
       name: metric.name,
       ts: metric.getTs(this._auditResults),
     });
   } catch (e) {
     log.error('pwmetrics-events', `${metric.name} timestamp not found: ${e.message}`);
   }
 });
Esempio n. 8
0
  /**
   * @param {Driver} driver
   * @return {Promise<void>}
   */
  static async disposeDriver(driver) {
    const status = {msg: 'Disconnecting from browser...', id: 'lh:gather:disconnect'};

    log.time(status);
    try {
      await driver.disconnect();
    } catch (err) {
      // Ignore disconnecting error if browser was already closed.
      // See https://github.com/GoogleChrome/lighthouse/issues/1583
      if (!(/close\/.*status: 500$/.test(err.message))) {
        log.error('GatherRunner disconnect', err.message);
      }
    }
    log.timeEnd(status);
  }
Esempio n. 9
0
  /**
   * Debounce enabling or disabling domains to prevent driver users from
   * stomping on each other. Maintains an internal count of the times a domain
   * has been enabled. Returns false if the command would have no effect (domain
   * is already enabled or disabled), or if command would interfere with another
   * user of that domain (e.g. two gatherers have enabled a domain, both need to
   * disable it for it to be disabled). Returns true otherwise.
   * @param {string} domain
   * @param {boolean} enable
   * @return {boolean}
   * @private
   */
  _shouldToggleDomain(domain, enable) {
    const enabledCount = this._domainEnabledCounts.get(domain) || 0;
    const newCount = enabledCount + (enable ? 1 : -1);
    this._domainEnabledCounts.set(domain, Math.max(0, newCount));

    // Switching to enabled or disabled, respectively.
    if ((enable && newCount === 1) || (!enable && newCount === 0)) {
      log.verbose('Driver', `${domain}.${enable ? 'enable' : 'disable'}`);
      return true;
    } else {
      if (newCount < 0) {
        log.error('Driver', `Attempted to disable domain '${domain}' when already disabled.`);
      }
      return false;
    }
  }
Esempio n. 10
0
      request.setTimeout(CONNECT_TIMEOUT, () => {
        request.abort();

        // After aborting, we expect an ECONNRESET error. Ignore.
        request.on('error', err => {
          if (err.code !== 'ECONNRESET') {
            throw err;
          }
        });

        // TODO: Replace this with an LHError on next major version bump
        // Reject on error with code specifically indicating timeout in connection setup.
        const err = new Error('Timeout waiting for initial Debugger Protocol connection.');
        err.code = 'CRI_TIMEOUT';
        log.error('CriConnection', err.message);
        reject(err);
      });
Esempio n. 11
0
  /**
   * Returns an error if the original network request failed or wasn't found.
   * @param {string} url The URL of the original requested page.
   * @param {Array<LH.Artifacts.NetworkRequest>} networkRecords
   * @return {LHError|undefined}
   */
  static getPageLoadError(url, networkRecords) {
    const mainRecord = networkRecords.find(record => {
      // record.url is actual request url, so needs to be compared without any URL fragment.
      return URL.equalWithExcludedFragments(record.url, url);
    });

    let errorCode;
    let errorReason;
    if (!mainRecord) {
      errorCode = LHError.errors.NO_DOCUMENT_REQUEST;
    } else if (mainRecord.failed) {
      errorCode = LHError.errors.FAILED_DOCUMENT_REQUEST;
      errorReason = mainRecord.localizedFailDescription;
    }

    if (errorCode) {
      const error = new LHError(errorCode, {reason: errorReason});
      log.error('GatherRunner', error.message, url);
      return error;
    }
  }
Esempio n. 12
0
  /**
   * Ends tracing and collects trace data (if requested for this pass), and runs
   * afterPass() on gatherers with trace data passed in. Promise resolves with
   * object containing trace and network data.
   * @param {LH.Gatherer.PassContext} passContext
   * @param {Partial<GathererResults>} gathererResults
   * @return {Promise<LH.Gatherer.LoadData>}
   */
  static async afterPass(passContext, gathererResults) {
    const driver = passContext.driver;
    const config = passContext.passConfig;
    const gatherers = config.gatherers;

    let trace;
    if (config.recordTrace) {
      log.log('status', 'Retrieving trace');
      trace = await driver.endTrace();
      log.verbose('statusEnd', 'Retrieving trace');
    }

    const status = 'Retrieving devtoolsLog and network records';
    log.log('status', status);
    const devtoolsLog = driver.endDevtoolsLog();
    const networkRecords = NetworkRecorder.recordsFromLogs(devtoolsLog);
    log.verbose('statusEnd', status);

    let pageLoadError = GatherRunner.getPageLoadError(passContext.url, networkRecords);
    // If the driver was offline, a page load error is expected, so do not save it.
    if (!driver.online) pageLoadError = undefined;

    if (pageLoadError) {
      log.error('GatherRunner', pageLoadError.message, passContext.url);
      passContext.LighthouseRunWarnings.push(pageLoadError.friendlyMessage);
    }

    // Expose devtoolsLog, networkRecords, and trace (if present) to gatherers
    /** @type {LH.Gatherer.LoadData} */
    const passData = {
      networkRecords,
      devtoolsLog,
      trace,
    };

    // Disable throttling so the afterPass analysis isn't throttled
    await driver.setThrottling(passContext.settings, {useThrottling: false});

    for (const gathererDefn of gatherers) {
      const gatherer = gathererDefn.instance;
      const status = `Retrieving: ${gatherer.name}`;
      log.log('status', status);

      // Add gatherer options to the passContext.
      passContext.options = gathererDefn.options || {};

      // If there was a pageLoadError, fail every afterPass with it rather than bail completely.
      const artifactPromise = pageLoadError ?
        Promise.reject(pageLoadError) :
        // Wrap gatherer response in promise, whether rejected or not.
        Promise.resolve().then(_ => gatherer.afterPass(passContext, passData));

      const gathererResult = gathererResults[gatherer.name] || [];
      gathererResult.push(artifactPromise);
      gathererResults[gatherer.name] = gathererResult;
      await GatherRunner.recoverOrThrow(artifactPromise);
      log.verbose('statusEnd', status);
    }

    // Resolve on tracing data using passName from config.
    return passData;
  }
Esempio n. 13
0
 server.on('error', err => log.error('PerformanceXServer', err.code, err));
Esempio n. 14
0
 }).catch(err => {
   log.error('PerformanceXServer', err.code, err);
   response.writeHead(500);
   response.end(http.STATUS_CODES[500]);
 });