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