/
githubmirror
/
servo
Обзор
Документация
Войти
/
githubmirror
/
servo
Код
Запросы
0
Пакеты
0
Релизы
0
Аналитика
Безопасность
main
components/script/dom/elementinternals.rs
439 строк
17 KB
Martin Robinson
script_bindings: Remove the `LayoutFromRaw` trait (#46506)
15 июл 2026, 01:56
Не верифицирован
15 июл 2026, 01:56
5b11a82
Код
Авторство
О чём код?
/* This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ use std::cell::Cell; use dom_struct::dom_struct; use html5ever::local_name; use js::context::JSContext; use script_bindings::cell::DomRefCell; use script_bindings::reflector::{Reflector, reflect_dom_object_with_cx}; use crate::dom::bindings::codegen::Bindings::ElementInternalsBinding::{ ElementInternalsMethods, ValidityStateFlags, }; use crate::dom::bindings::codegen::UnionTypes::FileOrUSVStringOrFormData; use crate::dom::bindings::error::{Error, ErrorResult, Fallible}; use crate::dom::bindings::inheritance::Castable; use crate::dom::bindings::root::{Dom, DomRoot, LayoutDom, MutNullableDom, ToLayoutOptional}; use crate::dom::bindings::str::{DOMString, USVString}; use crate::dom::customstateset::CustomStateSet; use crate::dom::element::Element; use crate::dom::file::File; use crate::dom::html::htmlelement::HTMLElement; use crate::dom::html::htmlformelement::{ FormDatum, FormDatumUnrooted, FormDatumValue, HTMLFormElement, }; use crate::dom::node::{Node, NodeTraits}; use crate::dom::nodelist::NodeList; use crate::dom::shadowroot::ShadowRoot; use crate::dom::validation::{Validatable, is_barred_by_datalist_ancestor}; use crate::dom::validitystate::{ValidationFlags, ValidityState}; #[derive(JSTraceable, MallocSizeOf)] #[cfg_attr(crown, crown::unrooted_must_root_lint::must_root)] enum SubmissionValue { File(Dom<File>), FormData(Vec<FormDatumUnrooted>), USVString(USVString), None, } impl From<Option<&FileOrUSVStringOrFormData>> for SubmissionValue { fn from(value: Option<&FileOrUSVStringOrFormData>) -> Self { match value { None => SubmissionValue::None, Some(FileOrUSVStringOrFormData::File(file)) => { SubmissionValue::File(Dom::from_ref(file)) }, Some(FileOrUSVStringOrFormData::USVString(usv_string)) => { SubmissionValue::USVString(usv_string.clone()) }, Some(FileOrUSVStringOrFormData::FormData(form_data)) => SubmissionValue::FormData( form_data .datums() .into_iter() .map(|data| data.into()) .collect(), ), } } } #[dom_struct] pub(crate) struct ElementInternals { reflector_: Reflector, /// If `attached` is false, we're using this to hold form-related state /// on an element for which `attachInternals()` wasn't called yet; this is /// necessary because it might have a form owner. attached: Cell<bool>, target_element: Dom<HTMLElement>, validity_state: MutNullableDom<ValidityState>, validation_message: DomRefCell<DOMString>, custom_validity_error_message: DomRefCell<DOMString>, validation_anchor: MutNullableDom<HTMLElement>, submission_value: DomRefCell<SubmissionValue>, state: DomRefCell<SubmissionValue>, form_owner: MutNullableDom<HTMLFormElement>, labels_node_list: MutNullableDom<NodeList>, /// <https://html.spec.whatwg.org/multipage/#dom-elementinternals-states> states: MutNullableDom<CustomStateSet>, } impl ElementInternals { fn new_inherited(target_element: &HTMLElement) -> ElementInternals { ElementInternals { reflector_: Reflector::new(), attached: Cell::new(false), target_element: Dom::from_ref(target_element), validity_state: Default::default(), validation_message: DomRefCell::new(DOMString::new()), custom_validity_error_message: DomRefCell::new(DOMString::new()), validation_anchor: MutNullableDom::new(None), submission_value: DomRefCell::new(SubmissionValue::None), state: DomRefCell::new(SubmissionValue::None), form_owner: MutNullableDom::new(None), labels_node_list: MutNullableDom::new(None), states: MutNullableDom::new(None), } } pub(crate) fn new(cx: &mut JSContext, element: &HTMLElement) -> DomRoot<ElementInternals> { let global = element.owner_window(); reflect_dom_object_with_cx( Box::new(ElementInternals::new_inherited(element)), &*global, cx, ) } fn is_target_form_associated(&self) -> bool { self.target_element.is_form_associated_custom_element() } fn set_validation_message(&self, message: DOMString) { *self.validation_message.borrow_mut() = message; } fn set_custom_validity_error_message(&self, message: DOMString) { *self.custom_validity_error_message.borrow_mut() = message; } pub(crate) fn set_form_owner(&self, form: Option<&HTMLFormElement>) { self.form_owner.set(form); } pub(crate) fn form_owner(&self) -> Option<DomRoot<HTMLFormElement>> { self.form_owner.get() } pub(crate) fn set_attached(&self) { self.attached.set(true); } pub(crate) fn attached(&self) -> bool { self.attached.get() } pub(crate) fn perform_entry_construction(&self, entry_list: &mut Vec<FormDatum>) { if self .target_element .upcast::<Element>() .has_attribute(&local_name!("disabled")) { warn!("We are in perform_entry_construction on an element with disabled attribute!"); } if self.target_element.upcast::<Element>().disabled_state() { warn!("We are in perform_entry_construction on an element with disabled bit!"); } if !self.target_element.upcast::<Element>().enabled_state() { warn!("We are in perform_entry_construction on an element without enabled bit!"); } if let SubmissionValue::FormData(datums) = &*self.submission_value.borrow() { entry_list.extend(datums.iter().map(|data| data.root())); return; } let name = self .target_element .upcast::<Element>() .get_string_attribute(&local_name!("name")); if name.is_empty() { return; } match &*self.submission_value.borrow() { SubmissionValue::FormData(_) => unreachable!( "The FormData submission value has been handled before name empty checking" ), SubmissionValue::None => {}, SubmissionValue::USVString(string) => { entry_list.push(FormDatum { ty: DOMString::from("string"), name, value: FormDatumValue::String(DOMString::from(string.to_string())), }); }, SubmissionValue::File(file) => { entry_list.push(FormDatum { ty: DOMString::from("file"), name, value: FormDatumValue::File(DomRoot::from_ref(file)), }); }, } } pub(crate) fn is_invalid(&self, cx: &mut JSContext) -> bool { self.is_target_form_associated() && self.is_instance_validatable() && !self.satisfies_constraints(cx) } pub(crate) fn custom_states_for_layout<'a>(&'a self) -> Option<LayoutDom<'a, CustomStateSet>> { #[expect(unsafe_code)] unsafe { self.states.to_layout() } } } impl ElementInternalsMethods<crate::DomTypeHolder> for ElementInternals { /// <https://html.spec.whatwg.org/multipage/#dom-elementinternals-shadowroot> fn GetShadowRoot(&self) -> Option<DomRoot<ShadowRoot>> { // Step 1. Let target be this's target element. // Step 2. If target is not a shadow host, then return null. // Step 3. Let shadow be target's shadow root. let shadow = self.target_element.upcast::<Element>().shadow_root()?; // Step 4. If shadow's available to element internals is false, then return null. if !shadow.is_available_to_element_internals() { return None; } // Step 5. Return shadow. Some(shadow) } /// <https://html.spec.whatwg.org/multipage#dom-elementinternals-setformvalue> fn SetFormValue( &self, value: Option<FileOrUSVStringOrFormData>, maybe_state: Option<Option<FileOrUSVStringOrFormData>>, ) -> ErrorResult { // Steps 1-2: If element is not a form-associated custom element, then throw a "NotSupportedError" DOMException if !self.is_target_form_associated() { return Err(Error::NotSupported(Some( "The target element is not a form-associated custom element".to_owned(), ))); } // Step 3: Set target element's submission value *self.submission_value.borrow_mut() = value.as_ref().into(); match maybe_state { // Step 4: If the state argument of the function is omitted, set element's state to its submission value None => *self.state.borrow_mut() = value.as_ref().into(), // Steps 5-6: Otherwise, set element's state to state Some(state) => *self.state.borrow_mut() = state.as_ref().into(), } Ok(()) } /// <https://html.spec.whatwg.org/multipage#dom-elementinternals-setvalidity> fn SetValidity( &self, cx: &mut JSContext, flags: &ValidityStateFlags, message: Option<DOMString>, anchor: Option<&HTMLElement>, ) -> ErrorResult { // Step 1. Let element be this's target element. // Step 2: If element is not a form-associated custom element, then throw a "NotSupportedError" DOMException. if !self.is_target_form_associated() { return Err(Error::NotSupported(Some( "The target element is not a form-associated custom element".to_owned(), ))); } // Step 3: If flags contains one or more true values and message is not given or is the empty // string, then throw a TypeError. let bits: ValidationFlags = flags.into(); if !bits.is_empty() && !message.as_ref().map_or_else(|| false, |m| !m.is_empty()) { return Err(Error::Type( c"Setting an element to invalid requires a message string as the second argument." .to_owned(), )); } // Step 4: For each entry `flag` → `value` of `flags`, set element's validity flag with the name // `flag` to `value`. self.validity_state(cx).update_invalid_flags(bits); self.validity_state(cx).update_pseudo_classes(cx); // Step 5: Set element's validation message to the empty string if message is not given // or all of element's validity flags are false, or to message otherwise. if bits.is_empty() { self.set_validation_message(DOMString::new()); } else { self.set_validation_message(message.unwrap_or_default()); } // Step 6: If element's customError validity flag is true, then set element's custom validity error // message to element's validation message. Otherwise, set element's custom validity error // message to the empty string. if bits.contains(ValidationFlags::CUSTOM_ERROR) { self.set_custom_validity_error_message(self.validation_message.borrow().clone()); } else { self.set_custom_validity_error_message(DOMString::new()); } let anchor = match anchor { // Step 7: If anchor is not given, then set it to element. None => &self.target_element, // Step 8. Otherwise, if anchor is not a shadow-including inclusive descendant of element, // then throw a "NotFoundError" DOMException. Some(anchor) => { if !self .target_element .upcast::<Node>() .is_shadow_including_inclusive_ancestor_of(anchor.upcast::<Node>()) { return Err(Error::NotFound(Some( "The anchor element is not a shadow-including inclusive descendant of the target element".to_owned(), ))); } anchor }, }; // Step 9. Set element's validation anchor to anchor. self.validation_anchor.set(Some(anchor)); Ok(()) } /// <https://html.spec.whatwg.org/multipage#dom-elementinternals-validationmessage> fn GetValidationMessage(&self) -> Fallible<DOMString> { // This check isn't in the spec but it's in WPT tests and it maintains // consistency with other methods that do specify it if !self.is_target_form_associated() { return Err(Error::NotSupported(Some( "The target element is not a form-associated custom element".to_owned(), ))); } Ok(self.validation_message.borrow().clone()) } /// <https://html.spec.whatwg.org/multipage#dom-elementinternals-validity> fn GetValidity(&self, cx: &mut JSContext) -> Fallible<DomRoot<ValidityState>> { if !self.is_target_form_associated() { return Err(Error::NotSupported(Some( "The target element is not a form-associated custom element".to_owned(), ))); } Ok(self.validity_state(cx)) } /// <https://html.spec.whatwg.org/multipage#dom-elementinternals-labels> fn GetLabels(&self, cx: &mut JSContext) -> Fallible<DomRoot<NodeList>> { if !self.is_target_form_associated() { return Err(Error::NotSupported(Some( "The target element is not a form-associated custom element".to_owned(), ))); } Ok(self.labels_node_list.or_init(|| { NodeList::new_labels_list( cx, self.target_element.upcast::<Node>().owner_doc().window(), &self.target_element, ) })) } /// <https://html.spec.whatwg.org/multipage#dom-elementinternals-willvalidate> fn GetWillValidate(&self) -> Fallible<bool> { if !self.is_target_form_associated() { return Err(Error::NotSupported(Some( "The target element is not a form-associated custom element".to_owned(), ))); } Ok(self.is_instance_validatable()) } /// <https://html.spec.whatwg.org/multipage#dom-elementinternals-form> fn GetForm(&self) -> Fallible<Option<DomRoot<HTMLFormElement>>> { if !self.is_target_form_associated() { return Err(Error::NotSupported(Some( "The target element is not a form-associated custom element".to_owned(), ))); } Ok(self.form_owner.get()) } /// <https://html.spec.whatwg.org/multipage#dom-elementinternals-checkvalidity> fn CheckValidity(&self, cx: &mut JSContext) -> Fallible<bool> { if !self.is_target_form_associated() { return Err(Error::NotSupported(Some( "The target element is not a form-associated custom element".to_owned(), ))); } Ok(self.check_validity(cx)) } /// <https://html.spec.whatwg.org/multipage#dom-elementinternals-reportvalidity> fn ReportValidity(&self, cx: &mut JSContext) -> Fallible<bool> { if !self.is_target_form_associated() { return Err(Error::NotSupported(Some( "The target element is not a form-associated custom element".to_owned(), ))); } Ok(self.report_validity(cx)) } /// <https://html.spec.whatwg.org/multipage/#dom-elementinternals-states> fn States(&self, cx: &mut JSContext) -> DomRoot<CustomStateSet> { self.states.or_init(|| { CustomStateSet::new( cx, &self.target_element.owner_window(), &self.target_element, ) }) } } // Form-associated custom elements also need the Validatable trait. impl Validatable for ElementInternals { fn as_element(&self) -> &Element { debug_assert!(self.is_target_form_associated()); self.target_element.upcast::<Element>() } fn validity_state(&self, cx: &mut JSContext) -> DomRoot<ValidityState> { debug_assert!(self.is_target_form_associated()); self.validity_state.or_init(|| { ValidityState::new( cx, &self.target_element.owner_window(), self.target_element.upcast(), ) }) } /// <https://html.spec.whatwg.org/multipage#candidate-for-constraint-validation> fn is_instance_validatable(&self) -> bool { debug_assert!(self.is_target_form_associated()); if !self.target_element.is_submittable_element() { return false; } // The form-associated custom element is barred from constraint validation, // if the readonly attribute is specified, the element is disabled, // or the element has a datalist element ancestor. !self.as_element().read_write_state() && !self.as_element().disabled_state() && !is_barred_by_datalist_ancestor(self.target_element.upcast::<Node>()) } }