Skip to content

Commit

Permalink
new_audit: add sized-images to experimental config (#11115)
Browse files Browse the repository at this point in the history
  • Loading branch information
lemcardenas authored Jul 29, 2020
1 parent 25b464b commit c75f1ba
Show file tree
Hide file tree
Showing 56 changed files with 618 additions and 153 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down Expand Up @@ -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 {
Expand Down
124 changes: 124 additions & 0 deletions lighthouse-core/audits/unsized-images.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,124 @@
/**
* @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.
*/
/**
* @fileoverview
* Audit that checks whether all images have explicit width and height.
*/

'use strict';

const Audit = require('./audit.js');
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 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 image elements to reduce layout shifts and improve CLS. [Learn more](https://web.dev/optimize-cls/#images-without-dimensions)',
};

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

class SizedImages extends Audit {
/**
* @return {LH.Audit.Meta}
*/
static get meta() {
return {
id: 'unsized-images',
title: str_(UIStrings.title),
failureTitle: str_(UIStrings.failureTitle),
description: str_(UIStrings.description),
requiredArtifacts: ['ImageElements'],
};
}

/**
* 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) {
const NON_NEGATIVE_INT_REGEX = /^\d+$/;
const ZERO_REGEX = /^0+$/;
return NON_NEGATIVE_INT_REGEX.test(attr) && !ZERO_REGEX.test(attr);
}

/**
* 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) {
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) {
const attrWidth = image.attributeWidth;
const attrHeight = image.attributeHeight;
const cssWidth = image.cssWidth;
const cssHeight = image.cssHeight;
const widthIsValidAttribute = SizedImages.isValidAttr(attrWidth);
const widthIsValidCss = SizedImages.isValidCss(cssWidth);
const heightIsValidAttribute = SizedImages.isValidAttr(attrHeight);
const heightIsValidCss = SizedImages.isValidCss(cssHeight);
const validWidth = widthIsValidAttribute || widthIsValidCss;
const validHeight = heightIsValidAttribute || heightIsValidCss;
return !validWidth || !validHeight;
}

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

for (const image of images) {
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']} */
const headings = [
{key: 'url', itemType: 'thumbnail', text: ''},
{key: 'url', itemType: 'url', text: str_(i18n.UIStrings.columnURL)},
{key: 'node', itemType: 'node', text: str_(i18n.UIStrings.columnFailingElem)},
];

return {
score: unsizedImages.length > 0 ? 0 : 1,
notApplicable: images.length === 0,
details: Audit.makeTableDetails(headings, unsizedImages),
};
}
}

module.exports = SizedImages;
module.exports.UIStrings = UIStrings;
8 changes: 8 additions & 0 deletions lighthouse-core/config/experimental-config.js
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
const config = {
extends: 'lighthouse:default',
audits: [
'unsized-images',
'full-page-screenshot',
],
passes: [{
Expand All @@ -23,6 +24,13 @@ 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.
'best-practices': {
auditRefs: [
{id: 'unsized-images', weight: 1, group: 'best-practices-ux'},
],
},
},
};

Expand Down
26 changes: 25 additions & 1 deletion lighthouse-core/gather/gatherers/image-elements.js
Original file line number Diff line number Diff line change
Expand Up @@ -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 */
Expand Down Expand Up @@ -51,6 +51,8 @@ function getHTMLImages(allElements) {
clientRect: getClientRect(element),
naturalWidth: element.naturalWidth,
naturalHeight: element.naturalHeight,
attributeWidth: element.getAttribute('width') || '',
attributeHeight: element.getAttribute('height') || '',
isCss: false,
// @ts-expect-error: loading attribute not yet added to HTMLImageElement definition.
loading: element.loading,
Expand All @@ -64,6 +66,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),
};
});
}
Expand Down Expand Up @@ -99,6 +109,8 @@ function getCSSImages(allElements) {
// CSS Images do not expose natural size, we'll determine the size later
naturalWidth: 0,
naturalHeight: 0,
attributeWidth: '',
attributeHeight: '',
isCss: true,
isPicture: false,
usesObjectFit: false,
Expand All @@ -107,6 +119,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),
});
}

Expand Down Expand Up @@ -192,6 +212,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()};
Expand Down
2 changes: 2 additions & 0 deletions lighthouse-core/lib/i18n/i18n.js
Original file line number Diff line number Diff line change
Expand Up @@ -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. */
Expand Down
3 changes: 0 additions & 3 deletions lighthouse-core/lib/i18n/locales/ar-XB.json
Original file line number Diff line number Diff line change
Expand Up @@ -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/)."
},
Expand Down
3 changes: 0 additions & 3 deletions lighthouse-core/lib/i18n/locales/ar.json
Original file line number Diff line number Diff line change
Expand Up @@ -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/)"
},
Expand Down
3 changes: 0 additions & 3 deletions lighthouse-core/lib/i18n/locales/bg.json
Original file line number Diff line number Diff line change
Expand Up @@ -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/)."
},
Expand Down
3 changes: 0 additions & 3 deletions lighthouse-core/lib/i18n/locales/ca.json
Original file line number Diff line number Diff line change
Expand Up @@ -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/)."
},
Expand Down
3 changes: 0 additions & 3 deletions lighthouse-core/lib/i18n/locales/cs.json
Original file line number Diff line number Diff line change
Expand Up @@ -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/)"
},
Expand Down
3 changes: 0 additions & 3 deletions lighthouse-core/lib/i18n/locales/da.json
Original file line number Diff line number Diff line change
Expand Up @@ -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/)."
},
Expand Down
3 changes: 0 additions & 3 deletions lighthouse-core/lib/i18n/locales/de.json
Original file line number Diff line number Diff line change
Expand Up @@ -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/)"
},
Expand Down
3 changes: 0 additions & 3 deletions lighthouse-core/lib/i18n/locales/el.json
Original file line number Diff line number Diff line change
Expand Up @@ -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/)."
},
Expand Down
3 changes: 0 additions & 3 deletions lighthouse-core/lib/i18n/locales/en-GB.json
Original file line number Diff line number Diff line change
Expand Up @@ -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/)."
},
Expand Down
15 changes: 12 additions & 3 deletions lighthouse-core/lib/i18n/locales/en-US.json
Original file line number Diff line number Diff line change
Expand Up @@ -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/)."
},
Expand Down Expand Up @@ -1259,6 +1256,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 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`"
},
"lighthouse-core/audits/unsized-images.js | title": {
"message": "Image elements have explicit `width` and `height`"
},
"lighthouse-core/audits/user-timings.js | columnType": {
"message": "Type"
},
Expand Down Expand Up @@ -1496,6 +1502,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"
},
Expand Down
3 changes: 0 additions & 3 deletions lighthouse-core/lib/i18n/locales/en-XA.json
Original file line number Diff line number Diff line change
Expand Up @@ -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]"
},
Expand Down
Loading

0 comments on commit c75f1ba

Please sign in to comment.