Skip to content
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(render-blocking): use trace engine as the source of truth #15839

Merged
merged 15 commits into from
Apr 11, 2024
4 changes: 2 additions & 2 deletions cli/test/fixtures/dobetterweb/dbw_tester.html
Original file line number Diff line number Diff line change
Expand Up @@ -42,10 +42,10 @@
<!-- PASS: async stylesheet -->
<link rel="stylesheet" href="./dbw_tester.css?delay=3000&async=true" disabled onload="this.disabled = false">

<!-- Alternate stylesheets should not show in TagsBlockingFirstPaint artifact -->
<!-- Alternate stylesheets should not be considered render blocking -->
<link rel="alternate stylesheet" href="./empty.css">

<!-- Malformed links should not show in TagsBlockingFirstPaint artifact -->
<!-- Malformed links should not be considered render blocking -->
<link rel="stylesheet" href="">

<script>
Expand Down
51 changes: 0 additions & 51 deletions cli/test/smokehouse/test-definitions/dobetterweb.js
Original file line number Diff line number Diff line change
Expand Up @@ -156,57 +156,6 @@ const expectations = {
property: 'og:description',
},
],
TagsBlockingFirstPaint: [
{
tag: {
tagName: 'LINK',
url: 'http://localhost:10200/dobetterweb/dbw_tester.css?delay=100',
},
},
{
tag: {
tagName: 'LINK',
url: 'http://localhost:10200/dobetterweb/unknown404.css?delay=200',
},
},
{
tag: {
tagName: 'LINK',
url: 'http://localhost:10200/dobetterweb/dbw_tester.css?delay=2200',
},

},
{
tag: {
tagName: 'LINK',
url: 'http://localhost:10200/dobetterweb/dbw_tester.css?delay=3000&capped',
mediaChanges: [
{
href: 'http://localhost:10200/dobetterweb/dbw_tester.css?delay=3000&capped',
media: 'not-matching',
matches: false,
},
{
href: 'http://localhost:10200/dobetterweb/dbw_tester.css?delay=3000&capped',
media: 'screen',
matches: true,
},
],
},
},
{
tag: {
tagName: 'SCRIPT',
url: 'http://localhost:10200/dobetterweb/dbw_tester.js',
},
},
{
tag: {
tagName: 'SCRIPT',
url: 'http://localhost:10200/dobetterweb/fcp-delayer.js?delay=5000',
},
},
],
DevtoolsLog: {
_includes: [
// Ensure we are getting async call stacks.
Expand Down
51 changes: 25 additions & 26 deletions core/audits/byte-efficiency/render-blocking-resources.js
Original file line number Diff line number Diff line change
Expand Up @@ -14,10 +14,10 @@ import * as i18n from '../../lib/i18n/i18n.js';
import {BaseNode} from '../../lib/lantern/base-node.js';
import {UnusedCSS} from '../../computed/unused-css.js';
import {NetworkRequest} from '../../lib/network-request.js';
import {ProcessedNavigation} from '../../computed/processed-navigation.js';
import {LoadSimulator} from '../../computed/load-simulator.js';
import {FirstContentfulPaint} from '../../computed/metrics/first-contentful-paint.js';
import {LCPImageRecord} from '../../computed/lcp-image-record.js';
import {NavigationInsights} from '../../computed/navigation-insights.js';


/** @typedef {import('../../lib/lantern/simulator/simulator.js').Simulator} Simulator */
Expand All @@ -44,21 +44,19 @@ const str_ = i18n.createIcuMessageFn(import.meta.url, UIStrings);
/**
* Given a simulation's nodeTimings, return an object with the nodes/timing keyed by network URL
* @param {LH.Gatherer.Simulation.Result['nodeTimings']} nodeTimings
* @return {Object<string, {node: Node, nodeTiming: LH.Gatherer.Simulation.NodeTiming}>}
* @return {Map<string, {node: Node, nodeTiming: LH.Gatherer.Simulation.NodeTiming}>}
*/
function getNodesAndTimingByUrl(nodeTimings) {
/** @type {Object<string, {node: Node, nodeTiming: LH.Gatherer.Simulation.NodeTiming}>} */
const urlMap = {};
const nodes = Array.from(nodeTimings.keys());
nodes.forEach(node => {
if (node.type !== 'network') return;
const nodeTiming = nodeTimings.get(node);
if (!nodeTiming) return;

urlMap[node.record.url] = {node, nodeTiming};
});
function getNodesAndTimingByRequestId(nodeTimings) {
/** @type {Map<string, {node: Node, nodeTiming: LH.Gatherer.Simulation.NodeTiming}>} */
const requestIdToNode = new Map();

for (const [node, nodeTiming] of nodeTimings) {
if (node.type !== 'network') continue;

return urlMap;
requestIdToNode.set(node.record.requestId, {node, nodeTiming});
}

return requestIdToNode;
}

/**
Expand Down Expand Up @@ -119,8 +117,7 @@ class RenderBlockingResources extends Audit {
guidanceLevel: 2,
// TODO: look into adding an `optionalArtifacts` property that captures the non-required nature
// of CSSUsage
requiredArtifacts: ['URL', 'TagsBlockingFirstPaint', 'traces', 'devtoolsLogs', 'CSSUsage',
'GatherContext', 'Stacks'],
requiredArtifacts: ['URL', 'traces', 'devtoolsLogs', 'CSSUsage', 'GatherContext', 'Stacks'],
};
}

Expand All @@ -134,9 +131,12 @@ class RenderBlockingResources extends Audit {
const trace = artifacts.traces[Audit.DEFAULT_PASS];
const devtoolsLog = artifacts.devtoolsLogs[Audit.DEFAULT_PASS];
const simulatorData = {devtoolsLog, settings: context.settings};
const processedNavigation = await ProcessedNavigation.request(trace, context);
const simulator = await LoadSimulator.request(simulatorData, context);
const wastedCssBytes = await RenderBlockingResources.computeWastedCSSBytes(artifacts, context);
const navInsights = await NavigationInsights.request(trace, context);

const renderBlocking = navInsights.RenderBlocking;
if (renderBlocking instanceof Error) throw renderBlocking;

/** @type {LH.Audit.Context['settings']} */
const metricSettings = {
Expand All @@ -150,19 +150,18 @@ class RenderBlockingResources extends Audit {
// Cast to just `LanternMetric` since we explicitly set `throttlingMethod: 'simulate'`.
const fcpSimulation = /** @type {LH.Artifacts.LanternMetric} */
(await FirstContentfulPaint.request(metricComputationData, context));
const fcpTsInMs = processedNavigation.timestamps.firstContentfulPaint / 1000;

const nodesByUrl = getNodesAndTimingByUrl(fcpSimulation.optimisticEstimate.nodeTimings);
const nodesAndTimingsByRequestId =
getNodesAndTimingByRequestId(fcpSimulation.optimisticEstimate.nodeTimings);

const results = [];
const deferredNodeIds = new Set();
for (const resource of artifacts.TagsBlockingFirstPaint) {
// Ignore any resources that finished after observed FCP (they're clearly not render-blocking)
if (resource.endTime > fcpTsInMs) continue;
for (const resource of renderBlocking.renderBlockingRequests) {
const nodeAndTiming = nodesAndTimingsByRequestId.get(resource.args.data.requestId);
// TODO: beacon to Sentry, https://github.com/GoogleChrome/lighthouse/issues/7041
if (!nodesByUrl[resource.tag.url]) continue;
if (!nodeAndTiming) continue;

const {node, nodeTiming} = nodesByUrl[resource.tag.url];
const {node, nodeTiming} = nodeAndTiming;

const stackSpecificTiming = computeStackSpecificTiming(node, nodeTiming, artifacts.Stacks);

Expand All @@ -174,8 +173,8 @@ class RenderBlockingResources extends Audit {
if (wastedMs < MINIMUM_WASTED_MS) continue;

results.push({
url: resource.tag.url,
totalBytes: resource.transferSize,
url: resource.args.data.url,
totalBytes: resource.args.data.encodedDataLength,
wastedMs,
});
}
Expand Down
2 changes: 1 addition & 1 deletion core/audits/layout-shifts.js
Original file line number Diff line number Diff line change
Expand Up @@ -61,7 +61,7 @@ class LayoutShifts extends Audit {
static async audit(artifacts, context) {
const trace = artifacts.traces[Audit.DEFAULT_PASS];
const traceEngineResult = await TraceEngineResult.request({trace}, context);
const clusters = traceEngineResult.LayoutShifts.clusters ?? [];
const clusters = traceEngineResult.data.LayoutShifts.clusters ?? [];
const {cumulativeLayoutShift: clsSavings, impactByNodeId} =
await CumulativeLayoutShiftComputed.request(trace, context);
const traceElements = artifacts.TraceElements
Expand Down
35 changes: 35 additions & 0 deletions core/computed/navigation-insights.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
/**
* @license
* Copyright 2024 Google LLC
* SPDX-License-Identifier: Apache-2.0
*/

import {makeComputedArtifact} from './computed-artifact.js';
import {ProcessedTrace} from './processed-trace.js';
import {TraceEngineResult} from './trace-engine-result.js';

/**
* @fileoverview Gets insights from the shared trace engine for the navigation audited by Lighthouse.
* Only usable in navigation mode.
*/
class NavigationInsights {
/**
* @param {LH.Trace} trace
* @param {LH.Artifacts.ComputedContext} context
*/
static async compute_(trace, context) {
const processedTrace = await ProcessedTrace.request(trace, context);
const traceEngineResult = await TraceEngineResult.request({trace}, context);

const navigationId = processedTrace.timeOriginEvt.args.data?.navigationId;
if (!navigationId) throw new Error('No navigationId found');

const navInsights = traceEngineResult.insights.get(navigationId);
if (!navInsights) throw new Error('No navigations insights found');

return navInsights;
}
}

const NavigationInsightsComputed = makeComputedArtifact(NavigationInsights, null);
export {NavigationInsightsComputed as NavigationInsights};
33 changes: 19 additions & 14 deletions core/computed/trace-engine-result.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,31 +8,39 @@ import * as TraceEngine from '../lib/trace-engine.js';
import {makeComputedArtifact} from './computed-artifact.js';
import {CumulativeLayoutShift} from './metrics/cumulative-layout-shift.js';
import {ProcessedTrace} from './processed-trace.js';
import * as LH from '../../types/lh.js';

/** @typedef {typeof ENABLED_HANDLERS} EnabledHandlers */

const ENABLED_HANDLERS = {
AuctionWorklets: TraceEngine.TraceHandlers.AuctionWorklets,
Initiators: TraceEngine.TraceHandlers.Initiators,
LayoutShifts: TraceEngine.TraceHandlers.LayoutShifts,
NetworkRequests: TraceEngine.TraceHandlers.NetworkRequests,
Renderer: TraceEngine.TraceHandlers.Renderer,
Samples: TraceEngine.TraceHandlers.Samples,
Screenshots: TraceEngine.TraceHandlers.Screenshots,
PageLoadMetrics: TraceEngine.TraceHandlers.PageLoadMetrics,
};

/**
* @fileoverview Processes trace with the shared trace engine.
*/
class TraceEngineResult {
/**
* @param {LH.TraceEvent[]} traceEvents
* @return {Promise<LH.Artifacts.TraceEngineResult>}
*/
static async runTraceEngine(traceEvents) {
const engine = new TraceEngine.TraceProcessor({
AuctionWorklets: TraceEngine.TraceHandlers.AuctionWorklets,
Initiators: TraceEngine.TraceHandlers.Initiators,
LayoutShifts: TraceEngine.TraceHandlers.LayoutShifts,
NetworkRequests: TraceEngine.TraceHandlers.NetworkRequests,
Renderer: TraceEngine.TraceHandlers.Renderer,
Samples: TraceEngine.TraceHandlers.Samples,
Screenshots: TraceEngine.TraceHandlers.Screenshots,
});
const engine = new TraceEngine.TraceProcessor(ENABLED_HANDLERS);
// eslint-disable-next-line max-len
await engine.parse(/** @type {import('@paulirish/trace_engine').Types.TraceEvents.TraceEventData[]} */ (
traceEvents
));
// TODO: use TraceEngine.TraceProcessor.createWithAllHandlers above.
return /** @type {import('@paulirish/trace_engine').Handlers.Types.TraceParseData} */(
engine.traceParsedData);
if (!engine.traceParsedData) throw new Error('No data');
if (!engine.insights) throw new Error('No insights');
return {data: engine.traceParsedData, insights: engine.insights};
}

/**
Expand Down Expand Up @@ -67,9 +75,6 @@ class TraceEngineResult {
}

const result = await TraceEngineResult.runTraceEngine(traceEvents);
if (!result) {
throw new Error('null trace engine result');
}
return result;
}
}
Expand Down
1 change: 0 additions & 1 deletion core/config/default-config.js
Original file line number Diff line number Diff line change
Expand Up @@ -137,7 +137,6 @@ const defaultConfig = {
{id: 'Scripts', gatherer: 'scripts'},
{id: 'SourceMaps', gatherer: 'source-maps'},
{id: 'Stacks', gatherer: 'stacks'},
{id: 'TagsBlockingFirstPaint', gatherer: 'dobetterweb/tags-blocking-first-paint'},
{id: 'TraceElements', gatherer: 'trace-elements'},
{id: 'ViewportDimensions', gatherer: 'viewport-dimensions'},

Expand Down
Loading
Loading