script/dom/html/internals/
elementinternals.rs1use std::cell::Cell;
6
7use dom_struct::dom_struct;
8use html5ever::local_name;
9use js::context::{JSContext, NoGC};
10use script_bindings::cell::DomRefCell;
11use script_bindings::domstring::TracedDOMString;
12use script_bindings::reflector::{Reflector, reflect_dom_object};
13
14use crate::dom::bindings::codegen::Bindings::ElementInternalsBinding::{
15 ElementInternalsMethods, ValidityStateFlags,
16};
17use crate::dom::bindings::codegen::UnionTypes::FileOrUSVStringOrFormData;
18use crate::dom::bindings::error::{Error, ErrorResult, Fallible};
19use crate::dom::bindings::inheritance::Castable;
20use crate::dom::bindings::root::{Dom, DomRoot, LayoutDom, MutNullableDom, ToLayoutOptional};
21use crate::dom::bindings::str::{DOMString, USVString};
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::html::internals::customstateset::CustomStateSet;
29use crate::dom::node::{Node, NodeTraits};
30use crate::dom::nodelist::NodeList;
31use crate::dom::shadowroot::shadowroot::ShadowRoot;
32use crate::dom::validation::{Validatable, is_barred_by_datalist_ancestor};
33use crate::dom::validitystate::{ValidationFlags, ValidityState};
34
35#[derive(JSTraceable, MallocSizeOf)]
36#[cfg_attr(crown, crown::unrooted_must_root_lint::must_root)]
37enum SubmissionValue {
38 File(Dom<File>),
39 FormData(Vec<FormDatumUnrooted>),
40 USVString(USVString),
41 None,
42}
43
44impl From<Option<&FileOrUSVStringOrFormData>> for SubmissionValue {
45 fn from(value: Option<&FileOrUSVStringOrFormData>) -> Self {
46 match value {
47 None => SubmissionValue::None,
48 Some(FileOrUSVStringOrFormData::File(file)) => {
49 SubmissionValue::File(Dom::from_ref(file))
50 },
51 Some(FileOrUSVStringOrFormData::USVString(usv_string)) => {
52 SubmissionValue::USVString(usv_string.clone())
53 },
54 Some(FileOrUSVStringOrFormData::FormData(form_data)) => SubmissionValue::FormData(
55 form_data
56 .datums()
57 .into_iter()
58 .map(|data| data.into())
59 .collect(),
60 ),
61 }
62 }
63}
64
65#[dom_struct]
66pub(crate) struct ElementInternals {
67 reflector_: Reflector,
68 attached: Cell<bool>,
72 target_element: Dom<HTMLElement>,
73 validity_state: MutNullableDom<ValidityState>,
74 validation_message: DomRefCell<TracedDOMString>,
75 custom_validity_error_message: DomRefCell<TracedDOMString>,
76 validation_anchor: MutNullableDom<HTMLElement>,
77 submission_value: DomRefCell<SubmissionValue>,
78 state: DomRefCell<SubmissionValue>,
79 form_owner: MutNullableDom<HTMLFormElement>,
80 labels_node_list: MutNullableDom<NodeList>,
81
82 states: MutNullableDom<CustomStateSet>,
84}
85
86impl ElementInternals {
87 fn new_inherited(target_element: &HTMLElement) -> ElementInternals {
88 ElementInternals {
89 reflector_: Reflector::new(),
90 attached: Cell::new(false),
91 target_element: Dom::from_ref(target_element),
92 validity_state: Default::default(),
93 validation_message: DomRefCell::new(DOMString::new().traced()),
94 custom_validity_error_message: DomRefCell::new(DOMString::new().traced()),
95 validation_anchor: MutNullableDom::new(None),
96 submission_value: DomRefCell::new(SubmissionValue::None),
97 state: DomRefCell::new(SubmissionValue::None),
98 form_owner: MutNullableDom::new(None),
99 labels_node_list: MutNullableDom::new(None),
100 states: MutNullableDom::new(None),
101 }
102 }
103
104 pub(crate) fn new(cx: &mut JSContext, element: &HTMLElement) -> DomRoot<ElementInternals> {
105 let global = element.owner_window();
106 reflect_dom_object(
107 cx,
108 Box::new(ElementInternals::new_inherited(element)),
109 &*global,
110 )
111 }
112
113 fn is_target_form_associated(&self) -> bool {
114 self.target_element.is_form_associated_custom_element()
115 }
116
117 fn set_validation_message(&self, message: DOMString) {
118 *self.validation_message.borrow_mut() = message.traced();
119 }
120
121 fn set_custom_validity_error_message(&self, message: DOMString) {
122 *self.custom_validity_error_message.borrow_mut() = message.traced();
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().map(|data| data.root()));
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_static("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_static("file"),
182 name,
183 value: FormDatumValue::File(DomRoot::from_ref(file)),
184 });
185 },
186 }
187 }
188
189 pub(crate) fn is_invalid(&self, cx: &mut JSContext) -> bool {
190 self.is_target_form_associated() &&
191 self.is_instance_validatable(cx.no_gc()) &&
192 !self.satisfies_constraints(cx)
193 }
194
195 pub(crate) fn custom_states_for_layout<'a>(&'a self) -> Option<LayoutDom<'a, CustomStateSet>> {
196 #[expect(unsafe_code)]
197 unsafe {
198 self.states.to_layout()
199 }
200 }
201}
202
203impl ElementInternalsMethods<crate::DomTypeHolder> for ElementInternals {
204 fn GetShadowRoot(&self) -> Option<DomRoot<ShadowRoot>> {
206 let shadow = self.target_element.upcast::<Element>().shadow_root()?;
210
211 if !shadow.is_available_to_element_internals() {
213 return None;
214 }
215
216 Some(shadow)
218 }
219
220 fn SetFormValue(
222 &self,
223 value: Option<FileOrUSVStringOrFormData>,
224 maybe_state: Option<Option<FileOrUSVStringOrFormData>>,
225 ) -> ErrorResult {
226 if !self.is_target_form_associated() {
228 return Err(Error::NotSupported(Some(
229 "The target element is not a form-associated custom element".to_owned(),
230 )));
231 }
232
233 *self.submission_value.borrow_mut() = value.as_ref().into();
235
236 match maybe_state {
237 None => *self.state.borrow_mut() = value.as_ref().into(),
239 Some(state) => *self.state.borrow_mut() = state.as_ref().into(),
241 }
242 Ok(())
243 }
244
245 fn SetValidity(
247 &self,
248 cx: &mut JSContext,
249 flags: &ValidityStateFlags,
250 message: Option<DOMString>,
251 anchor: Option<&HTMLElement>,
252 ) -> ErrorResult {
253 if !self.is_target_form_associated() {
256 return Err(Error::NotSupported(Some(
257 "The target element is not a form-associated custom element".to_owned(),
258 )));
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 c"Setting an element to invalid requires a message string as the second argument."
267 .to_owned(),
268 ));
269 }
270
271 self.validity_state(cx).update_invalid_flags(bits);
274 self.validity_state(cx).update_pseudo_classes(cx);
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().root());
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(Some(
305 "The anchor element is not a shadow-including inclusive descendant of the target element".to_owned(),
306 )));
307 }
308 anchor
309 },
310 };
311
312 self.validation_anchor.set(Some(anchor));
314
315 Ok(())
316 }
317
318 fn GetValidationMessage(&self) -> Fallible<DOMString> {
320 if !self.is_target_form_associated() {
323 return Err(Error::NotSupported(Some(
324 "The target element is not a form-associated custom element".to_owned(),
325 )));
326 }
327 Ok(self.validation_message.borrow().clone().root())
328 }
329
330 fn GetValidity(&self, cx: &mut JSContext) -> Fallible<DomRoot<ValidityState>> {
332 if !self.is_target_form_associated() {
333 return Err(Error::NotSupported(Some(
334 "The target element is not a form-associated custom element".to_owned(),
335 )));
336 }
337 Ok(self.validity_state(cx))
338 }
339
340 fn GetLabels(&self, cx: &mut JSContext) -> Fallible<DomRoot<NodeList>> {
342 if !self.is_target_form_associated() {
343 return Err(Error::NotSupported(Some(
344 "The target element is not a form-associated custom element".to_owned(),
345 )));
346 }
347 Ok(self.labels_node_list.or_init(|| {
348 NodeList::new_labels_list(
349 cx,
350 self.target_element.upcast::<Node>().owner_doc().window(),
351 &self.target_element,
352 )
353 }))
354 }
355
356 fn GetWillValidate(&self, no_gc: &NoGC) -> Fallible<bool> {
358 if !self.is_target_form_associated() {
359 return Err(Error::NotSupported(Some(
360 "The target element is not a form-associated custom element".to_owned(),
361 )));
362 }
363 Ok(self.is_instance_validatable(no_gc))
364 }
365
366 fn GetForm(&self) -> Fallible<Option<DomRoot<HTMLFormElement>>> {
368 if !self.is_target_form_associated() {
369 return Err(Error::NotSupported(Some(
370 "The target element is not a form-associated custom element".to_owned(),
371 )));
372 }
373 Ok(self.form_owner.get())
374 }
375
376 fn CheckValidity(&self, cx: &mut JSContext) -> Fallible<bool> {
378 if !self.is_target_form_associated() {
379 return Err(Error::NotSupported(Some(
380 "The target element is not a form-associated custom element".to_owned(),
381 )));
382 }
383 Ok(self.check_validity(cx))
384 }
385
386 fn ReportValidity(&self, cx: &mut JSContext) -> Fallible<bool> {
388 if !self.is_target_form_associated() {
389 return Err(Error::NotSupported(Some(
390 "The target element is not a form-associated custom element".to_owned(),
391 )));
392 }
393 Ok(self.report_validity(cx))
394 }
395
396 fn States(&self, cx: &mut JSContext) -> DomRoot<CustomStateSet> {
398 self.states.or_init(|| {
399 CustomStateSet::new(
400 cx,
401 &self.target_element.owner_window(),
402 &self.target_element,
403 )
404 })
405 }
406}
407
408impl Validatable for ElementInternals {
410 fn as_element(&self) -> &Element {
411 debug_assert!(self.is_target_form_associated());
412 self.target_element.upcast::<Element>()
413 }
414
415 fn validity_state(&self, cx: &mut JSContext) -> DomRoot<ValidityState> {
416 debug_assert!(self.is_target_form_associated());
417 self.validity_state.or_init(|| {
418 ValidityState::new(
419 cx,
420 &self.target_element.owner_window(),
421 self.target_element.upcast(),
422 )
423 })
424 }
425
426 fn is_instance_validatable(&self, no_gc: &NoGC) -> bool {
428 debug_assert!(self.is_target_form_associated());
429 if !self.target_element.is_submittable_element() {
430 return false;
431 }
432
433 !self.as_element().read_write_state() &&
437 !self.as_element().disabled_state() &&
438 !is_barred_by_datalist_ancestor(no_gc, self.target_element.upcast::<Node>())
439 }
440}