Skip to content

Commit

Permalink
perf(optimized-images): use Audits.getEncodedResponse (#3087)
Browse files Browse the repository at this point in the history
  • Loading branch information
patrickhulce authored and brendankenny committed Aug 24, 2017
1 parent 1750f34 commit c5c6f5d
Show file tree
Hide file tree
Showing 4 changed files with 80 additions and 27 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,7 @@ class UsesOptimizedImages extends ByteEfficiencyAudit {

results.push({
url,
fromProtocol: image.fromProtocol,
isCrossOrigin: !image.isSameOrigin,
preview: {url: image.url, mimeType: image.mimeType, type: 'thumbnail'},
totalBytes: image.originalSize,
Expand Down
1 change: 1 addition & 0 deletions lighthouse-core/audits/byte-efficiency/uses-webp-images.js
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,7 @@ class UsesWebPImages extends ByteEfficiencyAudit {

results.push({
url,
fromProtocol: image.fromProtocol,
isCrossOrigin: !image.isSameOrigin,
preview: {url: image.url, mimeType: image.mimeType, type: 'thumbnail'},
totalBytes: image.originalSize,
Expand Down
63 changes: 49 additions & 14 deletions lighthouse-core/gather/gatherers/dobetterweb/optimized-images.js
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,11 @@
const Gatherer = require('../gatherer');
const URL = require('../../../lib/url-shim');

const JPEG_QUALITY = 0.92;
const WEBP_QUALITY = 0.85;

const MINIMUM_IMAGE_SIZE = 4096; // savings of <4 KB will be ignored in the audit anyway

/* global document, Image, atob */

/**
Expand Down Expand Up @@ -71,7 +76,7 @@ class OptimizedImages extends Gatherer {
const isSameOrigin = URL.originsMatch(pageUrl, record._url);
const isBase64DataUri = /^data:.{2,40}base64\s*,/.test(record._url);

if (isOptimizableImage) {
if (isOptimizableImage && record._resourceSize > MINIMUM_IMAGE_SIZE) {
prev.push({
isSameOrigin,
isBase64DataUri,
Expand All @@ -86,29 +91,59 @@ class OptimizedImages extends Gatherer {
}, []);
}

/**
* @param {!Object} driver
* @param {string} requestId
* @param {string} encoding Either webp or jpeg.
* @return {!Promise<{encodedSize: number}>}
*/
_getEncodedResponse(driver, requestId, encoding) {
const quality = encoding === 'jpeg' ? JPEG_QUALITY : WEBP_QUALITY;
const params = {requestId, encoding, quality, sizeOnly: true};
return driver.sendCommand('Audits.getEncodedResponse', params);
}

/**
* @param {!Object} driver
* @param {{url: string, isBase64DataUri: boolean, resourceSize: number}} networkRecord
* @return {!Promise<?{originalSize: number, jpegSize: number, webpSize: number}>}
* @return {!Promise<?{fromProtocol: boolean, originalSize: number, jpegSize: number, webpSize: number}>}
*/
calculateImageStats(driver, networkRecord) {
if (!networkRecord.isSameOrigin && !networkRecord.isBase64DataUri) {
// Processing of cross-origin images is buggy and very slow, disable for now
// See https://github.com/GoogleChrome/lighthouse/issues/1853
// See https://github.com/GoogleChrome/lighthouse/issues/2146
return Promise.resolve(null);
}

return Promise.resolve(networkRecord.url).then(uri => {
const script = `(${getOptimizedNumBytes.toString()})(${JSON.stringify(uri)})`;
return driver.evaluateAsync(script).then(stats => {
if (!stats) {
return null;
// TODO(phulce): remove this dance of trying _getEncodedResponse with a fallback when Audits
// domain hits stable in Chrome 62
return Promise.resolve(networkRecord.requestId).then(requestId => {
if (this._getEncodedResponseUnsupported) return;
return this._getEncodedResponse(driver, requestId, 'jpeg').then(jpegData => {
return this._getEncodedResponse(driver, requestId, 'webp').then(webpData => {
return {
fromProtocol: true,
originalSize: networkRecord.resourceSize,
jpegSize: jpegData.encodedSize,
webpSize: webpData.encodedSize,
};
});
}).catch(err => {
if (/wasn't found/.test(err.message)) {
// Mark non-support so we don't keep attempting the protocol method over and over
this._getEncodedResponseUnsupported = true;
} else {
throw err;
}
});
}).then(result => {
if (result) return result;

// Take the slower fallback path if getEncodedResponse isn't available yet
// CORS canvas tainting doesn't support cross-origin images, so skip them early
if (!networkRecord.isSameOrigin && !networkRecord.isBase64DataUri) return null;

const script = `(${getOptimizedNumBytes.toString()})(${JSON.stringify(networkRecord.url)})`;
return driver.evaluateAsync(script).then(stats => {
if (!stats) return null;
const isBase64DataUri = networkRecord.isBase64DataUri;
const base64Length = networkRecord.url.length - networkRecord.url.indexOf(',') - 1;
return {
fromProtocol: false,
originalSize: isBase64DataUri ? base64Length : networkRecord.resourceSize,
jpegSize: isBase64DataUri ? stats.jpeg.base64 : stats.jpeg.binary,
webpSize: isBase64DataUri ? stats.webp.base64 : stats.webp.binary,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,49 +22,49 @@ const traceData = {
{
_url: 'http://google.com/image.jpg',
_mimeType: 'image/jpeg',
_resourceSize: 10,
_resourceSize: 10000,
finished: true,
},
{
_url: 'http://google.com/transparent.png',
_mimeType: 'image/png',
_resourceSize: 11,
_resourceSize: 11000,
finished: true,
},
{
_url: 'http://google.com/image.bmp',
_mimeType: 'image/bmp',
_resourceSize: 12,
_resourceSize: 12000,
finished: true,
},
{
_url: 'http://google.com/image.bmp',
_mimeType: 'image/bmp',
_resourceSize: 12,
_resourceSize: 12000,
finished: true,
},
{
_url: 'http://google.com/vector.svg',
_mimeType: 'image/svg+xml',
_resourceSize: 13,
_resourceSize: 13000,
finished: true,
},
{
_url: 'http://gmail.com/image.jpg',
_mimeType: 'image/jpeg',
_resourceSize: 15,
_resourceSize: 15000,
finished: true,
},
{
_url: 'data: image/jpeg ; base64 ,SgVcAT32587935321...',
_mimeType: 'image/jpeg',
_resourceSize: 14,
_resourceSize: 14000,
finished: true,
},
{
_url: 'http://google.com/big-image.bmp',
_mimeType: 'image/bmp',
_resourceSize: 12,
_resourceSize: 12000,
finished: false, // ignore for not finishing
},
]
Expand All @@ -81,7 +81,7 @@ describe('Optimized images', () => {
return Promise.resolve(fakeImageStats);
},
sendCommand: function() {
return Promise.resolve({base64Encoded: true, body: 'mydata'});
return Promise.reject(new Error('wasn\'t found'));
},
}
};
Expand All @@ -108,11 +108,11 @@ describe('Optimized images', () => {

return optimizedImages.afterPass(options, traceData).then(artifact => {
assert.equal(artifact.length, 4);
checkSizes(artifact[0], 10, 60, 80);
checkSizes(artifact[1], 11, 60, 80);
checkSizes(artifact[2], 12, 60, 80);
checkSizes(artifact[0], 10000, 60, 80);
checkSizes(artifact[1], 11000, 60, 80);
checkSizes(artifact[2], 12000, 60, 80);
// skip cross-origin for now
// checkSizes(artifact[3], 15, 60, 80);
// checkSizes(artifact[3], 15000, 60, 80);
checkSizes(artifact[3], 20, 80, 100); // uses base64 data
});
});
Expand All @@ -136,4 +136,20 @@ describe('Optimized images', () => {
assert.ok(/whoops/.test(failed.err.message), 'passed along error message');
});
});

it('supports Audits.getEncodedResponse', () => {
options.driver.sendCommand = (method, params) => {
const encodedSize = params.encoding === 'webp' ? 60 : 80;
return Promise.resolve({encodedSize});
};

return optimizedImages.afterPass(options, traceData).then(artifact => {
assert.equal(artifact.length, 5);
assert.equal(artifact[0].originalSize, 10000);
assert.equal(artifact[0].webpSize, 60);
assert.equal(artifact[0].jpegSize, 80);
// supports cross-origin
assert.ok(/gmail.*image.jpg/.test(artifact[3].url));
});
});
});

0 comments on commit c5c6f5d

Please sign in to comment.