first commit

This commit is contained in:
klein panic
2024-09-29 01:05:25 -04:00
commit 2bcc806b8b
670 changed files with 96169 additions and 0 deletions

View File

@@ -0,0 +1,21 @@
The MIT License (MIT)
Copyright (c) 2015 Jason Costello
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

View File

@@ -0,0 +1,34 @@
# ![alt text](https://raw.githubusercontent.com/jin/chromelens/master/images/logo.png "ChromeLens Logo")
<a href="https://chrome.google.com/webstore/detail/chrome-lens/idikgljglpfilbhaboonnpnnincjhjkd">Chrome Web Store</a>
ChromeLens is a Google Chrome extension that provides a suite of tools to help
with web accessibility development.
### Lens (Vision Simulator)
Interact with a website as a completely/partially blind or a colorblind person.
### Accessibility Audit
Run a website through an series of accessibility rules and easily discover
elements in your website that do not comply with them.
### Tab-tracker
Key website features should be navigable solely via the keyboard (tab button),
while not making the user jump through hoops to get to a feature. With the
tab-tracker, you can visually track the flow of navigation through a website.
### Website
Find out more about ChromeLens, why we made it, and more about the extension at <a href="http://chromelens.xyz">chromelens.xyz</a>
### Credits
- https://github.com/GoogleChrome/accessibility-developer-tools
- https://github.com/niklasvh/html2canvas
- https://github.com/Altreus/colourblind
### Reviews for ChromeLens
- http://briancoords.com/tech/analyzing-accessibility-chromelens/
- http://www.jaredrigby.co.uk/2016/07/11/accessibility-testing-with-chromelens.html

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,2 @@
var base = document.getElementById("chrome-lens-base")
if (base) { base.remove(); }

View File

@@ -0,0 +1,236 @@
var BASE_Z = 999999;
var WARNING_COUNT = 0;
var idToWarningsMap = {};
var STYLE = `<style>
:host {
all: initial;
contain: style layout size;
display: block;
position: absolute;
top: 0;
left: 0;
z-index: ${BASE_Z};
}
.chrome-lens-warning {
position: absolute;
box-shadow: 0 0 4px 4px #f7983a;
border-radius: 2px;
}
.tooltip .tooltip-text {
visibility: hidden;
display: inline-block;
background-color: #fffdfd;
color: #212121;
position: absolute;
max-width: 300px;
min-width: 200px;
padding: 0px 10px 5px 10px;
border-radius: 2px;
border: 1px solid black;
z-index: ${BASE_Z+1};
}
.tooltip .tooltip-text .severe {
color: #c10f0f;
font-weight: 900;
}
.tooltip .tooltip-text .warning {
color: #e69808;
font-weight: 700;
}
.tooltip:hover .tooltip-text {
visibility: visible;
}
</style>`;
var CHROME_LENS_BASE_ID = 'chrome-lens-base'
var CHROME_LENS_WARNING_CLASS = 'chrome-lens-warning'
var WARNING_ATTR_NAME = 'chromelens-warning-id';
function initDom() {
if (!document.getElementById(CHROME_LENS_BASE_ID)) {
const div = document.createElement('div');
div.id = CHROME_LENS_BASE_ID;
var root = div.attachShadow({mode: 'open'});
root.innerHTML = STYLE;
document.body.appendChild(div);
}
}
function tooltipHeader(severity, code, url) {
const el = document.createElement('p');
const severityEl = document.createElement('span');
if (severity === 'Severe') {
severityEl.className = 'severe'
} else if (severity === 'Warning') {
severityEl.className = 'warning'
}
severityEl.innerText = severity;
const codeLink = document.createElement('a');
codeLink.href = url;
codeLink.innerText = code;
codeLink.target = '_blank';
el.appendChild(severityEl);
el.appendChild(document.createTextNode(' '));
el.appendChild(codeLink);
return el;
}
function positionTooltip(tooltipText, el_top, el_left, el_height) {
const {width, height} = tooltipText.getBoundingClientRect();
if (height < el_top) {
// tooltip cannot fit above, make it below
tooltipText.style.bottom = el_height + 'px';
} else {
// tooltip goes above
tooltipText.style.top = el_height + 'px';
}
if (el_left + width > document.documentElement.clientWidth) {
// tooltip is wider than the el, right align el and tooltip
tooltipText.style.right = 0 + 'px';
}
}
function tooltipTextNode(rule_violated) {
const {code, heading, name, severity, url} = rule_violated;
const tooltipText = document.createElement('div');
tooltipText.className = 'tooltip-text';
tooltipText.appendChild(tooltipHeader(severity, code, url));
tooltipText.appendChild(document.createTextNode(heading));
suggestFix(rule_violated);
return tooltipText;
}
function getCoordinates(elem) {
var box = elem.getBoundingClientRect();
var body = document.body;
var docEl = document.documentElement;
var scrollTop = window.pageYOffset || docEl.scrollTop || body.scrollTop;
var scrollLeft = window.pageXOffset || docEl.scrollLeft || body.scrollLeft;
var clientTop = docEl.clientTop || body.clientTop || 0;
var clientLeft = docEl.clientLeft || body.clientLeft || 0;
var top = box.top + scrollTop - clientTop;
var left = box.left + scrollLeft - clientLeft;
return { top, left, height: box.height, width: box.width }
}
function tooltipNode(offendingEl) {
const { top, left, height, width} = getCoordinates(offendingEl);
const div = document.createElement('div');
div.className = CHROME_LENS_WARNING_CLASS;
div.classList.add('tooltip');
div.style.left = left + 'px';
div.style.top = top + 'px';
div.style.width = width + 'px';
div.style.height = height + 'px';
// this id will be useful if we want to reference specific warnings emitted
div.id = 'chrome-lens-warning-' + WARNING_COUNT;
div.onmouseover = function() {
chrome.runtime.sendMessage({
type: 'HIGHLIGHT_REPORT',
data: {
warningId: div.id
}
})
}
div.onmouseout = function() {
chrome.runtime.sendMessage({
type: 'UNHIGHLIGHT_REPORT',
data: {
warningId: div.id
}
})
}
return div;
}
function suggestFix(ruleViolated) {
const {code, heading, name, severity, url} = ruleViolated;
if (code === 'AX_ARIA_10') {
}
}
function highlightElementForRuleViolation(frag, el, rule_violated) {
const warningId = CHROME_LENS_WARNING_CLASS + '-' + (WARNING_COUNT++);
idToWarningsMap[warningId] = {el: el, rule: rule_violated};
el.setAttribute(WARNING_ATTR_NAME, warningId);
const { top, left, height, width } = getCoordinates(el);
// if this isn't visible let's not warn about
if (top < 0 || left < 0) return;
const tooltipText = tooltipTextNode(rule_violated);
const toolTip = tooltipNode(el);
toolTip.appendChild(tooltipText);
frag.appendChild(toolTip);
// we can only position after we append, because tooltipText has no
// bounding client rect before it gets added to the DOM
positionTooltip(tooltipText, top, left, height);
}
function run() {
const base = document.getElementById(CHROME_LENS_BASE_ID);
if (base) {
base.remove()
}
var run_result = axs.Audit.run();
initDom()
var frag = document.createDocumentFragment();
// we only want to highlight failures
const failures = run_result.filter(v => v.result === 'FAIL');
failures.forEach(function(v) {
v.elements.forEach(function(el) {
highlightElementForRuleViolation(frag, el, v.rule);
})
})
var root = document.getElementById(CHROME_LENS_BASE_ID).shadowRoot;
root.appendChild(frag);
chrome.runtime.sendMessage({
type: 'AXS_COMPLETE',
data: {
result: failures,
idToWarningsMap,
}
})
}
run();
chrome.runtime.onMessage.addListener(function(message) {
switch (message.type) {
case 'HIGHLIGHT_WARNING': {
const { warningId } = message.data;
const warningTooltip = document.querySelector('#' + warningId + ' .tooltip-text');
if (!warningTooltip) { return; }
warningTooltip.style.visibility = 'visible';
break;
}
case 'UNHIGHLIGHT_WARNING': {
const { warningId } = message.data;
const warningTooltip = document.querySelector('#' + warningId + ' .tooltip-text');
if (!warningTooltip) { return; }
// we must set to null so that CSS can take over
warningTooltip.style.visibility = null;
break;
}
default:{
break;
}
}
})

View File

@@ -0,0 +1,116 @@
const messageType = {
EXECUTE_SCRIPT: 'EXECUTE_SCRIPT',
RUN_AXS: 'RUN_AXS',
AXS_COMPLETE: 'AXS_COMPLETE',
HIGHLIGHT_WARNING: 'HIGHLIGHT_WARNING',
UNHIGHLIGHT_WARNING: 'UNHIGHLIGHT_WARNING',
HIGHLIGHT_REPORT: 'HIGHLIGHT_REPORT',
UNHIGHLIGHT_REPORT: 'UNHIGHLIGHT_REPORT',
TRACE_TAB_PATH: 'TRACE_TAB_PATH',
PNG_TAB_PATH: 'PNG_TAB_PATH',
CLEAR_AXS: 'CLEAR_AXS'
}
const auditDir = 'audit';
const tracerDir = 'tracer';
var devtools = null;
const devToolsListener = function(message, sender, sendResponse) {
switch (message.type) {
case messageType.EXECUTE_SCRIPT: {
const { scriptToInject } = message.data;
// Inject a content script into the active tab
chrome.tabs.executeScript({file: scriptToInject});
break;
}
case messageType.RUN_AXS: {
chrome.tabs.executeScript({ file: `${auditDir}/axs_testing.js` });
chrome.tabs.executeScript({ file: `${auditDir}/run_axs.js` });
break;
}
case messageType.CLEAR_AXS: {
chrome.tabs.executeScript({ file: `${auditDir}/clear_axs.js`})
break;
}
case messageType.AXS_COMPLETE: {
const { result, idToWarningsMap } = message.data;
if (devtools) {
devtools.postMessage({
type: 'AXS_SHOW_RESULTS',
data: {
idToWarningsMap: idToWarningsMap
}
})
}
break;
}
case messageType.HIGHLIGHT_WARNING: {
const { warningId } = message.data;
chrome.tabs.sendMessage({
type: messageType.HIGHLIGHT_WARNING,
data: {
warningId: warningId
}
})
break;
}
case messageType.UNHIGHLIGHT_WARNING: {
const { warningId } = message.data;
chrome.tabs.sendMessage({
type: messageType.UNHIGHLIGHT_WARNING,
data: {
warningId: warningId
}
})
break;
}
case messageType.HIGHLIGHT_REPORT: {
const { warningId } = message.data;
if (devtools) {
devtools.postMessage({
type: messageType.HIGHLIGHT_REPORT,
data: {
warningId: warningId
}
})
}
break;
}
case messageType.UNHIGHLIGHT_REPORT: {
const { warningId } = message.data;
if (devtools) {
devtools.postMessage({
type: messageType.UNHIGHLIGHT_REPORT,
data: {
warningId: warningId
}
})
}
break;
}
case messageType.TRACE_TAB_PATH: {
chrome.tabs.executeScript({ file: `${tracerDir}/trace_tab_path.js` });
break;
}
case messageType.PNG_TAB_PATH: {
chrome.tabs.executeScript({ file: `${tracerDir}/html2canvas.js` });
chrome.tabs.executeScript({ file: `${tracerDir}/png_tab_path.js` });
break;
}
default: {
console.log(message);
return null;
}
}
}
// might need to change this to be inside onConnect, and on devtools side
// pass the port to panel
chrome.runtime.onMessage.addListener(devToolsListener);
chrome.runtime.onConnect.addListener(function(devToolsConnection) {
// devToolsConnection.onMessage.addListener(devToolsListener);
devtools = devToolsConnection;
// assign the listener function to a variable so we can remove it later
// add the listener
})

Binary file not shown.

After

Width:  |  Height:  |  Size: 20 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.9 KiB

View File

@@ -0,0 +1,11 @@
var iDiv = document.createElement('div');
iDiv.id = 'chromelens-colorFilters';
iDiv.innerHTML = '<svg style="display: none" version="1.1" xmlns="http://www.w3.org/2000/svg" baseProfile="full"> ' +
'<filter id="achromatomaly"> ' +
'<feColorMatrix type="matrix" values="0.618,0.320,0.062,0,0,0.163,0.775,0.062,0,0,0.163,0.320,0.516,0,0,0,0,0,1,0" in="SourceGraphic" /> ' +
'</filter> ' +
'</svg>';
document.body.appendChild(iDiv);
document.body.style.filter = "url(#achromatomaly)";
document.body.style.webkitFilter = "url(#achromatomaly)";

View File

@@ -0,0 +1,11 @@
var iDiv = document.createElement('div');
iDiv.id = 'chromelens-colorFilters';
iDiv.innerHTML = '<svg style="display: none" version="1.1" xmlns="http://www.w3.org/2000/svg" baseProfile="full"> ' +
'<filter id="achromatopsia"> ' +
'<feColorMatrix type="matrix" values="0.299,0.587,0.114,0,0,0.299,0.587,0.114,0,0,0.299,0.587,0.114,0,0,0,0,0,1,0" in="SourceGraphic" /> ' +
'</filter> ' +
'</svg>';
document.body.appendChild(iDiv);
document.body.style.filter = "url(#achromatopsia)";
document.body.style.webkitFilter = "url(#achromatopsia)";

View File

@@ -0,0 +1,11 @@
var iDiv = document.createElement('div');
iDiv.id = 'chromelens-colorFilters';
iDiv.innerHTML = '<svg style="display: none" version="1.1" xmlns="http://www.w3.org/2000/svg" baseProfile="full"> ' +
'<filter id="deuteranomaly"> ' +
'<feColorMatrix type="matrix" values="0.8,0.2,0,0,0,0.258,0.742,0,0,0,0,0.142,0.858,0,0,0,0,0,1,0" in="SourceGraphic" /> ' +
'</filter> ' +
'</svg>';
document.body.appendChild(iDiv);
document.body.style.filter = "url(#deuteranomaly)";
document.body.style.webkitFilter = "url(#deuteranomaly)";

View File

@@ -0,0 +1,11 @@
var iDiv = document.createElement('div');
iDiv.id = 'chromelens-colorFilters';
iDiv.innerHTML = '<svg style="display: none" version="1.1" xmlns="http://www.w3.org/2000/svg" baseProfile="full"> ' +
'<filter id="deuteranopia"> ' +
'<feColorMatrix type="matrix" values="0.625,0.375,0,0,0,0.7,0.3,0,0,0,0,0.3,0.7,0,0,0,0,0,1,0" in="SourceGraphic" /> ' +
'</filter> ' +
'</svg>';
document.body.appendChild(iDiv);
document.body.style.filter = "url(#deuteranopia)";
document.body.style.webkitFilter = "url(#deuteranopia)";

View File

@@ -0,0 +1,2 @@
document.body.style.filter = "";
document.body.style.webkitFilter = "";

View File

@@ -0,0 +1,20 @@
delete document.body.style.filter;
delete document.body.style.webkitFilter;
// z-index at upper bound of 32bit int
var styles = `
background-color: black;
pointer-events: none;
top: 0;
left: 0;
height: 100%;
width: 100%;
position: fixed;
z-index: 2147483647;
`
var iDiv = document.createElement('div');
iDiv.id = 'chromelens-colorFilters';
iDiv.innerHTML = `<div style="${styles}"></div>`;
document.body.appendChild(iDiv);

View File

@@ -0,0 +1,20 @@
document.body.style.filter = "blur(3px)";
document.body.style.webkitFilter = "blur(3px)";
// z-index at upper bound of 32bit int
var styles = `
background: rgba(50, 50, 50, 0.3);
pointer-events: none;
top: 0;
left: 0;
height: 100%;
width: 100%;
position: fixed;
z-index: 2147483647;
`
var iDiv = document.createElement('div');
iDiv.id = 'chromelens-colorFilters';
iDiv.innerHTML = `<div style="${styles}"></div>`;
document.body.appendChild(iDiv);

View File

@@ -0,0 +1,20 @@
document.body.style.filter = "blur(2px)";
document.body.style.webkitFilter = "blur(2px)";
// z-index at upper bound of 32bit int
var styles = `
background: rgba(50, 50, 50, 0.1);
pointer-events: none;
top: 0;
left: 0;
height: 100%;
width: 100%;
position: fixed;
z-index: 2147483647;
`
var iDiv = document.createElement('div');
iDiv.id = 'chromelens-colorFilters';
iDiv.innerHTML = `<div style="${styles}"></div>`;
document.body.appendChild(iDiv);

View File

@@ -0,0 +1,2 @@
document.body.style.filter = "blur(1px)";
document.body.style.webkitFilter = "blur(1px)";

View File

@@ -0,0 +1,20 @@
document.body.style.filter = "blur(10px)";
document.body.style.webkitFilter = "blur(10px)";
// z-index at upper bound of 32bit int
var styles = `
background: rgba(50, 50, 50, 0.5);
pointer-events: none;
top: 0;
left: 0;
height: 100%;
width: 100%;
position: fixed;
z-index: 2147483647;
`
var iDiv = document.createElement('div');
iDiv.id = 'chromelens-colorFilters';
iDiv.innerHTML = `<div style="${styles}"></div>`;
document.body.appendChild(iDiv);

View File

@@ -0,0 +1,11 @@
var iDiv = document.createElement('div');
iDiv.id = 'chromelens-colorFilters';
iDiv.innerHTML = '<svg style="display: none" version="1.1" xmlns="http://www.w3.org/2000/svg" baseProfile="full"> ' +
'<filter id="protanomaly"> ' +
'<feColorMatrix type="matrix" values="0.817,0.183,0,0,0,0.333,0.667,0,0,0,0,0.125,0.875,0,0,0,0,0,1,0" in="SourceGraphic" /> ' +
'</filter> ' +
'</svg>';
document.body.appendChild(iDiv);
document.body.style.filter = "url(#protanomaly)";
document.body.style.webkitFilter = "url(#protanomaly)";

View File

@@ -0,0 +1,11 @@
var iDiv = document.createElement('div');
iDiv.id = 'chromelens-colorFilters';
iDiv.innerHTML = '<svg style="display: none" version="1.1" xmlns="http://www.w3.org/2000/svg" baseProfile="full"> ' +
'<filter id="protanopia"> ' +
'<feColorMatrix type="matrix" values="0.567,0.433,0,0,0,0.558,0.442,0,0,0,0,0.242,0.758,0,0,0,0,0,1,0" in="SourceGraphic" /> ' +
'</filter> ' +
'</svg>';
document.body.appendChild(iDiv);
document.body.style.filter = "url(#protanopia)";
document.body.style.webkitFilter = "url(#protanopia)";

View File

@@ -0,0 +1,11 @@
var iDiv = document.createElement('div');
iDiv.id = 'chromelens-colorFilters';
iDiv.innerHTML = '<svg style="display: none" version="1.1" xmlns="http://www.w3.org/2000/svg" baseProfile="full"> ' +
'<filter id="tritanomaly"> ' +
'<feColorMatrix type="matrix" values="0.967,0.033,0,0,0,0,0.733,0.267,0,0,0,0.183,0.817,0,0,0,0,0,1,0" in="SourceGraphic" /> ' +
'</filter> ' +
'</svg>';
document.body.appendChild(iDiv);
document.body.style.filter = "url(#tritanomaly)";
document.body.style.webkitFilter = "url(#tritanomaly)";

View File

@@ -0,0 +1,11 @@
var iDiv = document.createElement('div');
iDiv.id = 'chromelens-colorFilters';
iDiv.innerHTML = '<svg style="display: none" version="1.1" xmlns="http://www.w3.org/2000/svg" baseProfile="full"> ' +
'<filter id="tritanopia"> ' +
'<feColorMatrix type="matrix" values="0.95,0.05,0,0,0,0,0.433,0.567,0,0,0,0.475,0.525,0,0,0,0,0,1,0" in="SourceGraphic" /> ' +
'</filter> ' +
'</svg>';
document.body.appendChild(iDiv);
document.body.style.filter = "url(#tritanopia)";
document.body.style.webkitFilter = "url(#tritanopia)";

View File

@@ -0,0 +1,4 @@
var elem = document.getElementById("chromelens-colorFilters");
if (elem) {
elem.parentElement.removeChild(elem);
}

View File

@@ -0,0 +1,8 @@
<!doctype html>
<html>
<head>
<script src="main.js"></script>
</head>
<body>
</body>
</html>

View File

@@ -0,0 +1,46 @@
// Utilities
function log(...s) {
const string = JSON.stringify(s);
chrome.devtools.inspectedWindow.eval('console.log(' + string + ')')
};
// Create panel
function createPanel() {
chrome.devtools.panels.create('ChromeLens', '', 'panel.html', function(panel) {
var _window;
var backgroundPageConnection = chrome.runtime.connect();
backgroundPageConnection.onMessage.addListener(function (message) {
// Handle responses from the background page, if any
switch (message.type) {
case 'AXS_SHOW_RESULTS': {
const { idToWarningsMap } = message.data;
if (_window) {
_window.showAxsResults(idToWarningsMap);
}
break;
}
case 'HIGHLIGHT_REPORT': {
const { warningId } = message.data;
if (_window) {
_window.highlightReportLine(warningId);
}
break;
}
case 'UNHIGHLIGHT_REPORT': {
const { warningId } = message.data;
if (_window) {
_window.unhighlightReportLine(warningId);
}
break;
}
}
});
panel.onShown.addListener(function(window) {
_window = window;
});
panel.onHidden.addListener(function() {
});
})
}
createPanel();

View File

@@ -0,0 +1,28 @@
{
"update_url": "https://clients2.google.com/service/update2/crx",
"manifest_version": 2,
"name": "ChromeLens",
"description": "Visual impairment simulation and auditing tools to develop for accessibility.",
"version": "0.0.10",
"minimum_chrome_version": "42",
"devtools_page": "main.html",
"content_security_policy": "script-src 'self' 'unsafe-eval'; object-src 'self'",
"web_accessible_resources": [ "main.html", "panel.html", "build/backend.js"],
"background": {
"scripts": [ "background.js" ]
},
"icons": {
"48": "icons/chromelens2-48.png",
"128": "icons/chromelens2-128.png"
},
"permissions": [
"http://*/*",
"https://*/*",
"activeTab"
]
}

View File

@@ -0,0 +1,120 @@
<!doctype html>
<html style="display: flex">
<head>
<meta charset="utf8">
<style>
h1 {
color: rgb(70, 76, 88);
font-size: 20px;
line-height: 24px;
font-weight: normal;
margin-top: 0;
}
h2 {
color: rgb(110, 116, 128);
font-size: 16px;
line-height: 20px;
font-weight: normal;
margin-top: 0;
}
body {
padding: 16px;
background-color: white;
}
.warning {
color: #e69808;
}
.severe {
color: #c10f0f;
}
.vbox {
display: flex;
flex-direction: row;
}
.sidebar {
overflow-x: hidden;
background: #f3f3f3;
max-width: 330px;
min-width: 170px;
}
.f {
margin-bottom: 16px;
}
button {
background-image: linear-gradient(hsl(0, 0%, 93%), hsl(0, 0%, 93%) 38%, hsl(0, 0%, 87%));
border: 1px solid hsla(0, 0%, 0%, 0.25);
border-radius: 2px;
box-shadow: 0 1px 0 hsla(0, 0%, 0%, 0.08), inset 0 1px 2px hsla(0, 100%, 100%, 0.75);
color: hsl(0, 0%, 27%);
font-size: 12px;
margin: 0 1px 0 0;
text-shadow: 0 1px 0 hsl(0, 0%, 94%);
min-height: 2em !important;
padding-left: 10px;
padding-right: 10px;
-webkit-user-select: none;
flex: none;
}
</style>
</head>
<body>
<h1>ChromeLens</h1>
<p>A tool for accessibility development</p>
<section class="f">
<h2>Lenses</h2>
<p>See how the page looks like through the eyes of someone with vision issues.</p>
<input id="lensEnabledCheckbox" type="checkbox"><span>Enable lens</span></input>
<select id="lensSelector" disabled="true">
<option value="full_blindness">Full blindness</option>
<option value="partial_blindness_mild">Partial blindness (mild)</option>
<option value="partial_blindness_medium">Partial blindness (medium)</option>
<option value="partial_blindness_serious">Partial blindness (serious)</option>
<option value="protanomaly">Protanomaly (red-weak)</option>
<option value="protanopia">Protanopia (red-blind)</option>
<option value="deuteranomaly">Deuteranomaly (green-weak)</option>
<option value="deuteranopia">Deuteranopia (green-blind)</option>
<option value="tritanomaly">Tritanomaly (blue-weak)</option>
<option value="tritanopia">Tritanopia (blue-blind)</option>
<option value="achromatomaly">Achromatomaly (RGB-weak)</option>
<option value="achromatopsia">Achromatopsia (RGB-blind)</option>
</select>
<div id="lensDetail"></div>
<div id="statsDetail"></div>
</section>
<section class="f">
<h2>Accessibility checks</h2>
<p>Audits the current page using Google's accessibility developer tools.</p>
<button id='runAxs'>
Run accessibility checks
</button>
<button id='clearAxs' style='visibility: hidden'>
Clear checks
</button>
<div id="axs-results">
</div>
</section>
<section class="f">
<h2>Trace tab path</h2>
<p>
Key website features should be keyboard-navigable with the tab button.
Trace the path of your tab navigation flow graphically with this tool.
</p>
<button id='traceTabPath'>
Start trace
</button>
<button id='pngTabPath' style="visibility: hidden">
Download PNG
</button>
</section>
<script src="panel.js" type="text/javascript" charset="utf-8"></script>
</body>
</html>

View File

@@ -0,0 +1,319 @@
// Utilities
//
function log(...s) {
const string = JSON.stringify(s);
chrome.devtools.inspectedWindow.eval('console.log(' + string + ')')
};
const messageType = {
EXECUTE_SCRIPT: 'EXECUTE_SCRIPT',
RUN_AXS: 'RUN_AXS',
HIGHLIGHT_WARNING: 'HIGHLIGHT_WARNING',
UNHIGHLIGHT_WARNING: 'UNHIGHLIGHT_WARNING',
TRACE_TAB_PATH: 'TRACE_TAB_PATH',
PNG_TAB_PATH: 'PNG_TAB_PATH',
CLEAR_AXS: 'CLEAR_AXS'
}
const WARNING_ATTR_NAME = 'chromelens-warning-id';
const lensType = {
ACHROMATOMALY: {
name: 'Achromatomaly',
file: 'lens_achromatomaly.js',
stats: { },
description: 'Absence of most colors.'
},
ACHROMATOPSIA: {
name: 'Achromatopsia',
file: 'lens_achromatopsia.js',
stats: { },
description: 'No colors at all.'
},
DEUTERANOMALY: {
name: 'Deuteranomaly',
file: 'lens_deuteranomaly.js',
stats: {
male: 5,
female: 0.35
},
description: 'Low amounts of green color.'
},
DEUTERANOPIA: {
name: 'Deuteranopia',
file: 'lens_deuteranopia.js',
stats: {
male: 1,
female: 0.1
},
description: 'No green color at all.'
},
EMPTY: {
name: 'Normal vision',
file: 'lens_empty.js',
stats: { },
description: 'Normal vision.'
},
FULL_BLINDNESS: {
name: 'Full blindness',
file: 'lens_fullblindness.js',
stats: {
count: '39 million (WHO)'
},
description: 'No vision at all.'
},
PARTIAL_BLINDNESS_MILD: {
name: 'Partial blindness (mild)',
file: 'lens_partialblindness_mild.js',
stats: {
count: '246 million (WHO)'
},
description: 'Limited vision (mild).'
},
PARTIAL_BLINDNESS_MEDIUM: {
name: 'Partial blindness (medium)',
file: 'lens_partialblindness_medium.js',
stats: {
count: '246 million (WHO)'
},
description: 'Limited vision (medium).'
},
PARTIAL_BLINDNESS_SERIOUS: {
name: 'Partial blindness (serious)',
file: 'lens_partialblindness_serious.js',
stats: {
count: '246 million (WHO)'
},
description: 'Limited vision (serious). Able to differentiate light vs dark.'
},
PROTANOMALY: {
name: 'Protanomaly',
file: 'lens_protanomaly.js',
stats: {
male: 1.08,
female: 0.03
},
description: 'Low amounts of red color.',
},
PROTANOPIA: {
name: 'Protanopia',
file: 'lens_protanopia.js',
stats: {
male: 1.01,
female: 0.02
},
description: 'No red color at all.'
},
TRITANOMALY: {
name: 'Tritanomaly',
file: 'lens_tritanomaly.js',
stats: { },
description: 'Low amounts of blue color.'
},
TRITANOPIA: {
name: 'Tritanopia',
file: 'lens_tritanopia.js',
stats: { },
description: 'No blue color at all.'
}
}
// Global state
const lensDir = 'lenses/filters/';
const changeLens = (lens) => {
// Remove any lens div
chrome.runtime.sendMessage({
type: messageType.EXECUTE_SCRIPT,
data: {
tabId: chrome.devtools.inspectedWindow.tabId,
scriptToInject: lensDir + 'reset.js'
}
});
chrome.runtime.sendMessage({
type: messageType.EXECUTE_SCRIPT,
data: {
tabId: chrome.devtools.inspectedWindow.tabId,
scriptToInject: lensDir + lens.file
}
});
const { description, stats } = lens;
const lensDetail = document.getElementById('lensDetail');
lensDetail.innerHTML = description;
const statsDetail = document.getElementById('statsDetail');
statsDetail.innerHTML = `
Affects: <br />
Population % - Male: ${stats.male || 'unknown'}, Female: ${stats.female || 'unknown'} <br />
Population count: ${stats.count}
`;
// http://www.colourblindawareness.org/colour-blindness/types-of-colour-blindness/
// http://www.color-blindness.com/
}
const addEventListeners = () => {
const lensSelector = document.getElementById('lensSelector');
const getSelectedLens = () => {
const { options, selectedIndex } = lensSelector;
const { value } = options[selectedIndex];
const lens = lensType[value.toUpperCase()];
return lens;
}
const setSelectedLens = () => lensEnabledCheckbox.checked ?
changeLens(getSelectedLens()) : changeLens(lensType.EMPTY);
lensSelector.onchange = setSelectedLens;
const lensEnabledCheckbox = document.getElementById('lensEnabledCheckbox');
lensEnabledCheckbox.onclick = function() {
lensSelector.disabled = !this.checked;
setSelectedLens();
}
// Maintain lens across navigations
chrome.devtools.network.onNavigated.addListener(setSelectedLens);
const runAxsButton = document.getElementById('runAxs');
const clearAxsButton = document.getElementById('clearAxs');
runAxsButton.onclick = function() {
chrome.runtime.sendMessage({
type: messageType.RUN_AXS,
data: {
tabId: chrome.devtools.inspectedWindow.tabId
}
})
clearAxsButton.style.visibility = null;
};
clearAxsButton.onclick = function() {
chrome.runtime.sendMessage({
type: messageType.CLEAR_AXS,
data: {
tabId: chrome.devtools.inspectedWindow.tabId
}
})
const resultRoot = document.getElementById('axs-results');
removeChildren(resultRoot);
clearAxsButton.style.visibility = 'hidden';
}
const traceTabPathButton = document.getElementById('traceTabPath');
const pngTabPathButton = document.getElementById('pngTabPath');
traceTabPathButton.onclick = function() {
chrome.runtime.sendMessage({
type: messageType.TRACE_TAB_PATH,
data: {
tabId: chrome.devtools.inspectedWindow.tabId
}
})
pngTabPathButton.style.visibility = null;
};
pngTabPathButton.onclick = function() {
chrome.runtime.sendMessage({
type: messageType.PNG_TAB_PATH,
data: {
tabId: chrome.devtools.inspectedWindow.tabId
}
})
};
}
function removeChildren(el) {
if (!el) { return; }
while (el.children.length > 0) {
el.children[0].remove()
}
}
function severityNode(severity) {
var span = document.createElement('span');
span.classList.add( severity.toLowerCase());
span.innerText = severity;
return span;
}
function showAxsResults(idToWarningsMap) {
const resultRoot = document.querySelector('#axs-results');
removeChildren(resultRoot);
if (Object.keys(idToWarningsMap).length === 0) {
const p = document.createElement('p');
p.textContent = 'No failures found';
resultRoot.appendChild(p);
return;
}
const ul = document.createElement('ul');
for (i in idToWarningsMap) {
var div = document.createElement('li');
div.classList.add('result-line');
div.id = i;
var s_el = severityNode(idToWarningsMap[i].rule.severity);
div.appendChild(s_el);
div.appendChild(document.createTextNode(' '));
var div_note = idToWarningsMap[i].rule.heading;
div.appendChild(document.createTextNode(div_note))
var link = document.createElement('a');
link.href = idToWarningsMap[i].rule.url;
link.target = '_blank';
link.innerText = idToWarningsMap[i].rule.code;
div.appendChild(document.createTextNode(' '));
div.appendChild(link);
div.onmouseover = function() {
_highlight(this);
chrome.runtime.sendMessage({
type: messageType.HIGHLIGHT_WARNING,
data: {
tabId: chrome.devtools.inspectedWindow.tabId,
warningId: i
}
});
};
div.onmouseout = function() {
_unhighlight(this);
chrome.runtime.sendMessage({
type: messageType.UNHIGHLIGHT_WARNING,
data: {
tabId: chrome.devtools.inspectedWindow.tabId,
warningId: i
}
});
};
let evalString = `var node = document.querySelector("[${WARNING_ATTR_NAME}='${i}']"); inspect(node)`
div.onmousedown = function() {
chrome.devtools.inspectedWindow.eval(evalString)
};
ul.appendChild(div);
}
resultRoot.appendChild(ul);
}
function highlightReportLine(warningId) {
const line = document.getElementById(warningId);
if (line) { _highlight(line); }
}
function unhighlightReportLine(warningId) {
const line = document.getElementById(warningId);
if (line) { _unhighlight(line); }
}
function _highlight(lineEl) {
lineEl.style.border = '2px solid blue';
}
function _unhighlight(lineEl) {
lineEl.style.border = null;
}
addEventListeners();

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,10 @@
html2canvas(document.body).then(function(canvas) {
canvas.toBlob(function(blob) {
var a = document.createElement('a');
var url = URL.createObjectURL(blob);
a.href = url;
a.download = 'png.png';
a.click();
window.URL.revokeObjectURL(url);
})
});

View File

@@ -0,0 +1,88 @@
// Delete any applied filters
delete document.body.style.filter;
delete document.body.style.webkitFilter;
var body = document.body;
var html = document.documentElement;
function drawLine(ctx, x, y) {
ctx.lineTo(x, y);
ctx.stroke();
}
function drawDot(ctx, x, y) {
ctx.arc(x, y, 3, 0, 2 * Math.PI, false);
ctx.stroke();
}
function getCoordinates(elem) {
var box = elem.getBoundingClientRect();
var body = document.body;
var docEl = document.documentElement;
var halfWidth = box.width / 2;
var halfHeight = box.height / 2;
var scrollTop = window.pageYOffset || docEl.scrollTop || body.scrollTop;
var scrollLeft = window.pageXOffset || docEl.scrollLeft || body.scrollLeft;
var clientTop = docEl.clientTop || body.clientTop || 0;
var clientLeft = docEl.clientLeft || body.clientLeft || 0;
var top = box.top + scrollTop - clientTop;
var left = box.left + scrollLeft - clientLeft;
return {
top: Math.round(top + halfHeight),
left: Math.round(left + halfWidth)
};
}
var height = Math.max(
body.scrollHeight,
body.offsetHeight,
html.clientHeight,
html.scrollHeight,
html.offsetHeight
);
var width = Math.max(
body.scrollWidth,
body.offsetWidth,
html.clientWidth,
html.scrollWidth,
html.offsetWidth
);
var canvas = document.createElement("canvas");
canvas.id = 'chrome-lens-canvas';
canvas.style.pointerEvents = 'none';
canvas.style.top = 0;
canvas.style.left = 0;
canvas.height = height;
canvas.width = width;
canvas.style.position = 'absolute';
canvas.style.zIndex = 2147483647;
var ctx = canvas.getContext('2d');
ctx.strokeStyle = 'red';
ctx.lineWidth = 2;
ctx.beginPath();
ctx.moveTo(0, 0);
if (document.activeElement !== document.body) {
const { top: y, left: x } = getCoordinates(document.activeElement);
drawLine(ctx, x, y);
}
document.body.appendChild(canvas);
document.body.onkeyup = function (e) {
e = e || window.event;
if (e.keyCode === 9) {
// Tab
const { top: y, left: x } = getCoordinates(e.target);
drawLine(ctx, x, y);
drawDot(ctx, x, y);
}
};