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