script/dom/html/
htmltextareaelement.rs

1/* This Source Code Form is subject to the terms of the Mozilla Public
2 * License, v. 2.0. If a copy of the MPL was not distributed with this
3 * file, You can obtain one at https://mozilla.org/MPL/2.0/. */
4
5use std::cell::Cell;
6use std::default::Default;
7use std::ops::Range;
8
9use dom_struct::dom_struct;
10use html5ever::{LocalName, Prefix, local_name, ns};
11use js::rust::HandleObject;
12use style::attr::AttrValue;
13use stylo_dom::ElementState;
14
15use crate::clipboard_provider::EmbedderClipboardProvider;
16use crate::dom::attr::Attr;
17use crate::dom::bindings::cell::DomRefCell;
18use crate::dom::bindings::codegen::Bindings::EventBinding::EventMethods;
19use crate::dom::bindings::codegen::Bindings::HTMLFormElementBinding::SelectionMode;
20use crate::dom::bindings::codegen::Bindings::HTMLTextAreaElementBinding::HTMLTextAreaElementMethods;
21use crate::dom::bindings::codegen::Bindings::NodeBinding::NodeMethods;
22use crate::dom::bindings::error::ErrorResult;
23use crate::dom::bindings::inheritance::Castable;
24use crate::dom::bindings::root::{DomRoot, LayoutDom, MutNullableDom};
25use crate::dom::bindings::str::DOMString;
26use crate::dom::clipboardevent::ClipboardEvent;
27use crate::dom::compositionevent::CompositionEvent;
28use crate::dom::document::Document;
29use crate::dom::element::{AttributeMutation, Element, LayoutElementHelpers};
30use crate::dom::event::{Event, EventBubbles, EventCancelable};
31use crate::dom::html::htmlelement::HTMLElement;
32use crate::dom::html::htmlfieldsetelement::HTMLFieldSetElement;
33use crate::dom::html::htmlformelement::{FormControl, HTMLFormElement};
34use crate::dom::html::htmlinputelement::HTMLInputElement;
35use crate::dom::keyboardevent::KeyboardEvent;
36use crate::dom::node::{
37    BindContext, ChildrenMutation, CloneChildrenFlag, Node, NodeDamage, NodeTraits, UnbindContext,
38};
39use crate::dom::nodelist::NodeList;
40use crate::dom::textcontrol::{TextControlElement, TextControlSelection};
41use crate::dom::validation::{Validatable, is_barred_by_datalist_ancestor};
42use crate::dom::validitystate::{ValidationFlags, ValidityState};
43use crate::dom::virtualmethods::VirtualMethods;
44use crate::script_runtime::CanGc;
45use crate::textinput::{
46    ClipboardEventReaction, Direction, KeyReaction, Lines, SelectionDirection, TextInput,
47    UTF8Bytes, UTF16CodeUnits,
48};
49
50#[dom_struct]
51pub(crate) struct HTMLTextAreaElement {
52    htmlelement: HTMLElement,
53    #[no_trace]
54    textinput: DomRefCell<TextInput<EmbedderClipboardProvider>>,
55    placeholder: DomRefCell<DOMString>,
56    // https://html.spec.whatwg.org/multipage/#concept-textarea-dirty
57    value_dirty: Cell<bool>,
58    form_owner: MutNullableDom<HTMLFormElement>,
59    labels_node_list: MutNullableDom<NodeList>,
60    validity_state: MutNullableDom<ValidityState>,
61}
62
63pub(crate) trait LayoutHTMLTextAreaElementHelpers {
64    fn value_for_layout(self) -> String;
65    fn selection_for_layout(self) -> Option<Range<usize>>;
66    fn get_cols(self) -> u32;
67    fn get_rows(self) -> u32;
68}
69
70#[allow(unsafe_code)]
71impl<'dom> LayoutDom<'dom, HTMLTextAreaElement> {
72    fn textinput_content(self) -> DOMString {
73        unsafe {
74            self.unsafe_get()
75                .textinput
76                .borrow_for_layout()
77                .get_content()
78        }
79    }
80
81    fn textinput_sorted_selection_offsets_range(self) -> Range<UTF8Bytes> {
82        unsafe {
83            self.unsafe_get()
84                .textinput
85                .borrow_for_layout()
86                .sorted_selection_offsets_range()
87        }
88    }
89
90    fn placeholder(self) -> &'dom str {
91        unsafe { self.unsafe_get().placeholder.borrow_for_layout() }
92    }
93}
94
95impl LayoutHTMLTextAreaElementHelpers for LayoutDom<'_, HTMLTextAreaElement> {
96    fn value_for_layout(self) -> String {
97        let text = self.textinput_content();
98        if text.is_empty() {
99            // FIXME(nox): Would be cool to not allocate a new string if the
100            // placeholder is single line, but that's an unimportant detail.
101            self.placeholder().replace("\r\n", "\n").replace('\r', "\n")
102        } else {
103            text.into()
104        }
105    }
106
107    fn selection_for_layout(self) -> Option<Range<usize>> {
108        if !self.upcast::<Element>().focus_state() {
109            return None;
110        }
111        Some(UTF8Bytes::unwrap_range(
112            self.textinput_sorted_selection_offsets_range(),
113        ))
114    }
115
116    fn get_cols(self) -> u32 {
117        self.upcast::<Element>()
118            .get_attr_for_layout(&ns!(), &local_name!("cols"))
119            .map_or(DEFAULT_COLS, AttrValue::as_uint)
120    }
121
122    fn get_rows(self) -> u32 {
123        self.upcast::<Element>()
124            .get_attr_for_layout(&ns!(), &local_name!("rows"))
125            .map_or(DEFAULT_ROWS, AttrValue::as_uint)
126    }
127}
128
129// https://html.spec.whatwg.org/multipage/#attr-textarea-cols-value
130const DEFAULT_COLS: u32 = 20;
131
132// https://html.spec.whatwg.org/multipage/#attr-textarea-rows-value
133const DEFAULT_ROWS: u32 = 2;
134
135const DEFAULT_MAX_LENGTH: i32 = -1;
136const DEFAULT_MIN_LENGTH: i32 = -1;
137
138impl HTMLTextAreaElement {
139    fn new_inherited(
140        local_name: LocalName,
141        prefix: Option<Prefix>,
142        document: &Document,
143    ) -> HTMLTextAreaElement {
144        let constellation_sender = document
145            .window()
146            .as_global_scope()
147            .script_to_constellation_chan()
148            .clone();
149        HTMLTextAreaElement {
150            htmlelement: HTMLElement::new_inherited_with_state(
151                ElementState::ENABLED | ElementState::READWRITE,
152                local_name,
153                prefix,
154                document,
155            ),
156            placeholder: DomRefCell::new(DOMString::new()),
157            textinput: DomRefCell::new(TextInput::new(
158                Lines::Multiple,
159                DOMString::new(),
160                EmbedderClipboardProvider {
161                    constellation_sender,
162                    webview_id: document.webview_id(),
163                },
164                None,
165                None,
166                SelectionDirection::None,
167            )),
168            value_dirty: Cell::new(false),
169            form_owner: Default::default(),
170            labels_node_list: Default::default(),
171            validity_state: Default::default(),
172        }
173    }
174
175    #[cfg_attr(crown, allow(crown::unrooted_must_root))]
176    pub(crate) fn new(
177        local_name: LocalName,
178        prefix: Option<Prefix>,
179        document: &Document,
180        proto: Option<HandleObject>,
181        can_gc: CanGc,
182    ) -> DomRoot<HTMLTextAreaElement> {
183        Node::reflect_node_with_proto(
184            Box::new(HTMLTextAreaElement::new_inherited(
185                local_name, prefix, document,
186            )),
187            document,
188            proto,
189            can_gc,
190        )
191    }
192
193    pub(crate) fn auto_directionality(&self) -> String {
194        let value: String = self.Value().to_string();
195        HTMLInputElement::directionality_from_value(&value)
196    }
197
198    fn update_placeholder_shown_state(&self) {
199        let has_placeholder = !self.placeholder.borrow().is_empty();
200        let has_value = !self.textinput.borrow().is_empty();
201        let el = self.upcast::<Element>();
202        el.set_placeholder_shown_state(has_placeholder && !has_value);
203    }
204
205    // https://html.spec.whatwg.org/multipage/#concept-fe-mutable
206    pub(crate) fn is_mutable(&self) -> bool {
207        // https://html.spec.whatwg.org/multipage/#the-textarea-element%3Aconcept-fe-mutable
208        // https://html.spec.whatwg.org/multipage/#the-readonly-attribute:concept-fe-mutable
209        !(self.upcast::<Element>().disabled_state() || self.ReadOnly())
210    }
211}
212
213impl TextControlElement for HTMLTextAreaElement {
214    fn selection_api_applies(&self) -> bool {
215        true
216    }
217
218    fn has_selectable_text(&self) -> bool {
219        true
220    }
221
222    fn set_dirty_value_flag(&self, value: bool) {
223        self.value_dirty.set(value)
224    }
225}
226
227impl HTMLTextAreaElementMethods<crate::DomTypeHolder> for HTMLTextAreaElement {
228    // TODO A few of these attributes have default values and additional
229    // constraints
230
231    // https://html.spec.whatwg.org/multipage/#dom-textarea-cols
232    make_uint_getter!(Cols, "cols", DEFAULT_COLS);
233
234    // https://html.spec.whatwg.org/multipage/#dom-textarea-cols
235    make_limited_uint_setter!(SetCols, "cols", DEFAULT_COLS);
236
237    // https://html.spec.whatwg.org/multipage/#dom-input-dirName
238    make_getter!(DirName, "dirname");
239
240    // https://html.spec.whatwg.org/multipage/#dom-input-dirName
241    make_setter!(SetDirName, "dirname");
242
243    // https://html.spec.whatwg.org/multipage/#dom-fe-disabled
244    make_bool_getter!(Disabled, "disabled");
245
246    // https://html.spec.whatwg.org/multipage/#dom-fe-disabled
247    make_bool_setter!(SetDisabled, "disabled");
248
249    // https://html.spec.whatwg.org/multipage/#dom-fae-form
250    fn GetForm(&self) -> Option<DomRoot<HTMLFormElement>> {
251        self.form_owner()
252    }
253
254    // https://html.spec.whatwg.org/multipage/#attr-fe-name
255    make_getter!(Name, "name");
256
257    // https://html.spec.whatwg.org/multipage/#attr-fe-name
258    make_atomic_setter!(SetName, "name");
259
260    // https://html.spec.whatwg.org/multipage/#dom-textarea-placeholder
261    make_getter!(Placeholder, "placeholder");
262
263    // https://html.spec.whatwg.org/multipage/#dom-textarea-placeholder
264    make_setter!(SetPlaceholder, "placeholder");
265
266    // https://html.spec.whatwg.org/multipage/#attr-textarea-maxlength
267    make_int_getter!(MaxLength, "maxlength", DEFAULT_MAX_LENGTH);
268
269    // https://html.spec.whatwg.org/multipage/#attr-textarea-maxlength
270    make_limited_int_setter!(SetMaxLength, "maxlength", DEFAULT_MAX_LENGTH);
271
272    // https://html.spec.whatwg.org/multipage/#attr-textarea-minlength
273    make_int_getter!(MinLength, "minlength", DEFAULT_MIN_LENGTH);
274
275    // https://html.spec.whatwg.org/multipage/#attr-textarea-minlength
276    make_limited_int_setter!(SetMinLength, "minlength", DEFAULT_MIN_LENGTH);
277
278    // https://html.spec.whatwg.org/multipage/#attr-textarea-readonly
279    make_bool_getter!(ReadOnly, "readonly");
280
281    // https://html.spec.whatwg.org/multipage/#attr-textarea-readonly
282    make_bool_setter!(SetReadOnly, "readonly");
283
284    // https://html.spec.whatwg.org/multipage/#dom-textarea-required
285    make_bool_getter!(Required, "required");
286
287    // https://html.spec.whatwg.org/multipage/#dom-textarea-required
288    make_bool_setter!(SetRequired, "required");
289
290    // https://html.spec.whatwg.org/multipage/#dom-textarea-rows
291    make_uint_getter!(Rows, "rows", DEFAULT_ROWS);
292
293    // https://html.spec.whatwg.org/multipage/#dom-textarea-rows
294    make_limited_uint_setter!(SetRows, "rows", DEFAULT_ROWS);
295
296    // https://html.spec.whatwg.org/multipage/#dom-textarea-wrap
297    make_getter!(Wrap, "wrap");
298
299    // https://html.spec.whatwg.org/multipage/#dom-textarea-wrap
300    make_setter!(SetWrap, "wrap");
301
302    // https://html.spec.whatwg.org/multipage/#dom-textarea-type
303    fn Type(&self) -> DOMString {
304        DOMString::from("textarea")
305    }
306
307    // https://html.spec.whatwg.org/multipage/#dom-textarea-defaultvalue
308    fn DefaultValue(&self) -> DOMString {
309        self.upcast::<Node>().GetTextContent().unwrap()
310    }
311
312    // https://html.spec.whatwg.org/multipage/#dom-textarea-defaultvalue
313    fn SetDefaultValue(&self, value: DOMString, can_gc: CanGc) {
314        self.upcast::<Node>()
315            .set_text_content_for_element(Some(value), can_gc);
316
317        // if the element's dirty value flag is false, then the element's
318        // raw value must be set to the value of the element's textContent IDL attribute
319        if !self.value_dirty.get() {
320            self.reset();
321        }
322    }
323
324    // https://html.spec.whatwg.org/multipage/#dom-textarea-value
325    fn Value(&self) -> DOMString {
326        self.textinput.borrow().get_content()
327    }
328
329    // https://html.spec.whatwg.org/multipage/#dom-textarea-value
330    fn SetValue(&self, value: DOMString) {
331        {
332            let mut textinput = self.textinput.borrow_mut();
333
334            // Step 1
335            let old_value = textinput.get_content();
336
337            // Step 2
338            textinput.set_content(value);
339
340            // Step 3
341            self.value_dirty.set(true);
342
343            if old_value != textinput.get_content() {
344                // Step 4
345                textinput.clear_selection_to_limit(Direction::Forward);
346            }
347        }
348
349        self.validity_state()
350            .perform_validation_and_update(ValidationFlags::all(), CanGc::note());
351        self.upcast::<Node>().dirty(NodeDamage::Other);
352    }
353
354    // https://html.spec.whatwg.org/multipage/#dom-textarea-textlength
355    fn TextLength(&self) -> u32 {
356        let UTF16CodeUnits(num_units) = self.textinput.borrow().utf16_len();
357        num_units as u32
358    }
359
360    // https://html.spec.whatwg.org/multipage/#dom-lfe-labels
361    make_labels_getter!(Labels, labels_node_list);
362
363    // https://html.spec.whatwg.org/multipage/#dom-textarea/input-select
364    fn Select(&self) {
365        self.selection().dom_select();
366    }
367
368    // https://html.spec.whatwg.org/multipage/#dom-textarea/input-selectionstart
369    fn GetSelectionStart(&self) -> Option<u32> {
370        self.selection().dom_start()
371    }
372
373    // https://html.spec.whatwg.org/multipage/#dom-textarea/input-selectionstart
374    fn SetSelectionStart(&self, start: Option<u32>) -> ErrorResult {
375        self.selection().set_dom_start(start)
376    }
377
378    // https://html.spec.whatwg.org/multipage/#dom-textarea/input-selectionend
379    fn GetSelectionEnd(&self) -> Option<u32> {
380        self.selection().dom_end()
381    }
382
383    // https://html.spec.whatwg.org/multipage/#dom-textarea/input-selectionend
384    fn SetSelectionEnd(&self, end: Option<u32>) -> ErrorResult {
385        self.selection().set_dom_end(end)
386    }
387
388    // https://html.spec.whatwg.org/multipage/#dom-textarea/input-selectiondirection
389    fn GetSelectionDirection(&self) -> Option<DOMString> {
390        self.selection().dom_direction()
391    }
392
393    // https://html.spec.whatwg.org/multipage/#dom-textarea/input-selectiondirection
394    fn SetSelectionDirection(&self, direction: Option<DOMString>) -> ErrorResult {
395        self.selection().set_dom_direction(direction)
396    }
397
398    // https://html.spec.whatwg.org/multipage/#dom-textarea/input-setselectionrange
399    fn SetSelectionRange(&self, start: u32, end: u32, direction: Option<DOMString>) -> ErrorResult {
400        self.selection().set_dom_range(start, end, direction)
401    }
402
403    // https://html.spec.whatwg.org/multipage/#dom-textarea/input-setrangetext
404    fn SetRangeText(&self, replacement: DOMString) -> ErrorResult {
405        self.selection()
406            .set_dom_range_text(replacement, None, None, Default::default())
407    }
408
409    // https://html.spec.whatwg.org/multipage/#dom-textarea/input-setrangetext
410    fn SetRangeText_(
411        &self,
412        replacement: DOMString,
413        start: u32,
414        end: u32,
415        selection_mode: SelectionMode,
416    ) -> ErrorResult {
417        self.selection()
418            .set_dom_range_text(replacement, Some(start), Some(end), selection_mode)
419    }
420
421    // https://html.spec.whatwg.org/multipage/#dom-cva-willvalidate
422    fn WillValidate(&self) -> bool {
423        self.is_instance_validatable()
424    }
425
426    // https://html.spec.whatwg.org/multipage/#dom-cva-validity
427    fn Validity(&self) -> DomRoot<ValidityState> {
428        self.validity_state()
429    }
430
431    // https://html.spec.whatwg.org/multipage/#dom-cva-checkvalidity
432    fn CheckValidity(&self, can_gc: CanGc) -> bool {
433        self.check_validity(can_gc)
434    }
435
436    // https://html.spec.whatwg.org/multipage/#dom-cva-reportvalidity
437    fn ReportValidity(&self, can_gc: CanGc) -> bool {
438        self.report_validity(can_gc)
439    }
440
441    // https://html.spec.whatwg.org/multipage/#dom-cva-validationmessage
442    fn ValidationMessage(&self) -> DOMString {
443        self.validation_message()
444    }
445
446    // https://html.spec.whatwg.org/multipage/#dom-cva-setcustomvalidity
447    fn SetCustomValidity(&self, error: DOMString) {
448        self.validity_state().set_custom_error_message(error);
449    }
450}
451
452impl HTMLTextAreaElement {
453    /// <https://w3c.github.io/webdriver/#ref-for-dfn-clear-algorithm-4>
454    /// Used by WebDriver to clear the textarea element.
455    pub(crate) fn clear(&self) {
456        self.value_dirty.set(false);
457        self.textinput.borrow_mut().set_content(DOMString::from(""));
458    }
459
460    pub(crate) fn reset(&self) {
461        // https://html.spec.whatwg.org/multipage/#the-textarea-element:concept-form-reset-control
462        let mut textinput = self.textinput.borrow_mut();
463        textinput.set_content(self.DefaultValue());
464        self.value_dirty.set(false);
465    }
466
467    #[cfg_attr(crown, allow(crown::unrooted_must_root))]
468    fn selection(&self) -> TextControlSelection<'_, Self> {
469        TextControlSelection::new(self, &self.textinput)
470    }
471}
472
473impl VirtualMethods for HTMLTextAreaElement {
474    fn super_type(&self) -> Option<&dyn VirtualMethods> {
475        Some(self.upcast::<HTMLElement>() as &dyn VirtualMethods)
476    }
477
478    fn attribute_mutated(&self, attr: &Attr, mutation: AttributeMutation, can_gc: CanGc) {
479        self.super_type()
480            .unwrap()
481            .attribute_mutated(attr, mutation, can_gc);
482        match *attr.local_name() {
483            local_name!("disabled") => {
484                let el = self.upcast::<Element>();
485                match mutation {
486                    AttributeMutation::Set(_) => {
487                        el.set_disabled_state(true);
488                        el.set_enabled_state(false);
489
490                        el.set_read_write_state(false);
491                    },
492                    AttributeMutation::Removed => {
493                        el.set_disabled_state(false);
494                        el.set_enabled_state(true);
495                        el.check_ancestors_disabled_state_for_form_control();
496
497                        if !el.disabled_state() && !el.read_write_state() {
498                            el.set_read_write_state(true);
499                        }
500                    },
501                }
502                el.update_sequentially_focusable_status(CanGc::note());
503            },
504            local_name!("maxlength") => match *attr.value() {
505                AttrValue::Int(_, value) => {
506                    let mut textinput = self.textinput.borrow_mut();
507
508                    if value < 0 {
509                        textinput.set_max_length(None);
510                    } else {
511                        textinput.set_max_length(Some(UTF16CodeUnits(value as usize)))
512                    }
513                },
514                _ => panic!("Expected an AttrValue::Int"),
515            },
516            local_name!("minlength") => match *attr.value() {
517                AttrValue::Int(_, value) => {
518                    let mut textinput = self.textinput.borrow_mut();
519
520                    if value < 0 {
521                        textinput.set_min_length(None);
522                    } else {
523                        textinput.set_min_length(Some(UTF16CodeUnits(value as usize)))
524                    }
525                },
526                _ => panic!("Expected an AttrValue::Int"),
527            },
528            local_name!("placeholder") => {
529                {
530                    let mut placeholder = self.placeholder.borrow_mut();
531                    placeholder.clear();
532                    if let AttributeMutation::Set(_) = mutation {
533                        placeholder.push_str(&attr.value());
534                    }
535                }
536                self.update_placeholder_shown_state();
537            },
538            local_name!("readonly") => {
539                let el = self.upcast::<Element>();
540                match mutation {
541                    AttributeMutation::Set(_) => {
542                        el.set_read_write_state(false);
543                    },
544                    AttributeMutation::Removed => {
545                        el.set_read_write_state(!el.disabled_state());
546                    },
547                }
548            },
549            local_name!("form") => {
550                self.form_attribute_mutated(mutation, can_gc);
551            },
552            _ => {},
553        }
554
555        self.validity_state()
556            .perform_validation_and_update(ValidationFlags::all(), can_gc);
557    }
558
559    fn bind_to_tree(&self, context: &BindContext, can_gc: CanGc) {
560        if let Some(s) = self.super_type() {
561            s.bind_to_tree(context, can_gc);
562        }
563
564        self.upcast::<Element>()
565            .check_ancestors_disabled_state_for_form_control();
566
567        self.validity_state()
568            .perform_validation_and_update(ValidationFlags::all(), can_gc);
569    }
570
571    fn parse_plain_attribute(&self, name: &LocalName, value: DOMString) -> AttrValue {
572        match *name {
573            local_name!("cols") => AttrValue::from_limited_u32(value.into(), DEFAULT_COLS),
574            local_name!("rows") => AttrValue::from_limited_u32(value.into(), DEFAULT_ROWS),
575            local_name!("maxlength") => {
576                AttrValue::from_limited_i32(value.into(), DEFAULT_MAX_LENGTH)
577            },
578            local_name!("minlength") => {
579                AttrValue::from_limited_i32(value.into(), DEFAULT_MIN_LENGTH)
580            },
581            _ => self
582                .super_type()
583                .unwrap()
584                .parse_plain_attribute(name, value),
585        }
586    }
587
588    fn unbind_from_tree(&self, context: &UnbindContext, can_gc: CanGc) {
589        self.super_type().unwrap().unbind_from_tree(context, can_gc);
590
591        let node = self.upcast::<Node>();
592        let el = self.upcast::<Element>();
593        if node
594            .ancestors()
595            .any(|ancestor| ancestor.is::<HTMLFieldSetElement>())
596        {
597            el.check_ancestors_disabled_state_for_form_control();
598        } else {
599            el.check_disabled_attribute();
600        }
601
602        self.validity_state()
603            .perform_validation_and_update(ValidationFlags::all(), can_gc);
604    }
605
606    // The cloning steps for textarea elements must propagate the raw value
607    // and dirty value flag from the node being cloned to the copy.
608    fn cloning_steps(
609        &self,
610        copy: &Node,
611        maybe_doc: Option<&Document>,
612        clone_children: CloneChildrenFlag,
613        can_gc: CanGc,
614    ) {
615        if let Some(s) = self.super_type() {
616            s.cloning_steps(copy, maybe_doc, clone_children, can_gc);
617        }
618        let el = copy.downcast::<HTMLTextAreaElement>().unwrap();
619        el.value_dirty.set(self.value_dirty.get());
620        {
621            let mut textinput = el.textinput.borrow_mut();
622            textinput.set_content(self.textinput.borrow().get_content());
623        }
624        el.validity_state()
625            .perform_validation_and_update(ValidationFlags::all(), can_gc);
626    }
627
628    fn children_changed(&self, mutation: &ChildrenMutation) {
629        if let Some(s) = self.super_type() {
630            s.children_changed(mutation);
631        }
632        if !self.value_dirty.get() {
633            self.reset();
634        }
635    }
636
637    // copied and modified from htmlinputelement.rs
638    fn handle_event(&self, event: &Event, can_gc: CanGc) {
639        if let Some(s) = self.super_type() {
640            s.handle_event(event, can_gc);
641        }
642
643        if event.type_() == atom!("click") && !event.DefaultPrevented() {
644            // TODO: set the editing position for text inputs
645        } else if event.type_() == atom!("keydown") && !event.DefaultPrevented() {
646            if let Some(kevent) = event.downcast::<KeyboardEvent>() {
647                // This can't be inlined, as holding on to textinput.borrow_mut()
648                // during self.implicit_submission will cause a panic.
649                let action = self.textinput.borrow_mut().handle_keydown(kevent);
650                match action {
651                    KeyReaction::TriggerDefaultAction => (),
652                    KeyReaction::DispatchInput => {
653                        if event.IsTrusted() {
654                            self.owner_global()
655                                .task_manager()
656                                .user_interaction_task_source()
657                                .queue_event(
658                                    self.upcast(),
659                                    atom!("input"),
660                                    EventBubbles::Bubbles,
661                                    EventCancelable::NotCancelable,
662                                );
663                        }
664                        self.value_dirty.set(true);
665                        self.update_placeholder_shown_state();
666                        self.upcast::<Node>().dirty(NodeDamage::Other);
667                        event.mark_as_handled();
668                    },
669                    KeyReaction::RedrawSelection => {
670                        self.upcast::<Node>().dirty(NodeDamage::Other);
671                        event.mark_as_handled();
672                    },
673                    KeyReaction::Nothing => (),
674                }
675            }
676        } else if event.type_() == atom!("keypress") && !event.DefaultPrevented() {
677            // keypress should be deprecated and replaced by beforeinput.
678            // keypress was supposed to fire "blur" and "focus" events
679            // but already done in `document.rs`
680        } else if event.type_() == atom!("compositionstart") ||
681            event.type_() == atom!("compositionupdate") ||
682            event.type_() == atom!("compositionend")
683        {
684            if let Some(compositionevent) = event.downcast::<CompositionEvent>() {
685                if event.type_() == atom!("compositionend") {
686                    let _ = self
687                        .textinput
688                        .borrow_mut()
689                        .handle_compositionend(compositionevent);
690                    self.upcast::<Node>().dirty(NodeDamage::Other);
691                } else if event.type_() == atom!("compositionupdate") {
692                    let _ = self
693                        .textinput
694                        .borrow_mut()
695                        .handle_compositionupdate(compositionevent);
696                    self.upcast::<Node>().dirty(NodeDamage::Other);
697                }
698                event.mark_as_handled();
699            }
700        } else if let Some(clipboard_event) = event.downcast::<ClipboardEvent>() {
701            let reaction = self
702                .textinput
703                .borrow_mut()
704                .handle_clipboard_event(clipboard_event);
705            if reaction.contains(ClipboardEventReaction::FireClipboardChangedEvent) {
706                self.owner_document()
707                    .event_handler()
708                    .fire_clipboardchange_event(can_gc);
709            }
710            if reaction.contains(ClipboardEventReaction::QueueInputEvent) {
711                self.owner_global()
712                    .task_manager()
713                    .user_interaction_task_source()
714                    .queue_event(
715                        self.upcast(),
716                        atom!("input"),
717                        EventBubbles::Bubbles,
718                        EventCancelable::NotCancelable,
719                    );
720            }
721            if !reaction.is_empty() {
722                self.upcast::<Node>().dirty(NodeDamage::ContentOrHeritage);
723            }
724        }
725
726        self.validity_state()
727            .perform_validation_and_update(ValidationFlags::all(), can_gc);
728    }
729
730    fn pop(&self) {
731        self.super_type().unwrap().pop();
732
733        // https://html.spec.whatwg.org/multipage/#the-textarea-element:stack-of-open-elements
734        self.reset();
735    }
736}
737
738impl FormControl for HTMLTextAreaElement {
739    fn form_owner(&self) -> Option<DomRoot<HTMLFormElement>> {
740        self.form_owner.get()
741    }
742
743    fn set_form_owner(&self, form: Option<&HTMLFormElement>) {
744        self.form_owner.set(form);
745    }
746
747    fn to_element(&self) -> &Element {
748        self.upcast::<Element>()
749    }
750}
751
752impl Validatable for HTMLTextAreaElement {
753    fn as_element(&self) -> &Element {
754        self.upcast()
755    }
756
757    fn validity_state(&self) -> DomRoot<ValidityState> {
758        self.validity_state
759            .or_init(|| ValidityState::new(&self.owner_window(), self.upcast(), CanGc::note()))
760    }
761
762    fn is_instance_validatable(&self) -> bool {
763        // https://html.spec.whatwg.org/multipage/#enabling-and-disabling-form-controls%3A-the-disabled-attribute%3Abarred-from-constraint-validation
764        // https://html.spec.whatwg.org/multipage/#the-textarea-element%3Abarred-from-constraint-validation
765        // https://html.spec.whatwg.org/multipage/#the-datalist-element%3Abarred-from-constraint-validation
766        !self.upcast::<Element>().disabled_state() &&
767            !self.ReadOnly() &&
768            !is_barred_by_datalist_ancestor(self.upcast())
769    }
770
771    fn perform_validation(
772        &self,
773        validate_flags: ValidationFlags,
774        _can_gc: CanGc,
775    ) -> ValidationFlags {
776        let mut failed_flags = ValidationFlags::empty();
777
778        let textinput = self.textinput.borrow();
779        let UTF16CodeUnits(value_len) = textinput.utf16_len();
780        let last_edit_by_user = !textinput.was_last_change_by_set_content();
781        let value_dirty = self.value_dirty.get();
782
783        // https://html.spec.whatwg.org/multipage/#suffering-from-being-missing
784        // https://html.spec.whatwg.org/multipage/#the-textarea-element%3Asuffering-from-being-missing
785        if validate_flags.contains(ValidationFlags::VALUE_MISSING) &&
786            self.Required() &&
787            self.is_mutable() &&
788            value_len == 0
789        {
790            failed_flags.insert(ValidationFlags::VALUE_MISSING);
791        }
792
793        if value_dirty && last_edit_by_user && value_len > 0 {
794            // https://html.spec.whatwg.org/multipage/#suffering-from-being-too-long
795            // https://html.spec.whatwg.org/multipage/#limiting-user-input-length%3A-the-maxlength-attribute%3Asuffering-from-being-too-long
796            if validate_flags.contains(ValidationFlags::TOO_LONG) {
797                let max_length = self.MaxLength();
798                if max_length != DEFAULT_MAX_LENGTH && value_len > (max_length as usize) {
799                    failed_flags.insert(ValidationFlags::TOO_LONG);
800                }
801            }
802
803            // https://html.spec.whatwg.org/multipage/#suffering-from-being-too-short
804            // https://html.spec.whatwg.org/multipage/#setting-minimum-input-length-requirements%3A-the-minlength-attribute%3Asuffering-from-being-too-short
805            if validate_flags.contains(ValidationFlags::TOO_SHORT) {
806                let min_length = self.MinLength();
807                if min_length != DEFAULT_MIN_LENGTH && value_len < (min_length as usize) {
808                    failed_flags.insert(ValidationFlags::TOO_SHORT);
809                }
810            }
811        }
812
813        failed_flags
814    }
815}