script/dom/
elementinternals.rs

1/* This Source Code Form is subject to the terms of the Mozilla Public
2 * License, v. 2.0. If a copy of the MPL was not distributed with this
3 * file, You can obtain one at https://mozilla.org/MPL/2.0/. */
4
5use 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, 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    /// If `attached` is false, we're using this to hold form-related state
61    /// on an element for which `attachInternals()` wasn't called yet; this is
62    /// necessary because it might have a form owner.
63    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    /// <https://html.spec.whatwg.org/multipage/#dom-elementinternals-states>
75    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) -> bool {
190        self.is_target_form_associated() &&
191            self.is_instance_validatable() &&
192            !self.satisfies_constraints()
193    }
194
195    pub(crate) fn custom_states(&self) -> Option<DomRoot<CustomStateSet>> {
196        self.states.get()
197    }
198}
199
200impl ElementInternalsMethods<crate::DomTypeHolder> for ElementInternals {
201    /// <https://html.spec.whatwg.org/multipage/#dom-elementinternals-shadowroot>
202    fn GetShadowRoot(&self) -> Option<DomRoot<ShadowRoot>> {
203        // Step 1. Let target be this's target element.
204        // Step 2. If target is not a shadow host, then return null.
205        // Step 3. Let shadow be target's shadow root.
206        let shadow = self.target_element.upcast::<Element>().shadow_root()?;
207
208        // Step 4. If shadow's available to element internals is false, then return null.
209        if !shadow.is_available_to_element_internals() {
210            return None;
211        }
212
213        // Step 5. Return shadow.
214        Some(shadow)
215    }
216
217    /// <https://html.spec.whatwg.org/multipage#dom-elementinternals-setformvalue>
218    fn SetFormValue(
219        &self,
220        value: Option<FileOrUSVStringOrFormData>,
221        maybe_state: Option<Option<FileOrUSVStringOrFormData>>,
222    ) -> ErrorResult {
223        // Steps 1-2: If element is not a form-associated custom element, then throw a "NotSupportedError" DOMException
224        if !self.is_target_form_associated() {
225            return Err(Error::NotSupported);
226        }
227
228        // Step 3: Set target element's submission value
229        self.set_submission_value(value.as_ref().into());
230
231        match maybe_state {
232            // Step 4: If the state argument of the function is omitted, set element's state to its submission value
233            None => self.set_state(value.as_ref().into()),
234            // Steps 5-6: Otherwise, set element's state to state
235            Some(state) => self.set_state(state.as_ref().into()),
236        }
237        Ok(())
238    }
239
240    /// <https://html.spec.whatwg.org/multipage#dom-elementinternals-setvalidity>
241    fn SetValidity(
242        &self,
243        flags: &ValidityStateFlags,
244        message: Option<DOMString>,
245        anchor: Option<&HTMLElement>,
246        can_gc: CanGc,
247    ) -> ErrorResult {
248        // Step 1. Let element be this's target element.
249        // Step 2: If element is not a form-associated custom element, then throw a "NotSupportedError" DOMException.
250        if !self.is_target_form_associated() {
251            return Err(Error::NotSupported);
252        }
253
254        // Step 3: If flags contains one or more true values and message is not given or is the empty
255        // string, then throw a TypeError.
256        let bits: ValidationFlags = flags.into();
257        if !bits.is_empty() && !message.as_ref().map_or_else(|| false, |m| !m.is_empty()) {
258            return Err(Error::Type(
259                "Setting an element to invalid requires a message string as the second argument."
260                    .to_string(),
261            ));
262        }
263
264        // Step 4: For each entry `flag` → `value` of `flags`, set element's validity flag with the name
265        // `flag` to `value`.
266        self.validity_state().update_invalid_flags(bits);
267        self.validity_state().update_pseudo_classes(can_gc);
268
269        // Step 5: Set element's validation message to the empty string if message is not given
270        // or all of element's validity flags are false, or to message otherwise.
271        if bits.is_empty() {
272            self.set_validation_message(DOMString::new());
273        } else {
274            self.set_validation_message(message.unwrap_or_default());
275        }
276
277        // Step 6: If element's customError validity flag is true, then set element's custom validity error
278        // message to element's validation message. Otherwise, set element's custom validity error
279        // message to the empty string.
280        if bits.contains(ValidationFlags::CUSTOM_ERROR) {
281            self.set_custom_validity_error_message(self.validation_message.borrow().clone());
282        } else {
283            self.set_custom_validity_error_message(DOMString::new());
284        }
285
286        let anchor = match anchor {
287            // Step 7: If anchor is not given, then set it to element.
288            None => &self.target_element,
289            // Step 8. Otherwise, if anchor is not a shadow-including inclusive descendant of element,
290            // then throw a "NotFoundError" DOMException.
291            Some(anchor) => {
292                if !self
293                    .target_element
294                    .upcast::<Node>()
295                    .is_shadow_including_inclusive_ancestor_of(anchor.upcast::<Node>())
296                {
297                    return Err(Error::NotFound);
298                }
299                anchor
300            },
301        };
302
303        // Step 9. Set element's validation anchor to anchor.
304        self.validation_anchor.set(Some(anchor));
305
306        Ok(())
307    }
308
309    /// <https://html.spec.whatwg.org/multipage#dom-elementinternals-validationmessage>
310    fn GetValidationMessage(&self) -> Fallible<DOMString> {
311        // This check isn't in the spec but it's in WPT tests and it maintains
312        // consistency with other methods that do specify it
313        if !self.is_target_form_associated() {
314            return Err(Error::NotSupported);
315        }
316        Ok(self.validation_message.borrow().clone())
317    }
318
319    /// <https://html.spec.whatwg.org/multipage#dom-elementinternals-validity>
320    fn GetValidity(&self) -> Fallible<DomRoot<ValidityState>> {
321        if !self.is_target_form_associated() {
322            return Err(Error::NotSupported);
323        }
324        Ok(self.validity_state())
325    }
326
327    /// <https://html.spec.whatwg.org/multipage#dom-elementinternals-labels>
328    fn GetLabels(&self, can_gc: CanGc) -> Fallible<DomRoot<NodeList>> {
329        if !self.is_target_form_associated() {
330            return Err(Error::NotSupported);
331        }
332        Ok(self.labels_node_list.or_init(|| {
333            NodeList::new_labels_list(
334                self.target_element.upcast::<Node>().owner_doc().window(),
335                &self.target_element,
336                can_gc,
337            )
338        }))
339    }
340
341    /// <https://html.spec.whatwg.org/multipage#dom-elementinternals-willvalidate>
342    fn GetWillValidate(&self) -> Fallible<bool> {
343        if !self.is_target_form_associated() {
344            return Err(Error::NotSupported);
345        }
346        Ok(self.is_instance_validatable())
347    }
348
349    /// <https://html.spec.whatwg.org/multipage#dom-elementinternals-form>
350    fn GetForm(&self) -> Fallible<Option<DomRoot<HTMLFormElement>>> {
351        if !self.is_target_form_associated() {
352            return Err(Error::NotSupported);
353        }
354        Ok(self.form_owner.get())
355    }
356
357    /// <https://html.spec.whatwg.org/multipage#dom-elementinternals-checkvalidity>
358    fn CheckValidity(&self, can_gc: CanGc) -> Fallible<bool> {
359        if !self.is_target_form_associated() {
360            return Err(Error::NotSupported);
361        }
362        Ok(self.check_validity(can_gc))
363    }
364
365    /// <https://html.spec.whatwg.org/multipage#dom-elementinternals-reportvalidity>
366    fn ReportValidity(&self, can_gc: CanGc) -> Fallible<bool> {
367        if !self.is_target_form_associated() {
368            return Err(Error::NotSupported);
369        }
370        Ok(self.report_validity(can_gc))
371    }
372
373    /// <https://html.spec.whatwg.org/multipage/#dom-elementinternals-states>
374    fn States(&self, can_gc: CanGc) -> DomRoot<CustomStateSet> {
375        self.states.or_init(|| {
376            CustomStateSet::new(
377                &self.target_element.owner_window(),
378                &self.target_element,
379                can_gc,
380            )
381        })
382    }
383}
384
385// Form-associated custom elements also need the Validatable trait.
386impl Validatable for ElementInternals {
387    fn as_element(&self) -> &Element {
388        debug_assert!(self.is_target_form_associated());
389        self.target_element.upcast::<Element>()
390    }
391
392    fn validity_state(&self) -> DomRoot<ValidityState> {
393        debug_assert!(self.is_target_form_associated());
394        self.validity_state.or_init(|| {
395            ValidityState::new(
396                &self.target_element.owner_window(),
397                self.target_element.upcast(),
398                CanGc::note(),
399            )
400        })
401    }
402
403    /// <https://html.spec.whatwg.org/multipage#candidate-for-constraint-validation>
404    fn is_instance_validatable(&self) -> bool {
405        debug_assert!(self.is_target_form_associated());
406        if !self.target_element.is_submittable_element() {
407            return false;
408        }
409
410        // The form-associated custom element is barred from constraint validation,
411        // if the readonly attribute is specified, the element is disabled,
412        // or the element has a datalist element ancestor.
413        !self.as_element().read_write_state() &&
414            !self.as_element().disabled_state() &&
415            !is_barred_by_datalist_ancestor(self.target_element.upcast::<Node>())
416    }
417}