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_event(&self, event: &FocusEvent) {
216        // The focus state can afect the selection (see `selection_for_layout()`),
217        // thus dirty the node so that it is laid out again.
218        // TODO: Selection changes shouldn't require a new layout.
219        self.upcast::<Node>().dirty(NodeDamage::ContentOrHeritage);
220
221        let event_type = event.upcast::<Event>().type_();
222        if *event_type == *"blur" {
223            self.owner_document()
224                .embedder_controls()
225                .hide_embedder_control(self.upcast());
226        } else if *event_type == *"focus" {
227            self.owner_document()
228                .embedder_controls()
229                .show_embedder_control(
230                    ControlElement::Ime(DomRoot::from_ref(self.upcast())),
231                    EmbedderControlRequest::InputMethod(InputMethodRequest {
232                        input_method_type: InputMethodType::Text,
233                        text: self.Value().to_string(),
234                        insertion_point: self.GetSelectionEnd(),
235                        multiline: false,
236                    }),
237                    None,
238                );
239        } else {
240            unreachable!("Got unexpected FocusEvent {event_type:?}");
241        }
242    }
243}
244
245impl TextControlElement for HTMLTextAreaElement {
246    fn selection_api_applies(&self) -> bool {
247        true
248    }
249
250    fn has_selectable_text(&self) -> bool {
251        !self.textinput.borrow().get_content().is_empty()
252    }
253
254    fn has_selection(&self) -> bool {
255        self.textinput.borrow().has_selection()
256    }
257
258    fn set_dirty_value_flag(&self, value: bool) {
259        self.value_dirty.set(value)
260    }
261
262    fn select_all(&self) {
263        self.textinput.borrow_mut().select_all();
264        self.upcast::<Node>().dirty(NodeDamage::Other);
265    }
266}
267
268impl HTMLTextAreaElementMethods<crate::DomTypeHolder> for HTMLTextAreaElement {
269    // TODO A few of these attributes have default values and additional
270    // constraints
271
272    // https://html.spec.whatwg.org/multipage/#dom-textarea-cols
273    make_uint_getter!(Cols, "cols", DEFAULT_COLS);
274
275    // https://html.spec.whatwg.org/multipage/#dom-textarea-cols
276    make_limited_uint_setter!(SetCols, "cols", DEFAULT_COLS);
277
278    // https://html.spec.whatwg.org/multipage/#dom-input-dirName
279    make_getter!(DirName, "dirname");
280
281    // https://html.spec.whatwg.org/multipage/#dom-input-dirName
282    make_setter!(SetDirName, "dirname");
283
284    // https://html.spec.whatwg.org/multipage/#dom-fe-disabled
285    make_bool_getter!(Disabled, "disabled");
286
287    // https://html.spec.whatwg.org/multipage/#dom-fe-disabled
288    make_bool_setter!(SetDisabled, "disabled");
289
290    /// <https://html.spec.whatwg.org/multipage/#dom-fae-form>
291    fn GetForm(&self) -> Option<DomRoot<HTMLFormElement>> {
292        self.form_owner()
293    }
294
295    // https://html.spec.whatwg.org/multipage/#attr-fe-name
296    make_getter!(Name, "name");
297
298    // https://html.spec.whatwg.org/multipage/#attr-fe-name
299    make_atomic_setter!(SetName, "name");
300
301    // https://html.spec.whatwg.org/multipage/#dom-textarea-placeholder
302    make_getter!(Placeholder, "placeholder");
303
304    // https://html.spec.whatwg.org/multipage/#dom-textarea-placeholder
305    make_setter!(SetPlaceholder, "placeholder");
306
307    // https://html.spec.whatwg.org/multipage/#attr-textarea-maxlength
308    make_int_getter!(MaxLength, "maxlength", DEFAULT_MAX_LENGTH);
309
310    // https://html.spec.whatwg.org/multipage/#attr-textarea-maxlength
311    make_limited_int_setter!(SetMaxLength, "maxlength", DEFAULT_MAX_LENGTH);
312
313    // https://html.spec.whatwg.org/multipage/#attr-textarea-minlength
314    make_int_getter!(MinLength, "minlength", DEFAULT_MIN_LENGTH);
315
316    // https://html.spec.whatwg.org/multipage/#attr-textarea-minlength
317    make_limited_int_setter!(SetMinLength, "minlength", DEFAULT_MIN_LENGTH);
318
319    // https://html.spec.whatwg.org/multipage/#attr-textarea-readonly
320    make_bool_getter!(ReadOnly, "readonly");
321
322    // https://html.spec.whatwg.org/multipage/#attr-textarea-readonly
323    make_bool_setter!(SetReadOnly, "readonly");
324
325    // https://html.spec.whatwg.org/multipage/#dom-textarea-required
326    make_bool_getter!(Required, "required");
327
328    // https://html.spec.whatwg.org/multipage/#dom-textarea-required
329    make_bool_setter!(SetRequired, "required");
330
331    // https://html.spec.whatwg.org/multipage/#dom-textarea-rows
332    make_uint_getter!(Rows, "rows", DEFAULT_ROWS);
333
334    // https://html.spec.whatwg.org/multipage/#dom-textarea-rows
335    make_limited_uint_setter!(SetRows, "rows", DEFAULT_ROWS);
336
337    // https://html.spec.whatwg.org/multipage/#dom-textarea-wrap
338    make_getter!(Wrap, "wrap");
339
340    // https://html.spec.whatwg.org/multipage/#dom-textarea-wrap
341    make_setter!(SetWrap, "wrap");
342
343    /// <https://html.spec.whatwg.org/multipage/#dom-textarea-type>
344    fn Type(&self) -> DOMString {
345        DOMString::from("textarea")
346    }
347
348    /// <https://html.spec.whatwg.org/multipage/#dom-textarea-defaultvalue>
349    fn DefaultValue(&self) -> DOMString {
350        self.upcast::<Node>().GetTextContent().unwrap()
351    }
352
353    /// <https://html.spec.whatwg.org/multipage/#dom-textarea-defaultvalue>
354    fn SetDefaultValue(&self, value: DOMString, can_gc: CanGc) {
355        self.upcast::<Node>()
356            .set_text_content_for_element(Some(value), can_gc);
357
358        // if the element's dirty value flag is false, then the element's
359        // raw value must be set to the value of the element's textContent IDL attribute
360        if !self.value_dirty.get() {
361            self.reset();
362        }
363    }
364
365    /// <https://html.spec.whatwg.org/multipage/#dom-textarea-value>
366    fn Value(&self) -> DOMString {
367        self.textinput.borrow().get_content()
368    }
369
370    /// <https://html.spec.whatwg.org/multipage/#dom-textarea-value>
371    fn SetValue(&self, value: DOMString, can_gc: CanGc) {
372        {
373            let mut textinput = self.textinput.borrow_mut();
374
375            // Step 1
376            let old_value = textinput.get_content();
377
378            // Step 2
379            textinput.set_content(value);
380
381            // Step 3
382            self.value_dirty.set(true);
383
384            if old_value != textinput.get_content() {
385                // Step 4
386                textinput.clear_selection_to_limit(Direction::Forward);
387            }
388        }
389
390        self.validity_state(can_gc)
391            .perform_validation_and_update(ValidationFlags::all(), can_gc);
392        self.upcast::<Node>().dirty(NodeDamage::Other);
393    }
394
395    /// <https://html.spec.whatwg.org/multipage/#dom-textarea-textlength>
396    fn TextLength(&self) -> u32 {
397        let UTF16CodeUnits(num_units) = self.textinput.borrow().utf16_len();
398        num_units as u32
399    }
400
401    // https://html.spec.whatwg.org/multipage/#dom-lfe-labels
402    make_labels_getter!(Labels, labels_node_list);
403
404    /// <https://html.spec.whatwg.org/multipage/#dom-textarea/input-select>
405    fn Select(&self) {
406        self.selection().dom_select();
407    }
408
409    /// <https://html.spec.whatwg.org/multipage/#dom-textarea/input-selectionstart>
410    fn GetSelectionStart(&self) -> Option<u32> {
411        self.selection().dom_start()
412    }
413
414    /// <https://html.spec.whatwg.org/multipage/#dom-textarea/input-selectionstart>
415    fn SetSelectionStart(&self, start: Option<u32>) -> ErrorResult {
416        self.selection().set_dom_start(start)
417    }
418
419    /// <https://html.spec.whatwg.org/multipage/#dom-textarea/input-selectionend>
420    fn GetSelectionEnd(&self) -> Option<u32> {
421        self.selection().dom_end()
422    }
423
424    /// <https://html.spec.whatwg.org/multipage/#dom-textarea/input-selectionend>
425    fn SetSelectionEnd(&self, end: Option<u32>) -> ErrorResult {
426        self.selection().set_dom_end(end)
427    }
428
429    /// <https://html.spec.whatwg.org/multipage/#dom-textarea/input-selectiondirection>
430    fn GetSelectionDirection(&self) -> Option<DOMString> {
431        self.selection().dom_direction()
432    }
433
434    /// <https://html.spec.whatwg.org/multipage/#dom-textarea/input-selectiondirection>
435    fn SetSelectionDirection(&self, direction: Option<DOMString>) -> ErrorResult {
436        self.selection().set_dom_direction(direction)
437    }
438
439    /// <https://html.spec.whatwg.org/multipage/#dom-textarea/input-setselectionrange>
440    fn SetSelectionRange(&self, start: u32, end: u32, direction: Option<DOMString>) -> ErrorResult {
441        self.selection().set_dom_range(start, end, direction)
442    }
443
444    /// <https://html.spec.whatwg.org/multipage/#dom-textarea/input-setrangetext>
445    fn SetRangeText(&self, replacement: DOMString) -> ErrorResult {
446        self.selection()
447            .set_dom_range_text(replacement, None, None, Default::default())
448    }
449
450    /// <https://html.spec.whatwg.org/multipage/#dom-textarea/input-setrangetext>
451    fn SetRangeText_(
452        &self,
453        replacement: DOMString,
454        start: u32,
455        end: u32,
456        selection_mode: SelectionMode,
457    ) -> ErrorResult {
458        self.selection()
459            .set_dom_range_text(replacement, Some(start), Some(end), selection_mode)
460    }
461
462    /// <https://html.spec.whatwg.org/multipage/#dom-cva-willvalidate>
463    fn WillValidate(&self) -> bool {
464        self.is_instance_validatable()
465    }
466
467    /// <https://html.spec.whatwg.org/multipage/#dom-cva-validity>
468    fn Validity(&self, can_gc: CanGc) -> DomRoot<ValidityState> {
469        self.validity_state(can_gc)
470    }
471
472    /// <https://html.spec.whatwg.org/multipage/#dom-cva-checkvalidity>
473    fn CheckValidity(&self, can_gc: CanGc) -> bool {
474        self.check_validity(can_gc)
475    }
476
477    /// <https://html.spec.whatwg.org/multipage/#dom-cva-reportvalidity>
478    fn ReportValidity(&self, can_gc: CanGc) -> bool {
479        self.report_validity(can_gc)
480    }
481
482    /// <https://html.spec.whatwg.org/multipage/#dom-cva-validationmessage>
483    fn ValidationMessage(&self) -> DOMString {
484        self.validation_message()
485    }
486
487    /// <https://html.spec.whatwg.org/multipage/#dom-cva-setcustomvalidity>
488    fn SetCustomValidity(&self, error: DOMString, can_gc: CanGc) {
489        self.validity_state(can_gc).set_custom_error_message(error);
490    }
491}
492
493impl HTMLTextAreaElement {
494    /// <https://w3c.github.io/webdriver/#ref-for-dfn-clear-algorithm-4>
495    /// Used by WebDriver to clear the textarea element.
496    pub(crate) fn clear(&self) {
497        self.value_dirty.set(false);
498        self.textinput.borrow_mut().set_content(DOMString::from(""));
499    }
500
501    pub(crate) fn reset(&self) {
502        // https://html.spec.whatwg.org/multipage/#the-textarea-element:concept-form-reset-control
503        let mut textinput = self.textinput.borrow_mut();
504        textinput.set_content(self.DefaultValue());
505        self.value_dirty.set(false);
506    }
507
508    #[cfg_attr(crown, allow(crown::unrooted_must_root))]
509    fn selection(&self) -> TextControlSelection<'_, Self> {
510        TextControlSelection::new(self, &self.textinput)
511    }
512
513    fn handle_key_reaction(&self, action: KeyReaction, event: &Event) {
514        match action {
515            KeyReaction::TriggerDefaultAction => (),
516            KeyReaction::DispatchInput(text, is_composing, input_type) => {
517                if event.IsTrusted() {
518                    self.textinput.borrow().queue_input_event(
519                        self.upcast(),
520                        text,
521                        is_composing,
522                        input_type,
523                    );
524                }
525                self.value_dirty.set(true);
526                self.update_placeholder_shown_state();
527                self.upcast::<Node>().dirty(NodeDamage::Other);
528                event.mark_as_handled();
529            },
530            KeyReaction::RedrawSelection => {
531                self.upcast::<Node>().dirty(NodeDamage::Other);
532                event.mark_as_handled();
533            },
534            KeyReaction::Nothing => (),
535        }
536    }
537}
538
539impl VirtualMethods for HTMLTextAreaElement {
540    fn super_type(&self) -> Option<&dyn VirtualMethods> {
541        Some(self.upcast::<HTMLElement>() as &dyn VirtualMethods)
542    }
543
544    fn attribute_mutated(&self, attr: &Attr, mutation: AttributeMutation, can_gc: CanGc) {
545        self.super_type()
546            .unwrap()
547            .attribute_mutated(attr, mutation, can_gc);
548        match *attr.local_name() {
549            local_name!("disabled") => {
550                let el = self.upcast::<Element>();
551                match mutation {
552                    AttributeMutation::Set(..) => {
553                        el.set_disabled_state(true);
554                        el.set_enabled_state(false);
555
556                        el.set_read_write_state(false);
557                    },
558                    AttributeMutation::Removed => {
559                        el.set_disabled_state(false);
560                        el.set_enabled_state(true);
561                        el.check_ancestors_disabled_state_for_form_control();
562
563                        if !el.disabled_state() && !el.read_write_state() {
564                            el.set_read_write_state(true);
565                        }
566                    },
567                }
568                el.update_sequentially_focusable_status(CanGc::note());
569            },
570            local_name!("maxlength") => match *attr.value() {
571                AttrValue::Int(_, value) => {
572                    let mut textinput = self.textinput.borrow_mut();
573
574                    if value < 0 {
575                        textinput.set_max_length(None);
576                    } else {
577                        textinput.set_max_length(Some(UTF16CodeUnits(value as usize)))
578                    }
579                },
580                _ => panic!("Expected an AttrValue::Int"),
581            },
582            local_name!("minlength") => match *attr.value() {
583                AttrValue::Int(_, value) => {
584                    let mut textinput = self.textinput.borrow_mut();
585
586                    if value < 0 {
587                        textinput.set_min_length(None);
588                    } else {
589                        textinput.set_min_length(Some(UTF16CodeUnits(value as usize)))
590                    }
591                },
592                _ => panic!("Expected an AttrValue::Int"),
593            },
594            local_name!("placeholder") => {
595                {
596                    let mut placeholder = self.placeholder.borrow_mut();
597                    placeholder.clear();
598                    if let AttributeMutation::Set(..) = mutation {
599                        placeholder.push_str(attr.value().as_ref());
600                    }
601                }
602                self.update_placeholder_shown_state();
603            },
604            local_name!("readonly") => {
605                let el = self.upcast::<Element>();
606                match mutation {
607                    AttributeMutation::Set(..) => {
608                        el.set_read_write_state(false);
609                    },
610                    AttributeMutation::Removed => {
611                        el.set_read_write_state(!el.disabled_state());
612                    },
613                }
614            },
615            local_name!("form") => {
616                self.form_attribute_mutated(mutation, can_gc);
617            },
618            _ => {},
619        }
620
621        self.validity_state(can_gc)
622            .perform_validation_and_update(ValidationFlags::all(), can_gc);
623    }
624
625    fn bind_to_tree(&self, context: &BindContext, can_gc: CanGc) {
626        if let Some(s) = self.super_type() {
627            s.bind_to_tree(context, can_gc);
628        }
629
630        self.upcast::<Element>()
631            .check_ancestors_disabled_state_for_form_control();
632
633        self.validity_state(can_gc)
634            .perform_validation_and_update(ValidationFlags::all(), can_gc);
635    }
636
637    fn parse_plain_attribute(&self, name: &LocalName, value: DOMString) -> AttrValue {
638        match *name {
639            local_name!("cols") => AttrValue::from_limited_u32(value.into(), DEFAULT_COLS),
640            local_name!("rows") => AttrValue::from_limited_u32(value.into(), DEFAULT_ROWS),
641            local_name!("maxlength") => {
642                AttrValue::from_limited_i32(value.into(), DEFAULT_MAX_LENGTH)
643            },
644            local_name!("minlength") => {
645                AttrValue::from_limited_i32(value.into(), DEFAULT_MIN_LENGTH)
646            },
647            _ => self
648                .super_type()
649                .unwrap()
650                .parse_plain_attribute(name, value),
651        }
652    }
653
654    fn unbind_from_tree(&self, context: &UnbindContext, can_gc: CanGc) {
655        self.super_type().unwrap().unbind_from_tree(context, can_gc);
656
657        let node = self.upcast::<Node>();
658        let el = self.upcast::<Element>();
659        if node
660            .ancestors()
661            .any(|ancestor| ancestor.is::<HTMLFieldSetElement>())
662        {
663            el.check_ancestors_disabled_state_for_form_control();
664        } else {
665            el.check_disabled_attribute();
666        }
667
668        self.validity_state(can_gc)
669            .perform_validation_and_update(ValidationFlags::all(), can_gc);
670    }
671
672    // The cloning steps for textarea elements must propagate the raw value
673    // and dirty value flag from the node being cloned to the copy.
674    fn cloning_steps(
675        &self,
676        copy: &Node,
677        maybe_doc: Option<&Document>,
678        clone_children: CloneChildrenFlag,
679        can_gc: CanGc,
680    ) {
681        if let Some(s) = self.super_type() {
682            s.cloning_steps(copy, maybe_doc, clone_children, can_gc);
683        }
684        let el = copy.downcast::<HTMLTextAreaElement>().unwrap();
685        el.value_dirty.set(self.value_dirty.get());
686        {
687            let mut textinput = el.textinput.borrow_mut();
688            textinput.set_content(self.textinput.borrow().get_content());
689        }
690        el.validity_state(can_gc)
691            .perform_validation_and_update(ValidationFlags::all(), can_gc);
692    }
693
694    fn children_changed(&self, mutation: &ChildrenMutation, can_gc: CanGc) {
695        if let Some(s) = self.super_type() {
696            s.children_changed(mutation, can_gc);
697        }
698        if !self.value_dirty.get() {
699            self.reset();
700        }
701    }
702
703    // copied and modified from htmlinputelement.rs
704    fn handle_event(&self, event: &Event, can_gc: CanGc) {
705        if let Some(s) = self.super_type() {
706            s.handle_event(event, can_gc);
707        }
708
709        if event.type_() == atom!("click") && !event.DefaultPrevented() {
710            // TODO: set the editing position for text inputs
711        } else if event.type_() == atom!("keydown") && !event.DefaultPrevented() {
712            if let Some(kevent) = event.downcast::<KeyboardEvent>() {
713                // This can't be inlined, as holding on to textinput.borrow_mut()
714                // during self.implicit_submission will cause a panic.
715                let action = self.textinput.borrow_mut().handle_keydown(kevent);
716                self.handle_key_reaction(action, event);
717            }
718        } else if event.type_() == atom!("keypress") && !event.DefaultPrevented() {
719            // keypress should be deprecated and replaced by beforeinput.
720            // keypress was supposed to fire "blur" and "focus" events
721            // but already done in `document.rs`
722        } else if event.type_() == atom!("compositionstart") ||
723            event.type_() == atom!("compositionupdate") ||
724            event.type_() == atom!("compositionend")
725        {
726            if let Some(compositionevent) = event.downcast::<CompositionEvent>() {
727                if event.type_() == atom!("compositionend") {
728                    let action = self
729                        .textinput
730                        .borrow_mut()
731                        .handle_compositionend(compositionevent);
732                    self.handle_key_reaction(action, event);
733                    self.upcast::<Node>().dirty(NodeDamage::Other);
734                } else if event.type_() == atom!("compositionupdate") {
735                    let action = self
736                        .textinput
737                        .borrow_mut()
738                        .handle_compositionupdate(compositionevent);
739                    self.handle_key_reaction(action, event);
740                    self.upcast::<Node>().dirty(NodeDamage::Other);
741                }
742                event.mark_as_handled();
743            }
744        } else if let Some(clipboard_event) = event.downcast::<ClipboardEvent>() {
745            let reaction = self
746                .textinput
747                .borrow_mut()
748                .handle_clipboard_event(clipboard_event);
749
750            let flags = reaction.flags;
751            if flags.contains(ClipboardEventFlags::FireClipboardChangedEvent) {
752                self.owner_document().event_handler().fire_clipboard_event(
753                    None,
754                    ClipboardEventType::Change,
755                    can_gc,
756                );
757            }
758            if flags.contains(ClipboardEventFlags::QueueInputEvent) {
759                self.textinput.borrow().queue_input_event(
760                    self.upcast(),
761                    reaction.text,
762                    IsComposing::NotComposing,
763                    reaction.input_type,
764                );
765            }
766            if !flags.is_empty() {
767                self.upcast::<Node>().dirty(NodeDamage::ContentOrHeritage);
768            }
769        } else if let Some(event) = event.downcast::<FocusEvent>() {
770            self.handle_focus_event(event);
771        }
772
773        self.validity_state(can_gc)
774            .perform_validation_and_update(ValidationFlags::all(), can_gc);
775    }
776
777    fn pop(&self) {
778        self.super_type().unwrap().pop();
779
780        // https://html.spec.whatwg.org/multipage/#the-textarea-element:stack-of-open-elements
781        self.reset();
782    }
783}
784
785impl FormControl for HTMLTextAreaElement {
786    fn form_owner(&self) -> Option<DomRoot<HTMLFormElement>> {
787        self.form_owner.get()
788    }
789
790    fn set_form_owner(&self, form: Option<&HTMLFormElement>) {
791        self.form_owner.set(form);
792    }
793
794    fn to_element(&self) -> &Element {
795        self.upcast::<Element>()
796    }
797}
798
799impl Validatable for HTMLTextAreaElement {
800    fn as_element(&self) -> &Element {
801        self.upcast()
802    }
803
804    fn validity_state(&self, can_gc: CanGc) -> DomRoot<ValidityState> {
805        self.validity_state
806            .or_init(|| ValidityState::new(&self.owner_window(), self.upcast(), can_gc))
807    }
808
809    fn is_instance_validatable(&self) -> bool {
810        // https://html.spec.whatwg.org/multipage/#enabling-and-disabling-form-controls%3A-the-disabled-attribute%3Abarred-from-constraint-validation
811        // https://html.spec.whatwg.org/multipage/#the-textarea-element%3Abarred-from-constraint-validation
812        // https://html.spec.whatwg.org/multipage/#the-datalist-element%3Abarred-from-constraint-validation
813        !self.upcast::<Element>().disabled_state() &&
814            !self.ReadOnly() &&
815            !is_barred_by_datalist_ancestor(self.upcast())
816    }
817
818    fn perform_validation(
819        &self,
820        validate_flags: ValidationFlags,
821        _can_gc: CanGc,
822    ) -> ValidationFlags {
823        let mut failed_flags = ValidationFlags::empty();
824
825        let textinput = self.textinput.borrow();
826        let UTF16CodeUnits(value_len) = textinput.utf16_len();
827        let last_edit_by_user = !textinput.was_last_change_by_set_content();
828        let value_dirty = self.value_dirty.get();
829
830        // https://html.spec.whatwg.org/multipage/#suffering-from-being-missing
831        // https://html.spec.whatwg.org/multipage/#the-textarea-element%3Asuffering-from-being-missing
832        if validate_flags.contains(ValidationFlags::VALUE_MISSING) &&
833            self.Required() &&
834            self.is_mutable() &&
835            value_len == 0
836        {
837            failed_flags.insert(ValidationFlags::VALUE_MISSING);
838        }
839
840        if value_dirty && last_edit_by_user && value_len > 0 {
841            // https://html.spec.whatwg.org/multipage/#suffering-from-being-too-long
842            // https://html.spec.whatwg.org/multipage/#limiting-user-input-length%3A-the-maxlength-attribute%3Asuffering-from-being-too-long
843            if validate_flags.contains(ValidationFlags::TOO_LONG) {
844                let max_length = self.MaxLength();
845                if max_length != DEFAULT_MAX_LENGTH && value_len > (max_length as usize) {
846                    failed_flags.insert(ValidationFlags::TOO_LONG);
847                }
848            }
849
850            // https://html.spec.whatwg.org/multipage/#suffering-from-being-too-short
851            // https://html.spec.whatwg.org/multipage/#setting-minimum-input-length-requirements%3A-the-minlength-attribute%3Asuffering-from-being-too-short
852            if validate_flags.contains(ValidationFlags::TOO_SHORT) {
853                let min_length = self.MinLength();
854                if min_length != DEFAULT_MIN_LENGTH && value_len < (min_length as usize) {
855                    failed_flags.insert(ValidationFlags::TOO_SHORT);
856                }
857            }
858        }
859
860        failed_flags
861    }
862}