dsathyakumar/a11y-auditor

View on GitHub
lib/axs/axsUtils.js

Summary

Maintainability
F
1 wk
Test Coverage
// jshint ignore: start
/***************************************************************************************
 * dependency injection for rules creation
 * @param
 * @return an object with exposed functions
 **/

var enums = require('../enums/enums');

function _addAllParentRolesToSet(a, b) {
    if (a.parent) {
        for (var c = a.parent, d = 0; d < c.length; d++) {
            var e = c[d];
            b[e] = !0;
            _addAllParentRolesToSet(enums.ariaRolesEnum[e], b);
        }
    }
}

function _addAllPropertiesToSet(a, b, c) {
    var d = a[b];
    if (d) {
        for (var e = 0; e < d.length; e++) {
            c[d[e]] = !0;
        }
    }
    if (a.parent) {
        for (a = a.parent, d = 0; d < a.length; d++) {
            _addAllPropertiesToSet(enums.ariaRolesEnum[a[d]], b, c);
        }
    }
}

for (var roleName in enums.ariaRolesEnum) {
    var role = enums.ariaRolesEnum[roleName],
        propertiesSet = {},
        requiredPropertiesSet = {},
        parentRolesSet = {};

    _addAllPropertiesToSet(role, 'properties', propertiesSet);
    role.propertiesSet = propertiesSet;

    _addAllPropertiesToSet(role, 'requiredProperties', requiredPropertiesSet);
    role.requiredPropertiesSet = requiredPropertiesSet;

    _addAllParentRolesToSet(role, parentRolesSet);
    role.allParentRolesSet = parentRolesSet;

    'widget' in parentRolesSet && (enums.widgetRolesEnum[roleName] = role);
}

for (var a in enums.ariaPropertiesEnum) {
    var b = enums.ariaPropertiesEnum[a];
    if (b.values) {
        for (var c = {}, d = 0; d < b.values.length; d++) {
            c[b.values[d]] = !0;
        }
        b.valuesSet = c;
    }
}

enums.globalPropertiesEnum = enums.ariaRolesEnum.roletype.propertiesSet;

//> exposed exported object that will be consumed by other modules
module.exports = {

    getTextFromHostLanguageAttributes: function(a, b, c, d) {
        if (this.matchSelector(a, 'img') && a.hasAttribute('alt')) {
            var e = {
                type: 'string',
                valid: !0
            };
            e.text = a.getAttribute('alt');
            c ? e.unused = !0 : c = e.text;
            b.alt = e;
        }
        if (this.matchSelector(a,
                'input:not([type="hidden"]):not([disabled]), select:not([disabled]), textarea:not([disabled]), button:not([disabled]), video:not([disabled])') &&
            !d) {
            if (a.hasAttribute('id')) {
                d = document.querySelectorAll('label[for="' + a.id + '"]');
                for (var e = {}, f = [], g = [], h = 0; h < d.length; h++) {
                    var k = {
                            type: 'element'
                        },
                        m = d[h],
                        l = this.findTextAlternatives(m, {}, !0);
                    l && 0 < l.trim().length && (k.text = l.trim(), g.push(l.trim()));
                    k.element = m;
                    f.push(k);
                }
                0 < f.length &&
                (f[f.length - 1].last = !0, e.values = f, e.text = g.join(' '), e.lastWord = this.getLastWord(e.text), c
                    ? e.unused = !0 : c = e.text, b.labelFor = e);
            }
            d = this.parentElement(a);
            for (e = {}; d;) {
                if ('label' == d.tagName.toLowerCase() && (f = d, f.control == a)) {
                    e.type = 'element';
                    e.text = this.findTextAlternatives(f, {}, !0);
                    e.lastWord = this.getLastWord(e.text);
                    e.element = f;
                    break;
                }
                d = this.parentElement(d);
            }
            e.text && (c ? e.unused = !0 : c = e.text, b.labelWrapped = e);
            this.matchSelector(a, 'input[type="image"]') && a.hasAttribute('alt') && (e = {
                type: 'string',
                valid: !0
            }, e.text = a.getAttribute('alt'), c ? e.unused = !0 : c = e.text, b.alt = e);
            Object.keys(b).length || (b.noLabel = !0);
        }
        return c;
    },

    asElement: function(a) {
        switch (a.nodeType) {
        case Node.COMMENT_NODE:
            break;
        case Node.ELEMENT_NODE:
            if ('script' == a.localName || 'template' == a.localName) {
                break;
            }
            return a;
        case Node.DOCUMENT_FRAGMENT_NODE:
            return a.host;
        case Node.TEXT_NODE:
            return this.parentElement(a);
        default:
            console.warn('Unhandled node type: ', a.nodeType);
        }
        return null;
    },

    shadowHost: function(a) {
        return 'host' in a ? a.host : null;
    },

    composedParentNode: function(a) {
        if (!a) {
            return null;
        }
        if (a.nodeType === Node.DOCUMENT_FRAGMENT_NODE) {
            return this.shadowHost(a);
        }
        var b = a.parentNode;
        if (!b) {
            return null;
        }
        if (b.nodeType === Node.DOCUMENT_FRAGMENT_NODE) {
            return this.shadowHost(b);
        }
        if (!b.shadowRoot) {
            return b;
        }
        a = a.getDestinationInsertionPoints();
        return 0 < a.length ? this.composedParentNode(a[a.length - 1]) : null;
    },

    parentElement: function(a) {
        if (!a) {
            return null;
        }
        a = this.composedParentNode(a);
        if (!a) {
            return null;
        }
        switch (a.nodeType) {
        case Node.ELEMENT_NODE:
            return a;
        default:
            return this.parentElement(a);
        }
    },

    isElementHidden: function(a) {
        if (!(a instanceof a.ownerDocument.defaultView.HTMLElement)) {
            return !1;
        }
        if (a.hasAttribute('chromevoxignoreariahidden')) {
            var b = !0;
        }
        var c = window.getComputedStyle(a, null);
        return 'none' == c.display || 'hidden' == c.visibility ? !0 : a.hasAttribute('aria-hidden') &&
        'true' == a.getAttribute('aria-hidden').toLowerCase() ? !b : !1;
    },

    isElementOrAncestorHidden: function(a) {
        return this.isElementHidden(a) ? !0 : this.parentElement(a) ? this.isElementOrAncestorHidden(
            this.parentElement(a)) : !1;
    },

    getLastWord: function(a) {
        if (!a) {
            return null;
        }
        var b = a.lastIndexOf(' ') + 1,
            c = a.length - 10;
        return a.substring(b > c ? b : c);
    },

    elementIsHtmlControl: function(a) {
        var b = a.ownerDocument.defaultView;
        return a instanceof b.HTMLButtonElement || a instanceof b.HTMLInputElement ||
        a instanceof b.HTMLSelectElement || a instanceof b.HTMLTextAreaElement ? !0 : !1;
    },

    getTextFromAriaLabelledby: function(a, b) {
        var c = null;
        if (!a.hasAttribute('aria-labelledby')) {
            return c;
        }
        for (var d = a.getAttribute('aria-labelledby').split(/\s+/), e = {
            valid: !0
        }, f = [], g = [], h = 0; h < d.length; h++) {
            var k = {
                    type: 'element'
                },
                m = d[h];
            k.value = m;
            var l = document.getElementById(m);
            l ? (k.valid = !0, k.text = this.findTextAlternatives(l, {}, !0, !0), k.lastWord = this.getLastWord(
                k.text), f.push(k.text), k.element = l) : (k.valid = !1, e.valid = !1, k.errorMessage = {
                messageKey: 'noElementWithId',
                args: [m]
            });
            g.push(k);
        }
        0 < g.length && (g[g.length - 1].last = !0, e.values = g, e.text = f.join(' '), e.lastWord = this.getLastWord(
            e.text), c = e.text, b.ariaLabelledby = e);
        return c;
    },

    elementIsAriaWidget: function(a) {
        return a.hasAttribute('role') && (a = a.getAttribute('role')) && (a = enums.ariaRolesEnum[a]) &&
        'widget' in a.allParentRolesSet ? !0 : !1;
    },

    findTextAlternatives: function(a, b, c, d) {
        var e = c || !1;
        c = this.asElement(a);
        if (!c || !d && this.isElementOrAncestorHidden(c)) {
            return null;
        }
        if (a.nodeType == Node.TEXT_NODE) {
            return c = {
                type: 'text'
            }, c.text = a.textContent, c.lastWord = this.getLastWord(c.text), b.content = c, a.textContent;
        }
        a = null;
        e || (a = this.getTextFromAriaLabelledby(c, b));
        if (c.hasAttribute('aria-label')) {
            var f = {
                type: 'text'
            };
            f.text = c.getAttribute('aria-label');
            f.lastWord = this.getLastWord(f.text);
            a ? f.unused = !0 : e && this.elementIsHtmlControl(c) || (a = f.text);
            b.ariaLabel = f;
        }
        c.hasAttribute('role') && 'presentation' == c.getAttribute('role') ||
        (a = this.getTextFromHostLanguageAttributes(c, b, a, e));
        if (e && this.elementIsHtmlControl(c)) {
            f = c.ownerDocument.defaultView;
            if (c instanceof f.HTMLInputElement) {
                var g = c;
                'text' == g.type && g.value && 0 < g.value.length && (b.controlValue = {
                    text: g.value
                });
                'range' == g.type && (b.controlValue = {
                    text: g.value
                });
            }
            c instanceof f.HTMLSelectElement && (b.controlValue = {
                text: c.value
            });
            b.controlValue && (f = b.controlValue, a ? f.unused = !0 : a = f.text);
        }
        if (e && this.elementIsAriaWidget(c)) {
            e = c.getAttribute('role');
            'textbox' == e && c.textContent && 0 < c.textContent.length && (b.controlValue = {
                text: c.textContent
            });
            if ('slider' == e || 'spinbutton' == e) {
                c.hasAttribute('aria-valuetext') ? b.controlValue = {
                    text: c.getAttribute('aria-valuetext')
                } : c.hasAttribute('aria-valuenow') && (b.controlValue = {
                    value: c.getAttribute('aria-valuenow'),
                    text: '' + c.getAttribute('aria-valuenow')
                });
            }
            if ('menu' == e) {
                for (var h = c.querySelectorAll('[role=menuitemcheckbox], [role=menuitemradio]'), f = [], g = 0;
                    g < h.length; g++) {
                    'true' == h[g].getAttribute('aria-checked') && f.push(h[g]);
                }
                if (0 < f.length) {
                    h = '';
                    for (g = 0; g < f.length; g++) {
                        h += this.findTextAlternatives(f[g], {}, !0), g < f.length - 1 && (h += ', ');
                    }
                    b.controlValue = {
                        text: h
                    };
                }
            }
            if ('combobox' == e || 'select' == e) {
                b.controlValue = {
                    text: 'TODO'
                };
            }
            b.controlValue && (f = b.controlValue, a ? f.unused = !0 : a = f.text);
        }
        f = !0;
        c.hasAttribute('role') && (e = c.getAttribute('role'), (e = enums.ariaRolesEnum[e]) &&
        (!e.namefrom || 0 > e.namefrom.indexOf('contents')) && (f = !1));
        (d = this.getTextFromDescendantContent(c, d)) && f && (e = {
            type: 'text'
        }, e.text = d, e.lastWord = this.getLastWord(e.text), a ? e.unused = !0 : a = d, b.content = e);
        c.hasAttribute('title') && (d = {
            type: 'string',
            valid: !0
        }, d.text = c.getAttribute('title'), d.lastWord = this.getLastWord(d.lastWord), a ? d.unused = !0
            : a = d.text, b.title = d);
        return 0 == Object.keys(b).length && null == a ? null : a;
    },

    getTextFromDescendantContent: function(a, b) {
        for (var c = a.childNodes, d = [], e = 0; e < c.length; e++) {
            var f = this.findTextAlternatives(c[e], {}, !0, b);
            f && d.push(f.trim());
        }
        if (d.length) {
            c = '';
            for (e = 0; e < d.length; e++) {
                c = [
                    c,
                    d[e]
                ].join(' ').trim();
            }
            return c;
        }
        return null;
    },

    isValidIDRefValue: function(a, b) {
        return 0 == a.length ? {
            valid: !0,
            idref: a
        } : b.ownerDocument.getElementById(a) ? {
            valid: !0,
            idref: a
        } : {
            valid: !1,
            idref: a,
            reason: 'No element with ID "' + a + '"'
        };
    },

    //checks if an element can take ARIA attributes
    checkForAria: function(el) {
        if (!el) {
            return null;
        }
        var tagName = el.tagName;
        if (!tagName) {
            return null;
        }
        tagName = tagName.toUpperCase();
        tagName = enums.TagAndSemanticInfoEnum[tagName];
        if (!tagName || !tagName.length) {
            return null;
        }
        for (var _res = null, _count = 0; _count < tagName.length; _count++) {
            var _obj = tagName[_count];
            if (_obj.selector) {
                if (this.matchSelector(el, _obj.selector)) {
                    return _obj;
                }
            } else {
                _res = _obj;
            }
        }
        return _res;
    },

    getAriaPropertyValue: function(a, b, c) {
        var d = a.replace(/^aria-/, ''),
            e = enums.ariaPropertiesEnum[d],
            d = {
                name: a,
                rawValue: b
            };
        if (!e) {
            return d.valid = !1, d.reason = '"' + a + '" is not a valid ARIA property', d;
        }
        e = e.valueType;
        if (!e) {
            return d.valid = !1, d.reason = '"' + a + '" is not a valid ARIA property', d;
        }
        switch (e) {
        case 'idref':
            a = this.isValidIDRefValue(b, c), d.valid = a.valid, d.reason = a.reason, d.idref = a.idref;
        case 'idref_list':
            a = b.split(/\s+/);
            d.valid = !0;
            for (b = 0; b < a.length; b++) {
                e = this.isValidIDRefValue(a[b], c), e.valid || (d.valid = !1), d.values ? d.values.push(e)
                    : d.values = [e];
            }
            return d;
        case 'integer':
            c = this.isValidNumber(b);
            if (!c.valid) {
                return d.valid = !1, d.reason = c.reason, d;
            }
            Math.floor(c.value) !== c.value ? (d.valid = !1, d.reason = '' + b + ' is not a whole integer')
                : (d.valid = !0, d.value = c.value);
            return d;
        case 'decimal':
            ;
        case 'number':
            c = this.isValidNumber(b);
            d.valid = c.valid;
            if (!c.valid) {
                return d.reason = c.reason, d;
            }
            d.value = c.value;
            return d;
        case 'string':
            return d.valid = !0, d.value = b, d;
        case 'token':
            return c = this.isValidTokenValue(a, b.toLowerCase()), c.valid ? (d.valid = !0, d.value = c.value)
                : (d.valid = !1, d.value = b, d.reason = c.reason), d;
        case 'token_list':
            e = b.split(/\s+/);
            d.valid = !0;
            for (b = 0; b < e.length; b++) {
                c = this.isValidTokenValue(a, e[b].toLowerCase()), c.valid ||
                (d.valid = !1, d.reason ? (d.reason = [d.reason], d.reason.push(c.reason))
                    : (d.reason = c.reason, d.possibleValues = c.possibleValues)), d.values ? d.values.push(c.value)
                    : d.values = [c.value];
            }
            return d;
        case 'tristate':
            return c = this.isPossibleValue(b.toLowerCase(), enums.mixedValuesEnum, a), c.valid
                ? (d.valid = !0, d.value = c.value) : (d.valid = !1, d.value = b, d.reason = c.reason), d;
        case 'boolean':
            return c = this.isValidBoolean(b), c.valid ? (d.valid = !0, d.value = c.value)
                : (d.valid = !1, d.value = b, d.reason = c.reason), d;
        }
        d.valid = !1;
        d.reason = 'Not a valid ARIA property';
        return d;
    },

    isValidTokenValue: function(a, b) {
        var c = a.replace(/^aria-/, '');
        return this.isPossibleValue(b, enums.ariaPropertiesEnum[c].valuesSet, a);
    },

    isPossibleValue: function(a, b, c) {
        return b[a] ? {
            valid: !0,
            value: a
        } : {
            valid: !1,
            value: a,
            reason: '"' + a + '" is not a valid value for ' + c,
            possibleValues: Object.keys(b)
        };
    },

    isValidBoolean: function(a) {
        try {
            var b = JSON.parse(a);
        } catch (c) {
            b = '';
        }
        return 'boolean' != typeof b ? {
            valid: !1,
            value: a,
            reason: '"' + a + '" is not a true/false value'
        } : {
            valid: !0,
            value: b
        };
    },

    getImplicitRole: function(b) {
        return (b = this.checkForAria(b)) ? b.role : '';
    },

    getRoles: function(a, b) {
        if (!a || a.nodeType !== Node.ELEMENT_NODE || !a.hasAttribute('role') && !b) {
            return null;
        }
        var c = a.getAttribute('role');
        !c && b && (c = this.getImplicitRole(a));
        if (!c) {
            return null;
        }
        for (var c = c.split(' '), d = {
            roles: [],
            valid: !1
        }, e = 0; e < c.length; e++) {
            var f = c[e],
                g = enums.ariaRolesEnum[f],
                f = {
                    name: f
                };
            g && !g.abstract ? (f.details = g, d.applied || (d.applied = f), f.valid = d.valid = !0) : f.valid = !1;
            d.roles.push(f);
        }
        return d;
    },

    values: function(a) {
        var b = [],
            c;
        for (c in a) {
            a.hasOwnProperty(c) && 'function' != typeof a[c] && b.push(a[c]);
        }
        return b;
    },

    getAriaProperties: function(a) {
        var b = {},
            c = this.getGlobalAriaProperties(a),
            d;
        for (d in enums.ariaPropertiesEnum) {
            var e = 'aria-' + d;
            if (a.hasAttribute(e)) {
                var f = a.getAttribute(e);
                c[e] = this.getAriaPropertyValue(e, f, a);
            }
        }
        0 < Object.keys(c).length && (b.properties = this.values(c));
        f = this.getRoles(a);
        if (!f) {
            return Object.keys(b).length ? b : null;
        }
        b.roles = f;
        if (!f.valid || !f.roles) {
            return b;
        }
        for (var e = f.roles, g = 0; g < e.length; g++) {
            var h = e[g];
            if (h.details && h.details.propertiesSet) {
                for (d in h.details.propertiesSet) {
                    d in c ||
                    (a.hasAttribute(d) ? (f = a.getAttribute(d), c[d] = this.getAriaPropertyValue(d, f, a), 'values' in
                    c[d] && (f = c[d].values, f[f.length - 1].isLast = !0)) : h.details.requiredPropertiesSet[d] &&
                    (c[d] = {
                        name: d,
                        valid: !1,
                        reason: 'Required property not set'
                    }));
                }
            }
        }
        0 < Object.keys(c).length && (b.properties = this.values(c));
        return 0 < Object.keys(b).length ? b : null;
    },

    getGlobalAriaProperties: function(a) {
        var b = {},
            c;
        for (c in enums.globalPropertiesEnum) {
            if (a.hasAttribute(c)) {
                var d = a.getAttribute(c);
                b[c] = this.getAriaPropertyValue(c, d, a);
            }
        }
        return b;
    },

    matchSelector: function(a, b) {
        return a.matches ? a.matches(b) : a.webkitMatchesSelector ? a.webkitMatchesSelector(b) : a.mozMatchesSelector
            ? a.mozMatchesSelector(b) : a.msMatchesSelector ? a.msMatchesSelector(b) : !true;
    },

    isValidNumber: function(a) {
        var b = {
            valid: !1,
            value: a,
            reason: '"' + a + '" is not a number'
        };
        if (!a) {
            return b;
        }
        if (/^0x/i.test(a)) {
            return b.reason = '"' + a + '" is not a decimal number', b;
        }
        a *= 1;
        return isFinite(a) ? {
            valid: !0,
            value: a
        } : b;
    }

};
// jshint ignore: end