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