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