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