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