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