A11y

Test for assessibility problems

当前为 2018-06-08 提交的版本,查看 最新版本

  1. // ==UserScript==
  2. // @name A11y
  3. // @namespace assessibility.colivre.org
  4. // @version 0.1
  5. // @description Test for assessibility problems
  6. // @author Aurélio A. Heckert
  7. // @match *://*/*
  8. // @grant none
  9. // ==/UserScript==
  10.  
  11. (function() {
  12. 'use strict';
  13.  
  14. // Test if element has acessibility problems
  15. // If there is a problem, call `notify(el, <ERROR|WARN>, '<description string>')`
  16. function testA11yEl(el) {
  17.  
  18. if (el.tagName == 'IMG' && empty(el.alt) && empty(el.title))
  19. notify(el, ERROR, 'Image without alt text.', 'https://www.w3.org/WAI/tutorials/images/');
  20.  
  21. if (el.tagName.match(/^H[0-9]$/) && empty(el.innerText))
  22. notify(el, ERROR, `Heading ${el.tagName} without text.`);
  23.  
  24. if (el.tagName.match(/^H[0-9]$/) &&
  25. el.nextSibling.tagName && // The next tag are united to this.
  26. el.nextSibling.tagName.match(/^H[0-9]$/))
  27. notify(el, WARN, `Skipping heading ${el.tagName}.`, 'https://www.w3.org/WAI/tutorials/page-structure/headings#heading-ranks');
  28.  
  29. if (el.tagName.match(/^H[0-9]$/) &&
  30. el.nextSibling.constructor == Text && el.nextSibling.textContent.match(/^\s*$/) && // has no text between, only space.
  31. el.nextElementSibling.tagName.match(/^H[0-9]$/))
  32. notify(el, WARN, `Skipping heading ${el.tagName}.`, 'https://www.w3.org/WAI/tutorials/page-structure/headings#heading-ranks');
  33.  
  34. }
  35.  
  36. var elements = [];
  37. var a11yBox = null, a11yList = null;
  38. const ERROR = 'ERROR';
  39. const WARN = 'WARN';
  40. const BOXID = 'a11y-userscript-box';
  41.  
  42. function empty(val) {
  43. if (typeof(val) == 'string') val = val.replace(/\s/g, '');
  44. return typeof(val) == 'undefined' || val == null || val.length == 0
  45. }
  46.  
  47. function mk(tag, attrs) {
  48. tag = document.createElement(tag);
  49. for (var attName in attrs) {
  50. var attVal = attrs[attName];
  51. if (attName == 'children') attVal.forEach(([t,a])=> {
  52. mk(t, Object.assign(a, {parent: tag}));
  53. });
  54. else if (attName == 'parent') attVal.appendChild(tag);
  55. else if (attName == 'text') tag.innerText = attVal;
  56. else if (attName == 'html') tag.innerHTML = attVal;
  57. else if (attName.match(/^on/)) tag[attName] = attVal;
  58. else tag.setAttribute(attName, attVal);
  59. }
  60. return tag;
  61. }
  62.  
  63. function buildA11yBox() {
  64. a11yBox = mk('div', {
  65. parent: document.body,
  66. id: BOXID,
  67. class: BOXID+'-toggle-min',
  68. children: [
  69. ['i',{ id: BOXID+'-toggle-min-bt', onclick: toggleMin }]
  70. ]
  71. });
  72. a11yList = mk('ul', {parent: a11yBox});
  73. mk('style', {
  74. parent: document.documentElement.firstElementChild,
  75. html: `
  76. #${BOXID} {position: fixed; bottom: 0; right: 0; background: rgba(255,255,255,0.7); z-index: 99999; opacity: 1;
  77. box-shadow: 0 0 30px rgba(0,0,0,0.6); padding: 15px; border-radius: 15px 0 0 0; color: #000}
  78. #${BOXID}-toggle-min-bt::before {content: "\\00d7"; position: absolute; top: 2px; left: 2px;
  79. display: block; text-align: center; border: 1px solid #CCC; border-radius: 30px;
  80. font-size: 24px; line-height: 24px; width: 24px; background: #EEE; cursor: pointer}
  81. #${BOXID} ul {margin: 0; padding: 0; border: 1px solid rgba(0,0,0,0.2);
  82. border-top:none; max-height: 90vh; overflow: auto}
  83. #${BOXID} li {margin: 0; padding: 6px 10px; border-top: 1px solid rgba(0,0,0,0.1); list-style: none; cursor: pointer}
  84. .${BOXID}-toggle-min {opacity: 40%}
  85. .${BOXID}-toggle-min ul {display: none}
  86. .${BOXID}-ERROR {background: rgba(255,200,200,0.8)}
  87. .${BOXID}-WARN {background: rgba(255,250,200,0.8)}
  88. #${BOXID} li span { font-size: 70%; padding-right: 1em }
  89. #${BOXID} li p { display: inline; margin: 0 }
  90. #${BOXID} li a { display: inline-block; margin: 0 0 0 3px; padding: 1px 2px; text-decoration: none;
  91. border: 1px solid rgba(0,0,0,0.2); border-radius: 3px; color: #000 }
  92. `
  93. });
  94. }
  95.  
  96. function toggleMin() {
  97. a11yBox.className = (a11yBox.className.length > 0)? '' : BOXID+'-toggle-min';
  98. }
  99.  
  100. function notify(el, type, message, helpURL) {
  101. if (!a11yBox) buildA11yBox();
  102. if (elements.filter((reg)=> reg.el==el && reg.message==message).length > 0) return;
  103. elements.push({ el, message });
  104. console.log(elements);
  105. var li = mk('li', {
  106. parent: a11yList,
  107. class: BOXID+'-'+type,
  108. onclick: ()=> {
  109. console.log(message, el);
  110. el.style.outline = '2px dashed red';
  111. },
  112. children: [
  113. ['span', {text: type}],
  114. ['p', {text: message}],
  115. ]
  116. });
  117. if (helpURL) mk('a', {
  118. href: helpURL,
  119. target: '_blank',
  120. text: 'help',
  121. parent: li
  122. });
  123. }
  124.  
  125. function walk(el) {
  126. if (el.id == BOXID) return;
  127. testA11yEl(el);
  128. for (var e,i=0; e=el.children[i]; i++) ((e)=> setTimeout(()=> walk(e), 1))(e);
  129. }
  130.  
  131. function testPage() {
  132. console.log('testing page...');
  133. walk(document.body);
  134. }
  135.  
  136. testPage();
  137. setTimeout(testPage, 5000);
  138. })();