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