A11y

Test for assessibility problems

目前為 2018-06-16 提交的版本,檢視 最新版本

  1. // ==UserScript==
  2. // @name A11y
  3. // @namespace assessibility.colivre.org
  4. // @version 0.2
  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, a11yList, pointer;
  38. const ERROR = 'ERROR';
  39. const WARN = 'WARN';
  40. const BOXID = 'a11y-userscript-box';
  41.  
  42. const $ = function(query) { return document.querySelector(query) };
  43. const $$ = function(query) { return document.querySelectorAll(query) };
  44.  
  45. function empty(val) {
  46. if (typeof(val) == 'string') val = val.replace(/\s/g, '');
  47. return typeof(val) == 'undefined' || val == null || val.length == 0
  48. }
  49.  
  50. function mk(tag, attrs) {
  51. tag = document.createElement(tag);
  52. for (var attName in attrs) {
  53. var attVal = attrs[attName];
  54. if (attName == 'children') attVal.forEach(([t,a])=> {
  55. mk(t, Object.assign(a, {parent: tag}));
  56. });
  57. else if (attName == 'parent') attVal.appendChild(tag);
  58. else if (attName == 'text') tag.innerText = attVal;
  59. else if (attName == 'html') tag.innerHTML = attVal;
  60. else if (attName.match(/^on/)) tag[attName] = attVal;
  61. else tag.setAttribute(attName, attVal);
  62. }
  63. return tag;
  64. }
  65.  
  66. function buildA11yBox() {
  67. a11yBox = mk('div', {
  68. parent: document.body,
  69. id: BOXID,
  70. class: BOXID+'-toggle-min',
  71. children: [
  72. ['i',{ id: BOXID+'-toggle-min-bt', onclick: toggleMin }]
  73. ]
  74. });
  75. a11yList = mk('ul', {parent: a11yBox});
  76. mk('style', {
  77. parent: document.documentElement.firstElementChild,
  78. html: `
  79. #${BOXID} {position: fixed; bottom: 0; right: 0; background: rgba(255,255,255,0.7); z-index: 99999; opacity: 1;
  80. box-shadow: 0 0 30px rgba(0,0,0,0.6); padding: 15px; border-radius: 15px 0 0 0; color: #000}
  81. #${BOXID}-toggle-min-bt::before {content: "\\00d7"; position: absolute; top: 2px; left: 2px;
  82. display: block; text-align: center; border: 1px solid #CCC; border-radius: 30px;
  83. font-size: 24px; line-height: 24px; width: 24px; background: #EEE; cursor: pointer}
  84. #${BOXID} ul {margin: 0; padding: 0; border: 1px solid rgba(0,0,0,0.2);
  85. border-top:none; max-height: 90vh; overflow: auto}
  86. #${BOXID} li {margin: 0; padding: 6px 10px; border-top: 1px solid rgba(0,0,0,0.1); list-style: none; cursor: pointer}
  87. .${BOXID}-toggle-min {opacity: 40%}
  88. .${BOXID}-toggle-min ul {display: none}
  89. .${BOXID}-ERROR {background: rgba(255,200,200,0.8)}
  90. .${BOXID}-WARN {background: rgba(255,250,200,0.8)}
  91. #${BOXID} li span { font-size: 70%; padding-right: 1em }
  92. #${BOXID} li p { display: inline; margin: 0 }
  93. #${BOXID} li a { display: inline-block; margin: 0 0 0 3px; padding: 1px 2px; text-decoration: none;
  94. border: 1px solid rgba(0,0,0,0.2); border-radius: 3px; color: #000 }
  95. #${BOXID}-pointer { position: absolute; border: 2px dashed #FFF; box-shadow: 0 0 110px 25px rgba(0,0,0,0.6) }
  96. #${BOXID}-pointer i { position: absolute; color: red; text-shadow: 1px 1px 1px #000, 0 0 8px #000;
  97. font-size: 90px; line-height: 90px; text-align: center }
  98. #${BOXID}-pointer-N { top: -90px; width: 100% }
  99. #${BOXID}-pointer-S { bottom: -90px; width: 100% }
  100. #${BOXID}-pointer-W { left: -90px }
  101. #${BOXID}-pointer-E { right: -90px }
  102. `
  103. });
  104. pointer = mk('div', {
  105. parent: document.body,
  106. id: BOXID+'-pointer',
  107. children: [
  108. ['i', {id: BOXID+'-pointer-N', text: '↓'}],
  109. ['i', {id: BOXID+'-pointer-S', text: '↑'}],
  110. ['i', {id: BOXID+'-pointer-W', text: '→'}],
  111. ['i', {id: BOXID+'-pointer-E', text: '←'}]
  112. ]
  113. });
  114. }
  115.  
  116. function toggleMin() {
  117. a11yBox.className = (a11yBox.className.length > 0)? '' : BOXID+'-toggle-min';
  118. }
  119.  
  120. function notificationClick(notification, el) {
  121. var rect = el.getBoundingClientRect();
  122. var x = parseInt(rect.left + window.pageXOffset) + 'px';
  123. var y = parseInt(rect.top + window.pageYOffset) + 'px';
  124. var w = parseInt(rect.width) + 'px';
  125. var h = parseInt(rect.height) + 'px';
  126. pointer.style.left = x;
  127. pointer.style.top = y;
  128. pointer.style.width = w;
  129. pointer.style.height = h;
  130. $('#'+BOXID+'-pointer-W').style.lineHeight = h;
  131. $('#'+BOXID+'-pointer-E').style.lineHeight = h;
  132. }
  133.  
  134. function notify(el, type, message, helpURL) {
  135. if (!a11yBox) buildA11yBox();
  136. if (elements.filter((reg)=> reg.el==el && reg.message==message).length > 0) return;
  137. elements.push({ el, message });
  138. console.log(elements);
  139. var li = mk('li', {
  140. parent: a11yList,
  141. class: BOXID+'-'+type,
  142. onclick: ()=> notificationClick(this, el),
  143. children: [
  144. ['span', {text: type}],
  145. ['p', {text: message}],
  146. ]
  147. });
  148. if (helpURL) mk('a', {
  149. href: helpURL,
  150. target: '_blank',
  151. text: 'help',
  152. parent: li
  153. });
  154. }
  155.  
  156. function walk(el) {
  157. if (el.id == BOXID) return;
  158. testA11yEl(el);
  159. for (var e,i=0; e=el.children[i]; i++) ((e)=> setTimeout(()=> walk(e), 1))(e);
  160. }
  161.  
  162. function testPage() {
  163. console.log('testing page...');
  164. walk(document.body);
  165. }
  166.  
  167. testPage();
  168. setTimeout(testPage, 5000);
  169. })();