script/dom/html/input_element/
input_type.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/. */
4use embedder_traits::InputMethodType;
5use js::context::JSContext;
6use script_bindings::codegen::GenericBindings::HTMLInputElementBinding::HTMLInputElementMethods;
7use script_bindings::domstring::DOMString;
8use script_bindings::root::DomRoot;
9use script_bindings::script_runtime::CanGc;
10use stylo_atoms::Atom;
11use time::OffsetDateTime;
12
13use crate::dom::attr::Attr;
14use crate::dom::element::AttributeMutation;
15use crate::dom::event::Event;
16use crate::dom::eventtarget::EventTarget;
17use crate::dom::filelist::FileList;
18use crate::dom::htmlformelement::HTMLFormElement;
19use crate::dom::input_element::button_input_type::ButtonInputType;
20use crate::dom::input_element::checkbox_input_type::CheckboxInputType;
21use crate::dom::input_element::color_input_type::ColorInputType;
22use crate::dom::input_element::date_input_type::DateInputType;
23use crate::dom::input_element::datetime_local_input_type::DatetimeLocalInputType;
24use crate::dom::input_element::email_input_type::EmailInputType;
25use crate::dom::input_element::file_input_type::FileInputType;
26use crate::dom::input_element::hidden_input_type::HiddenInputType;
27use crate::dom::input_element::image_input_type::ImageInputType;
28use crate::dom::input_element::month_input_type::MonthInputType;
29use crate::dom::input_element::number_input_type::NumberInputType;
30use crate::dom::input_element::password_input_type::PasswordInputType;
31use crate::dom::input_element::radio_input_type::RadioInputType;
32use crate::dom::input_element::range_input_type::RangeInputType;
33use crate::dom::input_element::reset_input_type::ResetInputType;
34use crate::dom::input_element::search_input_type::SearchInputType;
35use crate::dom::input_element::submit_input_type::SubmitInputType;
36use crate::dom::input_element::tel_input_type::TelInputType;
37use crate::dom::input_element::text_input_type::TextInputType;
38use crate::dom::input_element::time_input_type::TimeInputType;
39use crate::dom::input_element::url_input_type::UrlInputType;
40use crate::dom::input_element::week_input_type::WeekInputType;
41use crate::dom::input_element::{HTMLInputElement, InputActivationState, ValueMode};
42use crate::dom::node::{BindContext, UnbindContext};
43
44/// <https://html.spec.whatwg.org/multipage/#attr-input-type>
45#[derive(JSTraceable, MallocSizeOf, PartialEq)]
46#[cfg_attr(crown, crown::unrooted_must_root_lint::must_root)]
47pub(crate) enum InputType {
48    /// <https://html.spec.whatwg.org/multipage/#button-state-(type=button)>
49    Button(ButtonInputType),
50
51    /// <https://html.spec.whatwg.org/multipage/#checkbox-state-(type=checkbox)>
52    Checkbox(CheckboxInputType),
53
54    /// <https://html.spec.whatwg.org/multipage/#color-state-(type=color)>
55    Color(ColorInputType),
56
57    /// <https://html.spec.whatwg.org/multipage/#date-state-(type=date)>
58    Date(DateInputType),
59
60    /// <https://html.spec.whatwg.org/multipage/#local-date-and-time-state-(type=datetime-local)>
61    DatetimeLocal(DatetimeLocalInputType),
62
63    /// <https://html.spec.whatwg.org/multipage/#email-state-(type=email)>
64    Email(EmailInputType),
65
66    /// <https://html.spec.whatwg.org/multipage/#file-upload-state-(type=file)>
67    File(FileInputType),
68
69    /// <https://html.spec.whatwg.org/multipage/#hidden-state-(type=hidden)>
70    Hidden(HiddenInputType),
71
72    /// <https://html.spec.whatwg.org/multipage/#image-button-state-(type=image)>
73    Image(ImageInputType),
74
75    /// <https://html.spec.whatwg.org/multipage/#month-state-(type=month)>
76    Month(MonthInputType),
77
78    /// <https://html.spec.whatwg.org/multipage/#number-state-(type=number)>
79    Number(NumberInputType),
80
81    /// <https://html.spec.whatwg.org/multipage/#password-state-(type=password)>
82    Password(PasswordInputType),
83
84    /// <https://html.spec.whatwg.org/multipage/#radio-button-state-(type=radio)>
85    Radio(RadioInputType),
86
87    /// <https://html.spec.whatwg.org/multipage/#range-state-(type=range)>
88    Range(RangeInputType),
89
90    /// <https://html.spec.whatwg.org/multipage/#reset-button-state-(type=reset)>
91    Reset(ResetInputType),
92
93    /// <https://html.spec.whatwg.org/multipage/#text-(type=text)-state-and-search-state-(type=search)>
94    Search(SearchInputType),
95
96    /// <https://html.spec.whatwg.org/multipage/#submit-button-state-(type=submit)>
97    Submit(SubmitInputType),
98
99    /// <https://html.spec.whatwg.org/multipage/#telephone-state-(type=tel)>
100    Tel(TelInputType),
101
102    /// <https://html.spec.whatwg.org/multipage/#text-(type=text)-state-and-search-state-(type=search)>
103    Text(TextInputType),
104
105    /// <https://html.spec.whatwg.org/multipage/#time-state-(type=time)>
106    Time(TimeInputType),
107
108    /// <https://html.spec.whatwg.org/multipage/#url-state-(type=url)>
109    Url(UrlInputType),
110
111    /// <https://html.spec.whatwg.org/multipage/#week-state-(type=week)>
112    Week(WeekInputType),
113}
114
115impl InputType {
116    pub(crate) fn new_from_atom(value: &Atom) -> Self {
117        match value.to_ascii_lowercase() {
118            atom!("button") => InputType::Button(Default::default()),
119            atom!("checkbox") => InputType::Checkbox(Default::default()),
120            atom!("color") => InputType::Color(Default::default()),
121            atom!("date") => InputType::Date(Default::default()),
122            atom!("datetime-local") => InputType::DatetimeLocal(Default::default()),
123            atom!("email") => InputType::Email(Default::default()),
124            atom!("file") => InputType::File(Default::default()),
125            atom!("hidden") => InputType::Hidden(Default::default()),
126            atom!("image") => InputType::Image(Default::default()),
127            atom!("month") => InputType::Month(Default::default()),
128            atom!("number") => InputType::Number(Default::default()),
129            atom!("password") => InputType::Password(Default::default()),
130            atom!("radio") => InputType::Radio(Default::default()),
131            atom!("range") => InputType::Range(Default::default()),
132            atom!("reset") => InputType::Reset(Default::default()),
133            atom!("search") => InputType::Search(Default::default()),
134            atom!("submit") => InputType::Submit(Default::default()),
135            atom!("tel") => InputType::Tel(Default::default()),
136            atom!("text") => InputType::Text(Default::default()),
137            atom!("time") => InputType::Time(Default::default()),
138            atom!("url") => InputType::Url(Default::default()),
139            atom!("week") => InputType::Week(Default::default()),
140            _ => InputType::Text(Default::default()),
141        }
142    }
143
144    pub(crate) fn new_text() -> Self {
145        Self::Text(TextInputType::default())
146    }
147
148    pub(crate) fn as_specific(&self) -> &dyn SpecificInputType {
149        match self {
150            Self::Button(input_type) => input_type as &dyn SpecificInputType,
151            Self::Checkbox(input_type) => input_type as &dyn SpecificInputType,
152            Self::Color(input_type) => input_type as &dyn SpecificInputType,
153            Self::Date(input_type) => input_type as &dyn SpecificInputType,
154            Self::DatetimeLocal(input_type) => input_type as &dyn SpecificInputType,
155            Self::Email(input_type) => input_type as &dyn SpecificInputType,
156            Self::File(input_type) => input_type as &dyn SpecificInputType,
157            Self::Hidden(input_type) => input_type as &dyn SpecificInputType,
158            Self::Image(input_type) => input_type as &dyn SpecificInputType,
159            Self::Month(input_type) => input_type as &dyn SpecificInputType,
160            Self::Number(input_type) => input_type as &dyn SpecificInputType,
161            Self::Password(input_type) => input_type as &dyn SpecificInputType,
162            Self::Radio(input_type) => input_type as &dyn SpecificInputType,
163            Self::Range(input_type) => input_type as &dyn SpecificInputType,
164            Self::Reset(input_type) => input_type as &dyn SpecificInputType,
165            Self::Search(input_type) => input_type as &dyn SpecificInputType,
166            Self::Submit(input_type) => input_type as &dyn SpecificInputType,
167            Self::Tel(input_type) => input_type as &dyn SpecificInputType,
168            Self::Text(input_type) => input_type as &dyn SpecificInputType,
169            Self::Time(input_type) => input_type as &dyn SpecificInputType,
170            Self::Url(input_type) => input_type as &dyn SpecificInputType,
171            Self::Week(input_type) => input_type as &dyn SpecificInputType,
172        }
173    }
174
175    /// Defines which input type that should perform like a text input,
176    /// specifically when it is interacting with JS. Note that Password
177    /// is not included here since it is handled slightly differently,
178    /// with placeholder characters shown rather than the underlying value.
179    pub(crate) fn is_textual(&self) -> bool {
180        matches!(
181            *self,
182            Self::Date(_) |
183                Self::DatetimeLocal(_) |
184                Self::Email(_) |
185                Self::Hidden(_) |
186                Self::Month(_) |
187                Self::Number(_) |
188                Self::Range(_) |
189                Self::Search(_) |
190                Self::Tel(_) |
191                Self::Text(_) |
192                Self::Time(_) |
193                Self::Url(_) |
194                Self::Week(_)
195        )
196    }
197
198    pub(crate) fn is_textual_or_password(&self) -> bool {
199        self.is_textual() || matches!(self, Self::Password(_))
200    }
201
202    /// <https://html.spec.whatwg.org/multipage/#has-a-periodic-domain>
203    pub(crate) fn has_periodic_domain(&self) -> bool {
204        matches!(self, Self::Time(_))
205    }
206
207    pub(crate) fn as_str(&self) -> &str {
208        match *self {
209            InputType::Button(_) => "button",
210            InputType::Checkbox(_) => "checkbox",
211            InputType::Color(_) => "color",
212            InputType::Date(_) => "date",
213            InputType::DatetimeLocal(_) => "datetime-local",
214            InputType::Email(_) => "email",
215            InputType::File(_) => "file",
216            InputType::Hidden(_) => "hidden",
217            InputType::Image(_) => "image",
218            InputType::Month(_) => "month",
219            InputType::Number(_) => "number",
220            InputType::Password(_) => "password",
221            InputType::Radio(_) => "radio",
222            InputType::Range(_) => "range",
223            InputType::Reset(_) => "reset",
224            InputType::Search(_) => "search",
225            InputType::Submit(_) => "submit",
226            InputType::Tel(_) => "tel",
227            InputType::Text(_) => "text",
228            InputType::Time(_) => "time",
229            InputType::Url(_) => "url",
230            InputType::Week(_) => "week",
231        }
232    }
233}
234
235impl TryFrom<&InputType> for InputMethodType {
236    type Error = &'static str;
237
238    fn try_from(input_type: &InputType) -> Result<Self, Self::Error> {
239        match input_type {
240            InputType::Color(_) => Ok(InputMethodType::Color),
241            InputType::Date(_) => Ok(InputMethodType::Date),
242            InputType::DatetimeLocal(_) => Ok(InputMethodType::DatetimeLocal),
243            InputType::Email(_) => Ok(InputMethodType::Email),
244            InputType::Month(_) => Ok(InputMethodType::Month),
245            InputType::Number(_) => Ok(InputMethodType::Number),
246            InputType::Password(_) => Ok(InputMethodType::Password),
247            InputType::Search(_) => Ok(InputMethodType::Search),
248            InputType::Tel(_) => Ok(InputMethodType::Tel),
249            InputType::Text(_) => Ok(InputMethodType::Text),
250            InputType::Time(_) => Ok(InputMethodType::Time),
251            InputType::Url(_) => Ok(InputMethodType::Url),
252            InputType::Week(_) => Ok(InputMethodType::Week),
253            _ => Err("Input does not support IME."),
254        }
255    }
256}
257
258pub(crate) trait SpecificInputType {
259    fn sanitize_value(&self, _input: &HTMLInputElement, _value: &mut DOMString) {}
260
261    fn convert_string_to_number(&self, _value: &str) -> Option<f64> {
262        None
263    }
264
265    fn convert_number_to_string(&self, _value: f64) -> Option<DOMString> {
266        unreachable!("Should not have called convert_number_to_string for non-Date types")
267    }
268
269    /// <https://html.spec.whatwg.org/multipage/#concept-input-value-string-date>
270    /// This does the safe Rust part of conversion; the unsafe JS Date part
271    /// is in GetValueAsDate
272    fn convert_string_to_naive_datetime(&self, _value: DOMString) -> Option<OffsetDateTime> {
273        None
274    }
275
276    /// <https://html.spec.whatwg.org/multipage/#concept-input-value-date-string>
277    /// This does the safe Rust part of conversion; the unsafe JS Date part
278    /// is in SetValueAsDate
279    fn convert_datetime_to_dom_string(&self, _value: OffsetDateTime) -> DOMString {
280        unreachable!("Should not have called convert_datetime_to_string for non-Date types")
281    }
282
283    /// <https://html.spec.whatwg.org/multipage/#the-required-attribute%3Asuffering-from-being-missing>
284    fn suffers_from_being_missing(&self, input: &HTMLInputElement, value: &DOMString) -> bool {
285        input.Required() &&
286            input.value_mode() == ValueMode::Value &&
287            input.is_mutable() &&
288            value.is_empty()
289    }
290
291    fn suffers_from_bad_input(&self, _value: &DOMString) -> bool {
292        false
293    }
294
295    fn suffers_from_type_mismatch(&self, _input: &HTMLInputElement, _value: &DOMString) -> bool {
296        false
297    }
298
299    fn value_for_shadow_dom(&self, _input: &HTMLInputElement) -> DOMString {
300        "".into()
301    }
302
303    /// <https://html.spec.whatwg.org/multipage/#signal-a-type-change>
304    fn signal_type_change(&self, _input: &HTMLInputElement, _can_gc: CanGc) {}
305
306    fn activation_behavior(
307        &self,
308        _input: &HTMLInputElement,
309        _event: &Event,
310        _target: &EventTarget,
311        _can_gc: CanGc,
312    ) {
313    }
314
315    fn legacy_pre_activation_behavior(
316        &self,
317        _input: &HTMLInputElement,
318        _can_gc: CanGc,
319    ) -> Option<InputActivationState> {
320        None
321    }
322
323    fn legacy_canceled_activation_behavior(
324        &self,
325        _input: &HTMLInputElement,
326        _cache: InputActivationState,
327        _can_gc: CanGc,
328    ) {
329    }
330
331    fn show_the_picker_if_applicable(&self, _input: &HTMLInputElement) {}
332
333    fn select_files(&self, _input: &HTMLInputElement, _test_paths: Option<Vec<DOMString>>) {}
334
335    fn get_files(&self) -> Option<DomRoot<FileList>> {
336        None
337    }
338
339    fn set_files(&self, _filelist: &FileList) {}
340
341    fn update_shadow_tree(&self, _cx: &mut JSContext, _input: &HTMLInputElement) {}
342
343    fn update_placeholder_contents(&self, _cx: &mut JSContext, _input: &HTMLInputElement) {}
344
345    fn attribute_mutated(
346        &self,
347        _cx: &mut JSContext,
348        _input: &HTMLInputElement,
349        _attr: &Attr,
350        _mutation: AttributeMutation,
351    ) {
352    }
353
354    fn bind_to_tree(&self, _cx: &mut JSContext, _input: &HTMLInputElement, _context: &BindContext) {
355    }
356
357    fn unbind_from_tree(
358        &self,
359        _input: &HTMLInputElement,
360        _form_owner: Option<DomRoot<HTMLFormElement>>,
361        _context: &UnbindContext,
362        _can_gc: CanGc,
363    ) {
364    }
365}