Skip to main content

script/dom/html/input_element/
mod.rs

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