core(prioritize-lcp-image): use request initiators for load path (#14807)

This commit is contained in:
Brendan Kenny 2023-02-17 18:32:31 -06:00 коммит произвёл GitHub
Родитель 621e10c219
Коммит c958468921
Не найден ключ, соответствующий данной подписи
Идентификатор ключа GPG: 4AEE18F83AFDEB23
6 изменённых файлов: 171 добавлений и 189 удалений

Просмотреть файл

@ -551,16 +551,18 @@ const expectations = {
debugData: {
initiatorPath: [{
url: 'http://localhost:10200/dobetterweb/lighthouse-1024x680.jpg?redirected-lcp',
initiatorType: 'redirect',
}, {
url: 'http://localhost:10200/dobetterweb/lighthouse-1024x680.jpg?lcp&redirect=lighthouse-1024x680.jpg%3Fredirected-lcp',
initiatorType: 'parser',
}, {
// TOD(bckenny): missing initiator step through redirected image url.
url: 'http://localhost:10200/dobetterweb/dbw_tester.css?delay=2000&async=true',
initiatorType: 'parser',
}, {
url: 'http://localhost:10200/dobetterweb/dbw_tester.html',
initiatorType: 'other',
}],
pathLength: 3,
pathLength: 4,
},
},
},

Просмотреть файл

@ -195,13 +195,12 @@ const expectations = {
score: 1,
numericValue: 0,
details: {
items: [{
url: 'http://localhost:10200/dobetterweb/lighthouse-480x318.jpg',
}],
items: [],
debugData: {
initiatorPath: [{
url: 'http://localhost:10200/dobetterweb/lighthouse-480x318.jpg',
initiatorType: 'other',
// Dynamically-added, lazy-loaded images currently have broken initiator chains.
initiatorType: 'fallbackToMain',
}, {
url: 'http://localhost:10200/perf/trace-elements.html',
initiatorType: 'other',

Просмотреть файл

@ -25,7 +25,8 @@ const UIStrings = {
const str_ = i18n.createIcuMessageFn(import.meta.url, UIStrings);
/**
* @typedef {Array<{url: string, initiatorType: string}>} InitiatorPath
* @typedef {LH.Crdp.Network.Initiator['type']|'redirect'|'fallbackToMain'} InitiatorType
* @typedef {Array<{url: string, initiatorType: InitiatorType}>} InitiatorPath
*/
class PrioritizeLcpImage extends Audit {
@ -47,18 +48,16 @@ class PrioritizeLcpImage extends Audit {
*
* @param {LH.Artifacts.NetworkRequest} request
* @param {LH.Artifacts.NetworkRequest} mainResource
* @param {Array<LH.Gatherer.Simulation.GraphNode>} initiatorPath
* @param {InitiatorPath} initiatorPath
* @return {boolean}
*/
static shouldPreloadRequest(request, mainResource, initiatorPath) {
const mainResourceDepth = mainResource.redirects ? mainResource.redirects.length : 0;
// If it's already preloaded, no need to recommend it.
if (request.isLinkPreload) return false;
// It's not a request loaded over the network, don't recommend it.
if (NetworkRequest.isNonNetworkRequest(request)) return false;
// It's already discoverable from the main document, don't recommend it.
if (initiatorPath.length <= mainResourceDepth) return false;
// It's already discoverable from the main document (a path of [lcpRecord, mainResource]), don't recommend it.
if (initiatorPath.length <= 2) return false;
// Finally, return whether or not it belongs to the main frame
return request.frameId === mainResource.frameId;
}
@ -66,23 +65,57 @@ class PrioritizeLcpImage extends Audit {
/**
* @param {LH.Gatherer.Simulation.GraphNode} graph
* @param {NetworkRequest} lcpRecord
* @return {{lcpNode: LH.Gatherer.Simulation.GraphNetworkNode|undefined, path: Array<LH.Gatherer.Simulation.GraphNetworkNode>|undefined}}
* @return {LH.Gatherer.Simulation.GraphNetworkNode|undefined}
*/
static findLCPNode(graph, lcpRecord) {
let lcpNode;
let path;
graph.traverse((node, traversalPath) => {
if (node.type !== 'network') return;
for (const {node} of graph.traverseGenerator()) {
if (node.type !== 'network') continue;
if (node.record.requestId === lcpRecord.requestId) {
lcpNode = node;
path =
traversalPath.slice(1).filter(initiator => initiator.type === 'network');
return node;
}
});
return {
lcpNode,
path,
};
}
}
/**
* Get the initiator path starting with lcpRecord back to mainResource, inclusive.
* Navigation redirects *to* the mainResource are not included.
* Path returned will always be at least [lcpRecord, mainResource].
* @param {NetworkRequest} lcpRecord
* @param {NetworkRequest} mainResource
* @return {InitiatorPath}
*/
static getLcpInitiatorPath(lcpRecord, mainResource) {
/** @type {InitiatorPath} */
const initiatorPath = [];
let mainResourceReached = false;
/** @type {NetworkRequest|undefined} */
let request = lcpRecord;
while (request) {
mainResourceReached ||= request.requestId === mainResource.requestId;
/** @type {InitiatorType} */
let initiatorType = request.initiator?.type ?? 'other';
// Initiator type usually comes from redirect, but 'redirect' is used for more informative debugData.
if (request.initiatorRequest && request.initiatorRequest === request.redirectSource) {
initiatorType = 'redirect';
}
// Sometimes the initiator chain is broken and the best that can be done is stitch
// back to the main resource. Note this in the initiatorType.
if (!request.initiatorRequest && !mainResourceReached) {
initiatorType = 'fallbackToMain';
}
initiatorPath.push({url: request.url, initiatorType});
// Can't preload before the main resource, so break off initiator path there.
if (mainResourceReached) break;
// Continue up chain, falling back to mainResource if chain is broken.
request = request.initiatorRequest || mainResource;
}
return initiatorPath;
}
/**
@ -93,18 +126,14 @@ class PrioritizeLcpImage extends Audit {
*/
static getLCPNodeToPreload(mainResource, graph, lcpRecord) {
if (!lcpRecord) return {};
const {lcpNode, path} = PrioritizeLcpImage.findLCPNode(graph, lcpRecord);
if (!lcpNode || !path) return {};
const lcpNode = PrioritizeLcpImage.findLCPNode(graph, lcpRecord);
const initiatorPath = PrioritizeLcpImage.getLcpInitiatorPath(lcpRecord, mainResource);
if (!lcpNode) return {initiatorPath};
// eslint-disable-next-line max-len
const shouldPreload = PrioritizeLcpImage.shouldPreloadRequest(lcpNode.record, mainResource, path);
const shouldPreload = PrioritizeLcpImage.shouldPreloadRequest(lcpRecord, mainResource, initiatorPath);
const lcpNodeToPreload = shouldPreload ? lcpNode : undefined;
const initiatorPath = [
{url: lcpNode.record.url, initiatorType: lcpNode.initiatorType},
...path.map(n => ({url: n.record.url, initiatorType: n.initiatorType})),
];
return {
lcpNodeToPreload,
initiatorPath,
@ -276,6 +305,7 @@ class PrioritizeLcpImage extends Audit {
const lcpRecord = PrioritizeLcpImage.getLcpRecord(trace, processedNavigation, networkRecords);
const graph = lanternLCP.pessimisticGraph;
// Note: if moving to LCPAllFrames, mainResource would need to be the LCP frame's main resource.
const {lcpNodeToPreload, initiatorPath} = PrioritizeLcpImage.getLCPNodeToPreload(mainResource,
graph, lcpRecord);

Просмотреть файл

@ -8,13 +8,15 @@ import PrioritizeLcpImage from '../../audits/prioritize-lcp-image.js';
import {networkRecordsToDevtoolsLog} from '../network-records-to-devtools-log.js';
import {createTestTrace} from '../create-test-trace.js';
const rootNodeUrl = 'http://example.com:3000';
const mainDocumentNodeUrl = 'http://www.example.com:3000';
const scriptNodeUrl = 'http://www.example.com/script.js';
const requestedUrl = 'http://example.com:3000';
const mainDocumentUrl = 'http://www.example.com:3000';
const finalDisplayedUrl = 'http://www.example.com:3000/some-page.html';
const scriptUrl = 'http://www.example.com/script.js';
const imageUrl = 'http://www.example.com/image.png';
describe('Performance: prioritize-lcp-image audit', () => {
const mockArtifacts = (networkRecords, finalDisplayedUrl) => {
const mockArtifacts = (networkRecords, URL) => {
return {
GatherContext: {gatherMode: 'navigation'},
traces: {
@ -26,11 +28,7 @@ describe('Performance: prioritize-lcp-image audit', () => {
devtoolsLogs: {
[PrioritizeLcpImage.DEFAULT_PASS]: networkRecordsToDevtoolsLog(networkRecords),
},
URL: {
requestedUrl: finalDisplayedUrl,
mainDocumentUrl: finalDisplayedUrl,
finalDisplayedUrl,
},
URL,
TraceElements: [
{
traceEventType: 'largest-contentful-paint',
@ -51,8 +49,8 @@ describe('Performance: prioritize-lcp-image audit', () => {
}
const mockNetworkRecords = () => {
return [
{
return {
networkRecords: [{
requestId: '2',
priority: 'High',
isLinkPreload: false,
@ -60,7 +58,7 @@ describe('Performance: prioritize-lcp-image audit', () => {
networkEndTime: 500,
timing: {receiveHeadersEnd: 500},
transferSize: 400,
url: rootNodeUrl,
url: requestedUrl,
frameId: 'ROOT_FRAME',
},
{
@ -71,7 +69,7 @@ describe('Performance: prioritize-lcp-image audit', () => {
networkRequestTime: 500,
networkEndTime: 1000,
transferSize: 16_000,
url: mainDocumentNodeUrl,
url: mainDocumentUrl,
frameId: 'ROOT_FRAME',
},
{
@ -82,8 +80,8 @@ describe('Performance: prioritize-lcp-image audit', () => {
networkRequestTime: 1000,
networkEndTime: 2000,
transferSize: 32_000,
url: scriptNodeUrl,
initiator: {type: 'parser', url: mainDocumentNodeUrl},
url: scriptUrl,
initiator: {type: 'parser', url: mainDocumentUrl},
frameId: 'ROOT_FRAME',
},
{
@ -95,15 +93,20 @@ describe('Performance: prioritize-lcp-image audit', () => {
networkEndTime: 4500,
transferSize: 64_000,
url: imageUrl,
initiator: {type: 'script', url: scriptNodeUrl},
initiator: {type: 'script', url: scriptUrl},
frameId: 'ROOT_FRAME',
}],
URL: {
requestedUrl,
mainDocumentUrl,
finalDisplayedUrl,
},
];
};
};
it('is not applicable if TraceElements does not include LCP', async () => {
const networkRecords = mockNetworkRecords();
const artifacts = mockArtifacts(networkRecords, mainDocumentNodeUrl);
const {networkRecords, URL} = mockNetworkRecords();
const artifacts = mockArtifacts(networkRecords, URL);
artifacts.TraceElements = [];
const result = await PrioritizeLcpImage.audit(artifacts, mockContext());
expect(result).toEqual({
@ -113,8 +116,8 @@ describe('Performance: prioritize-lcp-image audit', () => {
});
it('is not applicable if LCP was not an image', async () => {
const networkRecords = mockNetworkRecords();
const artifacts = mockArtifacts(networkRecords, mainDocumentNodeUrl);
const {networkRecords, URL} = mockNetworkRecords();
const artifacts = mockArtifacts(networkRecords, URL);
artifacts.TraceElements[0].type = 'text';
const result = await PrioritizeLcpImage.audit(artifacts, mockContext());
expect(result).toEqual({
@ -124,8 +127,8 @@ describe('Performance: prioritize-lcp-image audit', () => {
});
it('shouldn\'t be applicable if lcp image element is not found', async () => {
const networkRecords = mockNetworkRecords();
const artifacts = mockArtifacts(networkRecords, mainDocumentNodeUrl);
const {networkRecords, URL} = mockNetworkRecords();
const artifacts = mockArtifacts(networkRecords, URL);
// Make image paint event not apply to our node.
const imagePaintEvent = artifacts.traces.defaultPass
@ -139,9 +142,9 @@ describe('Performance: prioritize-lcp-image audit', () => {
});
it('shouldn\'t be applicable if the lcp is already preloaded', async () => {
const networkRecords = mockNetworkRecords();
const {networkRecords, URL} = mockNetworkRecords();
networkRecords[3].isLinkPreload = true;
const artifacts = mockArtifacts(networkRecords, mainDocumentNodeUrl);
const artifacts = mockArtifacts(networkRecords, URL);
const results = await PrioritizeLcpImage.audit(artifacts, mockContext());
expect(results.score).toEqual(1);
expect(results.details.overallSavingsMs).toEqual(0);
@ -150,18 +153,18 @@ describe('Performance: prioritize-lcp-image audit', () => {
// debugData should be included even if image shouldn't be preloaded.
expect(results.details.debugData).toMatchObject({
initiatorPath: [
{url: 'http://www.example.com/image.png', initiatorType: 'script'},
{url: 'http://www.example.com/script.js', initiatorType: 'parser'},
{url: 'http://www.example.com:3000', initiatorType: 'other'},
{url: imageUrl, initiatorType: 'script'},
{url: scriptUrl, initiatorType: 'parser'},
{url: mainDocumentUrl, initiatorType: 'redirect'},
],
pathLength: 3,
});
});
it('shouldn\'t be applicable if the lcp request is not from over the network', async () => {
const networkRecords = mockNetworkRecords();
const {networkRecords, URL} = mockNetworkRecords();
networkRecords[3].protocol = 'data';
const artifacts = mockArtifacts(networkRecords, mainDocumentNodeUrl);
const artifacts = mockArtifacts(networkRecords, URL);
const results = await PrioritizeLcpImage.audit(artifacts, mockContext());
expect(results.score).toEqual(1);
expect(results.details.overallSavingsMs).toEqual(0);
@ -169,8 +172,8 @@ describe('Performance: prioritize-lcp-image audit', () => {
});
it('should suggest preloading a lcp image if all criteria is met', async () => {
const networkRecords = mockNetworkRecords();
const artifacts = mockArtifacts(networkRecords, mainDocumentNodeUrl);
const {networkRecords, URL} = mockNetworkRecords();
const artifacts = mockArtifacts(networkRecords, URL);
const results = await PrioritizeLcpImage.audit(artifacts, mockContext());
expect(results.numericValue).toEqual(30);
expect(results.details.overallSavingsMs).toEqual(30);
@ -179,18 +182,18 @@ describe('Performance: prioritize-lcp-image audit', () => {
expect(results.details.debugData).toMatchObject({
initiatorPath: [
{url: 'http://www.example.com/image.png', initiatorType: 'script'},
{url: 'http://www.example.com/script.js', initiatorType: 'parser'},
{url: 'http://www.example.com:3000', initiatorType: 'other'},
{url: imageUrl, initiatorType: 'script'},
{url: scriptUrl, initiatorType: 'parser'},
{url: mainDocumentUrl, initiatorType: 'redirect'},
],
pathLength: 3,
});
});
it('should suggest preloading when LCP is waiting on a dependency', async () => {
const networkRecords = mockNetworkRecords();
const {networkRecords, URL} = mockNetworkRecords();
networkRecords[2].transferSize = 100 * 1000 * 1000;
const artifacts = mockArtifacts(networkRecords, mainDocumentNodeUrl);
const artifacts = mockArtifacts(networkRecords, URL);
const results = await PrioritizeLcpImage.audit(artifacts, mockContext());
expect(results.numericValue).toEqual(180);
expect(results.details.overallSavingsMs).toEqual(180);
@ -198,16 +201,16 @@ describe('Performance: prioritize-lcp-image audit', () => {
expect(results.details.items[0].wastedMs).toEqual(180);
expect(results.details.debugData).toMatchObject({
initiatorPath: [
{url: 'http://www.example.com/image.png', initiatorType: 'script'},
{url: 'http://www.example.com/script.js', initiatorType: 'parser'},
{url: 'http://www.example.com:3000', initiatorType: 'other'},
{url: imageUrl, initiatorType: 'script'},
{url: scriptUrl, initiatorType: 'parser'},
{url: mainDocumentUrl, initiatorType: 'redirect'},
],
pathLength: 3,
});
});
it('should use the initiator path of the first image instance loaded', async () => {
const networkRecords = mockNetworkRecords();
const {networkRecords, URL} = mockNetworkRecords();
networkRecords.push({
requestId: '15',
resourceType: 'Image',
@ -215,10 +218,10 @@ describe('Performance: prioritize-lcp-image audit', () => {
// Completed before other image request.
networkEndTime: 4000,
url: imageUrl,
initiator: {type: 'parser', url: mainDocumentNodeUrl},
initiator: {type: 'parser', url: mainDocumentUrl},
frameId: 'ROOT_FRAME',
});
const artifacts = mockArtifacts(networkRecords, mainDocumentNodeUrl);
const artifacts = mockArtifacts(networkRecords, URL);
const results = await PrioritizeLcpImage.audit(artifacts, mockContext());
expect(results).toMatchObject({
numericValue: 0,
@ -226,8 +229,8 @@ describe('Performance: prioritize-lcp-image audit', () => {
items: [],
debugData: {
initiatorPath: [
{url: 'http://www.example.com/image.png', initiatorType: 'parser'},
{url: 'http://www.example.com:3000', initiatorType: 'other'},
{url: imageUrl, initiatorType: 'parser'},
{url: mainDocumentUrl, initiatorType: 'redirect'},
],
pathLength: 2,
},
@ -236,7 +239,7 @@ describe('Performance: prioritize-lcp-image audit', () => {
});
it('should not use the initiator path of a non-image load', async () => {
const networkRecords = mockNetworkRecords();
const {networkRecords, URL} = mockNetworkRecords();
networkRecords.push({
requestId: '15',
// Not an image load.
@ -246,10 +249,10 @@ describe('Performance: prioritize-lcp-image audit', () => {
networkEndTime: 4000,
url: imageUrl,
// Parser, not script initiator.
initiator: {type: 'parser', url: mainDocumentNodeUrl},
initiator: {type: 'parser', url: mainDocumentUrl},
frameId: 'ROOT_FRAME',
});
const artifacts = mockArtifacts(networkRecords, mainDocumentNodeUrl);
const artifacts = mockArtifacts(networkRecords, URL);
const results = await PrioritizeLcpImage.audit(artifacts, mockContext());
expect(results).toMatchObject({
numericValue: 180,
@ -257,9 +260,9 @@ describe('Performance: prioritize-lcp-image audit', () => {
items: [{url: imageUrl}],
debugData: {
initiatorPath: [
{url: 'http://www.example.com/image.png', initiatorType: 'script'},
{url: 'http://www.example.com/script.js', initiatorType: 'parser'},
{url: 'http://www.example.com:3000', initiatorType: 'other'},
{url: imageUrl, initiatorType: 'script'},
{url: scriptUrl, initiatorType: 'parser'},
{url: mainDocumentUrl, initiatorType: 'redirect'},
],
pathLength: 3,
},
@ -268,7 +271,7 @@ describe('Performance: prioritize-lcp-image audit', () => {
});
it('should not use the initiator path of an image from a different frame', async () => {
const networkRecords = mockNetworkRecords();
const {networkRecords, URL} = mockNetworkRecords();
networkRecords.push({
requestId: '15',
resourceType: 'Image',
@ -276,11 +279,11 @@ describe('Performance: prioritize-lcp-image audit', () => {
// Completed before other image request.
networkEndTime: 4000,
url: imageUrl,
initiator: {type: 'parser', url: mainDocumentNodeUrl},
initiator: {type: 'parser', url: mainDocumentUrl},
// From different frame.
frameId: 'CHILD_FRAME',
});
const artifacts = mockArtifacts(networkRecords, mainDocumentNodeUrl);
const artifacts = mockArtifacts(networkRecords, URL);
const results = await PrioritizeLcpImage.audit(artifacts, mockContext());
expect(results).toMatchObject({
numericValue: 180,
@ -288,9 +291,9 @@ describe('Performance: prioritize-lcp-image audit', () => {
items: [{url: imageUrl}],
debugData: {
initiatorPath: [
{url: 'http://www.example.com/image.png', initiatorType: 'script'},
{url: 'http://www.example.com/script.js', initiatorType: 'parser'},
{url: 'http://www.example.com:3000', initiatorType: 'other'},
{url: imageUrl, initiatorType: 'script'},
{url: scriptUrl, initiatorType: 'parser'},
{url: mainDocumentUrl, initiatorType: 'redirect'},
],
pathLength: 3,
},
@ -300,7 +303,7 @@ describe('Performance: prioritize-lcp-image audit', () => {
it('should follow any redirected image requests', async () => {
const redirectedImageUrl = 'http://www.example.com/redirect.jpg';
const networkRecords = mockNetworkRecords();
const {networkRecords, URL} = mockNetworkRecords();
// Redirect image request to newly added request.
const redirectSource = networkRecords.at(-1);
@ -315,10 +318,10 @@ describe('Performance: prioritize-lcp-image audit', () => {
networkEndTime: 4500,
transferSize: 64_000,
url: redirectedImageUrl,
initiator: {type: 'script', url: scriptNodeUrl},
initiator: {type: 'script', url: scriptUrl},
frameId: 'ROOT_FRAME',
});
const artifacts = mockArtifacts(networkRecords, mainDocumentNodeUrl);
const artifacts = mockArtifacts(networkRecords, URL);
const results = await PrioritizeLcpImage.audit(artifacts, mockContext());
expect(results).toMatchObject({
numericValue: 210,
@ -326,12 +329,34 @@ describe('Performance: prioritize-lcp-image audit', () => {
items: [{url: redirectedImageUrl}],
debugData: {
initiatorPath: [
{url: redirectedImageUrl, initiatorType: 'script'},
// TOD(bckenny): missing initiator step through redirected image url.
{url: 'http://www.example.com/script.js', initiatorType: 'parser'},
{url: 'http://www.example.com:3000', initiatorType: 'other'},
{url: redirectedImageUrl, initiatorType: 'redirect'},
{url: imageUrl, initiatorType: 'script'},
{url: scriptUrl, initiatorType: 'parser'},
{url: mainDocumentUrl, initiatorType: 'redirect'},
],
pathLength: 3,
pathLength: 4,
},
},
});
});
it('should fall back mainResource in initiator path when chain is broken', async () => {
const {networkRecords, URL} = mockNetworkRecords();
// For whatever reason, initiator information isn't available for this image.
networkRecords[3].initiator = {type: 'other'};
const artifacts = mockArtifacts(networkRecords, URL);
const results = await PrioritizeLcpImage.audit(artifacts, mockContext());
expect(results).toMatchObject({
numericValue: 0,
details: {
items: [],
debugData: {
initiatorPath: [
{url: imageUrl, initiatorType: 'fallbackToMain'},
{url: mainDocumentUrl, initiatorType: 'redirect'},
],
pathLength: 2,
},
},
});

Просмотреть файл

@ -1798,45 +1798,8 @@
"displayValue": "",
"details": {
"type": "opportunity",
"headings": [
{
"key": "node",
"valueType": "node",
"label": ""
},
{
"key": "url",
"valueType": "url",
"label": "URL"
},
{
"key": "wastedMs",
"valueType": "timespanMs",
"label": "Potential Savings"
}
],
"items": [
{
"node": {
"type": "node",
"lhId": "page-0-IMG",
"path": "1,HTML,1,BODY,0,DIV,0,DIV,4,IMG",
"selector": "body > div#__next > div.flex > img.h-32",
"boundingRect": {
"top": 192,
"bottom": 320,
"left": 38,
"right": 322,
"width": 284,
"height": 128
},
"snippet": "<img src=\"/logo-text.svg\" class=\"h-32 sm:h-44 mt-12 md:mt-24 mb-8 lg:mb-16\" alt=\"Mike's Cereal Shack Logo\">",
"nodeLabel": "Mike's Cereal Shack Logo"
},
"url": "https://www.mikescerealshack.co/logo-text.svg",
"wastedMs": 0
}
],
"headings": [],
"items": [],
"overallSavingsMs": 0,
"debugData": {
"type": "debugdata",
@ -6276,7 +6239,6 @@
"audits[network-server-latency].details.headings[0].label",
"audits[long-tasks].details.headings[0].label",
"audits[unsized-images].details.headings[1].label",
"audits[prioritize-lcp-image].details.headings[1].label",
"audits[total-byte-weight].details.headings[0].label",
"audits[legacy-javascript].details.headings[0].label"
],
@ -6631,10 +6593,6 @@
"core/audits/prioritize-lcp-image.js | description": [
"audits[prioritize-lcp-image].description"
],
"core/lib/i18n/i18n.js | columnWastedBytes": [
"audits[prioritize-lcp-image].details.headings[2].label",
"audits[legacy-javascript].details.headings[2].label"
],
"core/audits/csp-xss.js | title": [
"audits[csp-xss].title"
],
@ -7052,6 +7010,9 @@
"path": "audits[legacy-javascript].displayValue"
}
],
"core/lib/i18n/i18n.js | columnWastedBytes": [
"audits[legacy-javascript].details.headings[2].label"
],
"core/audits/dobetterweb/doctype.js | title": [
"audits.doctype.title"
],
@ -17450,45 +17411,8 @@
"displayValue": "",
"details": {
"type": "opportunity",
"headings": [
{
"key": "node",
"valueType": "node",
"label": ""
},
{
"key": "url",
"valueType": "url",
"label": "URL"
},
{
"key": "wastedMs",
"valueType": "timespanMs",
"label": "Potential Savings"
}
],
"items": [
{
"node": {
"type": "node",
"lhId": "page-0-IMG",
"path": "1,HTML,1,BODY,0,DIV,0,DIV,3,MAIN,2,DIV,0,IMG",
"selector": "div.flex > main#main-content > div.flex > img.rounded-md",
"boundingRect": {
"top": 228,
"bottom": 388,
"left": 38,
"right": 322,
"width": 284,
"height": 160
},
"snippet": "<img class=\"rounded-md h-40 sm:h-48 md:h-60 lg:h-80\" src=\"/oscar-actually.jpg\">",
"nodeLabel": "div.flex > main#main-content > div.flex > img.rounded-md"
},
"url": "https://www.mikescerealshack.co/oscar-actually.jpg",
"wastedMs": 0
}
],
"headings": [],
"items": [],
"overallSavingsMs": 0,
"debugData": {
"type": "debugdata",
@ -22066,7 +21990,6 @@
"audits[network-server-latency].details.headings[0].label",
"audits[long-tasks].details.headings[0].label",
"audits[unsized-images].details.headings[1].label",
"audits[prioritize-lcp-image].details.headings[1].label",
"audits[total-byte-weight].details.headings[0].label",
"audits[modern-image-formats].details.headings[1].label",
"audits[uses-responsive-images].details.headings[1].label",
@ -22413,12 +22336,6 @@
"core/audits/prioritize-lcp-image.js | description": [
"audits[prioritize-lcp-image].description"
],
"core/lib/i18n/i18n.js | columnWastedBytes": [
"audits[prioritize-lcp-image].details.headings[2].label",
"audits[modern-image-formats].details.headings[3].label",
"audits[uses-responsive-images].details.headings[3].label",
"audits[legacy-javascript].details.headings[2].label"
],
"core/audits/csp-xss.js | title": [
"audits[csp-xss].title"
],
@ -22813,6 +22730,11 @@
"audits[modern-image-formats].details.headings[2].label",
"audits[uses-responsive-images].details.headings[2].label"
],
"core/lib/i18n/i18n.js | columnWastedBytes": [
"audits[modern-image-formats].details.headings[3].label",
"audits[uses-responsive-images].details.headings[3].label",
"audits[legacy-javascript].details.headings[2].label"
],
"core/audits/byte-efficiency/uses-optimized-images.js | title": [
"audits[uses-optimized-images].title"
],

Просмотреть файл

@ -2649,6 +2649,10 @@
"initiatorPath": [
{
"url": "http://localhost:10200/dobetterweb/lighthouse-1024x680.jpg?redirected-lcp",
"initiatorType": "redirect"
},
{
"url": "http://localhost:10200/dobetterweb/lighthouse-1024x680.jpg?lcp&redirect=lighthouse-1024x680.jpg%3Fredirected-lcp",
"initiatorType": "parser"
},
{
@ -2660,7 +2664,7 @@
"initiatorType": "other"
}
],
"pathLength": 3
"pathLength": 4
}
}
},