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