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