From 62f5d750fcab4812dfdee019bc57d9e87eeddd40 Mon Sep 17 00:00:00 2001 From: Steven Lambert <2433219+straker@users.noreply.github.com> Date: Mon, 5 May 2025 15:05:10 -0600 Subject: [PATCH 1/2] faster selectors! --- lib/core/utils/get-selector.js | 290 +++++++++++++++++++++++++++++---- test/playground.html | 286 ++++++++++++++++++++++++++++++-- test/run.js | 100 ++++++++++++ test/time.xlsx | Bin 0 -> 17092 bytes 4 files changed, 629 insertions(+), 47 deletions(-) create mode 100755 test/run.js create mode 100644 test/time.xlsx diff --git a/lib/core/utils/get-selector.js b/lib/core/utils/get-selector.js index 16e156f5fa..c0e2450ab6 100644 --- a/lib/core/utils/get-selector.js +++ b/lib/core/utils/get-selector.js @@ -6,6 +6,7 @@ import isXHTML from './is-xhtml'; import getShadowSelector from './get-shadow-selector'; import memoize from './memoize'; import constants from '../../core/constants'; +import getNodeFromTree from './get-node-from-tree'; const ignoredAttributes = [ 'class', @@ -104,7 +105,10 @@ export function getSelectorData(domTree) { const data = { classes: {}, tags: {}, - attributes: {} + attributes: {}, + frequency: {}, + newFreq: {}, + otherFreq: {}, }; domTree = Array.isArray(domTree) ? domTree : [domTree]; @@ -113,6 +117,8 @@ export function getSelectorData(domTree) { while (currentLevel.length) { const current = currentLevel.pop(); const node = current.actualNode; + const values = {}; + const newVals = []; if (!!node.querySelectorAll) { // ignore #text nodes @@ -125,10 +131,24 @@ export function getSelectorData(domTree) { data.tags[tag] = 1; } + data.otherFreq[tag] ??= {}; + + if (node.id) { + values[`#${node.id}`] = 1; + newVals.push(`#${node.id}`) + + data.otherFreq[tag][`#${node.id}`] ??= new Set(); + data.otherFreq[tag][`#${node.id}`].add(node); + } + // count all the classes if (node.classList) { Array.from(node.classList).forEach(cl => { const ind = escapeSelector(cl); + values[`.${ind}`] = 1 + newVals.push(`.${ind}`) + data.otherFreq[tag][`.${ind}`] ??= new Set(); + data.otherFreq[tag][`.${ind}`].add(node); if (data.classes[ind]) { data.classes[ind]++; } else { @@ -144,6 +164,11 @@ export function getSelectorData(domTree) { .forEach(at => { const atnv = getAttributeNameValue(node, at); if (atnv) { + values[`[${atnv}]`] = 1; + newVals.push(`[${atnv}]`) + data.otherFreq[tag][`[${atnv}]`] ??= new Set(); + data.otherFreq[tag][`[${atnv}]`].add(node); + if (data.attributes[atnv]) { data.attributes[atnv]++; } else { @@ -152,6 +177,11 @@ export function getSelectorData(domTree) { } }); } + + data.frequency[tag] ??= new Map(); + data.frequency[tag].set(node, values); + data.newFreq[tag] ??= new Map(); + data.newFreq[tag].set(node, newVals); } if (current.children.length) { // "recurse" @@ -165,6 +195,175 @@ export function getSelectorData(domTree) { return data; } +function generateFrequency(nodes) { + const ret = {}; + + nodes.forEach(node => { + // count the tag + const tag = node.nodeName; + if (node.id) { + ret[`#${node.id}`] ??= []; + ret[`#${node.id}`].push(node); + } + + // count all the classes + if (node.classList) { + Array.from(node.classList).forEach(cl => { + const ind = escapeSelector(cl); + ret[`.${ind}`] ??= []; + ret[`.${ind}`].push(node); + }); + } + + // count all the filtered attributes + if (node.hasAttributes()) { + Array.from(node.attributes) + .filter(filterAttributes) + .forEach(at => { + const atnv = getAttributeNameValue(node, at); + if (atnv) { + ret[`[${atnv}]`] ??= []; + ret[`[${atnv}]`].push(node); + } + }); + } + }); + + return ret; +} + +function leastFrequent(node, freqList) { + // shadow dom stuff causes problems + if (!freqList) { + return { + selector: '', + unique: false + } + } + + let selector = node.nodeName.toLowerCase(); + + // unique by default + if (selector === 'body' || selector === 'html') { + return { + selector, + unique: true + } + } + + const values = freqList.get(node); + let freq = axe._selectorData.otherFreq[node.nodeName]; + let min = Infinity; + + while (min > 1) { + let group; + let value = ''; + for (let i = 0; i < values.length; i++) { + const count = freq[ values[i] ].size + if (count < min) { + min = count; + value = values[i]; + group = freq[ values[i] ]; + } + } + + selector += value; + + // TODO: does 1 more path + if (!group) { + return { + selector, + unique: false + }; + } + + if (min > 1) { + const newFreq = {} + for (let i = 0; i < values.length; i++) { + if (values[i] === value) { + newFreq[ values[i] ] = freq[ values[i] ] + } + else { + newFreq[ values[i] ] = freq[ values[i] ].intersection( freq[value] ) + } + } + freq = newFreq; + } + } + + return { + selector, + unique: true + }; +} + +// function leastFrequent(node, feqList) { +// let selector = ''; + +// // Insert all elements in hash. +// const map = new Map(feqList); +// const keys = JSON.parse(JSON.stringify(map.get(node))) + +// let min_count = Infinity; +// while (min_count > 1) { +// const count = new Map(); + +// map.forEach((values, i) => { +// let hasKey = 0; +// const arr = Object.keys(values).forEach(key => { +// if (!keys[key]) { +// return; +// } + +// hasKey = 1; +// if (count.has(key)) { +// let freq = count.get(key); +// freq++; +// count.set(key, freq); +// } else { +// count.set(key, 1); +// } +// }); + +// if (!hasKey) { +// map.delete(i); +// } +// }) + +// // find min frequency. +// let res = ''; +// for (let [key, val] of count.entries()) { +// if (min_count >= val) { +// res = key; +// min_count = val; +// } +// } + +// selector += res; +// delete keys[res]; + +// if (min_count > 1) { +// map.forEach((keys, i) => { +// if (!keys[res]) { +// map.delete(i) +// } +// }) +// } + +// if (Object.values(keys).length === 0) { +// return { +// selector: getBaseSelector(node) + selector, +// unique: map.size <= 1 +// }; +// } +// } + +// return { +// selector: getBaseSelector(node) + selector, +// unique: true +// } +// } + /** * Given a node and the statistics on class frequency on the page, * return all its uncommon class data sorted in order of decreasing uniqueness @@ -342,6 +541,8 @@ function getThreeLeastCommonFeatures(elm, selectorData) { }, '')); } + + /** * generates a single selector for an element * @param {Element} elm The element for which to generate a selector @@ -350,49 +551,78 @@ function getThreeLeastCommonFeatures(elm, selectorData) { * @returns {String} The selector */ function generateSelector(elm, options, doc) { + const vNode = getNodeFromTree(elm); + if (vNode._selector) { + return vNode._selector; + } + /*eslint no-loop-func:0*/ // TODO: es-modules_selectorData if (!axe._selectorData) { throw new Error('Expect axe._selectorData to be set up'); } const { toRoot = false } = options; - let selector; - let similar; + // let selector; + // let similar; /** * Try to find a unique selector by filtering out all the clashing * nodes by adding ancestor selectors iteratively. * This loop is much faster than recursing and using querySelectorAll */ - do { - let features = getElmId(elm); - if (!features) { - features = getThreeLeastCommonFeatures(elm, axe._selectorData); - features += getNthChildString(elm, features); + // do { + // let features = getElmId(elm); + // if (!features) { + // features = getThreeLeastCommonFeatures(elm, axe._selectorData); + // features += getNthChildString(elm, features); + // } + // // if (selector) { + // // selector = features + ' > ' + selector; + // // } else { + // // selector = features; + // // } + // // If there are too many similar element running QSA again is faster + // const similar = findSimilar(doc, features); + // if (similar.length === 1) { + // vNode._selector = features; + // return features; + // } + const { selector, unique } = leastFrequent(elm, axe._selectorData.newFreq[elm.nodeName]); + + if (unique) { + vNode._selector = selector; + return selector; } - if (selector) { - selector = features + ' > ' + selector; - } else { - selector = features; - } - // If there are too many similar element running QSA again is faster - if (!similar || similar.length > constants.selectorSimilarFilterLimit) { - similar = findSimilar(doc, selector); - } else { - similar = similar.filter(item => { - return matchesSelector(item, selector); - }); + else { + // would probably need to add :nth-child to the end since selector is not guaranteed to be unique + // to its siblings + return generateSelector(elm.parentElement, options, doc) + ' > ' + selector; } - elm = elm.parentElement; - } while ((similar.length > 1 || toRoot) && elm && elm.nodeType !== 11); - - if (similar.length === 1) { - return selector; - } else if (selector.indexOf(' > ') !== -1) { - // For the odd case that document doesn't have a unique selector - return ':root' + selector.substring(selector.indexOf(' > ')); - } - return ':root'; + + // else if (similar.length > 1) { + // return generateSelector(elm.parentElement, options, doc) + ' > ' + features; + // } + + // vNode._selector = ':root'; + // return ':root'; + + // if (!similar || similar.length > constants.selectorSimilarFilterLimit) { + // similar = findSimilar(doc, selector); + // } else { + // similar = similar.filter(item => { + // return matchesSelector(item, selector); + // }); + // } + // elm = elm.parentElement; + // } while ((similar.length > 1 || toRoot) && elm && elm.nodeType !== 11); + + // if (similar.length === 1) { + // return selector; + // } else if (selector.indexOf(' > ') !== -1) { + // // For the odd case that document doesn't have a unique selector + // return ':root' + selector.substring(selector.indexOf(' > ')); + // } + // return ':root'; } /** diff --git a/test/playground.html b/test/playground.html index 29b6d3837d..2a166612d9 100644 --- a/test/playground.html +++ b/test/playground.html @@ -3,27 +3,279 @@ Axe Playground -
-

Hello World

-
+ + + + + + + + diff --git a/test/run.js b/test/run.js new file mode 100755 index 0000000000..ccdce2768c --- /dev/null +++ b/test/run.js @@ -0,0 +1,100 @@ +#!/usr/bin/env node +const playwright = require('playwright'); +const XLSX = require('xlsx'); +const { setTimeout } = require('node:timers/promises'); +const axe = require('../axe.js'); +const { source } = axe; +const path = require('path'); + +const dataAoa = [ + [ + 'Num Nodes', + 'Time nth-child (ms)', + 'Time base (ms)' + ] +]; +const numRuns = 5; +const basePages = { + 0: 'https://stevenklambert.com/writing/encroaching-darkness-postmortem/', + 1_000: 'https://developer.mozilla.org/en-US/docs/Web/API/Document/createElement', + 3_000: 'https://www.amazon.com/', + 5_000: 'https://www.google.com/search?q=hello+world&oq=hello+world', + 7_000: 'https://www.youtube.com/results?search_query=code', + 10_000: 'https://stackoverflow.com/questions/11832914/how-to-round-to-at-most-2-decimal-places-if-necessary', +}; + +const dataNodes = {}; + +(async () => { + const browser = await playwright.chromium.launch({ + headless: true + }); + + try { + const context = await browser.newContext(); + const page = await context.newPage(); + + for (let i = 0; i <= 10_000; i += 1000) { + await perfPage(page, `http://localhost:9876/test/playground.html?nodes=${i}`, i); + } + + for ([numNodes, url] of Object.entries(basePages)) { + await perfPage(page, url, numNodes); + } + + for ([numNodes, times] of Object.entries(dataNodes)) { + dataAoa.push([numNodes, ...times]); + } + save(); + } finally { + console.log(dataNodes); + await browser.close(); + } +})(); + +async function perfPage(page, url, numNodes) { + let total = 0; + + for (let num = 0; num < numRuns; num++) { + await page.goto(url); + await setTimeout(2000); + + const axeExist = await page.evaluate(() => !!window.axe); + if (!axeExist) { + await page.evaluate(source); + await setTimeout(500); + } + + performance.clearMarks(); + performance.clearMeasures(); + performance.mark('start'); + + await page.evaluate(async () => await axe.run()); + + performance.mark('end'); + performance.measure('time', 'start', 'end'); + const { duration } = performance.getEntriesByName('time')[0]; + total += duration; + } + + const average = (total / numRuns).toFixed(2); + dataNodes[numNodes] = dataNodes[numNodes] ?? []; + dataNodes[numNodes].push(average); + console.log(`average of ${numRuns} runs for ${url} and ${numNodes} nodes: ${average}ms`); +} + +function save() { + console.log(dataAoa) + const workbook = XLSX.utils.book_new(); + + const sheet = XLSX.utils.aoa_to_sheet(dataAoa); + XLSX.utils.book_append_sheet( + workbook, + sheet + ); + + XLSX.writeFile( + workbook, + path.join(__dirname, `time.xlsx`) + ); +} \ No newline at end of file diff --git a/test/time.xlsx b/test/time.xlsx new file mode 100644 index 0000000000000000000000000000000000000000..029f4f8d2ef9fc576a0ae1cbe2e978bab468c1d5 GIT binary patch literal 17092 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 literal 0 HcmV?d00001 From 4d6cbfec3e3a33c97397f8db82756b1b2f4eac2f Mon Sep 17 00:00:00 2001 From: Steven Lambert <2433219+straker@users.noreply.github.com> Date: Wed, 7 May 2025 11:17:30 -0600 Subject: [PATCH 2/2] updated proposal + selector timing --- lib/core/reporters/v1.js | 14 +- lib/core/reporters/v2.js | 11 +- lib/core/utils/get-selector.js | 512 ++++++---------------------- lib/core/utils/performance-timer.js | 1 + test/playground.html | 20 +- 5 files changed, 131 insertions(+), 427 deletions(-) diff --git a/lib/core/reporters/v1.js b/lib/core/reporters/v1.js index 10d939cc2f..b976c70847 100644 --- a/lib/core/reporters/v1.js +++ b/lib/core/reporters/v1.js @@ -1,5 +1,5 @@ import { processAggregate, failureSummary } from './helpers'; -import { getEnvironmentData } from '../utils'; +import { getEnvironmentData, performanceTimer } from '../utils'; const v1Reporter = (results, options, callback) => { if (typeof options === 'function') { @@ -7,8 +7,20 @@ const v1Reporter = (results, options, callback) => { options = {}; } const { environmentData, ...toolOptions } = options; + + if (options.performanceTimer) { + performanceTimer.mark('generateSelector:start'); + } + const out = processAggregate(results, options); + if (options.performanceTimer) { + performanceTimer.mark('generateSelector:end'); + performanceTimer.measure('generateSelector', 'generateSelector:start', 'generateSelector:end'); + const req = performance.getEntriesByName('generateSelector')[0]; + console.log('Measure ' + req.name + ' took ' + req.duration + 'ms'); + } + const addFailureSummaries = result => { result.nodes.forEach(nodeResult => { nodeResult.failureSummary = failureSummary(nodeResult); diff --git a/lib/core/reporters/v2.js b/lib/core/reporters/v2.js index 4597ca109b..f975dd526f 100644 --- a/lib/core/reporters/v2.js +++ b/lib/core/reporters/v2.js @@ -1,5 +1,5 @@ import { processAggregate } from './helpers'; -import { getEnvironmentData } from '../utils'; +import { getEnvironmentData, performanceTimer } from '../utils'; const v2Reporter = (results, options, callback) => { if (typeof options === 'function') { @@ -7,7 +7,16 @@ const v2Reporter = (results, options, callback) => { options = {}; } const { environmentData, ...toolOptions } = options; + if (options.performanceTimer) { + performance.mark('generateSelector:start'); + } + const out = processAggregate(results, options); + + if (options.performanceTimer) { + performance.mark('generateSelector:end'); + performance.measure('generateSelector', 'generateSelector:start', 'generateSelector:end'); + } callback({ ...getEnvironmentData(environmentData), toolOptions, diff --git a/lib/core/utils/get-selector.js b/lib/core/utils/get-selector.js index c0e2450ab6..13669f7ef6 100644 --- a/lib/core/utils/get-selector.js +++ b/lib/core/utils/get-selector.js @@ -75,10 +75,6 @@ function getAttributeNameValue(node, at) { return atnv; } -function countSort(a, b) { - return a.count < b.count ? -1 : a.count === b.count ? 0 : 1; -} - /** * Filter the attributes * @param {Attribute} The potential attribute @@ -103,12 +99,12 @@ function filterAttributes(at) { export function getSelectorData(domTree) { // Initialize the return structure with the three maps const data = { - classes: {}, - tags: {}, - attributes: {}, - frequency: {}, - newFreq: {}, - otherFreq: {}, + // map of DOM nodes to a list of its classes and attributes + // e.g. { @@ -14,17 +14,17 @@