Skip to main content

script/dom/html/form_controls/
htmlinputelement.rs

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