Skip to main content

script/dom/html/input_element/
mod.rs

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