Everything written this session that has not been through the device: rounds 43 to 48 (each patch adversarially verified, 43's transient-notice expectation fixed), a partial round 49, the pipeline scripts, the Android Auto design and the icon comparison mock. Also the two harness changes that were never A/B'd — adaptive waits, which may only shorten a declared wait and never lengthen it, and a ship that runs the mock walk and the deploy at the same time. The A/B and its 255-step baseline are in handoff/tools; main stays at what the phone has actually walked.
55 lines
3.0 KiB
Plaintext
55 lines
3.0 KiB
Plaintext
/* ── what a screen reader would be handed ────────────────────────────
|
|
'exposed' is the tree TalkBack actually walks: nothing under
|
|
aria-hidden or inert, nothing display:none or zero-boxed. 'aname' is
|
|
the name it would announce — aria-label, aria-labelledby, then the
|
|
visible text with the aria-hidden subtrees left out (an Icon is
|
|
aria-hidden, so an icon-only control computes to nothing at all). A
|
|
PLACEHOLDER is deliberately not a name here: it goes away the moment
|
|
the field has text in it. Every backslash in this block is doubled —
|
|
it lives inside a template literal. */
|
|
var A11Y_SEL = 'button, [role=button], a, input, select, textarea, [role=tab], [role=switch], [role=slider], [role=checkbox]';
|
|
function exposed(el) {
|
|
if (el.closest('[aria-hidden="true"], [inert]')) return false;
|
|
if (!el.getClientRects().length) return false;
|
|
var st = getComputedStyle(el);
|
|
return st.visibility !== 'hidden' && st.display !== 'none';
|
|
}
|
|
function vistext(el) {
|
|
var out = '';
|
|
for (var i = 0; i < el.childNodes.length; i++) {
|
|
var n = el.childNodes[i];
|
|
if (n.nodeType === 3) { out += n.nodeValue; continue; }
|
|
if (n.nodeType !== 1) continue;
|
|
if (n.getAttribute('aria-hidden') === 'true') continue;
|
|
if (n.tagName === 'IMG') { out += ' ' + (n.getAttribute('alt') || ''); continue; }
|
|
out += ' ' + vistext(n);
|
|
}
|
|
return out;
|
|
}
|
|
function aname(el) {
|
|
var l = el.getAttribute('aria-label');
|
|
if (l && l.trim()) return l.trim();
|
|
var by = el.getAttribute('aria-labelledby');
|
|
if (by) {
|
|
var s = '';
|
|
by.split(' ').forEach(function (id) { var n = document.getElementById(id); if (n) s += ' ' + vistext(n); });
|
|
if (s.trim()) return s.replace(/\\s+/g, ' ').trim();
|
|
}
|
|
var t = el.tagName;
|
|
if (t === 'INPUT' || t === 'TEXTAREA' || t === 'SELECT') {
|
|
if (el.id) { var lab = document.querySelector('label[for="' + el.id + '"]'); if (lab && lab.textContent.trim()) return lab.textContent.replace(/\\s+/g, ' ').trim(); }
|
|
return (el.getAttribute('title') || '').trim();
|
|
}
|
|
return vistext(el).replace(/\\s+/g, ' ').trim() || (el.getAttribute('title') || '').trim();
|
|
}
|
|
function whatis(el) { return (el.tagName.toLowerCase() + '.' + String(el.className || '').split(' ').slice(0, 2).join('.')).slice(0, 36); }
|
|
function junk(el, nm) {
|
|
if (/^[0-9 .,:;+·—–-]+$/.test(nm)) return 'digits';
|
|
if (/\\.(png|jpe?g|webp|gif|svg|mp3|flac|m4a|ogg|wav)$/i.test(nm)) return 'file';
|
|
if (/^[⋯…×✕✓→←]$/.test(nm)) return 'glyph';
|
|
if (nm.length > 200 && !el.hasAttribute('aria-expanded')) return 'essay';
|
|
return '';
|
|
}
|
|
var a11yAll = [].filter.call(document.querySelectorAll(A11Y_SEL), exposed);
|
|
var a11yNone = a11yAll.filter(function (el) { return !aname(el); });
|