diff --git a/lib/chrome/webdriver/chromium.js b/lib/chrome/webdriver/chromium.js index fcf69df96..d6ca27b38 100644 --- a/lib/chrome/webdriver/chromium.js +++ b/lib/chrome/webdriver/chromium.js @@ -16,6 +16,11 @@ import { pathToFolder } from '../../support/pathToFolder.js'; import { ChromeDevtoolsProtocol } from '../chromeDevtoolsProtocol.js'; import { NetworkManager } from '../networkManager.js'; import { Android, isAndroidConfigured } from '../../android/index.js'; +import { + getFirstContentFulPaintEvent, + getLargestContentfulPaintEvent, + getRecalculateStyleElementsAndTimeBefore +} from './traceUtilities.js'; const unlink = promisify(_unlink); const rm = promisify(_rm); @@ -354,6 +359,27 @@ export class Chromium { asset.args.data.renderBlocking; } + const fcpEvent = getFirstContentFulPaintEvent(trace.traceEvents); + const lcpEvent = getLargestContentfulPaintEvent(trace.traceEvents); + + result.renderBlocking = { recalculateStyle: {}, requests: {} }; + + if (fcpEvent) { + const beforeFCP = getRecalculateStyleElementsAndTimeBefore( + trace.traceEvents, + fcpEvent.ts + ); + result.renderBlocking.recalculateStyle.beforeFCP = beforeFCP; + } + + if (lcpEvent) { + const beforeLCP = getRecalculateStyleElementsAndTimeBefore( + trace.traceEvents, + lcpEvent.ts + ); + result.renderBlocking.recalculateStyle.beforeLCP = beforeLCP; + } + if (!this.options.skipHar) { for (let harRequest of this.hars[index - 1].log.entries) { if (renderBlockingInfo[harRequest.request.url]) { @@ -363,7 +389,7 @@ export class Chromium { } } - result.renderBlocking = renderBlockingInfo; + result.renderBlocking.requests = renderBlockingInfo; } // Google Web Vitals hacksery diff --git a/lib/chrome/webdriver/traceUtilities.js b/lib/chrome/webdriver/traceUtilities.js new file mode 100644 index 000000000..2ca1c8022 --- /dev/null +++ b/lib/chrome/webdriver/traceUtilities.js @@ -0,0 +1,64 @@ +import intel from 'intel'; +const log = intel.getLogger('browsertime.chrome'); + +export function getLargestContentfulPaintEvent(traceEvents) { + const lcpCandidates = traceEvents.filter( + task => task.name === 'largestContentfulPaint::Candidate' + ); + + if (lcpCandidates.length > 0) { + let lcpEvent = lcpCandidates[0]; + + for (const candidate of lcpCandidates) { + if (candidate.ts > lcpEvent.ts) { + lcpEvent = candidate; + } + } + return lcpEvent; + } else { + log.info('No LCP event found in the trace'); + } +} + +export function getFirstContentFulPaintEvent(traceEvents) { + // Get first contentful paint + const fcpEvent = traceEvents.find( + task => task.name === 'firstContentfulPaint' + ); + + if (fcpEvent) { + return fcpEvent; + } else { + log.info('Did not find the FCP event in the trace'); + } +} + +export function getRecalculateStyleElementsAndTimeBefore( + traceEvents, + timestamp +) { + const recalculatesBefore = traceEvents.filter( + task => + task.cat === 'disabled-by-default-devtools.timeline' && + task.name === 'ScheduleStyleRecalculation' && + task.ts < timestamp + ); + + const updateLayoutTree = traceEvents.filter( + task => + task.cat === 'blink,devtools.timeline' && + task.name === 'UpdateLayoutTree' && + task.ts < timestamp && + recalculatesBefore.some( + recalculate => task.args.beginData.frame === recalculate.args.data.frame + ) + ); + let elements = 0; + let duration = 0; + for (let a of updateLayoutTree) { + elements += a.args.elementCount; + duration += a.dur; + } + + return { elements, durationInMillis: duration / 1000 }; +} diff --git a/lib/core/engine/collector.js b/lib/core/engine/collector.js index 21f9dd9e0..29c301694 100644 --- a/lib/core/engine/collector.js +++ b/lib/core/engine/collector.js @@ -404,6 +404,12 @@ export class Collector { results.renderBlocking = []; } results.renderBlocking.push(data.renderBlocking); + + statistics.addDeep({ + renderBlocking: { + recalculateStyle: data.renderBlocking.recalculateStyle + } + }); } // Add power data (if we have it)