Skip to main content

script/dom/html/input_element/
mod.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, RefCell, RefMut};
6use std::ptr::NonNull;
7use std::{f64, ptr};
8
9use dom_struct::dom_struct;
10use embedder_traits::{EmbedderControlRequest, InputMethodRequest, RgbColor, SelectedFile};
11use encoding_rs::Encoding;
12use fonts::{ByteIndex, TextByteRange};
13use html5ever::{LocalName, Prefix, local_name};
14use js::context::JSContext;
15use js::jsapi::{
16    ClippedTime, JS_ClearPendingException, JSObject, NewDateObject, NewUCRegExpObject,
17    RegExpFlag_UnicodeSets, RegExpFlags,
18};
19use js::jsval::UndefinedValue;
20use js::rust::wrappers::{CheckRegExpSyntax, ExecuteRegExpNoStatics, ObjectIsRegExp};
21use js::rust::wrappers2::{DateGetMsecSinceEpoch, ObjectIsDate};
22use js::rust::{HandleObject, MutableHandleObject};
23use layout_api::{ScriptSelection, SharedSelection};
24use script_bindings::cell::{DomRefCell, Ref};
25use script_bindings::domstring::parse_floating_point_number;
26use servo_base::generic_channel::GenericSender;
27use servo_base::text::Utf16CodeUnitLength;
28use style::attr::AttrValue;
29use style::str::split_commas;
30use stylo_atoms::Atom;
31use stylo_dom::ElementState;
32use time::OffsetDateTime;
33use unicode_bidi::{BidiClass, bidi_class};
34use webdriver::error::ErrorStatus;
35
36use crate::clipboard_provider::EmbedderClipboardProvider;
37use crate::dom::activation::Activatable;
38use crate::dom::bindings::codegen::Bindings::ElementBinding::ElementMethods;
39use crate::dom::bindings::codegen::Bindings::EventBinding::EventMethods;
40use crate::dom::bindings::codegen::Bindings::FileListBinding::FileListMethods;
41use crate::dom::bindings::codegen::Bindings::HTMLFormElementBinding::SelectionMode;
42use crate::dom::bindings::codegen::Bindings::HTMLInputElementBinding::HTMLInputElementMethods;
43use crate::dom::bindings::codegen::Bindings::NodeBinding::{GetRootNodeOptions, NodeMethods};
44use crate::dom::bindings::error::{Error, ErrorResult};
45use crate::dom::bindings::inheritance::Castable;
46use crate::dom::bindings::refcounted::Trusted;
47use crate::dom::bindings::root::{DomRoot, LayoutDom, MutNullableDom};
48use crate::dom::bindings::str::{DOMString, USVString};
49use crate::dom::clipboardevent::{ClipboardEvent, ClipboardEventType};
50use crate::dom::compositionevent::CompositionEvent;
51use crate::dom::document::Document;
52use crate::dom::document_embedder_controls::ControlElement;
53use crate::dom::element::attributes::storage::AttrRef;
54use crate::dom::element::{AttributeMutation, Element};
55use crate::dom::event::Event;
56use crate::dom::event::event::{EventBubbles, EventCancelable, EventComposed};
57use crate::dom::eventtarget::EventTarget;
58use crate::dom::filelist::FileList;
59use crate::dom::globalscope::GlobalScope;
60use crate::dom::html::htmldatalistelement::HTMLDataListElement;
61use crate::dom::html::htmlelement::HTMLElement;
62use crate::dom::html::htmlfieldsetelement::HTMLFieldSetElement;
63use crate::dom::html::htmlformelement::{
64    FormControl, FormDatum, FormDatumValue, FormSubmitterElement, HTMLFormElement, SubmittedFrom,
65};
66use crate::dom::htmlinputelement::radio_input_type::{
67    broadcast_radio_checked, perform_radio_group_validation,
68};
69use crate::dom::input_element::input_type::InputType;
70use crate::dom::iterators::ShadowIncluding;
71use crate::dom::keyboardevent::KeyboardEvent;
72use crate::dom::node::{
73    BindContext, CloneChildrenFlag, Node, NodeDamage, NodeTraits, UnbindContext,
74};
75use crate::dom::nodelist::NodeList;
76use crate::dom::textcontrol::{TextControlElement, TextControlSelection};
77use crate::dom::types::{FocusEvent, MouseEvent};
78use crate::dom::validation::{Validatable, is_barred_by_datalist_ancestor};
79use crate::dom::validitystate::{ValidationFlags, ValidityState};
80use crate::dom::virtualmethods::VirtualMethods;
81use crate::realms::enter_realm;
82use crate::script_runtime::{CanGc, JSContext as SafeJSContext};
83use crate::textinput::{ClipboardEventFlags, IsComposing, KeyReaction, Lines, TextInput};
84
85pub(crate) mod button_input_type;
86pub(crate) mod checkbox_input_type;
87pub(crate) mod color_input_type;
88pub(crate) mod date_input_type;
89pub(crate) mod datetime_local_input_type;
90pub(crate) mod email_input_type;
91pub(crate) mod file_input_type;
92pub(crate) mod hidden_input_type;
93pub(crate) mod image_input_type;
94pub(crate) mod input_type;
95pub(crate) mod month_input_type;
96pub(crate) mod number_input_type;
97pub(crate) mod password_input_type;
98pub(crate) mod radio_input_type;
99pub(crate) mod range_input_type;
100pub(crate) mod reset_input_type;
101pub(crate) mod search_input_type;
102pub(crate) mod submit_input_type;
103pub(crate) mod tel_input_type;
104pub(crate) mod text_input_type;
105pub(crate) mod text_input_widget;
106pub(crate) mod text_value_widget;
107pub(crate) mod time_input_type;
108pub(crate) mod url_input_type;
109pub(crate) mod week_input_type;
110
111#[derive(Debug, PartialEq)]
112enum ValueMode {
113    /// <https://html.spec.whatwg.org/multipage/#dom-input-value-value>
114    Value,
115
116    /// <https://html.spec.whatwg.org/multipage/#dom-input-value-default>
117    Default,
118
119    /// <https://html.spec.whatwg.org/multipage/#dom-input-value-default-on>
120    DefaultOn,
121
122    /// <https://html.spec.whatwg.org/multipage/#dom-input-value-filename>
123    Filename,
124}
125
126#[derive(Debug, PartialEq)]
127enum StepDirection {
128    Up,
129    Down,
130}
131
132#[dom_struct]
133pub(crate) struct HTMLInputElement {
134    htmlelement: HTMLElement,
135    input_type: DomRefCell<InputType>,
136
137    /// Whether or not the [`InputType`] for this [`HTMLInputElement`] renders as
138    /// textual input. This is cached so that it can be read during layout.
139    is_textual_or_password: Cell<bool>,
140
141    /// <https://html.spec.whatwg.org/multipage/#concept-input-checked-dirty-flag>
142    checked_changed: Cell<bool>,
143    placeholder: DomRefCell<DOMString>,
144    size: Cell<u32>,
145    maxlength: Cell<i32>,
146    minlength: Cell<i32>,
147    #[no_trace]
148    textinput: DomRefCell<TextInput<EmbedderClipboardProvider>>,
149    /// <https://html.spec.whatwg.org/multipage/#concept-input-value-dirty-flag>
150    value_dirty: Cell<bool>,
151    /// A [`SharedSelection`] that is shared with layout. This can be updated dyanmnically
152    /// and layout should reflect the new value after a display list update.
153    #[no_trace]
154    #[conditional_malloc_size_of]
155    shared_selection: SharedSelection,
156
157    form_owner: MutNullableDom<HTMLFormElement>,
158    labels_node_list: MutNullableDom<NodeList>,
159    validity_state: MutNullableDom<ValidityState>,
160    #[no_trace]
161    pending_webdriver_response: RefCell<Option<PendingWebDriverResponse>>,
162
163    /// <https://w3c.github.io/selection-api/#dfn-has-scheduled-selectionchange-event>
164    has_scheduled_selectionchange_event: Cell<bool>,
165}
166
167#[derive(JSTraceable)]
168pub(crate) struct InputActivationState {
169    indeterminate: bool,
170    checked: bool,
171    checked_radio: Option<DomRoot<HTMLInputElement>>,
172    was_radio: bool,
173    was_checkbox: bool,
174    // was_mutable is implied: pre-activation would return None if it wasn't
175}
176
177static DEFAULT_INPUT_SIZE: u32 = 20;
178static DEFAULT_MAX_LENGTH: i32 = -1;
179static DEFAULT_MIN_LENGTH: i32 = -1;
180
181#[expect(non_snake_case)]
182impl HTMLInputElement {
183    fn new_inherited(
184        local_name: LocalName,
185        prefix: Option<Prefix>,
186        document: &Document,
187    ) -> HTMLInputElement {
188        let embedder_sender = document
189            .window()
190            .as_global_scope()
191            .script_to_embedder_chan()
192            .clone();
193        HTMLInputElement {
194            htmlelement: HTMLElement::new_inherited_with_state(
195                ElementState::ENABLED | ElementState::READWRITE,
196                local_name,
197                prefix,
198                document,
199            ),
200            input_type: DomRefCell::new(InputType::new_text()),
201            is_textual_or_password: Cell::new(true),
202            placeholder: DomRefCell::new(DOMString::new()),
203            checked_changed: Cell::new(false),
204            maxlength: Cell::new(DEFAULT_MAX_LENGTH),
205            minlength: Cell::new(DEFAULT_MIN_LENGTH),
206            size: Cell::new(DEFAULT_INPUT_SIZE),
207            textinput: DomRefCell::new(TextInput::new(
208                Lines::Single,
209                DOMString::new(),
210                EmbedderClipboardProvider {
211                    embedder_sender,
212                    webview_id: document.webview_id(),
213                },
214            )),
215            value_dirty: Cell::new(false),
216            shared_selection: Default::default(),
217            form_owner: Default::default(),
218            labels_node_list: MutNullableDom::new(None),
219            validity_state: Default::default(),
220            pending_webdriver_response: Default::default(),
221            has_scheduled_selectionchange_event: Default::default(),
222        }
223    }
224
225    pub(crate) fn new(
226        cx: &mut js::context::JSContext,
227        local_name: LocalName,
228        prefix: Option<Prefix>,
229        document: &Document,
230        proto: Option<HandleObject>,
231    ) -> DomRoot<HTMLInputElement> {
232        Node::reflect_node_with_proto(
233            cx,
234            Box::new(HTMLInputElement::new_inherited(
235                local_name, prefix, document,
236            )),
237            document,
238            proto,
239        )
240    }
241
242    pub(crate) fn auto_directionality(&self) -> Option<String> {
243        match *self.input_type() {
244            InputType::Text(_) | InputType::Search(_) | InputType::Url(_) | InputType::Email(_) => {
245                let value: String = String::from(self.Value());
246                Some(HTMLInputElement::directionality_from_value(&value))
247            },
248            _ => None,
249        }
250    }
251
252    pub(crate) fn directionality_from_value(value: &str) -> String {
253        if HTMLInputElement::is_first_strong_character_rtl(value) {
254            "rtl".to_owned()
255        } else {
256            "ltr".to_owned()
257        }
258    }
259
260    fn is_first_strong_character_rtl(value: &str) -> bool {
261        for ch in value.chars() {
262            return match bidi_class(ch) {
263                BidiClass::L => false,
264                BidiClass::AL => true,
265                BidiClass::R => true,
266                _ => continue,
267            };
268        }
269        false
270    }
271
272    // https://html.spec.whatwg.org/multipage/#dom-input-value
273    /// <https://html.spec.whatwg.org/multipage/#concept-input-apply>
274    fn value_mode(&self) -> ValueMode {
275        match *self.input_type() {
276            InputType::Submit(_) |
277            InputType::Reset(_) |
278            InputType::Button(_) |
279            InputType::Image(_) |
280            InputType::Hidden(_) => ValueMode::Default,
281
282            InputType::Checkbox(_) | InputType::Radio(_) => ValueMode::DefaultOn,
283
284            InputType::Color(_) |
285            InputType::Date(_) |
286            InputType::DatetimeLocal(_) |
287            InputType::Email(_) |
288            InputType::Month(_) |
289            InputType::Number(_) |
290            InputType::Password(_) |
291            InputType::Range(_) |
292            InputType::Search(_) |
293            InputType::Tel(_) |
294            InputType::Text(_) |
295            InputType::Time(_) |
296            InputType::Url(_) |
297            InputType::Week(_) => ValueMode::Value,
298
299            InputType::File(_) => ValueMode::Filename,
300        }
301    }
302
303    #[inline]
304    pub(crate) fn input_type(&self) -> Ref<'_, InputType> {
305        self.input_type.borrow()
306    }
307
308    /// <https://w3c.github.io/webdriver/#dfn-non-typeable-form-control>
309    pub(crate) fn is_nontypeable(&self) -> bool {
310        matches!(
311            *self.input_type(),
312            InputType::Button(_) |
313                InputType::Checkbox(_) |
314                InputType::Color(_) |
315                InputType::File(_) |
316                InputType::Hidden(_) |
317                InputType::Image(_) |
318                InputType::Radio(_) |
319                InputType::Range(_) |
320                InputType::Reset(_) |
321                InputType::Submit(_)
322        )
323    }
324
325    #[inline]
326    pub(crate) fn is_submit_button(&self) -> bool {
327        matches!(
328            *self.input_type(),
329            InputType::Submit(_) | InputType::Image(_)
330        )
331    }
332
333    fn does_minmaxlength_apply(&self) -> bool {
334        matches!(
335            *self.input_type(),
336            InputType::Text(_) |
337                InputType::Search(_) |
338                InputType::Url(_) |
339                InputType::Tel(_) |
340                InputType::Email(_) |
341                InputType::Password(_)
342        )
343    }
344
345    fn does_pattern_apply(&self) -> bool {
346        matches!(
347            *self.input_type(),
348            InputType::Text(_) |
349                InputType::Search(_) |
350                InputType::Url(_) |
351                InputType::Tel(_) |
352                InputType::Email(_) |
353                InputType::Password(_)
354        )
355    }
356
357    fn does_multiple_apply(&self) -> bool {
358        matches!(*self.input_type(), InputType::Email(_))
359    }
360
361    // valueAsNumber, step, min, and max all share the same set of
362    // input types they apply to
363    fn does_value_as_number_apply(&self) -> bool {
364        matches!(
365            *self.input_type(),
366            InputType::Date(_) |
367                InputType::Month(_) |
368                InputType::Week(_) |
369                InputType::Time(_) |
370                InputType::DatetimeLocal(_) |
371                InputType::Number(_) |
372                InputType::Range(_)
373        )
374    }
375
376    fn does_value_as_date_apply(&self) -> bool {
377        matches!(
378            *self.input_type(),
379            InputType::Date(_) | InputType::Month(_) | InputType::Week(_) | InputType::Time(_)
380        )
381    }
382
383    /// <https://html.spec.whatwg.org/multipage#concept-input-step>
384    fn allowed_value_step(&self) -> Option<f64> {
385        // Step 1. If the attribute does not apply, then there is no allowed value step.
386        // NOTE: The attribute applies iff there is a default step
387        let default_step = self.default_step()?;
388
389        // Step 2. Otherwise, if the attribute is absent, then the allowed value step
390        // is the default step multiplied by the step scale factor.
391        let Some(step_value) = self
392            .upcast::<Element>()
393            .get_attribute_string_value(&local_name!("step"))
394        else {
395            return Some(default_step * self.step_scale_factor());
396        };
397
398        // Step 3. Otherwise, if the attribute's value is an ASCII case-insensitive match
399        // for the string "any", then there is no allowed value step.
400        if step_value.eq_ignore_ascii_case("any") {
401            return None;
402        }
403
404        // Step 4. Otherwise, if the rules for parsing floating-point number values, when they
405        // are applied to the attribute's value, return an error, zero, or a number less than zero,
406        // then the allowed value step is the default step multiplied by the step scale factor.
407        let Some(parsed_value) =
408            parse_floating_point_number(&step_value).filter(|value| *value > 0.0)
409        else {
410            return Some(default_step * self.step_scale_factor());
411        };
412
413        // Step 5. Otherwise, the allowed value step is the number returned by the rules for parsing
414        // floating-point number values when they are applied to the attribute's value,
415        // multiplied by the step scale factor.
416        Some(parsed_value * self.step_scale_factor())
417    }
418
419    /// <https://html.spec.whatwg.org/multipage#concept-input-min>
420    fn minimum(&self) -> Option<f64> {
421        self.upcast::<Element>()
422            .get_attribute_string_value(&local_name!("min"))
423            .and_then(|value| self.convert_string_to_number(&value))
424            .or_else(|| self.default_minimum())
425    }
426
427    /// <https://html.spec.whatwg.org/multipage#concept-input-max>
428    fn maximum(&self) -> Option<f64> {
429        self.upcast::<Element>()
430            .get_attribute_string_value(&local_name!("max"))
431            .and_then(|value| self.convert_string_to_number(&value))
432            .or_else(|| self.default_maximum())
433    }
434
435    /// when allowed_value_step and minimum both exist, this is the smallest
436    /// value >= minimum that lies on an integer step
437    fn stepped_minimum(&self) -> Option<f64> {
438        match (self.minimum(), self.allowed_value_step()) {
439            (Some(min), Some(allowed_step)) => {
440                let step_base = self.step_base();
441                // how many steps is min from step_base?
442                let nsteps = (min - step_base) / allowed_step;
443                // count that many integer steps, rounded +, from step_base
444                Some(step_base + (allowed_step * nsteps.ceil()))
445            },
446            (_, _) => None,
447        }
448    }
449
450    /// when allowed_value_step and maximum both exist, this is the smallest
451    /// value <= maximum that lies on an integer step
452    fn stepped_maximum(&self) -> Option<f64> {
453        match (self.maximum(), self.allowed_value_step()) {
454            (Some(max), Some(allowed_step)) => {
455                let step_base = self.step_base();
456                // how many steps is max from step_base?
457                let nsteps = (max - step_base) / allowed_step;
458                // count that many integer steps, rounded -, from step_base
459                Some(step_base + (allowed_step * nsteps.floor()))
460            },
461            (_, _) => None,
462        }
463    }
464
465    /// <https://html.spec.whatwg.org/multipage#concept-input-min-default>
466    fn default_minimum(&self) -> Option<f64> {
467        match *self.input_type() {
468            InputType::Range(_) => Some(0.0),
469            _ => None,
470        }
471    }
472
473    /// <https://html.spec.whatwg.org/multipage#concept-input-max-default>
474    fn default_maximum(&self) -> Option<f64> {
475        match *self.input_type() {
476            InputType::Range(_) => Some(100.0),
477            _ => None,
478        }
479    }
480
481    /// <https://html.spec.whatwg.org/multipage#concept-input-value-default-range>
482    fn default_range_value(&self) -> f64 {
483        let min = self.minimum().unwrap_or(0.0);
484        let max = self.maximum().unwrap_or(100.0);
485        if max < min {
486            min
487        } else {
488            min + (max - min) * 0.5
489        }
490    }
491
492    /// <https://html.spec.whatwg.org/multipage#concept-input-step-default>
493    fn default_step(&self) -> Option<f64> {
494        match *self.input_type() {
495            InputType::Date(_) => Some(1.0),
496            InputType::Month(_) => Some(1.0),
497            InputType::Week(_) => Some(1.0),
498            InputType::Time(_) => Some(60.0),
499            InputType::DatetimeLocal(_) => Some(60.0),
500            InputType::Number(_) => Some(1.0),
501            InputType::Range(_) => Some(1.0),
502            _ => None,
503        }
504    }
505
506    /// <https://html.spec.whatwg.org/multipage#concept-input-step-scale>
507    fn step_scale_factor(&self) -> f64 {
508        match *self.input_type() {
509            InputType::Date(_) => 86400000.0,
510            InputType::Month(_) => 1.0,
511            InputType::Week(_) => 604800000.0,
512            InputType::Time(_) => 1000.0,
513            InputType::DatetimeLocal(_) => 1000.0,
514            InputType::Number(_) => 1.0,
515            InputType::Range(_) => 1.0,
516            _ => unreachable!(),
517        }
518    }
519
520    /// <https://html.spec.whatwg.org/multipage#concept-input-min-zero>
521    fn step_base(&self) -> f64 {
522        // Step 1. If the element has a min content attribute, and the result of applying
523        // the algorithm to convert a string to a number to the value of the min content attribute
524        // is not an error, then return that result.
525        if let Some(minimum) = self
526            .upcast::<Element>()
527            .get_attribute_string_value(&local_name!("min"))
528            .and_then(|value| self.convert_string_to_number(&value))
529        {
530            return minimum;
531        }
532
533        // Step 2. If the element has a value content attribute, and the result of applying the
534        // algorithm to convert a string to a number to the value of the value content attribute
535        // is not an error, then return that result.
536        if let Some(value) = self
537            .upcast::<Element>()
538            .get_attribute_string_value(&local_name!("value"))
539            .and_then(|value| self.convert_string_to_number(&value))
540        {
541            return value;
542        }
543
544        // Step 3. If a default step base is defined for this element given its type attribute's state, then return it.
545        if let Some(default_step_base) = self.default_step_base() {
546            return default_step_base;
547        }
548
549        // Step 4. Return zero.
550        0.0
551    }
552
553    /// <https://html.spec.whatwg.org/multipage#concept-input-step-default-base>
554    fn default_step_base(&self) -> Option<f64> {
555        match *self.input_type() {
556            InputType::Week(_) => Some(-259200000.0),
557            _ => None,
558        }
559    }
560
561    /// <https://html.spec.whatwg.org/multipage/#dom-input-stepup>
562    ///
563    /// <https://html.spec.whatwg.org/multipage/#dom-input-stepdown>
564    fn step_up_or_down(&self, cx: &mut JSContext, n: i32, dir: StepDirection) -> ErrorResult {
565        // Step 1. If the stepDown() and stepUp() methods do not apply, as defined for the
566        // input element's type attribute's current state, then throw an "InvalidStateError" DOMException.
567        if !self.does_value_as_number_apply() {
568            return Err(Error::InvalidState(None));
569        }
570        let step_base = self.step_base();
571
572        // Step 2. If the element has no allowed value step, then throw an "InvalidStateError" DOMException.
573        let Some(allowed_value_step) = self.allowed_value_step() else {
574            return Err(Error::InvalidState(None));
575        };
576
577        // Step 3. If the element has a minimum and a maximum and the minimum is greater than the maximum,
578        // then return.
579        let minimum = self.minimum();
580        let maximum = self.maximum();
581        if let (Some(min), Some(max)) = (minimum, maximum) {
582            if min > max {
583                return Ok(());
584            }
585
586            // Step 4. If the element has a minimum and a maximum and there is no value greater than or equal to the
587            // element's minimum and less than or equal to the element's maximum that, when subtracted from the step
588            // base, is an integral multiple of the allowed value step, then return.
589            if let Some(stepped_minimum) = self.stepped_minimum() &&
590                stepped_minimum > max
591            {
592                return Ok(());
593            }
594        }
595
596        // Step 5. If applying the algorithm to convert a string to a number to the string given
597        // by the element's value does not result in an error, then let value be the result of
598        // that algorithm. Otherwise, let value be zero.
599        let mut value: f64 = self
600            .convert_string_to_number(&self.Value().str())
601            .unwrap_or(0.0);
602
603        // Step 6. Let valueBeforeStepping be value.
604        let valueBeforeStepping = value;
605
606        // Step 7. If value subtracted from the step base is not an integral multiple of the allowed value step,
607        // then set value to the nearest value that, when subtracted from the step base, is an integral multiple
608        // of the allowed value step, and that is less than value if the method invoked was the stepDown() method,
609        // and more than value otherwise.
610        if (value - step_base) % allowed_value_step != 0.0 {
611            value = match dir {
612                StepDirection::Down =>
613                // step down a fractional step to be on a step multiple
614                {
615                    let intervals_from_base = ((value - step_base) / allowed_value_step).floor();
616                    intervals_from_base * allowed_value_step + step_base
617                },
618                StepDirection::Up =>
619                // step up a fractional step to be on a step multiple
620                {
621                    let intervals_from_base = ((value - step_base) / allowed_value_step).ceil();
622                    intervals_from_base * allowed_value_step + step_base
623                },
624            };
625        }
626        // Otherwise (value subtracted from the step base is an integral multiple of the allowed value step):
627        else {
628            // Step 7.1 Let n be the argument.
629            // Step 7.2 Let delta be the allowed value step multiplied by n.
630            // Step 7.3 If the method invoked was the stepDown() method, negate delta.
631            // Step 7.4 Let value be the result of adding delta to value.
632            value += match dir {
633                StepDirection::Down => -f64::from(n) * allowed_value_step,
634                StepDirection::Up => f64::from(n) * allowed_value_step,
635            };
636        }
637
638        // Step 8. If the element has a minimum, and value is less than that minimum, then set value to the smallest
639        // value that, when subtracted from the step base, is an integral multiple of the allowed value step, and that
640        // is more than or equal to that minimum.
641        if let Some(min) = minimum &&
642            value < min
643        {
644            value = self.stepped_minimum().unwrap_or(value);
645        }
646
647        // Step 9. If the element has a maximum, and value is greater than that maximum, then set value to the largest
648        // value that, when subtracted from the step base, is an integral multiple of the allowed value step, and that
649        // is less than or equal to that maximum.
650        if let Some(max) = maximum &&
651            value > max
652        {
653            value = self.stepped_maximum().unwrap_or(value);
654        }
655
656        // Step 10. If either the method invoked was the stepDown() method and value is greater than
657        // valueBeforeStepping, or the method invoked was the stepUp() method and value is less than
658        // valueBeforeStepping, then return.
659        match dir {
660            StepDirection::Down => {
661                if value > valueBeforeStepping {
662                    return Ok(());
663                }
664            },
665            StepDirection::Up => {
666                if value < valueBeforeStepping {
667                    return Ok(());
668                }
669            },
670        }
671
672        // Step 11. Let value as string be the result of running the algorithm to convert a number to a string,
673        // as defined for the input element's type attribute's current state, on value.
674        // Step 12. Set the value of the element to value as string.
675        self.SetValueAsNumber(cx, value)
676    }
677
678    /// <https://html.spec.whatwg.org/multipage/#concept-input-list>
679    fn suggestions_source_element(&self) -> Option<DomRoot<HTMLDataListElement>> {
680        let list_string = self
681            .upcast::<Element>()
682            .get_string_attribute(&local_name!("list"));
683        if list_string.is_empty() {
684            return None;
685        }
686        let ancestor = self
687            .upcast::<Node>()
688            .GetRootNode(&GetRootNodeOptions::empty());
689        let first_with_id = &ancestor
690            .traverse_preorder(ShadowIncluding::No)
691            .find(|node| {
692                node.downcast::<Element>()
693                    .is_some_and(|e| e.Id() == list_string)
694            });
695        first_with_id
696            .as_ref()
697            .and_then(|el| el.downcast::<HTMLDataListElement>())
698            .map(DomRoot::from_ref)
699    }
700
701    /// <https://html.spec.whatwg.org/multipage/#suffering-from-being-missing>
702    fn suffers_from_being_missing(&self, value: &DOMString) -> bool {
703        self.input_type()
704            .as_specific()
705            .suffers_from_being_missing(self, value)
706    }
707
708    /// <https://html.spec.whatwg.org/multipage/#suffering-from-a-type-mismatch>
709    fn suffers_from_type_mismatch(&self, value: &DOMString) -> bool {
710        if value.is_empty() {
711            return false;
712        }
713
714        self.input_type()
715            .as_specific()
716            .suffers_from_type_mismatch(self, value)
717    }
718
719    /// <https://html.spec.whatwg.org/multipage/#suffering-from-a-pattern-mismatch>
720    fn suffers_from_pattern_mismatch(&self, value: &DOMString, can_gc: CanGc) -> bool {
721        // https://html.spec.whatwg.org/multipage/#the-pattern-attribute%3Asuffering-from-a-pattern-mismatch
722        // https://html.spec.whatwg.org/multipage/#the-pattern-attribute%3Asuffering-from-a-pattern-mismatch-2
723        let pattern_str = self.Pattern();
724        if value.is_empty() || pattern_str.is_empty() || !self.does_pattern_apply() {
725            return false;
726        }
727
728        // Rust's regex is not compatible, we need to use mozjs RegExp.
729        let cx = GlobalScope::get_cx();
730        let _ac = enter_realm(self);
731        rooted!(in(*cx) let mut pattern = ptr::null_mut::<JSObject>());
732
733        if compile_pattern(cx, &pattern_str.str(), pattern.handle_mut(), can_gc) {
734            if self.Multiple() && self.does_multiple_apply() {
735                !split_commas(&value.str())
736                    .all(|s| matches_js_regex(cx, pattern.handle(), s, can_gc).unwrap_or(true))
737            } else {
738                !matches_js_regex(cx, pattern.handle(), &value.str(), can_gc).unwrap_or(true)
739            }
740        } else {
741            // Element doesn't suffer from pattern mismatch if pattern is invalid.
742            false
743        }
744    }
745
746    /// <https://html.spec.whatwg.org/multipage/#suffering-from-bad-input>
747    fn suffers_from_bad_input(&self, value: &DOMString) -> bool {
748        if value.is_empty() {
749            return false;
750        }
751
752        self.input_type()
753            .as_specific()
754            .suffers_from_bad_input(value)
755    }
756
757    // https://html.spec.whatwg.org/multipage/#suffering-from-being-too-long
758    /// <https://html.spec.whatwg.org/multipage/#suffering-from-being-too-short>
759    fn suffers_from_length_issues(&self, value: &DOMString) -> ValidationFlags {
760        // https://html.spec.whatwg.org/multipage/#limiting-user-input-length%3A-the-maxlength-attribute%3Asuffering-from-being-too-long
761        // https://html.spec.whatwg.org/multipage/#setting-minimum-input-length-requirements%3A-the-minlength-attribute%3Asuffering-from-being-too-short
762        let value_dirty = self.value_dirty.get();
763        let textinput = self.textinput.borrow();
764        let edit_by_user = !textinput.was_last_change_by_set_content();
765
766        if value.is_empty() || !value_dirty || !edit_by_user || !self.does_minmaxlength_apply() {
767            return ValidationFlags::empty();
768        }
769
770        let mut failed_flags = ValidationFlags::empty();
771        let Utf16CodeUnitLength(value_len) = textinput.len_utf16();
772        let min_length = self.MinLength();
773        let max_length = self.MaxLength();
774
775        if min_length != DEFAULT_MIN_LENGTH && value_len < (min_length as usize) {
776            failed_flags.insert(ValidationFlags::TOO_SHORT);
777        }
778
779        if max_length != DEFAULT_MAX_LENGTH && value_len > (max_length as usize) {
780            failed_flags.insert(ValidationFlags::TOO_LONG);
781        }
782
783        failed_flags
784    }
785
786    /// * <https://html.spec.whatwg.org/multipage/#suffering-from-an-underflow>
787    /// * <https://html.spec.whatwg.org/multipage/#suffering-from-an-overflow>
788    /// * <https://html.spec.whatwg.org/multipage/#suffering-from-a-step-mismatch>
789    fn suffers_from_range_issues(&self, value: &DOMString) -> ValidationFlags {
790        if value.is_empty() || !self.does_value_as_number_apply() {
791            return ValidationFlags::empty();
792        }
793
794        let Some(value_as_number) = self.convert_string_to_number(&value.str()) else {
795            return ValidationFlags::empty();
796        };
797
798        let mut failed_flags = ValidationFlags::empty();
799        let min_value = self.minimum();
800        let max_value = self.maximum();
801
802        // https://html.spec.whatwg.org/multipage/#has-a-reversed-range
803        let has_reversed_range = match (min_value, max_value) {
804            (Some(min), Some(max)) => self.input_type().has_periodic_domain() && min > max,
805            _ => false,
806        };
807
808        if has_reversed_range {
809            // https://html.spec.whatwg.org/multipage/#the-min-and-max-attributes:has-a-reversed-range-3
810            if value_as_number > max_value.unwrap() && value_as_number < min_value.unwrap() {
811                failed_flags.insert(ValidationFlags::RANGE_UNDERFLOW);
812                failed_flags.insert(ValidationFlags::RANGE_OVERFLOW);
813            }
814        } else {
815            // https://html.spec.whatwg.org/multipage/#the-min-and-max-attributes%3Asuffering-from-an-underflow-2
816            if let Some(min_value) = min_value &&
817                value_as_number < min_value
818            {
819                failed_flags.insert(ValidationFlags::RANGE_UNDERFLOW);
820            }
821            // https://html.spec.whatwg.org/multipage/#the-min-and-max-attributes%3Asuffering-from-an-overflow-2
822            if let Some(max_value) = max_value &&
823                value_as_number > max_value
824            {
825                failed_flags.insert(ValidationFlags::RANGE_OVERFLOW);
826            }
827        }
828
829        // https://html.spec.whatwg.org/multipage/#the-step-attribute%3Asuffering-from-a-step-mismatch
830        if let Some(step) = self.allowed_value_step() {
831            // TODO: Spec has some issues here, see https://github.com/whatwg/html/issues/5207.
832            // Chrome and Firefox parse values as decimals to get exact results,
833            // we probably should too.
834            let diff = (self.step_base() - value_as_number) % step / value_as_number;
835            if diff.abs() > 1e-12 {
836                failed_flags.insert(ValidationFlags::STEP_MISMATCH);
837            }
838        }
839
840        failed_flags
841    }
842
843    /// Whether this input type renders as a basic text input widget.
844    pub(crate) fn is_textual_or_password(&self) -> bool {
845        self.is_textual_or_password.get()
846    }
847
848    fn may_have_embedder_control(&self) -> bool {
849        let el = self.upcast::<Element>();
850        matches!(*self.input_type(), InputType::Color(_)) && !el.disabled_state()
851    }
852
853    fn handle_key_reaction(
854        &self,
855        cx: &mut js::context::JSContext,
856        action: KeyReaction,
857        event: &Event,
858    ) {
859        match action {
860            KeyReaction::TriggerDefaultAction => {
861                self.implicit_submission(cx);
862                event.mark_as_handled();
863            },
864            KeyReaction::DispatchInput(text, is_composing, input_type) => {
865                if event.IsTrusted() {
866                    self.textinput.borrow().queue_input_event(
867                        self.upcast(),
868                        text,
869                        is_composing,
870                        input_type,
871                    );
872                }
873                self.value_dirty.set(true);
874                self.update_placeholder_shown_state();
875                self.upcast::<Node>().dirty(NodeDamage::Other);
876                event.mark_as_handled();
877            },
878            KeyReaction::RedrawSelection => {
879                self.maybe_update_shared_selection();
880                event.mark_as_handled();
881            },
882            KeyReaction::Nothing => (),
883        }
884    }
885
886    /// Return a string that represents the contents of the element in its displayed shadow DOM.
887    fn value_for_shadow_dom(&self) -> DOMString {
888        let input_type = &*self.input_type();
889        match input_type {
890            InputType::Checkbox(_) |
891            InputType::Radio(_) |
892            InputType::Image(_) |
893            InputType::Hidden(_) |
894            InputType::Range(_) => input_type.as_specific().value_for_shadow_dom(self),
895            _ => {
896                if let Some(attribute_value) = self
897                    .upcast::<Element>()
898                    .get_attribute_string_value(&local_name!("value"))
899                {
900                    return attribute_value.into();
901                }
902                input_type.as_specific().value_for_shadow_dom(self)
903            },
904        }
905    }
906
907    fn textinput_mut(&self) -> RefMut<'_, TextInput<EmbedderClipboardProvider>> {
908        self.textinput.borrow_mut()
909    }
910
911    /// <https://w3c.github.io/selection-api/#dfn-schedule-a-selectionchange-event>
912    fn schedule_a_selection_change_event(&self) {
913        // Step 1. If target's has scheduled selectionchange event is true, abort these steps.
914        if self.has_scheduled_selectionchange_event.get() {
915            return;
916        }
917        // Step 2. Set target's has scheduled selectionchange event to true.
918        self.has_scheduled_selectionchange_event.set(true);
919        // Step 3. Queue a task on the user interaction task source to fire a selectionchange event on target.
920        let this = Trusted::new(self);
921        self.owner_global()
922            .task_manager()
923            .user_interaction_task_source()
924            .queue(
925                // https://w3c.github.io/selection-api/#firing-selectionchange-event
926                task!(selectionchange_task_steps: move |cx| {
927                    let this = this.root();
928                    // Step 1. Set target's has scheduled selectionchange event to false.
929                    this.has_scheduled_selectionchange_event.set(false);
930                    // Step 2. If target is an element, fire an event named selectionchange, which bubbles and not cancelable, at target.
931                    this.upcast::<EventTarget>().fire_event_with_params(cx,
932                        atom!("selectionchange"),
933                        EventBubbles::Bubbles,
934                        EventCancelable::NotCancelable,
935                        EventComposed::Composed,
936                    );
937                    // Step 3. Otherwise, if target is a document, fire an event named selectionchange,
938                    // which does not bubble and not cancelable, at target.
939                    //
940                    // n/a
941                }),
942            );
943    }
944}
945
946impl<'dom> LayoutDom<'dom, HTMLInputElement> {
947    /// Textual input, specifically text entry and domain specific input has
948    /// a default preferred size.
949    ///
950    /// <https://html.spec.whatwg.org/multipage/#the-input-element-as-a-text-entry-widget>
951    /// <https://html.spec.whatwg.org/multipage/#the-input-element-as-domain-specific-widgets>
952    // FIXME(stevennovaryo): Implement the calculation of default preferred size
953    //                       for domain specific input widgets correctly.
954    // FIXME(#4378): Implement the calculation of average character width for
955    //               textual input correctly.
956    pub(crate) fn size_for_layout(self) -> u32 {
957        self.unsafe_get().size.get()
958    }
959
960    pub(crate) fn selection_for_layout(self) -> Option<SharedSelection> {
961        if !self.unsafe_get().is_textual_or_password.get() {
962            return None;
963        }
964        Some(self.unsafe_get().shared_selection.clone())
965    }
966}
967
968impl TextControlElement for HTMLInputElement {
969    /// <https://html.spec.whatwg.org/multipage/#concept-input-apply>
970    fn selection_api_applies(&self) -> bool {
971        matches!(
972            *self.input_type(),
973            InputType::Text(_) |
974                InputType::Search(_) |
975                InputType::Url(_) |
976                InputType::Tel(_) |
977                InputType::Password(_)
978        )
979    }
980
981    // https://html.spec.whatwg.org/multipage/#concept-input-apply
982    //
983    // Defines input types to which the select() IDL method applies. These are a superset of the
984    // types for which selection_api_applies() returns true.
985    //
986    // Types omitted which could theoretically be included if they were
987    // rendered as a text control: file
988    fn has_selectable_text(&self) -> bool {
989        self.is_textual_or_password() && !self.textinput.borrow().get_content().is_empty()
990    }
991
992    fn has_uncollapsed_selection(&self) -> bool {
993        self.textinput.borrow().has_uncollapsed_selection()
994    }
995
996    fn set_dirty_value_flag(&self, value: bool) {
997        self.value_dirty.set(value)
998    }
999
1000    fn select_all(&self) {
1001        self.textinput.borrow_mut().select_all();
1002        self.maybe_update_shared_selection();
1003    }
1004
1005    fn maybe_update_shared_selection(&self) {
1006        let offsets = self.textinput.borrow().sorted_selection_offsets_range();
1007        let (start, end) = (offsets.start.0, offsets.end.0);
1008        let range = TextByteRange::new(ByteIndex(start), ByteIndex(end));
1009        let enabled = self.is_textual_or_password() && self.upcast::<Element>().focus_state();
1010
1011        let mut shared_selection = self.shared_selection.borrow_mut();
1012        let range_remained_equal = range == shared_selection.range;
1013        if range_remained_equal && enabled == shared_selection.enabled {
1014            return;
1015        }
1016
1017        if !range_remained_equal {
1018            // https://w3c.github.io/selection-api/#selectionchange-event
1019            // > When an input or textarea element provide a text selection and its selection changes
1020            // > (in either extent or direction),
1021            // > the user agent must schedule a selectionchange event on the element.
1022            self.schedule_a_selection_change_event();
1023        }
1024
1025        *shared_selection = ScriptSelection {
1026            range,
1027            character_range: self
1028                .textinput
1029                .borrow()
1030                .sorted_selection_character_offsets_range(),
1031            enabled,
1032        };
1033        self.owner_window().layout().set_needs_new_display_list();
1034    }
1035
1036    fn is_password_field(&self) -> bool {
1037        matches!(*self.input_type(), InputType::Password(_))
1038    }
1039
1040    fn placeholder_text<'a>(&'a self) -> Ref<'a, DOMString> {
1041        self.placeholder.borrow()
1042    }
1043
1044    fn value_text(&self) -> DOMString {
1045        self.Value()
1046    }
1047}
1048
1049impl HTMLInputElementMethods<crate::DomTypeHolder> for HTMLInputElement {
1050    // https://html.spec.whatwg.org/multipage/#dom-input-accept
1051    make_getter!(Accept, "accept");
1052
1053    // https://html.spec.whatwg.org/multipage/#dom-input-accept
1054    make_setter!(SetAccept, "accept");
1055
1056    // https://html.spec.whatwg.org/multipage/#dom-input-alpha
1057    make_bool_getter!(Alpha, "alpha");
1058
1059    // https://html.spec.whatwg.org/multipage/#dom-input-alpha
1060    make_bool_setter!(SetAlpha, "alpha");
1061
1062    // https://html.spec.whatwg.org/multipage/#dom-input-alt
1063    make_getter!(Alt, "alt");
1064
1065    // https://html.spec.whatwg.org/multipage/#dom-input-alt
1066    make_setter!(SetAlt, "alt");
1067
1068    // https://html.spec.whatwg.org/multipage/#dom-input-dirName
1069    make_getter!(DirName, "dirname");
1070
1071    // https://html.spec.whatwg.org/multipage/#dom-input-dirName
1072    make_setter!(SetDirName, "dirname");
1073
1074    // https://html.spec.whatwg.org/multipage/#dom-fe-disabled
1075    make_bool_getter!(Disabled, "disabled");
1076
1077    // https://html.spec.whatwg.org/multipage/#dom-fe-disabled
1078    make_bool_setter!(SetDisabled, "disabled");
1079
1080    /// <https://html.spec.whatwg.org/multipage/#dom-fae-form>
1081    fn GetForm(&self) -> Option<DomRoot<HTMLFormElement>> {
1082        self.form_owner()
1083    }
1084
1085    /// <https://html.spec.whatwg.org/multipage/#dom-input-files>
1086    fn GetFiles(&self) -> Option<DomRoot<FileList>> {
1087        self.input_type()
1088            .as_specific()
1089            .get_files()
1090            .as_ref()
1091            .cloned()
1092    }
1093
1094    /// <https://html.spec.whatwg.org/multipage/#dom-input-files>
1095    fn SetFiles(&self, _cx: &mut js::context::JSContext, files: Option<&FileList>) {
1096        if let Some(files) = files {
1097            self.input_type().as_specific().set_files(files)
1098        }
1099    }
1100
1101    // https://html.spec.whatwg.org/multipage/#dom-input-defaultchecked
1102    make_bool_getter!(DefaultChecked, "checked");
1103
1104    // https://html.spec.whatwg.org/multipage/#dom-input-defaultchecked
1105    make_bool_setter!(SetDefaultChecked, "checked");
1106
1107    /// <https://html.spec.whatwg.org/multipage/#dom-input-checked>
1108    fn Checked(&self) -> bool {
1109        self.upcast::<Element>()
1110            .state()
1111            .contains(ElementState::CHECKED)
1112    }
1113
1114    /// <https://html.spec.whatwg.org/multipage/#dom-input-checked>
1115    fn SetChecked(&self, cx: &mut JSContext, checked: bool) {
1116        self.update_checked_state(cx, checked, true);
1117        self.value_changed(cx);
1118    }
1119
1120    // https://html.spec.whatwg.org/multipage/#attr-input-colorspace
1121    make_enumerated_getter!(
1122        ColorSpace,
1123        "colorspace",
1124        "limited-srgb" | "display-p3",
1125        missing => "limited-srgb",
1126        invalid => "limited-srgb"
1127    );
1128
1129    // https://html.spec.whatwg.org/multipage/#attr-input-colorspace
1130    make_setter!(SetColorSpace, "colorspace");
1131
1132    // https://html.spec.whatwg.org/multipage/#dom-input-readonly
1133    make_bool_getter!(ReadOnly, "readonly");
1134
1135    // https://html.spec.whatwg.org/multipage/#dom-input-readonly
1136    make_bool_setter!(SetReadOnly, "readonly");
1137
1138    // https://html.spec.whatwg.org/multipage/#dom-input-size
1139    make_uint_getter!(Size, "size", DEFAULT_INPUT_SIZE);
1140
1141    // https://html.spec.whatwg.org/multipage/#dom-input-size
1142    make_limited_uint_setter!(SetSize, "size", DEFAULT_INPUT_SIZE);
1143
1144    /// <https://html.spec.whatwg.org/multipage/#dom-input-type>
1145    fn Type(&self) -> DOMString {
1146        DOMString::from(self.input_type().as_str())
1147    }
1148
1149    // https://html.spec.whatwg.org/multipage/#dom-input-type
1150    make_atomic_setter!(SetType, "type");
1151
1152    /// <https://html.spec.whatwg.org/multipage/#dom-input-value>
1153    fn Value(&self) -> DOMString {
1154        match self.value_mode() {
1155            ValueMode::Value => self.textinput.borrow().get_content(),
1156            ValueMode::Default => self
1157                .upcast::<Element>()
1158                .get_attribute_string_value(&local_name!("value"))
1159                .map(|value| value.into())
1160                .unwrap_or_default(),
1161            ValueMode::DefaultOn => self
1162                .upcast::<Element>()
1163                .get_attribute_string_value(&local_name!("value"))
1164                .map(|value| value.into())
1165                .unwrap_or(DOMString::from("on")),
1166            ValueMode::Filename => {
1167                let mut path = DOMString::from("");
1168                match self.input_type().as_specific().get_files() {
1169                    Some(ref fl) => match fl.Item(0) {
1170                        Some(ref f) => {
1171                            path.push_str("C:\\fakepath\\");
1172                            path.push_str(&f.name().str());
1173                            path
1174                        },
1175                        None => path,
1176                    },
1177                    None => path,
1178                }
1179            },
1180        }
1181    }
1182
1183    /// <https://html.spec.whatwg.org/multipage/#dom-input-value>
1184    fn SetValue(&self, cx: &mut JSContext, mut value: DOMString) -> ErrorResult {
1185        match self.value_mode() {
1186            ValueMode::Value => {
1187                {
1188                    // Step 3. Set the element's dirty value flag to true.
1189                    self.value_dirty.set(true);
1190
1191                    // Step 4. Invoke the value sanitization algorithm, if the element's type
1192                    // attribute's current state defines one.
1193                    self.sanitize_value(&mut value);
1194
1195                    let mut textinput = self.textinput.borrow_mut();
1196
1197                    // Step 5. If the element's value (after applying the value sanitization algorithm)
1198                    // is different from oldValue, and the element has a text entry cursor position,
1199                    // move the text entry cursor position to the end of the text control,
1200                    // unselecting any selected text and resetting the selection direction to "none".
1201                    if textinput.get_content() != value {
1202                        // Step 2. Set the element's value to the new value.
1203                        textinput.set_content(value);
1204
1205                        textinput.clear_selection_to_end();
1206                    }
1207                }
1208
1209                // Additionally, update the placeholder shown state in another
1210                // scope to prevent the borrow checker issue. This is normally
1211                // being done in the attributed mutated.
1212                self.update_placeholder_shown_state();
1213                self.maybe_update_shared_selection();
1214            },
1215            ValueMode::Default | ValueMode::DefaultOn => {
1216                self.upcast::<Element>()
1217                    .set_string_attribute(cx, &local_name!("value"), value);
1218            },
1219            ValueMode::Filename => {
1220                if value.is_empty() {
1221                    let window = self.owner_window();
1222                    let fl = FileList::new(&window, vec![], CanGc::from_cx(cx));
1223                    self.input_type().as_specific().set_files(&fl)
1224                } else {
1225                    return Err(Error::InvalidState(None));
1226                }
1227            },
1228        }
1229
1230        self.value_changed(cx);
1231        self.upcast::<Node>().dirty(NodeDamage::Other);
1232        Ok(())
1233    }
1234
1235    // https://html.spec.whatwg.org/multipage/#dom-input-defaultvalue
1236    make_getter!(DefaultValue, "value");
1237
1238    // https://html.spec.whatwg.org/multipage/#dom-input-defaultvalue
1239    make_setter!(SetDefaultValue, "value");
1240
1241    // https://html.spec.whatwg.org/multipage/#dom-input-min
1242    make_getter!(Min, "min");
1243
1244    // https://html.spec.whatwg.org/multipage/#dom-input-min
1245    make_setter!(SetMin, "min");
1246
1247    /// <https://html.spec.whatwg.org/multipage/#dom-input-list>
1248    fn GetList(&self) -> Option<DomRoot<HTMLDataListElement>> {
1249        self.suggestions_source_element()
1250    }
1251
1252    // https://html.spec.whatwg.org/multipage/#dom-input-valueasdate
1253    #[expect(unsafe_code)]
1254    fn GetValueAsDate(&self, cx: SafeJSContext) -> Option<NonNull<JSObject>> {
1255        self.input_type()
1256            .as_specific()
1257            .convert_string_to_naive_datetime(self.Value())
1258            .map(|date_time| unsafe {
1259                let time = ClippedTime {
1260                    t: (date_time - OffsetDateTime::UNIX_EPOCH).whole_milliseconds() as f64,
1261                };
1262                NonNull::new_unchecked(NewDateObject(*cx, time))
1263            })
1264    }
1265
1266    // https://html.spec.whatwg.org/multipage/#dom-input-valueasdate
1267    #[expect(unsafe_code)]
1268    fn SetValueAsDate(&self, cx: &mut JSContext, value: *mut JSObject) -> ErrorResult {
1269        rooted!(&in(cx) let value = value);
1270        if !self.does_value_as_date_apply() {
1271            return Err(Error::InvalidState(None));
1272        }
1273        if value.is_null() {
1274            return self.SetValue(cx, DOMString::from(""));
1275        }
1276        let mut msecs: f64 = 0.0;
1277        // We need to go through unsafe code to interrogate jsapi about a Date.
1278        // To minimize the amount of unsafe code to maintain, this just gets the milliseconds,
1279        // which we then reinflate into a NaiveDate for use in safe code.
1280        unsafe {
1281            let mut is_date = false;
1282            if !ObjectIsDate(cx, value.handle(), &mut is_date) {
1283                return Err(Error::JSFailed);
1284            }
1285            if !is_date {
1286                return Err(Error::Type(c"Value was not a date".to_owned()));
1287            }
1288            if !DateGetMsecSinceEpoch(cx, value.handle(), &mut msecs) {
1289                return Err(Error::JSFailed);
1290            }
1291            if !msecs.is_finite() {
1292                return self.SetValue(cx, DOMString::from(""));
1293            }
1294        }
1295
1296        let Ok(date_time) = OffsetDateTime::from_unix_timestamp_nanos((msecs * 1e6) as i128) else {
1297            return self.SetValue(cx, DOMString::from(""));
1298        };
1299        self.SetValue(
1300            cx,
1301            self.input_type()
1302                .as_specific()
1303                .convert_datetime_to_dom_string(date_time),
1304        )
1305    }
1306
1307    /// <https://html.spec.whatwg.org/multipage/#dom-input-valueasnumber>
1308    fn ValueAsNumber(&self) -> f64 {
1309        self.convert_string_to_number(&self.Value().str())
1310            .unwrap_or(f64::NAN)
1311    }
1312
1313    /// <https://html.spec.whatwg.org/multipage/#dom-input-valueasnumber>
1314    fn SetValueAsNumber(&self, cx: &mut JSContext, value: f64) -> ErrorResult {
1315        if value.is_infinite() {
1316            Err(Error::Type(c"value is not finite".to_owned()))
1317        } else if !self.does_value_as_number_apply() {
1318            Err(Error::InvalidState(None))
1319        } else if value.is_nan() {
1320            self.SetValue(cx, DOMString::from(""))
1321        } else if let Some(converted) = self.convert_number_to_string(value) {
1322            self.SetValue(cx, converted)
1323        } else {
1324            // The most literal spec-compliant implementation would use bignum types so
1325            // overflow is impossible, but just setting an overflow to the empty string
1326            // matches Firefox's behavior. For example, try input.valueAsNumber=1e30 on
1327            // a type="date" input.
1328            self.SetValue(cx, DOMString::from(""))
1329        }
1330    }
1331
1332    // https://html.spec.whatwg.org/multipage/#attr-fe-name
1333    make_getter!(Name, "name");
1334
1335    // https://html.spec.whatwg.org/multipage/#attr-fe-name
1336    make_atomic_setter!(SetName, "name");
1337
1338    // https://html.spec.whatwg.org/multipage/#dom-input-placeholder
1339    make_getter!(Placeholder, "placeholder");
1340
1341    // https://html.spec.whatwg.org/multipage/#dom-input-placeholder
1342    make_setter!(SetPlaceholder, "placeholder");
1343
1344    // https://html.spec.whatwg.org/multipage/#dom-input-formaction
1345    make_form_action_getter!(FormAction, "formaction");
1346
1347    // https://html.spec.whatwg.org/multipage/#dom-input-formaction
1348    make_setter!(SetFormAction, "formaction");
1349
1350    // https://html.spec.whatwg.org/multipage/#dom-fs-formenctype
1351    make_enumerated_getter!(
1352        FormEnctype,
1353        "formenctype",
1354        "application/x-www-form-urlencoded" | "text/plain" | "multipart/form-data",
1355        invalid => "application/x-www-form-urlencoded"
1356    );
1357
1358    // https://html.spec.whatwg.org/multipage/#dom-input-formenctype
1359    make_setter!(SetFormEnctype, "formenctype");
1360
1361    // https://html.spec.whatwg.org/multipage/#dom-fs-formmethod
1362    make_enumerated_getter!(
1363        FormMethod,
1364        "formmethod",
1365        "get" | "post" | "dialog",
1366        invalid => "get"
1367    );
1368
1369    // https://html.spec.whatwg.org/multipage/#dom-fs-formmethod
1370    make_setter!(SetFormMethod, "formmethod");
1371
1372    // https://html.spec.whatwg.org/multipage/#dom-input-formtarget
1373    make_getter!(FormTarget, "formtarget");
1374
1375    // https://html.spec.whatwg.org/multipage/#dom-input-formtarget
1376    make_setter!(SetFormTarget, "formtarget");
1377
1378    // https://html.spec.whatwg.org/multipage/#attr-fs-formnovalidate
1379    make_bool_getter!(FormNoValidate, "formnovalidate");
1380
1381    // https://html.spec.whatwg.org/multipage/#attr-fs-formnovalidate
1382    make_bool_setter!(SetFormNoValidate, "formnovalidate");
1383
1384    // https://html.spec.whatwg.org/multipage/#dom-input-max
1385    make_getter!(Max, "max");
1386
1387    // https://html.spec.whatwg.org/multipage/#dom-input-max
1388    make_setter!(SetMax, "max");
1389
1390    // https://html.spec.whatwg.org/multipage/#dom-input-maxlength
1391    make_int_getter!(MaxLength, "maxlength", DEFAULT_MAX_LENGTH);
1392
1393    // https://html.spec.whatwg.org/multipage/#dom-input-maxlength
1394    make_limited_int_setter!(SetMaxLength, "maxlength", DEFAULT_MAX_LENGTH);
1395
1396    // https://html.spec.whatwg.org/multipage/#dom-input-minlength
1397    make_int_getter!(MinLength, "minlength", DEFAULT_MIN_LENGTH);
1398
1399    // https://html.spec.whatwg.org/multipage/#dom-input-minlength
1400    make_limited_int_setter!(SetMinLength, "minlength", DEFAULT_MIN_LENGTH);
1401
1402    // https://html.spec.whatwg.org/multipage/#dom-input-multiple
1403    make_bool_getter!(Multiple, "multiple");
1404
1405    // https://html.spec.whatwg.org/multipage/#dom-input-multiple
1406    make_bool_setter!(SetMultiple, "multiple");
1407
1408    // https://html.spec.whatwg.org/multipage/#dom-input-pattern
1409    make_getter!(Pattern, "pattern");
1410
1411    // https://html.spec.whatwg.org/multipage/#dom-input-pattern
1412    make_setter!(SetPattern, "pattern");
1413
1414    // https://html.spec.whatwg.org/multipage/#dom-input-required
1415    make_bool_getter!(Required, "required");
1416
1417    // https://html.spec.whatwg.org/multipage/#dom-input-required
1418    make_bool_setter!(SetRequired, "required");
1419
1420    // https://html.spec.whatwg.org/multipage/#dom-input-src
1421    make_url_getter!(Src, "src");
1422
1423    // https://html.spec.whatwg.org/multipage/#dom-input-src
1424    make_url_setter!(SetSrc, "src");
1425
1426    // https://html.spec.whatwg.org/multipage/#dom-input-step
1427    make_getter!(Step, "step");
1428
1429    // https://html.spec.whatwg.org/multipage/#dom-input-step
1430    make_setter!(SetStep, "step");
1431
1432    // https://html.spec.whatwg.org/multipage/#dom-input-usemap
1433    make_getter!(UseMap, "usemap");
1434
1435    // https://html.spec.whatwg.org/multipage/#dom-input-usemap
1436    make_setter!(SetUseMap, "usemap");
1437
1438    /// <https://html.spec.whatwg.org/multipage/#dom-input-indeterminate>
1439    fn Indeterminate(&self) -> bool {
1440        self.upcast::<Element>()
1441            .state()
1442            .contains(ElementState::INDETERMINATE)
1443    }
1444
1445    /// <https://html.spec.whatwg.org/multipage/#dom-input-indeterminate>
1446    fn SetIndeterminate(&self, _cx: &mut JSContext, val: bool) {
1447        self.upcast::<Element>()
1448            .set_state(ElementState::INDETERMINATE, val)
1449    }
1450
1451    /// <https://html.spec.whatwg.org/multipage/#dom-lfe-labels>
1452    /// Different from make_labels_getter because this one
1453    /// conditionally returns null.
1454    fn GetLabels(&self, cx: &mut JSContext) -> Option<DomRoot<NodeList>> {
1455        if matches!(*self.input_type(), InputType::Hidden(_)) {
1456            None
1457        } else {
1458            Some(self.labels_node_list.or_init(|| {
1459                NodeList::new_labels_list(
1460                    self.upcast::<Node>().owner_doc().window(),
1461                    self.upcast::<HTMLElement>(),
1462                    CanGc::from_cx(cx),
1463                )
1464            }))
1465        }
1466    }
1467
1468    /// <https://html.spec.whatwg.org/multipage/#dom-textarea/input-select>
1469    fn Select(&self) {
1470        self.selection().dom_select();
1471    }
1472
1473    /// <https://html.spec.whatwg.org/multipage/#dom-textarea/input-selectionstart>
1474    fn GetSelectionStart(&self) -> Option<u32> {
1475        self.selection().dom_start().map(|start| start.0 as u32)
1476    }
1477
1478    /// <https://html.spec.whatwg.org/multipage/#dom-textarea/input-selectionstart>
1479    fn SetSelectionStart(&self, _cx: &mut JSContext, start: Option<u32>) -> ErrorResult {
1480        self.selection()
1481            .set_dom_start(start.map(Utf16CodeUnitLength::from))
1482    }
1483
1484    /// <https://html.spec.whatwg.org/multipage/#dom-textarea/input-selectionend>
1485    fn GetSelectionEnd(&self) -> Option<u32> {
1486        self.selection().dom_end().map(|end| end.0 as u32)
1487    }
1488
1489    /// <https://html.spec.whatwg.org/multipage/#dom-textarea/input-selectionend>
1490    fn SetSelectionEnd(&self, _cx: &mut JSContext, end: Option<u32>) -> ErrorResult {
1491        self.selection()
1492            .set_dom_end(end.map(Utf16CodeUnitLength::from))
1493    }
1494
1495    /// <https://html.spec.whatwg.org/multipage/#dom-textarea/input-selectiondirection>
1496    fn GetSelectionDirection(&self) -> Option<DOMString> {
1497        self.selection().dom_direction()
1498    }
1499
1500    /// <https://html.spec.whatwg.org/multipage/#dom-textarea/input-selectiondirection>
1501    fn SetSelectionDirection(
1502        &self,
1503        _cx: &mut JSContext,
1504        direction: Option<DOMString>,
1505    ) -> ErrorResult {
1506        self.selection().set_dom_direction(direction)
1507    }
1508
1509    /// <https://html.spec.whatwg.org/multipage/#dom-textarea/input-setselectionrange>
1510    fn SetSelectionRange(&self, start: u32, end: u32, direction: Option<DOMString>) -> ErrorResult {
1511        self.selection().set_dom_range(
1512            Utf16CodeUnitLength::from(start),
1513            Utf16CodeUnitLength::from(end),
1514            direction,
1515        )
1516    }
1517
1518    /// <https://html.spec.whatwg.org/multipage/#dom-textarea/input-setrangetext>
1519    fn SetRangeText(&self, replacement: DOMString) -> ErrorResult {
1520        self.selection()
1521            .set_dom_range_text(replacement, None, None, Default::default())
1522    }
1523
1524    /// <https://html.spec.whatwg.org/multipage/#dom-textarea/input-setrangetext>
1525    fn SetRangeText_(
1526        &self,
1527        replacement: DOMString,
1528        start: u32,
1529        end: u32,
1530        selection_mode: SelectionMode,
1531    ) -> ErrorResult {
1532        self.selection().set_dom_range_text(
1533            replacement,
1534            Some(Utf16CodeUnitLength::from(start)),
1535            Some(Utf16CodeUnitLength::from(end)),
1536            selection_mode,
1537        )
1538    }
1539
1540    /// Select the files based on filepaths passed in, enabled by
1541    /// `dom_testing_html_input_element_select_files_enabled`, used for test purpose.
1542    fn SelectFiles(&self, paths: Vec<DOMString>) {
1543        self.input_type()
1544            .as_specific()
1545            .select_files(self, Some(paths));
1546    }
1547
1548    /// <https://html.spec.whatwg.org/multipage/#dom-input-stepup>
1549    fn StepUp(&self, cx: &mut JSContext, n: i32) -> ErrorResult {
1550        self.step_up_or_down(cx, n, StepDirection::Up)
1551    }
1552
1553    /// <https://html.spec.whatwg.org/multipage/#dom-input-stepdown>
1554    fn StepDown(&self, cx: &mut JSContext, n: i32) -> ErrorResult {
1555        self.step_up_or_down(cx, n, StepDirection::Down)
1556    }
1557
1558    /// <https://html.spec.whatwg.org/multipage/#dom-cva-willvalidate>
1559    fn WillValidate(&self) -> bool {
1560        self.is_instance_validatable()
1561    }
1562
1563    /// <https://html.spec.whatwg.org/multipage/#dom-cva-validity>
1564    fn Validity(&self, cx: &mut JSContext) -> DomRoot<ValidityState> {
1565        self.validity_state(cx)
1566    }
1567
1568    /// <https://html.spec.whatwg.org/multipage/#dom-cva-checkvalidity>
1569    fn CheckValidity(&self, cx: &mut JSContext) -> bool {
1570        self.check_validity(cx)
1571    }
1572
1573    /// <https://html.spec.whatwg.org/multipage/#dom-cva-reportvalidity>
1574    fn ReportValidity(&self, cx: &mut JSContext) -> bool {
1575        self.report_validity(cx)
1576    }
1577
1578    /// <https://html.spec.whatwg.org/multipage/#dom-cva-validationmessage>
1579    fn ValidationMessage(&self, cx: &mut JSContext) -> DOMString {
1580        self.validation_message(cx)
1581    }
1582
1583    /// <https://html.spec.whatwg.org/multipage/#dom-cva-setcustomvalidity>
1584    fn SetCustomValidity(&self, cx: &mut JSContext, error: DOMString) {
1585        self.validity_state(cx).set_custom_error_message(cx, error);
1586    }
1587}
1588
1589impl HTMLInputElement {
1590    /// <https://html.spec.whatwg.org/multipage/#constructing-the-form-data-set>
1591    /// Steps range from 5.1 to 5.10 (specific to HTMLInputElement)
1592    pub(crate) fn form_datums(
1593        &self,
1594        submitter: Option<FormSubmitterElement>,
1595        encoding: Option<&'static Encoding>,
1596    ) -> Vec<FormDatum> {
1597        // 3.1: disabled state check is in get_unclean_dataset
1598
1599        // Step 5.2
1600        let ty = self.Type();
1601
1602        // Step 5.4
1603        let name = self.Name();
1604        let is_submitter = match submitter {
1605            Some(FormSubmitterElement::Input(s)) => self == s,
1606            _ => false,
1607        };
1608
1609        match *self.input_type() {
1610            // Step 5.1: it's a button but it is not submitter.
1611            InputType::Submit(_) | InputType::Button(_) | InputType::Reset(_) if !is_submitter => {
1612                return vec![];
1613            },
1614
1615            // Step 5.1: it's the "Checkbox" or "Radio Button" and whose checkedness is false.
1616            InputType::Radio(_) | InputType::Checkbox(_) => {
1617                if !self.Checked() || name.is_empty() {
1618                    return vec![];
1619                }
1620            },
1621
1622            InputType::File(_) => {
1623                let mut datums = vec![];
1624
1625                // Step 5.2-5.7
1626                let name = self.Name();
1627
1628                match self.GetFiles() {
1629                    Some(fl) => {
1630                        for f in fl.iter_files() {
1631                            datums.push(FormDatum {
1632                                ty: ty.clone(),
1633                                name: name.clone(),
1634                                value: FormDatumValue::File(DomRoot::from_ref(f)),
1635                            });
1636                        }
1637                    },
1638                    None => {
1639                        datums.push(FormDatum {
1640                            // XXX(izgzhen): Spec says 'application/octet-stream' as the type,
1641                            // but this is _type_ of element rather than content right?
1642                            ty,
1643                            name,
1644                            value: FormDatumValue::String(DOMString::from("")),
1645                        })
1646                    },
1647                }
1648
1649                return datums;
1650            },
1651
1652            InputType::Image(_) => return vec![], // Unimplemented
1653
1654            // Step 5.10: it's a hidden field named _charset_
1655            InputType::Hidden(_) => {
1656                if name.to_ascii_lowercase() == "_charset_" {
1657                    return vec![FormDatum {
1658                        ty,
1659                        name,
1660                        value: FormDatumValue::String(match encoding {
1661                            None => DOMString::from("UTF-8"),
1662                            Some(enc) => DOMString::from(enc.name()),
1663                        }),
1664                    }];
1665                }
1666            },
1667
1668            // Step 5.1: it's not the "Image Button" and doesn't have a name attribute.
1669            _ => {
1670                if name.is_empty() {
1671                    return vec![];
1672                }
1673            },
1674        }
1675
1676        // Step 5.12
1677        vec![FormDatum {
1678            ty,
1679            name,
1680            value: FormDatumValue::String(self.Value()),
1681        }]
1682    }
1683
1684    /// <https://html.spec.whatwg.org/multipage/#radio-button-group>
1685    fn radio_group_name(&self) -> Option<Atom> {
1686        self.upcast::<Element>()
1687            .get_name()
1688            .filter(|name| !name.is_empty())
1689    }
1690
1691    fn update_checked_state(&self, cx: &mut JSContext, checked: bool, dirty: bool) {
1692        self.upcast::<Element>()
1693            .set_state(ElementState::CHECKED, checked);
1694
1695        if dirty {
1696            self.checked_changed.set(true);
1697        }
1698
1699        if matches!(*self.input_type(), InputType::Radio(_)) && checked {
1700            broadcast_radio_checked(cx, self, self.radio_group_name().as_ref());
1701        }
1702
1703        self.upcast::<Node>().dirty(NodeDamage::Other);
1704    }
1705
1706    // https://html.spec.whatwg.org/multipage/#concept-fe-mutable
1707    pub(crate) fn is_mutable(&self) -> bool {
1708        // https://html.spec.whatwg.org/multipage/#the-input-element:concept-fe-mutable
1709        // https://html.spec.whatwg.org/multipage/#the-readonly-attribute:concept-fe-mutable
1710        !(self.upcast::<Element>().disabled_state() || self.ReadOnly())
1711    }
1712
1713    /// <https://html.spec.whatwg.org/multipage/#the-input-element:concept-form-reset-control>:
1714    ///
1715    /// > The reset algorithm for input elements is to set its user validity, dirty value
1716    /// > flag, and dirty checkedness flag back to false, set the value of the element to
1717    /// > the value of the value content attribute, if there is one, or the empty string
1718    /// > otherwise, set the checkedness of the element to true if the element has a checked
1719    /// > content attribute and false if it does not, empty the list of selected files, and
1720    /// > then invoke the value sanitization algorithm, if the type attribute's current
1721    /// > state defines one.
1722    pub(crate) fn reset(&self, cx: &mut JSContext) {
1723        self.value_dirty.set(false);
1724
1725        // We set the value and sanitize all in one go.
1726        let mut value = self.DefaultValue();
1727        self.sanitize_value(&mut value);
1728        self.textinput.borrow_mut().set_content(value);
1729
1730        let input_type = &*self.input_type();
1731        if matches!(input_type, InputType::Radio(_) | InputType::Checkbox(_)) {
1732            self.update_checked_state(cx, self.DefaultChecked(), false);
1733            self.checked_changed.set(false);
1734        }
1735
1736        if matches!(input_type, InputType::File(_)) {
1737            input_type.as_specific().set_files(&FileList::new(
1738                &self.owner_window(),
1739                vec![],
1740                CanGc::from_cx(cx),
1741            ));
1742        }
1743
1744        self.value_changed(cx);
1745    }
1746
1747    /// <https://w3c.github.io/webdriver/#ref-for-dfn-clear-algorithm-3>
1748    /// Used by WebDriver to clear the input element.
1749    pub(crate) fn clear(&self, cx: &mut JSContext) {
1750        // Step 1. Reset dirty value and dirty checkedness flags.
1751        self.value_dirty.set(false);
1752        self.checked_changed.set(false);
1753        // Step 2. Set value to empty string.
1754        self.textinput.borrow_mut().set_content(DOMString::from(""));
1755        // Step 3. Set checkedness based on presence of content attribute.
1756        self.update_checked_state(cx, self.DefaultChecked(), false);
1757        // Step 4. Empty selected files
1758        if self.input_type().as_specific().get_files().is_some() {
1759            let window = self.owner_window();
1760            let filelist = FileList::new(&window, vec![], CanGc::from_cx(cx));
1761            self.input_type().as_specific().set_files(&filelist);
1762        }
1763
1764        // Step 5. Invoke the value sanitization algorithm iff the type attribute's
1765        // current state defines one.
1766        {
1767            let mut textinput = self.textinput.borrow_mut();
1768            let mut value = textinput.get_content();
1769            self.sanitize_value(&mut value);
1770            textinput.set_content(value);
1771        }
1772
1773        self.value_changed(cx);
1774    }
1775
1776    fn update_placeholder_shown_state(&self) {
1777        if !self.input_type().is_textual_or_password() {
1778            self.upcast::<Element>().set_placeholder_shown_state(false);
1779        } else {
1780            let has_placeholder = !self.placeholder.borrow().is_empty();
1781            let has_value = !self.textinput.borrow().is_empty();
1782            self.upcast::<Element>()
1783                .set_placeholder_shown_state(has_placeholder && !has_value);
1784        }
1785    }
1786
1787    pub(crate) fn select_files_for_webdriver(
1788        &self,
1789        test_paths: Vec<DOMString>,
1790        response_sender: GenericSender<Result<bool, ErrorStatus>>,
1791    ) {
1792        let mut stored_sender = self.pending_webdriver_response.borrow_mut();
1793        assert!(stored_sender.is_none());
1794
1795        *stored_sender = Some(PendingWebDriverResponse {
1796            response_sender,
1797            expected_file_count: test_paths.len(),
1798        });
1799
1800        self.input_type()
1801            .as_specific()
1802            .select_files(self, Some(test_paths));
1803    }
1804
1805    /// <https://html.spec.whatwg.org/multipage/#value-sanitization-algorithm>
1806    fn sanitize_value(&self, value: &mut DOMString) {
1807        self.input_type().as_specific().sanitize_value(self, value);
1808    }
1809
1810    fn selection(&self) -> TextControlSelection<'_, Self> {
1811        TextControlSelection::new(self, &self.textinput)
1812    }
1813
1814    /// <https://html.spec.whatwg.org/multipage/#implicit-submission>
1815    fn implicit_submission(&self, cx: &mut js::context::JSContext) {
1816        let doc = self.owner_document();
1817        let node = doc.upcast::<Node>();
1818        let owner = self.form_owner();
1819        let form = match owner {
1820            None => return,
1821            Some(ref f) => f,
1822        };
1823
1824        if self.upcast::<Element>().click_in_progress() {
1825            return;
1826        }
1827        let submit_button = node
1828            .traverse_preorder(ShadowIncluding::No)
1829            .filter_map(DomRoot::downcast::<HTMLInputElement>)
1830            .filter(|input| matches!(*input.input_type(), InputType::Submit(_)))
1831            .find(|r| r.form_owner() == owner);
1832        match submit_button {
1833            Some(ref button) => {
1834                if button.is_instance_activatable() {
1835                    // spec does not actually say to set the not trusted flag,
1836                    // but we can get here from synthetic keydown events
1837                    button
1838                        .upcast::<Node>()
1839                        .fire_synthetic_pointer_event_not_trusted(cx, atom!("click"));
1840                }
1841            },
1842            None => {
1843                let mut inputs = node
1844                    .traverse_preorder(ShadowIncluding::No)
1845                    .filter_map(DomRoot::downcast::<HTMLInputElement>)
1846                    .filter(|input| {
1847                        input.form_owner() == owner &&
1848                            matches!(
1849                                *input.input_type(),
1850                                InputType::Text(_) |
1851                                    InputType::Search(_) |
1852                                    InputType::Url(_) |
1853                                    InputType::Tel(_) |
1854                                    InputType::Email(_) |
1855                                    InputType::Password(_) |
1856                                    InputType::Date(_) |
1857                                    InputType::Month(_) |
1858                                    InputType::Week(_) |
1859                                    InputType::Time(_) |
1860                                    InputType::DatetimeLocal(_) |
1861                                    InputType::Number(_)
1862                            )
1863                    });
1864
1865                if inputs.nth(1).is_some() {
1866                    // lazily test for > 1 submission-blocking inputs
1867                    return;
1868                }
1869                form.submit(
1870                    cx,
1871                    SubmittedFrom::NotFromForm,
1872                    FormSubmitterElement::Form(form),
1873                );
1874            },
1875        }
1876    }
1877
1878    /// <https://html.spec.whatwg.org/multipage/#concept-input-value-string-number>
1879    fn convert_string_to_number(&self, value: &str) -> Option<f64> {
1880        self.input_type()
1881            .as_specific()
1882            .convert_string_to_number(value)
1883    }
1884
1885    /// <https://html.spec.whatwg.org/multipage/#concept-input-value-string-number>
1886    fn convert_number_to_string(&self, value: f64) -> Option<DOMString> {
1887        self.input_type()
1888            .as_specific()
1889            .convert_number_to_string(value)
1890    }
1891
1892    fn update_related_validity_states(&self, cx: &mut JSContext) {
1893        match *self.input_type() {
1894            InputType::Radio(_) => {
1895                perform_radio_group_validation(cx, self, self.radio_group_name().as_ref())
1896            },
1897            _ => {
1898                self.validity_state(cx)
1899                    .perform_validation_and_update(cx, ValidationFlags::all());
1900            },
1901        }
1902    }
1903
1904    fn value_changed(&self, cx: &mut JSContext) {
1905        self.maybe_update_shared_selection();
1906        self.update_related_validity_states(cx);
1907        self.input_type().as_specific().update_shadow_tree(cx, self);
1908    }
1909
1910    /// <https://html.spec.whatwg.org/multipage/#show-the-picker,-if-applicable>
1911    fn show_the_picker_if_applicable(&self) {
1912        // FIXME: Implement most of this algorithm
1913
1914        // Step 2. If element is not mutable, then return.
1915        if !self.is_mutable() {
1916            return;
1917        }
1918
1919        // Step 6. Otherwise, the user agent should show the relevant user interface for selecting a value for element,
1920        // in the way it normally would when the user interacts with the control.
1921        self.input_type()
1922            .as_specific()
1923            .show_the_picker_if_applicable(self);
1924    }
1925
1926    pub(crate) fn handle_color_picker_response(
1927        &self,
1928        cx: &mut js::context::JSContext,
1929        response: Option<RgbColor>,
1930    ) {
1931        if let InputType::Color(ref color_input_type) = *self.input_type() {
1932            color_input_type.handle_color_picker_response(cx, self, response)
1933        }
1934    }
1935
1936    pub(crate) fn handle_file_picker_response(
1937        &self,
1938        cx: &mut js::context::JSContext,
1939        response: Option<Vec<SelectedFile>>,
1940    ) {
1941        if let InputType::File(ref file_input_type) = *self.input_type() {
1942            file_input_type.handle_file_picker_response(cx, self, response)
1943        }
1944    }
1945
1946    fn handle_focus_event(&self, event: &FocusEvent) {
1947        let event_type = event.upcast::<Event>().type_();
1948        if *event_type == *"blur" {
1949            self.owner_document()
1950                .embedder_controls()
1951                .hide_embedder_control(self.upcast());
1952        } else if *event_type == *"focus" {
1953            let input_type = &*self.input_type();
1954            let Ok(input_method_type) = input_type.try_into() else {
1955                return;
1956            };
1957
1958            self.owner_document()
1959                .embedder_controls()
1960                .show_embedder_control(
1961                    ControlElement::Ime(DomRoot::from_ref(self.upcast())),
1962                    EmbedderControlRequest::InputMethod(InputMethodRequest {
1963                        input_method_type,
1964                        text: String::from(self.Value()),
1965                        insertion_point: self.GetSelectionEnd(),
1966                        multiline: false,
1967                        // We follow chromium's heuristic to show the virtual keyboard only if user had interacted before.
1968                        allow_virtual_keyboard: self.owner_window().has_sticky_activation(),
1969                    }),
1970                    None,
1971                );
1972        }
1973    }
1974
1975    fn handle_mouse_event(&self, mouse_event: &MouseEvent) {
1976        if mouse_event.upcast::<Event>().DefaultPrevented() {
1977            return;
1978        }
1979
1980        // Only respond to mouse events if we are displayed as text input or a password. If the
1981        // placeholder is displayed, also don't do any interactive mouse event handling.
1982        if !self.input_type().is_textual_or_password() || self.textinput.borrow().is_empty() {
1983            return;
1984        }
1985        let node = self.upcast();
1986        if self
1987            .textinput
1988            .borrow_mut()
1989            .handle_mouse_event(node, mouse_event)
1990        {
1991            self.maybe_update_shared_selection();
1992        }
1993    }
1994}
1995
1996impl VirtualMethods for HTMLInputElement {
1997    fn super_type(&self) -> Option<&dyn VirtualMethods> {
1998        Some(self.upcast::<HTMLElement>() as &dyn VirtualMethods)
1999    }
2000
2001    fn attribute_mutated(
2002        &self,
2003        cx: &mut JSContext,
2004        attr: AttrRef<'_>,
2005        mutation: AttributeMutation,
2006    ) {
2007        let could_have_had_embedder_control = self.may_have_embedder_control();
2008
2009        self.super_type()
2010            .unwrap()
2011            .attribute_mutated(cx, attr, mutation);
2012
2013        match *attr.local_name() {
2014            local_name!("disabled") => {
2015                let disabled_state = match mutation {
2016                    AttributeMutation::Set(None, _) => true,
2017                    AttributeMutation::Set(Some(_), _) => {
2018                        // Input was already disabled before.
2019                        return;
2020                    },
2021                    AttributeMutation::Removed => false,
2022                };
2023                let el = self.upcast::<Element>();
2024                el.set_disabled_state(disabled_state);
2025                el.set_enabled_state(!disabled_state);
2026                el.check_ancestors_disabled_state_for_form_control();
2027
2028                if self.input_type().is_textual() {
2029                    let read_write = !(self.ReadOnly() || el.disabled_state());
2030                    el.set_read_write_state(read_write);
2031                }
2032            },
2033            local_name!("checked") if !self.checked_changed.get() => {
2034                let checked_state = match mutation {
2035                    AttributeMutation::Set(None, _) => true,
2036                    AttributeMutation::Set(Some(_), _) => {
2037                        // Input was already checked before.
2038                        return;
2039                    },
2040                    AttributeMutation::Removed => false,
2041                };
2042                self.update_checked_state(cx, checked_state, false);
2043            },
2044            local_name!("size") => {
2045                let size = mutation.new_value(attr).map(|value| value.as_uint());
2046                self.size.set(size.unwrap_or(DEFAULT_INPUT_SIZE));
2047            },
2048            local_name!("type") => {
2049                match mutation {
2050                    AttributeMutation::Set(..) => {
2051                        // https://html.spec.whatwg.org/multipage/#input-type-change
2052                        let (old_value_mode, old_idl_value) = (self.value_mode(), self.Value());
2053                        let previously_selectable = self.selection_api_applies();
2054
2055                        *self.input_type.borrow_mut() =
2056                            InputType::new_from_atom(attr.value().as_atom());
2057                        self.is_textual_or_password
2058                            .set(self.input_type().is_textual_or_password());
2059
2060                        let element = self.upcast::<Element>();
2061                        if self.input_type().is_textual() {
2062                            let read_write = !(self.ReadOnly() || element.disabled_state());
2063                            element.set_read_write_state(read_write);
2064                        } else {
2065                            element.set_read_write_state(false);
2066                        }
2067
2068                        let new_value_mode = self.value_mode();
2069                        match (&old_value_mode, old_idl_value.is_empty(), new_value_mode) {
2070                            // Step 1
2071                            (&ValueMode::Value, false, ValueMode::Default) |
2072                            (&ValueMode::Value, false, ValueMode::DefaultOn) => {
2073                                self.SetValue(cx, old_idl_value)
2074                                    .expect("Failed to set input value on type change to a default ValueMode.");
2075                            },
2076
2077                            // Step 2
2078                            (_, _, ValueMode::Value) if old_value_mode != ValueMode::Value => {
2079                                self.SetValue(
2080                                    cx,
2081                                    self.upcast::<Element>()
2082                                        .get_attribute_string_value(&local_name!("value"))
2083                                        .unwrap_or_default()
2084                                        .into(),
2085                                )
2086                                .expect(
2087                                    "Failed to set input value on type change to ValueMode::Value.",
2088                                );
2089                                self.value_dirty.set(false);
2090                            },
2091
2092                            // Step 3
2093                            (_, _, ValueMode::Filename)
2094                                if old_value_mode != ValueMode::Filename =>
2095                            {
2096                                self.SetValue(cx, DOMString::from(""))
2097                                    .expect("Failed to set input value on type change to ValueMode::Filename.");
2098                            },
2099                            _ => {},
2100                        }
2101
2102                        // Step 5
2103                        self.input_type().as_specific().signal_type_change(cx, self);
2104
2105                        // Step 6
2106                        let mut textinput = self.textinput.borrow_mut();
2107                        let mut value = textinput.get_content();
2108                        self.sanitize_value(&mut value);
2109                        textinput.set_content(value);
2110                        self.upcast::<Node>().dirty(NodeDamage::Other);
2111
2112                        // Steps 7-9
2113                        if !previously_selectable && self.selection_api_applies() {
2114                            textinput.clear_selection_to_start();
2115                        }
2116                    },
2117                    AttributeMutation::Removed => {
2118                        self.input_type().as_specific().signal_type_change(cx, self);
2119                        *self.input_type.borrow_mut() = InputType::new_text();
2120                        self.is_textual_or_password
2121                            .set(self.input_type().is_textual_or_password());
2122
2123                        let element = self.upcast::<Element>();
2124                        let read_write = !(self.ReadOnly() || element.disabled_state());
2125                        element.set_read_write_state(read_write);
2126                    },
2127                }
2128
2129                self.update_placeholder_shown_state();
2130                self.input_type()
2131                    .as_specific()
2132                    .update_placeholder_contents(cx, self);
2133            },
2134            local_name!("value") if !self.value_dirty.get() => {
2135                // This is only run when the `value` or `defaultValue` attribute is set. It
2136                // has a different behavior than `SetValue` which is triggered by setting the
2137                // value property in script.
2138                let value = mutation.new_value(attr).map(|value| (**value).to_owned());
2139                let mut value = value.map_or(DOMString::new(), DOMString::from);
2140
2141                self.sanitize_value(&mut value);
2142                self.textinput.borrow_mut().set_content(value);
2143                self.update_placeholder_shown_state();
2144            },
2145            local_name!("maxlength") => match *attr.value() {
2146                AttrValue::Int(_, value) => {
2147                    let mut textinput = self.textinput.borrow_mut();
2148
2149                    if value < 0 {
2150                        textinput.set_max_length(None);
2151                    } else {
2152                        textinput.set_max_length(Some(Utf16CodeUnitLength(value as usize)))
2153                    }
2154                },
2155                _ => panic!("Expected an AttrValue::Int"),
2156            },
2157            local_name!("minlength") => match *attr.value() {
2158                AttrValue::Int(_, value) => {
2159                    let mut textinput = self.textinput.borrow_mut();
2160
2161                    if value < 0 {
2162                        textinput.set_min_length(None);
2163                    } else {
2164                        textinput.set_min_length(Some(Utf16CodeUnitLength(value as usize)))
2165                    }
2166                },
2167                _ => panic!("Expected an AttrValue::Int"),
2168            },
2169            local_name!("placeholder") => {
2170                {
2171                    let mut placeholder = self.placeholder.borrow_mut();
2172                    placeholder.clear();
2173                    if let AttributeMutation::Set(..) = mutation {
2174                        placeholder
2175                            .extend(attr.value().chars().filter(|&c| c != '\n' && c != '\r'));
2176                    }
2177                }
2178                self.update_placeholder_shown_state();
2179                self.input_type()
2180                    .as_specific()
2181                    .update_placeholder_contents(cx, self);
2182            },
2183            local_name!("readonly") => {
2184                if self.input_type().is_textual() {
2185                    let el = self.upcast::<Element>();
2186                    match mutation {
2187                        AttributeMutation::Set(..) => {
2188                            el.set_read_write_state(false);
2189                        },
2190                        AttributeMutation::Removed => {
2191                            el.set_read_write_state(!el.disabled_state());
2192                        },
2193                    }
2194                }
2195            },
2196            local_name!("form") => {
2197                self.form_attribute_mutated(cx, mutation);
2198            },
2199            _ => {
2200                self.input_type()
2201                    .as_specific()
2202                    .attribute_mutated(cx, self, attr, mutation);
2203            },
2204        }
2205
2206        self.value_changed(cx);
2207
2208        if could_have_had_embedder_control && !self.may_have_embedder_control() {
2209            self.owner_document()
2210                .embedder_controls()
2211                .hide_embedder_control(self.upcast());
2212        }
2213    }
2214
2215    fn parse_plain_attribute(&self, name: &LocalName, value: DOMString) -> AttrValue {
2216        match *name {
2217            local_name!("accept") => AttrValue::from_comma_separated_tokenlist(value.into()),
2218            local_name!("size") => AttrValue::from_limited_u32(value.into(), DEFAULT_INPUT_SIZE),
2219            local_name!("type") => AttrValue::from_atomic(value.into()),
2220            local_name!("maxlength") => {
2221                AttrValue::from_limited_i32(value.into(), DEFAULT_MAX_LENGTH)
2222            },
2223            local_name!("minlength") => {
2224                AttrValue::from_limited_i32(value.into(), DEFAULT_MIN_LENGTH)
2225            },
2226            _ => self
2227                .super_type()
2228                .unwrap()
2229                .parse_plain_attribute(name, value),
2230        }
2231    }
2232
2233    fn bind_to_tree(&self, cx: &mut JSContext, context: &BindContext) {
2234        if let Some(s) = self.super_type() {
2235            s.bind_to_tree(cx, context);
2236        }
2237        self.upcast::<Element>()
2238            .check_ancestors_disabled_state_for_form_control();
2239
2240        self.input_type()
2241            .as_specific()
2242            .bind_to_tree(cx, self, context);
2243
2244        self.value_changed(cx);
2245    }
2246
2247    fn unbind_from_tree(&self, cx: &mut JSContext, context: &UnbindContext) {
2248        let form_owner = self.form_owner();
2249        self.super_type().unwrap().unbind_from_tree(cx, context);
2250
2251        let node = self.upcast::<Node>();
2252        let el = self.upcast::<Element>();
2253        if node
2254            .ancestors()
2255            .any(|ancestor| ancestor.is::<HTMLFieldSetElement>())
2256        {
2257            el.check_ancestors_disabled_state_for_form_control();
2258        } else {
2259            el.check_disabled_attribute();
2260        }
2261
2262        self.input_type()
2263            .as_specific()
2264            .unbind_from_tree(cx, self, form_owner, context);
2265
2266        self.validity_state(cx)
2267            .perform_validation_and_update(cx, ValidationFlags::all());
2268    }
2269
2270    // This represents behavior for which the UIEvents spec and the
2271    // DOM/HTML specs are out of sync.
2272    // Compare:
2273    // https://w3c.github.io/uievents/#default-action
2274    /// <https://dom.spec.whatwg.org/#action-versus-occurance>
2275    fn handle_event(&self, cx: &mut JSContext, event: &Event) {
2276        if let Some(mouse_event) = event.downcast::<MouseEvent>() {
2277            self.handle_mouse_event(mouse_event);
2278            event.mark_as_handled();
2279        } else if event.type_() == atom!("keydown") &&
2280            !event.DefaultPrevented() &&
2281            self.input_type().is_textual_or_password()
2282        {
2283            if let Some(keyevent) = event.downcast::<KeyboardEvent>() {
2284                // This can't be inlined, as holding on to textinput.borrow_mut()
2285                // during self.implicit_submission will cause a panic.
2286                let action = self.textinput.borrow_mut().handle_keydown(keyevent);
2287                self.handle_key_reaction(cx, action, event);
2288            }
2289        } else if (event.type_() == atom!("compositionstart") ||
2290            event.type_() == atom!("compositionupdate") ||
2291            event.type_() == atom!("compositionend")) &&
2292            self.input_type().is_textual_or_password()
2293        {
2294            if let Some(compositionevent) = event.downcast::<CompositionEvent>() {
2295                if event.type_() == atom!("compositionend") {
2296                    let action = self
2297                        .textinput
2298                        .borrow_mut()
2299                        .handle_compositionend(compositionevent);
2300                    self.handle_key_reaction(cx, action, event);
2301                    self.upcast::<Node>().dirty(NodeDamage::Other);
2302                    self.update_placeholder_shown_state();
2303                } else if event.type_() == atom!("compositionupdate") {
2304                    let action = self
2305                        .textinput
2306                        .borrow_mut()
2307                        .handle_compositionupdate(compositionevent);
2308                    self.handle_key_reaction(cx, action, event);
2309                    self.upcast::<Node>().dirty(NodeDamage::Other);
2310                    self.update_placeholder_shown_state();
2311                } else if event.type_() == atom!("compositionstart") {
2312                    // Update placeholder state when composition starts
2313                    self.update_placeholder_shown_state();
2314                }
2315                event.mark_as_handled();
2316            }
2317        } else if let Some(clipboard_event) = event.downcast::<ClipboardEvent>() {
2318            let reaction = self
2319                .textinput
2320                .borrow_mut()
2321                .handle_clipboard_event(clipboard_event);
2322            let flags = reaction.flags;
2323            if flags.contains(ClipboardEventFlags::FireClipboardChangedEvent) {
2324                self.owner_document().event_handler().fire_clipboard_event(
2325                    cx,
2326                    None,
2327                    ClipboardEventType::Change,
2328                );
2329            }
2330            if flags.contains(ClipboardEventFlags::QueueInputEvent) {
2331                self.textinput.borrow().queue_input_event(
2332                    self.upcast(),
2333                    reaction.text,
2334                    IsComposing::NotComposing,
2335                    reaction.input_type,
2336                );
2337            }
2338            if !flags.is_empty() {
2339                event.mark_as_handled();
2340                self.upcast::<Node>().dirty(NodeDamage::ContentOrHeritage);
2341            }
2342        } else if let Some(event) = event.downcast::<FocusEvent>() {
2343            self.handle_focus_event(event)
2344        }
2345
2346        self.value_changed(cx);
2347
2348        if let Some(super_type) = self.super_type() {
2349            super_type.handle_event(cx, event);
2350        }
2351    }
2352
2353    /// <https://html.spec.whatwg.org/multipage/#the-input-element%3Aconcept-node-clone-ext>
2354    fn cloning_steps(
2355        &self,
2356        cx: &mut JSContext,
2357        copy: &Node,
2358        maybe_doc: Option<&Document>,
2359        clone_children: CloneChildrenFlag,
2360    ) {
2361        if let Some(s) = self.super_type() {
2362            s.cloning_steps(cx, copy, maybe_doc, clone_children);
2363        }
2364        let elem = copy.downcast::<HTMLInputElement>().unwrap();
2365        elem.value_dirty.set(self.value_dirty.get());
2366        elem.checked_changed.set(self.checked_changed.get());
2367        elem.upcast::<Element>()
2368            .set_state(ElementState::CHECKED, self.Checked());
2369        elem.textinput
2370            .borrow_mut()
2371            .set_content(self.textinput.borrow().get_content());
2372        self.value_changed(cx);
2373    }
2374}
2375
2376impl FormControl for HTMLInputElement {
2377    fn form_owner(&self) -> Option<DomRoot<HTMLFormElement>> {
2378        self.form_owner.get()
2379    }
2380
2381    fn set_form_owner(&self, form: Option<&HTMLFormElement>) {
2382        self.form_owner.set(form);
2383    }
2384
2385    fn to_element(&self) -> &Element {
2386        self.upcast::<Element>()
2387    }
2388}
2389
2390impl Validatable for HTMLInputElement {
2391    fn as_element(&self) -> &Element {
2392        self.upcast()
2393    }
2394
2395    fn validity_state(&self, cx: &mut JSContext) -> DomRoot<ValidityState> {
2396        self.validity_state
2397            .or_init(|| ValidityState::new(cx, &self.owner_window(), self.upcast()))
2398    }
2399
2400    fn is_instance_validatable(&self) -> bool {
2401        // https://html.spec.whatwg.org/multipage/#hidden-state-(type%3Dhidden)%3Abarred-from-constraint-validation
2402        // https://html.spec.whatwg.org/multipage/#button-state-(type%3Dbutton)%3Abarred-from-constraint-validation
2403        // https://html.spec.whatwg.org/multipage/#reset-button-state-(type%3Dreset)%3Abarred-from-constraint-validation
2404        // https://html.spec.whatwg.org/multipage/#enabling-and-disabling-form-controls%3A-the-disabled-attribute%3Abarred-from-constraint-validation
2405        // https://html.spec.whatwg.org/multipage/#the-readonly-attribute%3Abarred-from-constraint-validation
2406        // https://html.spec.whatwg.org/multipage/#the-datalist-element%3Abarred-from-constraint-validation
2407        match *self.input_type() {
2408            InputType::Hidden(_) | InputType::Button(_) | InputType::Reset(_) => false,
2409            _ => {
2410                !(self.upcast::<Element>().disabled_state() ||
2411                    self.ReadOnly() ||
2412                    is_barred_by_datalist_ancestor(self.upcast()))
2413            },
2414        }
2415    }
2416
2417    fn perform_validation(
2418        &self,
2419        cx: &mut JSContext,
2420        validate_flags: ValidationFlags,
2421    ) -> ValidationFlags {
2422        let mut failed_flags = ValidationFlags::empty();
2423        let value = self.Value();
2424
2425        if validate_flags.contains(ValidationFlags::VALUE_MISSING) &&
2426            self.suffers_from_being_missing(&value)
2427        {
2428            failed_flags.insert(ValidationFlags::VALUE_MISSING);
2429        }
2430
2431        if validate_flags.contains(ValidationFlags::TYPE_MISMATCH) &&
2432            self.suffers_from_type_mismatch(&value)
2433        {
2434            failed_flags.insert(ValidationFlags::TYPE_MISMATCH);
2435        }
2436
2437        if validate_flags.contains(ValidationFlags::PATTERN_MISMATCH) &&
2438            self.suffers_from_pattern_mismatch(&value, CanGc::from_cx(cx))
2439        {
2440            failed_flags.insert(ValidationFlags::PATTERN_MISMATCH);
2441        }
2442
2443        if validate_flags.contains(ValidationFlags::BAD_INPUT) &&
2444            self.suffers_from_bad_input(&value)
2445        {
2446            failed_flags.insert(ValidationFlags::BAD_INPUT);
2447        }
2448
2449        if validate_flags.intersects(ValidationFlags::TOO_LONG | ValidationFlags::TOO_SHORT) {
2450            failed_flags |= self.suffers_from_length_issues(&value);
2451        }
2452
2453        if validate_flags.intersects(
2454            ValidationFlags::RANGE_UNDERFLOW |
2455                ValidationFlags::RANGE_OVERFLOW |
2456                ValidationFlags::STEP_MISMATCH,
2457        ) {
2458            failed_flags |= self.suffers_from_range_issues(&value);
2459        }
2460
2461        failed_flags & validate_flags
2462    }
2463}
2464
2465impl Activatable for HTMLInputElement {
2466    fn as_element(&self) -> &Element {
2467        self.upcast()
2468    }
2469
2470    fn is_instance_activatable(&self) -> bool {
2471        match *self.input_type() {
2472            // https://html.spec.whatwg.org/multipage/#submit-button-state-(type=submit):input-activation-behavior
2473            // https://html.spec.whatwg.org/multipage/#reset-button-state-(type=reset):input-activation-behavior
2474            // https://html.spec.whatwg.org/multipage/#file-upload-state-(type=file):input-activation-behavior
2475            // https://html.spec.whatwg.org/multipage/#image-button-state-(type=image):input-activation-behavior
2476            //
2477            // Although they do not have implicit activation behaviors, `type=button` is an activatable input event.
2478            InputType::Submit(_) |
2479            InputType::Reset(_) |
2480            InputType::File(_) |
2481            InputType::Image(_) |
2482            InputType::Button(_) => self.is_mutable(),
2483            // https://html.spec.whatwg.org/multipage/#checkbox-state-(type=checkbox):input-activation-behavior
2484            // https://html.spec.whatwg.org/multipage/#radio-button-state-(type=radio):input-activation-behavior
2485            // https://html.spec.whatwg.org/multipage/#color-state-(type=color):input-activation-behavior
2486            InputType::Checkbox(_) | InputType::Radio(_) | InputType::Color(_) => true,
2487            _ => false,
2488        }
2489    }
2490
2491    /// <https://dom.spec.whatwg.org/#eventtarget-legacy-pre-activation-behavior>
2492    fn legacy_pre_activation_behavior(&self, cx: &mut JSContext) -> Option<InputActivationState> {
2493        let activation_state = self
2494            .input_type()
2495            .as_specific()
2496            .legacy_pre_activation_behavior(cx, self);
2497
2498        if activation_state.is_some() {
2499            self.value_changed(cx);
2500        }
2501
2502        activation_state
2503    }
2504
2505    /// <https://dom.spec.whatwg.org/#eventtarget-legacy-canceled-activation-behavior>
2506    fn legacy_canceled_activation_behavior(
2507        &self,
2508        cx: &mut JSContext,
2509        cache: Option<InputActivationState>,
2510    ) {
2511        // Step 1
2512        let ty = self.input_type();
2513        let cache = match cache {
2514            Some(cache) => {
2515                if (cache.was_radio && !matches!(*ty, InputType::Radio(_))) ||
2516                    (cache.was_checkbox && !matches!(*ty, InputType::Checkbox(_)))
2517                {
2518                    // Type changed, abandon ship
2519                    // https://www.w3.org/Bugs/Public/show_bug.cgi?id=27414
2520                    return;
2521                }
2522                cache
2523            },
2524            None => {
2525                return;
2526            },
2527        };
2528
2529        // Step 2 and 3
2530        ty.as_specific()
2531            .legacy_canceled_activation_behavior(cx, self, cache);
2532
2533        self.value_changed(cx);
2534    }
2535
2536    /// <https://html.spec.whatwg.org/multipage/#input-activation-behavior>
2537    fn activation_behavior(
2538        &self,
2539        cx: &mut js::context::JSContext,
2540        event: &Event,
2541        target: &EventTarget,
2542    ) {
2543        self.input_type()
2544            .as_specific()
2545            .activation_behavior(cx, self, event, target);
2546    }
2547}
2548
2549/// This is used to compile JS-compatible regex provided in pattern attribute
2550/// that matches only the entirety of string.
2551/// <https://html.spec.whatwg.org/multipage/#compiled-pattern-regular-expression>
2552fn compile_pattern(
2553    cx: SafeJSContext,
2554    pattern_str: &str,
2555    out_regex: MutableHandleObject,
2556    can_gc: CanGc,
2557) -> bool {
2558    // First check if pattern compiles...
2559    if check_js_regex_syntax(cx, pattern_str, can_gc) {
2560        // ...and if it does make pattern that matches only the entirety of string
2561        let pattern_str = format!("^(?:{})$", pattern_str);
2562        let flags = RegExpFlags {
2563            flags_: RegExpFlag_UnicodeSets,
2564        };
2565        new_js_regex(cx, &pattern_str, flags, out_regex, can_gc)
2566    } else {
2567        false
2568    }
2569}
2570
2571#[expect(unsafe_code)]
2572/// Check if the pattern by itself is valid first, and not that it only becomes
2573/// valid once we add ^(?: and )$.
2574fn check_js_regex_syntax(cx: SafeJSContext, pattern: &str, _can_gc: CanGc) -> bool {
2575    let pattern: Vec<u16> = pattern.encode_utf16().collect();
2576    unsafe {
2577        rooted!(in(*cx) let mut exception = UndefinedValue());
2578
2579        let valid = CheckRegExpSyntax(
2580            *cx,
2581            pattern.as_ptr(),
2582            pattern.len(),
2583            RegExpFlags {
2584                flags_: RegExpFlag_UnicodeSets,
2585            },
2586            exception.handle_mut(),
2587        );
2588
2589        if !valid {
2590            JS_ClearPendingException(*cx);
2591            return false;
2592        }
2593
2594        // TODO(cybai): report `exception` to devtools
2595        // exception will be `undefined` if the regex is valid
2596        exception.is_undefined()
2597    }
2598}
2599
2600#[expect(unsafe_code)]
2601pub(crate) fn new_js_regex(
2602    cx: SafeJSContext,
2603    pattern: &str,
2604    flags: RegExpFlags,
2605    mut out_regex: MutableHandleObject,
2606    _can_gc: CanGc,
2607) -> bool {
2608    let pattern: Vec<u16> = pattern.encode_utf16().collect();
2609    unsafe {
2610        out_regex.set(NewUCRegExpObject(
2611            *cx,
2612            pattern.as_ptr(),
2613            pattern.len(),
2614            flags,
2615        ));
2616        if out_regex.is_null() {
2617            JS_ClearPendingException(*cx);
2618            return false;
2619        }
2620    }
2621    true
2622}
2623
2624#[expect(unsafe_code)]
2625fn matches_js_regex(
2626    cx: SafeJSContext,
2627    regex_obj: HandleObject,
2628    value: &str,
2629    _can_gc: CanGc,
2630) -> Result<bool, ()> {
2631    let mut value: Vec<u16> = value.encode_utf16().collect();
2632
2633    unsafe {
2634        let mut is_regex = false;
2635        assert!(ObjectIsRegExp(*cx, regex_obj, &mut is_regex));
2636        assert!(is_regex);
2637
2638        rooted!(in(*cx) let mut rval = UndefinedValue());
2639        let mut index = 0;
2640
2641        let ok = ExecuteRegExpNoStatics(
2642            *cx,
2643            regex_obj,
2644            value.as_mut_ptr(),
2645            value.len(),
2646            &mut index,
2647            true,
2648            rval.handle_mut(),
2649        );
2650
2651        if ok {
2652            Ok(!rval.is_null())
2653        } else {
2654            JS_ClearPendingException(*cx);
2655            Err(())
2656        }
2657    }
2658}
2659
2660/// When WebDriver asks the [`HTMLInputElement`] to do some asynchronous actions, such
2661/// as selecting files, this stores the details necessary to complete the response when
2662/// the action is complete.
2663#[derive(MallocSizeOf)]
2664struct PendingWebDriverResponse {
2665    /// An [`IpcSender`] to use to send the reply when the response is ready.
2666    response_sender: GenericSender<Result<bool, ErrorStatus>>,
2667    /// The number of files expected to be selected when the selection process is done.
2668    expected_file_count: usize,
2669}
2670
2671impl PendingWebDriverResponse {
2672    fn finish(self, number_files_selected: usize) {
2673        if number_files_selected == self.expected_file_count {
2674            let _ = self.response_sender.send(Ok(false));
2675        } else {
2676            // If not all files are found the WebDriver specification says to return
2677            // the InvalidArgument error.
2678            let _ = self.response_sender.send(Err(ErrorStatus::InvalidArgument));
2679        }
2680    }
2681}