/* global aria, treeview */
var DIALOG_ID = 'a11y-outline';
var focus = function(el) {
el.focus();
if (document.activeElement !== el) {
el.tabIndex = -1;
el.focus();
}
var y = el.getBoundingClientRect().top;
var h = document.documentElement.clientHeight;
if (y > h / 2) {
document.scrollingElement.scrollBy(0, y - h / 2);
}
};
var setTarget = function(target) {
document.querySelectorAll('.a11y-outline-target').forEach(el => {
el.classList.remove('a11y-outline-target');
});
if (target) {
target.classList.add('a11y-outline-target');
}
};
var createDialog = function() {
var dialog = document.createElement('dialog');
dialog.id = DIALOG_ID;
dialog.addEventListener('close', function() {
dialog.remove();
setTarget(null);
});
document.body.appendChild(dialog);
return dialog;
};
var createItem = function(el, i) {
var label = aria.getAttribute(el, 'roledescription') || aria.getRole(el);
if (aria.matches(el, 'heading')) {
label += ' ' + aria.getAttribute(el, 'level');
}
var name = aria.getName(el, null, true);
if (name) {
label = name + ' ' + label;
}
return {
label: label,
href: '#' + i,
children: [],
element: el,
};
};
var insertItem = function(item, list) {
var itemLevel = aria.getAttribute(item.element, 'level');
var last = list[list.length - 1];
if (last) {
if (itemLevel > aria.getAttribute(last.element, 'level') ||
last.element.contains(item.element)) {
return insertItem(item, last.children);
}
}
list.push(item);
};
var getMatches = function(role) {
return aria.querySelectorAll(document, role)
.filter(el => !aria.matches(el, ':hidden'));
};
var buildTree = function(matches) {
var items = [];
for (var i = 0; i < matches.length; i++) {
insertItem(createItem(matches[i], i), items);
}
return items;
};
var renderTree = function(role, dialog) {
var ul = treeview.build([], dialog.id + '-' + role);
ul.setAttribute('aria-busy', 'true');
dialog.appendChild(ul);
setTimeout(() => {
var matches = getMatches(role).filter(el => !dialog.contains(el));
var tree = buildTree(matches);
if (matches.length) {
treeview.update(ul, tree, ul.id);
} else {
ul.innerHTML = '
Nothing found';
}
ul.setAttribute('aria-busy', 'false');
var getTarget = function(a) {
var href = a.getAttribute('href');
var i = parseInt(href.substr(1), 10);
return matches[i];
};
ul.addEventListener('click', function(event) {
if (event.target.matches('a')) {
event.preventDefault();
dialog.close();
focus(getTarget(event.target));
}
});
var targetSelected = function() {
var target = null;
if (ul === document.activeElement) {
var selected = ul.querySelector('[aria-selected="true"] a');
target = getTarget(selected);
}
setTarget(target);
};
var mouseoutTimeoutId = null;
ul.addEventListener('mouseover', event => {
if (event.target.matches('a')) {
clearTimeout(mouseoutTimeoutId);
var target = getTarget(event.target);
setTarget(target);
}
});
ul.addEventListener('mouseout', () => {
clearTimeout(mouseoutTimeoutId);
mouseoutTimeoutId = setTimeout(targetSelected, 100);
});
ul.addEventListener('focus', targetSelected);
ul.addEventListener('select', targetSelected);
ul.addEventListener('blur', targetSelected);
});
};
var updateVisiblePane = function(select, dialog) {
var id = dialog.id + '-' + select.value;
if (!document.getElementById(id)) {
renderTree(select.value, dialog);
}
var trees = dialog.querySelectorAll('[role="tree"]');
Array.prototype.forEach.call(trees, function(tree) {
tree.hidden = (tree.id !== id);
});
};
var quickNav = function() {
var dialog = createDialog();
var header = document.createElement('header');
dialog.appendChild(header);
var select = document.createElement('select');
select.innerHTML = `
`;
select.addEventListener('change', function() {
updateVisiblePane(select, dialog);
});
select.autofocus = true;
header.appendChild(select);
var close = document.createElement('button');
close.addEventListener('click', () => dialog.close());
close.textContent = '✕';
close.title = 'Close';
close.setAttribute('aria-label', 'Close');
close.className = 'close';
close.tabIndex = -1;
header.appendChild(close);
updateVisiblePane(select, dialog);
dialog.showModal();
};
var _walk = function(root, fn) {
var owners = document.querySelectorAll('[aria-owns]');
var queue = [root];
while (queue.length) {
var item = queue.shift();
fn(item);
queue = aria.getChildNodes(item, owners).concat(queue);
}
};
var walk = function(root, fn) {
try {
_walk(root, function(node) {
if (node.nodeType === node.ELEMENT_NODE) {
fn(node);
}
});
} catch (e) {
if (e !== 'StopIteration') {
throw e;
}
}
};
var focusNext = function(selector) {
var target;
var next = false;
walk(document, function(node) {
if (node === document.activeElement) {
next = true;
} else if ((!target || next) && aria.matches(node, selector)) {
target = node;
if (next) {
throw 'StopIteration';
}
}
});
if (target) {
focus(target);
}
};
var focusPrev = function(selector) {
var target;
walk(document, function(node) {
if (target && node === document.activeElement) {
throw 'StopIteration';
} else if (aria.matches(node, selector)) {
target = node;
}
});
if (target) {
focus(target);
}
};
var onMessage = function(listener) {
if (!window.a11yOutlineRegistered) {
window.a11yOutlineRegistered = true;
chrome.runtime.onMessage.addListener(listener);
}
}
onMessage(function(request) {
if (document.getElementById(DIALOG_ID)) {
return;
} else if (request === 'showA11yOutline') {
quickNav();
} else if (request === 'cycle-main') {
focusNext('main');
} else if (request === 'next-landmark') {
focusNext('landmark');
} else if (request === 'prev-landmark') {
focusPrev('landmark');
} else if (request === 'next-heading') {
focusNext('heading');
} else if (request === 'prev-heading') {
focusPrev('heading');
}
});