Skip to main content

script/dom/html/input_element/
mod.rs

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