Skip to content

Commit 6df9360

Browse files
author
derek riemer
committed
add asusRouterA11yFixes which got removed after rename
1 parent f08ac75 commit 6df9360

File tree

1 file changed

+267
-0
lines changed

1 file changed

+267
-0
lines changed

AsusRouterA11yFixes.user.js

Lines changed: 267 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,267 @@
1+
// ==UserScript==
2+
// @name asus router interface Accessibility Fixes
3+
// @grant unsafeWindow
4+
// @namespace http://axSgrease.derekriemer.org/
5+
// @description Improves the accessibility of the asus router management interface
6+
// @author James Teh <jteh@mozilla.com>, derek riemer <git@derekriemer.com>
7+
// @copyright 2019-2024 Mozilla Corporation, Derek Riemer
8+
// @license Mozilla Public License version 2.0
9+
// @version 2024.1
10+
// @include http://asusrouter.com/*
11+
// @include http://www.asusrouter.com/*
12+
// ==/UserScript==
13+
14+
/*** Functions for common tweaks. ***/
15+
16+
/**
17+
* Adds text to the given live region, and clears it a second later so its no
18+
* longer in the virtual buffer.
19+
* @param {string} regionid an id of a region.
20+
*/
21+
function announce(text, regionId) {
22+
getLiveRegion(regionId)
23+
.then((region) => {
24+
region.innerText = text;
25+
setTimeout(() => {
26+
region.innerText = '';
27+
}, 1000);
28+
});
29+
}
30+
31+
/**
32+
* create or fetch a live region that can be used with updateLiveRegion. Returns a promise with the region.
33+
* @param {string} id the name of the new live region. This is an html id.
34+
* @return {!Promise<HTMLElement>} a div that contains the live region. This can typically be ignored, this exxists to aid in chaining creation of non-existant regions.
35+
*/
36+
function getLiveRegion(id) {
37+
const updatePromise = new Promise((resolve, reject) => {
38+
if (!id) {
39+
reject('Need a valid id!');
40+
return;
41+
}
42+
const existingRegion = document.getElementById(id);
43+
if (existingRegion) {
44+
resolve(existingRegion);
45+
return;
46+
}
47+
const region = document.createElement('div');
48+
region.id = id;
49+
region.setAttribute('aria-live', 'polite');
50+
region.setAttribute('aria-atomic', 'true');
51+
region.style.position = 'absolute';
52+
region.style.width = '50px';
53+
region.style.height = '50px';
54+
region.style.opasity = 0;
55+
document.body.appendChild(region);
56+
// we need to delay a little to get the new region to actually read contents.
57+
// A11y api probably don't considder the relevant changes, additions, until
58+
//an annimation frame has passed. It may, in reality be more like 2-4
59+
// annimation frames, so delay 134 ms to be safe.
60+
setTimeout(() => {
61+
resolve(region);
62+
}, 134);
63+
});
64+
return updatePromise;
65+
}
66+
67+
function makeHeading(el, level) {
68+
el.setAttribute("role", "heading");
69+
el.setAttribute("aria-level", level);
70+
}
71+
72+
function makeRegion(el, label) {
73+
el.setAttribute("role", "region");
74+
el.setAttribute("aria-label", label);
75+
}
76+
77+
function makeButton(el, label) {
78+
el.setAttribute("role", "button");
79+
if (label) {
80+
el.setAttribute("aria-label", label);
81+
}
82+
}
83+
84+
function setRole(el, role) {
85+
el.setAttribute('role', role);
86+
}
87+
88+
function makePresentational(el) {
89+
el.setAttribute("role", "presentation");
90+
}
91+
92+
function setLabel(el, label) {
93+
el.setAttribute("aria-label", label);
94+
}
95+
96+
function makeHidden(el) {
97+
el.setAttribute("aria-hidden", "true");
98+
}
99+
100+
function setExpanded(el, expanded) {
101+
el.setAttribute("aria-expanded", expanded ? "true" : "false");
102+
}
103+
104+
var idCounter = 0;
105+
// Get a node's id. If it doesn't have one, make and set one first.
106+
function setAriaIdIfNecessary(elem) {
107+
if (!elem.id) {
108+
elem.setAttribute("id", "axsg-" + idCounter++);
109+
}
110+
return elem.id;
111+
}
112+
113+
function makeElementOwn(parentElement, listOfNodes) {
114+
ids = [];
115+
for (let node of listOfNodes) {
116+
ids.push(setAriaIdIfNecessary(node));
117+
}
118+
parentElement.setAttribute("aria-owns", ids.join(" "));
119+
}
120+
121+
// Focus something even if it wasn't made focusable by the author.
122+
function forceFocus(el) {
123+
let focusable = el.hasAttribute("tabindex");
124+
if (focusable) {
125+
el.focus();
126+
return;
127+
}
128+
el.setAttribute("tabindex", "-1");
129+
el.focus();
130+
}
131+
132+
/*** Code to apply the tweaks when appropriate. ***/
133+
134+
function applyTweak(el, tweak) {
135+
if (Array.isArray(tweak.tweak)) {
136+
let [func, ...args] = tweak.tweak;
137+
func(el, ...args);
138+
} else {
139+
tweak.tweak(el);
140+
}
141+
}
142+
143+
function applyTweaks(root, tweaks, checkRoot) {
144+
for (let tweak of tweaks) {
145+
for (let el of root.querySelectorAll(tweak.selector)) {
146+
try {
147+
applyTweak(el, tweak);
148+
} catch (e) {
149+
console.log("Exception while applying tweak for '" + tweak.selector + "': " + e);
150+
}
151+
}
152+
if (checkRoot && root.matches(tweak.selector)) {
153+
try {
154+
applyTweak(root, tweak);
155+
} catch (e) {
156+
console.log("Exception while applying tweak for '" + tweak.selector + "': " + e);
157+
}
158+
}
159+
}
160+
}
161+
162+
let observer = new MutationObserver(function (mutations) {
163+
for (let mutation of mutations) {
164+
try {
165+
if (mutation.type === "childList") {
166+
for (let node of mutation.addedNodes) {
167+
if (node.nodeType != Node.ELEMENT_NODE) {
168+
continue;
169+
}
170+
applyTweaks(node, DYNAMIC_TWEAKS, true);
171+
}
172+
} else if (mutation.type === "attributes") {
173+
applyTweaks(mutation.target, DYNAMIC_TWEAKS, true);
174+
}
175+
} catch (e) {
176+
// Catch exceptions for individual mutations so other mutations are still handled.
177+
console.log("Exception while handling mutation: " + e);
178+
}
179+
}
180+
});
181+
182+
/** add your specific initialization here, so that if you ever update the framework from new skeleton your inits are not overridden. */
183+
function userInit() { }
184+
185+
function init() {
186+
applyTweaks(document, LOAD_TWEAKS, false);
187+
applyTweaks(document, DYNAMIC_TWEAKS, false);
188+
options = { childList: true, subtree: true };
189+
if (DYNAMIC_TWEAK_ATTRIBS.length > 0) {
190+
options.attributes = true;
191+
options.attributeFilter = DYNAMIC_TWEAK_ATTRIBS;
192+
}
193+
observer.observe(document, options);
194+
}
195+
196+
/*** Define the actual tweaks. ***/
197+
198+
globals = [];
199+
// Tweaks that only need to be applied on load.
200+
const LOAD_TWEAKS = [
201+
{
202+
selector: "#op_link",
203+
tweak: el => {
204+
const table = el.closest('table');
205+
setRole(table, 'banner');
206+
// Because I can, make the tbody a list, and each td a list item.
207+
setRole(table.firstElementChild, 'list');
208+
for (let pres of table.firstElementChild.children) {
209+
makePresentational(pres);
210+
}
211+
// td's become listitems
212+
Array.from(table.querySelectorAll('td')).forEach((e) => setRole(e, e.innerText ? 'listitem' : 'none'));
213+
},
214+
},
215+
];
216+
217+
// Attributes that should be watched for changes and cause dynamic tweaks to be
218+
// applied.
219+
const DYNAMIC_TWEAK_ATTRIBS = [];
220+
221+
// Tweaks that must be applied whenever an element is added/changed.
222+
const DYNAMIC_TWEAKS = [
223+
{
224+
selector: '.menu_Desc',
225+
tweak: [setRole, 'link'],
226+
},
227+
{
228+
selector: '.menu_Split',
229+
tweak: [makeHeading, 2],
230+
},
231+
{
232+
selector: '#mainMenu',
233+
tweak: [makeRegion, 'main navigation'],
234+
},
235+
{
236+
selector: '#tabMenu',
237+
tweak: [makeRegion, 'secondary navigation'],
238+
},
239+
{
240+
selector: '#tabMenu td',
241+
tweak: [setRole, 'link'],
242+
},
243+
{
244+
selector: '.formfonttitle',
245+
tweak: [makeHeading, 1],
246+
},
247+
{
248+
selector: 'img[src="/switcherplugin/iphone_switch_container_on.png"]',
249+
tweak: e => {
250+
e.alt = 'on';
251+
},
252+
},
253+
{
254+
selector: "#overDiv_table1",
255+
tweak: e => {
256+
// on rare occasions, this is delayed while the table renders, so
257+
// we wait a quarter second. Also kind of mimics a tutor help
258+
// with most screen readers.
259+
setTimeout(() => {
260+
announce(e.innerText, 'tutor');
261+
}, 250);
262+
},
263+
},
264+
];
265+
266+
/*** Lights, camera, action! ***/
267+
init();

0 commit comments

Comments
 (0)