-
Notifications
You must be signed in to change notification settings - Fork 17
add new title for asus router firmware #27
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
9433500
3a5bfcb
03bdbb3
eb5645d
f08ac75
6df9360
afa3b96
de51644
59fc967
64a755f
9fd0fcf
4622c54
f07ce14
4ab9889
2d57862
c0b6136
91adf76
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,266 @@ | ||
| // ==UserScript== | ||
| // @name asus router interface Accessibility Fixes | ||
| // @grant unsafeWindow | ||
| // @namespace http://axSgrease.derekriemer.org/ | ||
| // @description Improves the accessibility of the asus router management interface | ||
| // @author James Teh <jteh@mozilla.com>, derek riemer <git@derekriemer.com> | ||
| // @copyright 2019-2024 Mozilla Corporation, Derek Riemer | ||
| // @license Mozilla Public License version 2.0 | ||
| // @version 2024.1 | ||
| // @include http://asusrouter.com/* | ||
| // @include http://www.asusrouter.com/* | ||
| // ==/UserScript== | ||
|
|
||
| /*** Functions for common tweaks. ***/ | ||
|
|
||
| /** | ||
| * Adds text to the given live region, and clears it a second later so it's no | ||
| * longer perceivable. | ||
| * @param {string} regionid an id of a region. | ||
| */ | ||
| function announce(text, regionId) { | ||
| getLiveRegion(regionId) | ||
| .then((region) => { | ||
| region.innerText = text; | ||
| setTimeout(() => { | ||
| region.innerText = ''; | ||
| }, 1000); | ||
| }); | ||
| } | ||
|
|
||
| /** | ||
| * create or fetch a live region that can be used with announce(). Returns a promise with the region. | ||
| * @param {string} id the name of the new live region. This is an html id. | ||
| * @return {!Promise<HTMLElement>} a div that contains the live region. This can typically be ignored, this exists to aid in chaining creation of non-existant regions. | ||
| */ | ||
| function getLiveRegion(id) { | ||
| const updatePromise = new Promise((resolve, reject) => { | ||
| if (!id) { | ||
| reject('Need a valid id!'); | ||
| return; | ||
| } | ||
| const existingRegion = document.getElementById(id); | ||
| if (existingRegion) { | ||
| resolve(existingRegion); | ||
| return; | ||
| } | ||
| const region = document.createElement('div'); | ||
| region.id = id; | ||
| region.setAttribute('aria-live', 'polite'); | ||
| region.setAttribute('aria-atomic', 'true'); | ||
| region.style.position = 'absolute'; | ||
| region.style.width = '50px'; | ||
| region.style.height = '50px'; | ||
| region.style.opasity = 0; | ||
| document.body.appendChild(region); | ||
| // we need to delay a little to get the new region to actually read contents. | ||
| // A11y APIs probably don't considder the relevant changes, additions, until | ||
| //an annimation frame has passed. It may, in reality be more like 2-4 | ||
| // annimation frames, so delay 134 ms to be safe. | ||
| setTimeout(() => { | ||
| resolve(region); | ||
| }, 134); | ||
| }); | ||
| return updatePromise; | ||
| } | ||
|
|
||
| function makeHeading(el, level) { | ||
| el.setAttribute("role", "heading"); | ||
| el.setAttribute("aria-level", level); | ||
| } | ||
|
|
||
| function makeRegion(el, label) { | ||
| el.setAttribute("role", "region"); | ||
| el.setAttribute("aria-label", label); | ||
| } | ||
|
|
||
| function makeButton(el, label) { | ||
| el.setAttribute("role", "button"); | ||
| if (label) { | ||
| el.setAttribute("aria-label", label); | ||
| } | ||
| } | ||
|
|
||
| function setRole(el, role) { | ||
| el.setAttribute('role', role); | ||
| } | ||
|
|
||
| function makePresentational(el) { | ||
| el.setAttribute("role", "presentation"); | ||
| } | ||
|
|
||
| function setLabel(el, label) { | ||
| el.setAttribute("aria-label", label); | ||
| } | ||
|
|
||
| function makeHidden(el) { | ||
| el.setAttribute("aria-hidden", "true"); | ||
| } | ||
|
|
||
| function setExpanded(el, expanded) { | ||
| el.setAttribute("aria-expanded", expanded ? "true" : "false"); | ||
| } | ||
|
|
||
| var idCounter = 0; | ||
| // Get a node's id. If it doesn't have one, make and set one first. | ||
| function setAriaIdIfNecessary(elem) { | ||
| if (!elem.id) { | ||
| elem.setAttribute("id", "axsg-" + idCounter++); | ||
| } | ||
| return elem.id; | ||
| } | ||
|
|
||
| function makeElementOwn(parentElement, listOfNodes) { | ||
| ids = []; | ||
| for (let node of listOfNodes) { | ||
| ids.push(setAriaIdIfNecessary(node)); | ||
| } | ||
| parentElement.setAttribute("aria-owns", ids.join(" ")); | ||
| } | ||
|
|
||
| // Focus something even if it wasn't made focusable by the author. | ||
| function forceFocus(el) { | ||
| let focusable = el.hasAttribute("tabindex"); | ||
| if (focusable) { | ||
| el.focus(); | ||
| return; | ||
| } | ||
| el.setAttribute("tabindex", "-1"); | ||
| el.focus(); | ||
| } | ||
|
|
||
| /*** Code to apply the tweaks when appropriate. ***/ | ||
|
|
||
| function applyTweak(el, tweak) { | ||
| if (Array.isArray(tweak.tweak)) { | ||
| let [func, ...args] = tweak.tweak; | ||
| func(el, ...args); | ||
| } else { | ||
| tweak.tweak(el); | ||
| } | ||
| } | ||
|
|
||
| function applyTweaks(root, tweaks, checkRoot) { | ||
| for (let tweak of tweaks) { | ||
| for (let el of root.querySelectorAll(tweak.selector)) { | ||
| try { | ||
| applyTweak(el, tweak); | ||
| } catch (e) { | ||
| console.log("Exception while applying tweak for '" + tweak.selector + "': " + e); | ||
| } | ||
| } | ||
| if (checkRoot && root.matches(tweak.selector)) { | ||
| try { | ||
| applyTweak(root, tweak); | ||
| } catch (e) { | ||
| console.log("Exception while applying tweak for '" + tweak.selector + "': " + e); | ||
| } | ||
| } | ||
| } | ||
| } | ||
|
|
||
| let observer = new MutationObserver(function (mutations) { | ||
| for (let mutation of mutations) { | ||
| try { | ||
| if (mutation.type === "childList") { | ||
| for (let node of mutation.addedNodes) { | ||
| if (node.nodeType != Node.ELEMENT_NODE) { | ||
| continue; | ||
| } | ||
| applyTweaks(node, DYNAMIC_TWEAKS, true); | ||
| } | ||
| } else if (mutation.type === "attributes") { | ||
| applyTweaks(mutation.target, DYNAMIC_TWEAKS, true); | ||
| } | ||
| } catch (e) { | ||
| // Catch exceptions for individual mutations so other mutations are still handled. | ||
| console.log("Exception while handling mutation: " + e); | ||
| } | ||
| } | ||
| }); | ||
|
|
||
| function init() { | ||
| applyTweaks(document, LOAD_TWEAKS, false); | ||
| applyTweaks(document, DYNAMIC_TWEAKS, false); | ||
| options = { childList: true, subtree: true }; | ||
| if (DYNAMIC_TWEAK_ATTRIBS.length > 0) { | ||
| options.attributes = true; | ||
| options.attributeFilter = DYNAMIC_TWEAK_ATTRIBS; | ||
| } | ||
| observer.observe(document, options); | ||
| } | ||
|
|
||
| /*** Define the actual tweaks. ***/ | ||
|
|
||
| // Tweaks that only need to be applied on load. | ||
| const LOAD_TWEAKS = [ | ||
| { | ||
| selector: "#op_link", | ||
| tweak: el => { | ||
| const table = el.closest('table'); | ||
| setRole(table, 'banner'); | ||
| // Because I can, make the tbody a list, and each td a list item. | ||
| setRole(table.firstElementChild, 'list'); | ||
| for (let pres of table.firstElementChild.children) { | ||
| makePresentational(pres); | ||
| } | ||
| // td's become listitems | ||
| Array.from(table.querySelectorAll('td')).forEach((e) => setRole(e, e.innerText ? 'listitem' : 'none')); | ||
| }, | ||
| }, | ||
| ]; | ||
|
|
||
| // Attributes that should be watched for changes and cause dynamic tweaks to be | ||
| // applied. | ||
| const DYNAMIC_TWEAK_ATTRIBS = []; | ||
|
|
||
| // Tweaks that must be applied whenever an element is added/changed. | ||
| const DYNAMIC_TWEAKS = [ | ||
| { | ||
| selector: '.menu_Desc', | ||
| tweak: [setRole, 'link'], | ||
| }, | ||
| { | ||
| selector: '.menu_Split', | ||
| tweak: [makeHeading, 2], | ||
| }, | ||
| { | ||
| selector: '#mainMenu', | ||
| tweak: [makeRegion, 'main navigation'], | ||
| }, | ||
| { | ||
| selector: '#tabMenu', | ||
| tweak: [makeRegion, 'secondary navigation'], | ||
| }, | ||
| { | ||
| selector: '#tabMenu td', | ||
| tweak: [setRole, 'link'], | ||
| }, | ||
| { | ||
| selector: '.formfonttitle', | ||
| tweak: [makeHeading, 1], | ||
| }, | ||
| { | ||
| selector: 'img[src="/switcherplugin/iphone_switch_container_on.png"]', | ||
| tweak: e => { | ||
| e.alt = 'on'; | ||
| }, | ||
| }, | ||
| { | ||
| selector: "#overDiv_table1", | ||
| tweak: e => { | ||
| // on rare occasions, this is delayed while the table renders, so | ||
| // we wait a quarter second. Also kind of mimics a tutor help | ||
| // with most screen readers. | ||
| setTimeout(() => { | ||
| announce(e.innerText, 'tutor'); | ||
| }, 250); | ||
| }, | ||
| }, | ||
| ]; | ||
|
|
||
| /** Add your specific initialization here, so that if you ever update the framework from new skeleton your inits are not overridden. */ | ||
| function userInit() { } | ||
|
|
||
| /*** Lights, camera, action! ***/ | ||
| init(); |
| Original file line number | Diff line number | Diff line change | ||||
|---|---|---|---|---|---|---|
|
|
@@ -11,6 +11,57 @@ | |||||
|
|
||||||
| /*** Functions for common tweaks. ***/ | ||||||
|
|
||||||
| /** | ||||||
| * Adds text to the given live region, and clears it a second later so it's no | ||||||
| * longer perceivable. | ||||||
| * @param {string} regionid an id of a region. | ||||||
| */ | ||||||
| function announce(text, regionId) { | ||||||
| getLiveRegion(regionId) | ||||||
| .then((region) => { | ||||||
| region.innerText = text; | ||||||
| setTimeout(() => { | ||||||
| region.innerText = ''; | ||||||
| }, 1000); | ||||||
| }); | ||||||
| } | ||||||
|
|
||||||
| /** | ||||||
| * create or fetch a live region that can be used with announce(). Returns a promise with the region. | ||||||
| * @param {string} id the name of the new live region. This is an html id. | ||||||
| * @return {!Promise<HTMLElement>} a div that contains the live region. This can typically be ignored, this exists to aid in chaining creation of non-existant regions. | ||||||
| */ | ||||||
| function getLiveRegion(id) { | ||||||
| const updatePromise = new Promise((resolve, reject) => { | ||||||
| if (!id) { | ||||||
| reject('Need a valid id!'); | ||||||
| return; | ||||||
| } | ||||||
| const existingRegion = document.getElementById(id); | ||||||
| if (existingRegion) { | ||||||
| resolve(existingRegion); | ||||||
| return; | ||||||
| } | ||||||
| const region = document.createElement('div'); | ||||||
| region.id = id; | ||||||
| region.setAttribute('aria-live', 'polite'); | ||||||
| region.setAttribute('aria-atomic', 'true'); | ||||||
| region.style.position = 'absolute'; | ||||||
| region.style.width = '50px'; | ||||||
| region.style.height = '50px'; | ||||||
| region.style.opasity = 0; | ||||||
| document.body.appendChild(region); | ||||||
| // we need to delay a little to get the new region to actually read contents. | ||||||
| // A11y APIs probably don't treat the relevant changes as "additions" until | ||||||
| //an annimation frame has passed. It may, in reality be more like 2-4 | ||||||
| // annimation frames, so delay 134 ms to be safe. | ||||||
| setTimeout(() => { | ||||||
| resolve(region); | ||||||
| }, 134); | ||||||
|
Owner
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Just out of curiosity, why 134 (as opposed to 130 or similar)? Is 134 significant somehow?
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 1000/30 (frames per second) is 3.333... * 3 is 333 ...4 when rounded. Ballpark math tbh. |
||||||
| }); | ||||||
| return updatePromise; | ||||||
| } | ||||||
|
|
||||||
| function makeHeading(el, level) { | ||||||
| el.setAttribute("role", "heading"); | ||||||
| el.setAttribute("aria-level", level); | ||||||
|
|
@@ -53,9 +104,9 @@ function setAriaIdIfNecessary(elem) { | |||||
| return elem.id; | ||||||
| } | ||||||
|
|
||||||
| function makeElementOwn(parentElement, listOfNodes){ | ||||||
| function makeElementOwn(parentElement, listOfNodes) { | ||||||
derekriemer marked this conversation as resolved.
Show resolved
Hide resolved
|
||||||
| ids = []; | ||||||
| for(let node of listOfNodes){ | ||||||
| for (let node of listOfNodes) { | ||||||
| ids.push(setAriaIdIfNecessary(node)); | ||||||
| } | ||||||
| parentElement.setAttribute("aria-owns", ids.join(" ")); | ||||||
|
|
@@ -83,7 +134,7 @@ function applyTweak(el, tweak) { | |||||
| } | ||||||
| } | ||||||
|
|
||||||
| function applyTweaks(root, tweaks, checkRoot, forAttrChange=false) { | ||||||
| function applyTweaks(root, tweaks, checkRoot, forAttrChange = false) { | ||||||
| for (let tweak of tweaks) { | ||||||
| if (!forAttrChange || tweak.whenAttrChangedOnAncestor !== false) { | ||||||
| for (let el of root.querySelectorAll(tweak.selector)) { | ||||||
|
|
@@ -104,7 +155,7 @@ function applyTweaks(root, tweaks, checkRoot, forAttrChange=false) { | |||||
| } | ||||||
| } | ||||||
|
|
||||||
| let observer = new MutationObserver(function(mutations) { | ||||||
| let observer = new MutationObserver(function (mutations) { | ||||||
| for (let mutation of mutations) { | ||||||
| try { | ||||||
| if (mutation.type === "childList") { | ||||||
|
|
@@ -127,7 +178,7 @@ let observer = new MutationObserver(function(mutations) { | |||||
| function init() { | ||||||
| applyTweaks(document, LOAD_TWEAKS, false); | ||||||
| applyTweaks(document, DYNAMIC_TWEAKS, false); | ||||||
| options = {childList: true, subtree: true}; | ||||||
| options = { childList: true, subtree: true }; | ||||||
| if (DYNAMIC_TWEAK_ATTRIBS.length > 0) { | ||||||
| options.attributes = true; | ||||||
| options.attributeFilter = DYNAMIC_TWEAK_ATTRIBS; | ||||||
|
|
@@ -149,5 +200,9 @@ const DYNAMIC_TWEAK_ATTRIBS = []; | |||||
| const DYNAMIC_TWEAKS = [ | ||||||
| ]; | ||||||
|
|
||||||
| /** add your specific initialization here, so that if you ever update the framework from new skeleton your inits are not overridden. */ | ||||||
|
Owner
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
|
||||||
| function userInit(){} | ||||||
|
|
||||||
| /*** Lights, camera, action! ***/ | ||||||
| init(); | ||||||
| userInit(); | ||||||
Uh oh!
There was an error while loading. Please reload this page.