-
Notifications
You must be signed in to change notification settings - Fork 867
fix(idref): fallback to qsa #4844
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: develop
Are you sure you want to change the base?
Changes from 5 commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,38 @@ | ||
| import { nodeLookup } from '../../core/utils'; | ||
|
|
||
| /** | ||
| * Return the vNode(s) | ||
| * @method getRootVNodes | ||
| * @memberof axe.commons.dom | ||
| * @instance | ||
| * @param {Element|VirtualNode} node | ||
| * @returns {VirtualNode[]} | ||
| */ | ||
| export default function getRootVNodes(node) { | ||
| const { vNode } = nodeLookup(node); | ||
|
|
||
| if (vNode._rootNodes) { | ||
| return vNode._rootNodes; | ||
| } | ||
|
|
||
| // top of tree | ||
| if (vNode.parent === null) { | ||
| return [vNode]; | ||
| } | ||
|
|
||
| // disconnected tree | ||
| if (!vNode.parent) { | ||
| return undefined; | ||
| } | ||
|
|
||
| // since the virtual tree does not have a #shadowRoot element the root virtual | ||
| // node is the shadow host element. however the shadow host element is not inside | ||
| // the shadow DOM tree so we return the children of the shadow host element in | ||
| // order to not cross shadow DOM boundaries | ||
| if (vNode.shadowId !== vNode.parent.shadowId) { | ||
| return [...vNode.parent.children]; | ||
| } | ||
|
|
||
| vNode._rootNodes = getRootVNodes(vNode.parent); | ||
straker marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
| return vNode._rootNodes; | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,5 +1,10 @@ | ||
| import getRootNode from './get-root-node'; | ||
| import { tokenList } from '../../core/utils'; | ||
| import getRootVNodes from './get-root-vnodes'; | ||
| import { | ||
| tokenList, | ||
| nodeLookup, | ||
| querySelectorAll, | ||
| getRootNode | ||
| } from '../../core/utils'; | ||
|
|
||
| /** | ||
| * Get elements referenced via a space-separated token attribute; | ||
|
|
@@ -18,24 +23,41 @@ import { tokenList } from '../../core/utils'; | |
| * | ||
| */ | ||
| function idrefs(node, attr) { | ||
| node = node.actualNode || node; | ||
| const { domNode, vNode } = nodeLookup(node); | ||
| const results = []; | ||
| const attrValue = vNode ? vNode.attr(attr) : node.getAttribute(attr); | ||
|
|
||
| if (!attrValue) { | ||
| return results; | ||
| } | ||
|
|
||
| try { | ||
| const doc = getRootNode(node); | ||
| const result = []; | ||
| let attrValue = node.getAttribute(attr); | ||
|
|
||
| if (attrValue) { | ||
| attrValue = tokenList(attrValue); | ||
| for (let index = 0; index < attrValue.length; index++) { | ||
| result.push(doc.getElementById(attrValue[index])); | ||
| } | ||
| const root = getRootNode(domNode); | ||
| for (const token of tokenList(attrValue)) { | ||
| results.push(root.getElementById(token)); | ||
| } | ||
|
|
||
| return result; | ||
| } catch { | ||
| throw new TypeError('Cannot resolve id references for non-DOM nodes'); | ||
| const rootVNodes = getRootVNodes(vNode); | ||
|
||
| if (!rootVNodes) { | ||
| throw new TypeError('Cannot resolve id references for non-DOM nodes'); | ||
| } | ||
|
|
||
| for (const token of tokenList(attrValue)) { | ||
| let result = null; | ||
|
|
||
| for (const root of rootVNodes) { | ||
| const foundNode = querySelectorAll(root, `#${token}`)[0]; | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. For shadow root cases, this is going to essentially bypass QSA's selector-cache behavior that would otherwise make the ID lookup fast; we might want to consider making it smarter about that (
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Ya. We originally did the cache on the root to improve rule node selection. We cached the information on the root node since it was the only part that would have a complete picture of the entire tree. We could work around that limit for just id lookups, but it would require somehow passing the desired function getById(id, shadowId) {
const root = axe._tree;
return getNodesMatchingExpression(root, id, () => {}, shadowId)[0]
}
// selector-cache.js
export function getNodesMatchingExpression(domTree, expressions, filter, shadowId) {
shadowId = shadowId ?? domTree[0].shadowId;
} |
||
| if (foundNode) { | ||
| result = foundNode; | ||
| break; | ||
| } | ||
| } | ||
|
|
||
| results.push(result); | ||
| } | ||
| } | ||
|
|
||
| return results; | ||
| } | ||
|
|
||
| export default idrefs; | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,5 +1,5 @@ | ||
| import accessibleTextVirtual from './accessible-text-virtual'; | ||
| import { getNodeFromTree } from '../../core/utils'; | ||
| import { nodeLookup } from '../../core/utils'; | ||
|
|
||
| /** | ||
| * Finds virtual node and calls accessibleTextVirtual() | ||
|
|
@@ -12,8 +12,8 @@ import { getNodeFromTree } from '../../core/utils'; | |
| * @return {string} | ||
| */ | ||
| function accessibleText(element, context) { | ||
| const virtualNode = getNodeFromTree(element); // throws an exception on purpose if axe._tree not correct | ||
|
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This comment is not true as |
||
| return accessibleTextVirtual(virtualNode, context); | ||
| const { vNode } = nodeLookup(element); | ||
| return accessibleTextVirtual(vNode, context); | ||
| } | ||
|
|
||
| export default accessibleText; | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,27 @@ | ||
| describe('dom.getRootVNodes', () => { | ||
| const getRootVNodes = axe.commons.dom.getRootVNodes; | ||
| const fixture = document.querySelector('#fixture'); | ||
| const queryShadowFixture = axe.testUtils.queryShadowFixture; | ||
|
|
||
| it('should return the root vNode of complete tree', () => { | ||
| axe.setup(); | ||
| const expected = [axe.utils.getNodeFromTree(document.documentElement)]; | ||
| assert.deepEqual(getRootVNodes(fixture), expected); | ||
| }); | ||
|
|
||
| it('should return undefined for disconnected tree', () => { | ||
| axe.setup(); | ||
| axe.utils.getNodeFromTree(document.documentElement).parent = undefined; | ||
| assert.isUndefined(getRootVNodes(fixture)); | ||
| }); | ||
|
|
||
| it('should return each child of a shadow DOM host', () => { | ||
| const target = queryShadowFixture( | ||
| '<div id="shadow"></div>', | ||
| '<div id="target">Hello World</div><div id="child1"></div><div id="child2"></div>' | ||
| ); | ||
|
|
||
| const expected = target.parent.children; | ||
| assert.deepEqual(getRootVNodes(target), expected); | ||
| }); | ||
| }); |
Uh oh!
There was an error while loading. Please reload this page.