auto merge of #4133 : mttr/servo/form_resetting, r=jdm

We can reset `<input type=text>` fields! I wish I could've done something with checkboxes, but unfortunately, that's it for now.

In addition to that, this PR implements `HTMLInputAttribute.defaultValue`, updates wpt-test to expect passing tests as a result of that implementation, and fixes an index error crash with text inputs.

edit: also includes an html example where one may lazily watch form resets in action: ` tests/html/form_reset_handsfree.html`
This commit is contained in:
bors-servo 2014-12-16 15:03:49 -07:00
commit 5951056973
22 changed files with 376 additions and 446 deletions

View file

@ -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};
@ -212,6 +212,7 @@ pub trait RawLayoutElementHelpers {
unsafe fn get_integer_attribute_for_layout(&self, integer_attribute: IntegerAttribute)
-> Option<i32>;
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<u32>;
unsafe fn get_simple_color_attribute_for_layout(&self, attribute: SimpleColorAttribute)
@ -352,6 +353,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<u32> {
@ -1289,6 +1302,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.

View file

@ -2,23 +2,26 @@
* 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;
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};
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;
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;
@ -29,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<bool>,
}
impl HTMLFormElementDerived for EventTarget {
@ -43,7 +49,8 @@ impl HTMLFormElementDerived for EventTarget {
impl HTMLFormElement {
fn new_inherited(localName: DOMString, prefix: Option<DOMString>, document: JSRef<Document>) -> HTMLFormElement {
HTMLFormElement {
htmlelement: HTMLElement::new_inherited(HTMLFormElementTypeId, localName, prefix, document)
htmlelement: HTMLElement::new_inherited(HTMLFormElementTypeId, localName, prefix, document),
marked_for_reset: Cell::new(false),
}
}
@ -117,6 +124,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 +136,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<FormSubmitter>) -> Vec<FormDatum>;
// 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 +335,59 @@ impl<'a> HTMLFormElementHelpers for JSRef<'a, HTMLFormElement> {
};
ret
}
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(),
Bubbles, Cancelable).root();
let target: JSRef<EventTarget> = EventTargetCast::from_ref(self);
target.DispatchEvent(*event).ok();
if event.DefaultPrevented() {
return;
}
let node: JSRef<Node> = 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() {
match child.type_id() {
ElementNodeTypeId(HTMLInputElementTypeId) => {
let input: JSRef<HTMLInputElement> = HTMLInputElementCast::to_ref(child)
.unwrap();
input.reset()
}
// TODO HTMLKeygenElement unimplemented
//ElementNodeTypeID(HTMLKeygenElementTypeId) => {
// // Unimplemented
// {}
//}
ElementNodeTypeId(HTMLSelectElementTypeId) => {
// Unimplemented
{}
}
ElementNodeTypeId(HTMLTextAreaElementTypeId) => {
let textarea: JSRef<HTMLTextAreaElement> = HTMLTextAreaElementCast::to_ref(child)
.unwrap();
textarea.reset()
}
ElementNodeTypeId(HTMLOutputElementTypeId) => {
// Unimplemented
{}
}
_ => {}
}
};
self.marked_for_reset.set(false);
}
}
impl Reflectable for HTMLFormElement {
@ -412,7 +484,30 @@ impl<'a> FormSubmitter<'a> {
}
pub trait FormControl<'a> : Copy {
fn form_owner(self) -> Option<Temporary<HTMLFormElement>>;
// FIXME: This is wrong (https://github.com/servo/servo/issues/3553)
// but we need html5ever to do it correctly
fn form_owner(self) -> Option<Temporary<HTMLFormElement>> {
// 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<JSRef<HTMLFormElement>> = HTMLFormElementCast::to_ref(*o);
if maybe_form.is_some() {
return maybe_form.map(Temporary::from_rooted);
}
},
_ => ()
}
}
let node: JSRef<Node> = 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,
@ -426,4 +521,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);
}

View file

@ -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;
@ -27,7 +26,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;
@ -63,7 +62,9 @@ pub struct HTMLInputElement {
htmlelement: HTMLElement,
input_type: Cell<InputType>,
checked: Cell<bool>,
checked_changed: Cell<bool>,
indeterminate: Cell<bool>,
value_changed: Cell<bool>,
size: Cell<u32>,
textinput: DOMRefCell<TextInput>,
activation_state: DOMRefCell<InputActivationState>,
@ -74,6 +75,7 @@ pub struct HTMLInputElement {
struct InputActivationState {
indeterminate: bool,
checked: bool,
checked_changed: bool,
checked_radio: MutNullableJS<HTMLInputElement>,
// In case mutability changed
was_mutable: bool,
@ -86,6 +88,7 @@ impl InputActivationState {
InputActivationState {
indeterminate: false,
checked: false,
checked_changed: false,
checked_radio: Default::default(),
was_mutable: false,
old_type: InputText
@ -108,6 +111,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())
@ -128,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;
}
@ -170,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()
@ -183,6 +194,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()
@ -190,7 +207,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
@ -223,7 +240,17 @@ 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.value_changed.set(true);
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)
@ -262,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)
}
}
@ -271,8 +297,9 @@ pub trait HTMLInputElementHelpers {
fn force_relayout(self);
fn radio_group_updated(self, group: Option<&str>);
fn get_radio_group_name(self) -> Option<String>;
fn update_checked_state(self, checked: bool);
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<HTMLInputElement>, group: Option<&str>) {
@ -331,8 +358,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()
@ -346,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> {
@ -367,7 +403,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() {
@ -396,8 +435,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 {
@ -423,7 +464,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);
@ -440,8 +484,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 {
@ -493,7 +539,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),
_ => {}
}
@ -511,6 +557,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();
}
@ -528,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<Temporary<HTMLFormElement>> {
// https://html.spec.whatwg.org/multipage/forms.html#reset-the-form-owner
let elem: JSRef<Element> = 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<JSRef<HTMLFormElement>> = HTMLFormElementCast::to_ref(*o);
if maybe_form.is_some() {
return maybe_form.map(Temporary::from_rooted);
}
},
_ => ()
}
}
let node: JSRef<Node> = 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)
}
@ -562,6 +585,21 @@ 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())
}
// 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 => {
self.update_checked_state(self.DefaultChecked(), false);
self.checked_changed.set(false);
},
InputImage => (),
_ => ()
}
self.SetValue(self.DefaultValue());
self.value_changed.set(false);
}
}
@ -580,12 +618,15 @@ 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`
// 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);
},
@ -606,6 +647,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);
}
_ => ()
@ -625,12 +667,15 @@ 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
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
@ -654,6 +699,7 @@ impl<'a> Activatable for JSRef<'a, HTMLInputElement> {
},
None => self.SetChecked(false)
};
self.checked_changed.set(cache.checked_changed);
}
}
_ => ()
@ -678,6 +724,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

View file

@ -12,13 +12,14 @@ 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::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;
use dom::keyboardevent::KeyboardEvent;
use dom::node::{DisabledStateHelpers, Node, NodeHelpers, OtherNodeDamage, ElementNodeTypeId};
use dom::node::{document_from_node};
@ -124,6 +125,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)
@ -161,7 +168,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();
}
}
@ -172,7 +179,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();
}
}
@ -286,8 +295,8 @@ impl<'a> VirtualMethods for JSRef<'a, HTMLTextAreaElement> {
_ => (),
}
if child.is_text() {
self.SetValue(self.DefaultValue());
if child.is_text() && !self.value_changed.get() {
self.reset();
}
}
@ -326,3 +335,21 @@ impl Reflectable for HTMLTextAreaElement {
self.htmlelement.reflector()
}
}
impl<'a> FormControl<'a> for JSRef<'a, HTMLTextAreaElement> {
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) {
// https://html.spec.whatwg.org/multipage/forms.html#the-textarea-element:concept-form-reset-control
self.SetValue(self.DefaultValue());
self.value_changed.set(false);
}
}

View file

@ -22,7 +22,7 @@ interface HTMLFormElement : HTMLElement {
//getter (RadioNodeList or Element) (DOMString name);
void submit();
//void reset();
void reset();
//boolean checkValidity();
//boolean reportValidity();

View file

@ -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;
@ -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;

View file

@ -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;