1use std::cell::{Cell, RefCell, RefMut};
6use std::ptr::NonNull;
7use std::{f64, ptr};
8
9use dom_struct::dom_struct;
10use embedder_traits::{EmbedderControlRequest, InputMethodRequest, RgbColor, SelectedFile};
11use encoding_rs::Encoding;
12use fonts::{ByteIndex, TextByteRange};
13use html5ever::{LocalName, Prefix, local_name};
14use js::context::JSContext;
15use js::jsapi::{
16 ClippedTime, DateGetMsecSinceEpoch, Handle, JS_ClearPendingException, JSObject, NewDateObject,
17 NewUCRegExpObject, ObjectIsDate, RegExpFlag_UnicodeSets, RegExpFlags,
18};
19use js::jsval::UndefinedValue;
20use js::rust::wrappers::{CheckRegExpSyntax, ExecuteRegExpNoStatics, ObjectIsRegExp};
21use js::rust::{HandleObject, MutableHandleObject};
22use layout_api::wrapper_traits::{ScriptSelection, SharedSelection};
23use script_bindings::codegen::GenericBindings::AttrBinding::AttrMethods;
24use script_bindings::domstring::parse_floating_point_number;
25use servo_base::generic_channel::GenericSender;
26use servo_base::text::Utf16CodeUnitLength;
27use style::attr::AttrValue;
28use style::str::split_commas;
29use stylo_atoms::Atom;
30use stylo_dom::ElementState;
31use time::OffsetDateTime;
32use unicode_bidi::{BidiClass, bidi_class};
33use webdriver::error::ErrorStatus;
34
35use crate::clipboard_provider::EmbedderClipboardProvider;
36use crate::dom::activation::Activatable;
37use crate::dom::attr::Attr;
38use crate::dom::bindings::cell::{DomRefCell, Ref};
39use crate::dom::bindings::codegen::Bindings::ElementBinding::ElementMethods;
40use crate::dom::bindings::codegen::Bindings::EventBinding::EventMethods;
41use crate::dom::bindings::codegen::Bindings::FileListBinding::FileListMethods;
42use crate::dom::bindings::codegen::Bindings::HTMLFormElementBinding::SelectionMode;
43use crate::dom::bindings::codegen::Bindings::HTMLInputElementBinding::HTMLInputElementMethods;
44use crate::dom::bindings::codegen::Bindings::NodeBinding::{GetRootNodeOptions, NodeMethods};
45use crate::dom::bindings::error::{Error, ErrorResult};
46use crate::dom::bindings::inheritance::Castable;
47use crate::dom::bindings::root::{DomRoot, LayoutDom, MutNullableDom};
48use crate::dom::bindings::str::{DOMString, USVString};
49use crate::dom::clipboardevent::{ClipboardEvent, ClipboardEventType};
50use crate::dom::compositionevent::CompositionEvent;
51use crate::dom::document::Document;
52use crate::dom::document_embedder_controls::ControlElement;
53use crate::dom::element::{AttributeMutation, Element};
54use crate::dom::event::Event;
55use crate::dom::eventtarget::EventTarget;
56use crate::dom::filelist::FileList;
57use crate::dom::globalscope::GlobalScope;
58use crate::dom::html::htmldatalistelement::HTMLDataListElement;
59use crate::dom::html::htmlelement::HTMLElement;
60use crate::dom::html::htmlfieldsetelement::HTMLFieldSetElement;
61use crate::dom::html::htmlformelement::{
62 FormControl, FormDatum, FormDatumValue, FormSubmitterElement, HTMLFormElement, SubmittedFrom,
63};
64use crate::dom::htmlinputelement::radio_input_type::{
65 broadcast_radio_checked, perform_radio_group_validation,
66};
67use crate::dom::input_element::input_type::InputType;
68use crate::dom::keyboardevent::KeyboardEvent;
69use crate::dom::node::{
70 BindContext, CloneChildrenFlag, Node, NodeDamage, NodeTraits, ShadowIncluding, UnbindContext,
71};
72use crate::dom::nodelist::NodeList;
73use crate::dom::textcontrol::{TextControlElement, TextControlSelection};
74use crate::dom::types::{FocusEvent, MouseEvent};
75use crate::dom::validation::{Validatable, is_barred_by_datalist_ancestor};
76use crate::dom::validitystate::{ValidationFlags, ValidityState};
77use crate::dom::virtualmethods::VirtualMethods;
78use crate::realms::enter_realm;
79use crate::script_runtime::{CanGc, JSContext as SafeJSContext};
80use crate::textinput::{ClipboardEventFlags, IsComposing, KeyReaction, Lines, TextInput};
81
82pub(crate) mod button_input_type;
83pub(crate) mod checkbox_input_type;
84pub(crate) mod color_input_type;
85pub(crate) mod date_input_type;
86pub(crate) mod datetime_local_input_type;
87pub(crate) mod email_input_type;
88pub(crate) mod file_input_type;
89pub(crate) mod hidden_input_type;
90pub(crate) mod image_input_type;
91pub(crate) mod input_type;
92pub(crate) mod month_input_type;
93pub(crate) mod number_input_type;
94pub(crate) mod password_input_type;
95pub(crate) mod radio_input_type;
96pub(crate) mod range_input_type;
97pub(crate) mod reset_input_type;
98pub(crate) mod search_input_type;
99pub(crate) mod submit_input_type;
100pub(crate) mod tel_input_type;
101pub(crate) mod text_input_type;
102pub(crate) mod text_input_widget;
103pub(crate) mod text_value_widget;
104pub(crate) mod time_input_type;
105pub(crate) mod url_input_type;
106pub(crate) mod week_input_type;
107
108#[derive(Debug, PartialEq)]
109enum ValueMode {
110 Value,
112
113 Default,
115
116 DefaultOn,
118
119 Filename,
121}
122
123#[derive(Debug, PartialEq)]
124enum StepDirection {
125 Up,
126 Down,
127}
128
129#[dom_struct]
130pub(crate) struct HTMLInputElement {
131 htmlelement: HTMLElement,
132 input_type: DomRefCell<InputType>,
133
134 checked_changed: Cell<bool>,
136 placeholder: DomRefCell<DOMString>,
137 size: Cell<u32>,
138 maxlength: Cell<i32>,
139 minlength: Cell<i32>,
140 #[no_trace]
141 textinput: DomRefCell<TextInput<EmbedderClipboardProvider>>,
142 value_dirty: Cell<bool>,
144 #[no_trace]
147 #[conditional_malloc_size_of]
148 shared_selection: SharedSelection,
149
150 form_owner: MutNullableDom<HTMLFormElement>,
151 labels_node_list: MutNullableDom<NodeList>,
152 validity_state: MutNullableDom<ValidityState>,
153 #[no_trace]
154 pending_webdriver_response: RefCell<Option<PendingWebDriverResponse>>,
155}
156
157#[derive(JSTraceable)]
158pub(crate) struct InputActivationState {
159 indeterminate: bool,
160 checked: bool,
161 checked_radio: Option<DomRoot<HTMLInputElement>>,
162 was_radio: bool,
163 was_checkbox: bool,
164 }
166
167static DEFAULT_INPUT_SIZE: u32 = 20;
168static DEFAULT_MAX_LENGTH: i32 = -1;
169static DEFAULT_MIN_LENGTH: i32 = -1;
170
171#[expect(non_snake_case)]
172impl HTMLInputElement {
173 fn new_inherited(
174 local_name: LocalName,
175 prefix: Option<Prefix>,
176 document: &Document,
177 ) -> HTMLInputElement {
178 let embedder_sender = document
179 .window()
180 .as_global_scope()
181 .script_to_embedder_chan()
182 .clone();
183 HTMLInputElement {
184 htmlelement: HTMLElement::new_inherited_with_state(
185 ElementState::ENABLED | ElementState::READWRITE,
186 local_name,
187 prefix,
188 document,
189 ),
190 input_type: DomRefCell::new(InputType::new_text()),
191 placeholder: DomRefCell::new(DOMString::new()),
192 checked_changed: Cell::new(false),
193 maxlength: Cell::new(DEFAULT_MAX_LENGTH),
194 minlength: Cell::new(DEFAULT_MIN_LENGTH),
195 size: Cell::new(DEFAULT_INPUT_SIZE),
196 textinput: DomRefCell::new(TextInput::new(
197 Lines::Single,
198 DOMString::new(),
199 EmbedderClipboardProvider {
200 embedder_sender,
201 webview_id: document.webview_id(),
202 },
203 )),
204 value_dirty: Cell::new(false),
205 shared_selection: Default::default(),
206 form_owner: Default::default(),
207 labels_node_list: MutNullableDom::new(None),
208 validity_state: Default::default(),
209 pending_webdriver_response: Default::default(),
210 }
211 }
212
213 pub(crate) fn new(
214 local_name: LocalName,
215 prefix: Option<Prefix>,
216 document: &Document,
217 proto: Option<HandleObject>,
218 can_gc: CanGc,
219 ) -> DomRoot<HTMLInputElement> {
220 Node::reflect_node_with_proto(
221 Box::new(HTMLInputElement::new_inherited(
222 local_name, prefix, document,
223 )),
224 document,
225 proto,
226 can_gc,
227 )
228 }
229
230 pub(crate) fn auto_directionality(&self) -> Option<String> {
231 match *self.input_type() {
232 InputType::Text(_) | InputType::Search(_) | InputType::Url(_) | InputType::Email(_) => {
233 let value: String = self.Value().to_string();
234 Some(HTMLInputElement::directionality_from_value(&value))
235 },
236 _ => None,
237 }
238 }
239
240 pub(crate) fn directionality_from_value(value: &str) -> String {
241 if HTMLInputElement::is_first_strong_character_rtl(value) {
242 "rtl".to_owned()
243 } else {
244 "ltr".to_owned()
245 }
246 }
247
248 fn is_first_strong_character_rtl(value: &str) -> bool {
249 for ch in value.chars() {
250 return match bidi_class(ch) {
251 BidiClass::L => false,
252 BidiClass::AL => true,
253 BidiClass::R => true,
254 _ => continue,
255 };
256 }
257 false
258 }
259
260 fn value_mode(&self) -> ValueMode {
263 match *self.input_type() {
264 InputType::Submit(_) |
265 InputType::Reset(_) |
266 InputType::Button(_) |
267 InputType::Image(_) |
268 InputType::Hidden(_) => ValueMode::Default,
269
270 InputType::Checkbox(_) | InputType::Radio(_) => ValueMode::DefaultOn,
271
272 InputType::Color(_) |
273 InputType::Date(_) |
274 InputType::DatetimeLocal(_) |
275 InputType::Email(_) |
276 InputType::Month(_) |
277 InputType::Number(_) |
278 InputType::Password(_) |
279 InputType::Range(_) |
280 InputType::Search(_) |
281 InputType::Tel(_) |
282 InputType::Text(_) |
283 InputType::Time(_) |
284 InputType::Url(_) |
285 InputType::Week(_) => ValueMode::Value,
286
287 InputType::File(_) => ValueMode::Filename,
288 }
289 }
290
291 #[inline]
292 pub(crate) fn input_type(&self) -> Ref<'_, InputType> {
293 self.input_type.borrow()
294 }
295
296 pub(crate) fn is_nontypeable(&self) -> bool {
298 matches!(
299 *self.input_type(),
300 InputType::Button(_) |
301 InputType::Checkbox(_) |
302 InputType::Color(_) |
303 InputType::File(_) |
304 InputType::Hidden(_) |
305 InputType::Image(_) |
306 InputType::Radio(_) |
307 InputType::Range(_) |
308 InputType::Reset(_) |
309 InputType::Submit(_)
310 )
311 }
312
313 #[inline]
314 pub(crate) fn is_submit_button(&self) -> bool {
315 matches!(
316 *self.input_type(),
317 InputType::Submit(_) | InputType::Image(_)
318 )
319 }
320
321 fn does_minmaxlength_apply(&self) -> bool {
322 matches!(
323 *self.input_type(),
324 InputType::Text(_) |
325 InputType::Search(_) |
326 InputType::Url(_) |
327 InputType::Tel(_) |
328 InputType::Email(_) |
329 InputType::Password(_)
330 )
331 }
332
333 fn does_pattern_apply(&self) -> bool {
334 matches!(
335 *self.input_type(),
336 InputType::Text(_) |
337 InputType::Search(_) |
338 InputType::Url(_) |
339 InputType::Tel(_) |
340 InputType::Email(_) |
341 InputType::Password(_)
342 )
343 }
344
345 fn does_multiple_apply(&self) -> bool {
346 matches!(*self.input_type(), InputType::Email(_))
347 }
348
349 fn does_value_as_number_apply(&self) -> bool {
352 matches!(
353 *self.input_type(),
354 InputType::Date(_) |
355 InputType::Month(_) |
356 InputType::Week(_) |
357 InputType::Time(_) |
358 InputType::DatetimeLocal(_) |
359 InputType::Number(_) |
360 InputType::Range(_)
361 )
362 }
363
364 fn does_value_as_date_apply(&self) -> bool {
365 matches!(
366 *self.input_type(),
367 InputType::Date(_) | InputType::Month(_) | InputType::Week(_) | InputType::Time(_)
368 )
369 }
370
371 fn allowed_value_step(&self) -> Option<f64> {
373 let default_step = self.default_step()?;
376
377 let Some(attribute) = self.upcast::<Element>().get_attribute(&local_name!("step")) else {
380 return Some(default_step * self.step_scale_factor());
381 };
382
383 if attribute.value().eq_ignore_ascii_case("any") {
386 return None;
387 }
388
389 let Some(parsed_value) =
393 parse_floating_point_number(&attribute.value()).filter(|value| *value > 0.0)
394 else {
395 return Some(default_step * self.step_scale_factor());
396 };
397
398 Some(parsed_value * self.step_scale_factor())
402 }
403
404 fn minimum(&self) -> Option<f64> {
406 self.upcast::<Element>()
407 .get_attribute(&local_name!("min"))
408 .and_then(|attribute| self.convert_string_to_number(&attribute.value()))
409 .or_else(|| self.default_minimum())
410 }
411
412 fn maximum(&self) -> Option<f64> {
414 self.upcast::<Element>()
415 .get_attribute(&local_name!("max"))
416 .and_then(|attribute| self.convert_string_to_number(&attribute.value()))
417 .or_else(|| self.default_maximum())
418 }
419
420 fn stepped_minimum(&self) -> Option<f64> {
423 match (self.minimum(), self.allowed_value_step()) {
424 (Some(min), Some(allowed_step)) => {
425 let step_base = self.step_base();
426 let nsteps = (min - step_base) / allowed_step;
428 Some(step_base + (allowed_step * nsteps.ceil()))
430 },
431 (_, _) => None,
432 }
433 }
434
435 fn stepped_maximum(&self) -> Option<f64> {
438 match (self.maximum(), self.allowed_value_step()) {
439 (Some(max), Some(allowed_step)) => {
440 let step_base = self.step_base();
441 let nsteps = (max - step_base) / allowed_step;
443 Some(step_base + (allowed_step * nsteps.floor()))
445 },
446 (_, _) => None,
447 }
448 }
449
450 fn default_minimum(&self) -> Option<f64> {
452 match *self.input_type() {
453 InputType::Range(_) => Some(0.0),
454 _ => None,
455 }
456 }
457
458 fn default_maximum(&self) -> Option<f64> {
460 match *self.input_type() {
461 InputType::Range(_) => Some(100.0),
462 _ => None,
463 }
464 }
465
466 fn default_range_value(&self) -> f64 {
468 let min = self.minimum().unwrap_or(0.0);
469 let max = self.maximum().unwrap_or(100.0);
470 if max < min {
471 min
472 } else {
473 min + (max - min) * 0.5
474 }
475 }
476
477 fn default_step(&self) -> Option<f64> {
479 match *self.input_type() {
480 InputType::Date(_) => Some(1.0),
481 InputType::Month(_) => Some(1.0),
482 InputType::Week(_) => Some(1.0),
483 InputType::Time(_) => Some(60.0),
484 InputType::DatetimeLocal(_) => Some(60.0),
485 InputType::Number(_) => Some(1.0),
486 InputType::Range(_) => Some(1.0),
487 _ => None,
488 }
489 }
490
491 fn step_scale_factor(&self) -> f64 {
493 match *self.input_type() {
494 InputType::Date(_) => 86400000.0,
495 InputType::Month(_) => 1.0,
496 InputType::Week(_) => 604800000.0,
497 InputType::Time(_) => 1000.0,
498 InputType::DatetimeLocal(_) => 1000.0,
499 InputType::Number(_) => 1.0,
500 InputType::Range(_) => 1.0,
501 _ => unreachable!(),
502 }
503 }
504
505 fn step_base(&self) -> f64 {
507 if let Some(minimum) = self
511 .upcast::<Element>()
512 .get_attribute(&local_name!("min"))
513 .and_then(|attribute| self.convert_string_to_number(&attribute.value()))
514 {
515 return minimum;
516 }
517
518 if let Some(value) = self
522 .upcast::<Element>()
523 .get_attribute(&local_name!("value"))
524 .and_then(|attribute| self.convert_string_to_number(&attribute.value()))
525 {
526 return value;
527 }
528
529 if let Some(default_step_base) = self.default_step_base() {
531 return default_step_base;
532 }
533
534 0.0
536 }
537
538 fn default_step_base(&self) -> Option<f64> {
540 match *self.input_type() {
541 InputType::Week(_) => Some(-259200000.0),
542 _ => None,
543 }
544 }
545
546 fn step_up_or_down(&self, n: i32, dir: StepDirection, can_gc: CanGc) -> ErrorResult {
550 if !self.does_value_as_number_apply() {
553 return Err(Error::InvalidState(None));
554 }
555 let step_base = self.step_base();
556
557 let Some(allowed_value_step) = self.allowed_value_step() else {
559 return Err(Error::InvalidState(None));
560 };
561
562 let minimum = self.minimum();
565 let maximum = self.maximum();
566 if let (Some(min), Some(max)) = (minimum, maximum) {
567 if min > max {
568 return Ok(());
569 }
570
571 if let Some(stepped_minimum) = self.stepped_minimum() {
575 if stepped_minimum > max {
576 return Ok(());
577 }
578 }
579 }
580
581 let mut value: f64 = self
585 .convert_string_to_number(&self.Value().str())
586 .unwrap_or(0.0);
587
588 let valueBeforeStepping = value;
590
591 if (value - step_base) % allowed_value_step != 0.0 {
596 value = match dir {
597 StepDirection::Down =>
598 {
600 let intervals_from_base = ((value - step_base) / allowed_value_step).floor();
601 intervals_from_base * allowed_value_step + step_base
602 },
603 StepDirection::Up =>
604 {
606 let intervals_from_base = ((value - step_base) / allowed_value_step).ceil();
607 intervals_from_base * allowed_value_step + step_base
608 },
609 };
610 }
611 else {
613 value += match dir {
618 StepDirection::Down => -f64::from(n) * allowed_value_step,
619 StepDirection::Up => f64::from(n) * allowed_value_step,
620 };
621 }
622
623 if let Some(min) = minimum {
627 if value < min {
628 value = self.stepped_minimum().unwrap_or(value);
629 }
630 }
631
632 if let Some(max) = maximum {
636 if value > max {
637 value = self.stepped_maximum().unwrap_or(value);
638 }
639 }
640
641 match dir {
645 StepDirection::Down => {
646 if value > valueBeforeStepping {
647 return Ok(());
648 }
649 },
650 StepDirection::Up => {
651 if value < valueBeforeStepping {
652 return Ok(());
653 }
654 },
655 }
656
657 self.SetValueAsNumber(value, can_gc)
661 }
662
663 fn suggestions_source_element(&self) -> Option<DomRoot<HTMLDataListElement>> {
665 let list_string = self
666 .upcast::<Element>()
667 .get_string_attribute(&local_name!("list"));
668 if list_string.is_empty() {
669 return None;
670 }
671 let ancestor = self
672 .upcast::<Node>()
673 .GetRootNode(&GetRootNodeOptions::empty());
674 let first_with_id = &ancestor
675 .traverse_preorder(ShadowIncluding::No)
676 .find(|node| {
677 node.downcast::<Element>()
678 .is_some_and(|e| e.Id() == list_string)
679 });
680 first_with_id
681 .as_ref()
682 .and_then(|el| el.downcast::<HTMLDataListElement>())
683 .map(DomRoot::from_ref)
684 }
685
686 fn suffers_from_being_missing(&self, value: &DOMString) -> bool {
688 self.input_type()
689 .as_specific()
690 .suffers_from_being_missing(self, value)
691 }
692
693 fn suffers_from_type_mismatch(&self, value: &DOMString) -> bool {
695 if value.is_empty() {
696 return false;
697 }
698
699 self.input_type()
700 .as_specific()
701 .suffers_from_type_mismatch(self, value)
702 }
703
704 fn suffers_from_pattern_mismatch(&self, value: &DOMString, can_gc: CanGc) -> bool {
706 let pattern_str = self.Pattern();
709 if value.is_empty() || pattern_str.is_empty() || !self.does_pattern_apply() {
710 return false;
711 }
712
713 let cx = GlobalScope::get_cx();
715 let _ac = enter_realm(self);
716 rooted!(in(*cx) let mut pattern = ptr::null_mut::<JSObject>());
717
718 if compile_pattern(cx, &pattern_str.str(), pattern.handle_mut(), can_gc) {
719 if self.Multiple() && self.does_multiple_apply() {
720 !split_commas(&value.str())
721 .all(|s| matches_js_regex(cx, pattern.handle(), s, can_gc).unwrap_or(true))
722 } else {
723 !matches_js_regex(cx, pattern.handle(), &value.str(), can_gc).unwrap_or(true)
724 }
725 } else {
726 false
728 }
729 }
730
731 fn suffers_from_bad_input(&self, value: &DOMString) -> bool {
733 if value.is_empty() {
734 return false;
735 }
736
737 self.input_type()
738 .as_specific()
739 .suffers_from_bad_input(value)
740 }
741
742 fn suffers_from_length_issues(&self, value: &DOMString) -> ValidationFlags {
745 let value_dirty = self.value_dirty.get();
748 let textinput = self.textinput.borrow();
749 let edit_by_user = !textinput.was_last_change_by_set_content();
750
751 if value.is_empty() || !value_dirty || !edit_by_user || !self.does_minmaxlength_apply() {
752 return ValidationFlags::empty();
753 }
754
755 let mut failed_flags = ValidationFlags::empty();
756 let Utf16CodeUnitLength(value_len) = textinput.len_utf16();
757 let min_length = self.MinLength();
758 let max_length = self.MaxLength();
759
760 if min_length != DEFAULT_MIN_LENGTH && value_len < (min_length as usize) {
761 failed_flags.insert(ValidationFlags::TOO_SHORT);
762 }
763
764 if max_length != DEFAULT_MAX_LENGTH && value_len > (max_length as usize) {
765 failed_flags.insert(ValidationFlags::TOO_LONG);
766 }
767
768 failed_flags
769 }
770
771 fn suffers_from_range_issues(&self, value: &DOMString) -> ValidationFlags {
775 if value.is_empty() || !self.does_value_as_number_apply() {
776 return ValidationFlags::empty();
777 }
778
779 let Some(value_as_number) = self.convert_string_to_number(&value.str()) else {
780 return ValidationFlags::empty();
781 };
782
783 let mut failed_flags = ValidationFlags::empty();
784 let min_value = self.minimum();
785 let max_value = self.maximum();
786
787 let has_reversed_range = match (min_value, max_value) {
789 (Some(min), Some(max)) => self.input_type().has_periodic_domain() && min > max,
790 _ => false,
791 };
792
793 if has_reversed_range {
794 if value_as_number > max_value.unwrap() && value_as_number < min_value.unwrap() {
796 failed_flags.insert(ValidationFlags::RANGE_UNDERFLOW);
797 failed_flags.insert(ValidationFlags::RANGE_OVERFLOW);
798 }
799 } else {
800 if let Some(min_value) = min_value {
802 if value_as_number < min_value {
803 failed_flags.insert(ValidationFlags::RANGE_UNDERFLOW);
804 }
805 }
806 if let Some(max_value) = max_value {
808 if value_as_number > max_value {
809 failed_flags.insert(ValidationFlags::RANGE_OVERFLOW);
810 }
811 }
812 }
813
814 if let Some(step) = self.allowed_value_step() {
816 let diff = (self.step_base() - value_as_number) % step / value_as_number;
820 if diff.abs() > 1e-12 {
821 failed_flags.insert(ValidationFlags::STEP_MISMATCH);
822 }
823 }
824
825 failed_flags
826 }
827
828 pub(crate) fn renders_as_text_input_widget(&self) -> bool {
833 matches!(
834 *self.input_type(),
835 InputType::Date(_) |
836 InputType::DatetimeLocal(_) |
837 InputType::Email(_) |
838 InputType::Month(_) |
839 InputType::Number(_) |
840 InputType::Password(_) |
841 InputType::Range(_) |
842 InputType::Search(_) |
843 InputType::Tel(_) |
844 InputType::Text(_) |
845 InputType::Time(_) |
846 InputType::Url(_) |
847 InputType::Week(_)
848 )
849 }
850
851 fn may_have_embedder_control(&self) -> bool {
852 let el = self.upcast::<Element>();
853 matches!(*self.input_type(), InputType::Color(_)) && !el.disabled_state()
854 }
855
856 fn handle_key_reaction(&self, action: KeyReaction, event: &Event, can_gc: CanGc) {
857 match action {
858 KeyReaction::TriggerDefaultAction => {
859 self.implicit_submission(can_gc);
860 event.mark_as_handled();
861 },
862 KeyReaction::DispatchInput(text, is_composing, input_type) => {
863 if event.IsTrusted() {
864 self.textinput.borrow().queue_input_event(
865 self.upcast(),
866 text,
867 is_composing,
868 input_type,
869 );
870 }
871 self.value_dirty.set(true);
872 self.update_placeholder_shown_state();
873 self.upcast::<Node>().dirty(NodeDamage::Other);
874 event.mark_as_handled();
875 },
876 KeyReaction::RedrawSelection => {
877 self.maybe_update_shared_selection();
878 event.mark_as_handled();
879 },
880 KeyReaction::Nothing => (),
881 }
882 }
883
884 fn value_for_shadow_dom(&self) -> DOMString {
886 let input_type = &*self.input_type();
887 match input_type {
888 InputType::Checkbox(_) |
889 InputType::Radio(_) |
890 InputType::Image(_) |
891 InputType::Hidden(_) |
892 InputType::Range(_) => input_type.as_specific().value_for_shadow_dom(self),
893 _ => {
894 if let Some(attribute_value) = self
895 .upcast::<Element>()
896 .get_attribute(&local_name!("value"))
897 .map(|attribute| attribute.Value())
898 {
899 return attribute_value;
900 }
901 input_type.as_specific().value_for_shadow_dom(self)
902 },
903 }
904 }
905
906 fn textinput_mut(&self) -> RefMut<'_, TextInput<EmbedderClipboardProvider>> {
907 self.textinput.borrow_mut()
908 }
909
910 fn placeholder(&self) -> Ref<'_, DOMString> {
911 self.placeholder.borrow()
912 }
913}
914
915pub(crate) trait LayoutHTMLInputElementHelpers<'dom> {
916 fn size_for_layout(self) -> u32;
917 fn selection_for_layout(self) -> Option<SharedSelection>;
918}
919
920impl<'dom> LayoutHTMLInputElementHelpers<'dom> for LayoutDom<'dom, HTMLInputElement> {
921 fn size_for_layout(self) -> u32 {
931 self.unsafe_get().size.get()
932 }
933
934 fn selection_for_layout(self) -> Option<SharedSelection> {
935 Some(self.unsafe_get().shared_selection.clone())
936 }
937}
938
939impl TextControlElement for HTMLInputElement {
940 fn selection_api_applies(&self) -> bool {
942 matches!(
943 *self.input_type(),
944 InputType::Text(_) |
945 InputType::Search(_) |
946 InputType::Url(_) |
947 InputType::Tel(_) |
948 InputType::Password(_)
949 )
950 }
951
952 fn has_selectable_text(&self) -> bool {
960 self.renders_as_text_input_widget() && !self.textinput.borrow().get_content().is_empty()
961 }
962
963 fn has_uncollapsed_selection(&self) -> bool {
964 self.textinput.borrow().has_uncollapsed_selection()
965 }
966
967 fn set_dirty_value_flag(&self, value: bool) {
968 self.value_dirty.set(value)
969 }
970
971 fn select_all(&self) {
972 self.textinput.borrow_mut().select_all();
973 self.maybe_update_shared_selection();
974 }
975
976 fn maybe_update_shared_selection(&self) {
977 let offsets = self.textinput.borrow().sorted_selection_offsets_range();
978 let (start, end) = (offsets.start.0, offsets.end.0);
979 let range = TextByteRange::new(ByteIndex(start), ByteIndex(end));
980 let enabled = self.renders_as_text_input_widget() && self.upcast::<Element>().focus_state();
981
982 let mut shared_selection = self.shared_selection.borrow_mut();
983 if range == shared_selection.range && enabled == shared_selection.enabled {
984 return;
985 }
986
987 *shared_selection = ScriptSelection {
988 range,
989 character_range: self
990 .textinput
991 .borrow()
992 .sorted_selection_character_offsets_range(),
993 enabled,
994 };
995 self.owner_window().layout().set_needs_new_display_list();
996 }
997}
998
999impl HTMLInputElementMethods<crate::DomTypeHolder> for HTMLInputElement {
1000 make_getter!(Accept, "accept");
1002
1003 make_setter!(SetAccept, "accept");
1005
1006 make_bool_getter!(Alpha, "alpha");
1008
1009 make_bool_setter!(SetAlpha, "alpha");
1011
1012 make_getter!(Alt, "alt");
1014
1015 make_setter!(SetAlt, "alt");
1017
1018 make_getter!(DirName, "dirname");
1020
1021 make_setter!(SetDirName, "dirname");
1023
1024 make_bool_getter!(Disabled, "disabled");
1026
1027 make_bool_setter!(SetDisabled, "disabled");
1029
1030 fn GetForm(&self) -> Option<DomRoot<HTMLFormElement>> {
1032 self.form_owner()
1033 }
1034
1035 fn GetFiles(&self) -> Option<DomRoot<FileList>> {
1037 self.input_type()
1038 .as_specific()
1039 .get_files()
1040 .as_ref()
1041 .cloned()
1042 }
1043
1044 fn SetFiles(&self, files: Option<&FileList>) {
1046 if let Some(files) = files {
1047 self.input_type().as_specific().set_files(files)
1048 }
1049 }
1050
1051 make_bool_getter!(DefaultChecked, "checked");
1053
1054 make_bool_setter!(SetDefaultChecked, "checked");
1056
1057 fn Checked(&self) -> bool {
1059 self.upcast::<Element>()
1060 .state()
1061 .contains(ElementState::CHECKED)
1062 }
1063
1064 fn SetChecked(&self, checked: bool, can_gc: CanGc) {
1066 self.update_checked_state(checked, true, can_gc);
1067 self.value_changed(can_gc);
1068 }
1069
1070 make_enumerated_getter!(
1072 ColorSpace,
1073 "colorspace",
1074 "limited-srgb" | "display-p3",
1075 missing => "limited-srgb",
1076 invalid => "limited-srgb"
1077 );
1078
1079 make_setter!(SetColorSpace, "colorspace");
1081
1082 make_bool_getter!(ReadOnly, "readonly");
1084
1085 make_bool_setter!(SetReadOnly, "readonly");
1087
1088 make_uint_getter!(Size, "size", DEFAULT_INPUT_SIZE);
1090
1091 make_limited_uint_setter!(SetSize, "size", DEFAULT_INPUT_SIZE);
1093
1094 fn Type(&self) -> DOMString {
1096 DOMString::from(self.input_type().as_str())
1097 }
1098
1099 make_atomic_setter!(SetType, "type");
1101
1102 fn Value(&self) -> DOMString {
1104 match self.value_mode() {
1105 ValueMode::Value => self.textinput.borrow().get_content(),
1106 ValueMode::Default => self
1107 .upcast::<Element>()
1108 .get_attribute(&local_name!("value"))
1109 .map_or(DOMString::from(""), |a| {
1110 DOMString::from(a.summarize().value)
1111 }),
1112 ValueMode::DefaultOn => self
1113 .upcast::<Element>()
1114 .get_attribute(&local_name!("value"))
1115 .map_or(DOMString::from("on"), |a| {
1116 DOMString::from(a.summarize().value)
1117 }),
1118 ValueMode::Filename => {
1119 let mut path = DOMString::from("");
1120 match self.input_type().as_specific().get_files() {
1121 Some(ref fl) => match fl.Item(0) {
1122 Some(ref f) => {
1123 path.push_str("C:\\fakepath\\");
1124 path.push_str(&f.name().str());
1125 path
1126 },
1127 None => path,
1128 },
1129 None => path,
1130 }
1131 },
1132 }
1133 }
1134
1135 fn SetValue(&self, mut value: DOMString, can_gc: CanGc) -> ErrorResult {
1137 match self.value_mode() {
1138 ValueMode::Value => {
1139 {
1140 self.value_dirty.set(true);
1142
1143 self.sanitize_value(&mut value);
1146
1147 let mut textinput = self.textinput.borrow_mut();
1148
1149 if textinput.get_content() != value {
1154 textinput.set_content(value);
1156
1157 textinput.clear_selection_to_end();
1158 }
1159 }
1160
1161 self.update_placeholder_shown_state();
1165 self.maybe_update_shared_selection();
1166 },
1167 ValueMode::Default | ValueMode::DefaultOn => {
1168 self.upcast::<Element>()
1169 .set_string_attribute(&local_name!("value"), value, can_gc);
1170 },
1171 ValueMode::Filename => {
1172 if value.is_empty() {
1173 let window = self.owner_window();
1174 let fl = FileList::new(&window, vec![], can_gc);
1175 self.input_type().as_specific().set_files(&fl)
1176 } else {
1177 return Err(Error::InvalidState(None));
1178 }
1179 },
1180 }
1181
1182 self.value_changed(can_gc);
1183 self.upcast::<Node>().dirty(NodeDamage::Other);
1184 Ok(())
1185 }
1186
1187 make_getter!(DefaultValue, "value");
1189
1190 make_setter!(SetDefaultValue, "value");
1192
1193 make_getter!(Min, "min");
1195
1196 make_setter!(SetMin, "min");
1198
1199 fn GetList(&self) -> Option<DomRoot<HTMLDataListElement>> {
1201 self.suggestions_source_element()
1202 }
1203
1204 #[expect(unsafe_code)]
1206 fn GetValueAsDate(&self, cx: SafeJSContext) -> Option<NonNull<JSObject>> {
1207 self.input_type()
1208 .as_specific()
1209 .convert_string_to_naive_datetime(self.Value())
1210 .map(|date_time| unsafe {
1211 let time = ClippedTime {
1212 t: (date_time - OffsetDateTime::UNIX_EPOCH).whole_milliseconds() as f64,
1213 };
1214 NonNull::new_unchecked(NewDateObject(*cx, time))
1215 })
1216 }
1217
1218 #[expect(non_snake_case)]
1220 #[expect(unsafe_code)]
1221 fn SetValueAsDate(
1222 &self,
1223 cx: SafeJSContext,
1224 value: *mut JSObject,
1225 can_gc: CanGc,
1226 ) -> ErrorResult {
1227 rooted!(in(*cx) let value = value);
1228 if !self.does_value_as_date_apply() {
1229 return Err(Error::InvalidState(None));
1230 }
1231 if value.is_null() {
1232 return self.SetValue(DOMString::from(""), can_gc);
1233 }
1234 let mut msecs: f64 = 0.0;
1235 unsafe {
1239 let mut isDate = false;
1240 if !ObjectIsDate(*cx, Handle::from(value.handle()), &mut isDate) {
1241 return Err(Error::JSFailed);
1242 }
1243 if !isDate {
1244 return Err(Error::Type(c"Value was not a date".to_owned()));
1245 }
1246 if !DateGetMsecSinceEpoch(*cx, Handle::from(value.handle()), &mut msecs) {
1247 return Err(Error::JSFailed);
1248 }
1249 if !msecs.is_finite() {
1250 return self.SetValue(DOMString::from(""), can_gc);
1251 }
1252 }
1253
1254 let Ok(date_time) = OffsetDateTime::from_unix_timestamp_nanos((msecs * 1e6) as i128) else {
1255 return self.SetValue(DOMString::from(""), can_gc);
1256 };
1257 self.SetValue(
1258 self.input_type()
1259 .as_specific()
1260 .convert_datetime_to_dom_string(date_time),
1261 can_gc,
1262 )
1263 }
1264
1265 fn ValueAsNumber(&self) -> f64 {
1267 self.convert_string_to_number(&self.Value().str())
1268 .unwrap_or(f64::NAN)
1269 }
1270
1271 fn SetValueAsNumber(&self, value: f64, can_gc: CanGc) -> ErrorResult {
1273 if value.is_infinite() {
1274 Err(Error::Type(c"value is not finite".to_owned()))
1275 } else if !self.does_value_as_number_apply() {
1276 Err(Error::InvalidState(None))
1277 } else if value.is_nan() {
1278 self.SetValue(DOMString::from(""), can_gc)
1279 } else if let Some(converted) = self.convert_number_to_string(value) {
1280 self.SetValue(converted, can_gc)
1281 } else {
1282 self.SetValue(DOMString::from(""), can_gc)
1287 }
1288 }
1289
1290 make_getter!(Name, "name");
1292
1293 make_atomic_setter!(SetName, "name");
1295
1296 make_getter!(Placeholder, "placeholder");
1298
1299 make_setter!(SetPlaceholder, "placeholder");
1301
1302 make_form_action_getter!(FormAction, "formaction");
1304
1305 make_setter!(SetFormAction, "formaction");
1307
1308 make_enumerated_getter!(
1310 FormEnctype,
1311 "formenctype",
1312 "application/x-www-form-urlencoded" | "text/plain" | "multipart/form-data",
1313 invalid => "application/x-www-form-urlencoded"
1314 );
1315
1316 make_setter!(SetFormEnctype, "formenctype");
1318
1319 make_enumerated_getter!(
1321 FormMethod,
1322 "formmethod",
1323 "get" | "post" | "dialog",
1324 invalid => "get"
1325 );
1326
1327 make_setter!(SetFormMethod, "formmethod");
1329
1330 make_getter!(FormTarget, "formtarget");
1332
1333 make_setter!(SetFormTarget, "formtarget");
1335
1336 make_bool_getter!(FormNoValidate, "formnovalidate");
1338
1339 make_bool_setter!(SetFormNoValidate, "formnovalidate");
1341
1342 make_getter!(Max, "max");
1344
1345 make_setter!(SetMax, "max");
1347
1348 make_int_getter!(MaxLength, "maxlength", DEFAULT_MAX_LENGTH);
1350
1351 make_limited_int_setter!(SetMaxLength, "maxlength", DEFAULT_MAX_LENGTH);
1353
1354 make_int_getter!(MinLength, "minlength", DEFAULT_MIN_LENGTH);
1356
1357 make_limited_int_setter!(SetMinLength, "minlength", DEFAULT_MIN_LENGTH);
1359
1360 make_bool_getter!(Multiple, "multiple");
1362
1363 make_bool_setter!(SetMultiple, "multiple");
1365
1366 make_getter!(Pattern, "pattern");
1368
1369 make_setter!(SetPattern, "pattern");
1371
1372 make_bool_getter!(Required, "required");
1374
1375 make_bool_setter!(SetRequired, "required");
1377
1378 make_url_getter!(Src, "src");
1380
1381 make_url_setter!(SetSrc, "src");
1383
1384 make_getter!(Step, "step");
1386
1387 make_setter!(SetStep, "step");
1389
1390 make_getter!(UseMap, "usemap");
1392
1393 make_setter!(SetUseMap, "usemap");
1395
1396 fn Indeterminate(&self) -> bool {
1398 self.upcast::<Element>()
1399 .state()
1400 .contains(ElementState::INDETERMINATE)
1401 }
1402
1403 fn SetIndeterminate(&self, val: bool) {
1405 self.upcast::<Element>()
1406 .set_state(ElementState::INDETERMINATE, val)
1407 }
1408
1409 fn GetLabels(&self, can_gc: CanGc) -> Option<DomRoot<NodeList>> {
1413 if matches!(*self.input_type(), InputType::Hidden(_)) {
1414 None
1415 } else {
1416 Some(self.labels_node_list.or_init(|| {
1417 NodeList::new_labels_list(
1418 self.upcast::<Node>().owner_doc().window(),
1419 self.upcast::<HTMLElement>(),
1420 can_gc,
1421 )
1422 }))
1423 }
1424 }
1425
1426 fn Select(&self) {
1428 self.selection().dom_select();
1429 }
1430
1431 fn GetSelectionStart(&self) -> Option<u32> {
1433 self.selection().dom_start().map(|start| start.0 as u32)
1434 }
1435
1436 fn SetSelectionStart(&self, start: Option<u32>) -> ErrorResult {
1438 self.selection()
1439 .set_dom_start(start.map(Utf16CodeUnitLength::from))
1440 }
1441
1442 fn GetSelectionEnd(&self) -> Option<u32> {
1444 self.selection().dom_end().map(|end| end.0 as u32)
1445 }
1446
1447 fn SetSelectionEnd(&self, end: Option<u32>) -> ErrorResult {
1449 self.selection()
1450 .set_dom_end(end.map(Utf16CodeUnitLength::from))
1451 }
1452
1453 fn GetSelectionDirection(&self) -> Option<DOMString> {
1455 self.selection().dom_direction()
1456 }
1457
1458 fn SetSelectionDirection(&self, direction: Option<DOMString>) -> ErrorResult {
1460 self.selection().set_dom_direction(direction)
1461 }
1462
1463 fn SetSelectionRange(&self, start: u32, end: u32, direction: Option<DOMString>) -> ErrorResult {
1465 self.selection().set_dom_range(
1466 Utf16CodeUnitLength::from(start),
1467 Utf16CodeUnitLength::from(end),
1468 direction,
1469 )
1470 }
1471
1472 fn SetRangeText(&self, replacement: DOMString) -> ErrorResult {
1474 self.selection()
1475 .set_dom_range_text(replacement, None, None, Default::default())
1476 }
1477
1478 fn SetRangeText_(
1480 &self,
1481 replacement: DOMString,
1482 start: u32,
1483 end: u32,
1484 selection_mode: SelectionMode,
1485 ) -> ErrorResult {
1486 self.selection().set_dom_range_text(
1487 replacement,
1488 Some(Utf16CodeUnitLength::from(start)),
1489 Some(Utf16CodeUnitLength::from(end)),
1490 selection_mode,
1491 )
1492 }
1493
1494 fn SelectFiles(&self, paths: Vec<DOMString>) {
1497 self.input_type()
1498 .as_specific()
1499 .select_files(self, Some(paths));
1500 }
1501
1502 fn StepUp(&self, n: i32, can_gc: CanGc) -> ErrorResult {
1504 self.step_up_or_down(n, StepDirection::Up, can_gc)
1505 }
1506
1507 fn StepDown(&self, n: i32, can_gc: CanGc) -> ErrorResult {
1509 self.step_up_or_down(n, StepDirection::Down, can_gc)
1510 }
1511
1512 fn WillValidate(&self) -> bool {
1514 self.is_instance_validatable()
1515 }
1516
1517 fn Validity(&self, can_gc: CanGc) -> DomRoot<ValidityState> {
1519 self.validity_state(can_gc)
1520 }
1521
1522 fn CheckValidity(&self, cx: &mut JSContext) -> bool {
1524 self.check_validity(cx)
1525 }
1526
1527 fn ReportValidity(&self, cx: &mut JSContext) -> bool {
1529 self.report_validity(cx)
1530 }
1531
1532 fn ValidationMessage(&self) -> DOMString {
1534 self.validation_message()
1535 }
1536
1537 fn SetCustomValidity(&self, error: DOMString, can_gc: CanGc) {
1539 self.validity_state(can_gc).set_custom_error_message(error);
1540 }
1541}
1542
1543impl HTMLInputElement {
1544 pub(crate) fn form_datums(
1547 &self,
1548 submitter: Option<FormSubmitterElement>,
1549 encoding: Option<&'static Encoding>,
1550 ) -> Vec<FormDatum> {
1551 let ty = self.Type();
1555
1556 let name = self.Name();
1558 let is_submitter = match submitter {
1559 Some(FormSubmitterElement::Input(s)) => self == s,
1560 _ => false,
1561 };
1562
1563 match *self.input_type() {
1564 InputType::Submit(_) | InputType::Button(_) | InputType::Reset(_) if !is_submitter => {
1566 return vec![];
1567 },
1568
1569 InputType::Radio(_) | InputType::Checkbox(_) => {
1571 if !self.Checked() || name.is_empty() {
1572 return vec![];
1573 }
1574 },
1575
1576 InputType::File(_) => {
1577 let mut datums = vec![];
1578
1579 let name = self.Name();
1581
1582 match self.GetFiles() {
1583 Some(fl) => {
1584 for f in fl.iter_files() {
1585 datums.push(FormDatum {
1586 ty: ty.clone(),
1587 name: name.clone(),
1588 value: FormDatumValue::File(DomRoot::from_ref(f)),
1589 });
1590 }
1591 },
1592 None => {
1593 datums.push(FormDatum {
1594 ty,
1597 name,
1598 value: FormDatumValue::String(DOMString::from("")),
1599 })
1600 },
1601 }
1602
1603 return datums;
1604 },
1605
1606 InputType::Image(_) => return vec![], InputType::Hidden(_) => {
1610 if name.to_ascii_lowercase() == "_charset_" {
1611 return vec![FormDatum {
1612 ty,
1613 name,
1614 value: FormDatumValue::String(match encoding {
1615 None => DOMString::from("UTF-8"),
1616 Some(enc) => DOMString::from(enc.name()),
1617 }),
1618 }];
1619 }
1620 },
1621
1622 _ => {
1624 if name.is_empty() {
1625 return vec![];
1626 }
1627 },
1628 }
1629
1630 vec![FormDatum {
1632 ty,
1633 name,
1634 value: FormDatumValue::String(self.Value()),
1635 }]
1636 }
1637
1638 fn radio_group_name(&self) -> Option<Atom> {
1640 self.upcast::<Element>()
1641 .get_name()
1642 .filter(|name| !name.is_empty())
1643 }
1644
1645 fn update_checked_state(&self, checked: bool, dirty: bool, can_gc: CanGc) {
1646 self.upcast::<Element>()
1647 .set_state(ElementState::CHECKED, checked);
1648
1649 if dirty {
1650 self.checked_changed.set(true);
1651 }
1652
1653 if matches!(*self.input_type(), InputType::Radio(_)) && checked {
1654 broadcast_radio_checked(self, self.radio_group_name().as_ref(), can_gc);
1655 }
1656
1657 self.upcast::<Node>().dirty(NodeDamage::Other);
1658 }
1659
1660 pub(crate) fn is_mutable(&self) -> bool {
1662 !(self.upcast::<Element>().disabled_state() || self.ReadOnly())
1665 }
1666
1667 pub(crate) fn reset(&self, can_gc: CanGc) {
1677 self.value_dirty.set(false);
1678
1679 let mut value = self.DefaultValue();
1681 self.sanitize_value(&mut value);
1682 self.textinput.borrow_mut().set_content(value);
1683
1684 let input_type = &*self.input_type();
1685 if matches!(input_type, InputType::Radio(_) | InputType::Checkbox(_)) {
1686 self.update_checked_state(self.DefaultChecked(), false, can_gc);
1687 self.checked_changed.set(false);
1688 }
1689
1690 if matches!(input_type, InputType::File(_)) {
1691 input_type.as_specific().set_files(&FileList::new(
1692 &self.owner_window(),
1693 vec![],
1694 can_gc,
1695 ));
1696 }
1697
1698 self.value_changed(can_gc);
1699 }
1700
1701 pub(crate) fn clear(&self, can_gc: CanGc) {
1704 self.value_dirty.set(false);
1706 self.checked_changed.set(false);
1707 self.textinput.borrow_mut().set_content(DOMString::from(""));
1709 self.update_checked_state(self.DefaultChecked(), false, can_gc);
1711 if self.input_type().as_specific().get_files().is_some() {
1713 let window = self.owner_window();
1714 let filelist = FileList::new(&window, vec![], can_gc);
1715 self.input_type().as_specific().set_files(&filelist);
1716 }
1717
1718 {
1721 let mut textinput = self.textinput.borrow_mut();
1722 let mut value = textinput.get_content();
1723 self.sanitize_value(&mut value);
1724 textinput.set_content(value);
1725 }
1726
1727 self.value_changed(can_gc);
1728 }
1729
1730 fn update_placeholder_shown_state(&self) {
1731 if !self.input_type().is_textual_or_password() {
1732 self.upcast::<Element>().set_placeholder_shown_state(false);
1733 } else {
1734 let has_placeholder = !self.placeholder.borrow().is_empty();
1735 let has_value = !self.textinput.borrow().is_empty();
1736 self.upcast::<Element>()
1737 .set_placeholder_shown_state(has_placeholder && !has_value);
1738 }
1739 }
1740
1741 pub(crate) fn select_files_for_webdriver(
1742 &self,
1743 test_paths: Vec<DOMString>,
1744 response_sender: GenericSender<Result<bool, ErrorStatus>>,
1745 ) {
1746 let mut stored_sender = self.pending_webdriver_response.borrow_mut();
1747 assert!(stored_sender.is_none());
1748
1749 *stored_sender = Some(PendingWebDriverResponse {
1750 response_sender,
1751 expected_file_count: test_paths.len(),
1752 });
1753
1754 self.input_type()
1755 .as_specific()
1756 .select_files(self, Some(test_paths));
1757 }
1758
1759 fn sanitize_value(&self, value: &mut DOMString) {
1761 self.input_type().as_specific().sanitize_value(self, value);
1762 }
1763
1764 #[cfg_attr(crown, expect(crown::unrooted_must_root))]
1765 fn selection(&self) -> TextControlSelection<'_, Self> {
1766 TextControlSelection::new(self, &self.textinput)
1767 }
1768
1769 fn implicit_submission(&self, can_gc: CanGc) {
1771 let doc = self.owner_document();
1772 let node = doc.upcast::<Node>();
1773 let owner = self.form_owner();
1774 let form = match owner {
1775 None => return,
1776 Some(ref f) => f,
1777 };
1778
1779 if self.upcast::<Element>().click_in_progress() {
1780 return;
1781 }
1782 let submit_button = node
1783 .traverse_preorder(ShadowIncluding::No)
1784 .filter_map(DomRoot::downcast::<HTMLInputElement>)
1785 .filter(|input| matches!(*input.input_type(), InputType::Submit(_)))
1786 .find(|r| r.form_owner() == owner);
1787 match submit_button {
1788 Some(ref button) => {
1789 if button.is_instance_activatable() {
1790 button
1793 .upcast::<Node>()
1794 .fire_synthetic_pointer_event_not_trusted(atom!("click"), can_gc);
1795 }
1796 },
1797 None => {
1798 let mut inputs = node
1799 .traverse_preorder(ShadowIncluding::No)
1800 .filter_map(DomRoot::downcast::<HTMLInputElement>)
1801 .filter(|input| {
1802 input.form_owner() == owner &&
1803 matches!(
1804 *input.input_type(),
1805 InputType::Text(_) |
1806 InputType::Search(_) |
1807 InputType::Url(_) |
1808 InputType::Tel(_) |
1809 InputType::Email(_) |
1810 InputType::Password(_) |
1811 InputType::Date(_) |
1812 InputType::Month(_) |
1813 InputType::Week(_) |
1814 InputType::Time(_) |
1815 InputType::DatetimeLocal(_) |
1816 InputType::Number(_)
1817 )
1818 });
1819
1820 if inputs.nth(1).is_some() {
1821 return;
1823 }
1824 form.submit(
1825 SubmittedFrom::NotFromForm,
1826 FormSubmitterElement::Form(form),
1827 can_gc,
1828 );
1829 },
1830 }
1831 }
1832
1833 fn convert_string_to_number(&self, value: &str) -> Option<f64> {
1835 self.input_type()
1836 .as_specific()
1837 .convert_string_to_number(value)
1838 }
1839
1840 fn convert_number_to_string(&self, value: f64) -> Option<DOMString> {
1842 self.input_type()
1843 .as_specific()
1844 .convert_number_to_string(value)
1845 }
1846
1847 fn update_related_validity_states(&self, can_gc: CanGc) {
1848 match *self.input_type() {
1849 InputType::Radio(_) => {
1850 perform_radio_group_validation(self, self.radio_group_name().as_ref(), can_gc)
1851 },
1852 _ => {
1853 self.validity_state(can_gc)
1854 .perform_validation_and_update(ValidationFlags::all(), can_gc);
1855 },
1856 }
1857 }
1858
1859 #[expect(unsafe_code)]
1860 fn value_changed(&self, can_gc: CanGc) {
1861 self.maybe_update_shared_selection();
1862 self.update_related_validity_states(can_gc);
1863 let mut cx = unsafe { script_bindings::script_runtime::temp_cx() };
1865 let cx = &mut cx;
1866 self.input_type().as_specific().update_shadow_tree(cx, self);
1867 }
1868
1869 fn show_the_picker_if_applicable(&self) {
1871 if !self.is_mutable() {
1875 return;
1876 }
1877
1878 self.input_type()
1881 .as_specific()
1882 .show_the_picker_if_applicable(self);
1883 }
1884
1885 pub(crate) fn handle_color_picker_response(&self, response: Option<RgbColor>, can_gc: CanGc) {
1886 if let InputType::Color(ref color_input_type) = *self.input_type() {
1887 color_input_type.handle_color_picker_response(self, response, can_gc)
1888 }
1889 }
1890
1891 pub(crate) fn handle_file_picker_response(
1892 &self,
1893 response: Option<Vec<SelectedFile>>,
1894 can_gc: CanGc,
1895 ) {
1896 if let InputType::File(ref file_input_type) = *self.input_type() {
1897 file_input_type.handle_file_picker_response(self, response, can_gc)
1898 }
1899 }
1900
1901 fn handle_focus_event(&self, event: &FocusEvent) {
1902 let event_type = event.upcast::<Event>().type_();
1903 if *event_type == *"blur" {
1904 self.owner_document()
1905 .embedder_controls()
1906 .hide_embedder_control(self.upcast());
1907 } else if *event_type == *"focus" {
1908 let input_type = &*self.input_type();
1909 let Ok(input_method_type) = input_type.try_into() else {
1910 return;
1911 };
1912
1913 self.owner_document()
1914 .embedder_controls()
1915 .show_embedder_control(
1916 ControlElement::Ime(DomRoot::from_ref(self.upcast())),
1917 EmbedderControlRequest::InputMethod(InputMethodRequest {
1918 input_method_type,
1919 text: self.Value().to_string(),
1920 insertion_point: self.GetSelectionEnd(),
1921 multiline: false,
1922 allow_virtual_keyboard: self.owner_window().has_sticky_activation(),
1924 }),
1925 None,
1926 );
1927 } else {
1928 unreachable!("Got unexpected FocusEvent {event_type:?}");
1929 }
1930 }
1931
1932 fn handle_mouse_event(&self, mouse_event: &MouseEvent) {
1933 if mouse_event.upcast::<Event>().DefaultPrevented() {
1934 return;
1935 }
1936
1937 if !self.input_type().is_textual_or_password() || self.textinput.borrow().is_empty() {
1940 return;
1941 }
1942 let node = self.upcast();
1943 if self
1944 .textinput
1945 .borrow_mut()
1946 .handle_mouse_event(node, mouse_event)
1947 {
1948 self.maybe_update_shared_selection();
1949 }
1950 }
1951}
1952
1953impl VirtualMethods for HTMLInputElement {
1954 fn super_type(&self) -> Option<&dyn VirtualMethods> {
1955 Some(self.upcast::<HTMLElement>() as &dyn VirtualMethods)
1956 }
1957
1958 fn attribute_mutated(&self, cx: &mut JSContext, attr: &Attr, mutation: AttributeMutation) {
1959 let could_have_had_embedder_control = self.may_have_embedder_control();
1960
1961 self.super_type()
1962 .unwrap()
1963 .attribute_mutated(cx, attr, mutation);
1964
1965 match *attr.local_name() {
1966 local_name!("disabled") => {
1967 let disabled_state = match mutation {
1968 AttributeMutation::Set(None, _) => true,
1969 AttributeMutation::Set(Some(_), _) => {
1970 return;
1972 },
1973 AttributeMutation::Removed => false,
1974 };
1975 let el = self.upcast::<Element>();
1976 el.set_disabled_state(disabled_state);
1977 el.set_enabled_state(!disabled_state);
1978 el.check_ancestors_disabled_state_for_form_control();
1979
1980 if self.input_type().is_textual() {
1981 let read_write = !(self.ReadOnly() || el.disabled_state());
1982 el.set_read_write_state(read_write);
1983 }
1984 },
1985 local_name!("checked") if !self.checked_changed.get() => {
1986 let checked_state = match mutation {
1987 AttributeMutation::Set(None, _) => true,
1988 AttributeMutation::Set(Some(_), _) => {
1989 return;
1991 },
1992 AttributeMutation::Removed => false,
1993 };
1994 self.update_checked_state(checked_state, false, CanGc::from_cx(cx));
1995 },
1996 local_name!("size") => {
1997 let size = mutation.new_value(attr).map(|value| value.as_uint());
1998 self.size.set(size.unwrap_or(DEFAULT_INPUT_SIZE));
1999 },
2000 local_name!("type") => {
2001 match mutation {
2002 AttributeMutation::Set(..) => {
2003 let (old_value_mode, old_idl_value) = (self.value_mode(), self.Value());
2005 let previously_selectable = self.selection_api_applies();
2006
2007 *self.input_type.borrow_mut() =
2008 InputType::new_from_atom(attr.value().as_atom());
2009
2010 let element = self.upcast::<Element>();
2011 if self.input_type().is_textual() {
2012 let read_write = !(self.ReadOnly() || element.disabled_state());
2013 element.set_read_write_state(read_write);
2014 } else {
2015 element.set_read_write_state(false);
2016 }
2017
2018 let new_value_mode = self.value_mode();
2019 match (&old_value_mode, old_idl_value.is_empty(), new_value_mode) {
2020 (&ValueMode::Value, false, ValueMode::Default) |
2022 (&ValueMode::Value, false, ValueMode::DefaultOn) => {
2023 self.SetValue(old_idl_value, CanGc::from_cx(cx))
2024 .expect("Failed to set input value on type change to a default ValueMode.");
2025 },
2026
2027 (_, _, ValueMode::Value) if old_value_mode != ValueMode::Value => {
2029 self.SetValue(
2030 self.upcast::<Element>()
2031 .get_attribute(&local_name!("value"))
2032 .map_or(DOMString::from(""), |a| {
2033 DOMString::from(a.summarize().value)
2034 }),
2035 CanGc::from_cx(cx),
2036 )
2037 .expect(
2038 "Failed to set input value on type change to ValueMode::Value.",
2039 );
2040 self.value_dirty.set(false);
2041 },
2042
2043 (_, _, ValueMode::Filename)
2045 if old_value_mode != ValueMode::Filename =>
2046 {
2047 self.SetValue(DOMString::from(""), CanGc::from_cx(cx))
2048 .expect("Failed to set input value on type change to ValueMode::Filename.");
2049 },
2050 _ => {},
2051 }
2052
2053 self.input_type()
2055 .as_specific()
2056 .signal_type_change(self, CanGc::from_cx(cx));
2057
2058 let mut textinput = self.textinput.borrow_mut();
2060 let mut value = textinput.get_content();
2061 self.sanitize_value(&mut value);
2062 textinput.set_content(value);
2063 self.upcast::<Node>().dirty(NodeDamage::Other);
2064
2065 if !previously_selectable && self.selection_api_applies() {
2067 textinput.clear_selection_to_start();
2068 }
2069 },
2070 AttributeMutation::Removed => {
2071 self.input_type()
2072 .as_specific()
2073 .signal_type_change(self, CanGc::from_cx(cx));
2074 *self.input_type.borrow_mut() = InputType::new_text();
2075 let el = self.upcast::<Element>();
2076
2077 let read_write = !(self.ReadOnly() || el.disabled_state());
2078 el.set_read_write_state(read_write);
2079 },
2080 }
2081
2082 self.update_placeholder_shown_state();
2083 self.input_type()
2084 .as_specific()
2085 .update_placeholder_contents(cx, self);
2086 },
2087 local_name!("value") if !self.value_dirty.get() => {
2088 let value = mutation.new_value(attr).map(|value| (**value).to_owned());
2092 let mut value = value.map_or(DOMString::new(), DOMString::from);
2093
2094 self.sanitize_value(&mut value);
2095 self.textinput.borrow_mut().set_content(value);
2096 self.update_placeholder_shown_state();
2097 },
2098 local_name!("maxlength") => match *attr.value() {
2099 AttrValue::Int(_, value) => {
2100 let mut textinput = self.textinput.borrow_mut();
2101
2102 if value < 0 {
2103 textinput.set_max_length(None);
2104 } else {
2105 textinput.set_max_length(Some(Utf16CodeUnitLength(value as usize)))
2106 }
2107 },
2108 _ => panic!("Expected an AttrValue::Int"),
2109 },
2110 local_name!("minlength") => match *attr.value() {
2111 AttrValue::Int(_, value) => {
2112 let mut textinput = self.textinput.borrow_mut();
2113
2114 if value < 0 {
2115 textinput.set_min_length(None);
2116 } else {
2117 textinput.set_min_length(Some(Utf16CodeUnitLength(value as usize)))
2118 }
2119 },
2120 _ => panic!("Expected an AttrValue::Int"),
2121 },
2122 local_name!("placeholder") => {
2123 {
2124 let mut placeholder = self.placeholder.borrow_mut();
2125 placeholder.clear();
2126 if let AttributeMutation::Set(..) = mutation {
2127 placeholder
2128 .extend(attr.value().chars().filter(|&c| c != '\n' && c != '\r'));
2129 }
2130 }
2131 self.update_placeholder_shown_state();
2132 self.input_type()
2133 .as_specific()
2134 .update_placeholder_contents(cx, self);
2135 },
2136 local_name!("readonly") => {
2137 if self.input_type().is_textual() {
2138 let el = self.upcast::<Element>();
2139 match mutation {
2140 AttributeMutation::Set(..) => {
2141 el.set_read_write_state(false);
2142 },
2143 AttributeMutation::Removed => {
2144 el.set_read_write_state(!el.disabled_state());
2145 },
2146 }
2147 }
2148 },
2149 local_name!("form") => {
2150 self.form_attribute_mutated(mutation, CanGc::from_cx(cx));
2151 },
2152 _ => {
2153 self.input_type()
2154 .as_specific()
2155 .attribute_mutated(cx, self, attr, mutation);
2156 },
2157 }
2158
2159 self.value_changed(CanGc::from_cx(cx));
2160
2161 if could_have_had_embedder_control && !self.may_have_embedder_control() {
2162 self.owner_document()
2163 .embedder_controls()
2164 .hide_embedder_control(self.upcast());
2165 }
2166 }
2167
2168 fn parse_plain_attribute(&self, name: &LocalName, value: DOMString) -> AttrValue {
2169 match *name {
2170 local_name!("accept") => AttrValue::from_comma_separated_tokenlist(value.into()),
2171 local_name!("size") => AttrValue::from_limited_u32(value.into(), DEFAULT_INPUT_SIZE),
2172 local_name!("type") => AttrValue::from_atomic(value.into()),
2173 local_name!("maxlength") => {
2174 AttrValue::from_limited_i32(value.into(), DEFAULT_MAX_LENGTH)
2175 },
2176 local_name!("minlength") => {
2177 AttrValue::from_limited_i32(value.into(), DEFAULT_MIN_LENGTH)
2178 },
2179 _ => self
2180 .super_type()
2181 .unwrap()
2182 .parse_plain_attribute(name, value),
2183 }
2184 }
2185
2186 fn bind_to_tree(&self, cx: &mut JSContext, context: &BindContext) {
2187 if let Some(s) = self.super_type() {
2188 s.bind_to_tree(cx, context);
2189 }
2190 self.upcast::<Element>()
2191 .check_ancestors_disabled_state_for_form_control();
2192
2193 self.input_type()
2194 .as_specific()
2195 .bind_to_tree(cx, self, context);
2196
2197 self.value_changed(CanGc::from_cx(cx));
2198 }
2199
2200 fn unbind_from_tree(&self, context: &UnbindContext, can_gc: CanGc) {
2201 let form_owner = self.form_owner();
2202 self.super_type().unwrap().unbind_from_tree(context, can_gc);
2203
2204 let node = self.upcast::<Node>();
2205 let el = self.upcast::<Element>();
2206 if node
2207 .ancestors()
2208 .any(|ancestor| ancestor.is::<HTMLFieldSetElement>())
2209 {
2210 el.check_ancestors_disabled_state_for_form_control();
2211 } else {
2212 el.check_disabled_attribute();
2213 }
2214
2215 self.input_type()
2216 .as_specific()
2217 .unbind_from_tree(self, form_owner, context, can_gc);
2218
2219 self.validity_state(can_gc)
2220 .perform_validation_and_update(ValidationFlags::all(), can_gc);
2221 }
2222
2223 fn handle_event(&self, event: &Event, can_gc: CanGc) {
2229 if let Some(mouse_event) = event.downcast::<MouseEvent>() {
2230 self.handle_mouse_event(mouse_event);
2231 event.mark_as_handled();
2232 } else if event.type_() == atom!("keydown") &&
2233 !event.DefaultPrevented() &&
2234 self.input_type().is_textual_or_password()
2235 {
2236 if let Some(keyevent) = event.downcast::<KeyboardEvent>() {
2237 let action = self.textinput.borrow_mut().handle_keydown(keyevent);
2240 self.handle_key_reaction(action, event, can_gc);
2241 }
2242 } else if (event.type_() == atom!("compositionstart") ||
2243 event.type_() == atom!("compositionupdate") ||
2244 event.type_() == atom!("compositionend")) &&
2245 self.input_type().is_textual_or_password()
2246 {
2247 if let Some(compositionevent) = event.downcast::<CompositionEvent>() {
2248 if event.type_() == atom!("compositionend") {
2249 let action = self
2250 .textinput
2251 .borrow_mut()
2252 .handle_compositionend(compositionevent);
2253 self.handle_key_reaction(action, event, can_gc);
2254 self.upcast::<Node>().dirty(NodeDamage::Other);
2255 self.update_placeholder_shown_state();
2256 } else if event.type_() == atom!("compositionupdate") {
2257 let action = self
2258 .textinput
2259 .borrow_mut()
2260 .handle_compositionupdate(compositionevent);
2261 self.handle_key_reaction(action, event, can_gc);
2262 self.upcast::<Node>().dirty(NodeDamage::Other);
2263 self.update_placeholder_shown_state();
2264 } else if event.type_() == atom!("compositionstart") {
2265 self.update_placeholder_shown_state();
2267 }
2268 event.mark_as_handled();
2269 }
2270 } else if let Some(clipboard_event) = event.downcast::<ClipboardEvent>() {
2271 let reaction = self
2272 .textinput
2273 .borrow_mut()
2274 .handle_clipboard_event(clipboard_event);
2275 let flags = reaction.flags;
2276 if flags.contains(ClipboardEventFlags::FireClipboardChangedEvent) {
2277 self.owner_document().event_handler().fire_clipboard_event(
2278 None,
2279 ClipboardEventType::Change,
2280 can_gc,
2281 );
2282 }
2283 if flags.contains(ClipboardEventFlags::QueueInputEvent) {
2284 self.textinput.borrow().queue_input_event(
2285 self.upcast(),
2286 reaction.text,
2287 IsComposing::NotComposing,
2288 reaction.input_type,
2289 );
2290 }
2291 if !flags.is_empty() {
2292 event.mark_as_handled();
2293 self.upcast::<Node>().dirty(NodeDamage::ContentOrHeritage);
2294 }
2295 } else if let Some(event) = event.downcast::<FocusEvent>() {
2296 self.handle_focus_event(event)
2297 }
2298
2299 self.value_changed(can_gc);
2300
2301 if let Some(super_type) = self.super_type() {
2302 super_type.handle_event(event, can_gc);
2303 }
2304 }
2305
2306 fn cloning_steps(
2308 &self,
2309 cx: &mut JSContext,
2310 copy: &Node,
2311 maybe_doc: Option<&Document>,
2312 clone_children: CloneChildrenFlag,
2313 ) {
2314 if let Some(s) = self.super_type() {
2315 s.cloning_steps(cx, copy, maybe_doc, clone_children);
2316 }
2317 let elem = copy.downcast::<HTMLInputElement>().unwrap();
2318 elem.value_dirty.set(self.value_dirty.get());
2319 elem.checked_changed.set(self.checked_changed.get());
2320 elem.upcast::<Element>()
2321 .set_state(ElementState::CHECKED, self.Checked());
2322 elem.textinput
2323 .borrow_mut()
2324 .set_content(self.textinput.borrow().get_content());
2325 self.value_changed(CanGc::from_cx(cx));
2326 }
2327}
2328
2329impl FormControl for HTMLInputElement {
2330 fn form_owner(&self) -> Option<DomRoot<HTMLFormElement>> {
2331 self.form_owner.get()
2332 }
2333
2334 fn set_form_owner(&self, form: Option<&HTMLFormElement>) {
2335 self.form_owner.set(form);
2336 }
2337
2338 fn to_element(&self) -> &Element {
2339 self.upcast::<Element>()
2340 }
2341}
2342
2343impl Validatable for HTMLInputElement {
2344 fn as_element(&self) -> &Element {
2345 self.upcast()
2346 }
2347
2348 fn validity_state(&self, can_gc: CanGc) -> DomRoot<ValidityState> {
2349 self.validity_state
2350 .or_init(|| ValidityState::new(&self.owner_window(), self.upcast(), can_gc))
2351 }
2352
2353 fn is_instance_validatable(&self) -> bool {
2354 match *self.input_type() {
2361 InputType::Hidden(_) | InputType::Button(_) | InputType::Reset(_) => false,
2362 _ => {
2363 !(self.upcast::<Element>().disabled_state() ||
2364 self.ReadOnly() ||
2365 is_barred_by_datalist_ancestor(self.upcast()))
2366 },
2367 }
2368 }
2369
2370 fn perform_validation(
2371 &self,
2372 validate_flags: ValidationFlags,
2373 can_gc: CanGc,
2374 ) -> ValidationFlags {
2375 let mut failed_flags = ValidationFlags::empty();
2376 let value = self.Value();
2377
2378 if validate_flags.contains(ValidationFlags::VALUE_MISSING) &&
2379 self.suffers_from_being_missing(&value)
2380 {
2381 failed_flags.insert(ValidationFlags::VALUE_MISSING);
2382 }
2383
2384 if validate_flags.contains(ValidationFlags::TYPE_MISMATCH) &&
2385 self.suffers_from_type_mismatch(&value)
2386 {
2387 failed_flags.insert(ValidationFlags::TYPE_MISMATCH);
2388 }
2389
2390 if validate_flags.contains(ValidationFlags::PATTERN_MISMATCH) &&
2391 self.suffers_from_pattern_mismatch(&value, can_gc)
2392 {
2393 failed_flags.insert(ValidationFlags::PATTERN_MISMATCH);
2394 }
2395
2396 if validate_flags.contains(ValidationFlags::BAD_INPUT) &&
2397 self.suffers_from_bad_input(&value)
2398 {
2399 failed_flags.insert(ValidationFlags::BAD_INPUT);
2400 }
2401
2402 if validate_flags.intersects(ValidationFlags::TOO_LONG | ValidationFlags::TOO_SHORT) {
2403 failed_flags |= self.suffers_from_length_issues(&value);
2404 }
2405
2406 if validate_flags.intersects(
2407 ValidationFlags::RANGE_UNDERFLOW |
2408 ValidationFlags::RANGE_OVERFLOW |
2409 ValidationFlags::STEP_MISMATCH,
2410 ) {
2411 failed_flags |= self.suffers_from_range_issues(&value);
2412 }
2413
2414 failed_flags & validate_flags
2415 }
2416}
2417
2418impl Activatable for HTMLInputElement {
2419 fn as_element(&self) -> &Element {
2420 self.upcast()
2421 }
2422
2423 fn is_instance_activatable(&self) -> bool {
2424 match *self.input_type() {
2425 InputType::Submit(_) |
2432 InputType::Reset(_) |
2433 InputType::File(_) |
2434 InputType::Image(_) |
2435 InputType::Button(_) => self.is_mutable(),
2436 InputType::Checkbox(_) | InputType::Radio(_) | InputType::Color(_) => true,
2440 _ => false,
2441 }
2442 }
2443
2444 fn legacy_pre_activation_behavior(&self, can_gc: CanGc) -> Option<InputActivationState> {
2446 let activation_state = self
2447 .input_type()
2448 .as_specific()
2449 .legacy_pre_activation_behavior(self, can_gc);
2450
2451 if activation_state.is_some() {
2452 self.value_changed(can_gc);
2453 }
2454
2455 activation_state
2456 }
2457
2458 fn legacy_canceled_activation_behavior(
2460 &self,
2461 cache: Option<InputActivationState>,
2462 can_gc: CanGc,
2463 ) {
2464 let ty = self.input_type();
2466 let cache = match cache {
2467 Some(cache) => {
2468 if (cache.was_radio && !matches!(*ty, InputType::Radio(_))) ||
2469 (cache.was_checkbox && !matches!(*ty, InputType::Checkbox(_)))
2470 {
2471 return;
2474 }
2475 cache
2476 },
2477 None => {
2478 return;
2479 },
2480 };
2481
2482 ty.as_specific()
2484 .legacy_canceled_activation_behavior(self, cache, can_gc);
2485
2486 self.value_changed(can_gc);
2487 }
2488
2489 fn activation_behavior(&self, event: &Event, target: &EventTarget, can_gc: CanGc) {
2491 self.input_type()
2492 .as_specific()
2493 .activation_behavior(self, event, target, can_gc)
2494 }
2495}
2496
2497fn compile_pattern(
2501 cx: SafeJSContext,
2502 pattern_str: &str,
2503 out_regex: MutableHandleObject,
2504 can_gc: CanGc,
2505) -> bool {
2506 if check_js_regex_syntax(cx, pattern_str, can_gc) {
2508 let pattern_str = format!("^(?:{})$", pattern_str);
2510 let flags = RegExpFlags {
2511 flags_: RegExpFlag_UnicodeSets,
2512 };
2513 new_js_regex(cx, &pattern_str, flags, out_regex, can_gc)
2514 } else {
2515 false
2516 }
2517}
2518
2519#[expect(unsafe_code)]
2520fn check_js_regex_syntax(cx: SafeJSContext, pattern: &str, _can_gc: CanGc) -> bool {
2523 let pattern: Vec<u16> = pattern.encode_utf16().collect();
2524 unsafe {
2525 rooted!(in(*cx) let mut exception = UndefinedValue());
2526
2527 let valid = CheckRegExpSyntax(
2528 *cx,
2529 pattern.as_ptr(),
2530 pattern.len(),
2531 RegExpFlags {
2532 flags_: RegExpFlag_UnicodeSets,
2533 },
2534 exception.handle_mut(),
2535 );
2536
2537 if !valid {
2538 JS_ClearPendingException(*cx);
2539 return false;
2540 }
2541
2542 exception.is_undefined()
2545 }
2546}
2547
2548#[expect(unsafe_code)]
2549pub(crate) fn new_js_regex(
2550 cx: SafeJSContext,
2551 pattern: &str,
2552 flags: RegExpFlags,
2553 mut out_regex: MutableHandleObject,
2554 _can_gc: CanGc,
2555) -> bool {
2556 let pattern: Vec<u16> = pattern.encode_utf16().collect();
2557 unsafe {
2558 out_regex.set(NewUCRegExpObject(
2559 *cx,
2560 pattern.as_ptr(),
2561 pattern.len(),
2562 flags,
2563 ));
2564 if out_regex.is_null() {
2565 JS_ClearPendingException(*cx);
2566 return false;
2567 }
2568 }
2569 true
2570}
2571
2572#[expect(unsafe_code)]
2573fn matches_js_regex(
2574 cx: SafeJSContext,
2575 regex_obj: HandleObject,
2576 value: &str,
2577 _can_gc: CanGc,
2578) -> Result<bool, ()> {
2579 let mut value: Vec<u16> = value.encode_utf16().collect();
2580
2581 unsafe {
2582 let mut is_regex = false;
2583 assert!(ObjectIsRegExp(*cx, regex_obj, &mut is_regex));
2584 assert!(is_regex);
2585
2586 rooted!(in(*cx) let mut rval = UndefinedValue());
2587 let mut index = 0;
2588
2589 let ok = ExecuteRegExpNoStatics(
2590 *cx,
2591 regex_obj,
2592 value.as_mut_ptr(),
2593 value.len(),
2594 &mut index,
2595 true,
2596 rval.handle_mut(),
2597 );
2598
2599 if ok {
2600 Ok(!rval.is_null())
2601 } else {
2602 JS_ClearPendingException(*cx);
2603 Err(())
2604 }
2605 }
2606}
2607
2608#[derive(MallocSizeOf)]
2612struct PendingWebDriverResponse {
2613 response_sender: GenericSender<Result<bool, ErrorStatus>>,
2615 expected_file_count: usize,
2617}
2618
2619impl PendingWebDriverResponse {
2620 fn finish(self, number_files_selected: usize) {
2621 if number_files_selected == self.expected_file_count {
2622 let _ = self.response_sender.send(Ok(false));
2623 } else {
2624 let _ = self.response_sender.send(Err(ErrorStatus::InvalidArgument));
2627 }
2628 }
2629}