From f686943eb40aa25e0a06837eeae6a907d9617d2b Mon Sep 17 00:00:00 2001 From: Matthew Rasmus Date: Thu, 27 Nov 2014 11:42:40 -0800 Subject: [PATCH 01/12] Fix crash in textinput --- components/script/textinput.rs | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/components/script/textinput.rs b/components/script/textinput.rs index d39d22918c0..ed120aae943 100644 --- a/components/script/textinput.rs +++ b/components/script/textinput.rs @@ -295,6 +295,12 @@ impl TextInput { vec!(content) }; self.edit_point.line = min(self.edit_point.line, self.lines.len() - 1); - self.edit_point.index = min(self.edit_point.index, self.current_line_length() - 1); + + if self.current_line_length() == 0 { + self.edit_point.index = 0; + } + else { + self.edit_point.index = min(self.edit_point.index, self.current_line_length() - 1); + } } } From f932a6947a0c5df0ab82bbeebf0a289ab2fc1ae8 Mon Sep 17 00:00:00 2001 From: Matthew Rasmus Date: Thu, 27 Nov 2014 15:22:02 -0800 Subject: [PATCH 02/12] Implements HTMLInputElement.defaultValue ...and changes SetValue to update the input text instead of the content attr. Also includes a comment summarizing everything I currently know with respect to an input elements checkedness vs its IDL attributes vs its content attributes. --- components/script/dom/htmlinputelement.rs | 11 ++++++++++- components/script/dom/webidls/HTMLInputElement.webidl | 2 +- 2 files changed, 11 insertions(+), 2 deletions(-) diff --git a/components/script/dom/htmlinputelement.rs b/components/script/dom/htmlinputelement.rs index 5b0133395a7..12d6e11a639 100644 --- a/components/script/dom/htmlinputelement.rs +++ b/components/script/dom/htmlinputelement.rs @@ -223,7 +223,16 @@ impl<'a> HTMLInputElementMethods for JSRef<'a, HTMLInputElement> { } // https://html.spec.whatwg.org/multipage/forms.html#dom-input-value - make_setter!(SetValue, "value") + fn SetValue(self, value: DOMString) { + self.textinput.borrow_mut().set_content(value); + self.force_relayout(); + } + + // https://html.spec.whatwg.org/multipage/forms.html#dom-input-defaultvalue + make_getter!(DefaultValue, "value") + + // https://html.spec.whatwg.org/multipage/forms.html#dom-input-defaultvalue + make_setter!(SetDefaultValue, "value") // https://html.spec.whatwg.org/multipage/forms.html#attr-fe-name make_getter!(Name) diff --git a/components/script/dom/webidls/HTMLInputElement.webidl b/components/script/dom/webidls/HTMLInputElement.webidl index 434da309f21..532ac73e25a 100644 --- a/components/script/dom/webidls/HTMLInputElement.webidl +++ b/components/script/dom/webidls/HTMLInputElement.webidl @@ -38,7 +38,7 @@ interface HTMLInputElement : HTMLElement { // attribute DOMString src; // attribute DOMString step; attribute DOMString type; - // attribute DOMString defaultValue; + attribute DOMString defaultValue; [TreatNullAs=EmptyString] attribute DOMString value; // attribute Date? valueAsDate; // attribute unrestricted double valueAsNumber; From f0ce2af89ce264ddb707c67822ae1f4e4fb622e8 Mon Sep 17 00:00:00 2001 From: Matthew Rasmus Date: Thu, 27 Nov 2014 15:48:01 -0800 Subject: [PATCH 03/12] Implements basic form resetting What can this do? Reset `` fields back to their default value through a call to a form's reset method. That's all for now! Fixes compile error after rebase --- components/script/dom/htmlformelement.rs | 72 +++++++++++++++++++ .../script/dom/webidls/HTMLFormElement.webidl | 2 +- tests/html/form_reset_handsfree.html | 23 ++++++ 3 files changed, 96 insertions(+), 1 deletion(-) create mode 100644 tests/html/form_reset_handsfree.html diff --git a/components/script/dom/htmlformelement.rs b/components/script/dom/htmlformelement.rs index 1d12bdebf39..a08b8eec270 100644 --- a/components/script/dom/htmlformelement.rs +++ b/components/script/dom/htmlformelement.rs @@ -15,6 +15,7 @@ use dom::bindings::utils::{Reflectable, Reflector}; use dom::document::{Document, DocumentHelpers}; use dom::element::{Element, AttributeHandlers, HTMLFormElementTypeId, HTMLTextAreaElementTypeId, HTMLDataListElementTypeId}; use dom::element::{HTMLInputElementTypeId, HTMLButtonElementTypeId, HTMLObjectElementTypeId, HTMLSelectElementTypeId}; +use dom::element::{HTMLOutputElementTypeId}; use dom::event::{Event, EventHelpers, Bubbles, Cancelable}; use dom::eventtarget::{EventTarget, NodeTargetTypeId}; use dom::htmlelement::HTMLElement; @@ -117,6 +118,11 @@ impl<'a> HTMLFormElementMethods for JSRef<'a, HTMLFormElement> { fn Submit(self) { self.submit(FromFormSubmitMethod, FormElement(self)); } + + // https://html.spec.whatwg.org/multipage/forms.html#dom-form-reset + fn Reset(self) { + self.reset(FromFormResetMethod); + } } pub enum SubmittedFrom { @@ -124,11 +130,18 @@ pub enum SubmittedFrom { NotFromFormSubmitMethod } +pub enum ResetFrom { + FromFormResetMethod, + NotFromFormResetMethod +} + pub trait HTMLFormElementHelpers { // https://html.spec.whatwg.org/multipage/forms.html#concept-form-submit fn submit(self, submit_method_flag: SubmittedFrom, submitter: FormSubmitter); // https://html.spec.whatwg.org/multipage/forms.html#constructing-the-form-data-set fn get_form_dataset(self, submitter: Option) -> Vec; + // https://html.spec.whatwg.org/multipage/forms.html#dom-form-reset + fn reset(self, submit_method_flag: ResetFrom); } impl<'a> HTMLFormElementHelpers for JSRef<'a, HTMLFormElement> { @@ -316,6 +329,65 @@ impl<'a> HTMLFormElementHelpers for JSRef<'a, HTMLFormElement> { }; ret } + + fn reset(self, _reset_method_flag: ResetFrom) { + let win = window_from_node(self).root(); + let event = Event::new(Window(*win), + "reset".to_string(), + Bubbles, Cancelable).root(); + let target: JSRef = EventTargetCast::from_ref(self); + target.DispatchEvent(*event).ok(); + if event.DefaultPrevented() { + return; + } + + let node: JSRef = NodeCast::from_ref(self); + + // TODO: This is an incorrect way of getting controls owned + // by the form, but good enough until html5ever lands + for child in node.traverse_preorder() { + // TODO This is the wrong place to do this. Each resettable + // element should implement its own reset method (trait?) + // + // List of resettable elements: + // https://html.spec.whatwg.org/multipage/forms.html#category-reset + match child.type_id() { + ElementNodeTypeId(HTMLInputElementTypeId) => { + let input: JSRef = HTMLInputElementCast::to_ref(child) + .unwrap(); + let ty = input.Type(); + + match ty.as_slice() { + "radio" | "checkbox" => { + // TODO Reset radios/checkboxes here + }, + "image" => (), + _ => () + } + + input.SetValue(input.DefaultValue()); + } + // TODO HTMLKeygenElement unimplemented + /*ElementNodeTypeID(HTMLKeygenElementTypeId) => { + // Unimplemented + {} + }*/ + ElementNodeTypeId(HTMLSelectElementTypeId) => { + // Unimplemented + {} + } + ElementNodeTypeId(HTMLTextAreaElementTypeId) => { + // Unimplemented + {} + } + ElementNodeTypeId(HTMLOutputElementTypeId) => { + // Unimplemented + {} + } + _ => {} + } + }; + } } impl Reflectable for HTMLFormElement { diff --git a/components/script/dom/webidls/HTMLFormElement.webidl b/components/script/dom/webidls/HTMLFormElement.webidl index 1971680b5be..ce64cac7ec6 100644 --- a/components/script/dom/webidls/HTMLFormElement.webidl +++ b/components/script/dom/webidls/HTMLFormElement.webidl @@ -22,7 +22,7 @@ interface HTMLFormElement : HTMLElement { //getter (RadioNodeList or Element) (DOMString name); void submit(); - //void reset(); + void reset(); //boolean checkValidity(); //boolean reportValidity(); diff --git a/tests/html/form_reset_handsfree.html b/tests/html/form_reset_handsfree.html new file mode 100644 index 00000000000..4ba72451f09 --- /dev/null +++ b/tests/html/form_reset_handsfree.html @@ -0,0 +1,23 @@ + + + + +
+ + + + + + + +
+ + From a3b3295d80194becf83f153e480df7f19c3e2971 Mon Sep 17 00:00:00 2001 From: Matthew Rasmus Date: Sun, 7 Dec 2014 10:33:20 -0800 Subject: [PATCH 04/12] Adds reset method to FormControl --- components/script/dom/htmlformelement.rs | 26 ++++++----------------- components/script/dom/htmlinputelement.rs | 14 ++++++++++++ 2 files changed, 20 insertions(+), 20 deletions(-) diff --git a/components/script/dom/htmlformelement.rs b/components/script/dom/htmlformelement.rs index a08b8eec270..8f5b0180c02 100644 --- a/components/script/dom/htmlformelement.rs +++ b/components/script/dom/htmlformelement.rs @@ -346,32 +346,17 @@ impl<'a> HTMLFormElementHelpers for JSRef<'a, HTMLFormElement> { // TODO: This is an incorrect way of getting controls owned // by the form, but good enough until html5ever lands for child in node.traverse_preorder() { - // TODO This is the wrong place to do this. Each resettable - // element should implement its own reset method (trait?) - // - // List of resettable elements: - // https://html.spec.whatwg.org/multipage/forms.html#category-reset match child.type_id() { ElementNodeTypeId(HTMLInputElementTypeId) => { let input: JSRef = HTMLInputElementCast::to_ref(child) .unwrap(); - let ty = input.Type(); - - match ty.as_slice() { - "radio" | "checkbox" => { - // TODO Reset radios/checkboxes here - }, - "image" => (), - _ => () - } - - input.SetValue(input.DefaultValue()); + input.reset() } // TODO HTMLKeygenElement unimplemented - /*ElementNodeTypeID(HTMLKeygenElementTypeId) => { - // Unimplemented - {} - }*/ + //ElementNodeTypeID(HTMLKeygenElementTypeId) => { + // // Unimplemented + // {} + //} ElementNodeTypeId(HTMLSelectElementTypeId) => { // Unimplemented {} @@ -498,4 +483,5 @@ pub trait FormControl<'a> : Copy { fn to_element(self) -> JSRef<'a, Element>; // https://html.spec.whatwg.org/multipage/forms.html#concept-fe-mutable fn mutable(self) -> bool; + fn reset(self); } diff --git a/components/script/dom/htmlinputelement.rs b/components/script/dom/htmlinputelement.rs index 12d6e11a639..51280d27759 100644 --- a/components/script/dom/htmlinputelement.rs +++ b/components/script/dom/htmlinputelement.rs @@ -571,6 +571,20 @@ impl<'a> FormControl<'a> for JSRef<'a, HTMLInputElement> { // https://html.spec.whatwg.org/multipage/forms.html#the-readonly-attribute:concept-fe-mutable !(self.Disabled() || self.ReadOnly()) } + + fn reset(self) { + let ty = self.Type(); + + match ty.as_slice() { + "radio" | "checkbox" => { + // TODO Reset radios/checkboxes here + }, + "image" => (), + _ => () + } + + self.SetValue(self.DefaultValue()); + } } From 7e0c39a82d5ca9d766c0065f4cb6ba79b1cbe037 Mon Sep 17 00:00:00 2001 From: Matthew Rasmus Date: Sun, 7 Dec 2014 11:04:39 -0800 Subject: [PATCH 05/12] Implements FormControl for HTMLTextAreaElement --- components/script/dom/htmlformelement.rs | 8 ++- components/script/dom/htmltextareaelement.rs | 57 +++++++++++++++++-- .../dom/webidls/HTMLTextAreaElement.webidl | 2 +- tests/html/form_reset_handsfree.html | 3 + 4 files changed, 62 insertions(+), 8 deletions(-) diff --git a/components/script/dom/htmlformelement.rs b/components/script/dom/htmlformelement.rs index 8f5b0180c02..62b08649f7f 100644 --- a/components/script/dom/htmlformelement.rs +++ b/components/script/dom/htmlformelement.rs @@ -8,7 +8,7 @@ use dom::bindings::codegen::Bindings::HTMLFormElementBinding; use dom::bindings::codegen::Bindings::HTMLFormElementBinding::HTMLFormElementMethods; use dom::bindings::codegen::Bindings::HTMLInputElementBinding::HTMLInputElementMethods; use dom::bindings::codegen::InheritTypes::{EventTargetCast, HTMLFormElementDerived, NodeCast}; -use dom::bindings::codegen::InheritTypes::HTMLInputElementCast; +use dom::bindings::codegen::InheritTypes::{HTMLInputElementCast, HTMLTextAreaElementCast}; use dom::bindings::global::Window; use dom::bindings::js::{JSRef, Temporary}; use dom::bindings::utils::{Reflectable, Reflector}; @@ -20,6 +20,7 @@ use dom::event::{Event, EventHelpers, Bubbles, Cancelable}; use dom::eventtarget::{EventTarget, NodeTargetTypeId}; use dom::htmlelement::HTMLElement; use dom::htmlinputelement::HTMLInputElement; +use dom::htmltextareaelement::HTMLTextAreaElement; use dom::node::{Node, NodeHelpers, ElementNodeTypeId, document_from_node, window_from_node}; use hyper::method::Post; use servo_msg::constellation_msg::LoadData; @@ -362,8 +363,9 @@ impl<'a> HTMLFormElementHelpers for JSRef<'a, HTMLFormElement> { {} } ElementNodeTypeId(HTMLTextAreaElementTypeId) => { - // Unimplemented - {} + let textarea: JSRef = HTMLTextAreaElementCast::to_ref(child) + .unwrap(); + textarea.reset() } ElementNodeTypeId(HTMLOutputElementTypeId) => { // Unimplemented diff --git a/components/script/dom/htmltextareaelement.rs b/components/script/dom/htmltextareaelement.rs index 05d3a5d0868..a22279a3deb 100644 --- a/components/script/dom/htmltextareaelement.rs +++ b/components/script/dom/htmltextareaelement.rs @@ -5,20 +5,22 @@ use dom::attr::{Attr, AttrValue}; use dom::attr::AttrHelpers; use dom::bindings::cell::DOMRefCell; +use dom::bindings::codegen::Bindings::DocumentBinding::DocumentMethods; 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::codegen::InheritTypes::{ElementCast, HTMLElementCast, NodeCast}; use dom::bindings::codegen::InheritTypes::{HTMLTextAreaElementDerived, HTMLFieldSetElementDerived}; -use dom::bindings::codegen::InheritTypes::{KeyboardEventCast, TextDerived}; -use dom::bindings::js::{JS, JSRef, Temporary}; +use dom::bindings::codegen::InheritTypes::{KeyboardEventCast, TextDerived, HTMLFormElementCast}; +use dom::bindings::js::{JS, JSRef, Temporary, OptionalRootable}; use dom::bindings::utils::{Reflectable, Reflector}; use dom::document::{Document, DocumentHelpers}; -use dom::element::{AttributeHandlers, HTMLTextAreaElementTypeId}; +use dom::element::{AttributeHandlers, HTMLTextAreaElementTypeId, Element}; use dom::event::Event; use dom::eventtarget::{EventTarget, NodeTargetTypeId}; use dom::htmlelement::HTMLElement; +use dom::htmlformelement::{FormControl, HTMLFormElement}; use dom::keyboardevent::KeyboardEvent; use dom::node::{DisabledStateHelpers, Node, NodeHelpers, OtherNodeDamage, ElementNodeTypeId}; use dom::node::{document_from_node}; @@ -103,6 +105,12 @@ impl<'a> HTMLTextAreaElementMethods for JSRef<'a, HTMLTextAreaElement> { // https://html.spec.whatwg.org/multipage/forms.html#dom-textarea-placeholder make_setter!(SetPlaceholder, "placeholder") + // https://html.spec.whatwg.org/multipage/forms.html#attr-textarea-readonly + make_bool_getter!(ReadOnly) + + // https://html.spec.whatwg.org/multipage/forms.html#attr-textarea-readonly + make_bool_setter!(SetReadOnly, "readOnly") + // https://html.spec.whatwg.org/multipage/forms.html#dom-textarea-required make_bool_getter!(Required) @@ -248,7 +256,7 @@ impl<'a> VirtualMethods for JSRef<'a, HTMLTextAreaElement> { } if child.is_text() { - self.SetValue(self.DefaultValue()); + self.reset(); } } @@ -287,3 +295,44 @@ impl Reflectable for HTMLTextAreaElement { self.htmlelement.reflector() } } + +impl<'a> FormControl<'a> for JSRef<'a, HTMLTextAreaElement> { + // FIXME: This is wrong (https://github.com/servo/servo/issues/3553) + // but we need html5ever to do it correctly + fn form_owner(self) -> Option> { + // https://html.spec.whatwg.org/multipage/forms.html#reset-the-form-owner + let elem: JSRef = ElementCast::from_ref(self); + let owner = elem.get_string_attribute(&atom!("form")); + if !owner.is_empty() { + let doc = document_from_node(self).root(); + let owner = doc.GetElementById(owner).root(); + match owner { + Some(o) => { + let maybe_form: Option> = HTMLFormElementCast::to_ref(*o); + if maybe_form.is_some() { + return maybe_form.map(Temporary::from_rooted); + } + }, + _ => () + } + } + let node: JSRef = NodeCast::from_ref(self); + node.ancestors().filter_map(|a| HTMLFormElementCast::to_ref(a)).next() + .map(Temporary::from_rooted) + } + + fn to_element(self) -> JSRef<'a, Element> { + ElementCast::from_ref(self) + } + + // https://html.spec.whatwg.org/multipage/forms.html#concept-fe-mutable + fn mutable(self) -> bool { + // https://html.spec.whatwg.org/multipage/forms.html#the-textarea-element:concept-fe-mutable + !(self.Disabled() || self.ReadOnly()) + } + + fn reset(self) { + self.SetValue(self.DefaultValue()); + self.value_changed.set(false); + } +} diff --git a/components/script/dom/webidls/HTMLTextAreaElement.webidl b/components/script/dom/webidls/HTMLTextAreaElement.webidl index 59d10658b14..4bc4510d2a2 100644 --- a/components/script/dom/webidls/HTMLTextAreaElement.webidl +++ b/components/script/dom/webidls/HTMLTextAreaElement.webidl @@ -16,7 +16,7 @@ interface HTMLTextAreaElement : HTMLElement { // attribute long minLength; attribute DOMString name; attribute DOMString placeholder; - // attribute boolean readOnly; + attribute boolean readOnly; attribute boolean required; attribute unsigned long rows; attribute DOMString wrap; diff --git a/tests/html/form_reset_handsfree.html b/tests/html/form_reset_handsfree.html index 4ba72451f09..550a8efa9de 100644 --- a/tests/html/form_reset_handsfree.html +++ b/tests/html/form_reset_handsfree.html @@ -9,12 +9,15 @@ + From 4d0a6a6bd6f2aed259988969fab1f14c880c2257 Mon Sep 17 00:00:00 2001 From: Matthew Rasmus Date: Sun, 7 Dec 2014 11:18:53 -0800 Subject: [PATCH 06/12] Implements reset button --- components/script/dom/htmlinputelement.rs | 11 ++++++++++- tests/html/form_reset.html | 17 +++++++++++++++++ 2 files changed, 27 insertions(+), 1 deletion(-) create mode 100644 tests/html/form_reset.html diff --git a/components/script/dom/htmlinputelement.rs b/components/script/dom/htmlinputelement.rs index 51280d27759..7057ec91323 100644 --- a/components/script/dom/htmlinputelement.rs +++ b/components/script/dom/htmlinputelement.rs @@ -27,7 +27,7 @@ use dom::eventtarget::{EventTarget, NodeTargetTypeId}; use dom::htmlelement::HTMLElement; use dom::keyboardevent::KeyboardEvent; use dom::htmlformelement::{InputElement, FormControl, HTMLFormElement, HTMLFormElementHelpers}; -use dom::htmlformelement::{NotFromFormSubmitMethod}; +use dom::htmlformelement::{NotFromFormSubmitMethod, NotFromFormResetMethod}; use dom::node::{DisabledStateHelpers, Node, NodeHelpers, ElementNodeTypeId, OtherNodeDamage}; use dom::node::{document_from_node, window_from_node}; use dom::virtualmethods::VirtualMethods; @@ -701,6 +701,15 @@ impl<'a> Activatable for JSRef<'a, HTMLInputElement> { }); } }, + InputReset => { + // https://html.spec.whatwg.org/multipage/forms.html#reset-button-state-(type=reset):activation-behavior + // FIXME (Manishearth): support document owners (needs ability to get parent browsing context) + if self.mutable() /* and document owner is fully active */ { + self.form_owner().map(|o| { + o.root().reset(NotFromFormResetMethod) + }); + } + }, InputCheckbox | InputRadio => { // https://html.spec.whatwg.org/multipage/forms.html#checkbox-state-(type=checkbox):activation-behavior // https://html.spec.whatwg.org/multipage/forms.html#radio-button-state-(type=radio):activation-behavior diff --git a/tests/html/form_reset.html b/tests/html/form_reset.html new file mode 100644 index 00000000000..99f3f194c07 --- /dev/null +++ b/tests/html/form_reset.html @@ -0,0 +1,17 @@ + + + + +
+ + + + + + + + +
+ + From a5c0bb708d03b1e878f419617fb016853d2200bb Mon Sep 17 00:00:00 2001 From: Matthew Rasmus Date: Sun, 7 Dec 2014 12:55:43 -0800 Subject: [PATCH 07/12] htmltextarea: Fixed some value_changed issues Also modified tests/html/textarea.html to allow for the testing of the textarea's dirty value flag. --- components/script/dom/htmltextareaelement.rs | 6 ++++-- tests/html/textarea.html | 21 +++++++++++++++++++- 2 files changed, 24 insertions(+), 3 deletions(-) diff --git a/components/script/dom/htmltextareaelement.rs b/components/script/dom/htmltextareaelement.rs index a22279a3deb..431be82e9db 100644 --- a/components/script/dom/htmltextareaelement.rs +++ b/components/script/dom/htmltextareaelement.rs @@ -148,7 +148,7 @@ impl<'a> HTMLTextAreaElementMethods for JSRef<'a, 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() { - self.SetValue(node.GetTextContent().unwrap()); + self.reset(); } } @@ -159,7 +159,9 @@ impl<'a> HTMLTextAreaElementMethods for JSRef<'a, HTMLTextAreaElement> { // https://html.spec.whatwg.org/multipage/forms.html#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); self.force_relayout(); } } @@ -255,7 +257,7 @@ impl<'a> VirtualMethods for JSRef<'a, HTMLTextAreaElement> { _ => (), } - if child.is_text() { + if child.is_text() && !self.value_changed.get() { self.reset(); } } diff --git a/tests/html/textarea.html b/tests/html/textarea.html index b499465dff6..21705f1daab 100644 --- a/tests/html/textarea.html +++ b/tests/html/textarea.html @@ -1,6 +1,25 @@ - + + + + From 38e4d86b148c198771f3917c0e0d6f2a639a0ca1 Mon Sep 17 00:00:00 2001 From: Matthew Rasmus Date: Mon, 8 Dec 2014 09:19:47 -0800 Subject: [PATCH 08/12] Implements DefaultChecked and resets of checkboxes --- components/script/dom/htmlinputelement.rs | 16 ++++++++++------ .../script/dom/webidls/HTMLInputElement.webidl | 2 +- 2 files changed, 11 insertions(+), 7 deletions(-) diff --git a/components/script/dom/htmlinputelement.rs b/components/script/dom/htmlinputelement.rs index 7057ec91323..2d9eeef5f8f 100644 --- a/components/script/dom/htmlinputelement.rs +++ b/components/script/dom/htmlinputelement.rs @@ -183,6 +183,12 @@ impl<'a> HTMLInputElementMethods for JSRef<'a, HTMLInputElement> { // http://www.whatwg.org/html/#dom-fe-disabled make_bool_setter!(SetDisabled, "disabled") + // https://html.spec.whatwg.org/multipage/forms.html#dom-input-defaultchecked + make_bool_getter!(DefaultChecked, "checked") + + // https://html.spec.whatwg.org/multipage/forms.html#dom-input-defaultchecked + make_bool_setter!(SetDefaultChecked, "checked") + // https://html.spec.whatwg.org/multipage/forms.html#dom-input-checked fn Checked(self) -> bool { self.checked.get() @@ -573,13 +579,11 @@ impl<'a> FormControl<'a> for JSRef<'a, HTMLInputElement> { } fn reset(self) { - let ty = self.Type(); - - match ty.as_slice() { - "radio" | "checkbox" => { - // TODO Reset radios/checkboxes here + match self.input_type.get() { + InputRadio | InputCheckbox => { + self.SetChecked(self.DefaultChecked()); }, - "image" => (), + InputImage => (), _ => () } diff --git a/components/script/dom/webidls/HTMLInputElement.webidl b/components/script/dom/webidls/HTMLInputElement.webidl index 532ac73e25a..47180432736 100644 --- a/components/script/dom/webidls/HTMLInputElement.webidl +++ b/components/script/dom/webidls/HTMLInputElement.webidl @@ -9,7 +9,7 @@ interface HTMLInputElement : HTMLElement { // attribute DOMString alt; // attribute DOMString autocomplete; // attribute boolean autofocus; - // attribute boolean defaultChecked; + attribute boolean defaultChecked; attribute boolean checked; // attribute DOMString dirName; attribute boolean disabled; From 0c8e1aeda338c4fc6397e3e413fa8d93b7bad50c Mon Sep 17 00:00:00 2001 From: Matthew Rasmus Date: Mon, 8 Dec 2014 10:20:09 -0800 Subject: [PATCH 09/12] Implements dirty value/checked flags for input And modifies test-inputs.html to test. Fixes wpt breaking mistake --- components/script/dom/htmlinputelement.rs | 51 +++++++++++++++----- components/script/dom/htmltextareaelement.rs | 2 +- tests/html/test-inputs.html | 38 +++++++++++++-- 3 files changed, 76 insertions(+), 15 deletions(-) diff --git a/components/script/dom/htmlinputelement.rs b/components/script/dom/htmlinputelement.rs index 2d9eeef5f8f..66d806673d8 100644 --- a/components/script/dom/htmlinputelement.rs +++ b/components/script/dom/htmlinputelement.rs @@ -63,7 +63,9 @@ pub struct HTMLInputElement { htmlelement: HTMLElement, input_type: Cell, checked: Cell, + checked_changed: Cell, indeterminate: Cell, + value_changed: Cell, size: Cell, textinput: DOMRefCell, activation_state: DOMRefCell, @@ -74,6 +76,7 @@ pub struct HTMLInputElement { struct InputActivationState { indeterminate: bool, checked: bool, + checked_changed: bool, checked_radio: MutNullableJS, // In case mutability changed was_mutable: bool, @@ -86,6 +89,7 @@ impl InputActivationState { InputActivationState { indeterminate: false, checked: false, + checked_changed: false, checked_radio: Default::default(), was_mutable: false, old_type: InputText @@ -108,6 +112,8 @@ impl HTMLInputElement { input_type: Cell::new(InputText), checked: Cell::new(false), indeterminate: Cell::new(false), + checked_changed: Cell::new(false), + value_changed: Cell::new(false), size: Cell::new(DEFAULT_INPUT_SIZE), textinput: DOMRefCell::new(TextInput::new(Single, "".to_string())), activation_state: DOMRefCell::new(InputActivationState::new()) @@ -196,7 +202,7 @@ impl<'a> HTMLInputElementMethods for JSRef<'a, HTMLInputElement> { // https://html.spec.whatwg.org/multipage/forms.html#dom-input-checked fn SetChecked(self, checked: bool) { - self.update_checked_state(checked); + self.update_checked_state(checked, true); } // https://html.spec.whatwg.org/multipage/forms.html#dom-input-readonly @@ -231,6 +237,7 @@ impl<'a> HTMLInputElementMethods for JSRef<'a, HTMLInputElement> { // https://html.spec.whatwg.org/multipage/forms.html#dom-input-value fn SetValue(self, value: DOMString) { self.textinput.borrow_mut().set_content(value); + self.value_changed.set(true); self.force_relayout(); } @@ -286,7 +293,7 @@ pub trait HTMLInputElementHelpers { fn force_relayout(self); fn radio_group_updated(self, group: Option<&str>); fn get_radio_group_name(self) -> Option; - fn update_checked_state(self, checked: bool); + fn update_checked_state(self, checked: bool, dirty: bool); fn get_size(&self) -> u32; } @@ -346,8 +353,13 @@ impl<'a> HTMLInputElementHelpers for JSRef<'a, HTMLInputElement> { .map(|name| name.Value()) } - fn update_checked_state(self, checked: bool) { + fn update_checked_state(self, checked: bool, dirty: bool) { self.checked.set(checked); + + if dirty { + self.checked_changed.set(true); + } + if self.input_type.get() == InputRadio && checked { broadcast_radio_checked(self, self.get_radio_group_name() @@ -382,7 +394,10 @@ impl<'a> VirtualMethods for JSRef<'a, HTMLInputElement> { node.set_enabled_state(false); } &atom!("checked") => { - self.update_checked_state(true); + // https://html.spec.whatwg.org/multipage/forms.html#the-input-element:concept-input-checked-dirty + if !self.checked_changed.get() { + self.update_checked_state(true, false); + } } &atom!("size") => { match *attr.value() { @@ -411,8 +426,10 @@ impl<'a> VirtualMethods for JSRef<'a, HTMLInputElement> { self.force_relayout(); } &atom!("value") => { - self.textinput.borrow_mut().set_content(attr.value().as_slice().to_string()); - self.force_relayout(); + if !self.value_changed.get() { + self.textinput.borrow_mut().set_content(attr.value().as_slice().to_string()); + self.force_relayout(); + } } &atom!("name") => { if self.input_type.get() == InputRadio { @@ -438,7 +455,10 @@ impl<'a> VirtualMethods for JSRef<'a, HTMLInputElement> { node.check_ancestors_disabled_state_for_form_control(); } &atom!("checked") => { - self.update_checked_state(false); + // https://html.spec.whatwg.org/multipage/forms.html#the-input-element:concept-input-checked-dirty + if !self.checked_changed.get() { + self.update_checked_state(false, false); + } } &atom!("size") => { self.size.set(DEFAULT_INPUT_SIZE); @@ -455,8 +475,10 @@ impl<'a> VirtualMethods for JSRef<'a, HTMLInputElement> { self.force_relayout(); } &atom!("value") => { - self.textinput.borrow_mut().set_content("".to_string()); - self.force_relayout(); + if !self.value_changed.get() { + self.textinput.borrow_mut().set_content("".to_string()); + self.force_relayout(); + } } &atom!("name") => { if self.input_type.get() == InputRadio { @@ -508,7 +530,7 @@ impl<'a> VirtualMethods for JSRef<'a, HTMLInputElement> { if "click" == event.Type().as_slice() && !event.DefaultPrevented() { match self.input_type.get() { - InputRadio => self.SetChecked(true), + InputRadio => self.update_checked_state(true, true), _ => {} } @@ -526,6 +548,7 @@ impl<'a> VirtualMethods for JSRef<'a, HTMLInputElement> { match self.textinput.borrow_mut().handle_keydown(keyevent) { TriggerDefaultAction => (), DispatchInput => { + self.value_changed.set(true); self.force_relayout(); event.PreventDefault(); } @@ -581,13 +604,15 @@ impl<'a> FormControl<'a> for JSRef<'a, HTMLInputElement> { fn reset(self) { match self.input_type.get() { InputRadio | InputCheckbox => { - self.SetChecked(self.DefaultChecked()); + self.update_checked_state(self.DefaultChecked(), false); + self.checked_changed.set(false); }, InputImage => (), _ => () } self.SetValue(self.DefaultValue()); + self.value_changed.set(false); } } @@ -613,6 +638,7 @@ impl<'a> Activatable for JSRef<'a, HTMLInputElement> { // we may need to restore them later cache.indeterminate = self.Indeterminate(); cache.checked = self.Checked(); + cache.checked_changed = self.checked_changed.get(); self.SetIndeterminate(false); self.SetChecked(!cache.checked); }, @@ -633,6 +659,7 @@ impl<'a> Activatable for JSRef<'a, HTMLInputElement> { .find(|r| r.Checked()) }; cache.checked_radio.assign(checked_member); + cache.checked_changed = self.checked_changed.get(); self.SetChecked(true); } _ => () @@ -658,6 +685,7 @@ impl<'a> Activatable for JSRef<'a, HTMLInputElement> { if cache.was_mutable { self.SetIndeterminate(cache.indeterminate); self.SetChecked(cache.checked); + self.checked_changed.set(cache.checked_changed); } }, // https://html.spec.whatwg.org/multipage/forms.html#radio-button-state-(type=radio):canceled-activation-steps @@ -681,6 +709,7 @@ impl<'a> Activatable for JSRef<'a, HTMLInputElement> { }, None => self.SetChecked(false) }; + self.checked_changed.set(cache.checked_changed); } } _ => () diff --git a/components/script/dom/htmltextareaelement.rs b/components/script/dom/htmltextareaelement.rs index 431be82e9db..bfa382c6f5f 100644 --- a/components/script/dom/htmltextareaelement.rs +++ b/components/script/dom/htmltextareaelement.rs @@ -109,7 +109,7 @@ impl<'a> HTMLTextAreaElementMethods for JSRef<'a, HTMLTextAreaElement> { make_bool_getter!(ReadOnly) // https://html.spec.whatwg.org/multipage/forms.html#attr-textarea-readonly - make_bool_setter!(SetReadOnly, "readOnly") + make_bool_setter!(SetReadOnly, "readonly") // https://html.spec.whatwg.org/multipage/forms.html#dom-textarea-required make_bool_getter!(Required) diff --git a/tests/html/test-inputs.html b/tests/html/test-inputs.html index 0c94ed87d94..633bf3e23c4 100644 --- a/tests/html/test-inputs.html +++ b/tests/html/test-inputs.html @@ -1,12 +1,43 @@ +
-
+
+
+
+
+
+
+
-
-
+
group 1
@@ -15,3 +46,4 @@
+ From 2d05ac537cbd33c413da3516544023391d03ee82 Mon Sep 17 00:00:00 2001 From: Matthew Rasmus Date: Thu, 27 Nov 2014 16:25:50 -0800 Subject: [PATCH 10/12] Updated WPT to expect newly passing tests --- .../wpt/metadata/html/dom/interfaces.html.ini | 20 +- .../html/dom/reflection-forms.html.ini | 302 +++--------------- 2 files changed, 47 insertions(+), 275 deletions(-) diff --git a/tests/wpt/metadata/html/dom/interfaces.html.ini b/tests/wpt/metadata/html/dom/interfaces.html.ini index 7c5a523d188..e9dd1ed2a9d 100644 --- a/tests/wpt/metadata/html/dom/interfaces.html.ini +++ b/tests/wpt/metadata/html/dom/interfaces.html.ini @@ -4663,7 +4663,7 @@ expected: FAIL [HTMLFormElement interface: operation reset()] - expected: FAIL + expected: PASS [HTMLFormElement interface: operation checkValidity()] expected: FAIL @@ -4681,7 +4681,7 @@ expected: FAIL [HTMLFormElement interface: document.createElement("form") must inherit property "reset" with the proper type (14)] - expected: FAIL + expected: PASS [HTMLFormElement interface: document.createElement("form") must inherit property "checkValidity" with the proper type (15)] expected: FAIL @@ -4728,9 +4728,6 @@ [HTMLInputElement interface: attribute autofocus] expected: FAIL - [HTMLInputElement interface: attribute defaultChecked] - expected: FAIL - [HTMLInputElement interface: attribute dirName] expected: FAIL @@ -4783,7 +4780,7 @@ expected: FAIL [HTMLInputElement interface: attribute defaultValue] - expected: FAIL + expected: PASS [HTMLInputElement interface: attribute valueAsDate] expected: FAIL @@ -4866,9 +4863,6 @@ [HTMLInputElement interface: document.createElement("input") must inherit property "autofocus" with the proper type (3)] expected: FAIL - [HTMLInputElement interface: document.createElement("input") must inherit property "defaultChecked" with the proper type (4)] - expected: FAIL - [HTMLInputElement interface: document.createElement("input") must inherit property "dirName" with the proper type (6)] expected: FAIL @@ -4921,7 +4915,7 @@ expected: FAIL [HTMLInputElement interface: document.createElement("input") must inherit property "defaultValue" with the proper type (33)] - expected: FAIL + expected: PASS [HTMLInputElement interface: document.createElement("input") must inherit property "valueAsDate" with the proper type (35)] expected: FAIL @@ -5319,9 +5313,6 @@ [HTMLTextAreaElement interface: attribute minLength] expected: FAIL - [HTMLTextAreaElement interface: attribute readOnly] - expected: FAIL - [HTMLTextAreaElement interface: attribute textLength] expected: FAIL @@ -5388,9 +5379,6 @@ [HTMLTextAreaElement interface: document.createElement("textarea") must inherit property "minLength" with the proper type (8)] expected: FAIL - [HTMLTextAreaElement interface: document.createElement("textarea") must inherit property "readOnly" with the proper type (11)] - expected: FAIL - [HTMLTextAreaElement interface: document.createElement("textarea") must inherit property "textLength" with the proper type (18)] expected: FAIL diff --git a/tests/wpt/metadata/html/dom/reflection-forms.html.ini b/tests/wpt/metadata/html/dom/reflection-forms.html.ini index 55375dd30d2..b866226ffd9 100644 --- a/tests/wpt/metadata/html/dom/reflection-forms.html.ini +++ b/tests/wpt/metadata/html/dom/reflection-forms.html.ini @@ -4170,114 +4170,6 @@ [input.autofocus: IDL set to object "test-valueOf" followed by IDL get] expected: FAIL - [input.defaultChecked (): typeof IDL attribute] - expected: FAIL - - [input.defaultChecked (): IDL get with DOM attribute unset] - expected: FAIL - - [input.defaultChecked (): setAttribute() to "" followed by IDL get] - expected: FAIL - - [input.defaultChecked (): setAttribute() to " foo " followed by IDL get] - expected: FAIL - - [input.defaultChecked (): setAttribute() to undefined followed by IDL get] - expected: FAIL - - [input.defaultChecked (): setAttribute() to null followed by IDL get] - expected: FAIL - - [input.defaultChecked (): setAttribute() to 7 followed by IDL get] - expected: FAIL - - [input.defaultChecked (): setAttribute() to 1.5 followed by IDL get] - expected: FAIL - - [input.defaultChecked (): setAttribute() to true followed by IDL get] - expected: FAIL - - [input.defaultChecked (): setAttribute() to false followed by IDL get] - expected: FAIL - - [input.defaultChecked (): setAttribute() to object "[object Object\]" followed by IDL get] - expected: FAIL - - [input.defaultChecked (): setAttribute() to NaN followed by IDL get] - expected: FAIL - - [input.defaultChecked (): setAttribute() to Infinity followed by IDL get] - expected: FAIL - - [input.defaultChecked (): setAttribute() to -Infinity followed by IDL get] - expected: FAIL - - [input.defaultChecked (): setAttribute() to "\\0" followed by IDL get] - expected: FAIL - - [input.defaultChecked (): setAttribute() to object "test-toString" followed by IDL get] - expected: FAIL - - [input.defaultChecked (): setAttribute() to object "test-valueOf" followed by IDL get] - expected: FAIL - - [input.defaultChecked (): setAttribute() to "checked" followed by IDL get] - expected: FAIL - - [input.defaultChecked (): IDL set to "" followed by hasAttribute()] - expected: FAIL - - [input.defaultChecked (): IDL set to "" followed by IDL get] - expected: FAIL - - [input.defaultChecked (): IDL set to " foo " followed by IDL get] - expected: FAIL - - [input.defaultChecked (): IDL set to undefined followed by hasAttribute()] - expected: FAIL - - [input.defaultChecked (): IDL set to undefined followed by IDL get] - expected: FAIL - - [input.defaultChecked (): IDL set to null followed by hasAttribute()] - expected: FAIL - - [input.defaultChecked (): IDL set to null followed by IDL get] - expected: FAIL - - [input.defaultChecked (): IDL set to 7 followed by IDL get] - expected: FAIL - - [input.defaultChecked (): IDL set to 1.5 followed by IDL get] - expected: FAIL - - [input.defaultChecked (): IDL set to false followed by hasAttribute()] - expected: FAIL - - [input.defaultChecked (): IDL set to object "[object Object\]" followed by IDL get] - expected: FAIL - - [input.defaultChecked (): IDL set to NaN followed by hasAttribute()] - expected: FAIL - - [input.defaultChecked (): IDL set to NaN followed by IDL get] - expected: FAIL - - [input.defaultChecked (): IDL set to Infinity followed by IDL get] - expected: FAIL - - [input.defaultChecked (): IDL set to -Infinity followed by IDL get] - expected: FAIL - - [input.defaultChecked (): IDL set to "\\0" followed by IDL get] - expected: FAIL - - [input.defaultChecked (): IDL set to object "test-toString" followed by IDL get] - expected: FAIL - - [input.defaultChecked (): IDL set to object "test-valueOf" followed by IDL get] - expected: FAIL - [input.dirName: typeof IDL attribute] expected: FAIL @@ -6370,133 +6262,133 @@ expected: FAIL [input.defaultValue (): typeof IDL attribute] - expected: FAIL + expected: PASS [input.defaultValue (): IDL get with DOM attribute unset] - expected: FAIL + expected: PASS [input.defaultValue (): setAttribute() to "" followed by IDL get] - expected: FAIL + expected: PASS [input.defaultValue (): setAttribute() to " \\0\\x01\\x02\\x03\\x04\\x05\\x06\\x07 \\b\\t\\n\\v\\f\\r\\x0e\\x0f \\x10\\x11\\x12\\x13\\x14\\x15\\x16\\x17 \\x18\\x19\\x1a\\x1b\\x1c\\x1d\\x1e\\x1f foo " followed by IDL get] - expected: FAIL + expected: PASS [input.defaultValue (): setAttribute() to undefined followed by IDL get] - expected: FAIL + expected: PASS [input.defaultValue (): setAttribute() to 7 followed by IDL get] - expected: FAIL + expected: PASS [input.defaultValue (): setAttribute() to 1.5 followed by IDL get] - expected: FAIL + expected: PASS [input.defaultValue (): setAttribute() to true followed by IDL get] - expected: FAIL + expected: PASS [input.defaultValue (): setAttribute() to false followed by IDL get] - expected: FAIL + expected: PASS [input.defaultValue (): setAttribute() to object "[object Object\]" followed by IDL get] - expected: FAIL + expected: PASS [input.defaultValue (): setAttribute() to NaN followed by IDL get] - expected: FAIL + expected: PASS [input.defaultValue (): setAttribute() to Infinity followed by IDL get] - expected: FAIL + expected: PASS [input.defaultValue (): setAttribute() to -Infinity followed by IDL get] - expected: FAIL + expected: PASS [input.defaultValue (): setAttribute() to "\\0" followed by IDL get] - expected: FAIL + expected: PASS [input.defaultValue (): setAttribute() to null followed by IDL get] - expected: FAIL + expected: PASS [input.defaultValue (): setAttribute() to object "test-toString" followed by IDL get] - expected: FAIL + expected: PASS [input.defaultValue (): setAttribute() to object "test-valueOf" followed by IDL get] - expected: FAIL + expected: PASS [input.defaultValue (): IDL set to "" followed by getAttribute()] - expected: FAIL + expected: PASS [input.defaultValue (): IDL set to " \\0\\x01\\x02\\x03\\x04\\x05\\x06\\x07 \\b\\t\\n\\v\\f\\r\\x0e\\x0f \\x10\\x11\\x12\\x13\\x14\\x15\\x16\\x17 \\x18\\x19\\x1a\\x1b\\x1c\\x1d\\x1e\\x1f foo " followed by getAttribute()] - expected: FAIL + expected: PASS [input.defaultValue (): IDL set to undefined followed by getAttribute()] - expected: FAIL + expected: PASS [input.defaultValue (): IDL set to undefined followed by IDL get] - expected: FAIL + expected: PASS [input.defaultValue (): IDL set to 7 followed by getAttribute()] - expected: FAIL + expected: PASS [input.defaultValue (): IDL set to 7 followed by IDL get] - expected: FAIL + expected: PASS [input.defaultValue (): IDL set to 1.5 followed by getAttribute()] - expected: FAIL + expected: PASS [input.defaultValue (): IDL set to 1.5 followed by IDL get] - expected: FAIL + expected: PASS [input.defaultValue (): IDL set to true followed by getAttribute()] - expected: FAIL + expected: PASS [input.defaultValue (): IDL set to true followed by IDL get] - expected: FAIL + expected: PASS [input.defaultValue (): IDL set to false followed by getAttribute()] - expected: FAIL + expected: PASS [input.defaultValue (): IDL set to false followed by IDL get] - expected: FAIL + expected: PASS [input.defaultValue (): IDL set to object "[object Object\]" followed by getAttribute()] - expected: FAIL + expected: PASS [input.defaultValue (): IDL set to object "[object Object\]" followed by IDL get] - expected: FAIL + expected: PASS [input.defaultValue (): IDL set to NaN followed by getAttribute()] - expected: FAIL + expected: PASS [input.defaultValue (): IDL set to NaN followed by IDL get] - expected: FAIL + expected: PASS [input.defaultValue (): IDL set to Infinity followed by getAttribute()] - expected: FAIL + expected: PASS [input.defaultValue (): IDL set to Infinity followed by IDL get] - expected: FAIL + expected: PASS [input.defaultValue (): IDL set to -Infinity followed by getAttribute()] - expected: FAIL + expected: PASS [input.defaultValue (): IDL set to -Infinity followed by IDL get] - expected: FAIL + expected: PASS [input.defaultValue (): IDL set to "\\0" followed by getAttribute()] - expected: FAIL + expected: PASS [input.defaultValue (): IDL set to null followed by getAttribute()] - expected: FAIL + expected: PASS [input.defaultValue (): IDL set to null followed by IDL get] - expected: FAIL + expected: PASS [input.defaultValue (): IDL set to object "test-toString" followed by getAttribute()] - expected: FAIL + expected: PASS [input.defaultValue (): IDL set to object "test-toString" followed by IDL get] - expected: FAIL + expected: PASS [input.defaultValue (): IDL set to object "test-valueOf" followed by IDL get] - expected: FAIL + expected: PASS [input.align: typeof IDL attribute] expected: FAIL @@ -14244,114 +14136,6 @@ [textarea.maxLength: IDL set to 2147483647 followed by getAttribute()] expected: FAIL - [textarea.readOnly: typeof IDL attribute] - expected: FAIL - - [textarea.readOnly: IDL get with DOM attribute unset] - expected: FAIL - - [textarea.readOnly: setAttribute() to "" followed by IDL get] - expected: FAIL - - [textarea.readOnly: setAttribute() to " foo " followed by IDL get] - expected: FAIL - - [textarea.readOnly: setAttribute() to undefined followed by IDL get] - expected: FAIL - - [textarea.readOnly: setAttribute() to null followed by IDL get] - expected: FAIL - - [textarea.readOnly: setAttribute() to 7 followed by IDL get] - expected: FAIL - - [textarea.readOnly: setAttribute() to 1.5 followed by IDL get] - expected: FAIL - - [textarea.readOnly: setAttribute() to true followed by IDL get] - expected: FAIL - - [textarea.readOnly: setAttribute() to false followed by IDL get] - expected: FAIL - - [textarea.readOnly: setAttribute() to object "[object Object\]" followed by IDL get] - expected: FAIL - - [textarea.readOnly: setAttribute() to NaN followed by IDL get] - expected: FAIL - - [textarea.readOnly: setAttribute() to Infinity followed by IDL get] - expected: FAIL - - [textarea.readOnly: setAttribute() to -Infinity followed by IDL get] - expected: FAIL - - [textarea.readOnly: setAttribute() to "\\0" followed by IDL get] - expected: FAIL - - [textarea.readOnly: setAttribute() to object "test-toString" followed by IDL get] - expected: FAIL - - [textarea.readOnly: setAttribute() to object "test-valueOf" followed by IDL get] - expected: FAIL - - [textarea.readOnly: setAttribute() to "readOnly" followed by IDL get] - expected: FAIL - - [textarea.readOnly: IDL set to "" followed by hasAttribute()] - expected: FAIL - - [textarea.readOnly: IDL set to "" followed by IDL get] - expected: FAIL - - [textarea.readOnly: IDL set to " foo " followed by IDL get] - expected: FAIL - - [textarea.readOnly: IDL set to undefined followed by hasAttribute()] - expected: FAIL - - [textarea.readOnly: IDL set to undefined followed by IDL get] - expected: FAIL - - [textarea.readOnly: IDL set to null followed by hasAttribute()] - expected: FAIL - - [textarea.readOnly: IDL set to null followed by IDL get] - expected: FAIL - - [textarea.readOnly: IDL set to 7 followed by IDL get] - expected: FAIL - - [textarea.readOnly: IDL set to 1.5 followed by IDL get] - expected: FAIL - - [textarea.readOnly: IDL set to false followed by hasAttribute()] - expected: FAIL - - [textarea.readOnly: IDL set to object "[object Object\]" followed by IDL get] - expected: FAIL - - [textarea.readOnly: IDL set to NaN followed by hasAttribute()] - expected: FAIL - - [textarea.readOnly: IDL set to NaN followed by IDL get] - expected: FAIL - - [textarea.readOnly: IDL set to Infinity followed by IDL get] - expected: FAIL - - [textarea.readOnly: IDL set to -Infinity followed by IDL get] - expected: FAIL - - [textarea.readOnly: IDL set to "\\0" followed by IDL get] - expected: FAIL - - [textarea.readOnly: IDL set to object "test-toString" followed by IDL get] - expected: FAIL - - [textarea.readOnly: IDL set to object "test-valueOf" followed by IDL get] - expected: FAIL - [textarea.rows: IDL get with DOM attribute unset] expected: FAIL From 504f968b20b35f901f2f6af564023b2bc8aa998d Mon Sep 17 00:00:00 2001 From: Matthew Rasmus Date: Mon, 8 Dec 2014 11:29:42 -0800 Subject: [PATCH 11/12] Implements :indeterminate pseudo-class Addresses reviews --- components/layout/wrapper.rs | 7 + components/script/dom/element.rs | 21 ++- components/script/dom/htmlformelement.rs | 44 +++++- components/script/dom/htmlinputelement.rs | 44 +++--- components/script/dom/htmltextareaelement.rs | 30 +--- components/style/node.rs | 1 + components/style/selector_matching.rs | 8 +- components/style/selectors.rs | 4 +- resources/servo.css | 1 + tests/html/form_reset.html | 3 +- tests/html/form_reset_handsfree.html | 3 +- tests/html/test-inputs.html | 4 + .../wpt/metadata/html/dom/interfaces.html.ini | 12 -- .../html/dom/reflection-forms.html.ini | 129 ------------------ 14 files changed, 105 insertions(+), 206 deletions(-) diff --git a/components/layout/wrapper.rs b/components/layout/wrapper.rs index 97aa8dd189a..9517e7a2d45 100644 --- a/components/layout/wrapper.rs +++ b/components/layout/wrapper.rs @@ -562,6 +562,13 @@ impl<'le> TElement<'le> for LayoutElement<'le> { } } + #[inline] + fn get_indeterminate_state(self) -> bool { + unsafe { + self.element.get_indeterminate_state_for_layout() + } + } + #[inline] fn has_class(self, name: &Atom) -> bool { unsafe { diff --git a/components/script/dom/element.rs b/components/script/dom/element.rs index 3d7a6a861e9..2e2bbf50f5d 100644 --- a/components/script/dom/element.rs +++ b/components/script/dom/element.rs @@ -35,7 +35,7 @@ use dom::event::Event; use dom::eventtarget::{EventTarget, NodeTargetTypeId, EventTargetHelpers}; use dom::htmlbodyelement::{HTMLBodyElement, HTMLBodyElementHelpers}; use dom::htmlcollection::HTMLCollection; -use dom::htmlinputelement::{HTMLInputElement, RawLayoutHTMLInputElementHelpers}; +use dom::htmlinputelement::{HTMLInputElement, RawLayoutHTMLInputElementHelpers, HTMLInputElementHelpers}; use dom::htmlserializer::serialize; use dom::htmltableelement::{HTMLTableElement, HTMLTableElementHelpers}; use dom::htmltablecellelement::{HTMLTableCellElement, HTMLTableCellElementHelpers}; @@ -211,6 +211,7 @@ pub trait RawLayoutElementHelpers { unsafe fn get_integer_attribute_for_layout(&self, integer_attribute: IntegerAttribute) -> Option; unsafe fn get_checked_state_for_layout(&self) -> bool; + unsafe fn get_indeterminate_state_for_layout(&self) -> bool; unsafe fn get_unsigned_integer_attribute_for_layout(&self, attribute: UnsignedIntegerAttribute) -> Option; unsafe fn get_simple_color_attribute_for_layout(&self, attribute: SimpleColorAttribute) @@ -337,6 +338,18 @@ impl RawLayoutElementHelpers for Element { this.get_checked_state_for_layout() } + #[inline] + #[allow(unrooted_must_root)] + unsafe fn get_indeterminate_state_for_layout(&self) -> bool { + // TODO progress elements can also be matched with :indeterminate + if !self.is_htmlinputelement() { + return false + } + let this: &HTMLInputElement = mem::transmute(self); + this.get_indeterminate_state_for_layout() + } + + unsafe fn get_unsigned_integer_attribute_for_layout(&self, attribute: UnsignedIntegerAttribute) -> Option { @@ -1274,6 +1287,12 @@ impl<'a> style::TElement<'a> for JSRef<'a, Element> { None => false, } } + fn get_indeterminate_state(self) -> bool { + match HTMLInputElementCast::to_ref(self) { + Some(input) => input.get_indeterminate_state(), + None => false, + } + } fn has_class(self, name: &Atom) -> bool { // FIXME(zwarich): Remove this when UFCS lands and there is a better way // of disambiguating methods. diff --git a/components/script/dom/htmlformelement.rs b/components/script/dom/htmlformelement.rs index 62b08649f7f..62f5e3aa62e 100644 --- a/components/script/dom/htmlformelement.rs +++ b/components/script/dom/htmlformelement.rs @@ -2,15 +2,16 @@ * 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::bindings::codegen::Bindings::DocumentBinding::DocumentMethods; use dom::bindings::codegen::Bindings::EventBinding::EventMethods; use dom::bindings::codegen::Bindings::EventTargetBinding::EventTargetMethods; use dom::bindings::codegen::Bindings::HTMLFormElementBinding; use dom::bindings::codegen::Bindings::HTMLFormElementBinding::HTMLFormElementMethods; use dom::bindings::codegen::Bindings::HTMLInputElementBinding::HTMLInputElementMethods; use dom::bindings::codegen::InheritTypes::{EventTargetCast, HTMLFormElementDerived, NodeCast}; -use dom::bindings::codegen::InheritTypes::{HTMLInputElementCast, HTMLTextAreaElementCast}; +use dom::bindings::codegen::InheritTypes::{HTMLInputElementCast, HTMLTextAreaElementCast, HTMLFormElementCast}; use dom::bindings::global::Window; -use dom::bindings::js::{JSRef, Temporary}; +use dom::bindings::js::{JSRef, Temporary, OptionalRootable}; use dom::bindings::utils::{Reflectable, Reflector}; use dom::document::{Document, DocumentHelpers}; use dom::element::{Element, AttributeHandlers, HTMLFormElementTypeId, HTMLTextAreaElementTypeId, HTMLDataListElementTypeId}; @@ -31,9 +32,12 @@ use url::UrlParser; use url::form_urlencoded::serialize; use string_cache::Atom; +use std::cell::Cell; + #[dom_struct] pub struct HTMLFormElement { htmlelement: HTMLElement, + marked_for_reset: Cell, } impl HTMLFormElementDerived for EventTarget { @@ -45,7 +49,8 @@ impl HTMLFormElementDerived for EventTarget { impl HTMLFormElement { fn new_inherited(localName: DOMString, prefix: Option, document: JSRef) -> HTMLFormElement { HTMLFormElement { - htmlelement: HTMLElement::new_inherited(HTMLFormElementTypeId, localName, prefix, document) + htmlelement: HTMLElement::new_inherited(HTMLFormElementTypeId, localName, prefix, document), + marked_for_reset: Cell::new(false), } } @@ -332,6 +337,13 @@ impl<'a> HTMLFormElementHelpers for JSRef<'a, HTMLFormElement> { } fn reset(self, _reset_method_flag: ResetFrom) { + // https://html.spec.whatwg.org/multipage/forms.html#locked-for-reset + if self.marked_for_reset.get() { + return; + } else { + self.marked_for_reset.set(true); + } + let win = window_from_node(self).root(); let event = Event::new(Window(*win), "reset".to_string(), @@ -374,6 +386,7 @@ impl<'a> HTMLFormElementHelpers for JSRef<'a, HTMLFormElement> { _ => {} } }; + self.marked_for_reset.set(false); } } @@ -471,7 +484,30 @@ impl<'a> FormSubmitter<'a> { } pub trait FormControl<'a> : Copy { - fn form_owner(self) -> Option>; + // FIXME: This is wrong (https://github.com/servo/servo/issues/3553) + // but we need html5ever to do it correctly + fn form_owner(self) -> Option> { + // https://html.spec.whatwg.org/multipage/forms.html#reset-the-form-owner + let elem = self.to_element(); + let owner = elem.get_string_attribute(&atom!("form")); + if !owner.is_empty() { + let doc = document_from_node(elem).root(); + let owner = doc.GetElementById(owner).root(); + match owner { + Some(o) => { + let maybe_form: Option> = HTMLFormElementCast::to_ref(*o); + if maybe_form.is_some() { + return maybe_form.map(Temporary::from_rooted); + } + }, + _ => () + } + } + let node: JSRef = NodeCast::from_ref(elem); + node.ancestors().filter_map(|a| HTMLFormElementCast::to_ref(a)).next() + .map(Temporary::from_rooted) + } + fn get_form_attribute(self, attr: &Atom, input: |Self| -> DOMString, diff --git a/components/script/dom/htmlinputelement.rs b/components/script/dom/htmlinputelement.rs index 66d806673d8..3ea2a38a686 100644 --- a/components/script/dom/htmlinputelement.rs +++ b/components/script/dom/htmlinputelement.rs @@ -7,12 +7,11 @@ use dom::attr::{Attr, AttrValue, UIntAttrValue}; use dom::attr::AttrHelpers; use dom::bindings::cell::DOMRefCell; use dom::bindings::codegen::Bindings::AttrBinding::AttrMethods; -use dom::bindings::codegen::Bindings::DocumentBinding::DocumentMethods; use dom::bindings::codegen::Bindings::EventBinding::EventMethods; use dom::bindings::codegen::Bindings::EventTargetBinding::EventTargetMethods; use dom::bindings::codegen::Bindings::HTMLInputElementBinding; use dom::bindings::codegen::Bindings::HTMLInputElementBinding::HTMLInputElementMethods; -use dom::bindings::codegen::InheritTypes::{ElementCast, HTMLElementCast, HTMLFormElementCast, HTMLInputElementCast, NodeCast}; +use dom::bindings::codegen::InheritTypes::{ElementCast, HTMLElementCast, HTMLInputElementCast, NodeCast}; use dom::bindings::codegen::InheritTypes::{HTMLInputElementDerived, HTMLFieldSetElementDerived, EventTargetCast}; use dom::bindings::codegen::InheritTypes::KeyboardEventCast; use dom::bindings::global::Window; @@ -134,6 +133,7 @@ pub trait LayoutHTMLInputElementHelpers { pub trait RawLayoutHTMLInputElementHelpers { unsafe fn get_checked_state_for_layout(&self) -> bool; + unsafe fn get_indeterminate_state_for_layout(&self) -> bool; unsafe fn get_size_for_layout(&self) -> u32; } @@ -176,6 +176,11 @@ impl RawLayoutHTMLInputElementHelpers for HTMLInputElement { self.checked.get() } + #[allow(unrooted_must_root)] + unsafe fn get_indeterminate_state_for_layout(&self) -> bool { + self.indeterminate.get() + } + #[allow(unrooted_must_root)] unsafe fn get_size_for_layout(&self) -> u32 { self.size.get() @@ -284,7 +289,6 @@ impl<'a> HTMLInputElementMethods for JSRef<'a, HTMLInputElement> { // https://html.spec.whatwg.org/multipage/forms.html#dom-input-indeterminate fn SetIndeterminate(self, val: bool) { - // FIXME #4079 this should change the appearance self.indeterminate.set(val) } } @@ -295,6 +299,7 @@ pub trait HTMLInputElementHelpers { fn get_radio_group_name(self) -> Option; fn update_checked_state(self, checked: bool, dirty: bool); fn get_size(&self) -> u32; + fn get_indeterminate_state(self) -> bool; } fn broadcast_radio_checked(broadcaster: JSRef, group: Option<&str>) { @@ -373,6 +378,10 @@ impl<'a> HTMLInputElementHelpers for JSRef<'a, HTMLInputElement> { fn get_size(&self) -> u32 { self.size.get() } + + fn get_indeterminate_state(self) -> bool { + self.indeterminate.get() + } } impl<'a> VirtualMethods for JSRef<'a, HTMLInputElement> { @@ -566,30 +575,6 @@ impl Reflectable for HTMLInputElement { } impl<'a> FormControl<'a> for JSRef<'a, HTMLInputElement> { - // FIXME: This is wrong (https://github.com/servo/servo/issues/3553) - // but we need html5ever to do it correctly - fn form_owner(self) -> Option> { - // https://html.spec.whatwg.org/multipage/forms.html#reset-the-form-owner - let elem: JSRef = ElementCast::from_ref(self); - let owner = elem.get_string_attribute(&atom!("form")); - if !owner.is_empty() { - let doc = document_from_node(self).root(); - let owner = doc.GetElementById(owner).root(); - match owner { - Some(o) => { - let maybe_form: Option> = HTMLFormElementCast::to_ref(*o); - if maybe_form.is_some() { - return maybe_form.map(Temporary::from_rooted); - } - }, - _ => () - } - } - let node: JSRef = NodeCast::from_ref(self); - node.ancestors().filter_map(|a| HTMLFormElementCast::to_ref(a)).next() - .map(Temporary::from_rooted) - } - fn to_element(self) -> JSRef<'a, Element> { ElementCast::from_ref(self) } @@ -601,6 +586,7 @@ impl<'a> FormControl<'a> for JSRef<'a, HTMLInputElement> { !(self.Disabled() || self.ReadOnly()) } + // https://html.spec.whatwg.org/multipage/forms.html#the-input-element:concept-form-reset-control fn reset(self) { match self.input_type.get() { InputRadio | InputCheckbox => { @@ -632,6 +618,8 @@ impl<'a> Activatable for JSRef<'a, HTMLInputElement> { match ty { // https://html.spec.whatwg.org/multipage/forms.html#submit-button-state-(type=submit):activation-behavior // InputSubmit => (), // No behavior defined + // https://html.spec.whatwg.org/multipage/forms.html#reset-button-state-(type=reset):activation-behavior + // InputSubmit => (), // No behavior defined InputCheckbox => { // https://html.spec.whatwg.org/multipage/forms.html#checkbox-state-(type=checkbox):pre-click-activation-steps // cache current values of `checked` and `indeterminate` @@ -679,6 +667,8 @@ impl<'a> Activatable for JSRef<'a, HTMLInputElement> { match ty { // https://html.spec.whatwg.org/multipage/forms.html#submit-button-state-(type=submit):activation-behavior // InputSubmit => (), // No behavior defined + // https://html.spec.whatwg.org/multipage/forms.html#reset-button-state-(type=reset):activation-behavior + // InputReset => (), // No behavior defined // https://html.spec.whatwg.org/multipage/forms.html#checkbox-state-(type=checkbox):canceled-activation-steps InputCheckbox => { // We want to restore state only if the element had been changed in the first place diff --git a/components/script/dom/htmltextareaelement.rs b/components/script/dom/htmltextareaelement.rs index bfa382c6f5f..210864ebf72 100644 --- a/components/script/dom/htmltextareaelement.rs +++ b/components/script/dom/htmltextareaelement.rs @@ -5,14 +5,13 @@ use dom::attr::{Attr, AttrValue}; use dom::attr::AttrHelpers; use dom::bindings::cell::DOMRefCell; -use dom::bindings::codegen::Bindings::DocumentBinding::DocumentMethods; 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::codegen::InheritTypes::{ElementCast, HTMLElementCast, NodeCast}; use dom::bindings::codegen::InheritTypes::{HTMLTextAreaElementDerived, HTMLFieldSetElementDerived}; -use dom::bindings::codegen::InheritTypes::{KeyboardEventCast, TextDerived, HTMLFormElementCast}; +use dom::bindings::codegen::InheritTypes::{KeyboardEventCast, TextDerived}; use dom::bindings::js::{JS, JSRef, Temporary, OptionalRootable}; use dom::bindings::utils::{Reflectable, Reflector}; use dom::document::{Document, DocumentHelpers}; @@ -20,7 +19,7 @@ use dom::element::{AttributeHandlers, HTMLTextAreaElementTypeId, Element}; use dom::event::Event; use dom::eventtarget::{EventTarget, NodeTargetTypeId}; use dom::htmlelement::HTMLElement; -use dom::htmlformelement::{FormControl, HTMLFormElement}; +use dom::htmlformelement::FormControl; use dom::keyboardevent::KeyboardEvent; use dom::node::{DisabledStateHelpers, Node, NodeHelpers, OtherNodeDamage, ElementNodeTypeId}; use dom::node::{document_from_node}; @@ -299,30 +298,6 @@ impl Reflectable for HTMLTextAreaElement { } impl<'a> FormControl<'a> for JSRef<'a, HTMLTextAreaElement> { - // FIXME: This is wrong (https://github.com/servo/servo/issues/3553) - // but we need html5ever to do it correctly - fn form_owner(self) -> Option> { - // https://html.spec.whatwg.org/multipage/forms.html#reset-the-form-owner - let elem: JSRef = ElementCast::from_ref(self); - let owner = elem.get_string_attribute(&atom!("form")); - if !owner.is_empty() { - let doc = document_from_node(self).root(); - let owner = doc.GetElementById(owner).root(); - match owner { - Some(o) => { - let maybe_form: Option> = HTMLFormElementCast::to_ref(*o); - if maybe_form.is_some() { - return maybe_form.map(Temporary::from_rooted); - } - }, - _ => () - } - } - let node: JSRef = NodeCast::from_ref(self); - node.ancestors().filter_map(|a| HTMLFormElementCast::to_ref(a)).next() - .map(Temporary::from_rooted) - } - fn to_element(self) -> JSRef<'a, Element> { ElementCast::from_ref(self) } @@ -334,6 +309,7 @@ impl<'a> FormControl<'a> for JSRef<'a, HTMLTextAreaElement> { } fn reset(self) { + // https://html.spec.whatwg.org/multipage/forms.html#the-textarea-element:concept-form-reset-control self.SetValue(self.DefaultValue()); self.value_changed.set(false); } diff --git a/components/style/node.rs b/components/style/node.rs index a1c9a8071c4..48afb072c43 100644 --- a/components/style/node.rs +++ b/components/style/node.rs @@ -47,6 +47,7 @@ pub trait TElement<'a> : Copy { fn get_disabled_state(self) -> bool; fn get_enabled_state(self) -> bool; fn get_checked_state(self) -> bool; + fn get_indeterminate_state(self) -> bool; fn has_class(self, name: &Atom) -> bool; fn has_nonzero_border(self) -> bool; diff --git a/components/style/selector_matching.rs b/components/style/selector_matching.rs index 5482745721f..091d6204886 100644 --- a/components/style/selector_matching.rs +++ b/components/style/selector_matching.rs @@ -23,7 +23,7 @@ use properties::{PropertyDeclaration, PropertyDeclarationBlock}; use selectors::{After, AnyLink, AttrDashMatch, AttrEqual}; use selectors::{AttrExists, AttrIncludes, AttrPrefixMatch}; use selectors::{AttrSubstringMatch, AttrSuffixMatch, Before, CaseInsensitive, CaseSensitive}; -use selectors::{Checked, Child, ClassSelector}; +use selectors::{Checked, Child, ClassSelector, Indeterminate}; use selectors::{CompoundSelector, Descendant, Disabled, Enabled, FirstChild, FirstOfType}; use selectors::{Hover, IDSelector, LastChild, LastOfType}; use selectors::{LaterSibling, LocalName, LocalNameSelector}; @@ -972,6 +972,12 @@ pub fn matches_simple_selector<'a,E,N>(selector: &SimpleSelector, let elem = element.as_element(); elem.get_checked_state() } + // https://html.spec.whatwg.org/multipage/scripting.html#selector-indeterminate + Indeterminate => { + *shareable = false; + let elem = element.as_element(); + elem.get_indeterminate_state() + } FirstChild => { *shareable = false; matches_first_child(element) diff --git a/components/style/selectors.rs b/components/style/selectors.rs index 1d72a530383..5217ea2828a 100644 --- a/components/style/selectors.rs +++ b/components/style/selectors.rs @@ -75,6 +75,7 @@ pub enum SimpleSelector { Disabled, Enabled, Checked, + Indeterminate, FirstChild, LastChild, OnlyChild, // Empty, Root, @@ -167,7 +168,7 @@ fn compute_specificity(mut selector: &CompoundSelector, | &AttrExists(..) | &AttrEqual(..) | &AttrIncludes(..) | &AttrDashMatch(..) | &AttrPrefixMatch(..) | &AttrSubstringMatch(..) | &AttrSuffixMatch(..) | &AnyLink | &Link | &Visited | &Hover | &Disabled | &Enabled - | &FirstChild | &LastChild | &OnlyChild | &Root | &Checked + | &FirstChild | &LastChild | &OnlyChild | &Root | &Checked | &Indeterminate // | &Empty | &Lang(*) | &NthChild(..) | &NthLastChild(..) | &NthOfType(..) | &NthLastOfType(..) @@ -568,6 +569,7 @@ fn parse_simple_pseudo_class(context: &ParserContext, name: &str) -> Result Ok(Disabled), "enabled" => Ok(Enabled), "checked" => Ok(Checked), + "indeterminate" => Ok(Indeterminate), "first-child" => Ok(FirstChild), "last-child" => Ok(LastChild), "only-child" => Ok(OnlyChild), diff --git a/resources/servo.css b/resources/servo.css index 5fb55e1734f..eec446505d4 100644 --- a/resources/servo.css +++ b/resources/servo.css @@ -11,6 +11,7 @@ input[type="radio"] { font-family: monospace !important; border: none !impor input[type="checkbox"]::before { content: "[ ]"; padding: 0; } input[type="checkbox"]:checked::before { content: "[✓]"; } +input[type="checkbox"]:indeterminate::before { content: "[-]"; } input[type="radio"]::before { content: "( )"; padding: 0; } input[type="radio"]:checked::before { content: "(●)"; } diff --git a/tests/html/form_reset.html b/tests/html/form_reset.html index 99f3f194c07..9d841a2058b 100644 --- a/tests/html/form_reset.html +++ b/tests/html/form_reset.html @@ -1,8 +1,7 @@ - -
+ diff --git a/tests/html/form_reset_handsfree.html b/tests/html/form_reset_handsfree.html index 550a8efa9de..4c0f09eaf3c 100644 --- a/tests/html/form_reset_handsfree.html +++ b/tests/html/form_reset_handsfree.html @@ -1,8 +1,7 @@ - - + diff --git a/tests/html/test-inputs.html b/tests/html/test-inputs.html index 633bf3e23c4..455c3624eef 100644 --- a/tests/html/test-inputs.html +++ b/tests/html/test-inputs.html @@ -12,7 +12,11 @@
+
+