diff --git a/lib/core/constants.js b/lib/core/constants.js index 3880c0a1..71918e37 100644 --- a/lib/core/constants.js +++ b/lib/core/constants.js @@ -28,6 +28,7 @@ const definitions = [ const constants = { helpUrlBase: 'https://dequeuniversity.com/rules/', gridSize: 200, + selectorSimilarFilterLimit: 700, results: [], resultGroups: [], resultGroupMap: {}, diff --git a/lib/core/utils/get-selector.js b/lib/core/utils/get-selector.js index 17ffdb0f..67b81438 100644 --- a/lib/core/utils/get-selector.js +++ b/lib/core/utils/get-selector.js @@ -4,6 +4,8 @@ import getNodeAttributes from './get-node-attributes'; import matchesSelector from './element-matches'; import isXHTML from './is-xhtml'; import getShadowSelector from './get-shadow-selector'; +import memoize from './memoize'; +import constants from '../../core/constants'; const ignoredAttributes = [ 'class', @@ -376,8 +378,8 @@ function generateSelector(elm, options, doc) { } else { selector = features; } - if (!similar) { - similar = Array.from(doc.querySelectorAll(selector)); + if (!similar || similar.length > constants.selectorSimilarFilterLimit) { + similar = findSimilar(doc, selector); } else { similar = similar.filter(item => { return matchesSelector(item, selector); @@ -401,6 +403,16 @@ function generateSelector(elm, options, doc) { * @param {Object} optional options * @returns {String|Array} Unique CSS selector for the node */ -export default function getSelector(elm, options) { +function getSelector(elm, options) { return getShadowSelector(generateSelector, elm, options); } + +// Axe can call getSelector more than once for the same element because +// the same element can end up on multiple DqElements. +export default memoize(getSelector); + +// Similar elements create similar selectors. If there are lots of similar elements on the page, +// axe ends up needing to run that same selector many times. We can memoize for a huge perf boost. +const findSimilar = memoize((doc, selector) => + Array.from(doc.querySelectorAll(selector)) +);