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