UNPKG

lighthouse

Version:

Automated auditing, performance metrics, and best practices for the web.

110 lines (90 loc) 3.14 kB
/** * @license * Copyright 2024 Google LLC * SPDX-License-Identifier: Apache-2.0 */ /** * @fileoverview Collects all stylesheets and their contents. */ import log from 'lighthouse-logger'; import BaseGatherer from '../base-gatherer.js'; import {Sentry} from '../../lib/sentry.js'; class Stylesheets extends BaseGatherer { constructor() { super(); /** @type {LH.Gatherer.ProtocolSession|undefined} */ this._session = undefined; /** @type {Map<string, Promise<LH.Artifacts.CSSStyleSheetInfo|null>>} */ this._sheetPromises = new Map(); this._onStylesheetAdded = this._onStylesheetAdded.bind(this); } /** @type {LH.Gatherer.GathererMeta} */ meta = { supportedModes: ['snapshot', 'timespan', 'navigation'], }; /** * @param {LH.Crdp.CSS.StyleSheetAddedEvent} event */ _onStylesheetAdded(event) { if (!this._session) throw new Error('Session not initialized'); const styleSheetId = event.header.styleSheetId; const sheetPromise = this._session.sendCommand('CSS.getStyleSheetText', {styleSheetId}) .then(content => ({ header: event.header, content: content.text, })) .catch(err => { log.warn( 'Stylesheets', `Error fetching content of stylesheet with URL "${event.header.sourceURL}"` ); Sentry.captureException(err, { tags: { gatherer: 'Stylesheets', }, extra: { url: event.header.sourceURL, }, level: 'error', }); return null; }); this._sheetPromises.set(styleSheetId, sheetPromise); } /** * @param {LH.Gatherer.Context} context * @return {Promise<LH.Artifacts['Stylesheets']>} */ async getArtifact(context) { const executionContext = context.driver.executionContext; const session = context.driver.defaultSession; this._session = session; session.on('CSS.styleSheetAdded', this._onStylesheetAdded); await session.sendCommand('DOM.enable'); await session.sendCommand('CSS.enable'); // Force style to recompute. // Doesn't appear to be necessary in newer versions of Chrome. await executionContext.evaluate(() => window.getComputedStyle(document.body), { args: [], }); session.off('CSS.styleSheetAdded', this._onStylesheetAdded); // Ensure we finish fetching all stylesheet contents before disabling the CSS domain const sheets = await Promise.all(this._sheetPromises.values()); await session.sendCommand('CSS.disable'); await session.sendCommand('DOM.disable'); /** @type {Map<string, LH.Artifacts.CSSStyleSheetInfo>} */ const dedupedStylesheets = new Map(); for (const sheet of sheets) { // Erroneous sheets will be reported via sentry and the log. // We can ignore them here without throwing a fatal error. if (!sheet) continue; // Exclude empty stylesheets. if (sheet.header.length === 0) { continue; } dedupedStylesheets.set(sheet.content, sheet); } return Array.from(dedupedStylesheets.values()); } } export default Stylesheets;