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