Skip to content

Commit 4f0ae39

Browse files
committed
Initial import.
0 parents  commit 4f0ae39

File tree

4 files changed

+312
-0
lines changed

4 files changed

+312
-0
lines changed

GoogleDocsA11yFixes.user.js

Lines changed: 98 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,98 @@
1+
// ==UserScript==
2+
// @name Google Docs Accessibility Fixes
3+
// @namespace http://www.jantrid.net/gmScripts/
4+
// @description Improves the accessibility of Google Docs.
5+
// @author James Teh <jamie@jantrid.net>
6+
// @version 0.20111110.03
7+
// @include https://docs.google.com/*
8+
// ==/UserScript==
9+
10+
function onNodeInserted(evt) {
11+
var target = evt.target;
12+
if (target.nodeType != Node.ELEMENT_NODE)
13+
return;
14+
if (target.getAttribute("id") == "docs-aria-speakable") {
15+
// The Google Docs live region has aria-relevant set to additions,
16+
// but the changes are actually text.
17+
// Therefore, remove aria-relevant.
18+
target.removeAttribute("aria-relevant");
19+
}
20+
}
21+
22+
document.addEventListener("DOMNodeInserted", onNodeInserted, false);
23+
24+
var url = document.location.href;
25+
if (url.indexOf("/spreadsheet/ccc") != -1) {
26+
// In a spreadsheet, the input box always has focus, even when not editing.
27+
// This can cause screen readers to try to read text at the cursor when cursor keys are pressed.
28+
// Work around this by faking focus on a non-editable control when not editing.
29+
var inputBox = document.getElementsByTagName("textarea")[0];
30+
inputBox.setAttribute("id", "input-box");
31+
// Create our fake focus node.
32+
var gridFocus = document.createElement("div");
33+
gridFocus.setAttribute("id", "grid-focus");
34+
gridFocus.style.position = "-10000px";
35+
gridFocus.setAttribute("role", "grid");
36+
inputBox.parentNode.appendChild(gridFocus);
37+
38+
function setInputBoxFocus() {
39+
// aria-hidden is set to true when not editing.
40+
if (inputBox.getAttribute("aria-hidden") == "true")
41+
inputBox.setAttribute("aria-activedescendant", "grid-focus");
42+
else
43+
inputBox.setAttribute("aria-activedescendant", "input-box");
44+
}
45+
46+
function onInputBoxAttrModified(evt) {
47+
attrName = evt.attrName;
48+
if ((attrName == "aria-hidden" || attrName == "aria-activedescendant")) {
49+
// Editing has started or stopped.
50+
setInputBoxFocus();
51+
}
52+
}
53+
54+
setInputBoxFocus();
55+
inputBox.addEventListener("DOMAttrModified", onInputBoxAttrModified, false);
56+
57+
} else if (url.indexOf("/spreadsheet/gform?") != -1) {
58+
// Fix some buttons which have no content or role.
59+
const BUTTON_LABELS = {
60+
"ss-formwidget-edit-icon": "Edit",
61+
"ss-formwidget-duplicate-icon": "Duplicate",
62+
"ss-formwidget-delete-icon": "Delete",
63+
"ss-x-box": "Delete",
64+
}
65+
66+
function fixButton(node) {
67+
var classes = node.getAttribute("class");
68+
if (!classes)
69+
return;
70+
classes = classes.split(" ");
71+
for (var i = 0; i < classes.length; ++i) {
72+
var label;
73+
if (!(label = BUTTON_LABELS[classes[i]]))
74+
continue;
75+
node.setAttribute("role", "button");
76+
node.setAttribute("aria-label", label);
77+
}
78+
}
79+
80+
function onFormNodeInserted(evt) {
81+
var target = evt.target;
82+
if (target.nodeType != Node.ELEMENT_NODE)
83+
return;
84+
if (target.tagName != "DIV")
85+
return;
86+
var elements = target.getElementsByTagName("div");
87+
for (var i = 0; i < elements.length; ++i)
88+
fixButton(elements[i]);
89+
elements = target.getElementsByTagName("span");
90+
for (var i = 0; i < elements.length; ++i)
91+
fixButton(elements[i]);
92+
}
93+
94+
document.addEventListener("DOMNodeInserted", onFormNodeInserted, false);
95+
var elements = document.getElementsByTagName("div");
96+
for (var i = 0; i < elements.length; ++i)
97+
fixButton(elements[i]);
98+
}

KillWindowlessFlash.user.js

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
// ==UserScript==
2+
// @name Kill Windowless Flash
3+
// @namespace http://www.jantrid.net/gmScripts/
4+
// @description Makes windowless (transparent or opaque) Adobe Flash objects windowed so they have a chance of being accessible.
5+
// @author James Teh <jamie@jantrid.net>
6+
// @version 0.20111126.02
7+
// ==/UserScript==
8+
9+
function killWindowlessFlash() {
10+
// First, deal with embed elements.
11+
var elms = document.getElementsByTagName("embed");
12+
for (var i = 0; i < elms.length; ++i) {
13+
var elm = elms[i];
14+
if (elm.getAttribute("type") != "application/x-shockwave-flash")
15+
continue;
16+
if (elm.getAttribute("wmode") == "window")
17+
continue;
18+
elm.setAttribute("wmode", "window");
19+
// Parameters are only read when Flash is loaded,
20+
// so reinsert the element to reload it.
21+
elm.parentNode.replaceChild(elm, elm);
22+
}
23+
24+
// Now, deal with object elements.
25+
var elms = document.getElementsByTagName("object");
26+
for (var i = 0; i < elms.length; ++i) {
27+
var elm = elms[i];
28+
if (elm.getAttribute("type") != "application/x-shockwave-flash")
29+
continue;
30+
var params = elm.getElementsByTagName("param");
31+
for (var j = 0; j < params.length; ++j) {
32+
var param = params[j];
33+
if (param.getAttribute("name") != "wmode")
34+
continue;
35+
if (param.getAttribute("value") == "window")
36+
continue;
37+
param.setAttribute("value", "window");
38+
// Parameters are only read when Flash is loaded,
39+
// so reinsert the element to reload it.
40+
elm.parentNode.replaceChild(elm, elm);
41+
break;
42+
}
43+
}
44+
}
45+
46+
function onLoad(evt) {
47+
killWindowlessFlash();
48+
}
49+
50+
window.addEventListener("load", onLoad);

PennyTelA11yFixes.user.js

Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,66 @@
1+
// ==UserScript==
2+
// @name PennyTel Accessibility Fixes
3+
// @namespace http://www.jantrid.net/gmScripts/
4+
// @description Improves the accessibility of the PennyTel customer portal.
5+
// @author James Teh <jamie@jantrid.net>
6+
// @version 0.20110803.01
7+
// @include https://www.pennytel.com/*.jsp
8+
// @include https://www.pennytel.com/*.jsp?*
9+
// ==/UserScript==
10+
11+
const BUTTON_LABELS = {
12+
"btn_Edit.gif": "Edit",
13+
"btn_Add_2.gif": "Add",
14+
"btn_Delete_2.gif": "Delete",
15+
"btn_Save.gif": "Save",
16+
"btn_Cancel.gif": "Cancel",
17+
"btn_Login.gif": "Login",
18+
"btn_Settings.gif": "Settings",
19+
"btn_More.gif": "More",
20+
"btn_Update.gif": "Update",
21+
"btn_Listen.gif": "Listen",
22+
"btn_Mark_as_Read.gif": "Mark as read",
23+
"btn_Mark_as_Unread.gif": "Mark as unread",
24+
"btn_remove_2.gif": "Remove",
25+
"btn_Change.gif": "Change",
26+
};
27+
28+
// Make sidebar menu items into links.
29+
var elements = document.evaluate("//td[@class='tdSideMenu']",
30+
document, null, XPathResult.UNORDERED_NODE_SNAPSHOT_TYPE, null);
31+
for (var i = 0; i < elements.snapshotLength; ++i) {
32+
var element = elements.snapshotItem(i);
33+
element.setAttribute("role", "link");
34+
}
35+
36+
// Fix images.
37+
var elements = document.getElementsByTagName("img");
38+
for (var i = 0; i < elements.length; ++i) {
39+
var element = elements[i];
40+
var fileName = element.getAttribute("src").split("/");
41+
fileName = fileName[fileName.length - 1];
42+
var label;
43+
if (label = BUTTON_LABELS[fileName]) {
44+
// Button.
45+
element.setAttribute("role", "button");
46+
element.setAttribute("aria-label", label);
47+
} else if (/btn_Return_.*\.gif/.test(fileName)) {
48+
// Return button.
49+
element.setAttribute("role", "button");
50+
element.setAttribute("aria-label", "Continue");
51+
} else if (element.getAttribute("onclick") && element.getAttribute("alt") === "") {
52+
// This image has an onClick handler, but has @alt="".
53+
// This is incorrect and will cause screen readers not to render the image.
54+
element.removeAttribute("alt");
55+
} else if (fileName === "radio_check.gif") {
56+
// Checked disabled radio button.
57+
element.setAttribute("role", "radio");
58+
element.setAttribute("aria-disabled", "true");
59+
element.setAttribute("aria-checked", "true");
60+
} else if (fileName === "radio_uncheck.gif") {
61+
// Unchecked disabled radio button.
62+
element.setAttribute("role", "radio");
63+
element.setAttribute("aria-disabled", "true");
64+
element.setAttribute("aria-checked", "false");
65+
}
66+
}

TwitterA11yFixes.user.js

Lines changed: 98 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,98 @@
1+
// ==UserScript==
2+
// @name TwitterAccessibilityFixes
3+
// @namespace http://www.jantrid.net/gmScripts/
4+
// @description Improves the accessibility of Twitter.
5+
// @author James Teh <jamie@jantrid.net>
6+
// @version 0.20111210.03
7+
// @include https://twitter.com/*
8+
// @include http://twitter.com/*
9+
// ==/UserScript==
10+
11+
var lastFocusedTweet = null;
12+
13+
function onAttrModified(evt) {
14+
var attrName = evt.attrName;
15+
if (attrName != "class")
16+
return;
17+
var target = evt.target;
18+
var classes = target.getAttribute("class");
19+
if (!classes)
20+
return;
21+
if (classes.indexOf(" stream-item") != -1) {
22+
if (classes.indexOf("hovered-stream-item") != -1) {
23+
// This tweet just got focus.
24+
// Twitter doesn't use real focus for this, so screen readers don't know which tweet has focus.
25+
// Force real focus.
26+
lastFocusedTweet = target;
27+
if (!target.getAttribute("role")) {
28+
// Make the node focusable and accessible.
29+
target.setAttribute("tabindex", "-1");
30+
target.setAttribute("role", "listitem");
31+
target.setAttribute("aria-selected", "true");
32+
}
33+
var orig = target.getElementsByClassName("original-tweet")[0];
34+
target.setAttribute("aria-label", (
35+
orig.getElementsByClassName("account-group")[0].textContent
36+
+ orig.getElementsByClassName("js-tweet-text")[0].textContent
37+
+ orig.getElementsByClassName("context")[0].textContent
38+
+ orig.getElementsByClassName("tweet-stats-container")[0].textContent
39+
+ orig.getElementsByClassName("metadata")[0].textContent
40+
));
41+
target.setAttribute("aria-expanded",
42+
(classes.indexOf(" open ") == -1) ? "false" : "true");
43+
target.focus();
44+
} else
45+
lastFocusedTweet = null;
46+
}
47+
}
48+
49+
function onNodeRemoved(evt) {
50+
if (!lastFocusedTweet)
51+
return;
52+
var target = evt.target;
53+
if (target.nodeType != Node.ELEMENT_NODE)
54+
return;
55+
var classes = target.getAttribute("class");
56+
if (!classes)
57+
return;
58+
if (classes == "twttr-dialog-container") {
59+
// A dialog was just dismissed.
60+
// Focus the last focused tweet.
61+
lastFocusedTweet.focus();
62+
}
63+
}
64+
65+
var idCounter = 0;
66+
function onFocus(evt) {
67+
var target = evt.target;
68+
var tag = target.tagName;
69+
var classes = target.getAttribute("class");
70+
71+
if (tag == "INPUT" && classes == "twttr-hidden-input") {
72+
// This is an input field for a confirmation prompt.
73+
// Pressing enter here will activate the OK button.
74+
if (target.getAttribute("aria-activedescendant"))
75+
return;
76+
// Make the OK button accessible and fake focus on it,
77+
// which makes much more sense to the user.
78+
var elm = target.parentNode.getElementsByClassName("primary-btn")[0];
79+
var id = "ok" + ++idCounter;
80+
elm.setAttribute("id", id);
81+
elm.setAttribute("role", "button");
82+
target.setAttribute("aria-activedescendant", id);
83+
84+
} else if (tag == "TEXTAREA" && classes == "twitter-anywhere-tweet-box-editor") {
85+
// This is a tweet box.
86+
if (target.getAttribute("aria-describedby"))
87+
return;
88+
// Make the tweet counter the description of the tweet box for easy access.
89+
var elm = target.parentNode.parentNode.parentNode.getElementsByClassName("tweet-counter")[0];
90+
var id = "counter" + ++idCounter;
91+
elm.setAttribute("id", id);
92+
target.setAttribute("aria-describedby", id);
93+
}
94+
}
95+
96+
document.addEventListener("DOMAttrModified", onAttrModified, false);
97+
document.addEventListener("DOMNodeRemoved", onNodeRemoved, false);
98+
document.addEventListener("focus", onFocus, true);

0 commit comments

Comments
 (0)