Skip to content

Commit

Permalink
new_audit: unsized-images reland (#11340)
Browse files Browse the repository at this point in the history
  • Loading branch information
connorjclark committed Aug 28, 2020
1 parent 4651b52 commit 960d7c7
Show file tree
Hide file tree
Showing 15 changed files with 348 additions and 73 deletions.
8 changes: 8 additions & 0 deletions lighthouse-cli/test/cli/__snapshots__/index-test.js.snap
Original file line number Diff line number Diff line change
Expand Up @@ -168,6 +168,9 @@ Object {
Object {
"path": "non-composited-animations",
},
Object {
"path": "unsized-images",
},
Object {
"path": "large-javascript-libraries",
},
Expand Down Expand Up @@ -1028,6 +1031,11 @@ Object {
"id": "non-composited-animations",
"weight": 0,
},
Object {
"group": "diagnostics",
"id": "unsized-images",
"weight": 0,
},
Object {
"group": "diagnostics",
"id": "large-javascript-libraries",
Expand Down
32 changes: 32 additions & 0 deletions lighthouse-cli/test/fixtures/byte-efficiency/tester.html
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,7 @@ <h2>Byte efficiency tester page</h2>
<!-- PASS(responsive): image is used at full size -->
<!-- PASS(responsive-inverse): image is not visible -->
<!-- FAIL(offscreen): image is offscreen -->
<!-- PASS(unsized-images): css position is absolute -->
<img style="position: absolute; top: -10000px;" src="lighthouse-unoptimized.jpg">

<!-- Image is cross-origin, which are disabled for now -->
Expand All @@ -90,41 +91,63 @@ <h2>Byte efficiency tester page</h2>
<!-- FAIL(responsive): image is 50% used at DPR 3 -->
<!-- PASS(responsive-inverse): image is big enough -->
<!-- PASS(offscreen): image is onscreen -->
<!-- PASS(unsized-images): has CSS sizing -->
<img style="width: 256px; height: 170px;" src="lighthouse-1024x680.jpg">

<!-- PASS(optimized): image is JPEG optimized -->
<!-- PASS(webp): image is WebP optimized -->
<!-- FAIL(responsive): image is 25% used at DPR 2.625 -->
<!-- PASS(responsive-inverse): image is big enough -->
<!-- PASS(offscreen): image is offscreen but lazily loaded -->
<!-- PASS(unsized-images): CSS sizing is valid with current checks -->
<img style="width: 0px; height: 0px;" src="lighthouse-2048x1356.webp?size0" loading="lazy">

<!-- PASS(optimized): image is JPEG optimized -->
<!-- FAIL(webp): image is not WebP optimized -->
<!-- FAIL(responsive): image is not fully used at DPR 2.625 -->
<!-- PASS(responsive-inverse): image is big enough -->
<!-- PASS(offscreen): image is onscreen -->
<!-- PASS(unsized-images): has CSS sizing -->
<img style="width: 160px; height: 110px;" src="lighthouse-480x320.jpg">

<!-- PASS(optimized): image is JPEG optimized -->
<!-- FAIL(webp): image is not WebP optimized -->
<!-- FAIL(responsive): image is not fully used at DPR 2.625 -->
<!-- PASS(responsive-inverse): image is big enough -->
<!-- PASS(offscreen): image is onscreen -->
<!-- PASS(unsized-images): has attribute sizing -->
<img width="160" height="110" src="lighthouse-480x320.jpg?attributesized">

<!-- PASS(optimized): image is JPEG optimized -->
<!-- PASS(webp): image has insigificant WebP savings -->
<!-- PASS(responsive): image is used at full size -->
<!-- FAIL(responsive-inverse): image does not account for DPR 2.625 -->
<!-- PASS(offscreen): image is onscreen -->
<!-- FAIL(unsized-images): no sizing information -->
<img src="lighthouse-320x212-poor.jpg">

<!-- PASS(optimized): image is JPEG optimized -->
<!-- PASS(webp): image has insigificant WebP savings -->
<!-- PASS(responsive): image is used at full size -->
<!-- FAIL(responsive-inverse): image does not account for DPR 2.625 -->
<!-- PASS(offscreen): image is onscreen -->
<!-- FAIL(unsized-images): invalid CSS sizing -->
<img style="width: auto; height: auto;" src="lighthouse-320x212-poor.jpg?cssauto">

<!-- PASS(optimized): image is JPEG optimized -->
<!-- PASS(webp): image is WebP savings -->
<!-- PASS(responsive): image is used at full size with srcset -->
<!-- PASS(responsive-inverse): image is big enough -->
<!-- PASS(offscreen): image is onscreen -->
<!-- PASS(unsized-images): css position is absolute -->
<img class="onscreen" sizes="(max-width: 2000px) 160px, 2048px" src="lighthouse-320x212-poor.jpg?srcset" srcset="lighthouse-480x320.webp?srcset 480w, lighthouse-2048x1356.webp?srcset 2048w">

<!-- PASS(optimized): image is JPEG optimized -->
<!-- PASS(webp): image is WebP savings -->
<!-- PASS(responsive): image is used at full size with <picture> -->
<!-- PASS(responsive-inverse): image is big enough -->
<!-- PASS(offscreen): image is onscreen -->
<!-- PASS(unsized-images): css position is absolute -->
<picture class="onscreen">
<source media="(max-width: 2000px)" srcset="lighthouse-480x320.webp?picture">
<source media="(min-width: 2001px)" srcset="lighthouse-2048x1356.webp?picture">
Expand All @@ -136,55 +159,63 @@ <h2>Byte efficiency tester page</h2>
<!-- FAIL(responsive): image is 50% used at DPR 2.625 (but small savings) -->
<!-- PASS(responsive-inverse): image is big enough -->
<!-- FAIL(offscreen): image is offscreen -->
<!-- PASS(unsized-images): has CSS sizing -->
<img style="margin-top: 4000px; width: 120px; height: 80px;" src="lighthouse-480x320.webp">

<!-- PASS(optimized): image is JPEG optimized -->
<!-- PASS(webp): image is WebP optimized -->
<!-- PASS(responsive): image is not visible -->
<!-- PASS(responsive-inverse): is not visible -->
<!-- FAIL(offscreen): image is not visible -->
<!-- PASS(unsized-images): has CSS sizing -->
<div class="onscreen" style="display: none;"><img class="onscreen" style="width: 120px; height: 80px;" src="lighthouse-480x320.webp?invisible"></div>

<!-- PASS(optimized): image is vector -->
<!-- PASS(webp): image is vector -->
<!-- PASS(responsive): image is vector -->
<!-- PASS(responsive-inverse): is vector -->
<!-- FAIL(offscreen): image is offscreen -->
<!-- PASS(unsized-images): has CSS sizing -->
<img style="width: 100px; height: 100px;" src="large.svg">

<!-- PASS(optimized): image is vector -->
<!-- PASS(webp): image is vector -->
<!-- PASS(responsive): image is vector -->
<!-- PASS(responsive-inverse): is vector -->
<!-- PASS(offscreen): image is offscreen and loads before TTI, but loads lazily -->
<!-- PASS(unsized-images): has CSS sizing -->
<img style="width: 100px; height: 100px;" src="large.svg?nativeLazyLoad" loading="lazy">

<!-- PASS(optimized): image is JPEG optimized -->
<!-- PASS(webp): image has insigificant WebP savings -->
<!-- PASS(responsive): image is later used at full size -->
<!-- PASS(responsive-inverse): image is big enough -->
<!-- PASS(offscreen): image is later used onscreen -->
<!-- PASS(unsized-images): has CSS sizing -->
<img style="width: 24px; height: 16px;"src="lighthouse-320x212-poor.jpg?duplicate">

<!-- PASS(optimized): image is JPEG optimized -->
<!-- PASS(webp): image has insigificant WebP savings -->
<!-- PASS(responsive): image is used at full size -->
<!-- FAIL(responsive-inverse): image does not account for DPR 2.625 -->
<!-- PASS(offscreen): image is onscreen -->
<!-- PASS(unsized-images): css position is absolute -->
<img class="onscreen" src="lighthouse-320x212-poor.jpg?duplicate">

<!-- PASS(optimized): image is JPEG optimized -->
<!-- FAIL(webp): image is not WebP optimized -->
<!-- PASS(responsive): image is in CSS -->
<!-- PASS(responsive-inverse): image is in CSS -->
<!-- PASS(offscreen): image is onscreen -->
<!-- PASS(unsized-images): CSS background images are ignored -->
<div class="onscreen" style="width: 120px; height: 80px; background: 50% 50% url(lighthouse-480x320.jpg?css);"></div>

<!-- PASS(optimized): image is JPEG optimized -->
<!-- FAIL(webp): image is not WebP optimized -->
<!-- PASS(responsive): image is in CSS -->
<!-- PASS(responsive-inverse): image is in CSS -->
<!-- PASS(offscreen): image is onscreen -->
<!-- PASS(unsized-images): CSS background images are ignored -->
<div class="onscreen" style="width: 30px; height: 30px; background: 0% 50% url(lighthouse-480x320.jpg?sprite);"></div>
<div class="onscreen" style="width: 30px; height: 30px; background: 25% 50% url(lighthouse-480x320.jpg?sprite);"></div>
<div class="onscreen" style="width: 30px; height: 30px; background: 50% 50% url(lighthouse-480x320.jpg?sprite);"></div>
Expand All @@ -196,6 +227,7 @@ <h2>Byte efficiency tester page</h2>
<!-- PASS(optimized): image is WebP -->
<!-- PASS(responsive): image is used at full size -->
<!-- PASS(offscreen): image is lazily loaded after TTI -->
<!-- PASS(unsized-images): css position is absolute -->
<img style="position: absolute; top: -5000px;" src="lighthouse-480x320.webp?lazilyLoaded=true">
</template>

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,10 +28,14 @@ const config = {
// image-size-responsive is not a byte-efficiency audit but a counterbalance to the byte-efficiency audits
// that makes sense to test together.
'image-size-responsive',
// unsized-images is not a byte-efficiency audit but can easily leverage the variety of images present in
// byte-efficiency tests & thus makes sense to test together.
'unsized-images',
],
throttlingMethod: 'devtools',
},
audits: [
'unsized-images',
{path: 'byte-efficiency/unused-javascript', options: {
// Lower the threshold so we don't need huge resources to make a test.
unusedThreshold: 2000,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -193,7 +193,7 @@ const expectations = [
details: {
overallSavingsBytes: '>60000',
items: {
length: 5,
length: 6,
},
},
},
Expand All @@ -220,23 +220,34 @@ const expectations = [
// Check that images aren't TOO BIG.
'uses-responsive-images': {
details: {
overallSavingsBytes: '108000 +/- 5000',
overallSavingsBytes: '113000 +/- 5000',
items: [
{wastedPercent: '56 +/- 5', url: /lighthouse-1024x680.jpg/},
{wastedPercent: '78 +/- 5', url: /lighthouse-2048x1356.webp\?size0/},
{wastedPercent: '56 +/- 5', url: /lighthouse-480x320.webp/},
{wastedPercent: '20 +/- 5', url: /lighthouse-480x320.jpg/},
{wastedPercent: '20 +/- 5', url: /lighthouse-480x320\.jpg\?attributesized/},
],
},
},
// Checks that images aren't TOO SMALL.
'image-size-responsive': {
details: {
items: [
// One of these two is the ?duplicate variant but sort order isn't guaranteed
// One of these is the ?duplicate variant and another is the
// ?cssauto variant but sort order isn't guaranteed
// since the pixel diff is equivalent for identical images.
{url: /lighthouse-320x212-poor.jpg/},
{url: /lighthouse-320x212-poor.jpg/},
{url: /lighthouse-320x212-poor.jpg/},
],
},
},
'unsized-images': {
details: {
items: [
{url: /lighthouse-320x212-poor\.jpg/},
{url: /lighthouse-320x212-poor\.jpg\?cssauto/},
],
},
},
Expand Down
27 changes: 15 additions & 12 deletions lighthouse-core/audits/unsized-images.js
Original file line number Diff line number Diff line change
Expand Up @@ -20,12 +20,12 @@ const UIStrings = {
/** Title of a Lighthouse audit that provides detail on whether all images have explicit width and height. This descriptive title is shown to users when one or more images does not have explicit width and height */
failureTitle: 'Image elements do not have explicit `width` and `height`',
/** Description of a Lighthouse audit that tells the user why they should include explicit width and height for all images. This is displayed after a user expands the section to see more. No character length limits. 'Learn More' becomes link text to additional documentation. */
description: 'Always include explicit width and height on image elements to reduce layout shifts and improve CLS. [Learn more](https://web.dev/optimize-cls/#images-without-dimensions)',
description: 'Set an explicit width and height on image elements to reduce layout shifts and improve CLS. [Learn more](https://web.dev/optimize-cls/#images-without-dimensions)',
};

const str_ = i18n.createMessageInstanceIdFn(__filename, UIStrings);

class SizedImages extends Audit {
class UnsizedImages extends Audit {
/**
* @return {LH.Audit.Meta}
*/
Expand Down Expand Up @@ -67,31 +67,34 @@ class SizedImages extends Audit {
* @param {LH.Artifacts.ImageElement} image
* @return {boolean}
*/
static isUnsizedImage(image) {
static isSizedImage(image) {
const attrWidth = image.attributeWidth;
const attrHeight = image.attributeHeight;
const cssWidth = image.cssWidth;
const cssHeight = image.cssHeight;
const widthIsValidAttribute = SizedImages.isValidAttr(attrWidth);
const widthIsValidCss = SizedImages.isValidCss(cssWidth);
const heightIsValidAttribute = SizedImages.isValidAttr(attrHeight);
const heightIsValidCss = SizedImages.isValidCss(cssHeight);
const widthIsValidAttribute = UnsizedImages.isValidAttr(attrWidth);
const widthIsValidCss = UnsizedImages.isValidCss(cssWidth);
const heightIsValidAttribute = UnsizedImages.isValidAttr(attrHeight);
const heightIsValidCss = UnsizedImages.isValidCss(cssHeight);
const validWidth = widthIsValidAttribute || widthIsValidCss;
const validHeight = heightIsValidAttribute || heightIsValidCss;
return !validWidth || !validHeight;
return validWidth && validHeight;
}

/**
* @param {LH.Artifacts} artifacts
* @return {Promise<LH.Audit.Product>}
*/
static async audit(artifacts) {
// CSS background-images are ignored for this audit.
const images = artifacts.ImageElements.filter(el => !el.isCss);
// CSS background-images & ShadowRoot images are ignored for this audit.
const images = artifacts.ImageElements.filter(el => !el.isCss && !el.isInShadowDOM);
const unsizedImages = [];

for (const image of images) {
if (!SizedImages.isUnsizedImage(image)) continue;
const isFixedImage =
image.cssComputedPosition === 'fixed' || image.cssComputedPosition === 'absolute';

if (isFixedImage || UnsizedImages.isSizedImage(image)) continue;
const url = URL.elideDataURI(image.src);
unsizedImages.push({
url,
Expand Down Expand Up @@ -120,5 +123,5 @@ class SizedImages extends Audit {
}
}

module.exports = SizedImages;
module.exports = UnsizedImages;
module.exports.UIStrings = UIStrings;
2 changes: 2 additions & 0 deletions lighthouse-core/config/default-config.js
Original file line number Diff line number Diff line change
Expand Up @@ -241,6 +241,7 @@ const defaultConfig = {
'long-tasks',
'no-unload-listeners',
'non-composited-animations',
'unsized-images',
'large-javascript-libraries',
'valid-source-maps',
'manual/pwa-cross-browser',
Expand Down Expand Up @@ -473,6 +474,7 @@ const defaultConfig = {
{id: 'no-document-write', weight: 0, group: 'diagnostics'},
{id: 'long-tasks', weight: 0, group: 'diagnostics'},
{id: 'non-composited-animations', weight: 0, group: 'diagnostics'},
{id: 'unsized-images', weight: 0, group: 'diagnostics'},
{id: 'large-javascript-libraries', weight: 0, group: 'diagnostics'},
// Audits past this point don't belong to a group and will not be shown automatically
{id: 'network-requests', weight: 0},
Expand Down
7 changes: 0 additions & 7 deletions lighthouse-core/config/experimental-config.js
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,6 @@ const config = {
audits: [
'autocomplete',
'full-page-screenshot',
'unsized-images',
],
passes: [{
passName: 'defaultPass',
Expand All @@ -32,12 +31,6 @@ const config = {
{id: 'autocomplete', weight: 0, group: 'best-practices-ux'},
],
},
// @ts-ignore same reason as above
'performance': {
auditRefs: [
{id: 'unsized-images', weight: 0, group: 'diagnostics'},
],
},
},
};

Expand Down
Loading

0 comments on commit 960d7c7

Please sign in to comment.