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