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};
6use std::default::Default;
7
8use dom_struct::dom_struct;
9use embedder_traits::{EmbedderControlRequest, InputMethodRequest, InputMethodType};
10use fonts::{ByteIndex, TextByteRange};
11use html5ever::{LocalName, Prefix, local_name, ns};
12use js::context::JSContext;
13use js::rust::HandleObject;
14use layout_api::{ScriptSelection, SharedSelection};
15use script_bindings::cell::DomRefCell;
16use servo_base::text::Utf16CodeUnits;
17use style::attr::AttrValue;
18use stylo_dom::ElementState;
19
20use crate::dom::bindings::codegen::Bindings::EventBinding::EventMethods;
21use crate::dom::bindings::codegen::Bindings::HTMLFormElementBinding::SelectionMode;
22use crate::dom::bindings::codegen::Bindings::HTMLTextAreaElementBinding::HTMLTextAreaElementMethods;
23use crate::dom::bindings::codegen::Bindings::NodeBinding::NodeMethods;
24use crate::dom::bindings::error::ErrorResult;
25use crate::dom::bindings::inheritance::Castable;
26use crate::dom::bindings::refcounted::Trusted;
27use crate::dom::bindings::root::{Dom, DomRoot, LayoutDom, MutNullableDom};
28use crate::dom::bindings::str::DOMString;
29use crate::dom::clipboardevent::{ClipboardEvent, ClipboardEventType};
30use crate::dom::compositionevent::CompositionEvent;
31use crate::dom::document::Document;
32use crate::dom::document_embedder_controls::ControlElement;
33use crate::dom::element::attributes::storage::AttrRef;
34use crate::dom::element::{AttributeMutation, Element};
35use crate::dom::event::Event;
36use crate::dom::event::event::{EventBubbles, EventCancelable, EventComposed};
37use crate::dom::eventtarget::EventTarget;
38use crate::dom::html::form_controls::htmlinputelement::HTMLInputElement;
39use crate::dom::html::form_controls::input_type::text_input_widget::TextInputWidget;
40use crate::dom::html::form_controls::text_control::{TextControlElement, TextControlSelection};
41use crate::dom::html::form_controls::text_input::{
42    ClipboardEventFlags, EmbedderClipboardProvider, IsComposing, KeyReaction, Lines, TextInput,
43};
44use crate::dom::html::htmlelement::HTMLElement;
45use crate::dom::html::htmlfieldsetelement::HTMLFieldSetElement;
46use crate::dom::html::htmlformelement::{FormControl, HTMLFormElement};
47use crate::dom::keyboardevent::KeyboardEvent;
48use crate::dom::node::virtualmethods::VirtualMethods;
49use crate::dom::node::{
50    BindContext, ChildrenMutation, CloneChildrenFlag, Node, NodeDamage, NodeTraits, UnbindContext,
51};
52use crate::dom::nodelist::NodeList;
53use crate::dom::types::{FocusEvent, MouseEvent};
54use crate::dom::validation::{Validatable, is_barred_by_datalist_ancestor};
55use crate::dom::validitystate::{ValidationFlags, ValidityState};
56
57#[dom_struct]
58pub(crate) struct HTMLTextAreaElement {
59    htmlelement: HTMLElement,
60    #[no_trace]
61    textinput: DomRefCell<TextInput<EmbedderClipboardProvider>>,
62    placeholder: RefCell<DOMString>,
63    // https://html.spec.whatwg.org/multipage/#concept-textarea-dirty
64    value_dirty: Cell<bool>,
65    form_owner: MutNullableDom<HTMLFormElement>,
66    labels_node_list: MutNullableDom<NodeList>,
67    validity_state: MutNullableDom<ValidityState>,
68    /// A [`TextInputWidget`] that manages the shadow DOM for this `<textarea>`.
69    text_input_widget: DomRefCell<TextInputWidget>,
70    /// A [`SharedSelection`] that is shared with layout. This can be updated dyanmnically
71    /// and layout should reflect the new value after a display list update.
72    #[no_trace]
73    #[conditional_malloc_size_of]
74    shared_selection: SharedSelection,
75
76    /// <https://w3c.github.io/selection-api/#dfn-has-scheduled-selectionchange-event>
77    has_scheduled_selectionchange_event: Cell<bool>,
78}
79
80impl LayoutDom<'_, HTMLTextAreaElement> {
81    pub(crate) fn selection_for_layout(self) -> SharedSelection {
82        self.unsafe_get().shared_selection.clone()
83    }
84
85    pub(crate) fn get_cols(self) -> u32 {
86        self.upcast::<Element>()
87            .get_attr_for_layout(&ns!(), &local_name!("cols"))
88            .map_or(DEFAULT_COLS, AttrValue::as_uint)
89    }
90
91    pub(crate) fn get_rows(self) -> u32 {
92        self.upcast::<Element>()
93            .get_attr_for_layout(&ns!(), &local_name!("rows"))
94            .map_or(DEFAULT_ROWS, AttrValue::as_uint)
95    }
96}
97
98// https://html.spec.whatwg.org/multipage/#attr-textarea-cols-value
99const DEFAULT_COLS: u32 = 20;
100
101// https://html.spec.whatwg.org/multipage/#attr-textarea-rows-value
102const DEFAULT_ROWS: u32 = 2;
103
104const DEFAULT_MAX_LENGTH: i32 = -1;
105const DEFAULT_MIN_LENGTH: i32 = -1;
106
107impl HTMLTextAreaElement {
108    fn new_inherited(
109        local_name: LocalName,
110        prefix: Option<Prefix>,
111        document: &Document,
112    ) -> HTMLTextAreaElement {
113        let embedder_sender = document
114            .window()
115            .as_global_scope()
116            .script_to_embedder_chan()
117            .clone();
118        HTMLTextAreaElement {
119            htmlelement: HTMLElement::new_inherited_with_state(
120                ElementState::ENABLED | ElementState::READWRITE,
121                local_name,
122                prefix,
123                document,
124            ),
125            placeholder: Default::default(),
126            textinput: DomRefCell::new(TextInput::new(
127                Lines::Multiple,
128                DOMString::new(),
129                EmbedderClipboardProvider {
130                    embedder_sender,
131                    webview_id: document.webview_id(),
132                },
133            )),
134            value_dirty: Cell::new(false),
135            form_owner: Default::default(),
136            labels_node_list: Default::default(),
137            validity_state: Default::default(),
138            text_input_widget: Default::default(),
139            shared_selection: Default::default(),
140            has_scheduled_selectionchange_event: Default::default(),
141        }
142    }
143
144    pub(crate) fn new(
145        cx: &mut JSContext,
146        local_name: LocalName,
147        prefix: Option<Prefix>,
148        document: &Document,
149        proto: Option<HandleObject>,
150    ) -> DomRoot<HTMLTextAreaElement> {
151        Node::reflect_node_with_proto(
152            cx,
153            Box::new(HTMLTextAreaElement::new_inherited(
154                local_name, prefix, document,
155            )),
156            document,
157            proto,
158        )
159    }
160
161    pub(crate) fn auto_directionality(&self) -> String {
162        let value: String = String::from(self.Value());
163        HTMLInputElement::directionality_from_value(&value)
164    }
165
166    // https://html.spec.whatwg.org/multipage/#concept-fe-mutable
167    pub(crate) fn is_mutable(&self) -> bool {
168        // https://html.spec.whatwg.org/multipage/#the-textarea-element%3Aconcept-fe-mutable
169        // https://html.spec.whatwg.org/multipage/#the-readonly-attribute:concept-fe-mutable
170        !(self.upcast::<Element>().disabled_state() || self.ReadOnly())
171    }
172
173    fn handle_focus_event(&self, event: &FocusEvent) {
174        let event_type = event.upcast::<Event>().type_();
175        if *event_type == *"blur" {
176            self.owner_document()
177                .embedder_controls()
178                .hide_embedder_control(self.upcast());
179        } else if *event_type == *"focus" {
180            self.owner_document()
181                .embedder_controls()
182                .show_embedder_control(
183                    ControlElement::Ime(Dom::from_ref(self.upcast())),
184                    EmbedderControlRequest::InputMethod(InputMethodRequest {
185                        input_method_type: InputMethodType::Text,
186                        text: String::from(self.Value()),
187                        insertion_point: self.GetSelectionEnd(),
188                        multiline: false,
189                        // We follow chromium's heuristic to show the virtual keyboard only if user had interacted before.
190                        allow_virtual_keyboard: self.owner_window().has_sticky_activation(),
191                    }),
192                    None,
193                );
194        }
195
196        // Focus changes can activate or deactivate a selection.
197        self.maybe_update_shared_selection();
198    }
199
200    fn handle_text_content_changed(&self, cx: &mut JSContext) {
201        self.validity_state(cx)
202            .perform_validation_and_update(cx, ValidationFlags::all());
203
204        let placeholder_shown =
205            self.textinput.borrow().is_empty() && !self.placeholder.borrow().is_empty();
206        self.upcast::<Element>()
207            .set_placeholder_shown_state(placeholder_shown);
208
209        self.text_input_widget.borrow().update_shadow_tree(cx, self);
210        self.text_input_widget
211            .borrow()
212            .update_placeholder_contents(cx, self);
213        self.maybe_update_shared_selection();
214    }
215
216    fn handle_mouse_event(&self, mouse_event: &MouseEvent) {
217        if mouse_event.upcast::<Event>().DefaultPrevented() {
218            return;
219        }
220
221        // Only respond to mouse events if we are displayed as text input or a password. If the
222        // placeholder is displayed, also don't do any interactive mouse event handling.
223        if self.textinput.borrow().is_empty() {
224            return;
225        }
226        if self.textinput.borrow_mut().handle_mouse_event(mouse_event) {
227            self.maybe_update_shared_selection();
228        }
229    }
230
231    /// <https://w3c.github.io/selection-api/#dfn-schedule-a-selectionchange-event>
232    fn schedule_a_selection_change_event(&self) {
233        // Step 1. If target's has scheduled selectionchange event is true, abort these steps.
234        if self.has_scheduled_selectionchange_event.get() {
235            return;
236        }
237        // Step 2. Set target's has scheduled selectionchange event to true.
238        self.has_scheduled_selectionchange_event.set(true);
239        // Step 3. Queue a task on the user interaction task source to fire a selectionchange event on target.
240        let this = Trusted::new(self);
241        self.owner_global()
242            .task_manager()
243            .user_interaction_task_source()
244            .queue(
245                // https://w3c.github.io/selection-api/#firing-selectionchange-event
246                task!(selectionchange_task_steps: move |cx| {
247                    let this = this.root();
248                    // Step 1. Set target's has scheduled selectionchange event to false.
249                    this.has_scheduled_selectionchange_event.set(false);
250                    // Step 2. If target is an element, fire an event named selectionchange, which bubbles and not cancelable, at target.
251                    this.upcast::<EventTarget>().fire_event_with_params(
252                        cx,
253                        atom!("selectionchange"),
254                        EventBubbles::Bubbles,
255                        EventCancelable::NotCancelable,
256                        EventComposed::Composed,
257                    );
258                    // Step 3. Otherwise, if target is a document, fire an event named selectionchange,
259                    // which does not bubble and not cancelable, at target.
260                    //
261                    // n/a
262                }),
263            );
264    }
265}
266
267impl TextControlElement for HTMLTextAreaElement {
268    fn selection_api_applies(&self) -> bool {
269        true
270    }
271
272    fn has_selectable_text(&self) -> bool {
273        !self.textinput.borrow().get_content().is_empty()
274    }
275
276    fn has_uncollapsed_selection(&self) -> bool {
277        self.textinput.borrow().has_uncollapsed_selection()
278    }
279
280    fn set_dirty_value_flag(&self, value: bool) {
281        self.value_dirty.set(value)
282    }
283
284    fn select_all(&self) {
285        self.textinput.borrow_mut().select_all();
286        self.maybe_update_shared_selection();
287    }
288
289    fn maybe_update_shared_selection(&self) {
290        let offsets = self.textinput.borrow().sorted_selection_offsets_range();
291        let (start, end) = (offsets.start.0, offsets.end.0);
292        let range = TextByteRange::new(ByteIndex(start), ByteIndex(end));
293        let enabled = self.upcast::<Element>().focus_state();
294
295        let mut shared_selection = self.shared_selection.borrow_mut();
296        let range_remained_equal = range == shared_selection.range;
297        if range_remained_equal && enabled == shared_selection.enabled {
298            return;
299        }
300
301        if !range_remained_equal {
302            // https://w3c.github.io/selection-api/#selectionchange-event
303            // > When an input or textarea element provide a text selection and its selection changes
304            // > (in either extent or direction),
305            // > the user agent must schedule a selectionchange event on the element.
306            self.schedule_a_selection_change_event();
307        }
308
309        *shared_selection = ScriptSelection {
310            range,
311            character_range: self
312                .textinput
313                .borrow()
314                .sorted_selection_character_offsets_range(),
315            enabled,
316        };
317        self.owner_window().layout().set_needs_new_display_list();
318    }
319
320    fn placeholder_text<'a>(&'a self) -> Ref<'a, DOMString> {
321        self.placeholder.borrow()
322    }
323
324    fn value_text(&self) -> DOMString {
325        self.Value()
326    }
327}
328
329impl HTMLTextAreaElementMethods<crate::DomTypeHolder> for HTMLTextAreaElement {
330    // TODO A few of these attributes have default values and additional
331    // constraints
332
333    // https://html.spec.whatwg.org/multipage/#dom-textarea-cols
334    make_uint_getter!(Cols, "cols", DEFAULT_COLS);
335
336    // https://html.spec.whatwg.org/multipage/#dom-textarea-cols
337    make_limited_uint_setter!(SetCols, "cols", DEFAULT_COLS);
338
339    // https://html.spec.whatwg.org/multipage/#dom-input-dirName
340    make_getter!(DirName, "dirname");
341
342    // https://html.spec.whatwg.org/multipage/#dom-input-dirName
343    make_setter!(SetDirName, "dirname");
344
345    // https://html.spec.whatwg.org/multipage/#dom-fe-disabled
346    make_bool_getter!(Disabled, "disabled");
347
348    // https://html.spec.whatwg.org/multipage/#dom-fe-disabled
349    make_bool_setter!(SetDisabled, "disabled");
350
351    /// <https://html.spec.whatwg.org/multipage/#dom-fae-form>
352    fn GetForm(&self) -> Option<DomRoot<HTMLFormElement>> {
353        self.form_owner()
354    }
355
356    // https://html.spec.whatwg.org/multipage/#attr-fe-name
357    make_getter!(Name, "name");
358
359    // https://html.spec.whatwg.org/multipage/#attr-fe-name
360    make_atomic_setter!(SetName, "name");
361
362    // https://html.spec.whatwg.org/multipage/#dom-textarea-placeholder
363    make_getter!(Placeholder, "placeholder");
364
365    // https://html.spec.whatwg.org/multipage/#dom-textarea-placeholder
366    make_setter!(SetPlaceholder, "placeholder");
367
368    // https://html.spec.whatwg.org/multipage/#attr-textarea-maxlength
369    make_int_getter!(MaxLength, "maxlength", DEFAULT_MAX_LENGTH);
370
371    // https://html.spec.whatwg.org/multipage/#attr-textarea-maxlength
372    make_limited_int_setter!(SetMaxLength, "maxlength", DEFAULT_MAX_LENGTH);
373
374    // https://html.spec.whatwg.org/multipage/#attr-textarea-minlength
375    make_int_getter!(MinLength, "minlength", DEFAULT_MIN_LENGTH);
376
377    // https://html.spec.whatwg.org/multipage/#attr-textarea-minlength
378    make_limited_int_setter!(SetMinLength, "minlength", DEFAULT_MIN_LENGTH);
379
380    // https://html.spec.whatwg.org/multipage/#attr-textarea-readonly
381    make_bool_getter!(ReadOnly, "readonly");
382
383    // https://html.spec.whatwg.org/multipage/#attr-textarea-readonly
384    make_bool_setter!(SetReadOnly, "readonly");
385
386    // https://html.spec.whatwg.org/multipage/#dom-textarea-required
387    make_bool_getter!(Required, "required");
388
389    // https://html.spec.whatwg.org/multipage/#dom-textarea-required
390    make_bool_setter!(SetRequired, "required");
391
392    // https://html.spec.whatwg.org/multipage/#dom-textarea-rows
393    make_uint_getter!(Rows, "rows", DEFAULT_ROWS);
394
395    // https://html.spec.whatwg.org/multipage/#dom-textarea-rows
396    make_limited_uint_setter!(SetRows, "rows", DEFAULT_ROWS);
397
398    // https://html.spec.whatwg.org/multipage/#dom-textarea-wrap
399    make_getter!(Wrap, "wrap");
400
401    // https://html.spec.whatwg.org/multipage/#dom-textarea-wrap
402    make_setter!(SetWrap, "wrap");
403
404    /// <https://html.spec.whatwg.org/multipage/#dom-textarea-type>
405    fn Type(&self) -> DOMString {
406        DOMString::from("textarea")
407    }
408
409    /// <https://html.spec.whatwg.org/multipage/#dom-textarea-defaultvalue>
410    fn DefaultValue(&self) -> DOMString {
411        self.upcast::<Node>().GetTextContent().unwrap()
412    }
413
414    /// <https://html.spec.whatwg.org/multipage/#dom-textarea-defaultvalue>
415    fn SetDefaultValue(&self, cx: &mut JSContext, value: DOMString) {
416        self.upcast::<Node>()
417            .set_text_content_for_element(cx, Some(value));
418
419        // if the element's dirty value flag is false, then the element's
420        // raw value must be set to the value of the element's textContent IDL attribute
421        if !self.value_dirty.get() {
422            self.reset(cx);
423        }
424    }
425
426    /// <https://html.spec.whatwg.org/multipage/#dom-textarea-value>
427    fn Value(&self) -> DOMString {
428        self.textinput.borrow().get_content()
429    }
430
431    /// <https://html.spec.whatwg.org/multipage/#dom-textarea-value>
432    fn SetValue(&self, cx: &mut JSContext, value: DOMString) {
433        // Step 1: Let oldAPIValue be this element's API value.
434        let old_api_value = self.Value();
435
436        // Step 2:  Set this element's raw value to the new value.
437        self.textinput.borrow_mut().set_content(value);
438
439        // Step 3: Set this element's dirty value flag to true.
440        self.value_dirty.set(true);
441
442        // Step 4: If the new API value is different from oldAPIValue, then move
443        // the text entry cursor position to the end of the text control,
444        // unselecting any selected text and resetting the selection direction to
445        // "none".
446        if old_api_value != self.Value() {
447            self.textinput.borrow_mut().clear_selection_to_end();
448            self.handle_text_content_changed(cx);
449        }
450    }
451
452    /// <https://html.spec.whatwg.org/multipage/#dom-textarea-textlength>
453    fn TextLength(&self) -> u32 {
454        self.textinput.borrow().len_utf16().0 as u32
455    }
456
457    // https://html.spec.whatwg.org/multipage/#dom-lfe-labels
458    make_labels_getter!(Labels, labels_node_list);
459
460    /// <https://html.spec.whatwg.org/multipage/#dom-textarea/input-select>
461    fn Select(&self) {
462        self.selection().dom_select();
463    }
464
465    /// <https://html.spec.whatwg.org/multipage/#dom-textarea/input-selectionstart>
466    fn GetSelectionStart(&self) -> Option<u32> {
467        self.selection().dom_start().map(|start| start.0 as u32)
468    }
469
470    /// <https://html.spec.whatwg.org/multipage/#dom-textarea/input-selectionstart>
471    fn SetSelectionStart(&self, _cx: &mut JSContext, start: Option<u32>) -> ErrorResult {
472        self.selection()
473            .set_dom_start(start.map(Utf16CodeUnits::from))
474    }
475
476    /// <https://html.spec.whatwg.org/multipage/#dom-textarea/input-selectionend>
477    fn GetSelectionEnd(&self) -> Option<u32> {
478        self.selection().dom_end().map(|end| end.0 as u32)
479    }
480
481    /// <https://html.spec.whatwg.org/multipage/#dom-textarea/input-selectionend>
482    fn SetSelectionEnd(&self, _cx: &mut JSContext, end: Option<u32>) -> ErrorResult {
483        self.selection().set_dom_end(end.map(Utf16CodeUnits::from))
484    }
485
486    /// <https://html.spec.whatwg.org/multipage/#dom-textarea/input-selectiondirection>
487    fn GetSelectionDirection(&self) -> Option<DOMString> {
488        self.selection().dom_direction()
489    }
490
491    /// <https://html.spec.whatwg.org/multipage/#dom-textarea/input-selectiondirection>
492    fn SetSelectionDirection(
493        &self,
494        _cx: &mut JSContext,
495        direction: Option<DOMString>,
496    ) -> ErrorResult {
497        self.selection().set_dom_direction(direction)
498    }
499
500    /// <https://html.spec.whatwg.org/multipage/#dom-textarea/input-setselectionrange>
501    fn SetSelectionRange(&self, start: u32, end: u32, direction: Option<DOMString>) -> ErrorResult {
502        self.selection().set_dom_range(
503            Utf16CodeUnits::from(start),
504            Utf16CodeUnits::from(end),
505            direction,
506        )
507    }
508
509    /// <https://html.spec.whatwg.org/multipage/#dom-textarea/input-setrangetext>
510    fn SetRangeText(&self, replacement: DOMString) -> ErrorResult {
511        self.selection()
512            .set_dom_range_text(replacement, None, None, Default::default())
513    }
514
515    /// <https://html.spec.whatwg.org/multipage/#dom-textarea/input-setrangetext>
516    fn SetRangeText_(
517        &self,
518        replacement: DOMString,
519        start: u32,
520        end: u32,
521        selection_mode: SelectionMode,
522    ) -> ErrorResult {
523        self.selection().set_dom_range_text(
524            replacement,
525            Some(Utf16CodeUnits::from(start)),
526            Some(Utf16CodeUnits::from(end)),
527            selection_mode,
528        )
529    }
530
531    /// <https://html.spec.whatwg.org/multipage/#dom-cva-willvalidate>
532    fn WillValidate(&self) -> bool {
533        self.is_instance_validatable()
534    }
535
536    /// <https://html.spec.whatwg.org/multipage/#dom-cva-validity>
537    fn Validity(&self, cx: &mut JSContext) -> DomRoot<ValidityState> {
538        self.validity_state(cx)
539    }
540
541    /// <https://html.spec.whatwg.org/multipage/#dom-cva-checkvalidity>
542    fn CheckValidity(&self, cx: &mut JSContext) -> bool {
543        self.check_validity(cx)
544    }
545
546    /// <https://html.spec.whatwg.org/multipage/#dom-cva-reportvalidity>
547    fn ReportValidity(&self, cx: &mut JSContext) -> bool {
548        self.report_validity(cx)
549    }
550
551    /// <https://html.spec.whatwg.org/multipage/#dom-cva-validationmessage>
552    fn ValidationMessage(&self, cx: &mut JSContext) -> DOMString {
553        self.validation_message(cx)
554    }
555
556    /// <https://html.spec.whatwg.org/multipage/#dom-cva-setcustomvalidity>
557    fn SetCustomValidity(&self, cx: &mut JSContext, error: DOMString) {
558        self.validity_state(cx).set_custom_error_message(cx, error);
559    }
560}
561
562impl HTMLTextAreaElement {
563    /// <https://w3c.github.io/webdriver/#ref-for-dfn-clear-algorithm-4>
564    /// Used by WebDriver to clear the textarea element.
565    pub(crate) fn clear(&self) {
566        self.value_dirty.set(false);
567        self.textinput.borrow_mut().set_content(DOMString::from(""));
568    }
569
570    pub(crate) fn reset(&self, cx: &mut JSContext) {
571        // https://html.spec.whatwg.org/multipage/#the-textarea-element:concept-form-reset-control
572        self.value_dirty.set(false);
573        self.textinput.borrow_mut().set_content(self.DefaultValue());
574        self.handle_text_content_changed(cx);
575    }
576
577    fn selection(&self) -> TextControlSelection<'_, Self> {
578        TextControlSelection::new(self, &self.textinput)
579    }
580
581    fn handle_key_reaction(&self, cx: &mut JSContext, action: KeyReaction, event: &Event) {
582        match action {
583            KeyReaction::TriggerDefaultAction => (),
584            KeyReaction::DispatchInput(text, is_composing, input_type) => {
585                if event.IsTrusted() {
586                    self.textinput.borrow().queue_input_event(
587                        self.upcast(),
588                        text,
589                        is_composing,
590                        input_type,
591                    );
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 textinput = self.textinput.borrow_mut();
644
645                    if value < 0 {
646                        textinput.set_max_length(None);
647                    } else {
648                        textinput.set_max_length(Some(Utf16CodeUnits(value as usize)))
649                    }
650                },
651                _ => panic!("Expected an AttrValue::Int"),
652            },
653            local_name!("minlength") => match *attr.value() {
654                AttrValue::Int(_, value) => {
655                    let mut textinput = self.textinput.borrow_mut();
656
657                    if value < 0 {
658                        textinput.set_min_length(None);
659                    } else {
660                        textinput.set_min_length(Some(Utf16CodeUnits(value as usize)))
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 textinput = el.textinput.borrow_mut();
768            textinput.set_content(self.textinput.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 let Some(mouse_event) = event.downcast::<MouseEvent>() {
786            self.handle_mouse_event(mouse_event);
787            event.mark_as_handled();
788        } else if event.type_() == atom!("keydown") && !event.DefaultPrevented() {
789            if let Some(keyboard_event) = event.downcast::<KeyboardEvent>() {
790                // This can't be inlined, as holding on to textinput.borrow_mut()
791                // during self.implicit_submission will cause a panic.
792                let action = self.textinput.borrow_mut().handle_keydown(keyboard_event);
793                self.handle_key_reaction(cx, action, event);
794            }
795        } else if event.type_() == atom!("compositionstart") ||
796            event.type_() == atom!("compositionupdate") ||
797            event.type_() == atom!("compositionend")
798        {
799            if let Some(compositionevent) = event.downcast::<CompositionEvent>() {
800                if event.type_() == atom!("compositionend") {
801                    let action = self
802                        .textinput
803                        .borrow_mut()
804                        .handle_compositionend(compositionevent);
805                    self.handle_key_reaction(cx, action, event);
806                    self.upcast::<Node>().dirty(cx.no_gc(), NodeDamage::Other);
807                } else if event.type_() == atom!("compositionupdate") {
808                    let action = self
809                        .textinput
810                        .borrow_mut()
811                        .handle_compositionupdate(compositionevent);
812                    self.handle_key_reaction(cx, action, event);
813                    self.upcast::<Node>().dirty(cx.no_gc(), NodeDamage::Other);
814                }
815                self.maybe_update_shared_selection();
816                event.mark_as_handled();
817            }
818        } else if let Some(clipboard_event) = event.downcast::<ClipboardEvent>() {
819            let reaction = self
820                .textinput
821                .borrow_mut()
822                .handle_clipboard_event(clipboard_event);
823
824            let flags = reaction.flags;
825            if flags.contains(ClipboardEventFlags::FireClipboardChangedEvent) {
826                self.owner_document().event_handler().fire_clipboard_event(
827                    cx,
828                    None,
829                    ClipboardEventType::Change,
830                );
831            }
832            if flags.contains(ClipboardEventFlags::QueueInputEvent) {
833                self.textinput.borrow().queue_input_event(
834                    self.upcast(),
835                    reaction.text,
836                    IsComposing::NotComposing,
837                    reaction.input_type,
838                );
839            }
840            if !flags.is_empty() {
841                event.mark_as_handled();
842                self.handle_text_content_changed(cx);
843            }
844        } else if let Some(event) = event.downcast::<FocusEvent>() {
845            self.handle_focus_event(event);
846        }
847
848        self.validity_state(cx)
849            .perform_validation_and_update(cx, ValidationFlags::all());
850
851        if let Some(super_type) = self.super_type() {
852            super_type.handle_event(cx, event);
853        }
854    }
855
856    fn pop(&self, cx: &mut JSContext) {
857        self.super_type().unwrap().pop(cx);
858
859        // https://html.spec.whatwg.org/multipage/#the-textarea-element:stack-of-open-elements
860        self.reset(cx);
861    }
862}
863
864impl FormControl for HTMLTextAreaElement {
865    fn form_owner(&self) -> Option<DomRoot<HTMLFormElement>> {
866        self.form_owner.get()
867    }
868
869    fn set_form_owner(&self, _cx: &mut JSContext, form: Option<&HTMLFormElement>) {
870        self.form_owner.set(form);
871    }
872
873    fn to_html_element(&self) -> &HTMLElement {
874        self.upcast::<HTMLElement>()
875    }
876}
877
878impl Validatable for HTMLTextAreaElement {
879    fn as_element(&self) -> &Element {
880        self.upcast()
881    }
882
883    fn validity_state(&self, cx: &mut JSContext) -> DomRoot<ValidityState> {
884        self.validity_state
885            .or_init(|| ValidityState::new(cx, &self.owner_window(), self.upcast()))
886    }
887
888    fn is_instance_validatable(&self) -> bool {
889        // https://html.spec.whatwg.org/multipage/#enabling-and-disabling-form-controls%3A-the-disabled-attribute%3Abarred-from-constraint-validation
890        // https://html.spec.whatwg.org/multipage/#the-textarea-element%3Abarred-from-constraint-validation
891        // https://html.spec.whatwg.org/multipage/#the-datalist-element%3Abarred-from-constraint-validation
892        !self.upcast::<Element>().disabled_state() &&
893            !self.ReadOnly() &&
894            !is_barred_by_datalist_ancestor(self.upcast())
895    }
896
897    fn perform_validation(
898        &self,
899        _cx: &mut JSContext,
900        validate_flags: ValidationFlags,
901    ) -> ValidationFlags {
902        let mut failed_flags = ValidationFlags::empty();
903
904        let textinput = self.textinput.borrow();
905        let Utf16CodeUnits(value_len) = textinput.len_utf16();
906        let last_edit_by_user = !textinput.was_last_change_by_set_content();
907        let value_dirty = self.value_dirty.get();
908
909        // https://html.spec.whatwg.org/multipage/#suffering-from-being-missing
910        // https://html.spec.whatwg.org/multipage/#the-textarea-element%3Asuffering-from-being-missing
911        if validate_flags.contains(ValidationFlags::VALUE_MISSING) &&
912            self.Required() &&
913            self.is_mutable() &&
914            value_len == 0
915        {
916            failed_flags.insert(ValidationFlags::VALUE_MISSING);
917        }
918
919        if value_dirty && last_edit_by_user && value_len > 0 {
920            // https://html.spec.whatwg.org/multipage/#suffering-from-being-too-long
921            // https://html.spec.whatwg.org/multipage/#limiting-user-input-length%3A-the-maxlength-attribute%3Asuffering-from-being-too-long
922            if validate_flags.contains(ValidationFlags::TOO_LONG) {
923                let max_length = self.MaxLength();
924                if max_length != DEFAULT_MAX_LENGTH && value_len > (max_length as usize) {
925                    failed_flags.insert(ValidationFlags::TOO_LONG);
926                }
927            }
928
929            // https://html.spec.whatwg.org/multipage/#suffering-from-being-too-short
930            // https://html.spec.whatwg.org/multipage/#setting-minimum-input-length-requirements%3A-the-minlength-attribute%3Asuffering-from-being-too-short
931            if validate_flags.contains(ValidationFlags::TOO_SHORT) {
932                let min_length = self.MinLength();
933                if min_length != DEFAULT_MIN_LENGTH && value_len < (min_length as usize) {
934                    failed_flags.insert(ValidationFlags::TOO_SHORT);
935                }
936            }
937        }
938
939        failed_flags
940    }
941}