Skip to content

Implement :nth-child(An+B of S) - #800

Open
nicoburns wants to merge 3 commits into
mainfrom
devin/1787704034-nth-child-of
Open

Implement :nth-child(An+B of S)#800
nicoburns wants to merge 3 commits into
mainfrom
devin/1787704034-nth-child-of

Conversation

@nicoburns

@nicoburns nicoburns commented Aug 26, 2026

Copy link
Copy Markdown
Member

Summary

Enables :nth-child(An+B of S) / :nth-last-child(An+B of S) parsing and adds the embedder-side invalidation it requires. Matching is already generic in stylo/selectors; the missing piece is that a mutation on one child can change which siblings match (their nth-of index changes), which stylo's downward snapshot invalidation can't reach. This mirrors Gecko's RestyleManager::RestyleSiblingsForNthOf.

Depends on (and temporarily rev-pins stylo to) DioxusLabs/stylo#16, which adds a separate layout.css.nth-child-of.enabled pref (and a layout.css.has-selector.enabled pref for later :has() work). Blitz enables only the nth-of pref. TODO comment notes reverting to crates.io once a stylo release ships the prefs.

Invalidation

New BaseDocument::restyle_siblings_for_nth_of(node_id):

  • early-out unless the parent carries HAS_SLOW_SELECTOR_NTH_OF (set by stylo via apply_selector_flags during matching)
  • restyles earlier siblings only if parent has HAS_SLOW_SELECTOR (:nth-last-child(.. of S)), later siblings only if HAS_SLOW_SELECTOR_LATER_SIBLINGS (:nth-child(.. of S)), by inserting RestyleHint::restyle_subtree() and marking ancestors dirty

Hooked at the two mutation classes that can change of S matching:

  • DocumentMutator::{set_attribute, clear_attribute} — gated by new attribute_might_affect_nth_of, which consults the stylist's nth-of dependency maps (might_have_nth_of_{attribute,id,class}_dependency) using both old and new id/class values
  • BaseDocument::snapshot_node_and (element state changes, e.g. :checked) — gated by has_nth_of_state_dependency

Also fixes DocumentMutator::remove_node to insert restyle_subtree() on the parent like the other removal paths already do (plain positional :nth-child needs this too; found via the new insertion/removal test).

Tests

packages/blitz-test-harness/tests/nth_child_of.rs: static matching plus dynamic invalidation for sibling class add/remove, id change, arbitrary attribute change, :checked state change, insertion/removal, and :nth-last-child(.. of S).

Link to Devin session: https://dioxus.staging.devinenterprise.com/sessions/65287500bfb04b2589b9fc6a08bc2d14
Open in Devin Desktop: https://dioxus.staging.devinenterprise.com/desktop/session/65287500bfb04b2589b9fc6a08bc2d14?variant=devin-insiders
Requested by: @nicoburns

WPT results

36 newly passing, 2 newly failing (net +34).

Full diff (38 changed tests)
+ Fail => Pass css/css-color-adjust/rendering/dark-color-scheme/color-scheme-rule-cache.html
- Pass => Fail css/css-color-adjust/rendering/dark-color-scheme/color-scheme-table-border-currentcolor-responsive.html
- Pass => Fail css/css-images/gradient/color-scheme-dependent-color-stops.html
+ Fail => Pass css/css-properties-values-api/registered-property-computation-color-001.html
+ Fail => Pass css/css-properties-values-api/registered-property-computation-color-002.html
+ Fail => Pass css/css-variables/registered-property-light-dark.html
+ Fail => Pass css/selectors/invalidation/nth-child-of-attr-largedom.html
+ Fail => Pass css/selectors/nth-child-and-nth-last-child.html
+ Fail => Pass css/selectors/nth-child-of-attribute.html
+ Fail => Pass css/selectors/nth-child-of-classname-002.html
+ Fail => Pass css/selectors/nth-child-of-classname.html
+ Fail => Pass css/selectors/nth-child-of-complex-selector-many-children.html
+ Fail => Pass css/selectors/nth-child-of-complex-selector.html
+ Fail => Pass css/selectors/nth-child-of-compound-selector.html
+ Fail => Pass css/selectors/nth-child-of-nesting.html
+ Fail => Pass css/selectors/nth-child-of-no-space-after-of.html
+ Fail => Pass css/selectors/nth-child-of-not.html
+ Fail => Pass css/selectors/nth-child-of-nth-child.html
+ Fail => Pass css/selectors/nth-child-of-pseudo.html
+ Fail => Pass css/selectors/nth-child-of-tagname.html
+ Fail => Pass css/selectors/nth-child-of-universal-selector.html
+ Fail => Pass css/selectors/nth-child-specificity-1.html
+ Fail => Pass css/selectors/nth-child-specificity-2.html
+ Fail => Pass css/selectors/nth-child-specificity-3.html
+ Fail => Pass css/selectors/nth-child-specificity-4.html
+ Fail => Pass css/selectors/nth-last-child-of-classname.html
+ Fail => Pass css/selectors/nth-last-child-of-complex-selector.html
+ Fail => Pass css/selectors/nth-last-child-of-compound-selector.html
+ Fail => Pass css/selectors/nth-last-child-of-nesting.html
+ Fail => Pass css/selectors/nth-last-child-of-no-space-after-of.html
+ Fail => Pass css/selectors/nth-last-child-of-style-sharing-1.html
+ Fail => Pass css/selectors/nth-last-child-of-style-sharing-2.html
+ Fail => Pass css/selectors/nth-last-child-of-tagname.html
+ Fail => Pass css/selectors/nth-last-child-specificity-1.html
+ Fail => Pass css/selectors/nth-last-child-specificity-2.html
+ Fail => Pass css/selectors/nth-last-child-specificity-3.html
+ Fail => Pass css/selectors/nth-last-child-specificity-4.html
+ Fail => Pass css/selectors/nth-of-selector-before.html

Generated by the WPT workflow.

@staging-devin-ai-integration

Copy link
Copy Markdown
Contributor

🤖 Devin AI Engineer

I'll be helping with this pull request! Here's what you should know:

✅ I will automatically:

  • Address comments on this PR that start with 'DevinAI' or '@devin'.
  • Look at CI failures and help fix them

Note: I can only respond to comments from users who have write access to this repository.

⚙️ Control Options:

  • Disable automatic comment, CI, and merge conflict monitoring

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.

1 participant