Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP
Browse files

Merge pull request #60 from lwright-sq/master

Pull of various commits including Sec.508 support, issue #57
  • Loading branch information...
commit 7a2678c79c45a946aeef41951f91f826431fdbae 2 parents 394f99d + 6ddc3c4
Luke Wright lwright-sq authored
Showing with 1,688 additions and 559 deletions.
  1. +2 −0  Auditor/HTMLCSAuditor.css
  2. +48 −45 Auditor/HTMLCSAuditor.js
  3. +173 −0 HTMLCS.js
  4. +143 −0 Standards/Section508/Sniffs/A.js
  5. +47 −0 Standards/Section508/Sniffs/B.js
  6. +40 −0 Standards/Section508/Sniffs/C.js
  7. +98 −0 Standards/Section508/Sniffs/D.js
  8. +43 −0 Standards/Section508/Sniffs/G.js
  9. +63 −0 Standards/Section508/Sniffs/H.js
  10. +49 −0 Standards/Section508/Sniffs/I.js
  11. +40 −0 Standards/Section508/Sniffs/J.js
  12. +40 −0 Standards/Section508/Sniffs/K.js
  13. +100 −0 Standards/Section508/Sniffs/L.js
  14. +47 −0 Standards/Section508/Sniffs/M.js
  15. +45 −0 Standards/Section508/Sniffs/N.js
  16. +77 −0 Standards/Section508/Sniffs/O.js
  17. +59 −0 Standards/Section508/Sniffs/P.js
  18. +43 −0 Standards/Section508/ruleset.js
  19. +4 −1 Standards/WCAG2A/ruleset.js
  20. +4 −1 Standards/WCAG2AA/ruleset.js
  21. +235 −149 Standards/WCAG2AAA/Sniffs/Principle1/Guideline1_1/1_1_1.js
  22. +6 −175 Standards/WCAG2AAA/Sniffs/Principle1/Guideline1_3/1_3_1.js
  23. +243 −187 Standards/WCAG2AAA/Sniffs/Principle4/Guideline4_1/4_1_2.js
  24. +39 −1 Standards/WCAG2AAA/ruleset.js
2  Auditor/HTMLCSAuditor.css
View
@@ -479,9 +479,11 @@
#HTMLCS-wrapper .HTMLCS-issue-source-inner-u2p {
padding: 0.35em;
clear: both;
+ color: white;
}
#HTMLCS-wrapper .HTMLCS-issue-source-not-supported {
+ color: white;
font-size: 1em;
line-height: 1.4em;
margin: 0 0 0.5em 0.2em;
93 Auditor/HTMLCSAuditor.js
View
@@ -486,7 +486,7 @@ var HTMLCSAuditor = new function()
useStandardSelect.id = _prefix + 'settings-use-standard-select';
useStandardSelect.innerHTML = '';
- var standards = ['WCAG2AAA', 'WCAG2AA', 'WCAG2A'];
+ var standards = HTMLCSAuditor.getStandardList();
for (var i = 0; i < standards.length; i++) {
var standard = standards[i];
var option = _doc.createElement('option');
@@ -725,27 +725,12 @@ var HTMLCSAuditor = new function()
}
var buildMessageDetail = function(id, message, standard) {
- var typeText = '';
-
- var principles = {
- 'Principle1': {
- name: 'Perceivable',
- link: 'http://www.w3.org/TR/WCAG20/#perceivable'
- },
- 'Principle2': {
- name: 'Operable',
- link: 'http://www.w3.org/TR/WCAG20/#operable'
- },
- 'Principle3': {
- name: 'Understandable',
- link: 'http://www.w3.org/TR/WCAG20/#understandable'
- },
- 'Principle4': {
- name: 'Robust',
- link: 'http://www.w3.org/TR/WCAG20/#robust'
- }
+ if (standard === undefined) {
+ standard = _standard;
}
+ var typeText = '';
+
switch (message.type) {
case HTMLCS.ERROR:
typeText = 'Error';
@@ -765,14 +750,11 @@ var HTMLCSAuditor = new function()
}//end switch
var typeClass = _prefix + typeText.toLowerCase();
- var msgCodeParts = message.code.split('.', 5);
- var principle = msgCodeParts[1];
- var techniques = msgCodeParts[4].split(',');
- var techniquesStr = [];
-
- for (var i = 0; i < techniques.length; i++) {
- techniques[i] = techniques[i].split('.');
- techniquesStr.push('<a href="http://www.w3.org/TR/WCAG20-TECHS/' + techniques[i][0] + '" target="_blank">' + techniques[i][0] + '</a>');
+
+ var standardObj = HTMLCS.util.getElementWindow(_doc)['HTMLCS_' + standard];
+ var msgInfo = [];
+ if (standardObj.getMsgInfo) {
+ msgInfo = standardObj.getMsgInfo(message.code);
}
var msgDiv = _doc.createElement('li');
@@ -789,16 +771,18 @@ var HTMLCSAuditor = new function()
msgTitle.className = _prefix + 'issue-title';
msgTitle.innerHTML = message.msg;
- var msgWcagRef = _doc.createElement('div');
- msgWcagRef.className = _prefix + 'issue-wcag-ref';
+ var msgRef = _doc.createElement('div');
+ msgRef.className = _prefix + 'issue-wcag-ref';
- var refContent = '<em>Principle:</em> <a href="' + principles[principle].link + '" target="_blank">' + principles[principle].name + '</a><br/>';
- refContent += '<em>Technique:</em> ' + techniquesStr.join(' '); + '<br/>';
- msgWcagRef.innerHTML = refContent;
+ var refContent = '';
+ for (var i = 0; i < msgInfo.length; i++) {
+ refContent += '<em>' + msgInfo[i][0] + ':</em> ' + msgInfo[i][1] + '<br/>';
+ }
+ msgRef.innerHTML = refContent;
msgDetailsDiv.appendChild(msgType);
msgDetailsDiv.appendChild(msgTitle);
- msgDetailsDiv.appendChild(msgWcagRef);
+ msgDetailsDiv.appendChild(msgRef);
msgDiv.appendChild(msgDetailsDiv);
// If the item cannot be pointed to, tell them why.
@@ -1302,13 +1286,28 @@ var HTMLCSAuditor = new function()
}
}
+ this.getStandardList = function() {
+ var pattern = /^HTMLCS_[^_]+$/;
+ var standards = [];
+ for (i in window) {
+ if (pattern.test(i) === true) {
+ var standard = window[i];
+ if (standard.sniffs && standard.name) {
+ standards.push(i.substr(7));
+ }
+ }
+ }
+
+ return standards;
+ };
+
/**
* Run HTML_CodeSniffer and place the results in the auditor.
*
* @returns undefined
*/
this.run = function(standard, source, options) {
- var standards = ['WCAG2AAA', 'WCAG2AA', 'WCAG2A'];
+ var standards = this.getStandardList();
var standardsToLoad = [];
for (var i = 0; i < standards.length; i++) {
if (!window['HTMLCS_' + standards[i]]) {
@@ -1921,16 +1920,20 @@ var HTMLCSAuditor = new function()
},
getPointer: function(targetElement) {
- var doc = targetElement.ownerDocument;
- HTMLCSAuditor.includeCss('HTMLCS', doc);
- var c = 'HTMLCS';
-
- var myPointer = doc.getElementById(c + '-pointer');
- if (!myPointer) {
- myPointer = doc.createElement('div');
- myPointer.id = c + '-pointer';
- myPointer.className = c + '-pointer ' + c + '-pointer-hidden';
- doc.body.appendChild(myPointer);
+ try {
+ var doc = targetElement.ownerDocument;
+ HTMLCSAuditor.includeCss('HTMLCS', doc);
+ var c = 'HTMLCS';
+
+ var myPointer = doc.getElementById(c + '-pointer');
+ if (!myPointer) {
+ myPointer = doc.createElement('div');
+ myPointer.id = c + '-pointer';
+ myPointer.className = c + '-pointer ' + c + '-pointer-hidden';
+ doc.body.appendChild(myPointer);
+ }
+ } catch (ex) {
+ // Can't get to owner document due to unsafe access.
}
return myPointer;
173 HTMLCS.js
View
@@ -1120,6 +1120,179 @@ var HTMLCS = new function()
};
/**
+ * Test for the correct headers attributes on table cell elements.
+ *
+ * Return value contains the following elements:
+ * - required (Boolean): Whether header association at all is required.
+ * - used (Boolean): Whether headers attribute has been used on at least
+ * one table data (td) cell.
+ * - allowScope (Boolean): Whether scope is allowed to satisfy the association
+ * requirement (ie. max one row/one column).
+ * - correct (Boolean): Whether headers have been correctly used.
+ * - missingThId (Array): Array of th elements without IDs.
+ * - missingTd (Array): Array of elements without headers attribute.
+ * - wrongHeaders (Array): Array of elements where headers attr is incorrect.
+ * Each is a structure with following keys: element,
+ * expected [headers attr], actual [headers attr].
+ *
+ * @param {DOMNode} element Table element to test upon.
+ *
+ * @return {Object} The above return value structure.
+ */
+ this.testTableHeaders = function(element)
+ {
+ var retval = {
+ required: true,
+ used: false,
+ correct: true,
+ allowScope: true,
+ missingThId: [],
+ missingTd: [],
+ wrongHeaders: []
+ }
+
+ var rows = element.getElementsByTagName('tr');
+ var tdCells = {};
+ var skipCells = [];
+
+ // Header IDs already used.
+ var headerIds = {
+ rows: [],
+ cols: []
+ };
+ var multiHeaders = {
+ rows: 0,
+ cols: 0
+ }
+ var missingIds = false;
+
+ for (var rownum = 0; rownum < rows.length; rownum++) {
+ var row = rows[rownum];
+ var colnum = 0;
+
+ for (var item = 0; item < row.childNodes.length; item++) {
+ var cell = row.childNodes[item];
+ if (cell.nodeType === 1) {
+ // Skip columns that are skipped due to rowspan.
+ if (skipCells[rownum]) {
+ while (skipCells[rownum][0] === colnum) {
+ skipCells[rownum].shift();
+ colnum++;
+ }
+ }
+
+ var nodeName = cell.nodeName.toLowerCase();
+ var rowspan = Number(cell.getAttribute('rowspan')) || 1;
+ var colspan = Number(cell.getAttribute('colspan')) || 1;
+
+ // If rowspanned, mark columns as skippable in the following
+ // row(s).
+ if (rowspan > 1) {
+ for (var i = rownum + 1; i < rownum + rowspan; i++) {
+ if (!skipCells[i]) {
+ skipCells[i] = [];
+ }
+
+ for (var j = colnum; j < colnum + colspan; j++) {
+ skipCells[i].push(j);
+ }
+ }
+ }
+
+ if (nodeName === 'th') {
+ var id = (cell.getAttribute('id') || '');
+
+ // Save the fact that we have a missing ID on the header.
+ if (id === '') {
+ retval.correct = false;
+ retval.missingThId.push(cell);
+ }
+
+ if ((rowspan > 1) && (colspan > 1)) {
+ // Multi-column AND multi-row header. Abandon all hope,
+ // As it must span across more than one row+column
+ retval.allowScope = false;
+ } else if (retval.allowScope === true) {
+ // If we haven't had a th in this column (row) yet,
+ // record it. if we find another th in this column (row),
+ // record that has multi-ths. If we already have a column
+ // (row) with multi-ths, we cannot use scope.
+ if (headerIds.cols[colnum] === undefined) {
+ headerIds.cols[colnum] = 0;
+ }
+
+ if (headerIds.rows[rownum] === undefined) {
+ headerIds.rows[rownum] = 0;
+ }
+
+ headerIds.rows[rownum] += colspan;
+ headerIds.cols[colnum] += rowspan;
+ }//end if
+ } else if ((nodeName === 'td')) {
+ if ((cell.hasAttribute('headers') === true) && (/^\s*$/.test(cell.getAttribute('headers')) === false)) {
+ retval.used = true;
+ }
+ }//end if
+
+ colnum += colspan;
+ }//end if
+ }//end for
+ }//end for
+
+ for (var i = 0; i < headerIds.rows.length; i++) {
+ if (headerIds.rows[i] > 1) {
+ multiHeaders.rows++;
+ }
+ }
+
+ for (var i = 0; i < headerIds.cols.length; i++) {
+ if (headerIds.cols[i] > 1) {
+ multiHeaders.cols++;
+ }
+ }
+
+ if ((multiHeaders.rows > 1) || (multiHeaders.cols > 1)) {
+ retval.allowScope = false;
+ } else if ((retval.allowScope === true) && ((multiHeaders.rows === 0) || (multiHeaders.cols === 0))) {
+ // If only one column OR one row header.
+ retval.required = false;
+ }//end if
+
+ // Calculate expected heading IDs. If they are not there or incorrect, flag
+ // them.
+ var cells = HTMLCS.util.getCellHeaders(element);
+ for (var i = 0; i < cells.length; i++) {
+ var cell = cells[i].cell;
+ var expected = cells[i].headers;
+
+ if (cell.hasAttribute('headers') === false) {
+ retval.correct = false;
+ retval.missingTd.push(cell);
+ } else {
+ var actual = (cell.getAttribute('headers') || '').split(/\s+/);
+ if (actual.length === 0) {
+ retval.correct = false;
+ retval.missingTd.push(cell);
+ } else {
+ actual = ' ' + actual.sort().join(' ') + ' ';
+ actual = actual.replace(/\s+/g, ' ').replace(/(\w+\s)\1+/g, '$1').replace(/^\s*(.*?)\s*$/g, '$1');
+ if (expected !== actual) {
+ retval.correct = false;
+ var val = {
+ element: cell,
+ expected: expected,
+ actual: (cell.getAttribute('headers') || '')
+ }
+ retval.wrongHeaders.push(val);
+ }
+ }//end if
+ }//end if
+ }//end for
+
+ return retval;
+ };
+
+ /**
* Return expected cell headers from a table.
*
* Returns null if not a table.
143 Standards/Section508/Sniffs/A.js
View
@@ -0,0 +1,143 @@
+/**
+ * +--------------------------------------------------------------------+
+ * | 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. |
+ * +--------------------------------------------------------------------+
+ *
+ */
+
+var 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 [
+ 'img',
+ 'input',
+ 'area',
+ 'object',
+ 'applet',
+ '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.0). 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');
+ }
+ }
+ },
+
+ /**
+ * Driver function for the null alt text tests.
+ *
+ * This takes the generic result given by the alt text testing functions
+ * (located in WCAG 2.0 SC 1.1.1), and converts them into Section 508-specific
+ * messages.
+ *
+ * @param {DOMNode} element The element to test.
+ */
+ addNullAltTextResults: function(top)
+ {
+ var errors = HTMLCS_WCAG2AAA_Sniffs_Principle1_Guideline1_1_1_1_1.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');
+ }
+ },
+
+ /**
+ * 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.0 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++) {A
+ 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');
+ }
+ }
+};
47 Standards/Section508/Sniffs/B.js
View
@@ -0,0 +1,47 @@
+/**
+ * +--------------------------------------------------------------------+
+ * | 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. |
+ * +--------------------------------------------------------------------+
+ *
+ */
+
+var HTMLCS_Section508_Sniffs_B = {
+ /**
+ * 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 [
+ 'object',
+ 'applet',
+ 'embed',
+ 'video'
+ ];
+
+ },
+
+ /**
+ * Process the registered element.
+ *
+ * @param {DOMNode} element The element registered.
+ * @param {DOMNode} top The top element of the tested code.
+ */
+ process: function(element, top)
+ {
+ var nodeName = element.nodeName.toLowerCase();
+ HTMLCS.addMessage(HTMLCS.NOTICE, element, 'For multimedia containing video, ensure a synchronised audio description or text alternative for the video portion is provided.', 'Video');
+ HTMLCS.addMessage(HTMLCS.NOTICE, element, 'For multimedia containing synchronised audio and video, ensure synchronised captions are provided for the audio portion.', 'Captions');
+
+ }
+};
40 Standards/Section508/Sniffs/C.js
View
@@ -0,0 +1,40 @@
+/**
+ * +--------------------------------------------------------------------+
+ * | 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. |
+ * +--------------------------------------------------------------------+
+ *
+ */
+
+var HTMLCS_Section508_Sniffs_C = {
+ /**
+ * 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'];
+
+ },
+
+ /**
+ * Process the registered element.
+ *
+ * @param {DOMNode} element The element registered.
+ * @param {DOMNode} top The top element of the tested code.
+ */
+ process: function(element, top)
+ {
+ HTMLCS.addMessage(HTMLCS.NOTICE, top, 'Ensure that any information conveyed using colour alone is also available without colour, such as through context or markup.', 'Colour');
+
+ }
+};
98 Standards/Section508/Sniffs/D.js
View
@@ -0,0 +1,98 @@
+/**
+ * +--------------------------------------------------------------------+
+ * | 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. |
+ * +--------------------------------------------------------------------+
+ *
+ */
+
+var HTMLCS_Section508_Sniffs_D = {
+ /**
+ * 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'];
+
+ },
+
+ /**
+ * 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) {
+ HTMLCS.addMessage(HTMLCS.NOTICE, top, 'Ensure that content is ordered in a meaningful sequence when linearised, such as when style sheets are disabled.', 'Linearised');
+ this.testPresentationMarkup(top);
+ this.testHeadingOrder(top);
+
+ // Look for any script elements, and fire off another notice regarding
+ // potentially hidden text (eg. "click to expand" sections). For instance,
+ // such text should be stored semantically in the page, not loaded into
+ // a container through AJAX (and thus not accessible with scripting off).
+ var hasScript = top.querySelectorAll('script, link[rel="stylesheet"]');
+ if (hasScript.length > 0) {
+ HTMLCS.addMessage(HTMLCS.NOTICE, top, 'If content is hidden and made visible using scripting (such as "click to expand" sections), ensure this content is readable when scripts and style sheets are disabled.', 'HiddenText');
+ }
+ }
+ },
+
+ /**
+ * Test for the use of presentational elements.
+ *
+ * @param [DOMNode] top The top element of the tested code.
+ */
+ testPresentationMarkup: function(top)
+ {
+ // Presentation tags that should have no place in modern HTML.
+ var tags = top.querySelectorAll('b, i, u, s, strike, tt, big, small, center, font');
+
+ for (var i = 0; i < tags.length; i++) {
+ var msgCode = 'PresMarkup.' + tags[i].nodeName.substr(0, 1).toUpperCase() + tags[i].nodeName.substr(1).toLowerCase();
+ HTMLCS.addMessage(HTMLCS.WARNING, tags[i], 'Semantic markup should be used to mark emphasised or special text so that it can be programmatically determined.', msgCode);
+ }
+
+ // Align attributes, too.
+ var tags = top.querySelectorAll('*[align]');
+
+ for (var i = 0; i < tags.length; i++) {
+ var msgCode = 'PresMarkup.AlignAttr';
+ HTMLCS.addMessage(HTMLCS.WARNING, tags[i], 'Semantic markup should be used to mark emphasised or special text so that it can be programmatically determined.', msgCode);
+ }
+ },
+
+ testHeadingOrder: function(top) {
+ var lastHeading = 0;
+ var headings = top.querySelectorAll('h1, h2, h3, h4, h5, h6');
+
+ for (var i = 0; i < headings.length; i++) {
+ var headingNum = parseInt(headings[i].nodeName.substr(1, 1));
+ if (headingNum - lastHeading > 1) {
+ var exampleMsg = 'should be an h' + (lastHeading + 1) + ' to be properly nested';
+ if (lastHeading === 0) {
+ // If last heading is empty, we are at document top and we are
+ // expecting a H1, generally speaking.
+ exampleMsg = 'appears to be the primary document heading, so should be an h1 element';
+ }
+
+ HTMLCS.addMessage(HTMLCS.ERROR, headings[i], 'The heading structure is not logically nested. This h' + headingNum + ' element ' + exampleMsg + '.', 'HeadingOrder');
+ }
+
+ lastHeading = headingNum;
+ }
+ }
+
+};
43 Standards/Section508/Sniffs/G.js
View
@@ -0,0 +1,43 @@
+/**
+ * +--------------------------------------------------------------------+
+ * | 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. |
+ * +--------------------------------------------------------------------+
+ *
+ */
+
+var HTMLCS_Section508_Sniffs_G = {
+ /**
+ * 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 ['table'];
+
+ },
+
+ /**
+ * 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 no table headers, emit notice about the table.
+ if (HTMLCS.util.isLayoutTable(element) === true) {
+ HTMLCS.addMessage(HTMLCS.NOTICE, element, 'This table has no headers. If this is a data table, ensure row and column headers are identified using th elements.', 'TableHeaders');
+ }
+ }
+
+};
63 Standards/Section508/Sniffs/H.js
View
@@ -0,0 +1,63 @@
+/**
+ * +--------------------------------------------------------------------+
+ * | 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. |
+ * +--------------------------------------------------------------------+
+ *
+ */
+
+var HTMLCS_Section508_Sniffs_H = {
+ /**
+ * 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 ['table'];
+
+ },
+
+ /**
+ * Process the registered element.
+ *
+ * @param {DOMNode} element The element registered.
+ * @param {DOMNode} top The top element of the tested code.
+ */
+ process: function(table, top)
+ {
+ var headersAttr = HTMLCS.util.testTableHeaders(table);
+
+ // Incorrect usage of headers - error; emit always.
+ for (var i = 0; i < headersAttr.wrongHeaders.length; i++) {
+ HTMLCS.addMessage(HTMLCS.ERROR, headersAttr.wrongHeaders[i].element, 'Incorrect headers attribute on this td element. Expected "' + headersAttr.wrongHeaders[i].expected + '" but found "' + headersAttr.wrongHeaders[i].actual + '"', 'IncorrectHeadersAttr');
+ }
+
+ // Errors where headers are compulsory.
+ if ((headersAttr.required === true) && (headersAttr.allowScope === false)) {
+ if (headersAttr.used === false) {
+ // Headers not used at all, and they are mandatory.
+ HTMLCS.addMessage(HTMLCS.ERROR, table, 'The relationship between td elements and their associated th elements is not defined. As this table has multiple levels of th elements, you must use the headers attribute on td elements.', 'MissingHeadersAttrs');
+ } else {
+ // Missing TH IDs - error; emit at this stage only if headers are compulsory.
+ if (headersAttr.missingThId.length > 0) {
+ HTMLCS.addMessage(HTMLCS.ERROR, table, 'Not all th elements in this table contain an id attribute. These cells should contain ids so that they may be referenced by td elements\' headers attributes.', 'MissingHeaderIds');
+ }
+
+ // Missing TD headers attributes - error; emit at this stage only if headers are compulsory.
+ if (headersAttr.missingTd.length > 0) {
+ HTMLCS.addMessage(HTMLCS.ERROR, table, 'Not all td elements in this table contain a headers attribute. Each headers attribute should list the ids of all th elements associated with that cell.', 'IncompleteHeadersAttrs');
+ }
+ }//end if
+ }//end if
+ }
+
+};
49 Standards/Section508/Sniffs/I.js
View
@@ -0,0 +1,49 @@
+/**
+ * +--------------------------------------------------------------------+
+ * | 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. |
+ * +--------------------------------------------------------------------+
+ *
+ */
+
+var HTMLCS_Section508_Sniffs_I = {
+ /**
+ * 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 [
+ 'frame',
+ 'iframe',
+ 'object'
+ ];
+
+ },
+
+ /**
+ * Process the registered element.
+ *
+ * @param {DOMNode} element The element registered.
+ * @param {DOMNode} top The top element of the tested code.
+ */
+ process: function(element, top)
+ {
+ var nodeName = element.nodeName.toLowerCase();
+ var hasTitle = element.hasAttribute('title');
+ var titleEmpty = HTMLCS.util.isStringEmpty(element.getAttribute('title'));
+
+ if ((hasTitle === true) || (titleEmpty === true)) {
+ HTMLCS.addMessage(HTMLCS.ERROR, top, 'This ' + nodeName + ' element is missing title text. Frames should be titled with text that facilitates frame identification and navigation.', 'Frames');
+ }
+ }
+};
40 Standards/Section508/Sniffs/J.js
View
@@ -0,0 +1,40 @@
+/**
+ * +--------------------------------------------------------------------+
+ * | 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. |
+ * +--------------------------------------------------------------------+
+ *
+ */
+
+var HTMLCS_Section508_Sniffs_J = {
+ /**
+ * 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'];
+
+ },
+
+ /**
+ * Process the registered element.
+ *
+ * @param {DOMNode} element The element registered.
+ * @param {DOMNode} top The top element of the tested code.
+ */
+ process: function(element, top)
+ {
+ // The term in Sec. 508 is "flicker" rather than flash.
+ HTMLCS.addMessage(HTMLCS.NOTICE, top, 'Check that no component of the content flickers at a rate of greater than 2 and less than 55 times per second.', 'Flicker');
+ }
+};
40 Standards/Section508/Sniffs/K.js
View
@@ -0,0 +1,40 @@
+/**
+ * +--------------------------------------------------------------------+
+ * | 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. |
+ * +--------------------------------------------------------------------+
+ *
+ */
+
+var HTMLCS_Section508_Sniffs_K = {
+ /**
+ * 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'];
+
+ },
+
+ /**
+ * Process the registered element.
+ *
+ * @param {DOMNode} element The element registered.
+ * @param {DOMNode} top The top element of the tested code.
+ */
+ process: function(element, top)
+ {
+ HTMLCS.addMessage(HTMLCS.NOTICE, top, 'If this page cannot be made compliant, a text-only page with equivalent information or functionality should be provided. The alternative page needs to be updated in line with this page\'s content.', 'AltVersion');
+ }
+
+};
100 Standards/Section508/Sniffs/L.js
View
@@ -0,0 +1,100 @@
+/**
+ * +--------------------------------------------------------------------+
+ * | 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. |
+ * +--------------------------------------------------------------------+
+ *
+ */
+
+var HTMLCS_Section508_Sniffs_L = {
+ /**
+ * 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'];
+
+ },
+
+ /**
+ * 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.addProcessLinksMessages(top);
+ this.testKeyboard(top);
+ }
+ },
+
+ addProcessLinksMessages: function(top)
+ {
+ var errors = HTMLCS_WCAG2AAA_Sniffs_Principle4_Guideline4_1_4_1_2.processLinks(top);
+ for (var i = 0; i < errors.emptyNoId.length; i++) {
+ HTMLCS.addMessage(HTMLCS.ERROR, errors.emptyNoId[i], 'Anchor element found with no link content and no name and/or ID attribute.', 'EmptyAnchorNoId');
+ }
+
+ for (var i = 0; i < errors.placeholder.length; i++) {
+ HTMLCS.addMessage(HTMLCS.WARNING, errors.placeholder[i], 'Anchor element found with link content, but no href and/or ID attribute has been supplied.', 'PlaceholderAnchor');
+ }
+
+ for (var i = 0; i < errors.noContent.length; i++) {
+ HTMLCS.addMessage(HTMLCS.ERROR, errors.noContent[i], 'Anchor element found with a valid href attribute, but no link content has been supplied.', 'NoContentAnchor');
+ }
+ },
+
+ /**
+ * Process mouse-specific functions.
+ *
+ * @param {DOMNode} top The top element of the tested code.
+ */
+ testKeyboard: function(top)
+ {
+ // Testing for elements that have explicit attributes for mouse-specific
+ // events. Note: onclick is considered keyboard accessible, as it is actually
+ // tied to the default action of a link or button - not merely a click.
+ var dblClickEls = top.querySelectorAll('*[ondblclick]');
+ for (var i = 0; i < dblClickEls.length; i++) {
+ HTMLCS.addMessage(HTMLCS.WARNING, dblClickEls[i], 'Ensure the functionality provided by double-clicking on this element is available through the keyboard.', 'DblClick');
+ }
+
+ var mouseOverEls = top.querySelectorAll('*[onmouseover]');
+ for (var i = 0; i < mouseOverEls.length; i++) {
+ HTMLCS.addMessage(HTMLCS.WARNING, mouseOverEls[i], 'Ensure the functionality provided by mousing over this element is available through the keyboard; for instance, using the focus event.', 'MouseOver');
+ }
+
+ var mouseOutEls = top.querySelectorAll('*[onmouseout]');
+ for (var i = 0; i < mouseOutEls.length; i++) {
+ HTMLCS.addMessage(HTMLCS.WARNING, mouseOutEls[i], 'Ensure the functionality provided by mousing out of this element is available through the keyboard; for instance, using the blur event.', 'MouseOut');
+ }
+
+ var mouseMoveEls = top.querySelectorAll('*[onmousemove]');
+ for (var i = 0; i < mouseMoveEls.length; i++) {
+ HTMLCS.addMessage(HTMLCS.WARNING, mouseMoveEls[i], 'Ensure the functionality provided by moving the mouse on this element is available through the keyboard.', 'MouseMove');
+ }
+
+ var mouseDownEls = top.querySelectorAll('*[onmousedown]');
+ for (var i = 0; i < mouseDownEls.length; i++) {
+ HTMLCS.addMessage(HTMLCS.WARNING, mouseDownEls[i], 'Ensure the functionality provided by mousing down on this element is available through the keyboard; for instance, using the keydown event.', 'MouseDown');
+ }
+
+ var mouseUpEls = top.querySelectorAll('*[onmouseup]');
+ for (var i = 0; i < mouseUpEls.length; i++) {
+ HTMLCS.addMessage(HTMLCS.WARNING, mouseUpEls[i], 'Ensure the functionality provided by mousing up on this element is available through the keyboard; for instance, using the keyup event.', 'MouseUp');
+ }
+ }
+
+};
47 Standards/Section508/Sniffs/M.js
View
@@ -0,0 +1,47 @@
+/**
+ * +--------------------------------------------------------------------+
+ * | 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. |
+ * +--------------------------------------------------------------------+
+ *
+ */
+
+var HTMLCS_Section508_Sniffs_M = {
+ /**
+ * 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 [
+ 'object',
+ 'applet',
+ 'bgsound',
+ 'embed',
+ 'audio',
+ 'video'
+ ];
+
+ },
+
+ /**
+ * Process the registered element.
+ *
+ * @param {DOMNode} element The element registered.
+ * @param {DOMNode} top The top element of the tested code.
+ */
+ process: function(element, top)
+ {
+ HTMLCS.addMessage(HTMLCS.NOTICE, element, 'If external media requires a plugin or application to view, ensure a link is provided to a plugin or application that complies with Section 508 accessibility requirements for applications.', 'PluginLink');
+ }
+
+};
45 Standards/Section508/Sniffs/N.js
View
@@ -0,0 +1,45 @@
+/**
+ * +--------------------------------------------------------------------+
+ * | 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. |
+ * +--------------------------------------------------------------------+
+ *
+ */
+
+var HTMLCS_Section508_Sniffs_N = {
+ /**
+ * 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 ['form'];
+
+ },
+
+ /**
+ * Process the registered element.
+ *
+ * @param {DOMNode} element The element registered.
+ * @param {DOMNode} top The top element of the tested code.
+ */
+ process: function(element, top)
+ {
+ var nodeName = element.nodeName.toLowerCase();
+ if (nodeName === 'form') {
+ HTMLCS.addMessage(HTMLCS.NOTICE, element, 'If an input error is automatically detected in this form, check that the item(s) in error are identified and the error(s) are described to the user in text.', 'Errors');
+ HTMLCS.addMessage(HTMLCS.NOTICE, element, 'Check that descriptive labels or instructions (including for required fields) are provided for user input in this form.', 'Labels');
+ HTMLCS.addMessage(HTMLCS.NOTICE, element, 'Ensure that this form can be navigated using the keyboard and other accessibility tools.', 'KeyboardNav');
+ }
+ }
+
+};
77 Standards/Section508/Sniffs/O.js
View
@@ -0,0 +1,77 @@
+/**
+ * +--------------------------------------------------------------------+
+ * | 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. |
+ * +--------------------------------------------------------------------+
+ *
+ */
+
+var HTMLCS_Section508_Sniffs_O = {
+ /**
+ * 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',
+ 'a',
+ 'area'
+ ];
+
+ },
+
+ /**
+ * 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) {
+ HTMLCS.addMessage(HTMLCS.NOTICE, top, 'Ensure that any common navigation elements can be bypassed; for instance, by use of skip links, header elements, or ARIA landmark roles.', 'SkipLinks');
+ } else {
+ if (element.hasAttribute('href') === true) {
+ var href = element.getAttribute('href');
+ href = HTMLCS.util.trim(href);
+ if ((href.length > 1) && (href.charAt(0) === '#')) {
+ var id = href.substr(1);
+
+ try {
+ var doc = top;
+ if (doc.ownerDocument) {
+ doc = doc.ownerDocument;
+ }
+
+ // First search for an element with the appropriate ID, then search for a
+ // named anchor using the name attribute.
+ var target = doc.getElementById(id);
+ if (target === null) {
+ target = doc.querySelector('a[name="' + id + '"]');
+ }
+
+ if ((target === null) || (HTMLCS.util.contains(top, target) === false)) {
+ if ((HTMLCS.isFullDoc(top) === true) || (top.nodeName.toLowerCase() === 'body')) {
+ HTMLCS.addMessage(HTMLCS.ERROR, element, 'This link points to a named anchor "' + id + '" within the document, but no anchor exists with that name.', 'NoSuchID');
+ } else {
+ HTMLCS.addMessage(HTMLCS.WARNING, element, 'This link points to a named anchor "' + id + '" within the document, but no anchor exists with that name in the fragment tested.', 'NoSuchIDFragment');
+ }
+ }
+ } catch (ex) {
+ }//end try
+ }//end if
+ }
+ }
+ }
+
+};
59 Standards/Section508/Sniffs/P.js
View
@@ -0,0 +1,59 @@
+/**
+ * +--------------------------------------------------------------------+
+ * | 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. |
+ * +--------------------------------------------------------------------+
+ *
+ */
+
+var HTMLCS_Section508_Sniffs_P = {
+ /**
+ * 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',
+ 'meta'
+ ];
+
+ },
+
+ /**
+ * 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) {
+ HTMLCS.addMessage(HTMLCS.NOTICE, top, 'If a timed response is required on this page, alert the user and provide sufficient time to allow them to indicate that more time is required.', 'TimeLimit');
+ } else {
+ if (element.hasAttribute('http-equiv') === true) {
+ if ((String(element.getAttribute('http-equiv'))).toLowerCase() === 'refresh') {
+ if (/^[1-9]\d*/.test(element.getAttribute('content').toLowerCase()) === true) {
+ if (/url=/.test(element.getAttribute('content').toLowerCase()) === true) {
+ // Redirect.
+ HTMLCS.addMessage(HTMLCS.ERROR, element, 'Meta refresh tag used to redirect to another page, with a time limit that is not zero. Users cannot control this time limit.', 'MetaRedirect');
+ } else {
+ // Just a refresh.
+ HTMLCS.addMessage(HTMLCS.ERROR, element, 'Meta refresh tag used to refresh the current page. Users cannot control the time limit for this refresh.', 'MetaRefresh');
+ }
+ }
+ }//end if
+ }//end if
+ }//end if
+ }
+
+};
43 Standards/Section508/ruleset.js
View
@@ -0,0 +1,43 @@
+/**
+ * +--------------------------------------------------------------------+
+ * | 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. |
+ * +--------------------------------------------------------------------+
+ *
+ */
+
+var HTMLCS_Section508 = {
+ name: 'Section508',
+ description: 'U.S. Section 508 Standard',
+ sniffs: [
+ 'A',
+ 'B',
+ 'C',
+ 'D',
+ 'G',
+ 'H',
+ 'I',
+ 'J',
+ 'K',
+ 'L',
+ 'M',
+ 'N',
+ 'O',
+ 'P'
+ ],
+ getMsgInfo: function(code) {
+ var msgCodeParts = code.split('.', 3);
+ var paragraph = msgCodeParts[1].toLowerCase();
+
+ var retval = [
+ ['Section', '1194.22 (' + paragraph + ')']
+ ];
+
+ return retval;
+ }
+};
5 Standards/WCAG2A/ruleset.js
View
@@ -46,5 +46,8 @@ var HTMLCS_WCAG2A = {
'Principle4.Guideline4_1.4_1_2'
]
}
- ]
+ ],
+ getMsgInfo: function(code) {
+ return HTMLCS_WCAG2AAA.getMsgInfo(code);
+ }
};
5 Standards/WCAG2AA/ruleset.js
View
@@ -60,5 +60,8 @@ var HTMLCS_WCAG2AA = {
'Principle4.Guideline4_1.4_1_2'
]
}
- ]
+ ],
+ getMsgInfo: function(code) {
+ return HTMLCS_WCAG2AAA.getMsgInfo(code);
+ }
};
384 Standards/WCAG2AAA/Sniffs/Principle1/Guideline1_1/1_1_1.js
View
@@ -23,11 +23,8 @@ var HTMLCS_WCAG2AAA_Sniffs_Principle1_Guideline1_1_1_1_1 = {
register: function()
{
return [
- 'img',
- 'input',
- 'area',
- 'object',
- 'applet'
+ '_top',
+ 'img'
];
},
@@ -40,34 +37,67 @@ var HTMLCS_WCAG2AAA_Sniffs_Principle1_Guideline1_1_1_1_1 = {
*/
process: function(element, top)
{
- var nodeName = element.nodeName.toLowerCase();
-
- switch (nodeName) {
- case 'img':
- this.testNullAltText(element);
- this.testLinkStutter(element);
- this.testLongdesc(element);
- break;
-
- case 'input':
- // Only look for input type="image" tags.
- if ((element.hasAttribute('type') === true) && (element.getAttribute('type') === 'image')) {
- this.testNullAltText(element);
- }
- break;
+ if (element === top) {
+ this.addNullAltTextResults(top);
+ this.addMediaAlternativesResults(top);
+ } else {
+ var nodeName = element.nodeName.toLowerCase();
+
+ switch (nodeName) {
+ case 'img':
+ this.testLinkStutter(element);
+ this.testLongdesc(element);
+ break;
+ }//end if
+ }//end if
+ },
+
+ /**
+ * Driver function for the null alt text tests.
+ *
+ * This takes the generic result given by the alt text testing functions,
+ * and converts them into WCAG 2.0-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.', 'H30.2');
+ }
+
+ 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.', 'H67.1');
+ }
+
+ 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.', 'H67.2');
+ }
+
+ 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.', 'H37');
+ }
+
+ 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.', 'G94.Image');
+ }
+
+ 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.', 'H36');
+ }
- case 'area':
- // Client-side image maps.
- this.testNullAltText(element);
- break;
+ 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.', 'G94.Button');
+ }
- case 'object':
- this.testObjectTextAlternative(element);
- break;
+ 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.', 'H24');
+ }
- case 'applet':
- this.testAppletTextAlternative(element);
- break;
+ 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.', 'H24.2');
}
},
@@ -81,86 +111,112 @@ var HTMLCS_WCAG2AAA_Sniffs_Principle1_Guideline1_1_1_1_1 = {
*
* @param {DOMNode} element The element to test.
*
- * @returns void
+ * @returns {Object} A structured list of errors.
*/
- testNullAltText: function(element)
+ testNullAltText: function(top)
{
- var nodeName = element.nodeName.toLowerCase();
- var linkOnlyChild = false;
- var missingAlt = false;
- var nullAlt = false;
+ var errors = {
+ img: {
+ generalAlt: [],
+ missingAlt: [],
+ ignored: [],
+ nullAltWithTitle: [],
+ emptyAltInLink: []
+ },
+ inputImage: {
+ generalAlt: [],
+ missingAlt: []
+ },
+ area: {
+ generalAlt: [],
+ missingAlt: []
+ }
+ };
- if (element.parentNode.nodeName.toLowerCase() === 'a') {
- var prevNode = this._getPreviousSiblingElement(element, null);
- var nextNode = this._getNextSiblingElement(element, null);
+ elements = top.querySelectorAll('img, area, input[type="image"]');
+
+ for (var el = 0; el < elements.length; el++) {
+ var element = elements[el];
- if ((prevNode === null) && (nextNode === null)) {
- var textContent = element.parentNode.textContent;
+ var nodeName = element.nodeName.toLowerCase();
+ var linkOnlyChild = false;
+ var missingAlt = false;
+ var nullAlt = false;
- if (element.parentNode.textContent !== undefined) {
+ if (element.parentNode.nodeName.toLowerCase() === 'a') {
+ var prevNode = this._getPreviousSiblingElement(element, null);
+ var nextNode = this._getNextSiblingElement(element, null);
+
+ if ((prevNode === null) && (nextNode === null)) {
var textContent = element.parentNode.textContent;
- } else {
- // Keep IE8 happy.
- var textContent = element.parentNode.innerText;
- }
- if (HTMLCS.isStringEmpty(textContent) === true) {
- linkOnlyChild = true;
+ if (element.parentNode.textContent !== undefined) {
+ var textContent = element.parentNode.textContent;
+ } else {
+ // Keep IE8 happy.
+ var textContent = element.parentNode.innerText;
+ }
+
+ if (HTMLCS.isStringEmpty(textContent) === true) {
+ linkOnlyChild = true;
+ }
}
+ }//end if
+
+ if (element.hasAttribute('alt') === false) {
+ missingAlt = true;
+ } else if (!element.getAttribute('alt') || HTMLCS.isStringEmpty(element.getAttribute('alt')) === true) {
+ nullAlt = true;
}
- }//end if
- if (element.hasAttribute('alt') === false) {
- missingAlt = true;
- } else if (!element.getAttribute('alt') || HTMLCS.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.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;
- // 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).
- HTMLCS.addMessage(HTMLCS.ERROR, element.parentNode, 'Img element is the only content of the link, but is missing alt text. The alt text should describe the purpose of the link.', 'H30.2');
- } else if (missingAlt === true) {
- HTMLCS.addMessage(HTMLCS.ERROR, element, 'Img element missing an alt attribute. Use the alt attribute to specify a short text alternative.', 'H37');
- } else if (nullAlt === true) {
- if ((element.hasAttribute('title') === true) && (HTMLCS.isStringEmpty(element.getAttribute('title')) === false)) {
- // Title attribute present and not empty. This is wrong when
- // an image is marked as ignored.
- HTMLCS.addMessage(HTMLCS.ERROR, element, 'Img element with empty alt text must have absent or empty title attribute.', 'H67.1');
+ case 'input':
+ // Image submit buttons.
+ if ((missingAlt === true) || (nullAlt === true)) {
+ errors.inputImage.missingAlt.push(element);
} else {
- HTMLCS.addMessage(HTMLCS.WARNING, element, 'Img element is marked so that it is ignored by Assistive Technology.', 'H67.2');
+ errors.inputImage.generalAlt.push(element);
}
- } else {
- HTMLCS.addMessage(HTMLCS.NOTICE, element, 'Ensure that the img element\'s alt text serves the same purpose and presents the same information as the image.', 'G94.Image');
- }
- break;
+ break;
- case 'input':
- // Image submit buttons.
- if ((missingAlt === true) || (nullAlt === true)) {
- HTMLCS.addMessage(HTMLCS.ERROR, element, 'Image submit button missing an alt attribute. Specify a text alternative that describes the button\'s function, using the alt attribute.', 'H36');
- } else {
- HTMLCS.addMessage(HTMLCS.NOTICE, element, 'Ensure that the image submit button\'s alt text identifies the purpose of the button.', 'G94.Button');
- }
- 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;
- case 'area':
- // Area tags in a client-side image map.
- if ((missingAlt === true) || (nullAlt === true)) {
- HTMLCS.addMessage(HTMLCS.ERROR, element, '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.', 'H24');
- } else {
- HTMLCS.addMessage(HTMLCS.NOTICE, element, 'Ensure that the area element\'s text alternative serves the same purpose as the part of image map image it references.', 'H24.2');
- }
- break;
+ default:
+ // No other tags defined.
+ break;
+ }//end switch
+ }//end for
- default:
- // No other tags defined.
- break;
- }//end switch
+ return errors;
},
/**
@@ -279,73 +335,103 @@ var HTMLCS_WCAG2AAA_Sniffs_Principle1_Guideline1_1_1_1_1 = {
},
/**
- * Test the inclusion of a text alternative on OBJECT tags (technique H53).
+ * Driver function for the media alternative (object/applet) tests.
*
- * OBJECT tags can be nested inside themselves to provide lesser-functioning
- * alternatives to the primary (outermost) tag, but a text alternative must be
- * provided inside. Alt text from an image is sufficient.
+ * This takes the generic result given by the media alternative testing function,
+ * and converts them into WCAG 2.0-specific messages.
*
* @param {DOMNode} element The element to test.
- *
- * @returns void
*/
- testObjectTextAlternative: function(element)
+ addMediaAlternativesResults: function(top)
{
- // Test firstly for whether we have an object alternative.
- 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 === '') {
- HTMLCS.addMessage(HTMLCS.ERROR, element, 'Object elements must contain a text alternative after all other alternatives are exhausted.', 'H53');
- } else {
- HTMLCS.addMessage(HTMLCS.NOTICE, element, '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.', 'G94,G92.Object');
- }
- }//end if
+ var errors = this.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.', 'H53');
+ }
+
+ 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.', 'G94,G92.Object');
+ }
+
+ 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.', 'H35.3');
+ }
+
+ 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.', 'H35.2');
+ }
+
+ 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.', 'G94,G92.Applet');
+ }
},
- /**
- * Test the inclusion of a text alternative on APPLET tags (technique H35).
- *
- * These might still be used in HTML 4.01 and XHTML 1.0 Transitional DTDs. Both
- * alt text and body text alternative are required: Oracle's docs state that
- * "alt" is for those that understand APPLET but not Java; the body text for
- * those that don't understand APPLET. WCAG 2.0 suggests support for either alt
- * method is inconsistent and therefore to use both.
- *
- * @param {DOMNode} element The element to test.
- *
- * @returns void
- */
- testAppletTextAlternative: function(element)
+ testMediaTextAlternatives: function(top)
{
- // 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.isStringEmpty(textAlt) === true) {
- HTMLCS.addMessage(HTMLCS.ERROR, element, 'Applet elements must contain a text alternative in the element\'s body, for browsers without support for the applet element.', 'H35.3');
- hasError = true;
+ var errors = {
+ object: {
+ missingBody: [],
+ generalAlt: []
+ },
+ applet: {
+ missingBody: [],
+ missingAlt: [],
+ generalAlt: []
}
+ };
+
+ var elements = top.querySelectorAll('object');
+
+ for (var el = 0; el < elements.length; el++) {
+ var element = elements[el];
+ var nodeName = element.nodeName.toLowerCase();
+
+ 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 altAttr = element.getAttribute('alt') || '';
- if (HTMLCS.isStringEmpty(altAttr) === true) {
- HTMLCS.addMessage(HTMLCS.ERROR, element, 'Applet elements must contain an alt attribute, to provide a text alternative to browsers supporting the element but are unable to load the applet.', 'H35.2');
- hasError = true;
- }
+ var elements = top.querySelectorAll('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.isStringEmpty(textAlt) === true) {
+ errors.applet.missingBody.push(element);
+ hasError = true;
+ }
+ }//end if
- if (hasError === false) {
- // No error? Remind of obligations about equivalence of alternatives.
- HTMLCS.addMessage(HTMLCS.NOTICE, element, '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.', 'G94,G92.Applet');
- }
+ var altAttr = element.getAttribute('alt') || '';
+ if (HTMLCS.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;
},
/**
181 Standards/WCAG2AAA/Sniffs/Principle1/Guideline1_3/1_3_1.js
View
@@ -169,7 +169,11 @@ var HTMLCS_WCAG2AAA_Sniffs_Principle1_Guideline1_3_1_3_1 = {
var nodeName = element.nodeName.toLowerCase();
var inputType = nodeName;
if (inputType === 'input') {
- inputType = element.getAttribute('type');
+ if (element.hasAttribute('type') === true) {
+ inputType = element.getAttribute('type');
+ } else {
+ inputType = 'text';
+ }
}
var isNoLabelControl = false;
@@ -340,7 +344,7 @@ var HTMLCS_WCAG2AAA_Sniffs_Principle1_Guideline1_3_1_3_1 = {
*/
testTableHeaders: function(table)
{
- var headersAttr = this._testTableHeadersAttrs(table);
+ var headersAttr = HTMLCS.util.testTableHeaders(table);
var scopeAttr = this._testTableScopeAttrs(table);
// Invalid scope attribute - emit always if scope tested.
@@ -497,179 +501,6 @@ var HTMLCS_WCAG2AAA_Sniffs_Principle1_Guideline1_3_1_3_1 = {
},
/**
- * Test for the correct headers attributes on table cell elements.
- *
- * Return value contains the following elements:
- * - required (Boolean): Whether header association at all is required.
- * - used (Boolean): Whether headers attribute has been used on at least
- * one table data (td) cell.
- * - allowScope (Boolean): Whether scope is allowed to satisfy the association
- * requirement (ie. max one row/one column).
- * - correct (Boolean): Whether headers have been correctly used.
- * - missingThId (Array): Array of th elements without IDs.
- * - missingTd (Array): Array of elements without headers attribute.
- * - wrongHeaders (Array): Array of elements where headers attr is incorrect.
- * Each is a structure with following keys: element,
- * expected [headers attr], actual [headers attr].
- *
- * @param {DOMNode} element Table element to test upon.
- *
- * @return {Object} The above return value structure.
- */
- _testTableHeadersAttrs: function(element)
- {
- var retval = {
- required: true,
- used: false,
- correct: true,
- allowScope: true,
- missingThId: [],
- missingTd: [],
- wrongHeaders: []
- }
-
- var rows = element.getElementsByTagName('tr');
- var tdCells = {};
- var skipCells = [];
-
- // Header IDs already used.
- var headerIds = {
- rows: [],
- cols: []
- };
- var multiHeaders = {
- rows: 0,
- cols: 0
- }
- var missingIds = false;
-
- for (var rownum = 0; rownum < rows.length; rownum++) {
- var row = rows[rownum];
- var colnum = 0;
-
- for (var item = 0; item < row.childNodes.length; item++) {
- var cell = row.childNodes[item];
- if (cell.nodeType === 1) {
- // Skip columns that are skipped due to rowspan.
- if (skipCells[rownum]) {
- while (skipCells[rownum][0] === colnum) {
- skipCells[rownum].shift();
- colnum++;
- }
- }
-
- var nodeName = cell.nodeName.toLowerCase();
- var rowspan = Number(cell.getAttribute('rowspan')) || 1;
- var colspan = Number(cell.getAttribute('colspan')) || 1;
-
- // If rowspanned, mark columns as skippable in the following
- // row(s).
- if (rowspan > 1) {
- for (var i = rownum + 1; i < rownum + rowspan; i++) {
- if (!skipCells[i]) {
- skipCells[i] = [];
- }
-
- for (var j = colnum; j < colnum + colspan; j++) {
- skipCells[i].push(j);
- }
- }
- }
-
- if (nodeName === 'th') {
- var id = (cell.getAttribute('id') || '');
-
- // Save the fact that we have a missing ID on the header.
- if (id === '') {
- retval.correct = false;
- retval.missingThId.push(cell);
- }
-
- if ((rowspan > 1) && (colspan > 1)) {
- // Multi-column AND multi-row header. Abandon all hope,
- // As it must span across more than one row+column
- retval.allowScope = false;
- } else if (retval.allowScope === true) {
- // If we haven't had a th in this column (row) yet,
- // record it. if we find another th in this column (row),
- // record that has multi-ths. If we already have a column
- // (row) with multi-ths, we cannot use scope.
- if (headerIds.cols[colnum] === undefined) {
- headerIds.cols[colnum] = 0;
- }
-
- if (headerIds.rows[rownum] === undefined) {
- headerIds.rows[rownum] = 0;
- }
-
- headerIds.rows[rownum] += colspan;
- headerIds.cols[colnum] += rowspan;
- }//end if
- } else if ((nodeName === 'td')) {
- if ((cell.hasAttribute('headers') === true) && (/^\s*$/.test(cell.getAttribute('headers')) === false)) {
- retval.used = true;
- }
- }//end if
-
- colnum += colspan;
- }//end if
- }//end for
- }//end for
-
- for (var i = 0; i < headerIds.rows.length; i++) {
- if (headerIds.rows[i] > 1) {
- multiHeaders.rows++;
- }
- }
-
- for (var i = 0; i < headerIds.cols.length; i++) {
- if (headerIds.cols[i] > 1) {
- multiHeaders.cols++;
- }
- }
-
- if ((multiHeaders.rows > 1) || (multiHeaders.cols > 1)) {
- retval.allowScope = false;
- } else if ((retval.allowScope === true) && ((multiHeaders.rows === 0) || (multiHeaders.cols === 0))) {
- // If only one column OR one row header.
- retval.required = false;
- }//end if
-
- // Calculate expected heading IDs. If they are not there or incorrect, flag
- // them.
- var cells = HTMLCS.util.getCellHeaders(element);
- for (var i = 0; i < cells.length; i++) {
- var cell = cells[i].cell;
- var expected = cells[i].headers;
-
- if (cell.hasAttribute('headers') === false) {
- retval.correct = false;
- retval.missingTd.push(cell);
- } else {
- var actual = (cell.getAttribute('headers') || '').split(/\s+/);
- if (actual.length === 0) {
- retval.correct = false;
- retval.missingTd.push(cell);
- } else {
- actual = ' ' + actual.sort().join(' ') + ' ';
- actual = actual.replace(/\s+/g, ' ').replace(/(\w+\s)\1+/g, '$1').replace(/^\s*(.*?)\s*$/g, '$1');
- if (expected !== actual) {
- retval.correct = false;
- var val = {
- element: cell,
- expected: expected,
- actual: (cell.getAttribute('headers') || '')
- }
- retval.wrongHeaders.push(val);
- }
- }//end if
- }//end if
- }//end for
-
- return retval;
- },
-
- /**
* Test table captions and summaries (techniques H39, H73).
*
* @param {DOMNode} table Table element to test upon.
430 Standards/WCAG2AAA/Sniffs/Principle4/Guideline4_1/4_1_2.js
View
@@ -22,14 +22,7 @@ var HTMLCS_WCAG2AAA_Sniffs_Principle4_Guideline4_1_4_1_2 = {
*/
register: function()
{
- return [
- 'a',
- 'button',
- 'fieldset',
- 'input',
- 'select',
- 'textarea'
- ];
+ return ['_top'];
},
@@ -41,75 +34,123 @@ var HTMLCS_WCAG2AAA_Sniffs_Principle4_Guideline4_1_4_1_2 = {
*/
process: function(element, top)
{
- if (element.nodeName.toLowerCase() === 'a') {
- this._processLinks(element);
- } else {
- this._processFormControls(element, top);
- }
+ if (element === top) {
+ var errors = this.processFormControls(top);
+ for (var i = 0; i < errors.length; i++) {
+ HTMLCS.addMessage(HTMLCS.ERROR, errors[i].element, errors[i].msg, 'H91.' + errors[i].subcode);
+ }
+
+ this.addProcessLinksMessages(top);
+ }//end if
},
- _processLinks: function(element)
+ addProcessLinksMessages: function(top)
{
- // Name is title attr or content
- // Value is href
+ var errors = this.processLinks(top);
+ for (var i = 0; i < errors.empty.length; i++) {
+ HTMLCS.addMessage(HTMLCS.WARNING, errors.empty[i], 'Anchor element found with an ID but without a href or link text. Consider moving its ID to a parent or nearby element.', 'H91.A.Empty');
+ }
- var nameFound = false;
- var hrefFound = false;
- var content = HTMLCS.util.getElementTextContent(element);
+ for (var i = 0; i < errors.emptyWithName.length; i++) {
+ HTMLCS.addMessage(HTMLCS.WARNING, errors.emptyWithName[i], 'Anchor element found with a name attribute but without a href or link text. Consider moving the name attribute to become an ID of a parent or nearby element.', 'H91.A.EmptyWithName');
+ }
- if ((element.hasAttribute('title') === true) && (/^\s*$/.test(element.getAttribute('title')) === false)) {
- nameFound = true;
- } else if (/^\s*$/.test(content) === false) {
- nameFound = true;
+ for (var i = 0; i < errors.emptyNoId.length; i++) {
+ HTMLCS.addMessage(HTMLCS.ERROR, errors.emptyNoId[i], 'Anchor element found with no link content and no name and/or ID attribute.', 'H91.A.EmptyNoId');
}
- if ((element.hasAttribute('href') === true) && (/^\s*$/.test(element.getAttribute('href')) === false)) {
- hrefFound = true;
+ for (var i = 0; i < errors.noHref.length; i++) {
+ HTMLCS.addMessage(HTMLCS.WARNING, errors.noHref[i], 'Anchor elements should not be used for defining in-page link targets. If not using the ID for other purposes (such as CSS or scripting), consider moving it to a parent element.', 'H91.A.NoHref');
}
- if (hrefFound === false) {
- // No href. We don't want these because, although they are commonly used
- // to create targets, they can be picked up by screen readers and
- // displayed to the user as empty links. A elements are defined by H91 as
- // having an (ARIA) role of "link", and using them as targets are
- // essentially misusing them. Place an ID on a parent element instead.
- if (/^\s*$/.test(content) === true) {
- // Also no content. (eg. <a id=""></a> or <a name=""></a>)
- if (element.hasAttribute('id') === true) {
- HTMLCS.addMessage(HTMLCS.WARNING, element, 'Anchor element found with an ID but without a href or link text. Consider moving its ID to a parent or nearby element.', 'H91.A.Empty');
- } else if (element.hasAttribute('name') === true) {
- HTMLCS.addMessage(HTMLCS.WARNING, element, 'Anchor element found with a name attribute but without a href or link text. Consider moving the name attribute to become an ID of a parent or nearby element.', 'H91.A.EmptyWithName');
- } else {
- HTMLCS.addMessage(HTMLCS.ERROR, element, 'Anchor element found with no link content and no name and/or ID attribute.', 'H91.A.EmptyNoId');
- }
- } else {
- // Giving a benefit of the doubt here - if a link has text and also
- // an ID, but no href, it might be because it is being manipulated by
- // a script.
- if (element.hasAttribute('id') === true) {
- HTMLCS.addMessage(HTMLCS.WARNING, element, 'Anchor elements should not be used for defining in-page link targets. If not using the ID for other purposes (such as CSS or scripting), consider moving it to a parent element.', 'H91.A.NoHref');
- } else {
- // HTML5 allows A elements with text but no href, "for where a
- // link might otherwise have been placed, if it had been relevant".
- // Hence, thrown as a warning, not an error.
- HTMLCS.addMessage(HTMLCS.WARNING, element, 'Anchor element found with link content, but no href and/or ID attribute has been supplied.', 'H91.A.Placeholder');
- }
+ for (var i = 0; i < errors.placeholder.length; i++) {
+ HTMLCS.addMessage(HTMLCS.WARNING, errors.placeholder[i], 'Anchor element found with link content, but no href, ID or name attribute has been supplied.', 'H91.A.Placeholder');
+ }
+
+ for (var i = 0; i < errors.noContent.length; i++) {
+ HTMLCS.addMessage(HTMLCS.ERROR, errors.noContent[i], 'Anchor element found with a valid href attribute, but no link content has been supplied.', 'H91.A.NoContent');
+ }
+ },
+
+ processLinks: function(top)
+ {
+ var errors = {
+ empty: [],
+ emptyWithName: [],
+ emptyNoId: [],
+ noHref: [],
+ placeholder: [],
+ noContent: []
+ };
+
+ var elements = top.querySelectorAll('a');
+
+ for (var el = 0; el < elements.length; el++) {
+ var element = elements[el];
+
+ var nameFound = false;
+ var hrefFound = false;
+ var content = HTMLCS.util.getElementTextContent(element);
+
+ if ((element.hasAttribute('title') === true) && (/^\s*$/.test(element.getAttribute('title')) === false)) {
+ nameFound = true;
+ } else if (/^\s*$/.test(content) === false) {
+ nameFound = true;
}
- } else {
- if (/^\s*$/.test(content) === true) {
- // Href provided, but no content.
- // We only fire this message when there are no images in the content.
- // A link around an image with no alt text is already covered in SC
- // 1.1.1 (test H30).
- if (element.querySelectorAll('img').length === 0) {
- HTMLCS.addMessage(HTMLCS.ERROR, element, 'Anchor element found with a valid href attribute, but no link content has been supplied.', 'H91.A.NoContent');
- }
+
+ if ((element.hasAttribute('href') === true) && (/^\s*$/.test(element.getAttribute('href')) === false)) {
+ hrefFound = true;
}
- }
+
+ if (hrefFound === false) {
+ // No href. We don't want these because, although they are commonly used
+ // to create targets, they can be picked up by screen readers and
+ // displayed to the user as empty links. A elements are defined by H91 as
+ // having an (ARIA) role of "link", and using them as targets are
+ // essentially misusing them. Place an ID on a parent element instead.
+ if (/^\s*$/.test(content) === true) {
+ // Also no content. (eg. <a id=""></a> or <a name=""></a>)
+ if (element.hasAttribute('id') === true) {
+ errors.empty.push(element);
+ } else if (element.hasAttribute('name') === true) {
+ errors.emptyWithName.push(element);
+ } else {
+ errors.emptyNoId.push(element);
+ }
+ } else {
+ // Giving a benefit of the doubt here - if a link has text and also
+ // an ID, but no href, it might be because it is being manipulated by
+ // a script.
+ if ((element.hasAttribute('id') === true) || (element.hasAttribute('name') === true)) {
+ errors.noHref.push(element);
+ } else {
+ // HTML5 allows A elements with text but no href, "for where a
+ // link might otherwise have been placed, if it had been relevant".
+ // Hence, thrown as a warning, not an error.
+ errors.placeholder.push(element);
+ }
+ }//end if
+ } else {
+ if (/^\s*$/.test(content) === true) {
+ // Href provided, but no content.
+ // We only fire this message when there are no images in the content.
+ // A link around an image with no alt text is already covered in SC
+ // 1.1.1 (test H30).
+ if (element.querySelectorAll('img').length === 0) {
+ errors.noContent.push(element);
+ }
+ }//end if
+ }//end if
+ }//end for
+
+ return errors;
},
- _processFormControls: function(element, top)
+ processFormControls: function(top)
{
+ var elements = top.querySelectorAll('button, fieldset, input, select, textarea');
+ var errors = [];
+
var requiredNames = {
button: ['@title', '_content'],
fieldset: ['legend'],
@@ -128,151 +169,166 @@ var HTMLCS_WCAG2AAA_Sniffs_Principle4_Guideline4_1_4_1_2 = {
select: 'option_selected'
};
- var nodeName = element.nodeName.toLowerCase();
- var msgSubCode = element.nodeName.substr(0, 1).toUpperCase() + element.nodeName.substr(1).toLowerCase();
- if (nodeName === 'input') {
- if (element.hasAttribute('type') === false) {
- // If no type attribute, default to text.
- nodeName += '_text';
- } else {
- nodeName += '_' + element.getAttribute('type').toLowerCase();
- }
+ for (var el = 0; el < elements.length; el++) {
+ var element = elements[el];
+ var nodeName = element.nodeName.toLowerCase();
+ var msgSubCode = element.nodeName.substr(0, 1).toUpperCase() + element.nodeName.substr(1).toLowerCase();
+ if (nodeName === 'input') {
+ if (element.hasAttribute('type') === false) {
+ // If no type attribute, default to text.
+ nodeName += '_text';
+ } else {
+ nodeName += '_' + element.getAttribute('type').toLowerCase();
+ }
- // Treat all input buttons as the same
- if ((nodeName === 'input_submit') || (nodeName === 'input_reset')) {
- nodeName = 'input_button';
- }
+ // Treat all input buttons as the same
+ if ((nodeName === 'input_submit') || (nodeName === 'input_reset')) {
+ nodeName = 'input_button';
+ }
- // Get a format like "InputText".
- var msgSubCode = 'Input' + nodeName.substr(6, 1).toUpperCase() + nodeName.substr(7).toLowerCase();
- }//end if
+ // Get a format like "InputText".
+ var msgSubCode = 'Input' + nodeName.substr(6, 1).toUpperCase() + nodeName.substr(7).toLowerCase();
+ }//end if
- var requiredName = requiredNames[nodeName];
- var requiredValue = requiredValues[nodeName];
-
- // Check all possible combinations of names to ensure that one exists.
- if (requiredName) {
- for (var i = 0; i < requiredNames[nodeName].length; i++) {
- var requiredName = requiredNames[nodeName][i];
- if (requiredName === '_content') {
- // Work with content.
- var content = HTMLCS.util.getElementTextContent(element);
- if (/^\s*$/.test(content) === false) {
- break;
- }
- } else if (requiredName === 'label') {
- // Label element.
- if ((element.hasAttribute('id')) && (/^\s*$/.test(element.getAttribute('id')) === false)) {
- if (/^\-?[A-Za-z][A-Za-z0-9\-_]*$/.test(element.getAttribute('id')) === true) {
- var label = top.querySelector('label[for=' + element.getAttribute('id') + ']');
- if (label !== null) {
- break;
- }
- } else {
- // Characters not suitable for querySelector. Use slower method.
- var labels = top.getElementsByTagName('label');
- var found = false;
- for (var x = 0; x < labels.length; x++) {
- if ((labels[x].hasAttribute('for') === true) && (labels[x].getAttribute('for') === element.getAttribute('id'))) {
- found = true;
+ var requiredName = requiredNames[nodeName];
+ var requiredValue = requiredValues[nodeName];
+
+ // Check all possible combinations of names to ensure that one exists.
+ if (requiredName) {
+ for (var i = 0; i < requiredNames[nodeName].length; i++) {
+ var requiredName = requiredNames[nodeName][i];
+ if (requiredName === '_content') {
+ // Work with content.
+ var content = HTMLCS.util.getElementTextContent(element);
+ if (/^\s*$/.test(content) === false) {
+ break;
+ }
+ } else if (requiredName === 'label') {
+ // Label element.
+ if ((element.hasAttribute('id')) && (/^\s*$/.test(element.getAttribute('id')) === false)) {
+ if (/^\-?[A-Za-z][A-Za-z0-9\-_]*$/.test(element.getAttribute('id')) === true) {
+ var label = top.querySelector('label[for=' + element.getAttribute('id') + ']');
+ if (label !== null) {
break;
}
- }//end for
+ } else {
+ // Characters not suitable for querySelector. Use slower method.
+ var labels = top.getElementsByTagName('label');
+ var found = false;
+ for (var x = 0; x < labels.length; x++) {
+ if ((labels[x].hasAttribute('for') === true) && (labels[x].getAttribute('for') === element.getAttribute('id'))) {
+ found = true;
+ break;
+ }
+ }//end for
- if (found === true) {
+ if (found === true) {
+ break;
+ }
+ }//end if
+ }//end if
+ } else if (requiredName.charAt(0) === '@') {
+ // Attribute.
+ requiredName = requiredName.substr(1, requiredName.length);
+ if ((element.hasAttribute(requiredName) === true) && (/^\s*$/.test(element.getAttribute(requiredName)) === false)) {
+ break;
+ }
+ } else {
+ // Sub-element contents.
+ var subEl = element.querySelector(requiredName);
+ if (subEl !== null) {
+ var content = HTMLCS.util.getElementTextContent(subEl);
+ if (/^\s*$/.test(content) === false) {
break;
}
- }//end if
+ }
}//end if
- } else if (requiredName.charAt(0) === '@') {
- // Attribute.
- requiredName = requiredName.substr(1, requiredName.length);
- if ((element.hasAttribute(requiredName) === true) && (/^\s*$/.test(element.getAttribute(requiredName)) === false)) {
- break;
+ }//end for
+
+ if (i === requiredNames[nodeName].length) {
+ var msgNodeType = nodeName + ' element';
+ if (nodeName.substr(0, 6) === 'input_') {
+ msgNodeType = nodeName.substr(6) + ' input element';
}
- } else {
- // Sub-element contents.
- var subEl = element.querySelector(requiredName);
- if (subEl !== null) {
- var content = HTMLCS.util.getElementTextContent(subEl);
- if (/^\s*$/.test(content) === false) {
- break;
+
+ var builtAttrs = requiredNames[nodeName].slice(0, requiredNames[nodeName].length);
+ for (var a = 0; a < builtAttrs.length; a++) {
+ if (builtAttrs[a] === '_content') {
+ builtAttrs[a] = 'element content';
+ } else if (builtAttrs[a].charAt(0) === '@') {
+ builtAttrs[a] = builtAttrs[a].substr(1) + ' attribute';
+ } else {
+ builtAttrs[a] = builtAttrs[a] + ' element';
}
}
- }//end if
- }//end for
- if (i === requiredNames[nodeName].length) {
- var msgNodeType = nodeName + ' element';
- if (nodeName.substr(0, 6) === 'input_') {
- msgNodeType = nodeName.substr(6) + ' input element';
+ var msg = 'This ' + msgNodeType + ' does not have a name available to an accessibility API. Valid names are: ' + builtAttrs.join(', ') + '.';
+ errors.push({
+ element: element,
+ msg: msg,
+ subcode: (msgSubCode + '.Name')
+ });
}
+ }//end if