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