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