diff --git a/lighthouse-core/audits/byte-efficiency/uses-optimized-animated-images.js b/lighthouse-core/audits/byte-efficiency/uses-optimized-animated-images.js index 72cfcd5427ed..55ea997225ab 100644 --- a/lighthouse-core/audits/byte-efficiency/uses-optimized-animated-images.js +++ b/lighthouse-core/audits/byte-efficiency/uses-optimized-animated-images.js @@ -8,20 +8,20 @@ */ 'use strict'; -const Audit = require('../audit'); const WebInspector = require('../../lib/web-inspector'); +const ByteEfficiencyAudit = require('./byte-efficiency-audit'); // the threshold for the size of GIFs wich we flag as unoptimized const GIF_BYTE_THRESHOLD = 100 * 1024; -class UsesOptimizedAnimatedImages extends Audit { +class UsesOptimizedAnimatedImages extends ByteEfficiencyAudit { /** * @return {!AuditMeta} */ static get meta() { return { name: 'uses-optimized-animated-images', - informative: true, + scoreDisplayMode: ByteEfficiencyAudit.SCORING_MODES.NUMERIC, description: 'Use a video formats for animated content', helpText: 'Large GIFs are inefficient for delivering animated content. Consider using ' + 'MPEG4/WebM videos for animations and PNG/WebP for static images instead of GIF to save ' + @@ -30,44 +30,52 @@ class UsesOptimizedAnimatedImages extends Audit { }; } + /** + * Calculate savings percentage + * @param {number} bytes + * @see https://github.com/GoogleChrome/lighthouse/issues/4696#issuecomment-380296510} bytes + */ + static getPercentSavings(bytes) { + return (29.1 * Math.log10(bytes) - 100.7) / 100; + } + /** * @param {!Artifacts} artifacts * @return {!AuditResult} */ - static async audit(artifacts) { + static async audit_(artifacts) { const devtoolsLogs = artifacts.devtoolsLogs[UsesOptimizedAnimatedImages.DEFAULT_PASS]; const networkRecords = await artifacts.requestNetworkRecords(devtoolsLogs); const unoptimizedContent = networkRecords.filter( record => record.mimeType === 'image/gif' && record._resourceType === WebInspector.resourceTypes.Image && - record.transferSize > GIF_BYTE_THRESHOLD + record.resourceSize > GIF_BYTE_THRESHOLD ); const results = unoptimizedContent.map(record => { return { url: record.url, - transferSize: record.transferSize, + totalBytes: record.resourceSize, + wastedBytes: record.resourceSize * UsesOptimizedAnimatedImages.getPercentSavings(record.resourceSize), }; }); const headings = [ - {key: 'url', itemType: 'url', text: 'Url'}, + {key: 'url', itemType: 'url', text: 'URL'}, { - key: 'transferSize', + key: 'totalBytes', itemType: 'bytes', displayUnit: 'kb', granularity: 1, text: 'Transfer Size', }, + {key: 'wastedBytes', itemType: 'bytes', displayUnit: 'kb', granularity: 1, text: 'Byte Savings'}, ]; - const summary = {}; - const details = Audit.makeTableDetails(headings, results, summary); return { - score: Number(results.length === 0), - rawValue: results.length === 0, - details, + results, + headings, }; } } diff --git a/lighthouse-core/config/default-config.js b/lighthouse-core/config/default-config.js index d632939b3ea4..8249053e75ab 100644 --- a/lighthouse-core/config/default-config.js +++ b/lighthouse-core/config/default-config.js @@ -283,6 +283,7 @@ module.exports = { {id: 'time-to-first-byte', weight: 0, group: 'perf-hint'}, {id: 'redirects', weight: 0, group: 'perf-hint'}, {id: 'uses-rel-preload', weight: 0, group: 'perf-hint'}, + {id: 'uses-optimized-animated-images', weight: 0, group: 'perf-hint'}, {id: 'total-byte-weight', weight: 0, group: 'perf-info'}, {id: 'uses-long-cache-ttl', weight: 0, group: 'perf-info'}, {id: 'dom-size', weight: 0, group: 'perf-info'}, @@ -293,7 +294,6 @@ module.exports = { {id: 'screenshot-thumbnails', weight: 0}, {id: 'mainthread-work-breakdown', weight: 0, group: 'perf-info'}, {id: 'font-display', weight: 0, group: 'perf-info'}, - {id: 'uses-optimized-animated-images', weight: 0, group: 'perf-info'}, ], }, 'pwa': { diff --git a/lighthouse-core/test/audits/byte-efficiency/uses-optimized-animated-images-test.js b/lighthouse-core/test/audits/byte-efficiency/uses-optimized-animated-images-test.js index 20b6c19edcfd..0d5f40bf0df5 100644 --- a/lighthouse-core/test/audits/byte-efficiency/uses-optimized-animated-images-test.js +++ b/lighthouse-core/test/audits/byte-efficiency/uses-optimized-animated-images-test.js @@ -17,12 +17,14 @@ describe('Page uses videos for animated GIFs', () => { { _resourceType: WebInspector.resourceTypes.Image, mimeType: 'image/gif', - transferSize: 100240, + resourceSize: 100240, + url: 'https://example.com/example.gif', }, { _resourceType: WebInspector.resourceTypes.Image, mimeType: 'image/gif', - transferSize: 110000, + resourceSize: 110000, + url: 'https://example.com/example2.gif', }, ]; const artifacts = { @@ -30,10 +32,11 @@ describe('Page uses videos for animated GIFs', () => { requestNetworkRecords: () => Promise.resolve(networkRecords), }; - const {score, rawValue, details} = await UsesOptimizedAnimatedImages.audit(artifacts); - assert.equal(score, 0); - assert.equal(rawValue, 0); - assert.equal(details.items.length, 1); + const {results} = await UsesOptimizedAnimatedImages.audit_(artifacts); + assert.equal(results.length, 1); + assert.equal(results[0].url, 'https://example.com/example2.gif'); + assert.equal(results[0].totalBytes, 110000); + assert.equal(Math.round(results[0].wastedBytes), 50605); }); it(`shouldn't flag content that looks like a gif but isn't`, async () => { @@ -41,7 +44,7 @@ describe('Page uses videos for animated GIFs', () => { { mimeType: 'image/gif', _resourceType: WebInspector.resourceTypes.Media, - transferSize: 150000, + resourceSize: 150000, }, ]; const artifacts = { @@ -49,10 +52,8 @@ describe('Page uses videos for animated GIFs', () => { requestNetworkRecords: () => Promise.resolve(networkRecords), }; - const {score, rawValue, details} = await UsesOptimizedAnimatedImages.audit(artifacts); - assert.equal(score, 1); - assert.equal(rawValue, 1); - assert.equal(details.items.length, 0); + const {results} = await UsesOptimizedAnimatedImages.audit_(artifacts); + assert.equal(results.length, 0); }); it(`shouldn't flag non gif content`, async () => { @@ -60,12 +61,12 @@ describe('Page uses videos for animated GIFs', () => { { _resourceType: WebInspector.resourceTypes.Document, mimeType: 'text/html', - transferSize: 150000, + resourceSize: 150000, }, { _resourceType: WebInspector.resourceTypes.Stylesheet, mimeType: 'text/css', - transferSize: 150000, + resourceSize: 150000, }, ]; const artifacts = { @@ -73,9 +74,7 @@ describe('Page uses videos for animated GIFs', () => { requestNetworkRecords: () => Promise.resolve(networkRecords), }; - const {score, rawValue, details} = await UsesOptimizedAnimatedImages.audit(artifacts); - assert.equal(score, 1); - assert.equal(rawValue, 1); - assert.equal(details.items.length, 0); + const {results} = await UsesOptimizedAnimatedImages.audit_(artifacts); + assert.equal(results.length, 0); }); });