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