diff options
Diffstat (limited to 'components/script/dom/htmltextareaelement.rs')
-rwxr-xr-x | components/script/dom/htmltextareaelement.rs | 593 |
1 files changed, 438 insertions, 155 deletions
diff --git a/components/script/dom/htmltextareaelement.rs b/components/script/dom/htmltextareaelement.rs index 8a4e9f41cd3..c6d9331532f 100755 --- a/components/script/dom/htmltextareaelement.rs +++ b/components/script/dom/htmltextareaelement.rs @@ -1,134 +1,191 @@ /* This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this - * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ - -use dom::attr::Attr; -use dom::bindings::cell::DOMRefCell; -use dom::bindings::codegen::Bindings::EventBinding::EventMethods; -use dom::bindings::codegen::Bindings::HTMLTextAreaElementBinding; -use dom::bindings::codegen::Bindings::HTMLTextAreaElementBinding::HTMLTextAreaElementMethods; -use dom::bindings::codegen::Bindings::NodeBinding::NodeMethods; -use dom::bindings::inheritance::Castable; -use dom::bindings::js::{LayoutJS, MutNullableJS, Root}; -use dom::bindings::str::DOMString; -use dom::document::Document; -use dom::element::{AttributeMutation, Element}; -use dom::element::RawLayoutElementHelpers; -use dom::event::{Event, EventBubbles, EventCancelable}; -use dom::globalscope::GlobalScope; -use dom::htmlelement::HTMLElement; -use dom::htmlfieldsetelement::HTMLFieldSetElement; -use dom::htmlformelement::{FormControl, HTMLFormElement}; -use dom::keyboardevent::KeyboardEvent; -use dom::node::{ChildrenMutation, Node, NodeDamage, UnbindContext}; -use dom::node::{document_from_node, window_from_node}; -use dom::nodelist::NodeList; -use dom::validation::Validatable; -use dom::virtualmethods::VirtualMethods; + * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ + +use crate::dom::attr::Attr; +use crate::dom::bindings::cell::DomRefCell; +use crate::dom::bindings::codegen::Bindings::EventBinding::EventMethods; +use crate::dom::bindings::codegen::Bindings::HTMLFormElementBinding::SelectionMode; +use crate::dom::bindings::codegen::Bindings::HTMLTextAreaElementBinding::HTMLTextAreaElementMethods; +use crate::dom::bindings::codegen::Bindings::NodeBinding::NodeMethods; +use crate::dom::bindings::error::ErrorResult; +use crate::dom::bindings::inheritance::Castable; +use crate::dom::bindings::root::{DomRoot, LayoutDom, MutNullableDom}; +use crate::dom::bindings::str::DOMString; +use crate::dom::compositionevent::CompositionEvent; +use crate::dom::document::Document; +use crate::dom::element::LayoutElementHelpers; +use crate::dom::element::{AttributeMutation, Element}; +use crate::dom::event::{Event, EventBubbles, EventCancelable}; +use crate::dom::globalscope::GlobalScope; +use crate::dom::htmlelement::HTMLElement; +use crate::dom::htmlfieldsetelement::HTMLFieldSetElement; +use crate::dom::htmlformelement::{FormControl, HTMLFormElement}; +use crate::dom::htmlinputelement::HTMLInputElement; +use crate::dom::keyboardevent::KeyboardEvent; +use crate::dom::node::window_from_node; +use crate::dom::node::{ + BindContext, ChildrenMutation, CloneChildrenFlag, Node, NodeDamage, UnbindContext, +}; +use crate::dom::nodelist::NodeList; +use crate::dom::textcontrol::{TextControlElement, TextControlSelection}; +use crate::dom::validation::{is_barred_by_datalist_ancestor, Validatable}; +use crate::dom::validitystate::{ValidationFlags, ValidityState}; +use crate::dom::virtualmethods::VirtualMethods; +use crate::textinput::{ + Direction, KeyReaction, Lines, SelectionDirection, TextInput, UTF16CodeUnits, UTF8Bytes, +}; use dom_struct::dom_struct; -use html5ever_atoms::LocalName; -use ipc_channel::ipc::IpcSender; -use script_traits::ScriptMsg as ConstellationMsg; +use html5ever::{LocalName, Prefix}; +use script_traits::ScriptToConstellationChan; use std::cell::Cell; use std::default::Default; use std::ops::Range; use style::attr::AttrValue; -use style::element_state::*; -use textinput::{KeyReaction, Lines, SelectionDirection, TextInput}; +use style::element_state::ElementState; #[dom_struct] pub struct HTMLTextAreaElement { htmlelement: HTMLElement, - #[ignore_heap_size_of = "#7193"] - textinput: DOMRefCell<TextInput<IpcSender<ConstellationMsg>>>, - placeholder: DOMRefCell<DOMString>, + #[ignore_malloc_size_of = "#7193"] + textinput: DomRefCell<TextInput<ScriptToConstellationChan>>, + placeholder: DomRefCell<DOMString>, // https://html.spec.whatwg.org/multipage/#concept-textarea-dirty - value_changed: Cell<bool>, - form_owner: MutNullableJS<HTMLFormElement>, + value_dirty: Cell<bool>, + form_owner: MutNullableDom<HTMLFormElement>, + labels_node_list: MutNullableDom<NodeList>, + validity_state: MutNullableDom<ValidityState>, } pub trait LayoutHTMLTextAreaElementHelpers { - #[allow(unsafe_code)] - unsafe fn get_value_for_layout(self) -> String; - #[allow(unsafe_code)] - unsafe fn selection_for_layout(self) -> Option<Range<usize>>; - #[allow(unsafe_code)] + fn value_for_layout(self) -> String; + fn selection_for_layout(self) -> Option<Range<usize>>; fn get_cols(self) -> u32; - #[allow(unsafe_code)] fn get_rows(self) -> u32; } -impl LayoutHTMLTextAreaElementHelpers for LayoutJS<HTMLTextAreaElement> { - #[allow(unrooted_must_root)] - #[allow(unsafe_code)] - unsafe fn get_value_for_layout(self) -> String { - let text = (*self.unsafe_get()).textinput.borrow_for_layout().get_content(); - String::from(if text.is_empty() { - (*self.unsafe_get()).placeholder.borrow_for_layout().clone() +#[allow(unsafe_code)] +impl<'dom> LayoutDom<'dom, HTMLTextAreaElement> { + fn textinput_content(self) -> DOMString { + unsafe { + self.unsafe_get() + .textinput + .borrow_for_layout() + .get_content() + } + } + + fn textinput_sorted_selection_offsets_range(self) -> Range<UTF8Bytes> { + unsafe { + self.unsafe_get() + .textinput + .borrow_for_layout() + .sorted_selection_offsets_range() + } + } + + fn placeholder(self) -> &'dom str { + unsafe { self.unsafe_get().placeholder.borrow_for_layout() } + } +} + +impl LayoutHTMLTextAreaElementHelpers for LayoutDom<'_, HTMLTextAreaElement> { + fn value_for_layout(self) -> String { + let text = self.textinput_content(); + if text.is_empty() { + // FIXME(nox): Would be cool to not allocate a new string if the + // placeholder is single line, but that's an unimportant detail. + self.placeholder() + .replace("\r\n", "\n") + .replace("\r", "\n") + .into() } else { - text - }) + text.into() + } } - #[allow(unrooted_must_root)] - #[allow(unsafe_code)] - unsafe fn selection_for_layout(self) -> Option<Range<usize>> { - if !(*self.unsafe_get()).upcast::<Element>().focus_state() { + fn selection_for_layout(self) -> Option<Range<usize>> { + if !self.upcast::<Element>().focus_state() { return None; } - let textinput = (*self.unsafe_get()).textinput.borrow_for_layout(); - Some(textinput.get_absolute_selection_range()) + Some(UTF8Bytes::unwrap_range( + self.textinput_sorted_selection_offsets_range(), + )) } - #[allow(unsafe_code)] fn get_cols(self) -> u32 { - unsafe { - (*self.upcast::<Element>().unsafe_get()) - .get_attr_for_layout(&ns!(), &local_name!("cols")) - .map_or(DEFAULT_COLS, AttrValue::as_uint) - } + self.upcast::<Element>() + .get_attr_for_layout(&ns!(), &local_name!("cols")) + .map_or(DEFAULT_COLS, AttrValue::as_uint) } - #[allow(unsafe_code)] fn get_rows(self) -> u32 { - unsafe { - (*self.upcast::<Element>().unsafe_get()) - .get_attr_for_layout(&ns!(), &local_name!("rows")) - .map_or(DEFAULT_ROWS, AttrValue::as_uint) - } + self.upcast::<Element>() + .get_attr_for_layout(&ns!(), &local_name!("rows")) + .map_or(DEFAULT_ROWS, AttrValue::as_uint) } } // https://html.spec.whatwg.org/multipage/#attr-textarea-cols-value -static DEFAULT_COLS: u32 = 20; +const DEFAULT_COLS: u32 = 20; // https://html.spec.whatwg.org/multipage/#attr-textarea-rows-value -static DEFAULT_ROWS: u32 = 2; +const DEFAULT_ROWS: u32 = 2; + +const DEFAULT_MAX_LENGTH: i32 = -1; +const DEFAULT_MIN_LENGTH: i32 = -1; impl HTMLTextAreaElement { - fn new_inherited(local_name: LocalName, - prefix: Option<DOMString>, - document: &Document) -> HTMLTextAreaElement { - let chan = document.window().upcast::<GlobalScope>().constellation_chan().clone(); + fn new_inherited( + local_name: LocalName, + prefix: Option<Prefix>, + document: &Document, + ) -> HTMLTextAreaElement { + let chan = document + .window() + .upcast::<GlobalScope>() + .script_to_constellation_chan() + .clone(); HTMLTextAreaElement { - htmlelement: - HTMLElement::new_inherited_with_state(IN_ENABLED_STATE | IN_READ_WRITE_STATE, - local_name, prefix, document), - placeholder: DOMRefCell::new(DOMString::new()), - textinput: DOMRefCell::new(TextInput::new( - Lines::Multiple, DOMString::new(), chan, None, None, SelectionDirection::None)), - value_changed: Cell::new(false), + htmlelement: HTMLElement::new_inherited_with_state( + ElementState::IN_ENABLED_STATE | ElementState::IN_READ_WRITE_STATE, + local_name, + prefix, + document, + ), + placeholder: DomRefCell::new(DOMString::new()), + textinput: DomRefCell::new(TextInput::new( + Lines::Multiple, + DOMString::new(), + chan, + None, + None, + SelectionDirection::None, + )), + value_dirty: Cell::new(false), form_owner: Default::default(), + labels_node_list: Default::default(), + validity_state: Default::default(), } } #[allow(unrooted_must_root)] - pub fn new(local_name: LocalName, - prefix: Option<DOMString>, - document: &Document) -> Root<HTMLTextAreaElement> { - Node::reflect_node(box HTMLTextAreaElement::new_inherited(local_name, prefix, document), - document, - HTMLTextAreaElementBinding::Wrap) + pub fn new( + local_name: LocalName, + prefix: Option<Prefix>, + document: &Document, + ) -> DomRoot<HTMLTextAreaElement> { + Node::reflect_node( + Box::new(HTMLTextAreaElement::new_inherited( + local_name, prefix, document, + )), + document, + ) + } + + pub fn auto_directionality(&self) -> String { + let value: String = self.Value().to_string(); + return HTMLInputElement::directionality_from_value(&value); } fn update_placeholder_shown_state(&self) { @@ -136,7 +193,27 @@ impl HTMLTextAreaElement { let has_value = !self.textinput.borrow().is_empty(); let el = self.upcast::<Element>(); el.set_placeholder_shown_state(has_placeholder && !has_value); - el.set_placeholder_shown_state(has_placeholder); + } + + // https://html.spec.whatwg.org/multipage/#concept-fe-mutable + fn is_mutable(&self) -> bool { + // https://html.spec.whatwg.org/multipage/#the-textarea-element%3Aconcept-fe-mutable + // https://html.spec.whatwg.org/multipage/#the-readonly-attribute:concept-fe-mutable + !(self.upcast::<Element>().disabled_state() || self.ReadOnly()) + } +} + +impl TextControlElement for HTMLTextAreaElement { + fn selection_api_applies(&self) -> bool { + true + } + + fn has_selectable_text(&self) -> bool { + true + } + + fn set_dirty_value_flag(&self, value: bool) { + self.value_dirty.set(value) } } @@ -150,6 +227,12 @@ impl HTMLTextAreaElementMethods for HTMLTextAreaElement { // https://html.spec.whatwg.org/multipage/#dom-textarea-cols make_limited_uint_setter!(SetCols, "cols", DEFAULT_COLS); + // https://html.spec.whatwg.org/multipage/#dom-input-dirName + make_getter!(DirName, "dirname"); + + // https://html.spec.whatwg.org/multipage/#dom-input-dirName + make_setter!(SetDirName, "dirname"); + // https://html.spec.whatwg.org/multipage/#dom-fe-disabled make_bool_getter!(Disabled, "disabled"); @@ -157,7 +240,7 @@ impl HTMLTextAreaElementMethods for HTMLTextAreaElement { make_bool_setter!(SetDisabled, "disabled"); // https://html.spec.whatwg.org/multipage/#dom-fae-form - fn GetForm(&self) -> Option<Root<HTMLFormElement>> { + fn GetForm(&self) -> Option<DomRoot<HTMLFormElement>> { self.form_owner() } @@ -165,7 +248,7 @@ impl HTMLTextAreaElementMethods for HTMLTextAreaElement { make_getter!(Name, "name"); // https://html.spec.whatwg.org/multipage/#attr-fe-name - make_setter!(SetName, "name"); + make_atomic_setter!(SetName, "name"); // https://html.spec.whatwg.org/multipage/#dom-textarea-placeholder make_getter!(Placeholder, "placeholder"); @@ -173,6 +256,18 @@ impl HTMLTextAreaElementMethods for HTMLTextAreaElement { // https://html.spec.whatwg.org/multipage/#dom-textarea-placeholder make_setter!(SetPlaceholder, "placeholder"); + // https://html.spec.whatwg.org/multipage/#attr-textarea-maxlength + make_int_getter!(MaxLength, "maxlength", DEFAULT_MAX_LENGTH); + + // https://html.spec.whatwg.org/multipage/#attr-textarea-maxlength + make_limited_int_setter!(SetMaxLength, "maxlength", DEFAULT_MAX_LENGTH); + + // https://html.spec.whatwg.org/multipage/#attr-textarea-minlength + make_int_getter!(MinLength, "minlength", DEFAULT_MIN_LENGTH); + + // https://html.spec.whatwg.org/multipage/#attr-textarea-minlength + make_limited_int_setter!(SetMinLength, "minlength", DEFAULT_MIN_LENGTH); + // https://html.spec.whatwg.org/multipage/#attr-textarea-readonly make_bool_getter!(ReadOnly, "readonly"); @@ -213,7 +308,7 @@ impl HTMLTextAreaElementMethods for HTMLTextAreaElement { // if the element's dirty value flag is false, then the element's // raw value must be set to the value of the element's textContent IDL attribute - if !self.value_changed.get() { + if !self.value_dirty.get() { self.reset(); } } @@ -225,81 +320,140 @@ impl HTMLTextAreaElementMethods for HTMLTextAreaElement { // https://html.spec.whatwg.org/multipage/#dom-textarea-value fn SetValue(&self, value: DOMString) { - // TODO move the cursor to the end of the field - self.textinput.borrow_mut().set_content(value); - self.value_changed.set(true); + let mut textinput = self.textinput.borrow_mut(); + + // Step 1 + let old_value = textinput.get_content(); + + // Step 2 + textinput.set_content(value); + + // Step 3 + self.value_dirty.set(true); + + if old_value != textinput.get_content() { + // Step 4 + textinput.clear_selection_to_limit(Direction::Forward); + } self.upcast::<Node>().dirty(NodeDamage::OtherNodeDamage); } + // https://html.spec.whatwg.org/multipage/#dom-textarea-textlength + fn TextLength(&self) -> u32 { + let UTF16CodeUnits(num_units) = self.textinput.borrow().utf16_len(); + num_units as u32 + } + // https://html.spec.whatwg.org/multipage/#dom-lfe-labels - fn Labels(&self) -> Root<NodeList> { - self.upcast::<HTMLElement>().labels() + make_labels_getter!(Labels, labels_node_list); + + // https://html.spec.whatwg.org/multipage/#dom-textarea/input-select + fn Select(&self) { + self.selection().dom_select(); } - // https://html.spec.whatwg.org/multipage/#dom-textarea/input-selectiondirection - fn SetSelectionDirection(&self, direction: DOMString) { - self.textinput.borrow_mut().selection_direction = SelectionDirection::from(direction); + // https://html.spec.whatwg.org/multipage/#dom-textarea/input-selectionstart + fn GetSelectionStart(&self) -> Option<u32> { + self.selection().dom_start() } - // https://html.spec.whatwg.org/multipage/#dom-textarea/input-selectiondirection - fn SelectionDirection(&self) -> DOMString { - DOMString::from(self.textinput.borrow().selection_direction) + // https://html.spec.whatwg.org/multipage/#dom-textarea/input-selectionstart + fn SetSelectionStart(&self, start: Option<u32>) -> ErrorResult { + self.selection().set_dom_start(start) } // https://html.spec.whatwg.org/multipage/#dom-textarea/input-selectionend - fn SetSelectionEnd(&self, end: u32) { - let selection_start = self.SelectionStart(); - self.textinput.borrow_mut().set_selection_range(selection_start, end); - self.upcast::<Node>().dirty(NodeDamage::OtherNodeDamage); + fn GetSelectionEnd(&self) -> Option<u32> { + self.selection().dom_end() } // https://html.spec.whatwg.org/multipage/#dom-textarea/input-selectionend - fn SelectionEnd(&self) -> u32 { - self.textinput.borrow().get_absolute_insertion_point() as u32 + fn SetSelectionEnd(&self, end: Option<u32>) -> ErrorResult { + self.selection().set_dom_end(end) } - // https://html.spec.whatwg.org/multipage/#dom-textarea/input-selectionstart - fn SetSelectionStart(&self, start: u32) { - let selection_end = self.SelectionEnd(); - self.textinput.borrow_mut().set_selection_range(start, selection_end); - self.upcast::<Node>().dirty(NodeDamage::OtherNodeDamage); + // https://html.spec.whatwg.org/multipage/#dom-textarea/input-selectiondirection + fn GetSelectionDirection(&self) -> Option<DOMString> { + self.selection().dom_direction() } - // https://html.spec.whatwg.org/multipage/#dom-textarea/input-selectionstart - fn SelectionStart(&self) -> u32 { - self.textinput.borrow().get_selection_start() + // https://html.spec.whatwg.org/multipage/#dom-textarea/input-selectiondirection + fn SetSelectionDirection(&self, direction: Option<DOMString>) -> ErrorResult { + self.selection().set_dom_direction(direction) } // https://html.spec.whatwg.org/multipage/#dom-textarea/input-setselectionrange - fn SetSelectionRange(&self, start: u32, end: u32, direction: Option<DOMString>) { - let direction = direction.map_or(SelectionDirection::None, |d| SelectionDirection::from(d)); - self.textinput.borrow_mut().selection_direction = direction; - self.textinput.borrow_mut().set_selection_range(start, end); - let window = window_from_node(self); - let _ = window.user_interaction_task_source().queue_event( - &self.upcast(), - atom!("select"), - EventBubbles::Bubbles, - EventCancelable::NotCancelable, - &window); - self.upcast::<Node>().dirty(NodeDamage::OtherNodeDamage); + fn SetSelectionRange(&self, start: u32, end: u32, direction: Option<DOMString>) -> ErrorResult { + self.selection().set_dom_range(start, end, direction) } -} + // https://html.spec.whatwg.org/multipage/#dom-textarea/input-setrangetext + fn SetRangeText(&self, replacement: DOMString) -> ErrorResult { + self.selection() + .set_dom_range_text(replacement, None, None, Default::default()) + } + + // https://html.spec.whatwg.org/multipage/#dom-textarea/input-setrangetext + fn SetRangeText_( + &self, + replacement: DOMString, + start: u32, + end: u32, + selection_mode: SelectionMode, + ) -> ErrorResult { + self.selection() + .set_dom_range_text(replacement, Some(start), Some(end), selection_mode) + } + + // https://html.spec.whatwg.org/multipage/#dom-cva-willvalidate + fn WillValidate(&self) -> bool { + self.is_instance_validatable() + } + + // https://html.spec.whatwg.org/multipage/#dom-cva-validity + fn Validity(&self) -> DomRoot<ValidityState> { + self.validity_state() + } + + // https://html.spec.whatwg.org/multipage/#dom-cva-checkvalidity + fn CheckValidity(&self) -> bool { + self.check_validity() + } + + // https://html.spec.whatwg.org/multipage/#dom-cva-reportvalidity + fn ReportValidity(&self) -> bool { + self.report_validity() + } + + // https://html.spec.whatwg.org/multipage/#dom-cva-validationmessage + fn ValidationMessage(&self) -> DOMString { + self.validation_message() + } + + // https://html.spec.whatwg.org/multipage/#dom-cva-setcustomvalidity + fn SetCustomValidity(&self, error: DOMString) { + self.validity_state().set_custom_error_message(error); + } +} impl HTMLTextAreaElement { pub fn reset(&self) { // https://html.spec.whatwg.org/multipage/#the-textarea-element:concept-form-reset-control - self.SetValue(self.DefaultValue()); - self.value_changed.set(false); + let mut textinput = self.textinput.borrow_mut(); + textinput.set_content(self.DefaultValue()); + self.value_dirty.set(false); } -} + #[allow(unrooted_must_root)] + fn selection(&self) -> TextControlSelection<Self> { + TextControlSelection::new(&self, &self.textinput) + } +} impl VirtualMethods for HTMLTextAreaElement { - fn super_type(&self) -> Option<&VirtualMethods> { - Some(self.upcast::<HTMLElement>() as &VirtualMethods) + fn super_type(&self) -> Option<&dyn VirtualMethods> { + Some(self.upcast::<HTMLElement>() as &dyn VirtualMethods) } fn attribute_mutated(&self, attr: &Attr, mutation: AttributeMutation) { @@ -322,8 +476,33 @@ impl VirtualMethods for HTMLTextAreaElement { if !el.disabled_state() && !el.read_write_state() { el.set_read_write_state(true); } - } + }, } + el.update_sequentially_focusable_status(); + }, + local_name!("maxlength") => match *attr.value() { + AttrValue::Int(_, value) => { + let mut textinput = self.textinput.borrow_mut(); + + if value < 0 { + textinput.set_max_length(None); + } else { + textinput.set_max_length(Some(UTF16CodeUnits(value as usize))) + } + }, + _ => panic!("Expected an AttrValue::Int"), + }, + local_name!("minlength") => match *attr.value() { + AttrValue::Int(_, value) => { + let mut textinput = self.textinput.borrow_mut(); + + if value < 0 { + textinput.set_min_length(None); + } else { + textinput.set_min_length(Some(UTF16CodeUnits(value as usize))) + } + }, + _ => panic!("Expected an AttrValue::Int"), }, local_name!("placeholder") => { { @@ -343,7 +522,7 @@ impl VirtualMethods for HTMLTextAreaElement { }, AttributeMutation::Removed => { el.set_read_write_state(!el.disabled_state()); - } + }, } }, local_name!("form") => { @@ -353,19 +532,29 @@ impl VirtualMethods for HTMLTextAreaElement { } } - fn bind_to_tree(&self, tree_in_doc: bool) { + fn bind_to_tree(&self, context: &BindContext) { if let Some(ref s) = self.super_type() { - s.bind_to_tree(tree_in_doc); + s.bind_to_tree(context); } - self.upcast::<Element>().check_ancestors_disabled_state_for_form_control(); + self.upcast::<Element>() + .check_ancestors_disabled_state_for_form_control(); } fn parse_plain_attribute(&self, name: &LocalName, value: DOMString) -> AttrValue { match *name { local_name!("cols") => AttrValue::from_limited_u32(value.into(), DEFAULT_COLS), local_name!("rows") => AttrValue::from_limited_u32(value.into(), DEFAULT_ROWS), - _ => self.super_type().unwrap().parse_plain_attribute(name, value), + local_name!("maxlength") => { + AttrValue::from_limited_i32(value.into(), DEFAULT_MAX_LENGTH) + }, + local_name!("minlength") => { + AttrValue::from_limited_i32(value.into(), DEFAULT_MIN_LENGTH) + }, + _ => self + .super_type() + .unwrap() + .parse_plain_attribute(name, value), } } @@ -374,18 +563,38 @@ impl VirtualMethods for HTMLTextAreaElement { let node = self.upcast::<Node>(); let el = self.upcast::<Element>(); - if node.ancestors().any(|ancestor| ancestor.is::<HTMLFieldSetElement>()) { + if node + .ancestors() + .any(|ancestor| ancestor.is::<HTMLFieldSetElement>()) + { el.check_ancestors_disabled_state_for_form_control(); } else { el.check_disabled_attribute(); } } + // The cloning steps for textarea elements must propagate the raw value + // and dirty value flag from the node being cloned to the copy. + fn cloning_steps( + &self, + copy: &Node, + maybe_doc: Option<&Document>, + clone_children: CloneChildrenFlag, + ) { + if let Some(ref s) = self.super_type() { + s.cloning_steps(copy, maybe_doc, clone_children); + } + let el = copy.downcast::<HTMLTextAreaElement>().unwrap(); + el.value_dirty.set(self.value_dirty.get()); + let mut textinput = el.textinput.borrow_mut(); + textinput.set_content(self.textinput.borrow().get_content()); + } + fn children_changed(&self, mutation: &ChildrenMutation) { if let Some(ref s) = self.super_type() { s.children_changed(mutation); } - if !self.value_changed.get() { + if !self.value_dirty.get() { self.reset(); } } @@ -398,8 +607,6 @@ impl VirtualMethods for HTMLTextAreaElement { if event.type_() == atom!("click") && !event.DefaultPrevented() { //TODO: set the editing position for text inputs - - document_from_node(self).request_focus(self.upcast()); } else if event.type_() == atom!("keydown") && !event.DefaultPrevented() { if let Some(kevent) = event.downcast::<KeyboardEvent>() { // This can't be inlined, as holding on to textinput.borrow_mut() @@ -408,27 +615,47 @@ impl VirtualMethods for HTMLTextAreaElement { match action { KeyReaction::TriggerDefaultAction => (), KeyReaction::DispatchInput => { - self.value_changed.set(true); + self.value_dirty.set(true); self.update_placeholder_shown_state(); self.upcast::<Node>().dirty(NodeDamage::OtherNodeDamage); event.mark_as_handled(); - } + }, KeyReaction::RedrawSelection => { self.upcast::<Node>().dirty(NodeDamage::OtherNodeDamage); event.mark_as_handled(); - } + }, KeyReaction::Nothing => (), } } } else if event.type_() == atom!("keypress") && !event.DefaultPrevented() { if event.IsTrusted() { let window = window_from_node(self); - let _ = window.user_interaction_task_source() - .queue_event(&self.upcast(), - atom!("input"), - EventBubbles::Bubbles, - EventCancelable::NotCancelable, - &window); + let _ = window + .task_manager() + .user_interaction_task_source() + .queue_event( + &self.upcast(), + atom!("input"), + EventBubbles::Bubbles, + EventCancelable::NotCancelable, + &window, + ); + } + } else if event.type_() == atom!("compositionstart") || + event.type_() == atom!("compositionupdate") || + event.type_() == atom!("compositionend") + { + // TODO: Update DOM on start and continue + // and generally do proper CompositionEvent handling. + if let Some(compositionevent) = event.downcast::<CompositionEvent>() { + if event.type_() == atom!("compositionend") { + let _ = self + .textinput + .borrow_mut() + .handle_compositionend(compositionevent); + self.upcast::<Node>().dirty(NodeDamage::OtherNodeDamage); + } + event.mark_as_handled(); } } } @@ -442,7 +669,7 @@ impl VirtualMethods for HTMLTextAreaElement { } impl FormControl for HTMLTextAreaElement { - fn form_owner(&self) -> Option<Root<HTMLFormElement>> { + fn form_owner(&self) -> Option<DomRoot<HTMLFormElement>> { self.form_owner.get() } @@ -455,5 +682,61 @@ impl FormControl for HTMLTextAreaElement { } } +impl Validatable for HTMLTextAreaElement { + fn as_element(&self) -> &Element { + self.upcast() + } + + fn validity_state(&self) -> DomRoot<ValidityState> { + self.validity_state + .or_init(|| ValidityState::new(&window_from_node(self), self.upcast())) + } + + fn is_instance_validatable(&self) -> bool { + // https://html.spec.whatwg.org/multipage/#enabling-and-disabling-form-controls%3A-the-disabled-attribute%3Abarred-from-constraint-validation + // https://html.spec.whatwg.org/multipage/#the-textarea-element%3Abarred-from-constraint-validation + // https://html.spec.whatwg.org/multipage/#the-datalist-element%3Abarred-from-constraint-validation + !self.upcast::<Element>().disabled_state() && + !self.ReadOnly() && + !is_barred_by_datalist_ancestor(self.upcast()) + } -impl Validatable for HTMLTextAreaElement {} + fn perform_validation(&self, validate_flags: ValidationFlags) -> ValidationFlags { + let mut failed_flags = ValidationFlags::empty(); + + let textinput = self.textinput.borrow(); + let UTF16CodeUnits(value_len) = textinput.utf16_len(); + let last_edit_by_user = !textinput.was_last_change_by_set_content(); + let value_dirty = self.value_dirty.get(); + + // https://html.spec.whatwg.org/multipage/#suffering-from-being-missing + // https://html.spec.whatwg.org/multipage/#the-textarea-element%3Asuffering-from-being-missing + if validate_flags.contains(ValidationFlags::VALUE_MISSING) { + if self.Required() && self.is_mutable() && value_len == 0 { + failed_flags.insert(ValidationFlags::VALUE_MISSING); + } + } + + if value_dirty && last_edit_by_user && value_len > 0 { + // https://html.spec.whatwg.org/multipage/#suffering-from-being-too-long + // https://html.spec.whatwg.org/multipage/#limiting-user-input-length%3A-the-maxlength-attribute%3Asuffering-from-being-too-long + if validate_flags.contains(ValidationFlags::TOO_LONG) { + let max_length = self.MaxLength(); + if max_length != DEFAULT_MAX_LENGTH && value_len > (max_length as usize) { + failed_flags.insert(ValidationFlags::TOO_LONG); + } + } + + // https://html.spec.whatwg.org/multipage/#suffering-from-being-too-short + // https://html.spec.whatwg.org/multipage/#setting-minimum-input-length-requirements%3A-the-minlength-attribute%3Asuffering-from-being-too-short + if validate_flags.contains(ValidationFlags::TOO_SHORT) { + let min_length = self.MinLength(); + if min_length != DEFAULT_MIN_LENGTH && value_len < (min_length as usize) { + failed_flags.insert(ValidationFlags::TOO_SHORT); + } + } + } + + failed_flags + } +} |