html_codesniffer
Version:
HTML_CodeSniffer is a client-side JavaScript that checks a HTML document or source code, and detects violations of a defined coding standard.
326 lines (281 loc) • 14 kB
JavaScript
/**
* +--------------------------------------------------------------------+
* | This HTML_CodeSniffer file is Copyright (c) |
* | Squiz Australia Pty Ltd ABN 53 131 581 247 |
* +--------------------------------------------------------------------+
* | IMPORTANT: Your use of this Software is subject to the terms of |
* | the Licence provided in the file licence.txt. If you cannot find |
* | this file please contact Squiz (www.squiz.com.au) so we may |
* | provide you a copy. |
* +--------------------------------------------------------------------+
*
*/
_global.HTMLCS_Section508_Sniffs_A = {
/**
* Determines the elements to register for processing.
*
* Each element of the returned array can either be an element name, or "_top"
* which is the top element of the tested code.
*
* @returns {Array} The list of elements.
*/
register: function()
{
return [
'_top',
'img',
'object',
'bgsound',
'audio'
];
},
/**
* Process the registered element.
*
* @param {DOMNode} element The element registered.
* @param {DOMNode} top The top element of the tested code.
*/
process: function(element, top)
{
if (element === top) {
this.addNullAltTextResults(top);
this.addMediaAlternativesResults(top);
} else {
var nodeName = element.nodeName.toLowerCase();
if ((nodeName === 'object') || (nodeName === 'bgsound') || (nodeName === 'audio')) {
// Audio transcript notice. Yes, this is in A rather than B, since
// audio is not considered "multimedia" (roughly equivalent to a
// "synchronised media" presentation in WCAG 2.1). It is non-text,
// though, so a transcript is required.
HTMLCS.addMessage(HTMLCS.NOTICE, element, 'For multimedia containing audio only, ensure an alternative is available, such as a full text transcript.', 'Audio');
}
}
},
/**
* Test for missing or null alt text in certain elements.
*
* Tested elements are:
* - IMG elements
* - INPUT elements with type="image" (ie. image submit buttons).
* - AREA elements (ie. in client-side image maps).
*
* @param {DOMNode} element The element to test.
*
* @returns {Object} A structured list of errors.
*/
testNullAltText: function(top)
{
var errors = {
img: {
generalAlt: [],
missingAlt: [],
ignored: [],
nullAltWithTitle: [],
emptyAltInLink: []
},
inputImage: {
generalAlt: [],
missingAlt: []
},
area: {
generalAlt: [],
missingAlt: []
}
};
elements = HTMLCS.util.getAllElements(top, 'img, area, input[type="image"]');
for (var el = 0; el < elements.length; el++) {
var element = elements[el];
var nodeName = element.nodeName.toLowerCase();
var linkOnlyChild = false;
var missingAlt = false;
var nullAlt = false;
if (element.parentNode.nodeName.toLowerCase() === 'a') {
var prevNode = HTMLCS.util.getPreviousSiblingElement(element, null);
var nextNode = HTMLCS.util.getNextSiblingElement(element, null);
if ((prevNode === null) && (nextNode === null)) {
var textContent = element.parentNode.textContent;
if (element.parentNode.textContent !== undefined) {
var textContent = element.parentNode.textContent;
} else {
// Keep IE8 happy.
var textContent = element.parentNode.innerText;
}
if (HTMLCS.util.isStringEmpty(textContent) === true) {
linkOnlyChild = true;
}
}
}//end if
if (element.hasAttribute('alt') === false) {
missingAlt = true;
} else if (!element.getAttribute('alt') || HTMLCS.util.isStringEmpty(element.getAttribute('alt')) === true) {
nullAlt = true;
}
// Now determine which test(s) should fire.
switch (nodeName) {
case 'img':
if ((linkOnlyChild === true) && ((missingAlt === true) || (nullAlt === true))) {
// Img tags cannot have an empty alt text if it is the
// only content in a link (as the link would not have a text
// alternative).
errors.img.emptyAltInLink.push(element.parentNode);
} else if (missingAlt === true) {
errors.img.missingAlt.push(element);
} else if (nullAlt === true) {
if ((element.hasAttribute('title') === true) && (HTMLCS.util.isStringEmpty(element.getAttribute('title')) === false)) {
// Title attribute present and not empty. This is wrong when
// an image is marked as ignored.
errors.img.nullAltWithTitle.push(element);
} else {
errors.img.ignored.push(element);
}
} else {
errors.img.generalAlt.push(element);
}
break;
case 'input':
// Image submit buttons.
if ((missingAlt === true) || (nullAlt === true)) {
errors.inputImage.missingAlt.push(element);
} else {
errors.inputImage.generalAlt.push(element);
}
break;
case 'area':
// Area tags in a client-side image map.
if ((missingAlt === true) || (nullAlt === true)) {
errors.area.missingAlt.push(element);
} else {
errors.inputImage.generalAlt.push(element);
}
break;
default:
// No other tags defined.
break;
}//end switch
}//end for
return errors;
},
/**
* Driver function for the null alt text tests.
*
* This takes the generic result given by the alt text testing functions
* (located in WCAG 2.1 SC 1.1.1), and converts them into Section 508-specific
* messages.
*
* @param {DOMNode} element The element to test.
*/
addNullAltTextResults: function(top)
{
var errors = this.testNullAltText(top);
for (var i = 0; i < errors.img.emptyAltInLink.length; i++) {
HTMLCS.addMessage(HTMLCS.ERROR, errors.img.emptyAltInLink[i], 'Img element is the only content of the link, but is missing alt text. The alt text should describe the purpose of the link.', 'Img.EmptyAltInLink');
}
for (var i = 0; i < errors.img.nullAltWithTitle.length; i++) {
HTMLCS.addMessage(HTMLCS.ERROR, errors.img.nullAltWithTitle[i], 'Img element with empty alt text must have absent or empty title attribute.', 'Img.NullAltWithTitle');
}
for (var i = 0; i < errors.img.ignored.length; i++) {
HTMLCS.addMessage(HTMLCS.WARNING, errors.img.ignored[i], 'Img element is marked so that it is ignored by Assistive Technology.', 'Img.Ignored');
}
for (var i = 0; i < errors.img.missingAlt.length; i++) {
HTMLCS.addMessage(HTMLCS.ERROR, errors.img.missingAlt[i], 'Img element missing an alt attribute. Use the alt attribute to specify a short text alternative.', 'Img.MissingAlt');
}
for (var i = 0; i < errors.img.generalAlt.length; i++) {
HTMLCS.addMessage(HTMLCS.NOTICE, errors.img.generalAlt[i], 'Ensure that the img element\'s alt text serves the same purpose and presents the same information as the image.', 'Img.GeneralAlt');
}
for (var i = 0; i < errors.inputImage.missingAlt.length; i++) {
HTMLCS.addMessage(HTMLCS.ERROR, errors.inputImage.missingAlt[i], 'Image submit button missing an alt attribute. Specify a text alternative that describes the button\'s function, using the alt attribute.', 'InputImage.MissingAlt');
}
for (var i = 0; i < errors.inputImage.generalAlt.length; i++) {
HTMLCS.addMessage(HTMLCS.NOTICE, errors.inputImage.generalAlt[i], 'Ensure that the image submit button\'s alt text identifies the purpose of the button.', 'InputImage.GeneralAlt');
}
for (var i = 0; i < errors.area.missingAlt.length; i++) {
HTMLCS.addMessage(HTMLCS.ERROR, errors.area.missingAlt[i], 'Area element in an image map missing an alt attribute. Each area element must have a text alternative that describes the function of the image map area.', 'Area.MissingAlt');
}
for (var i = 0; i < errors.area.generalAlt.length; i++) {
HTMLCS.addMessage(HTMLCS.NOTICE, errors.area.generalAlt[i], 'Ensure that the area element\'s text alternative serves the same purpose as the part of image map image it references.', 'Area.GeneralAlt');
}
},
testMediaTextAlternatives: function(top)
{
var errors = {
object: {
missingBody: [],
generalAlt: []
},
applet: {
missingBody: [],
missingAlt: [],
generalAlt: []
}
};
var elements = HTMLCS.util.getAllElements(top, 'object');
for (var el = 0; el < elements.length; el++) {
var element = elements[el];
var childObject = element.querySelector('object');
// If we have an object as our alternative, skip it. Pass the blame onto
// the child.
if (childObject === null) {
var textAlt = HTMLCS.util.getElementTextContent(element, true);
if (textAlt === '') {
errors.object.missingBody.push(element);
} else {
errors.object.generalAlt.push(element);
}
}//end if
}//end if
var elements = HTMLCS.util.getAllElements(top, 'applet');
for (var el = 0; el < elements.length; el++) {
// Test firstly for whether we have an object alternative.
var childObject = element.querySelector('object');
var hasError = false;
// If we have an object as our alternative, skip it. Pass the blame onto
// the child. (This is a special case: those that don't understand APPLET
// may understand OBJECT, but APPLET shouldn't be nested.)
if (childObject === null) {
var textAlt = HTMLCS.util.getElementTextContent(element, true);
if (HTMLCS.util.isStringEmpty(textAlt) === true) {
errors.applet.missingBody.push(element);
hasError = true;
}
}//end if
var altAttr = element.getAttribute('alt') || '';
if (HTMLCS.util.isStringEmpty(altAttr) === true) {
errors.applet.missingAlt.push(element);
hasError = true;
}
if (hasError === false) {
// No error? Remind of obligations about equivalence of alternatives.
errors.applet.generalAlt.push(element);
}
}//end if
return errors;
},
/**
* Driver function for the media alternative (object/applet) tests.
*
* This takes the generic result given by the media alternative testing function
* (located in WCAG 2.1 SC 1.1.1), and converts them into Section
* 508-specific messages.
*
* @param {DOMNode} element The element to test.
*/
addMediaAlternativesResults: function(top)
{
var errors = HTMLCS_WCAG2AAA_Sniffs_Principle1_Guideline1_1_1_1_1.testMediaTextAlternatives(top);
for (var i = 0; i < errors.object.missingBody.length; i++) {
HTMLCS.addMessage(HTMLCS.ERROR, errors.object.missingBody[i], 'Object elements must contain a text alternative after all other alternatives are exhausted.', 'Object.MissingBody');
}
for (var i = 0; i < errors.object.generalAlt.length; i++) {
HTMLCS.addMessage(HTMLCS.NOTICE, errors.object.generalAlt[i], 'Check that short (and if appropriate, long) text alternatives are available for non-text content that serve the same purpose and present the same information.', 'Object.GeneralAlt');
}
for (var i = 0; i < errors.applet.missingBody.length; i++) {
HTMLCS.addMessage(HTMLCS.ERROR, errors.applet.missingBody[i], 'Applet elements must contain a text alternative in the element\'s body, for browsers without support for the applet element.', 'Applet.MissingBody');
}
for (var i = 0; i < errors.applet.missingAlt.length; i++) {
HTMLCS.addMessage(HTMLCS.ERROR, errors.applet.missingAlt[i], 'Applet elements must contain an alt attribute, to provide a text alternative to browsers supporting the element but are unable to load the applet.', 'Applet.MissingAlt');
}
for (var i = 0; i < errors.applet.generalAlt.length; i++) {
HTMLCS.addMessage(HTMLCS.NOTICE, errors.applet.generalAlt[i], 'Check that short (and if appropriate, long) text alternatives are available for non-text content that serve the same purpose and present the same information.', 'Applet.GeneralAlt');
}
}
};