From e6c31e305dcae1583be5e6764cc5f9778f73c128 Mon Sep 17 00:00:00 2001 From: Greg Guthe Date: Tue, 2 Feb 2016 21:02:46 -0500 Subject: [PATCH] Get input value IDL attribute matching spec Refs: https://github.com/servo/servo/issues/9455 --- components/script/dom/htmlinputelement.rs | 127 +++++++++++++++--- .../dom/webidls/HTMLInputElement.webidl | 3 +- .../collections/radionodelist.html.ini | 5 - .../input-type-checkbox.html.ini | 5 - .../type-change-state.html.ini | 126 ----------------- 5 files changed, 114 insertions(+), 152 deletions(-) delete mode 100644 tests/wpt/metadata/html/infrastructure/common-dom-interfaces/collections/radionodelist.html.ini delete mode 100644 tests/wpt/metadata/html/semantics/forms/the-input-element/input-type-checkbox.html.ini diff --git a/components/script/dom/htmlinputelement.rs b/components/script/dom/htmlinputelement.rs index b1941b0db1a..bda81450d3d 100644 --- a/components/script/dom/htmlinputelement.rs +++ b/components/script/dom/htmlinputelement.rs @@ -11,6 +11,7 @@ use dom::bindings::codegen::Bindings::EventBinding::EventMethods; use dom::bindings::codegen::Bindings::HTMLInputElementBinding; use dom::bindings::codegen::Bindings::HTMLInputElementBinding::HTMLInputElementMethods; use dom::bindings::codegen::Bindings::KeyboardEventBinding::KeyboardEventMethods; +use dom::bindings::error::{Error, ErrorResult}; use dom::bindings::global::GlobalRef; use dom::bindings::inheritance::Castable; use dom::bindings::js::{JS, LayoutJS, Root, RootedReference}; @@ -59,6 +60,14 @@ enum InputType { InputPassword } +#[derive(Debug, PartialEq)] +enum ValueMode { + Value, + Default, + DefaultOn, + Filename, +} + #[dom_struct] pub struct HTMLInputElement { htmlelement: HTMLElement, @@ -71,6 +80,10 @@ pub struct HTMLInputElement { #[ignore_heap_size_of = "#7193"] textinput: DOMRefCell>>, activation_state: DOMRefCell, + // https://html.spec.whatwg.org/multipage/#concept-input-value-dirty-flag + value_dirty: Cell, + + // TODO: selected files for file input } #[derive(JSTraceable)] @@ -117,7 +130,8 @@ impl HTMLInputElement { maxlength: Cell::new(DEFAULT_MAX_LENGTH), size: Cell::new(DEFAULT_INPUT_SIZE), textinput: DOMRefCell::new(TextInput::new(Single, DOMString::new(), chan, None)), - activation_state: DOMRefCell::new(InputActivationState::new()) + activation_state: DOMRefCell::new(InputActivationState::new()), + value_dirty: Cell::new(false), } } @@ -134,6 +148,22 @@ impl HTMLInputElement { .get_attribute(&ns!(), &atom!("type")) .map_or_else(|| atom!(""), |a| a.value().as_atom().to_owned()) } + + // https://html.spec.whatwg.org/multipage/#input-type-attr-summary + fn get_value_mode(&self) -> ValueMode { + match self.input_type.get() { + InputType::InputSubmit | + InputType::InputReset | + InputType::InputButton | + InputType::InputImage => ValueMode::Default, + InputType::InputCheckbox | + InputType::InputRadio => ValueMode::DefaultOn, + InputType::InputPassword | + InputType::InputText => ValueMode::Value, + InputType::InputFile => ValueMode::Filename, + } + } + } pub trait LayoutHTMLInputElementHelpers { @@ -292,14 +322,50 @@ impl HTMLInputElementMethods for HTMLInputElement { // https://html.spec.whatwg.org/multipage/#dom-input-value fn Value(&self) -> DOMString { - self.textinput.borrow().get_content() + match self.get_value_mode() { + ValueMode::Value => self.textinput.borrow().get_content(), + ValueMode::Default => { + self.upcast::() + .get_attribute(&ns!(), &atom!("value")) + .map_or(DOMString::from(""), + |a| DOMString::from(a.summarize().value)) + } + ValueMode::DefaultOn => { + self.upcast::() + .get_attribute(&ns!(), &atom!("value")) + .map_or(DOMString::from("on"), + |a| DOMString::from(a.summarize().value)) + } + ValueMode::Filename => { + // TODO: return C:\fakepath\ when a file is selected + DOMString::from("") + } + } } // https://html.spec.whatwg.org/multipage/#dom-input-value - fn SetValue(&self, value: DOMString) { - self.textinput.borrow_mut().set_content(value); + fn SetValue(&self, value: DOMString) -> ErrorResult { + match self.get_value_mode() { + ValueMode::Value => { + self.textinput.borrow_mut().set_content(value); + self.value_dirty.set(true); + } + ValueMode::Default | + ValueMode::DefaultOn => { + self.upcast::().set_string_attribute(&atom!("value"), value); + } + ValueMode::Filename => { + if value.is_empty() { + // TODO: empty list of selected files + } else { + return Err(Error::InvalidState); + } + } + } + self.value_changed.set(true); self.force_relayout(); + Ok(()) } // https://html.spec.whatwg.org/multipage/#dom-input-defaultvalue @@ -465,17 +531,11 @@ impl HTMLInputElement { } - let mut value = self.Value(); // Step 3.6 - if ty == atom!("radio") || ty == atom!("checkbox") { - if value.is_empty() { - value = DOMString::from("on"); - } - } Some(FormDatum { ty: DOMString::from(&*ty), // FIXME(ajeffrey): Convert directly from Atoms to DOMStrings name: name, - value: value + value: self.Value() }) } @@ -525,7 +585,9 @@ impl HTMLInputElement { _ => () } - self.SetValue(self.DefaultValue()); + self.SetValue(self.DefaultValue()) + .expect("Failed to reset input value to default."); + self.value_dirty.set(false); self.value_changed.set(false); self.force_relayout(); } @@ -574,7 +636,7 @@ impl VirtualMethods for HTMLInputElement { &atom!("type") => { match mutation { AttributeMutation::Set(_) => { - let value = match attr.value().as_atom() { + let new_type = match attr.value().as_atom() { &atom!("button") => InputType::InputButton, &atom!("submit") => InputType::InputSubmit, &atom!("reset") => InputType::InputReset, @@ -584,11 +646,46 @@ impl VirtualMethods for HTMLInputElement { &atom!("password") => InputType::InputPassword, _ => InputType::InputText, }; - self.input_type.set(value); - if value == InputType::InputRadio { + + // https://html.spec.whatwg.org/multipage/#input-type-change + let (old_value_mode, old_idl_value) = (self.get_value_mode(), self.Value()); + self.input_type.set(new_type); + let new_value_mode = self.get_value_mode(); + + match (&old_value_mode, old_idl_value.is_empty(), new_value_mode) { + + // Step 1 + (&ValueMode::Value, false, ValueMode::Default) | + (&ValueMode::Value, false, ValueMode::DefaultOn) => { + self.SetValue(old_idl_value) + .expect("Failed to set input value on type change to a default ValueMode."); + } + + // Step 2 + (_, _, ValueMode::Value) if old_value_mode != ValueMode::Value => { + self.SetValue(self.upcast::() + .get_attribute(&ns!(), &atom!("value")) + .map_or(DOMString::from(""), + |a| DOMString::from(a.summarize().value))) + .expect("Failed to set input value on type change to ValueMode::Value."); + self.value_dirty.set(false); + } + + // Step 3 + (_, _, ValueMode::Filename) if old_value_mode != ValueMode::Filename => { + self.SetValue(DOMString::from("")) + .expect("Failed to set input value on type change to ValueMode::Filename."); + } + _ => {} + } + + // Step 5 + if new_type == InputType::InputRadio { self.radio_group_updated( self.get_radio_group_name().as_ref()); } + + // TODO: Step 6 - value sanitization }, AttributeMutation::Removed => { if self.input_type.get() == InputType::InputRadio { diff --git a/components/script/dom/webidls/HTMLInputElement.webidl b/components/script/dom/webidls/HTMLInputElement.webidl index 8cc0fa66ae7..d213334ef83 100644 --- a/components/script/dom/webidls/HTMLInputElement.webidl +++ b/components/script/dom/webidls/HTMLInputElement.webidl @@ -41,7 +41,8 @@ interface HTMLInputElement : HTMLElement { // attribute DOMString step; attribute DOMString type; attribute DOMString defaultValue; - [TreatNullAs=EmptyString] attribute DOMString value; +[TreatNullAs=EmptyString, SetterThrows] + attribute DOMString value; // attribute Date? valueAsDate; // attribute unrestricted double valueAsNumber; // attribute double valueLow; diff --git a/tests/wpt/metadata/html/infrastructure/common-dom-interfaces/collections/radionodelist.html.ini b/tests/wpt/metadata/html/infrastructure/common-dom-interfaces/collections/radionodelist.html.ini deleted file mode 100644 index d1621994aa7..00000000000 --- a/tests/wpt/metadata/html/infrastructure/common-dom-interfaces/collections/radionodelist.html.ini +++ /dev/null @@ -1,5 +0,0 @@ -[radionodelist.html] - type: testharness - [Check the RadioNodeList.value on setting to 'on'] - expected: FAIL - diff --git a/tests/wpt/metadata/html/semantics/forms/the-input-element/input-type-checkbox.html.ini b/tests/wpt/metadata/html/semantics/forms/the-input-element/input-type-checkbox.html.ini deleted file mode 100644 index 603f07108a7..00000000000 --- a/tests/wpt/metadata/html/semantics/forms/the-input-element/input-type-checkbox.html.ini +++ /dev/null @@ -1,5 +0,0 @@ -[input-type-checkbox.html] - type: testharness - [default/on: on getting, if the element has a value attribute, it must return that attribute's value; otherwise, it must return the string 'on'] - expected: FAIL - diff --git a/tests/wpt/metadata/html/semantics/forms/the-input-element/type-change-state.html.ini b/tests/wpt/metadata/html/semantics/forms/the-input-element/type-change-state.html.ini index 6ff71a3aeb7..b2ef4477faa 100644 --- a/tests/wpt/metadata/html/semantics/forms/the-input-element/type-change-state.html.ini +++ b/tests/wpt/metadata/html/semantics/forms/the-input-element/type-change-state.html.ini @@ -672,69 +672,6 @@ [change state from radio to color] expected: FAIL - [change state from file to hidden] - expected: FAIL - - [change state from file to text] - expected: FAIL - - [change state from file to search] - expected: FAIL - - [change state from file to tel] - expected: FAIL - - [change state from file to url] - expected: FAIL - - [change state from file to email] - expected: FAIL - - [change state from file to password] - expected: FAIL - - [change state from file to datetime] - expected: FAIL - - [change state from file to date] - expected: FAIL - - [change state from file to month] - expected: FAIL - - [change state from file to week] - expected: FAIL - - [change state from file to time] - expected: FAIL - - [change state from file to number] - expected: FAIL - - [change state from file to range] - expected: FAIL - - [change state from file to color] - expected: FAIL - - [change state from file to checkbox] - expected: FAIL - - [change state from file to radio] - expected: FAIL - - [change state from file to submit] - expected: FAIL - - [change state from file to image] - expected: FAIL - - [change state from file to reset] - expected: FAIL - - [change state from file to button] - expected: FAIL - [change state from submit to text] expected: FAIL @@ -903,66 +840,3 @@ [change state from button to color] expected: FAIL - [change state from hidden to file] - expected: FAIL - - [change state from text to file] - expected: FAIL - - [change state from search to file] - expected: FAIL - - [change state from tel to file] - expected: FAIL - - [change state from url to file] - expected: FAIL - - [change state from email to file] - expected: FAIL - - [change state from password to file] - expected: FAIL - - [change state from datetime to file] - expected: FAIL - - [change state from date to file] - expected: FAIL - - [change state from month to file] - expected: FAIL - - [change state from week to file] - expected: FAIL - - [change state from time to file] - expected: FAIL - - [change state from number to file] - expected: FAIL - - [change state from range to file] - expected: FAIL - - [change state from color to file] - expected: FAIL - - [change state from checkbox to file] - expected: FAIL - - [change state from radio to file] - expected: FAIL - - [change state from submit to file] - expected: FAIL - - [change state from image to file] - expected: FAIL - - [change state from reset to file] - expected: FAIL - - [change state from button to file] - expected: FAIL -