Implement form-associated custom elements and their ElementInternals (#31980)

* FACEs work, setFormValue test is awful so now has _mozilla backup

* 1. Impl Validatable in ElementInternals instead of HTMLElement. 2. Reuse the code in Validatable trait. 3. The form associated custom element is not a customized built-in element.

* add some comments

* support readonly attribute and complete barred from constraint validation

* Addressed the code review comments

* Updated the legacy-layout results

* Fixed the WPT failures in ElementInternals-validation.html

* Addressed the code review comments

* Review suggestions

* Fixed silly mistakes and update the test result outside elementinternals

* update the test results

---------

Co-authored-by: Patrick Shaughnessy <pshaughn@comcast.net>
Co-authored-by: Martin Robinson <mrobinson@igalia.com>
This commit is contained in:
cathiechen 2024-04-11 15:17:11 +02:00 committed by GitHub
parent 2eb959a159
commit 4e4a4c0a28
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
67 changed files with 1641 additions and 619 deletions

View file

@ -46,6 +46,7 @@ use crate::dom::bindings::reflector::DomObject;
use crate::dom::bindings::root::{Dom, DomOnceCell, DomRoot, MutNullableDom};
use crate::dom::bindings::str::DOMString;
use crate::dom::blob::Blob;
use crate::dom::customelementregistry::CallbackReaction;
use crate::dom::document::Document;
use crate::dom::domtokenlist::DOMTokenList;
use crate::dom::element::{AttributeMutation, Element};
@ -77,9 +78,9 @@ use crate::dom::node::{
use crate::dom::nodelist::{NodeList, RadioListMode};
use crate::dom::radionodelist::RadioNodeList;
use crate::dom::submitevent::SubmitEvent;
use crate::dom::validitystate::ValidationFlags;
use crate::dom::virtualmethods::VirtualMethods;
use crate::dom::window::Window;
use crate::script_thread::ScriptThread;
use crate::task_source::TaskSource;
#[derive(Clone, Copy, JSTraceable, MallocSizeOf, PartialEq)]
@ -363,6 +364,14 @@ impl HTMLFormElementMethods for HTMLFormElement {
HTMLElementTypeId::HTMLTextAreaElement => {
elem.downcast::<HTMLTextAreaElement>().unwrap().form_owner()
},
HTMLElementTypeId::HTMLElement => {
let html_element = elem.downcast::<HTMLElement>().unwrap();
if html_element.is_form_associated_custom_element() {
html_element.form_owner()
} else {
return false;
}
},
_ => {
debug_assert!(!elem
.downcast::<HTMLElement>()
@ -673,14 +682,7 @@ impl HTMLFormElement {
pub fn update_validity(&self) {
let controls = self.controls.borrow();
let is_any_invalid = controls
.iter()
.filter_map(|control| control.as_maybe_validatable())
.any(|validatable| {
validatable.is_instance_validatable() &&
!validatable.validity_state().invalid_flags().is_empty()
});
let is_any_invalid = controls.iter().any(|control| control.is_invalid(false));
self.upcast::<Element>()
.set_state(ElementState::VALID, !is_any_invalid);
@ -1027,6 +1029,8 @@ impl HTMLFormElement {
}
}
// If it's form-associated and has a validation anchor, point the
// user there instead of the element itself.
// Step 4
Err(())
}
@ -1039,20 +1043,11 @@ impl HTMLFormElement {
let invalid_controls = controls
.iter()
.filter_map(|field| {
if let Some(el) = field.downcast::<Element>() {
let validatable = match el.as_maybe_validatable() {
Some(v) => v,
None => return None,
};
validatable
.validity_state()
.perform_validation_and_update(ValidationFlags::all());
if !validatable.is_instance_validatable() ||
validatable.validity_state().invalid_flags().is_empty()
{
None
if let Some(element) = field.downcast::<Element>() {
if element.is_invalid(true) {
Some(DomRoot::from_ref(element))
} else {
Some(DomRoot::from_ref(el))
None
}
} else {
None
@ -1135,6 +1130,15 @@ impl HTMLFormElement {
});
}
},
HTMLElementTypeId::HTMLElement => {
let custom = child.downcast::<HTMLElement>().unwrap();
if custom.is_form_associated_custom_element() {
// https://html.spec.whatwg.org/multipage/#face-entry-construction
let internals = custom.upcast::<Element>().ensure_element_internals();
internals.perform_entry_construction(&mut data_set);
// Otherwise no form value has been set so there is nothing to do.
}
},
_ => (),
}
}
@ -1287,6 +1291,16 @@ impl HTMLFormElement {
)) => {
child.downcast::<HTMLOutputElement>().unwrap().reset();
},
NodeTypeId::Element(ElementTypeId::HTMLElement(HTMLElementTypeId::HTMLElement)) => {
let html_element = child.downcast::<HTMLElement>().unwrap();
if html_element.is_form_associated_custom_element() {
ScriptThread::enqueue_callback_reaction(
html_element.upcast::<Element>(),
CallbackReaction::FormReset,
None,
)
}
},
_ => {},
}
}
@ -1550,6 +1564,19 @@ pub trait FormControl: DomObject {
if let Some(ref new_owner) = new_owner {
new_owner.add_control(self);
}
// https://html.spec.whatwg.org/multipage/#custom-element-reactions:reset-the-form-owner
if let Some(html_elem) = elem.downcast::<HTMLElement>() {
if html_elem.is_form_associated_custom_element() {
ScriptThread::enqueue_callback_reaction(
elem,
CallbackReaction::FormAssociated(match new_owner {
None => None,
Some(ref form) => Some(DomRoot::from_ref(&**form)),
}),
None,
)
}
}
self.set_form_owner(new_owner.as_deref());
}
}
@ -1745,7 +1772,13 @@ impl FormControlElementHelpers for Element {
NodeTypeId::Element(ElementTypeId::HTMLElement(
HTMLElementTypeId::HTMLTextAreaElement,
)) => Some(self.downcast::<HTMLTextAreaElement>().unwrap() as &dyn FormControl),
_ => None,
_ => self.downcast::<HTMLElement>().and_then(|elem| {
if elem.is_form_associated_custom_element() {
Some(elem as &dyn FormControl)
} else {
None
}
}),
}
}
}