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