Fix radio group validity update when removing or selecting an input (#36252)

This PR fixes an issue where radio inputs in the same group failed to
correctly update their `validity.valueMissing` state when:

- A **checked radio button was removed** from the DOM.
- A **different radio button was selected** by user interaction.

This behavior caused mismatches with how browsers like Firefox handle
radio group validation.

---

### Changes in This PR

#### Radio group revalidation on DOM removal
- Updated `unbind_from_tree()` to revalidate other radio buttons in the
same group when a checked input is removed.
- Uses `UnbindContext::parent` as the DOM root to ensure the correct
context is used during traversal.

#### New helper: `find_related_radios()`
- Encapsulates logic for finding other inputs in the same group.
- Used during both removal and attribute changes for consistency.

#### Validation on `checked`/`value` updates
- Introduced `update_related_validity_states()` to revalidate all group
members when a radio's `checked` or `value` is changed.

#### Web Platform Test (WPT) coverage
- Created a new WPT file: `radio-group-valueMissing.html`.
- Tests follow recommended `test()` pattern:
  - **Precondition**: Assert initial `valueMissing`.
  - **Action**: Remove or select a radio.
  - **Postcondition**: Assert expected `valueMissing`.

#### Manifest updated
- The WPT manifest now includes the new test.

---
<!-- Thank you for contributing to Servo! Please replace each `[ ]` by
`[X]` when the step is complete, and replace `___` with appropriate
data: -->
- [X] `./mach build -d` does not report any errors
- [X] `./mach test-tidy` does not report any errors
- [X] These changes fix #36110

<!-- Either: -->
- [X] There are tests for these changes

Signed-off-by: Emmanuel Elom <elomemmanuel007@gmail.com>
This commit is contained in:
elomscansio 2025-04-07 00:26:15 +01:00 committed by GitHub
parent 0caa271176
commit 1f558a0d49
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
6 changed files with 157 additions and 38 deletions

View file

@ -507,7 +507,7 @@ impl Event {
// corresponding pre-activation behavior.
pre_activation_result = activation_target
.as_maybe_activatable()
.and_then(|activatable| activatable.legacy_pre_activation_behavior());
.and_then(|activatable| activatable.legacy_pre_activation_behavior(can_gc));
}
let timeline_window = DomRoot::downcast::<Window>(target.global())
@ -623,7 +623,7 @@ impl Event {
// Step 11.2 Otherwise, if activationTarget has legacy-canceled-activation behavior, then run
// activationTargets legacy-canceled-activation behavior.
else {
activatable.legacy_canceled_activation_behavior(pre_activation_result);
activatable.legacy_canceled_activation_behavior(pre_activation_result, can_gc);
}
}
}