-
Notifications
You must be signed in to change notification settings - Fork 9.3k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
core(fr): add timespan support to css-usage #12728
Changes from 1 commit
d4d10a3
43230fb
1c04831
3faf0f4
c7680ff
72adda0
ac25ec1
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
- Loading branch information
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,28 @@ | ||
'use strict'; | ||
const puppeteer = require('puppeteer'); | ||
const lighthouse = require('./lighthouse-core/fraggle-rock/api.js'); | ||
const open = require('open'); | ||
const fs = require('fs'); | ||
|
||
const defaultConfig = require('./lighthouse-core/fraggle-rock/config/default-config.js'); | ||
|
||
async function run() { | ||
const browser = await puppeteer.launch({ | ||
headless: false, | ||
}); | ||
const page = await browser.newPage(); | ||
|
||
const config = {...defaultConfig, settings: {output: 'html'}}; | ||
const run = await lighthouse.startTimespan({page, config}); | ||
|
||
await page.goto('https://store.google.com'); | ||
await new Promise(r => setTimeout(r, 1000)); | ||
|
||
const time = await run.endTimespan(); | ||
fs.writeFileSync('fr-report-timespan.html', time.report); | ||
open('fr-report-timespan.html'); | ||
|
||
await page.close(); | ||
await browser.close(); | ||
} | ||
run(); |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -11,38 +11,79 @@ const FRGatherer = require('../../fraggle-rock/gather/base-gatherer.js'); | |
* @fileoverview Tracks unused CSS rules. | ||
*/ | ||
class CSSUsage extends FRGatherer { | ||
constructor() { | ||
super(); | ||
/** @type {Array<LH.Crdp.CSS.StyleSheetAddedEvent>} */ | ||
this._stylesheets = []; | ||
/** @param {LH.Crdp.CSS.StyleSheetAddedEvent} sheet */ | ||
this._onStylesheetAdded = sheet => this._stylesheets.push(sheet); | ||
/** @type {LH.Crdp.CSS.RuleUsage[]|undefined} */ | ||
this._ruleUsage = undefined; | ||
} | ||
|
||
/** @type {LH.Gatherer.GathererMeta} */ | ||
meta = { | ||
// TODO(FR-COMPAT): Add support for timespan. | ||
supportedModes: ['snapshot', 'navigation'], | ||
supportedModes: ['snapshot', 'timespan', 'navigation'], | ||
}; | ||
|
||
/** | ||
* @param {LH.Gatherer.FRTransitionalContext} context | ||
* @return {Promise<LH.Artifacts['CSSUsage']>} | ||
*/ | ||
async getArtifact(context) { | ||
async startCollection(context) { | ||
adamraine marked this conversation as resolved.
Show resolved
Hide resolved
|
||
const session = context.driver.defaultSession; | ||
const executionContext = context.driver.executionContext; | ||
|
||
/** @type {Array<LH.Crdp.CSS.StyleSheetAddedEvent>} */ | ||
const stylesheets = []; | ||
/** @param {LH.Crdp.CSS.StyleSheetAddedEvent} sheet */ | ||
const => stylesheets.push(sheet); | ||
session.on('CSS.styleSheetAdded', onStylesheetAdded); | ||
session.on('CSS.styleSheetAdded', this._onStylesheetAdded); | ||
|
||
await session.sendCommand('DOM.enable'); | ||
await session.sendCommand('CSS.enable'); | ||
await session.sendCommand('CSS.startRuleUsageTracking'); | ||
} | ||
|
||
/** | ||
* @param {LH.Gatherer.FRTransitionalContext} context | ||
*/ | ||
async stopCollection(context) { | ||
const session = context.driver.defaultSession; | ||
const coverageResponse = await session.sendCommand('CSS.stopRuleUsageTracking'); | ||
this._ruleUsage = coverageResponse.ruleUsage; | ||
session.off('CSS.styleSheetAdded', this._onStylesheetAdded); | ||
} | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. nit: swap order of stop/start functions There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Are you saying both start functions should come before both stop functions? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. not necessarily both, just the ones that are logically paired. it helps with readability when you can move top to bottom and the fns build off each other. so startThing before stopThing is preferred There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Pretty sure that's the way I had it, but going start/start/stop/stop looks better imo. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. the counter-point to this (that I happen to agree with) is that the version here intermixes bespoke helper functions with the official gatherer API functions whereas before the "private" methods were grouped away from "public" :) I don't think either is a big deal, and perhaps this is just down to how familiar the gatherer API methods are to the reader, but I did like the flow of the original. 🤷 |
||
|
||
/** | ||
* @param {LH.Gatherer.FRTransitionalContext} context | ||
*/ | ||
async startSensitiveInstrumentation(context) { | ||
adamraine marked this conversation as resolved.
Show resolved
Hide resolved
|
||
if (context.gatherMode !== 'timespan') return; | ||
await this.startCollection(context); | ||
} | ||
|
||
// Force style to recompute. | ||
// Doesn't appear to be necessary in newer versions of Chrome. | ||
await executionContext.evaluateAsync('getComputedStyle(document.body)'); | ||
/** | ||
* @param {LH.Gatherer.FRTransitionalContext} context | ||
*/ | ||
async stopSensitiveInstrumentation(context) { | ||
if (context.gatherMode !== 'timespan') return; | ||
await this.stopCollection(context); | ||
} | ||
|
||
session.off('CSS.styleSheetAdded', onStylesheetAdded); | ||
/** | ||
* @param {LH.Gatherer.FRTransitionalContext} context | ||
* @return {Promise<LH.Artifacts['CSSUsage']>} | ||
*/ | ||
async getArtifact(context) { | ||
const session = context.driver.defaultSession; | ||
const executionContext = context.driver.executionContext; | ||
|
||
if (context.gatherMode !== 'timespan') { | ||
patrickhulce marked this conversation as resolved.
Show resolved
Hide resolved
|
||
await this.startCollection(context); | ||
|
||
// Force style to recompute. | ||
// Doesn't appear to be necessary in newer versions of Chrome. | ||
await executionContext.evaluateAsync('getComputedStyle(document.body)'); | ||
|
||
await this.stopCollection(context); | ||
} | ||
|
||
// Fetch style sheet content in parallel. | ||
const promises = stylesheets.map(sheet => { | ||
const promises = this._stylesheets.map(sheet => { | ||
const styleSheetId = sheet.header.styleSheetId; | ||
return session.sendCommand('CSS.getStyleSheetText', {styleSheetId}).then(content => { | ||
return { | ||
|
@@ -53,15 +94,17 @@ class CSSUsage extends FRGatherer { | |
}); | ||
const styleSheetInfo = await Promise.all(promises); | ||
|
||
const ruleUsageResponse = await session.sendCommand('CSS.stopRuleUsageTracking'); | ||
await session.sendCommand('CSS.disable'); | ||
await session.sendCommand('DOM.disable'); | ||
|
||
const dedupedStylesheets = new Map(styleSheetInfo.map(sheet => { | ||
return [sheet.content, sheet]; | ||
})); | ||
|
||
if (!this._ruleUsage) throw new Error('Issue collecting rule usages'); | ||
|
||
return { | ||
rules: ruleUsageResponse.ruleUsage, | ||
rules: this._ruleUsage, | ||
stylesheets: Array.from(dedupedStylesheets.values()), | ||
}; | ||
} | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This should always be set by the time we use it, which is why I opted to throw an error on
undefined
rather than initialize to[]
. I'm open to changing it though.