Skip to main content

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