1use std::borrow::Cow;
6use std::cell::Cell;
7use std::cmp::Ordering;
8use std::ops::Range;
9use std::path::PathBuf;
10use std::ptr::NonNull;
11use std::str::FromStr;
12use std::{f64, ptr};
13
14use base::generic_channel;
15use dom_struct::dom_struct;
16use embedder_traits::{
17 EmbedderMsg, FilterPattern, FormControl as EmbedderFormControl, InputMethodType, RgbColor,
18};
19use encoding_rs::Encoding;
20use euclid::{Point2D, Rect, Size2D};
21use html5ever::{LocalName, Prefix, local_name, ns};
22use js::jsapi::{
23 ClippedTime, DateGetMsecSinceEpoch, Handle, JS_ClearPendingException, JSObject, NewDateObject,
24 NewUCRegExpObject, ObjectIsDate, RegExpFlag_UnicodeSets, RegExpFlags,
25};
26use js::jsval::UndefinedValue;
27use js::rust::wrappers::{CheckRegExpSyntax, ExecuteRegExpNoStatics, ObjectIsRegExp};
28use js::rust::{HandleObject, MutableHandleObject};
29use net_traits::blob_url_store::get_blob_origin;
30use net_traits::filemanager_thread::{FileManagerResult, FileManagerThreadMsg};
31use net_traits::{CoreResourceMsg, IpcSend};
32use script_bindings::codegen::GenericBindings::CharacterDataBinding::CharacterDataMethods;
33use script_bindings::codegen::GenericBindings::DocumentBinding::DocumentMethods;
34use servo_config::pref;
35use style::attr::AttrValue;
36use style::selector_parser::PseudoElement;
37use style::str::{split_commas, str_join};
38use stylo_atoms::Atom;
39use stylo_dom::ElementState;
40use time::{Month, OffsetDateTime, Time};
41use unicode_bidi::{BidiClass, bidi_class};
42use url::Url;
43use webrender_api::units::DeviceIntRect;
44
45use crate::clipboard_provider::EmbedderClipboardProvider;
46use crate::dom::activation::Activatable;
47use crate::dom::attr::Attr;
48use crate::dom::bindings::cell::{DomRefCell, Ref};
49use crate::dom::bindings::codegen::Bindings::ElementBinding::ElementMethods;
50use crate::dom::bindings::codegen::Bindings::EventBinding::EventMethods;
51use crate::dom::bindings::codegen::Bindings::FileListBinding::FileListMethods;
52use crate::dom::bindings::codegen::Bindings::HTMLFormElementBinding::SelectionMode;
53use crate::dom::bindings::codegen::Bindings::HTMLInputElementBinding::HTMLInputElementMethods;
54use crate::dom::bindings::codegen::Bindings::NodeBinding::{GetRootNodeOptions, NodeMethods};
55use crate::dom::bindings::error::{Error, ErrorResult};
56use crate::dom::bindings::inheritance::Castable;
57use crate::dom::bindings::reflector::DomGlobal;
58use crate::dom::bindings::root::{Dom, DomRoot, LayoutDom, MutNullableDom};
59use crate::dom::bindings::str::{DOMString, FromInputValueString, ToInputValueString, USVString};
60use crate::dom::clipboardevent::ClipboardEvent;
61use crate::dom::compositionevent::CompositionEvent;
62use crate::dom::document::Document;
63use crate::dom::element::{AttributeMutation, Element, LayoutElementHelpers};
64use crate::dom::event::{Event, EventBubbles, EventCancelable};
65use crate::dom::eventtarget::EventTarget;
66use crate::dom::file::File;
67use crate::dom::filelist::{FileList, LayoutFileListHelpers};
68use crate::dom::globalscope::GlobalScope;
69use crate::dom::html::htmldatalistelement::HTMLDataListElement;
70use crate::dom::html::htmldivelement::HTMLDivElement;
71use crate::dom::html::htmlelement::HTMLElement;
72use crate::dom::html::htmlfieldsetelement::HTMLFieldSetElement;
73use crate::dom::html::htmlformelement::{
74 FormControl, FormDatum, FormDatumValue, FormSubmitterElement, HTMLFormElement, ResetFrom,
75 SubmittedFrom,
76};
77use crate::dom::keyboardevent::KeyboardEvent;
78use crate::dom::mouseevent::MouseEvent;
79use crate::dom::node::{
80 BindContext, CloneChildrenFlag, Node, NodeDamage, NodeTraits, ShadowIncluding, UnbindContext,
81};
82use crate::dom::nodelist::NodeList;
83use crate::dom::shadowroot::ShadowRoot;
84use crate::dom::textcontrol::{TextControlElement, TextControlSelection};
85use crate::dom::types::CharacterData;
86use crate::dom::validation::{Validatable, is_barred_by_datalist_ancestor};
87use crate::dom::validitystate::{ValidationFlags, ValidityState};
88use crate::dom::virtualmethods::VirtualMethods;
89use crate::realms::enter_realm;
90use crate::script_runtime::{CanGc, JSContext as SafeJSContext};
91use crate::textinput::KeyReaction::{
92 DispatchInput, Nothing, RedrawSelection, TriggerDefaultAction,
93};
94use crate::textinput::Lines::Single;
95use crate::textinput::{
96 ClipboardEventReaction, Direction, SelectionDirection, TextInput, UTF8Bytes, UTF16CodeUnits,
97};
98
99const DEFAULT_SUBMIT_VALUE: &str = "Submit";
100const DEFAULT_RESET_VALUE: &str = "Reset";
101const PASSWORD_REPLACEMENT_CHAR: char = '●';
102const DEFAULT_FILE_INPUT_VALUE: &str = "No file chosen";
103
104#[derive(Clone, JSTraceable, MallocSizeOf)]
105#[cfg_attr(crown, crown::unrooted_must_root_lint::must_root)]
106struct InputTypeTextShadowTree {
126 inner_container: Dom<HTMLDivElement>,
127 text_container: Dom<HTMLDivElement>,
128 placeholder_container: DomRefCell<Option<Dom<HTMLDivElement>>>,
129}
130
131impl InputTypeTextShadowTree {
132 fn init_placeholder_container_if_necessary(&self, host: &HTMLInputElement, can_gc: CanGc) {
135 if self.placeholder_container.borrow().is_some() || host.placeholder.borrow().is_empty() {
138 return;
139 }
140
141 *self.placeholder_container.borrow_mut() = Some(
142 create_ua_widget_div_with_text_node(
143 &host.owner_document(),
144 self.inner_container.upcast::<Node>(),
145 PseudoElement::Placeholder,
146 true,
147 can_gc,
148 )
149 .as_traced(),
150 );
151 }
152}
153
154#[derive(Clone, JSTraceable, MallocSizeOf)]
155#[cfg_attr(crown, crown::unrooted_must_root_lint::must_root)]
156struct InputTypeColorShadowTree {
161 color_value: Dom<HTMLDivElement>,
162}
163
164#[derive(Clone, JSTraceable, MallocSizeOf)]
165#[cfg_attr(crown, crown::unrooted_must_root_lint::must_root)]
166#[non_exhaustive]
167enum ShadowTree {
168 Text(InputTypeTextShadowTree),
169 Color(InputTypeColorShadowTree),
170 }
172
173fn create_ua_widget_div_with_text_node(
176 document: &Document,
177 parent: &Node,
178 implemented_pseudo: PseudoElement,
179 as_first_child: bool,
180 can_gc: CanGc,
181) -> DomRoot<HTMLDivElement> {
182 let el = HTMLDivElement::new(local_name!("div"), None, document, None, can_gc);
183 parent
184 .upcast::<Node>()
185 .AppendChild(el.upcast::<Node>(), can_gc)
186 .unwrap();
187 el.upcast::<Node>()
188 .set_implemented_pseudo_element(implemented_pseudo);
189 let text_node = document.CreateTextNode("".into(), can_gc);
190
191 if !as_first_child {
192 el.upcast::<Node>()
193 .AppendChild(text_node.upcast::<Node>(), can_gc)
194 .unwrap();
195 } else {
196 el.upcast::<Node>()
197 .InsertBefore(
198 text_node.upcast::<Node>(),
199 el.upcast::<Node>().GetFirstChild().as_deref(),
200 can_gc,
201 )
202 .unwrap();
203 }
204 el
205}
206
207#[derive(Clone, Copy, Debug, Default, JSTraceable, PartialEq)]
209#[allow(dead_code)]
210#[derive(MallocSizeOf)]
211pub(crate) enum InputType {
212 Button,
214
215 Checkbox,
217
218 Color,
220
221 Date,
223
224 DatetimeLocal,
226
227 Email,
229
230 File,
232
233 Hidden,
235
236 Image,
238
239 Month,
241
242 Number,
244
245 Password,
247
248 Radio,
250
251 Range,
253
254 Reset,
256
257 Search,
259
260 Submit,
262
263 Tel,
265
266 #[default]
268 Text,
269
270 Time,
272
273 Url,
275
276 Week,
278}
279
280impl InputType {
281 pub(crate) fn is_textual(&self) -> bool {
286 matches!(
287 *self,
288 InputType::Date |
289 InputType::DatetimeLocal |
290 InputType::Email |
291 InputType::Hidden |
292 InputType::Month |
293 InputType::Number |
294 InputType::Range |
295 InputType::Search |
296 InputType::Tel |
297 InputType::Text |
298 InputType::Time |
299 InputType::Url |
300 InputType::Week
301 )
302 }
303
304 fn is_textual_or_password(&self) -> bool {
305 self.is_textual() || *self == InputType::Password
306 }
307
308 fn has_periodic_domain(&self) -> bool {
310 *self == InputType::Time
311 }
312
313 fn as_str(&self) -> &str {
314 match *self {
315 InputType::Button => "button",
316 InputType::Checkbox => "checkbox",
317 InputType::Color => "color",
318 InputType::Date => "date",
319 InputType::DatetimeLocal => "datetime-local",
320 InputType::Email => "email",
321 InputType::File => "file",
322 InputType::Hidden => "hidden",
323 InputType::Image => "image",
324 InputType::Month => "month",
325 InputType::Number => "number",
326 InputType::Password => "password",
327 InputType::Radio => "radio",
328 InputType::Range => "range",
329 InputType::Reset => "reset",
330 InputType::Search => "search",
331 InputType::Submit => "submit",
332 InputType::Tel => "tel",
333 InputType::Text => "text",
334 InputType::Time => "time",
335 InputType::Url => "url",
336 InputType::Week => "week",
337 }
338 }
339
340 pub(crate) fn as_ime_type(&self) -> Option<InputMethodType> {
341 match *self {
342 InputType::Color => Some(InputMethodType::Color),
343 InputType::Date => Some(InputMethodType::Date),
344 InputType::DatetimeLocal => Some(InputMethodType::DatetimeLocal),
345 InputType::Email => Some(InputMethodType::Email),
346 InputType::Month => Some(InputMethodType::Month),
347 InputType::Number => Some(InputMethodType::Number),
348 InputType::Password => Some(InputMethodType::Password),
349 InputType::Search => Some(InputMethodType::Search),
350 InputType::Tel => Some(InputMethodType::Tel),
351 InputType::Text => Some(InputMethodType::Text),
352 InputType::Time => Some(InputMethodType::Time),
353 InputType::Url => Some(InputMethodType::Url),
354 InputType::Week => Some(InputMethodType::Week),
355 _ => None,
356 }
357 }
358}
359
360impl From<&Atom> for InputType {
361 fn from(value: &Atom) -> InputType {
362 match value.to_ascii_lowercase() {
363 atom!("button") => InputType::Button,
364 atom!("checkbox") => InputType::Checkbox,
365 atom!("color") => InputType::Color,
366 atom!("date") => InputType::Date,
367 atom!("datetime-local") => InputType::DatetimeLocal,
368 atom!("email") => InputType::Email,
369 atom!("file") => InputType::File,
370 atom!("hidden") => InputType::Hidden,
371 atom!("image") => InputType::Image,
372 atom!("month") => InputType::Month,
373 atom!("number") => InputType::Number,
374 atom!("password") => InputType::Password,
375 atom!("radio") => InputType::Radio,
376 atom!("range") => InputType::Range,
377 atom!("reset") => InputType::Reset,
378 atom!("search") => InputType::Search,
379 atom!("submit") => InputType::Submit,
380 atom!("tel") => InputType::Tel,
381 atom!("text") => InputType::Text,
382 atom!("time") => InputType::Time,
383 atom!("url") => InputType::Url,
384 atom!("week") => InputType::Week,
385 _ => Self::default(),
386 }
387 }
388}
389
390#[derive(Debug, PartialEq)]
391enum ValueMode {
392 Value,
394
395 Default,
397
398 DefaultOn,
400
401 Filename,
403}
404
405#[derive(Debug, PartialEq)]
406enum StepDirection {
407 Up,
408 Down,
409}
410
411#[dom_struct]
412pub(crate) struct HTMLInputElement {
413 htmlelement: HTMLElement,
414 input_type: Cell<InputType>,
415
416 checked_changed: Cell<bool>,
418 placeholder: DomRefCell<DOMString>,
419 size: Cell<u32>,
420 maxlength: Cell<i32>,
421 minlength: Cell<i32>,
422 #[no_trace]
423 textinput: DomRefCell<TextInput<EmbedderClipboardProvider>>,
424 value_dirty: Cell<bool>,
426 sanitization_flag: Cell<bool>,
430
431 filelist: MutNullableDom<FileList>,
432 form_owner: MutNullableDom<HTMLFormElement>,
433 labels_node_list: MutNullableDom<NodeList>,
434 validity_state: MutNullableDom<ValidityState>,
435 shadow_tree: DomRefCell<Option<ShadowTree>>,
436}
437
438#[derive(JSTraceable)]
439pub(crate) struct InputActivationState {
440 indeterminate: bool,
441 checked: bool,
442 checked_radio: Option<DomRoot<HTMLInputElement>>,
443 old_type: InputType,
445 }
447
448static DEFAULT_INPUT_SIZE: u32 = 20;
449static DEFAULT_MAX_LENGTH: i32 = -1;
450static DEFAULT_MIN_LENGTH: i32 = -1;
451
452#[allow(non_snake_case)]
453impl HTMLInputElement {
454 fn new_inherited(
455 local_name: LocalName,
456 prefix: Option<Prefix>,
457 document: &Document,
458 ) -> HTMLInputElement {
459 let constellation_sender = document
460 .window()
461 .as_global_scope()
462 .script_to_constellation_chan()
463 .clone();
464 HTMLInputElement {
465 htmlelement: HTMLElement::new_inherited_with_state(
466 ElementState::ENABLED | ElementState::READWRITE,
467 local_name,
468 prefix,
469 document,
470 ),
471 input_type: Cell::new(Default::default()),
472 placeholder: DomRefCell::new(DOMString::new()),
473 checked_changed: Cell::new(false),
474 maxlength: Cell::new(DEFAULT_MAX_LENGTH),
475 minlength: Cell::new(DEFAULT_MIN_LENGTH),
476 size: Cell::new(DEFAULT_INPUT_SIZE),
477 textinput: DomRefCell::new(TextInput::new(
478 Single,
479 DOMString::new(),
480 EmbedderClipboardProvider {
481 constellation_sender,
482 webview_id: document.webview_id(),
483 },
484 None,
485 None,
486 SelectionDirection::None,
487 )),
488 value_dirty: Cell::new(false),
489 sanitization_flag: Cell::new(true),
490 filelist: MutNullableDom::new(None),
491 form_owner: Default::default(),
492 labels_node_list: MutNullableDom::new(None),
493 validity_state: Default::default(),
494 shadow_tree: Default::default(),
495 }
496 }
497
498 #[cfg_attr(crown, allow(crown::unrooted_must_root))]
499 pub(crate) fn new(
500 local_name: LocalName,
501 prefix: Option<Prefix>,
502 document: &Document,
503 proto: Option<HandleObject>,
504 can_gc: CanGc,
505 ) -> DomRoot<HTMLInputElement> {
506 Node::reflect_node_with_proto(
507 Box::new(HTMLInputElement::new_inherited(
508 local_name, prefix, document,
509 )),
510 document,
511 proto,
512 can_gc,
513 )
514 }
515
516 pub(crate) fn auto_directionality(&self) -> Option<String> {
517 match self.input_type() {
518 InputType::Text | InputType::Search | InputType::Url | InputType::Email => {
519 let value: String = self.Value().to_string();
520 Some(HTMLInputElement::directionality_from_value(&value))
521 },
522 _ => None,
523 }
524 }
525
526 pub(crate) fn directionality_from_value(value: &str) -> String {
527 if HTMLInputElement::is_first_strong_character_rtl(value) {
528 "rtl".to_owned()
529 } else {
530 "ltr".to_owned()
531 }
532 }
533
534 fn is_first_strong_character_rtl(value: &str) -> bool {
535 for ch in value.chars() {
536 return match bidi_class(ch) {
537 BidiClass::L => false,
538 BidiClass::AL => true,
539 BidiClass::R => true,
540 _ => continue,
541 };
542 }
543 false
544 }
545
546 fn value_mode(&self) -> ValueMode {
549 match self.input_type() {
550 InputType::Submit |
551 InputType::Reset |
552 InputType::Button |
553 InputType::Image |
554 InputType::Hidden => ValueMode::Default,
555
556 InputType::Checkbox | InputType::Radio => ValueMode::DefaultOn,
557
558 InputType::Color |
559 InputType::Date |
560 InputType::DatetimeLocal |
561 InputType::Email |
562 InputType::Month |
563 InputType::Number |
564 InputType::Password |
565 InputType::Range |
566 InputType::Search |
567 InputType::Tel |
568 InputType::Text |
569 InputType::Time |
570 InputType::Url |
571 InputType::Week => ValueMode::Value,
572
573 InputType::File => ValueMode::Filename,
574 }
575 }
576
577 #[inline]
578 pub(crate) fn input_type(&self) -> InputType {
579 self.input_type.get()
580 }
581
582 pub(crate) fn is_nontypeable(&self) -> bool {
584 matches!(
585 self.input_type(),
586 InputType::Button |
587 InputType::Checkbox |
588 InputType::Color |
589 InputType::File |
590 InputType::Hidden |
591 InputType::Image |
592 InputType::Radio |
593 InputType::Range |
594 InputType::Reset |
595 InputType::Submit
596 )
597 }
598
599 #[inline]
600 pub(crate) fn is_submit_button(&self) -> bool {
601 let input_type = self.input_type.get();
602 input_type == InputType::Submit || input_type == InputType::Image
603 }
604
605 pub(crate) fn disable_sanitization(&self) {
606 self.sanitization_flag.set(false);
607 }
608
609 pub(crate) fn enable_sanitization(&self) {
610 self.sanitization_flag.set(true);
611 let mut textinput = self.textinput.borrow_mut();
612 let mut value = textinput.single_line_content().clone();
613 self.sanitize_value(&mut value);
614 textinput.set_content(value);
615 self.upcast::<Node>().dirty(NodeDamage::Other);
616 }
617
618 fn does_minmaxlength_apply(&self) -> bool {
619 matches!(
620 self.input_type(),
621 InputType::Text |
622 InputType::Search |
623 InputType::Url |
624 InputType::Tel |
625 InputType::Email |
626 InputType::Password
627 )
628 }
629
630 fn does_pattern_apply(&self) -> bool {
631 matches!(
632 self.input_type(),
633 InputType::Text |
634 InputType::Search |
635 InputType::Url |
636 InputType::Tel |
637 InputType::Email |
638 InputType::Password
639 )
640 }
641
642 fn does_multiple_apply(&self) -> bool {
643 self.input_type() == InputType::Email
644 }
645
646 fn does_value_as_number_apply(&self) -> bool {
649 matches!(
650 self.input_type(),
651 InputType::Date |
652 InputType::Month |
653 InputType::Week |
654 InputType::Time |
655 InputType::DatetimeLocal |
656 InputType::Number |
657 InputType::Range
658 )
659 }
660
661 fn does_value_as_date_apply(&self) -> bool {
662 matches!(
663 self.input_type(),
664 InputType::Date | InputType::Month | InputType::Week | InputType::Time
665 )
666 }
667
668 fn allowed_value_step(&self) -> Option<f64> {
670 if let Some(attr) = self
671 .upcast::<Element>()
672 .get_attribute(&ns!(), &local_name!("step"))
673 {
674 if let Some(step) =
675 DOMString::from(attr.summarize().value).parse_floating_point_number()
676 {
677 if step > 0.0 {
678 return Some(step * self.step_scale_factor());
679 }
680 }
681 }
682 self.default_step()
683 .map(|step| step * self.step_scale_factor())
684 }
685
686 fn minimum(&self) -> Option<f64> {
688 if let Some(attr) = self
689 .upcast::<Element>()
690 .get_attribute(&ns!(), &local_name!("min"))
691 {
692 if let Some(min) =
693 self.convert_string_to_number(&DOMString::from(attr.summarize().value))
694 {
695 return Some(min);
696 }
697 }
698 self.default_minimum()
699 }
700
701 fn maximum(&self) -> Option<f64> {
703 if let Some(attr) = self
704 .upcast::<Element>()
705 .get_attribute(&ns!(), &local_name!("max"))
706 {
707 if let Some(max) =
708 self.convert_string_to_number(&DOMString::from(attr.summarize().value))
709 {
710 return Some(max);
711 }
712 }
713 self.default_maximum()
714 }
715
716 fn stepped_minimum(&self) -> Option<f64> {
719 match (self.minimum(), self.allowed_value_step()) {
720 (Some(min), Some(allowed_step)) => {
721 let step_base = self.step_base();
722 let nsteps = (min - step_base) / allowed_step;
724 Some(step_base + (allowed_step * nsteps.ceil()))
726 },
727 (_, _) => None,
728 }
729 }
730
731 fn stepped_maximum(&self) -> Option<f64> {
734 match (self.maximum(), self.allowed_value_step()) {
735 (Some(max), Some(allowed_step)) => {
736 let step_base = self.step_base();
737 let nsteps = (max - step_base) / allowed_step;
739 Some(step_base + (allowed_step * nsteps.floor()))
741 },
742 (_, _) => None,
743 }
744 }
745
746 fn default_minimum(&self) -> Option<f64> {
748 match self.input_type() {
749 InputType::Range => Some(0.0),
750 _ => None,
751 }
752 }
753
754 fn default_maximum(&self) -> Option<f64> {
756 match self.input_type() {
757 InputType::Range => Some(100.0),
758 _ => None,
759 }
760 }
761
762 fn default_range_value(&self) -> f64 {
764 let min = self.minimum().unwrap_or(0.0);
765 let max = self.maximum().unwrap_or(100.0);
766 if max < min {
767 min
768 } else {
769 min + (max - min) * 0.5
770 }
771 }
772
773 fn default_step(&self) -> Option<f64> {
775 match self.input_type() {
776 InputType::Date => Some(1.0),
777 InputType::Month => Some(1.0),
778 InputType::Week => Some(1.0),
779 InputType::Time => Some(60.0),
780 InputType::DatetimeLocal => Some(60.0),
781 InputType::Number => Some(1.0),
782 InputType::Range => Some(1.0),
783 _ => None,
784 }
785 }
786
787 fn step_scale_factor(&self) -> f64 {
789 match self.input_type() {
790 InputType::Date => 86400000.0,
791 InputType::Month => 1.0,
792 InputType::Week => 604800000.0,
793 InputType::Time => 1000.0,
794 InputType::DatetimeLocal => 1000.0,
795 InputType::Number => 1.0,
796 InputType::Range => 1.0,
797 _ => unreachable!(),
798 }
799 }
800
801 fn step_base(&self) -> f64 {
803 if let Some(attr) = self
804 .upcast::<Element>()
805 .get_attribute(&ns!(), &local_name!("min"))
806 {
807 let minstr = &DOMString::from(attr.summarize().value);
808 if let Some(min) = self.convert_string_to_number(minstr) {
809 return min;
810 }
811 }
812 if let Some(attr) = self
813 .upcast::<Element>()
814 .get_attribute(&ns!(), &local_name!("value"))
815 {
816 if let Some(value) =
817 self.convert_string_to_number(&DOMString::from(attr.summarize().value))
818 {
819 return value;
820 }
821 }
822 self.default_step_base().unwrap_or(0.0)
823 }
824
825 fn default_step_base(&self) -> Option<f64> {
827 match self.input_type() {
828 InputType::Week => Some(-259200000.0),
829 _ => None,
830 }
831 }
832
833 fn step_up_or_down(&self, n: i32, dir: StepDirection, can_gc: CanGc) -> ErrorResult {
836 if !self.does_value_as_number_apply() {
838 return Err(Error::InvalidState);
839 }
840 let step_base = self.step_base();
841 let allowed_value_step = match self.allowed_value_step() {
843 Some(avs) => avs,
844 None => return Err(Error::InvalidState),
845 };
846 let minimum = self.minimum();
847 let maximum = self.maximum();
848 if let (Some(min), Some(max)) = (minimum, maximum) {
849 if min > max {
851 return Ok(());
852 }
853 if let Some(smin) = self.stepped_minimum() {
855 if smin > max {
856 return Ok(());
857 }
858 }
859 }
860 let mut value: f64 = self.convert_string_to_number(&self.Value()).unwrap_or(0.0);
862
863 let valueBeforeStepping = value;
865
866 if (value - step_base) % allowed_value_step != 0.0 {
868 value = match dir {
869 StepDirection::Down =>
870 {
872 let intervals_from_base = ((value - step_base) / allowed_value_step).floor();
873 intervals_from_base * allowed_value_step + step_base
874 },
875 StepDirection::Up =>
876 {
878 let intervals_from_base = ((value - step_base) / allowed_value_step).ceil();
879 intervals_from_base * allowed_value_step + step_base
880 },
881 };
882 } else {
883 value += match dir {
884 StepDirection::Down => -f64::from(n) * allowed_value_step,
885 StepDirection::Up => f64::from(n) * allowed_value_step,
886 };
887 }
888
889 if let Some(min) = minimum {
891 if value < min {
892 value = self.stepped_minimum().unwrap_or(value);
893 }
894 }
895
896 if let Some(max) = maximum {
898 if value > max {
899 value = self.stepped_maximum().unwrap_or(value);
900 }
901 }
902
903 match dir {
905 StepDirection::Down => {
906 if value > valueBeforeStepping {
907 return Ok(());
908 }
909 },
910 StepDirection::Up => {
911 if value < valueBeforeStepping {
912 return Ok(());
913 }
914 },
915 }
916
917 self.SetValueAsNumber(value, can_gc)
919 }
920
921 fn suggestions_source_element(&self) -> Option<DomRoot<HTMLDataListElement>> {
923 let list_string = self
924 .upcast::<Element>()
925 .get_string_attribute(&local_name!("list"));
926 if list_string.is_empty() {
927 return None;
928 }
929 let ancestor = self
930 .upcast::<Node>()
931 .GetRootNode(&GetRootNodeOptions::empty());
932 let first_with_id = &ancestor
933 .traverse_preorder(ShadowIncluding::No)
934 .find(|node| {
935 node.downcast::<Element>()
936 .is_some_and(|e| e.Id() == list_string)
937 });
938 first_with_id
939 .as_ref()
940 .and_then(|el| el.downcast::<HTMLDataListElement>())
941 .map(DomRoot::from_ref)
942 }
943
944 fn suffers_from_being_missing(&self, value: &DOMString) -> bool {
946 match self.input_type() {
947 InputType::Checkbox => self.Required() && !self.Checked(),
949 InputType::Radio => {
951 if self.radio_group_name().is_none() {
952 return false;
953 }
954 let mut is_required = self.Required();
955 let mut is_checked = self.Checked();
956 let root = self
957 .upcast::<Node>()
958 .GetRootNode(&GetRootNodeOptions::empty());
959 let form = self.form_owner();
960 for other in radio_group_iter(
961 self,
962 self.radio_group_name().as_ref(),
963 form.as_deref(),
964 &root,
965 ) {
966 is_required = is_required || other.Required();
967 is_checked = is_checked || other.Checked();
968 }
969 is_required && !is_checked
970 },
971 InputType::File => {
973 self.Required() && self.filelist.get().is_none_or(|files| files.Length() == 0)
974 },
975 _ => {
977 self.Required() &&
978 self.value_mode() == ValueMode::Value &&
979 self.is_mutable() &&
980 value.is_empty()
981 },
982 }
983 }
984
985 fn suffers_from_type_mismatch(&self, value: &DOMString) -> bool {
987 if value.is_empty() {
988 return false;
989 }
990
991 match self.input_type() {
992 InputType::Url => Url::parse(value).is_err(),
994 InputType::Email => {
997 if self.Multiple() {
998 !split_commas(value).all(|string| string.is_valid_email_address_string())
999 } else {
1000 !value.str().is_valid_email_address_string()
1001 }
1002 },
1003 _ => false,
1005 }
1006 }
1007
1008 fn suffers_from_pattern_mismatch(&self, value: &DOMString) -> bool {
1010 let pattern_str = self.Pattern();
1013 if value.is_empty() || pattern_str.is_empty() || !self.does_pattern_apply() {
1014 return false;
1015 }
1016
1017 let cx = GlobalScope::get_cx();
1019 let _ac = enter_realm(self);
1020 rooted!(in(*cx) let mut pattern = ptr::null_mut::<JSObject>());
1021
1022 if compile_pattern(cx, &pattern_str, pattern.handle_mut()) {
1023 if self.Multiple() && self.does_multiple_apply() {
1024 !split_commas(value)
1025 .all(|s| matches_js_regex(cx, pattern.handle(), s).unwrap_or(true))
1026 } else {
1027 !matches_js_regex(cx, pattern.handle(), value).unwrap_or(true)
1028 }
1029 } else {
1030 false
1032 }
1033 }
1034
1035 fn suffers_from_bad_input(&self, value: &DOMString) -> bool {
1037 if value.is_empty() {
1038 return false;
1039 }
1040
1041 match self.input_type() {
1042 InputType::Email => {
1045 false
1049 },
1050 InputType::Date => !value.str().is_valid_date_string(),
1052 InputType::Month => !value.str().is_valid_month_string(),
1054 InputType::Week => !value.str().is_valid_week_string(),
1056 InputType::Time => !value.str().is_valid_time_string(),
1058 InputType::DatetimeLocal => !value.str().is_valid_local_date_time_string(),
1060 InputType::Number | InputType::Range => !value.is_valid_floating_point_number_string(),
1063 InputType::Color => !value.str().is_valid_simple_color_string(),
1065 _ => false,
1067 }
1068 }
1069
1070 fn suffers_from_length_issues(&self, value: &DOMString) -> ValidationFlags {
1073 let value_dirty = self.value_dirty.get();
1076 let textinput = self.textinput.borrow();
1077 let edit_by_user = !textinput.was_last_change_by_set_content();
1078
1079 if value.is_empty() || !value_dirty || !edit_by_user || !self.does_minmaxlength_apply() {
1080 return ValidationFlags::empty();
1081 }
1082
1083 let mut failed_flags = ValidationFlags::empty();
1084 let UTF16CodeUnits(value_len) = textinput.utf16_len();
1085 let min_length = self.MinLength();
1086 let max_length = self.MaxLength();
1087
1088 if min_length != DEFAULT_MIN_LENGTH && value_len < (min_length as usize) {
1089 failed_flags.insert(ValidationFlags::TOO_SHORT);
1090 }
1091
1092 if max_length != DEFAULT_MAX_LENGTH && value_len > (max_length as usize) {
1093 failed_flags.insert(ValidationFlags::TOO_LONG);
1094 }
1095
1096 failed_flags
1097 }
1098
1099 fn suffers_from_range_issues(&self, value: &DOMString) -> ValidationFlags {
1103 if value.is_empty() || !self.does_value_as_number_apply() {
1104 return ValidationFlags::empty();
1105 }
1106
1107 let Some(value_as_number) = self.convert_string_to_number(value) else {
1108 return ValidationFlags::empty();
1109 };
1110
1111 let mut failed_flags = ValidationFlags::empty();
1112 let min_value = self.minimum();
1113 let max_value = self.maximum();
1114
1115 let has_reversed_range = match (min_value, max_value) {
1117 (Some(min), Some(max)) => self.input_type().has_periodic_domain() && min > max,
1118 _ => false,
1119 };
1120
1121 if has_reversed_range {
1122 if value_as_number > max_value.unwrap() && value_as_number < min_value.unwrap() {
1124 failed_flags.insert(ValidationFlags::RANGE_UNDERFLOW);
1125 failed_flags.insert(ValidationFlags::RANGE_OVERFLOW);
1126 }
1127 } else {
1128 if let Some(min_value) = min_value {
1130 if value_as_number < min_value {
1131 failed_flags.insert(ValidationFlags::RANGE_UNDERFLOW);
1132 }
1133 }
1134 if let Some(max_value) = max_value {
1136 if value_as_number > max_value {
1137 failed_flags.insert(ValidationFlags::RANGE_OVERFLOW);
1138 }
1139 }
1140 }
1141
1142 if let Some(step) = self.allowed_value_step() {
1144 let diff = (self.step_base() - value_as_number) % step / value_as_number;
1148 if diff.abs() > 1e-12 {
1149 failed_flags.insert(ValidationFlags::STEP_MISMATCH);
1150 }
1151 }
1152
1153 failed_flags
1154 }
1155
1156 fn shadow_root(&self, can_gc: CanGc) -> DomRoot<ShadowRoot> {
1161 self.upcast::<Element>()
1162 .shadow_root()
1163 .unwrap_or_else(|| self.upcast::<Element>().attach_ua_shadow_root(true, can_gc))
1164 }
1165
1166 fn create_text_shadow_tree(&self, can_gc: CanGc) {
1167 let document = self.owner_document();
1168 let shadow_root = self.shadow_root(can_gc);
1169 Node::replace_all(None, shadow_root.upcast::<Node>(), can_gc);
1170
1171 let inner_container =
1172 HTMLDivElement::new(local_name!("div"), None, &document, None, can_gc);
1173 shadow_root
1174 .upcast::<Node>()
1175 .AppendChild(inner_container.upcast::<Node>(), can_gc)
1176 .unwrap();
1177 inner_container
1178 .upcast::<Node>()
1179 .set_implemented_pseudo_element(PseudoElement::ServoTextControlInnerContainer);
1180
1181 let text_container = create_ua_widget_div_with_text_node(
1182 &document,
1183 inner_container.upcast::<Node>(),
1184 PseudoElement::ServoTextControlInnerEditor,
1185 false,
1186 can_gc,
1187 );
1188
1189 let _ = self
1190 .shadow_tree
1191 .borrow_mut()
1192 .insert(ShadowTree::Text(InputTypeTextShadowTree {
1193 inner_container: inner_container.as_traced(),
1194 text_container: text_container.as_traced(),
1195 placeholder_container: DomRefCell::new(None),
1196 }));
1197 }
1198
1199 fn text_shadow_tree(&self, can_gc: CanGc) -> Ref<'_, InputTypeTextShadowTree> {
1200 let has_text_shadow_tree = self
1201 .shadow_tree
1202 .borrow()
1203 .as_ref()
1204 .is_some_and(|shadow_tree| matches!(shadow_tree, ShadowTree::Text(_)));
1205 if !has_text_shadow_tree {
1206 self.create_text_shadow_tree(can_gc);
1207 }
1208
1209 let shadow_tree = self.shadow_tree.borrow();
1210 Ref::filter_map(shadow_tree, |shadow_tree| {
1211 let shadow_tree = shadow_tree.as_ref()?;
1212 match shadow_tree {
1213 ShadowTree::Text(text_tree) => Some(text_tree),
1214 _ => None,
1215 }
1216 })
1217 .ok()
1218 .expect("UA shadow tree was not created")
1219 }
1220
1221 fn create_color_shadow_tree(&self, can_gc: CanGc) {
1222 let document = self.owner_document();
1223 let shadow_root = self.shadow_root(can_gc);
1224 Node::replace_all(None, shadow_root.upcast::<Node>(), can_gc);
1225
1226 let color_value = HTMLDivElement::new(local_name!("div"), None, &document, None, can_gc);
1227 shadow_root
1228 .upcast::<Node>()
1229 .AppendChild(color_value.upcast::<Node>(), can_gc)
1230 .unwrap();
1231 color_value
1232 .upcast::<Node>()
1233 .set_implemented_pseudo_element(PseudoElement::ColorSwatch);
1234
1235 let _ = self
1236 .shadow_tree
1237 .borrow_mut()
1238 .insert(ShadowTree::Color(InputTypeColorShadowTree {
1239 color_value: color_value.as_traced(),
1240 }));
1241 }
1242
1243 fn color_shadow_tree(&self, can_gc: CanGc) -> Ref<'_, InputTypeColorShadowTree> {
1250 let has_color_shadow_tree = self
1251 .shadow_tree
1252 .borrow()
1253 .as_ref()
1254 .is_some_and(|shadow_tree| matches!(shadow_tree, ShadowTree::Color(_)));
1255 if !has_color_shadow_tree {
1256 self.create_color_shadow_tree(can_gc);
1257 }
1258
1259 let shadow_tree = self.shadow_tree.borrow();
1260 Ref::filter_map(shadow_tree, |shadow_tree| {
1261 let shadow_tree = shadow_tree.as_ref()?;
1262 match shadow_tree {
1263 ShadowTree::Color(color_tree) => Some(color_tree),
1264 _ => None,
1265 }
1266 })
1267 .ok()
1268 .expect("UA shadow tree was not created")
1269 }
1270
1271 pub(crate) fn is_textual_widget(&self) -> bool {
1276 matches!(
1277 self.input_type(),
1278 InputType::Date |
1279 InputType::DatetimeLocal |
1280 InputType::Email |
1281 InputType::Month |
1282 InputType::Number |
1283 InputType::Password |
1284 InputType::Range |
1285 InputType::Search |
1286 InputType::Tel |
1287 InputType::Text |
1288 InputType::Time |
1289 InputType::Url |
1290 InputType::Week
1291 )
1292 }
1293
1294 fn update_textual_shadow_tree(&self, can_gc: CanGc) {
1298 debug_assert!(self.is_textual_widget());
1300
1301 let text_shadow_tree = self.text_shadow_tree(can_gc);
1302 let value = self.Value();
1303
1304 let value_text = match (value.is_empty(), self.input_type()) {
1313 (false, InputType::Password) => value
1315 .chars()
1316 .map(|_| PASSWORD_REPLACEMENT_CHAR)
1317 .collect::<String>()
1318 .into(),
1319 (false, _) => value,
1320 (true, _) => "\u{200B}".into(),
1321 };
1322
1323 text_shadow_tree
1325 .text_container
1326 .upcast::<Node>()
1327 .GetFirstChild()
1328 .expect("UA widget text container without child")
1329 .downcast::<CharacterData>()
1330 .expect("First child is not a CharacterData node")
1331 .SetData(value_text);
1332 }
1333
1334 fn update_color_shadow_tree(&self, can_gc: CanGc) {
1335 debug_assert_eq!(self.input_type(), InputType::Color);
1337
1338 let color_shadow_tree = self.color_shadow_tree(can_gc);
1339 let mut value = self.Value();
1340 if value.str().is_valid_simple_color_string() {
1341 value.make_ascii_lowercase();
1342 } else {
1343 value = DOMString::from("#000000");
1344 }
1345 let style = format!("background-color: {value}");
1346 color_shadow_tree
1347 .color_value
1348 .upcast::<Element>()
1349 .set_string_attribute(&local_name!("style"), style.into(), can_gc);
1350 }
1351
1352 fn update_shadow_tree(&self, can_gc: CanGc) {
1353 match self.input_type() {
1354 _ if self.is_textual_widget() => self.update_textual_shadow_tree(can_gc),
1355 InputType::Color => self.update_color_shadow_tree(can_gc),
1356 _ => {},
1357 }
1358 }
1359}
1360
1361pub(crate) trait LayoutHTMLInputElementHelpers<'dom> {
1362 fn value_for_layout(self) -> Cow<'dom, str>;
1364 fn size_for_layout(self) -> u32;
1365 fn selection_for_layout(self) -> Option<Range<usize>>;
1366}
1367
1368#[allow(unsafe_code)]
1369impl<'dom> LayoutDom<'dom, HTMLInputElement> {
1370 fn get_raw_textinput_value(self) -> DOMString {
1371 unsafe {
1372 self.unsafe_get()
1373 .textinput
1374 .borrow_for_layout()
1375 .get_content()
1376 }
1377 }
1378 fn get_filelist(self) -> Option<LayoutDom<'dom, FileList>> {
1379 unsafe { self.unsafe_get().filelist.get_inner_as_layout() }
1380 }
1381
1382 fn input_type(self) -> InputType {
1383 self.unsafe_get().input_type.get()
1384 }
1385
1386 fn textinput_sorted_selection_offsets_range(self) -> Range<UTF8Bytes> {
1387 unsafe {
1388 self.unsafe_get()
1389 .textinput
1390 .borrow_for_layout()
1391 .sorted_selection_offsets_range()
1392 }
1393 }
1394}
1395
1396impl<'dom> LayoutHTMLInputElementHelpers<'dom> for LayoutDom<'dom, HTMLInputElement> {
1397 fn value_for_layout(self) -> Cow<'dom, str> {
1401 fn get_raw_attr_value<'dom>(
1402 input: LayoutDom<'dom, HTMLInputElement>,
1403 default: &'static str,
1404 ) -> Cow<'dom, str> {
1405 input
1406 .upcast::<Element>()
1407 .get_attr_val_for_layout(&ns!(), &local_name!("value"))
1408 .unwrap_or(default)
1409 .into()
1410 }
1411
1412 match self.input_type() {
1413 InputType::Checkbox | InputType::Radio | InputType::Image | InputType::Hidden => {
1414 "".into()
1415 },
1416 InputType::File => {
1417 let filelist = self.get_filelist();
1418 match filelist {
1419 Some(filelist) => {
1420 let length = filelist.len();
1421 if length == 0 {
1422 DEFAULT_FILE_INPUT_VALUE.into()
1423 } else if length == 1 {
1424 match filelist.file_for_layout(0) {
1425 Some(file) => file.name().to_string().into(),
1426 None => DEFAULT_FILE_INPUT_VALUE.into(),
1427 }
1428 } else {
1429 format!("{} files", length).into()
1430 }
1431 },
1432 None => DEFAULT_FILE_INPUT_VALUE.into(),
1433 }
1434 },
1435 InputType::Button => get_raw_attr_value(self, ""),
1436 InputType::Submit => get_raw_attr_value(self, DEFAULT_SUBMIT_VALUE),
1437 InputType::Reset => get_raw_attr_value(self, DEFAULT_RESET_VALUE),
1438 InputType::Range => "".into(),
1440 _ => {
1441 unreachable!("Input with shadow tree should use internal shadow tree for layout");
1442 },
1443 }
1444 }
1445
1446 fn size_for_layout(self) -> u32 {
1456 self.unsafe_get().size.get()
1457 }
1458
1459 fn selection_for_layout(self) -> Option<Range<usize>> {
1460 if !self.upcast::<Element>().focus_state() {
1461 return None;
1462 }
1463
1464 let sorted_selection_offsets_range = self.textinput_sorted_selection_offsets_range();
1465
1466 match self.input_type() {
1467 InputType::Password => {
1468 let text = self.get_raw_textinput_value();
1469 let sel = UTF8Bytes::unwrap_range(sorted_selection_offsets_range);
1470
1471 let char_start = text[..sel.start].chars().count();
1473 let char_end = char_start + text[sel].chars().count();
1474
1475 let bytes_per_char = PASSWORD_REPLACEMENT_CHAR.len_utf8();
1476 Some(char_start * bytes_per_char..char_end * bytes_per_char)
1477 },
1478 input_type if input_type.is_textual() => {
1479 Some(UTF8Bytes::unwrap_range(sorted_selection_offsets_range))
1480 },
1481 _ => None,
1482 }
1483 }
1484}
1485
1486impl TextControlElement for HTMLInputElement {
1487 fn selection_api_applies(&self) -> bool {
1489 matches!(
1490 self.input_type(),
1491 InputType::Text |
1492 InputType::Search |
1493 InputType::Url |
1494 InputType::Tel |
1495 InputType::Password
1496 )
1497 }
1498
1499 fn has_selectable_text(&self) -> bool {
1507 match self.input_type() {
1508 InputType::Text |
1509 InputType::Search |
1510 InputType::Url |
1511 InputType::Tel |
1512 InputType::Password |
1513 InputType::Email |
1514 InputType::Date |
1515 InputType::Month |
1516 InputType::Week |
1517 InputType::Time |
1518 InputType::DatetimeLocal |
1519 InputType::Number => true,
1520
1521 InputType::Button |
1522 InputType::Checkbox |
1523 InputType::Color |
1524 InputType::File |
1525 InputType::Hidden |
1526 InputType::Image |
1527 InputType::Radio |
1528 InputType::Range |
1529 InputType::Reset |
1530 InputType::Submit => false,
1531 }
1532 }
1533
1534 fn set_dirty_value_flag(&self, value: bool) {
1535 self.value_dirty.set(value)
1536 }
1537}
1538
1539#[allow(non_snake_case)]
1540impl HTMLInputElementMethods<crate::DomTypeHolder> for HTMLInputElement {
1541 make_getter!(Accept, "accept");
1543
1544 make_setter!(SetAccept, "accept");
1546
1547 make_getter!(Alt, "alt");
1549
1550 make_setter!(SetAlt, "alt");
1552
1553 make_getter!(DirName, "dirname");
1555
1556 make_setter!(SetDirName, "dirname");
1558
1559 make_bool_getter!(Disabled, "disabled");
1561
1562 make_bool_setter!(SetDisabled, "disabled");
1564
1565 fn GetForm(&self) -> Option<DomRoot<HTMLFormElement>> {
1567 self.form_owner()
1568 }
1569
1570 fn GetFiles(&self) -> Option<DomRoot<FileList>> {
1572 self.filelist.get().as_ref().cloned()
1573 }
1574
1575 fn SetFiles(&self, files: Option<&FileList>) {
1577 if self.input_type() == InputType::File && files.is_some() {
1578 self.filelist.set(files);
1579 }
1580 }
1581
1582 make_bool_getter!(DefaultChecked, "checked");
1584
1585 make_bool_setter!(SetDefaultChecked, "checked");
1587
1588 fn Checked(&self) -> bool {
1590 self.upcast::<Element>()
1591 .state()
1592 .contains(ElementState::CHECKED)
1593 }
1594
1595 fn SetChecked(&self, checked: bool) {
1597 self.update_checked_state(checked, true);
1598 self.value_changed(CanGc::note());
1599 }
1600
1601 make_bool_getter!(ReadOnly, "readonly");
1603
1604 make_bool_setter!(SetReadOnly, "readonly");
1606
1607 make_uint_getter!(Size, "size", DEFAULT_INPUT_SIZE);
1609
1610 make_limited_uint_setter!(SetSize, "size", DEFAULT_INPUT_SIZE);
1612
1613 fn Type(&self) -> DOMString {
1615 DOMString::from(self.input_type().as_str())
1616 }
1617
1618 make_atomic_setter!(SetType, "type");
1620
1621 fn Value(&self) -> DOMString {
1623 match self.value_mode() {
1624 ValueMode::Value => self.textinput.borrow().get_content(),
1625 ValueMode::Default => self
1626 .upcast::<Element>()
1627 .get_attribute(&ns!(), &local_name!("value"))
1628 .map_or(DOMString::from(""), |a| {
1629 DOMString::from(a.summarize().value)
1630 }),
1631 ValueMode::DefaultOn => self
1632 .upcast::<Element>()
1633 .get_attribute(&ns!(), &local_name!("value"))
1634 .map_or(DOMString::from("on"), |a| {
1635 DOMString::from(a.summarize().value)
1636 }),
1637 ValueMode::Filename => {
1638 let mut path = DOMString::from("");
1639 match self.filelist.get() {
1640 Some(ref fl) => match fl.Item(0) {
1641 Some(ref f) => {
1642 path.push_str("C:\\fakepath\\");
1643 path.push_str(f.name());
1644 path
1645 },
1646 None => path,
1647 },
1648 None => path,
1649 }
1650 },
1651 }
1652 }
1653
1654 fn SetValue(&self, mut value: DOMString, can_gc: CanGc) -> ErrorResult {
1656 match self.value_mode() {
1657 ValueMode::Value => {
1658 {
1659 self.value_dirty.set(true);
1661
1662 self.sanitize_value(&mut value);
1664
1665 let mut textinput = self.textinput.borrow_mut();
1666
1667 if *textinput.single_line_content() != value {
1669 textinput.set_content(value);
1671
1672 textinput.clear_selection_to_limit(Direction::Forward);
1674 }
1675 }
1676
1677 self.update_placeholder_shown_state();
1681 },
1682 ValueMode::Default | ValueMode::DefaultOn => {
1683 self.upcast::<Element>()
1684 .set_string_attribute(&local_name!("value"), value, can_gc);
1685 },
1686 ValueMode::Filename => {
1687 if value.is_empty() {
1688 let window = self.owner_window();
1689 let fl = FileList::new(&window, vec![], can_gc);
1690 self.filelist.set(Some(&fl));
1691 } else {
1692 return Err(Error::InvalidState);
1693 }
1694 },
1695 }
1696
1697 self.value_changed(can_gc);
1698 self.upcast::<Node>().dirty(NodeDamage::Other);
1699 Ok(())
1700 }
1701
1702 make_getter!(DefaultValue, "value");
1704
1705 make_setter!(SetDefaultValue, "value");
1707
1708 make_getter!(Min, "min");
1710
1711 make_setter!(SetMin, "min");
1713
1714 fn GetList(&self) -> Option<DomRoot<HTMLDataListElement>> {
1716 self.suggestions_source_element()
1717 }
1718
1719 #[allow(unsafe_code)]
1721 fn GetValueAsDate(&self, cx: SafeJSContext) -> Option<NonNull<JSObject>> {
1722 self.convert_string_to_naive_datetime(self.Value())
1723 .map(|date_time| unsafe {
1724 let time = ClippedTime {
1725 t: (date_time - OffsetDateTime::UNIX_EPOCH).whole_milliseconds() as f64,
1726 };
1727 NonNull::new_unchecked(NewDateObject(*cx, time))
1728 })
1729 }
1730
1731 #[allow(unsafe_code, non_snake_case)]
1733 fn SetValueAsDate(
1734 &self,
1735 cx: SafeJSContext,
1736 value: *mut JSObject,
1737 can_gc: CanGc,
1738 ) -> ErrorResult {
1739 rooted!(in(*cx) let value = value);
1740 if !self.does_value_as_date_apply() {
1741 return Err(Error::InvalidState);
1742 }
1743 if value.is_null() {
1744 return self.SetValue(DOMString::from(""), can_gc);
1745 }
1746 let mut msecs: f64 = 0.0;
1747 unsafe {
1751 let mut isDate = false;
1752 if !ObjectIsDate(*cx, Handle::from(value.handle()), &mut isDate) {
1753 return Err(Error::JSFailed);
1754 }
1755 if !isDate {
1756 return Err(Error::Type("Value was not a date".to_string()));
1757 }
1758 if !DateGetMsecSinceEpoch(*cx, Handle::from(value.handle()), &mut msecs) {
1759 return Err(Error::JSFailed);
1760 }
1761 if !msecs.is_finite() {
1762 return self.SetValue(DOMString::from(""), can_gc);
1763 }
1764 }
1765
1766 let Ok(date_time) = OffsetDateTime::from_unix_timestamp_nanos((msecs * 1e6) as i128) else {
1767 return self.SetValue(DOMString::from(""), can_gc);
1768 };
1769 self.SetValue(self.convert_datetime_to_dom_string(date_time), can_gc)
1770 }
1771
1772 fn ValueAsNumber(&self) -> f64 {
1774 self.convert_string_to_number(&self.Value())
1775 .unwrap_or(f64::NAN)
1776 }
1777
1778 fn SetValueAsNumber(&self, value: f64, can_gc: CanGc) -> ErrorResult {
1780 if value.is_infinite() {
1781 Err(Error::Type("value is not finite".to_string()))
1782 } else if !self.does_value_as_number_apply() {
1783 Err(Error::InvalidState)
1784 } else if value.is_nan() {
1785 self.SetValue(DOMString::from(""), can_gc)
1786 } else if let Some(converted) = self.convert_number_to_string(value) {
1787 self.SetValue(converted, can_gc)
1788 } else {
1789 self.SetValue(DOMString::from(""), can_gc)
1794 }
1795 }
1796
1797 make_getter!(Name, "name");
1799
1800 make_atomic_setter!(SetName, "name");
1802
1803 make_getter!(Placeholder, "placeholder");
1805
1806 make_setter!(SetPlaceholder, "placeholder");
1808
1809 make_form_action_getter!(FormAction, "formaction");
1811
1812 make_setter!(SetFormAction, "formaction");
1814
1815 make_enumerated_getter!(
1817 FormEnctype,
1818 "formenctype",
1819 "application/x-www-form-urlencoded" | "text/plain" | "multipart/form-data",
1820 missing => "",
1821 invalid => "application/x-www-form-urlencoded"
1822 );
1823
1824 make_setter!(SetFormEnctype, "formenctype");
1826
1827 make_enumerated_getter!(
1829 FormMethod,
1830 "formmethod",
1831 "get" | "post" | "dialog",
1832 missing => "get",
1833 invalid => "get"
1834 );
1835
1836 make_setter!(SetFormMethod, "formmethod");
1838
1839 make_getter!(FormTarget, "formtarget");
1841
1842 make_setter!(SetFormTarget, "formtarget");
1844
1845 make_bool_getter!(FormNoValidate, "formnovalidate");
1847
1848 make_bool_setter!(SetFormNoValidate, "formnovalidate");
1850
1851 make_getter!(Max, "max");
1853
1854 make_setter!(SetMax, "max");
1856
1857 make_int_getter!(MaxLength, "maxlength", DEFAULT_MAX_LENGTH);
1859
1860 make_limited_int_setter!(SetMaxLength, "maxlength", DEFAULT_MAX_LENGTH);
1862
1863 make_int_getter!(MinLength, "minlength", DEFAULT_MIN_LENGTH);
1865
1866 make_limited_int_setter!(SetMinLength, "minlength", DEFAULT_MIN_LENGTH);
1868
1869 make_bool_getter!(Multiple, "multiple");
1871
1872 make_bool_setter!(SetMultiple, "multiple");
1874
1875 make_getter!(Pattern, "pattern");
1877
1878 make_setter!(SetPattern, "pattern");
1880
1881 make_bool_getter!(Required, "required");
1883
1884 make_bool_setter!(SetRequired, "required");
1886
1887 make_url_getter!(Src, "src");
1889
1890 make_url_setter!(SetSrc, "src");
1892
1893 make_getter!(Step, "step");
1895
1896 make_setter!(SetStep, "step");
1898
1899 fn Indeterminate(&self) -> bool {
1901 self.upcast::<Element>()
1902 .state()
1903 .contains(ElementState::INDETERMINATE)
1904 }
1905
1906 fn SetIndeterminate(&self, val: bool) {
1908 self.upcast::<Element>()
1909 .set_state(ElementState::INDETERMINATE, val)
1910 }
1911
1912 fn GetLabels(&self, can_gc: CanGc) -> Option<DomRoot<NodeList>> {
1916 if self.input_type() == InputType::Hidden {
1917 None
1918 } else {
1919 Some(self.labels_node_list.or_init(|| {
1920 NodeList::new_labels_list(
1921 self.upcast::<Node>().owner_doc().window(),
1922 self.upcast::<HTMLElement>(),
1923 can_gc,
1924 )
1925 }))
1926 }
1927 }
1928
1929 fn Select(&self) {
1931 self.selection().dom_select();
1932 }
1933
1934 fn GetSelectionStart(&self) -> Option<u32> {
1936 self.selection().dom_start()
1937 }
1938
1939 fn SetSelectionStart(&self, start: Option<u32>) -> ErrorResult {
1941 self.selection().set_dom_start(start)
1942 }
1943
1944 fn GetSelectionEnd(&self) -> Option<u32> {
1946 self.selection().dom_end()
1947 }
1948
1949 fn SetSelectionEnd(&self, end: Option<u32>) -> ErrorResult {
1951 self.selection().set_dom_end(end)
1952 }
1953
1954 fn GetSelectionDirection(&self) -> Option<DOMString> {
1956 self.selection().dom_direction()
1957 }
1958
1959 fn SetSelectionDirection(&self, direction: Option<DOMString>) -> ErrorResult {
1961 self.selection().set_dom_direction(direction)
1962 }
1963
1964 fn SetSelectionRange(&self, start: u32, end: u32, direction: Option<DOMString>) -> ErrorResult {
1966 self.selection().set_dom_range(start, end, direction)
1967 }
1968
1969 fn SetRangeText(&self, replacement: DOMString) -> ErrorResult {
1971 self.selection()
1972 .set_dom_range_text(replacement, None, None, Default::default())
1973 }
1974
1975 fn SetRangeText_(
1977 &self,
1978 replacement: DOMString,
1979 start: u32,
1980 end: u32,
1981 selection_mode: SelectionMode,
1982 ) -> ErrorResult {
1983 self.selection()
1984 .set_dom_range_text(replacement, Some(start), Some(end), selection_mode)
1985 }
1986
1987 fn SelectFiles(&self, paths: Vec<DOMString>, can_gc: CanGc) {
1992 if self.input_type() == InputType::File {
1993 let _ = self.select_files(Some(paths), can_gc);
1994 }
1995 }
1996
1997 fn StepUp(&self, n: i32, can_gc: CanGc) -> ErrorResult {
1999 self.step_up_or_down(n, StepDirection::Up, can_gc)
2000 }
2001
2002 fn StepDown(&self, n: i32, can_gc: CanGc) -> ErrorResult {
2004 self.step_up_or_down(n, StepDirection::Down, can_gc)
2005 }
2006
2007 fn WillValidate(&self) -> bool {
2009 self.is_instance_validatable()
2010 }
2011
2012 fn Validity(&self) -> DomRoot<ValidityState> {
2014 self.validity_state()
2015 }
2016
2017 fn CheckValidity(&self, can_gc: CanGc) -> bool {
2019 self.check_validity(can_gc)
2020 }
2021
2022 fn ReportValidity(&self, can_gc: CanGc) -> bool {
2024 self.report_validity(can_gc)
2025 }
2026
2027 fn ValidationMessage(&self) -> DOMString {
2029 self.validation_message()
2030 }
2031
2032 fn SetCustomValidity(&self, error: DOMString) {
2034 self.validity_state().set_custom_error_message(error);
2035 }
2036}
2037
2038fn radio_group_iter<'a>(
2039 elem: &'a HTMLInputElement,
2040 group: Option<&'a Atom>,
2041 form: Option<&'a HTMLFormElement>,
2042 root: &'a Node,
2043) -> impl Iterator<Item = DomRoot<HTMLInputElement>> + 'a {
2044 root.traverse_preorder(ShadowIncluding::No)
2045 .filter_map(DomRoot::downcast::<HTMLInputElement>)
2046 .filter(move |r| &**r == elem || in_same_group(r, form, group, Some(root)))
2047}
2048
2049fn broadcast_radio_checked(broadcaster: &HTMLInputElement, group: Option<&Atom>) {
2050 let root = broadcaster
2051 .upcast::<Node>()
2052 .GetRootNode(&GetRootNodeOptions::empty());
2053 let form = broadcaster.form_owner();
2054 for r in radio_group_iter(broadcaster, group, form.as_deref(), &root) {
2055 if broadcaster != &*r && r.Checked() {
2056 r.SetChecked(false);
2057 }
2058 }
2059}
2060
2061fn perform_radio_group_validation(elem: &HTMLInputElement, group: Option<&Atom>, can_gc: CanGc) {
2062 let root = elem
2063 .upcast::<Node>()
2064 .GetRootNode(&GetRootNodeOptions::empty());
2065 let form = elem.form_owner();
2066 for r in radio_group_iter(elem, group, form.as_deref(), &root) {
2067 r.validity_state()
2068 .perform_validation_and_update(ValidationFlags::all(), can_gc);
2069 }
2070}
2071
2072fn in_same_group(
2074 other: &HTMLInputElement,
2075 owner: Option<&HTMLFormElement>,
2076 group: Option<&Atom>,
2077 tree_root: Option<&Node>,
2078) -> bool {
2079 if group.is_none() {
2080 return false;
2082 }
2083
2084 if other.input_type() != InputType::Radio ||
2085 other.form_owner().as_deref() != owner ||
2086 other.radio_group_name().as_ref() != group
2087 {
2088 return false;
2089 }
2090
2091 match tree_root {
2092 Some(tree_root) => {
2093 let other_root = other
2094 .upcast::<Node>()
2095 .GetRootNode(&GetRootNodeOptions::empty());
2096 tree_root == &*other_root
2097 },
2098 None => {
2099 true
2101 },
2102 }
2103}
2104
2105impl HTMLInputElement {
2106 fn radio_group_updated(&self, group: Option<&Atom>) {
2107 if self.Checked() {
2108 broadcast_radio_checked(self, group);
2109 }
2110 }
2111
2112 pub(crate) fn form_datums(
2115 &self,
2116 submitter: Option<FormSubmitterElement>,
2117 encoding: Option<&'static Encoding>,
2118 ) -> Vec<FormDatum> {
2119 let ty = self.Type();
2123
2124 let name = self.Name();
2126 let is_submitter = match submitter {
2127 Some(FormSubmitterElement::Input(s)) => self == s,
2128 _ => false,
2129 };
2130
2131 match self.input_type() {
2132 InputType::Submit | InputType::Button | InputType::Reset if !is_submitter => {
2134 return vec![];
2135 },
2136
2137 InputType::Radio | InputType::Checkbox => {
2139 if !self.Checked() || name.is_empty() {
2140 return vec![];
2141 }
2142 },
2143
2144 InputType::File => {
2145 let mut datums = vec![];
2146
2147 let name = self.Name();
2149
2150 match self.GetFiles() {
2151 Some(fl) => {
2152 for f in fl.iter_files() {
2153 datums.push(FormDatum {
2154 ty: ty.clone(),
2155 name: name.clone(),
2156 value: FormDatumValue::File(DomRoot::from_ref(f)),
2157 });
2158 }
2159 },
2160 None => {
2161 datums.push(FormDatum {
2162 ty: ty.clone(),
2165 name: name.clone(),
2166 value: FormDatumValue::String(DOMString::from("")),
2167 })
2168 },
2169 }
2170
2171 return datums;
2172 },
2173
2174 InputType::Image => return vec![], InputType::Hidden => {
2178 if name.to_ascii_lowercase() == "_charset_" {
2179 return vec![FormDatum {
2180 ty: ty.clone(),
2181 name,
2182 value: FormDatumValue::String(match encoding {
2183 None => DOMString::from("UTF-8"),
2184 Some(enc) => DOMString::from(enc.name()),
2185 }),
2186 }];
2187 }
2188 },
2189
2190 _ => {
2192 if name.is_empty() {
2193 return vec![];
2194 }
2195 },
2196 }
2197
2198 vec![FormDatum {
2200 ty: ty.clone(),
2201 name,
2202 value: FormDatumValue::String(self.Value()),
2203 }]
2204 }
2205
2206 fn radio_group_name(&self) -> Option<Atom> {
2208 self.upcast::<Element>()
2209 .get_name()
2210 .and_then(|name| if name == atom!("") { None } else { Some(name) })
2211 }
2212
2213 fn update_checked_state(&self, checked: bool, dirty: bool) {
2214 self.upcast::<Element>()
2215 .set_state(ElementState::CHECKED, checked);
2216
2217 if dirty {
2218 self.checked_changed.set(true);
2219 }
2220
2221 if self.input_type() == InputType::Radio && checked {
2222 broadcast_radio_checked(self, self.radio_group_name().as_ref());
2223 }
2224
2225 self.upcast::<Node>().dirty(NodeDamage::Other);
2226 }
2227
2228 pub(crate) fn is_mutable(&self) -> bool {
2230 !(self.upcast::<Element>().disabled_state() || self.ReadOnly())
2233 }
2234
2235 pub(crate) fn reset(&self, can_gc: CanGc) {
2237 match self.input_type() {
2238 InputType::Radio | InputType::Checkbox => {
2239 self.update_checked_state(self.DefaultChecked(), false);
2240 self.checked_changed.set(false);
2241 self.value_changed(can_gc);
2242 },
2243 InputType::Image => (),
2244 _ => (),
2245 }
2246 self.textinput.borrow_mut().set_content(self.DefaultValue());
2247 self.value_dirty.set(false);
2248 self.upcast::<Node>().dirty(NodeDamage::Other);
2249 }
2250
2251 pub(crate) fn clear(&self, can_gc: CanGc) {
2254 self.value_dirty.set(false);
2256 self.checked_changed.set(false);
2257 self.textinput.borrow_mut().set_content(DOMString::from(""));
2259 self.update_checked_state(self.DefaultChecked(), false);
2261 self.value_changed(can_gc);
2262 if self.filelist.get().is_some() {
2264 let window = self.owner_window();
2265 let filelist = FileList::new(&window, vec![], can_gc);
2266 self.filelist.set(Some(&filelist));
2267 }
2268 self.enable_sanitization();
2272 self.upcast::<Node>().dirty(NodeDamage::Other);
2273 }
2274
2275 fn update_placeholder_shown_state(&self) {
2276 if !self.input_type().is_textual_or_password() {
2277 return;
2278 }
2279
2280 let has_placeholder = !self.placeholder.borrow().is_empty();
2281 let has_value = !self.textinput.borrow().is_empty();
2282 let el = self.upcast::<Element>();
2283
2284 el.set_placeholder_shown_state(has_placeholder && !has_value);
2285 }
2286
2287 fn update_text_shadow_tree_placeholder(&self, can_gc: CanGc) {
2290 if !self.is_textual_widget() {
2291 return;
2292 }
2293
2294 let text_shadow_tree = self.text_shadow_tree(can_gc);
2295 text_shadow_tree.init_placeholder_container_if_necessary(self, can_gc);
2296
2297 let Some(ref placeholder_container) = *text_shadow_tree.placeholder_container.borrow()
2298 else {
2299 return;
2301 };
2302 let placeholder_text = self.placeholder.borrow().clone();
2303
2304 placeholder_container
2306 .upcast::<Node>()
2307 .GetFirstChild()
2308 .expect("UA widget text container without child")
2309 .downcast::<CharacterData>()
2310 .expect("First child is not a CharacterData node")
2311 .SetData(placeholder_text);
2312 }
2313
2314 pub(crate) fn select_files(
2317 &self,
2318 opt_test_paths: Option<Vec<DOMString>>,
2319 can_gc: CanGc,
2320 ) -> FileManagerResult<()> {
2321 let window = self.owner_window();
2322 let origin = get_blob_origin(&window.get_url());
2323 let resource_threads = window.as_global_scope().resource_threads();
2324
2325 let mut files: Vec<DomRoot<File>> = vec![];
2326
2327 let webview_id = window.webview_id();
2328 let filter = filter_from_accept(&self.Accept());
2329 let target = self.upcast::<EventTarget>();
2330
2331 if self.Multiple() {
2332 if pref!(dom_testing_html_input_element_select_files_enabled) {
2335 let filelist = self.filelist.get();
2336 if let Some(filelist) = filelist {
2337 for i in 0..filelist.Length() {
2338 files.push(
2339 filelist
2340 .Item(i)
2341 .expect("We should have iterate within filelist length"),
2342 );
2343 }
2344 }
2345 }
2346
2347 let opt_test_paths = opt_test_paths.map(|paths| {
2348 paths
2349 .iter()
2350 .filter_map(|p| PathBuf::from_str(p).ok())
2351 .collect()
2352 });
2353
2354 let (chan, recv) =
2355 profile_traits::ipc::channel(self.global().time_profiler_chan().clone())
2356 .expect("Error initializing channel");
2357 let msg =
2358 FileManagerThreadMsg::SelectFiles(webview_id, filter, chan, origin, opt_test_paths);
2359 resource_threads
2360 .send(CoreResourceMsg::ToFileManager(msg))
2361 .unwrap();
2362
2363 match recv.recv().expect("IpcSender side error") {
2364 Ok(selected_files) => {
2365 for selected in selected_files {
2366 files.push(File::new_from_selected(&window, selected, can_gc));
2367 }
2368 },
2369 Err(err) => {
2370 debug!("Input multiple file select error: {:?}", err);
2371 return Err(err);
2372 },
2373 };
2374 } else {
2375 let opt_test_path = match opt_test_paths {
2376 Some(paths) => {
2377 if paths.is_empty() {
2378 return Ok(());
2379 } else {
2380 Some(PathBuf::from(paths[0].to_string())) }
2382 },
2383 None => None,
2384 };
2385
2386 let (chan, recv) =
2387 profile_traits::ipc::channel(self.global().time_profiler_chan().clone())
2388 .expect("Error initializing channel");
2389 let msg =
2390 FileManagerThreadMsg::SelectFile(webview_id, filter, chan, origin, opt_test_path);
2391 resource_threads
2392 .send(CoreResourceMsg::ToFileManager(msg))
2393 .unwrap();
2394
2395 match recv.recv().expect("IpcSender side error") {
2396 Ok(selected) => {
2397 files.push(File::new_from_selected(&window, selected, can_gc));
2398 },
2399 Err(err) => {
2400 debug!("Input file select error: {:?}", err);
2401 return Err(err);
2402 },
2403 };
2404 }
2405
2406 let filelist = FileList::new(&window, files, can_gc);
2407 self.filelist.set(Some(&filelist));
2408
2409 target.fire_bubbling_event(atom!("input"), can_gc);
2410 target.fire_bubbling_event(atom!("change"), can_gc);
2411
2412 Ok(())
2413 }
2414
2415 fn sanitize_value(&self, value: &mut DOMString) {
2417 if !self.sanitization_flag.get() {
2422 return;
2423 }
2424 match self.input_type() {
2425 InputType::Text | InputType::Search | InputType::Tel | InputType::Password => {
2426 value.strip_newlines();
2427 },
2428 InputType::Url => {
2429 value.strip_newlines();
2430 value.strip_leading_and_trailing_ascii_whitespace();
2431 },
2432 InputType::Date => {
2433 if !value.str().is_valid_date_string() {
2434 value.clear();
2435 }
2436 },
2437 InputType::Month => {
2438 if !value.str().is_valid_month_string() {
2439 value.clear();
2440 }
2441 },
2442 InputType::Week => {
2443 if !value.str().is_valid_week_string() {
2444 value.clear();
2445 }
2446 },
2447 InputType::Color => {
2448 if value.str().is_valid_simple_color_string() {
2449 value.make_ascii_lowercase();
2450 } else {
2451 *value = "#000000".into();
2452 }
2453 },
2454 InputType::Time => {
2455 if !value.str().is_valid_time_string() {
2456 value.clear();
2457 }
2458 },
2459 InputType::DatetimeLocal => {
2460 match value
2461 .str()
2462 .parse_local_date_time_string()
2463 .map(|date_time| date_time.to_local_date_time_string())
2464 {
2465 Some(normalized_string) => *value = DOMString::from_string(normalized_string),
2466 None => value.clear(),
2467 }
2468 },
2469 InputType::Number => {
2470 if !value.is_valid_floating_point_number_string() {
2471 value.clear();
2472 }
2473 },
2480 InputType::Range => {
2482 if !value.is_valid_floating_point_number_string() {
2483 *value = DOMString::from(self.default_range_value().to_string());
2484 }
2485 if let Ok(fval) = &value.parse::<f64>() {
2486 let mut fval = *fval;
2487 if let Some(max) = self.maximum() {
2490 if fval > max {
2491 fval = max;
2492 }
2493 }
2494 if let Some(min) = self.minimum() {
2495 if fval < min {
2496 fval = min;
2497 }
2498 }
2499 if let Some(allowed_value_step) = self.allowed_value_step() {
2504 let step_base = self.step_base();
2505 let steps_from_base = (fval - step_base) / allowed_value_step;
2506 if steps_from_base.fract() != 0.0 {
2507 let int_steps = round_halves_positive(steps_from_base);
2510 fval = int_steps * allowed_value_step + step_base;
2512
2513 if let Some(stepped_maximum) = self.stepped_maximum() {
2517 if fval > stepped_maximum {
2518 fval = stepped_maximum;
2519 }
2520 }
2521 if let Some(stepped_minimum) = self.stepped_minimum() {
2522 if fval < stepped_minimum {
2523 fval = stepped_minimum;
2524 }
2525 }
2526 }
2527 }
2528 *value = DOMString::from(fval.to_string());
2529 };
2530 },
2531 InputType::Email => {
2532 if !self.Multiple() {
2533 value.strip_newlines();
2534 value.strip_leading_and_trailing_ascii_whitespace();
2535 } else {
2536 let sanitized = str_join(
2537 split_commas(value).map(|token| {
2538 let mut token = DOMString::from_string(token.to_string());
2539 token.strip_newlines();
2540 token.strip_leading_and_trailing_ascii_whitespace();
2541 token
2542 }),
2543 ",",
2544 );
2545 value.clear();
2546 value.push_str(sanitized.as_str());
2547 }
2548 },
2549 InputType::Button |
2552 InputType::Checkbox |
2553 InputType::File |
2554 InputType::Hidden |
2555 InputType::Image |
2556 InputType::Radio |
2557 InputType::Reset |
2558 InputType::Submit => (),
2559 }
2560 }
2561
2562 #[cfg_attr(crown, allow(crown::unrooted_must_root))]
2563 fn selection(&self) -> TextControlSelection<'_, Self> {
2564 TextControlSelection::new(self, &self.textinput)
2565 }
2566
2567 #[allow(unsafe_code)]
2569 fn implicit_submission(&self, can_gc: CanGc) {
2570 let doc = self.owner_document();
2571 let node = doc.upcast::<Node>();
2572 let owner = self.form_owner();
2573 let form = match owner {
2574 None => return,
2575 Some(ref f) => f,
2576 };
2577
2578 if self.upcast::<Element>().click_in_progress() {
2579 return;
2580 }
2581 let submit_button = node
2582 .query_selector_iter(DOMString::from("input[type=submit]"))
2583 .unwrap()
2584 .filter_map(DomRoot::downcast::<HTMLInputElement>)
2585 .find(|r| r.form_owner() == owner);
2586 match submit_button {
2587 Some(ref button) => {
2588 if button.is_instance_activatable() {
2589 button
2592 .upcast::<Node>()
2593 .fire_synthetic_pointer_event_not_trusted(DOMString::from("click"), can_gc);
2594 }
2595 },
2596 None => {
2597 let mut inputs = node
2598 .query_selector_iter(DOMString::from("input"))
2599 .unwrap()
2600 .filter_map(DomRoot::downcast::<HTMLInputElement>)
2601 .filter(|input| {
2602 input.form_owner() == owner &&
2603 matches!(
2604 input.input_type(),
2605 InputType::Text |
2606 InputType::Search |
2607 InputType::Url |
2608 InputType::Tel |
2609 InputType::Email |
2610 InputType::Password |
2611 InputType::Date |
2612 InputType::Month |
2613 InputType::Week |
2614 InputType::Time |
2615 InputType::DatetimeLocal |
2616 InputType::Number
2617 )
2618 });
2619
2620 if inputs.nth(1).is_some() {
2621 return;
2623 }
2624 form.submit(
2625 SubmittedFrom::NotFromForm,
2626 FormSubmitterElement::Form(form),
2627 can_gc,
2628 );
2629 },
2630 }
2631 }
2632
2633 fn convert_string_to_number(&self, value: &DOMString) -> Option<f64> {
2635 match self.input_type() {
2636 InputType::Date => value.str().parse_date_string().map(|date_time| {
2643 (date_time - OffsetDateTime::UNIX_EPOCH).whole_milliseconds() as f64
2644 }),
2645 InputType::Month => value.str().parse_month_string().map(|date_time| {
2654 ((date_time.year() - 1970) * 12) as f64 + (date_time.month() as u8 - 1) as f64
2655 }),
2656 InputType::Week => value.str().parse_week_string().map(|date_time| {
2663 (date_time - OffsetDateTime::UNIX_EPOCH).whole_milliseconds() as f64
2664 }),
2665 InputType::Time => value
2670 .str()
2671 .parse_time_string()
2672 .map(|date_time| (date_time.time() - Time::MIDNIGHT).whole_milliseconds() as f64),
2673 InputType::DatetimeLocal => {
2680 value.str().parse_local_date_time_string().map(|date_time| {
2681 (date_time - OffsetDateTime::UNIX_EPOCH).whole_milliseconds() as f64
2682 })
2683 },
2684 InputType::Number | InputType::Range => value.parse_floating_point_number(),
2685 _ => None,
2688 }
2689 }
2690
2691 fn convert_number_to_string(&self, value: f64) -> Option<DOMString> {
2693 match self.input_type() {
2694 InputType::Date | InputType::Week | InputType::Time | InputType::DatetimeLocal => {
2695 OffsetDateTime::from_unix_timestamp_nanos((value * 1e6) as i128)
2696 .ok()
2697 .map(|value| self.convert_datetime_to_dom_string(value))
2698 },
2699 InputType::Month => {
2700 let date = OffsetDateTime::UNIX_EPOCH;
2704 let years = (value / 12.) as i32;
2705 let year = date.year() + years;
2706
2707 let months = value as i32 - (years * 12);
2708 let months = match months.cmp(&0) {
2709 Ordering::Less => (12 - months) as u8,
2710 Ordering::Equal | Ordering::Greater => months as u8,
2711 } + 1;
2712
2713 let date = date
2714 .replace_year(year)
2715 .ok()?
2716 .replace_month(Month::try_from(months).ok()?)
2717 .ok()?;
2718 Some(self.convert_datetime_to_dom_string(date))
2719 },
2720 InputType::Number | InputType::Range => {
2721 let mut value = DOMString::from(value.to_string());
2722 value.set_best_representation_of_the_floating_point_number();
2723 Some(value)
2724 },
2725 _ => unreachable!("Should not have called convert_number_to_string for non-Date types"),
2726 }
2727 }
2728
2729 fn convert_string_to_naive_datetime(&self, value: DOMString) -> Option<OffsetDateTime> {
2733 match self.input_type() {
2734 InputType::Date => value.str().parse_date_string(),
2735 InputType::Time => value.str().parse_time_string(),
2736 InputType::Week => value.str().parse_week_string(),
2737 InputType::Month => value.str().parse_month_string(),
2738 InputType::DatetimeLocal => value.str().parse_local_date_time_string(),
2739 _ => None,
2741 }
2742 }
2743
2744 fn convert_datetime_to_dom_string(&self, value: OffsetDateTime) -> DOMString {
2748 DOMString::from_string(match self.input_type() {
2749 InputType::Date => value.to_date_string(),
2750 InputType::Month => value.to_month_string(),
2751 InputType::Week => value.to_week_string(),
2752 InputType::Time => value.to_time_string(),
2753 InputType::DatetimeLocal => value.to_local_date_time_string(),
2754 _ => {
2755 unreachable!("Should not have called convert_datetime_to_string for non-Date types")
2756 },
2757 })
2758 }
2759
2760 fn update_related_validity_states(&self, can_gc: CanGc) {
2761 match self.input_type() {
2762 InputType::Radio => {
2763 perform_radio_group_validation(self, self.radio_group_name().as_ref(), can_gc)
2764 },
2765 _ => {
2766 self.validity_state()
2767 .perform_validation_and_update(ValidationFlags::all(), can_gc);
2768 },
2769 }
2770 }
2771
2772 fn value_changed(&self, can_gc: CanGc) {
2773 self.update_related_validity_states(can_gc);
2774 self.update_shadow_tree(can_gc);
2775 }
2776
2777 fn show_the_picker_if_applicable(&self, can_gc: CanGc) {
2779 if !self.is_mutable() {
2783 return;
2784 }
2785
2786 if self.input_type() == InputType::Color {
2789 let (ipc_sender, ipc_receiver) = generic_channel::channel::<Option<RgbColor>>()
2790 .expect("Failed to create IPC channel!");
2791 let document = self.owner_document();
2792 let rect = self.upcast::<Node>().border_box().unwrap_or_default();
2793 let rect = Rect::new(
2794 Point2D::new(rect.origin.x.to_px(), rect.origin.y.to_px()),
2795 Size2D::new(rect.size.width.to_px(), rect.size.height.to_px()),
2796 );
2797 let current_value = self.Value();
2798 let current_color = RgbColor {
2799 red: u8::from_str_radix(¤t_value[1..3], 16).unwrap(),
2800 green: u8::from_str_radix(¤t_value[3..5], 16).unwrap(),
2801 blue: u8::from_str_radix(¤t_value[5..7], 16).unwrap(),
2802 };
2803 document.send_to_embedder(EmbedderMsg::ShowFormControl(
2804 document.webview_id(),
2805 DeviceIntRect::from_untyped(&rect.to_box2d()),
2806 EmbedderFormControl::ColorPicker(current_color, ipc_sender),
2807 ));
2808
2809 let Ok(response) = ipc_receiver.recv() else {
2810 log::error!("Failed to receive response");
2811 return;
2812 };
2813
2814 if let Some(selected_color) = response {
2815 let formatted_color = format!(
2816 "#{:0>2x}{:0>2x}{:0>2x}",
2817 selected_color.red, selected_color.green, selected_color.blue
2818 );
2819 let _ = self.SetValue(formatted_color.into(), can_gc);
2820 }
2821 }
2822 }
2823}
2824
2825impl VirtualMethods for HTMLInputElement {
2826 fn super_type(&self) -> Option<&dyn VirtualMethods> {
2827 Some(self.upcast::<HTMLElement>() as &dyn VirtualMethods)
2828 }
2829
2830 fn attribute_mutated(&self, attr: &Attr, mutation: AttributeMutation, can_gc: CanGc) {
2831 self.super_type()
2832 .unwrap()
2833 .attribute_mutated(attr, mutation, can_gc);
2834
2835 match *attr.local_name() {
2836 local_name!("disabled") => {
2837 let disabled_state = match mutation {
2838 AttributeMutation::Set(None) => true,
2839 AttributeMutation::Set(Some(_)) => {
2840 return;
2842 },
2843 AttributeMutation::Removed => false,
2844 };
2845 let el = self.upcast::<Element>();
2846 el.set_disabled_state(disabled_state);
2847 el.set_enabled_state(!disabled_state);
2848 el.check_ancestors_disabled_state_for_form_control();
2849
2850 if self.input_type().is_textual() {
2851 let read_write = !(self.ReadOnly() || el.disabled_state());
2852 el.set_read_write_state(read_write);
2853 }
2854
2855 el.update_sequentially_focusable_status(can_gc);
2856 },
2857 local_name!("checked") if !self.checked_changed.get() => {
2858 let checked_state = match mutation {
2859 AttributeMutation::Set(None) => true,
2860 AttributeMutation::Set(Some(_)) => {
2861 return;
2863 },
2864 AttributeMutation::Removed => false,
2865 };
2866 self.update_checked_state(checked_state, false);
2867 },
2868 local_name!("size") => {
2869 let size = mutation.new_value(attr).map(|value| value.as_uint());
2870 self.size.set(size.unwrap_or(DEFAULT_INPUT_SIZE));
2871 },
2872 local_name!("type") => {
2873 let el = self.upcast::<Element>();
2874 match mutation {
2875 AttributeMutation::Set(_) => {
2876 let new_type = InputType::from(attr.value().as_atom());
2877
2878 let (old_value_mode, old_idl_value) = (self.value_mode(), self.Value());
2880 let previously_selectable = self.selection_api_applies();
2881
2882 self.input_type.set(new_type);
2883
2884 if new_type.is_textual() {
2885 let read_write = !(self.ReadOnly() || el.disabled_state());
2886 el.set_read_write_state(read_write);
2887 } else {
2888 el.set_read_write_state(false);
2889 }
2890
2891 if new_type == InputType::File {
2892 let window = self.owner_window();
2893 let filelist = FileList::new(&window, vec![], can_gc);
2894 self.filelist.set(Some(&filelist));
2895 }
2896
2897 let new_value_mode = self.value_mode();
2898 match (&old_value_mode, old_idl_value.is_empty(), new_value_mode) {
2899 (&ValueMode::Value, false, ValueMode::Default) |
2901 (&ValueMode::Value, false, ValueMode::DefaultOn) => {
2902 self.SetValue(old_idl_value, can_gc)
2903 .expect("Failed to set input value on type change to a default ValueMode.");
2904 },
2905
2906 (_, _, ValueMode::Value) if old_value_mode != ValueMode::Value => {
2908 self.SetValue(
2909 self.upcast::<Element>()
2910 .get_attribute(&ns!(), &local_name!("value"))
2911 .map_or(DOMString::from(""), |a| {
2912 DOMString::from(a.summarize().value)
2913 }),
2914 can_gc,
2915 )
2916 .expect(
2917 "Failed to set input value on type change to ValueMode::Value.",
2918 );
2919 self.value_dirty.set(false);
2920 },
2921
2922 (_, _, ValueMode::Filename)
2924 if old_value_mode != ValueMode::Filename =>
2925 {
2926 self.SetValue(DOMString::from(""), can_gc)
2927 .expect("Failed to set input value on type change to ValueMode::Filename.");
2928 },
2929 _ => {},
2930 }
2931
2932 if new_type == InputType::Radio {
2934 self.radio_group_updated(self.radio_group_name().as_ref());
2935 }
2936
2937 let mut textinput = self.textinput.borrow_mut();
2939 let mut value = textinput.single_line_content().clone();
2940 self.sanitize_value(&mut value);
2941 textinput.set_content(value);
2942 self.upcast::<Node>().dirty(NodeDamage::Other);
2943
2944 if !previously_selectable && self.selection_api_applies() {
2946 textinput.clear_selection_to_limit(Direction::Backward);
2947 }
2948 },
2949 AttributeMutation::Removed => {
2950 if self.input_type() == InputType::Radio {
2951 broadcast_radio_checked(self, self.radio_group_name().as_ref());
2952 }
2953 self.input_type.set(InputType::default());
2954 let el = self.upcast::<Element>();
2955
2956 let read_write = !(self.ReadOnly() || el.disabled_state());
2957 el.set_read_write_state(read_write);
2958 },
2959 }
2960
2961 self.update_placeholder_shown_state();
2962 self.update_text_shadow_tree_placeholder(can_gc);
2963 },
2964 local_name!("value") if !self.value_dirty.get() => {
2967 let value = mutation.new_value(attr).map(|value| (**value).to_owned());
2968 let mut value = value.map_or(DOMString::new(), DOMString::from);
2969
2970 self.sanitize_value(&mut value);
2971 self.textinput.borrow_mut().set_content(value);
2972 self.update_placeholder_shown_state();
2973
2974 self.upcast::<Node>().dirty(NodeDamage::Other);
2975 },
2976 local_name!("name") if self.input_type() == InputType::Radio => {
2977 self.radio_group_updated(
2978 mutation.new_value(attr).as_ref().map(|name| name.as_atom()),
2979 );
2980 },
2981 local_name!("maxlength") => match *attr.value() {
2982 AttrValue::Int(_, value) => {
2983 let mut textinput = self.textinput.borrow_mut();
2984
2985 if value < 0 {
2986 textinput.set_max_length(None);
2987 } else {
2988 textinput.set_max_length(Some(UTF16CodeUnits(value as usize)))
2989 }
2990 },
2991 _ => panic!("Expected an AttrValue::Int"),
2992 },
2993 local_name!("minlength") => match *attr.value() {
2994 AttrValue::Int(_, value) => {
2995 let mut textinput = self.textinput.borrow_mut();
2996
2997 if value < 0 {
2998 textinput.set_min_length(None);
2999 } else {
3000 textinput.set_min_length(Some(UTF16CodeUnits(value as usize)))
3001 }
3002 },
3003 _ => panic!("Expected an AttrValue::Int"),
3004 },
3005 local_name!("placeholder") => {
3006 {
3007 let mut placeholder = self.placeholder.borrow_mut();
3008 placeholder.clear();
3009 if let AttributeMutation::Set(_) = mutation {
3010 placeholder
3011 .extend(attr.value().chars().filter(|&c| c != '\n' && c != '\r'));
3012 }
3013 }
3014 self.update_placeholder_shown_state();
3015 self.update_text_shadow_tree_placeholder(can_gc);
3016 },
3017 local_name!("readonly") => {
3018 if self.input_type().is_textual() {
3019 let el = self.upcast::<Element>();
3020 match mutation {
3021 AttributeMutation::Set(_) => {
3022 el.set_read_write_state(false);
3023 },
3024 AttributeMutation::Removed => {
3025 el.set_read_write_state(!el.disabled_state());
3026 },
3027 }
3028 }
3029 },
3030 local_name!("form") => {
3031 self.form_attribute_mutated(mutation, can_gc);
3032 },
3033 _ => {},
3034 }
3035
3036 self.value_changed(can_gc);
3037 }
3038
3039 fn parse_plain_attribute(&self, name: &LocalName, value: DOMString) -> AttrValue {
3040 match *name {
3041 local_name!("accept") => AttrValue::from_comma_separated_tokenlist(value.into()),
3042 local_name!("size") => AttrValue::from_limited_u32(value.into(), DEFAULT_INPUT_SIZE),
3043 local_name!("type") => AttrValue::from_atomic(value.into()),
3044 local_name!("maxlength") => {
3045 AttrValue::from_limited_i32(value.into(), DEFAULT_MAX_LENGTH)
3046 },
3047 local_name!("minlength") => {
3048 AttrValue::from_limited_i32(value.into(), DEFAULT_MIN_LENGTH)
3049 },
3050 _ => self
3051 .super_type()
3052 .unwrap()
3053 .parse_plain_attribute(name, value),
3054 }
3055 }
3056
3057 fn bind_to_tree(&self, context: &BindContext, can_gc: CanGc) {
3058 if let Some(s) = self.super_type() {
3059 s.bind_to_tree(context, can_gc);
3060 }
3061 self.upcast::<Element>()
3062 .check_ancestors_disabled_state_for_form_control();
3063
3064 if self.input_type() == InputType::Radio {
3065 self.radio_group_updated(self.radio_group_name().as_ref());
3066 }
3067
3068 self.value_changed(can_gc);
3069 }
3070
3071 fn unbind_from_tree(&self, context: &UnbindContext, can_gc: CanGc) {
3072 let form_owner = self.form_owner();
3073
3074 self.super_type().unwrap().unbind_from_tree(context, can_gc);
3075
3076 let node = self.upcast::<Node>();
3077 let el = self.upcast::<Element>();
3078 if node
3079 .ancestors()
3080 .any(|ancestor| ancestor.is::<HTMLFieldSetElement>())
3081 {
3082 el.check_ancestors_disabled_state_for_form_control();
3083 } else {
3084 el.check_disabled_attribute();
3085 }
3086
3087 if self.input_type() == InputType::Radio {
3088 let root = context.parent.GetRootNode(&GetRootNodeOptions::empty());
3089 for r in radio_group_iter(
3090 self,
3091 self.radio_group_name().as_ref(),
3092 form_owner.as_deref(),
3093 &root,
3094 ) {
3095 r.validity_state()
3096 .perform_validation_and_update(ValidationFlags::all(), can_gc);
3097 }
3098 }
3099
3100 self.validity_state()
3101 .perform_validation_and_update(ValidationFlags::all(), can_gc);
3102 }
3103
3104 fn handle_event(&self, event: &Event, can_gc: CanGc) {
3110 if let Some(s) = self.super_type() {
3111 s.handle_event(event, can_gc);
3112 }
3113
3114 if event.type_() == atom!("click") && !event.DefaultPrevented() {
3115 if self.input_type().is_textual_or_password() &&
3121 !self.textinput.borrow().is_empty()
3123 {
3124 if let Some(mouse_event) = event.downcast::<MouseEvent>() {
3125 if let Some(point_in_target) = mouse_event.point_in_target() {
3129 let window = self.owner_window();
3130 let index = window
3131 .text_index_query(self.upcast::<Node>(), point_in_target.to_untyped());
3132 let edit_point_index = match index {
3135 Some(i) => i,
3136 None => self.textinput.borrow().char_count(),
3137 };
3138 self.textinput
3139 .borrow_mut()
3140 .set_edit_point_index(edit_point_index);
3141 self.upcast::<Node>().dirty(NodeDamage::Other);
3143 event.PreventDefault();
3144 }
3145 }
3146 }
3147 } else if event.type_() == atom!("keydown") &&
3148 !event.DefaultPrevented() &&
3149 self.input_type().is_textual_or_password()
3150 {
3151 if let Some(keyevent) = event.downcast::<KeyboardEvent>() {
3152 let action = self.textinput.borrow_mut().handle_keydown(keyevent);
3155 match action {
3156 TriggerDefaultAction => {
3157 self.implicit_submission(can_gc);
3158 },
3159 DispatchInput => {
3160 if event.IsTrusted() {
3161 self.owner_global()
3162 .task_manager()
3163 .user_interaction_task_source()
3164 .queue_event(
3165 self.upcast(),
3166 atom!("input"),
3167 EventBubbles::Bubbles,
3168 EventCancelable::NotCancelable,
3169 );
3170 }
3171 self.value_dirty.set(true);
3172 self.update_placeholder_shown_state();
3173 self.upcast::<Node>().dirty(NodeDamage::Other);
3174 event.mark_as_handled();
3175 },
3176 RedrawSelection => {
3177 self.upcast::<Node>().dirty(NodeDamage::Other);
3178 event.mark_as_handled();
3179 },
3180 Nothing => (),
3181 }
3182 }
3183 } else if event.type_() == atom!("keypress") &&
3184 !event.DefaultPrevented() &&
3185 self.input_type().is_textual_or_password()
3186 {
3187 } else if (event.type_() == atom!("compositionstart") ||
3191 event.type_() == atom!("compositionupdate") ||
3192 event.type_() == atom!("compositionend")) &&
3193 self.input_type().is_textual_or_password()
3194 {
3195 if let Some(compositionevent) = event.downcast::<CompositionEvent>() {
3196 if event.type_() == atom!("compositionend") {
3197 let _ = self
3198 .textinput
3199 .borrow_mut()
3200 .handle_compositionend(compositionevent);
3201 self.upcast::<Node>().dirty(NodeDamage::Other);
3202 } else if event.type_() == atom!("compositionupdate") {
3203 let _ = self
3204 .textinput
3205 .borrow_mut()
3206 .handle_compositionupdate(compositionevent);
3207 self.upcast::<Node>().dirty(NodeDamage::Other);
3208 }
3209 event.mark_as_handled();
3210 }
3211 } else if let Some(clipboard_event) = event.downcast::<ClipboardEvent>() {
3212 let reaction = self
3213 .textinput
3214 .borrow_mut()
3215 .handle_clipboard_event(clipboard_event);
3216 if reaction.contains(ClipboardEventReaction::FireClipboardChangedEvent) {
3217 self.owner_document()
3218 .event_handler()
3219 .fire_clipboardchange_event(can_gc);
3220 }
3221 if reaction.contains(ClipboardEventReaction::QueueInputEvent) {
3222 self.owner_global()
3223 .task_manager()
3224 .user_interaction_task_source()
3225 .queue_event(
3226 self.upcast(),
3227 atom!("input"),
3228 EventBubbles::Bubbles,
3229 EventCancelable::NotCancelable,
3230 );
3231 }
3232 if !reaction.is_empty() {
3233 self.upcast::<Node>().dirty(NodeDamage::ContentOrHeritage);
3234 }
3235 }
3236
3237 self.value_changed(can_gc);
3238 }
3239
3240 fn cloning_steps(
3242 &self,
3243 copy: &Node,
3244 maybe_doc: Option<&Document>,
3245 clone_children: CloneChildrenFlag,
3246 can_gc: CanGc,
3247 ) {
3248 if let Some(s) = self.super_type() {
3249 s.cloning_steps(copy, maybe_doc, clone_children, can_gc);
3250 }
3251 let elem = copy.downcast::<HTMLInputElement>().unwrap();
3252 elem.value_dirty.set(self.value_dirty.get());
3253 elem.checked_changed.set(self.checked_changed.get());
3254 elem.upcast::<Element>()
3255 .set_state(ElementState::CHECKED, self.Checked());
3256 elem.textinput
3257 .borrow_mut()
3258 .set_content(self.textinput.borrow().get_content());
3259 self.value_changed(can_gc);
3260 }
3261}
3262
3263impl FormControl for HTMLInputElement {
3264 fn form_owner(&self) -> Option<DomRoot<HTMLFormElement>> {
3265 self.form_owner.get()
3266 }
3267
3268 fn set_form_owner(&self, form: Option<&HTMLFormElement>) {
3269 self.form_owner.set(form);
3270 }
3271
3272 fn to_element(&self) -> &Element {
3273 self.upcast::<Element>()
3274 }
3275}
3276
3277impl Validatable for HTMLInputElement {
3278 fn as_element(&self) -> &Element {
3279 self.upcast()
3280 }
3281
3282 fn validity_state(&self) -> DomRoot<ValidityState> {
3283 self.validity_state
3284 .or_init(|| ValidityState::new(&self.owner_window(), self.upcast(), CanGc::note()))
3285 }
3286
3287 fn is_instance_validatable(&self) -> bool {
3288 match self.input_type() {
3295 InputType::Hidden | InputType::Button | InputType::Reset => false,
3296 _ => {
3297 !(self.upcast::<Element>().disabled_state() ||
3298 self.ReadOnly() ||
3299 is_barred_by_datalist_ancestor(self.upcast()))
3300 },
3301 }
3302 }
3303
3304 fn perform_validation(
3305 &self,
3306 validate_flags: ValidationFlags,
3307 _can_gc: CanGc,
3308 ) -> ValidationFlags {
3309 let mut failed_flags = ValidationFlags::empty();
3310 let value = self.Value();
3311
3312 if validate_flags.contains(ValidationFlags::VALUE_MISSING) &&
3313 self.suffers_from_being_missing(&value)
3314 {
3315 failed_flags.insert(ValidationFlags::VALUE_MISSING);
3316 }
3317
3318 if validate_flags.contains(ValidationFlags::TYPE_MISMATCH) &&
3319 self.suffers_from_type_mismatch(&value)
3320 {
3321 failed_flags.insert(ValidationFlags::TYPE_MISMATCH);
3322 }
3323
3324 if validate_flags.contains(ValidationFlags::PATTERN_MISMATCH) &&
3325 self.suffers_from_pattern_mismatch(&value)
3326 {
3327 failed_flags.insert(ValidationFlags::PATTERN_MISMATCH);
3328 }
3329
3330 if validate_flags.contains(ValidationFlags::BAD_INPUT) &&
3331 self.suffers_from_bad_input(&value)
3332 {
3333 failed_flags.insert(ValidationFlags::BAD_INPUT);
3334 }
3335
3336 if validate_flags.intersects(ValidationFlags::TOO_LONG | ValidationFlags::TOO_SHORT) {
3337 failed_flags |= self.suffers_from_length_issues(&value);
3338 }
3339
3340 if validate_flags.intersects(
3341 ValidationFlags::RANGE_UNDERFLOW |
3342 ValidationFlags::RANGE_OVERFLOW |
3343 ValidationFlags::STEP_MISMATCH,
3344 ) {
3345 failed_flags |= self.suffers_from_range_issues(&value);
3346 }
3347
3348 failed_flags & validate_flags
3349 }
3350}
3351
3352impl Activatable for HTMLInputElement {
3353 fn as_element(&self) -> &Element {
3354 self.upcast()
3355 }
3356
3357 fn is_instance_activatable(&self) -> bool {
3358 match self.input_type() {
3359 InputType::Submit | InputType::Reset | InputType::File | InputType::Image => {
3364 self.is_mutable()
3365 },
3366 InputType::Checkbox | InputType::Radio | InputType::Color => true,
3370 _ => false,
3371 }
3372 }
3373
3374 fn legacy_pre_activation_behavior(&self, can_gc: CanGc) -> Option<InputActivationState> {
3376 let ty = self.input_type();
3377 let activation_state = match ty {
3378 InputType::Checkbox => {
3379 let was_checked = self.Checked();
3380 let was_indeterminate = self.Indeterminate();
3381 self.SetIndeterminate(false);
3382 self.SetChecked(!was_checked);
3383 Some(InputActivationState {
3384 checked: was_checked,
3385 indeterminate: was_indeterminate,
3386 checked_radio: None,
3387 old_type: InputType::Checkbox,
3388 })
3389 },
3390 InputType::Radio => {
3391 let root = self
3392 .upcast::<Node>()
3393 .GetRootNode(&GetRootNodeOptions::empty());
3394 let form_owner = self.form_owner();
3395 let checked_member = radio_group_iter(
3396 self,
3397 self.radio_group_name().as_ref(),
3398 form_owner.as_deref(),
3399 &root,
3400 )
3401 .find(|r| r.Checked());
3402 let was_checked = self.Checked();
3403 self.SetChecked(true);
3404 Some(InputActivationState {
3405 checked: was_checked,
3406 indeterminate: false,
3407 checked_radio: checked_member.as_deref().map(DomRoot::from_ref),
3408 old_type: InputType::Radio,
3409 })
3410 },
3411 _ => None,
3412 };
3413
3414 if activation_state.is_some() {
3415 self.value_changed(can_gc);
3416 }
3417
3418 activation_state
3419 }
3420
3421 fn legacy_canceled_activation_behavior(
3423 &self,
3424 cache: Option<InputActivationState>,
3425 can_gc: CanGc,
3426 ) {
3427 let ty = self.input_type();
3429 let cache = match cache {
3430 Some(cache) => {
3431 if cache.old_type != ty {
3432 return;
3435 }
3436 cache
3437 },
3438 None => {
3439 return;
3440 },
3441 };
3442
3443 match ty {
3444 InputType::Checkbox => {
3446 self.SetIndeterminate(cache.indeterminate);
3447 self.SetChecked(cache.checked);
3448 },
3449 InputType::Radio => {
3451 if let Some(ref o) = cache.checked_radio {
3452 let tree_root = self
3453 .upcast::<Node>()
3454 .GetRootNode(&GetRootNodeOptions::empty());
3455 if in_same_group(
3458 o,
3459 self.form_owner().as_deref(),
3460 self.radio_group_name().as_ref(),
3461 Some(&*tree_root),
3462 ) {
3463 o.SetChecked(true);
3464 } else {
3465 self.SetChecked(false);
3466 }
3467 } else {
3468 self.SetChecked(false);
3469 }
3470 },
3471 _ => (),
3472 }
3473
3474 self.value_changed(can_gc);
3475 }
3476
3477 fn activation_behavior(&self, _event: &Event, _target: &EventTarget, can_gc: CanGc) {
3479 match self.input_type() {
3480 InputType::Submit | InputType::Image => {
3483 if let Some(form_owner) = self.form_owner() {
3485 let document = self.owner_document();
3487
3488 if !document.is_fully_active() {
3489 return;
3490 }
3491
3492 form_owner.submit(
3495 SubmittedFrom::NotFromForm,
3496 FormSubmitterElement::Input(self),
3497 can_gc,
3498 )
3499 }
3500 },
3501 InputType::Reset => {
3502 if let Some(form_owner) = self.form_owner() {
3505 let document = self.owner_document();
3506
3507 if !document.is_fully_active() {
3509 return;
3510 }
3511
3512 form_owner.reset(ResetFrom::NotFromForm, can_gc);
3514 }
3515 },
3516 InputType::Checkbox | InputType::Radio => {
3519 if !self.upcast::<Node>().is_connected() {
3521 return;
3522 }
3523
3524 let target = self.upcast::<EventTarget>();
3525
3526 target.fire_bubbling_event(atom!("input"), can_gc);
3529
3530 target.fire_bubbling_event(atom!("change"), can_gc);
3533 },
3534 InputType::File => {
3536 let _ = self.select_files(None, can_gc);
3537 },
3538 InputType::Color => {
3540 self.show_the_picker_if_applicable(can_gc);
3541 },
3542 _ => (),
3543 }
3544 }
3545}
3546
3547fn filter_from_accept(s: &DOMString) -> Vec<FilterPattern> {
3549 let mut filter = vec![];
3550 for p in split_commas(s) {
3551 let p = p.trim();
3552 if let Some('.') = p.chars().next() {
3553 filter.push(FilterPattern(p[1..].to_string()));
3554 } else if let Some(exts) = mime_guess::get_mime_extensions_str(p) {
3555 for ext in exts {
3556 filter.push(FilterPattern(ext.to_string()));
3557 }
3558 }
3559 }
3560
3561 filter
3562}
3563
3564fn round_halves_positive(n: f64) -> f64 {
3565 if n.fract() == -0.5 {
3569 n.ceil()
3570 } else {
3571 n.round()
3572 }
3573}
3574
3575fn compile_pattern(cx: SafeJSContext, pattern_str: &str, out_regex: MutableHandleObject) -> bool {
3579 if check_js_regex_syntax(cx, pattern_str) {
3581 let pattern_str = format!("^(?:{})$", pattern_str);
3583 let flags = RegExpFlags {
3584 flags_: RegExpFlag_UnicodeSets,
3585 };
3586 new_js_regex(cx, &pattern_str, flags, out_regex)
3587 } else {
3588 false
3589 }
3590}
3591
3592#[allow(unsafe_code)]
3593fn check_js_regex_syntax(cx: SafeJSContext, pattern: &str) -> bool {
3596 let pattern: Vec<u16> = pattern.encode_utf16().collect();
3597 unsafe {
3598 rooted!(in(*cx) let mut exception = UndefinedValue());
3599
3600 let valid = CheckRegExpSyntax(
3601 *cx,
3602 pattern.as_ptr(),
3603 pattern.len(),
3604 RegExpFlags {
3605 flags_: RegExpFlag_UnicodeSets,
3606 },
3607 exception.handle_mut(),
3608 );
3609
3610 if !valid {
3611 JS_ClearPendingException(*cx);
3612 return false;
3613 }
3614
3615 exception.is_undefined()
3618 }
3619}
3620
3621#[allow(unsafe_code)]
3624pub(crate) fn new_js_regex(
3625 cx: SafeJSContext,
3626 pattern: &str,
3627 flags: RegExpFlags,
3628 mut out_regex: MutableHandleObject,
3629) -> bool {
3630 let pattern: Vec<u16> = pattern.encode_utf16().collect();
3631 unsafe {
3632 out_regex.set(NewUCRegExpObject(
3633 *cx,
3634 pattern.as_ptr(),
3635 pattern.len(),
3636 flags,
3637 ));
3638 if out_regex.is_null() {
3639 JS_ClearPendingException(*cx);
3640 return false;
3641 }
3642 }
3643 true
3644}
3645
3646#[allow(unsafe_code)]
3647fn matches_js_regex(cx: SafeJSContext, regex_obj: HandleObject, value: &str) -> Result<bool, ()> {
3648 let mut value: Vec<u16> = value.encode_utf16().collect();
3649
3650 unsafe {
3651 let mut is_regex = false;
3652 assert!(ObjectIsRegExp(*cx, regex_obj, &mut is_regex));
3653 assert!(is_regex);
3654
3655 rooted!(in(*cx) let mut rval = UndefinedValue());
3656 let mut index = 0;
3657
3658 let ok = ExecuteRegExpNoStatics(
3659 *cx,
3660 regex_obj,
3661 value.as_mut_ptr(),
3662 value.len(),
3663 &mut index,
3664 true,
3665 rval.handle_mut(),
3666 );
3667
3668 if ok {
3669 Ok(!rval.is_null())
3670 } else {
3671 JS_ClearPendingException(*cx);
3672 Err(())
3673 }
3674 }
3675}