Skip to content

Apply CSS specificity when computing styles#4059

Merged
domenic merged 11 commits intojsdom:mainfrom
asamuzaK:spec
Feb 15, 2026
Merged

Apply CSS specificity when computing styles#4059
domenic merged 11 commits intojsdom:mainfrom
asamuzaK:spec

Conversation

@asamuzaK
Copy link
Copy Markdown
Contributor

@asamuzaK asamuzaK commented Feb 14, 2026

Fix #3318 and some wpt tests regarding specificity.

  • Add @bramus/specificity to compute and compare selector specificities so stylesheet rules are applied in specificity order.
  • Refactor handleRule/handleProperty to consider inline declarations, property priorities, and selector specificity.
  • Simplify matches() selector checking.
  • Includes minor formatting and argument-passing cleanup.
  • Replaced test/web-platform-tests/to-upstream/css/cssom/getComputedStyle-specificity.html with test/web-platform-tests/to-upstream/css/selectors/selector-specificity.html.

Add @bramus/specificity to compute and compare selector specificities so stylesheet rules are applied in specificity order. Pass a specificities Map through applyStyleSheetRules and propagate selectorText from rules; refactor handleRule/handleProperty to consider inline declarations, property priorities, and selector specificity. Introduce setProperty helper to consolidate property-setting logic and simplify matches() selector checking. Includes minor formatting and argument-passing cleanup.
@asamuzaK asamuzaK marked this pull request as draft February 14, 2026 01:27
@asamuzaK asamuzaK marked this pull request as ready for review February 14, 2026 16:30
@asamuzaK asamuzaK marked this pull request as draft February 14, 2026 23:12
@asamuzaK asamuzaK marked this pull request as ready for review February 14, 2026 23:55
Handling CSS-wide keywords should be done in cssstyle.
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.

Amazing work!! I'm so excited to get specificity support in jsdom after all these years.

I have one higher-level organization issue we should sort out as part of this. I'd like us to move away from the opt argument pattern. The existing code had a simple version of this, with { declaration } threaded through the call chain, but this PR significantly expands it. This makes the data flow hard to follow and fragile:

  • prepareComputedStyleDeclaration creates { declaration, specificities } and passes it to applyStyleSheetRules
  • applyStyleSheetRules passes the same opt through to handleSheet
  • handleSheet mutates opt.ast = ast before passing to handleRule
  • handleRule mutates opt.style = rule.style before passing to handleProperty
  • handleProperty finally destructures { ast, declaration, inline, specificities, style }: five fields, set at four different layers

I think a cleaner approach would be to use explicit parameters for each function, so each one receives only what it needs. Here's how I'd suggest restructuring the existing functions:

prepareComputedStyleDeclaration: Pass just declaration to applyStyleSheetRules (no opts bag at all). Handle inline styles separately afterward. Since inline styles always win over non-!important stylesheet declarations, they don't need specificity tracking, so you can just call declaration.setProperty directly in the loop (with appropriate !important handling) instead of routing through handleProperty with an inline: true flag.

applyStyleSheetRules: Accept (elementImpl, declaration) as direct parameters. Create specificities = new Map() internally. That's an implementation detail, not something the caller should need to know about. Pass declaration and specificities down to handleSheet.

handleSheet: Accept (sheet, elementImpl, declaration, specificities). When a rule matches, pass the ast returned by matches() directly to handleRule as a parameter, instead of stuffing it into a shared object.

handleRule: Accept (rule, ast, declaration, specificities). Pass rule.style directly to handleProperty as a parameter, instead of mutating a shared object.

handleProperty: Accept (property, style, ast, declaration, specificities). All the data it needs arrives as explicit parameters, with no reliance on a shared mutable bag.

This way each function's signature documents exactly what data it needs, there's no shared mutable object being threaded and mutated across layers, and implementation details like specificities are created where they're used rather than by a distant caller.

WDYT?

@asamuzaK asamuzaK force-pushed the spec branch 2 times, most recently from f380d29 to d7b0673 Compare February 15, 2026 02:00
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.

Awesome, only minor suggestions remaining.

@asamuzaK asamuzaK requested a review from domenic February 15, 2026 02:53
@domenic domenic merged commit ce4c58f into jsdom:main Feb 15, 2026
9 checks passed
@asamuzaK asamuzaK deleted the spec branch February 15, 2026 03: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

Development

Successfully merging this pull request may close these issues.

getComputedStyle() ignores specificity priority

2 participants