diff --git a/lib/commons/aria/get-accessible-refs.js b/lib/commons/aria/get-accessible-refs.js
index 1f0ed55d57..9ad5627f03 100644
--- a/lib/commons/aria/get-accessible-refs.js
+++ b/lib/commons/aria/get-accessible-refs.js
@@ -13,22 +13,26 @@ function cacheIdRefs(node, idRefs, refAttrs) {
if (node.hasAttribute) {
if (node.nodeName.toUpperCase() === 'LABEL' && node.hasAttribute('for')) {
const id = node.getAttribute('for');
- idRefs[id] = idRefs[id] || [];
- idRefs[id].push(node);
+ if (!idRefs.has(id)) {
+ idRefs.set(id, [node]);
+ } else {
+ idRefs.get(id).push(node);
+ }
}
for (let i = 0; i < refAttrs.length; ++i) {
const attr = refAttrs[i];
const attrValue = sanitize(node.getAttribute(attr) || '');
-
if (!attrValue) {
continue;
}
- const tokens = tokenList(attrValue);
- for (let k = 0; k < tokens.length; ++k) {
- idRefs[tokens[k]] = idRefs[tokens[k]] || [];
- idRefs[tokens[k]].push(node);
+ for (const token of tokenList(attrValue)) {
+ if (!idRefs.has(token)) {
+ idRefs.set(token, [node]);
+ } else {
+ idRefs.get(token).push(node);
+ }
}
}
}
@@ -50,22 +54,21 @@ function getAccessibleRefs(node) {
let root = getRootNode(node);
root = root.documentElement || root; // account for shadow roots
- const idRefsByRoot = cache.get('idRefsByRoot', () => new WeakMap());
+ const idRefsByRoot = cache.get('idRefsByRoot', () => new Map());
let idRefs = idRefsByRoot.get(root);
if (!idRefs) {
- idRefs = {};
+ idRefs = new Map();
idRefsByRoot.set(root, idRefs);
const refAttrs = Object.keys(standards.ariaAttrs).filter(attr => {
const { type } = standards.ariaAttrs[attr];
return idRefsRegex.test(type);
});
-
cacheIdRefs(root, idRefs, refAttrs);
}
- return idRefs[node.id] || [];
+ return idRefs.get(node.id) ?? [];
}
export default getAccessibleRefs;
diff --git a/lib/core/utils/find-by.js b/lib/core/utils/find-by.js
index ee84ecdb35..252a0c1903 100644
--- a/lib/core/utils/find-by.js
+++ b/lib/core/utils/find-by.js
@@ -9,7 +9,13 @@
*/
function findBy(array, key, value) {
if (Array.isArray(array)) {
- return array.find(obj => typeof obj === 'object' && obj[key] === value);
+ return array.find(
+ obj =>
+ obj !== null &&
+ typeof obj === 'object' &&
+ Object.hasOwn(obj, key) &&
+ obj[key] === value
+ );
}
}
diff --git a/lib/core/utils/selector-cache.js b/lib/core/utils/selector-cache.js
index 5f81700678..1771c89c76 100644
--- a/lib/core/utils/selector-cache.js
+++ b/lib/core/utils/selector-cache.js
@@ -91,9 +91,12 @@ function findMatchingNodes(expression, selectorMap, shadowId) {
nodes = selectorMap['*'];
} else {
if (exp.id) {
- // a selector must match all parts, otherwise we can just exit
- // early
- if (!selectorMap[idsKey] || !selectorMap[idsKey][exp.id]?.length) {
+ // a selector must match all parts, otherwise we can just exit early
+ if (
+ !selectorMap[idsKey] ||
+ !Object.hasOwn(selectorMap[idsKey], exp.id) ||
+ !selectorMap[idsKey][exp.id]?.length
+ ) {
return;
}
@@ -176,7 +179,9 @@ function getSharedValues(a, b) {
* @param {Object} map
*/
function cacheSelector(key, vNode, map) {
- map[key] = map[key] || [];
+ if (!Object.hasOwn(map, key)) {
+ map[key] = [];
+ }
map[key].push(vNode);
}
diff --git a/lib/standards/html-elms.js b/lib/standards/html-elms.js
index 2ccb779c7c..1b75a8bbb8 100644
--- a/lib/standards/html-elms.js
+++ b/lib/standards/html-elms.js
@@ -358,7 +358,7 @@ const htmlElms = {
'menuitem',
'menuitemcheckbox',
'menuitemradio',
- 'meter',
+ 'meter',
'option',
'progressbar',
'radio',
diff --git a/test/commons/aria/get-accessible-refs.js b/test/commons/aria/get-accessible-refs.js
index 57941a5336..00820526e3 100644
--- a/test/commons/aria/get-accessible-refs.js
+++ b/test/commons/aria/get-accessible-refs.js
@@ -80,6 +80,34 @@ describe('aria.getAccessibleRefs', function () {
assert.deepEqual(getAccessibleRefs(node), [ref]);
});
+ describe('when JavaScript object names are used as IDs', function () {
+ const ids = [
+ 'prototype',
+ 'constructor',
+ '__proto__',
+ 'Element',
+ 'nodeName',
+ 'valueOf',
+ 'toString'
+ ];
+ for (const id of ids) {
+ it(`does not break with id="${id}"`, function () {
+ setLookup({ 'aria-bar': { type: 'idrefs' } });
+ fixture.innerHTML = `
`;
+
+ var node = document.getElementById(id);
+ var ref = document.getElementById('ref');
+ assert.deepEqual(
+ getAccessibleRefs(node),
+ [ref],
+ `Not equal for ID ${id}`
+ );
+ });
+ }
+ });
+
(shadowSupport ? it : xit)('works inside shadow DOM', function () {
setLookup({ 'aria-bar': { type: 'idref' } });
fixture.innerHTML = '';
diff --git a/test/core/utils/find-by.js b/test/core/utils/find-by.js
index 79911a6ff8..e6482fcec3 100644
--- a/test/core/utils/find-by.js
+++ b/test/core/utils/find-by.js
@@ -40,4 +40,21 @@ describe('axe.utils.findBy', function () {
it('should not throw if passed falsey first parameter', function () {
assert.isUndefined(axe.utils.findBy(null, 'id', 'macaque'));
});
+
+ it('ignores any non-object elements in the array', function () {
+ const obj = {
+ id: 'monkeys',
+ foo: 'bar'
+ };
+ const array = ['bananas', true, null, 123, obj];
+
+ assert.equal(axe.utils.findBy(array, 'id', 'monkeys'), obj);
+ });
+
+ it('only looks at owned properties', function () {
+ const obj1 = { id: 'monkeys', eat: 'bananas' };
+ const obj2 = Object.create(obj1);
+ obj2.id = 'gorillas';
+ assert.equal(axe.utils.findBy([obj2, obj1], 'eat', 'bananas'), obj1);
+ });
});
diff --git a/test/core/utils/selector-cache.js b/test/core/utils/selector-cache.js
index 9e6e70deeb..7a06c5288e 100644
--- a/test/core/utils/selector-cache.js
+++ b/test/core/utils/selector-cache.js
@@ -55,6 +55,29 @@ describe('utils.selector-cache', function () {
assert.lengthOf(Object.keys(map), 0);
});
+
+ describe('with javascripty attribute selectors', function () {
+ const terms = [
+ 'prototype',
+ 'constructor',
+ '__proto__',
+ 'Element',
+ 'nodeName',
+ 'valueOf',
+ 'toString'
+ ];
+ for (const term of terms) {
+ it(`works with ${term}`, function () {
+ fixture.innerHTML = ``;
+ const vNode = new axe.VirtualNode(fixture.firstChild);
+ const map = {};
+ cacheNodeSelectors(vNode, map);
+ assert.deepEqual(map['[id]'], [vNode]);
+ assert.deepEqual(map['[class]'], [vNode]);
+ assert.deepEqual(map['[aria-label]'], [vNode]);
+ });
+ }
+ });
});
describe('getNodesMatchingExpression', function () {
diff --git a/test/integration/full/all-rules/all-rules.html b/test/integration/full/all-rules/all-rules.html
index 05c32b4ad4..ec5a5af5c1 100644
--- a/test/integration/full/all-rules/all-rules.html
+++ b/test/integration/full/all-rules/all-rules.html
@@ -28,11 +28,11 @@
>
-
+
- Foo
+ Foo
Home
@@ -51,7 +51,7 @@
Item
-
+
Banana error
diff --git a/test/integration/rules/aria-allowed-role/aria-allowed-role.html b/test/integration/rules/aria-allowed-role/aria-allowed-role.html
index 24e4f8ba48..46f11eb1b2 100644
--- a/test/integration/rules/aria-allowed-role/aria-allowed-role.html
+++ b/test/integration/rules/aria-allowed-role/aria-allowed-role.html
@@ -246,7 +246,15 @@
ok
ok
-
+
hazaar