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