Skip to content

Commit

Permalink
feat: add rules for 1.1 Text Alternatives (#19)
Browse files Browse the repository at this point in the history
* fix: broken selector

* refactor: prefer for..of over forEach

* refactor: prefer template literals over string concatenation

* fix: post-refactor logic and compatibility issues

* feat: set up initial check for missing alt text

* feat: add more checks for detectable alt text issues

* fix: prevent buggy behavior from content scripts auto-loading

* chore: bump version number
  • Loading branch information
dustinwhisman authored Jul 21, 2024
1 parent f48eebd commit b3d6f1b
Show file tree
Hide file tree
Showing 7 changed files with 106 additions and 73 deletions.
119 changes: 63 additions & 56 deletions background.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,45 +2,55 @@ let scriptsEnabled = false;
globalThis.browser = globalThis.browser ?? chrome;

const contentScripts = [
{
id: '1-1-text-alternatives-js',
js: ['rules/1-1-text-alternatives/checks.js'],
},
{
id: '1-1-text-alternatives-css',
css: ['rules/1-1-text-alternatives/checks.css'],
},
{
id: 'info-js',
js: ['rules/info/checks.js'],
matches: ['*://*/*'],
},
{
id: 'info-css',
css: ['rules/info/checks.css'],
matches: ['*://*/*'],
},
{
id: 'warning-js',
js: ['rules/warning/checks.js'],
matches: ['*://*/*'],
},
{
id: 'warning-css',
css: ['rules/warning/checks.css'],
matches: ['*://*/*'],
},
{
id: 'error-js',
js: ['rules/error/checks.js'],
matches: ['*://*/*'],
},
{
id: 'error-css',
css: ['rules/error/checks.css'],
matches: ['*://*/*'],
},
];

const optionState = {
'1-1-text-alternatives': true,
info: true,
warning: true,
error: true,
};

const menuOptions = [
{
id: '1-1-text-alternatives',
type: 'checkbox',
title: '1.1 Text Alternatives',
contexts: ['action'],
checked: optionState['1-1-text-alternatives'],
},
{
id: 'info',
type: 'checkbox',
Expand All @@ -65,9 +75,9 @@ const menuOptions = [
];

const initializeContextMenuOptions = () => {
menuOptions.forEach((option) => {
browser.menus.create(option);
});
for (const option of menuOptions) {
browser.contextMenus.create(option);
}
};

const registerContentScripts = async () => {
Expand All @@ -80,24 +90,20 @@ const isScriptEnabled = (script) => {
};

const toggleContentScripts = async (tab) => {
const scripts = await browser.scripting.getRegisteredContentScripts({
ids: contentScripts.map(({ id }) => id),
});

if (scriptsEnabled) {
scripts.forEach((script) => {
for (const script of contentScripts) {
if (script.css) {
browser.scripting.removeCSS({
files: script.css,
target: {
tabId: tab.id,
},
origin: 'USER',
});
return;
}
});
}
} else {
scripts.forEach((script) => {
for (const script of contentScripts) {
const enabled = isScriptEnabled(script);
if (script.js && enabled) {
browser.scripting.executeScript({
Expand All @@ -106,7 +112,7 @@ const toggleContentScripts = async (tab) => {
tabId: tab.id,
},
});
return;
continue;
}

if (script.css && enabled) {
Expand All @@ -115,63 +121,64 @@ const toggleContentScripts = async (tab) => {
target: {
tabId: tab.id,
},
origin: 'USER',
});
return;
}
});
}
}

scriptsEnabled = !scriptsEnabled;
};

const toggleRules = async (event) => {
const { checked, menuItemId } = event;
const toggleRules = async (info, tab) => {
const { checked, menuItemId } = info;
optionState[menuItemId] = checked;
const currentTabs = await browser.tabs.query({ active: true });
const scripts = await browser.scripting.getRegisteredContentScripts({
ids: [`${menuItemId}-js`, `${menuItemId}-css`],
});
currentTabs.forEach((tab) => {
scripts.forEach((script) => {
if (script.css && !checked) {
browser.scripting.removeCSS({
files: script.css,
const scripts = contentScripts.filter(({ id }) => id.replace(/\-(js|css)/, '') === menuItemId);
for (const script of scripts) {
if (script.css && !checked) {
browser.scripting.removeCSS({
files: script.css,
target: {
tabId: tab.id,
},
origin: 'USER',
});
continue;
}

if (checked && scriptsEnabled) {
if (script.js) {
browser.scripting.executeScript({
files: script.js,
target: {
tabId: tab.id,
},
});
return;
continue;
}

if (checked && scriptsEnabled) {
if (script.js) {
browser.scripting.executeScript({
files: script.js,
target: {
tabId: tab.id,
},
});
return;
}

if (script.css) {
browser.scripting.insertCSS({
files: script.css,
target: {
tabId: tab.id,
},
});
return;
}
if (script.css) {
browser.scripting.insertCSS({
files: script.css,
target: {
tabId: tab.id,
},
origin: 'USER',
});
}
});
});
}
}
};

browser.runtime.onInstalled.addListener(async () => {
await registerContentScripts();
initializeContextMenuOptions();
});

browser.action.onClicked.addListener(toggleContentScripts);
browser.menus.onClicked.addListener(toggleRules);
browser.contextMenus.onClicked.addListener(toggleRules);
browser.tabs.onUpdated.addListener((tabId, changeInfo, tab) => {
if (changeInfo.status === 'complete') {
scriptsEnabled = !scriptsEnabled;
toggleContentScripts(tab);
}
});
4 changes: 2 additions & 2 deletions manifest.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
{
"manifest_version": 3,
"name": "Visua11yze",
"version": "0.3.0",
"version": "0.4.0",
"description": "Loads CSS to add visual regressions for HTML anti-patterns and accessibility issues. Loads JS to analyze and log details that can't be determined through CSS alone.",
"homepage_url": "https://github.com/dustinwhisman/visua11yze",
"background": {
Expand All @@ -16,7 +16,7 @@
},
"permissions": [
"activeTab",
"menus",
"contextMenus",
"scripting"
]
}
37 changes: 37 additions & 0 deletions rules/1-1-text-alternatives/checks.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
:root {
--error-color: hotpink;
--inverted-error-color: #00964b;
--warning-color: goldenrod;
--info-color: cornflowerblue;
--error-outline: 0.25rem solid var(--error-color);
--inverted-error-outline: 0.25rem solid var(--inverted-error-color);
--warning-outline: 0.25rem solid var(--warning-color);
--info-outline: 0.25rem solid var(--info-color);
}

img:not([alt]) {
--wcag-success-criteria: '1.1.1 Non-Text Content';
--error-missing-alt-text: 'This image does not have alt text. Even if it is purely decorative, the alt attribute must exist.';

background-color: var(--inverted-error-color);
outline: var(--inverted-error-outline);
outline-offset: 0.25rem;
filter: invert(100%);
}

img[alt=''] {
--wcag-success-criteria: '1.1.1 Non-Text Content';
--warning-decorative-image: 'This image will be treated as purely decorative. Are you sure it does not convey any useful information or context?';

filter: opacity(5%) blur(5px);
}

img[alt^="image" i],
img[alt^="picture" i],
img[alt^="graphic" i] {
--wcag-success-criteria: '1.1.1 Non-Text Content';
--info-repetitive-alt-text: 'Screen readers will let users know they are describing an image, so it is redundant to start alt text with immage, picture, or graphic.';

outline: var(--info-outline);
outline-offset: 0.25rem;
}
1 change: 1 addition & 0 deletions rules/1-1-text-alternatives/checks.js
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
// TODO: add scripts to check for errors
8 changes: 1 addition & 7 deletions rules/error/checks.css
Original file line number Diff line number Diff line change
Expand Up @@ -74,12 +74,6 @@ fieldset:not(:has(legend)) {
outline: var(--error-outline);
}

img:not([alt]) {
--error-missing-alt-text: 'This image does not have alt text. Even if it is purely decorative, the alt attribute must exist.';

outline: var(--error-outline);
}

figcaption:not(figure > figcaption) {
--error-figcaption-not-child: 'The figcaption is not a direct child of a figure';

Expand All @@ -104,7 +98,7 @@ figure:not(:is([aria-label], [aria-labelledby]), :has(figcaption)) {
outline: var(--error-outline);
}

body :not(> :is(header, nav, main, aside, footer), :is(header, nav, main, aside, footer) *) {
body :not(body > :is(header, nav, main, aside, footer), :is(header, nav, main, aside, footer) *) {
--error-content-outside-landmark: 'You have some content that is not inside a landmark (header, nav, main, aside, or footer)';

outline: var(--error-outline);
Expand Down
6 changes: 0 additions & 6 deletions rules/info/checks.css
Original file line number Diff line number Diff line change
Expand Up @@ -13,9 +13,3 @@ a[target='_blank'] {

outline: var(--info-outline);
}

img[alt=''] {
--info-decorative-image: 'This image will be treated as purely decorative. Are you sure it does not convey any useful information or context?';

outline: var(--info-outline);
}
4 changes: 2 additions & 2 deletions rules/info/checks.js
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
};
}

return getDeepestDirectDiv(selector + ' > div', depth + 1, element);
return getDeepestDirectDiv(`${selector} > div`, depth + 1, element);
};

const getDeepestDiv = (selector = 'div div', depth = 2, lastElement = null) => {
Expand All @@ -22,7 +22,7 @@
};
}

return getDeepestDiv(selector + ' div', depth + 1, element);
return getDeepestDiv(`${selector} div`, depth + 1, element);
};

const checkSiteForIssues = () => {
Expand Down

0 comments on commit b3d6f1b

Please sign in to comment.