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_with_cx};
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, ToLayoutOptional};
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::{
26 FormDatum, FormDatumUnrooted, FormDatumValue, HTMLFormElement,
27};
28use crate::dom::node::{Node, NodeTraits};
29use crate::dom::nodelist::NodeList;
30use crate::dom::shadowroot::ShadowRoot;
31use crate::dom::validation::{Validatable, is_barred_by_datalist_ancestor};
32use crate::dom::validitystate::{ValidationFlags, ValidityState};
33
34#[derive(JSTraceable, MallocSizeOf)]
35#[cfg_attr(crown, crown::unrooted_must_root_lint::must_root)]
36enum SubmissionValue {
37 File(Dom<File>),
38 FormData(Vec<FormDatumUnrooted>),
39 USVString(USVString),
40 None,
41}
42
43impl From<Option<&FileOrUSVStringOrFormData>> for SubmissionValue {
44 fn from(value: Option<&FileOrUSVStringOrFormData>) -> Self {
45 match value {
46 None => SubmissionValue::None,
47 Some(FileOrUSVStringOrFormData::File(file)) => {
48 SubmissionValue::File(Dom::from_ref(file))
49 },
50 Some(FileOrUSVStringOrFormData::USVString(usv_string)) => {
51 SubmissionValue::USVString(usv_string.clone())
52 },
53 Some(FileOrUSVStringOrFormData::FormData(form_data)) => SubmissionValue::FormData(
54 form_data
55 .datums()
56 .into_iter()
57 .map(|data| data.into())
58 .collect(),
59 ),
60 }
61 }
62}
63
64#[dom_struct]
65pub(crate) struct ElementInternals {
66 reflector_: Reflector,
67 attached: Cell<bool>,
71 target_element: Dom<HTMLElement>,
72 validity_state: MutNullableDom<ValidityState>,
73 validation_message: DomRefCell<DOMString>,
74 custom_validity_error_message: DomRefCell<DOMString>,
75 validation_anchor: MutNullableDom<HTMLElement>,
76 submission_value: DomRefCell<SubmissionValue>,
77 state: DomRefCell<SubmissionValue>,
78 form_owner: MutNullableDom<HTMLFormElement>,
79 labels_node_list: MutNullableDom<NodeList>,
80
81 states: MutNullableDom<CustomStateSet>,
83}
84
85impl ElementInternals {
86 fn new_inherited(target_element: &HTMLElement) -> ElementInternals {
87 ElementInternals {
88 reflector_: Reflector::new(),
89 attached: Cell::new(false),
90 target_element: Dom::from_ref(target_element),
91 validity_state: Default::default(),
92 validation_message: DomRefCell::new(DOMString::new()),
93 custom_validity_error_message: DomRefCell::new(DOMString::new()),
94 validation_anchor: MutNullableDom::new(None),
95 submission_value: DomRefCell::new(SubmissionValue::None),
96 state: DomRefCell::new(SubmissionValue::None),
97 form_owner: MutNullableDom::new(None),
98 labels_node_list: MutNullableDom::new(None),
99 states: MutNullableDom::new(None),
100 }
101 }
102
103 pub(crate) fn new(cx: &mut JSContext, element: &HTMLElement) -> DomRoot<ElementInternals> {
104 let global = element.owner_window();
105 reflect_dom_object_with_cx(
106 Box::new(ElementInternals::new_inherited(element)),
107 &*global,
108 cx,
109 )
110 }
111
112 fn is_target_form_associated(&self) -> bool {
113 self.target_element.is_form_associated_custom_element()
114 }
115
116 fn set_validation_message(&self, message: DOMString) {
117 *self.validation_message.borrow_mut() = message;
118 }
119
120 fn set_custom_validity_error_message(&self, message: DOMString) {
121 *self.custom_validity_error_message.borrow_mut() = message;
122 }
123
124 pub(crate) fn set_form_owner(&self, form: Option<&HTMLFormElement>) {
125 self.form_owner.set(form);
126 }
127
128 pub(crate) fn form_owner(&self) -> Option<DomRoot<HTMLFormElement>> {
129 self.form_owner.get()
130 }
131
132 pub(crate) fn set_attached(&self) {
133 self.attached.set(true);
134 }
135
136 pub(crate) fn attached(&self) -> bool {
137 self.attached.get()
138 }
139
140 pub(crate) fn perform_entry_construction(&self, entry_list: &mut Vec<FormDatum>) {
141 if self
142 .target_element
143 .upcast::<Element>()
144 .has_attribute(&local_name!("disabled"))
145 {
146 warn!("We are in perform_entry_construction on an element with disabled attribute!");
147 }
148 if self.target_element.upcast::<Element>().disabled_state() {
149 warn!("We are in perform_entry_construction on an element with disabled bit!");
150 }
151 if !self.target_element.upcast::<Element>().enabled_state() {
152 warn!("We are in perform_entry_construction on an element without enabled bit!");
153 }
154
155 if let SubmissionValue::FormData(datums) = &*self.submission_value.borrow() {
156 entry_list.extend(datums.iter().map(|data| data.root()));
157 return;
158 }
159 let name = self
160 .target_element
161 .upcast::<Element>()
162 .get_string_attribute(&local_name!("name"));
163 if name.is_empty() {
164 return;
165 }
166 match &*self.submission_value.borrow() {
167 SubmissionValue::FormData(_) => unreachable!(
168 "The FormData submission value has been handled before name empty checking"
169 ),
170 SubmissionValue::None => {},
171 SubmissionValue::USVString(string) => {
172 entry_list.push(FormDatum {
173 ty: DOMString::from("string"),
174 name,
175 value: FormDatumValue::String(DOMString::from(string.to_string())),
176 });
177 },
178 SubmissionValue::File(file) => {
179 entry_list.push(FormDatum {
180 ty: DOMString::from("file"),
181 name,
182 value: FormDatumValue::File(DomRoot::from_ref(file)),
183 });
184 },
185 }
186 }
187
188 pub(crate) fn is_invalid(&self, cx: &mut JSContext) -> bool {
189 self.is_target_form_associated() &&
190 self.is_instance_validatable() &&
191 !self.satisfies_constraints(cx)
192 }
193
194 pub(crate) fn custom_states_for_layout<'a>(&'a self) -> Option<LayoutDom<'a, CustomStateSet>> {
195 #[expect(unsafe_code)]
196 unsafe {
197 self.states.to_layout()
198 }
199 }
200}
201
202impl ElementInternalsMethods<crate::DomTypeHolder> for ElementInternals {
203 fn GetShadowRoot(&self) -> Option<DomRoot<ShadowRoot>> {
205 let shadow = self.target_element.upcast::<Element>().shadow_root()?;
209
210 if !shadow.is_available_to_element_internals() {
212 return None;
213 }
214
215 Some(shadow)
217 }
218
219 fn SetFormValue(
221 &self,
222 value: Option<FileOrUSVStringOrFormData>,
223 maybe_state: Option<Option<FileOrUSVStringOrFormData>>,
224 ) -> ErrorResult {
225 if !self.is_target_form_associated() {
227 return Err(Error::NotSupported(Some(
228 "The target element is not a form-associated custom element".to_owned(),
229 )));
230 }
231
232 *self.submission_value.borrow_mut() = value.as_ref().into();
234
235 match maybe_state {
236 None => *self.state.borrow_mut() = value.as_ref().into(),
238 Some(state) => *self.state.borrow_mut() = state.as_ref().into(),
240 }
241 Ok(())
242 }
243
244 fn SetValidity(
246 &self,
247 cx: &mut JSContext,
248 flags: &ValidityStateFlags,
249 message: Option<DOMString>,
250 anchor: Option<&HTMLElement>,
251 ) -> ErrorResult {
252 if !self.is_target_form_associated() {
255 return Err(Error::NotSupported(Some(
256 "The target element is not a form-associated custom element".to_owned(),
257 )));
258 }
259
260 let bits: ValidationFlags = flags.into();
263 if !bits.is_empty() && !message.as_ref().map_or_else(|| false, |m| !m.is_empty()) {
264 return Err(Error::Type(
265 c"Setting an element to invalid requires a message string as the second argument."
266 .to_owned(),
267 ));
268 }
269
270 self.validity_state(cx).update_invalid_flags(bits);
273 self.validity_state(cx).update_pseudo_classes(cx);
274
275 if bits.is_empty() {
278 self.set_validation_message(DOMString::new());
279 } else {
280 self.set_validation_message(message.unwrap_or_default());
281 }
282
283 if bits.contains(ValidationFlags::CUSTOM_ERROR) {
287 self.set_custom_validity_error_message(self.validation_message.borrow().clone());
288 } else {
289 self.set_custom_validity_error_message(DOMString::new());
290 }
291
292 let anchor = match anchor {
293 None => &self.target_element,
295 Some(anchor) => {
298 if !self
299 .target_element
300 .upcast::<Node>()
301 .is_shadow_including_inclusive_ancestor_of(anchor.upcast::<Node>())
302 {
303 return Err(Error::NotFound(Some(
304 "The anchor element is not a shadow-including inclusive descendant of the target element".to_owned(),
305 )));
306 }
307 anchor
308 },
309 };
310
311 self.validation_anchor.set(Some(anchor));
313
314 Ok(())
315 }
316
317 fn GetValidationMessage(&self) -> Fallible<DOMString> {
319 if !self.is_target_form_associated() {
322 return Err(Error::NotSupported(Some(
323 "The target element is not a form-associated custom element".to_owned(),
324 )));
325 }
326 Ok(self.validation_message.borrow().clone())
327 }
328
329 fn GetValidity(&self, cx: &mut JSContext) -> Fallible<DomRoot<ValidityState>> {
331 if !self.is_target_form_associated() {
332 return Err(Error::NotSupported(Some(
333 "The target element is not a form-associated custom element".to_owned(),
334 )));
335 }
336 Ok(self.validity_state(cx))
337 }
338
339 fn GetLabels(&self, cx: &mut JSContext) -> Fallible<DomRoot<NodeList>> {
341 if !self.is_target_form_associated() {
342 return Err(Error::NotSupported(Some(
343 "The target element is not a form-associated custom element".to_owned(),
344 )));
345 }
346 Ok(self.labels_node_list.or_init(|| {
347 NodeList::new_labels_list(
348 cx,
349 self.target_element.upcast::<Node>().owner_doc().window(),
350 &self.target_element,
351 )
352 }))
353 }
354
355 fn GetWillValidate(&self) -> Fallible<bool> {
357 if !self.is_target_form_associated() {
358 return Err(Error::NotSupported(Some(
359 "The target element is not a form-associated custom element".to_owned(),
360 )));
361 }
362 Ok(self.is_instance_validatable())
363 }
364
365 fn GetForm(&self) -> Fallible<Option<DomRoot<HTMLFormElement>>> {
367 if !self.is_target_form_associated() {
368 return Err(Error::NotSupported(Some(
369 "The target element is not a form-associated custom element".to_owned(),
370 )));
371 }
372 Ok(self.form_owner.get())
373 }
374
375 fn CheckValidity(&self, cx: &mut JSContext) -> Fallible<bool> {
377 if !self.is_target_form_associated() {
378 return Err(Error::NotSupported(Some(
379 "The target element is not a form-associated custom element".to_owned(),
380 )));
381 }
382 Ok(self.check_validity(cx))
383 }
384
385 fn ReportValidity(&self, cx: &mut JSContext) -> Fallible<bool> {
387 if !self.is_target_form_associated() {
388 return Err(Error::NotSupported(Some(
389 "The target element is not a form-associated custom element".to_owned(),
390 )));
391 }
392 Ok(self.report_validity(cx))
393 }
394
395 fn States(&self, cx: &mut JSContext) -> DomRoot<CustomStateSet> {
397 self.states.or_init(|| {
398 CustomStateSet::new(
399 cx,
400 &self.target_element.owner_window(),
401 &self.target_element,
402 )
403 })
404 }
405}
406
407impl Validatable for ElementInternals {
409 fn as_element(&self) -> &Element {
410 debug_assert!(self.is_target_form_associated());
411 self.target_element.upcast::<Element>()
412 }
413
414 fn validity_state(&self, cx: &mut JSContext) -> DomRoot<ValidityState> {
415 debug_assert!(self.is_target_form_associated());
416 self.validity_state.or_init(|| {
417 ValidityState::new(
418 cx,
419 &self.target_element.owner_window(),
420 self.target_element.upcast(),
421 )
422 })
423 }
424
425 fn is_instance_validatable(&self) -> bool {
427 debug_assert!(self.is_target_form_associated());
428 if !self.target_element.is_submittable_element() {
429 return false;
430 }
431
432 !self.as_element().read_write_state() &&
436 !self.as_element().disabled_state() &&
437 !is_barred_by_datalist_ancestor(self.target_element.upcast::<Node>())
438 }
439}