Skip to content

Resolve computed style value#4119

Merged
domenic merged 27 commits intojsdom:mainfrom
asamuzaK:computed
Apr 6, 2026
Merged

Resolve computed style value#4119
domenic merged 27 commits intojsdom:mainfrom
asamuzaK:computed

Conversation

@asamuzaK
Copy link
Copy Markdown
Contributor

@asamuzaK asamuzaK commented Mar 19, 2026

This pull request improves how jsdom resolves computed style values for DOM elements. This includes updating the style calculation logic, which will improve the accuracy and performance of handling computed CSS properties within jsdom.

Fixes #3614, fixes #3339, fixes #3563, fixes #3971, fixes #3972.
Related #3984, #4085, #3615.

@asamuzaK
Copy link
Copy Markdown
Contributor Author

Benchmark result

main:

> jsdom@29.0.1 benchmark
> node ./benchmark/runner --suite style


# style/get-computed-style

┌─────────┬───────────────────────────────────────┬───────────────────┬───────────────────┬────────────────────────┬────────────────────────┬─────────┐
│ (index) │ Task name                             │ Latency avg (ns)  │ Latency med (ns)  │ Throughput avg (ops/s) │ Throughput med (ops/s) │ Samples │
├─────────┼───────────────────────────────────────┼───────────────────┼───────────────────┼────────────────────────┼────────────────────────┼─────────┤
│ 0       │ 'flat (20 siblings)'                  │ '1182170 ± 2.34%' │ '1085900 ± 13000' │ '873 ± 0.82%'          │ '921 ± 11'             │ 846     │
│ 1       │ 'deep (10-level nesting)'             │ '58390 ± 2.09%'   │ '54200 ± 800.00'  │ '17738 ± 0.16%'        │ '18450 ± 276'          │ 17127   │
│ 2       │ 'deep, all levels (10-level nesting)' │ '583804 ± 2.02%'  │ '540050 ± 8550.0' │ '1761 ± 0.55%'         │ '1852 ± 30'            │ 1714    │
└─────────┴───────────────────────────────────────┴───────────────────┴───────────────────┴────────────────────────┴────────────────────────┴─────────┘

# style/get-computed-style-cold

┌─────────┬───────────────────────────────────────┬───────────────────┬────────────────────┬────────────────────────┬────────────────────────┬─────────┐
│ (index) │ Task name                             │ Latency avg (ns)  │ Latency med (ns)   │ Throughput avg (ops/s) │ Throughput med (ops/s) │ Samples │
├─────────┼───────────────────────────────────────┼───────────────────┼────────────────────┼────────────────────────┼────────────────────────┼─────────┤
│ 0       │ 'flat (20 siblings)'                  │ '7744845 ± 2.63%' │ '7386700 ± 402800' │ '131 ± 2.03%'          │ '135 ± 8'              │ 130     │
│ 1       │ 'deep (10-level nesting)'             │ '4269738 ± 2.01%' │ '3943300 ± 84600'  │ '239 ± 1.55%'          │ '254 ± 6'              │ 235     │
│ 2       │ 'deep, all levels (10-level nesting)' │ '4234559 ± 1.64%' │ '4027400 ± 81500'  │ '239 ± 1.21%'          │ '248 ± 5'              │ 237     │
└─────────┴───────────────────────────────────────┴───────────────────┴────────────────────┴────────────────────────┴────────────────────────┴─────────┘

# style/get-computed-style-property-access

┌─────────┬────────────────────────┬──────────────────┬──────────────────┬────────────────────────┬────────────────────────┬─────────┐
│ (index) │ Task name              │ Latency avg (ns) │ Latency med (ns) │ Throughput avg (ops/s) │ Throughput med (ops/s) │ Samples │
├─────────┼────────────────────────┼──────────────────┼──────────────────┼────────────────────────┼────────────────────────┼─────────┤
│ 0       │ 'few properties (4)'   │ '63425 ± 0.61%'  │ '59000 ± 700.00' │ '16191 ± 0.17%'        │ '16949 ± 199'          │ 15767   │
│ 1       │ 'many properties (30)' │ '79848 ± 0.49%'  │ '74400 ± 800.00' │ '12813 ± 0.19%'        │ '13441 ± 146'          │ 12524   │
└─────────┴────────────────────────┴──────────────────┴──────────────────┴────────────────────────┴────────────────────────┴─────────┘

# style/style-property

┌─────────┬─────────────────────────────────────────┬───────────────────┬────────────────────┬────────────────────────┬────────────────────────┬─────────┐
│ (index) │ Task name                               │ Latency avg (ns)  │ Latency med (ns)   │ Throughput avg (ops/s) │ Throughput med (ops/s) │ Samples │
├─────────┼─────────────────────────────────────────┼───────────────────┼────────────────────┼────────────────────────┼────────────────────────┼─────────┤
│ 0       │ 'innerHTML: simple divs (no styles)'    │ '257829 ± 0.81%'  │ '238400 ± 6100.0'  │ '3992 ± 0.40%'         │ '4195 ± 108'           │ 3879    │
│ 1       │ 'innerHTML: divs with inline styles'    │ '7135030 ± 3.42%' │ '6638000 ± 189800' │ '143 ± 1.96%'          │ '151 ± 4'              │ 141     │
│ 2       │ 'createElement + style properties'      │ '7049646 ± 4.16%' │ '6368150 ± 142100' │ '146 ± 2.29%'          │ '157 ± 4'              │ 142     │
│ 3       │ "createElement + setAttribute('style')" │ '6656416 ± 2.30%' │ '6142600 ± 199700' │ '153 ± 1.96%'          │ '163 ± 5'              │ 151     │
│ 4       │ 'createElement + style.cssText'         │ '6836103 ± 2.91%' │ '6291600 ± 159400' │ '149 ± 2.04%'          │ '159 ± 4'              │ 147     │
└─────────┴─────────────────────────────────────────┴───────────────────┴────────────────────┴────────────────────────┴────────────────────────┴─────────┘

@asamuzaK
Copy link
Copy Markdown
Contributor Author

PR (commit 1836c3f):

> jsdom@29.0.1 benchmark
> node ./benchmark/runner --suite style


# style/get-computed-style

┌─────────┬───────────────────────────────────────┬──────────────────┬───────────────────┬────────────────────────┬────────────────────────┬─────────┐
│ (index) │ Task name                             │ Latency avg (ns) │ Latency med (ns)  │ Throughput avg (ops/s) │ Throughput med (ops/s) │ Samples │
├─────────┼───────────────────────────────────────┼──────────────────┼───────────────────┼────────────────────────┼────────────────────────┼─────────┤
│ 0       │ 'flat (20 siblings)'                  │ '199578 ± 2.15%' │ '189300 ± 1400.0' │ '5144 ± 0.23%'         │ '5283 ± 39'            │ 5011    │
│ 1       │ 'deep (10-level nesting)'             │ '13513 ± 1.41%'  │ '12500 ± 200.00'  │ '76714 ± 0.08%'        │ '80000 ± 1301'         │ 74005   │
│ 2       │ 'deep, all levels (10-level nesting)' │ '117644 ± 1.36%' │ '109800 ± 1100.0' │ '8732 ± 0.22%'         │ '9107 ± 90'            │ 8501    │
└─────────┴───────────────────────────────────────┴──────────────────┴───────────────────┴────────────────────────┴────────────────────────┴─────────┘

# style/get-computed-style-cold

┌─────────┬───────────────────────────────────────┬───────────────────┬────────────────────┬────────────────────────┬────────────────────────┬─────────┐
│ (index) │ Task name                             │ Latency avg (ns)  │ Latency med (ns)   │ Throughput avg (ops/s) │ Throughput med (ops/s) │ Samples │
├─────────┼───────────────────────────────────────┼───────────────────┼────────────────────┼────────────────────────┼────────────────────────┼─────────┤
│ 0       │ 'flat (20 siblings)'                  │ '5516371 ± 2.67%' │ '5054450 ± 128700' │ '186 ± 1.94%'          │ '198 ± 5'              │ 182     │
│ 1       │ 'deep (10-level nesting)'             │ '328438 ± 2.57%'  │ '294000 ± 5900.0'  │ '3242 ± 0.46%'         │ '3401 ± 70'            │ 3052    │
│ 2       │ 'deep, all levels (10-level nesting)' │ '2899199 ± 3.18%' │ '2687800 ± 33600'  │ '357 ± 1.32%'          │ '372 ± 5'              │ 345     │
└─────────┴───────────────────────────────────────┴───────────────────┴────────────────────┴────────────────────────┴────────────────────────┴─────────┘

# style/get-computed-style-property-access

┌─────────┬────────────────────────┬──────────────────┬──────────────────┬────────────────────────┬────────────────────────┬─────────┐
│ (index) │ Task name              │ Latency avg (ns) │ Latency med (ns) │ Throughput avg (ops/s) │ Throughput med (ops/s) │ Samples │
├─────────┼────────────────────────┼──────────────────┼──────────────────┼────────────────────────┼────────────────────────┼─────────┤
│ 0       │ 'few properties (4)'   │ '24917 ± 0.82%'  │ '23100 ± 300.00' │ '41403 ± 0.11%'        │ '43290 ± 570'          │ 40133   │
│ 1       │ 'many properties (30)' │ '95703 ± 0.58%'  │ '90500 ± 700.00' │ '10655 ± 0.19%'        │ '11050 ± 86'           │ 10449   │
└─────────┴────────────────────────┴──────────────────┴──────────────────┴────────────────────────┴────────────────────────┴─────────┘

# style/style-property

┌─────────┬─────────────────────────────────────────┬───────────────────┬────────────────────┬────────────────────────┬────────────────────────┬─────────┐
│ (index) │ Task name                               │ Latency avg (ns)  │ Latency med (ns)   │ Throughput avg (ops/s) │ Throughput med (ops/s) │ Samples │
├─────────┼─────────────────────────────────────────┼───────────────────┼────────────────────┼────────────────────────┼────────────────────────┼─────────┤
│ 0       │ 'innerHTML: simple divs (no styles)'    │ '264658 ± 0.77%'  │ '245100 ± 4500.0'  │ '3882 ± 0.39%'         │ '4080 ± 75'            │ 3779    │
│ 1       │ 'innerHTML: divs with inline styles'    │ '7424120 ± 2.73%' │ '6930300 ± 290500' │ '137 ± 2.05%'          │ '144 ± 6'              │ 135     │
│ 2       │ 'createElement + style properties'      │ '6719809 ± 2.53%' │ '6270500 ± 98200'  │ '151 ± 1.78%'          │ '159 ± 3'              │ 149     │
│ 3       │ "createElement + setAttribute('style')" │ '6682417 ± 2.38%' │ '6176400 ± 148100' │ '152 ± 1.91%'          │ '162 ± 4'              │ 150     │
│ 4       │ 'createElement + style.cssText'         │ '7097118 ± 5.15%' │ '6382600 ± 154200' │ '146 ± 2.38%'          │ '157 ± 4'              │ 141     │
└─────────┴─────────────────────────────────────────┴───────────────────┴────────────────────┴────────────────────────┴────────────────────────┴─────────┘

@asamuzaK asamuzaK marked this pull request as ready for review March 20, 2026 07:45
@asamuzaK asamuzaK force-pushed the computed branch 3 times, most recently from c9b0359 to 77fc2ea Compare March 20, 2026 13:17
@domenic
Copy link
Copy Markdown
Member

domenic commented Mar 20, 2026

Comparison with #4085:

Tests

Overall, #4119 needs to do more work to match #4085.

Benchmarks

Basically, #4085 is about 2x as fast as #4119 on get-computed-style and get-computed-style-property-access.

Code complexity

#4119 deletes a lot of code, which is good. Maybe #4085 could also delete these? Maybe we should just do a color-related refactoring PR before doing any computed style resolution work?

  • Deletes lib/jsdom/living/css/helpers/colors.js
  • Deletes SYS_COLORS

#4119 introduces duplicate code into many property handlers, and makes it so that anyone who writes a property handler has to know which kind of getter to use. I think this is crucial difference for correctness: by making getPropertyValue() always use the metadata, more tests can pass, with various properties not needing special handlers.

#4119 moves several properties from truly-private (#updateStyleAttribute(), #computed) to jsdom-internal (_-prefixed), which is slightly less good encapsulation. Similarly, it introduces two new jsdom-internal methods _prepareComputedValueOpts() and _resolveLonghandPropertyValue().

#4119 exports many functions from lib/jsdom/living/css/helpers/computed-style.js, which is also bad for encapsulation. However, probably some of these can be removed, since they are not used outside that file.

#4119 overall seems to need more code to get similar results. (Or, per the tests, actually slightly worse results.) However, the code in #4119 is broken up into clearly-named functions and is pretty easy to follow.

#4085 has a separate _resolvedValues cache on top of the style cache and the css-values cache, which is not great. But, #4119 may benefit from some sort of _resolvedValues lazy cache too, in order to match the performance of #4085, so maybe this will not be a real advantage of #4119. I think this is the crucial difference for speed.

Both #4085 and #4119 bounce back and forth between CSSStyleDeclaration-impl.js and computed-style.js in confusing ways. #4085 does it via closures, the computing set, the resolveProperty() callback, and the _resolvedValues lazy cache. #4119 goes this.getPropertyValue() -> this._getComputedValue() -> computed-style.js for keyword resolution -> CSSStyleDeclaration-impl.js for value transformation -> this._prepareComputedValueOpts() -> computed-style.js to resolve color-scheme and color.

Overall, #4085 matches the spec better, with a clear cascaded -> specified -> computed (-> resolved) pipeline all in computed-style.js, with CSSStyleDeclaration-impl.js involved only after the whole pipeline is finished. #4119's back and forth does not match the spec. The preparePartiallyResolvedValue() method in particular is confusing, as there is no "partially resolved" concept in the spec.

Another example of where #4119's non-spec architecture makes things more complicated: resolveGlobalKeywords() has a while (element) loop with a switch and a trailing if/else chain. In #4085, there's just a switch statement inside getComputedValue(), because getSpecifiedValue() already handled the empty-cascade defaulting.


Overall I think it is possible to make #4119 catch up to #4085, but it needs more work.

@asamuzaK
Copy link
Copy Markdown
Contributor Author

* `background-331.html` is partially passing in [Use resolveProperty callback for lazy computed style resolution #4085](https://github.com/jsdom/jsdom/pull/4085), marked fully failed in [Resolve computed style value #4119](https://github.com/jsdom/jsdom/pull/4119). (Maybe it's partially passing?)

Fixed.
Now, it's partially passing.

* `inherit-initial.html` is fully passing in [Use resolveProperty callback for lazy computed style resolution #4085](https://github.com/jsdom/jsdom/pull/4085), partially passing in [Resolve computed style value #4119](https://github.com/jsdom/jsdom/pull/4119).

I think it's also fully passing in #4119?

* `system-color-compute.html` is same as main in [Use resolveProperty callback for lazy computed style resolution #4085](https://github.com/jsdom/jsdom/pull/4085), regressed in [Resolve computed style value #4119](https://github.com/jsdom/jsdom/pull/4119).

Still 2 regressions but I think it's natural that the tests would fail for box-shadow and text-shadow because their syntax is complex.

* `getComputedStyle-root-variables.html` is fully passing in [Use resolveProperty callback for lazy computed style resolution #4085](https://github.com/jsdom/jsdom/pull/4085), still failing in [Resolve computed style value #4119](https://github.com/jsdom/jsdom/pull/4119).

Fixed.

#4119 introduces duplicate code into many property handlers, and makes it so that anyone who writes a property handler has to know which kind of getter to use. I think this is crucial difference for correctness: by making getPropertyValue() always use the metadata, more tests can pass, with various properties not needing special handlers.

Reverted and made getPropertyValue() always use the metadata.

#4119 moves several properties from truly-private (#updateStyleAttribute(), #computed) to jsdom-internal (_-prefixed), which is slightly less good encapsulation.

Reverted.

Similarly, it introduces two new jsdom-internal methods _prepareComputedValueOpts() and _resolveLonghandPropertyValue().

Changed them truly-private.

#4119 exports many functions from lib/jsdom/living/css/helpers/computed-style.js, which is also bad for encapsulation. However, probably some of these can be removed, since they are not used outside that file.

Removed some exports.

#4085 has a separate _resolvedValues cache on top of the style cache and the css-values cache, which is not great. But, #4119 may benefit from some sort of _resolvedValues lazy cache too, in order to match the performance of #4085, so maybe this will not be a real advantage of #4119. I think this is the crucial difference for speed.

Added caching mechanism.

Overall, #4085 matches the spec better, with a clear cascaded -> specified -> computed (-> resolved) pipeline all in computed-style.js, with CSSStyleDeclaration-impl.js involved only after the whole pipeline is finished. #4119's back and forth does not match the spec.
The preparePartiallyResolvedValue() method in particular is confusing, as there is no "partially resolved" concept in the spec.

Renamed function.

@asamuzaK
Copy link
Copy Markdown
Contributor Author

New benchmark result:

> jsdom@29.0.1 benchmark
> node ./benchmark/runner --suite style


# style/get-computed-style

┌─────────┬───────────────────────────────────────┬──────────────────┬───────────────────┬────────────────────────┬────────────────────────┬─────────┐
│ (index) │ Task name                             │ Latency avg (ns) │ Latency med (ns)  │ Throughput avg (ops/s) │ Throughput med (ops/s) │ Samples │
├─────────┼───────────────────────────────────────┼──────────────────┼───────────────────┼────────────────────────┼────────────────────────┼─────────┤
│ 0       │ 'flat (20 siblings)'                  │ '102149 ± 1.80%' │ '96700 ± 800.00'  │ '10033 ± 0.18%'        │ '10341 ± 86'           │ 9790    │
│ 1       │ 'deep (10-level nesting)'             │ '5223.5 ± 1.91%' │ '4900.0 ± 100.00' │ '198199 ± 0.04%'       │ '204082 ± 4252'        │ 191444  │
│ 2       │ 'deep, all levels (10-level nesting)' │ '52137 ± 1.95%'  │ '48100 ± 700.00'  │ '19845 ± 0.15%'        │ '20790 ± 307'          │ 19181   │
└─────────┴───────────────────────────────────────┴──────────────────┴───────────────────┴────────────────────────┴────────────────────────┴─────────┘

# style/get-computed-style-cold

┌─────────┬───────────────────────────────────────┬───────────────────┬────────────────────┬────────────────────────┬────────────────────────┬─────────┐
│ (index) │ Task name                             │ Latency avg (ns)  │ Latency med (ns)   │ Throughput avg (ops/s) │ Throughput med (ops/s) │ Samples │
├─────────┼───────────────────────────────────────┼───────────────────┼────────────────────┼────────────────────────┼────────────────────────┼─────────┤
│ 0       │ 'flat (20 siblings)'                  │ '5417755 ± 2.86%' │ '5031600 ± 113100' │ '189 ± 1.75%'          │ '199 ± 4'              │ 185     │
│ 1       │ 'deep (10-level nesting)'             │ '328878 ± 2.57%'  │ '295300 ± 6800.0'  │ '3224 ± 0.45%'         │ '3386 ± 78'            │ 3041    │
│ 2       │ 'deep, all levels (10-level nesting)' │ '2984954 ± 2.60%' │ '2697000 ± 41950'  │ '347 ± 1.57%'          │ '371 ± 6'              │ 336     │
└─────────┴───────────────────────────────────────┴───────────────────┴────────────────────┴────────────────────────┴────────────────────────┴─────────┘

# style/get-computed-style-property-access

┌─────────┬────────────────────────┬──────────────────┬──────────────────┬────────────────────────┬────────────────────────┬─────────┐
│ (index) │ Task name              │ Latency avg (ns) │ Latency med (ns) │ Throughput avg (ops/s) │ Throughput med (ops/s) │ Samples │
├─────────┼────────────────────────┼──────────────────┼──────────────────┼────────────────────────┼────────────────────────┼─────────┤
│ 0       │ 'few properties (4)'   │ '18517 ± 0.51%'  │ '17400 ± 200.00' │ '55344 ± 0.09%'        │ '57471 ± 668'          │ 54004   │
│ 1       │ 'many properties (30)' │ '67385 ± 0.51%'  │ '65000 ± 450.00' │ '15068 ± 0.12%'        │ '15385 ± 106'          │ 14840   │
└─────────┴────────────────────────┴──────────────────┴──────────────────┴────────────────────────┴────────────────────────┴─────────┘

# style/style-property

┌─────────┬─────────────────────────────────────────┬───────────────────┬────────────────────┬────────────────────────┬────────────────────────┬─────────┐
│ (index) │ Task name                               │ Latency avg (ns)  │ Latency med (ns)   │ Throughput avg (ops/s) │ Throughput med (ops/s) │ Samples │
├─────────┼─────────────────────────────────────────┼───────────────────┼────────────────────┼────────────────────────┼────────────────────────┼─────────┤
│ 0       │ 'innerHTML: simple divs (no styles)'    │ '263922 ± 0.80%'  │ '242300 ± 4100.0'  │ '3904 ± 0.42%'         │ '4127 ± 71'            │ 3789    │
│ 1       │ 'innerHTML: divs with inline styles'    │ '7590590 ± 2.57%' │ '7048600 ± 263300' │ '134 ± 2.07%'          │ '142 ± 5'              │ 132     │
│ 2       │ 'createElement + style properties'      │ '6784516 ± 1.83%' │ '6403350 ± 80350'  │ '149 ± 1.53%'          │ '156 ± 2'              │ 148     │
│ 3       │ "createElement + setAttribute('style')" │ '7255320 ± 3.24%' │ '6510750 ± 267900' │ '142 ± 2.61%'          │ '154 ± 7'              │ 138     │
│ 4       │ 'createElement + style.cssText'         │ '7139867 ± 2.68%' │ '6521900 ± 123500' │ '143 ± 2.17%'          │ '153 ± 3'              │ 141     │
└─────────┴─────────────────────────────────────────┴───────────────────┴────────────────────┴────────────────────────┴────────────────────────┴─────────┘

@domenic
Copy link
Copy Markdown
Member

domenic commented Mar 22, 2026

I am going to extract out some pieces of this PR into separate PRs to make it easier to review. (Definitely the colors improvements, maybe the cache bug fix and new WPT.) I will take care of the rebasing.

Copy link
Copy Markdown
Member

@domenic domenic left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for your patience.

Test improvement

  • #4119 completely fixes inherit-initial.html. #4085 does not.
  • #4119 regresses nested-color-mix-with-currentcolor.html. #4085 keeps it passing. This is because #prepareComputedValueOpts() eagerly resolves currentColor into a concrete value globally, which is not correct.
  • #4119 partially regresses system-color-compute.html (on box-shadow and text-shadow). #4085 keeps those passing. This is because serializeColor() only inspects the first AST node, missing colors inside multi-token values like shadow.

My worry is that since #4119 does not follow the spec architecture, it might not have a clear path toward parity with #4085. Do you think it will be possible to fix the regressions?

Performance

Both PRs are generally faster than main.

  • get-computed-style: #4119 is 1.08x-1.25x slower than #4085.
  • get-computed-style-cold: #4119 is 1.07x faster to 1.25x slower than #4085.
  • get-computed-style-property-access: #4119 is 1.29x-1.67x slower than #4085.
  • style-property: #4119 is 1.0x-1.18x faster than #4085.

Overall, #4119 is still a bit slower. I am not sure why, but...

Here is Claude Code's analysis

The performance difference is likely explained by:

  • Property access: #4085's _resolvedValues is a flat Map check + callback. #4119's getPropertyValue goes through #cachedPropertyValues check → #getPropertyMetadata (possibly 2 Map lookups + hasVarFunc check) → #getComputedValue (another propertyDefinitions.has check, #getPropertyDefinition, keyword checks) → #resolveLonghandPropertyValue#prepareComputedValueOpts. There are simply more layers and Map lookups per property access.
  • Cold path: #4119's getInheritedPropertyValue manually walks ancestors in a loop, calling prepareComputedStyleDeclaration + getPropertyValue on each. #4085's getComputedValue recurses through the spec's getSpecifiedValuegetComputedValue(parentElement) chain, which benefits from the _styleCache + _resolvedValues caching at each level.

Code architecture

Overall I think #4119 is easier to understand than #4085 now. It has a clear division between helpers in computed-style.js and the main computations in CSSStyleDeclaration-impl.js.

I still like how #4085 has steps that clearly correspond to the specification. But if you believe the architecture in #4119 is better going forward, I can trust you.

The only issue is that #prepareComputedValueOpts() is quite complicated, and is still incorrect, as discussed above. Maybe more documentation would be helpful. Probably "computed value opts" is not a good name? From what I can see, they are the options fed into the CSS parser when resolving longhands? "longhand parser opts"? But it's mostly about colors, so maybe color stuff should be in the name?

Comment thread lib/jsdom/living/css/CSSStyleDeclaration-impl.js Outdated
Comment thread lib/jsdom/living/css/helpers/css-values.js
@asamuzaK
Copy link
Copy Markdown
Contributor Author

My worry is that since #4119 does not follow the spec architecture, it might not have a clear path toward parity with #4085. Do you think it will be possible to fix the regressions?

Fixed regressions except box-shadow and text-shadow.
They need special parsers for setters and resolving computed values.

The only issue is that #prepareComputedValueOpts() is quite complicated, and is still incorrect, as discussed above. Maybe more documentation would be helpful. Probably "computed value opts" is not a good name?

Currently, we only offer color-related options, but I plan to add options to resolve computed values such as relative length, custom properties etc.

@asamuzaK asamuzaK requested a review from domenic March 27, 2026 18:33
@asamuzaK asamuzaK marked this pull request as draft April 2, 2026 21:23
@asamuzaK asamuzaK force-pushed the computed branch 2 times, most recently from 723a58f to bfcfa0a Compare April 3, 2026 00:43
Copy link
Copy Markdown
Member

@domenic domenic left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM, but I guess since you marked it as draft you still have some more tweaks?

@asamuzaK asamuzaK marked this pull request as ready for review April 3, 2026 01:26
@asamuzaK
Copy link
Copy Markdown
Contributor Author

asamuzaK commented Apr 3, 2026

It is ready, but haven't run benchmark.

@asamuzaK
Copy link
Copy Markdown
Contributor Author

asamuzaK commented Apr 3, 2026

If we don't need latest benchmark, please go on.

@asamuzaK
Copy link
Copy Markdown
Contributor Author

asamuzaK commented Apr 3, 2026

Benchmark:

> jsdom@29.0.1 benchmark
> node ./benchmark/runner --suite style


# style/get-computed-style

┌─────────┬───────────────────────────────────────┬──────────────────┬───────────────────┬────────────────────────┬────────────────────────┬─────────┐
│ (index) │ Task name                             │ Latency avg (ns) │ Latency med (ns)  │ Throughput avg (ops/s) │ Throughput med (ops/s) │ Samples │
├─────────┼───────────────────────────────────────┼──────────────────┼───────────────────┼────────────────────────┼────────────────────────┼─────────┤
│ 0       │ 'flat (20 siblings)'                  │ '111895 ± 1.72%' │ '100900 ± 2700.0' │ '9324 ± 0.28%'         │ '9911 ± 272'           │ 8937    │
│ 1       │ 'deep (10-level nesting)'             │ '5807.1 ± 1.55%' │ '5100.0 ± 200.00' │ '183480 ± 0.07%'       │ '196078 ± 8003'        │ 172205  │
│ 2       │ 'deep, all levels (10-level nesting)' │ '56364 ± 1.97%'  │ '50100 ± 1400.0'  │ '18770 ± 0.21%'        │ '19960 ± 574'          │ 17743   │
└─────────┴───────────────────────────────────────┴──────────────────┴───────────────────┴────────────────────────┴────────────────────────┴─────────┘

# style/get-computed-style-cold

┌─────────┬───────────────────────────────────────┬───────────────────┬────────────────────┬────────────────────────┬────────────────────────┬─────────┐
│ (index) │ Task name                             │ Latency avg (ns)  │ Latency med (ns)   │ Throughput avg (ops/s) │ Throughput med (ops/s) │ Samples │
├─────────┼───────────────────────────────────────┼───────────────────┼────────────────────┼────────────────────────┼────────────────────────┼─────────┤
│ 0       │ 'flat (20 siblings)'                  │ '5799413 ± 2.71%' │ '5396000 ± 333200' │ '177 ± 2.08%'          │ '185 ± 12'             │ 173     │
│ 1       │ 'deep (10-level nesting)'             │ '365115 ± 3.83%'  │ '308700 ± 15900'   │ '3026 ± 0.62%'         │ '3239 ± 175'           │ 2750    │
│ 2       │ 'deep, all levels (10-level nesting)' │ '3203313 ± 3.20%' │ '2831400 ± 156400' │ '326 ± 1.91%'          │ '353 ± 20'             │ 313     │
└─────────┴───────────────────────────────────────┴───────────────────┴────────────────────┴────────────────────────┴────────────────────────┴─────────┘

# style/get-computed-style-property-access

┌─────────┬────────────────────────┬──────────────────┬──────────────────┬────────────────────────┬────────────────────────┬─────────┐
│ (index) │ Task name              │ Latency avg (ns) │ Latency med (ns) │ Throughput avg (ops/s) │ Throughput med (ops/s) │ Samples │
├─────────┼────────────────────────┼──────────────────┼──────────────────┼────────────────────────┼────────────────────────┼─────────┤
│ 0       │ 'few properties (4)'   │ '19734 ± 0.67%'  │ '17400 ± 400.00' │ '53611 ± 0.13%'        │ '57471 ± 1352'         │ 50675   │
│ 1       │ 'many properties (30)' │ '71631 ± 0.79%'  │ '63500 ± 1900.0' │ '14644 ± 0.25%'        │ '15748 ± 481'          │ 13961   │
└─────────┴────────────────────────┴──────────────────┴──────────────────┴────────────────────────┴────────────────────────┴─────────┘

# style/style-property

┌─────────┬─────────────────────────────────────────┬───────────────────┬────────────────────┬────────────────────────┬────────────────────────┬─────────┐
│ (index) │ Task name                               │ Latency avg (ns)  │ Latency med (ns)   │ Throughput avg (ops/s) │ Throughput med (ops/s) │ Samples │
├─────────┼─────────────────────────────────────────┼───────────────────┼────────────────────┼────────────────────────┼────────────────────────┼─────────┤
│ 0       │ 'innerHTML: simple divs (no styles)'    │ '289368 ± 1.09%'  │ '253800 ± 11700'   │ '3628 ± 0.57%'         │ '3940 ± 187'           │ 3456    │
│ 1       │ 'innerHTML: divs with inline styles'    │ '7886045 ± 3.01%' │ '7554500 ± 706800' │ '130 ± 2.37%'          │ '132 ± 13'             │ 127     │
│ 2       │ 'createElement + style properties'      │ '7309391 ± 2.94%' │ '6852300 ± 682700' │ '140 ± 2.55%'          │ '146 ± 16'             │ 137     │
│ 3       │ "createElement + setAttribute('style')" │ '7150604 ± 3.02%' │ '6726550 ± 630650' │ '144 ± 2.52%'          │ '149 ± 15'             │ 140     │
│ 4       │ 'createElement + style.cssText'         │ '7369724 ± 2.62%' │ '7040500 ± 680600' │ '139 ± 2.29%'          │ '142 ± 15'             │ 136     │
└─────────┴─────────────────────────────────────────┴───────────────────┴────────────────────┴────────────────────────┴────────────────────────┴─────────┘

@asamuzaK
Copy link
Copy Markdown
Contributor Author

asamuzaK commented Apr 4, 2026

Benchmark after updating css-color and dom-selector

> jsdom@29.0.1 benchmark
> node ./benchmark/runner --suite style


# style/get-computed-style

┌─────────┬───────────────────────────────────────┬──────────────────┬───────────────────┬────────────────────────┬────────────────────────┬─────────┐
│ (index) │ Task name                             │ Latency avg (ns) │ Latency med (ns)  │ Throughput avg (ops/s) │ Throughput med (ops/s) │ Samples │
├─────────┼───────────────────────────────────────┼──────────────────┼───────────────────┼────────────────────────┼────────────────────────┼─────────┤
│ 0       │ 'flat (20 siblings)'                  │ '87862 ± 1.93%'  │ '80200 ± 1600.0'  │ '11858 ± 0.23%'        │ '12469 ± 254'          │ 11382   │
│ 1       │ 'deep (10-level nesting)'             │ '4341.9 ± 1.18%' │ '4000.0 ± 100.00' │ '240430 ± 0.04%'       │ '250000 ± 6410'        │ 230314  │
│ 2       │ 'deep, all levels (10-level nesting)' │ '42741 ± 1.50%'  │ '39300 ± 800.00'  │ '24311 ± 0.15%'        │ '25445 ± 529'          │ 23397   │
└─────────┴───────────────────────────────────────┴──────────────────┴───────────────────┴────────────────────────┴────────────────────────┴─────────┘

# style/get-computed-style-cold

┌─────────┬───────────────────────────────────────┬───────────────────┬────────────────────┬────────────────────────┬────────────────────────┬─────────┐
│ (index) │ Task name                             │ Latency avg (ns)  │ Latency med (ns)   │ Throughput avg (ops/s) │ Throughput med (ops/s) │ Samples │
├─────────┼───────────────────────────────────────┼───────────────────┼────────────────────┼────────────────────────┼────────────────────────┼─────────┤
│ 0       │ 'flat (20 siblings)'                  │ '5788020 ± 3.82%' │ '5277100 ± 182100' │ '179 ± 2.15%'          │ '189 ± 7'              │ 173     │
│ 1       │ 'deep (10-level nesting)'             │ '357211 ± 3.31%'  │ '312000 ± 8700.0'  │ '3026 ± 0.54%'         │ '3205 ± 92'            │ 2800    │
│ 2       │ 'deep, all levels (10-level nesting)' │ '3218226 ± 3.05%' │ '2832700 ± 93800'  │ '325 ± 1.89%'          │ '353 ± 12'             │ 311     │
└─────────┴───────────────────────────────────────┴───────────────────┴────────────────────┴────────────────────────┴────────────────────────┴─────────┘

# style/get-computed-style-property-access

┌─────────┬────────────────────────┬──────────────────┬──────────────────┬────────────────────────┬────────────────────────┬─────────┐
│ (index) │ Task name              │ Latency avg (ns) │ Latency med (ns) │ Throughput avg (ops/s) │ Throughput med (ops/s) │ Samples │
├─────────┼────────────────────────┼──────────────────┼──────────────────┼────────────────────────┼────────────────────────┼─────────┤
│ 0       │ 'few properties (4)'   │ '16241 ± 0.58%'  │ '15000 ± 300.00' │ '63777 ± 0.10%'        │ '66667 ± 1361'         │ 61572   │
│ 1       │ 'many properties (30)' │ '64373 ± 0.61%'  │ '59600 ± 900.00' │ '15994 ± 0.19%'        │ '16779 ± 257'          │ 15535   │
└─────────┴────────────────────────┴──────────────────┴──────────────────┴────────────────────────┴────────────────────────┴─────────┘

# style/style-property

┌─────────┬─────────────────────────────────────────┬───────────────────┬────────────────────┬────────────────────────┬────────────────────────┬─────────┐
│ (index) │ Task name                               │ Latency avg (ns)  │ Latency med (ns)   │ Throughput avg (ops/s) │ Throughput med (ops/s) │ Samples │
├─────────┼─────────────────────────────────────────┼───────────────────┼────────────────────┼────────────────────────┼────────────────────────┼─────────┤
│ 0       │ 'innerHTML: simple divs (no styles)'    │ '286768 ± 1.17%'  │ '247100 ± 10100'   │ '3691 ± 0.61%'         │ '4047 ± 171'           │ 3488    │
│ 1       │ 'innerHTML: divs with inline styles'    │ '7204066 ± 3.25%' │ '6599500 ± 342400' │ '143 ± 2.44%'          │ '152 ± 8'              │ 139     │
│ 2       │ 'createElement + style properties'      │ '7198150 ± 2.81%' │ '6660400 ± 412500' │ '142 ± 2.33%'          │ '150 ± 10'             │ 139     │
│ 3       │ "createElement + setAttribute('style')" │ '6694085 ± 2.75%' │ '6211250 ± 504600' │ '153 ± 2.41%'          │ '161 ± 14'             │ 150     │
│ 4       │ 'createElement + style.cssText'         │ '7123665 ± 4.13%' │ '6491500 ± 606300' │ '146 ± 2.95%'          │ '154 ± 16'             │ 141     │
└─────────┴─────────────────────────────────────────┴───────────────────┴────────────────────┴────────────────────────┴────────────────────────┴─────────┘

@domenic domenic merged commit 4097d66 into jsdom:main Apr 6, 2026
9 checks passed
@asamuzaK asamuzaK deleted the computed branch April 10, 2026 13:49
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

2 participants