Skip to content

Commit f04edee

Browse files
committed
Add new scripts I've been hosting separately on Gist.
1 parent f7a4891 commit f04edee

8 files changed

+1247
-0
lines changed

AppleMusicA11yFixes.user.js

Lines changed: 169 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,169 @@
1+
// ==UserScript==
2+
// @name Apple Music Accessibility Fixes
3+
// @namespace http://axSgrease.nvaccess.org/
4+
// @description Improves the accessibility of Apple Music.
5+
// @author James Teh <jteh@mozilla.com>
6+
// @copyright 2019 Mozilla Corporation, Derek Riemer
7+
// @license Mozilla Public License version 2.0
8+
// @version 2019.1
9+
// @grant GM_log
10+
// @include https://beta.music.apple.com/*
11+
// ==/UserScript==
12+
13+
/*** Functions for common tweaks. ***/
14+
15+
function makeHeading(el, level) {
16+
el.setAttribute("role", "heading");
17+
el.setAttribute("aria-level", level);
18+
}
19+
20+
function makeRegion(el, label) {
21+
el.setAttribute("role", "region");
22+
el.setAttribute("aria-label", label);
23+
}
24+
25+
function makeButton(el, label) {
26+
el.setAttribute("role", "button");
27+
if (label) {
28+
el.setAttribute("aria-label", label);
29+
}
30+
}
31+
32+
function makePresentational(el) {
33+
el.setAttribute("role", "presentation");
34+
}
35+
36+
function setLabel(el, label) {
37+
el.setAttribute("aria-label", label);
38+
}
39+
40+
function makeHidden(el) {
41+
el.setAttribute("aria-hidden", "true");
42+
}
43+
44+
function setExpanded(el, expanded) {
45+
el.setAttribute("aria-expanded", expanded ? "true" : "false");
46+
}
47+
48+
var idCounter = 0;
49+
// Get a node's id. If it doesn't have one, make and set one first.
50+
function setAriaIdIfNecessary(elem) {
51+
if (!elem.id) {
52+
elem.setAttribute("id", "axsg-" + idCounter++);
53+
}
54+
return elem.id;
55+
}
56+
57+
function makeElementOwn(parentElement, listOfNodes){
58+
ids = [];
59+
for(let node of listOfNodes){
60+
ids.push(setAriaIdIfNecessary(node));
61+
}
62+
parentElement.setAttribute("aria-owns", ids.join(" "));
63+
}
64+
65+
// Focus something even if it wasn't made focusable by the author.
66+
function forceFocus(el) {
67+
let focusable = el.hasAttribute("tabindex");
68+
if (focusable) {
69+
el.focus();
70+
return;
71+
}
72+
el.setAttribute("tabindex", "-1");
73+
el.focus();
74+
}
75+
76+
/*** Code to apply the tweaks when appropriate. ***/
77+
78+
function applyTweak(el, tweak) {
79+
if (Array.isArray(tweak.tweak)) {
80+
let [func, ...args] = tweak.tweak;
81+
func(el, ...args);
82+
} else {
83+
tweak.tweak(el);
84+
}
85+
}
86+
87+
function applyTweaks(root, tweaks, checkRoot) {
88+
for (let tweak of tweaks) {
89+
for (let el of root.querySelectorAll(tweak.selector)) {
90+
try {
91+
applyTweak(el, tweak);
92+
} catch (e) {
93+
GM_log("Exception while applying tweak for '" + tweak.selector + "': " + e);
94+
}
95+
}
96+
if (checkRoot && root.matches(tweak.selector)) {
97+
try {
98+
applyTweak(root, tweak);
99+
} catch (e) {
100+
GM_log("Exception while applying tweak for '" + tweak.selector + "': " + e);
101+
}
102+
}
103+
}
104+
}
105+
106+
let observer = new MutationObserver(function(mutations) {
107+
for (let mutation of mutations) {
108+
try {
109+
if (mutation.type === "childList") {
110+
for (let node of mutation.addedNodes) {
111+
if (node.nodeType != Node.ELEMENT_NODE) {
112+
continue;
113+
}
114+
applyTweaks(node, DYNAMIC_TWEAKS, true);
115+
}
116+
} else if (mutation.type === "attributes") {
117+
applyTweaks(mutation.target, DYNAMIC_TWEAKS, true);
118+
}
119+
} catch (e) {
120+
// Catch exceptions for individual mutations so other mutations are still handled.
121+
GM_log("Exception while handling mutation: " + e);
122+
}
123+
}
124+
});
125+
126+
function init() {
127+
applyTweaks(document, LOAD_TWEAKS, false);
128+
applyTweaks(document, DYNAMIC_TWEAKS, false);
129+
options = {childList: true, subtree: true};
130+
if (DYNAMIC_TWEAK_ATTRIBS.length > 0) {
131+
options.attributes = true;
132+
options.attributeFilter = DYNAMIC_TWEAK_ATTRIBS;
133+
}
134+
observer.observe(document, options);
135+
}
136+
137+
/*** Define the actual tweaks. ***/
138+
139+
// Tweaks that only need to be applied on load.
140+
const LOAD_TWEAKS = [
141+
];
142+
143+
// Attributes that should be watched for changes and cause dynamic tweaks to be
144+
// applied. For example, if there is a dynamic tweak which handles the state of
145+
// a check box and that state is determined using an attribute, that attribute
146+
// should be included here.
147+
const DYNAMIC_TWEAK_ATTRIBS = [];
148+
149+
// Tweaks that must be applied whenever a node is added/changed.
150+
const DYNAMIC_TWEAKS = [
151+
// Make "Library" and "Playlists" headings.
152+
{selector: '.web-navigation__header-text',
153+
tweak: [makeHeading, 2]},
154+
// Make the section containing playback controls, etc. into a region.
155+
{selector: '.web-chrome',
156+
tweak: [makeRegion, "Controls"]},
157+
// Make the currently playing song title into a heading.
158+
{selector: '.web-chrome-playback-lcd__song-name-scroll',
159+
tweak: [makeHeading, 1]},
160+
// Fix cells in song lists.
161+
{selector: '.col',
162+
tweak: el => el.setAttribute("role", "cell")},
163+
// The Add to library button for songs in song lists.
164+
{selector: '.add-to-library',
165+
tweak: [setLabel, "Add to library"]},
166+
];
167+
168+
/*** Lights, camera, action! ***/
169+
init();

CultureAmpA11yFixes.user.js

Lines changed: 146 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,146 @@
1+
// ==UserScript==
2+
// @name Culture Amp Accessibility Fixes
3+
// @namespace http://axSgrease.nvaccess.org/
4+
// @description Improves the accessibility of Culture Amp.
5+
// @author James Teh <jteh@mozilla.com>
6+
// @copyright 2018 Mozilla Corporation
7+
// @license Mozilla Public License version 2.0
8+
// @version 2018.1
9+
// @grant GM_log
10+
// @include https://*.cultureamp.com/*
11+
// ==/UserScript==
12+
13+
/*** Functions for common tweaks. ***/
14+
15+
function makeHeading(el, level) {
16+
el.setAttribute("role", "heading");
17+
el.setAttribute("aria-level", level);
18+
}
19+
20+
function makeRegion(el, label) {
21+
el.setAttribute("role", "region");
22+
el.setAttribute("aria-label", label);
23+
}
24+
25+
function makeButton(el, label) {
26+
el.setAttribute("role", "button");
27+
el.setAttribute("aria-label", label);
28+
}
29+
30+
function makePresentational(el) {
31+
el.setAttribute("role", "presentation");
32+
}
33+
34+
function setLabel(el, label) {
35+
el.setAttribute("aria-label", label);
36+
}
37+
38+
function makeHidden(el) {
39+
el.setAttribute("aria-hidden", "true");
40+
}
41+
42+
function setExpanded(el, expanded) {
43+
el.setAttribute("aria-expanded", expanded ? "true" : "false");
44+
}
45+
46+
/*** Code to apply the tweaks when appropriate. ***/
47+
48+
function applyTweak(el, tweak) {
49+
if (Array.isArray(tweak.tweak)) {
50+
let [func, ...args] = tweak.tweak;
51+
func(el, ...args);
52+
} else {
53+
tweak.tweak(el);
54+
}
55+
}
56+
57+
function applyTweaks(root, tweaks, checkRoot) {
58+
for (let tweak of tweaks) {
59+
for (let el of root.querySelectorAll(tweak.selector)) {
60+
applyTweak(el, tweak);
61+
}
62+
if (checkRoot && root.matches(tweak.selector)) {
63+
applyTweak(root, tweak);
64+
}
65+
}
66+
}
67+
68+
let observer = new MutationObserver(function(mutations) {
69+
for (let mutation of mutations) {
70+
try {
71+
if (mutation.type === "childList") {
72+
for (let node of mutation.addedNodes) {
73+
if (node.nodeType != Node.ELEMENT_NODE) {
74+
continue;
75+
}
76+
applyTweaks(node, DYNAMIC_TWEAKS, true);
77+
}
78+
} else if (mutation.type === "attributes") {
79+
applyTweaks(mutation.target, DYNAMIC_TWEAKS, true);
80+
}
81+
} catch (e) {
82+
// Catch exceptions for individual mutations so other mutations are still handled.
83+
GM_log("Exception while handling mutation: " + e);
84+
}
85+
}
86+
});
87+
88+
function init() {
89+
applyTweaks(document, LOAD_TWEAKS, false);
90+
applyTweaks(document, DYNAMIC_TWEAKS, false);
91+
observer.observe(document, {childList: true, attributes: DYNAMIC_TWEAK_ATTRIBS.length > 0,
92+
subtree: true, attributeFilter: DYNAMIC_TWEAK_ATTRIBS});
93+
}
94+
95+
/*** Define the actual tweaks. ***/
96+
97+
// Tweaks that only need to be applied on load.
98+
const LOAD_TWEAKS = [
99+
// Make individual questions headings.
100+
{selector: '.question p',
101+
tweak: [makeHeading, 4]},
102+
// Answers should be radio buttons.
103+
{selector: '.option, .heatbarraterRatingUnit',
104+
tweak: el => el.setAttribute("role", "radio")},
105+
// The screen reader only text "You Have Answered" appears after each question.
106+
// It serves absolutely no purpose, so kill it.
107+
{selector: '.heatbarraterContainer > .screenreader',
108+
tweak: makeHidden},
109+
]
110+
111+
// Attributes that should be watched for changes and cause dynamic tweaks to be
112+
// applied. For example, if there is a dynamic tweak which handles the state of
113+
// a check box and that state is determined using an attribute, that attribute
114+
// should be included here.
115+
const DYNAMIC_TWEAK_ATTRIBS = ["class", "data-score", "selected"];
116+
117+
// Tweaks that must be applied whenever a node is added/changed.
118+
const DYNAMIC_TWEAKS = [
119+
// Expose whether a survey section is expanded or collapsed.
120+
{selector: '.survey > li',
121+
tweak: section => {
122+
let heading = section.querySelector("h3");
123+
if (!heading) return;
124+
let expanded = section.classList.contains("selected");
125+
heading.setAttribute("aria-expanded", expanded ? "true" : "false");
126+
}},
127+
// Expose whether an answer is selected.
128+
{selector: '.option',
129+
tweak: option => {
130+
let checked = option.classList.contains("on");
131+
option.setAttribute("aria-checked", checked ? "true" : "false");
132+
}},
133+
{selector: '.heatbarraterContainer',
134+
tweak: container => {
135+
// Individual options don't have an attribute we can use to determine
136+
// selection. However, the container does.
137+
let score = container.getAttribute("data-score");
138+
for (let option of container.querySelectorAll('.heatbarraterRatingUnit')) {
139+
let checked = option.getAttribute("value") == score;
140+
option.setAttribute("aria-checked", checked ? "true" : "false");
141+
}
142+
}},
143+
]
144+
145+
/*** Lights, camera, action! ***/
146+
init();

0 commit comments

Comments
 (0)