1use std::cell::Cell;
6
7use dom_struct::dom_struct;
8use html5ever::local_name;
9
10use crate::dom::bindings::cell::DomRefCell;
11use crate::dom::bindings::codegen::Bindings::ElementInternalsBinding::{
12 ElementInternalsMethods, ValidityStateFlags,
13};
14use crate::dom::bindings::codegen::UnionTypes::FileOrUSVStringOrFormData;
15use crate::dom::bindings::error::{Error, ErrorResult, Fallible};
16use crate::dom::bindings::inheritance::Castable;
17use crate::dom::bindings::reflector::{Reflector, reflect_dom_object};
18use crate::dom::bindings::root::{Dom, DomRoot, LayoutDom, MutNullableDom};
19use crate::dom::bindings::str::{DOMString, USVString};
20use crate::dom::customstateset::CustomStateSet;
21use crate::dom::element::Element;
22use crate::dom::file::File;
23use crate::dom::html::htmlelement::HTMLElement;
24use crate::dom::html::htmlformelement::{FormDatum, FormDatumValue, HTMLFormElement};
25use crate::dom::node::{Node, NodeTraits};
26use crate::dom::nodelist::NodeList;
27use crate::dom::shadowroot::ShadowRoot;
28use crate::dom::validation::{Validatable, is_barred_by_datalist_ancestor};
29use crate::dom::validitystate::{ValidationFlags, ValidityState};
30use crate::script_runtime::CanGc;
31
32#[derive(Clone, JSTraceable, MallocSizeOf)]
33enum SubmissionValue {
34 File(DomRoot<File>),
35 FormData(Vec<FormDatum>),
36 USVString(USVString),
37 None,
38}
39
40impl From<Option<&FileOrUSVStringOrFormData>> for SubmissionValue {
41 fn from(value: Option<&FileOrUSVStringOrFormData>) -> Self {
42 match value {
43 None => SubmissionValue::None,
44 Some(FileOrUSVStringOrFormData::File(file)) => {
45 SubmissionValue::File(DomRoot::from_ref(file))
46 },
47 Some(FileOrUSVStringOrFormData::USVString(usv_string)) => {
48 SubmissionValue::USVString(usv_string.clone())
49 },
50 Some(FileOrUSVStringOrFormData::FormData(form_data)) => {
51 SubmissionValue::FormData(form_data.datums())
52 },
53 }
54 }
55}
56
57#[dom_struct]
58pub(crate) struct ElementInternals {
59 reflector_: Reflector,
60 attached: Cell<bool>,
64 target_element: Dom<HTMLElement>,
65 validity_state: MutNullableDom<ValidityState>,
66 validation_message: DomRefCell<DOMString>,
67 custom_validity_error_message: DomRefCell<DOMString>,
68 validation_anchor: MutNullableDom<HTMLElement>,
69 submission_value: DomRefCell<SubmissionValue>,
70 state: DomRefCell<SubmissionValue>,
71 form_owner: MutNullableDom<HTMLFormElement>,
72 labels_node_list: MutNullableDom<NodeList>,
73
74 states: MutNullableDom<CustomStateSet>,
76}
77
78impl ElementInternals {
79 fn new_inherited(target_element: &HTMLElement) -> ElementInternals {
80 ElementInternals {
81 reflector_: Reflector::new(),
82 attached: Cell::new(false),
83 target_element: Dom::from_ref(target_element),
84 validity_state: Default::default(),
85 validation_message: DomRefCell::new(DOMString::new()),
86 custom_validity_error_message: DomRefCell::new(DOMString::new()),
87 validation_anchor: MutNullableDom::new(None),
88 submission_value: DomRefCell::new(SubmissionValue::None),
89 state: DomRefCell::new(SubmissionValue::None),
90 form_owner: MutNullableDom::new(None),
91 labels_node_list: MutNullableDom::new(None),
92 states: MutNullableDom::new(None),
93 }
94 }
95
96 pub(crate) fn new(element: &HTMLElement, can_gc: CanGc) -> DomRoot<ElementInternals> {
97 let global = element.owner_window();
98 reflect_dom_object(
99 Box::new(ElementInternals::new_inherited(element)),
100 &*global,
101 can_gc,
102 )
103 }
104
105 fn is_target_form_associated(&self) -> bool {
106 self.target_element.is_form_associated_custom_element()
107 }
108
109 fn set_validation_message(&self, message: DOMString) {
110 *self.validation_message.borrow_mut() = message;
111 }
112
113 fn set_custom_validity_error_message(&self, message: DOMString) {
114 *self.custom_validity_error_message.borrow_mut() = message;
115 }
116
117 fn set_submission_value(&self, value: SubmissionValue) {
118 *self.submission_value.borrow_mut() = value;
119 }
120
121 fn set_state(&self, value: SubmissionValue) {
122 *self.state.borrow_mut() = value;
123 }
124
125 pub(crate) fn set_form_owner(&self, form: Option<&HTMLFormElement>) {
126 self.form_owner.set(form);
127 }
128
129 pub(crate) fn form_owner(&self) -> Option<DomRoot<HTMLFormElement>> {
130 self.form_owner.get()
131 }
132
133 pub(crate) fn set_attached(&self) {
134 self.attached.set(true);
135 }
136
137 pub(crate) fn attached(&self) -> bool {
138 self.attached.get()
139 }
140
141 pub(crate) fn perform_entry_construction(&self, entry_list: &mut Vec<FormDatum>) {
142 if self
143 .target_element
144 .upcast::<Element>()
145 .has_attribute(&local_name!("disabled"))
146 {
147 warn!("We are in perform_entry_construction on an element with disabled attribute!");
148 }
149 if self.target_element.upcast::<Element>().disabled_state() {
150 warn!("We are in perform_entry_construction on an element with disabled bit!");
151 }
152 if !self.target_element.upcast::<Element>().enabled_state() {
153 warn!("We are in perform_entry_construction on an element without enabled bit!");
154 }
155
156 if let SubmissionValue::FormData(datums) = &*self.submission_value.borrow() {
157 entry_list.extend(datums.iter().cloned());
158 return;
159 }
160 let name = self
161 .target_element
162 .upcast::<Element>()
163 .get_string_attribute(&local_name!("name"));
164 if name.is_empty() {
165 return;
166 }
167 match &*self.submission_value.borrow() {
168 SubmissionValue::FormData(_) => unreachable!(
169 "The FormData submission value has been handled before name empty checking"
170 ),
171 SubmissionValue::None => {},
172 SubmissionValue::USVString(string) => {
173 entry_list.push(FormDatum {
174 ty: DOMString::from("string"),
175 name,
176 value: FormDatumValue::String(DOMString::from(string.to_string())),
177 });
178 },
179 SubmissionValue::File(file) => {
180 entry_list.push(FormDatum {
181 ty: DOMString::from("file"),
182 name,
183 value: FormDatumValue::File(DomRoot::from_ref(file)),
184 });
185 },
186 }
187 }
188
189 pub(crate) fn is_invalid(&self, can_gc: CanGc) -> bool {
190 self.is_target_form_associated() &&
191 self.is_instance_validatable() &&
192 !self.satisfies_constraints(can_gc)
193 }
194
195 pub(crate) fn custom_states(&self) -> Option<DomRoot<CustomStateSet>> {
196 self.states.get()
197 }
198
199 pub(crate) fn custom_states_for_layout<'a>(&'a self) -> Option<LayoutDom<'a, CustomStateSet>> {
200 #[expect(unsafe_code)]
201 unsafe {
202 self.states.get_inner_as_layout()
203 }
204 }
205}
206
207impl ElementInternalsMethods<crate::DomTypeHolder> for ElementInternals {
208 fn GetShadowRoot(&self) -> Option<DomRoot<ShadowRoot>> {
210 let shadow = self.target_element.upcast::<Element>().shadow_root()?;
214
215 if !shadow.is_available_to_element_internals() {
217 return None;
218 }
219
220 Some(shadow)
222 }
223
224 fn SetFormValue(
226 &self,
227 value: Option<FileOrUSVStringOrFormData>,
228 maybe_state: Option<Option<FileOrUSVStringOrFormData>>,
229 ) -> ErrorResult {
230 if !self.is_target_form_associated() {
232 return Err(Error::NotSupported(None));
233 }
234
235 self.set_submission_value(value.as_ref().into());
237
238 match maybe_state {
239 None => self.set_state(value.as_ref().into()),
241 Some(state) => self.set_state(state.as_ref().into()),
243 }
244 Ok(())
245 }
246
247 fn SetValidity(
249 &self,
250 flags: &ValidityStateFlags,
251 message: Option<DOMString>,
252 anchor: Option<&HTMLElement>,
253 can_gc: CanGc,
254 ) -> ErrorResult {
255 if !self.is_target_form_associated() {
258 return Err(Error::NotSupported(None));
259 }
260
261 let bits: ValidationFlags = flags.into();
264 if !bits.is_empty() && !message.as_ref().map_or_else(|| false, |m| !m.is_empty()) {
265 return Err(Error::Type(
266 "Setting an element to invalid requires a message string as the second argument."
267 .to_string(),
268 ));
269 }
270
271 self.validity_state(can_gc).update_invalid_flags(bits);
274 self.validity_state(can_gc).update_pseudo_classes(can_gc);
275
276 if bits.is_empty() {
279 self.set_validation_message(DOMString::new());
280 } else {
281 self.set_validation_message(message.unwrap_or_default());
282 }
283
284 if bits.contains(ValidationFlags::CUSTOM_ERROR) {
288 self.set_custom_validity_error_message(self.validation_message.borrow().clone());
289 } else {
290 self.set_custom_validity_error_message(DOMString::new());
291 }
292
293 let anchor = match anchor {
294 None => &self.target_element,
296 Some(anchor) => {
299 if !self
300 .target_element
301 .upcast::<Node>()
302 .is_shadow_including_inclusive_ancestor_of(anchor.upcast::<Node>())
303 {
304 return Err(Error::NotFound(None));
305 }
306 anchor
307 },
308 };
309
310 self.validation_anchor.set(Some(anchor));
312
313 Ok(())
314 }
315
316 fn GetValidationMessage(&self) -> Fallible<DOMString> {
318 if !self.is_target_form_associated() {
321 return Err(Error::NotSupported(None));
322 }
323 Ok(self.validation_message.borrow().clone())
324 }
325
326 fn GetValidity(&self, can_gc: CanGc) -> Fallible<DomRoot<ValidityState>> {
328 if !self.is_target_form_associated() {
329 return Err(Error::NotSupported(None));
330 }
331 Ok(self.validity_state(can_gc))
332 }
333
334 fn GetLabels(&self, can_gc: CanGc) -> Fallible<DomRoot<NodeList>> {
336 if !self.is_target_form_associated() {
337 return Err(Error::NotSupported(None));
338 }
339 Ok(self.labels_node_list.or_init(|| {
340 NodeList::new_labels_list(
341 self.target_element.upcast::<Node>().owner_doc().window(),
342 &self.target_element,
343 can_gc,
344 )
345 }))
346 }
347
348 fn GetWillValidate(&self) -> Fallible<bool> {
350 if !self.is_target_form_associated() {
351 return Err(Error::NotSupported(None));
352 }
353 Ok(self.is_instance_validatable())
354 }
355
356 fn GetForm(&self) -> Fallible<Option<DomRoot<HTMLFormElement>>> {
358 if !self.is_target_form_associated() {
359 return Err(Error::NotSupported(None));
360 }
361 Ok(self.form_owner.get())
362 }
363
364 fn CheckValidity(&self, can_gc: CanGc) -> Fallible<bool> {
366 if !self.is_target_form_associated() {
367 return Err(Error::NotSupported(None));
368 }
369 Ok(self.check_validity(can_gc))
370 }
371
372 fn ReportValidity(&self, can_gc: CanGc) -> Fallible<bool> {
374 if !self.is_target_form_associated() {
375 return Err(Error::NotSupported(None));
376 }
377 Ok(self.report_validity(can_gc))
378 }
379
380 fn States(&self, can_gc: CanGc) -> DomRoot<CustomStateSet> {
382 self.states.or_init(|| {
383 CustomStateSet::new(
384 &self.target_element.owner_window(),
385 &self.target_element,
386 can_gc,
387 )
388 })
389 }
390}
391
392impl Validatable for ElementInternals {
394 fn as_element(&self) -> &Element {
395 debug_assert!(self.is_target_form_associated());
396 self.target_element.upcast::<Element>()
397 }
398
399 fn validity_state(&self, can_gc: CanGc) -> DomRoot<ValidityState> {
400 debug_assert!(self.is_target_form_associated());
401 self.validity_state.or_init(|| {
402 ValidityState::new(
403 &self.target_element.owner_window(),
404 self.target_element.upcast(),
405 can_gc,
406 )
407 })
408 }
409
410 fn is_instance_validatable(&self) -> bool {
412 debug_assert!(self.is_target_form_associated());
413 if !self.target_element.is_submittable_element() {
414 return false;
415 }
416
417 !self.as_element().read_write_state() &&
421 !self.as_element().disabled_state() &&
422 !is_barred_by_datalist_ancestor(self.target_element.upcast::<Node>())
423 }
424}