script/dom/html/
htmlinputelement.rs

1/* This Source Code Form is subject to the terms of the Mozilla Public
2 * License, v. 2.0. If a copy of the MPL was not distributed with this
3 * file, You can obtain one at https://mozilla.org/MPL/2.0/. */
4
5use std::borrow::Cow;
6use std::cell::{Cell, RefCell};
7use std::cmp::Ordering;
8use std::ops::Range;
9use std::path::PathBuf;
10use std::ptr::NonNull;
11use std::str::FromStr;
12use std::{f64, ptr};
13
14use dom_struct::dom_struct;
15use embedder_traits::{
16    EmbedderControlRequest, FilePickerRequest, FilterPattern, InputMethodRequest, InputMethodType,
17    RgbColor, SelectedFile,
18};
19use encoding_rs::Encoding;
20use html5ever::{LocalName, Prefix, QualName, local_name, ns};
21use ipc_channel::ipc::IpcSender;
22use itertools::Itertools;
23use js::jsapi::{
24    ClippedTime, DateGetMsecSinceEpoch, Handle, JS_ClearPendingException, JSObject, NewDateObject,
25    NewUCRegExpObject, ObjectIsDate, RegExpFlag_UnicodeSets, RegExpFlags,
26};
27use js::jsval::UndefinedValue;
28use js::rust::wrappers::{CheckRegExpSyntax, ExecuteRegExpNoStatics, ObjectIsRegExp};
29use js::rust::{HandleObject, MutableHandleObject};
30use net_traits::blob_url_store::get_blob_origin;
31use script_bindings::codegen::GenericBindings::CharacterDataBinding::CharacterDataMethods;
32use script_bindings::codegen::GenericBindings::DocumentBinding::DocumentMethods;
33use script_bindings::domstring::parse_floating_point_number;
34use style::attr::AttrValue;
35use style::selector_parser::PseudoElement;
36use style::str::split_commas;
37use stylo_atoms::Atom;
38use stylo_dom::ElementState;
39use time::{Month, OffsetDateTime, Time};
40use unicode_bidi::{BidiClass, bidi_class};
41use url::Url;
42use webdriver::error::ErrorStatus;
43
44use crate::clipboard_provider::EmbedderClipboardProvider;
45use crate::dom::activation::Activatable;
46use crate::dom::attr::Attr;
47use crate::dom::bindings::cell::{DomRefCell, Ref};
48use crate::dom::bindings::codegen::Bindings::ElementBinding::ElementMethods;
49use crate::dom::bindings::codegen::Bindings::EventBinding::EventMethods;
50use crate::dom::bindings::codegen::Bindings::FileListBinding::FileListMethods;
51use crate::dom::bindings::codegen::Bindings::HTMLFormElementBinding::SelectionMode;
52use crate::dom::bindings::codegen::Bindings::HTMLInputElementBinding::HTMLInputElementMethods;
53use crate::dom::bindings::codegen::Bindings::NodeBinding::{GetRootNodeOptions, NodeMethods};
54use crate::dom::bindings::error::{Error, ErrorResult};
55use crate::dom::bindings::inheritance::Castable;
56use crate::dom::bindings::root::{Dom, DomRoot, LayoutDom, MutNullableDom};
57use crate::dom::bindings::str::{DOMString, FromInputValueString, ToInputValueString, USVString};
58use crate::dom::clipboardevent::{ClipboardEvent, ClipboardEventType};
59use crate::dom::compositionevent::CompositionEvent;
60use crate::dom::document::Document;
61use crate::dom::document_embedder_controls::ControlElement;
62use crate::dom::element::{
63    AttributeMutation, CustomElementCreationMode, Element, ElementCreator, LayoutElementHelpers,
64};
65use crate::dom::event::{Event, EventBubbles, EventCancelable, EventComposed};
66use crate::dom::eventtarget::EventTarget;
67use crate::dom::file::File;
68use crate::dom::filelist::{FileList, LayoutFileListHelpers};
69use crate::dom::globalscope::GlobalScope;
70use crate::dom::html::htmldatalistelement::HTMLDataListElement;
71use crate::dom::html::htmlelement::HTMLElement;
72use crate::dom::html::htmlfieldsetelement::HTMLFieldSetElement;
73use crate::dom::html::htmlformelement::{
74    FormControl, FormDatum, FormDatumValue, FormSubmitterElement, HTMLFormElement, ResetFrom,
75    SubmittedFrom,
76};
77use crate::dom::keyboardevent::KeyboardEvent;
78use crate::dom::mouseevent::MouseEvent;
79use crate::dom::node::{
80    BindContext, CloneChildrenFlag, Node, NodeDamage, NodeTraits, ShadowIncluding, UnbindContext,
81};
82use crate::dom::nodelist::NodeList;
83use crate::dom::shadowroot::ShadowRoot;
84use crate::dom::textcontrol::{TextControlElement, TextControlSelection};
85use crate::dom::types::{CharacterData, FocusEvent};
86use crate::dom::validation::{Validatable, is_barred_by_datalist_ancestor};
87use crate::dom::validitystate::{ValidationFlags, ValidityState};
88use crate::dom::virtualmethods::VirtualMethods;
89use crate::realms::enter_realm;
90use crate::script_runtime::{CanGc, JSContext as SafeJSContext};
91use crate::textinput::Lines::Single;
92use crate::textinput::{
93    ClipboardEventFlags, Direction, IsComposing, KeyReaction, SelectionDirection, TextInput,
94    UTF8Bytes, UTF16CodeUnits,
95};
96
97const DEFAULT_SUBMIT_VALUE: &str = "Submit";
98const DEFAULT_RESET_VALUE: &str = "Reset";
99const PASSWORD_REPLACEMENT_CHAR: char = '●';
100const DEFAULT_FILE_INPUT_VALUE: &str = "No file chosen";
101
102#[derive(Clone, JSTraceable, MallocSizeOf)]
103#[cfg_attr(crown, crown::unrooted_must_root_lint::must_root)]
104/// Contains reference to text control inner editor and placeholder container element in the UA
105/// shadow tree for `text`, `password`, `url`, `tel`, and `email` input. The following is the
106/// structure of the shadow tree.
107///
108/// ```
109/// <input type="text">
110///     #shadow-root
111///         <div id="inner-container">
112///             <div id="input-editor"></div>
113///             <div id="input-placeholder"></div>
114///         </div>
115/// </input>
116/// ```
117///
118// TODO(stevennovaryo): We are trying to use CSS to mimic Chrome and Firefox's layout for the <input> element.
119//                      But, this could be slower in performance and does have some discrepancies. For example,
120//                      they would try to vertically align <input> text baseline with the baseline of other
121//                      TextNode within an inline flow. Another example is the horizontal scroll.
122// FIXME(#38263): Refactor these logics into a TextControl wrapper that would decouple all textual input.
123struct InputTypeTextShadowTree {
124    inner_container: Dom<Element>,
125    text_container: Dom<Element>,
126    placeholder_container: DomRefCell<Option<Dom<Element>>>,
127}
128
129impl InputTypeTextShadowTree {
130    /// Initialize the placeholder container only when it is necessary. This would help the performance of input
131    /// element with shadow dom that is quite bulky.
132    fn init_placeholder_container_if_necessary(&self, host: &HTMLInputElement, can_gc: CanGc) {
133        // If the container is already initialized or there is no placeholder then it is not necessary to
134        // initialize a new placeholder container.
135        if self.placeholder_container.borrow().is_some() || host.placeholder.borrow().is_empty() {
136            return;
137        }
138
139        *self.placeholder_container.borrow_mut() = Some(
140            create_ua_widget_div_with_text_node(
141                &host.owner_document(),
142                self.inner_container.upcast::<Node>(),
143                PseudoElement::Placeholder,
144                true,
145                can_gc,
146            )
147            .as_traced(),
148        );
149    }
150}
151
152#[derive(Clone, JSTraceable, MallocSizeOf)]
153#[cfg_attr(crown, crown::unrooted_must_root_lint::must_root)]
154/// Contains references to the elements in the shadow tree for `<input type=color>`.
155///
156/// The shadow tree consists of a single div with the currently selected color as
157/// the background.
158struct InputTypeColorShadowTree {
159    color_value: Dom<Element>,
160}
161
162#[derive(Clone, JSTraceable, MallocSizeOf)]
163#[cfg_attr(crown, crown::unrooted_must_root_lint::must_root)]
164#[non_exhaustive]
165enum ShadowTree {
166    Text(InputTypeTextShadowTree),
167    Color(InputTypeColorShadowTree),
168    // TODO: Add shadow trees for other input types (range etc) here
169}
170
171/// Create a div element with a text node within an UA Widget and either append or prepend it to
172/// the designated parent. This is used to create the text container for input elements.
173fn create_ua_widget_div_with_text_node(
174    document: &Document,
175    parent: &Node,
176    implemented_pseudo: PseudoElement,
177    as_first_child: bool,
178    can_gc: CanGc,
179) -> DomRoot<Element> {
180    let el = Element::create(
181        QualName::new(None, ns!(html), local_name!("div")),
182        None,
183        document,
184        ElementCreator::ScriptCreated,
185        CustomElementCreationMode::Asynchronous,
186        None,
187        can_gc,
188    );
189
190    parent
191        .upcast::<Node>()
192        .AppendChild(el.upcast::<Node>(), can_gc)
193        .unwrap();
194    el.upcast::<Node>()
195        .set_implemented_pseudo_element(implemented_pseudo);
196    let text_node = document.CreateTextNode("".into(), can_gc);
197
198    if !as_first_child {
199        el.upcast::<Node>()
200            .AppendChild(text_node.upcast::<Node>(), can_gc)
201            .unwrap();
202    } else {
203        el.upcast::<Node>()
204            .InsertBefore(
205                text_node.upcast::<Node>(),
206                el.upcast::<Node>().GetFirstChild().as_deref(),
207                can_gc,
208            )
209            .unwrap();
210    }
211    el
212}
213
214/// <https://html.spec.whatwg.org/multipage/#attr-input-type>
215#[derive(Clone, Copy, Debug, Default, JSTraceable, PartialEq, MallocSizeOf)]
216pub(crate) enum InputType {
217    /// <https://html.spec.whatwg.org/multipage/#button-state-(type=button)>
218    Button,
219
220    /// <https://html.spec.whatwg.org/multipage/#checkbox-state-(type=checkbox)>
221    Checkbox,
222
223    /// <https://html.spec.whatwg.org/multipage/#color-state-(type=color)>
224    Color,
225
226    /// <https://html.spec.whatwg.org/multipage/#date-state-(type=date)>
227    Date,
228
229    /// <https://html.spec.whatwg.org/multipage/#local-date-and-time-state-(type=datetime-local)>
230    DatetimeLocal,
231
232    /// <https://html.spec.whatwg.org/multipage/#email-state-(type=email)>
233    Email,
234
235    /// <https://html.spec.whatwg.org/multipage/#file-upload-state-(type=file)>
236    File,
237
238    /// <https://html.spec.whatwg.org/multipage/#hidden-state-(type=hidden)>
239    Hidden,
240
241    /// <https://html.spec.whatwg.org/multipage/#image-button-state-(type=image)>
242    Image,
243
244    /// <https://html.spec.whatwg.org/multipage/#month-state-(type=month)>
245    Month,
246
247    /// <https://html.spec.whatwg.org/multipage/#number-state-(type=number)>
248    Number,
249
250    /// <https://html.spec.whatwg.org/multipage/#password-state-(type=password)>
251    Password,
252
253    /// <https://html.spec.whatwg.org/multipage/#radio-button-state-(type=radio)>
254    Radio,
255
256    /// <https://html.spec.whatwg.org/multipage/#range-state-(type=range)>
257    Range,
258
259    /// <https://html.spec.whatwg.org/multipage/#reset-button-state-(type=reset)>
260    Reset,
261
262    /// <https://html.spec.whatwg.org/multipage/#text-(type=text)-state-and-search-state-(type=search)>
263    Search,
264
265    /// <https://html.spec.whatwg.org/multipage/#submit-button-state-(type=submit)>
266    Submit,
267
268    /// <https://html.spec.whatwg.org/multipage/#telephone-state-(type=tel)>
269    Tel,
270
271    /// <https://html.spec.whatwg.org/multipage/#text-(type=text)-state-and-search-state-(type=search)>
272    #[default]
273    Text,
274
275    /// <https://html.spec.whatwg.org/multipage/#time-state-(type=time)>
276    Time,
277
278    /// <https://html.spec.whatwg.org/multipage/#url-state-(type=url)>
279    Url,
280
281    /// <https://html.spec.whatwg.org/multipage/#week-state-(type=week)>
282    Week,
283}
284
285impl InputType {
286    /// Defines which input type that should perform like a text input,
287    /// specifically when it is interacting with JS. Note that Password
288    /// is not included here since it is handled slightly differently,
289    /// with placeholder characters shown rather than the underlying value.
290    pub(crate) fn is_textual(&self) -> bool {
291        matches!(
292            *self,
293            InputType::Date |
294                InputType::DatetimeLocal |
295                InputType::Email |
296                InputType::Hidden |
297                InputType::Month |
298                InputType::Number |
299                InputType::Range |
300                InputType::Search |
301                InputType::Tel |
302                InputType::Text |
303                InputType::Time |
304                InputType::Url |
305                InputType::Week
306        )
307    }
308
309    fn is_textual_or_password(&self) -> bool {
310        self.is_textual() || *self == InputType::Password
311    }
312
313    /// <https://html.spec.whatwg.org/multipage/#has-a-periodic-domain>
314    fn has_periodic_domain(&self) -> bool {
315        *self == InputType::Time
316    }
317
318    fn as_str(&self) -> &str {
319        match *self {
320            InputType::Button => "button",
321            InputType::Checkbox => "checkbox",
322            InputType::Color => "color",
323            InputType::Date => "date",
324            InputType::DatetimeLocal => "datetime-local",
325            InputType::Email => "email",
326            InputType::File => "file",
327            InputType::Hidden => "hidden",
328            InputType::Image => "image",
329            InputType::Month => "month",
330            InputType::Number => "number",
331            InputType::Password => "password",
332            InputType::Radio => "radio",
333            InputType::Range => "range",
334            InputType::Reset => "reset",
335            InputType::Search => "search",
336            InputType::Submit => "submit",
337            InputType::Tel => "tel",
338            InputType::Text => "text",
339            InputType::Time => "time",
340            InputType::Url => "url",
341            InputType::Week => "week",
342        }
343    }
344}
345
346impl TryFrom<InputType> for InputMethodType {
347    type Error = &'static str;
348
349    fn try_from(input_type: InputType) -> Result<Self, Self::Error> {
350        match input_type {
351            InputType::Color => Ok(InputMethodType::Color),
352            InputType::Date => Ok(InputMethodType::Date),
353            InputType::DatetimeLocal => Ok(InputMethodType::DatetimeLocal),
354            InputType::Email => Ok(InputMethodType::Email),
355            InputType::Month => Ok(InputMethodType::Month),
356            InputType::Number => Ok(InputMethodType::Number),
357            InputType::Password => Ok(InputMethodType::Password),
358            InputType::Search => Ok(InputMethodType::Search),
359            InputType::Tel => Ok(InputMethodType::Tel),
360            InputType::Text => Ok(InputMethodType::Text),
361            InputType::Time => Ok(InputMethodType::Time),
362            InputType::Url => Ok(InputMethodType::Url),
363            InputType::Week => Ok(InputMethodType::Week),
364            _ => Err("Input does not support IME."),
365        }
366    }
367}
368
369impl From<&Atom> for InputType {
370    fn from(value: &Atom) -> InputType {
371        match value.to_ascii_lowercase() {
372            atom!("button") => InputType::Button,
373            atom!("checkbox") => InputType::Checkbox,
374            atom!("color") => InputType::Color,
375            atom!("date") => InputType::Date,
376            atom!("datetime-local") => InputType::DatetimeLocal,
377            atom!("email") => InputType::Email,
378            atom!("file") => InputType::File,
379            atom!("hidden") => InputType::Hidden,
380            atom!("image") => InputType::Image,
381            atom!("month") => InputType::Month,
382            atom!("number") => InputType::Number,
383            atom!("password") => InputType::Password,
384            atom!("radio") => InputType::Radio,
385            atom!("range") => InputType::Range,
386            atom!("reset") => InputType::Reset,
387            atom!("search") => InputType::Search,
388            atom!("submit") => InputType::Submit,
389            atom!("tel") => InputType::Tel,
390            atom!("text") => InputType::Text,
391            atom!("time") => InputType::Time,
392            atom!("url") => InputType::Url,
393            atom!("week") => InputType::Week,
394            _ => Self::default(),
395        }
396    }
397}
398
399#[derive(Debug, PartialEq)]
400enum ValueMode {
401    /// <https://html.spec.whatwg.org/multipage/#dom-input-value-value>
402    Value,
403
404    /// <https://html.spec.whatwg.org/multipage/#dom-input-value-default>
405    Default,
406
407    /// <https://html.spec.whatwg.org/multipage/#dom-input-value-default-on>
408    DefaultOn,
409
410    /// <https://html.spec.whatwg.org/multipage/#dom-input-value-filename>
411    Filename,
412}
413
414#[derive(Debug, PartialEq)]
415enum StepDirection {
416    Up,
417    Down,
418}
419
420#[dom_struct]
421pub(crate) struct HTMLInputElement {
422    htmlelement: HTMLElement,
423    input_type: Cell<InputType>,
424
425    /// <https://html.spec.whatwg.org/multipage/#concept-input-checked-dirty-flag>
426    checked_changed: Cell<bool>,
427    placeholder: DomRefCell<DOMString>,
428    size: Cell<u32>,
429    maxlength: Cell<i32>,
430    minlength: Cell<i32>,
431    #[no_trace]
432    textinput: DomRefCell<TextInput<EmbedderClipboardProvider>>,
433    // https://html.spec.whatwg.org/multipage/#concept-input-value-dirty-flag
434    value_dirty: Cell<bool>,
435    // not specified explicitly, but implied by the fact that sanitization can't
436    // happen until after all of step/min/max/value content attributes have
437    // been added
438    sanitization_flag: Cell<bool>,
439
440    filelist: MutNullableDom<FileList>,
441    form_owner: MutNullableDom<HTMLFormElement>,
442    labels_node_list: MutNullableDom<NodeList>,
443    validity_state: MutNullableDom<ValidityState>,
444    shadow_tree: DomRefCell<Option<ShadowTree>>,
445    #[no_trace]
446    pending_webdriver_response: RefCell<Option<PendingWebDriverResponse>>,
447}
448
449#[derive(JSTraceable)]
450pub(crate) struct InputActivationState {
451    indeterminate: bool,
452    checked: bool,
453    checked_radio: Option<DomRoot<HTMLInputElement>>,
454    // In case the type changed
455    old_type: InputType,
456    // was_mutable is implied: pre-activation would return None if it wasn't
457}
458
459static DEFAULT_INPUT_SIZE: u32 = 20;
460static DEFAULT_MAX_LENGTH: i32 = -1;
461static DEFAULT_MIN_LENGTH: i32 = -1;
462
463#[allow(non_snake_case)]
464impl HTMLInputElement {
465    fn new_inherited(
466        local_name: LocalName,
467        prefix: Option<Prefix>,
468        document: &Document,
469    ) -> HTMLInputElement {
470        let embedder_sender = document
471            .window()
472            .as_global_scope()
473            .script_to_embedder_chan()
474            .clone();
475        HTMLInputElement {
476            htmlelement: HTMLElement::new_inherited_with_state(
477                ElementState::ENABLED | ElementState::READWRITE,
478                local_name,
479                prefix,
480                document,
481            ),
482            input_type: Cell::new(Default::default()),
483            placeholder: DomRefCell::new(DOMString::new()),
484            checked_changed: Cell::new(false),
485            maxlength: Cell::new(DEFAULT_MAX_LENGTH),
486            minlength: Cell::new(DEFAULT_MIN_LENGTH),
487            size: Cell::new(DEFAULT_INPUT_SIZE),
488            textinput: DomRefCell::new(TextInput::new(
489                Single,
490                DOMString::new(),
491                EmbedderClipboardProvider {
492                    embedder_sender,
493                    webview_id: document.webview_id(),
494                },
495                None,
496                None,
497                SelectionDirection::None,
498            )),
499            value_dirty: Cell::new(false),
500            sanitization_flag: Cell::new(true),
501            filelist: MutNullableDom::new(None),
502            form_owner: Default::default(),
503            labels_node_list: MutNullableDom::new(None),
504            validity_state: Default::default(),
505            shadow_tree: Default::default(),
506            pending_webdriver_response: Default::default(),
507        }
508    }
509
510    #[cfg_attr(crown, allow(crown::unrooted_must_root))]
511    pub(crate) fn new(
512        local_name: LocalName,
513        prefix: Option<Prefix>,
514        document: &Document,
515        proto: Option<HandleObject>,
516        can_gc: CanGc,
517    ) -> DomRoot<HTMLInputElement> {
518        Node::reflect_node_with_proto(
519            Box::new(HTMLInputElement::new_inherited(
520                local_name, prefix, document,
521            )),
522            document,
523            proto,
524            can_gc,
525        )
526    }
527
528    pub(crate) fn auto_directionality(&self) -> Option<String> {
529        match self.input_type() {
530            InputType::Text | InputType::Search | InputType::Url | InputType::Email => {
531                let value: String = self.Value().to_string();
532                Some(HTMLInputElement::directionality_from_value(&value))
533            },
534            _ => None,
535        }
536    }
537
538    pub(crate) fn directionality_from_value(value: &str) -> String {
539        if HTMLInputElement::is_first_strong_character_rtl(value) {
540            "rtl".to_owned()
541        } else {
542            "ltr".to_owned()
543        }
544    }
545
546    fn is_first_strong_character_rtl(value: &str) -> bool {
547        for ch in value.chars() {
548            return match bidi_class(ch) {
549                BidiClass::L => false,
550                BidiClass::AL => true,
551                BidiClass::R => true,
552                _ => continue,
553            };
554        }
555        false
556    }
557
558    // https://html.spec.whatwg.org/multipage/#dom-input-value
559    /// <https://html.spec.whatwg.org/multipage/#concept-input-apply>
560    fn value_mode(&self) -> ValueMode {
561        match self.input_type() {
562            InputType::Submit |
563            InputType::Reset |
564            InputType::Button |
565            InputType::Image |
566            InputType::Hidden => ValueMode::Default,
567
568            InputType::Checkbox | InputType::Radio => ValueMode::DefaultOn,
569
570            InputType::Color |
571            InputType::Date |
572            InputType::DatetimeLocal |
573            InputType::Email |
574            InputType::Month |
575            InputType::Number |
576            InputType::Password |
577            InputType::Range |
578            InputType::Search |
579            InputType::Tel |
580            InputType::Text |
581            InputType::Time |
582            InputType::Url |
583            InputType::Week => ValueMode::Value,
584
585            InputType::File => ValueMode::Filename,
586        }
587    }
588
589    #[inline]
590    pub(crate) fn input_type(&self) -> InputType {
591        self.input_type.get()
592    }
593
594    /// <https://w3c.github.io/webdriver/#dfn-non-typeable-form-control>
595    pub(crate) fn is_nontypeable(&self) -> bool {
596        matches!(
597            self.input_type(),
598            InputType::Button |
599                InputType::Checkbox |
600                InputType::Color |
601                InputType::File |
602                InputType::Hidden |
603                InputType::Image |
604                InputType::Radio |
605                InputType::Range |
606                InputType::Reset |
607                InputType::Submit
608        )
609    }
610
611    #[inline]
612    pub(crate) fn is_submit_button(&self) -> bool {
613        let input_type = self.input_type.get();
614        input_type == InputType::Submit || input_type == InputType::Image
615    }
616
617    pub(crate) fn disable_sanitization(&self) {
618        self.sanitization_flag.set(false);
619    }
620
621    pub(crate) fn enable_sanitization(&self) {
622        self.sanitization_flag.set(true);
623        let mut textinput = self.textinput.borrow_mut();
624        let mut value = textinput.single_line_content().clone();
625        self.sanitize_value(&mut value);
626        textinput.set_content(value);
627        self.upcast::<Node>().dirty(NodeDamage::Other);
628    }
629
630    fn does_minmaxlength_apply(&self) -> bool {
631        matches!(
632            self.input_type(),
633            InputType::Text |
634                InputType::Search |
635                InputType::Url |
636                InputType::Tel |
637                InputType::Email |
638                InputType::Password
639        )
640    }
641
642    fn does_pattern_apply(&self) -> bool {
643        matches!(
644            self.input_type(),
645            InputType::Text |
646                InputType::Search |
647                InputType::Url |
648                InputType::Tel |
649                InputType::Email |
650                InputType::Password
651        )
652    }
653
654    fn does_multiple_apply(&self) -> bool {
655        self.input_type() == InputType::Email
656    }
657
658    // valueAsNumber, step, min, and max all share the same set of
659    // input types they apply to
660    fn does_value_as_number_apply(&self) -> bool {
661        matches!(
662            self.input_type(),
663            InputType::Date |
664                InputType::Month |
665                InputType::Week |
666                InputType::Time |
667                InputType::DatetimeLocal |
668                InputType::Number |
669                InputType::Range
670        )
671    }
672
673    fn does_value_as_date_apply(&self) -> bool {
674        matches!(
675            self.input_type(),
676            InputType::Date | InputType::Month | InputType::Week | InputType::Time
677        )
678    }
679
680    /// <https://html.spec.whatwg.org/multipage#concept-input-step>
681    fn allowed_value_step(&self) -> Option<f64> {
682        // Step 1. If the attribute does not apply, then there is no allowed value step.
683        // NOTE: The attribute applies iff there is a default step
684        let default_step = self.default_step()?;
685
686        // Step 2. Otherwise, if the attribute is absent, then the allowed value step
687        // is the default step multiplied by the step scale factor.
688        let Some(attribute) = self
689            .upcast::<Element>()
690            .get_attribute(&ns!(), &local_name!("step"))
691        else {
692            return Some(default_step * self.step_scale_factor());
693        };
694
695        // Step 3. Otherwise, if the attribute's value is an ASCII case-insensitive match
696        // for the string "any", then there is no allowed value step.
697        if attribute.value().eq_ignore_ascii_case("any") {
698            return None;
699        }
700
701        // Step 4. Otherwise, if the rules for parsing floating-point number values, when they
702        // are applied to the attribute's value, return an error, zero, or a number less than zero,
703        // then the allowed value step is the default step multiplied by the step scale factor.
704        let Some(parsed_value) =
705            parse_floating_point_number(&attribute.value()).filter(|value| *value > 0.0)
706        else {
707            return Some(default_step * self.step_scale_factor());
708        };
709
710        // Step 5. Otherwise, the allowed value step is the number returned by the rules for parsing
711        // floating-point number values when they are applied to the attribute's value,
712        // multiplied by the step scale factor.
713        Some(parsed_value * self.step_scale_factor())
714    }
715
716    /// <https://html.spec.whatwg.org/multipage#concept-input-min>
717    fn minimum(&self) -> Option<f64> {
718        self.upcast::<Element>()
719            .get_attribute(&ns!(), &local_name!("min"))
720            .and_then(|attribute| self.convert_string_to_number(&attribute.value()))
721            .or_else(|| self.default_minimum())
722    }
723
724    /// <https://html.spec.whatwg.org/multipage#concept-input-max>
725    fn maximum(&self) -> Option<f64> {
726        self.upcast::<Element>()
727            .get_attribute(&ns!(), &local_name!("max"))
728            .and_then(|attribute| self.convert_string_to_number(&attribute.value()))
729            .or_else(|| self.default_maximum())
730    }
731
732    /// when allowed_value_step and minimum both exist, this is the smallest
733    /// value >= minimum that lies on an integer step
734    fn stepped_minimum(&self) -> Option<f64> {
735        match (self.minimum(), self.allowed_value_step()) {
736            (Some(min), Some(allowed_step)) => {
737                let step_base = self.step_base();
738                // how many steps is min from step_base?
739                let nsteps = (min - step_base) / allowed_step;
740                // count that many integer steps, rounded +, from step_base
741                Some(step_base + (allowed_step * nsteps.ceil()))
742            },
743            (_, _) => None,
744        }
745    }
746
747    /// when allowed_value_step and maximum both exist, this is the smallest
748    /// value <= maximum that lies on an integer step
749    fn stepped_maximum(&self) -> Option<f64> {
750        match (self.maximum(), self.allowed_value_step()) {
751            (Some(max), Some(allowed_step)) => {
752                let step_base = self.step_base();
753                // how many steps is max from step_base?
754                let nsteps = (max - step_base) / allowed_step;
755                // count that many integer steps, rounded -, from step_base
756                Some(step_base + (allowed_step * nsteps.floor()))
757            },
758            (_, _) => None,
759        }
760    }
761
762    /// <https://html.spec.whatwg.org/multipage#concept-input-min-default>
763    fn default_minimum(&self) -> Option<f64> {
764        match self.input_type() {
765            InputType::Range => Some(0.0),
766            _ => None,
767        }
768    }
769
770    /// <https://html.spec.whatwg.org/multipage#concept-input-max-default>
771    fn default_maximum(&self) -> Option<f64> {
772        match self.input_type() {
773            InputType::Range => Some(100.0),
774            _ => None,
775        }
776    }
777
778    /// <https://html.spec.whatwg.org/multipage#concept-input-value-default-range>
779    fn default_range_value(&self) -> f64 {
780        let min = self.minimum().unwrap_or(0.0);
781        let max = self.maximum().unwrap_or(100.0);
782        if max < min {
783            min
784        } else {
785            min + (max - min) * 0.5
786        }
787    }
788
789    /// <https://html.spec.whatwg.org/multipage#concept-input-step-default>
790    fn default_step(&self) -> Option<f64> {
791        match self.input_type() {
792            InputType::Date => Some(1.0),
793            InputType::Month => Some(1.0),
794            InputType::Week => Some(1.0),
795            InputType::Time => Some(60.0),
796            InputType::DatetimeLocal => Some(60.0),
797            InputType::Number => Some(1.0),
798            InputType::Range => Some(1.0),
799            _ => None,
800        }
801    }
802
803    /// <https://html.spec.whatwg.org/multipage#concept-input-step-scale>
804    fn step_scale_factor(&self) -> f64 {
805        match self.input_type() {
806            InputType::Date => 86400000.0,
807            InputType::Month => 1.0,
808            InputType::Week => 604800000.0,
809            InputType::Time => 1000.0,
810            InputType::DatetimeLocal => 1000.0,
811            InputType::Number => 1.0,
812            InputType::Range => 1.0,
813            _ => unreachable!(),
814        }
815    }
816
817    /// <https://html.spec.whatwg.org/multipage#concept-input-min-zero>
818    fn step_base(&self) -> f64 {
819        // Step 1. If the element has a min content attribute, and the result of applying
820        // the algorithm to convert a string to a number to the value of the min content attribute
821        // is not an error, then return that result.
822        if let Some(minimum) = self
823            .upcast::<Element>()
824            .get_attribute(&ns!(), &local_name!("min"))
825            .and_then(|attribute| self.convert_string_to_number(&attribute.value()))
826        {
827            return minimum;
828        }
829
830        // Step 2. If the element has a value content attribute, and the result of applying the
831        // algorithm to convert a string to a number to the value of the value content attribute
832        // is not an error, then return that result.
833        if let Some(value) = self
834            .upcast::<Element>()
835            .get_attribute(&ns!(), &local_name!("value"))
836            .and_then(|attribute| self.convert_string_to_number(&attribute.value()))
837        {
838            return value;
839        }
840
841        // Step 3. If a default step base is defined for this element given its type attribute's state, then return it.
842        if let Some(default_step_base) = self.default_step_base() {
843            return default_step_base;
844        }
845
846        // Step 4. Return zero.
847        0.0
848    }
849
850    /// <https://html.spec.whatwg.org/multipage#concept-input-step-default-base>
851    fn default_step_base(&self) -> Option<f64> {
852        match self.input_type() {
853            InputType::Week => Some(-259200000.0),
854            _ => None,
855        }
856    }
857
858    /// <https://html.spec.whatwg.org/multipage/#dom-input-stepup>
859    ///
860    /// <https://html.spec.whatwg.org/multipage/#dom-input-stepdown>
861    fn step_up_or_down(&self, n: i32, dir: StepDirection, can_gc: CanGc) -> ErrorResult {
862        // Step 1. If the stepDown() and stepUp() methods do not apply, as defined for the
863        // input element's type attribute's current state, then throw an "InvalidStateError" DOMException.
864        if !self.does_value_as_number_apply() {
865            return Err(Error::InvalidState(None));
866        }
867        let step_base = self.step_base();
868
869        // Step 2. If the element has no allowed value step, then throw an "InvalidStateError" DOMException.
870        let Some(allowed_value_step) = self.allowed_value_step() else {
871            return Err(Error::InvalidState(None));
872        };
873
874        // Step 3. If the element has a minimum and a maximum and the minimum is greater than the maximum,
875        // then return.
876        let minimum = self.minimum();
877        let maximum = self.maximum();
878        if let (Some(min), Some(max)) = (minimum, maximum) {
879            if min > max {
880                return Ok(());
881            }
882
883            // Step 4. If the element has a minimum and a maximum and there is no value greater than or equal to the
884            // element's minimum and less than or equal to the element's maximum that, when subtracted from the step
885            // base, is an integral multiple of the allowed value step, then return.
886            if let Some(stepped_minimum) = self.stepped_minimum() {
887                if stepped_minimum > max {
888                    return Ok(());
889                }
890            }
891        }
892
893        // Step 5. If applying the algorithm to convert a string to a number to the string given
894        // by the element's value does not result in an error, then let value be the result of
895        // that algorithm. Otherwise, let value be zero.
896        let mut value: f64 = self
897            .convert_string_to_number(&self.Value().str())
898            .unwrap_or(0.0);
899
900        // Step 6. Let valueBeforeStepping be value.
901        let valueBeforeStepping = value;
902
903        // Step 7. If value subtracted from the step base is not an integral multiple of the allowed value step,
904        // then set value to the nearest value that, when subtracted from the step base, is an integral multiple
905        // of the allowed value step, and that is less than value if the method invoked was the stepDown() method,
906        // and more than value otherwise.
907        if (value - step_base) % allowed_value_step != 0.0 {
908            value = match dir {
909                StepDirection::Down =>
910                // step down a fractional step to be on a step multiple
911                {
912                    let intervals_from_base = ((value - step_base) / allowed_value_step).floor();
913                    intervals_from_base * allowed_value_step + step_base
914                },
915                StepDirection::Up =>
916                // step up a fractional step to be on a step multiple
917                {
918                    let intervals_from_base = ((value - step_base) / allowed_value_step).ceil();
919                    intervals_from_base * allowed_value_step + step_base
920                },
921            };
922        }
923        // Otherwise (value subtracted from the step base is an integral multiple of the allowed value step):
924        else {
925            // Step 7.1 Let n be the argument.
926            // Step 7.2 Let delta be the allowed value step multiplied by n.
927            // Step 7.3 If the method invoked was the stepDown() method, negate delta.
928            // Step 7.4 Let value be the result of adding delta to value.
929            value += match dir {
930                StepDirection::Down => -f64::from(n) * allowed_value_step,
931                StepDirection::Up => f64::from(n) * allowed_value_step,
932            };
933        }
934
935        // Step 8. If the element has a minimum, and value is less than that minimum, then set value to the smallest
936        // value that, when subtracted from the step base, is an integral multiple of the allowed value step, and that
937        // is more than or equal to that minimum.
938        if let Some(min) = minimum {
939            if value < min {
940                value = self.stepped_minimum().unwrap_or(value);
941            }
942        }
943
944        // Step 9. If the element has a maximum, and value is greater than that maximum, then set value to the largest
945        // value that, when subtracted from the step base, is an integral multiple of the allowed value step, and that
946        // is less than or equal to that maximum.
947        if let Some(max) = maximum {
948            if value > max {
949                value = self.stepped_maximum().unwrap_or(value);
950            }
951        }
952
953        // Step 10. If either the method invoked was the stepDown() method and value is greater than
954        // valueBeforeStepping, or the method invoked was the stepUp() method and value is less than
955        // valueBeforeStepping, then return.
956        match dir {
957            StepDirection::Down => {
958                if value > valueBeforeStepping {
959                    return Ok(());
960                }
961            },
962            StepDirection::Up => {
963                if value < valueBeforeStepping {
964                    return Ok(());
965                }
966            },
967        }
968
969        // Step 11. Let value as string be the result of running the algorithm to convert a number to a string,
970        // as defined for the input element's type attribute's current state, on value.
971        // Step 12. Set the value of the element to value as string.
972        self.SetValueAsNumber(value, can_gc)
973    }
974
975    /// <https://html.spec.whatwg.org/multipage/#concept-input-list>
976    fn suggestions_source_element(&self) -> Option<DomRoot<HTMLDataListElement>> {
977        let list_string = self
978            .upcast::<Element>()
979            .get_string_attribute(&local_name!("list"));
980        if list_string.is_empty() {
981            return None;
982        }
983        let ancestor = self
984            .upcast::<Node>()
985            .GetRootNode(&GetRootNodeOptions::empty());
986        let first_with_id = &ancestor
987            .traverse_preorder(ShadowIncluding::No)
988            .find(|node| {
989                node.downcast::<Element>()
990                    .is_some_and(|e| e.Id() == list_string)
991            });
992        first_with_id
993            .as_ref()
994            .and_then(|el| el.downcast::<HTMLDataListElement>())
995            .map(DomRoot::from_ref)
996    }
997
998    /// <https://html.spec.whatwg.org/multipage/#suffering-from-being-missing>
999    fn suffers_from_being_missing(&self, value: &DOMString) -> bool {
1000        match self.input_type() {
1001            // https://html.spec.whatwg.org/multipage/#checkbox-state-(type%3Dcheckbox)%3Asuffering-from-being-missing
1002            InputType::Checkbox => self.Required() && !self.Checked(),
1003            // https://html.spec.whatwg.org/multipage/#radio-button-state-(type%3Dradio)%3Asuffering-from-being-missing
1004            InputType::Radio => {
1005                if self.radio_group_name().is_none() {
1006                    return false;
1007                }
1008                let mut is_required = self.Required();
1009                let mut is_checked = self.Checked();
1010                let root = self
1011                    .upcast::<Node>()
1012                    .GetRootNode(&GetRootNodeOptions::empty());
1013                let form = self.form_owner();
1014                for other in radio_group_iter(
1015                    self,
1016                    self.radio_group_name().as_ref(),
1017                    form.as_deref(),
1018                    &root,
1019                ) {
1020                    is_required = is_required || other.Required();
1021                    is_checked = is_checked || other.Checked();
1022                }
1023                is_required && !is_checked
1024            },
1025            // https://html.spec.whatwg.org/multipage/#file-upload-state-(type%3Dfile)%3Asuffering-from-being-missing
1026            InputType::File => {
1027                self.Required() && self.filelist.get().is_none_or(|files| files.Length() == 0)
1028            },
1029            // https://html.spec.whatwg.org/multipage/#the-required-attribute%3Asuffering-from-being-missing
1030            _ => {
1031                self.Required() &&
1032                    self.value_mode() == ValueMode::Value &&
1033                    self.is_mutable() &&
1034                    value.is_empty()
1035            },
1036        }
1037    }
1038
1039    /// <https://html.spec.whatwg.org/multipage/#suffering-from-a-type-mismatch>
1040    fn suffers_from_type_mismatch(&self, value: &DOMString) -> bool {
1041        if value.is_empty() {
1042            return false;
1043        }
1044
1045        match self.input_type() {
1046            // https://html.spec.whatwg.org/multipage/#url-state-(type%3Durl)%3Asuffering-from-a-type-mismatch
1047            InputType::Url => Url::parse(&value.str()).is_err(),
1048            // https://html.spec.whatwg.org/multipage/#e-mail-state-(type%3Demail)%3Asuffering-from-a-type-mismatch
1049            // https://html.spec.whatwg.org/multipage/#e-mail-state-(type%3Demail)%3Asuffering-from-a-type-mismatch-2
1050            InputType::Email => {
1051                if self.Multiple() {
1052                    !split_commas(&value.str()).all(|string| string.is_valid_email_address_string())
1053                } else {
1054                    !value.str().is_valid_email_address_string()
1055                }
1056            },
1057            // Other input types don't suffer from type mismatch
1058            _ => false,
1059        }
1060    }
1061
1062    /// <https://html.spec.whatwg.org/multipage/#suffering-from-a-pattern-mismatch>
1063    fn suffers_from_pattern_mismatch(&self, value: &DOMString, can_gc: CanGc) -> bool {
1064        // https://html.spec.whatwg.org/multipage/#the-pattern-attribute%3Asuffering-from-a-pattern-mismatch
1065        // https://html.spec.whatwg.org/multipage/#the-pattern-attribute%3Asuffering-from-a-pattern-mismatch-2
1066        let pattern_str = self.Pattern();
1067        if value.is_empty() || pattern_str.is_empty() || !self.does_pattern_apply() {
1068            return false;
1069        }
1070
1071        // Rust's regex is not compatible, we need to use mozjs RegExp.
1072        let cx = GlobalScope::get_cx();
1073        let _ac = enter_realm(self);
1074        rooted!(in(*cx) let mut pattern = ptr::null_mut::<JSObject>());
1075
1076        if compile_pattern(cx, &pattern_str.str(), pattern.handle_mut(), can_gc) {
1077            if self.Multiple() && self.does_multiple_apply() {
1078                !split_commas(&value.str())
1079                    .all(|s| matches_js_regex(cx, pattern.handle(), s, can_gc).unwrap_or(true))
1080            } else {
1081                !matches_js_regex(cx, pattern.handle(), &value.str(), can_gc).unwrap_or(true)
1082            }
1083        } else {
1084            // Element doesn't suffer from pattern mismatch if pattern is invalid.
1085            false
1086        }
1087    }
1088
1089    /// <https://html.spec.whatwg.org/multipage/#suffering-from-bad-input>
1090    fn suffers_from_bad_input(&self, value: &DOMString) -> bool {
1091        if value.is_empty() {
1092            return false;
1093        }
1094
1095        match self.input_type() {
1096            // https://html.spec.whatwg.org/multipage/#e-mail-state-(type%3Demail)%3Asuffering-from-bad-input
1097            // https://html.spec.whatwg.org/multipage/#e-mail-state-(type%3Demail)%3Asuffering-from-bad-input-2
1098            InputType::Email => {
1099                // TODO: Check for input that cannot be converted to punycode.
1100                // Currently we don't support conversion of email values to punycode
1101                // so always return false.
1102                false
1103            },
1104            // https://html.spec.whatwg.org/multipage/#date-state-(type%3Ddate)%3Asuffering-from-bad-input
1105            InputType::Date => !value.str().is_valid_date_string(),
1106            // https://html.spec.whatwg.org/multipage/#month-state-(type%3Dmonth)%3Asuffering-from-bad-input
1107            InputType::Month => !value.str().is_valid_month_string(),
1108            // https://html.spec.whatwg.org/multipage/#week-state-(type%3Dweek)%3Asuffering-from-bad-input
1109            InputType::Week => !value.str().is_valid_week_string(),
1110            // https://html.spec.whatwg.org/multipage/#time-state-(type%3Dtime)%3Asuffering-from-bad-input
1111            InputType::Time => !value.str().is_valid_time_string(),
1112            // https://html.spec.whatwg.org/multipage/#local-date-and-time-state-(type%3Ddatetime-local)%3Asuffering-from-bad-input
1113            InputType::DatetimeLocal => !value.str().is_valid_local_date_time_string(),
1114            // https://html.spec.whatwg.org/multipage/#number-state-(type%3Dnumber)%3Asuffering-from-bad-input
1115            // https://html.spec.whatwg.org/multipage/#range-state-(type%3Drange)%3Asuffering-from-bad-input
1116            InputType::Number | InputType::Range => !value.is_valid_floating_point_number_string(),
1117            // https://html.spec.whatwg.org/multipage/#color-state-(type%3Dcolor)%3Asuffering-from-bad-input
1118            InputType::Color => !value.str().is_valid_simple_color_string(),
1119            // Other input types don't suffer from bad input
1120            _ => false,
1121        }
1122    }
1123
1124    // https://html.spec.whatwg.org/multipage/#suffering-from-being-too-long
1125    /// <https://html.spec.whatwg.org/multipage/#suffering-from-being-too-short>
1126    fn suffers_from_length_issues(&self, value: &DOMString) -> ValidationFlags {
1127        // https://html.spec.whatwg.org/multipage/#limiting-user-input-length%3A-the-maxlength-attribute%3Asuffering-from-being-too-long
1128        // https://html.spec.whatwg.org/multipage/#setting-minimum-input-length-requirements%3A-the-minlength-attribute%3Asuffering-from-being-too-short
1129        let value_dirty = self.value_dirty.get();
1130        let textinput = self.textinput.borrow();
1131        let edit_by_user = !textinput.was_last_change_by_set_content();
1132
1133        if value.is_empty() || !value_dirty || !edit_by_user || !self.does_minmaxlength_apply() {
1134            return ValidationFlags::empty();
1135        }
1136
1137        let mut failed_flags = ValidationFlags::empty();
1138        let UTF16CodeUnits(value_len) = textinput.utf16_len();
1139        let min_length = self.MinLength();
1140        let max_length = self.MaxLength();
1141
1142        if min_length != DEFAULT_MIN_LENGTH && value_len < (min_length as usize) {
1143            failed_flags.insert(ValidationFlags::TOO_SHORT);
1144        }
1145
1146        if max_length != DEFAULT_MAX_LENGTH && value_len > (max_length as usize) {
1147            failed_flags.insert(ValidationFlags::TOO_LONG);
1148        }
1149
1150        failed_flags
1151    }
1152
1153    /// * <https://html.spec.whatwg.org/multipage/#suffering-from-an-underflow>
1154    /// * <https://html.spec.whatwg.org/multipage/#suffering-from-an-overflow>
1155    /// * <https://html.spec.whatwg.org/multipage/#suffering-from-a-step-mismatch>
1156    fn suffers_from_range_issues(&self, value: &DOMString) -> ValidationFlags {
1157        if value.is_empty() || !self.does_value_as_number_apply() {
1158            return ValidationFlags::empty();
1159        }
1160
1161        let Some(value_as_number) = self.convert_string_to_number(&value.str()) else {
1162            return ValidationFlags::empty();
1163        };
1164
1165        let mut failed_flags = ValidationFlags::empty();
1166        let min_value = self.minimum();
1167        let max_value = self.maximum();
1168
1169        // https://html.spec.whatwg.org/multipage/#has-a-reversed-range
1170        let has_reversed_range = match (min_value, max_value) {
1171            (Some(min), Some(max)) => self.input_type().has_periodic_domain() && min > max,
1172            _ => false,
1173        };
1174
1175        if has_reversed_range {
1176            // https://html.spec.whatwg.org/multipage/#the-min-and-max-attributes:has-a-reversed-range-3
1177            if value_as_number > max_value.unwrap() && value_as_number < min_value.unwrap() {
1178                failed_flags.insert(ValidationFlags::RANGE_UNDERFLOW);
1179                failed_flags.insert(ValidationFlags::RANGE_OVERFLOW);
1180            }
1181        } else {
1182            // https://html.spec.whatwg.org/multipage/#the-min-and-max-attributes%3Asuffering-from-an-underflow-2
1183            if let Some(min_value) = min_value {
1184                if value_as_number < min_value {
1185                    failed_flags.insert(ValidationFlags::RANGE_UNDERFLOW);
1186                }
1187            }
1188            // https://html.spec.whatwg.org/multipage/#the-min-and-max-attributes%3Asuffering-from-an-overflow-2
1189            if let Some(max_value) = max_value {
1190                if value_as_number > max_value {
1191                    failed_flags.insert(ValidationFlags::RANGE_OVERFLOW);
1192                }
1193            }
1194        }
1195
1196        // https://html.spec.whatwg.org/multipage/#the-step-attribute%3Asuffering-from-a-step-mismatch
1197        if let Some(step) = self.allowed_value_step() {
1198            // TODO: Spec has some issues here, see https://github.com/whatwg/html/issues/5207.
1199            // Chrome and Firefox parse values as decimals to get exact results,
1200            // we probably should too.
1201            let diff = (self.step_base() - value_as_number) % step / value_as_number;
1202            if diff.abs() > 1e-12 {
1203                failed_flags.insert(ValidationFlags::STEP_MISMATCH);
1204            }
1205        }
1206
1207        failed_flags
1208    }
1209
1210    /// Return a reference to the ShadowRoot that this element is a host of,
1211    /// or create one if none exists.
1212    // FIXME(stevennovaryo): We should encapsulate the logics for the initiation and maintainance of
1213    //                       form UA widget inside another struct.
1214    fn shadow_root(&self, can_gc: CanGc) -> DomRoot<ShadowRoot> {
1215        self.upcast::<Element>()
1216            .shadow_root()
1217            .unwrap_or_else(|| self.upcast::<Element>().attach_ua_shadow_root(true, can_gc))
1218    }
1219
1220    fn create_text_shadow_tree(&self, can_gc: CanGc) {
1221        let document = self.owner_document();
1222        let shadow_root = self.shadow_root(can_gc);
1223        Node::replace_all(None, shadow_root.upcast::<Node>(), can_gc);
1224
1225        let inner_container = Element::create(
1226            QualName::new(None, ns!(html), local_name!("div")),
1227            None,
1228            &document,
1229            ElementCreator::ScriptCreated,
1230            CustomElementCreationMode::Asynchronous,
1231            None,
1232            can_gc,
1233        );
1234        shadow_root
1235            .upcast::<Node>()
1236            .AppendChild(inner_container.upcast::<Node>(), can_gc)
1237            .unwrap();
1238        inner_container
1239            .upcast::<Node>()
1240            .set_implemented_pseudo_element(PseudoElement::ServoTextControlInnerContainer);
1241
1242        let text_container = create_ua_widget_div_with_text_node(
1243            &document,
1244            inner_container.upcast::<Node>(),
1245            PseudoElement::ServoTextControlInnerEditor,
1246            false,
1247            can_gc,
1248        );
1249
1250        let _ = self
1251            .shadow_tree
1252            .borrow_mut()
1253            .insert(ShadowTree::Text(InputTypeTextShadowTree {
1254                inner_container: inner_container.as_traced(),
1255                text_container: text_container.as_traced(),
1256                placeholder_container: DomRefCell::new(None),
1257            }));
1258    }
1259
1260    fn text_shadow_tree(&self, can_gc: CanGc) -> Ref<'_, InputTypeTextShadowTree> {
1261        let has_text_shadow_tree = self
1262            .shadow_tree
1263            .borrow()
1264            .as_ref()
1265            .is_some_and(|shadow_tree| matches!(shadow_tree, ShadowTree::Text(_)));
1266        if !has_text_shadow_tree {
1267            self.create_text_shadow_tree(can_gc);
1268        }
1269
1270        let shadow_tree = self.shadow_tree.borrow();
1271        Ref::filter_map(shadow_tree, |shadow_tree| {
1272            let shadow_tree = shadow_tree.as_ref()?;
1273            match shadow_tree {
1274                ShadowTree::Text(text_tree) => Some(text_tree),
1275                _ => None,
1276            }
1277        })
1278        .ok()
1279        .expect("UA shadow tree was not created")
1280    }
1281
1282    fn create_color_shadow_tree(&self, can_gc: CanGc) {
1283        let document = self.owner_document();
1284        let shadow_root = self.shadow_root(can_gc);
1285        Node::replace_all(None, shadow_root.upcast::<Node>(), can_gc);
1286
1287        let color_value = Element::create(
1288            QualName::new(None, ns!(html), local_name!("div")),
1289            None,
1290            &document,
1291            ElementCreator::ScriptCreated,
1292            CustomElementCreationMode::Asynchronous,
1293            None,
1294            can_gc,
1295        );
1296        shadow_root
1297            .upcast::<Node>()
1298            .AppendChild(color_value.upcast::<Node>(), can_gc)
1299            .unwrap();
1300        color_value
1301            .upcast::<Node>()
1302            .set_implemented_pseudo_element(PseudoElement::ColorSwatch);
1303
1304        let _ = self
1305            .shadow_tree
1306            .borrow_mut()
1307            .insert(ShadowTree::Color(InputTypeColorShadowTree {
1308                color_value: color_value.as_traced(),
1309            }));
1310    }
1311
1312    /// Get a handle to the shadow tree for this input, assuming it's [InputType] is `Color`.
1313    ///
1314    /// If the input is not currently a shadow host, a new shadow tree will be created.
1315    ///
1316    /// If the input is a shadow host for a different kind of shadow tree then the old
1317    /// tree will be removed and a new one will be created.
1318    fn color_shadow_tree(&self, can_gc: CanGc) -> Ref<'_, InputTypeColorShadowTree> {
1319        let has_color_shadow_tree = self
1320            .shadow_tree
1321            .borrow()
1322            .as_ref()
1323            .is_some_and(|shadow_tree| matches!(shadow_tree, ShadowTree::Color(_)));
1324        if !has_color_shadow_tree {
1325            self.create_color_shadow_tree(can_gc);
1326        }
1327
1328        let shadow_tree = self.shadow_tree.borrow();
1329        Ref::filter_map(shadow_tree, |shadow_tree| {
1330            let shadow_tree = shadow_tree.as_ref()?;
1331            match shadow_tree {
1332                ShadowTree::Color(color_tree) => Some(color_tree),
1333                _ => None,
1334            }
1335        })
1336        .ok()
1337        .expect("UA shadow tree was not created")
1338    }
1339
1340    /// Should this input type render as a basic text UA widget.
1341    // TODO(#38251): Ideally, the most basic shadow dom should cover only `text`, `password`, `url`, `tel`,
1342    //               and `email`. But we are leaving the others textual inputs here while tackling them one
1343    //               by one.
1344    pub(crate) fn is_textual_widget(&self) -> bool {
1345        matches!(
1346            self.input_type(),
1347            InputType::Date |
1348                InputType::DatetimeLocal |
1349                InputType::Email |
1350                InputType::Month |
1351                InputType::Number |
1352                InputType::Password |
1353                InputType::Range |
1354                InputType::Search |
1355                InputType::Tel |
1356                InputType::Text |
1357                InputType::Time |
1358                InputType::Url |
1359                InputType::Week
1360        )
1361    }
1362
1363    /// Construct the most basic shadow tree structure for textual input.
1364    /// TODO(stevennovaryo): The rest of textual input shadow dom structure should act like an
1365    ///                       exstension to this one.
1366    fn update_textual_shadow_tree(&self, can_gc: CanGc) {
1367        // Should only do this for textual input widget.
1368        debug_assert!(self.is_textual_widget());
1369
1370        let text_shadow_tree = self.text_shadow_tree(can_gc);
1371        let value = self.Value();
1372
1373        // The addition of zero-width space here forces the text input to have an inline formatting
1374        // context that might otherwise be trimmed if there's no text. This is important to ensure
1375        // that the input element is at least as tall as the line gap of the caret:
1376        // <https://drafts.csswg.org/css-ui/#element-with-default-preferred-size>.
1377        //
1378        // This is also used to ensure that the caret will still be rendered when the input is empty.
1379        // TODO: Could append `<br>` element to prevent collapses and avoid this hack, but we would
1380        //       need to fix the rendering of caret beforehand.
1381        let value_text = match (value.is_empty(), self.input_type()) {
1382            // For a password input, we replace all of the character with its replacement char.
1383            (false, InputType::Password) => value
1384                .str()
1385                .chars()
1386                .map(|_| PASSWORD_REPLACEMENT_CHAR)
1387                .collect::<String>()
1388                .into(),
1389            (false, _) => value,
1390            (true, _) => "\u{200B}".into(),
1391        };
1392
1393        // We are finding and updating the CharacterData child directly to optimize the update.
1394        text_shadow_tree
1395            .text_container
1396            .upcast::<Node>()
1397            .GetFirstChild()
1398            .expect("UA widget text container without child")
1399            .downcast::<CharacterData>()
1400            .expect("First child is not a CharacterData node")
1401            .SetData(value_text);
1402    }
1403
1404    fn update_color_shadow_tree(&self, can_gc: CanGc) {
1405        // Should only do this for `type=color` input.
1406        debug_assert_eq!(self.input_type(), InputType::Color);
1407
1408        let color_shadow_tree = self.color_shadow_tree(can_gc);
1409        let mut value = self.Value();
1410        if value.str().is_valid_simple_color_string() {
1411            value.make_ascii_lowercase();
1412        } else {
1413            value = DOMString::from("#000000");
1414        }
1415        let style = format!("background-color: {value}");
1416        color_shadow_tree.color_value.set_string_attribute(
1417            &local_name!("style"),
1418            style.into(),
1419            can_gc,
1420        );
1421    }
1422
1423    fn update_shadow_tree(&self, can_gc: CanGc) {
1424        match self.input_type() {
1425            _ if self.is_textual_widget() => self.update_textual_shadow_tree(can_gc),
1426            InputType::Color => self.update_color_shadow_tree(can_gc),
1427            _ => {},
1428        }
1429    }
1430
1431    fn may_have_embedder_control(&self) -> bool {
1432        let el = self.upcast::<Element>();
1433        self.input_type() == InputType::Color && !el.disabled_state()
1434    }
1435
1436    fn handle_key_reaction(&self, action: KeyReaction, event: &Event, can_gc: CanGc) {
1437        match action {
1438            KeyReaction::TriggerDefaultAction => {
1439                self.implicit_submission(can_gc);
1440            },
1441            KeyReaction::DispatchInput(text, is_composing, input_type) => {
1442                if event.IsTrusted() {
1443                    self.textinput.borrow().queue_input_event(
1444                        self.upcast(),
1445                        text,
1446                        is_composing,
1447                        input_type,
1448                    );
1449                }
1450                self.value_dirty.set(true);
1451                self.update_placeholder_shown_state();
1452                self.upcast::<Node>().dirty(NodeDamage::Other);
1453                event.mark_as_handled();
1454            },
1455            KeyReaction::RedrawSelection => {
1456                self.upcast::<Node>().dirty(NodeDamage::Other);
1457                event.mark_as_handled();
1458            },
1459            KeyReaction::Nothing => (),
1460        }
1461    }
1462}
1463
1464pub(crate) trait LayoutHTMLInputElementHelpers<'dom> {
1465    /// Return a string that represents the contents of the element for layout.
1466    fn value_for_layout(self) -> Cow<'dom, str>;
1467    fn size_for_layout(self) -> u32;
1468    fn selection_for_layout(self) -> Option<Range<usize>>;
1469}
1470
1471#[expect(unsafe_code)]
1472impl<'dom> LayoutDom<'dom, HTMLInputElement> {
1473    fn get_raw_textinput_value(self) -> DOMString {
1474        unsafe {
1475            self.unsafe_get()
1476                .textinput
1477                .borrow_for_layout()
1478                .get_content()
1479        }
1480    }
1481    fn get_filelist(self) -> Option<LayoutDom<'dom, FileList>> {
1482        unsafe { self.unsafe_get().filelist.get_inner_as_layout() }
1483    }
1484
1485    fn input_type(self) -> InputType {
1486        self.unsafe_get().input_type.get()
1487    }
1488
1489    fn textinput_sorted_selection_offsets_range(self) -> Range<UTF8Bytes> {
1490        unsafe {
1491            self.unsafe_get()
1492                .textinput
1493                .borrow_for_layout()
1494                .sorted_selection_offsets_range()
1495        }
1496    }
1497}
1498
1499impl<'dom> LayoutHTMLInputElementHelpers<'dom> for LayoutDom<'dom, HTMLInputElement> {
1500    /// In the past, we are handling the display of <input> element inside the dom tree traversal.
1501    /// With the introduction of shadow DOM, these implementations will be replaced one by one
1502    /// and these will be obselete,
1503    fn value_for_layout(self) -> Cow<'dom, str> {
1504        fn get_raw_attr_value<'dom>(
1505            input: LayoutDom<'dom, HTMLInputElement>,
1506            default: &'static str,
1507        ) -> Cow<'dom, str> {
1508            input
1509                .upcast::<Element>()
1510                .get_attr_val_for_layout(&ns!(), &local_name!("value"))
1511                .unwrap_or(default)
1512                .into()
1513        }
1514
1515        match self.input_type() {
1516            InputType::Checkbox | InputType::Radio | InputType::Image | InputType::Hidden => {
1517                "".into()
1518            },
1519            InputType::File => {
1520                let filelist = self.get_filelist();
1521                match filelist {
1522                    Some(filelist) => {
1523                        let length = filelist.len();
1524                        if length == 0 {
1525                            DEFAULT_FILE_INPUT_VALUE.into()
1526                        } else if length == 1 {
1527                            match filelist.file_for_layout(0) {
1528                                Some(file) => file.name().to_string().into(),
1529                                None => DEFAULT_FILE_INPUT_VALUE.into(),
1530                            }
1531                        } else {
1532                            format!("{} files", length).into()
1533                        }
1534                    },
1535                    None => DEFAULT_FILE_INPUT_VALUE.into(),
1536                }
1537            },
1538            InputType::Button => get_raw_attr_value(self, ""),
1539            InputType::Submit => get_raw_attr_value(self, DEFAULT_SUBMIT_VALUE),
1540            InputType::Reset => get_raw_attr_value(self, DEFAULT_RESET_VALUE),
1541            // FIXME(#22728): input `type=range` has yet to be implemented.
1542            InputType::Range => "".into(),
1543            _ => {
1544                unreachable!("Input with shadow tree should use internal shadow tree for layout");
1545            },
1546        }
1547    }
1548
1549    /// Textual input, specifically text entry and domain specific input has
1550    /// a default preferred size.
1551    ///
1552    /// <https://html.spec.whatwg.org/multipage/#the-input-element-as-a-text-entry-widget>
1553    /// <https://html.spec.whatwg.org/multipage/#the-input-element-as-domain-specific-widgets>
1554    // FIXME(stevennovaryo): Implement the calculation of default preferred size
1555    //                       for domain specific input widgets correctly.
1556    // FIXME(#4378): Implement the calculation of average character width for
1557    //               textual input correctly.
1558    fn size_for_layout(self) -> u32 {
1559        self.unsafe_get().size.get()
1560    }
1561
1562    fn selection_for_layout(self) -> Option<Range<usize>> {
1563        if !self.upcast::<Element>().focus_state() {
1564            return None;
1565        }
1566
1567        let sorted_selection_offsets_range = self.textinput_sorted_selection_offsets_range();
1568
1569        match self.input_type() {
1570            InputType::Password => {
1571                let text = self.get_raw_textinput_value();
1572                let sel = UTF8Bytes::unwrap_range(sorted_selection_offsets_range);
1573
1574                // Translate indices from the raw value to indices in the replacement value.
1575                let char_start = text.str()[..sel.start].chars().count();
1576                let char_end = char_start + text.str()[sel].chars().count();
1577
1578                let bytes_per_char = PASSWORD_REPLACEMENT_CHAR.len_utf8();
1579                Some(char_start * bytes_per_char..char_end * bytes_per_char)
1580            },
1581            input_type if input_type.is_textual() => {
1582                Some(UTF8Bytes::unwrap_range(sorted_selection_offsets_range))
1583            },
1584            _ => None,
1585        }
1586    }
1587}
1588
1589impl TextControlElement for HTMLInputElement {
1590    /// <https://html.spec.whatwg.org/multipage/#concept-input-apply>
1591    fn selection_api_applies(&self) -> bool {
1592        matches!(
1593            self.input_type(),
1594            InputType::Text |
1595                InputType::Search |
1596                InputType::Url |
1597                InputType::Tel |
1598                InputType::Password
1599        )
1600    }
1601
1602    // https://html.spec.whatwg.org/multipage/#concept-input-apply
1603    //
1604    // Defines input types to which the select() IDL method applies. These are a superset of the
1605    // types for which selection_api_applies() returns true.
1606    //
1607    // Types omitted which could theoretically be included if they were
1608    // rendered as a text control: file
1609    fn has_selectable_text(&self) -> bool {
1610        match self.input_type() {
1611            InputType::Text |
1612            InputType::Search |
1613            InputType::Url |
1614            InputType::Tel |
1615            InputType::Password |
1616            InputType::Email |
1617            InputType::Date |
1618            InputType::Month |
1619            InputType::Week |
1620            InputType::Time |
1621            InputType::DatetimeLocal |
1622            InputType::Number => !self.textinput.borrow().get_content().is_empty(),
1623
1624            InputType::Button |
1625            InputType::Checkbox |
1626            InputType::Color |
1627            InputType::File |
1628            InputType::Hidden |
1629            InputType::Image |
1630            InputType::Radio |
1631            InputType::Range |
1632            InputType::Reset |
1633            InputType::Submit => false,
1634        }
1635    }
1636
1637    fn has_selection(&self) -> bool {
1638        self.textinput.borrow().has_selection()
1639    }
1640
1641    fn set_dirty_value_flag(&self, value: bool) {
1642        self.value_dirty.set(value)
1643    }
1644
1645    fn select_all(&self) {
1646        self.textinput.borrow_mut().select_all();
1647        self.upcast::<Node>().dirty(NodeDamage::Other);
1648    }
1649}
1650
1651#[allow(non_snake_case)]
1652impl HTMLInputElementMethods<crate::DomTypeHolder> for HTMLInputElement {
1653    // https://html.spec.whatwg.org/multipage/#dom-input-accept
1654    make_getter!(Accept, "accept");
1655
1656    // https://html.spec.whatwg.org/multipage/#dom-input-accept
1657    make_setter!(SetAccept, "accept");
1658
1659    // https://html.spec.whatwg.org/multipage/#dom-input-alt
1660    make_getter!(Alt, "alt");
1661
1662    // https://html.spec.whatwg.org/multipage/#dom-input-alt
1663    make_setter!(SetAlt, "alt");
1664
1665    // https://html.spec.whatwg.org/multipage/#dom-input-dirName
1666    make_getter!(DirName, "dirname");
1667
1668    // https://html.spec.whatwg.org/multipage/#dom-input-dirName
1669    make_setter!(SetDirName, "dirname");
1670
1671    // https://html.spec.whatwg.org/multipage/#dom-fe-disabled
1672    make_bool_getter!(Disabled, "disabled");
1673
1674    // https://html.spec.whatwg.org/multipage/#dom-fe-disabled
1675    make_bool_setter!(SetDisabled, "disabled");
1676
1677    /// <https://html.spec.whatwg.org/multipage/#dom-fae-form>
1678    fn GetForm(&self) -> Option<DomRoot<HTMLFormElement>> {
1679        self.form_owner()
1680    }
1681
1682    /// <https://html.spec.whatwg.org/multipage/#dom-input-files>
1683    fn GetFiles(&self) -> Option<DomRoot<FileList>> {
1684        self.filelist.get().as_ref().cloned()
1685    }
1686
1687    /// <https://html.spec.whatwg.org/multipage/#dom-input-files>
1688    fn SetFiles(&self, files: Option<&FileList>) {
1689        if self.input_type() == InputType::File && files.is_some() {
1690            self.filelist.set(files);
1691        }
1692    }
1693
1694    // https://html.spec.whatwg.org/multipage/#dom-input-defaultchecked
1695    make_bool_getter!(DefaultChecked, "checked");
1696
1697    // https://html.spec.whatwg.org/multipage/#dom-input-defaultchecked
1698    make_bool_setter!(SetDefaultChecked, "checked");
1699
1700    /// <https://html.spec.whatwg.org/multipage/#dom-input-checked>
1701    fn Checked(&self) -> bool {
1702        self.upcast::<Element>()
1703            .state()
1704            .contains(ElementState::CHECKED)
1705    }
1706
1707    /// <https://html.spec.whatwg.org/multipage/#dom-input-checked>
1708    fn SetChecked(&self, checked: bool, can_gc: CanGc) {
1709        self.update_checked_state(checked, true, can_gc);
1710        self.value_changed(can_gc);
1711    }
1712
1713    // https://html.spec.whatwg.org/multipage/#dom-input-readonly
1714    make_bool_getter!(ReadOnly, "readonly");
1715
1716    // https://html.spec.whatwg.org/multipage/#dom-input-readonly
1717    make_bool_setter!(SetReadOnly, "readonly");
1718
1719    // https://html.spec.whatwg.org/multipage/#dom-input-size
1720    make_uint_getter!(Size, "size", DEFAULT_INPUT_SIZE);
1721
1722    // https://html.spec.whatwg.org/multipage/#dom-input-size
1723    make_limited_uint_setter!(SetSize, "size", DEFAULT_INPUT_SIZE);
1724
1725    /// <https://html.spec.whatwg.org/multipage/#dom-input-type>
1726    fn Type(&self) -> DOMString {
1727        DOMString::from(self.input_type().as_str())
1728    }
1729
1730    // https://html.spec.whatwg.org/multipage/#dom-input-type
1731    make_atomic_setter!(SetType, "type");
1732
1733    /// <https://html.spec.whatwg.org/multipage/#dom-input-value>
1734    fn Value(&self) -> DOMString {
1735        match self.value_mode() {
1736            ValueMode::Value => self.textinput.borrow().get_content(),
1737            ValueMode::Default => self
1738                .upcast::<Element>()
1739                .get_attribute(&ns!(), &local_name!("value"))
1740                .map_or(DOMString::from(""), |a| {
1741                    DOMString::from(a.summarize().value)
1742                }),
1743            ValueMode::DefaultOn => self
1744                .upcast::<Element>()
1745                .get_attribute(&ns!(), &local_name!("value"))
1746                .map_or(DOMString::from("on"), |a| {
1747                    DOMString::from(a.summarize().value)
1748                }),
1749            ValueMode::Filename => {
1750                let mut path = DOMString::from("");
1751                match self.filelist.get() {
1752                    Some(ref fl) => match fl.Item(0) {
1753                        Some(ref f) => {
1754                            path.push_str("C:\\fakepath\\");
1755                            path.push_str(&f.name().str());
1756                            path
1757                        },
1758                        None => path,
1759                    },
1760                    None => path,
1761                }
1762            },
1763        }
1764    }
1765
1766    /// <https://html.spec.whatwg.org/multipage/#dom-input-value>
1767    fn SetValue(&self, mut value: DOMString, can_gc: CanGc) -> ErrorResult {
1768        match self.value_mode() {
1769            ValueMode::Value => {
1770                {
1771                    // Step 3.
1772                    self.value_dirty.set(true);
1773
1774                    // Step 4.
1775                    self.sanitize_value(&mut value);
1776
1777                    let mut textinput = self.textinput.borrow_mut();
1778
1779                    // Step 5.
1780                    if *textinput.single_line_content() != value {
1781                        // Steps 1-2
1782                        textinput.set_content(value);
1783
1784                        // Step 5.
1785                        textinput.clear_selection_to_limit(Direction::Forward);
1786                    }
1787                }
1788
1789                // Additionally, update the placeholder shown state in another
1790                // scope to prevent the borrow checker issue. This is normally
1791                // being done in the attributed mutated.
1792                self.update_placeholder_shown_state();
1793            },
1794            ValueMode::Default | ValueMode::DefaultOn => {
1795                self.upcast::<Element>()
1796                    .set_string_attribute(&local_name!("value"), value, can_gc);
1797            },
1798            ValueMode::Filename => {
1799                if value.is_empty() {
1800                    let window = self.owner_window();
1801                    let fl = FileList::new(&window, vec![], can_gc);
1802                    self.filelist.set(Some(&fl));
1803                } else {
1804                    return Err(Error::InvalidState(None));
1805                }
1806            },
1807        }
1808
1809        self.value_changed(can_gc);
1810        self.upcast::<Node>().dirty(NodeDamage::Other);
1811        Ok(())
1812    }
1813
1814    // https://html.spec.whatwg.org/multipage/#dom-input-defaultvalue
1815    make_getter!(DefaultValue, "value");
1816
1817    // https://html.spec.whatwg.org/multipage/#dom-input-defaultvalue
1818    make_setter!(SetDefaultValue, "value");
1819
1820    // https://html.spec.whatwg.org/multipage/#dom-input-min
1821    make_getter!(Min, "min");
1822
1823    // https://html.spec.whatwg.org/multipage/#dom-input-min
1824    make_setter!(SetMin, "min");
1825
1826    /// <https://html.spec.whatwg.org/multipage/#dom-input-list>
1827    fn GetList(&self) -> Option<DomRoot<HTMLDataListElement>> {
1828        self.suggestions_source_element()
1829    }
1830
1831    // https://html.spec.whatwg.org/multipage/#dom-input-valueasdate
1832    #[expect(unsafe_code)]
1833    fn GetValueAsDate(&self, cx: SafeJSContext) -> Option<NonNull<JSObject>> {
1834        self.convert_string_to_naive_datetime(self.Value())
1835            .map(|date_time| unsafe {
1836                let time = ClippedTime {
1837                    t: (date_time - OffsetDateTime::UNIX_EPOCH).whole_milliseconds() as f64,
1838                };
1839                NonNull::new_unchecked(NewDateObject(*cx, time))
1840            })
1841    }
1842
1843    // https://html.spec.whatwg.org/multipage/#dom-input-valueasdate
1844    #[allow(non_snake_case)]
1845    #[expect(unsafe_code)]
1846    fn SetValueAsDate(
1847        &self,
1848        cx: SafeJSContext,
1849        value: *mut JSObject,
1850        can_gc: CanGc,
1851    ) -> ErrorResult {
1852        rooted!(in(*cx) let value = value);
1853        if !self.does_value_as_date_apply() {
1854            return Err(Error::InvalidState(None));
1855        }
1856        if value.is_null() {
1857            return self.SetValue(DOMString::from(""), can_gc);
1858        }
1859        let mut msecs: f64 = 0.0;
1860        // We need to go through unsafe code to interrogate jsapi about a Date.
1861        // To minimize the amount of unsafe code to maintain, this just gets the milliseconds,
1862        // which we then reinflate into a NaiveDate for use in safe code.
1863        unsafe {
1864            let mut isDate = false;
1865            if !ObjectIsDate(*cx, Handle::from(value.handle()), &mut isDate) {
1866                return Err(Error::JSFailed);
1867            }
1868            if !isDate {
1869                return Err(Error::Type("Value was not a date".to_string()));
1870            }
1871            if !DateGetMsecSinceEpoch(*cx, Handle::from(value.handle()), &mut msecs) {
1872                return Err(Error::JSFailed);
1873            }
1874            if !msecs.is_finite() {
1875                return self.SetValue(DOMString::from(""), can_gc);
1876            }
1877        }
1878
1879        let Ok(date_time) = OffsetDateTime::from_unix_timestamp_nanos((msecs * 1e6) as i128) else {
1880            return self.SetValue(DOMString::from(""), can_gc);
1881        };
1882        self.SetValue(self.convert_datetime_to_dom_string(date_time), can_gc)
1883    }
1884
1885    /// <https://html.spec.whatwg.org/multipage/#dom-input-valueasnumber>
1886    fn ValueAsNumber(&self) -> f64 {
1887        self.convert_string_to_number(&self.Value().str())
1888            .unwrap_or(f64::NAN)
1889    }
1890
1891    /// <https://html.spec.whatwg.org/multipage/#dom-input-valueasnumber>
1892    fn SetValueAsNumber(&self, value: f64, can_gc: CanGc) -> ErrorResult {
1893        if value.is_infinite() {
1894            Err(Error::Type("value is not finite".to_string()))
1895        } else if !self.does_value_as_number_apply() {
1896            Err(Error::InvalidState(None))
1897        } else if value.is_nan() {
1898            self.SetValue(DOMString::from(""), can_gc)
1899        } else if let Some(converted) = self.convert_number_to_string(value) {
1900            self.SetValue(converted, can_gc)
1901        } else {
1902            // The most literal spec-compliant implementation would use bignum types so
1903            // overflow is impossible, but just setting an overflow to the empty string
1904            // matches Firefox's behavior. For example, try input.valueAsNumber=1e30 on
1905            // a type="date" input.
1906            self.SetValue(DOMString::from(""), can_gc)
1907        }
1908    }
1909
1910    // https://html.spec.whatwg.org/multipage/#attr-fe-name
1911    make_getter!(Name, "name");
1912
1913    // https://html.spec.whatwg.org/multipage/#attr-fe-name
1914    make_atomic_setter!(SetName, "name");
1915
1916    // https://html.spec.whatwg.org/multipage/#dom-input-placeholder
1917    make_getter!(Placeholder, "placeholder");
1918
1919    // https://html.spec.whatwg.org/multipage/#dom-input-placeholder
1920    make_setter!(SetPlaceholder, "placeholder");
1921
1922    // https://html.spec.whatwg.org/multipage/#dom-input-formaction
1923    make_form_action_getter!(FormAction, "formaction");
1924
1925    // https://html.spec.whatwg.org/multipage/#dom-input-formaction
1926    make_setter!(SetFormAction, "formaction");
1927
1928    // https://html.spec.whatwg.org/multipage/#dom-fs-formenctype
1929    make_enumerated_getter!(
1930        FormEnctype,
1931        "formenctype",
1932        "application/x-www-form-urlencoded" | "text/plain" | "multipart/form-data",
1933        invalid => "application/x-www-form-urlencoded"
1934    );
1935
1936    // https://html.spec.whatwg.org/multipage/#dom-input-formenctype
1937    make_setter!(SetFormEnctype, "formenctype");
1938
1939    // https://html.spec.whatwg.org/multipage/#dom-fs-formmethod
1940    make_enumerated_getter!(
1941        FormMethod,
1942        "formmethod",
1943        "get" | "post" | "dialog",
1944        invalid => "get"
1945    );
1946
1947    // https://html.spec.whatwg.org/multipage/#dom-fs-formmethod
1948    make_setter!(SetFormMethod, "formmethod");
1949
1950    // https://html.spec.whatwg.org/multipage/#dom-input-formtarget
1951    make_getter!(FormTarget, "formtarget");
1952
1953    // https://html.spec.whatwg.org/multipage/#dom-input-formtarget
1954    make_setter!(SetFormTarget, "formtarget");
1955
1956    // https://html.spec.whatwg.org/multipage/#attr-fs-formnovalidate
1957    make_bool_getter!(FormNoValidate, "formnovalidate");
1958
1959    // https://html.spec.whatwg.org/multipage/#attr-fs-formnovalidate
1960    make_bool_setter!(SetFormNoValidate, "formnovalidate");
1961
1962    // https://html.spec.whatwg.org/multipage/#dom-input-max
1963    make_getter!(Max, "max");
1964
1965    // https://html.spec.whatwg.org/multipage/#dom-input-max
1966    make_setter!(SetMax, "max");
1967
1968    // https://html.spec.whatwg.org/multipage/#dom-input-maxlength
1969    make_int_getter!(MaxLength, "maxlength", DEFAULT_MAX_LENGTH);
1970
1971    // https://html.spec.whatwg.org/multipage/#dom-input-maxlength
1972    make_limited_int_setter!(SetMaxLength, "maxlength", DEFAULT_MAX_LENGTH);
1973
1974    // https://html.spec.whatwg.org/multipage/#dom-input-minlength
1975    make_int_getter!(MinLength, "minlength", DEFAULT_MIN_LENGTH);
1976
1977    // https://html.spec.whatwg.org/multipage/#dom-input-minlength
1978    make_limited_int_setter!(SetMinLength, "minlength", DEFAULT_MIN_LENGTH);
1979
1980    // https://html.spec.whatwg.org/multipage/#dom-input-multiple
1981    make_bool_getter!(Multiple, "multiple");
1982
1983    // https://html.spec.whatwg.org/multipage/#dom-input-multiple
1984    make_bool_setter!(SetMultiple, "multiple");
1985
1986    // https://html.spec.whatwg.org/multipage/#dom-input-pattern
1987    make_getter!(Pattern, "pattern");
1988
1989    // https://html.spec.whatwg.org/multipage/#dom-input-pattern
1990    make_setter!(SetPattern, "pattern");
1991
1992    // https://html.spec.whatwg.org/multipage/#dom-input-required
1993    make_bool_getter!(Required, "required");
1994
1995    // https://html.spec.whatwg.org/multipage/#dom-input-required
1996    make_bool_setter!(SetRequired, "required");
1997
1998    // https://html.spec.whatwg.org/multipage/#dom-input-src
1999    make_url_getter!(Src, "src");
2000
2001    // https://html.spec.whatwg.org/multipage/#dom-input-src
2002    make_url_setter!(SetSrc, "src");
2003
2004    // https://html.spec.whatwg.org/multipage/#dom-input-step
2005    make_getter!(Step, "step");
2006
2007    // https://html.spec.whatwg.org/multipage/#dom-input-step
2008    make_setter!(SetStep, "step");
2009
2010    /// <https://html.spec.whatwg.org/multipage/#dom-input-indeterminate>
2011    fn Indeterminate(&self) -> bool {
2012        self.upcast::<Element>()
2013            .state()
2014            .contains(ElementState::INDETERMINATE)
2015    }
2016
2017    /// <https://html.spec.whatwg.org/multipage/#dom-input-indeterminate>
2018    fn SetIndeterminate(&self, val: bool) {
2019        self.upcast::<Element>()
2020            .set_state(ElementState::INDETERMINATE, val)
2021    }
2022
2023    // https://html.spec.whatwg.org/multipage/#dom-lfe-labels
2024    // Different from make_labels_getter because this one
2025    // conditionally returns null.
2026    fn GetLabels(&self, can_gc: CanGc) -> Option<DomRoot<NodeList>> {
2027        if self.input_type() == InputType::Hidden {
2028            None
2029        } else {
2030            Some(self.labels_node_list.or_init(|| {
2031                NodeList::new_labels_list(
2032                    self.upcast::<Node>().owner_doc().window(),
2033                    self.upcast::<HTMLElement>(),
2034                    can_gc,
2035                )
2036            }))
2037        }
2038    }
2039
2040    /// <https://html.spec.whatwg.org/multipage/#dom-textarea/input-select>
2041    fn Select(&self) {
2042        self.selection().dom_select();
2043    }
2044
2045    /// <https://html.spec.whatwg.org/multipage/#dom-textarea/input-selectionstart>
2046    fn GetSelectionStart(&self) -> Option<u32> {
2047        self.selection().dom_start()
2048    }
2049
2050    /// <https://html.spec.whatwg.org/multipage/#dom-textarea/input-selectionstart>
2051    fn SetSelectionStart(&self, start: Option<u32>) -> ErrorResult {
2052        self.selection().set_dom_start(start)
2053    }
2054
2055    /// <https://html.spec.whatwg.org/multipage/#dom-textarea/input-selectionend>
2056    fn GetSelectionEnd(&self) -> Option<u32> {
2057        self.selection().dom_end()
2058    }
2059
2060    /// <https://html.spec.whatwg.org/multipage/#dom-textarea/input-selectionend>
2061    fn SetSelectionEnd(&self, end: Option<u32>) -> ErrorResult {
2062        self.selection().set_dom_end(end)
2063    }
2064
2065    /// <https://html.spec.whatwg.org/multipage/#dom-textarea/input-selectiondirection>
2066    fn GetSelectionDirection(&self) -> Option<DOMString> {
2067        self.selection().dom_direction()
2068    }
2069
2070    /// <https://html.spec.whatwg.org/multipage/#dom-textarea/input-selectiondirection>
2071    fn SetSelectionDirection(&self, direction: Option<DOMString>) -> ErrorResult {
2072        self.selection().set_dom_direction(direction)
2073    }
2074
2075    /// <https://html.spec.whatwg.org/multipage/#dom-textarea/input-setselectionrange>
2076    fn SetSelectionRange(&self, start: u32, end: u32, direction: Option<DOMString>) -> ErrorResult {
2077        self.selection().set_dom_range(start, end, direction)
2078    }
2079
2080    /// <https://html.spec.whatwg.org/multipage/#dom-textarea/input-setrangetext>
2081    fn SetRangeText(&self, replacement: DOMString) -> ErrorResult {
2082        self.selection()
2083            .set_dom_range_text(replacement, None, None, Default::default())
2084    }
2085
2086    /// <https://html.spec.whatwg.org/multipage/#dom-textarea/input-setrangetext>
2087    fn SetRangeText_(
2088        &self,
2089        replacement: DOMString,
2090        start: u32,
2091        end: u32,
2092        selection_mode: SelectionMode,
2093    ) -> ErrorResult {
2094        self.selection()
2095            .set_dom_range_text(replacement, Some(start), Some(end), selection_mode)
2096    }
2097
2098    /// Select the files based on filepaths passed in, enabled by
2099    /// `dom_testing_html_input_element_select_files_enabled`, used for test purpose.
2100    fn SelectFiles(&self, paths: Vec<DOMString>) {
2101        if self.input_type() == InputType::File {
2102            self.select_files(Some(paths));
2103        }
2104    }
2105
2106    /// <https://html.spec.whatwg.org/multipage/#dom-input-stepup>
2107    fn StepUp(&self, n: i32, can_gc: CanGc) -> ErrorResult {
2108        self.step_up_or_down(n, StepDirection::Up, can_gc)
2109    }
2110
2111    /// <https://html.spec.whatwg.org/multipage/#dom-input-stepdown>
2112    fn StepDown(&self, n: i32, can_gc: CanGc) -> ErrorResult {
2113        self.step_up_or_down(n, StepDirection::Down, can_gc)
2114    }
2115
2116    /// <https://html.spec.whatwg.org/multipage/#dom-cva-willvalidate>
2117    fn WillValidate(&self) -> bool {
2118        self.is_instance_validatable()
2119    }
2120
2121    /// <https://html.spec.whatwg.org/multipage/#dom-cva-validity>
2122    fn Validity(&self, can_gc: CanGc) -> DomRoot<ValidityState> {
2123        self.validity_state(can_gc)
2124    }
2125
2126    /// <https://html.spec.whatwg.org/multipage/#dom-cva-checkvalidity>
2127    fn CheckValidity(&self, can_gc: CanGc) -> bool {
2128        self.check_validity(can_gc)
2129    }
2130
2131    /// <https://html.spec.whatwg.org/multipage/#dom-cva-reportvalidity>
2132    fn ReportValidity(&self, can_gc: CanGc) -> bool {
2133        self.report_validity(can_gc)
2134    }
2135
2136    /// <https://html.spec.whatwg.org/multipage/#dom-cva-validationmessage>
2137    fn ValidationMessage(&self) -> DOMString {
2138        self.validation_message()
2139    }
2140
2141    /// <https://html.spec.whatwg.org/multipage/#dom-cva-setcustomvalidity>
2142    fn SetCustomValidity(&self, error: DOMString, can_gc: CanGc) {
2143        self.validity_state(can_gc).set_custom_error_message(error);
2144    }
2145}
2146
2147fn radio_group_iter<'a>(
2148    elem: &'a HTMLInputElement,
2149    group: Option<&'a Atom>,
2150    form: Option<&'a HTMLFormElement>,
2151    root: &'a Node,
2152) -> impl Iterator<Item = DomRoot<HTMLInputElement>> + 'a {
2153    root.traverse_preorder(ShadowIncluding::No)
2154        .filter_map(DomRoot::downcast::<HTMLInputElement>)
2155        .filter(move |r| &**r == elem || in_same_group(r, form, group, Some(root)))
2156}
2157
2158fn broadcast_radio_checked(broadcaster: &HTMLInputElement, group: Option<&Atom>, can_gc: CanGc) {
2159    let root = broadcaster
2160        .upcast::<Node>()
2161        .GetRootNode(&GetRootNodeOptions::empty());
2162    let form = broadcaster.form_owner();
2163    for r in radio_group_iter(broadcaster, group, form.as_deref(), &root) {
2164        if broadcaster != &*r && r.Checked() {
2165            r.SetChecked(false, can_gc);
2166        }
2167    }
2168}
2169
2170fn perform_radio_group_validation(elem: &HTMLInputElement, group: Option<&Atom>, can_gc: CanGc) {
2171    let root = elem
2172        .upcast::<Node>()
2173        .GetRootNode(&GetRootNodeOptions::empty());
2174    let form = elem.form_owner();
2175    for r in radio_group_iter(elem, group, form.as_deref(), &root) {
2176        r.validity_state(can_gc)
2177            .perform_validation_and_update(ValidationFlags::all(), can_gc);
2178    }
2179}
2180
2181/// <https://html.spec.whatwg.org/multipage/#radio-button-group>
2182fn in_same_group(
2183    other: &HTMLInputElement,
2184    owner: Option<&HTMLFormElement>,
2185    group: Option<&Atom>,
2186    tree_root: Option<&Node>,
2187) -> bool {
2188    if group.is_none() {
2189        // Radio input elements with a missing or empty name are alone in their own group.
2190        return false;
2191    }
2192
2193    if other.input_type() != InputType::Radio ||
2194        other.form_owner().as_deref() != owner ||
2195        other.radio_group_name().as_ref() != group
2196    {
2197        return false;
2198    }
2199
2200    match tree_root {
2201        Some(tree_root) => {
2202            let other_root = other
2203                .upcast::<Node>()
2204                .GetRootNode(&GetRootNodeOptions::empty());
2205            tree_root == &*other_root
2206        },
2207        None => {
2208            // Skip check if the tree root isn't provided.
2209            true
2210        },
2211    }
2212}
2213
2214impl HTMLInputElement {
2215    fn radio_group_updated(&self, group: Option<&Atom>, can_gc: CanGc) {
2216        if self.Checked() {
2217            broadcast_radio_checked(self, group, can_gc);
2218        }
2219    }
2220
2221    /// <https://html.spec.whatwg.org/multipage/#constructing-the-form-data-set>
2222    /// Steps range from 5.1 to 5.10 (specific to HTMLInputElement)
2223    pub(crate) fn form_datums(
2224        &self,
2225        submitter: Option<FormSubmitterElement>,
2226        encoding: Option<&'static Encoding>,
2227    ) -> Vec<FormDatum> {
2228        // 3.1: disabled state check is in get_unclean_dataset
2229
2230        // Step 5.2
2231        let ty = self.Type();
2232
2233        // Step 5.4
2234        let name = self.Name();
2235        let is_submitter = match submitter {
2236            Some(FormSubmitterElement::Input(s)) => self == s,
2237            _ => false,
2238        };
2239
2240        match self.input_type() {
2241            // Step 5.1: it's a button but it is not submitter.
2242            InputType::Submit | InputType::Button | InputType::Reset if !is_submitter => {
2243                return vec![];
2244            },
2245
2246            // Step 5.1: it's the "Checkbox" or "Radio Button" and whose checkedness is false.
2247            InputType::Radio | InputType::Checkbox => {
2248                if !self.Checked() || name.is_empty() {
2249                    return vec![];
2250                }
2251            },
2252
2253            InputType::File => {
2254                let mut datums = vec![];
2255
2256                // Step 5.2-5.7
2257                let name = self.Name();
2258
2259                match self.GetFiles() {
2260                    Some(fl) => {
2261                        for f in fl.iter_files() {
2262                            datums.push(FormDatum {
2263                                ty: ty.clone(),
2264                                name: name.clone(),
2265                                value: FormDatumValue::File(DomRoot::from_ref(f)),
2266                            });
2267                        }
2268                    },
2269                    None => {
2270                        datums.push(FormDatum {
2271                            // XXX(izgzhen): Spec says 'application/octet-stream' as the type,
2272                            // but this is _type_ of element rather than content right?
2273                            ty: ty.clone(),
2274                            name: name.clone(),
2275                            value: FormDatumValue::String(DOMString::from("")),
2276                        })
2277                    },
2278                }
2279
2280                return datums;
2281            },
2282
2283            InputType::Image => return vec![], // Unimplemented
2284
2285            // Step 5.10: it's a hidden field named _charset_
2286            InputType::Hidden => {
2287                if name.to_ascii_lowercase() == "_charset_" {
2288                    return vec![FormDatum {
2289                        ty: ty.clone(),
2290                        name,
2291                        value: FormDatumValue::String(match encoding {
2292                            None => DOMString::from("UTF-8"),
2293                            Some(enc) => DOMString::from(enc.name()),
2294                        }),
2295                    }];
2296                }
2297            },
2298
2299            // Step 5.1: it's not the "Image Button" and doesn't have a name attribute.
2300            _ => {
2301                if name.is_empty() {
2302                    return vec![];
2303                }
2304            },
2305        }
2306
2307        // Step 5.12
2308        vec![FormDatum {
2309            ty: ty.clone(),
2310            name,
2311            value: FormDatumValue::String(self.Value()),
2312        }]
2313    }
2314
2315    /// <https://html.spec.whatwg.org/multipage/#radio-button-group>
2316    fn radio_group_name(&self) -> Option<Atom> {
2317        self.upcast::<Element>()
2318            .get_name()
2319            .filter(|name| !name.is_empty())
2320    }
2321
2322    fn update_checked_state(&self, checked: bool, dirty: bool, can_gc: CanGc) {
2323        self.upcast::<Element>()
2324            .set_state(ElementState::CHECKED, checked);
2325
2326        if dirty {
2327            self.checked_changed.set(true);
2328        }
2329
2330        if self.input_type() == InputType::Radio && checked {
2331            broadcast_radio_checked(self, self.radio_group_name().as_ref(), can_gc);
2332        }
2333
2334        self.upcast::<Node>().dirty(NodeDamage::Other);
2335    }
2336
2337    // https://html.spec.whatwg.org/multipage/#concept-fe-mutable
2338    pub(crate) fn is_mutable(&self) -> bool {
2339        // https://html.spec.whatwg.org/multipage/#the-input-element:concept-fe-mutable
2340        // https://html.spec.whatwg.org/multipage/#the-readonly-attribute:concept-fe-mutable
2341        !(self.upcast::<Element>().disabled_state() || self.ReadOnly())
2342    }
2343
2344    // https://html.spec.whatwg.org/multipage/#the-input-element:concept-form-reset-control
2345    pub(crate) fn reset(&self, can_gc: CanGc) {
2346        match self.input_type() {
2347            InputType::Radio | InputType::Checkbox => {
2348                self.update_checked_state(self.DefaultChecked(), false, can_gc);
2349                self.checked_changed.set(false);
2350                self.value_changed(can_gc);
2351            },
2352            InputType::Image => (),
2353            _ => (),
2354        }
2355        self.textinput.borrow_mut().set_content(self.DefaultValue());
2356        self.value_dirty.set(false);
2357        self.upcast::<Node>().dirty(NodeDamage::Other);
2358    }
2359
2360    /// <https://w3c.github.io/webdriver/#ref-for-dfn-clear-algorithm-3>
2361    /// Used by WebDriver to clear the input element.
2362    pub(crate) fn clear(&self, can_gc: CanGc) {
2363        // Step 1. Reset dirty value and dirty checkedness flags.
2364        self.value_dirty.set(false);
2365        self.checked_changed.set(false);
2366        // Step 2. Set value to empty string.
2367        self.textinput.borrow_mut().set_content(DOMString::from(""));
2368        // Step 3. Set checkedness based on presence of content attribute.
2369        self.update_checked_state(self.DefaultChecked(), false, can_gc);
2370        self.value_changed(can_gc);
2371        // Step 4. Empty selected files
2372        if self.filelist.get().is_some() {
2373            let window = self.owner_window();
2374            let filelist = FileList::new(&window, vec![], can_gc);
2375            self.filelist.set(Some(&filelist));
2376        }
2377        // Step 5. invoke the value sanitization algorithm iff
2378        // the type attribute's current state defines one.
2379        // This is covered in `fn sanitize_value` called below.
2380        self.enable_sanitization();
2381        self.upcast::<Node>().dirty(NodeDamage::Other);
2382    }
2383
2384    fn update_placeholder_shown_state(&self) {
2385        if !self.input_type().is_textual_or_password() {
2386            return;
2387        }
2388
2389        let has_placeholder = !self.placeholder.borrow().is_empty();
2390        let has_value = !self.textinput.borrow().is_empty();
2391        let el = self.upcast::<Element>();
2392
2393        el.set_placeholder_shown_state(has_placeholder && !has_value);
2394    }
2395
2396    // Update the placeholder text in the text shadow tree.
2397    // To increase the performance, we would only do this when it is necessary.
2398    fn update_text_shadow_tree_placeholder(&self, can_gc: CanGc) {
2399        if !self.is_textual_widget() {
2400            return;
2401        }
2402
2403        let text_shadow_tree = self.text_shadow_tree(can_gc);
2404        text_shadow_tree.init_placeholder_container_if_necessary(self, can_gc);
2405
2406        let Some(ref placeholder_container) = *text_shadow_tree.placeholder_container.borrow()
2407        else {
2408            // No update is necesssary.
2409            return;
2410        };
2411        let placeholder_text = self.placeholder.borrow().clone();
2412
2413        // We are finding and updating the CharacterData child directly to optimize the update.
2414        placeholder_container
2415            .upcast::<Node>()
2416            .GetFirstChild()
2417            .expect("UA widget text container without child")
2418            .downcast::<CharacterData>()
2419            .expect("First child is not a CharacterData node")
2420            .SetData(placeholder_text);
2421    }
2422
2423    pub(crate) fn select_files_for_webdriver(
2424        &self,
2425        test_paths: Vec<DOMString>,
2426        response_sender: IpcSender<Result<bool, ErrorStatus>>,
2427    ) {
2428        let mut stored_sender = self.pending_webdriver_response.borrow_mut();
2429        assert!(stored_sender.is_none());
2430
2431        *stored_sender = Some(PendingWebDriverResponse {
2432            response_sender,
2433            expected_file_count: test_paths.len(),
2434        });
2435
2436        self.select_files(Some(test_paths));
2437    }
2438
2439    /// Select files by invoking UI or by passed in argument.
2440    ///
2441    /// <https://html.spec.whatwg.org/multipage/#file-upload-state-(type=file)>
2442    pub(crate) fn select_files(&self, test_paths: Option<Vec<DOMString>>) {
2443        let current_paths = match &test_paths {
2444            Some(test_paths) => test_paths
2445                .iter()
2446                .filter_map(|path_str| PathBuf::from_str(&path_str.str()).ok())
2447                .collect(),
2448            // TODO: This should get the pathnames of the current files, but we currently don't have
2449            // that information in Script. It should be passed through here.
2450            None => Default::default(),
2451        };
2452
2453        let accept_current_paths_for_testing = test_paths.is_some();
2454        self.owner_document()
2455            .embedder_controls()
2456            .show_embedder_control(
2457                ControlElement::FileInput(DomRoot::from_ref(self)),
2458                EmbedderControlRequest::FilePicker(FilePickerRequest {
2459                    origin: get_blob_origin(&self.owner_window().get_url()),
2460                    current_paths,
2461                    filter_patterns: filter_from_accept(&self.Accept()),
2462                    allow_select_multiple: self.Multiple(),
2463                    accept_current_paths_for_testing,
2464                }),
2465                None,
2466            );
2467    }
2468
2469    /// <https://html.spec.whatwg.org/multipage/#value-sanitization-algorithm>
2470    fn sanitize_value(&self, value: &mut DOMString) {
2471        // if sanitization_flag is false, we are setting content attributes
2472        // on an element we haven't really finished creating; we will
2473        // enable the flag and really sanitize before this element becomes
2474        // observable.
2475        if !self.sanitization_flag.get() {
2476            return;
2477        }
2478        match self.input_type() {
2479            InputType::Text | InputType::Search | InputType::Tel | InputType::Password => {
2480                value.strip_newlines();
2481            },
2482            InputType::Url => {
2483                value.strip_newlines();
2484                value.strip_leading_and_trailing_ascii_whitespace();
2485            },
2486            InputType::Date => {
2487                if !value.str().is_valid_date_string() {
2488                    value.clear();
2489                }
2490            },
2491            InputType::Month => {
2492                if !value.str().is_valid_month_string() {
2493                    value.clear();
2494                }
2495            },
2496            InputType::Week => {
2497                if !value.str().is_valid_week_string() {
2498                    value.clear();
2499                }
2500            },
2501            InputType::Color => {
2502                if value.str().is_valid_simple_color_string() {
2503                    value.make_ascii_lowercase();
2504                } else {
2505                    *value = "#000000".into();
2506                }
2507            },
2508            InputType::Time => {
2509                if !value.str().is_valid_time_string() {
2510                    value.clear();
2511                }
2512            },
2513            InputType::DatetimeLocal => {
2514                let time = value
2515                    .str()
2516                    .parse_local_date_time_string()
2517                    .map(|date_time| date_time.to_local_date_time_string());
2518                match time {
2519                    Some(normalized_string) => *value = DOMString::from_string(normalized_string),
2520                    None => value.clear(),
2521                }
2522            },
2523            InputType::Number => {
2524                if !value.is_valid_floating_point_number_string() {
2525                    value.clear();
2526                }
2527                // Spec says that user agent "may" round the value
2528                // when it's suffering a step mismatch, but WPT tests
2529                // want it unrounded, and this matches other browser
2530                // behavior (typing an unrounded number into an
2531                // integer field box and pressing enter generally keeps
2532                // the number intact but makes the input box :invalid)
2533            },
2534            // https://html.spec.whatwg.org/multipage/#range-state-(type=range):value-sanitization-algorithm
2535            InputType::Range => {
2536                if !value.is_valid_floating_point_number_string() {
2537                    *value = DOMString::from(self.default_range_value().to_string());
2538                }
2539                if let Ok(fval) = &value.parse::<f64>() {
2540                    let mut fval = *fval;
2541                    // comparing max first, because if they contradict
2542                    // the spec wants min to be the one that applies
2543                    if let Some(max) = self.maximum() {
2544                        if fval > max {
2545                            fval = max;
2546                        }
2547                    }
2548                    if let Some(min) = self.minimum() {
2549                        if fval < min {
2550                            fval = min;
2551                        }
2552                    }
2553                    // https://html.spec.whatwg.org/multipage/#range-state-(type=range):suffering-from-a-step-mismatch
2554                    // Spec does not describe this in a way that lends itself to
2555                    // reproducible handling of floating-point rounding;
2556                    // Servo may fail a WPT test because .1 * 6 == 6.000000000000001
2557                    if let Some(allowed_value_step) = self.allowed_value_step() {
2558                        let step_base = self.step_base();
2559                        let steps_from_base = (fval - step_base) / allowed_value_step;
2560                        if steps_from_base.fract() != 0.0 {
2561                            // not an integer number of steps, there's a mismatch
2562                            // round the number of steps...
2563                            let int_steps = round_halves_positive(steps_from_base);
2564                            // and snap the value to that rounded value...
2565                            fval = int_steps * allowed_value_step + step_base;
2566
2567                            // but if after snapping we're now outside min..max
2568                            // we have to adjust! (adjusting to min last because
2569                            // that "wins" over max in the spec)
2570                            if let Some(stepped_maximum) = self.stepped_maximum() {
2571                                if fval > stepped_maximum {
2572                                    fval = stepped_maximum;
2573                                }
2574                            }
2575                            if let Some(stepped_minimum) = self.stepped_minimum() {
2576                                if fval < stepped_minimum {
2577                                    fval = stepped_minimum;
2578                                }
2579                            }
2580                        }
2581                    }
2582                    *value = DOMString::from(fval.to_string());
2583                };
2584            },
2585            InputType::Email => {
2586                if !self.Multiple() {
2587                    value.strip_newlines();
2588                    value.strip_leading_and_trailing_ascii_whitespace();
2589                } else {
2590                    let sanitized = split_commas(&value.str())
2591                        .map(|token| {
2592                            let mut token = DOMString::from(token.to_string());
2593                            token.strip_newlines();
2594                            token.strip_leading_and_trailing_ascii_whitespace();
2595                            token
2596                        })
2597                        .join(",");
2598                    value.clear();
2599                    value.push_str(sanitized.as_str());
2600                }
2601            },
2602            // The following inputs don't have a value sanitization algorithm.
2603            // See https://html.spec.whatwg.org/multipage/#value-sanitization-algorithm
2604            InputType::Button |
2605            InputType::Checkbox |
2606            InputType::File |
2607            InputType::Hidden |
2608            InputType::Image |
2609            InputType::Radio |
2610            InputType::Reset |
2611            InputType::Submit => (),
2612        }
2613    }
2614
2615    #[cfg_attr(crown, allow(crown::unrooted_must_root))]
2616    fn selection(&self) -> TextControlSelection<'_, Self> {
2617        TextControlSelection::new(self, &self.textinput)
2618    }
2619
2620    // https://html.spec.whatwg.org/multipage/#implicit-submission
2621    fn implicit_submission(&self, can_gc: CanGc) {
2622        let doc = self.owner_document();
2623        let node = doc.upcast::<Node>();
2624        let owner = self.form_owner();
2625        let form = match owner {
2626            None => return,
2627            Some(ref f) => f,
2628        };
2629
2630        if self.upcast::<Element>().click_in_progress() {
2631            return;
2632        }
2633        let submit_button = node
2634            .query_selector_iter(DOMString::from("input[type=submit]"))
2635            .unwrap()
2636            .filter_map(DomRoot::downcast::<HTMLInputElement>)
2637            .find(|r| r.form_owner() == owner);
2638        match submit_button {
2639            Some(ref button) => {
2640                if button.is_instance_activatable() {
2641                    // spec does not actually say to set the not trusted flag,
2642                    // but we can get here from synthetic keydown events
2643                    button
2644                        .upcast::<Node>()
2645                        .fire_synthetic_pointer_event_not_trusted(DOMString::from("click"), can_gc);
2646                }
2647            },
2648            None => {
2649                let mut inputs = node
2650                    .query_selector_iter(DOMString::from("input"))
2651                    .unwrap()
2652                    .filter_map(DomRoot::downcast::<HTMLInputElement>)
2653                    .filter(|input| {
2654                        input.form_owner() == owner &&
2655                            matches!(
2656                                input.input_type(),
2657                                InputType::Text |
2658                                    InputType::Search |
2659                                    InputType::Url |
2660                                    InputType::Tel |
2661                                    InputType::Email |
2662                                    InputType::Password |
2663                                    InputType::Date |
2664                                    InputType::Month |
2665                                    InputType::Week |
2666                                    InputType::Time |
2667                                    InputType::DatetimeLocal |
2668                                    InputType::Number
2669                            )
2670                    });
2671
2672                if inputs.nth(1).is_some() {
2673                    // lazily test for > 1 submission-blocking inputs
2674                    return;
2675                }
2676                form.submit(
2677                    SubmittedFrom::NotFromForm,
2678                    FormSubmitterElement::Form(form),
2679                    can_gc,
2680                );
2681            },
2682        }
2683    }
2684
2685    /// <https://html.spec.whatwg.org/multipage/#concept-input-value-string-number>
2686    fn convert_string_to_number(&self, value: &str) -> Option<f64> {
2687        match self.input_type() {
2688            // > The algorithm to convert a string to a number, given a string input, is as
2689            // > follows: If parsing a date from input results in an error, then return an
2690            // > error; otherwise, return the number of milliseconds elapsed from midnight
2691            // > UTC on the morning of 1970-01-01 (the time represented by the value
2692            // > "1970-01-01T00:00:00.0Z") to midnight UTC on the morning of the parsed
2693            // > date, ignoring leap seconds.
2694            InputType::Date => value.parse_date_string().map(|date_time| {
2695                (date_time - OffsetDateTime::UNIX_EPOCH).whole_milliseconds() as f64
2696            }),
2697            // > The algorithm to convert a string to a number, given a string input, is as
2698            // > follows: If parsing a month from input results in an error, then return an
2699            // > error; otherwise, return the number of months between January 1970 and the
2700            // > parsed month.
2701            //
2702            // This one returns number of months, not milliseconds (specification requires
2703            // this, presumably because number of milliseconds is not consistent across
2704            // months) the - 1.0 is because january is 1, not 0
2705            InputType::Month => value.parse_month_string().map(|date_time| {
2706                ((date_time.year() - 1970) * 12) as f64 + (date_time.month() as u8 - 1) as f64
2707            }),
2708            // > The algorithm to convert a string to a number, given a string input, is as
2709            // > follows: If parsing a week string from input results in an error, then
2710            // > return an error; otherwise, return the number of milliseconds elapsed from
2711            // > midnight UTC on the morning of 1970-01-01 (the time represented by the
2712            // > value "1970-01-01T00:00:00.0Z") to midnight UTC on the morning of the
2713            // > Monday of the parsed week, ignoring leap seconds.
2714            InputType::Week => value.parse_week_string().map(|date_time| {
2715                (date_time - OffsetDateTime::UNIX_EPOCH).whole_milliseconds() as f64
2716            }),
2717            // > The algorithm to convert a string to a number, given a string input, is as
2718            // > follows: If parsing a time from input results in an error, then return an
2719            // > error; otherwise, return the number of milliseconds elapsed from midnight to
2720            // > the parsed time on a day with no time changes.
2721            InputType::Time => value
2722                .parse_time_string()
2723                .map(|date_time| (date_time.time() - Time::MIDNIGHT).whole_milliseconds() as f64),
2724            // > The algorithm to convert a string to a number, given a string input, is as
2725            // > follows: If parsing a date and time from input results in an error, then
2726            // > return an error; otherwise, return the number of milliseconds elapsed from
2727            // > midnight on the morning of 1970-01-01 (the time represented by the value
2728            // > "1970-01-01T00:00:00.0") to the parsed local date and time, ignoring leap
2729            // > seconds.
2730            InputType::DatetimeLocal => value.parse_local_date_time_string().map(|date_time| {
2731                (date_time - OffsetDateTime::UNIX_EPOCH).whole_milliseconds() as f64
2732            }),
2733            InputType::Number | InputType::Range => parse_floating_point_number(value),
2734            // min/max/valueAsNumber/stepDown/stepUp do not apply to
2735            // the remaining types
2736            _ => None,
2737        }
2738    }
2739
2740    /// <https://html.spec.whatwg.org/multipage/#concept-input-value-string-number>
2741    fn convert_number_to_string(&self, value: f64) -> Option<DOMString> {
2742        match self.input_type() {
2743            InputType::Date | InputType::Week | InputType::Time | InputType::DatetimeLocal => {
2744                OffsetDateTime::from_unix_timestamp_nanos((value * 1e6) as i128)
2745                    .ok()
2746                    .map(|value| self.convert_datetime_to_dom_string(value))
2747            },
2748            InputType::Month => {
2749                // > The algorithm to convert a number to a string, given a number input,
2750                // > is as follows: Return a valid month string that represents the month
2751                // > that has input months between it and January 1970.
2752                let date = OffsetDateTime::UNIX_EPOCH;
2753                let years = (value / 12.) as i32;
2754                let year = date.year() + years;
2755
2756                let months = value as i32 - (years * 12);
2757                let months = match months.cmp(&0) {
2758                    Ordering::Less => (12 - months) as u8,
2759                    Ordering::Equal | Ordering::Greater => months as u8,
2760                } + 1;
2761
2762                let date = date
2763                    .replace_year(year)
2764                    .ok()?
2765                    .replace_month(Month::try_from(months).ok()?)
2766                    .ok()?;
2767                Some(self.convert_datetime_to_dom_string(date))
2768            },
2769            InputType::Number | InputType::Range => {
2770                let mut value = DOMString::from(value.to_string());
2771                value.set_best_representation_of_the_floating_point_number();
2772                Some(value)
2773            },
2774            _ => unreachable!("Should not have called convert_number_to_string for non-Date types"),
2775        }
2776    }
2777
2778    // <https://html.spec.whatwg.org/multipage/#concept-input-value-string-date>
2779    // This does the safe Rust part of conversion; the unsafe JS Date part
2780    // is in GetValueAsDate
2781    fn convert_string_to_naive_datetime(&self, value: DOMString) -> Option<OffsetDateTime> {
2782        match self.input_type() {
2783            InputType::Date => value.str().parse_date_string(),
2784            InputType::Time => value.str().parse_time_string(),
2785            InputType::Week => value.str().parse_week_string(),
2786            InputType::Month => value.str().parse_month_string(),
2787            InputType::DatetimeLocal => value.str().parse_local_date_time_string(),
2788            // does not apply to other types
2789            _ => None,
2790        }
2791    }
2792
2793    /// <https://html.spec.whatwg.org/multipage/#concept-input-value-date-string>
2794    /// This does the safe Rust part of conversion; the unsafe JS Date part
2795    /// is in SetValueAsDate
2796    fn convert_datetime_to_dom_string(&self, value: OffsetDateTime) -> DOMString {
2797        DOMString::from_string(match self.input_type() {
2798            InputType::Date => value.to_date_string(),
2799            InputType::Month => value.to_month_string(),
2800            InputType::Week => value.to_week_string(),
2801            InputType::Time => value.to_time_string(),
2802            InputType::DatetimeLocal => value.to_local_date_time_string(),
2803            _ => {
2804                unreachable!("Should not have called convert_datetime_to_string for non-Date types")
2805            },
2806        })
2807    }
2808
2809    fn update_related_validity_states(&self, can_gc: CanGc) {
2810        match self.input_type() {
2811            InputType::Radio => {
2812                perform_radio_group_validation(self, self.radio_group_name().as_ref(), can_gc)
2813            },
2814            _ => {
2815                self.validity_state(can_gc)
2816                    .perform_validation_and_update(ValidationFlags::all(), can_gc);
2817            },
2818        }
2819    }
2820
2821    fn value_changed(&self, can_gc: CanGc) {
2822        self.update_related_validity_states(can_gc);
2823        self.update_shadow_tree(can_gc);
2824    }
2825
2826    /// <https://html.spec.whatwg.org/multipage/#show-the-picker,-if-applicable>
2827    fn show_the_picker_if_applicable(&self) {
2828        // FIXME: Implement most of this algorithm
2829
2830        // Step 2. If element is not mutable, then return.
2831        if !self.is_mutable() {
2832            return;
2833        }
2834
2835        // Step 6. Otherwise, the user agent should show the relevant user interface for selecting a value for element,
2836        // in the way it normally would when the user interacts with the control.
2837        if self.input_type() == InputType::Color {
2838            let document = self.owner_document();
2839            let current_value = self.Value();
2840            let current_color = RgbColor {
2841                red: u8::from_str_radix(&current_value.str()[1..3], 16).unwrap(),
2842                green: u8::from_str_radix(&current_value.str()[3..5], 16).unwrap(),
2843                blue: u8::from_str_radix(&current_value.str()[5..7], 16).unwrap(),
2844            };
2845            document.embedder_controls().show_embedder_control(
2846                ControlElement::ColorInput(DomRoot::from_ref(self)),
2847                EmbedderControlRequest::ColorPicker(current_color),
2848                None,
2849            );
2850        }
2851    }
2852
2853    pub(crate) fn handle_color_picker_response(&self, response: Option<RgbColor>, can_gc: CanGc) {
2854        let Some(selected_color) = response else {
2855            return;
2856        };
2857        let formatted_color = format!(
2858            "#{:0>2x}{:0>2x}{:0>2x}",
2859            selected_color.red, selected_color.green, selected_color.blue
2860        );
2861        let _ = self.SetValue(formatted_color.into(), can_gc);
2862    }
2863
2864    pub(crate) fn handle_file_picker_response(
2865        &self,
2866        response: Option<Vec<SelectedFile>>,
2867        can_gc: CanGc,
2868    ) {
2869        let mut files = Vec::new();
2870
2871        if let Some(pending_webdriver_reponse) = self.pending_webdriver_response.borrow_mut().take()
2872        {
2873            // From: <https://w3c.github.io/webdriver/#dfn-dispatch-actions-for-a-string>
2874            // "Complete implementation specific steps equivalent to setting the selected
2875            // files on the input element. If multiple is true files are be appended to
2876            // element's selected files."
2877            //
2878            // Note: This is annoying.
2879            if self.Multiple() {
2880                if let Some(filelist) = self.filelist.get() {
2881                    files = filelist.iter_files().map(|file| file.as_rooted()).collect();
2882                }
2883            }
2884
2885            let number_files_selected = response.as_ref().map(Vec::len).unwrap_or_default();
2886            pending_webdriver_reponse.finish(number_files_selected);
2887        }
2888
2889        let Some(response_files) = response else {
2890            return;
2891        };
2892
2893        let window = self.owner_window();
2894        files.extend(
2895            response_files
2896                .into_iter()
2897                .map(|file| File::new_from_selected(&window, file, can_gc)),
2898        );
2899
2900        // Only use the last file if this isn't a multi-select file input. This could
2901        // happen if the attribute changed after the file dialog was initiated.
2902        if !self.Multiple() {
2903            files = files
2904                .pop()
2905                .map(|last_file| vec![last_file])
2906                .unwrap_or_default();
2907        }
2908
2909        self.filelist
2910            .set(Some(&FileList::new(&window, files, can_gc)));
2911
2912        let target = self.upcast::<EventTarget>();
2913        target.fire_event_with_params(
2914            atom!("input"),
2915            EventBubbles::Bubbles,
2916            EventCancelable::NotCancelable,
2917            EventComposed::Composed,
2918            can_gc,
2919        );
2920        target.fire_bubbling_event(atom!("change"), can_gc);
2921    }
2922
2923    fn handle_focus(&self) {
2924        let Ok(input_method_type) = self.input_type().try_into() else {
2925            return;
2926        };
2927
2928        self.owner_document()
2929            .embedder_controls()
2930            .show_embedder_control(
2931                ControlElement::Ime(DomRoot::from_ref(self.upcast())),
2932                EmbedderControlRequest::InputMethod(InputMethodRequest {
2933                    input_method_type,
2934                    text: self.Value().to_string(),
2935                    insertion_point: self.GetSelectionEnd(),
2936                    multiline: false,
2937                }),
2938                None,
2939            );
2940    }
2941}
2942
2943impl VirtualMethods for HTMLInputElement {
2944    fn super_type(&self) -> Option<&dyn VirtualMethods> {
2945        Some(self.upcast::<HTMLElement>() as &dyn VirtualMethods)
2946    }
2947
2948    fn attribute_mutated(&self, attr: &Attr, mutation: AttributeMutation, can_gc: CanGc) {
2949        let could_have_had_embedder_control = self.may_have_embedder_control();
2950
2951        self.super_type()
2952            .unwrap()
2953            .attribute_mutated(attr, mutation, can_gc);
2954
2955        match *attr.local_name() {
2956            local_name!("disabled") => {
2957                let disabled_state = match mutation {
2958                    AttributeMutation::Set(None, _) => true,
2959                    AttributeMutation::Set(Some(_), _) => {
2960                        // Input was already disabled before.
2961                        return;
2962                    },
2963                    AttributeMutation::Removed => false,
2964                };
2965                let el = self.upcast::<Element>();
2966                el.set_disabled_state(disabled_state);
2967                el.set_enabled_state(!disabled_state);
2968                el.check_ancestors_disabled_state_for_form_control();
2969
2970                if self.input_type().is_textual() {
2971                    let read_write = !(self.ReadOnly() || el.disabled_state());
2972                    el.set_read_write_state(read_write);
2973                }
2974
2975                el.update_sequentially_focusable_status(can_gc);
2976            },
2977            local_name!("checked") if !self.checked_changed.get() => {
2978                let checked_state = match mutation {
2979                    AttributeMutation::Set(None, _) => true,
2980                    AttributeMutation::Set(Some(_), _) => {
2981                        // Input was already checked before.
2982                        return;
2983                    },
2984                    AttributeMutation::Removed => false,
2985                };
2986                self.update_checked_state(checked_state, false, can_gc);
2987            },
2988            local_name!("size") => {
2989                let size = mutation.new_value(attr).map(|value| value.as_uint());
2990                self.size.set(size.unwrap_or(DEFAULT_INPUT_SIZE));
2991            },
2992            local_name!("type") => {
2993                let el = self.upcast::<Element>();
2994                match mutation {
2995                    AttributeMutation::Set(..) => {
2996                        let new_type = InputType::from(attr.value().as_atom());
2997
2998                        // https://html.spec.whatwg.org/multipage/#input-type-change
2999                        let (old_value_mode, old_idl_value) = (self.value_mode(), self.Value());
3000                        let previously_selectable = self.selection_api_applies();
3001
3002                        self.input_type.set(new_type);
3003
3004                        if new_type.is_textual() {
3005                            let read_write = !(self.ReadOnly() || el.disabled_state());
3006                            el.set_read_write_state(read_write);
3007                        } else {
3008                            el.set_read_write_state(false);
3009                        }
3010
3011                        if new_type == InputType::File {
3012                            let window = self.owner_window();
3013                            let filelist = FileList::new(&window, vec![], can_gc);
3014                            self.filelist.set(Some(&filelist));
3015                        }
3016
3017                        let new_value_mode = self.value_mode();
3018                        match (&old_value_mode, old_idl_value.is_empty(), new_value_mode) {
3019                            // Step 1
3020                            (&ValueMode::Value, false, ValueMode::Default) |
3021                            (&ValueMode::Value, false, ValueMode::DefaultOn) => {
3022                                self.SetValue(old_idl_value, can_gc)
3023                                    .expect("Failed to set input value on type change to a default ValueMode.");
3024                            },
3025
3026                            // Step 2
3027                            (_, _, ValueMode::Value) if old_value_mode != ValueMode::Value => {
3028                                self.SetValue(
3029                                    self.upcast::<Element>()
3030                                        .get_attribute(&ns!(), &local_name!("value"))
3031                                        .map_or(DOMString::from(""), |a| {
3032                                            DOMString::from(a.summarize().value)
3033                                        }),
3034                                    can_gc,
3035                                )
3036                                .expect(
3037                                    "Failed to set input value on type change to ValueMode::Value.",
3038                                );
3039                                self.value_dirty.set(false);
3040                            },
3041
3042                            // Step 3
3043                            (_, _, ValueMode::Filename)
3044                                if old_value_mode != ValueMode::Filename =>
3045                            {
3046                                self.SetValue(DOMString::from(""), can_gc)
3047                                    .expect("Failed to set input value on type change to ValueMode::Filename.");
3048                            },
3049                            _ => {},
3050                        }
3051
3052                        // Step 5
3053                        if new_type == InputType::Radio {
3054                            self.radio_group_updated(self.radio_group_name().as_ref(), can_gc);
3055                        }
3056
3057                        // Step 6
3058                        let mut textinput = self.textinput.borrow_mut();
3059                        let mut value = textinput.single_line_content().clone();
3060                        self.sanitize_value(&mut value);
3061                        textinput.set_content(value);
3062                        self.upcast::<Node>().dirty(NodeDamage::Other);
3063
3064                        // Steps 7-9
3065                        if !previously_selectable && self.selection_api_applies() {
3066                            textinput.clear_selection_to_limit(Direction::Backward);
3067                        }
3068                    },
3069                    AttributeMutation::Removed => {
3070                        if self.input_type() == InputType::Radio {
3071                            broadcast_radio_checked(self, self.radio_group_name().as_ref(), can_gc);
3072                        }
3073                        self.input_type.set(InputType::default());
3074                        let el = self.upcast::<Element>();
3075
3076                        let read_write = !(self.ReadOnly() || el.disabled_state());
3077                        el.set_read_write_state(read_write);
3078                    },
3079                }
3080
3081                self.update_placeholder_shown_state();
3082                self.update_text_shadow_tree_placeholder(can_gc);
3083            },
3084            // FIXME(stevennovaryo): This is only reachable by Default and DefaultOn value mode. While others
3085            //                       are being handled in [Self::SetValue]. Should we merge this two together?
3086            local_name!("value") if !self.value_dirty.get() => {
3087                let value = mutation.new_value(attr).map(|value| (**value).to_owned());
3088                let mut value = value.map_or(DOMString::new(), DOMString::from);
3089
3090                self.sanitize_value(&mut value);
3091                self.textinput.borrow_mut().set_content(value);
3092                self.update_placeholder_shown_state();
3093
3094                self.upcast::<Node>().dirty(NodeDamage::Other);
3095            },
3096            local_name!("name") if self.input_type() == InputType::Radio => {
3097                self.radio_group_updated(
3098                    mutation.new_value(attr).as_ref().map(|name| name.as_atom()),
3099                    can_gc,
3100                );
3101            },
3102            local_name!("maxlength") => match *attr.value() {
3103                AttrValue::Int(_, value) => {
3104                    let mut textinput = self.textinput.borrow_mut();
3105
3106                    if value < 0 {
3107                        textinput.set_max_length(None);
3108                    } else {
3109                        textinput.set_max_length(Some(UTF16CodeUnits(value as usize)))
3110                    }
3111                },
3112                _ => panic!("Expected an AttrValue::Int"),
3113            },
3114            local_name!("minlength") => match *attr.value() {
3115                AttrValue::Int(_, value) => {
3116                    let mut textinput = self.textinput.borrow_mut();
3117
3118                    if value < 0 {
3119                        textinput.set_min_length(None);
3120                    } else {
3121                        textinput.set_min_length(Some(UTF16CodeUnits(value as usize)))
3122                    }
3123                },
3124                _ => panic!("Expected an AttrValue::Int"),
3125            },
3126            local_name!("placeholder") => {
3127                {
3128                    let mut placeholder = self.placeholder.borrow_mut();
3129                    placeholder.clear();
3130                    if let AttributeMutation::Set(..) = mutation {
3131                        placeholder
3132                            .extend(attr.value().chars().filter(|&c| c != '\n' && c != '\r'));
3133                    }
3134                }
3135                self.update_placeholder_shown_state();
3136                self.update_text_shadow_tree_placeholder(can_gc);
3137            },
3138            local_name!("readonly") => {
3139                if self.input_type().is_textual() {
3140                    let el = self.upcast::<Element>();
3141                    match mutation {
3142                        AttributeMutation::Set(..) => {
3143                            el.set_read_write_state(false);
3144                        },
3145                        AttributeMutation::Removed => {
3146                            el.set_read_write_state(!el.disabled_state());
3147                        },
3148                    }
3149                }
3150            },
3151            local_name!("form") => {
3152                self.form_attribute_mutated(mutation, can_gc);
3153            },
3154            _ => {},
3155        }
3156
3157        self.value_changed(can_gc);
3158
3159        if could_have_had_embedder_control && !self.may_have_embedder_control() {
3160            self.owner_document()
3161                .embedder_controls()
3162                .hide_embedder_control(self.upcast());
3163        }
3164    }
3165
3166    fn parse_plain_attribute(&self, name: &LocalName, value: DOMString) -> AttrValue {
3167        match *name {
3168            local_name!("accept") => AttrValue::from_comma_separated_tokenlist(value.into()),
3169            local_name!("size") => AttrValue::from_limited_u32(value.into(), DEFAULT_INPUT_SIZE),
3170            local_name!("type") => AttrValue::from_atomic(value.into()),
3171            local_name!("maxlength") => {
3172                AttrValue::from_limited_i32(value.into(), DEFAULT_MAX_LENGTH)
3173            },
3174            local_name!("minlength") => {
3175                AttrValue::from_limited_i32(value.into(), DEFAULT_MIN_LENGTH)
3176            },
3177            _ => self
3178                .super_type()
3179                .unwrap()
3180                .parse_plain_attribute(name, value),
3181        }
3182    }
3183
3184    fn bind_to_tree(&self, context: &BindContext, can_gc: CanGc) {
3185        if let Some(s) = self.super_type() {
3186            s.bind_to_tree(context, can_gc);
3187        }
3188        self.upcast::<Element>()
3189            .check_ancestors_disabled_state_for_form_control();
3190
3191        if self.input_type() == InputType::Radio {
3192            self.radio_group_updated(self.radio_group_name().as_ref(), can_gc);
3193        }
3194
3195        self.value_changed(can_gc);
3196    }
3197
3198    fn unbind_from_tree(&self, context: &UnbindContext, can_gc: CanGc) {
3199        let form_owner = self.form_owner();
3200
3201        self.super_type().unwrap().unbind_from_tree(context, can_gc);
3202
3203        let node = self.upcast::<Node>();
3204        let el = self.upcast::<Element>();
3205        if node
3206            .ancestors()
3207            .any(|ancestor| ancestor.is::<HTMLFieldSetElement>())
3208        {
3209            el.check_ancestors_disabled_state_for_form_control();
3210        } else {
3211            el.check_disabled_attribute();
3212        }
3213
3214        if self.input_type() == InputType::Radio {
3215            let root = context.parent.GetRootNode(&GetRootNodeOptions::empty());
3216            for r in radio_group_iter(
3217                self,
3218                self.radio_group_name().as_ref(),
3219                form_owner.as_deref(),
3220                &root,
3221            ) {
3222                r.validity_state(can_gc)
3223                    .perform_validation_and_update(ValidationFlags::all(), can_gc);
3224            }
3225        }
3226
3227        self.validity_state(can_gc)
3228            .perform_validation_and_update(ValidationFlags::all(), can_gc);
3229
3230        if self.input_type() == InputType::Color {
3231            self.owner_document()
3232                .embedder_controls()
3233                .hide_embedder_control(self.upcast());
3234        }
3235    }
3236
3237    // This represents behavior for which the UIEvents spec and the
3238    // DOM/HTML specs are out of sync.
3239    // Compare:
3240    // https://w3c.github.io/uievents/#default-action
3241    /// <https://dom.spec.whatwg.org/#action-versus-occurance>
3242    fn handle_event(&self, event: &Event, can_gc: CanGc) {
3243        if let Some(s) = self.super_type() {
3244            s.handle_event(event, can_gc);
3245        }
3246
3247        if event.type_() == atom!("click") && !event.DefaultPrevented() {
3248            // WHATWG-specified activation behaviors are handled elsewhere;
3249            // this is for all the other things a UI click might do
3250
3251            // TODO(#10083): set the editing position for text inputs
3252
3253            if self.input_type().is_textual_or_password() &&
3254                // Check if we display a placeholder. Layout doesn't know about this.
3255                !self.textinput.borrow().is_empty()
3256            {
3257                if let Some(mouse_event) = event.downcast::<MouseEvent>() {
3258                    // dispatch_key_event (document.rs) triggers a click event when releasing
3259                    // the space key. There's no nice way to catch this so let's use this for
3260                    // now.
3261                    if let Some(point_in_target) = mouse_event.point_in_target() {
3262                        let window = self.owner_window();
3263
3264                        // Position the caret at the click position or at the end of the current value.
3265                        let edit_point_index = window
3266                            .text_index_query(self.upcast::<Node>(), point_in_target.to_untyped())
3267                            .unwrap_or_else(|| self.textinput.borrow().char_count());
3268                        self.textinput.borrow_mut().clear_selection();
3269                        self.textinput
3270                            .borrow_mut()
3271                            .set_edit_point_index(edit_point_index);
3272                        self.upcast::<Node>().dirty(NodeDamage::Other);
3273                        event.PreventDefault();
3274                    }
3275                }
3276            }
3277        } else if event.type_() == atom!("keydown") &&
3278            !event.DefaultPrevented() &&
3279            self.input_type().is_textual_or_password()
3280        {
3281            if let Some(keyevent) = event.downcast::<KeyboardEvent>() {
3282                // This can't be inlined, as holding on to textinput.borrow_mut()
3283                // during self.implicit_submission will cause a panic.
3284                let action = self.textinput.borrow_mut().handle_keydown(keyevent);
3285                self.handle_key_reaction(action, event, can_gc);
3286            }
3287        } else if event.type_() == atom!("keypress") &&
3288            !event.DefaultPrevented() &&
3289            self.input_type().is_textual_or_password()
3290        {
3291            // keypress should be deprecated and replaced by beforeinput.
3292            // keypress was supposed to fire "blur" and "focus" events
3293            // but already done in `document.rs`
3294        } else if (event.type_() == atom!("compositionstart") ||
3295            event.type_() == atom!("compositionupdate") ||
3296            event.type_() == atom!("compositionend")) &&
3297            self.input_type().is_textual_or_password()
3298        {
3299            if let Some(compositionevent) = event.downcast::<CompositionEvent>() {
3300                if event.type_() == atom!("compositionend") {
3301                    let action = self
3302                        .textinput
3303                        .borrow_mut()
3304                        .handle_compositionend(compositionevent);
3305                    self.handle_key_reaction(action, event, can_gc);
3306                    self.upcast::<Node>().dirty(NodeDamage::Other);
3307                    self.update_placeholder_shown_state();
3308                } else if event.type_() == atom!("compositionupdate") {
3309                    let action = self
3310                        .textinput
3311                        .borrow_mut()
3312                        .handle_compositionupdate(compositionevent);
3313                    self.handle_key_reaction(action, event, can_gc);
3314                    self.upcast::<Node>().dirty(NodeDamage::Other);
3315                    self.update_placeholder_shown_state();
3316                } else if event.type_() == atom!("compositionstart") {
3317                    // Update placeholder state when composition starts
3318                    self.update_placeholder_shown_state();
3319                }
3320                event.mark_as_handled();
3321            }
3322        } else if let Some(clipboard_event) = event.downcast::<ClipboardEvent>() {
3323            let reaction = self
3324                .textinput
3325                .borrow_mut()
3326                .handle_clipboard_event(clipboard_event);
3327            let flags = reaction.flags;
3328            if flags.contains(ClipboardEventFlags::FireClipboardChangedEvent) {
3329                self.owner_document().event_handler().fire_clipboard_event(
3330                    None,
3331                    ClipboardEventType::Change,
3332                    can_gc,
3333                );
3334            }
3335            if flags.contains(ClipboardEventFlags::QueueInputEvent) {
3336                self.textinput.borrow().queue_input_event(
3337                    self.upcast(),
3338                    reaction.text,
3339                    IsComposing::NotComposing,
3340                    reaction.input_type,
3341                );
3342            }
3343            if !flags.is_empty() {
3344                self.upcast::<Node>().dirty(NodeDamage::ContentOrHeritage);
3345            }
3346        } else if let Some(event) = event.downcast::<FocusEvent>() {
3347            if *event.upcast::<Event>().type_() == *"blur" {
3348                self.owner_document()
3349                    .embedder_controls()
3350                    .hide_embedder_control(self.upcast());
3351            }
3352            if *event.upcast::<Event>().type_() == *"focus" {
3353                self.handle_focus();
3354            }
3355        }
3356
3357        self.value_changed(can_gc);
3358    }
3359
3360    /// <https://html.spec.whatwg.org/multipage/#the-input-element%3Aconcept-node-clone-ext>
3361    fn cloning_steps(
3362        &self,
3363        copy: &Node,
3364        maybe_doc: Option<&Document>,
3365        clone_children: CloneChildrenFlag,
3366        can_gc: CanGc,
3367    ) {
3368        if let Some(s) = self.super_type() {
3369            s.cloning_steps(copy, maybe_doc, clone_children, can_gc);
3370        }
3371        let elem = copy.downcast::<HTMLInputElement>().unwrap();
3372        elem.value_dirty.set(self.value_dirty.get());
3373        elem.checked_changed.set(self.checked_changed.get());
3374        elem.upcast::<Element>()
3375            .set_state(ElementState::CHECKED, self.Checked());
3376        elem.textinput
3377            .borrow_mut()
3378            .set_content(self.textinput.borrow().get_content());
3379        self.value_changed(can_gc);
3380    }
3381}
3382
3383impl FormControl for HTMLInputElement {
3384    fn form_owner(&self) -> Option<DomRoot<HTMLFormElement>> {
3385        self.form_owner.get()
3386    }
3387
3388    fn set_form_owner(&self, form: Option<&HTMLFormElement>) {
3389        self.form_owner.set(form);
3390    }
3391
3392    fn to_element(&self) -> &Element {
3393        self.upcast::<Element>()
3394    }
3395}
3396
3397impl Validatable for HTMLInputElement {
3398    fn as_element(&self) -> &Element {
3399        self.upcast()
3400    }
3401
3402    fn validity_state(&self, can_gc: CanGc) -> DomRoot<ValidityState> {
3403        self.validity_state
3404            .or_init(|| ValidityState::new(&self.owner_window(), self.upcast(), can_gc))
3405    }
3406
3407    fn is_instance_validatable(&self) -> bool {
3408        // https://html.spec.whatwg.org/multipage/#hidden-state-(type%3Dhidden)%3Abarred-from-constraint-validation
3409        // https://html.spec.whatwg.org/multipage/#button-state-(type%3Dbutton)%3Abarred-from-constraint-validation
3410        // https://html.spec.whatwg.org/multipage/#reset-button-state-(type%3Dreset)%3Abarred-from-constraint-validation
3411        // https://html.spec.whatwg.org/multipage/#enabling-and-disabling-form-controls%3A-the-disabled-attribute%3Abarred-from-constraint-validation
3412        // https://html.spec.whatwg.org/multipage/#the-readonly-attribute%3Abarred-from-constraint-validation
3413        // https://html.spec.whatwg.org/multipage/#the-datalist-element%3Abarred-from-constraint-validation
3414        match self.input_type() {
3415            InputType::Hidden | InputType::Button | InputType::Reset => false,
3416            _ => {
3417                !(self.upcast::<Element>().disabled_state() ||
3418                    self.ReadOnly() ||
3419                    is_barred_by_datalist_ancestor(self.upcast()))
3420            },
3421        }
3422    }
3423
3424    fn perform_validation(
3425        &self,
3426        validate_flags: ValidationFlags,
3427        can_gc: CanGc,
3428    ) -> ValidationFlags {
3429        let mut failed_flags = ValidationFlags::empty();
3430        let value = self.Value();
3431
3432        if validate_flags.contains(ValidationFlags::VALUE_MISSING) &&
3433            self.suffers_from_being_missing(&value)
3434        {
3435            failed_flags.insert(ValidationFlags::VALUE_MISSING);
3436        }
3437
3438        if validate_flags.contains(ValidationFlags::TYPE_MISMATCH) &&
3439            self.suffers_from_type_mismatch(&value)
3440        {
3441            failed_flags.insert(ValidationFlags::TYPE_MISMATCH);
3442        }
3443
3444        if validate_flags.contains(ValidationFlags::PATTERN_MISMATCH) &&
3445            self.suffers_from_pattern_mismatch(&value, can_gc)
3446        {
3447            failed_flags.insert(ValidationFlags::PATTERN_MISMATCH);
3448        }
3449
3450        if validate_flags.contains(ValidationFlags::BAD_INPUT) &&
3451            self.suffers_from_bad_input(&value)
3452        {
3453            failed_flags.insert(ValidationFlags::BAD_INPUT);
3454        }
3455
3456        if validate_flags.intersects(ValidationFlags::TOO_LONG | ValidationFlags::TOO_SHORT) {
3457            failed_flags |= self.suffers_from_length_issues(&value);
3458        }
3459
3460        if validate_flags.intersects(
3461            ValidationFlags::RANGE_UNDERFLOW |
3462                ValidationFlags::RANGE_OVERFLOW |
3463                ValidationFlags::STEP_MISMATCH,
3464        ) {
3465            failed_flags |= self.suffers_from_range_issues(&value);
3466        }
3467
3468        failed_flags & validate_flags
3469    }
3470}
3471
3472impl Activatable for HTMLInputElement {
3473    fn as_element(&self) -> &Element {
3474        self.upcast()
3475    }
3476
3477    fn is_instance_activatable(&self) -> bool {
3478        match self.input_type() {
3479            // https://html.spec.whatwg.org/multipage/#submit-button-state-(type=submit):input-activation-behavior
3480            // https://html.spec.whatwg.org/multipage/#reset-button-state-(type=reset):input-activation-behavior
3481            // https://html.spec.whatwg.org/multipage/#file-upload-state-(type=file):input-activation-behavior
3482            // https://html.spec.whatwg.org/multipage/#image-button-state-(type=image):input-activation-behavior
3483            InputType::Submit | InputType::Reset | InputType::File | InputType::Image => {
3484                self.is_mutable()
3485            },
3486            // https://html.spec.whatwg.org/multipage/#checkbox-state-(type=checkbox):input-activation-behavior
3487            // https://html.spec.whatwg.org/multipage/#radio-button-state-(type=radio):input-activation-behavior
3488            // https://html.spec.whatwg.org/multipage/#color-state-(type=color):input-activation-behavior
3489            InputType::Checkbox | InputType::Radio | InputType::Color => true,
3490            _ => false,
3491        }
3492    }
3493
3494    /// <https://dom.spec.whatwg.org/#eventtarget-legacy-pre-activation-behavior>
3495    fn legacy_pre_activation_behavior(&self, can_gc: CanGc) -> Option<InputActivationState> {
3496        let ty = self.input_type();
3497        let activation_state = match ty {
3498            InputType::Checkbox => {
3499                let was_checked = self.Checked();
3500                let was_indeterminate = self.Indeterminate();
3501                self.SetIndeterminate(false);
3502                self.SetChecked(!was_checked, can_gc);
3503                Some(InputActivationState {
3504                    checked: was_checked,
3505                    indeterminate: was_indeterminate,
3506                    checked_radio: None,
3507                    old_type: InputType::Checkbox,
3508                })
3509            },
3510            InputType::Radio => {
3511                let root = self
3512                    .upcast::<Node>()
3513                    .GetRootNode(&GetRootNodeOptions::empty());
3514                let form_owner = self.form_owner();
3515                let checked_member = radio_group_iter(
3516                    self,
3517                    self.radio_group_name().as_ref(),
3518                    form_owner.as_deref(),
3519                    &root,
3520                )
3521                .find(|r| r.Checked());
3522                let was_checked = self.Checked();
3523                self.SetChecked(true, can_gc);
3524                Some(InputActivationState {
3525                    checked: was_checked,
3526                    indeterminate: false,
3527                    checked_radio: checked_member.as_deref().map(DomRoot::from_ref),
3528                    old_type: InputType::Radio,
3529                })
3530            },
3531            _ => None,
3532        };
3533
3534        if activation_state.is_some() {
3535            self.value_changed(can_gc);
3536        }
3537
3538        activation_state
3539    }
3540
3541    /// <https://dom.spec.whatwg.org/#eventtarget-legacy-canceled-activation-behavior>
3542    fn legacy_canceled_activation_behavior(
3543        &self,
3544        cache: Option<InputActivationState>,
3545        can_gc: CanGc,
3546    ) {
3547        // Step 1
3548        let ty = self.input_type();
3549        let cache = match cache {
3550            Some(cache) => {
3551                if cache.old_type != ty {
3552                    // Type changed, abandon ship
3553                    // https://www.w3.org/Bugs/Public/show_bug.cgi?id=27414
3554                    return;
3555                }
3556                cache
3557            },
3558            None => {
3559                return;
3560            },
3561        };
3562
3563        match ty {
3564            // Step 2
3565            InputType::Checkbox => {
3566                self.SetIndeterminate(cache.indeterminate);
3567                self.SetChecked(cache.checked, can_gc);
3568            },
3569            // Step 3
3570            InputType::Radio => {
3571                if let Some(ref o) = cache.checked_radio {
3572                    let tree_root = self
3573                        .upcast::<Node>()
3574                        .GetRootNode(&GetRootNodeOptions::empty());
3575                    // Avoiding iterating through the whole tree here, instead
3576                    // we can check if the conditions for radio group siblings apply
3577                    if in_same_group(
3578                        o,
3579                        self.form_owner().as_deref(),
3580                        self.radio_group_name().as_ref(),
3581                        Some(&*tree_root),
3582                    ) {
3583                        o.SetChecked(true, can_gc);
3584                    } else {
3585                        self.SetChecked(false, can_gc);
3586                    }
3587                } else {
3588                    self.SetChecked(false, can_gc);
3589                }
3590            },
3591            _ => (),
3592        }
3593
3594        self.value_changed(can_gc);
3595    }
3596
3597    /// <https://html.spec.whatwg.org/multipage/#input-activation-behavior>
3598    fn activation_behavior(&self, _event: &Event, _target: &EventTarget, can_gc: CanGc) {
3599        match self.input_type() {
3600            // https://html.spec.whatwg.org/multipage/#submit-button-state-(type=submit):activation-behavior
3601            // https://html.spec.whatwg.org/multipage/#submit-button-state-(type=image):activation-behavior
3602            InputType::Submit | InputType::Image => {
3603                // Step 1: If the element does not have a form owner, then return.
3604                if let Some(form_owner) = self.form_owner() {
3605                    // Step 2: If the element's node document is not fully active, then return.
3606                    let document = self.owner_document();
3607
3608                    if !document.is_fully_active() {
3609                        return;
3610                    }
3611
3612                    // Step 3: Submit the element's form owner from the element with userInvolvement
3613                    // set to event's user navigation involvement.
3614                    form_owner.submit(
3615                        SubmittedFrom::NotFromForm,
3616                        FormSubmitterElement::Input(self),
3617                        can_gc,
3618                    )
3619                }
3620            },
3621            InputType::Reset => {
3622                // https://html.spec.whatwg.org/multipage/#reset-button-state-(type=reset):activation-behavior
3623                // Step 1: If the element does not have a form owner, then return.
3624                if let Some(form_owner) = self.form_owner() {
3625                    let document = self.owner_document();
3626
3627                    // Step 2: If the element's node document is not fully active, then return.
3628                    if !document.is_fully_active() {
3629                        return;
3630                    }
3631
3632                    // Step 3: Reset the form owner from the element.
3633                    form_owner.reset(ResetFrom::NotFromForm, can_gc);
3634                }
3635            },
3636            // https://html.spec.whatwg.org/multipage/#checkbox-state-(type=checkbox):activation-behavior
3637            // https://html.spec.whatwg.org/multipage/#radio-button-state-(type=radio):activation-behavior
3638            InputType::Checkbox | InputType::Radio => {
3639                // Step 1: If the element is not connected, then return.
3640                if !self.upcast::<Node>().is_connected() {
3641                    return;
3642                }
3643
3644                let target = self.upcast::<EventTarget>();
3645
3646                // Step 2: Fire an event named input at the element with the bubbles and composed
3647                // attributes initialized to true.
3648                target.fire_event_with_params(
3649                    atom!("input"),
3650                    EventBubbles::Bubbles,
3651                    EventCancelable::NotCancelable,
3652                    EventComposed::Composed,
3653                    can_gc,
3654                );
3655
3656                // Step 3: Fire an event named change at the element with the bubbles attribute
3657                // initialized to true.
3658                target.fire_bubbling_event(atom!("change"), can_gc);
3659            },
3660            // https://html.spec.whatwg.org/multipage/#file-upload-state-(type=file):input-activation-behavior
3661            InputType::File => {
3662                self.select_files(None);
3663            },
3664            // https://html.spec.whatwg.org/multipage/#color-state-(type=color):input-activation-behavior
3665            InputType::Color => {
3666                self.show_the_picker_if_applicable();
3667            },
3668            _ => (),
3669        }
3670    }
3671}
3672
3673/// <https://html.spec.whatwg.org/multipage/#attr-input-accept>
3674fn filter_from_accept(s: &DOMString) -> Vec<FilterPattern> {
3675    let mut filter = vec![];
3676    for p in split_commas(&s.str()) {
3677        let p = p.trim();
3678        if let Some('.') = p.chars().next() {
3679            filter.push(FilterPattern(p[1..].to_string()));
3680        } else if let Some(exts) = mime_guess::get_mime_extensions_str(p) {
3681            for ext in exts {
3682                filter.push(FilterPattern(ext.to_string()));
3683            }
3684        }
3685    }
3686
3687    filter
3688}
3689
3690fn round_halves_positive(n: f64) -> f64 {
3691    // WHATWG specs about input steps say to round to the nearest step,
3692    // rounding halves always to positive infinity.
3693    // This differs from Rust's .round() in the case of -X.5.
3694    if n.fract() == -0.5 {
3695        n.ceil()
3696    } else {
3697        n.round()
3698    }
3699}
3700
3701/// This is used to compile JS-compatible regex provided in pattern attribute
3702/// that matches only the entirety of string.
3703/// <https://html.spec.whatwg.org/multipage/#compiled-pattern-regular-expression>
3704fn compile_pattern(
3705    cx: SafeJSContext,
3706    pattern_str: &str,
3707    out_regex: MutableHandleObject,
3708    can_gc: CanGc,
3709) -> bool {
3710    // First check if pattern compiles...
3711    if check_js_regex_syntax(cx, pattern_str, can_gc) {
3712        // ...and if it does make pattern that matches only the entirety of string
3713        let pattern_str = format!("^(?:{})$", pattern_str);
3714        let flags = RegExpFlags {
3715            flags_: RegExpFlag_UnicodeSets,
3716        };
3717        new_js_regex(cx, &pattern_str, flags, out_regex, can_gc)
3718    } else {
3719        false
3720    }
3721}
3722
3723#[expect(unsafe_code)]
3724/// Check if the pattern by itself is valid first, and not that it only becomes
3725/// valid once we add ^(?: and )$.
3726fn check_js_regex_syntax(cx: SafeJSContext, pattern: &str, _can_gc: CanGc) -> bool {
3727    let pattern: Vec<u16> = pattern.encode_utf16().collect();
3728    unsafe {
3729        rooted!(in(*cx) let mut exception = UndefinedValue());
3730
3731        let valid = CheckRegExpSyntax(
3732            *cx,
3733            pattern.as_ptr(),
3734            pattern.len(),
3735            RegExpFlags {
3736                flags_: RegExpFlag_UnicodeSets,
3737            },
3738            exception.handle_mut(),
3739        );
3740
3741        if !valid {
3742            JS_ClearPendingException(*cx);
3743            return false;
3744        }
3745
3746        // TODO(cybai): report `exception` to devtools
3747        // exception will be `undefined` if the regex is valid
3748        exception.is_undefined()
3749    }
3750}
3751
3752#[expect(unsafe_code)]
3753pub(crate) fn new_js_regex(
3754    cx: SafeJSContext,
3755    pattern: &str,
3756    flags: RegExpFlags,
3757    mut out_regex: MutableHandleObject,
3758    _can_gc: CanGc,
3759) -> bool {
3760    let pattern: Vec<u16> = pattern.encode_utf16().collect();
3761    unsafe {
3762        out_regex.set(NewUCRegExpObject(
3763            *cx,
3764            pattern.as_ptr(),
3765            pattern.len(),
3766            flags,
3767        ));
3768        if out_regex.is_null() {
3769            JS_ClearPendingException(*cx);
3770            return false;
3771        }
3772    }
3773    true
3774}
3775
3776#[expect(unsafe_code)]
3777fn matches_js_regex(
3778    cx: SafeJSContext,
3779    regex_obj: HandleObject,
3780    value: &str,
3781    _can_gc: CanGc,
3782) -> Result<bool, ()> {
3783    let mut value: Vec<u16> = value.encode_utf16().collect();
3784
3785    unsafe {
3786        let mut is_regex = false;
3787        assert!(ObjectIsRegExp(*cx, regex_obj, &mut is_regex));
3788        assert!(is_regex);
3789
3790        rooted!(in(*cx) let mut rval = UndefinedValue());
3791        let mut index = 0;
3792
3793        let ok = ExecuteRegExpNoStatics(
3794            *cx,
3795            regex_obj,
3796            value.as_mut_ptr(),
3797            value.len(),
3798            &mut index,
3799            true,
3800            rval.handle_mut(),
3801        );
3802
3803        if ok {
3804            Ok(!rval.is_null())
3805        } else {
3806            JS_ClearPendingException(*cx);
3807            Err(())
3808        }
3809    }
3810}
3811
3812/// When WebDriver asks the [`HTMLInputElement`] to do some asynchronous actions, such
3813/// as selecting files, this stores the details necessary to complete the response when
3814/// the action is complete.
3815#[derive(MallocSizeOf)]
3816struct PendingWebDriverResponse {
3817    /// An [`IpcSender`] to use to send the reply when the response is ready.
3818    response_sender: IpcSender<Result<bool, ErrorStatus>>,
3819    /// The number of files expected to be selected when the selection process is done.
3820    expected_file_count: usize,
3821}
3822
3823impl PendingWebDriverResponse {
3824    fn finish(self, number_files_selected: usize) {
3825        if number_files_selected == self.expected_file_count {
3826            let _ = self.response_sender.send(Ok(false));
3827        } else {
3828            // If not all files are found the WebDriver specification says to return
3829            // the InvalidArgument error.
3830            let _ = self.response_sender.send(Err(ErrorStatus::InvalidArgument));
3831        }
3832    }
3833}