Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
31 changes: 26 additions & 5 deletions core/audits/image-size-responsive.js
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,8 @@


import {Audit} from './audit.js';
import {ImageRecords} from '../computed/image-records.js';
import {NetworkRecords} from '../computed/network-records.js';
import UrlUtils from '../lib/url-utils.js';
import * as i18n from '../lib/i18n/i18n.js';

Expand Down Expand Up @@ -77,9 +79,10 @@ function isSmallerThanViewport(imageRect, viewportDimensions) {

/**
* @param {LH.Artifacts.ImageElement} image
* @param {LH.Artifacts.ImageElementRecord | undefined} imageRecord
* @return {boolean}
*/
function isCandidate(image) {
function isCandidate(image, imageRecord) {
/** image-rendering solution for pixel art scaling.
* https://developer.mozilla.org/en-US/docs/Games/Techniques/Crisp_pixel_art_look
*/
Expand All @@ -96,6 +99,10 @@ function isCandidate(image) {
) {
return false;
}
// Check the actual mimeType before guessing, since file extension is not guaranteed
if (imageRecord?.mimeType === 'image/svg+xml') {
return false;
}
if (UrlUtils.guessMimeType(image.src) === 'image/svg+xml') {
return false;
}
Expand Down Expand Up @@ -242,20 +249,34 @@ class ImageSizeResponsive extends Audit {
title: str_(UIStrings.title),
failureTitle: str_(UIStrings.failureTitle),
description: str_(UIStrings.description),
requiredArtifacts: ['ImageElements', 'ViewportDimensions'],
requiredArtifacts: ['ImageElements', 'ViewportDimensions', 'devtoolsLogs'],
};
}

/**
* @param {LH.Artifacts} artifacts
* @return {LH.Audit.Product}
* @param {LH.Audit.Context} context
* @return {Promise<LH.Audit.Product>}
*/
static audit(artifacts) {
static async audit(artifacts, context) {
const DPR = artifacts.ViewportDimensions.devicePixelRatio;

// Prepare ImageElementRecord map for retrieving the real mimeType
// Derived from ./is-on-https.js and ./byte-efficiency/uses-responsive-images.js
const devtoolsLogs = artifacts.devtoolsLogs[Audit.DEFAULT_PASS];
const networkRecords = await NetworkRecords.request(devtoolsLogs, context);
const images = await ImageRecords.request({
ImageElements: artifacts.ImageElements,
networkRecords,
}, context);

/** @type {Map<string, LH.Artifacts.ImageElementRecord>} */
const imageRecordsByURL = new Map();
images.forEach(img => imageRecordsByURL.set(img.src, img));

const results = Array
.from(artifacts.ImageElements)
.filter(isCandidate)
.filter(image => isCandidate(image, imageRecordsByURL.get(image.src)))
.filter(imageHasNaturalDimensions)
.filter(image => !imageHasRightSize(image, DPR))
.filter(image => isVisible(image.clientRect, artifacts.ViewportDimensions))
Expand Down
38 changes: 25 additions & 13 deletions core/test/audits/image-size-responsive-test.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,8 @@
*/

import assert from 'assert/strict';

import ImageSizeResponsiveAudit from '../../audits/image-size-responsive.js';
import {networkRecordsToDevtoolsLog} from '../network-records-to-devtools-log.js';

const WIDTH = 800;
const HEIGHT = 600;
Expand All @@ -31,11 +31,19 @@ function generateImage(clientSize, naturalDimensions, props, src) {
};
}

function generateDevToolsLogs() {
const networkRecords = [
{url: 'https://google.com/', parsedURL: {scheme: 'https', host: 'google.com'}},
];
const devtoolsLogs = networkRecordsToDevtoolsLog(networkRecords);
return {[ImageSizeResponsiveAudit.DEFAULT_PASS]: devtoolsLogs};
}

describe('Images: size audit', () => {
function testImage(condition, data, src = 'https://google.com/logo.png') {
const description = `identifies when an image ${condition}`;
it(description, () => {
const result = ImageSizeResponsiveAudit.audit({
it(description, async () => {
const result = await ImageSizeResponsiveAudit.audit({
ImageElements: [
generateImage(
{displayedWidth: data.clientSize[0], displayedHeight: data.clientSize[1]},
Expand All @@ -49,7 +57,8 @@ describe('Images: size audit', () => {
innerHeight: HEIGHT,
devicePixelRatio: data.devicePixelRatio || 1,
},
});
devtoolsLogs: generateDevToolsLogs(),
}, {computedCache: new Map()});
let details = '';
if (result.score === 0) {
const {displayedSize: displayed, actualSize: actual, expectedSize: expected} =
Expand Down Expand Up @@ -379,8 +388,8 @@ describe('Images: size audit', () => {
});
});

it('de-dupes images', () => {
const result = ImageSizeResponsiveAudit.audit({
it('de-dupes images', async () => {
const result = await ImageSizeResponsiveAudit.audit({
ImageElements: [
generateImage(
{displayedWidth: 80, displayedHeight: 40},
Expand All @@ -400,13 +409,14 @@ describe('Images: size audit', () => {
innerHeight: HEIGHT,
devicePixelRatio: 1,
},
});
devtoolsLogs: generateDevToolsLogs(),
}, {computedCache: new Map()});
assert.equal(result.details.items.length, 1);
assert.equal(result.details.items[0].expectedSize, '160 x 80');
});

it('sorts images by size delta', () => {
const result = ImageSizeResponsiveAudit.audit({
it('sorts images by size delta', async () => {
const result = await ImageSizeResponsiveAudit.audit({
ImageElements: [
generateImage(
{displayedWidth: 80, displayedHeight: 40},
Expand All @@ -432,14 +442,15 @@ describe('Images: size audit', () => {
innerHeight: HEIGHT,
devicePixelRatio: 1,
},
});
devtoolsLogs: generateDevToolsLogs(),
}, {computedCache: new Map()});
assert.equal(result.details.items.length, 3);
const srcs = result.details.items.map(item => item.url);
assert.deepEqual(srcs, ['image2.png', 'image3.png', 'image1.png']);
});

it('shows the right expected size', () => {
const result = ImageSizeResponsiveAudit.audit({
it('shows the right expected size', async () => {
const result = await ImageSizeResponsiveAudit.audit({
ImageElements: [
generateImage(
{displayedWidth: 80, displayedHeight: 40},
Expand All @@ -451,7 +462,8 @@ describe('Images: size audit', () => {
innerHeight: HEIGHT,
devicePixelRatio: 2.71,
},
});
devtoolsLogs: generateDevToolsLogs(),
}, {computedCache: new Map()});
assert.equal(result.details.items.length, 1);
assert.equal(result.details.items[0].expectedSize, '160 x 80');
});
Expand Down
Loading