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