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