diff --git a/accessibility-checker-engine/src/v4/rules/input_label_exists.ts b/accessibility-checker-engine/src/v4/rules/input_label_exists.ts index 797b992f1..060663c02 100644 --- a/accessibility-checker-engine/src/v4/rules/input_label_exists.ts +++ b/accessibility-checker-engine/src/v4/rules/input_label_exists.ts @@ -176,7 +176,7 @@ export const input_label_exists: Rule = { passed = CommonUtil.hasInnerContentHidden(ruleContext); } */ - const pair = AccNameUtil.computeAccessibleName(ruleContext);console.log("node="+ruleContext.nodeName+", pair="+JSON.stringify(pair)); + const pair = AccNameUtil.computeAccessibleName(ruleContext); passed = pair && pair.name && pair.name.trim().length > 0; if (passed) { diff --git a/accessibility-checker-engine/src/v4/rules/input_label_visible.ts b/accessibility-checker-engine/src/v4/rules/input_label_visible.ts index 839175158..f605c89f2 100644 --- a/accessibility-checker-engine/src/v4/rules/input_label_visible.ts +++ b/accessibility-checker-engine/src/v4/rules/input_label_visible.ts @@ -18,6 +18,7 @@ import { CommonUtil } from "../util/CommonUtil"; import { FragmentUtil } from "../../v2/checker/accessibility/util/fragment"; import { VisUtil } from "../util/VisUtil"; import { DOMUtil } from "../../v2/dom/DOMUtil"; +import { AccNameUtil } from "../util/AccNameUtil"; export const input_label_visible: Rule = { id: "input_label_visible", @@ -94,10 +95,11 @@ export const input_label_visible: Rule = { } } } - + + const pair = AccNameUtil.computeAccessibleName(ruleContext); // check visible label for input or button if (nodeName === 'input' || nodeName === 'button') { - + /** if (CommonUtil.hasImplicitLabel(ruleContext)) return RulePass("pass"); @@ -116,6 +118,9 @@ export const input_label_visible: Rule = { if (type === 'image' && CommonUtil.attributeNonEmpty(ruleContext, "alt")) return RulePass("pass"); } + */ + if (pair && pair.name && pair.name.trim().length > 0 && (pair.nameFrom === 'label' || pair.nameFrom === 'internal' || pair.nameFrom === 'alt')) + return RulePass("pass"); } // custom widget submission is not in scope for this success criteria (IBMa/equal-access#204) if it is not associated with data entry @@ -128,13 +133,17 @@ export const input_label_visible: Rule = { // check if any visible text from the control. // note that (1) the text doesn’t need to be associated with the control to form a relationship - // and (2) the text doesn't need to follow accessible name requirement (e.g. nameFrom) - if (!CommonUtil.isInnerTextEmpty(ruleContext)) + // (2) the text doesn't need to follow accessible name requirement (e.g. nameFrom) + // and (3) an alternative tooltip exists that can be made visible through mouseover + /**if (!CommonUtil.isInnerTextEmpty(ruleContext)) return RulePass("pass"); - + // check if an alternative tooltip exists that can be made visible through mouseover if (CommonUtil.attributeNonEmpty(ruleContext, "title")) return RulePass("pass"); + */ + if (pair && pair.name && pair.name.trim().length > 0 && (pair.nameFrom === 'text' || pair.nameFrom === 'title')) + return RulePass("pass"); // check if any descendant with an alternative tooltip that can be made visible through mouseover // only consider img and svg, and other text content of the descendant is covered in the isInnerText above @@ -158,12 +167,15 @@ export const input_label_visible: Rule = { } } - if (nodeName === "optgroup" && CommonUtil.attributeNonEmpty(ruleContext, "label")) + /**if (nodeName === "optgroup" && CommonUtil.attributeNonEmpty(ruleContext, "label")) return RulePass("pass"); if (nodeName == "option" && (CommonUtil.attributeNonEmpty(ruleContext, "label") || ruleContext.innerHTML.trim().length > 0)) return RulePass("pass"); - + */ + if ((nodeName === "optgroup" || nodeName == "option") && (pair && pair.name && pair.name.trim().length > 0 && (pair.nameFrom === 'label' || pair.nameFrom === 'content'))) + return RulePass("pass"); + // Determine if this is referenced by a combobox. If so, the label belongs to the combobox let id = ruleContext.getAttribute("id"); if (id && id.trim().length > 0) { diff --git a/accessibility-checker-engine/src/v4/util/AccNameUtil.ts b/accessibility-checker-engine/src/v4/util/AccNameUtil.ts index f5acd86a0..cc3b88a81 100644 --- a/accessibility-checker-engine/src/v4/util/AccNameUtil.ts +++ b/accessibility-checker-engine/src/v4/util/AccNameUtil.ts @@ -39,7 +39,7 @@ export class AccNameUtil { // get aria label even for the role where the name is prohibited or is 'presentation' or 'none' let accName = AriaUtil.getAriaLabel(elem); if (accName && accName.trim() !== "") { - CacheUtil.setCache(elem, "ELEMENT_ACCESSBLE_NAME", {"name":accName, "nameFrom": "ariaLabel"}); + CacheUtil.setCache(elem, "ELEMENT_ACCESSBLE_NAME", {"name":CommonUtil.truncateText(accName), "nameFrom": "ariaLabel"}); return {"name":CommonUtil.truncateText(accName), "nameFrom": "ariaLabel"}; } @@ -82,7 +82,7 @@ export class AccNameUtil { let placeholder = elem.getAttribute("placeholder"); if (placeholder && placeholder.trim().length > 0) { placeholder = CommonUtil.truncateText(placeholder); - CacheUtil.setCache(elem, "ELEMENT_ACCESSBLE_NAME", placeholder); + CacheUtil.setCache(elem, "ELEMENT_ACCESSBLE_NAME", {"name":placeholder, "nameFrom": "placeholder"}); return {"name":placeholder, "nameFrom": "placeholder"}; } } @@ -265,10 +265,20 @@ export class AccNameUtil { } }); if (text.trim() !== '') - return {"name":text.trim(), "nameFrom": "iamges"}; + return {"name":text.trim(), "nameFrom": "alt"}; } } + // optgroup + // label participate in accessible name calculation: https://www.w3.org/TR/html-aam-1.0/#att-label + // The label attribute must be specified. Its value gives the name of the group + // the value is disabled in the interface + if (nodeName === "optgroup" || nodeName === "option" || nodeName === "track") { + const label = elem.getAttribute("label"); + if (label && label.trim().length > 0) + return {"name":CommonUtil.truncateText(label), "nameFrom": "label"}; + } + // svg if (nodeName === "svg") { const pair = AccNameUtil.computeAccessibleNameForSVGElement(elem); @@ -341,52 +351,6 @@ export class AccNameUtil { } } - // calculate accessible name for custom elements marked with aria - /**public static computeAccessibleNameFromAttribute(elem: Element) : any | null { - const nodeName = elem.nodeName.toLowerCase(); - const role = AriaUtil.getResolvedRole(elem); - - // textbox etc. return its text value - if (role === "textbox") { - const name = elem.getAttribute("value"); - if (name && name.trim().length > 0) - return {"name":CommonUtil.truncateText(name), "nameFrom": "value"}; - } - - // for combobox or listbox roles, return the text alternative of the chosen option. - if (role === "combobox" || role === "listbox") { - const selectedId = elem.getAttribute("aria-activedescendant") || elem.getAttribute("aria-selected") || elem.getAttribute("aria-checked"); - if (selectedId) { - let selectedOption = elem.ownerDocument.getElementById(selectedId); - if (selectedOption && !DOMUtil.sameNode(elem, selectedOption)) { - const pair = AccNameUtil.computeAccessibleName(selectedOption); - if (pair && pair.name) - return {"name": pair.name, "nameFrom": "option"}; - } - } - } - - // for range role type, including "progressbar", "scrollbar", "slider", "spinbutton" roles - if (["progressbar", "scrollbar", "slider", "spinbutton", "meter"].includes(role)) { - // If the aria-valuetext property is present, return its value - let value = elem.getAttribute("aria-valuetext"); - if (value && value.trim().length > 0) - return {"name":value, "nameFrom": "aria-valuetext"}; - // Otherwise, if the aria-valuenow property is present, return its value, - value = elem.getAttribute("aria-valuenow"); - if (value && value.trim().length > 0) - return {"name":CommonUtil.truncateText(value), "nameFrom": "aria-valuenow"}; - - // finally use native value attribute - value = elem.getAttribute("value"); - if (value && value.trim().length > 0) - return {"name":CommonUtil.truncateText(value), "nameFrom": "value"}; - } - - // no accessible name exists - return null; - }*/ - // calculate accessible name for custom elements marked with aria public static computeAccessibleNameFromContent(elem: Element) : any | null { const nodeName = elem.nodeName.toLowerCase(); diff --git a/accessibility-checker-engine/src/v4/util/CommonUtil.ts b/accessibility-checker-engine/src/v4/util/CommonUtil.ts index 971aae58e..2c648373c 100644 --- a/accessibility-checker-engine/src/v4/util/CommonUtil.ts +++ b/accessibility-checker-engine/src/v4/util/CommonUtil.ts @@ -851,27 +851,34 @@ export class CommonUtil { public static getFormFieldLabel(elem) : string | null { // get the label from the attribute "for" of the label element // Get only the non-hidden labels for element + let value = ""; + let label = null; let labelElem = CommonUtil.getLabelForElementHidden(elem, true); - - /** if it's not label with for attribute, then find implicit label - * cases for explict label: - * - * - * cases for implicit label: - * - */ - if (!labelElem) { + if (labelElem) { + // value directly from element text + label = labelElem.innerText; // ignore hidden text + } else { + /** if it's not label with for attribute, then find implicit label + * cases for explict label: + * + * + * cases for implicit label: + * + */ labelElem = CommonUtil.getAncestor(elem, "label"); - if (!labelElem || labelElem.tagName.toLowerCase() !== "label" || !CommonUtil.isFirstFormElement(labelElem, elem)) - return null; + if (labelElem && labelElem.tagName.toLowerCase() === "label" && CommonUtil.isFirstFormElement(labelElem, elem)) { + let parentClone = labelElem.cloneNode(true); + // exclude all the text from the first form element since they might also + // have inner content that is part of innerText + parentClone = CommonUtil.removeAllFormElementsFromLabel(parentClone); + label = CommonUtil.getInnerText(parentClone); + } else + return null; } - - let value = ""; - // value directly from element text - let label = labelElem.innerText; // ignore hidden text + if (label && label.trim() !== "") value += label.trim(); - + // value from child element attribute label = CommonUtil.getLabelTextFromAttribute(labelElem, true); if (label && label.trim() !== "") @@ -906,13 +913,6 @@ export class CommonUtil { break; } } - //let labeledElem = null; - /**if (labelElem.hasAttribute("for")) { - const id = labelElem.getAttribute("for").trim(); - labeledElem = document.getElementById(id); - if (!labeledElem || DOMUtil.sameNode(labeledElem, labelElem)) - labeledElem = null; - }*/ let nw = new DOMWalker(labelElem); let text = '';