Skip to main content

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