script/dom/html/
htmltextareaelement.rs

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