/* This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ "use strict"; const { accessibility: { AUDIT_TYPE: { TEXT_LABEL }, ISSUE_TYPE, SCORES: { BEST_PRACTICES, FAIL, WARNING }, }, } = require("resource://devtools/shared/constants.js"); const { AREA_NO_NAME_FROM_ALT, DIALOG_NO_NAME, DOCUMENT_NO_TITLE, EMBED_NO_NAME, FIGURE_NO_NAME, FORM_FIELDSET_NO_NAME, FORM_FIELDSET_NO_NAME_FROM_LEGEND, FORM_NO_NAME, FORM_NO_VISIBLE_NAME, FORM_OPTGROUP_NO_NAME_FROM_LABEL, FRAME_NO_NAME, HEADING_NO_CONTENT, HEADING_NO_NAME, IFRAME_NO_NAME_FROM_TITLE, IMAGE_NO_NAME, INTERACTIVE_NO_NAME, MATHML_GLYPH_NO_NAME, TOOLBAR_NO_NAME, } = ISSUE_TYPE[TEXT_LABEL]; /** * Check if the accessible is visible to the assistive technology. * @param {nsIAccessible} accessible * Accessible object to be tested for visibility. * * @returns {Boolean} * True if accessible object is visible to assistive technology. */ function isVisible(accessible) { const state = {}; accessible.getState(state, {}); return !(state.value & Ci.nsIAccessibleStates.STATE_INVISIBLE); } /** * Get related accessible objects that are targets of labelled by relation e.g. * labels. * @param {nsIAccessible} accessible * Accessible objects to get labels for. * * @returns {Array} * A list of accessible objects that are labels for a given accessible. */ function getLabels(accessible) { const relation = accessible.getRelationByType( Ci.nsIAccessibleRelation.RELATION_LABELLED_BY ); return [...relation.getTargets().enumerate(Ci.nsIAccessible)]; } /** * Get a trimmed name of the accessible object. * * @param {nsIAccessible} accessible * Accessible objects to get a name for. * * @returns {null|String} * Trimmed name of the accessible object if available. */ function getAccessibleName(accessible) { return accessible.name && accessible.name.trim(); } /** * A text label rule for accessible objects that must have a non empty * accessible name. * * @returns {null|Object} * Failure audit report if accessible object has no or empty name, null * otherwise. */ const mustHaveNonEmptyNameRule = function (issue, accessible) { const name = getAccessibleName(accessible); return name ? null : { score: FAIL, issue }; }; /** * A text label rule for accessible objects that should have a non empty * accessible name as a best practice. * * @returns {null|Object} * Best practices audit report if accessible object has no or empty * name, null otherwise. */ const shouldHaveNonEmptyNameRule = function (issue, accessible) { const name = getAccessibleName(accessible); return name ? null : { score: BEST_PRACTICES, issue }; }; /** * A text label rule for accessible objects that can be activated via user * action and must have a non-empty name. * * @returns {null|Object} * Failure audit report if interactive accessible object has no or * empty name, null otherwise. */ const interactiveRule = mustHaveNonEmptyNameRule.bind( null, INTERACTIVE_NO_NAME ); /** * A text label rule for accessible objects that correspond to dialogs and thus * should have a non-empty name. * * @returns {null|Object} * Best practices audit report if dialog accessible object has no or * empty name, null otherwise. */ const dialogRule = shouldHaveNonEmptyNameRule.bind(null, DIALOG_NO_NAME); /** * A text label rule for accessible objects that provide visual information * (images, canvas, etc.) and must have a defined name (that can be empty, e.g. * ""). * * @returns {null|Object} * Failure audit report if interactive accessible object has no name, * null otherwise. */ const imageRule = function (accessible) { const name = getAccessibleName(accessible); return name != null ? null : { score: FAIL, issue: IMAGE_NO_NAME }; }; /** * A text label rule for accessible objects that correspond to form elements. * These objects must have a non-empty name and must have a visible label. * * @returns {null|Object} * Failure audit report if form element accessible object has no name, * warning if the name does not come from a visible label, null * otherwise. */ const formRule = function (accessible) { const name = getAccessibleName(accessible); if (!name) { return { score: FAIL, issue: FORM_NO_NAME }; } const labels = getLabels(accessible); const hasNameFromVisibleLabel = labels.some(label => isVisible(label)); return hasNameFromVisibleLabel ? null : { score: WARNING, issue: FORM_NO_VISIBLE_NAME }; }; /** * A text label rule for elements that map to ROLE_GROUPING: * * must have a non-empty name and must be provided via the * "label" attribute. * *
must have a non-empty name and must be provided via the * corresponding element. * * @returns {null|Object} * Failure audit report if form grouping accessible object has no name, * or has a name that is not derived from a required location, null * otherwise. */ const formGroupingRule = function (accessible) { const name = getAccessibleName(accessible); const { DOMNode } = accessible; switch (DOMNode.nodeName) { case "OPTGROUP": return name && DOMNode.label && DOMNode.label.trim() === name ? null : { score: FAIL, issue: FORM_OPTGROUP_NO_NAME_FROM_LABEL, }; case "FIELDSET": if (!name) { return { score: FAIL, issue: FORM_FIELDSET_NO_NAME }; } const labels = getLabels(accessible); const hasNameFromLegend = labels.some( label => label.DOMNode.nodeName === "LEGEND" && label.name && label.name.trim() === name && isVisible(label) ); return hasNameFromLegend ? null : { score: WARNING, issue: FORM_FIELDSET_NO_NAME_FROM_LEGEND, }; default: return null; } }; /** * A text label rule for elements that map to ROLE_TEXT_CONTAINER: * * mapps to ROLE_TEXT_CONTAINER and must have a name provided via * the visible label. Note: Will only work when bug 559770 is resolved (right * now, unlabelled meters are not mapped to an accessible object). * * @returns {null|Object} * Failure audit report depending on requirements for dialogs or form * meter element, null otherwise. */ const textContainerRule = function (accessible) { const { DOMNode } = accessible; switch (DOMNode.nodeName) { case "DIALOG": return dialogRule(accessible); case "METER": return formRule(accessible); default: return null; } }; /** * A text label rule for elements that map to ROLE_INTERNAL_FRAME: * * maps to ROLE_INTERNAL_FRAME. Check the type attribute and whether * it includes "image/" (e.g. image/jpeg, image/png, image/gif). If so, audit * it the same way other image roles are audited. * * maps to ROLE_INTERNAL_FRAME and must have a non-empty name. * * and