Skip to main content

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