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