From c00a5794bbef2eed2c52ec725138b1a3fcf01630 Mon Sep 17 00:00:00 2001 From: Lemuel Cardenas-arriaga Date: Fri, 26 Jun 2020 14:36:31 -0700 Subject: [PATCH 01/68] Added new docTypeError fxn and LHError --- lighthouse-core/gather/gather-runner.js | 33 +++++++++++++++++++++ lighthouse-core/lib/i18n/locales/en-US.json | 3 ++ lighthouse-core/lib/i18n/locales/en-XL.json | 3 ++ lighthouse-core/lib/lh-error.js | 7 +++++ 4 files changed, 46 insertions(+) diff --git a/lighthouse-core/gather/gather-runner.js b/lighthouse-core/gather/gather-runner.js index ce70e4b87047..deb4c919455a 100644 --- a/lighthouse-core/gather/gather-runner.js +++ b/lighthouse-core/gather/gather-runner.js @@ -215,6 +215,31 @@ class GatherRunner { return new LHError(LHError.errors.CHROME_INTERSTITIAL_ERROR); } + /** + * Returns an error if we try to load a non-HTML page. + * @param {LH.Artifacts.NetworkRequest|undefined} mainRecord + * @return {LH.LighthouseError|undefined} + */ + static getDocTypeError(mainRecord) { + // MIME types are case-insenstive + const HTML_MIME_REGEX = /^text\/html$/i; + + // If we never requested a document, there's no doctype error, let other cases handle it. + if (!mainRecord) return undefined; + + // If the main document failed, this error case is undefined, let other cases handle it. + if (mainRecord.failed) return undefined; + + // mimeType is determined by the browser, we assume Chrome is determining mimeType correctly, + // independently of 'Content-Type' response headers, and always sending mimeType if well-formed. + if (mainRecord.mimeType) { + if (!HTML_MIME_REGEX.test(mainRecord.mimeType)) { + return new LHError(LHError.errors.INVALID_DOC_TYPE); + } + } + return undefined; + } + /** * Returns an error if the page load should be considered failed, e.g. from a * main document request failure, a security issue, etc. @@ -231,8 +256,13 @@ class GatherRunner { mainRecord = NetworkAnalyzer.findMainDocument(networkRecords, passContext.url); } catch (_) {} + console.log('mainRecord'); + console.log(mainRecord); const networkError = GatherRunner.getNetworkError(mainRecord); const interstitialError = GatherRunner.getInterstitialError(mainRecord, networkRecords); + const docTypeError = GatherRunner.getDocTypeError(mainRecord); + console.log('docTypeError'); + console.log(docTypeError); // Check to see if we need to ignore the page load failure. // e.g. When the driver is offline, the load will fail without page offline support. @@ -246,6 +276,9 @@ class GatherRunner { // Example: `DNS_FAILURE` is better than `NO_FCP`. if (networkError) return networkError; + // We want to error when the page is not of MIME type text/html + if (docTypeError) return docTypeError; + // Navigation errors are rather generic and express some failure of the page to render properly. // Use `navigationError` as the last resort. // Example: `NO_FCP`, the page never painted content for some unknown reason. diff --git a/lighthouse-core/lib/i18n/locales/en-US.json b/lighthouse-core/lib/i18n/locales/en-US.json index c7364f13cc5b..c22538fbdaf2 100644 --- a/lighthouse-core/lib/i18n/locales/en-US.json +++ b/lighthouse-core/lib/i18n/locales/en-US.json @@ -1592,6 +1592,9 @@ "lighthouse-core/lib/lh-error.js | dnsFailure": { "message": "DNS servers could not resolve the provided domain." }, + "lighthouse-core/lib/lh-error.js | docTypeInvalid": { + "message": "The webpage you have provided appears to be non-HTML" + }, "lighthouse-core/lib/lh-error.js | erroredRequiredArtifact": { "message": "Required {artifactName} gatherer encountered an error: {errorMessage}" }, diff --git a/lighthouse-core/lib/i18n/locales/en-XL.json b/lighthouse-core/lib/i18n/locales/en-XL.json index 7d579c7a108a..0ae629d7a6a7 100644 --- a/lighthouse-core/lib/i18n/locales/en-XL.json +++ b/lighthouse-core/lib/i18n/locales/en-XL.json @@ -1592,6 +1592,9 @@ "lighthouse-core/lib/lh-error.js | dnsFailure": { "message": "D̂ŃŜ śêŕv̂ér̂ś ĉóûĺd̂ ńôt́ r̂éŝól̂v́ê t́ĥé p̂ŕôv́îd́êd́ d̂óm̂áîń." }, + "lighthouse-core/lib/lh-error.js | docTypeInvalid": { + "message": "T̂h́ê ẃêb́p̂áĝé ŷóû h́âv́ê ṕr̂óv̂íd̂éd̂ áp̂ṕêár̂ś t̂ó b̂é n̂ón̂-H́T̂ḾL̂" + }, "lighthouse-core/lib/lh-error.js | erroredRequiredArtifact": { "message": "R̂éq̂úîŕêd́ {artifactName} ĝát̂h́êŕêŕ êńĉóûńt̂ér̂éd̂ án̂ ér̂ŕôŕ: {errorMessage}" }, diff --git a/lighthouse-core/lib/lh-error.js b/lighthouse-core/lib/lh-error.js index a1015433b3a4..7e58ae7e3994 100644 --- a/lighthouse-core/lib/lh-error.js +++ b/lighthouse-core/lib/lh-error.js @@ -47,6 +47,8 @@ const UIStrings = { internalChromeError: 'An internal Chrome error occurred. Please restart Chrome and try re-running Lighthouse.', /** Error message explaining that fetching the resources of the webpage has taken longer than the maximum time. */ requestContentTimeout: 'Fetching resource content has exceeded the allotted time', + /** Error message explaining that the webpage is non-HTML, so audits are ill-defined **/ + docTypeInvalid: 'The webpage you have provided appears to be non-HTML', /** Error message explaining that the provided URL Lighthouse points to is not valid, and cannot be loaded. */ urlInvalid: 'The URL you have provided appears to be invalid.', /** @@ -312,6 +314,11 @@ const ERRORS = { message: UIStrings.pageLoadFailedHung, lhrRuntimeError: true, }, + /* Used when the page is non-HTML. */ + INVALID_DOC_TYPE: { + code: 'INVALID_DOC_TYPE', + message: UIStrings.docTypeInvalid, + }, // Protocol internal failures TRACING_ALREADY_STARTED: { From 09682f41a34ac0a32a35638bbecef71c0d2d6a3b Mon Sep 17 00:00:00 2001 From: Lemuel Cardenas-arriaga Date: Fri, 26 Jun 2020 15:22:56 -0700 Subject: [PATCH 02/68] starting testing code --- lighthouse-core/test/gather/gather-runner-test.js | 1 + 1 file changed, 1 insertion(+) diff --git a/lighthouse-core/test/gather/gather-runner-test.js b/lighthouse-core/test/gather/gather-runner-test.js index d8f42c70c1c4..97e615ad8b66 100644 --- a/lighthouse-core/test/gather/gather-runner-test.js +++ b/lighthouse-core/test/gather/gather-runner-test.js @@ -38,6 +38,7 @@ const GatherRunner = { getInstallabilityErrors: makeParamsOptional(GatherRunner_.getInstallabilityErrors), getInterstitialError: makeParamsOptional(GatherRunner_.getInterstitialError), getNetworkError: makeParamsOptional(GatherRunner_.getNetworkError), + getDocTypeError: makeParamsOptional(GatherRunner_.getDocTypeError), getPageLoadError: makeParamsOptional(GatherRunner_.getPageLoadError), getWebAppManifest: makeParamsOptional(GatherRunner_.getWebAppManifest), initializeBaseArtifacts: makeParamsOptional(GatherRunner_.initializeBaseArtifacts), From eec825dfbb4d726bdb1373be78523663f09c1378 Mon Sep 17 00:00:00 2001 From: Lemuel Cardenas-arriaga Date: Mon, 29 Jun 2020 11:50:37 -0700 Subject: [PATCH 03/68] starting tests --- lighthouse-core/test/gather/gather-runner-test.js | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/lighthouse-core/test/gather/gather-runner-test.js b/lighthouse-core/test/gather/gather-runner-test.js index 97e615ad8b66..b4ac75f361de 100644 --- a/lighthouse-core/test/gather/gather-runner-test.js +++ b/lighthouse-core/test/gather/gather-runner-test.js @@ -1096,6 +1096,18 @@ describe('GatherRunner', function() { }); }); + describe('#getDocTypeError', () => { + /** + * @param {NetworkRequest} mainRecord + */ + function getAndExpectError(mainRecord) { + const error = GatherRunner.getDocTypeError(mainRecord); + if (!error) throw new Error('expected a docType error'); + return error; + } + }); + + describe('#getPageLoadError', () => { /** * @param {RecursivePartial} passContext From a63f81372652bb2e0d3ff88315d5bdd2c5700910 Mon Sep 17 00:00:00 2001 From: Lemuel Cardenas-arriaga Date: Mon, 29 Jun 2020 17:22:55 -0700 Subject: [PATCH 04/68] finished unit tests for getDocTypeError --- .../test/gather/gather-runner-test.js | 34 +++++++++++++++++++ 1 file changed, 34 insertions(+) diff --git a/lighthouse-core/test/gather/gather-runner-test.js b/lighthouse-core/test/gather/gather-runner-test.js index b4ac75f361de..959714ee9b56 100644 --- a/lighthouse-core/test/gather/gather-runner-test.js +++ b/lighthouse-core/test/gather/gather-runner-test.js @@ -1105,6 +1105,40 @@ describe('GatherRunner', function() { if (!error) throw new Error('expected a docType error'); return error; } + + it('passes when the page was not requested', () => { + expect(GatherRunner.getDocTypeError(undefined)).toBeUndefined(); + }); + + it('passes when page fails to load normally', () => { + const url = 'http://the-page.com'; + const mainRecord = new NetworkRequest(); + mainRecord.url = url; + mainRecord.failed = true; + mainRecord.localizedFailDescription = 'foobar'; + expect(GatherRunner.getDocTypeError(mainRecord)).toBeUndefined(); + }); + + it('passes when the page is of MIME type text/html', () => { + const url = 'http://the-page.com'; + const mainRecord = new NetworkRequest(); + const mimeType = 'text/html'; + mainRecord.url = url; + mainRecord.mimeType = mimeType; + expect(GatherRunner.getDocTypeError(mainRecord)).toBeUndefined(); + }); + + it('fails when the page is not of MIME type text/html', () => { + const url = 'http://the-page.com'; + const mimeType = 'application/xml'; + const mainRecord = new NetworkRequest(); + mainRecord.url = url; + mainRecord.mimeType = mimeType; + const error = getAndExpectError(mainRecord); + expect(error.message).toEqual('INVALID_DOC_TYPE'); + expect(error.code).toEqual('INVALID_DOC_TYPE'); + expect(error.friendlyMessage).toBeDisplayString(/appears to be non-HTML/); + }); }); From 40bf826f812ccf4bc229a3a8e8c41c619e407095 Mon Sep 17 00:00:00 2001 From: Lemuel Cardenas-arriaga Date: Tue, 30 Jun 2020 09:19:18 -0700 Subject: [PATCH 05/68] bugfixing --- lighthouse-core/gather/gather-runner.js | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/lighthouse-core/gather/gather-runner.js b/lighthouse-core/gather/gather-runner.js index deb4c919455a..b28c05c93d1f 100644 --- a/lighthouse-core/gather/gather-runner.js +++ b/lighthouse-core/gather/gather-runner.js @@ -232,7 +232,7 @@ class GatherRunner { // mimeType is determined by the browser, we assume Chrome is determining mimeType correctly, // independently of 'Content-Type' response headers, and always sending mimeType if well-formed. - if (mainRecord.mimeType) { + if (mainRecord.mimeType || mainRecord.mimeType === '') { if (!HTML_MIME_REGEX.test(mainRecord.mimeType)) { return new LHError(LHError.errors.INVALID_DOC_TYPE); } @@ -256,13 +256,15 @@ class GatherRunner { mainRecord = NetworkAnalyzer.findMainDocument(networkRecords, passContext.url); } catch (_) {} - console.log('mainRecord'); - console.log(mainRecord); + //console.log('mainRecord'); + //if (mainRecord) { + // console.log(mainRecord.mimeType); + //console.log(mainRecord.mimeType === ''); + //} const networkError = GatherRunner.getNetworkError(mainRecord); const interstitialError = GatherRunner.getInterstitialError(mainRecord, networkRecords); const docTypeError = GatherRunner.getDocTypeError(mainRecord); - console.log('docTypeError'); - console.log(docTypeError); + //console.log(docTypeError); // Check to see if we need to ignore the page load failure. // e.g. When the driver is offline, the load will fail without page offline support. From 28cad19ef7841e186de8d2600a3b18231f4e1add Mon Sep 17 00:00:00 2001 From: Lemuel Cardenas-arriaga Date: Tue, 30 Jun 2020 14:50:49 -0700 Subject: [PATCH 06/68] integrated getDocTypeError into getPageLoadError --- .../test/gather/gather-runner-test.js | 30 +++++++++++++++++-- 1 file changed, 27 insertions(+), 3 deletions(-) diff --git a/lighthouse-core/test/gather/gather-runner-test.js b/lighthouse-core/test/gather/gather-runner-test.js index 959714ee9b56..36330ec30b25 100644 --- a/lighthouse-core/test/gather/gather-runner-test.js +++ b/lighthouse-core/test/gather/gather-runner-test.js @@ -1161,26 +1161,30 @@ describe('GatherRunner', function() { navigationError = /** @type {LH.LighthouseError} */ (new Error('NAVIGATION_ERROR')); }); - it('passes when the page is loaded', () => { + it('passes when the page is loaded and doc type is text/html', () => { const passContext = { url: 'http://the-page.com', passConfig: {loadFailureMode: LoadFailureMode.fatal}, }; const mainRecord = new NetworkRequest(); const loadData = {networkRecords: [mainRecord]}; + const mimeType = 'text/html'; mainRecord.url = passContext.url; + mainRecord.mimeType = mimeType; const error = GatherRunner.getPageLoadError(passContext, loadData, undefined); expect(error).toBeUndefined(); }); - it('passes when the page is loaded, ignoring any fragment', () => { + it('passes when the page is loaded and doc type is text/html, ignoring any fragment', () => { const passContext = { url: 'http://example.com/#/page/list', passConfig: {loadFailureMode: LoadFailureMode.fatal}, }; const mainRecord = new NetworkRequest(); const loadData = {networkRecords: [mainRecord]}; + const mimeType = 'text/html'; mainRecord.url = 'http://example.com'; + mainRecord.mimeType = mimeType; const error = GatherRunner.getPageLoadError(passContext, loadData, undefined); expect(error).toBeUndefined(); }); @@ -1217,7 +1221,7 @@ describe('GatherRunner', function() { expect(error.message).toEqual('CHROME_INTERSTITIAL_ERROR'); }); - it('fails with network error next', () => { + it('fails with network error second', () => { const passContext = { url: 'http://the-page.com', passConfig: {loadFailureMode: LoadFailureMode.fatal}, @@ -1232,6 +1236,22 @@ describe('GatherRunner', function() { expect(error.message).toEqual('FAILED_DOCUMENT_REQUEST'); }); + it('fails with doc type error third', () => { + const passContext = { + url: 'http://the-page.com', + passConfig: {loadFailureMode: LoadFailureMode.fatal}, + }; + const mainRecord = new NetworkRequest(); + const loadData = {networkRecords: [mainRecord]}; + + const mimeType = 'application/xml'; + mainRecord.url = passContext.url; + mainRecord.mimeType = mimeType; + + const error = getAndExpectError(passContext, loadData, navigationError); + expect(error.message).toEqual('INVALID_DOC_TYPE'); + }); + it('fails with nav error last', () => { const passContext = { url: 'http://the-page.com', @@ -1240,7 +1260,9 @@ describe('GatherRunner', function() { const mainRecord = new NetworkRequest(); const loadData = {networkRecords: [mainRecord]}; + const mimeType = 'text/html'; mainRecord.url = passContext.url; + mainRecord.mimeType = mimeType; const error = getAndExpectError(passContext, loadData, navigationError); expect(error.message).toEqual('NAVIGATION_ERROR'); @@ -1254,7 +1276,9 @@ describe('GatherRunner', function() { const mainRecord = new NetworkRequest(); const loadData = {networkRecords: [mainRecord]}; + const mimeType = 'text/html'; mainRecord.url = passContext.url; + mainRecord.mimeType = mimeType; const error = getAndExpectError(passContext, loadData, navigationError); expect(error.message).toEqual('NAVIGATION_ERROR'); From 6ac8d69c97a3fd28ec28ff40858bb23702b03e56 Mon Sep 17 00:00:00 2001 From: Lemuel Cardenas-arriaga Date: Tue, 30 Jun 2020 14:52:23 -0700 Subject: [PATCH 07/68] removed console logging from debugging --- lighthouse-core/gather/gather-runner.js | 6 ------ 1 file changed, 6 deletions(-) diff --git a/lighthouse-core/gather/gather-runner.js b/lighthouse-core/gather/gather-runner.js index b28c05c93d1f..5449af58efe0 100644 --- a/lighthouse-core/gather/gather-runner.js +++ b/lighthouse-core/gather/gather-runner.js @@ -256,15 +256,9 @@ class GatherRunner { mainRecord = NetworkAnalyzer.findMainDocument(networkRecords, passContext.url); } catch (_) {} - //console.log('mainRecord'); - //if (mainRecord) { - // console.log(mainRecord.mimeType); - //console.log(mainRecord.mimeType === ''); - //} const networkError = GatherRunner.getNetworkError(mainRecord); const interstitialError = GatherRunner.getInterstitialError(mainRecord, networkRecords); const docTypeError = GatherRunner.getDocTypeError(mainRecord); - //console.log(docTypeError); // Check to see if we need to ignore the page load failure. // e.g. When the driver is offline, the load will fail without page offline support. From fbc63807349f10aa5c0694387da8ba14b3845ced Mon Sep 17 00:00:00 2001 From: Lemuel Cardenas-arriaga Date: Wed, 1 Jul 2020 09:52:11 -0700 Subject: [PATCH 08/68] changes from code review --- lighthouse-core/gather/gather-runner.js | 2 +- lighthouse-core/lib/i18n/locales/en-US.json | 2 +- lighthouse-core/lib/i18n/locales/en-XL.json | 2 +- lighthouse-core/lib/lh-error.js | 2 +- .../test/gather/gather-runner-test.js | 19 +++++++------------ 5 files changed, 11 insertions(+), 16 deletions(-) diff --git a/lighthouse-core/gather/gather-runner.js b/lighthouse-core/gather/gather-runner.js index 5449af58efe0..0ede58d8546e 100644 --- a/lighthouse-core/gather/gather-runner.js +++ b/lighthouse-core/gather/gather-runner.js @@ -272,7 +272,7 @@ class GatherRunner { // Example: `DNS_FAILURE` is better than `NO_FCP`. if (networkError) return networkError; - // We want to error when the page is not of MIME type text/html + // Error if page is not HTML. if (docTypeError) return docTypeError; // Navigation errors are rather generic and express some failure of the page to render properly. diff --git a/lighthouse-core/lib/i18n/locales/en-US.json b/lighthouse-core/lib/i18n/locales/en-US.json index c22538fbdaf2..0f1856d2465c 100644 --- a/lighthouse-core/lib/i18n/locales/en-US.json +++ b/lighthouse-core/lib/i18n/locales/en-US.json @@ -1593,7 +1593,7 @@ "message": "DNS servers could not resolve the provided domain." }, "lighthouse-core/lib/lh-error.js | docTypeInvalid": { - "message": "The webpage you have provided appears to be non-HTML" + "message": "The page provided is not HTML" }, "lighthouse-core/lib/lh-error.js | erroredRequiredArtifact": { "message": "Required {artifactName} gatherer encountered an error: {errorMessage}" diff --git a/lighthouse-core/lib/i18n/locales/en-XL.json b/lighthouse-core/lib/i18n/locales/en-XL.json index 0ae629d7a6a7..62475ee16f64 100644 --- a/lighthouse-core/lib/i18n/locales/en-XL.json +++ b/lighthouse-core/lib/i18n/locales/en-XL.json @@ -1593,7 +1593,7 @@ "message": "D̂ŃŜ śêŕv̂ér̂ś ĉóûĺd̂ ńôt́ r̂éŝól̂v́ê t́ĥé p̂ŕôv́îd́êd́ d̂óm̂áîń." }, "lighthouse-core/lib/lh-error.js | docTypeInvalid": { - "message": "T̂h́ê ẃêb́p̂áĝé ŷóû h́âv́ê ṕr̂óv̂íd̂éd̂ áp̂ṕêár̂ś t̂ó b̂é n̂ón̂-H́T̂ḾL̂" + "message": "T̂h́ê ṕâǵê ṕr̂óv̂íd̂éd̂ íŝ ńôt́ ĤT́M̂Ĺ" }, "lighthouse-core/lib/lh-error.js | erroredRequiredArtifact": { "message": "R̂éq̂úîŕêd́ {artifactName} ĝát̂h́êŕêŕ êńĉóûńt̂ér̂éd̂ án̂ ér̂ŕôŕ: {errorMessage}" diff --git a/lighthouse-core/lib/lh-error.js b/lighthouse-core/lib/lh-error.js index 7e58ae7e3994..8385a408ecea 100644 --- a/lighthouse-core/lib/lh-error.js +++ b/lighthouse-core/lib/lh-error.js @@ -48,7 +48,7 @@ const UIStrings = { /** Error message explaining that fetching the resources of the webpage has taken longer than the maximum time. */ requestContentTimeout: 'Fetching resource content has exceeded the allotted time', /** Error message explaining that the webpage is non-HTML, so audits are ill-defined **/ - docTypeInvalid: 'The webpage you have provided appears to be non-HTML', + docTypeInvalid: 'The page provided is not HTML', /** Error message explaining that the provided URL Lighthouse points to is not valid, and cannot be loaded. */ urlInvalid: 'The URL you have provided appears to be invalid.', /** diff --git a/lighthouse-core/test/gather/gather-runner-test.js b/lighthouse-core/test/gather/gather-runner-test.js index 36330ec30b25..5ac6496391ba 100644 --- a/lighthouse-core/test/gather/gather-runner-test.js +++ b/lighthouse-core/test/gather/gather-runner-test.js @@ -1161,30 +1161,28 @@ describe('GatherRunner', function() { navigationError = /** @type {LH.LighthouseError} */ (new Error('NAVIGATION_ERROR')); }); - it('passes when the page is loaded and doc type is text/html', () => { + it('passes when the page is loaded', () => { const passContext = { url: 'http://the-page.com', passConfig: {loadFailureMode: LoadFailureMode.fatal}, }; const mainRecord = new NetworkRequest(); const loadData = {networkRecords: [mainRecord]}; - const mimeType = 'text/html'; mainRecord.url = passContext.url; - mainRecord.mimeType = mimeType; + mainRecord.mimeType = 'text/html'; const error = GatherRunner.getPageLoadError(passContext, loadData, undefined); expect(error).toBeUndefined(); }); - it('passes when the page is loaded and doc type is text/html, ignoring any fragment', () => { + it('passes when the page is loaded, ignoring any fragment', () => { const passContext = { url: 'http://example.com/#/page/list', passConfig: {loadFailureMode: LoadFailureMode.fatal}, }; const mainRecord = new NetworkRequest(); const loadData = {networkRecords: [mainRecord]}; - const mimeType = 'text/html'; mainRecord.url = 'http://example.com'; - mainRecord.mimeType = mimeType; + mainRecord.mimeType = 'text/html'; const error = GatherRunner.getPageLoadError(passContext, loadData, undefined); expect(error).toBeUndefined(); }); @@ -1244,9 +1242,8 @@ describe('GatherRunner', function() { const mainRecord = new NetworkRequest(); const loadData = {networkRecords: [mainRecord]}; - const mimeType = 'application/xml'; mainRecord.url = passContext.url; - mainRecord.mimeType = mimeType; + mainRecord.mimeType = 'application/xml'; const error = getAndExpectError(passContext, loadData, navigationError); expect(error.message).toEqual('INVALID_DOC_TYPE'); @@ -1260,9 +1257,8 @@ describe('GatherRunner', function() { const mainRecord = new NetworkRequest(); const loadData = {networkRecords: [mainRecord]}; - const mimeType = 'text/html'; mainRecord.url = passContext.url; - mainRecord.mimeType = mimeType; + mainRecord.mimeType = 'text/html'; const error = getAndExpectError(passContext, loadData, navigationError); expect(error.message).toEqual('NAVIGATION_ERROR'); @@ -1276,9 +1272,8 @@ describe('GatherRunner', function() { const mainRecord = new NetworkRequest(); const loadData = {networkRecords: [mainRecord]}; - const mimeType = 'text/html'; mainRecord.url = passContext.url; - mainRecord.mimeType = mimeType; + mainRecord.mimeType = 'text/html'; const error = getAndExpectError(passContext, loadData, navigationError); expect(error.message).toEqual('NAVIGATION_ERROR'); From ba1deddf881f7998382e06c18f5ed0e1899d0322 Mon Sep 17 00:00:00 2001 From: Lemuel Cardenas-arriaga Date: Wed, 1 Jul 2020 09:57:16 -0700 Subject: [PATCH 09/68] more code review changes --- lighthouse-core/test/gather/gather-runner-test.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lighthouse-core/test/gather/gather-runner-test.js b/lighthouse-core/test/gather/gather-runner-test.js index 5ac6496391ba..ff8754c1c5f8 100644 --- a/lighthouse-core/test/gather/gather-runner-test.js +++ b/lighthouse-core/test/gather/gather-runner-test.js @@ -1137,7 +1137,7 @@ describe('GatherRunner', function() { const error = getAndExpectError(mainRecord); expect(error.message).toEqual('INVALID_DOC_TYPE'); expect(error.code).toEqual('INVALID_DOC_TYPE'); - expect(error.friendlyMessage).toBeDisplayString(/appears to be non-HTML/); + expect(error.friendlyMessage).toBeDisplayString(/The page provided is not HTML/); }); }); From 85ac35a0d8db705259d8a04cf3149c97f46c63c0 Mon Sep 17 00:00:00 2001 From: Lemuel Cardenas-arriaga Date: Wed, 1 Jul 2020 17:35:59 -0700 Subject: [PATCH 10/68] committed new strings for testing --- lighthouse-core/lib/i18n/locales/en-US.json | 6 +++--- lighthouse-core/lib/i18n/locales/en-XL.json | 6 +++--- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/lighthouse-core/lib/i18n/locales/en-US.json b/lighthouse-core/lib/i18n/locales/en-US.json index 0f1856d2465c..1a62d12f60bf 100644 --- a/lighthouse-core/lib/i18n/locales/en-US.json +++ b/lighthouse-core/lib/i18n/locales/en-US.json @@ -1592,9 +1592,6 @@ "lighthouse-core/lib/lh-error.js | dnsFailure": { "message": "DNS servers could not resolve the provided domain." }, - "lighthouse-core/lib/lh-error.js | docTypeInvalid": { - "message": "The page provided is not HTML" - }, "lighthouse-core/lib/lh-error.js | erroredRequiredArtifact": { "message": "Required {artifactName} gatherer encountered an error: {errorMessage}" }, @@ -1604,6 +1601,9 @@ "lighthouse-core/lib/lh-error.js | missingRequiredArtifact": { "message": "Required {artifactName} gatherer did not run." }, + "lighthouse-core/lib/lh-error.js | nonHtml": { + "message": "The page provided is not HTML (served as MIME type {mimeType})." + }, "lighthouse-core/lib/lh-error.js | pageLoadFailed": { "message": "Lighthouse was unable to reliably load the page you requested. Make sure you are testing the correct URL and that the server is properly responding to all requests." }, diff --git a/lighthouse-core/lib/i18n/locales/en-XL.json b/lighthouse-core/lib/i18n/locales/en-XL.json index 62475ee16f64..a8ee59c6143d 100644 --- a/lighthouse-core/lib/i18n/locales/en-XL.json +++ b/lighthouse-core/lib/i18n/locales/en-XL.json @@ -1592,9 +1592,6 @@ "lighthouse-core/lib/lh-error.js | dnsFailure": { "message": "D̂ŃŜ śêŕv̂ér̂ś ĉóûĺd̂ ńôt́ r̂éŝól̂v́ê t́ĥé p̂ŕôv́îd́êd́ d̂óm̂áîń." }, - "lighthouse-core/lib/lh-error.js | docTypeInvalid": { - "message": "T̂h́ê ṕâǵê ṕr̂óv̂íd̂éd̂ íŝ ńôt́ ĤT́M̂Ĺ" - }, "lighthouse-core/lib/lh-error.js | erroredRequiredArtifact": { "message": "R̂éq̂úîŕêd́ {artifactName} ĝát̂h́êŕêŕ êńĉóûńt̂ér̂éd̂ án̂ ér̂ŕôŕ: {errorMessage}" }, @@ -1604,6 +1601,9 @@ "lighthouse-core/lib/lh-error.js | missingRequiredArtifact": { "message": "R̂éq̂úîŕêd́ {artifactName} ĝát̂h́êŕêŕ d̂íd̂ ńôt́ r̂ún̂." }, + "lighthouse-core/lib/lh-error.js | nonHtml": { + "message": "T̂h́ê ṕâǵê ṕr̂óv̂íd̂éd̂ íŝ ńôt́ ĤT́M̂Ĺ (ŝér̂v́êd́ âś M̂ÍM̂É t̂ýp̂é {mimeType})." + }, "lighthouse-core/lib/lh-error.js | pageLoadFailed": { "message": "L̂íĝh́t̂h́ôúŝé ŵáŝ ún̂áb̂ĺê t́ô ŕêĺîáb̂ĺŷ ĺôád̂ t́ĥé p̂áĝé ŷóû ŕêq́ûéŝt́êd́. M̂ák̂é ŝúr̂é ŷóû ár̂é t̂éŝt́îńĝ t́ĥé ĉór̂ŕêćt̂ ÚR̂Ĺ âńd̂ t́ĥát̂ t́ĥé ŝér̂v́êŕ îś p̂ŕôṕêŕl̂ý r̂éŝṕôńd̂ín̂ǵ t̂ó âĺl̂ ŕêq́ûéŝt́ŝ." }, From 4f1b3211e13e60dd383c340af23ac16d31e5b1d1 Mon Sep 17 00:00:00 2001 From: Lemuel Cardenas-arriaga Date: Wed, 1 Jul 2020 18:02:54 -0700 Subject: [PATCH 11/68] even more code review changes --- lighthouse-core/gather/gather-runner.js | 11 +++---- lighthouse-core/lib/lh-error.js | 14 ++++++--- .../test/gather/gather-runner-test.js | 31 +++++++------------ proto/lighthouse-result.proto | 2 ++ 4 files changed, 26 insertions(+), 32 deletions(-) diff --git a/lighthouse-core/gather/gather-runner.js b/lighthouse-core/gather/gather-runner.js index 0ede58d8546e..bf87a9952ff6 100644 --- a/lighthouse-core/gather/gather-runner.js +++ b/lighthouse-core/gather/gather-runner.js @@ -220,21 +220,18 @@ class GatherRunner { * @param {LH.Artifacts.NetworkRequest|undefined} mainRecord * @return {LH.LighthouseError|undefined} */ - static getDocTypeError(mainRecord) { + static getNonHtmlError(mainRecord) { // MIME types are case-insenstive const HTML_MIME_REGEX = /^text\/html$/i; // If we never requested a document, there's no doctype error, let other cases handle it. if (!mainRecord) return undefined; - // If the main document failed, this error case is undefined, let other cases handle it. - if (mainRecord.failed) return undefined; - // mimeType is determined by the browser, we assume Chrome is determining mimeType correctly, // independently of 'Content-Type' response headers, and always sending mimeType if well-formed. if (mainRecord.mimeType || mainRecord.mimeType === '') { if (!HTML_MIME_REGEX.test(mainRecord.mimeType)) { - return new LHError(LHError.errors.INVALID_DOC_TYPE); + return new LHError(LHError.errors.NON_HTML, {mimeType: mainRecord.mimeType}); } } return undefined; @@ -258,7 +255,7 @@ class GatherRunner { const networkError = GatherRunner.getNetworkError(mainRecord); const interstitialError = GatherRunner.getInterstitialError(mainRecord, networkRecords); - const docTypeError = GatherRunner.getDocTypeError(mainRecord); + const nonHtmlError = GatherRunner.getNonHtmlError(mainRecord); // Check to see if we need to ignore the page load failure. // e.g. When the driver is offline, the load will fail without page offline support. @@ -273,7 +270,7 @@ class GatherRunner { if (networkError) return networkError; // Error if page is not HTML. - if (docTypeError) return docTypeError; + if (nonHtmlError) return nonHtmlError; // Navigation errors are rather generic and express some failure of the page to render properly. // Use `navigationError` as the last resort. diff --git a/lighthouse-core/lib/lh-error.js b/lighthouse-core/lib/lh-error.js index 8385a408ecea..302286df5d16 100644 --- a/lighthouse-core/lib/lh-error.js +++ b/lighthouse-core/lib/lh-error.js @@ -47,8 +47,11 @@ const UIStrings = { internalChromeError: 'An internal Chrome error occurred. Please restart Chrome and try re-running Lighthouse.', /** Error message explaining that fetching the resources of the webpage has taken longer than the maximum time. */ requestContentTimeout: 'Fetching resource content has exceeded the allotted time', - /** Error message explaining that the webpage is non-HTML, so audits are ill-defined **/ - docTypeInvalid: 'The page provided is not HTML', + /** + * @description Error message explaining that the webpage is non-HTML, so audits are ill-defined. + * @example {application/xml} mimeType + * */ + nonHtml: 'The page provided is not HTML (served as MIME type {mimeType}).', /** Error message explaining that the provided URL Lighthouse points to is not valid, and cannot be loaded. */ urlInvalid: 'The URL you have provided appears to be invalid.', /** @@ -315,9 +318,10 @@ const ERRORS = { lhrRuntimeError: true, }, /* Used when the page is non-HTML. */ - INVALID_DOC_TYPE: { - code: 'INVALID_DOC_TYPE', - message: UIStrings.docTypeInvalid, + NON_HTML: { + code: 'NON_HTML', + message: UIStrings.nonHtml, + lhrRuntimeError: true, }, // Protocol internal failures diff --git a/lighthouse-core/test/gather/gather-runner-test.js b/lighthouse-core/test/gather/gather-runner-test.js index ff8754c1c5f8..6a26ee90140e 100644 --- a/lighthouse-core/test/gather/gather-runner-test.js +++ b/lighthouse-core/test/gather/gather-runner-test.js @@ -38,7 +38,7 @@ const GatherRunner = { getInstallabilityErrors: makeParamsOptional(GatherRunner_.getInstallabilityErrors), getInterstitialError: makeParamsOptional(GatherRunner_.getInterstitialError), getNetworkError: makeParamsOptional(GatherRunner_.getNetworkError), - getDocTypeError: makeParamsOptional(GatherRunner_.getDocTypeError), + getNonHtmlError: makeParamsOptional(GatherRunner_.getNonHtmlError), getPageLoadError: makeParamsOptional(GatherRunner_.getPageLoadError), getWebAppManifest: makeParamsOptional(GatherRunner_.getWebAppManifest), initializeBaseArtifacts: makeParamsOptional(GatherRunner_.initializeBaseArtifacts), @@ -1096,27 +1096,18 @@ describe('GatherRunner', function() { }); }); - describe('#getDocTypeError', () => { + describe('#getNonHtmlError', () => { /** * @param {NetworkRequest} mainRecord */ function getAndExpectError(mainRecord) { - const error = GatherRunner.getDocTypeError(mainRecord); - if (!error) throw new Error('expected a docType error'); + const error = GatherRunner.getNonHtmlError(mainRecord); + if (!error) throw new Error('expected a non-HTML error'); return error; } it('passes when the page was not requested', () => { - expect(GatherRunner.getDocTypeError(undefined)).toBeUndefined(); - }); - - it('passes when page fails to load normally', () => { - const url = 'http://the-page.com'; - const mainRecord = new NetworkRequest(); - mainRecord.url = url; - mainRecord.failed = true; - mainRecord.localizedFailDescription = 'foobar'; - expect(GatherRunner.getDocTypeError(mainRecord)).toBeUndefined(); + expect(GatherRunner.getNonHtmlError(undefined)).toBeUndefined(); }); it('passes when the page is of MIME type text/html', () => { @@ -1125,7 +1116,7 @@ describe('GatherRunner', function() { const mimeType = 'text/html'; mainRecord.url = url; mainRecord.mimeType = mimeType; - expect(GatherRunner.getDocTypeError(mainRecord)).toBeUndefined(); + expect(GatherRunner.getNonHtmlError(mainRecord)).toBeUndefined(); }); it('fails when the page is not of MIME type text/html', () => { @@ -1135,9 +1126,9 @@ describe('GatherRunner', function() { mainRecord.url = url; mainRecord.mimeType = mimeType; const error = getAndExpectError(mainRecord); - expect(error.message).toEqual('INVALID_DOC_TYPE'); - expect(error.code).toEqual('INVALID_DOC_TYPE'); - expect(error.friendlyMessage).toBeDisplayString(/The page provided is not HTML/); + expect(error.message).toEqual('NON_HTML'); + expect(error.code).toEqual('NON_HTML'); + expect(error.friendlyMessage).toBeDisplayString(/is not HTML \(served as/); }); }); @@ -1234,7 +1225,7 @@ describe('GatherRunner', function() { expect(error.message).toEqual('FAILED_DOCUMENT_REQUEST'); }); - it('fails with doc type error third', () => { + it('fails with non-HTML error third', () => { const passContext = { url: 'http://the-page.com', passConfig: {loadFailureMode: LoadFailureMode.fatal}, @@ -1246,7 +1237,7 @@ describe('GatherRunner', function() { mainRecord.mimeType = 'application/xml'; const error = getAndExpectError(passContext, loadData, navigationError); - expect(error.message).toEqual('INVALID_DOC_TYPE'); + expect(error.message).toEqual('NON_HTML'); }); it('fails with nav error last', () => { diff --git a/proto/lighthouse-result.proto b/proto/lighthouse-result.proto index ece8b4739aaa..16067949b08d 100644 --- a/proto/lighthouse-result.proto +++ b/proto/lighthouse-result.proto @@ -55,6 +55,8 @@ enum LighthouseError { DNS_FAILURE = 19; // A timeout in the initial connection to the debugger protocol. CRI_TIMEOUT = 20; + // The page requested was non-HTML. + NON_HTML = 21; } // The overarching Lighthouse Response object (LHR) From 9dd709d63007d5038da73c7ad0b603e8addd60d5 Mon Sep 17 00:00:00 2001 From: Lemuel Cardenas-arriaga Date: Tue, 7 Jul 2020 15:33:25 -0700 Subject: [PATCH 12/68] changed naming from nonHtml to notHTML, removed an unnecessary if statement in getNonHtmlError --- lighthouse-core/gather/gather-runner.js | 6 ++---- lighthouse-core/lib/lh-error.js | 8 ++++---- lighthouse-core/test/gather/gather-runner-test.js | 6 +++--- proto/lighthouse-result.proto | 2 +- 4 files changed, 10 insertions(+), 12 deletions(-) diff --git a/lighthouse-core/gather/gather-runner.js b/lighthouse-core/gather/gather-runner.js index bf87a9952ff6..ad60d2331562 100644 --- a/lighthouse-core/gather/gather-runner.js +++ b/lighthouse-core/gather/gather-runner.js @@ -229,10 +229,8 @@ class GatherRunner { // mimeType is determined by the browser, we assume Chrome is determining mimeType correctly, // independently of 'Content-Type' response headers, and always sending mimeType if well-formed. - if (mainRecord.mimeType || mainRecord.mimeType === '') { - if (!HTML_MIME_REGEX.test(mainRecord.mimeType)) { - return new LHError(LHError.errors.NON_HTML, {mimeType: mainRecord.mimeType}); - } + if (!HTML_MIME_REGEX.test(mainRecord.mimeType)) { + return new LHError(LHError.errors.NOT_HTML, {mimeType: mainRecord.mimeType}); } return undefined; } diff --git a/lighthouse-core/lib/lh-error.js b/lighthouse-core/lib/lh-error.js index 302286df5d16..1f85256b41b6 100644 --- a/lighthouse-core/lib/lh-error.js +++ b/lighthouse-core/lib/lh-error.js @@ -51,7 +51,7 @@ const UIStrings = { * @description Error message explaining that the webpage is non-HTML, so audits are ill-defined. * @example {application/xml} mimeType * */ - nonHtml: 'The page provided is not HTML (served as MIME type {mimeType}).', + notHtml: 'The page provided is not HTML (served as MIME type {mimeType}).', /** Error message explaining that the provided URL Lighthouse points to is not valid, and cannot be loaded. */ urlInvalid: 'The URL you have provided appears to be invalid.', /** @@ -318,9 +318,9 @@ const ERRORS = { lhrRuntimeError: true, }, /* Used when the page is non-HTML. */ - NON_HTML: { - code: 'NON_HTML', - message: UIStrings.nonHtml, + NOT_HTML: { + code: 'NOT_HTML', + message: UIStrings.notHtml, lhrRuntimeError: true, }, diff --git a/lighthouse-core/test/gather/gather-runner-test.js b/lighthouse-core/test/gather/gather-runner-test.js index 6a26ee90140e..6a6b9729e5a8 100644 --- a/lighthouse-core/test/gather/gather-runner-test.js +++ b/lighthouse-core/test/gather/gather-runner-test.js @@ -1126,8 +1126,8 @@ describe('GatherRunner', function() { mainRecord.url = url; mainRecord.mimeType = mimeType; const error = getAndExpectError(mainRecord); - expect(error.message).toEqual('NON_HTML'); - expect(error.code).toEqual('NON_HTML'); + expect(error.message).toEqual('NOT_HTML'); + expect(error.code).toEqual('NOT_HTML'); expect(error.friendlyMessage).toBeDisplayString(/is not HTML \(served as/); }); }); @@ -1237,7 +1237,7 @@ describe('GatherRunner', function() { mainRecord.mimeType = 'application/xml'; const error = getAndExpectError(passContext, loadData, navigationError); - expect(error.message).toEqual('NON_HTML'); + expect(error.message).toEqual('NOT_HTML'); }); it('fails with nav error last', () => { diff --git a/proto/lighthouse-result.proto b/proto/lighthouse-result.proto index 16067949b08d..bfcc80dd9a32 100644 --- a/proto/lighthouse-result.proto +++ b/proto/lighthouse-result.proto @@ -56,7 +56,7 @@ enum LighthouseError { // A timeout in the initial connection to the debugger protocol. CRI_TIMEOUT = 20; // The page requested was non-HTML. - NON_HTML = 21; + NOT_HTML = 21; } // The overarching Lighthouse Response object (LHR) From cd32b47e310aa0cf03fefc7cd937bb6d6002f9fd Mon Sep 17 00:00:00 2001 From: Lemuel Cardenas-arriaga Date: Tue, 7 Jul 2020 15:34:36 -0700 Subject: [PATCH 13/68] added changed i18n strings --- lighthouse-core/lib/i18n/locales/en-US.json | 2 +- lighthouse-core/lib/i18n/locales/en-XL.json | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/lighthouse-core/lib/i18n/locales/en-US.json b/lighthouse-core/lib/i18n/locales/en-US.json index 1a62d12f60bf..fd70b5f77401 100644 --- a/lighthouse-core/lib/i18n/locales/en-US.json +++ b/lighthouse-core/lib/i18n/locales/en-US.json @@ -1601,7 +1601,7 @@ "lighthouse-core/lib/lh-error.js | missingRequiredArtifact": { "message": "Required {artifactName} gatherer did not run." }, - "lighthouse-core/lib/lh-error.js | nonHtml": { + "lighthouse-core/lib/lh-error.js | notHtml": { "message": "The page provided is not HTML (served as MIME type {mimeType})." }, "lighthouse-core/lib/lh-error.js | pageLoadFailed": { diff --git a/lighthouse-core/lib/i18n/locales/en-XL.json b/lighthouse-core/lib/i18n/locales/en-XL.json index a8ee59c6143d..f0dc69892d0f 100644 --- a/lighthouse-core/lib/i18n/locales/en-XL.json +++ b/lighthouse-core/lib/i18n/locales/en-XL.json @@ -1601,7 +1601,7 @@ "lighthouse-core/lib/lh-error.js | missingRequiredArtifact": { "message": "R̂éq̂úîŕêd́ {artifactName} ĝát̂h́êŕêŕ d̂íd̂ ńôt́ r̂ún̂." }, - "lighthouse-core/lib/lh-error.js | nonHtml": { + "lighthouse-core/lib/lh-error.js | notHtml": { "message": "T̂h́ê ṕâǵê ṕr̂óv̂íd̂éd̂ íŝ ńôt́ ĤT́M̂Ĺ (ŝér̂v́êd́ âś M̂ÍM̂É t̂ýp̂é {mimeType})." }, "lighthouse-core/lib/lh-error.js | pageLoadFailed": { From 8e3b37149f1d3dfbfd52375d76d6b093372b2499 Mon Sep 17 00:00:00 2001 From: Lemuel Cardenas-arriaga Date: Tue, 7 Jul 2020 17:13:17 -0700 Subject: [PATCH 14/68] removed regex and added const str for comparison --- lighthouse-core/gather/gather-runner.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lighthouse-core/gather/gather-runner.js b/lighthouse-core/gather/gather-runner.js index 6356966fb530..be34109f5222 100644 --- a/lighthouse-core/gather/gather-runner.js +++ b/lighthouse-core/gather/gather-runner.js @@ -223,14 +223,14 @@ class GatherRunner { */ static getNonHtmlError(mainRecord) { // MIME types are case-insenstive - const HTML_MIME_REGEX = /^text\/html$/i; + const HTML_MIME_TYPE = 'text/html'; // If we never requested a document, there's no doctype error, let other cases handle it. if (!mainRecord) return undefined; // mimeType is determined by the browser, we assume Chrome is determining mimeType correctly, // independently of 'Content-Type' response headers, and always sending mimeType if well-formed. - if (!HTML_MIME_REGEX.test(mainRecord.mimeType)) { + if (HTML_MIME_TYPE !== mainRecord.mimeType) { return new LHError(LHError.errors.NOT_HTML, {mimeType: mainRecord.mimeType}); } return undefined; From 6f90641a9a1885a90cab43a678fc9c01ae7b24ab Mon Sep 17 00:00:00 2001 From: Lemuel Cardenas-arriaga Date: Tue, 7 Jul 2020 17:18:33 -0700 Subject: [PATCH 15/68] included a comment about Chrome MIME type normalization --- lighthouse-core/gather/gather-runner.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lighthouse-core/gather/gather-runner.js b/lighthouse-core/gather/gather-runner.js index be34109f5222..f1a4ba6d0d39 100644 --- a/lighthouse-core/gather/gather-runner.js +++ b/lighthouse-core/gather/gather-runner.js @@ -222,7 +222,7 @@ class GatherRunner { * @return {LH.LighthouseError|undefined} */ static getNonHtmlError(mainRecord) { - // MIME types are case-insenstive + // MIME types are case-insenstive but Chrome normalizes MIME types to be lowercase. const HTML_MIME_TYPE = 'text/html'; // If we never requested a document, there's no doctype error, let other cases handle it. From d8437ca2c7b996d4df16d5fabd9ca51f9518235e Mon Sep 17 00:00:00 2001 From: Lemuel Cardenas-arriaga Date: Thu, 16 Jul 2020 13:54:19 -0700 Subject: [PATCH 16/68] started fleshing out new audit --- lighthouse-core/audits/sized-images.js | 83 +++++++++++++++++++ .../gather/gatherers/image-elements.js | 4 + types/artifacts.d.ts | 4 + 3 files changed, 91 insertions(+) create mode 100644 lighthouse-core/audits/sized-images.js diff --git a/lighthouse-core/audits/sized-images.js b/lighthouse-core/audits/sized-images.js new file mode 100644 index 000000000000..c0441421b2fb --- /dev/null +++ b/lighthouse-core/audits/sized-images.js @@ -0,0 +1,83 @@ +/** + * @license Copyright 2020 The Lighthouse Authors. All Rights Reserved. + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 + * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. + */ +'use strict'; + +const Audit = require('./audit.js'); +const i18n = require('./../lib/i18n/i18n.js'); + +const UIStrings = { + /** Short, user-visible title for the audit when successful. */ + title: '', + /** Short, user-visible title for the audit when failing. */ + failureTitle: '', + /** A more detailed description that describes why the audit is important and links to Lighthouse documentation on the audit; markdown links supported. */ + description: '', +}; + +const str_ = i18n.createMessageInstanceIdFn(__filename, UIStrings); + +/** + * @fileoverview + * Audits if a + */ + +class SizedImages extends Audit { + /** + * @return {LH.Audit.Meta} + */ + static get meta() { + return { + id: 'sized-images', + title: str_(UIStrings.title), + failureTitle: str_(UIStrings.failureTitle), + description: str_(UIStrings.description), + requiredArtifacts: ['ImageElements'], + }; + } + + /** + * @param {string} attr + * @return {boolean} + */ + static isValid(attr) { + // an img size attribute is valid for preventing CLS + // if it is a non-negative, non-zero integer + const NON_NEGATIVE_INT_REGEX = /^\d+$/; + const ZERO_REGEX = /^0+$/; + return NON_NEGATIVE_INT_REGEX.test(attr) && !ZERO_REGEX.test(attr); + } + + /** + * @param {LH.Artifacts} artifacts + * @param {LH.Audit.Context} context + * @return {Promise} + */ + static async audit(artifacts, context) { + // CSS background-images are ignored for this audit + const images = artifacts.ImageElements.filter(el => !el.isCss); + const unsizedImages = []; + + for (const image of images) { + const width = image.attributeWidth; + const height = image.attributeHeight; + // images are considered sized if they have defined & valid values + if (!width || !height || !SizedImages.isValid(width) || !SizedImages.isValid(height)) { + unsizedImages.push(image); + } + } + + + + return { + score: unsizedImages.length > 0 ? 0 : 1, + notApplicable: images.length === 0, + details: , + }; + } +} + +module.exports = SizedImages; +module.exports.UIStrings = UIStrings; diff --git a/lighthouse-core/gather/gatherers/image-elements.js b/lighthouse-core/gather/gatherers/image-elements.js index d9ca423e48e3..c54847be19bf 100644 --- a/lighthouse-core/gather/gatherers/image-elements.js +++ b/lighthouse-core/gather/gatherers/image-elements.js @@ -50,6 +50,8 @@ function getHTMLImages(allElements) { clientRect: getClientRect(element), naturalWidth: element.naturalWidth, naturalHeight: element.naturalHeight, + attributeWidth: element.getAttribute('width') || '', + attributeHeight: element.getAttribute('height') || '', isCss: false, // @ts-ignore: loading attribute not yet added to HTMLImageElement definition. loading: element.loading, @@ -97,6 +99,8 @@ function getCSSImages(allElements) { // CSS Images do not expose natural size, we'll determine the size later naturalWidth: 0, naturalHeight: 0, + attributeWidth: element.getAttribute('width') || '', + attributeHeight: element.getAttribute('height') || '', isCss: true, isPicture: false, usesObjectFit: false, diff --git a/types/artifacts.d.ts b/types/artifacts.d.ts index a6cfefd26b0c..1b18f66e23fc 100644 --- a/types/artifacts.d.ts +++ b/types/artifacts.d.ts @@ -397,6 +397,10 @@ declare global { naturalWidth: number; /** The natural height of the underlying image, uses img.naturalHeight. See https://codepen.io/patrickhulce/pen/PXvQbM for examples. */ naturalHeight: number; + /** The attribute width of the image, ... */ + attributeWidth: string; + /** The attribute height of the image, ... */ + attributeHeight: string; /** The BoundingClientRect of the element. */ clientRect: { top: number; From 0fca1e6f4f08536020a453342d2b24e0a3c8f204 Mon Sep 17 00:00:00 2001 From: Lemuel Cardenas-arriaga Date: Thu, 16 Jul 2020 15:27:30 -0700 Subject: [PATCH 17/68] working audit on basic examples --- lighthouse-core/audits/sized-images.js | 32 +++++++++++++++---- lighthouse-core/config/default-config.js | 2 ++ .../gather/gatherers/image-elements.js | 22 ++++++++++++- types/artifacts.d.ts | 5 +++ 4 files changed, 54 insertions(+), 7 deletions(-) diff --git a/lighthouse-core/audits/sized-images.js b/lighthouse-core/audits/sized-images.js index c0441421b2fb..cd5c4254d2e0 100644 --- a/lighthouse-core/audits/sized-images.js +++ b/lighthouse-core/audits/sized-images.js @@ -7,14 +7,17 @@ const Audit = require('./audit.js'); const i18n = require('./../lib/i18n/i18n.js'); +const URL = require('./../lib/url-shim.js'); const UIStrings = { /** Short, user-visible title for the audit when successful. */ - title: '', + title: 'Success', /** Short, user-visible title for the audit when failing. */ - failureTitle: '', + failureTitle: 'Failure', /** A more detailed description that describes why the audit is important and links to Lighthouse documentation on the audit; markdown links supported. */ - description: '', + description: 'Description', + /** Table column header for the HTML elements that do not allow pasting of content. */ + columnFailingElem: 'Failing Elements', }; const str_ = i18n.createMessageInstanceIdFn(__filename, UIStrings); @@ -65,16 +68,33 @@ class SizedImages extends Audit { const height = image.attributeHeight; // images are considered sized if they have defined & valid values if (!width || !height || !SizedImages.isValid(width) || !SizedImages.isValid(height)) { - unsizedImages.push(image); + + const url = URL.elideDataURI(image.src); + + unsizedImages.push({ + url, + node: /** @type {LH.Audit.Details.NodeValue} */ ({ + type: 'node', + path: image.devtoolsNodePath, + selector: image.selector, + nodeLabel: image.nodeLabel, + snippet: image.snippet, + }), + }); } } - + /** @type {LH.Audit.Details.Table['headings']} */ + const headings = [ + {key: 'url', itemType: 'thumbnail', text: ''}, + {key: 'url', itemType: 'url', text: str_(i18n.UIStrings.columnURL)}, + {key: 'node', itemType: 'node', text: str_(UIStrings.columnFailingElem)}, + ]; return { score: unsizedImages.length > 0 ? 0 : 1, notApplicable: images.length === 0, - details: , + details: Audit.makeTableDetails(headings, unsizedImages), }; } } diff --git a/lighthouse-core/config/default-config.js b/lighthouse-core/config/default-config.js index 9d6f841489b0..ac672db35d39 100644 --- a/lighthouse-core/config/default-config.js +++ b/lighthouse-core/config/default-config.js @@ -216,6 +216,7 @@ const defaultConfig = { 'content-width', 'image-aspect-ratio', 'image-size-responsive', + 'sized-images', 'deprecations', 'mainthread-work-breakdown', 'bootup-time', @@ -546,6 +547,7 @@ const defaultConfig = { {id: 'no-vulnerable-libraries', weight: 1, group: 'best-practices-trust-safety'}, // User Experience {id: 'password-inputs-can-be-pasted-into', weight: 1, group: 'best-practices-ux'}, + {id: 'sized-images', weight: 1, group: 'best-practices-ux'}, {id: 'image-aspect-ratio', weight: 1, group: 'best-practices-ux'}, {id: 'image-size-responsive', weight: 1, group: 'best-practices-ux'}, // Browser Compatibility diff --git a/lighthouse-core/gather/gatherers/image-elements.js b/lighthouse-core/gather/gatherers/image-elements.js index c54847be19bf..fb77f993e636 100644 --- a/lighthouse-core/gather/gatherers/image-elements.js +++ b/lighthouse-core/gather/gatherers/image-elements.js @@ -13,7 +13,7 @@ const Gatherer = require('./gatherer.js'); const pageFunctions = require('../../lib/page-functions.js'); const Driver = require('../driver.js'); // eslint-disable-line no-unused-vars -/* global window, getElementsInDocument, Image */ +/* global window, getElementsInDocument, Image, getNodePath, getNodeSelector, getNodeLabel, getOuterHTMLSnippet */ /** @param {Element} element */ @@ -65,6 +65,14 @@ function getHTMLImages(allElements) { ), // https://html.spec.whatwg.org/multipage/images.html#pixel-density-descriptor usesSrcSetDensityDescriptor: / \d+(\.\d+)?x/.test(element.srcset), + // @ts-ignore - getNodePath put into scope via stringification + devtoolsNodePath: getNodePath(element), + // @ts-ignore - put into scope via stringification + selector: getNodeSelector(element), + // @ts-ignore - put into scope via stringification + nodeLabel: getNodeLabel(element), + // @ts-ignore - put into scope via stringification + snippet: getOuterHTMLSnippet(element), }; }); } @@ -109,6 +117,14 @@ function getCSSImages(allElements) { ), usesSrcSetDensityDescriptor: false, resourceSize: 0, // this will get overwritten below + // @ts-ignore - getNodePath put into scope via stringification + devtoolsNodePath: getNodePath(element), + // @ts-ignore - put into scope via stringification + selector: getNodeSelector(element), + // @ts-ignore - put into scope via stringification + nodeLabel: getNodeLabel(element), + // @ts-ignore - put into scope via stringification + snippet: getOuterHTMLSnippet(element), }); } @@ -194,6 +210,10 @@ class ImageElements extends Gatherer { const expression = `(function() { ${pageFunctions.getElementsInDocumentString}; // define function on page + ${pageFunctions.getNodePathString}; + ${pageFunctions.getNodeSelectorString}; + ${pageFunctions.getNodeLabelString}; + ${pageFunctions.getOuterHTMLSnippetString}; ${getClientRect.toString()}; ${getHTMLImages.toString()}; ${getCSSImages.toString()}; diff --git a/types/artifacts.d.ts b/types/artifacts.d.ts index 1b18f66e23fc..48ffc1766c65 100644 --- a/types/artifacts.d.ts +++ b/types/artifacts.d.ts @@ -425,6 +425,11 @@ declare global { usesSrcSetDensityDescriptor: boolean; /** The size of the underlying image file in bytes. 0 if the file could not be identified. */ resourceSize: number; + /** Path that uniquely identifies the node in the DOM */ + devtoolsNodePath: string; + snippet: string; + selector: string; + nodeLabel: string; /** The MIME type of the underlying image file. */ mimeType?: string; /** The loading attribute of the image. */ From 37628a89c945375c97991e7bc03e54442f7d1cad Mon Sep 17 00:00:00 2001 From: Lemuel Cardenas-arriaga Date: Thu, 16 Jul 2020 16:29:28 -0700 Subject: [PATCH 18/68] removed spaces and added titles and comments --- lighthouse-core/audits/sized-images.js | 16 +++++++--------- 1 file changed, 7 insertions(+), 9 deletions(-) diff --git a/lighthouse-core/audits/sized-images.js b/lighthouse-core/audits/sized-images.js index cd5c4254d2e0..89bcf6842e44 100644 --- a/lighthouse-core/audits/sized-images.js +++ b/lighthouse-core/audits/sized-images.js @@ -10,13 +10,13 @@ const i18n = require('./../lib/i18n/i18n.js'); const URL = require('./../lib/url-shim.js'); const UIStrings = { - /** Short, user-visible title for the audit when successful. */ - title: 'Success', - /** Short, user-visible title for the audit when failing. */ - failureTitle: 'Failure', - /** A more detailed description that describes why the audit is important and links to Lighthouse documentation on the audit; markdown links supported. */ - description: 'Description', - /** Table column header for the HTML elements that do not allow pasting of content. */ + /** Title of a Lighthouse audit that provides detail on whether all images had width and height attributes. This descriptive title is shown to users when every image has width and height attributes */ + title: 'Image elements have `width` and `height` attributes', + /** Title of a Lighthouse audit that provides detail on whether all images had width and height attributes. This descriptive title is shown to users when one or more images does not have width and height attributes */ + failureTitle: 'Image elements do not have `width` and `height` attributes', + /** Description of a Lighthouse audit that tells the user why they should include width and height attributes 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 width and height attributes on your image elements to reduce layout shifting and improve CLS. [Learn more](https://web.dev/optimize-cls/#images-without-dimensions)', + /** Table column header for the image elements that do not have image and height attributes. */ columnFailingElem: 'Failing Elements', }; @@ -68,9 +68,7 @@ class SizedImages extends Audit { const height = image.attributeHeight; // images are considered sized if they have defined & valid values if (!width || !height || !SizedImages.isValid(width) || !SizedImages.isValid(height)) { - const url = URL.elideDataURI(image.src); - unsizedImages.push({ url, node: /** @type {LH.Audit.Details.NodeValue} */ ({ From 488f73bb405dc6c4305c837408896e06941a1924 Mon Sep 17 00:00:00 2001 From: Lemuel Cardenas-arriaga Date: Thu, 16 Jul 2020 16:54:45 -0700 Subject: [PATCH 19/68] added a file overview --- lighthouse-core/audits/sized-images.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lighthouse-core/audits/sized-images.js b/lighthouse-core/audits/sized-images.js index 89bcf6842e44..884ec23c6e83 100644 --- a/lighthouse-core/audits/sized-images.js +++ b/lighthouse-core/audits/sized-images.js @@ -24,7 +24,7 @@ const str_ = i18n.createMessageInstanceIdFn(__filename, UIStrings); /** * @fileoverview - * Audits if a + * Audit that checks whether all images have width and height attributes. */ class SizedImages extends Audit { From a6417f143470e59180e800515ddb527eacbba399 Mon Sep 17 00:00:00 2001 From: Lemuel Cardenas-arriaga Date: Thu, 16 Jul 2020 17:03:31 -0700 Subject: [PATCH 20/68] baked new strings & changed duplicate string assertion --- lighthouse-core/lib/i18n/locales/en-US.json | 12 ++++++++++++ lighthouse-core/lib/i18n/locales/en-XL.json | 12 ++++++++++++ lighthouse-core/scripts/i18n/collect-strings.js | 2 +- 3 files changed, 25 insertions(+), 1 deletion(-) diff --git a/lighthouse-core/lib/i18n/locales/en-US.json b/lighthouse-core/lib/i18n/locales/en-US.json index 610dd40e9352..6deb30d44181 100644 --- a/lighthouse-core/lib/i18n/locales/en-US.json +++ b/lighthouse-core/lib/i18n/locales/en-US.json @@ -1205,6 +1205,18 @@ "lighthouse-core/audits/service-worker.js | title": { "message": "Registers a service worker that controls page and `start_url`" }, + "lighthouse-core/audits/sized-images.js | columnFailingElem": { + "message": "Failing Elements" + }, + "lighthouse-core/audits/sized-images.js | description": { + "message": "Always include width and height attributes on your image elements to reduce layout shifting and improve CLS. [Learn more](https://web.dev/optimize-cls/#images-without-dimensions)" + }, + "lighthouse-core/audits/sized-images.js | failureTitle": { + "message": "Image elements do not have `width` and `height` attributes" + }, + "lighthouse-core/audits/sized-images.js | title": { + "message": "Image elements have `width` and `height` attributes" + }, "lighthouse-core/audits/splash-screen.js | description": { "message": "A themed splash screen ensures a high-quality experience when users launch your app from their homescreens. [Learn more](https://web.dev/splash-screen/)." }, diff --git a/lighthouse-core/lib/i18n/locales/en-XL.json b/lighthouse-core/lib/i18n/locales/en-XL.json index 488563e86a32..ac6fadb54cb0 100644 --- a/lighthouse-core/lib/i18n/locales/en-XL.json +++ b/lighthouse-core/lib/i18n/locales/en-XL.json @@ -1205,6 +1205,18 @@ "lighthouse-core/audits/service-worker.js | title": { "message": "R̂éĝíŝt́êŕŝ á ŝér̂v́îćê ẃôŕk̂ér̂ t́ĥát̂ ćôńt̂ŕôĺŝ ṕâǵê án̂d́ `start_url`" }, + "lighthouse-core/audits/sized-images.js | columnFailingElem": { + "message": "F̂áîĺîńĝ Él̂ém̂én̂t́ŝ" + }, + "lighthouse-core/audits/sized-images.js | description": { + "message": "Âĺŵáŷś îńĉĺûd́ê ẃîd́t̂h́ âńd̂ h́êíĝh́t̂ át̂t́r̂íb̂út̂éŝ ón̂ ýôúr̂ ím̂áĝé êĺêḿêńt̂ś t̂ó r̂éd̂úĉé l̂áŷóût́ ŝh́îf́t̂ín̂ǵ âńd̂ ím̂ṕr̂óv̂é ĈĹŜ. [Ĺêár̂ń m̂ór̂é](https://web.dev/optimize-cls/#images-without-dimensions)" + }, + "lighthouse-core/audits/sized-images.js | failureTitle": { + "message": "Îḿâǵê él̂ém̂én̂t́ŝ d́ô ńôt́ ĥáv̂é `width` âńd̂ `height` át̂t́r̂íb̂út̂éŝ" + }, + "lighthouse-core/audits/sized-images.js | title": { + "message": "Îḿâǵê él̂ém̂én̂t́ŝ h́âv́ê `width` án̂d́ `height` ât́t̂ŕîb́ût́êś" + }, "lighthouse-core/audits/splash-screen.js | description": { "message": " t́ĥém̂éd̂ śp̂ĺâśĥ śĉŕêén̂ én̂śûŕêś â h́îǵĥ-q́ûál̂ít̂ý êx́p̂ér̂íêńĉé ŵh́êń ûśêŕŝ ĺâún̂ćĥ ýôúr̂ áp̂ṕ f̂ŕôḿ t̂h́êír̂ h́ôḿêśĉŕêén̂ś. [L̂éâŕn̂ ḿôŕê](https://web.dev/splash-screen/)." }, diff --git a/lighthouse-core/scripts/i18n/collect-strings.js b/lighthouse-core/scripts/i18n/collect-strings.js index 0e07b85e8757..56f05c61975f 100644 --- a/lighthouse-core/scripts/i18n/collect-strings.js +++ b/lighthouse-core/scripts/i18n/collect-strings.js @@ -570,7 +570,7 @@ if (require.main === module) { if ((collisions) > 0) { console.log(`MEANING COLLISION: ${collisions} string(s) have the same content.`); - assert.equal(collisions, 16, `The number of duplicate strings have changed, update this assertion if that is expected, or reword strings. Collisions: ${collisionStrings}`); + assert.equal(collisions, 17, `The number of duplicate strings have changed, update this assertion if that is expected, or reword strings. Collisions: ${collisionStrings}`); } const strings = {...coreStrings, ...stackPackStrings}; From 3cc6c240550994aa0a98f18be3fe1ab2e0042695 Mon Sep 17 00:00:00 2001 From: Lemuel Cardenas-arriaga Date: Fri, 17 Jul 2020 11:16:51 -0700 Subject: [PATCH 21/68] feedback fixed from draft pr, sized-images-test --- .../password-inputs-can-be-pasted-into.js | 4 +- lighthouse-core/audits/sized-images.js | 7 +- .../gather/gatherers/image-elements.js | 4 +- lighthouse-core/lib/i18n/i18n.js | 2 + lighthouse-core/lib/i18n/locales/ar-XB.json | 3 - lighthouse-core/lib/i18n/locales/ar.json | 3 - lighthouse-core/lib/i18n/locales/bg.json | 3 - lighthouse-core/lib/i18n/locales/ca.json | 3 - lighthouse-core/lib/i18n/locales/cs.json | 3 - lighthouse-core/lib/i18n/locales/da.json | 3 - lighthouse-core/lib/i18n/locales/de.json | 3 - lighthouse-core/lib/i18n/locales/el.json | 3 - lighthouse-core/lib/i18n/locales/en-GB.json | 3 - lighthouse-core/lib/i18n/locales/en-US.json | 9 +- lighthouse-core/lib/i18n/locales/en-XA.json | 3 - lighthouse-core/lib/i18n/locales/en-XL.json | 9 +- lighthouse-core/lib/i18n/locales/es-419.json | 3 - lighthouse-core/lib/i18n/locales/es.json | 3 - lighthouse-core/lib/i18n/locales/fi.json | 3 - lighthouse-core/lib/i18n/locales/fil.json | 3 - lighthouse-core/lib/i18n/locales/fr.json | 3 - lighthouse-core/lib/i18n/locales/he.json | 3 - lighthouse-core/lib/i18n/locales/hi.json | 3 - lighthouse-core/lib/i18n/locales/hr.json | 3 - lighthouse-core/lib/i18n/locales/hu.json | 3 - lighthouse-core/lib/i18n/locales/id.json | 3 - lighthouse-core/lib/i18n/locales/it.json | 3 - lighthouse-core/lib/i18n/locales/ja.json | 3 - lighthouse-core/lib/i18n/locales/ko.json | 3 - lighthouse-core/lib/i18n/locales/lt.json | 3 - lighthouse-core/lib/i18n/locales/lv.json | 3 - lighthouse-core/lib/i18n/locales/nl.json | 3 - lighthouse-core/lib/i18n/locales/no.json | 3 - lighthouse-core/lib/i18n/locales/pl.json | 3 - lighthouse-core/lib/i18n/locales/pt-PT.json | 3 - lighthouse-core/lib/i18n/locales/pt.json | 3 - lighthouse-core/lib/i18n/locales/ro.json | 3 - lighthouse-core/lib/i18n/locales/ru.json | 3 - lighthouse-core/lib/i18n/locales/sk.json | 3 - lighthouse-core/lib/i18n/locales/sl.json | 3 - lighthouse-core/lib/i18n/locales/sr-Latn.json | 3 - lighthouse-core/lib/i18n/locales/sr.json | 3 - lighthouse-core/lib/i18n/locales/sv.json | 3 - lighthouse-core/lib/i18n/locales/ta.json | 3 - lighthouse-core/lib/i18n/locales/te.json | 3 - lighthouse-core/lib/i18n/locales/th.json | 3 - lighthouse-core/lib/i18n/locales/tr.json | 3 - lighthouse-core/lib/i18n/locales/uk.json | 3 - lighthouse-core/lib/i18n/locales/vi.json | 3 - lighthouse-core/lib/i18n/locales/zh-HK.json | 3 - lighthouse-core/lib/i18n/locales/zh-TW.json | 3 - lighthouse-core/lib/i18n/locales/zh.json | 3 - .../scripts/i18n/collect-strings.js | 2 +- .../test/audits/sized-images-test.js | 166 ++++++++++++++++++ types/artifacts.d.ts | 4 +- 55 files changed, 182 insertions(+), 163 deletions(-) create mode 100644 lighthouse-core/test/audits/sized-images-test.js diff --git a/lighthouse-core/audits/dobetterweb/password-inputs-can-be-pasted-into.js b/lighthouse-core/audits/dobetterweb/password-inputs-can-be-pasted-into.js index b262e5ba6545..89ca318ce907 100644 --- a/lighthouse-core/audits/dobetterweb/password-inputs-can-be-pasted-into.js +++ b/lighthouse-core/audits/dobetterweb/password-inputs-can-be-pasted-into.js @@ -16,8 +16,6 @@ const UIStrings = { /** Description of a Lighthouse audit that tells the user why they should allow pasting of content into password fields. 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: 'Preventing password pasting undermines good security policy. ' + '[Learn more](https://web.dev/password-inputs-can-be-pasted-into/).', - /** Table column header for the HTML elements that do not allow pasting of content. */ - columnFailingElem: 'Failing Elements', }; const str_ = i18n.createMessageInstanceIdFn(__filename, UIStrings); @@ -53,7 +51,7 @@ class PasswordInputsCanBePastedIntoAudit extends Audit { /** @type {LH.Audit.Details.Table['headings']} */ const headings = [ - {key: 'node', itemType: 'node', text: str_(UIStrings.columnFailingElem)}, + {key: 'node', itemType: 'node', text: str_(i18n.UIStrings.columnFailingElem)}, ]; return { diff --git a/lighthouse-core/audits/sized-images.js b/lighthouse-core/audits/sized-images.js index 884ec23c6e83..b8bba43c7527 100644 --- a/lighthouse-core/audits/sized-images.js +++ b/lighthouse-core/audits/sized-images.js @@ -16,8 +16,6 @@ const UIStrings = { failureTitle: 'Image elements do not have `width` and `height` attributes', /** Description of a Lighthouse audit that tells the user why they should include width and height attributes 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 width and height attributes on your image elements to reduce layout shifting and improve CLS. [Learn more](https://web.dev/optimize-cls/#images-without-dimensions)', - /** Table column header for the image elements that do not have image and height attributes. */ - columnFailingElem: 'Failing Elements', }; const str_ = i18n.createMessageInstanceIdFn(__filename, UIStrings); @@ -55,10 +53,9 @@ class SizedImages extends Audit { /** * @param {LH.Artifacts} artifacts - * @param {LH.Audit.Context} context * @return {Promise} */ - static async audit(artifacts, context) { + static async audit(artifacts) { // CSS background-images are ignored for this audit const images = artifacts.ImageElements.filter(el => !el.isCss); const unsizedImages = []; @@ -86,7 +83,7 @@ class SizedImages extends Audit { const headings = [ {key: 'url', itemType: 'thumbnail', text: ''}, {key: 'url', itemType: 'url', text: str_(i18n.UIStrings.columnURL)}, - {key: 'node', itemType: 'node', text: str_(UIStrings.columnFailingElem)}, + {key: 'node', itemType: 'node', text: str_(i18n.UIStrings.columnFailingElem)}, ]; return { diff --git a/lighthouse-core/gather/gatherers/image-elements.js b/lighthouse-core/gather/gatherers/image-elements.js index 2ede2ddb98f2..337b48ab30f3 100644 --- a/lighthouse-core/gather/gatherers/image-elements.js +++ b/lighthouse-core/gather/gatherers/image-elements.js @@ -109,8 +109,8 @@ function getCSSImages(allElements) { // CSS Images do not expose natural size, we'll determine the size later naturalWidth: 0, naturalHeight: 0, - attributeWidth: element.getAttribute('width') || '', - attributeHeight: element.getAttribute('height') || '', + attributeWidth: '', + attributeHeight: '', isCss: true, isPicture: false, usesObjectFit: false, diff --git a/lighthouse-core/lib/i18n/i18n.js b/lighthouse-core/lib/i18n/i18n.js index eec8d71a3a01..60591ac0e3bc 100644 --- a/lighthouse-core/lib/i18n/i18n.js +++ b/lighthouse-core/lib/i18n/i18n.js @@ -88,6 +88,8 @@ const UIStrings = { columnStartTime: 'Start Time', /** Label for a column in a data table; entries will be the total number of milliseconds from the start time until the end time. */ columnDuration: 'Duration', + /** Label for a column in a data table; entries will be a representation of a DOM element that did not meet certain suggestions. */ + columnFailingElem: 'Failing Elements', /** Label for a row in a data table; entries will be the total number and byte size of all resources loaded by a web page. */ totalResourceType: 'Total', /** Label for a row in a data table; entries will be the total number and byte size of all 'Document' resources loaded by a web page. */ diff --git a/lighthouse-core/lib/i18n/locales/ar-XB.json b/lighthouse-core/lib/i18n/locales/ar-XB.json index 0de2ee591c02..96dcf33741f1 100644 --- a/lighthouse-core/lib/i18n/locales/ar-XB.json +++ b/lighthouse-core/lib/i18n/locales/ar-XB.json @@ -689,9 +689,6 @@ "lighthouse-core/audits/dobetterweb/notification-on-start.js | title": { "message": "‏‮Avoids‬‏ ‏‮requesting‬‏ ‏‮the‬‏ ‏‮notification‬‏ ‏‮permission‬‏ ‏‮on‬‏ ‏‮page‬‏ ‏‮load‬‏" }, - "lighthouse-core/audits/dobetterweb/password-inputs-can-be-pasted-into.js | columnFailingElem": { - "message": "‏‮Failing‬‏ ‏‮Elements‬‏" - }, "lighthouse-core/audits/dobetterweb/password-inputs-can-be-pasted-into.js | description": { "message": "‏‮Preventing‬‏ ‏‮password‬‏ ‏‮pasting‬‏ ‏‮undermines‬‏ ‏‮good‬‏ ‏‮security‬‏ ‏‮policy‬‏. [‏‮Learn‬‏ ‏‮more‬‏](https://web.dev/password-inputs-can-be-pasted-into/)." }, diff --git a/lighthouse-core/lib/i18n/locales/ar.json b/lighthouse-core/lib/i18n/locales/ar.json index 46c7563f698e..673641bb4f1c 100644 --- a/lighthouse-core/lib/i18n/locales/ar.json +++ b/lighthouse-core/lib/i18n/locales/ar.json @@ -689,9 +689,6 @@ "lighthouse-core/audits/dobetterweb/notification-on-start.js | title": { "message": "يتم تجنُّب طلب إذن الإشعار عند تحميل الصفحة" }, - "lighthouse-core/audits/dobetterweb/password-inputs-can-be-pasted-into.js | columnFailingElem": { - "message": "العناصر التي لا تسمح بلصق المحتوى" - }, "lighthouse-core/audits/dobetterweb/password-inputs-can-be-pasted-into.js | description": { "message": "يؤدي منع لصق كلمة المرور إلى تقويض سياسة الأمان الجيدة. [مزيد من المعلومات](https://web.dev/password-inputs-can-be-pasted-into/)" }, diff --git a/lighthouse-core/lib/i18n/locales/bg.json b/lighthouse-core/lib/i18n/locales/bg.json index 3dd05e1a7ada..ab5fcaf74f55 100644 --- a/lighthouse-core/lib/i18n/locales/bg.json +++ b/lighthouse-core/lib/i18n/locales/bg.json @@ -689,9 +689,6 @@ "lighthouse-core/audits/dobetterweb/notification-on-start.js | title": { "message": "Избягва да иска разрешение за известяване при зареждането на страницата" }, - "lighthouse-core/audits/dobetterweb/password-inputs-can-be-pasted-into.js | columnFailingElem": { - "message": "Елементи с грешки" - }, "lighthouse-core/audits/dobetterweb/password-inputs-can-be-pasted-into.js | description": { "message": "Забраната на поставянето на пароли неутрализира добра практика за сигурност. [Научете повече](https://web.dev/password-inputs-can-be-pasted-into/)." }, diff --git a/lighthouse-core/lib/i18n/locales/ca.json b/lighthouse-core/lib/i18n/locales/ca.json index e3aaf65a1284..b5b274544190 100644 --- a/lighthouse-core/lib/i18n/locales/ca.json +++ b/lighthouse-core/lib/i18n/locales/ca.json @@ -689,9 +689,6 @@ "lighthouse-core/audits/dobetterweb/notification-on-start.js | title": { "message": "Evita sol·licitar el permís de notificació en carregar la pàgina" }, - "lighthouse-core/audits/dobetterweb/password-inputs-can-be-pasted-into.js | columnFailingElem": { - "message": "Elements amb errors" - }, "lighthouse-core/audits/dobetterweb/password-inputs-can-be-pasted-into.js | description": { "message": "Impedir enganxar la contrasenya va en detriment d'una bona política de seguretat. [Obtén més informació](https://web.dev/password-inputs-can-be-pasted-into/)." }, diff --git a/lighthouse-core/lib/i18n/locales/cs.json b/lighthouse-core/lib/i18n/locales/cs.json index 2181c619ddf8..e13feec6a2d8 100644 --- a/lighthouse-core/lib/i18n/locales/cs.json +++ b/lighthouse-core/lib/i18n/locales/cs.json @@ -689,9 +689,6 @@ "lighthouse-core/audits/dobetterweb/notification-on-start.js | title": { "message": "Nežádá při načtení stránky o oprávnění zobrazovat oznámení" }, - "lighthouse-core/audits/dobetterweb/password-inputs-can-be-pasted-into.js | columnFailingElem": { - "message": "Prvky, které neprošly" - }, "lighthouse-core/audits/dobetterweb/password-inputs-can-be-pasted-into.js | description": { "message": "Blokování vkládání hesel je v rozporu s dobrými bezpečnostními zásadami. [Další informace](https://web.dev/password-inputs-can-be-pasted-into/)" }, diff --git a/lighthouse-core/lib/i18n/locales/da.json b/lighthouse-core/lib/i18n/locales/da.json index ce51be779ab7..96dc9c79219e 100644 --- a/lighthouse-core/lib/i18n/locales/da.json +++ b/lighthouse-core/lib/i18n/locales/da.json @@ -689,9 +689,6 @@ "lighthouse-core/audits/dobetterweb/notification-on-start.js | title": { "message": "Undgår at anmode om tilladelse til notifikationer ved indlæsning af siden" }, - "lighthouse-core/audits/dobetterweb/password-inputs-can-be-pasted-into.js | columnFailingElem": { - "message": "Elementer, der ikke bestod gennemgangen" - }, "lighthouse-core/audits/dobetterweb/password-inputs-can-be-pasted-into.js | description": { "message": "En god sikkerhedspolitik undermineres ved at forhindre indsættelse af adgangskoder. [Få flere oplysninger](https://web.dev/password-inputs-can-be-pasted-into/)." }, diff --git a/lighthouse-core/lib/i18n/locales/de.json b/lighthouse-core/lib/i18n/locales/de.json index 81cf6a79be75..c921d4f9dfa4 100644 --- a/lighthouse-core/lib/i18n/locales/de.json +++ b/lighthouse-core/lib/i18n/locales/de.json @@ -689,9 +689,6 @@ "lighthouse-core/audits/dobetterweb/notification-on-start.js | title": { "message": "Fordert während des Seitenaufbaus keine Benachrichtigungsberechtigung an" }, - "lighthouse-core/audits/dobetterweb/password-inputs-can-be-pasted-into.js | columnFailingElem": { - "message": "Fehlerhafte Elemente" - }, "lighthouse-core/audits/dobetterweb/password-inputs-can-be-pasted-into.js | description": { "message": "Das Einfügen von Passwörtern sollte entsprechend guten Sicherheitsrichtlinien zulässig sein. [Weitere Informationen.](https://web.dev/password-inputs-can-be-pasted-into/)" }, diff --git a/lighthouse-core/lib/i18n/locales/el.json b/lighthouse-core/lib/i18n/locales/el.json index b747812c142f..302745c0613b 100644 --- a/lighthouse-core/lib/i18n/locales/el.json +++ b/lighthouse-core/lib/i18n/locales/el.json @@ -689,9 +689,6 @@ "lighthouse-core/audits/dobetterweb/notification-on-start.js | title": { "message": "Αποφυγή αιτήματος για άδεια ειδοποίησης κατά τη φόρτωση σελίδων" }, - "lighthouse-core/audits/dobetterweb/password-inputs-can-be-pasted-into.js | columnFailingElem": { - "message": "Στοιχεία που απέτυχαν" - }, "lighthouse-core/audits/dobetterweb/password-inputs-can-be-pasted-into.js | description": { "message": "Η απαγόρευση της επικόλλησης κωδικών πρόσβασης υπονομεύει την ορθή πολιτική ασφάλειας. [Μάθετε περισσότερα](https://web.dev/password-inputs-can-be-pasted-into/)." }, diff --git a/lighthouse-core/lib/i18n/locales/en-GB.json b/lighthouse-core/lib/i18n/locales/en-GB.json index b11898517022..868c697b1239 100644 --- a/lighthouse-core/lib/i18n/locales/en-GB.json +++ b/lighthouse-core/lib/i18n/locales/en-GB.json @@ -689,9 +689,6 @@ "lighthouse-core/audits/dobetterweb/notification-on-start.js | title": { "message": "Avoids requesting the notification permission on page load" }, - "lighthouse-core/audits/dobetterweb/password-inputs-can-be-pasted-into.js | columnFailingElem": { - "message": "Failing Elements" - }, "lighthouse-core/audits/dobetterweb/password-inputs-can-be-pasted-into.js | description": { "message": "Preventing password pasting undermines good security policy. [Learn more](https://web.dev/password-inputs-can-be-pasted-into/)." }, diff --git a/lighthouse-core/lib/i18n/locales/en-US.json b/lighthouse-core/lib/i18n/locales/en-US.json index 6deb30d44181..26a81eeeadc9 100644 --- a/lighthouse-core/lib/i18n/locales/en-US.json +++ b/lighthouse-core/lib/i18n/locales/en-US.json @@ -695,9 +695,6 @@ "lighthouse-core/audits/dobetterweb/notification-on-start.js | title": { "message": "Avoids requesting the notification permission on page load" }, - "lighthouse-core/audits/dobetterweb/password-inputs-can-be-pasted-into.js | columnFailingElem": { - "message": "Failing Elements" - }, "lighthouse-core/audits/dobetterweb/password-inputs-can-be-pasted-into.js | description": { "message": "Preventing password pasting undermines good security policy. [Learn more](https://web.dev/password-inputs-can-be-pasted-into/)." }, @@ -1205,9 +1202,6 @@ "lighthouse-core/audits/service-worker.js | title": { "message": "Registers a service worker that controls page and `start_url`" }, - "lighthouse-core/audits/sized-images.js | columnFailingElem": { - "message": "Failing Elements" - }, "lighthouse-core/audits/sized-images.js | description": { "message": "Always include width and height attributes on your image elements to reduce layout shifting and improve CLS. [Learn more](https://web.dev/optimize-cls/#images-without-dimensions)" }, @@ -1502,6 +1496,9 @@ "lighthouse-core/lib/i18n/i18n.js | columnElement": { "message": "Element" }, + "lighthouse-core/lib/i18n/i18n.js | columnFailingElem": { + "message": "Failing Elements" + }, "lighthouse-core/lib/i18n/i18n.js | columnLocation": { "message": "Location" }, diff --git a/lighthouse-core/lib/i18n/locales/en-XA.json b/lighthouse-core/lib/i18n/locales/en-XA.json index be01b25f67d4..f2364c91cc9e 100644 --- a/lighthouse-core/lib/i18n/locales/en-XA.json +++ b/lighthouse-core/lib/i18n/locales/en-XA.json @@ -689,9 +689,6 @@ "lighthouse-core/audits/dobetterweb/notification-on-start.js | title": { "message": "[Åvöîðš ŕéqûéšţîñĝ ţĥé ñöţîƒîçåţîöñ þéŕmîššîöñ öñ þåĝé ļöåð one two three four five six seven eight nine ten eleven twelve]" }, - "lighthouse-core/audits/dobetterweb/password-inputs-can-be-pasted-into.js | columnFailingElem": { - "message": "[Fåîļîñĝ Éļéméñţš one two]" - }, "lighthouse-core/audits/dobetterweb/password-inputs-can-be-pasted-into.js | description": { "message": "[Þŕévéñţîñĝ þåššŵöŕð þåšţîñĝ ûñðéŕmîñéš ĝööð šéçûŕîţý þöļîçý. ᐅ[ᐊĻéåŕñ möŕéᐅ](https://web.dev/password-inputs-can-be-pasted-into/)ᐊ. one two three four five six seven eight nine ten eleven twelve thirteen fourteen]" }, diff --git a/lighthouse-core/lib/i18n/locales/en-XL.json b/lighthouse-core/lib/i18n/locales/en-XL.json index ac6fadb54cb0..29437c5748ea 100644 --- a/lighthouse-core/lib/i18n/locales/en-XL.json +++ b/lighthouse-core/lib/i18n/locales/en-XL.json @@ -695,9 +695,6 @@ "lighthouse-core/audits/dobetterweb/notification-on-start.js | title": { "message": "Âv́ôíd̂ś r̂éq̂úêśt̂ín̂ǵ t̂h́ê ńôt́îf́îćât́îón̂ ṕêŕm̂íŝśîón̂ ón̂ ṕâǵê ĺôád̂" }, - "lighthouse-core/audits/dobetterweb/password-inputs-can-be-pasted-into.js | columnFailingElem": { - "message": "F̂áîĺîńĝ Él̂ém̂én̂t́ŝ" - }, "lighthouse-core/audits/dobetterweb/password-inputs-can-be-pasted-into.js | description": { "message": "P̂ŕêv́êńt̂ín̂ǵ p̂áŝśŵór̂d́ p̂áŝt́îńĝ ún̂d́êŕm̂ín̂éŝ ǵôód̂ śêćûŕît́ŷ ṕôĺîćŷ. [Ĺêár̂ń m̂ór̂é](https://web.dev/password-inputs-can-be-pasted-into/)." }, @@ -1205,9 +1202,6 @@ "lighthouse-core/audits/service-worker.js | title": { "message": "R̂éĝíŝt́êŕŝ á ŝér̂v́îćê ẃôŕk̂ér̂ t́ĥát̂ ćôńt̂ŕôĺŝ ṕâǵê án̂d́ `start_url`" }, - "lighthouse-core/audits/sized-images.js | columnFailingElem": { - "message": "F̂áîĺîńĝ Él̂ém̂én̂t́ŝ" - }, "lighthouse-core/audits/sized-images.js | description": { "message": "Âĺŵáŷś îńĉĺûd́ê ẃîd́t̂h́ âńd̂ h́êíĝh́t̂ át̂t́r̂íb̂út̂éŝ ón̂ ýôúr̂ ím̂áĝé êĺêḿêńt̂ś t̂ó r̂éd̂úĉé l̂áŷóût́ ŝh́îf́t̂ín̂ǵ âńd̂ ím̂ṕr̂óv̂é ĈĹŜ. [Ĺêár̂ń m̂ór̂é](https://web.dev/optimize-cls/#images-without-dimensions)" }, @@ -1502,6 +1496,9 @@ "lighthouse-core/lib/i18n/i18n.js | columnElement": { "message": "Êĺêḿêńt̂" }, + "lighthouse-core/lib/i18n/i18n.js | columnFailingElem": { + "message": "F̂áîĺîńĝ Él̂ém̂én̂t́ŝ" + }, "lighthouse-core/lib/i18n/i18n.js | columnLocation": { "message": "L̂óĉát̂íôń" }, diff --git a/lighthouse-core/lib/i18n/locales/es-419.json b/lighthouse-core/lib/i18n/locales/es-419.json index 8289988a4539..3b9692ded5bd 100644 --- a/lighthouse-core/lib/i18n/locales/es-419.json +++ b/lighthouse-core/lib/i18n/locales/es-419.json @@ -689,9 +689,6 @@ "lighthouse-core/audits/dobetterweb/notification-on-start.js | title": { "message": "Evita solicitar el permiso de notificaciones al cargar la página" }, - "lighthouse-core/audits/dobetterweb/password-inputs-can-be-pasted-into.js | columnFailingElem": { - "message": "Elementos con errores" - }, "lighthouse-core/audits/dobetterweb/password-inputs-can-be-pasted-into.js | description": { "message": "Evitar el pegado de contraseñas debilita las buenas políticas de seguridad. [Obtén más información](https://web.dev/password-inputs-can-be-pasted-into/)." }, diff --git a/lighthouse-core/lib/i18n/locales/es.json b/lighthouse-core/lib/i18n/locales/es.json index 2461f8dbe69f..c0e67c7f93a1 100644 --- a/lighthouse-core/lib/i18n/locales/es.json +++ b/lighthouse-core/lib/i18n/locales/es.json @@ -689,9 +689,6 @@ "lighthouse-core/audits/dobetterweb/notification-on-start.js | title": { "message": "Evita solicitar el permiso de notificación al cargar la página" }, - "lighthouse-core/audits/dobetterweb/password-inputs-can-be-pasted-into.js | columnFailingElem": { - "message": "Elementos con errores" - }, "lighthouse-core/audits/dobetterweb/password-inputs-can-be-pasted-into.js | description": { "message": "Evitar que se pueda pegar texto en el campo de contraseña debilita una buena política de seguridad. [Más información](https://web.dev/password-inputs-can-be-pasted-into/)" }, diff --git a/lighthouse-core/lib/i18n/locales/fi.json b/lighthouse-core/lib/i18n/locales/fi.json index 04179dd97419..1b35ea735676 100644 --- a/lighthouse-core/lib/i18n/locales/fi.json +++ b/lighthouse-core/lib/i18n/locales/fi.json @@ -689,9 +689,6 @@ "lighthouse-core/audits/dobetterweb/notification-on-start.js | title": { "message": "Välttää ilmoitusten käyttöoikeuden pyytämistä sivun latauksessa" }, - "lighthouse-core/audits/dobetterweb/password-inputs-can-be-pasted-into.js | columnFailingElem": { - "message": "Hylätyt elementit" - }, "lighthouse-core/audits/dobetterweb/password-inputs-can-be-pasted-into.js | description": { "message": "Salasanan liittämisen estäminen on hyvän tietoturvakäytännön vastaista. [Lue lisää](https://web.dev/password-inputs-can-be-pasted-into/)." }, diff --git a/lighthouse-core/lib/i18n/locales/fil.json b/lighthouse-core/lib/i18n/locales/fil.json index ffee34f392d5..cf1b1304fd4a 100644 --- a/lighthouse-core/lib/i18n/locales/fil.json +++ b/lighthouse-core/lib/i18n/locales/fil.json @@ -689,9 +689,6 @@ "lighthouse-core/audits/dobetterweb/notification-on-start.js | title": { "message": "Iniiwasan ang paghiling ng pahintulot sa notification sa pag-load ng page" }, - "lighthouse-core/audits/dobetterweb/password-inputs-can-be-pasted-into.js | columnFailingElem": { - "message": "Mga Hindi Nakapasang Element" - }, "lighthouse-core/audits/dobetterweb/password-inputs-can-be-pasted-into.js | description": { "message": "Pinapahina ng paghadlang sa pag-paste ng password ang magandang patakarang panseguridad. [Matuto pa](https://web.dev/password-inputs-can-be-pasted-into/)." }, diff --git a/lighthouse-core/lib/i18n/locales/fr.json b/lighthouse-core/lib/i18n/locales/fr.json index 88abf97dbccc..b794e8ea2677 100644 --- a/lighthouse-core/lib/i18n/locales/fr.json +++ b/lighthouse-core/lib/i18n/locales/fr.json @@ -689,9 +689,6 @@ "lighthouse-core/audits/dobetterweb/notification-on-start.js | title": { "message": "Aucune autorisation d'envoi de notifications n'est demandée au chargement de la page" }, - "lighthouse-core/audits/dobetterweb/password-inputs-can-be-pasted-into.js | columnFailingElem": { - "message": "Éléments non conformes" - }, "lighthouse-core/audits/dobetterweb/password-inputs-can-be-pasted-into.js | description": { "message": "Empêcher la copie de contenu dans les champs de mot de passe nuit aux règles de sécurité. [En savoir plus](https://web.dev/password-inputs-can-be-pasted-into/)" }, diff --git a/lighthouse-core/lib/i18n/locales/he.json b/lighthouse-core/lib/i18n/locales/he.json index 7e8616d9510f..6ab371f64835 100644 --- a/lighthouse-core/lib/i18n/locales/he.json +++ b/lighthouse-core/lib/i18n/locales/he.json @@ -689,9 +689,6 @@ "lighthouse-core/audits/dobetterweb/notification-on-start.js | title": { "message": "הדף לא מבקש הרשאה להתראות במהלך טעינת הדף" }, - "lighthouse-core/audits/dobetterweb/password-inputs-can-be-pasted-into.js | columnFailingElem": { - "message": "רכיבים שנכשלו בבדיקה" - }, "lighthouse-core/audits/dobetterweb/password-inputs-can-be-pasted-into.js | description": { "message": "מניעה של הדבקת סיסמאות פוגעת ביכולת לקיים מדיניות אבטחה טובה. [מידע נוסף](https://web.dev/password-inputs-can-be-pasted-into/)." }, diff --git a/lighthouse-core/lib/i18n/locales/hi.json b/lighthouse-core/lib/i18n/locales/hi.json index bc8c575c9e5e..efde293cfc76 100644 --- a/lighthouse-core/lib/i18n/locales/hi.json +++ b/lighthouse-core/lib/i18n/locales/hi.json @@ -689,9 +689,6 @@ "lighthouse-core/audits/dobetterweb/notification-on-start.js | title": { "message": "पेज लोड पर सूचना भेजने की मंज़ूरी का अनुरोध नहीं किया जाता" }, - "lighthouse-core/audits/dobetterweb/password-inputs-can-be-pasted-into.js | columnFailingElem": { - "message": "फ़ेल होने वाले एलिमेंट" - }, "lighthouse-core/audits/dobetterweb/password-inputs-can-be-pasted-into.js | description": { "message": "पासवर्ड वाले फ़ील्ड में कुछ कॉपी करके चिपकाने की सुविधा न लागू करने का मतलब है एक अच्छी सुरक्षा नीति को कमज़ोर समझना. [ज़्यादा जानें](https://web.dev/password-inputs-can-be-pasted-into/)." }, diff --git a/lighthouse-core/lib/i18n/locales/hr.json b/lighthouse-core/lib/i18n/locales/hr.json index 5aadaeed956a..677c8baf7356 100644 --- a/lighthouse-core/lib/i18n/locales/hr.json +++ b/lighthouse-core/lib/i18n/locales/hr.json @@ -689,9 +689,6 @@ "lighthouse-core/audits/dobetterweb/notification-on-start.js | title": { "message": "Izbjegava traženje dopuštenja za obavještavanje pri učitavanju stranice" }, - "lighthouse-core/audits/dobetterweb/password-inputs-can-be-pasted-into.js | columnFailingElem": { - "message": "Elementi koji nisu prošli provjeru" - }, "lighthouse-core/audits/dobetterweb/password-inputs-can-be-pasted-into.js | description": { "message": "Sprječavanje lijepljenja zaporke narušava kvalitetu dobrih sigurnosnih pravila. [Saznajte više](https://web.dev/password-inputs-can-be-pasted-into/)." }, diff --git a/lighthouse-core/lib/i18n/locales/hu.json b/lighthouse-core/lib/i18n/locales/hu.json index b0d5207b45ed..14c1c19cdae3 100644 --- a/lighthouse-core/lib/i18n/locales/hu.json +++ b/lighthouse-core/lib/i18n/locales/hu.json @@ -689,9 +689,6 @@ "lighthouse-core/audits/dobetterweb/notification-on-start.js | title": { "message": "Kerüli az értesítésekre vonatkozó engedély kérését oldalbetöltéskor" }, - "lighthouse-core/audits/dobetterweb/password-inputs-can-be-pasted-into.js | columnFailingElem": { - "message": "Hibás elemek" - }, "lighthouse-core/audits/dobetterweb/password-inputs-can-be-pasted-into.js | description": { "message": "A jelszóbeillesztés megakadályozása rossz biztonsági gyakorlat. [További információ](https://web.dev/password-inputs-can-be-pasted-into/)." }, diff --git a/lighthouse-core/lib/i18n/locales/id.json b/lighthouse-core/lib/i18n/locales/id.json index 450f9d2d7b6a..cc1c4d609c72 100644 --- a/lighthouse-core/lib/i18n/locales/id.json +++ b/lighthouse-core/lib/i18n/locales/id.json @@ -689,9 +689,6 @@ "lighthouse-core/audits/dobetterweb/notification-on-start.js | title": { "message": "Menghindari meminta izin notifikasi pada pemuatan halaman" }, - "lighthouse-core/audits/dobetterweb/password-inputs-can-be-pasted-into.js | columnFailingElem": { - "message": "Elemen yang Gagal" - }, "lighthouse-core/audits/dobetterweb/password-inputs-can-be-pasted-into.js | description": { "message": "Mencegah menempelkan sandi akan merusak kebijakan keamanan yang baik. [Pelajari lebih lanjut](https://web.dev/password-inputs-can-be-pasted-into/)." }, diff --git a/lighthouse-core/lib/i18n/locales/it.json b/lighthouse-core/lib/i18n/locales/it.json index 846ae9cf32e8..dc175ef36e59 100644 --- a/lighthouse-core/lib/i18n/locales/it.json +++ b/lighthouse-core/lib/i18n/locales/it.json @@ -689,9 +689,6 @@ "lighthouse-core/audits/dobetterweb/notification-on-start.js | title": { "message": "Evita di chiedere l'autorizzazione di accesso alle notifiche durante il caricamento della pagina" }, - "lighthouse-core/audits/dobetterweb/password-inputs-can-be-pasted-into.js | columnFailingElem": { - "message": "Elementi che non consentono di incollare" - }, "lighthouse-core/audits/dobetterweb/password-inputs-can-be-pasted-into.js | description": { "message": "Impedire di incollare le password pregiudica l'efficacia delle norme di sicurezza. [Ulteriori informazioni](https://web.dev/password-inputs-can-be-pasted-into/)." }, diff --git a/lighthouse-core/lib/i18n/locales/ja.json b/lighthouse-core/lib/i18n/locales/ja.json index d0c2e06aed01..a1fcb8223f8e 100644 --- a/lighthouse-core/lib/i18n/locales/ja.json +++ b/lighthouse-core/lib/i18n/locales/ja.json @@ -689,9 +689,6 @@ "lighthouse-core/audits/dobetterweb/notification-on-start.js | title": { "message": "ページの読み込み時に通知の許可はリクエストされません" }, - "lighthouse-core/audits/dobetterweb/password-inputs-can-be-pasted-into.js | columnFailingElem": { - "message": "問題のある要素" - }, "lighthouse-core/audits/dobetterweb/password-inputs-can-be-pasted-into.js | description": { "message": "パスワードの貼り付けを禁止すると、良好なセキュリティ ポリシーが損なわれます。[詳細](https://web.dev/password-inputs-can-be-pasted-into/)" }, diff --git a/lighthouse-core/lib/i18n/locales/ko.json b/lighthouse-core/lib/i18n/locales/ko.json index 24013b4bfe0f..2f224c8323f2 100644 --- a/lighthouse-core/lib/i18n/locales/ko.json +++ b/lighthouse-core/lib/i18n/locales/ko.json @@ -689,9 +689,6 @@ "lighthouse-core/audits/dobetterweb/notification-on-start.js | title": { "message": "페이지 로드 시 알림 권한 요청 방지하기" }, - "lighthouse-core/audits/dobetterweb/password-inputs-can-be-pasted-into.js | columnFailingElem": { - "message": "통과하지 못한 요소" - }, "lighthouse-core/audits/dobetterweb/password-inputs-can-be-pasted-into.js | description": { "message": "비밀번호 붙여넣기 방지로 인해 타당한 보안 정책이 저해됩니다. [자세히 알아보기](https://web.dev/password-inputs-can-be-pasted-into/)" }, diff --git a/lighthouse-core/lib/i18n/locales/lt.json b/lighthouse-core/lib/i18n/locales/lt.json index ac1106c5838a..bcb969f26b36 100644 --- a/lighthouse-core/lib/i18n/locales/lt.json +++ b/lighthouse-core/lib/i18n/locales/lt.json @@ -689,9 +689,6 @@ "lighthouse-core/audits/dobetterweb/notification-on-start.js | title": { "message": "Įkeliant puslapį vengiama pateikti užklausą dėl pranešimų leidimo" }, - "lighthouse-core/audits/dobetterweb/password-inputs-can-be-pasted-into.js | columnFailingElem": { - "message": "Netinkami elementai" - }, "lighthouse-core/audits/dobetterweb/password-inputs-can-be-pasted-into.js | description": { "message": "Neleidžiant įklijuoti slaptažodžių, pažeidžiama tinkamos saugos politika. [Sužinokite daugiau](https://web.dev/password-inputs-can-be-pasted-into/)." }, diff --git a/lighthouse-core/lib/i18n/locales/lv.json b/lighthouse-core/lib/i18n/locales/lv.json index e4abe0bbedd2..2b480a796d0e 100644 --- a/lighthouse-core/lib/i18n/locales/lv.json +++ b/lighthouse-core/lib/i18n/locales/lv.json @@ -689,9 +689,6 @@ "lighthouse-core/audits/dobetterweb/notification-on-start.js | title": { "message": "Netiek pieprasīta paziņojumu atļauja lapas ielādei" }, - "lighthouse-core/audits/dobetterweb/password-inputs-can-be-pasted-into.js | columnFailingElem": { - "message": "Nederīgi elementi" - }, "lighthouse-core/audits/dobetterweb/password-inputs-can-be-pasted-into.js | description": { "message": "Paroles ielīmēšanas novēršana neatbilst labai drošības politikai. [Uzziniet vairāk](https://web.dev/password-inputs-can-be-pasted-into/)." }, diff --git a/lighthouse-core/lib/i18n/locales/nl.json b/lighthouse-core/lib/i18n/locales/nl.json index ef799b674d81..6d5af5ad586a 100644 --- a/lighthouse-core/lib/i18n/locales/nl.json +++ b/lighthouse-core/lib/i18n/locales/nl.json @@ -689,9 +689,6 @@ "lighthouse-core/audits/dobetterweb/notification-on-start.js | title": { "message": "Vermijdt verzoeken om de meldingsrechten bij laden van pagina" }, - "lighthouse-core/audits/dobetterweb/password-inputs-can-be-pasted-into.js | columnFailingElem": { - "message": "Mislukte elementen" - }, "lighthouse-core/audits/dobetterweb/password-inputs-can-be-pasted-into.js | description": { "message": "Verhindering van het plakken van wachtwoorden ondermijnt een goed beveiligingsbeleid. [Meer informatie](https://web.dev/password-inputs-can-be-pasted-into/)" }, diff --git a/lighthouse-core/lib/i18n/locales/no.json b/lighthouse-core/lib/i18n/locales/no.json index e4152664e6c3..19d2647c1aee 100644 --- a/lighthouse-core/lib/i18n/locales/no.json +++ b/lighthouse-core/lib/i18n/locales/no.json @@ -689,9 +689,6 @@ "lighthouse-core/audits/dobetterweb/notification-on-start.js | title": { "message": "Unngår å spørre om varseltillatelsen ved sideinnlasting" }, - "lighthouse-core/audits/dobetterweb/password-inputs-can-be-pasted-into.js | columnFailingElem": { - "message": "Elementer som ikke besto kontrollen" - }, "lighthouse-core/audits/dobetterweb/password-inputs-can-be-pasted-into.js | description": { "message": "Å hindre brukere i å lime inn passord underminerer gode retningslinjer for sikkerhet. [Finn ut mer](https://web.dev/password-inputs-can-be-pasted-into/)." }, diff --git a/lighthouse-core/lib/i18n/locales/pl.json b/lighthouse-core/lib/i18n/locales/pl.json index 0ff0ce1c1bf3..388a3a7dbaea 100644 --- a/lighthouse-core/lib/i18n/locales/pl.json +++ b/lighthouse-core/lib/i18n/locales/pl.json @@ -689,9 +689,6 @@ "lighthouse-core/audits/dobetterweb/notification-on-start.js | title": { "message": "Nie pyta o zgodę na wyświetlanie powiadomień podczas wczytywania strony" }, - "lighthouse-core/audits/dobetterweb/password-inputs-can-be-pasted-into.js | columnFailingElem": { - "message": "Nieprawidłowe elementy" - }, "lighthouse-core/audits/dobetterweb/password-inputs-can-be-pasted-into.js | description": { "message": "Uniemożliwianie wklejania haseł jest sprzeczne z dobrymi zasadami bezpieczeństwa. [Więcej informacji](https://web.dev/password-inputs-can-be-pasted-into/)" }, diff --git a/lighthouse-core/lib/i18n/locales/pt-PT.json b/lighthouse-core/lib/i18n/locales/pt-PT.json index 73bb73666c32..4b8531c1fa9a 100644 --- a/lighthouse-core/lib/i18n/locales/pt-PT.json +++ b/lighthouse-core/lib/i18n/locales/pt-PT.json @@ -689,9 +689,6 @@ "lighthouse-core/audits/dobetterweb/notification-on-start.js | title": { "message": "Evita a solicitação da autorização de notificações no carregamento da página" }, - "lighthouse-core/audits/dobetterweb/password-inputs-can-be-pasted-into.js | columnFailingElem": { - "message": "Elementos reprovados" - }, "lighthouse-core/audits/dobetterweb/password-inputs-can-be-pasted-into.js | description": { "message": "Impedir a colagem de palavras-passe compromete o cumprimento de uma política de segurança adequada. [Saiba mais](https://web.dev/password-inputs-can-be-pasted-into/)." }, diff --git a/lighthouse-core/lib/i18n/locales/pt.json b/lighthouse-core/lib/i18n/locales/pt.json index 44ef00d9eca8..85ad62f621f4 100644 --- a/lighthouse-core/lib/i18n/locales/pt.json +++ b/lighthouse-core/lib/i18n/locales/pt.json @@ -689,9 +689,6 @@ "lighthouse-core/audits/dobetterweb/notification-on-start.js | title": { "message": "Evita o pedido da permissão de notificação no carregamento de página" }, - "lighthouse-core/audits/dobetterweb/password-inputs-can-be-pasted-into.js | columnFailingElem": { - "message": "Elementos com falha" - }, "lighthouse-core/audits/dobetterweb/password-inputs-can-be-pasted-into.js | description": { "message": "Impedir a colagem da senha prejudica a política de boa segurança. [Saiba mais](https://web.dev/password-inputs-can-be-pasted-into/)." }, diff --git a/lighthouse-core/lib/i18n/locales/ro.json b/lighthouse-core/lib/i18n/locales/ro.json index bd834e3b6936..eee0a0a57000 100644 --- a/lighthouse-core/lib/i18n/locales/ro.json +++ b/lighthouse-core/lib/i18n/locales/ro.json @@ -689,9 +689,6 @@ "lighthouse-core/audits/dobetterweb/notification-on-start.js | title": { "message": "Evită solicitarea permisiunii de notificare la încărcarea paginii" }, - "lighthouse-core/audits/dobetterweb/password-inputs-can-be-pasted-into.js | columnFailingElem": { - "message": "Elemente cu probleme" - }, "lighthouse-core/audits/dobetterweb/password-inputs-can-be-pasted-into.js | description": { "message": "Împiedicarea inserării parolelor subminează buna politică de securitate. [Află mai multe](https://web.dev/password-inputs-can-be-pasted-into/)." }, diff --git a/lighthouse-core/lib/i18n/locales/ru.json b/lighthouse-core/lib/i18n/locales/ru.json index 1eff964dfa31..308c77f40acd 100644 --- a/lighthouse-core/lib/i18n/locales/ru.json +++ b/lighthouse-core/lib/i18n/locales/ru.json @@ -689,9 +689,6 @@ "lighthouse-core/audits/dobetterweb/notification-on-start.js | title": { "message": "Разрешение на отправку уведомлений не запрашивается при загрузке страницы" }, - "lighthouse-core/audits/dobetterweb/password-inputs-can-be-pasted-into.js | columnFailingElem": { - "message": "Элементы, запрещающие вставку из буфера обмена" - }, "lighthouse-core/audits/dobetterweb/password-inputs-can-be-pasted-into.js | description": { "message": "Запрет на вставку пароля из буфера обмена отрицательно сказывается на безопасности пользователей. [Подробнее…](https://web.dev/password-inputs-can-be-pasted-into/)" }, diff --git a/lighthouse-core/lib/i18n/locales/sk.json b/lighthouse-core/lib/i18n/locales/sk.json index 549fe445a536..22521b615185 100644 --- a/lighthouse-core/lib/i18n/locales/sk.json +++ b/lighthouse-core/lib/i18n/locales/sk.json @@ -689,9 +689,6 @@ "lighthouse-core/audits/dobetterweb/notification-on-start.js | title": { "message": "Nepožaduje povolenie na upozornenie pri načítaní stránky" }, - "lighthouse-core/audits/dobetterweb/password-inputs-can-be-pasted-into.js | columnFailingElem": { - "message": "Prvky s chybami" - }, "lighthouse-core/audits/dobetterweb/password-inputs-can-be-pasted-into.js | description": { "message": "Ak zakážete prilepovanie hesiel, zhoršíte kvalitu zabezpečenia. [Ďalšie informácie](https://web.dev/password-inputs-can-be-pasted-into/)" }, diff --git a/lighthouse-core/lib/i18n/locales/sl.json b/lighthouse-core/lib/i18n/locales/sl.json index 792e79e27ff8..c9b84355a602 100644 --- a/lighthouse-core/lib/i18n/locales/sl.json +++ b/lighthouse-core/lib/i18n/locales/sl.json @@ -689,9 +689,6 @@ "lighthouse-core/audits/dobetterweb/notification-on-start.js | title": { "message": "Ne zahteva dovoljenja za obvestila pri nalaganju strani" }, - "lighthouse-core/audits/dobetterweb/password-inputs-can-be-pasted-into.js | columnFailingElem": { - "message": "Neuspešni elementi" - }, "lighthouse-core/audits/dobetterweb/password-inputs-can-be-pasted-into.js | description": { "message": "Preprečevanje lepljenja gesel omeji dober pravilnik o varnosti. [Več o tem](https://web.dev/password-inputs-can-be-pasted-into/)." }, diff --git a/lighthouse-core/lib/i18n/locales/sr-Latn.json b/lighthouse-core/lib/i18n/locales/sr-Latn.json index 1785683d494d..dcbbb34e936e 100644 --- a/lighthouse-core/lib/i18n/locales/sr-Latn.json +++ b/lighthouse-core/lib/i18n/locales/sr-Latn.json @@ -689,9 +689,6 @@ "lighthouse-core/audits/dobetterweb/notification-on-start.js | title": { "message": "Izbegavajte traženje dozvole za obaveštenja pri učitavanju stranice" }, - "lighthouse-core/audits/dobetterweb/password-inputs-can-be-pasted-into.js | columnFailingElem": { - "message": "Elementi koji nisu prošli proveru" - }, "lighthouse-core/audits/dobetterweb/password-inputs-can-be-pasted-into.js | description": { "message": "Sprečavanje lepljenja lozinke narušava dobre smernice za bezbednost. [Saznajte više](https://web.dev/password-inputs-can-be-pasted-into/)." }, diff --git a/lighthouse-core/lib/i18n/locales/sr.json b/lighthouse-core/lib/i18n/locales/sr.json index c8f5f99ad3f0..6b80288ad3a7 100644 --- a/lighthouse-core/lib/i18n/locales/sr.json +++ b/lighthouse-core/lib/i18n/locales/sr.json @@ -689,9 +689,6 @@ "lighthouse-core/audits/dobetterweb/notification-on-start.js | title": { "message": "Избегавајте тражење дозволе за обавештења при учитавању странице" }, - "lighthouse-core/audits/dobetterweb/password-inputs-can-be-pasted-into.js | columnFailingElem": { - "message": "Елементи који нису прошли проверу" - }, "lighthouse-core/audits/dobetterweb/password-inputs-can-be-pasted-into.js | description": { "message": "Спречавање лепљења лозинке нарушава добре смернице за безбедност. [Сазнајте више](https://web.dev/password-inputs-can-be-pasted-into/)." }, diff --git a/lighthouse-core/lib/i18n/locales/sv.json b/lighthouse-core/lib/i18n/locales/sv.json index 9c47664ef319..dba291938ce5 100644 --- a/lighthouse-core/lib/i18n/locales/sv.json +++ b/lighthouse-core/lib/i18n/locales/sv.json @@ -689,9 +689,6 @@ "lighthouse-core/audits/dobetterweb/notification-on-start.js | title": { "message": "Undviker att begära aviseringsbehörighet vid sidinläsning" }, - "lighthouse-core/audits/dobetterweb/password-inputs-can-be-pasted-into.js | columnFailingElem": { - "message": "Element med fel" - }, "lighthouse-core/audits/dobetterweb/password-inputs-can-be-pasted-into.js | description": { "message": "Att förhindra att lösenord klistras in underminerar en bra säkerhetspolicy. [Läs mer](https://web.dev/password-inputs-can-be-pasted-into/)." }, diff --git a/lighthouse-core/lib/i18n/locales/ta.json b/lighthouse-core/lib/i18n/locales/ta.json index c8d9229ac2a5..53c7f02f1f64 100644 --- a/lighthouse-core/lib/i18n/locales/ta.json +++ b/lighthouse-core/lib/i18n/locales/ta.json @@ -689,9 +689,6 @@ "lighthouse-core/audits/dobetterweb/notification-on-start.js | title": { "message": "பக்கம் ஏற்றப்படும்போது அதுகுறித்த அறிவிப்பைத் தெரிந்துகொள்வதற்கான அனுமதியைக் கோராது" }, - "lighthouse-core/audits/dobetterweb/password-inputs-can-be-pasted-into.js | columnFailingElem": { - "message": "தோல்வியுறும் உறுப்புகள்" - }, "lighthouse-core/audits/dobetterweb/password-inputs-can-be-pasted-into.js | description": { "message": "கடவுச்சொல்லை ஒட்டுவதைத் தடுப்பதால் நல்ல பாதுகாப்புக் கொள்கை பாதிக்கப்படுகிறது. [மேலும் அறிக](https://web.dev/password-inputs-can-be-pasted-into/)." }, diff --git a/lighthouse-core/lib/i18n/locales/te.json b/lighthouse-core/lib/i18n/locales/te.json index 03cd7c227ccb..58f5b140613e 100644 --- a/lighthouse-core/lib/i18n/locales/te.json +++ b/lighthouse-core/lib/i18n/locales/te.json @@ -689,9 +689,6 @@ "lighthouse-core/audits/dobetterweb/notification-on-start.js | title": { "message": "పేజీ లోడ్ సమయంలో నోటిఫికేషన్ అనుమతిని అభ్యర్థించడం నివారిస్తుంది" }, - "lighthouse-core/audits/dobetterweb/password-inputs-can-be-pasted-into.js | columnFailingElem": { - "message": "విఫలం అవుతున్న మూలకాలు" - }, "lighthouse-core/audits/dobetterweb/password-inputs-can-be-pasted-into.js | description": { "message": "పాస్‌వర్డ్‌లో అతికించే చర్యను నిరోధించడం వలన మంచి భద్రతా విధానానికి ఆటంకం ఏర్పడుతుంది. [మరింత తెలుసుకోండి](https://web.dev/password-inputs-can-be-pasted-into/)." }, diff --git a/lighthouse-core/lib/i18n/locales/th.json b/lighthouse-core/lib/i18n/locales/th.json index e6891366121e..94f3c5be5b19 100644 --- a/lighthouse-core/lib/i18n/locales/th.json +++ b/lighthouse-core/lib/i18n/locales/th.json @@ -689,9 +689,6 @@ "lighthouse-core/audits/dobetterweb/notification-on-start.js | title": { "message": "หลีกเลี่ยงการขอสิทธิ์การแจ้งเตือนในการโหลดหน้าเว็บ" }, - "lighthouse-core/audits/dobetterweb/password-inputs-can-be-pasted-into.js | columnFailingElem": { - "message": "องค์ประกอบที่ไม่ผ่านการตรวจสอบ" - }, "lighthouse-core/audits/dobetterweb/password-inputs-can-be-pasted-into.js | description": { "message": "การป้องกันการวางรหัสผ่านทำให้นโยบายความปลอดภัยที่ดีอ่อนแอลง [ดูข้อมูลเพิ่มเติม](https://web.dev/password-inputs-can-be-pasted-into/)" }, diff --git a/lighthouse-core/lib/i18n/locales/tr.json b/lighthouse-core/lib/i18n/locales/tr.json index 579013747940..515f15ee5e15 100644 --- a/lighthouse-core/lib/i18n/locales/tr.json +++ b/lighthouse-core/lib/i18n/locales/tr.json @@ -689,9 +689,6 @@ "lighthouse-core/audits/dobetterweb/notification-on-start.js | title": { "message": "Sayfa yüklemede bildirim izni istemiyor" }, - "lighthouse-core/audits/dobetterweb/password-inputs-can-be-pasted-into.js | columnFailingElem": { - "message": "Başarısız Öğeler" - }, "lighthouse-core/audits/dobetterweb/password-inputs-can-be-pasted-into.js | description": { "message": "Şifre yapıştırmanın engellenmesi, iyi güvenlik politikasına zarar verir. [Daha fazla bilgi](https://web.dev/password-inputs-can-be-pasted-into/)." }, diff --git a/lighthouse-core/lib/i18n/locales/uk.json b/lighthouse-core/lib/i18n/locales/uk.json index 82c839ee609b..f7dfc73b872c 100644 --- a/lighthouse-core/lib/i18n/locales/uk.json +++ b/lighthouse-core/lib/i18n/locales/uk.json @@ -689,9 +689,6 @@ "lighthouse-core/audits/dobetterweb/notification-on-start.js | title": { "message": "Уникає надсилання запитів на показ сповіщень під час завантаження сторінки" }, - "lighthouse-core/audits/dobetterweb/password-inputs-can-be-pasted-into.js | columnFailingElem": { - "message": "Відхилені елементи" - }, "lighthouse-core/audits/dobetterweb/password-inputs-can-be-pasted-into.js | description": { "message": "Якщо заборонити вставляти пароль, це порушить правила щодо високого рівня безпеки. [Докладніше](https://web.dev/password-inputs-can-be-pasted-into/)." }, diff --git a/lighthouse-core/lib/i18n/locales/vi.json b/lighthouse-core/lib/i18n/locales/vi.json index 6ab5a1a016c4..72961627a707 100644 --- a/lighthouse-core/lib/i18n/locales/vi.json +++ b/lighthouse-core/lib/i18n/locales/vi.json @@ -689,9 +689,6 @@ "lighthouse-core/audits/dobetterweb/notification-on-start.js | title": { "message": "Tránh yêu cầu quyền truy cập thông báo khi tải trang" }, - "lighthouse-core/audits/dobetterweb/password-inputs-can-be-pasted-into.js | columnFailingElem": { - "message": "Các phần tử không đạt" - }, "lighthouse-core/audits/dobetterweb/password-inputs-can-be-pasted-into.js | description": { "message": "Việc ngăn dán mật khẩu sẽ làm giảm tác dụng của chính sách bảo mật hiệu quả. [Tìm hiểu thêm](https://web.dev/password-inputs-can-be-pasted-into/)." }, diff --git a/lighthouse-core/lib/i18n/locales/zh-HK.json b/lighthouse-core/lib/i18n/locales/zh-HK.json index 1cdf6b6caab8..27602dc3d1d3 100644 --- a/lighthouse-core/lib/i18n/locales/zh-HK.json +++ b/lighthouse-core/lib/i18n/locales/zh-HK.json @@ -689,9 +689,6 @@ "lighthouse-core/audits/dobetterweb/notification-on-start.js | title": { "message": "避免在載入網頁時要求使用者允許網站顯示通知" }, - "lighthouse-core/audits/dobetterweb/password-inputs-can-be-pasted-into.js | columnFailingElem": { - "message": "審核失敗的元素" - }, "lighthouse-core/audits/dobetterweb/password-inputs-can-be-pasted-into.js | description": { "message": "禁止貼上密碼會對安全政策造成不良影響。[瞭解詳情](https://web.dev/password-inputs-can-be-pasted-into/)。" }, diff --git a/lighthouse-core/lib/i18n/locales/zh-TW.json b/lighthouse-core/lib/i18n/locales/zh-TW.json index 8c076befa770..f1a00abd6333 100644 --- a/lighthouse-core/lib/i18n/locales/zh-TW.json +++ b/lighthouse-core/lib/i18n/locales/zh-TW.json @@ -689,9 +689,6 @@ "lighthouse-core/audits/dobetterweb/notification-on-start.js | title": { "message": "避免在載入網頁時要求使用者允許網站顯示通知" }, - "lighthouse-core/audits/dobetterweb/password-inputs-can-be-pasted-into.js | columnFailingElem": { - "message": "未通過稽核的元素" - }, "lighthouse-core/audits/dobetterweb/password-inputs-can-be-pasted-into.js | description": { "message": "禁止貼上密碼會對安全性政策造成不良影響[瞭解詳情](https://web.dev/password-inputs-can-be-pasted-into/)。" }, diff --git a/lighthouse-core/lib/i18n/locales/zh.json b/lighthouse-core/lib/i18n/locales/zh.json index f75753c88874..538ae3444d78 100644 --- a/lighthouse-core/lib/i18n/locales/zh.json +++ b/lighthouse-core/lib/i18n/locales/zh.json @@ -689,9 +689,6 @@ "lighthouse-core/audits/dobetterweb/notification-on-start.js | title": { "message": "避免在网页加载时请求通知权限" }, - "lighthouse-core/audits/dobetterweb/password-inputs-can-be-pasted-into.js | columnFailingElem": { - "message": "失败的元素" - }, "lighthouse-core/audits/dobetterweb/password-inputs-can-be-pasted-into.js | description": { "message": "阻止密码粘贴,会破坏良好的安全政策。[了解详情](https://web.dev/password-inputs-can-be-pasted-into/)。" }, diff --git a/lighthouse-core/scripts/i18n/collect-strings.js b/lighthouse-core/scripts/i18n/collect-strings.js index 56f05c61975f..0e07b85e8757 100644 --- a/lighthouse-core/scripts/i18n/collect-strings.js +++ b/lighthouse-core/scripts/i18n/collect-strings.js @@ -570,7 +570,7 @@ if (require.main === module) { if ((collisions) > 0) { console.log(`MEANING COLLISION: ${collisions} string(s) have the same content.`); - assert.equal(collisions, 17, `The number of duplicate strings have changed, update this assertion if that is expected, or reword strings. Collisions: ${collisionStrings}`); + assert.equal(collisions, 16, `The number of duplicate strings have changed, update this assertion if that is expected, or reword strings. Collisions: ${collisionStrings}`); } const strings = {...coreStrings, ...stackPackStrings}; diff --git a/lighthouse-core/test/audits/sized-images-test.js b/lighthouse-core/test/audits/sized-images-test.js new file mode 100644 index 000000000000..46e63496ff62 --- /dev/null +++ b/lighthouse-core/test/audits/sized-images-test.js @@ -0,0 +1,166 @@ +/** + * @license Copyright 2020 The Lighthouse Authors. All Rights Reserved. + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 + * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. + */ +'use strict'; + +const SizedImagesAudit = require('../../audits/sized-images.js'); + +/* eslint-env jest */ + +function generateImage(props, src = 'https://google.com/logo.png', isCss = false, + path = '1,HTML,1,BODY,1,IMG', selector = 'body > img', nodeLabel = 'img', + snippet = '') { + const image = {src, isCss, path, selector, nodeLabel, snippet}; + Object.assign(image, props); + return image; +} + +describe('Sized images audit', () => { + function testImage(condition, data) { + const description = `handles when an image ${condition}`; + it(description, async () => { + const result = SizedImagesAudit.audit({ + ImageElements: [ + generateImage( + data.props + ), + ], + }); + expect(result.score).toEqual(data.score); + }); + } + + testImage('is a css image', { + score: 1, + props: { + isCss: true, + attributeWidth: '', + attributeHeight: '', + }, + }); + + describe('has empty', () => { + testImage('has empty width attribute', { + score: 0, + props: { + attributeWidth: '', + attributeHeight: '100', + }, + }); + + testImage('has empty height attribute', { + score: 0, + props: { + attributeWidth: '100', + attributeHeight: '', + }, + }); + + testImage('has empty width and height attributes', { + score: 0, + props: { + attributeWidth: '', + attributeHeight: '', + }, + }); + }); + + describe('has invalid', () => { + testImage('has invalid width attribute', { + score: 0, + props: { + attributeWidth: '-200', + attributeHeight: '100', + }, + }); + + testImage('has invalid height attribute', { + score: 0, + props: { + attributeWidth: '100', + attributeHeight: '300.5', + }, + }); + + testImage('has invalid width and height attributes', { + score: 0, + props: { + attributeWidth: '0', + attributeHeight: '100/2', + }, + }); + }); + + testImage('has valid width and height attributes', { + score: 1, + props: { + attributeWidth: '100', + attributeHeight: '100', + }, + }); + + it('is not applicable when there are no images', async () => { + const result = SizedImagesAudit.audit({ + ImageElements: [], + }); + expect(result.notApplicable).toEqual(true); + expect(result.score).toEqual(1); + }); + + it('can return multiple unsized images', async () => { + const result = SizedImagesAudit.audit({ + ImageElements: [ + generateImage( + { + attributeWidth: '100', + attributeHeight: '150', + }, + 'image1.png' + ), + generateImage( + { + attributeWidth: '', + attributeHeight: '', + }, + 'image2.png' + ), + generateImage( + { + attributeWidth: '200', + attributeHeight: '75', + }, + 'image3.png' + ), + ], + }); + expect(result.score).toEqual(0); + expect(result.details.items).toHaveLength(2); + const srcs = result.details.items.map(item => item.url); + expect(srcs).toEqual(['image1.png', 'image3.png']); + }); +}); + +describe('Size attribute validity check', () => { + it('fails on non-numeric characters', async () => { + expect(SizedImagesAudit.isValid('zero')).toEqual(false); + expect(SizedImagesAudit.isValid('1002$')).toEqual(false); + expect(SizedImagesAudit.isValid('s-5')).toEqual(false); + expect(SizedImagesAudit.isValid('3,000')).toEqual(false); + expect(SizedImagesAudit.isValid('100.0')).toEqual(false); + expect(SizedImagesAudit.isValid('2/3')).toEqual(false); + expect(SizedImagesAudit.isValid('-2020')).toEqual(false); + expect(SizedImagesAudit.isValid('+2020')).toEqual(false); + }); + + it('fails on zero input', async () => { + expect(SizedImagesAudit.isValid('0')).toEqual(false); + }); + + it('passes on non-zero non-negative integer input', async () => { + expect(SizedImagesAudit.isValid('1')).toEqual(true); + expect(SizedImagesAudit.isValid('250')).toEqual(true); + expect(SizedImagesAudit.isValid('4000000')).toEqual(true); + }); +}); diff --git a/types/artifacts.d.ts b/types/artifacts.d.ts index ecae0e5d66a2..d7535e56b1f8 100644 --- a/types/artifacts.d.ts +++ b/types/artifacts.d.ts @@ -399,9 +399,9 @@ declare global { naturalWidth: number; /** The natural height of the underlying image, uses img.naturalHeight. See https://codepen.io/patrickhulce/pen/PXvQbM for examples. */ naturalHeight: number; - /** The attribute width of the image, ... */ + /** The raw width attribute of the image element. CSS images will be set to the empty string. */ attributeWidth: string; - /** The attribute height of the image, ... */ + /** The raw height attribute of the image element. CSS images will be set to the empty string. */ attributeHeight: string; /** The BoundingClientRect of the element. */ clientRect: { From b5c3679a8d0976c0ed39ce91d0f3dc269cd8ba11 Mon Sep 17 00:00:00 2001 From: Lemuel Cardenas-arriaga Date: Fri, 17 Jul 2020 11:34:27 -0700 Subject: [PATCH 22/68] fixed testing bugs, updated sample json --- .../test/audits/sized-images-test.js | 18 ++-- lighthouse-core/test/results/sample_v2.json | 90 ++++++++++++++++--- 2 files changed, 86 insertions(+), 22 deletions(-) diff --git a/lighthouse-core/test/audits/sized-images-test.js b/lighthouse-core/test/audits/sized-images-test.js index 46e63496ff62..d73b5ab7fe14 100644 --- a/lighthouse-core/test/audits/sized-images-test.js +++ b/lighthouse-core/test/audits/sized-images-test.js @@ -21,7 +21,7 @@ describe('Sized images audit', () => { function testImage(condition, data) { const description = `handles when an image ${condition}`; it(description, async () => { - const result = SizedImagesAudit.audit({ + const result = await SizedImagesAudit.audit({ ImageElements: [ generateImage( data.props @@ -102,7 +102,7 @@ describe('Sized images audit', () => { }); it('is not applicable when there are no images', async () => { - const result = SizedImagesAudit.audit({ + const result = await SizedImagesAudit.audit({ ImageElements: [], }); expect(result.notApplicable).toEqual(true); @@ -110,26 +110,26 @@ describe('Sized images audit', () => { }); it('can return multiple unsized images', async () => { - const result = SizedImagesAudit.audit({ + const result = await SizedImagesAudit.audit({ ImageElements: [ generateImage( { - attributeWidth: '100', - attributeHeight: '150', + attributeWidth: '', + attributeHeight: '', }, 'image1.png' ), generateImage( { - attributeWidth: '', - attributeHeight: '', + attributeWidth: '100', + attributeHeight: '150', }, 'image2.png' ), generateImage( { - attributeWidth: '200', - attributeHeight: '75', + attributeWidth: '', + attributeHeight: '', }, 'image3.png' ), diff --git a/lighthouse-core/test/results/sample_v2.json b/lighthouse-core/test/results/sample_v2.json index 9293ae14fcba..bdf32d527e9d 100644 --- a/lighthouse-core/test/results/sample_v2.json +++ b/lighthouse-core/test/results/sample_v2.json @@ -679,6 +679,53 @@ ] } }, + "sized-images": { + "id": "sized-images", + "title": "Image elements do not have `width` and `height` attributes", + "description": "Always include width and height attributes on your image elements to reduce layout shifting and improve CLS. [Learn more](https://web.dev/optimize-cls/#images-without-dimensions)", + "score": 0, + "scoreDisplayMode": "binary", + "details": { + "type": "table", + "headings": [ + { + "key": "url", + "itemType": "thumbnail", + "text": "" + }, + { + "key": "url", + "itemType": "url", + "text": "URL" + }, + { + "key": "node", + "itemType": "node", + "text": "Failing Elements" + } + ], + "items": [ + { + "url": "http://localhost:10200/dobetterweb/lighthouse-480x318.jpg", + "node": { + "type": "node" + } + }, + { + "url": "http://localhost:10200/dobetterweb/lighthouse-480x318.jpg", + "node": { + "type": "node" + } + }, + { + "url": "blob:http://localhost:10200/ae0eac03-ab9b-4a6a-b299-f5212153e277", + "node": { + "type": "node" + } + } + ] + } + }, "deprecations": { "id": "deprecations", "title": "Uses deprecated APIs", @@ -4523,6 +4570,11 @@ "weight": 1, "group": "best-practices-ux" }, + { + "id": "sized-images", + "weight": 1, + "group": "best-practices-ux" + }, { "id": "image-aspect-ratio", "weight": 1, @@ -4565,7 +4617,7 @@ } ], "id": "best-practices", - "score": 0.08 + "score": 0.07 }, "seo": { "title": "SEO", @@ -5216,6 +5268,12 @@ "duration": 100, "entryType": "measure" }, + { + "startTime": 0, + "name": "lh:audit:sized-images", + "duration": 100, + "entryType": "measure" + }, { "startTime": 0, "name": "lh:audit:deprecations", @@ -6216,6 +6274,7 @@ "audits[errors-in-console].details.headings[0].text", "audits[image-aspect-ratio].details.headings[1].text", "audits[image-size-responsive].details.headings[1].text", + "audits[sized-images].details.headings[1].text", "audits.deprecations.details.headings[1].text", "audits[bootup-time].details.headings[0].text", "audits[network-rtt].details.headings[0].text", @@ -6354,6 +6413,22 @@ "lighthouse-core/audits/image-size-responsive.js | columnExpected": [ "audits[image-size-responsive].details.headings[4].text" ], + "lighthouse-core/audits/sized-images.js | failureTitle": [ + "audits[sized-images].title" + ], + "lighthouse-core/audits/sized-images.js | description": [ + "audits[sized-images].description" + ], + "lighthouse-core/lib/i18n/i18n.js | columnFailingElem": [ + "audits[sized-images].details.headings[2].text", + "audits[color-contrast].details.headings[0].text", + "audits[html-has-lang].details.headings[0].text", + "audits[image-alt].details.headings[0].text", + "audits.label.details.headings[0].text", + "audits[link-name].details.headings[0].text", + "audits[object-alt].details.headings[0].text", + "audits[password-inputs-can-be-pasted-into].details.headings[0].text" + ], "lighthouse-core/audits/deprecations.js | failureTitle": [ "audits.deprecations.title" ], @@ -6737,14 +6812,6 @@ "lighthouse-core/audits/accessibility/color-contrast.js | description": [ "audits[color-contrast].description" ], - "lighthouse-core/audits/accessibility/axe-audit.js | failingElementsHeader": [ - "audits[color-contrast].details.headings[0].text", - "audits[html-has-lang].details.headings[0].text", - "audits[image-alt].details.headings[0].text", - "audits.label.details.headings[0].text", - "audits[link-name].details.headings[0].text", - "audits[object-alt].details.headings[0].text" - ], "lighthouse-core/audits/accessibility/definition-list.js | title": [ "audits[definition-list].title" ], @@ -7194,9 +7261,6 @@ "lighthouse-core/audits/dobetterweb/password-inputs-can-be-pasted-into.js | description": [ "audits[password-inputs-can-be-pasted-into].description" ], - "lighthouse-core/audits/dobetterweb/password-inputs-can-be-pasted-into.js | columnFailingElem": [ - "audits[password-inputs-can-be-pasted-into].details.headings[0].text" - ], "lighthouse-core/audits/dobetterweb/uses-http2.js | failureTitle": [ "audits[uses-http2].title" ], @@ -7528,4 +7592,4 @@ } } ] -} +} \ No newline at end of file From 18abbc61a7c387071513e9e25a60a54a7a07ba3b Mon Sep 17 00:00:00 2001 From: Lemuel Cardenas-arriaga Date: Fri, 17 Jul 2020 14:00:48 -0700 Subject: [PATCH 23/68] test edits and removed from default config --- lighthouse-core/config/default-config.js | 2 - .../test/audits/sized-images-test.js | 12 ++- lighthouse-core/test/results/sample_v2.json | 86 +++---------------- 3 files changed, 15 insertions(+), 85 deletions(-) diff --git a/lighthouse-core/config/default-config.js b/lighthouse-core/config/default-config.js index 1a8efacbba73..b2af8b14b8c0 100644 --- a/lighthouse-core/config/default-config.js +++ b/lighthouse-core/config/default-config.js @@ -216,7 +216,6 @@ const defaultConfig = { 'content-width', 'image-aspect-ratio', 'image-size-responsive', - 'sized-images', 'deprecations', 'mainthread-work-breakdown', 'bootup-time', @@ -551,7 +550,6 @@ const defaultConfig = { {id: 'no-vulnerable-libraries', weight: 1, group: 'best-practices-trust-safety'}, // User Experience {id: 'password-inputs-can-be-pasted-into', weight: 1, group: 'best-practices-ux'}, - {id: 'sized-images', weight: 1, group: 'best-practices-ux'}, {id: 'image-aspect-ratio', weight: 1, group: 'best-practices-ux'}, {id: 'image-size-responsive', weight: 1, group: 'best-practices-ux'}, // Browser Compatibility diff --git a/lighthouse-core/test/audits/sized-images-test.js b/lighthouse-core/test/audits/sized-images-test.js index d73b5ab7fe14..311e621d4bfc 100644 --- a/lighthouse-core/test/audits/sized-images-test.js +++ b/lighthouse-core/test/audits/sized-images-test.js @@ -9,10 +9,8 @@ const SizedImagesAudit = require('../../audits/sized-images.js'); /* eslint-env jest */ -function generateImage(props, src = 'https://google.com/logo.png', isCss = false, - path = '1,HTML,1,BODY,1,IMG', selector = 'body > img', nodeLabel = 'img', - snippet = '') { - const image = {src, isCss, path, selector, nodeLabel, snippet}; +function generateImage(props, src = 'https://google.com/logo.png', isCss = false) { + const image = {src, isCss}; Object.assign(image, props); return image; } @@ -143,7 +141,7 @@ describe('Sized images audit', () => { }); describe('Size attribute validity check', () => { - it('fails on non-numeric characters', async () => { + it('fails on non-numeric characters', () => { expect(SizedImagesAudit.isValid('zero')).toEqual(false); expect(SizedImagesAudit.isValid('1002$')).toEqual(false); expect(SizedImagesAudit.isValid('s-5')).toEqual(false); @@ -154,11 +152,11 @@ describe('Size attribute validity check', () => { expect(SizedImagesAudit.isValid('+2020')).toEqual(false); }); - it('fails on zero input', async () => { + it('fails on zero input', () => { expect(SizedImagesAudit.isValid('0')).toEqual(false); }); - it('passes on non-zero non-negative integer input', async () => { + it('passes on non-zero non-negative integer input', () => { expect(SizedImagesAudit.isValid('1')).toEqual(true); expect(SizedImagesAudit.isValid('250')).toEqual(true); expect(SizedImagesAudit.isValid('4000000')).toEqual(true); diff --git a/lighthouse-core/test/results/sample_v2.json b/lighthouse-core/test/results/sample_v2.json index bdf32d527e9d..e7bb9bcdcef4 100644 --- a/lighthouse-core/test/results/sample_v2.json +++ b/lighthouse-core/test/results/sample_v2.json @@ -679,53 +679,6 @@ ] } }, - "sized-images": { - "id": "sized-images", - "title": "Image elements do not have `width` and `height` attributes", - "description": "Always include width and height attributes on your image elements to reduce layout shifting and improve CLS. [Learn more](https://web.dev/optimize-cls/#images-without-dimensions)", - "score": 0, - "scoreDisplayMode": "binary", - "details": { - "type": "table", - "headings": [ - { - "key": "url", - "itemType": "thumbnail", - "text": "" - }, - { - "key": "url", - "itemType": "url", - "text": "URL" - }, - { - "key": "node", - "itemType": "node", - "text": "Failing Elements" - } - ], - "items": [ - { - "url": "http://localhost:10200/dobetterweb/lighthouse-480x318.jpg", - "node": { - "type": "node" - } - }, - { - "url": "http://localhost:10200/dobetterweb/lighthouse-480x318.jpg", - "node": { - "type": "node" - } - }, - { - "url": "blob:http://localhost:10200/ae0eac03-ab9b-4a6a-b299-f5212153e277", - "node": { - "type": "node" - } - } - ] - } - }, "deprecations": { "id": "deprecations", "title": "Uses deprecated APIs", @@ -4570,11 +4523,6 @@ "weight": 1, "group": "best-practices-ux" }, - { - "id": "sized-images", - "weight": 1, - "group": "best-practices-ux" - }, { "id": "image-aspect-ratio", "weight": 1, @@ -4617,7 +4565,7 @@ } ], "id": "best-practices", - "score": 0.07 + "score": 0.08 }, "seo": { "title": "SEO", @@ -5268,12 +5216,6 @@ "duration": 100, "entryType": "measure" }, - { - "startTime": 0, - "name": "lh:audit:sized-images", - "duration": 100, - "entryType": "measure" - }, { "startTime": 0, "name": "lh:audit:deprecations", @@ -6274,7 +6216,6 @@ "audits[errors-in-console].details.headings[0].text", "audits[image-aspect-ratio].details.headings[1].text", "audits[image-size-responsive].details.headings[1].text", - "audits[sized-images].details.headings[1].text", "audits.deprecations.details.headings[1].text", "audits[bootup-time].details.headings[0].text", "audits[network-rtt].details.headings[0].text", @@ -6413,22 +6354,6 @@ "lighthouse-core/audits/image-size-responsive.js | columnExpected": [ "audits[image-size-responsive].details.headings[4].text" ], - "lighthouse-core/audits/sized-images.js | failureTitle": [ - "audits[sized-images].title" - ], - "lighthouse-core/audits/sized-images.js | description": [ - "audits[sized-images].description" - ], - "lighthouse-core/lib/i18n/i18n.js | columnFailingElem": [ - "audits[sized-images].details.headings[2].text", - "audits[color-contrast].details.headings[0].text", - "audits[html-has-lang].details.headings[0].text", - "audits[image-alt].details.headings[0].text", - "audits.label.details.headings[0].text", - "audits[link-name].details.headings[0].text", - "audits[object-alt].details.headings[0].text", - "audits[password-inputs-can-be-pasted-into].details.headings[0].text" - ], "lighthouse-core/audits/deprecations.js | failureTitle": [ "audits.deprecations.title" ], @@ -6812,6 +6737,15 @@ "lighthouse-core/audits/accessibility/color-contrast.js | description": [ "audits[color-contrast].description" ], + "lighthouse-core/lib/i18n/i18n.js | columnFailingElem": [ + "audits[color-contrast].details.headings[0].text", + "audits[html-has-lang].details.headings[0].text", + "audits[image-alt].details.headings[0].text", + "audits.label.details.headings[0].text", + "audits[link-name].details.headings[0].text", + "audits[object-alt].details.headings[0].text", + "audits[password-inputs-can-be-pasted-into].details.headings[0].text" + ], "lighthouse-core/audits/accessibility/definition-list.js | title": [ "audits[definition-list].title" ], From 9c16d43e3bebef9256c480069652a4dc65940944 Mon Sep 17 00:00:00 2001 From: Lemuel Cardenas-arriaga Date: Fri, 17 Jul 2020 15:44:04 -0700 Subject: [PATCH 24/68] added to experimental config --- lighthouse-core/config/experimental-config.js | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/lighthouse-core/config/experimental-config.js b/lighthouse-core/config/experimental-config.js index 23d8423eb424..a6d88d5a5ce7 100644 --- a/lighthouse-core/config/experimental-config.js +++ b/lighthouse-core/config/experimental-config.js @@ -13,9 +13,23 @@ /** @type {LH.Config.Json} */ const config = { extends: 'lighthouse:default', + passes: [{ + passName: 'defaultPass', + gatherers: [ + 'image-elements', + ], + }], audits: [ + 'sized-images', ], + // @ts-ignore: `title` is required in CategoryJson. setting to the same value as the default + // config is awkward - easier to omit the property here. Will defer to default config. categories: { + 'best-practices': { + auditRefs: [ + {id: 'sized-images', weight: 1, group: 'best-practices-ux'}, + ], + }, }, }; From 1498e2d77a502c8d3e34b3108e451a63961bd0c6 Mon Sep 17 00:00:00 2001 From: Lemuel Cardenas-arriaga Date: Fri, 17 Jul 2020 15:55:02 -0700 Subject: [PATCH 25/68] fixed ts error and edited exp config --- lighthouse-core/config/experimental-config.js | 8 +------- 1 file changed, 1 insertion(+), 7 deletions(-) diff --git a/lighthouse-core/config/experimental-config.js b/lighthouse-core/config/experimental-config.js index a6d88d5a5ce7..77858c2e7a6c 100644 --- a/lighthouse-core/config/experimental-config.js +++ b/lighthouse-core/config/experimental-config.js @@ -13,18 +13,12 @@ /** @type {LH.Config.Json} */ const config = { extends: 'lighthouse:default', - passes: [{ - passName: 'defaultPass', - gatherers: [ - 'image-elements', - ], - }], audits: [ 'sized-images', ], + categories: { // @ts-ignore: `title` is required in CategoryJson. setting to the same value as the default // config is awkward - easier to omit the property here. Will defer to default config. - categories: { 'best-practices': { auditRefs: [ {id: 'sized-images', weight: 1, group: 'best-practices-ux'}, From bc64ee379cef882b01a6c4a778f0ca06b681aa5a Mon Sep 17 00:00:00 2001 From: Lemuel Cardenas-arriaga Date: Tue, 21 Jul 2020 18:02:53 -0700 Subject: [PATCH 26/68] added preliminary css sizing logic --- lighthouse-core/audits/sized-images.js | 31 +++++- .../gather/gatherers/image-elements.js | 102 +++++++++++++++++- types/artifacts.d.ts | 4 + 3 files changed, 133 insertions(+), 4 deletions(-) diff --git a/lighthouse-core/audits/sized-images.js b/lighthouse-core/audits/sized-images.js index b8bba43c7527..7ec31d96cb28 100644 --- a/lighthouse-core/audits/sized-images.js +++ b/lighthouse-core/audits/sized-images.js @@ -51,6 +51,29 @@ class SizedImages extends Audit { return NON_NEGATIVE_INT_REGEX.test(attr) && !ZERO_REGEX.test(attr); } + /** + * @param {string} attrWidth + * @param {string} attrHeight + * @param {string} cssWidth + * @param {string} cssHeight + * @return {boolean} + */ + static unsizedImage(attrWidth, attrHeight, cssWidth, cssHeight) { + if (attrWidth && attrHeight) { + return !SizedImages.isValid(attrWidth) || !SizedImages.isValid(attrHeight); + } + if (attrWidth && cssHeight) { + return !SizedImages.isValid(attrWidth) || cssHeight === 'auto'; + } + if (cssWidth && attrHeight) { + return cssWidth === 'auto' || !SizedImages.isValid(attrHeight); + } + if (cssWidth && cssHeight) { + return cssWidth === 'auto' || cssHeight === 'auto'; + } + return true; + } + /** * @param {LH.Artifacts} artifacts * @return {Promise} @@ -61,10 +84,12 @@ class SizedImages extends Audit { const unsizedImages = []; for (const image of images) { - const width = image.attributeWidth; - const height = image.attributeHeight; + const attrWidth = image.attributeWidth; + const attrHeight = image.attributeHeight; + const cssWidth = image.propertyWidth; + const cssHeight = image.propertyHeight; // images are considered sized if they have defined & valid values - if (!width || !height || !SizedImages.isValid(width) || !SizedImages.isValid(height)) { + if (SizedImages.unsizedImage(attrWidth, attrHeight, cssWidth, cssHeight)) { const url = URL.elideDataURI(image.src); unsizedImages.push({ url, diff --git a/lighthouse-core/gather/gatherers/image-elements.js b/lighthouse-core/gather/gatherers/image-elements.js index 337b48ab30f3..ad2e735eef7d 100644 --- a/lighthouse-core/gather/gatherers/image-elements.js +++ b/lighthouse-core/gather/gatherers/image-elements.js @@ -12,6 +12,7 @@ const Gatherer = require('./gatherer.js'); const pageFunctions = require('../../lib/page-functions.js'); const Driver = require('../driver.js'); // eslint-disable-line no-unused-vars +const FontSize = require('./seo/font-size.js'); /* global window, getElementsInDocument, Image, getNodePath, getNodeSelector, getNodeLabel, getOuterHTMLSnippet */ @@ -53,6 +54,8 @@ function getHTMLImages(allElements) { naturalHeight: element.naturalHeight, attributeWidth: element.getAttribute('width') || '', attributeHeight: element.getAttribute('height') || '', + propertyWidth: '', + propertyHeight: '', isCss: false, // @ts-ignore: loading attribute not yet added to HTMLImageElement definition. loading: element.loading, @@ -111,6 +114,8 @@ function getCSSImages(allElements) { naturalHeight: 0, attributeWidth: '', attributeHeight: '', + propertyWidth: '', + propertyHeight: '', isCss: true, isPicture: false, usesObjectFit: false, @@ -162,6 +167,72 @@ function determineNaturalSize(url) { }); } +/** + * @param {LH.Crdp.CSS.CSSStyle} [style] + * @param {string} property + * @return {boolean} + */ +function hasSizeDeclaration(style, property) { + return !!style && !!style.cssProperties.find(({name}) => name === property); +} + +/** + * Finds the most specific directly matched CSS font-size rule from the list. + * + * @param {Array} [matchedCSSRules] + * @param {string} property + * @returns {string} + */ +function findMostSpecificMatchedCSSRule(matchedCSSRules = [], property) { + let maxSpecificity = -Infinity; + /** @type {LH.Crdp.CSS.CSSRule|undefined} */ + let maxSpecificityRule; + + for (const {rule, matchingSelectors} of matchedCSSRules) { + if (hasSizeDeclaration(rule.style, property)) { + const specificities = matchingSelectors.map(idx => + FontSize.computeSelectorSpecificity(rule.selectorList.selectors[idx].text) + ); + const specificity = Math.max(...specificities); + // Use greater OR EQUAL so that the last rule wins in the event of a tie + if (specificity >= maxSpecificity) { + maxSpecificity = specificity; + maxSpecificityRule = rule; + } + } + } + + if (maxSpecificityRule) { + // @ts-ignore the existence of the property object is checked in hasSizeDeclaration + return maxSpecificityRule.style.cssProperties.find(({name}) => name === property).value; + } + return ''; +} + +/** + * @param {LH.Crdp.CSS.GetMatchedStylesForNodeResponse} matched CSS rules} + * @param {string} property + * @returns {string} + */ +function getEffectiveSizingRule({attributesStyle, inlineStyle, matchedCSSRules}, property) { + // CSS sizing can't be inherited + // We only need to check inline & matched styles + // Inline styles have highest priority + if (hasSizeDeclaration(inlineStyle, property)) { + // @ts-ignore the existence of the property object is checked in hasSizeDeclaration + return inlineStyle.cssProperties.find(({name}) => name === property).value; + } + + if (hasSizeDeclaration(attributesStyle, property)) { + // @ts-ignore the existence of the property object is checked in hasSizeDeclaration + return attributesStyle.cssProperties.find(({name}) => name === property).value; + } + // Rules directly referencing the node come next + const matchedRule = findMostSpecificMatchedCSSRule(matchedCSSRules, property); + if (matchedRule) return matchedRule; + return ''; +} + class ImageElements extends Gatherer { constructor() { super(); @@ -193,6 +264,25 @@ class ImageElements extends Gatherer { } } + /** + * @param {Driver} driver + * @param {string} devtoolsNodePath + * @param {LH.Artifacts.ImageElement} element + */ + async fetchSourceRules(driver, devtoolsNodePath, element) { + const {nodeId} = await driver.sendCommand('DOM.pushNodeByPathToFrontend', { + path: devtoolsNodePath, + }); + if (!nodeId) return; + const matchedRules = await driver.sendCommand('CSS.getMatchedStylesForNode', { + nodeId: nodeId, + }); + const sourceWidth = getEffectiveSizingRule(matchedRules, 'width'); + const sourceHeight = getEffectiveSizingRule(matchedRules, 'height'); + const sourceRules = {propertyWidth: sourceWidth, propertyHeight: sourceHeight}; + Object.assign(element, sourceRules); + } + /** * @param {LH.Gatherer.PassContext} passContext * @param {LH.Gatherer.LoadData} loadData @@ -232,6 +322,11 @@ class ImageElements extends Gatherer { const top50Images = Object.values(indexedNetworkRecords) .sort((a, b) => b.resourceSize - a.resourceSize) .slice(0, 50); + await Promise.all([ + driver.sendCommand('DOM.enable'), + driver.sendCommand('CSS.enable'), + driver.sendCommand('DOM.getDocument', {depth: -1, pierce: true}), + ]); for (let element of elements) { // Pull some of our information directly off the network record. @@ -244,7 +339,7 @@ class ImageElements extends Gatherer { // Use the min of the two numbers to be safe. const {resourceSize = 0, transferSize = 0} = networkRecord; element.resourceSize = Math.min(resourceSize, transferSize); - + await this.fetchSourceRules(driver, element.devtoolsNodePath, element); // Images within `picture` behave strangely and natural size information isn't accurate, // CSS images have no natural size information at all. Try to get the actual size if we can. // Additional fetch is expensive; don't bother if we don't have a networkRecord for the image, @@ -260,6 +355,11 @@ class ImageElements extends Gatherer { imageUsage.push(element); } + await Promise.all([ + driver.sendCommand('DOM.disable'), + driver.sendCommand('CSS.disable'), + ]); + return imageUsage; } } diff --git a/types/artifacts.d.ts b/types/artifacts.d.ts index d7535e56b1f8..ce13be2eb443 100644 --- a/types/artifacts.d.ts +++ b/types/artifacts.d.ts @@ -403,6 +403,10 @@ declare global { attributeWidth: string; /** The raw height attribute of the image element. CSS images will be set to the empty string. */ attributeHeight: string; + /** The CSS width property of the image element */ + propertyWidth: string; + /** The CSS height property of the image element */ + propertyHeight: string; /** The BoundingClientRect of the element. */ clientRect: { top: number; From b5bc5870092b3c74a7e98f9448b6653b0c57bafb Mon Sep 17 00:00:00 2001 From: Lemuel Cardenas-arriaga Date: Tue, 21 Jul 2020 18:14:19 -0700 Subject: [PATCH 27/68] added and shifted comments --- lighthouse-core/audits/sized-images.js | 2 +- lighthouse-core/gather/gatherers/image-elements.js | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/lighthouse-core/audits/sized-images.js b/lighthouse-core/audits/sized-images.js index 7ec31d96cb28..27d1a362bf5c 100644 --- a/lighthouse-core/audits/sized-images.js +++ b/lighthouse-core/audits/sized-images.js @@ -59,6 +59,7 @@ class SizedImages extends Audit { * @return {boolean} */ static unsizedImage(attrWidth, attrHeight, cssWidth, cssHeight) { + // images are considered sized if they have defined & valid values if (attrWidth && attrHeight) { return !SizedImages.isValid(attrWidth) || !SizedImages.isValid(attrHeight); } @@ -88,7 +89,6 @@ class SizedImages extends Audit { const attrHeight = image.attributeHeight; const cssWidth = image.propertyWidth; const cssHeight = image.propertyHeight; - // images are considered sized if they have defined & valid values if (SizedImages.unsizedImage(attrWidth, attrHeight, cssWidth, cssHeight)) { const url = URL.elideDataURI(image.src); unsizedImages.push({ diff --git a/lighthouse-core/gather/gatherers/image-elements.js b/lighthouse-core/gather/gatherers/image-elements.js index ad2e735eef7d..0f2825afda79 100644 --- a/lighthouse-core/gather/gatherers/image-elements.js +++ b/lighthouse-core/gather/gatherers/image-elements.js @@ -54,8 +54,8 @@ function getHTMLImages(allElements) { naturalHeight: element.naturalHeight, attributeWidth: element.getAttribute('width') || '', attributeHeight: element.getAttribute('height') || '', - propertyWidth: '', - propertyHeight: '', + propertyWidth: '', // this will get overwritten below + propertyHeight: '', // this will get overwritten below isCss: false, // @ts-ignore: loading attribute not yet added to HTMLImageElement definition. loading: element.loading, From ed14f5393031e018a19f0fc9a4f461126a3651e4 Mon Sep 17 00:00:00 2001 From: Lemuel Cardenas-arriaga Date: Wed, 22 Jul 2020 16:39:37 -0700 Subject: [PATCH 28/68] edited UIstrings and filenames --- .../{sized-images.js => unsized-images.js} | 14 +++++++------- lighthouse-core/config/experimental-config.js | 4 ++-- lighthouse-core/lib/i18n/locales/en-US.json | 18 +++++++++--------- lighthouse-core/lib/i18n/locales/en-XL.json | 18 +++++++++--------- ...d-images-test.js => unsized-images-test.js} | 2 +- 5 files changed, 28 insertions(+), 28 deletions(-) rename lighthouse-core/audits/{sized-images.js => unsized-images.js} (82%) rename lighthouse-core/test/audits/{sized-images-test.js => unsized-images-test.js} (98%) diff --git a/lighthouse-core/audits/sized-images.js b/lighthouse-core/audits/unsized-images.js similarity index 82% rename from lighthouse-core/audits/sized-images.js rename to lighthouse-core/audits/unsized-images.js index 27d1a362bf5c..7371ccbc82c0 100644 --- a/lighthouse-core/audits/sized-images.js +++ b/lighthouse-core/audits/unsized-images.js @@ -10,12 +10,12 @@ const i18n = require('./../lib/i18n/i18n.js'); const URL = require('./../lib/url-shim.js'); const UIStrings = { - /** Title of a Lighthouse audit that provides detail on whether all images had width and height attributes. This descriptive title is shown to users when every image has width and height attributes */ - title: 'Image elements have `width` and `height` attributes', - /** Title of a Lighthouse audit that provides detail on whether all images had width and height attributes. This descriptive title is shown to users when one or more images does not have width and height attributes */ - failureTitle: 'Image elements do not have `width` and `height` attributes', - /** Description of a Lighthouse audit that tells the user why they should include width and height attributes 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 width and height attributes on your image elements to reduce layout shifting and improve CLS. [Learn more](https://web.dev/optimize-cls/#images-without-dimensions)', + /** 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 every image has explicit width and height */ + title: 'Image elements have explicit `width` and `height`', + /** 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 your image elements to reduce layout shifting and improve CLS. [Learn more](https://web.dev/optimize-cls/#images-without-dimensions)', }; const str_ = i18n.createMessageInstanceIdFn(__filename, UIStrings); @@ -31,7 +31,7 @@ class SizedImages extends Audit { */ static get meta() { return { - id: 'sized-images', + id: 'unsized-images', title: str_(UIStrings.title), failureTitle: str_(UIStrings.failureTitle), description: str_(UIStrings.description), diff --git a/lighthouse-core/config/experimental-config.js b/lighthouse-core/config/experimental-config.js index ec9d7a3efd5e..3f9a4fb9b06b 100644 --- a/lighthouse-core/config/experimental-config.js +++ b/lighthouse-core/config/experimental-config.js @@ -14,7 +14,7 @@ const config = { extends: 'lighthouse:default', audits: [ - 'sized-images', + 'unsized-images', 'full-page-screenshot', ], passes: [{ @@ -28,7 +28,7 @@ const config = { // config is awkward - easier to omit the property here. Will defer to default config. 'best-practices': { auditRefs: [ - {id: 'sized-images', weight: 1, group: 'best-practices-ux'}, + {id: 'unsized-images', weight: 1, group: 'best-practices-ux'}, ], }, }, diff --git a/lighthouse-core/lib/i18n/locales/en-US.json b/lighthouse-core/lib/i18n/locales/en-US.json index 1c662aaceb70..853a7ebe4a9f 100644 --- a/lighthouse-core/lib/i18n/locales/en-US.json +++ b/lighthouse-core/lib/i18n/locales/en-US.json @@ -1211,15 +1211,6 @@ "lighthouse-core/audits/service-worker.js | title": { "message": "Registers a service worker that controls page and `start_url`" }, - "lighthouse-core/audits/sized-images.js | description": { - "message": "Always include width and height attributes on your image elements to reduce layout shifting and improve CLS. [Learn more](https://web.dev/optimize-cls/#images-without-dimensions)" - }, - "lighthouse-core/audits/sized-images.js | failureTitle": { - "message": "Image elements do not have `width` and `height` attributes" - }, - "lighthouse-core/audits/sized-images.js | title": { - "message": "Image elements have `width` and `height` attributes" - }, "lighthouse-core/audits/splash-screen.js | description": { "message": "A themed splash screen ensures a high-quality experience when users launch your app from their homescreens. [Learn more](https://web.dev/splash-screen/)." }, @@ -1268,6 +1259,15 @@ "lighthouse-core/audits/timing-budget.js | title": { "message": "Timing budget" }, + "lighthouse-core/audits/unsized-images.js | description": { + "message": "Always include explicit width and height on your image elements to reduce layout shifting and improve CLS. [Learn more](https://web.dev/optimize-cls/#images-without-dimensions)" + }, + "lighthouse-core/audits/unsized-images.js | failureTitle": { + "message": "Image elements do not have explicit `width` and `height`" + }, + "lighthouse-core/audits/unsized-images.js | title": { + "message": "Image elements have explicit `width` and `height`" + }, "lighthouse-core/audits/user-timings.js | columnType": { "message": "Type" }, diff --git a/lighthouse-core/lib/i18n/locales/en-XL.json b/lighthouse-core/lib/i18n/locales/en-XL.json index edc7fc7d9021..3a1d6cc3387f 100644 --- a/lighthouse-core/lib/i18n/locales/en-XL.json +++ b/lighthouse-core/lib/i18n/locales/en-XL.json @@ -1211,15 +1211,6 @@ "lighthouse-core/audits/service-worker.js | title": { "message": "R̂éĝíŝt́êŕŝ á ŝér̂v́îćê ẃôŕk̂ér̂ t́ĥát̂ ćôńt̂ŕôĺŝ ṕâǵê án̂d́ `start_url`" }, - "lighthouse-core/audits/sized-images.js | description": { - "message": "Âĺŵáŷś îńĉĺûd́ê ẃîd́t̂h́ âńd̂ h́êíĝh́t̂ át̂t́r̂íb̂út̂éŝ ón̂ ýôúr̂ ím̂áĝé êĺêḿêńt̂ś t̂ó r̂éd̂úĉé l̂áŷóût́ ŝh́îf́t̂ín̂ǵ âńd̂ ím̂ṕr̂óv̂é ĈĹŜ. [Ĺêár̂ń m̂ór̂é](https://web.dev/optimize-cls/#images-without-dimensions)" - }, - "lighthouse-core/audits/sized-images.js | failureTitle": { - "message": "Îḿâǵê él̂ém̂én̂t́ŝ d́ô ńôt́ ĥáv̂é `width` âńd̂ `height` át̂t́r̂íb̂út̂éŝ" - }, - "lighthouse-core/audits/sized-images.js | title": { - "message": "Îḿâǵê él̂ém̂én̂t́ŝ h́âv́ê `width` án̂d́ `height` ât́t̂ŕîb́ût́êś" - }, "lighthouse-core/audits/splash-screen.js | description": { "message": " t́ĥém̂éd̂ śp̂ĺâśĥ śĉŕêén̂ én̂śûŕêś â h́îǵĥ-q́ûál̂ít̂ý êx́p̂ér̂íêńĉé ŵh́êń ûśêŕŝ ĺâún̂ćĥ ýôúr̂ áp̂ṕ f̂ŕôḿ t̂h́êír̂ h́ôḿêśĉŕêén̂ś. [L̂éâŕn̂ ḿôŕê](https://web.dev/splash-screen/)." }, @@ -1268,6 +1259,15 @@ "lighthouse-core/audits/timing-budget.js | title": { "message": "T̂ím̂ín̂ǵ b̂úd̂ǵêt́" }, + "lighthouse-core/audits/unsized-images.js | description": { + "message": "Âĺŵáŷś îńĉĺûd́ê éx̂ṕl̂íĉít̂ ẃîd́t̂h́ âńd̂ h́êíĝh́t̂ ón̂ ýôúr̂ ím̂áĝé êĺêḿêńt̂ś t̂ó r̂éd̂úĉé l̂áŷóût́ ŝh́îf́t̂ín̂ǵ âńd̂ ím̂ṕr̂óv̂é ĈĹŜ. [Ĺêár̂ń m̂ór̂é](https://web.dev/optimize-cls/#images-without-dimensions)" + }, + "lighthouse-core/audits/unsized-images.js | failureTitle": { + "message": "Îḿâǵê él̂ém̂én̂t́ŝ d́ô ńôt́ ĥáv̂é êx́p̂ĺîćît́ `width` âńd̂ `height`" + }, + "lighthouse-core/audits/unsized-images.js | title": { + "message": "Îḿâǵê él̂ém̂én̂t́ŝ h́âv́ê éx̂ṕl̂íĉít̂ `width` án̂d́ `height`" + }, "lighthouse-core/audits/user-timings.js | columnType": { "message": "T̂ýp̂é" }, diff --git a/lighthouse-core/test/audits/sized-images-test.js b/lighthouse-core/test/audits/unsized-images-test.js similarity index 98% rename from lighthouse-core/test/audits/sized-images-test.js rename to lighthouse-core/test/audits/unsized-images-test.js index 311e621d4bfc..333e019871cb 100644 --- a/lighthouse-core/test/audits/sized-images-test.js +++ b/lighthouse-core/test/audits/unsized-images-test.js @@ -5,7 +5,7 @@ */ 'use strict'; -const SizedImagesAudit = require('../../audits/sized-images.js'); +const SizedImagesAudit = require('../../audits/unsized-images.js'); /* eslint-env jest */ From e5a2c7c7e844091dab38c540d9d927a51523e856 Mon Sep 17 00:00:00 2001 From: Lemuel Cardenas-arriaga Date: Wed, 22 Jul 2020 17:39:17 -0700 Subject: [PATCH 29/68] made css props optional in artifacts & refactored image-elements --- lighthouse-core/audits/unsized-images.js | 49 ++++++++++--------- .../gather/gatherers/image-elements.js | 32 ++++++------ types/artifacts.d.ts | 8 +-- 3 files changed, 45 insertions(+), 44 deletions(-) diff --git a/lighthouse-core/audits/unsized-images.js b/lighthouse-core/audits/unsized-images.js index 7371ccbc82c0..77f61d7465fe 100644 --- a/lighthouse-core/audits/unsized-images.js +++ b/lighthouse-core/audits/unsized-images.js @@ -43,7 +43,7 @@ class SizedImages extends Audit { * @param {string} attr * @return {boolean} */ - static isValid(attr) { + static isValidAttr(attr) { // an img size attribute is valid for preventing CLS // if it is a non-negative, non-zero integer const NON_NEGATIVE_INT_REGEX = /^\d+$/; @@ -52,27 +52,32 @@ class SizedImages extends Audit { } /** - * @param {string} attrWidth - * @param {string} attrHeight - * @param {string} cssWidth - * @param {string} cssHeight + * @param {string} property * @return {boolean} */ - static unsizedImage(attrWidth, attrHeight, cssWidth, cssHeight) { + static isValidCss(property) { + // an img css size property is valid for preventing CLS + // if it ... + return property !== 'auto'; + } + + /** + * @param {LH.Artifacts.ImageElement} image + * @return {boolean} + */ + static isUnsizedImage(image) { // images are considered sized if they have defined & valid values - if (attrWidth && attrHeight) { - return !SizedImages.isValid(attrWidth) || !SizedImages.isValid(attrHeight); - } - if (attrWidth && cssHeight) { - return !SizedImages.isValid(attrWidth) || cssHeight === 'auto'; - } - if (cssWidth && attrHeight) { - return cssWidth === 'auto' || !SizedImages.isValid(attrHeight); - } - if (cssWidth && cssHeight) { - return cssWidth === 'auto' || cssHeight === 'auto'; - } - return true; + const attrWidth = image.attributeWidth; + const attrHeight = image.attributeHeight; + const cssWidth = image.cssWidth; + const cssHeight = image.cssHeight; + const widthIsValidAttribute = attrWidth && SizedImages.isValidAttr(attrWidth); + const widthIsValidCss = cssWidth && SizedImages.isValidCss(cssWidth); + const heightIsValidAttribute = attrHeight && SizedImages.isValidAttr(attrHeight); + const heightIsValidCss = cssHeight && SizedImages.isValidCss(cssHeight); + const validWidth = widthIsValidAttribute || widthIsValidCss; + const validHeight = heightIsValidAttribute || heightIsValidCss; + return !validWidth || !validHeight; } /** @@ -85,11 +90,7 @@ class SizedImages extends Audit { const unsizedImages = []; for (const image of images) { - const attrWidth = image.attributeWidth; - const attrHeight = image.attributeHeight; - const cssWidth = image.propertyWidth; - const cssHeight = image.propertyHeight; - if (SizedImages.unsizedImage(attrWidth, attrHeight, cssWidth, cssHeight)) { + if (SizedImages.isUnsizedImage(image)) { const url = URL.elideDataURI(image.src); unsizedImages.push({ url, diff --git a/lighthouse-core/gather/gatherers/image-elements.js b/lighthouse-core/gather/gatherers/image-elements.js index 0f2825afda79..7c5e14609073 100644 --- a/lighthouse-core/gather/gatherers/image-elements.js +++ b/lighthouse-core/gather/gatherers/image-elements.js @@ -54,8 +54,8 @@ function getHTMLImages(allElements) { naturalHeight: element.naturalHeight, attributeWidth: element.getAttribute('width') || '', attributeHeight: element.getAttribute('height') || '', - propertyWidth: '', // this will get overwritten below - propertyHeight: '', // this will get overwritten below + cssWidth: '', // this will get overwritten below + cssHeight: '', // this will get overwritten below isCss: false, // @ts-ignore: loading attribute not yet added to HTMLImageElement definition. loading: element.loading, @@ -114,8 +114,8 @@ function getCSSImages(allElements) { naturalHeight: 0, attributeWidth: '', attributeHeight: '', - propertyWidth: '', - propertyHeight: '', + cssWidth: '', + cssHeight: '', isCss: true, isPicture: false, usesObjectFit: false, @@ -170,10 +170,13 @@ function determineNaturalSize(url) { /** * @param {LH.Crdp.CSS.CSSStyle} [style] * @param {string} property - * @return {boolean} + * @return {string | undefined} */ -function hasSizeDeclaration(style, property) { - return !!style && !!style.cssProperties.find(({name}) => name === property); +function findSizeDeclaration(style, property) { + if (!style) return; + const definedProp = style.cssProperties.find(({name}) => name === property); + if (!definedProp) return; + return definedProp.value; } /** @@ -189,7 +192,8 @@ function findMostSpecificMatchedCSSRule(matchedCSSRules = [], property) { let maxSpecificityRule; for (const {rule, matchingSelectors} of matchedCSSRules) { - if (hasSizeDeclaration(rule.style, property)) { + // hasSizeDeclaration from font-size.js using `.some()` + if (!!rule.style && rule.style.cssProperties.some(({name}) => name === property)) { const specificities = matchingSelectors.map(idx => FontSize.computeSelectorSpecificity(rule.selectorList.selectors[idx].text) ); @@ -218,15 +222,11 @@ function getEffectiveSizingRule({attributesStyle, inlineStyle, matchedCSSRules}, // CSS sizing can't be inherited // We only need to check inline & matched styles // Inline styles have highest priority - if (hasSizeDeclaration(inlineStyle, property)) { - // @ts-ignore the existence of the property object is checked in hasSizeDeclaration - return inlineStyle.cssProperties.find(({name}) => name === property).value; - } + const inlineRule = findSizeDeclaration(inlineStyle, property); + if (inlineRule) return inlineRule; - if (hasSizeDeclaration(attributesStyle, property)) { - // @ts-ignore the existence of the property object is checked in hasSizeDeclaration - return attributesStyle.cssProperties.find(({name}) => name === property).value; - } + const attributeRule = findSizeDeclaration(attributesStyle, property); + if (attributeRule) return attributeRule; // Rules directly referencing the node come next const matchedRule = findMostSpecificMatchedCSSRule(matchedCSSRules, property); if (matchedRule) return matchedRule; diff --git a/types/artifacts.d.ts b/types/artifacts.d.ts index 546c9f130a13..a33d870f6bc6 100644 --- a/types/artifacts.d.ts +++ b/types/artifacts.d.ts @@ -414,10 +414,10 @@ declare global { attributeWidth: string; /** The raw height attribute of the image element. CSS images will be set to the empty string. */ attributeHeight: string; - /** The CSS width property of the image element */ - propertyWidth: string; - /** The CSS height property of the image element */ - propertyHeight: string; + /** The CSS width property of the image element. */ + cssWidth?: string; + /** The CSS height property of the image element. */ + cssHeight?: string; /** The BoundingClientRect of the element. */ clientRect: { top: number; From 30648c03293f938ac46540afec9b1bc838c48bc3 Mon Sep 17 00:00:00 2001 From: Lemuel Cardenas-arriaga Date: Wed, 22 Jul 2020 17:44:37 -0700 Subject: [PATCH 30/68] fixed unsized-images-test --- .../test/audits/unsized-images-test.js | 32 +++++++++---------- 1 file changed, 16 insertions(+), 16 deletions(-) diff --git a/lighthouse-core/test/audits/unsized-images-test.js b/lighthouse-core/test/audits/unsized-images-test.js index 333e019871cb..10cc0eb3971a 100644 --- a/lighthouse-core/test/audits/unsized-images-test.js +++ b/lighthouse-core/test/audits/unsized-images-test.js @@ -5,7 +5,7 @@ */ 'use strict'; -const SizedImagesAudit = require('../../audits/unsized-images.js'); +const UnSizedImagesAudit = require('../../audits/unsized-images.js'); /* eslint-env jest */ @@ -19,7 +19,7 @@ describe('Sized images audit', () => { function testImage(condition, data) { const description = `handles when an image ${condition}`; it(description, async () => { - const result = await SizedImagesAudit.audit({ + const result = await UnSizedImagesAudit.audit({ ImageElements: [ generateImage( data.props @@ -100,7 +100,7 @@ describe('Sized images audit', () => { }); it('is not applicable when there are no images', async () => { - const result = await SizedImagesAudit.audit({ + const result = await UnSizedImagesAudit.audit({ ImageElements: [], }); expect(result.notApplicable).toEqual(true); @@ -108,7 +108,7 @@ describe('Sized images audit', () => { }); it('can return multiple unsized images', async () => { - const result = await SizedImagesAudit.audit({ + const result = await UnSizedImagesAudit.audit({ ImageElements: [ generateImage( { @@ -142,23 +142,23 @@ describe('Sized images audit', () => { describe('Size attribute validity check', () => { it('fails on non-numeric characters', () => { - expect(SizedImagesAudit.isValid('zero')).toEqual(false); - expect(SizedImagesAudit.isValid('1002$')).toEqual(false); - expect(SizedImagesAudit.isValid('s-5')).toEqual(false); - expect(SizedImagesAudit.isValid('3,000')).toEqual(false); - expect(SizedImagesAudit.isValid('100.0')).toEqual(false); - expect(SizedImagesAudit.isValid('2/3')).toEqual(false); - expect(SizedImagesAudit.isValid('-2020')).toEqual(false); - expect(SizedImagesAudit.isValid('+2020')).toEqual(false); + expect(UnSizedImagesAudit.isValidAttr('zero')).toEqual(false); + expect(UnSizedImagesAudit.isValidAttr('1002$')).toEqual(false); + expect(UnSizedImagesAudit.isValidAttr('s-5')).toEqual(false); + expect(UnSizedImagesAudit.isValidAttr('3,000')).toEqual(false); + expect(UnSizedImagesAudit.isValidAttr('100.0')).toEqual(false); + expect(UnSizedImagesAudit.isValidAttr('2/3')).toEqual(false); + expect(UnSizedImagesAudit.isValidAttr('-2020')).toEqual(false); + expect(UnSizedImagesAudit.isValidAttr('+2020')).toEqual(false); }); it('fails on zero input', () => { - expect(SizedImagesAudit.isValid('0')).toEqual(false); + expect(UnSizedImagesAudit.isValidAttr('0')).toEqual(false); }); it('passes on non-zero non-negative integer input', () => { - expect(SizedImagesAudit.isValid('1')).toEqual(true); - expect(SizedImagesAudit.isValid('250')).toEqual(true); - expect(SizedImagesAudit.isValid('4000000')).toEqual(true); + expect(UnSizedImagesAudit.isValidAttr('1')).toEqual(true); + expect(UnSizedImagesAudit.isValidAttr('250')).toEqual(true); + expect(UnSizedImagesAudit.isValidAttr('4000000')).toEqual(true); }); }); From e8ff1d09a92dc4a9467700b1147feb7a66a3aafd Mon Sep 17 00:00:00 2001 From: Lemuel Cardenas-arriaga Date: Fri, 24 Jul 2020 15:50:39 -0700 Subject: [PATCH 31/68] fixed small bugs in image-elements --- lighthouse-core/gather/gatherers/image-elements.js | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/lighthouse-core/gather/gatherers/image-elements.js b/lighthouse-core/gather/gatherers/image-elements.js index 7c5e14609073..f40ec90f7240 100644 --- a/lighthouse-core/gather/gatherers/image-elements.js +++ b/lighthouse-core/gather/gatherers/image-elements.js @@ -207,7 +207,7 @@ function findMostSpecificMatchedCSSRule(matchedCSSRules = [], property) { } if (maxSpecificityRule) { - // @ts-ignore the existence of the property object is checked in hasSizeDeclaration + // @ts-ignore the existence of the property object is checked in the for loop return maxSpecificityRule.style.cssProperties.find(({name}) => name === property).value; } return ''; @@ -216,7 +216,7 @@ function findMostSpecificMatchedCSSRule(matchedCSSRules = [], property) { /** * @param {LH.Crdp.CSS.GetMatchedStylesForNodeResponse} matched CSS rules} * @param {string} property - * @returns {string} + * @returns {string | undefined} */ function getEffectiveSizingRule({attributesStyle, inlineStyle, matchedCSSRules}, property) { // CSS sizing can't be inherited @@ -230,7 +230,6 @@ function getEffectiveSizingRule({attributesStyle, inlineStyle, matchedCSSRules}, // Rules directly referencing the node come next const matchedRule = findMostSpecificMatchedCSSRule(matchedCSSRules, property); if (matchedRule) return matchedRule; - return ''; } class ImageElements extends Gatherer { @@ -279,7 +278,7 @@ class ImageElements extends Gatherer { }); const sourceWidth = getEffectiveSizingRule(matchedRules, 'width'); const sourceHeight = getEffectiveSizingRule(matchedRules, 'height'); - const sourceRules = {propertyWidth: sourceWidth, propertyHeight: sourceHeight}; + const sourceRules = {cssWidth: sourceWidth, cssHeight: sourceHeight}; Object.assign(element, sourceRules); } From f531a74ab0157656c4f1b95ad2da487ec4b02748 Mon Sep 17 00:00:00 2001 From: Lemuel Cardenas-arriaga Date: Thu, 23 Jul 2020 15:00:27 -0700 Subject: [PATCH 32/68] changed size typedef, isValidCss, added tests (cherry picked from commit 1e0504c5032fd95dd096a5a7fe1324a5c85beedf) --- lighthouse-core/audits/unsized-images.js | 5 ++++- .../test/audits/unsized-images-test.js | 18 ++++++++++++++++++ types/artifacts.d.ts | 4 ++-- 3 files changed, 24 insertions(+), 3 deletions(-) diff --git a/lighthouse-core/audits/unsized-images.js b/lighthouse-core/audits/unsized-images.js index 77f61d7465fe..93eea7270fc8 100644 --- a/lighthouse-core/audits/unsized-images.js +++ b/lighthouse-core/audits/unsized-images.js @@ -57,7 +57,10 @@ class SizedImages extends Audit { */ static isValidCss(property) { // an img css size property is valid for preventing CLS - // if it ... + // if it is defined and not equal to 'auto' + // `undefined` and `''` are implicitly rejected as invalid + // because of their falsy short-circuit of && in isUnsizedImage + if (!property) return false; return property !== 'auto'; } diff --git a/lighthouse-core/test/audits/unsized-images-test.js b/lighthouse-core/test/audits/unsized-images-test.js index 10cc0eb3971a..ff6f76660eab 100644 --- a/lighthouse-core/test/audits/unsized-images-test.js +++ b/lighthouse-core/test/audits/unsized-images-test.js @@ -162,3 +162,21 @@ describe('Size attribute validity check', () => { expect(UnSizedImagesAudit.isValidAttr('4000000')).toEqual(true); }); }); + +describe('CSS size property validity check', () => { + it('fails if it was never defined', () => { + expect(UnSizedImagesAudit.isValidCss(undefined)).toEqual(false); + }); + + it('fails if it is empty', () => { + expect(UnSizedImagesAudit.isValidCss('')).toEqual(false); + }); + + it('fails if it is auto', () => { + expect(UnSizedImagesAudit.isValidCss('auto')).toEqual(false); + }); + + it('passes if it is defined and not auto', () => { + expect(UnSizedImagesAudit.isValidCss('200')).toEqual(true); + }); +}); diff --git a/types/artifacts.d.ts b/types/artifacts.d.ts index a33d870f6bc6..18fc796579e8 100644 --- a/types/artifacts.d.ts +++ b/types/artifacts.d.ts @@ -415,9 +415,9 @@ declare global { /** The raw height attribute of the image element. CSS images will be set to the empty string. */ attributeHeight: string; /** The CSS width property of the image element. */ - cssWidth?: string; + cssWidth?: string | undefined; /** The CSS height property of the image element. */ - cssHeight?: string; + cssHeight?: string | undefined; /** The BoundingClientRect of the element. */ clientRect: { top: number; From efb033d859f2156a28c779b9740c73408b3139af Mon Sep 17 00:00:00 2001 From: Lemuel Cardenas-arriaga Date: Fri, 24 Jul 2020 15:33:05 -0700 Subject: [PATCH 33/68] fixed isValidCss and added css testing (cherry picked from commit cb50193f821cdb073c910597dc9943a414bfb3aa) --- lighthouse-core/audits/unsized-images.js | 2 +- .../test/audits/unsized-images-test.js | 270 ++++++++++++++++-- 2 files changed, 255 insertions(+), 17 deletions(-) diff --git a/lighthouse-core/audits/unsized-images.js b/lighthouse-core/audits/unsized-images.js index 93eea7270fc8..619018a59dc1 100644 --- a/lighthouse-core/audits/unsized-images.js +++ b/lighthouse-core/audits/unsized-images.js @@ -57,7 +57,7 @@ class SizedImages extends Audit { */ static isValidCss(property) { // an img css size property is valid for preventing CLS - // if it is defined and not equal to 'auto' + // if it is defined, not empty, and not equal to 'auto' // `undefined` and `''` are implicitly rejected as invalid // because of their falsy short-circuit of && in isUnsizedImage if (!property) return false; diff --git a/lighthouse-core/test/audits/unsized-images-test.js b/lighthouse-core/test/audits/unsized-images-test.js index ff6f76660eab..3a1ab44de13a 100644 --- a/lighthouse-core/test/audits/unsized-images-test.js +++ b/lighthouse-core/test/audits/unsized-images-test.js @@ -36,41 +36,185 @@ describe('Sized images audit', () => { isCss: true, attributeWidth: '', attributeHeight: '', + cssWidth: '', + cssHeight: '', }, }); - describe('has empty', () => { - testImage('has empty width attribute', { + describe('has empty width', () => { + testImage('only has attribute height', { score: 0, props: { attributeWidth: '', attributeHeight: '100', + cssWidth: '', + cssHeight: '', }, }); - testImage('has empty height attribute', { + testImage('only has css height', { + score: 0, + props: { + attributeWidth: '', + attributeHeight: '', + cssWidth: '', + cssHeight: '100', + }, + }); + + testImage('only has attribute height & css height', { + score: 0, + props: { + attributeWidth: '', + attributeHeight: '100', + cssWidth: '', + cssHeight: '100', + }, + }); + }); + + describe('has empty height', () => { + testImage('only has attribute width', { score: 0, props: { attributeWidth: '100', attributeHeight: '', + cssWidth: '', + cssHeight: '', }, }); - testImage('has empty width and height attributes', { + testImage('only has css width', { score: 0, props: { attributeWidth: '', attributeHeight: '', + cssWidth: '100', + cssHeight: '', + }, + }); + + testImage('only has attribute width & css width', { + score: 0, + props: { + attributeWidth: '100', + attributeHeight: '', + cssWidth: '100', + cssHeight: '', + }, + }); + }); + + testImage('has empty width and height', { + score: 0, + props: { + attributeWidth: '', + attributeHeight: '', + cssWidth: '', + cssHeight: '', + }, + }); + + describe('has valid width and height', () => { + testImage('has attribute width and css height', { + score: 1, + props: { + attributeWidth: '100', + attributeHeight: '', + cssWidth: '', + cssHeight: '100', + }, + }); + + testImage('has attribute width and attribute height', { + score: 1, + props: { + attributeWidth: '100', + attributeHeight: '100', + cssWidth: '', + cssHeight: '', + }, + }); + + testImage('has css width and attribute height', { + score: 1, + props: { + attributeWidth: '', + attributeHeight: '100', + cssWidth: '100', + cssHeight: '', + }, + }); + + testImage('has css width and css height', { + score: 1, + props: { + attributeWidth: '', + attributeHeight: '', + cssWidth: '100', + cssHeight: '100', + }, + }); + + testImage('has css & attribute width and css height', { + score: 1, + props: { + attributeWidth: '100', + attributeHeight: '', + cssWidth: '100', + cssHeight: '100', + }, + }); + + testImage('has css & attribute width and attribute height', { + score: 1, + props: { + attributeWidth: '100', + attributeHeight: '100', + cssWidth: '100', + cssHeight: '', + }, + }); + + testImage('has css & attribute height and css width', { + score: 1, + props: { + attributeWidth: '', + attributeHeight: '100', + cssWidth: '100', + cssHeight: '100', + }, + }); + + testImage('has css & attribute height and attribute width', { + score: 1, + props: { + attributeWidth: '100', + attributeHeight: '100', + cssWidth: '', + cssHeight: '100', + }, + }); + + testImage('has css & attribute height and css & attribute width', { + score: 1, + props: { + attributeWidth: '100', + attributeHeight: '100', + cssWidth: '100', + cssHeight: '100', }, }); }); - describe('has invalid', () => { + describe('has invalid width', () => { testImage('has invalid width attribute', { score: 0, props: { attributeWidth: '-200', attributeHeight: '100', + cssWidth: '', + cssHeight: '', }, }); @@ -78,25 +222,101 @@ describe('Sized images audit', () => { score: 0, props: { attributeWidth: '100', - attributeHeight: '300.5', + attributeHeight: '-200', + cssWidth: '', + cssHeight: '', }, }); - testImage('has invalid width and height attributes', { + testImage('has invalid css width', { score: 0, props: { - attributeWidth: '0', - attributeHeight: '100/2', + attributeWidth: '', + attributeHeight: '', + cssWidth: 'auto', + cssHeight: '100', }, }); - }); - testImage('has valid width and height attributes', { - score: 1, - props: { - attributeWidth: '100', - attributeHeight: '100', - }, + testImage('has invalid css height', { + score: 0, + props: { + attributeWidth: '', + attributeHeight: '', + cssWidth: '100', + cssHeight: 'auto', + }, + }); + + testImage('has invalid width attribute, and valid css width', { + score: 1, + props: { + attributeWidth: '-200', + attributeHeight: '100', + cssWidth: '100', + cssHeight: '', + }, + }); + + testImage('has invalid height attribute, and valid css height', { + score: 1, + props: { + attributeWidth: '100', + attributeHeight: '-200', + cssWidth: '', + cssHeight: '100', + }, + }); + + testImage('has invalid css width, and valid attribute width', { + score: 1, + props: { + attributeWidth: '100', + attributeHeight: '', + cssWidth: 'auto', + cssHeight: '100', + }, + }); + + testImage('has invalid css height, and valid attribute height', { + score: 1, + props: { + attributeWidth: '', + attributeHeight: '100', + cssWidth: '100', + cssHeight: 'auto', + }, + }); + + testImage('has invalid css width & height, and valid attribute width & height', { + score: 1, + props: { + attributeWidth: '100', + attributeHeight: '100', + cssWidth: 'auto', + cssHeight: 'auto', + }, + }); + + testImage('has invalid attribute width & height, and valid css width & height', { + score: 1, + props: { + attributeWidth: '-200', + attributeHeight: '-200', + cssWidth: '100', + cssHeight: '100', + }, + }); + + testImage('has invalid attribute width & height, and invalid css width & height', { + score: 0, + props: { + attributeWidth: '-200', + attributeHeight: '-200', + cssWidth: 'auto', + cssHeight: 'auto', + }, + }); }); it('is not applicable when there are no images', async () => { @@ -114,6 +334,8 @@ describe('Sized images audit', () => { { attributeWidth: '', attributeHeight: '', + cssWidth: '', + cssHeight: '', }, 'image1.png' ), @@ -128,6 +350,8 @@ describe('Sized images audit', () => { { attributeWidth: '', attributeHeight: '', + cssWidth: '', + cssHeight: '', }, 'image3.png' ), @@ -141,6 +365,10 @@ describe('Sized images audit', () => { }); describe('Size attribute validity check', () => { + it('fails if it is empty', () => { + expect(UnSizedImagesAudit.isValidAttr('')).toEqual(false); + }); + it('fails on non-numeric characters', () => { expect(UnSizedImagesAudit.isValidAttr('zero')).toEqual(false); expect(UnSizedImagesAudit.isValidAttr('1002$')).toEqual(false); @@ -178,5 +406,15 @@ describe('CSS size property validity check', () => { it('passes if it is defined and not auto', () => { expect(UnSizedImagesAudit.isValidCss('200')).toEqual(true); + expect(UnSizedImagesAudit.isValidCss('300.5')).toEqual(true); + expect(UnSizedImagesAudit.isValidCss('150px')).toEqual(true); + expect(UnSizedImagesAudit.isValidCss('80%')).toEqual(true); + expect(UnSizedImagesAudit.isValidCss('5cm')).toEqual(true); + expect(UnSizedImagesAudit.isValidCss('20rem')).toEqual(true); + expect(UnSizedImagesAudit.isValidCss('7vw')).toEqual(true); + expect(UnSizedImagesAudit.isValidCss('-20')).toEqual(true); + expect(UnSizedImagesAudit.isValidCss('0')).toEqual(true); + expect(UnSizedImagesAudit.isValidCss('three')).toEqual(true); + expect(UnSizedImagesAudit.isValidCss('-20')).toEqual(true); }); }); From 7b4753cc697e0923c10e74c44d700d37b5bab819 Mon Sep 17 00:00:00 2001 From: Lemuel Cardenas-arriaga Date: Mon, 27 Jul 2020 10:47:12 -0700 Subject: [PATCH 34/68] removed an empty string branch in findMostSpecificMatched --- lighthouse-core/gather/gatherers/image-elements.js | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/lighthouse-core/gather/gatherers/image-elements.js b/lighthouse-core/gather/gatherers/image-elements.js index f40ec90f7240..a3ca8fa54579 100644 --- a/lighthouse-core/gather/gatherers/image-elements.js +++ b/lighthouse-core/gather/gatherers/image-elements.js @@ -184,7 +184,7 @@ function findSizeDeclaration(style, property) { * * @param {Array} [matchedCSSRules] * @param {string} property - * @returns {string} + * @returns {string | undefined} */ function findMostSpecificMatchedCSSRule(matchedCSSRules = [], property) { let maxSpecificity = -Infinity; @@ -210,7 +210,6 @@ function findMostSpecificMatchedCSSRule(matchedCSSRules = [], property) { // @ts-ignore the existence of the property object is checked in the for loop return maxSpecificityRule.style.cssProperties.find(({name}) => name === property).value; } - return ''; } /** From 9f4b2c9b787ad2e63e6468a3bbf01550675d25cb Mon Sep 17 00:00:00 2001 From: Lemuel Cardenas-arriaga Date: Mon, 27 Jul 2020 16:08:51 -0700 Subject: [PATCH 35/68] first attempt at smoke test --- .../test/fixtures/byte-efficiency/tester.html | 16 ++++++++++++++++ .../byte-efficiency/byte-config.js | 2 ++ .../byte-efficiency/expectations.js | 13 +++++++++++++ 3 files changed, 31 insertions(+) diff --git a/lighthouse-cli/test/fixtures/byte-efficiency/tester.html b/lighthouse-cli/test/fixtures/byte-efficiency/tester.html index bacbd14215e4..1f302ae762f2 100644 --- a/lighthouse-cli/test/fixtures/byte-efficiency/tester.html +++ b/lighthouse-cli/test/fixtures/byte-efficiency/tester.html @@ -76,6 +76,7 @@

Byte efficiency tester page

+ @@ -90,6 +91,7 @@

Byte efficiency tester page

+ @@ -97,6 +99,7 @@

Byte efficiency tester page

+ @@ -104,6 +107,7 @@

Byte efficiency tester page

+ @@ -111,6 +115,7 @@

Byte efficiency tester page

+ @@ -118,6 +123,7 @@

Byte efficiency tester page

+ @@ -125,6 +131,7 @@

Byte efficiency tester page

+ @@ -136,6 +143,7 @@

Byte efficiency tester page

+ @@ -143,6 +151,7 @@

Byte efficiency tester page

+ @@ -150,6 +159,7 @@

Byte efficiency tester page

+ @@ -157,6 +167,7 @@

Byte efficiency tester page

+ @@ -164,6 +175,7 @@

Byte efficiency tester page

+ @@ -171,6 +183,7 @@

Byte efficiency tester page

+ @@ -178,6 +191,7 @@

Byte efficiency tester page

+
@@ -185,6 +199,7 @@

Byte efficiency tester page

+
@@ -196,6 +211,7 @@

Byte efficiency tester page

+ diff --git a/lighthouse-cli/test/smokehouse/test-definitions/byte-efficiency/byte-config.js b/lighthouse-cli/test/smokehouse/test-definitions/byte-efficiency/byte-config.js index f1f852c75d4c..5afe0b446170 100644 --- a/lighthouse-cli/test/smokehouse/test-definitions/byte-efficiency/byte-config.js +++ b/lighthouse-cli/test/smokehouse/test-definitions/byte-efficiency/byte-config.js @@ -27,6 +27,8 @@ 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', + // same as above + 'unsized-images', ], throttlingMethod: 'devtools', }, diff --git a/lighthouse-cli/test/smokehouse/test-definitions/byte-efficiency/expectations.js b/lighthouse-cli/test/smokehouse/test-definitions/byte-efficiency/expectations.js index 743d13d269b1..0c08452bb374 100644 --- a/lighthouse-cli/test/smokehouse/test-definitions/byte-efficiency/expectations.js +++ b/lighthouse-cli/test/smokehouse/test-definitions/byte-efficiency/expectations.js @@ -231,6 +231,19 @@ const expectations = [ ], }, }, + 'unsized-images': { + details: { + items: [ + {url: /'lighthouse-unoptimized.jpg'/}, + {url: /'lighthouse-2048x1356.webp?size0'/}, + {url: /'lighthouse-320x212-poor.jpg'/}, + {url: /'lighthouse-320x212-poor.jpg?srcset'/}, // unsure if this works since srcset + {url: /'lighthouse-320x212-poor.jpg?picture'/}, // unsure if this works since + {url: /'lighthouse-320x212-poor.jpg?duplicate'/}, + {url: /'lighthouse-480x320.webp?lazilyLoaded=true'/}, + ], + }, + }, }, }, }, From e4a6e23ceea3b8a0d3b49fd5145cdfe047daca37 Mon Sep 17 00:00:00 2001 From: Lemuel Cardenas-arriaga Date: Mon, 27 Jul 2020 16:14:05 -0700 Subject: [PATCH 36/68] fixed commenting format (cherry picked from commit 7c5935899234321862fa0ed2e77af74b982d69ca) --- lighthouse-core/audits/unsized-images.js | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/lighthouse-core/audits/unsized-images.js b/lighthouse-core/audits/unsized-images.js index 619018a59dc1..67cc6ee3d49f 100644 --- a/lighthouse-core/audits/unsized-images.js +++ b/lighthouse-core/audits/unsized-images.js @@ -22,7 +22,7 @@ const str_ = i18n.createMessageInstanceIdFn(__filename, UIStrings); /** * @fileoverview - * Audit that checks whether all images have width and height attributes. + * Audit that checks whether all images have explicit width and height. */ class SizedImages extends Audit { @@ -44,8 +44,8 @@ class SizedImages extends Audit { * @return {boolean} */ static isValidAttr(attr) { - // an img size attribute is valid for preventing CLS - // if it is a non-negative, non-zero integer + // An img size attribute is valid for preventing CLS + // if it is a non-negative, non-zero integer. const NON_NEGATIVE_INT_REGEX = /^\d+$/; const ZERO_REGEX = /^0+$/; return NON_NEGATIVE_INT_REGEX.test(attr) && !ZERO_REGEX.test(attr); @@ -56,10 +56,10 @@ class SizedImages extends Audit { * @return {boolean} */ static isValidCss(property) { - // an img css size property is valid for preventing CLS - // if it is defined, not empty, and not equal to 'auto' + // An img css size property is valid for preventing CLS + // if it is defined, not empty, and not equal to 'auto'. // `undefined` and `''` are implicitly rejected as invalid - // because of their falsy short-circuit of && in isUnsizedImage + // because of their falsy short-circuit of && in isUnsizedImage. if (!property) return false; return property !== 'auto'; } @@ -69,7 +69,7 @@ class SizedImages extends Audit { * @return {boolean} */ static isUnsizedImage(image) { - // images are considered sized if they have defined & valid values + // Images are considered sized if they have defined & valid values. const attrWidth = image.attributeWidth; const attrHeight = image.attributeHeight; const cssWidth = image.cssWidth; @@ -88,7 +88,7 @@ class SizedImages extends Audit { * @return {Promise} */ static async audit(artifacts) { - // CSS background-images are ignored for this audit + // CSS background-images are ignored for this audit. const images = artifacts.ImageElements.filter(el => !el.isCss); const unsizedImages = []; From cfdb2bed8c5988043e1a8003610a04a6b8a05df2 Mon Sep 17 00:00:00 2001 From: Lemuel Cardenas-arriaga Date: Mon, 27 Jul 2020 16:27:33 -0700 Subject: [PATCH 37/68] updated audit description UIString (cherry picked from commit 9c97f250ed7ed27793ed55db6a8d5e6295986b43) --- lighthouse-core/audits/unsized-images.js | 2 +- lighthouse-core/lib/i18n/locales/en-US.json | 2 +- lighthouse-core/lib/i18n/locales/en-XL.json | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/lighthouse-core/audits/unsized-images.js b/lighthouse-core/audits/unsized-images.js index 67cc6ee3d49f..c9962cfadd93 100644 --- a/lighthouse-core/audits/unsized-images.js +++ b/lighthouse-core/audits/unsized-images.js @@ -15,7 +15,7 @@ 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 your image elements to reduce layout shifting and improve CLS. [Learn more](https://web.dev/optimize-cls/#images-without-dimensions)', + 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)', }; const str_ = i18n.createMessageInstanceIdFn(__filename, UIStrings); diff --git a/lighthouse-core/lib/i18n/locales/en-US.json b/lighthouse-core/lib/i18n/locales/en-US.json index 853a7ebe4a9f..7ab944ef2b6b 100644 --- a/lighthouse-core/lib/i18n/locales/en-US.json +++ b/lighthouse-core/lib/i18n/locales/en-US.json @@ -1260,7 +1260,7 @@ "message": "Timing budget" }, "lighthouse-core/audits/unsized-images.js | description": { - "message": "Always include explicit width and height on your image elements to reduce layout shifting and improve CLS. [Learn more](https://web.dev/optimize-cls/#images-without-dimensions)" + "message": "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)" }, "lighthouse-core/audits/unsized-images.js | failureTitle": { "message": "Image elements do not have explicit `width` and `height`" diff --git a/lighthouse-core/lib/i18n/locales/en-XL.json b/lighthouse-core/lib/i18n/locales/en-XL.json index 3a1d6cc3387f..db6894dfd96a 100644 --- a/lighthouse-core/lib/i18n/locales/en-XL.json +++ b/lighthouse-core/lib/i18n/locales/en-XL.json @@ -1260,7 +1260,7 @@ "message": "T̂ím̂ín̂ǵ b̂úd̂ǵêt́" }, "lighthouse-core/audits/unsized-images.js | description": { - "message": "Âĺŵáŷś îńĉĺûd́ê éx̂ṕl̂íĉít̂ ẃîd́t̂h́ âńd̂ h́êíĝh́t̂ ón̂ ýôúr̂ ím̂áĝé êĺêḿêńt̂ś t̂ó r̂éd̂úĉé l̂áŷóût́ ŝh́îf́t̂ín̂ǵ âńd̂ ím̂ṕr̂óv̂é ĈĹŜ. [Ĺêár̂ń m̂ór̂é](https://web.dev/optimize-cls/#images-without-dimensions)" + "message": "Âĺŵáŷś îńĉĺûd́ê éx̂ṕl̂íĉít̂ ẃîd́t̂h́ âńd̂ h́êíĝh́t̂ ón̂ ím̂áĝé êĺêḿêńt̂ś t̂ó r̂éd̂úĉé l̂áŷóût́ ŝh́îf́t̂ś âńd̂ ím̂ṕr̂óv̂é ĈĹŜ. [Ĺêár̂ń m̂ór̂é](https://web.dev/optimize-cls/#images-without-dimensions)" }, "lighthouse-core/audits/unsized-images.js | failureTitle": { "message": "Îḿâǵê él̂ém̂én̂t́ŝ d́ô ńôt́ ĥáv̂é êx́p̂ĺîćît́ `width` âńd̂ `height`" From a578844d85b5d9d67c1722607008033939bb05e5 Mon Sep 17 00:00:00 2001 From: Lemuel Cardenas-arriaga Date: Mon, 27 Jul 2020 16:51:36 -0700 Subject: [PATCH 38/68] fixed nits (cherry picked from commit a684510e2a75a8500ff6131af26c60df79fdb6b4) --- lighthouse-core/audits/unsized-images.js | 57 +++++++++---------- lighthouse-core/config/experimental-config.js | 4 +- 2 files changed, 29 insertions(+), 32 deletions(-) diff --git a/lighthouse-core/audits/unsized-images.js b/lighthouse-core/audits/unsized-images.js index c9962cfadd93..1036b0b62b1f 100644 --- a/lighthouse-core/audits/unsized-images.js +++ b/lighthouse-core/audits/unsized-images.js @@ -3,6 +3,11 @@ * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. */ +/** + * @fileoverview + * Audit that checks whether all images have explicit width and height. + */ + 'use strict'; const Audit = require('./audit.js'); @@ -20,11 +25,6 @@ const UIStrings = { const str_ = i18n.createMessageInstanceIdFn(__filename, UIStrings); -/** - * @fileoverview - * Audit that checks whether all images have explicit width and height. - */ - class SizedImages extends Audit { /** * @return {LH.Audit.Meta} @@ -40,44 +40,42 @@ class SizedImages extends Audit { } /** + * An img size attribute is valid for preventing CLS + * if it is a non-negative, non-zero integer. * @param {string} attr * @return {boolean} */ static isValidAttr(attr) { - // An img size attribute is valid for preventing CLS - // if it is a non-negative, non-zero integer. const NON_NEGATIVE_INT_REGEX = /^\d+$/; const ZERO_REGEX = /^0+$/; return NON_NEGATIVE_INT_REGEX.test(attr) && !ZERO_REGEX.test(attr); } /** - * @param {string} property + * An img css size property is valid for preventing CLS + * if it is defined, not empty, and not equal to 'auto'. + * @param {string | undefined} property * @return {boolean} */ static isValidCss(property) { - // An img css size property is valid for preventing CLS - // if it is defined, not empty, and not equal to 'auto'. - // `undefined` and `''` are implicitly rejected as invalid - // because of their falsy short-circuit of && in isUnsizedImage. if (!property) return false; return property !== 'auto'; } /** + * Images are considered sized if they have defined & valid values. * @param {LH.Artifacts.ImageElement} image * @return {boolean} */ static isUnsizedImage(image) { - // Images are considered sized if they have defined & valid values. const attrWidth = image.attributeWidth; const attrHeight = image.attributeHeight; const cssWidth = image.cssWidth; const cssHeight = image.cssHeight; - const widthIsValidAttribute = attrWidth && SizedImages.isValidAttr(attrWidth); - const widthIsValidCss = cssWidth && SizedImages.isValidCss(cssWidth); - const heightIsValidAttribute = attrHeight && SizedImages.isValidAttr(attrHeight); - const heightIsValidCss = cssHeight && SizedImages.isValidCss(cssHeight); + const widthIsValidAttribute = SizedImages.isValidAttr(attrWidth); + const widthIsValidCss = SizedImages.isValidCss(cssWidth); + const heightIsValidAttribute = SizedImages.isValidAttr(attrHeight); + const heightIsValidCss = SizedImages.isValidCss(cssHeight); const validWidth = widthIsValidAttribute || widthIsValidCss; const validHeight = heightIsValidAttribute || heightIsValidCss; return !validWidth || !validHeight; @@ -93,19 +91,18 @@ class SizedImages extends Audit { const unsizedImages = []; for (const image of images) { - if (SizedImages.isUnsizedImage(image)) { - const url = URL.elideDataURI(image.src); - unsizedImages.push({ - url, - node: /** @type {LH.Audit.Details.NodeValue} */ ({ - type: 'node', - path: image.devtoolsNodePath, - selector: image.selector, - nodeLabel: image.nodeLabel, - snippet: image.snippet, - }), - }); - } + if (!SizedImages.isUnsizedImage(image)) continue; + const url = URL.elideDataURI(image.src); + unsizedImages.push({ + url, + node: /** @type {LH.Audit.Details.NodeValue} */ ({ + type: 'node', + path: image.devtoolsNodePath, + selector: image.selector, + nodeLabel: image.nodeLabel, + snippet: image.snippet, + }), + }); } /** @type {LH.Audit.Details.Table['headings']} */ diff --git a/lighthouse-core/config/experimental-config.js b/lighthouse-core/config/experimental-config.js index 3f9a4fb9b06b..a9ba9327eb5c 100644 --- a/lighthouse-core/config/experimental-config.js +++ b/lighthouse-core/config/experimental-config.js @@ -24,8 +24,8 @@ const config = { ], }], categories: { - // @ts-ignore: `title` is required in CategoryJson. setting to the same value as the default - // config is awkward - easier to omit the property here. Will defer to default config. + // @ts-ignore: `title` is required in CategoryJson. setting to the same value as the default + // config is awkward - easier to omit the property here. Will defer to default config. 'best-practices': { auditRefs: [ {id: 'unsized-images', weight: 1, group: 'best-practices-ux'}, From 3e0098cf016280671af0e66023421c97dd27970d Mon Sep 17 00:00:00 2001 From: Lemuel Cardenas-arriaga Date: Tue, 28 Jul 2020 15:29:50 -0700 Subject: [PATCH 39/68] working smoke test --- .../test/fixtures/byte-efficiency/tester.html | 2 +- .../byte-efficiency/expectations.js | 13 ++++++------- lighthouse-core/audits/unsized-images.js | 4 ++-- lighthouse-core/config/default-config.js | 2 ++ lighthouse-core/gather/gatherers/image-elements.js | 9 ++++++--- types/artifacts.d.ts | 2 ++ 6 files changed, 19 insertions(+), 13 deletions(-) diff --git a/lighthouse-cli/test/fixtures/byte-efficiency/tester.html b/lighthouse-cli/test/fixtures/byte-efficiency/tester.html index 1f302ae762f2..ba37fb32c3a9 100644 --- a/lighthouse-cli/test/fixtures/byte-efficiency/tester.html +++ b/lighthouse-cli/test/fixtures/byte-efficiency/tester.html @@ -99,7 +99,7 @@

Byte efficiency tester page

- + diff --git a/lighthouse-cli/test/smokehouse/test-definitions/byte-efficiency/expectations.js b/lighthouse-cli/test/smokehouse/test-definitions/byte-efficiency/expectations.js index 0c08452bb374..9353fdbddc9d 100644 --- a/lighthouse-cli/test/smokehouse/test-definitions/byte-efficiency/expectations.js +++ b/lighthouse-cli/test/smokehouse/test-definitions/byte-efficiency/expectations.js @@ -234,13 +234,12 @@ const expectations = [ 'unsized-images': { details: { items: [ - {url: /'lighthouse-unoptimized.jpg'/}, - {url: /'lighthouse-2048x1356.webp?size0'/}, - {url: /'lighthouse-320x212-poor.jpg'/}, - {url: /'lighthouse-320x212-poor.jpg?srcset'/}, // unsure if this works since srcset - {url: /'lighthouse-320x212-poor.jpg?picture'/}, // unsure if this works since - {url: /'lighthouse-320x212-poor.jpg?duplicate'/}, - {url: /'lighthouse-480x320.webp?lazilyLoaded=true'/}, + {url: /lighthouse-unoptimized\.jpg/}, + {url: /lighthouse-320x212-poor\.jpg/}, + {url: /lighthouse-.+\?srcset/}, + {url: /lighthouse-.+\?picture/}, + {url: /lighthouse-320x212-poor\.jpg\?duplicate/}, + {url: /lighthouse-480x320\.webp\?lazilyLoaded=true/}, ], }, }, diff --git a/lighthouse-core/audits/unsized-images.js b/lighthouse-core/audits/unsized-images.js index 1036b0b62b1f..d3e4683a6036 100644 --- a/lighthouse-core/audits/unsized-images.js +++ b/lighthouse-core/audits/unsized-images.js @@ -86,8 +86,8 @@ class SizedImages extends Audit { * @return {Promise} */ 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.isShadow); const unsizedImages = []; for (const image of images) { diff --git a/lighthouse-core/config/default-config.js b/lighthouse-core/config/default-config.js index 19a06ce9ca0c..f41b53322a0d 100644 --- a/lighthouse-core/config/default-config.js +++ b/lighthouse-core/config/default-config.js @@ -238,6 +238,7 @@ const defaultConfig = { 'layout-shift-elements', 'long-tasks', 'no-unload-listeners', + 'unsized-images', 'manual/pwa-cross-browser', 'manual/pwa-page-transitions', 'manual/pwa-each-page-has-url', @@ -552,6 +553,7 @@ const defaultConfig = { {id: 'no-vulnerable-libraries', weight: 1, group: 'best-practices-trust-safety'}, // User Experience {id: 'password-inputs-can-be-pasted-into', weight: 1, group: 'best-practices-ux'}, + {id: 'unsized-images', weight: 1, group: 'best-practices-ux'}, {id: 'image-aspect-ratio', weight: 1, group: 'best-practices-ux'}, {id: 'image-size-responsive', weight: 1, group: 'best-practices-ux'}, // Browser Compatibility diff --git a/lighthouse-core/gather/gatherers/image-elements.js b/lighthouse-core/gather/gatherers/image-elements.js index a3ca8fa54579..18ef0e2601e3 100644 --- a/lighthouse-core/gather/gatherers/image-elements.js +++ b/lighthouse-core/gather/gatherers/image-elements.js @@ -14,7 +14,7 @@ const pageFunctions = require('../../lib/page-functions.js'); const Driver = require('../driver.js'); // eslint-disable-line no-unused-vars const FontSize = require('./seo/font-size.js'); -/* global window, getElementsInDocument, Image, getNodePath, getNodeSelector, getNodeLabel, getOuterHTMLSnippet */ +/* global window, getElementsInDocument, Image, getNodePath, getNodeSelector, getNodeLabel, getOuterHTMLSnippet, ShadowRoot */ /** @param {Element} element */ @@ -67,6 +67,7 @@ function getHTMLImages(allElements) { usesPixelArtScaling: ['pixelated', 'crisp-edges'].includes( computedStyle.getPropertyValue('image-rendering') ), + isShadow: element.getRootNode() instanceof ShadowRoot, // https://html.spec.whatwg.org/multipage/images.html#pixel-density-descriptor usesSrcSetDensityDescriptor: / \d+(\.\d+)?x/.test(element.srcset), // @ts-ignore - getNodePath put into scope via stringification @@ -118,6 +119,7 @@ function getCSSImages(allElements) { cssHeight: '', isCss: true, isPicture: false, + isShadow: element.getRootNode() instanceof ShadowRoot, usesObjectFit: false, usesPixelArtScaling: ['pixelated', 'crisp-edges'].includes( style.getPropertyValue('image-rendering') @@ -337,7 +339,9 @@ class ImageElements extends Gatherer { // Use the min of the two numbers to be safe. const {resourceSize = 0, transferSize = 0} = networkRecord; element.resourceSize = Math.min(resourceSize, transferSize); - await this.fetchSourceRules(driver, element.devtoolsNodePath, element); + if (!element.isShadow) { + await this.fetchSourceRules(driver, element.devtoolsNodePath, element); + } // Images within `picture` behave strangely and natural size information isn't accurate, // CSS images have no natural size information at all. Try to get the actual size if we can. // Additional fetch is expensive; don't bother if we don't have a networkRecord for the image, @@ -349,7 +353,6 @@ class ImageElements extends Gatherer { ) { element = await this.fetchElementWithSizeInformation(driver, element); } - imageUsage.push(element); } diff --git a/types/artifacts.d.ts b/types/artifacts.d.ts index 18fc796579e8..5f1b9146f148 100644 --- a/types/artifacts.d.ts +++ b/types/artifacts.d.ts @@ -429,6 +429,8 @@ declare global { isCss: boolean; /** Flags whether this element was contained within a tag. */ isPicture: boolean; + /** Flags whether this element was contained within a ShadowRoot */ + isShadow: boolean; /** Flags whether this element was sized using a non-default `object-fit` CSS property. */ usesObjectFit: boolean; /** Flags whether this element was rendered using a pixel art scaling method. From f9699044c2d8f4b5dd405d9af2b3a4aa5074a2b8 Mon Sep 17 00:00:00 2001 From: Lemuel Cardenas-arriaga Date: Tue, 28 Jul 2020 15:42:48 -0700 Subject: [PATCH 40/68] updated sample json --- lighthouse-core/test/results/sample_v2.json | 84 ++++++++++++++++++--- 1 file changed, 75 insertions(+), 9 deletions(-) diff --git a/lighthouse-core/test/results/sample_v2.json b/lighthouse-core/test/results/sample_v2.json index 686c0255c53c..348642b62a41 100644 --- a/lighthouse-core/test/results/sample_v2.json +++ b/lighthouse-core/test/results/sample_v2.json @@ -1876,6 +1876,53 @@ ] } }, + "unsized-images": { + "id": "unsized-images", + "title": "Image elements do not have explicit `width` and `height`", + "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)", + "score": 0, + "scoreDisplayMode": "binary", + "details": { + "type": "table", + "headings": [ + { + "key": "url", + "itemType": "thumbnail", + "text": "" + }, + { + "key": "url", + "itemType": "url", + "text": "URL" + }, + { + "key": "node", + "itemType": "node", + "text": "Failing Elements" + } + ], + "items": [ + { + "url": "http://localhost:10200/dobetterweb/lighthouse-480x318.jpg", + "node": { + "type": "node" + } + }, + { + "url": "http://localhost:10200/dobetterweb/lighthouse-480x318.jpg", + "node": { + "type": "node" + } + }, + { + "url": "blob:http://localhost:10200/ae0eac03-ab9b-4a6a-b299-f5212153e277", + "node": { + "type": "node" + } + } + ] + } + }, "pwa-cross-browser": { "id": "pwa-cross-browser", "title": "Site works cross-browser", @@ -4659,6 +4706,11 @@ "weight": 1, "group": "best-practices-ux" }, + { + "id": "unsized-images", + "weight": 1, + "group": "best-practices-ux" + }, { "id": "image-aspect-ratio", "weight": 1, @@ -5507,6 +5559,12 @@ "duration": 100, "entryType": "measure" }, + { + "startTime": 0, + "name": "lh:audit:unsized-images", + "duration": 100, + "entryType": "measure" + }, { "startTime": 0, "name": "lh:audit:pwa-cross-browser", @@ -6369,6 +6427,7 @@ "audits[network-server-latency].details.headings[0].text", "audits[long-tasks].details.headings[0].text", "audits[no-unload-listeners].details.headings[0].text", + "audits[unsized-images].details.headings[1].text", "audits[uses-long-cache-ttl].details.headings[0].text", "audits[total-byte-weight].details.headings[0].text", "audits[render-blocking-resources].details.headings[0].label", @@ -6783,6 +6842,22 @@ "lighthouse-core/audits/no-unload-listeners.js | description": [ "audits[no-unload-listeners].description" ], + "lighthouse-core/audits/unsized-images.js | failureTitle": [ + "audits[unsized-images].title" + ], + "lighthouse-core/audits/unsized-images.js | description": [ + "audits[unsized-images].description" + ], + "lighthouse-core/lib/i18n/i18n.js | columnFailingElem": [ + "audits[unsized-images].details.headings[2].text", + "audits[color-contrast].details.headings[0].text", + "audits[html-has-lang].details.headings[0].text", + "audits[image-alt].details.headings[0].text", + "audits.label.details.headings[0].text", + "audits[link-name].details.headings[0].text", + "audits[object-alt].details.headings[0].text", + "audits[password-inputs-can-be-pasted-into].details.headings[0].text" + ], "lighthouse-core/audits/manual/pwa-cross-browser.js | title": [ "audits[pwa-cross-browser].title" ], @@ -6891,15 +6966,6 @@ "lighthouse-core/audits/accessibility/color-contrast.js | description": [ "audits[color-contrast].description" ], - "lighthouse-core/lib/i18n/i18n.js | columnFailingElem": [ - "audits[color-contrast].details.headings[0].text", - "audits[html-has-lang].details.headings[0].text", - "audits[image-alt].details.headings[0].text", - "audits.label.details.headings[0].text", - "audits[link-name].details.headings[0].text", - "audits[object-alt].details.headings[0].text", - "audits[password-inputs-can-be-pasted-into].details.headings[0].text" - ], "lighthouse-core/audits/accessibility/definition-list.js | title": [ "audits[definition-list].title" ], From 2aa3f6179c3acf6eb529cd28502a0fbdc3054458 Mon Sep 17 00:00:00 2001 From: Lemuel Cardenas-arriaga Date: Tue, 28 Jul 2020 15:54:20 -0700 Subject: [PATCH 41/68] fixed index snapshot --- lighthouse-cli/test/cli/__snapshots__/index-test.js.snap | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/lighthouse-cli/test/cli/__snapshots__/index-test.js.snap b/lighthouse-cli/test/cli/__snapshots__/index-test.js.snap index 609b5ff1b327..744cbc947059 100644 --- a/lighthouse-cli/test/cli/__snapshots__/index-test.js.snap +++ b/lighthouse-cli/test/cli/__snapshots__/index-test.js.snap @@ -93,6 +93,9 @@ Object { Object { "path": "content-width", }, + Object { + "path": "unsized-images", + }, Object { "path": "image-aspect-ratio", }, @@ -730,6 +733,11 @@ Object { "id": "password-inputs-can-be-pasted-into", "weight": 1, }, + Object { + "group": "best-practices-ux", + "id": "unsized-images", + "weight": 1, + }, Object { "group": "best-practices-ux", "id": "image-aspect-ratio", From 6acbbfd443ec8147e4cc5bf40b2a23d1dc644ab8 Mon Sep 17 00:00:00 2001 From: Lemuel Cardenas-arriaga Date: Tue, 28 Jul 2020 15:58:15 -0700 Subject: [PATCH 42/68] fixed default config --- lighthouse-core/config/default-config.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lighthouse-core/config/default-config.js b/lighthouse-core/config/default-config.js index 966fc8889f5a..0e79110d1ad8 100644 --- a/lighthouse-core/config/default-config.js +++ b/lighthouse-core/config/default-config.js @@ -215,6 +215,7 @@ const defaultConfig = { 'themed-omnibox', 'maskable-icon', 'content-width', + 'unsized-images', 'image-aspect-ratio', 'image-size-responsive', 'deprecations', @@ -238,7 +239,6 @@ const defaultConfig = { 'layout-shift-elements', 'long-tasks', 'no-unload-listeners', - 'unsized-images', 'manual/pwa-cross-browser', 'manual/pwa-page-transitions', 'manual/pwa-each-page-has-url', From d4574f1cea9d0bdefb2bf50a4d72c7902cc40294 Mon Sep 17 00:00:00 2001 From: Lemuel Cardenas-arriaga Date: Tue, 28 Jul 2020 15:58:51 -0700 Subject: [PATCH 43/68] update sample json --- lighthouse-core/test/results/sample_v2.json | 140 ++++++++++---------- 1 file changed, 70 insertions(+), 70 deletions(-) diff --git a/lighthouse-core/test/results/sample_v2.json b/lighthouse-core/test/results/sample_v2.json index 348642b62a41..0e4adf4eb443 100644 --- a/lighthouse-core/test/results/sample_v2.json +++ b/lighthouse-core/test/results/sample_v2.json @@ -581,6 +581,53 @@ "scoreDisplayMode": "binary", "explanation": "" }, + "unsized-images": { + "id": "unsized-images", + "title": "Image elements do not have explicit `width` and `height`", + "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)", + "score": 0, + "scoreDisplayMode": "binary", + "details": { + "type": "table", + "headings": [ + { + "key": "url", + "itemType": "thumbnail", + "text": "" + }, + { + "key": "url", + "itemType": "url", + "text": "URL" + }, + { + "key": "node", + "itemType": "node", + "text": "Failing Elements" + } + ], + "items": [ + { + "url": "http://localhost:10200/dobetterweb/lighthouse-480x318.jpg", + "node": { + "type": "node" + } + }, + { + "url": "http://localhost:10200/dobetterweb/lighthouse-480x318.jpg", + "node": { + "type": "node" + } + }, + { + "url": "blob:http://localhost:10200/ae0eac03-ab9b-4a6a-b299-f5212153e277", + "node": { + "type": "node" + } + } + ] + } + }, "image-aspect-ratio": { "id": "image-aspect-ratio", "title": "Displays images with incorrect aspect ratio", @@ -1876,53 +1923,6 @@ ] } }, - "unsized-images": { - "id": "unsized-images", - "title": "Image elements do not have explicit `width` and `height`", - "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)", - "score": 0, - "scoreDisplayMode": "binary", - "details": { - "type": "table", - "headings": [ - { - "key": "url", - "itemType": "thumbnail", - "text": "" - }, - { - "key": "url", - "itemType": "url", - "text": "URL" - }, - { - "key": "node", - "itemType": "node", - "text": "Failing Elements" - } - ], - "items": [ - { - "url": "http://localhost:10200/dobetterweb/lighthouse-480x318.jpg", - "node": { - "type": "node" - } - }, - { - "url": "http://localhost:10200/dobetterweb/lighthouse-480x318.jpg", - "node": { - "type": "node" - } - }, - { - "url": "blob:http://localhost:10200/ae0eac03-ab9b-4a6a-b299-f5212153e277", - "node": { - "type": "node" - } - } - ] - } - }, "pwa-cross-browser": { "id": "pwa-cross-browser", "title": "Site works cross-browser", @@ -5397,6 +5397,12 @@ "duration": 100, "entryType": "measure" }, + { + "startTime": 0, + "name": "lh:audit:unsized-images", + "duration": 100, + "entryType": "measure" + }, { "startTime": 0, "name": "lh:audit:image-aspect-ratio", @@ -5559,12 +5565,6 @@ "duration": 100, "entryType": "measure" }, - { - "startTime": 0, - "name": "lh:audit:unsized-images", - "duration": 100, - "entryType": "measure" - }, { "startTime": 0, "name": "lh:audit:pwa-cross-browser", @@ -6419,6 +6419,7 @@ ], "lighthouse-core/lib/i18n/i18n.js | columnURL": [ "audits[errors-in-console].details.headings[0].text", + "audits[unsized-images].details.headings[1].text", "audits[image-aspect-ratio].details.headings[1].text", "audits[image-size-responsive].details.headings[1].text", "audits.deprecations.details.headings[1].text", @@ -6427,7 +6428,6 @@ "audits[network-server-latency].details.headings[0].text", "audits[long-tasks].details.headings[0].text", "audits[no-unload-listeners].details.headings[0].text", - "audits[unsized-images].details.headings[1].text", "audits[uses-long-cache-ttl].details.headings[0].text", "audits[total-byte-weight].details.headings[0].text", "audits[render-blocking-resources].details.headings[0].label", @@ -6534,6 +6534,22 @@ "lighthouse-core/audits/content-width.js | description": [ "audits[content-width].description" ], + "lighthouse-core/audits/unsized-images.js | failureTitle": [ + "audits[unsized-images].title" + ], + "lighthouse-core/audits/unsized-images.js | description": [ + "audits[unsized-images].description" + ], + "lighthouse-core/lib/i18n/i18n.js | columnFailingElem": [ + "audits[unsized-images].details.headings[2].text", + "audits[color-contrast].details.headings[0].text", + "audits[html-has-lang].details.headings[0].text", + "audits[image-alt].details.headings[0].text", + "audits.label.details.headings[0].text", + "audits[link-name].details.headings[0].text", + "audits[object-alt].details.headings[0].text", + "audits[password-inputs-can-be-pasted-into].details.headings[0].text" + ], "lighthouse-core/audits/image-aspect-ratio.js | failureTitle": [ "audits[image-aspect-ratio].title" ], @@ -6842,22 +6858,6 @@ "lighthouse-core/audits/no-unload-listeners.js | description": [ "audits[no-unload-listeners].description" ], - "lighthouse-core/audits/unsized-images.js | failureTitle": [ - "audits[unsized-images].title" - ], - "lighthouse-core/audits/unsized-images.js | description": [ - "audits[unsized-images].description" - ], - "lighthouse-core/lib/i18n/i18n.js | columnFailingElem": [ - "audits[unsized-images].details.headings[2].text", - "audits[color-contrast].details.headings[0].text", - "audits[html-has-lang].details.headings[0].text", - "audits[image-alt].details.headings[0].text", - "audits.label.details.headings[0].text", - "audits[link-name].details.headings[0].text", - "audits[object-alt].details.headings[0].text", - "audits[password-inputs-can-be-pasted-into].details.headings[0].text" - ], "lighthouse-core/audits/manual/pwa-cross-browser.js | title": [ "audits[pwa-cross-browser].title" ], From 430d97fcc05a3866552c22eede669a2d898adcbb Mon Sep 17 00:00:00 2001 From: Lemuel Cardenas-arriaga Date: Wed, 29 Jul 2020 09:21:57 -0700 Subject: [PATCH 44/68] new comments --- .../smokehouse/test-definitions/byte-efficiency/byte-config.js | 3 ++- lighthouse-core/audits/unsized-images.js | 2 +- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/lighthouse-cli/test/smokehouse/test-definitions/byte-efficiency/byte-config.js b/lighthouse-cli/test/smokehouse/test-definitions/byte-efficiency/byte-config.js index 8934be4337ab..343de10ba307 100644 --- a/lighthouse-cli/test/smokehouse/test-definitions/byte-efficiency/byte-config.js +++ b/lighthouse-cli/test/smokehouse/test-definitions/byte-efficiency/byte-config.js @@ -28,7 +28,8 @@ 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', - // same as above + // unsized-images is not a byte-efficiency audit but can easily leverage the variety of images present in + // byte-efficiency tests; removing the need for a completely new smoke test. 'unsized-images', ], throttlingMethod: 'devtools', diff --git a/lighthouse-core/audits/unsized-images.js b/lighthouse-core/audits/unsized-images.js index d3e4683a6036..44cbfa434d92 100644 --- a/lighthouse-core/audits/unsized-images.js +++ b/lighthouse-core/audits/unsized-images.js @@ -78,7 +78,7 @@ class SizedImages extends Audit { const heightIsValidCss = SizedImages.isValidCss(cssHeight); const validWidth = widthIsValidAttribute || widthIsValidCss; const validHeight = heightIsValidAttribute || heightIsValidCss; - return !validWidth || !validHeight; + return !validWidth || !validHeight; // change to validWidth && validHeight + isSizedImage, remove ! below } /** From 0d1b43bf09e4adbfb018969ed2e99bc74420b36b Mon Sep 17 00:00:00 2001 From: Lemuel Cardenas-arriaga Date: Tue, 28 Jul 2020 17:27:00 -0700 Subject: [PATCH 45/68] refactored unsized-images-test (cherry picked from commit 1504d1dc56d6a6c9657bb802dd6f8d66d2fcd5bd) --- .../test/audits/unsized-images-test.js | 246 +++++++++--------- 1 file changed, 123 insertions(+), 123 deletions(-) diff --git a/lighthouse-core/test/audits/unsized-images-test.js b/lighthouse-core/test/audits/unsized-images-test.js index 3a1ab44de13a..caae685f5335 100644 --- a/lighthouse-core/test/audits/unsized-images-test.js +++ b/lighthouse-core/test/audits/unsized-images-test.js @@ -16,306 +16,306 @@ function generateImage(props, src = 'https://google.com/logo.png', isCss = false } describe('Sized images audit', () => { - function testImage(condition, data) { - const description = `handles when an image ${condition}`; - it(description, async () => { - const result = await UnSizedImagesAudit.audit({ - ImageElements: [ - generateImage( - data.props - ), - ], - }); - expect(result.score).toEqual(data.score); + function runAudit(props) { + const result = UnSizedImagesAudit.audit({ + ImageElements: [ + generateImage( + props + ), + ], }); + return result; } - testImage('is a css image', { - score: 1, - props: { + it('passes when an image is a css image', async () => { + const result = await runAudit({ isCss: true, attributeWidth: '', attributeHeight: '', cssWidth: '', cssHeight: '', - }, + }); + expect(result.score).toEqual(1); }); describe('has empty width', () => { - testImage('only has attribute height', { - score: 0, - props: { + it('fails when an image only has attribute height', async () => { + const result = await runAudit({ attributeWidth: '', attributeHeight: '100', cssWidth: '', cssHeight: '', - }, + }); + expect(result.score).toEqual(0); }); - testImage('only has css height', { - score: 0, - props: { + it('fails when an image only has css height', async () => { + const result = await runAudit({ attributeWidth: '', attributeHeight: '', cssWidth: '', cssHeight: '100', - }, + }); + expect(result.score).toEqual(0); }); - testImage('only has attribute height & css height', { - score: 0, - props: { + it('fails when an image only has attribute height & css height', async () => { + const result = await runAudit({ attributeWidth: '', attributeHeight: '100', cssWidth: '', cssHeight: '100', - }, + }); + expect(result.score).toEqual(0); }); }); describe('has empty height', () => { - testImage('only has attribute width', { - score: 0, - props: { + it('fails when an image only has attribute width', async () => { + const result = await runAudit({ attributeWidth: '100', attributeHeight: '', cssWidth: '', cssHeight: '', - }, + }); + expect(result.score).toEqual(0); }); - testImage('only has css width', { - score: 0, - props: { + it('fails when an image only has css width', async () => { + const result = await runAudit({ attributeWidth: '', attributeHeight: '', cssWidth: '100', cssHeight: '', - }, + }); + expect(result.score).toEqual(0); }); - testImage('only has attribute width & css width', { - score: 0, - props: { + it('fails when an image only has attribute width & css width', async () => { + const result = await runAudit({ attributeWidth: '100', attributeHeight: '', cssWidth: '100', cssHeight: '', - }, + }); + expect(result.score).toEqual(0); }); }); - testImage('has empty width and height', { - score: 0, - props: { + it('fails when an image has empty width and height', async () => { + const result = await runAudit({ attributeWidth: '', attributeHeight: '', cssWidth: '', cssHeight: '', - }, + }); + expect(result.score).toEqual(0); }); describe('has valid width and height', () => { - testImage('has attribute width and css height', { - score: 1, - props: { + it('passes when an image has attribute width and css height', async () => { + const result = await runAudit({ attributeWidth: '100', attributeHeight: '', cssWidth: '', cssHeight: '100', - }, + }); + expect(result.score).toEqual(1); }); - testImage('has attribute width and attribute height', { - score: 1, - props: { + it('passes when an image has attribute width and attribute height', async () => { + const result = await runAudit({ attributeWidth: '100', attributeHeight: '100', cssWidth: '', cssHeight: '', - }, + }); + expect(result.score).toEqual(1); }); - testImage('has css width and attribute height', { - score: 1, - props: { + it('passes when an image has css width and attribute height', async () => { + const result = await runAudit({ attributeWidth: '', attributeHeight: '100', cssWidth: '100', cssHeight: '', - }, + }); + expect(result.score).toEqual(1); }); - testImage('has css width and css height', { - score: 1, - props: { + it('passes when an image has css width and css height', async () => { + const result = await runAudit({ attributeWidth: '', attributeHeight: '', cssWidth: '100', cssHeight: '100', - }, + }); + expect(result.score).toEqual(1); }); - testImage('has css & attribute width and css height', { - score: 1, - props: { + it('passes when an image has css & attribute width and css height', async () => { + const result = await runAudit({ attributeWidth: '100', attributeHeight: '', cssWidth: '100', cssHeight: '100', - }, + }); + expect(result.score).toEqual(1); }); - testImage('has css & attribute width and attribute height', { - score: 1, - props: { + it('passes when an image has css & attribute width and attribute height', async () => { + const result = await runAudit({ attributeWidth: '100', attributeHeight: '100', cssWidth: '100', cssHeight: '', - }, + }); + expect(result.score).toEqual(1); }); - testImage('has css & attribute height and css width', { - score: 1, - props: { + it('passes when an image has css & attribute height and css width', async () => { + const result = await runAudit({ attributeWidth: '', attributeHeight: '100', cssWidth: '100', cssHeight: '100', - }, + }); + expect(result.score).toEqual(1); }); - testImage('has css & attribute height and attribute width', { - score: 1, - props: { + it('passes when an image has css & attribute height and attribute width', async () => { + const result = await runAudit({ attributeWidth: '100', attributeHeight: '100', cssWidth: '', cssHeight: '100', - }, + }); + expect(result.score).toEqual(1); }); - testImage('has css & attribute height and css & attribute width', { - score: 1, - props: { + it('passes when an image has css & attribute height and css & attribute width', async () => { + const result = await runAudit({ attributeWidth: '100', attributeHeight: '100', cssWidth: '100', cssHeight: '100', - }, + }); + expect(result.score).toEqual(1); }); }); describe('has invalid width', () => { - testImage('has invalid width attribute', { - score: 0, - props: { + it('fails when an image has invalid width attribute', async () => { + const result = await runAudit({ attributeWidth: '-200', attributeHeight: '100', cssWidth: '', cssHeight: '', - }, + }); + expect(result.score).toEqual(0); }); - testImage('has invalid height attribute', { - score: 0, - props: { + it('fails when an image has invalid height attribute', async () => { + const result = await runAudit({ attributeWidth: '100', attributeHeight: '-200', cssWidth: '', cssHeight: '', - }, + }); + expect(result.score).toEqual(0); }); - testImage('has invalid css width', { - score: 0, - props: { + it('fails when an image has invalid css width', async () => { + const result = await runAudit({ attributeWidth: '', attributeHeight: '', cssWidth: 'auto', cssHeight: '100', - }, + }); + expect(result.score).toEqual(0); }); - testImage('has invalid css height', { - score: 0, - props: { + it('fails when an image has invalid css height', async () => { + const result = await runAudit({ attributeWidth: '', attributeHeight: '', cssWidth: '100', cssHeight: 'auto', - }, + }); + expect(result.score).toEqual(0); }); - testImage('has invalid width attribute, and valid css width', { - score: 1, - props: { + it('passes when an image has invalid width attribute, and valid css width', async () => { + const result = await runAudit({ attributeWidth: '-200', attributeHeight: '100', cssWidth: '100', cssHeight: '', - }, + }); + expect(result.score).toEqual(1); }); - testImage('has invalid height attribute, and valid css height', { - score: 1, - props: { + it('passes when an image has invalid height attribute, and valid css height', async () => { + const result = await runAudit({ attributeWidth: '100', attributeHeight: '-200', cssWidth: '', cssHeight: '100', - }, + }); + expect(result.score).toEqual(1); }); - testImage('has invalid css width, and valid attribute width', { - score: 1, - props: { + it('passes when an image has invalid css width, and valid attribute width', async () => { + const result = await runAudit({ attributeWidth: '100', attributeHeight: '', cssWidth: 'auto', cssHeight: '100', - }, + }); + expect(result.score).toEqual(1); }); - testImage('has invalid css height, and valid attribute height', { - score: 1, - props: { + it('passes when an image has invalid css height, and valid attribute height', async () => { + const result = await runAudit({ attributeWidth: '', attributeHeight: '100', cssWidth: '100', cssHeight: 'auto', - }, + }); + expect(result.score).toEqual(1); }); - testImage('has invalid css width & height, and valid attribute width & height', { - score: 1, - props: { + it('passes when an image has invalid css width & height, and valid attribute width & height', + async () => { + const result = await runAudit({ attributeWidth: '100', attributeHeight: '100', cssWidth: 'auto', cssHeight: 'auto', - }, + }); + expect(result.score).toEqual(1); }); - testImage('has invalid attribute width & height, and valid css width & height', { - score: 1, - props: { + it('passes when an image has invalid attribute width & height, and valid css width & height', + async () => { + const result = await runAudit({ attributeWidth: '-200', attributeHeight: '-200', cssWidth: '100', cssHeight: '100', - }, + }); + expect(result.score).toEqual(1); }); - testImage('has invalid attribute width & height, and invalid css width & height', { - score: 0, - props: { + it('fails when an image has invalid attribute width & height, and invalid css width & height', + async () => { + const result = await runAudit({ attributeWidth: '-200', attributeHeight: '-200', cssWidth: 'auto', cssHeight: 'auto', - }, + }); + expect(result.score).toEqual(0); }); }); From c5a1b9c164c392222a16106cd72911c5778737b4 Mon Sep 17 00:00:00 2001 From: Lemuel Cardenas-arriaga Date: Tue, 28 Jul 2020 17:32:17 -0700 Subject: [PATCH 46/68] small change in runAudit (cherry picked from commit 5d545e4a2a8500e68386d5e489461f55a067970b) --- lighthouse-core/test/audits/unsized-images-test.js | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/lighthouse-core/test/audits/unsized-images-test.js b/lighthouse-core/test/audits/unsized-images-test.js index caae685f5335..4ee5eab63534 100644 --- a/lighthouse-core/test/audits/unsized-images-test.js +++ b/lighthouse-core/test/audits/unsized-images-test.js @@ -19,9 +19,7 @@ describe('Sized images audit', () => { function runAudit(props) { const result = UnSizedImagesAudit.audit({ ImageElements: [ - generateImage( - props - ), + generateImage(props), ], }); return result; From e77d35fa3ae17670b1fb44c454ecf6e10ea452aa Mon Sep 17 00:00:00 2001 From: Lemuel Cardenas-arriaga Date: Wed, 29 Jul 2020 09:57:43 -0700 Subject: [PATCH 47/68] comment changes --- .../test-definitions/byte-efficiency/byte-config.js | 2 +- lighthouse-core/audits/unsized-images.js | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/lighthouse-cli/test/smokehouse/test-definitions/byte-efficiency/byte-config.js b/lighthouse-cli/test/smokehouse/test-definitions/byte-efficiency/byte-config.js index 343de10ba307..48f5ac997dc0 100644 --- a/lighthouse-cli/test/smokehouse/test-definitions/byte-efficiency/byte-config.js +++ b/lighthouse-cli/test/smokehouse/test-definitions/byte-efficiency/byte-config.js @@ -29,7 +29,7 @@ const config = { // 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; removing the need for a completely new smoke test. + // byte-efficiency tests & thus makes sense to test together. 'unsized-images', ], throttlingMethod: 'devtools', diff --git a/lighthouse-core/audits/unsized-images.js b/lighthouse-core/audits/unsized-images.js index 44cbfa434d92..6885569589a5 100644 --- a/lighthouse-core/audits/unsized-images.js +++ b/lighthouse-core/audits/unsized-images.js @@ -67,7 +67,7 @@ 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; @@ -78,7 +78,7 @@ class SizedImages extends Audit { const heightIsValidCss = SizedImages.isValidCss(cssHeight); const validWidth = widthIsValidAttribute || widthIsValidCss; const validHeight = heightIsValidAttribute || heightIsValidCss; - return !validWidth || !validHeight; // change to validWidth && validHeight + isSizedImage, remove ! below + return validWidth && validHeight; } /** @@ -91,7 +91,7 @@ class SizedImages extends Audit { const unsizedImages = []; for (const image of images) { - if (!SizedImages.isUnsizedImage(image)) continue; + if (SizedImages.isSizedImage(image)) continue; const url = URL.elideDataURI(image.src); unsizedImages.push({ url, From cc80c1d1aa8ad79d86307bd11c8c3ae3f694cb6f Mon Sep 17 00:00:00 2001 From: Lemuel Cardenas-arriaga Date: Wed, 29 Jul 2020 10:12:27 -0700 Subject: [PATCH 48/68] spacing changes --- lighthouse-core/gather/gatherers/image-elements.js | 2 ++ 1 file changed, 2 insertions(+) diff --git a/lighthouse-core/gather/gatherers/image-elements.js b/lighthouse-core/gather/gatherers/image-elements.js index 3fd785bd50dd..7b1fd8a884d7 100644 --- a/lighthouse-core/gather/gatherers/image-elements.js +++ b/lighthouse-core/gather/gatherers/image-elements.js @@ -339,6 +339,7 @@ class ImageElements extends Gatherer { // Use the min of the two numbers to be safe. const {resourceSize = 0, transferSize = 0} = networkRecord; element.resourceSize = Math.min(resourceSize, transferSize); + if (!element.isShadow) { await this.fetchSourceRules(driver, element.devtoolsNodePath, element); } @@ -353,6 +354,7 @@ class ImageElements extends Gatherer { ) { element = await this.fetchElementWithSizeInformation(driver, element); } + imageUsage.push(element); } From 6fb5c39e38c3612145366369d66754a2ee961d34 Mon Sep 17 00:00:00 2001 From: Lemuel Cardenas-arriaga Date: Wed, 29 Jul 2020 11:23:21 -0700 Subject: [PATCH 49/68] removed unsized-images out of experimental config --- lighthouse-core/config/experimental-config.js | 10 ---------- 1 file changed, 10 deletions(-) diff --git a/lighthouse-core/config/experimental-config.js b/lighthouse-core/config/experimental-config.js index a9ba9327eb5c..c3fbdeca8181 100644 --- a/lighthouse-core/config/experimental-config.js +++ b/lighthouse-core/config/experimental-config.js @@ -14,7 +14,6 @@ const config = { extends: 'lighthouse:default', audits: [ - 'unsized-images', 'full-page-screenshot', ], passes: [{ @@ -23,15 +22,6 @@ const config = { 'full-page-screenshot', ], }], - categories: { - // @ts-ignore: `title` is required in CategoryJson. setting to the same value as the default - // config is awkward - easier to omit the property here. Will defer to default config. - 'best-practices': { - auditRefs: [ - {id: 'unsized-images', weight: 1, group: 'best-practices-ux'}, - ], - }, - }, }; module.exports = config; From 9613e4f555ba3b0222ef05c357207f96f5a0f5d4 Mon Sep 17 00:00:00 2001 From: Lemuel Cardenas-arriaga Date: Wed, 29 Jul 2020 14:32:18 -0700 Subject: [PATCH 50/68] removed unsized-images from default config --- .../test/cli/__snapshots__/index-test.js.snap | 8 -- lighthouse-core/config/default-config.js | 2 - lighthouse-core/config/experimental-config.js | 10 +++ lighthouse-core/test/results/sample_v2.json | 84 ++----------------- 4 files changed, 19 insertions(+), 85 deletions(-) diff --git a/lighthouse-cli/test/cli/__snapshots__/index-test.js.snap b/lighthouse-cli/test/cli/__snapshots__/index-test.js.snap index 744cbc947059..609b5ff1b327 100644 --- a/lighthouse-cli/test/cli/__snapshots__/index-test.js.snap +++ b/lighthouse-cli/test/cli/__snapshots__/index-test.js.snap @@ -93,9 +93,6 @@ Object { Object { "path": "content-width", }, - Object { - "path": "unsized-images", - }, Object { "path": "image-aspect-ratio", }, @@ -733,11 +730,6 @@ Object { "id": "password-inputs-can-be-pasted-into", "weight": 1, }, - Object { - "group": "best-practices-ux", - "id": "unsized-images", - "weight": 1, - }, Object { "group": "best-practices-ux", "id": "image-aspect-ratio", diff --git a/lighthouse-core/config/default-config.js b/lighthouse-core/config/default-config.js index 0e79110d1ad8..2a65df36861d 100644 --- a/lighthouse-core/config/default-config.js +++ b/lighthouse-core/config/default-config.js @@ -215,7 +215,6 @@ const defaultConfig = { 'themed-omnibox', 'maskable-icon', 'content-width', - 'unsized-images', 'image-aspect-ratio', 'image-size-responsive', 'deprecations', @@ -553,7 +552,6 @@ const defaultConfig = { {id: 'no-vulnerable-libraries', weight: 1, group: 'best-practices-trust-safety'}, // User Experience {id: 'password-inputs-can-be-pasted-into', weight: 1, group: 'best-practices-ux'}, - {id: 'unsized-images', weight: 1, group: 'best-practices-ux'}, {id: 'image-aspect-ratio', weight: 1, group: 'best-practices-ux'}, {id: 'image-size-responsive', weight: 1, group: 'best-practices-ux'}, // Browser Compatibility diff --git a/lighthouse-core/config/experimental-config.js b/lighthouse-core/config/experimental-config.js index c3fbdeca8181..a9ba9327eb5c 100644 --- a/lighthouse-core/config/experimental-config.js +++ b/lighthouse-core/config/experimental-config.js @@ -14,6 +14,7 @@ const config = { extends: 'lighthouse:default', audits: [ + 'unsized-images', 'full-page-screenshot', ], passes: [{ @@ -22,6 +23,15 @@ const config = { 'full-page-screenshot', ], }], + categories: { + // @ts-ignore: `title` is required in CategoryJson. setting to the same value as the default + // config is awkward - easier to omit the property here. Will defer to default config. + 'best-practices': { + auditRefs: [ + {id: 'unsized-images', weight: 1, group: 'best-practices-ux'}, + ], + }, + }, }; module.exports = config; diff --git a/lighthouse-core/test/results/sample_v2.json b/lighthouse-core/test/results/sample_v2.json index 0e4adf4eb443..686c0255c53c 100644 --- a/lighthouse-core/test/results/sample_v2.json +++ b/lighthouse-core/test/results/sample_v2.json @@ -581,53 +581,6 @@ "scoreDisplayMode": "binary", "explanation": "" }, - "unsized-images": { - "id": "unsized-images", - "title": "Image elements do not have explicit `width` and `height`", - "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)", - "score": 0, - "scoreDisplayMode": "binary", - "details": { - "type": "table", - "headings": [ - { - "key": "url", - "itemType": "thumbnail", - "text": "" - }, - { - "key": "url", - "itemType": "url", - "text": "URL" - }, - { - "key": "node", - "itemType": "node", - "text": "Failing Elements" - } - ], - "items": [ - { - "url": "http://localhost:10200/dobetterweb/lighthouse-480x318.jpg", - "node": { - "type": "node" - } - }, - { - "url": "http://localhost:10200/dobetterweb/lighthouse-480x318.jpg", - "node": { - "type": "node" - } - }, - { - "url": "blob:http://localhost:10200/ae0eac03-ab9b-4a6a-b299-f5212153e277", - "node": { - "type": "node" - } - } - ] - } - }, "image-aspect-ratio": { "id": "image-aspect-ratio", "title": "Displays images with incorrect aspect ratio", @@ -4706,11 +4659,6 @@ "weight": 1, "group": "best-practices-ux" }, - { - "id": "unsized-images", - "weight": 1, - "group": "best-practices-ux" - }, { "id": "image-aspect-ratio", "weight": 1, @@ -5397,12 +5345,6 @@ "duration": 100, "entryType": "measure" }, - { - "startTime": 0, - "name": "lh:audit:unsized-images", - "duration": 100, - "entryType": "measure" - }, { "startTime": 0, "name": "lh:audit:image-aspect-ratio", @@ -6419,7 +6361,6 @@ ], "lighthouse-core/lib/i18n/i18n.js | columnURL": [ "audits[errors-in-console].details.headings[0].text", - "audits[unsized-images].details.headings[1].text", "audits[image-aspect-ratio].details.headings[1].text", "audits[image-size-responsive].details.headings[1].text", "audits.deprecations.details.headings[1].text", @@ -6534,22 +6475,6 @@ "lighthouse-core/audits/content-width.js | description": [ "audits[content-width].description" ], - "lighthouse-core/audits/unsized-images.js | failureTitle": [ - "audits[unsized-images].title" - ], - "lighthouse-core/audits/unsized-images.js | description": [ - "audits[unsized-images].description" - ], - "lighthouse-core/lib/i18n/i18n.js | columnFailingElem": [ - "audits[unsized-images].details.headings[2].text", - "audits[color-contrast].details.headings[0].text", - "audits[html-has-lang].details.headings[0].text", - "audits[image-alt].details.headings[0].text", - "audits.label.details.headings[0].text", - "audits[link-name].details.headings[0].text", - "audits[object-alt].details.headings[0].text", - "audits[password-inputs-can-be-pasted-into].details.headings[0].text" - ], "lighthouse-core/audits/image-aspect-ratio.js | failureTitle": [ "audits[image-aspect-ratio].title" ], @@ -6966,6 +6891,15 @@ "lighthouse-core/audits/accessibility/color-contrast.js | description": [ "audits[color-contrast].description" ], + "lighthouse-core/lib/i18n/i18n.js | columnFailingElem": [ + "audits[color-contrast].details.headings[0].text", + "audits[html-has-lang].details.headings[0].text", + "audits[image-alt].details.headings[0].text", + "audits.label.details.headings[0].text", + "audits[link-name].details.headings[0].text", + "audits[object-alt].details.headings[0].text", + "audits[password-inputs-can-be-pasted-into].details.headings[0].text" + ], "lighthouse-core/audits/accessibility/definition-list.js | title": [ "audits[definition-list].title" ], From c1c7dc1c0a4c773f70d6baa293bc89713ac8db5d Mon Sep 17 00:00:00 2001 From: Lemuel Cardenas-arriaga Date: Wed, 29 Jul 2020 14:53:13 -0700 Subject: [PATCH 51/68] byte-config extends experimental-config --- .../test-definitions/byte-efficiency/byte-config.js | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/lighthouse-cli/test/smokehouse/test-definitions/byte-efficiency/byte-config.js b/lighthouse-cli/test/smokehouse/test-definitions/byte-efficiency/byte-config.js index 48f5ac997dc0..b2125d0a8f8c 100644 --- a/lighthouse-cli/test/smokehouse/test-definitions/byte-efficiency/byte-config.js +++ b/lighthouse-cli/test/smokehouse/test-definitions/byte-efficiency/byte-config.js @@ -5,12 +5,14 @@ */ 'use strict'; +const experimentalConfig = require('../../../../../lighthouse-core/config/experimental-config.js'); + /** * @type {LH.Config.Json} * Config file for running byte efficiency smokehouse audits. */ const config = { - extends: 'lighthouse:default', + ...experimentalConfig, settings: { onlyAudits: [ 'accesskeys', // run axe on the page since we've had problems with interactions @@ -35,6 +37,7 @@ const config = { 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, From 66e491eddd3d21b65a5ec6cb3c0e76e3bc75fca2 Mon Sep 17 00:00:00 2001 From: Lemuel Cardenas-arriaga Date: Wed, 29 Jul 2020 15:05:06 -0700 Subject: [PATCH 52/68] comment changes, typedef change --- lighthouse-core/gather/gatherers/image-elements.js | 12 ++++++------ types/artifacts.d.ts | 4 ++-- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/lighthouse-core/gather/gatherers/image-elements.js b/lighthouse-core/gather/gatherers/image-elements.js index 7b1fd8a884d7..50fe603cb72a 100644 --- a/lighthouse-core/gather/gatherers/image-elements.js +++ b/lighthouse-core/gather/gatherers/image-elements.js @@ -194,7 +194,7 @@ function findMostSpecificMatchedCSSRule(matchedCSSRules = [], property) { let maxSpecificityRule; for (const {rule, matchingSelectors} of matchedCSSRules) { - // hasSizeDeclaration from font-size.js using `.some()` + // hasSizeDeclaration from font-size.js using `.some()`. if (!!rule.style && rule.style.cssProperties.some(({name}) => name === property)) { const specificities = matchingSelectors.map(idx => FontSize.computeSelectorSpecificity(rule.selectorList.selectors[idx].text) @@ -209,7 +209,7 @@ function findMostSpecificMatchedCSSRule(matchedCSSRules = [], property) { } if (maxSpecificityRule) { - // @ts-ignore the existence of the property object is checked in the for loop + // @ts-expect-error the existence of the property object is checked in the for loop return maxSpecificityRule.style.cssProperties.find(({name}) => name === property).value; } } @@ -220,15 +220,15 @@ function findMostSpecificMatchedCSSRule(matchedCSSRules = [], property) { * @returns {string | undefined} */ function getEffectiveSizingRule({attributesStyle, inlineStyle, matchedCSSRules}, property) { - // CSS sizing can't be inherited - // We only need to check inline & matched styles - // Inline styles have highest priority + // CSS sizing can't be inherited. + // We only need to check inline & matched styles. + // Inline styles have highest priority. const inlineRule = findSizeDeclaration(inlineStyle, property); if (inlineRule) return inlineRule; const attributeRule = findSizeDeclaration(attributesStyle, property); if (attributeRule) return attributeRule; - // Rules directly referencing the node come next + // Rules directly referencing the node come next. const matchedRule = findMostSpecificMatchedCSSRule(matchedCSSRules, property); if (matchedRule) return matchedRule; } diff --git a/types/artifacts.d.ts b/types/artifacts.d.ts index b232305597a3..39e3b215acd1 100644 --- a/types/artifacts.d.ts +++ b/types/artifacts.d.ts @@ -415,9 +415,9 @@ declare global { /** The raw height attribute of the image element. CSS images will be set to the empty string. */ attributeHeight: string; /** The CSS width property of the image element. */ - cssWidth?: string | undefined; + cssWidth: string | undefined; /** The CSS height property of the image element. */ - cssHeight?: string | undefined; + cssHeight: string | undefined; /** The BoundingClientRect of the element. */ clientRect: { top: number; From 20fb3509b1b27ed75ae21355e9398b73f9fc46b2 Mon Sep 17 00:00:00 2001 From: Lemuel Cardenas-arriaga Date: Wed, 29 Jul 2020 15:09:25 -0700 Subject: [PATCH 53/68] added a shadowroot unit test --- .../test/audits/unsized-images-test.js | 15 +++++++++++++-- 1 file changed, 13 insertions(+), 2 deletions(-) diff --git a/lighthouse-core/test/audits/unsized-images-test.js b/lighthouse-core/test/audits/unsized-images-test.js index 4ee5eab63534..883114b90c53 100644 --- a/lighthouse-core/test/audits/unsized-images-test.js +++ b/lighthouse-core/test/audits/unsized-images-test.js @@ -9,8 +9,8 @@ const UnSizedImagesAudit = require('../../audits/unsized-images.js'); /* eslint-env jest */ -function generateImage(props, src = 'https://google.com/logo.png', isCss = false) { - const image = {src, isCss}; +function generateImage(props, src = 'https://google.com/logo.png', isCss = false, isShadow = false) { + const image = {src, isCss, isShadow}; Object.assign(image, props); return image; } @@ -36,6 +36,17 @@ describe('Sized images audit', () => { expect(result.score).toEqual(1); }); + it('passes when an image is a shadowroot image', async () => { + const result = await runAudit({ + isShadow: true, + attributeWidth: '', + attributeHeight: '', + cssWidth: '', + cssHeight: '', + }); + expect(result.score).toEqual(1); + }); + describe('has empty width', () => { it('fails when an image only has attribute height', async () => { const result = await runAudit({ From 9f260e5df49d07c73759c8683ca54bfa4b5cc920 Mon Sep 17 00:00:00 2001 From: Lemuel Cardenas-arriaga Date: Wed, 29 Jul 2020 15:50:46 -0700 Subject: [PATCH 54/68] added two extra image sizing tests in byte-efficiency --- .../test/fixtures/byte-efficiency/tester.html | 16 ++++++++++++++++ .../byte-efficiency/expectations.js | 1 + 2 files changed, 17 insertions(+) diff --git a/lighthouse-cli/test/fixtures/byte-efficiency/tester.html b/lighthouse-cli/test/fixtures/byte-efficiency/tester.html index ba37fb32c3a9..e8d6fe97260c 100644 --- a/lighthouse-cli/test/fixtures/byte-efficiency/tester.html +++ b/lighthouse-cli/test/fixtures/byte-efficiency/tester.html @@ -110,6 +110,14 @@

Byte efficiency tester page

+ + + + + + + + @@ -118,6 +126,14 @@

Byte efficiency tester page

+ + + + + + + + diff --git a/lighthouse-cli/test/smokehouse/test-definitions/byte-efficiency/expectations.js b/lighthouse-cli/test/smokehouse/test-definitions/byte-efficiency/expectations.js index 5978f936db09..db4fd34ec2aa 100644 --- a/lighthouse-cli/test/smokehouse/test-definitions/byte-efficiency/expectations.js +++ b/lighthouse-cli/test/smokehouse/test-definitions/byte-efficiency/expectations.js @@ -245,6 +245,7 @@ const expectations = [ items: [ {url: /lighthouse-unoptimized\.jpg/}, {url: /lighthouse-320x212-poor\.jpg/}, + {url: /lighthouse-320x212-poor\.jpg/}, {url: /lighthouse-.+\?srcset/}, {url: /lighthouse-.+\?picture/}, {url: /lighthouse-320x212-poor\.jpg\?duplicate/}, From 44cbb6683b92a86dce3449c961c4803b59bdc13c Mon Sep 17 00:00:00 2001 From: Lemuel Cardenas-arriaga Date: Thu, 30 Jul 2020 15:17:20 -0700 Subject: [PATCH 55/68] added unique urls for images in smoke test --- lighthouse-cli/test/fixtures/byte-efficiency/tester.html | 4 ++-- .../test-definitions/byte-efficiency/expectations.js | 9 ++++++--- 2 files changed, 8 insertions(+), 5 deletions(-) diff --git a/lighthouse-cli/test/fixtures/byte-efficiency/tester.html b/lighthouse-cli/test/fixtures/byte-efficiency/tester.html index e8d6fe97260c..0f493848f99c 100644 --- a/lighthouse-cli/test/fixtures/byte-efficiency/tester.html +++ b/lighthouse-cli/test/fixtures/byte-efficiency/tester.html @@ -116,7 +116,7 @@

Byte efficiency tester page

- + @@ -132,7 +132,7 @@

Byte efficiency tester page

- + diff --git a/lighthouse-cli/test/smokehouse/test-definitions/byte-efficiency/expectations.js b/lighthouse-cli/test/smokehouse/test-definitions/byte-efficiency/expectations.js index db4fd34ec2aa..6b602b0f4fa1 100644 --- a/lighthouse-cli/test/smokehouse/test-definitions/byte-efficiency/expectations.js +++ b/lighthouse-cli/test/smokehouse/test-definitions/byte-efficiency/expectations.js @@ -193,7 +193,7 @@ const expectations = [ details: { overallSavingsBytes: '>60000', items: { - length: 5, + length: 6, }, }, }, @@ -220,12 +220,13 @@ 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/}, ], }, }, @@ -233,10 +234,12 @@ const expectations = [ '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/}, ], }, }, From e3b13c9c726a480d55d3252837f567735d15953a Mon Sep 17 00:00:00 2001 From: Lemuel Cardenas-arriaga Date: Thu, 30 Jul 2020 15:22:13 -0700 Subject: [PATCH 56/68] refactored byte-config to not use experimental-config --- .../smokehouse/test-definitions/byte-efficiency/byte-config.js | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/lighthouse-cli/test/smokehouse/test-definitions/byte-efficiency/byte-config.js b/lighthouse-cli/test/smokehouse/test-definitions/byte-efficiency/byte-config.js index b2125d0a8f8c..b779a69ff384 100644 --- a/lighthouse-cli/test/smokehouse/test-definitions/byte-efficiency/byte-config.js +++ b/lighthouse-cli/test/smokehouse/test-definitions/byte-efficiency/byte-config.js @@ -5,14 +5,13 @@ */ 'use strict'; -const experimentalConfig = require('../../../../../lighthouse-core/config/experimental-config.js'); /** * @type {LH.Config.Json} * Config file for running byte efficiency smokehouse audits. */ const config = { - ...experimentalConfig, + extends: 'lighthouse:default', settings: { onlyAudits: [ 'accesskeys', // run axe on the page since we've had problems with interactions From dbe18127ce32c0315941c0e3fd92d93453af4b91 Mon Sep 17 00:00:00 2001 From: Lemuel Cardenas-arriaga Date: Thu, 30 Jul 2020 15:25:25 -0700 Subject: [PATCH 57/68] changed isShadow to isInShadowDOM --- lighthouse-core/audits/unsized-images.js | 2 +- lighthouse-core/gather/gatherers/image-elements.js | 6 +++--- lighthouse-core/test/audits/unsized-images-test.js | 6 +++--- types/artifacts.d.ts | 2 +- 4 files changed, 8 insertions(+), 8 deletions(-) diff --git a/lighthouse-core/audits/unsized-images.js b/lighthouse-core/audits/unsized-images.js index 6885569589a5..aec60076ffa2 100644 --- a/lighthouse-core/audits/unsized-images.js +++ b/lighthouse-core/audits/unsized-images.js @@ -87,7 +87,7 @@ class SizedImages extends Audit { */ static async audit(artifacts) { // CSS background-images & ShadowRoot images are ignored for this audit. - const images = artifacts.ImageElements.filter(el => !el.isCss && !el.isShadow); + const images = artifacts.ImageElements.filter(el => !el.isCss && !el.isInShadowDOM); const unsizedImages = []; for (const image of images) { diff --git a/lighthouse-core/gather/gatherers/image-elements.js b/lighthouse-core/gather/gatherers/image-elements.js index 50fe603cb72a..d03d7107f0dd 100644 --- a/lighthouse-core/gather/gatherers/image-elements.js +++ b/lighthouse-core/gather/gatherers/image-elements.js @@ -67,7 +67,7 @@ function getHTMLImages(allElements) { usesPixelArtScaling: ['pixelated', 'crisp-edges'].includes( computedStyle.getPropertyValue('image-rendering') ), - isShadow: element.getRootNode() instanceof ShadowRoot, + isInShadowDOM: element.getRootNode() instanceof ShadowRoot, // https://html.spec.whatwg.org/multipage/images.html#pixel-density-descriptor usesSrcSetDensityDescriptor: / \d+(\.\d+)?x/.test(element.srcset), // @ts-ignore - getNodePath put into scope via stringification @@ -119,7 +119,7 @@ function getCSSImages(allElements) { cssHeight: '', isCss: true, isPicture: false, - isShadow: element.getRootNode() instanceof ShadowRoot, + isInShadowDOM: element.getRootNode() instanceof ShadowRoot, usesObjectFit: false, usesPixelArtScaling: ['pixelated', 'crisp-edges'].includes( style.getPropertyValue('image-rendering') @@ -340,7 +340,7 @@ class ImageElements extends Gatherer { const {resourceSize = 0, transferSize = 0} = networkRecord; element.resourceSize = Math.min(resourceSize, transferSize); - if (!element.isShadow) { + if (!element.isInShadowDOM) { await this.fetchSourceRules(driver, element.devtoolsNodePath, element); } // Images within `picture` behave strangely and natural size information isn't accurate, diff --git a/lighthouse-core/test/audits/unsized-images-test.js b/lighthouse-core/test/audits/unsized-images-test.js index 883114b90c53..72b472d9d7bc 100644 --- a/lighthouse-core/test/audits/unsized-images-test.js +++ b/lighthouse-core/test/audits/unsized-images-test.js @@ -9,8 +9,8 @@ const UnSizedImagesAudit = require('../../audits/unsized-images.js'); /* eslint-env jest */ -function generateImage(props, src = 'https://google.com/logo.png', isCss = false, isShadow = false) { - const image = {src, isCss, isShadow}; +function generateImage(props, src = 'https://google.com/logo.png', isCss = false, isInShadowDOM = false) { + const image = {src, isCss, isInShadowDOM}; Object.assign(image, props); return image; } @@ -38,7 +38,7 @@ describe('Sized images audit', () => { it('passes when an image is a shadowroot image', async () => { const result = await runAudit({ - isShadow: true, + isInShadowDOM: true, attributeWidth: '', attributeHeight: '', cssWidth: '', diff --git a/types/artifacts.d.ts b/types/artifacts.d.ts index 39e3b215acd1..82146af349e1 100644 --- a/types/artifacts.d.ts +++ b/types/artifacts.d.ts @@ -430,7 +430,7 @@ declare global { /** Flags whether this element was contained within a tag. */ isPicture: boolean; /** Flags whether this element was contained within a ShadowRoot */ - isShadow: boolean; + isInShadowDOM: boolean; /** Flags whether this element was sized using a non-default `object-fit` CSS property. */ usesObjectFit: boolean; /** Flags whether this element was rendered using a pixel art scaling method. From ae7ac769438d2a1e19475744abb0ec14e6d6fc46 Mon Sep 17 00:00:00 2001 From: Lemuel Cardenas-arriaga Date: Thu, 30 Jul 2020 17:22:11 -0700 Subject: [PATCH 58/68] refactored findMostSpecificMatchedCSSRule to take extra param --- .../gather/gatherers/image-elements.js | 33 +++++-------------- .../gather/gatherers/seo/font-size.js | 10 +++--- 2 files changed, 14 insertions(+), 29 deletions(-) diff --git a/lighthouse-core/gather/gatherers/image-elements.js b/lighthouse-core/gather/gatherers/image-elements.js index d03d7107f0dd..4dfeb224da9d 100644 --- a/lighthouse-core/gather/gatherers/image-elements.js +++ b/lighthouse-core/gather/gatherers/image-elements.js @@ -188,30 +188,13 @@ function findSizeDeclaration(style, property) { * @param {string} property * @returns {string | undefined} */ -function findMostSpecificMatchedCSSRule(matchedCSSRules = [], property) { - let maxSpecificity = -Infinity; - /** @type {LH.Crdp.CSS.CSSRule|undefined} */ - let maxSpecificityRule; - - for (const {rule, matchingSelectors} of matchedCSSRules) { - // hasSizeDeclaration from font-size.js using `.some()`. - if (!!rule.style && rule.style.cssProperties.some(({name}) => name === property)) { - const specificities = matchingSelectors.map(idx => - FontSize.computeSelectorSpecificity(rule.selectorList.selectors[idx].text) - ); - const specificity = Math.max(...specificities); - // Use greater OR EQUAL so that the last rule wins in the event of a tie - if (specificity >= maxSpecificity) { - maxSpecificity = specificity; - maxSpecificityRule = rule; - } - } - } - - if (maxSpecificityRule) { - // @ts-expect-error the existence of the property object is checked in the for loop - return maxSpecificityRule.style.cssProperties.find(({name}) => name === property).value; - } +function findMostSpecificCSSRule(matchedCSSRules, property) { + /** @type {function(LH.Crdp.CSS.CSSStyle): string | undefined} */ + const isDeclarationofInterest = (declaration) => findSizeDeclaration(declaration, property); + const rule = FontSize.findMostSpecificMatchedCSSRule(matchedCSSRules, isDeclarationofInterest); + if (!rule) return; + // @ts-expect-error style is guaranteed to exist if a rule exists + return findSizeDeclaration(rule.style, property); } /** @@ -229,7 +212,7 @@ function getEffectiveSizingRule({attributesStyle, inlineStyle, matchedCSSRules}, const attributeRule = findSizeDeclaration(attributesStyle, property); if (attributeRule) return attributeRule; // Rules directly referencing the node come next. - const matchedRule = findMostSpecificMatchedCSSRule(matchedCSSRules, property); + const matchedRule = findMostSpecificCSSRule(matchedCSSRules, property); if (matchedRule) return matchedRule; } diff --git a/lighthouse-core/gather/gatherers/seo/font-size.js b/lighthouse-core/gather/gatherers/seo/font-size.js index 5a02d1f82f4c..0ad5b636cadb 100644 --- a/lighthouse-core/gather/gatherers/seo/font-size.js +++ b/lighthouse-core/gather/gatherers/seo/font-size.js @@ -97,15 +97,16 @@ function computeSelectorSpecificity(selector) { * Finds the most specific directly matched CSS font-size rule from the list. * * @param {Array} [matchedCSSRules] + * @param {function(LH.Crdp.CSS.CSSStyle):boolean|string|undefined} isDeclarationOfInterest * @returns {NodeFontData['cssRule']|undefined} */ -function findMostSpecificMatchedCSSRule(matchedCSSRules = []) { +function findMostSpecificMatchedCSSRule(matchedCSSRules = [], isDeclarationOfInterest) { let maxSpecificity = -Infinity; /** @type {LH.Crdp.CSS.CSSRule|undefined} */ let maxSpecificityRule; for (const {rule, matchingSelectors} of matchedCSSRules) { - if (hasFontSizeDeclaration(rule.style)) { + if (isDeclarationOfInterest(rule.style)) { const specificities = matchingSelectors.map(idx => computeSelectorSpecificity(rule.selectorList.selectors[idx].text) ); @@ -144,7 +145,7 @@ function findInheritedCSSRule(inheritedEntries = []) { for (const {inlineStyle, matchedCSSRules} of inheritedEntries) { if (hasFontSizeDeclaration(inlineStyle)) return {type: 'Inline', ...inlineStyle}; - const directRule = findMostSpecificMatchedCSSRule(matchedCSSRules); + const directRule = findMostSpecificMatchedCSSRule(matchedCSSRules, hasFontSizeDeclaration); if (directRule) return directRule; } } @@ -162,7 +163,7 @@ function getEffectiveFontRule({attributesStyle, inlineStyle, matchedCSSRules, in if (hasFontSizeDeclaration(inlineStyle)) return {type: 'Inline', ...inlineStyle}; // Rules directly referencing the node come next - const matchedRule = findMostSpecificMatchedCSSRule(matchedCSSRules); + const matchedRule = findMostSpecificMatchedCSSRule(matchedCSSRules, hasFontSizeDeclaration); if (matchedRule) return matchedRule; // Then comes attributes styles () @@ -376,3 +377,4 @@ class FontSize extends Gatherer { module.exports = FontSize; module.exports.computeSelectorSpecificity = computeSelectorSpecificity; module.exports.getEffectiveFontRule = getEffectiveFontRule; +module.exports.findMostSpecificMatchedCSSRule = findMostSpecificMatchedCSSRule; From 9133fff97a28c00111fc0b070408952f8a28f807 Mon Sep 17 00:00:00 2001 From: Lemuel Cardenas-arriaga Date: Thu, 30 Jul 2020 17:38:18 -0700 Subject: [PATCH 59/68] moved driver commands inside a try/catch --- .../gather/gatherers/image-elements.js | 27 +++++++++++-------- 1 file changed, 16 insertions(+), 11 deletions(-) diff --git a/lighthouse-core/gather/gatherers/image-elements.js b/lighthouse-core/gather/gatherers/image-elements.js index 4dfeb224da9d..e96e64fc67e7 100644 --- a/lighthouse-core/gather/gatherers/image-elements.js +++ b/lighthouse-core/gather/gatherers/image-elements.js @@ -253,17 +253,22 @@ class ImageElements extends Gatherer { * @param {LH.Artifacts.ImageElement} element */ async fetchSourceRules(driver, devtoolsNodePath, element) { - const {nodeId} = await driver.sendCommand('DOM.pushNodeByPathToFrontend', { - path: devtoolsNodePath, - }); - if (!nodeId) return; - const matchedRules = await driver.sendCommand('CSS.getMatchedStylesForNode', { - nodeId: nodeId, - }); - const sourceWidth = getEffectiveSizingRule(matchedRules, 'width'); - const sourceHeight = getEffectiveSizingRule(matchedRules, 'height'); - const sourceRules = {cssWidth: sourceWidth, cssHeight: sourceHeight}; - Object.assign(element, sourceRules); + try { + const {nodeId} = await driver.sendCommand('DOM.pushNodeByPathToFrontend', { + path: devtoolsNodePath, + }); + if (!nodeId) return; + const matchedRules = await driver.sendCommand('CSS.getMatchedStylesForNode', { + nodeId: nodeId, + }); + const sourceWidth = getEffectiveSizingRule(matchedRules, 'width'); + const sourceHeight = getEffectiveSizingRule(matchedRules, 'height'); + const sourceRules = {cssWidth: sourceWidth, cssHeight: sourceHeight}; + Object.assign(element, sourceRules); + } catch (err) { + if (/No node.*found/.test(err.message)) return; + throw err; + } } /** From 490cc03a26037b45573ccddc48a4d725506d52ab Mon Sep 17 00:00:00 2001 From: Lemuel Cardenas-arriaga Date: Fri, 31 Jul 2020 14:20:34 -0700 Subject: [PATCH 60/68] added positioning checking for the audit --- .../test/fixtures/byte-efficiency/tester.html | 10 +++++----- .../byte-efficiency/expectations.js | 7 +------ lighthouse-core/audits/unsized-images.js | 4 +++- .../gather/gatherers/image-elements.js | 17 +++++++++++++++++ types/artifacts.d.ts | 2 ++ 5 files changed, 28 insertions(+), 12 deletions(-) diff --git a/lighthouse-cli/test/fixtures/byte-efficiency/tester.html b/lighthouse-cli/test/fixtures/byte-efficiency/tester.html index 0f493848f99c..c1dd99ce9838 100644 --- a/lighthouse-cli/test/fixtures/byte-efficiency/tester.html +++ b/lighthouse-cli/test/fixtures/byte-efficiency/tester.html @@ -76,7 +76,7 @@

Byte efficiency tester page

- + @@ -139,7 +139,7 @@

Byte efficiency tester page

- + @@ -147,7 +147,7 @@

Byte efficiency tester page

- + @@ -199,7 +199,7 @@

Byte efficiency tester page

- + @@ -227,7 +227,7 @@

Byte efficiency tester page

- + diff --git a/lighthouse-cli/test/smokehouse/test-definitions/byte-efficiency/expectations.js b/lighthouse-cli/test/smokehouse/test-definitions/byte-efficiency/expectations.js index 6b602b0f4fa1..f00559567a5e 100644 --- a/lighthouse-cli/test/smokehouse/test-definitions/byte-efficiency/expectations.js +++ b/lighthouse-cli/test/smokehouse/test-definitions/byte-efficiency/expectations.js @@ -246,13 +246,8 @@ const expectations = [ 'unsized-images': { details: { items: [ - {url: /lighthouse-unoptimized\.jpg/}, {url: /lighthouse-320x212-poor\.jpg/}, - {url: /lighthouse-320x212-poor\.jpg/}, - {url: /lighthouse-.+\?srcset/}, - {url: /lighthouse-.+\?picture/}, - {url: /lighthouse-320x212-poor\.jpg\?duplicate/}, - {url: /lighthouse-480x320\.webp\?lazilyLoaded=true/}, + {url: /lighthouse-320x212-poor\.jpg\?cssauto/}, ], }, }, diff --git a/lighthouse-core/audits/unsized-images.js b/lighthouse-core/audits/unsized-images.js index aec60076ffa2..9562dc4b6449 100644 --- a/lighthouse-core/audits/unsized-images.js +++ b/lighthouse-core/audits/unsized-images.js @@ -91,7 +91,9 @@ class SizedImages extends Audit { const unsizedImages = []; for (const image of images) { - if (SizedImages.isSizedImage(image)) continue; + const positioning = image.positioning; + const isFixedImage = positioning === 'fixed' || positioning === 'absolute'; + if (SizedImages.isSizedImage(image) || isFixedImage) continue; const url = URL.elideDataURI(image.src); unsizedImages.push({ url, diff --git a/lighthouse-core/gather/gatherers/image-elements.js b/lighthouse-core/gather/gatherers/image-elements.js index e96e64fc67e7..f8a77ce25b89 100644 --- a/lighthouse-core/gather/gatherers/image-elements.js +++ b/lighthouse-core/gather/gatherers/image-elements.js @@ -30,6 +30,20 @@ function getClientRect(element) { }; } +/** + * If an image is within `picture`, the `picture` element's positioning + * is what we want to collect, since that positioning is relevant to CLS. + * @param {Element} element + * @param {CSSStyleDeclaration} computedStyle + */ +function getPositioning(element, computedStyle) { + if (!!element.parentElement && element.parentElement.tagName === 'PICTURE') { + const parentStyle = window.getComputedStyle(element.parentElement); + return parentStyle.getPropertyValue('position'); + } + return computedStyle.getPropertyValue('position'); +} + /** * @param {Array} allElements * @return {Array} @@ -56,6 +70,7 @@ function getHTMLImages(allElements) { attributeHeight: element.getAttribute('height') || '', cssWidth: '', // this will get overwritten below cssHeight: '', // this will get overwritten below + positioning: getPositioning(element, computedStyle), isCss: false, // @ts-expect-error: loading attribute not yet added to HTMLImageElement definition. loading: element.loading, @@ -117,6 +132,7 @@ function getCSSImages(allElements) { attributeHeight: '', cssWidth: '', cssHeight: '', + positioning: getPositioning(element, style), isCss: true, isPicture: false, isInShadowDOM: element.getRootNode() instanceof ShadowRoot, @@ -295,6 +311,7 @@ class ImageElements extends Gatherer { ${pageFunctions.getNodeLabelString}; ${pageFunctions.getOuterHTMLSnippetString}; ${getClientRect.toString()}; + ${getPositioning.toString()}; ${getHTMLImages.toString()}; ${getCSSImages.toString()}; ${collectImageElementInfo.toString()}; diff --git a/types/artifacts.d.ts b/types/artifacts.d.ts index 82146af349e1..6d686630c347 100644 --- a/types/artifacts.d.ts +++ b/types/artifacts.d.ts @@ -425,6 +425,8 @@ declare global { left: number; right: number; }; + /** The CSS position attribute of the element */ + positioning: string; /** Flags whether this element was an image via CSS background-image rather than tag. */ isCss: boolean; /** Flags whether this element was contained within a tag. */ From 48d39edc751933b6d713cab9103424d3405cbdba Mon Sep 17 00:00:00 2001 From: Lemuel Cardenas-arriaga Date: Fri, 31 Jul 2020 14:29:33 -0700 Subject: [PATCH 61/68] made audit description less imperative, added tests --- lighthouse-core/audits/unsized-images.js | 2 +- lighthouse-core/lib/i18n/locales/en-US.json | 2 +- lighthouse-core/lib/i18n/locales/en-XL.json | 2 +- .../test/audits/unsized-images-test.js | 27 +++++++++++++++++-- 4 files changed, 28 insertions(+), 5 deletions(-) diff --git a/lighthouse-core/audits/unsized-images.js b/lighthouse-core/audits/unsized-images.js index 9562dc4b6449..a6aaa183fa7d 100644 --- a/lighthouse-core/audits/unsized-images.js +++ b/lighthouse-core/audits/unsized-images.js @@ -20,7 +20,7 @@ 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: '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)', }; const str_ = i18n.createMessageInstanceIdFn(__filename, UIStrings); diff --git a/lighthouse-core/lib/i18n/locales/en-US.json b/lighthouse-core/lib/i18n/locales/en-US.json index 2576cf8287f2..caf25bdafa2e 100644 --- a/lighthouse-core/lib/i18n/locales/en-US.json +++ b/lighthouse-core/lib/i18n/locales/en-US.json @@ -1257,7 +1257,7 @@ "message": "Timing budget" }, "lighthouse-core/audits/unsized-images.js | description": { - "message": "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)" + "message": "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)" }, "lighthouse-core/audits/unsized-images.js | failureTitle": { "message": "Image elements do not have explicit `width` and `height`" diff --git a/lighthouse-core/lib/i18n/locales/en-XL.json b/lighthouse-core/lib/i18n/locales/en-XL.json index acce4a0dab86..e57071e0f6cf 100644 --- a/lighthouse-core/lib/i18n/locales/en-XL.json +++ b/lighthouse-core/lib/i18n/locales/en-XL.json @@ -1257,7 +1257,7 @@ "message": "T̂ím̂ín̂ǵ b̂úd̂ǵêt́" }, "lighthouse-core/audits/unsized-images.js | description": { - "message": "Âĺŵáŷś îńĉĺûd́ê éx̂ṕl̂íĉít̂ ẃîd́t̂h́ âńd̂ h́êíĝh́t̂ ón̂ ím̂áĝé êĺêḿêńt̂ś t̂ó r̂éd̂úĉé l̂áŷóût́ ŝh́îf́t̂ś âńd̂ ím̂ṕr̂óv̂é ĈĹŜ. [Ĺêár̂ń m̂ór̂é](https://web.dev/optimize-cls/#images-without-dimensions)" + "message": "Îńĉĺûd́ê éx̂ṕl̂íĉít̂ ẃîd́t̂h́ âńd̂ h́êíĝh́t̂ ón̂ ím̂áĝé êĺêḿêńt̂ś t̂ó r̂éd̂úĉé l̂áŷóût́ ŝh́îf́t̂ś âńd̂ ím̂ṕr̂óv̂é ĈĹŜ. [Ĺêár̂ń m̂ór̂é](https://web.dev/optimize-cls/#images-without-dimensions)" }, "lighthouse-core/audits/unsized-images.js | failureTitle": { "message": "Îḿâǵê él̂ém̂én̂t́ŝ d́ô ńôt́ ĥáv̂é êx́p̂ĺîćît́ `width` âńd̂ `height`" diff --git a/lighthouse-core/test/audits/unsized-images-test.js b/lighthouse-core/test/audits/unsized-images-test.js index 72b472d9d7bc..e51fcaf47d3d 100644 --- a/lighthouse-core/test/audits/unsized-images-test.js +++ b/lighthouse-core/test/audits/unsized-images-test.js @@ -9,8 +9,9 @@ const UnSizedImagesAudit = require('../../audits/unsized-images.js'); /* eslint-env jest */ -function generateImage(props, src = 'https://google.com/logo.png', isCss = false, isInShadowDOM = false) { - const image = {src, isCss, isInShadowDOM}; +function generateImage(props, src = 'https://google.com/logo.png', isCss = false, + isInShadowDOM = false, positioning = 'static') { + const image = {src, isCss, isInShadowDOM, positioning}; Object.assign(image, props); return image; } @@ -47,6 +48,28 @@ describe('Sized images audit', () => { expect(result.score).toEqual(1); }); + it('passes when an image has absolute positioning', async () => { + const result = await runAudit({ + positioning: 'absolute', + attributeWidth: '', + attributeHeight: '', + cssWidth: '', + cssHeight: '', + }); + expect(result.score).toEqual(1); + }); + + it('passes when an image has fixed positioning', async () => { + const result = await runAudit({ + positioning: 'fixed', + attributeWidth: '', + attributeHeight: '', + cssWidth: '', + cssHeight: '', + }); + expect(result.score).toEqual(1); + }); + describe('has empty width', () => { it('fails when an image only has attribute height', async () => { const result = await runAudit({ From c88e8769d4dfc2138dfe39f2ffc80d002addf980 Mon Sep 17 00:00:00 2001 From: Lemuel Cardenas-arriaga Date: Fri, 31 Jul 2020 14:40:54 -0700 Subject: [PATCH 62/68] removed extra line --- .../smokehouse/test-definitions/byte-efficiency/byte-config.js | 1 - 1 file changed, 1 deletion(-) diff --git a/lighthouse-cli/test/smokehouse/test-definitions/byte-efficiency/byte-config.js b/lighthouse-cli/test/smokehouse/test-definitions/byte-efficiency/byte-config.js index b779a69ff384..e10b83c66a2c 100644 --- a/lighthouse-cli/test/smokehouse/test-definitions/byte-efficiency/byte-config.js +++ b/lighthouse-cli/test/smokehouse/test-definitions/byte-efficiency/byte-config.js @@ -5,7 +5,6 @@ */ 'use strict'; - /** * @type {LH.Config.Json} * Config file for running byte efficiency smokehouse audits. From 9bb98499d4eb5723221018bf7c609b7c23960c56 Mon Sep 17 00:00:00 2001 From: Lemuel Cardenas-arriaga Date: Fri, 31 Jul 2020 16:30:25 -0700 Subject: [PATCH 63/68] fixed naming convention --- lighthouse-core/audits/unsized-images.js | 14 ++--- .../test/audits/unsized-images-test.js | 62 +++++++++---------- 2 files changed, 38 insertions(+), 38 deletions(-) diff --git a/lighthouse-core/audits/unsized-images.js b/lighthouse-core/audits/unsized-images.js index a6aaa183fa7d..214f62314c7b 100644 --- a/lighthouse-core/audits/unsized-images.js +++ b/lighthouse-core/audits/unsized-images.js @@ -25,7 +25,7 @@ const UIStrings = { const str_ = i18n.createMessageInstanceIdFn(__filename, UIStrings); -class SizedImages extends Audit { +class UnsizedImages extends Audit { /** * @return {LH.Audit.Meta} */ @@ -72,10 +72,10 @@ class SizedImages extends Audit { 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; @@ -93,7 +93,7 @@ class SizedImages extends Audit { for (const image of images) { const positioning = image.positioning; const isFixedImage = positioning === 'fixed' || positioning === 'absolute'; - if (SizedImages.isSizedImage(image) || isFixedImage) continue; + if (UnsizedImages.isSizedImage(image) || isFixedImage) continue; const url = URL.elideDataURI(image.src); unsizedImages.push({ url, @@ -122,5 +122,5 @@ class SizedImages extends Audit { } } -module.exports = SizedImages; +module.exports = UnsizedImages; module.exports.UIStrings = UIStrings; diff --git a/lighthouse-core/test/audits/unsized-images-test.js b/lighthouse-core/test/audits/unsized-images-test.js index e51fcaf47d3d..ff6fee4abc88 100644 --- a/lighthouse-core/test/audits/unsized-images-test.js +++ b/lighthouse-core/test/audits/unsized-images-test.js @@ -5,7 +5,7 @@ */ 'use strict'; -const UnSizedImagesAudit = require('../../audits/unsized-images.js'); +const UnsizedImagesAudit = require('../../audits/unsized-images.js'); /* eslint-env jest */ @@ -18,7 +18,7 @@ function generateImage(props, src = 'https://google.com/logo.png', isCss = false describe('Sized images audit', () => { function runAudit(props) { - const result = UnSizedImagesAudit.audit({ + const result = UnsizedImagesAudit.audit({ ImageElements: [ generateImage(props), ], @@ -352,7 +352,7 @@ describe('Sized images audit', () => { }); it('is not applicable when there are no images', async () => { - const result = await UnSizedImagesAudit.audit({ + const result = await UnsizedImagesAudit.audit({ ImageElements: [], }); expect(result.notApplicable).toEqual(true); @@ -360,7 +360,7 @@ describe('Sized images audit', () => { }); it('can return multiple unsized images', async () => { - const result = await UnSizedImagesAudit.audit({ + const result = await UnsizedImagesAudit.audit({ ImageElements: [ generateImage( { @@ -398,55 +398,55 @@ describe('Sized images audit', () => { describe('Size attribute validity check', () => { it('fails if it is empty', () => { - expect(UnSizedImagesAudit.isValidAttr('')).toEqual(false); + expect(UnsizedImagesAudit.isValidAttr('')).toEqual(false); }); it('fails on non-numeric characters', () => { - expect(UnSizedImagesAudit.isValidAttr('zero')).toEqual(false); - expect(UnSizedImagesAudit.isValidAttr('1002$')).toEqual(false); - expect(UnSizedImagesAudit.isValidAttr('s-5')).toEqual(false); - expect(UnSizedImagesAudit.isValidAttr('3,000')).toEqual(false); - expect(UnSizedImagesAudit.isValidAttr('100.0')).toEqual(false); - expect(UnSizedImagesAudit.isValidAttr('2/3')).toEqual(false); - expect(UnSizedImagesAudit.isValidAttr('-2020')).toEqual(false); - expect(UnSizedImagesAudit.isValidAttr('+2020')).toEqual(false); + expect(UnsizedImagesAudit.isValidAttr('zero')).toEqual(false); + expect(UnsizedImagesAudit.isValidAttr('1002$')).toEqual(false); + expect(UnsizedImagesAudit.isValidAttr('s-5')).toEqual(false); + expect(UnsizedImagesAudit.isValidAttr('3,000')).toEqual(false); + expect(UnsizedImagesAudit.isValidAttr('100.0')).toEqual(false); + expect(UnsizedImagesAudit.isValidAttr('2/3')).toEqual(false); + expect(UnsizedImagesAudit.isValidAttr('-2020')).toEqual(false); + expect(UnsizedImagesAudit.isValidAttr('+2020')).toEqual(false); }); it('fails on zero input', () => { - expect(UnSizedImagesAudit.isValidAttr('0')).toEqual(false); + expect(UnsizedImagesAudit.isValidAttr('0')).toEqual(false); }); it('passes on non-zero non-negative integer input', () => { - expect(UnSizedImagesAudit.isValidAttr('1')).toEqual(true); - expect(UnSizedImagesAudit.isValidAttr('250')).toEqual(true); - expect(UnSizedImagesAudit.isValidAttr('4000000')).toEqual(true); + expect(UnsizedImagesAudit.isValidAttr('1')).toEqual(true); + expect(UnsizedImagesAudit.isValidAttr('250')).toEqual(true); + expect(UnsizedImagesAudit.isValidAttr('4000000')).toEqual(true); }); }); describe('CSS size property validity check', () => { it('fails if it was never defined', () => { - expect(UnSizedImagesAudit.isValidCss(undefined)).toEqual(false); + expect(UnsizedImagesAudit.isValidCss(undefined)).toEqual(false); }); it('fails if it is empty', () => { - expect(UnSizedImagesAudit.isValidCss('')).toEqual(false); + expect(UnsizedImagesAudit.isValidCss('')).toEqual(false); }); it('fails if it is auto', () => { - expect(UnSizedImagesAudit.isValidCss('auto')).toEqual(false); + expect(UnsizedImagesAudit.isValidCss('auto')).toEqual(false); }); it('passes if it is defined and not auto', () => { - expect(UnSizedImagesAudit.isValidCss('200')).toEqual(true); - expect(UnSizedImagesAudit.isValidCss('300.5')).toEqual(true); - expect(UnSizedImagesAudit.isValidCss('150px')).toEqual(true); - expect(UnSizedImagesAudit.isValidCss('80%')).toEqual(true); - expect(UnSizedImagesAudit.isValidCss('5cm')).toEqual(true); - expect(UnSizedImagesAudit.isValidCss('20rem')).toEqual(true); - expect(UnSizedImagesAudit.isValidCss('7vw')).toEqual(true); - expect(UnSizedImagesAudit.isValidCss('-20')).toEqual(true); - expect(UnSizedImagesAudit.isValidCss('0')).toEqual(true); - expect(UnSizedImagesAudit.isValidCss('three')).toEqual(true); - expect(UnSizedImagesAudit.isValidCss('-20')).toEqual(true); + expect(UnsizedImagesAudit.isValidCss('200')).toEqual(true); + expect(UnsizedImagesAudit.isValidCss('300.5')).toEqual(true); + expect(UnsizedImagesAudit.isValidCss('150px')).toEqual(true); + expect(UnsizedImagesAudit.isValidCss('80%')).toEqual(true); + expect(UnsizedImagesAudit.isValidCss('5cm')).toEqual(true); + expect(UnsizedImagesAudit.isValidCss('20rem')).toEqual(true); + expect(UnsizedImagesAudit.isValidCss('7vw')).toEqual(true); + expect(UnsizedImagesAudit.isValidCss('-20')).toEqual(true); + expect(UnsizedImagesAudit.isValidCss('0')).toEqual(true); + expect(UnsizedImagesAudit.isValidCss('three')).toEqual(true); + expect(UnsizedImagesAudit.isValidCss('-20')).toEqual(true); }); }); From 6e18cbef39686d04fd42776d3863ea90308b58a6 Mon Sep 17 00:00:00 2001 From: Lemuel Cardenas-arriaga Date: Mon, 3 Aug 2020 10:58:41 -0700 Subject: [PATCH 64/68] nits, positioning -> cssComputedPosition, spacing --- .../test/fixtures/byte-efficiency/tester.html | 10 +++++----- lighthouse-core/audits/unsized-images.js | 9 +++++---- .../gather/gatherers/image-elements.js | 19 ++++++++++++------- lighthouse-core/lib/i18n/locales/en-US.json | 2 +- lighthouse-core/lib/i18n/locales/en-XL.json | 2 +- .../test/audits/unsized-images-test.js | 12 ++++++------ types/artifacts.d.ts | 2 +- 7 files changed, 31 insertions(+), 25 deletions(-) diff --git a/lighthouse-cli/test/fixtures/byte-efficiency/tester.html b/lighthouse-cli/test/fixtures/byte-efficiency/tester.html index c1dd99ce9838..d3016c908af0 100644 --- a/lighthouse-cli/test/fixtures/byte-efficiency/tester.html +++ b/lighthouse-cli/test/fixtures/byte-efficiency/tester.html @@ -76,7 +76,7 @@

Byte efficiency tester page

- + @@ -139,7 +139,7 @@

Byte efficiency tester page

- + @@ -147,7 +147,7 @@

Byte efficiency tester page

- + @@ -199,7 +199,7 @@

Byte efficiency tester page

- + @@ -227,7 +227,7 @@

Byte efficiency tester page

- + diff --git a/lighthouse-core/audits/unsized-images.js b/lighthouse-core/audits/unsized-images.js index 214f62314c7b..6b0a443c4438 100644 --- a/lighthouse-core/audits/unsized-images.js +++ b/lighthouse-core/audits/unsized-images.js @@ -20,7 +20,7 @@ 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: '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); @@ -91,9 +91,10 @@ class UnsizedImages extends Audit { const unsizedImages = []; for (const image of images) { - const positioning = image.positioning; - const isFixedImage = positioning === 'fixed' || positioning === 'absolute'; - if (UnsizedImages.isSizedImage(image) || isFixedImage) continue; + const isFixedImage = + image.cssComputedPosition === 'fixed' || image.cssComputedPosition === 'absolute'; + + if (isFixedImage || UnsizedImages.isSizedImage(image)) continue; const url = URL.elideDataURI(image.src); unsizedImages.push({ url, diff --git a/lighthouse-core/gather/gatherers/image-elements.js b/lighthouse-core/gather/gatherers/image-elements.js index f8a77ce25b89..f537700b1f2a 100644 --- a/lighthouse-core/gather/gatherers/image-elements.js +++ b/lighthouse-core/gather/gatherers/image-elements.js @@ -31,13 +31,13 @@ function getClientRect(element) { } /** - * If an image is within `picture`, the `picture` element's positioning - * is what we want to collect, since that positioning is relevant to CLS. + * If an image is within `picture`, the `picture` element's css position + * is what we want to collect, since that position is relevant to CLS. * @param {Element} element * @param {CSSStyleDeclaration} computedStyle */ -function getPositioning(element, computedStyle) { - if (!!element.parentElement && element.parentElement.tagName === 'PICTURE') { +function getPosition(element, computedStyle) { + if (element.parentElement && element.parentElement.tagName === 'PICTURE') { const parentStyle = window.getComputedStyle(element.parentElement); return parentStyle.getPropertyValue('position'); } @@ -70,7 +70,7 @@ function getHTMLImages(allElements) { attributeHeight: element.getAttribute('height') || '', cssWidth: '', // this will get overwritten below cssHeight: '', // this will get overwritten below - positioning: getPositioning(element, computedStyle), + cssComputedPosition: getPosition(element, computedStyle), isCss: false, // @ts-expect-error: loading attribute not yet added to HTMLImageElement definition. loading: element.loading, @@ -132,7 +132,7 @@ function getCSSImages(allElements) { attributeHeight: '', cssWidth: '', cssHeight: '', - positioning: getPositioning(element, style), + cssComputedPosition: getPosition(element, style), isCss: true, isPicture: false, isInShadowDOM: element.getRootNode() instanceof ShadowRoot, @@ -192,8 +192,10 @@ function determineNaturalSize(url) { */ function findSizeDeclaration(style, property) { if (!style) return; + const definedProp = style.cssProperties.find(({name}) => name === property); if (!definedProp) return; + return definedProp.value; } @@ -209,6 +211,7 @@ function findMostSpecificCSSRule(matchedCSSRules, property) { const isDeclarationofInterest = (declaration) => findSizeDeclaration(declaration, property); const rule = FontSize.findMostSpecificMatchedCSSRule(matchedCSSRules, isDeclarationofInterest); if (!rule) return; + // @ts-expect-error style is guaranteed to exist if a rule exists return findSizeDeclaration(rule.style, property); } @@ -227,6 +230,7 @@ function getEffectiveSizingRule({attributesStyle, inlineStyle, matchedCSSRules}, const attributeRule = findSizeDeclaration(attributesStyle, property); if (attributeRule) return attributeRule; + // Rules directly referencing the node come next. const matchedRule = findMostSpecificCSSRule(matchedCSSRules, property); if (matchedRule) return matchedRule; @@ -274,6 +278,7 @@ class ImageElements extends Gatherer { path: devtoolsNodePath, }); if (!nodeId) return; + const matchedRules = await driver.sendCommand('CSS.getMatchedStylesForNode', { nodeId: nodeId, }); @@ -311,7 +316,7 @@ class ImageElements extends Gatherer { ${pageFunctions.getNodeLabelString}; ${pageFunctions.getOuterHTMLSnippetString}; ${getClientRect.toString()}; - ${getPositioning.toString()}; + ${getPosition.toString()}; ${getHTMLImages.toString()}; ${getCSSImages.toString()}; ${collectImageElementInfo.toString()}; diff --git a/lighthouse-core/lib/i18n/locales/en-US.json b/lighthouse-core/lib/i18n/locales/en-US.json index caf25bdafa2e..e05206237909 100644 --- a/lighthouse-core/lib/i18n/locales/en-US.json +++ b/lighthouse-core/lib/i18n/locales/en-US.json @@ -1257,7 +1257,7 @@ "message": "Timing budget" }, "lighthouse-core/audits/unsized-images.js | description": { - "message": "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)" + "message": "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)" }, "lighthouse-core/audits/unsized-images.js | failureTitle": { "message": "Image elements do not have explicit `width` and `height`" diff --git a/lighthouse-core/lib/i18n/locales/en-XL.json b/lighthouse-core/lib/i18n/locales/en-XL.json index e57071e0f6cf..2d63b75d2de7 100644 --- a/lighthouse-core/lib/i18n/locales/en-XL.json +++ b/lighthouse-core/lib/i18n/locales/en-XL.json @@ -1257,7 +1257,7 @@ "message": "T̂ím̂ín̂ǵ b̂úd̂ǵêt́" }, "lighthouse-core/audits/unsized-images.js | description": { - "message": "Îńĉĺûd́ê éx̂ṕl̂íĉít̂ ẃîd́t̂h́ âńd̂ h́êíĝh́t̂ ón̂ ím̂áĝé êĺêḿêńt̂ś t̂ó r̂éd̂úĉé l̂áŷóût́ ŝh́îf́t̂ś âńd̂ ím̂ṕr̂óv̂é ĈĹŜ. [Ĺêár̂ń m̂ór̂é](https://web.dev/optimize-cls/#images-without-dimensions)" + "message": "Ŝét̂ án̂ éx̂ṕl̂íĉít̂ ẃîd́t̂h́ âńd̂ h́êíĝh́t̂ ón̂ ím̂áĝé êĺêḿêńt̂ś t̂ó r̂éd̂úĉé l̂áŷóût́ ŝh́îf́t̂ś âńd̂ ím̂ṕr̂óv̂é ĈĹŜ. [Ĺêár̂ń m̂ór̂é](https://web.dev/optimize-cls/#images-without-dimensions)" }, "lighthouse-core/audits/unsized-images.js | failureTitle": { "message": "Îḿâǵê él̂ém̂én̂t́ŝ d́ô ńôt́ ĥáv̂é êx́p̂ĺîćît́ `width` âńd̂ `height`" diff --git a/lighthouse-core/test/audits/unsized-images-test.js b/lighthouse-core/test/audits/unsized-images-test.js index ff6fee4abc88..e15cc576e328 100644 --- a/lighthouse-core/test/audits/unsized-images-test.js +++ b/lighthouse-core/test/audits/unsized-images-test.js @@ -10,8 +10,8 @@ const UnsizedImagesAudit = require('../../audits/unsized-images.js'); /* eslint-env jest */ function generateImage(props, src = 'https://google.com/logo.png', isCss = false, - isInShadowDOM = false, positioning = 'static') { - const image = {src, isCss, isInShadowDOM, positioning}; + isInShadowDOM = false, cssComputedPosition = 'static') { + const image = {src, isCss, isInShadowDOM, cssComputedPosition}; Object.assign(image, props); return image; } @@ -48,9 +48,9 @@ describe('Sized images audit', () => { expect(result.score).toEqual(1); }); - it('passes when an image has absolute positioning', async () => { + it('passes when an image has absolute css position', async () => { const result = await runAudit({ - positioning: 'absolute', + cssComputedPosition: 'absolute', attributeWidth: '', attributeHeight: '', cssWidth: '', @@ -59,9 +59,9 @@ describe('Sized images audit', () => { expect(result.score).toEqual(1); }); - it('passes when an image has fixed positioning', async () => { + it('passes when an image has fixed css position', async () => { const result = await runAudit({ - positioning: 'fixed', + cssComputedPosition: 'fixed', attributeWidth: '', attributeHeight: '', cssWidth: '', diff --git a/types/artifacts.d.ts b/types/artifacts.d.ts index 6d686630c347..4987abd8ec30 100644 --- a/types/artifacts.d.ts +++ b/types/artifacts.d.ts @@ -426,7 +426,7 @@ declare global { right: number; }; /** The CSS position attribute of the element */ - positioning: string; + cssComputedPosition: string; /** Flags whether this element was an image via CSS background-image rather than tag. */ isCss: boolean; /** Flags whether this element was contained within a tag. */ From 34080a9164ea5b55949731505d836d683e37e557 Mon Sep 17 00:00:00 2001 From: Lemuel Cardenas-arriaga Date: Mon, 3 Aug 2020 11:18:48 -0700 Subject: [PATCH 65/68] changed @function -> @param --- lighthouse-core/gather/gatherers/image-elements.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lighthouse-core/gather/gatherers/image-elements.js b/lighthouse-core/gather/gatherers/image-elements.js index f537700b1f2a..a1ffc0299e01 100644 --- a/lighthouse-core/gather/gatherers/image-elements.js +++ b/lighthouse-core/gather/gatherers/image-elements.js @@ -207,7 +207,7 @@ function findSizeDeclaration(style, property) { * @returns {string | undefined} */ function findMostSpecificCSSRule(matchedCSSRules, property) { - /** @type {function(LH.Crdp.CSS.CSSStyle): string | undefined} */ + /** @param {LH.Crdp.CSS.CSSStyle} declaration */ const isDeclarationofInterest = (declaration) => findSizeDeclaration(declaration, property); const rule = FontSize.findMostSpecificMatchedCSSRule(matchedCSSRules, isDeclarationofInterest); if (!rule) return; From 1763d262105c2cb2dc4011723ebeb95e18bbc104 Mon Sep 17 00:00:00 2001 From: Lemuel Cardenas-arriaga Date: Tue, 4 Aug 2020 09:23:03 -0700 Subject: [PATCH 66/68] changed cssWidth/Height to be optional --- types/artifacts.d.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/types/artifacts.d.ts b/types/artifacts.d.ts index 4987abd8ec30..97ef957b76bc 100644 --- a/types/artifacts.d.ts +++ b/types/artifacts.d.ts @@ -415,9 +415,9 @@ declare global { /** The raw height attribute of the image element. CSS images will be set to the empty string. */ attributeHeight: string; /** The CSS width property of the image element. */ - cssWidth: string | undefined; + cssWidth?: string; /** The CSS height property of the image element. */ - cssHeight: string | undefined; + cssHeight?: string; /** The BoundingClientRect of the element. */ clientRect: { top: number; From dc7ebf578052c5d7e7929a04991c9a4788753dd4 Mon Sep 17 00:00:00 2001 From: Lemuel Cardenas-arriaga Date: Tue, 4 Aug 2020 09:30:55 -0700 Subject: [PATCH 67/68] changed initialization of cssWidth/Height to be undefined --- lighthouse-core/gather/gatherers/image-elements.js | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/lighthouse-core/gather/gatherers/image-elements.js b/lighthouse-core/gather/gatherers/image-elements.js index a1ffc0299e01..a963e89e2e25 100644 --- a/lighthouse-core/gather/gatherers/image-elements.js +++ b/lighthouse-core/gather/gatherers/image-elements.js @@ -68,8 +68,8 @@ function getHTMLImages(allElements) { naturalHeight: element.naturalHeight, attributeWidth: element.getAttribute('width') || '', attributeHeight: element.getAttribute('height') || '', - cssWidth: '', // this will get overwritten below - cssHeight: '', // this will get overwritten below + cssWidth: undefined, // this will get overwritten below + cssHeight: undefined, // this will get overwritten below cssComputedPosition: getPosition(element, computedStyle), isCss: false, // @ts-expect-error: loading attribute not yet added to HTMLImageElement definition. @@ -130,8 +130,8 @@ function getCSSImages(allElements) { naturalHeight: 0, attributeWidth: '', attributeHeight: '', - cssWidth: '', - cssHeight: '', + cssWidth: undefined, + cssHeight: undefined, cssComputedPosition: getPosition(element, style), isCss: true, isPicture: false, From 0e332ea5d3b7adf699365610b4ab5ceca89fdc2c Mon Sep 17 00:00:00 2001 From: lemcardenas <32499374+lemcardenas@users.noreply.github.com> Date: Tue, 4 Aug 2020 19:18:44 +0000 Subject: [PATCH 68/68] Update lighthouse-core/audits/unsized-images.js Co-authored-by: Connor Clark --- lighthouse-core/audits/unsized-images.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lighthouse-core/audits/unsized-images.js b/lighthouse-core/audits/unsized-images.js index 6b0a443c4438..ec844b6cf742 100644 --- a/lighthouse-core/audits/unsized-images.js +++ b/lighthouse-core/audits/unsized-images.js @@ -92,7 +92,7 @@ class UnsizedImages extends Audit { for (const image of images) { const isFixedImage = - image.cssComputedPosition === 'fixed' || image.cssComputedPosition === 'absolute'; + image.cssComputedPosition === 'fixed' || image.cssComputedPosition === 'absolute'; if (isFixedImage || UnsizedImages.isSizedImage(image)) continue; const url = URL.elideDataURI(image.src);