diff --git a/lighthouse-core/audits/byte-efficiency/uses-optimized-images.js b/lighthouse-core/audits/byte-efficiency/uses-optimized-images.js index 88c6c55af8bb..b72871fcb51d 100644 --- a/lighthouse-core/audits/byte-efficiency/uses-optimized-images.js +++ b/lighthouse-core/audits/byte-efficiency/uses-optimized-images.js @@ -54,6 +54,7 @@ class UsesOptimizedImages extends ByteEfficiencyAudit { * @return {number} */ static estimateJPEGSizeFromDimensions(imageElement) { + // @ts-ignore - TS warns that naturalWidth and naturalHeight can be undefined, checked on L100. const totalPixels = imageElement.naturalWidth * imageElement.naturalHeight; // Even JPEGs with lots of detail can usually be compressed down to <1 byte per pixel // Using 4:2:2 subsampling already gets an uncompressed bitmap to 2 bytes per pixel. @@ -96,6 +97,9 @@ class UsesOptimizedImages extends ByteEfficiencyAudit { continue; } + // If naturalHeight or naturalWidth are undefined, information is not valid, skip. + if (!imageElement.naturalHeight || !imageElement.naturalWidth) continue; + jpegSize = UsesOptimizedImages.estimateJPEGSizeFromDimensions(imageElement); fromProtocol = false; } diff --git a/lighthouse-core/audits/byte-efficiency/uses-responsive-images.js b/lighthouse-core/audits/byte-efficiency/uses-responsive-images.js index 64047edf386e..891688db807f 100644 --- a/lighthouse-core/audits/byte-efficiency/uses-responsive-images.js +++ b/lighthouse-core/audits/byte-efficiency/uses-responsive-images.js @@ -64,6 +64,7 @@ class UsesResponsiveImages extends ByteEfficiencyAudit { if (!usedPixels) { const viewportWidth = ViewportDimensions.innerWidth; const viewportHeight = ViewportDimensions.innerHeight * 2; + // @ts-ignore TS warns that naturalWidth and naturalHeight can be undefined, checked on L123. const imageAspectRatio = image.naturalWidth / image.naturalHeight; const viewportAspectRatio = viewportWidth / viewportHeight; let usedViewportWidth = viewportWidth; @@ -79,6 +80,7 @@ class UsesResponsiveImages extends ByteEfficiencyAudit { } const url = URL.elideDataURI(image.src); + // @ts-ignore TS warns that naturalWidth and naturalHeight can be undefined, checked on L123. const actualPixels = image.naturalWidth * image.naturalHeight; const wastedRatio = 1 - (usedPixels / actualPixels); const totalBytes = image.resourceSize; @@ -117,6 +119,9 @@ class UsesResponsiveImages extends ByteEfficiencyAudit { continue; } + // If naturalHeight or naturalWidth are undefined, information is not valid, skip. + if (!image.naturalWidth || !image.naturalHeight) continue; + const processed = UsesResponsiveImages.computeWaste(image, ViewportDimensions); if (!processed) continue; diff --git a/lighthouse-core/audits/byte-efficiency/uses-webp-images.js b/lighthouse-core/audits/byte-efficiency/uses-webp-images.js index 7d01414b3b2b..93ed5317b549 100644 --- a/lighthouse-core/audits/byte-efficiency/uses-webp-images.js +++ b/lighthouse-core/audits/byte-efficiency/uses-webp-images.js @@ -54,6 +54,7 @@ class UsesWebPImages extends ByteEfficiencyAudit { * @return {number} */ static estimateWebPSizeFromDimensions(imageElement) { + // @ts-ignore - TS warns that naturalWidth and naturalHeight can be undefined, checked on L100. const totalPixels = imageElement.naturalWidth * imageElement.naturalHeight; // See uses-optimized-images for the rationale behind our 2 byte-per-pixel baseline and // JPEG compression ratio of 8:1. @@ -95,6 +96,9 @@ class UsesWebPImages extends ByteEfficiencyAudit { continue; } + // If naturalHeight or naturalWidth are undefined, information is not valid, skip. + if (!imageElement.naturalWidth || !imageElement.naturalHeight) continue; + webpSize = UsesWebPImages.estimateWebPSizeFromDimensions(imageElement); fromProtocol = false; } diff --git a/lighthouse-core/audits/image-aspect-ratio.js b/lighthouse-core/audits/image-aspect-ratio.js index 0b658cc2fc3a..2d4f3b378327 100644 --- a/lighthouse-core/audits/image-aspect-ratio.js +++ b/lighthouse-core/audits/image-aspect-ratio.js @@ -98,11 +98,12 @@ class ImageAspectRatio extends Audit { // - filter out images that don't have following properties: // networkRecord, width, height, images that use `object-fit`: `cover` or `contain` // - filter all svgs as they have no natural dimensions to audit + // - filter out images that have undefined naturalWidth or naturalHeight return !image.isCss && image.mimeType && image.mimeType !== 'image/svg+xml' && - image.naturalHeight > 5 && - image.naturalWidth > 5 && + image.naturalHeight && image.naturalHeight > 5 && + image.naturalWidth && image.naturalWidth > 5 && image.displayedWidth && image.displayedHeight && !image.usesObjectFit; diff --git a/lighthouse-core/audits/image-size-responsive.js b/lighthouse-core/audits/image-size-responsive.js index 51a5b182a8ba..4c47091f7f4b 100644 --- a/lighthouse-core/audits/image-size-responsive.js +++ b/lighthouse-core/audits/image-size-responsive.js @@ -68,7 +68,7 @@ function isCandidate(image) { if (image.displayedWidth <= 1 || image.displayedHeight <= 1) { return false; } - if (image.naturalWidth === 0 || image.naturalHeight === 0) { + if (!image.naturalWidth || !image.naturalHeight) { return false; } if (image.mimeType === 'image/svg+xml') { @@ -97,6 +97,7 @@ function isCandidate(image) { function imageHasRightSize(image, DPR) { const [expectedWidth, expectedHeight] = allowedImageSize(image.displayedWidth, image.displayedHeight, DPR); + // @ts-ignore - TS warns that naturalWidth and naturalHeight can be undefined, checked in L220. return image.naturalWidth >= expectedWidth && image.naturalHeight >= expectedHeight; } @@ -113,6 +114,7 @@ function getResult(image, DPR) { elidedUrl: URL.elideDataURI(image.src), displayedSize: `${image.displayedWidth} x ${image.displayedHeight}`, actualSize: `${image.naturalWidth} x ${image.naturalHeight}`, + // @ts-ignore - TS warns that naturalWidth and naturalHeight can be undefined, checked in L220. actualPixels: image.naturalWidth * image.naturalHeight, expectedSize: `${expectedWidth} x ${expectedHeight}`, expectedPixels: expectedWidth * expectedHeight, diff --git a/lighthouse-core/gather/gatherers/image-elements.js b/lighthouse-core/gather/gatherers/image-elements.js index a6f7ca0f5a4f..6c52457c111e 100644 --- a/lighthouse-core/gather/gatherers/image-elements.js +++ b/lighthouse-core/gather/gatherers/image-elements.js @@ -67,8 +67,8 @@ function getHTMLImages(allElements) { displayedWidth: element.width, displayedHeight: element.height, clientRect: getClientRect(element), - naturalWidth: canTrustNaturalDimensions ? element.naturalWidth : 0, - naturalHeight: canTrustNaturalDimensions ? element.naturalHeight : 0, + naturalWidth: canTrustNaturalDimensions ? element.naturalWidth : undefined, + naturalHeight: canTrustNaturalDimensions ? element.naturalHeight : undefined, attributeWidth: element.getAttribute('width') || '', attributeHeight: element.getAttribute('height') || '', cssWidth: undefined, // this will get overwritten below @@ -122,9 +122,6 @@ function getCSSImages(allElements) { displayedWidth: element.clientWidth, displayedHeight: element.clientHeight, clientRect: getClientRect(element), - // CSS Images do not expose natural size, we'll determine the size later - naturalWidth: 0, - naturalHeight: 0, attributeWidth: '', attributeHeight: '', cssWidth: undefined, diff --git a/lighthouse-core/test/audits/byte-efficiency/uses-responsive-images-test.js b/lighthouse-core/test/audits/byte-efficiency/uses-responsive-images-test.js index 8bfd8c5fdcd1..1128b0609af8 100644 --- a/lighthouse-core/test/audits/byte-efficiency/uses-responsive-images-test.js +++ b/lighthouse-core/test/audits/byte-efficiency/uses-responsive-images-test.js @@ -196,7 +196,8 @@ describe('Page uses responsive images', () => { }); assert.equal(auditResult.items.length, 0); - assert.equal(auditResult.warnings.length, 1); + // Images with non valid naturalWidth or naturalHeight are ignored. + assert.equal(auditResult.warnings.length, 0); }); it('de-dupes images', () => { diff --git a/types/artifacts.d.ts b/types/artifacts.d.ts index cf5e71198bed..60f1037427cf 100644 --- a/types/artifacts.d.ts +++ b/types/artifacts.d.ts @@ -405,9 +405,9 @@ declare global { /** The displayed height of the image, uses img.height when available falling back to clientHeight. See https://codepen.io/patrickhulce/pen/PXvQbM for examples. */ displayedHeight: number; /** The natural width of the underlying image, uses img.naturalWidth. See https://codepen.io/patrickhulce/pen/PXvQbM for examples. */ - naturalWidth: number; + naturalWidth?: number; /** The natural height of the underlying image, uses img.naturalHeight. See https://codepen.io/patrickhulce/pen/PXvQbM for examples. */ - naturalHeight: number; + naturalHeight?: number; /** The raw width attribute of the image element. CSS images will be set to the empty string. */ attributeWidth: string; /** The raw height attribute of the image element. CSS images will be set to the empty string. */