@@ -35,11 +35,15 @@ import { isElement } from "./utilities-iselement.js";
3535import { getPowerSet , powerSetGenerator } from "./utilities-powerset.js" ;
3636import { cartesianProductGenerator } from "./utilities-cartesian.js" ;
3737
38- export const ESCAPED_COLON : string = ":" . charCodeAt ( 0 ) . toString ( 16 ) . toUpperCase ( ) ;
38+ export const ESCAPED_COLON : string = ":"
39+ . charCodeAt ( 0 )
40+ . toString ( 16 )
41+ . toUpperCase ( ) ;
3942
4043// Square brackets need to be escaped, but eslint has a problem with that.
4144/* eslint-disable-next-line no-useless-escape */
42- export const SPECIAL_CHARACTERS_RE : RegExp = / [ ! " # $ % & ' ( ) \[ \] { | } < > * + , . / ; = ? @ ^ ` ~ \\ ] / ;
45+ export const SPECIAL_CHARACTERS_RE : RegExp =
46+ / [ ! " # $ % & ' ( ) \[ \] { | } < > * + , . / ; = ? @ ^ ` ~ \\ ] / ;
4347
4448/**
4549 * Escapes special characters used by CSS selector items.
@@ -87,7 +91,10 @@ export const SELECTOR_TYPE_GETTERS: Record<
8791
8892export const ELEMENT_SELECTOR_TYPE_GETTERS : Record <
8993 CssSelectorType ,
90- ( element : Element , options ?: CssSelectorGeneratorOptions ) => CssSelectorGenerated [ ]
94+ (
95+ element : Element ,
96+ options ?: CssSelectorGeneratorOptions ,
97+ ) => CssSelectorGenerated [ ]
9198> = {
9299 tag : getElementTagSelectors ,
93100 id : getElementIdSelectors ,
@@ -151,15 +158,43 @@ export function orderSelectors(
151158 } ) ;
152159}
153160
161+ export type SelectorsListCache = (
162+ elements : Element [ ] ,
163+ options : CssSelectorGeneratorOptions ,
164+ ) => CssSelectorData ;
165+
166+ /**
167+ * Creates a cache for `getSelectorsList` results, scoped to a single selector-generation run.
168+ */
169+ export function createSelectorsListCache ( ) : SelectorsListCache {
170+ const cache = new Map < Element , CssSelectorData > ( ) ;
171+ let multiElementResult : CssSelectorData | undefined ;
172+
173+ return ( elements , options ) => {
174+ if ( elements . length !== 1 ) {
175+ // multi-element needle is always the same array reference within one generator run
176+ return ( multiElementResult ??= getSelectorsList ( elements , options ) ) ;
177+ }
178+ const [ element ] = elements ;
179+ let result = cache . get ( element ) ;
180+ if ( ! result ) {
181+ result = getSelectorsList ( elements , options ) ;
182+ cache . set ( element , result ) ;
183+ }
184+ return result ;
185+ } ;
186+ }
187+
154188/**
155189 * Yields list of unique selectors applicable to given element.
156190 */
157191export function * allSelectorsGenerator (
158192 elements : Element [ ] ,
159193 options : CssSelectorGeneratorOptions ,
194+ selectorsListCache : SelectorsListCache = createSelectorsListCache ( ) ,
160195) : IterableIterator < CssSelector > {
161196 const yieldedSelectors = new Set < string > ( ) ;
162- const selectors_list = getSelectorsList ( elements , options ) ;
197+ const selectors_list = selectorsListCache ( elements , options ) ;
163198 for ( const selector of selectorTypeCombinationsGenerator (
164199 selectors_list ,
165200 options ,
@@ -185,7 +220,11 @@ export function getSelectorsList(
185220 const matchWhitelist = createPatternMatcher ( whitelist ) ;
186221
187222 const reducer = ( data : CssSelectorData , selector_type : CssSelectorType ) => {
188- const selectors_by_type = getSelectorsByType ( elements , selector_type , options ) ;
223+ const selectors_by_type = getSelectorsByType (
224+ elements ,
225+ selector_type ,
226+ options ,
227+ ) ;
189228 const filtered_selectors = filterSelectors (
190229 selectors_by_type ,
191230 matchBlacklist ,
@@ -358,8 +397,13 @@ export function* selectorWithinRootGenerator(
358397 root : ParentNode ,
359398 rootSelector : CssSelector | undefined = "" ,
360399 options : CssSelectorGeneratorOptions ,
400+ selectorsListCache : SelectorsListCache = createSelectorsListCache ( ) ,
361401) : IterableIterator < CssSelector , undefined > {
362- const elementSelectorsIterator = allSelectorsGenerator ( elements , options ) ;
402+ const elementSelectorsIterator = allSelectorsGenerator (
403+ elements ,
404+ options ,
405+ selectorsListCache ,
406+ ) ;
363407 for ( const candidateSelector of candidatesGenerator (
364408 elementSelectorsIterator ,
365409 rootSelector ,
@@ -379,6 +423,7 @@ export function* closestIdentifiableParentGenerator(
379423 root : ParentNode ,
380424 rootSelector : CssSelector | undefined = "" ,
381425 options : CssSelectorGeneratorOptions ,
426+ selectorsListCache : SelectorsListCache = createSelectorsListCache ( ) ,
382427) : IterableIterator < IdentifiableParent > {
383428 if ( elements . length === 0 ) {
384429 return null ;
@@ -395,6 +440,7 @@ export function* closestIdentifiableParentGenerator(
395440 root ,
396441 rootSelector ,
397442 options ,
443+ selectorsListCache ,
398444 ) ) {
399445 yield {
400446 foundElements : currentElements ,
@@ -423,6 +469,7 @@ export function* selectorGenerator({
423469 let currentRoot = root ;
424470 let partialSelector = rootSelector ;
425471 let shouldContinue = true ;
472+ const selectorsListCache = createSelectorsListCache ( ) ;
426473
427474 while ( shouldContinue ) {
428475 let foundAny = false ;
@@ -432,6 +479,7 @@ export function* selectorGenerator({
432479 currentRoot ,
433480 partialSelector ,
434481 options ,
482+ selectorsListCache ,
435483 ) ) {
436484 const { foundElements, selector } = item ;
437485 foundAny = true ;
0 commit comments