diff --git a/components/layout/construct.rs b/components/layout/construct.rs index 95c2919b764..d4d52c2ebb4 100644 --- a/components/layout/construct.rs +++ b/components/layout/construct.rs @@ -49,6 +49,7 @@ use script::dom::element::{HTMLObjectElementTypeId, HTMLInputElementTypeId}; use script::dom::element::{HTMLTableColElementTypeId, HTMLTableDataCellElementTypeId}; use script::dom::element::{HTMLTableElementTypeId, HTMLTableHeaderCellElementTypeId}; use script::dom::element::{HTMLTableRowElementTypeId, HTMLTableSectionElementTypeId}; +use script::dom::element::HTMLTextAreaElementTypeId; use script::dom::node::{CommentNodeTypeId, DoctypeNodeTypeId, DocumentFragmentNodeTypeId}; use script::dom::node::{DocumentNodeTypeId, ElementNodeTypeId, ProcessingInstructionNodeTypeId}; use script::dom::node::{TextNodeTypeId}; @@ -273,7 +274,8 @@ impl<'a> FlowConstructor<'a> { TableColumnFragment(TableColumnFragmentInfo::new(node)) } Some(ElementNodeTypeId(HTMLTableDataCellElementTypeId)) | - Some(ElementNodeTypeId(HTMLTableHeaderCellElementTypeId)) => TableCellFragment, + Some(ElementNodeTypeId(HTMLTableHeaderCellElementTypeId)) | + Some(ElementNodeTypeId(HTMLTextAreaElementTypeId)) => TableCellFragment, Some(ElementNodeTypeId(HTMLTableRowElementTypeId)) | Some(ElementNodeTypeId(HTMLTableSectionElementTypeId)) => TableRowFragment, Some(TextNodeTypeId) => UnscannedTextFragment(UnscannedTextFragmentInfo::new(node)), @@ -487,7 +489,16 @@ impl<'a> FlowConstructor<'a> { // Special case: If this is generated content, then we need to initialize the accumulator // with the fragment corresponding to that content. if node.get_pseudo_element_type() != Normal || - node.type_id() == Some(ElementNodeTypeId(HTMLInputElementTypeId)) { + node.type_id() == Some(ElementNodeTypeId(HTMLInputElementTypeId)) || + node.type_id() == Some(ElementNodeTypeId(HTMLTextAreaElementTypeId)) { + // A TextArea's text contents are displayed through the input text + // box, so don't construct them. + // TODO Maybe this belongs somewhere else? + if node.type_id() == Some(ElementNodeTypeId(HTMLTextAreaElementTypeId)) { + for kid in node.children() { + kid.set_flow_construction_result(NoConstructionResult) + } + } let fragment_info = UnscannedTextFragment(UnscannedTextFragmentInfo::new(node)); let fragment = Fragment::new_from_specific_info(node, fragment_info); inline_fragment_accumulator.fragments.push_back(fragment); diff --git a/components/layout/wrapper.rs b/components/layout/wrapper.rs index 11ef5b66822..595ae6a760e 100644 --- a/components/layout/wrapper.rs +++ b/components/layout/wrapper.rs @@ -39,13 +39,14 @@ use util::{PrivateLayoutData}; use gfx::display_list::OpaqueNode; use script::dom::bindings::codegen::InheritTypes::{ElementCast, HTMLIFrameElementCast}; use script::dom::bindings::codegen::InheritTypes::{HTMLImageElementCast, HTMLInputElementCast}; -use script::dom::bindings::codegen::InheritTypes::{NodeCast, TextCast}; +use script::dom::bindings::codegen::InheritTypes::{HTMLTextAreaElementCast, NodeCast, TextCast}; use script::dom::bindings::js::JS; use script::dom::element::{Element, HTMLAreaElementTypeId, HTMLAnchorElementTypeId}; use script::dom::element::{HTMLLinkElementTypeId, LayoutElementHelpers, RawLayoutElementHelpers}; use script::dom::htmliframeelement::HTMLIFrameElement; use script::dom::htmlimageelement::LayoutHTMLImageElementHelpers; use script::dom::htmlinputelement::LayoutHTMLInputElementHelpers; +use script::dom::htmltextareaelement::LayoutHTMLTextAreaElementHelpers; use script::dom::node::{DocumentNodeTypeId, ElementNodeTypeId, Node, NodeTypeId}; use script::dom::node::{LayoutNodeHelpers, RawLayoutNodeHelpers, SharedLayoutData}; use script::dom::node::{HAS_CHANGED, IS_DIRTY, HAS_DIRTY_SIBLINGS, HAS_DIRTY_DESCENDANTS}; @@ -188,7 +189,10 @@ impl<'ln> TLayoutNode for LayoutNode<'ln> { Some(text) => (*text.unsafe_get()).characterdata().data_for_layout().to_string(), None => match HTMLInputElementCast::to_js(self.get_jsmanaged()) { Some(input) => input.get_value_for_layout(), - None => panic!("not text!") + None => match HTMLTextAreaElementCast::to_js(self.get_jsmanaged()) { + Some(area) => area.get_value_for_layout(), + None => panic!("not text!") + } } } } diff --git a/components/script/dom/htmltextareaelement.rs b/components/script/dom/htmltextareaelement.rs index 8888178df5f..e34ec36aeca 100644 --- a/components/script/dom/htmltextareaelement.rs +++ b/components/script/dom/htmltextareaelement.rs @@ -4,18 +4,24 @@ use dom::attr::Attr; use dom::attr::AttrHelpers; +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::InheritTypes::{HTMLElementCast, NodeCast}; use dom::bindings::codegen::Bindings::NodeBinding::NodeMethods; +use dom::bindings::codegen::InheritTypes::{ElementCast, HTMLElementCast, NodeCast}; use dom::bindings::codegen::InheritTypes::{HTMLTextAreaElementDerived, HTMLFieldSetElementDerived}; -use dom::bindings::js::{JSRef, Temporary}; +use dom::bindings::codegen::InheritTypes::{KeyboardEventCast, TextDerived}; +use dom::bindings::js::{JS, JSRef, Temporary}; use dom::bindings::utils::{Reflectable, Reflector}; -use dom::document::Document; +use dom::document::{Document, DocumentHelpers}; use dom::element::{AttributeHandlers, HTMLTextAreaElementTypeId}; +use dom::event::Event; use dom::eventtarget::{EventTarget, NodeTargetTypeId}; use dom::htmlelement::HTMLElement; -use dom::node::{DisabledStateHelpers, Node, NodeHelpers, ElementNodeTypeId}; +use dom::keyboardevent::KeyboardEvent; +use dom::node::{DisabledStateHelpers, Node, NodeHelpers, ElementNodeTypeId, document_from_node}; +use textinput::{Multiple, TextInput, TriggerDefaultAction, DispatchInput, Nothing}; use dom::virtualmethods::VirtualMethods; use servo_util::str::DOMString; @@ -24,6 +30,7 @@ use string_cache::Atom; #[dom_struct] pub struct HTMLTextAreaElement { htmlelement: HTMLElement, + textinput: DOMRefCell, } impl HTMLTextAreaElementDerived for EventTarget { @@ -32,10 +39,22 @@ impl HTMLTextAreaElementDerived for EventTarget { } } +pub trait LayoutHTMLTextAreaElementHelpers { + unsafe fn get_value_for_layout(self) -> String; +} + +impl LayoutHTMLTextAreaElementHelpers for JS { + #[allow(unrooted_must_root)] + unsafe fn get_value_for_layout(self) -> String { + (*self.unsafe_get()).textinput.borrow_for_layout().get_content() + } +} + impl HTMLTextAreaElement { fn new_inherited(localName: DOMString, prefix: Option, document: JSRef) -> HTMLTextAreaElement { HTMLTextAreaElement { - htmlelement: HTMLElement::new_inherited(HTMLTextAreaElementTypeId, localName, prefix, document) + htmlelement: HTMLElement::new_inherited(HTMLTextAreaElementTypeId, localName, prefix, document), + textinput: DOMRefCell::new(TextInput::new(Multiple, "".to_string())), } } @@ -108,6 +127,28 @@ impl<'a> HTMLTextAreaElementMethods for JSRef<'a, HTMLTextAreaElement> { let node: JSRef = NodeCast::from_ref(self); node.SetTextContent(Some(value)) } + + // https://html.spec.whatwg.org/multipage/forms.html#dom-textarea-value + fn Value(self) -> DOMString { + self.textinput.borrow().get_content() + } + + // https://html.spec.whatwg.org/multipage/forms.html#dom-textarea-value + fn SetValue(self, value: DOMString) { + self.textinput.borrow_mut().set_content(value); + } +} + +pub trait HTMLTextAreaElementHelpers { + fn force_relayout(self); +} + +impl<'a> HTMLTextAreaElementHelpers for JSRef<'a, HTMLTextAreaElement> { + fn force_relayout(self) { + let doc = document_from_node(self).root(); + let node: JSRef = NodeCast::from_ref(self); + doc.content_changed(node) + } } impl<'a> VirtualMethods for JSRef<'a, HTMLTextAreaElement> { @@ -172,6 +213,47 @@ impl<'a> VirtualMethods for JSRef<'a, HTMLTextAreaElement> { node.check_disabled_attribute(); } } + + fn child_inserted(&self, child: JSRef) { + match self.super_type() { + Some(s) => { + s.child_inserted(child); + } + _ => (), + } + + if child.is_text() { + self.SetValue(child.GetTextContent().unwrap()); + } + } + + // copied and modified from htmlinputelement.rs + fn handle_event(&self, event: JSRef) { + match self.super_type() { + Some(s) => { + s.handle_event(event); + } + _ => (), + } + + if "click" == event.Type().as_slice() && !event.DefaultPrevented() { + //TODO: set the editing position for text inputs + + let doc = document_from_node(*self).root(); + doc.request_focus(ElementCast::from_ref(*self)); + } else if "keydown" == event.Type().as_slice() && !event.DefaultPrevented() { + let keyevent: Option> = KeyboardEventCast::to_ref(event); + keyevent.map(|event| { + match self.textinput.borrow_mut().handle_keydown(event) { + TriggerDefaultAction => (), + DispatchInput => { + self.force_relayout(); + } + Nothing => (), + } + }); + } + } } impl Reflectable for HTMLTextAreaElement { diff --git a/components/script/dom/webidls/HTMLTextAreaElement.webidl b/components/script/dom/webidls/HTMLTextAreaElement.webidl index 124de2015f1..59d10658b14 100644 --- a/components/script/dom/webidls/HTMLTextAreaElement.webidl +++ b/components/script/dom/webidls/HTMLTextAreaElement.webidl @@ -23,7 +23,7 @@ interface HTMLTextAreaElement : HTMLElement { readonly attribute DOMString type; attribute DOMString defaultValue; - //[TreatNullAs=EmptyString] attribute DOMString value; + [TreatNullAs=EmptyString] attribute DOMString value; //readonly attribute unsigned long textLength; //readonly attribute boolean willValidate; diff --git a/resources/servo.css b/resources/servo.css index cf81713a015..1fa54c8b816 100644 --- a/resources/servo.css +++ b/resources/servo.css @@ -1,5 +1,6 @@ -input, select { display: inline-block; } +input, textarea, select { display: inline-block; } input { background: white; min-height: 1.0em; padding: 0em; padding-left: 0.25em; padding-right: 0.25em; border: solid lightgrey 1px; color: black; white-space: nowrap; } +textarea { background: white; min-height: 1.0em; padding: 0em; padding-left: 0.25em; padding-right: 0.25em; border: solid lightgrey 1px; color: black; white-space: pre; } input[type="button"], input[type="submit"], input[type="reset"] { background: lightgrey; border-top: solid 1px #EEEEEE; border-left: solid 1px #CCCCCC; border-right: solid 1px #999999; border-bottom: solid 1px #999999; text-align: center; vertical-align: middle; color: black; width: 100%; } diff --git a/tests/html/textarea.html b/tests/html/textarea.html new file mode 100644 index 00000000000..b499465dff6 --- /dev/null +++ b/tests/html/textarea.html @@ -0,0 +1,6 @@ + + + + +