script/dom/html/
htmlinputelement.rs

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