diff --git a/accessibility-checker-engine/src/v2/checker/accessibility/util/legacy.ts b/accessibility-checker-engine/src/v2/checker/accessibility/util/legacy.ts index cfca19aeb..63958f4a8 100644 --- a/accessibility-checker-engine/src/v2/checker/accessibility/util/legacy.ts +++ b/accessibility-checker-engine/src/v2/checker/accessibility/util/legacy.ts @@ -223,7 +223,7 @@ export class RPTUtil { } /** - * this method returns user-defined aria attribute from dom + * this method returns user-defined aria attribute name from dom * @param ele element * @returns user defined aria attributes */ @@ -241,6 +241,67 @@ export class RPTUtil { return ariaAttributes; } + /** + * this method returns user-defined html attribute name from dom + * @param ele element + * @returns user defined html attributes + */ + public static getUserDefinedHtmlAttributes(elem) { + let htmlAttributes = []; + let domAttributes = elem.attributes; + if (domAttributes) { + for (let i = 0; i < domAttributes.length; i++) { + let attrName = domAttributes[i].name.trim().toLowerCase(); + let isAria = attrName.substring(0, 5) === 'aria-'; + if (!isAria) + htmlAttributes.push(attrName); + } + } + return htmlAttributes; + } + + /** + * this method returns user-defined aria attribute name-value pair from dom + * @param ele element + * @returns user defined aria attributes + */ + public static getUserDefinedAriaAttributeNameValuePairs(elem) { + let ariaAttributes = []; + let domAttributes = elem.attributes; + if (domAttributes) { + for (let i = 0; i < domAttributes.length; i++) { + let attrName = domAttributes[i].name.trim().toLowerCase(); + let attrValue = elem.getAttribute(attrName); + if (attrValue === '') attrValue = null; + let isAria = attrName.substring(0, 5) === 'aria-'; + if (isAria) + ariaAttributes.push({name: attrName, value: attrValue}); + } + } + return ariaAttributes; + } + + /** + * this method returns user-defined html attribute name-value pair from dom + * @param ele element + * @returns user defined html attributes + */ + public static getUserDefinedHtmlAttributeNameValuePairs(elem) { + let htmlAttributes = []; + let domAttributes = elem.attributes; + if (domAttributes) { + for (let i = 0; i < domAttributes.length; i++) { + let attrName = domAttributes[i].name.trim().toLowerCase(); + let attrValue = elem.getAttribute(attrName); + if (attrValue === '') attrValue = null; + let isAria = attrName.substring(0, 5) === 'aria-'; + if (!isAria) + htmlAttributes.push({name: attrName, value: attrValue}); + } + } + return htmlAttributes; + } + /** * This method handles implicit aria definitions, for example, an input with checked is equivalent to aria-checked="true" */ diff --git a/accessibility-checker-engine/src/v4/rules/Rpt_Aria_ValidPropertyValue.ts b/accessibility-checker-engine/src/v4/rules/Rpt_Aria_ValidPropertyValue.ts index 37a035255..4bd930bc5 100644 --- a/accessibility-checker-engine/src/v4/rules/Rpt_Aria_ValidPropertyValue.ts +++ b/accessibility-checker-engine/src/v4/rules/Rpt_Aria_ValidPropertyValue.ts @@ -19,6 +19,7 @@ import { ARIADefinitions } from "../../v2/aria/ARIADefinitions"; export let Rpt_Aria_ValidPropertyValue: Rule = { id: "Rpt_Aria_ValidPropertyValue", context: "dom:*", + dependencies: ["Rpt_Aria_ValidProperty"], help: { "en-US": { "group": "Rpt_Aria_ValidPropertyValue.html", diff --git a/accessibility-checker-engine/src/v4/rules/aria_attribute_conflict.ts b/accessibility-checker-engine/src/v4/rules/aria_attribute_conflict.ts index 6f68cb24f..9287cc1d3 100644 --- a/accessibility-checker-engine/src/v4/rules/aria_attribute_conflict.ts +++ b/accessibility-checker-engine/src/v4/rules/aria_attribute_conflict.ts @@ -14,7 +14,7 @@ import { Rule, RuleResult, RuleFail, RuleContext, RulePass, RuleContextHierarchy } from "../api/IRule"; import { eRulePolicy, eToolkitLevel } from "../api/IRule"; import { RPTUtil } from "../../v2/checker/accessibility/util/legacy"; -import { getCache, setCache } from "../util/CacheUtil"; +import { getInvalidAriaAttributes, getConflictAriaAndHtmlAttributes } from "../util/CommonUtil"; export let aria_attribute_conflict: Rule = { id: "aria_attribute_conflict", @@ -44,39 +44,30 @@ export let aria_attribute_conflict: Rule = { act: [], run: (context: RuleContext, options?: {}, contextHierarchies?: RuleContextHierarchy): RuleResult | RuleResult[] => { const ruleContext = context["dom"].node as Element; + // dependency check: if the ARIA attribute is completely invalid, skip this check + let invalidAttributes = getInvalidAriaAttributes(ruleContext); + if (invalidAttributes && invalidAttributes.length > 0) + return null; + + let ret = []; + let ariaAttributes = RPTUtil.getUserDefinedAriaAttributes(ruleContext); + if (!ariaAttributes || ariaAttributes.length ===0) + return null; - if (getCache(ruleContext, "aria_attribute_allowed", "") === "Fail") return null; - - let domAttributes = ruleContext.attributes; - let ariaAttrs = []; - let htmlAttrs = []; - if (domAttributes) { - for (let i = 0; i < domAttributes.length; i++) { - let attrName = domAttributes[i].name.trim().toLowerCase(); - let attrValue = ruleContext.getAttribute(attrName); - if (attrValue === '') attrValue = null; - if (attrName.substring(0, 5) === 'aria-') - ariaAttrs.push({name: attrName, value: attrValue}); - else - htmlAttrs.push({name: attrName, value: attrValue}); - } + let conflictAttributes = getConflictAriaAndHtmlAttributes(ruleContext); + for (let i = 0; i < conflictAttributes.length; i++) { + ret.push(RuleFail("fail_conflict", [conflictAttributes[i]['ariaAttr'], conflictAttributes[i]['htmlAttr']])); + if (ariaAttributes.includes(conflictAttributes[i]['ariaAttr'])) + RPTUtil.reduceArrayItemList([conflictAttributes[i]['ariaAttr']], ariaAttributes); } - let ret = []; - for (let i = 0; i < ariaAttrs.length; i++) { - const examinedHtmlAtrNames = RPTUtil.getConflictOrOverlappingHtmlAttribute(ariaAttrs[i], htmlAttrs, 'conflict'); - if (examinedHtmlAtrNames === null) continue; - examinedHtmlAtrNames.forEach(item => { - if (item['result'] === 'Pass') { //pass - ret.push(RulePass("pass")); - } else if (item['result'] === 'Failed') { //failed - setCache(ruleContext, "aria_attribute_conflict", "fail_conflict"); - ret.push(RuleFail("fail_conflict", [ariaAttrs[i]['name'], item['attr']])); - } - }); - } + + for (let i = 0; i < ariaAttributes.length; i++) + ret.push(RulePass("pass")); + if (ret.length > 0) return ret; - return null; + + return null; } } \ No newline at end of file diff --git a/accessibility-checker-engine/src/v4/rules/aria_attribute_deprecated.ts b/accessibility-checker-engine/src/v4/rules/aria_attribute_deprecated.ts index a2fc50de4..17aee5e09 100644 --- a/accessibility-checker-engine/src/v4/rules/aria_attribute_deprecated.ts +++ b/accessibility-checker-engine/src/v4/rules/aria_attribute_deprecated.ts @@ -47,9 +47,7 @@ export let aria_attribute_deprecated: Rule = { act: [], run: (context: RuleContext, options?: {}, contextHierarchies?: RuleContextHierarchy): RuleResult | RuleResult[] => { const ruleContext = context["dom"].node as Element; - // dependency check: if the ARIA attribute is completely invalid, skip this check - if (getCache(ruleContext, "aria_semantics_role", "") === "Fail_1") return null; - + let domAttributes = ruleContext.attributes; let ariaAttrs = []; if (domAttributes) { diff --git a/accessibility-checker-engine/src/v4/rules/aria_attribute_redundant.ts b/accessibility-checker-engine/src/v4/rules/aria_attribute_redundant.ts index 49bcfd49e..296553cfc 100644 --- a/accessibility-checker-engine/src/v4/rules/aria_attribute_redundant.ts +++ b/accessibility-checker-engine/src/v4/rules/aria_attribute_redundant.ts @@ -15,6 +15,7 @@ import { Rule, RuleResult, RuleFail, RuleContext, RulePass, RuleContextHierarchy import { eRulePolicy, eToolkitLevel } from "../api/IRule"; import { RPTUtil } from "../../v2/checker/accessibility/util/legacy"; import { getCache } from "../util/CacheUtil"; +import { getInvalidAriaAttributes, getConflictAriaAndHtmlAttributes } from "../util/CommonUtil"; export let aria_attribute_redundant: Rule = { id: "aria_attribute_redundant", @@ -44,13 +45,17 @@ export let aria_attribute_redundant: Rule = { act: [], run: (context: RuleContext, options?: {}, contextHierarchies?: RuleContextHierarchy): RuleResult | RuleResult[] => { const ruleContext = context["dom"].node as Element; + // dependency check: if the ARIA attribute is completely invalid, skip this check - - if (getCache(ruleContext, "aria_attribute_allowed", "") === "Fail") return null; + let invalidAttributes = getInvalidAriaAttributes(ruleContext); + if (invalidAttributes && invalidAttributes.length > 0) + return null; // if conflict already reported, ignore reporting overlap - if (getCache(ruleContext, "aria_attribute_conflict", "") === "fail_conflict") return null; - + let conflictAttributes = getConflictAriaAndHtmlAttributes(ruleContext); + if (conflictAttributes && conflictAttributes.length > 0) + return null; + let domAttributes = ruleContext.attributes; let ariaAttrs = []; let htmlAttrs = []; diff --git a/accessibility-checker-engine/src/v4/rules/aria_role_redundant.ts b/accessibility-checker-engine/src/v4/rules/aria_role_redundant.ts index ec4827a3c..710468180 100644 --- a/accessibility-checker-engine/src/v4/rules/aria_role_redundant.ts +++ b/accessibility-checker-engine/src/v4/rules/aria_role_redundant.ts @@ -15,6 +15,7 @@ import { Rule, RuleResult, RuleFail, RuleContext, RulePass, RuleContextHierarchy import { eRulePolicy, eToolkitLevel } from "../api/IRule"; import { RPTUtil } from "../../v2/checker/accessibility/util/legacy"; import { getCache } from "../util/CacheUtil"; +import { isTableDescendant, areRolesDefined } from "../util/CommonUtil"; export let aria_role_redundant: Rule = { id: "aria_role_redundant", @@ -44,15 +45,21 @@ export let aria_role_redundant: Rule = { const ruleContext = context["dom"].node as Element; let elemName = ruleContext.tagName.toLowerCase(); - // dependency check: if the ARIA attribute is completely invalid, skip this check - if (getCache(ruleContext, "aria_semantics_role", "") === "Fail_1") return null; - // dependency check: if it's already failed in the parent relation, then skip this check - if (["td", "th", "tr"].includes(elemName) && getCache(ruleContext, "table_aria_descendants", "") === "explicit_role") - return null; - let ariaRoles = RPTUtil.getRoles(ruleContext, false); if (!ariaRoles || ariaRoles.length === 0) return; + // the invalid role case: handled by Rpt_Aria_ValidRole. Ignore to avoid duplicated report + let role_defined = areRolesDefined(ariaRoles); + if (!role_defined) + return null; + + // dependency check: if it's already failed in the parent relation, then skip this check + if (["td", "th", "tr"].includes(elemName)) { + let parentRole = isTableDescendant(contextHierarchies); + if (parentRole !== null && parentRole.length > 0) + return null; + } + let implicitRoles = RPTUtil.getImplicitRole(ruleContext); if (!implicitRoles || implicitRoles.length === 0) return RulePass("pass"); diff --git a/accessibility-checker-engine/src/v4/rules/aria_semantics.ts b/accessibility-checker-engine/src/v4/rules/aria_semantics.ts index 23dcbef1b..02bb4a27e 100644 --- a/accessibility-checker-engine/src/v4/rules/aria_semantics.ts +++ b/accessibility-checker-engine/src/v4/rules/aria_semantics.ts @@ -11,11 +11,10 @@ limitations under the License. *****************************************************************************/ -import { Rule, RuleResult, RuleFail, RuleContext, RulePotential, RuleManual, RulePass, RuleContextHierarchy } from "../api/IRule"; +import { Rule, RuleResult, RuleFail, RuleContext, RulePass, RuleContextHierarchy } from "../api/IRule"; import { eRulePolicy, eToolkitLevel } from "../api/IRule"; import { RPTUtil } from "../../v2/checker/accessibility/util/legacy"; -import { ARIADefinitions } from "../../v2/aria/ARIADefinitions"; -import { getCache, setCache } from "../util/CacheUtil"; +import { getInvalidAriaAttributes, areRolesDefined, isTableDescendant, getInvalidRoles } from "../util/CommonUtil"; export let aria_semantics_role: Rule = { id: "aria_semantics_role", @@ -52,68 +51,39 @@ export let aria_semantics_role: Rule = { return null; // dependency check: if it's already failed, then skip - if (["td", "th", "tr"].includes(tagName) && getCache(ruleContext, "table_aria_descendants", "") === "explicit_role") - return null; - + if (["td", "th", "tr"].includes(tagName)) { + let parentRole = isTableDescendant(contextHierarchies); + if (parentRole !== null && parentRole.length > 0) + return null; + } + let domRoles: string[] = RPTUtil.getUserDefinedRoles(ruleContext); + if (!domRoles || domRoles.length ===0) + return null; // check the 'generic' role first - if (domRoles && domRoles.includes('generic')) { - setCache(ruleContext, "aria_semantics_role", "Fail_1"); + if (domRoles.includes('generic')) return RuleFail("Fail_1", ["generic", tagName]); - } // the invalid role case: handled by Rpt_Aria_ValidRole. Ignore to avoid duplicated report - let designPatterns = ARIADefinitions.designPatterns; - for (const role of domRoles) - if (!(role.toLowerCase() in designPatterns)) - return null; - // Roles allowed on this node - let allowedRoles = []; - - // Failing roles - let failRoleTokens = []; - // Passing roles - let passRoleTokens = []; - - let tagProperty = RPTUtil.getElementAriaProperty(ruleContext); - allowedRoles = RPTUtil.getAllowedAriaRoles(ruleContext, tagProperty); - // Testing restrictions for each role and adding the corresponding attributes to the allowed attribute list - for (let i = 0; i < domRoles.length; i++) { - if (allowedRoles.length === 0) { - if (!failRoleTokens.includes(domRoles[i])) { - failRoleTokens.push(domRoles[i]); - } - } else if (!allowedRoles.includes("any")) { // any role is valid so no checking here. the validity of the aria role is checked by Rpt_Aria_ValidRole - if (!allowedRoles.includes(domRoles[i])) { - if (!failRoleTokens.includes(domRoles[i])) { - failRoleTokens.push(domRoles[i]); - } - } else if (!passRoleTokens.includes(domRoles[i])) { - passRoleTokens.push(domRoles[i]) - } - } else if (allowedRoles.includes("any")) { - if (domRoles[i] === 'generic' && failRoleTokens.indexOf(domRoles[i]) === -1) { - failRoleTokens.push(domRoles[i]); - } else { - if (passRoleTokens.indexOf(domRoles[i]) === -1) - passRoleTokens.push(domRoles[i]); - } - } - } // for loop - if (failRoleTokens.includes("presentation") || failRoleTokens.includes("none") && RPTUtil.isTabbable(ruleContext)) { - return RuleFail("Fail_2", [failRoleTokens.join(", "), tagName]); - } else if (failRoleTokens.length > 0) { - setCache(ruleContext, "aria_semantics_role", "Fail_1"); - return RuleFail("Fail_1", [failRoleTokens.join(", "), tagName]); - } else if (passRoleTokens.length > 0) { - return RulePass("Pass_0", [passRoleTokens.join(", "), tagName]); - } else { + let role_defined = areRolesDefined(domRoles); + if (!role_defined) return null; - } + + let invalidRoles = getInvalidRoles(ruleContext); + if (invalidRoles === null || invalidRoles.length ===0) + return RulePass("Pass_0", [domRoles.join(", "), tagName]); - // below for listing all allowed role and attributes. We can delete it if we are not using it next year (2018) #283 - // return new ValidationResult(passed, [ruleContext], '', '', passed == true ? [] : [roleOrAttributeTokens, tagName, allowedRoleOrAttributeTokens]); + if (invalidRoles.includes("presentation") || invalidRoles.includes("none") && RPTUtil.isTabbable(ruleContext)) + return RuleFail("Fail_2", [invalidRoles.join(", "), tagName]); + + if (invalidRoles.length > 0) + return RuleFail("Fail_1", [invalidRoles.join(", "), tagName]); + + if (domRoles.length > 0) + return RulePass("Pass_0", [domRoles.join(", "), tagName]); + + return null; } } @@ -124,7 +94,7 @@ export let aria_attribute_allowed: Rule = { id: "aria_attribute_allowed", context: "dom:*", // The the ARIA role is completely invalid, skip this check - dependencies: ["aria_semantics_role"], + dependencies: ["aria_attribute_deprecated", "aria_semantics_role"], help: { "en-US": { "group": "aria_attribute_allowed.html", @@ -154,59 +124,35 @@ export let aria_attribute_allowed: Rule = { if (ruleContext.nodeType !== Node.ELEMENT_NODE) return null; - // get roles from RPTUtil because multiple explicit roles are allowed - let roles = RPTUtil.getRoles(ruleContext, false); - - // the invalid role (not defined in the spec) case: handled by Rpt_Aria_ValidRole. Ignore to avoid duplicated report - // for mutiple roles, skip if any role is invalid - let designPatterns = ARIADefinitions.designPatterns; - for (const role of roles) - if (!(role.toLowerCase() in designPatterns)) + // ignore if no aria attribute + let ariaAttributes:string[] = RPTUtil.getUserDefinedAriaAttributes(ruleContext); + if (ariaAttributes === null || ariaAttributes.length === 0) + return null; + + let roles: string[] = RPTUtil.getUserDefinedRoles(ruleContext); + let explicit = true; + if (roles && roles.length > 0) { + // the invalid role case: handled by Rpt_Aria_ValidRole. Ignore to avoid duplicated report + if (!areRolesDefined(roles)) return null; - - let type = ""; - if (roles && roles.length > 0) - type = "role_attr"; - else { + } else { + //no explicit role defined roles = RPTUtil.getImplicitRole(ruleContext); - if (roles && roles.length > 0) - type = "implicit_role_attr"; - + explicit = false; } + let tagName = ruleContext.tagName.toLowerCase(); + let failedAttributes = getInvalidAriaAttributes(ruleContext); + if (!failedAttributes || failedAttributes.length === 0) + return RulePass("Pass", [ariaAttributes.join(", "), tagName, roles.join(", ")]); - // Failing attributes - let failAttributeTokens = []; - // Passing attributes - let passAttributeTokens = []; - - let tagProperty = RPTUtil.getElementAriaProperty(ruleContext); - // Attributes allowed on this node - let allowedAttributes = RPTUtil.getAllowedAriaAttributes(ruleContext, roles, tagProperty); - let domAriaAttributes = RPTUtil.getUserDefinedAriaAttributes(ruleContext); - for (let i = 0; i < domAriaAttributes.length; i++) { - if (!allowedAttributes.includes(domAriaAttributes[i])) { - //valid attributes can be none also which is covered here - !failAttributeTokens.includes(domAriaAttributes[i]) ? failAttributeTokens.push(domAriaAttributes[i]) : false; - } else { - !passAttributeTokens.includes(domAriaAttributes[i]) ? passAttributeTokens.push(domAriaAttributes[i]) : false; - } + if (roles.length > 0) { + if (explicit) + return RuleFail("Fail_invalid_role_attr", [failedAttributes.join(", "), tagName, roles.join(", ")]); + else + return RuleFail("Fail_invalid_implicit_role_attr", [failedAttributes.join(", "), tagName, roles.join(", ")]); } - if (failAttributeTokens.length > 0) { - - setCache(ruleContext, "aria_attribute_allowed", "Fail"); - if (type === "implicit_role_attr") - return RuleFail("Fail_invalid_implicit_role_attr", [failAttributeTokens.join(", "), tagName, roles.join(", ")]); - else { - if (!roles || roles.length === 0) roles = ["none"]; - return RuleFail("Fail_invalid_role_attr", [failAttributeTokens.join(", "), tagName, roles.join(", ")]); - } - - } else if (passAttributeTokens.length > 0) { - return RulePass("Pass", [passAttributeTokens.join(", "), tagName, roles.join(", ")]); - } else { - return null; - } + return RuleFail("Fail_invalid_role_attr", [failedAttributes.join(", "), tagName, "none"]); } } \ No newline at end of file diff --git a/accessibility-checker-engine/src/v4/rules/table_aria_descendants.ts b/accessibility-checker-engine/src/v4/rules/table_aria_descendants.ts index 87f36df50..912a70801 100644 --- a/accessibility-checker-engine/src/v4/rules/table_aria_descendants.ts +++ b/accessibility-checker-engine/src/v4/rules/table_aria_descendants.ts @@ -11,9 +11,10 @@ limitations under the License. *****************************************************************************/ -import { Rule, RuleResult, RuleFail, RuleContext, RulePotential, RuleManual, RulePass, RuleContextHierarchy } from "../api/IRule"; +import { Rule, RuleResult, RuleFail, RuleContext, RuleContextHierarchy } from "../api/IRule"; import { eRulePolicy, eToolkitLevel } from "../api/IRule"; import { setCache } from "../util/CacheUtil"; +import { isTableDescendant } from "../util/CommonUtil"; export let table_aria_descendants: Rule = { id: "table_aria_descendants", @@ -38,10 +39,12 @@ export let table_aria_descendants: Rule = { }], act: [], run: (context: RuleContext, options?: {}, contextHierarchies?: RuleContextHierarchy): RuleResult | RuleResult[] => { - const ruleContext = context["dom"].node as Element; - let parentRole = contextHierarchies["aria"].filter(hier => ["table", "grid", "treegrid"].includes(hier.role)); + const ruleContext = context["dom"].node as Element; + let parentRole = isTableDescendant(contextHierarchies); // cache the result - setCache(ruleContext, "table_aria_descendants", "explicit_role"); + if (parentRole === null || parentRole.length === 0) + return; + return RuleFail("explicit_role", [context["dom"].node.nodeName.toLowerCase(), parentRole[0].role]); } } \ No newline at end of file diff --git a/accessibility-checker-engine/src/v4/util/CommonUtil.ts b/accessibility-checker-engine/src/v4/util/CommonUtil.ts new file mode 100644 index 000000000..0cdbf8d8b --- /dev/null +++ b/accessibility-checker-engine/src/v4/util/CommonUtil.ts @@ -0,0 +1,139 @@ +/****************************************************************************** + Copyright:: 2022- IBM, Inc + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + http://www.apache.org/licenses/LICENSE-2.0 + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +*****************************************************************************/ + +import {RuleContextHierarchy } from "../api/IRule"; +import { RPTUtil } from "../../v2/checker/accessibility/util/legacy"; +import { ARIADefinitions } from "../../v2/aria/ARIADefinitions"; + +/* + * check if any explicit role specified for the element is a valid ARIA role + * return: null if no explicit role is defined, + * true if the role(s) are defined in ARIA + * false if any role is not defined in ARIA +*/ +export function areRolesDefined(roles: string[]) { + if (!roles || roles.length ===0) return null; + + let designPatterns = ARIADefinitions.designPatterns; + for (const role of roles) + if (!(role.toLowerCase() in designPatterns)) + return false; + + return true; +} + +/* + * check if any explicit role specified for the element is a valid ARIA role + * return: null if no explicit role is defined, + * true if the role(s) are defined in ARIA + * false if any role is not defined in ARIA +*/ +export function getInvalidRoles(ruleContext: Element) { + let domRoles: string[] = RPTUtil.getUserDefinedRoles(ruleContext); + + if (!domRoles || domRoles.length === 0) + return null; + + // check the 'generic' role first + if (domRoles && domRoles.includes('generic')) + return ["generic"]; + + // Failing roles + let failRoleTokens = []; + // Passing roles + let passRoleTokens = []; + + let tagProperty = RPTUtil.getElementAriaProperty(ruleContext); + let allowedRoles = RPTUtil.getAllowedAriaRoles(ruleContext, tagProperty); + if (!allowedRoles && allowedRoles.length === 0) + return domRoles; + + let invalidRoles = []; + + if (allowedRoles && allowedRoles.includes('any')) + return []; + + for (let i = 0; i < domRoles.length; i++) + if (!allowedRoles.includes(domRoles[i]) && !invalidRoles.includes(domRoles[i])) + invalidRoles.push(domRoles[i]); + + return invalidRoles; +} + +/* + * this method first checks explicit roles, if no explicit role, it will check the implicit role + * return: null if any explicit role is invalid, + * a list of invalid attributes + * empty list if all attributes are valid, or no aria attributes are specified + */ +export function getInvalidAriaAttributes(ruleContext: Element): string[] { + let roles = RPTUtil.getUserDefinedRoles(ruleContext); + + // the invalid role case: handled by Rpt_Aria_ValidRole. Ignore to avoid duplicated report + // for mutiple roles, skip if any role is invalid + let defined = areRolesDefined(roles); + if (defined !==null && !defined) + return null; + + let attrs = []; + if (!roles || roles.length == 0) + roles = RPTUtil.getImplicitRole(ruleContext); + + let aria_attrs: string[] = RPTUtil.getUserDefinedAriaAttributes(ruleContext); + + let tagProperty = RPTUtil.getElementAriaProperty(ruleContext); + // Attributes allowed on this node + let allowedAttributes = RPTUtil.getAllowedAriaAttributes(ruleContext, roles, tagProperty); + + if (aria_attrs) { + for (let i = 0; i < aria_attrs.length; i++) { + let attrName = aria_attrs[i].trim().toLowerCase(); + if (!allowedAttributes.includes(attrName) && !attrs.includes(attrName)) + attrs.push(attrName); + } + } + return attrs; +} + +/* + * get conflict Aria and Html attributes + * return: a list of Aria and Html attribute pairs that are conflict +*/ +export function getConflictAriaAndHtmlAttributes(elem: Element) { + + let ariaAttrs = RPTUtil.getUserDefinedAriaAttributeNameValuePairs(elem); + let htmlAttrs = RPTUtil.getUserDefinedHtmlAttributeNameValuePairs(elem); + + let ret = []; + if (ariaAttrs && ariaAttrs.length > 0 && htmlAttrs && htmlAttrs.length > 0) { + for (let i = 0; i < ariaAttrs.length; i++) { + const examinedHtmlAtrNames = RPTUtil.getConflictOrOverlappingHtmlAttribute(ariaAttrs[i], htmlAttrs, 'conflict'); + if (examinedHtmlAtrNames === null) continue; + examinedHtmlAtrNames.forEach(item => { + if (item['result'] === 'Failed') //failed + ret.push({'ariaAttr': ariaAttrs[i]['name'], 'htmlAttr': item['attr']}); + }); + } + } + return ret; +} + +/* + * get conflict Aria and Html attributes + * return: a list of Aria and Html attribute pairs that are conflict +*/ +export function isTableDescendant(contextHierarchies?: RuleContextHierarchy) { + if (!contextHierarchies) return null; + + return contextHierarchies["aria"].filter(hier => ["table", "grid", "treegrid"].includes(hier.role)); +} diff --git a/accessibility-checker-engine/test/v2/checker/accessibility/rules/aria_attribute_conflict_ruleunit/Fail.html b/accessibility-checker-engine/test/v2/checker/accessibility/rules/aria_attribute_conflict_ruleunit/Fail.html index adfdb7057..202a5614f 100755 --- a/accessibility-checker-engine/test/v2/checker/accessibility/rules/aria_attribute_conflict_ruleunit/Fail.html +++ b/accessibility-checker-engine/test/v2/checker/accessibility/rules/aria_attribute_conflict_ruleunit/Fail.html @@ -69,12 +69,7 @@ passedXpaths: [ ], failedXpaths: [ - "/html[1]/body[1]/div[1]", - "/html[1]/body[1]/div[2]", - "/html[1]/body[1]/div[3]", - "/html[1]/body[1]/div[4]", "/html[1]/body[1]/form[1]/input[1]", - "/html[1]/body[1]/form[2]", "/html[1]/body[1]/form[3]/input[1]", "/html[1]/body[1]/form[4]/input[2]" diff --git a/accessibility-checker-engine/test/v2/checker/accessibility/rules/aria_attribute_redundant_ruleunit/Fail.html b/accessibility-checker-engine/test/v2/checker/accessibility/rules/aria_attribute_redundant_ruleunit/Fail.html index 53ce47ddb..e2726667a 100755 --- a/accessibility-checker-engine/test/v2/checker/accessibility/rules/aria_attribute_redundant_ruleunit/Fail.html +++ b/accessibility-checker-engine/test/v2/checker/accessibility/rules/aria_attribute_redundant_ruleunit/Fail.html @@ -120,44 +120,6 @@ ], "apiArgs": [], "category": "Accessibility" - }, - { - "ruleId": "aria_attribute_redundant", - "value": [ - "INFORMATION", - "FAIL" - ], - "path": { - "dom": "/html[1]/body[1]/div[3]", - "aria": "/document[1]" - }, - "reasonId": "fail_redundant", - "message": "The ARIA attribute \"aria-colspan\" is redundant with the HTML attribute \"colspan\"", - "messageArgs": [ - "aria-colspan", - "colspan" - ], - "apiArgs": [], - "category": "Accessibility" - }, - { - "ruleId": "aria_attribute_redundant", - "value": [ - "INFORMATION", - "FAIL" - ], - "path": { - "dom": "/html[1]/body[1]/div[4]", - "aria": "/document[1]" - }, - "reasonId": "fail_redundant", - "message": "The ARIA attribute \"aria-rowspan\" is redundant with the HTML attribute \"rowspan\"", - "messageArgs": [ - "aria-rowspan", - "rowspan" - ], - "apiArgs": [], - "category": "Accessibility" } ] }