From 1354367109ca942e01f0bae5da7000803e2543a1 Mon Sep 17 00:00:00 2001 From: John-David Dalton Date: Fri, 4 Sep 2026 13:59:55 -0400 Subject: [PATCH] Answer a constant nth-child index without building the sibling list MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ':nth-child(3)' compiles to n=s.nthElement(e,false) followed by n==3, and nthElement numbers an element by building the sibling list of its parent. That is the right trade for an an+b form, which has to know where the element sits, and pure overhead for a constant index, which only has to know whether three steps back runs out of siblings. The generated code now counts siblings and stops as soon as the index is exceeded, so it walks at most b of them and allocates nothing. div:nth-child(3) 115.99us -> 46.54us 2.49x div:nth-last-child(3) 115.18us -> 46.08us 2.50x div:nth-child(7) 115.47us -> 81.02us 1.43x li:nth-child(2) 253.96us -> 197.74us 1.28x Only the -child forms. Of-type has to compare the name of every sibling it steps over, and reading localName through the host on each one costs more than the list it avoids — measured 2.0x and 2.6x slower than the cached list for ':nth-of-type(3)' and ':nth-last-of-type(3)' — so those keep it. The an+b forms are untouched: ':nth-child(2n)' and ':nth-child(n+3)' still need the index. Results agree with the native engine on every form tested. References: - Spec: https://drafts.csswg.org/selectors-4/#nth-child-pseudo — the An+B forms, of which a constant index is one - Chromium: https://github.com/chromium/chromium/blob/155.0.8041.1/third_party/blink/renderer/core/css/selector_checker.cc#L2443 — ':nth-child' goes through a cache of sibling indexes - Chromium: https://github.com/chromium/chromium/blob/155.0.8041.1/third_party/blink/renderer/core/dom/nth_index_cache.h — that cache, which is the same trade this patch avoids for a constant - MDN: https://developer.mozilla.org/en-US/docs/Web/CSS/:nth-child --- docs/api.md | 62 +++++++++++++++++++++++++------------------------- src/nwsapi.mts | 27 ++++++++++++++++++++++ 2 files changed, 58 insertions(+), 31 deletions(-) diff --git a/docs/api.md b/docs/api.md index 7821e791..fce3699b 100644 --- a/docs/api.md +++ b/docs/api.md @@ -11,21 +11,21 @@ Query contexts default to the factory document when omitted. `closest()`, `first | Method | Result | | --- | --- | -| [`byClass(cls, context)`](../src/nwsapi.mts#L2972) | Returns elements with the class name. | -| [`byId(id, context)`](../src/nwsapi.mts#L2970) | Returns elements with the ID. Duplicate IDs are allowed by default. | -| [`byTag(tag, context)`](../src/nwsapi.mts#L2971) | Returns elements with the tag name. Use `*` for all elements. | -| [`closest(selectors, element, callback)`](../src/nwsapi.mts#L2978) | Returns the nearest match, starting with the element, or `null`. | -| [`compile(selector, mode, callback)`](../src/nwsapi.mts#L2980) | Compiles a selector into a resolver function. This is an advanced API. | -| [`configure(option, clear)`](../src/nwsapi.mts#L2981) | Reads or changes options. Pass `true` as the second argument to clear compiled selectors. | -| [`emit(message, proto)`](../src/nwsapi.mts#L2983) | Reports an error using the configured error policy. | -| [`first(selectors, context, callback)`](../src/nwsapi.mts#L2974) | Returns the first matching descendant, or `null`. | -| [`install(all)`](../src/nwsapi.mts#L2989) | Replaces native selector methods. Pass `true` to also replace collection methods. | -| [`match(selectors, element, callback)`](../src/nwsapi.mts#L2975) | Returns whether the element matches. | -| [`registerCombinator(combinator, resolver)`](../src/nwsapi.mts#L2996) | Adds a relationship between elements using trusted resolver code. | -| [`registerOperator(operator, resolver)`](../src/nwsapi.mts#L3021) | Adds an attribute operator using a resolver with `p1`, `p2`, and `p3` fields. | -| [`registerSelector(name, rexp, func)`](../src/nwsapi.mts#L3043) | Adds a selector pattern and a compiler callback that returns `source` and `status`. | -| [`select(selectors, context, callback)`](../src/nwsapi.mts#L2976) | Returns an array of matching descendants, or an empty array. | -| [`uninstall()`](../src/nwsapi.mts#L2990) | Restores the native methods saved by `install()`. | +| [`byClass(cls, context)`](../src/nwsapi.mts#L2999) | Returns elements with the class name. | +| [`byId(id, context)`](../src/nwsapi.mts#L2997) | Returns elements with the ID. Duplicate IDs are allowed by default. | +| [`byTag(tag, context)`](../src/nwsapi.mts#L2998) | Returns elements with the tag name. Use `*` for all elements. | +| [`closest(selectors, element, callback)`](../src/nwsapi.mts#L3005) | Returns the nearest match, starting with the element, or `null`. | +| [`compile(selector, mode, callback)`](../src/nwsapi.mts#L3007) | Compiles a selector into a resolver function. This is an advanced API. | +| [`configure(option, clear)`](../src/nwsapi.mts#L3008) | Reads or changes options. Pass `true` as the second argument to clear compiled selectors. | +| [`emit(message, proto)`](../src/nwsapi.mts#L3010) | Reports an error using the configured error policy. | +| [`first(selectors, context, callback)`](../src/nwsapi.mts#L3001) | Returns the first matching descendant, or `null`. | +| [`install(all)`](../src/nwsapi.mts#L3016) | Replaces native selector methods. Pass `true` to also replace collection methods. | +| [`match(selectors, element, callback)`](../src/nwsapi.mts#L3002) | Returns whether the element matches. | +| [`registerCombinator(combinator, resolver)`](../src/nwsapi.mts#L3023) | Adds a relationship between elements using trusted resolver code. | +| [`registerOperator(operator, resolver)`](../src/nwsapi.mts#L3048) | Adds an attribute operator using a resolver with `p1`, `p2`, and `p3` fields. | +| [`registerSelector(name, rexp, func)`](../src/nwsapi.mts#L3070) | Adds a selector pattern and a compiler callback that returns `source` and `status`. | +| [`select(selectors, context, callback)`](../src/nwsapi.mts#L3003) | Returns an array of matching descendants, or an empty array. | +| [`uninstall()`](../src/nwsapi.mts#L3017) | Restores the native methods saved by `install()`. |
Configuration @@ -58,22 +58,22 @@ These exports support extensions and debugging. Prefer query methods and `config | Member | Purpose | | --- | --- | -| [`CFG`](../src/nwsapi.mts#L2958) | Contains the compiler syntax settings. | -| [`Config`](../src/nwsapi.mts#L2984) | Contains the active options. Use `configure()` to change them. | -| [`M_BODY`](../src/nwsapi.mts#L2961) | Contains the matching resolver body template. | -| [`M_TEST`](../src/nwsapi.mts#L2965) | Contains the matching resolver test template. | -| [`matchLambdas`](../src/nwsapi.mts#L2950) | Caches compiled matching functions, not DOM results. | -| [`matchResolvers`](../src/nwsapi.mts#L2953) | Caches matching plans, not DOM results. | -| [`N_BODY`](../src/nwsapi.mts#L2962) | Exposes the matching resolver body template. | -| [`N_TEST`](../src/nwsapi.mts#L2966) | Contains the alternate resolver test template. | -| [`Operators`](../src/nwsapi.mts#L2992) | Contains registered attribute operators. | -| [`S_BODY`](../src/nwsapi.mts#L2960) | Contains the selection resolver body template. | -| [`S_TEST`](../src/nwsapi.mts#L2964) | Contains the selection resolver test template. | -| [`selectLambdas`](../src/nwsapi.mts#L2951) | Caches compiled selection functions, not DOM results. | -| [`Selectors`](../src/nwsapi.mts#L2993) | Contains registered selector extensions. | -| [`selectResolvers`](../src/nwsapi.mts#L2954) | Caches selection plans, not DOM results. | -| [`Snapshot`](../src/nwsapi.mts#L2985) | Contains the document state and helpers used by compiled selectors. | -| [`Version`](../src/nwsapi.mts#L2987) | Contains the engine version string. | +| [`CFG`](../src/nwsapi.mts#L2985) | Contains the compiler syntax settings. | +| [`Config`](../src/nwsapi.mts#L3011) | Contains the active options. Use `configure()` to change them. | +| [`M_BODY`](../src/nwsapi.mts#L2988) | Contains the matching resolver body template. | +| [`M_TEST`](../src/nwsapi.mts#L2992) | Contains the matching resolver test template. | +| [`matchLambdas`](../src/nwsapi.mts#L2977) | Caches compiled matching functions, not DOM results. | +| [`matchResolvers`](../src/nwsapi.mts#L2980) | Caches matching plans, not DOM results. | +| [`N_BODY`](../src/nwsapi.mts#L2989) | Exposes the matching resolver body template. | +| [`N_TEST`](../src/nwsapi.mts#L2993) | Contains the alternate resolver test template. | +| [`Operators`](../src/nwsapi.mts#L3019) | Contains registered attribute operators. | +| [`S_BODY`](../src/nwsapi.mts#L2987) | Contains the selection resolver body template. | +| [`S_TEST`](../src/nwsapi.mts#L2991) | Contains the selection resolver test template. | +| [`selectLambdas`](../src/nwsapi.mts#L2978) | Caches compiled selection functions, not DOM results. | +| [`Selectors`](../src/nwsapi.mts#L3020) | Contains registered selector extensions. | +| [`selectResolvers`](../src/nwsapi.mts#L2981) | Caches selection plans, not DOM results. | +| [`Snapshot`](../src/nwsapi.mts#L3012) | Contains the document state and helpers used by compiled selectors. | +| [`Version`](../src/nwsapi.mts#L3014) | Contains the engine version string. |
diff --git a/src/nwsapi.mts b/src/nwsapi.mts index da9a1bbe..8fd2f216 100644 --- a/src/nwsapi.mts +++ b/src/nwsapi.mts @@ -1829,6 +1829,33 @@ : 'n>' + (b - 1) : 'false' } + // A constant index needs no index. nth(Element|OfType) + // builds the sibling list of the parent to number the + // element within it, which is the right trade for an an+b + // form that has to know where the element sits, and pure + // overhead for ':nth-child(3)', which only has to know + // whether three steps back runs out of siblings. + // + // Only for the -child forms: of-type has to compare the + // name of every sibling it steps over, and reading + // localName through the host on each one costs more than + // the list it avoids. + if (test == 'n==' + a && a >= 1 && !expr) { + test = type ? 'next' : 'previous' + source = + 'n=1,o=e;' + + 'while(n<=' + + a + + '&&(o=o.' + + test + + 'ElementSibling))++n;' + + 'if(n==' + + a + + '){' + + source + + '}' + break + } expr = expr ? 'OfType' : 'Element' type = type ? 'true' : 'false' source =