diff --git a/src/components/layout/wrapper.rs b/src/components/layout/wrapper.rs index b0ecbbcd2fd..9d1884579da 100644 --- a/src/components/layout/wrapper.rs +++ b/src/components/layout/wrapper.rs @@ -398,6 +398,18 @@ impl<'le> TElement for LayoutElement<'le> { fn get_id(&self) -> Option { unsafe { self.element.get_attr_atom_for_layout(&namespace::Null, "id") } } + + fn get_disabled_state(&self) -> bool { + unsafe { + self.element.node.get_disabled_state_for_layout() + } + } + + fn get_enabled_state(&self) -> bool { + unsafe { + self.element.node.get_enabled_state_for_layout() + } + } } fn get_content(content_list: &content::T) -> String { diff --git a/src/components/script/dom/element.rs b/src/components/script/dom/element.rs index 04d9603bdb6..354d6a66a1b 100644 --- a/src/components/script/dom/element.rs +++ b/src/components/script/dom/element.rs @@ -257,6 +257,8 @@ pub trait AttributeHandlers { fn set_atomic_attribute(&self, name: &str, value: DOMString); // http://www.whatwg.org/html/#reflecting-content-attributes-in-idl-attributes + fn has_attribute(&self, name: &str) -> bool; + fn set_bool_attribute(&self, name: &str, value: bool); fn get_url_attribute(&self, name: &str) -> DOMString; fn set_url_attribute(&self, name: &str, value: DOMString); fn get_string_attribute(&self, name: &str) -> DOMString; @@ -385,6 +387,25 @@ impl<'a> AttributeHandlers for JSRef<'a, Element> { self.set_attribute(name, value); } + fn has_attribute(&self, name: &str) -> bool { + let name = match self.html_element_in_html_document() { + true => name.to_ascii_lower(), + false => name.to_string() + }; + self.deref().attrs.borrow().iter().map(|attr| attr.root()).any(|attr| { + name == attr.local_name && attr.namespace == Null + }) + } + + fn set_bool_attribute(&self, name: &str, value: bool) { + if self.has_attribute(name) == value { return; } + if value { + self.set_string_attribute(name, String::new()); + } else { + self.remove_attribute(Null, name); + } + } + fn get_url_attribute(&self, name: &str) -> DOMString { // XXX Resolve URL. self.get_string_attribute(name) @@ -664,7 +685,7 @@ impl<'a> ElementMethods for JSRef<'a, Element> { // http://dom.spec.whatwg.org/#dom-element-hasattribute fn HasAttribute(&self, name: DOMString) -> bool { - self.GetAttribute(name).is_some() + self.has_attribute(name.as_slice()) } // http://dom.spec.whatwg.org/#dom-element-hasattributens @@ -925,4 +946,12 @@ impl<'a> style::TElement for JSRef<'a, Element> { } }) } + fn get_disabled_state(&self) -> bool { + let node: &JSRef = NodeCast::from_ref(self); + node.get_disabled_state() + } + fn get_enabled_state(&self) -> bool { + let node: &JSRef = NodeCast::from_ref(self); + node.get_enabled_state() + } } diff --git a/src/components/script/dom/htmlanchorelement.rs b/src/components/script/dom/htmlanchorelement.rs index 5b905b1bd44..b69536a3303 100644 --- a/src/components/script/dom/htmlanchorelement.rs +++ b/src/components/script/dom/htmlanchorelement.rs @@ -72,6 +72,32 @@ impl<'a> VirtualMethods for JSRef<'a, HTMLAnchorElement> { Some(htmlelement as &VirtualMethods) } + fn after_set_attr(&self, name: DOMString, value: DOMString) { + match self.super_type() { + Some(ref s) => s.after_set_attr(name.clone(), value.clone()), + _ => (), + } + + let node: &JSRef = NodeCast::from_ref(self); + match name.as_slice() { + "href" => node.set_enabled_state(true), + _ => () + } + } + + fn before_remove_attr(&self, name: DOMString, value: DOMString) { + match self.super_type() { + Some(ref s) => s.before_remove_attr(name.clone(), value.clone()), + _ => (), + } + + let node: &JSRef = NodeCast::from_ref(self); + match name.as_slice() { + "href" => node.set_enabled_state(false), + _ => () + } + } + fn handle_event(&self, event: &JSRef) { match self.super_type() { Some(s) => { diff --git a/src/components/script/dom/htmlareaelement.rs b/src/components/script/dom/htmlareaelement.rs index 61f7890945c..339355c844f 100644 --- a/src/components/script/dom/htmlareaelement.rs +++ b/src/components/script/dom/htmlareaelement.rs @@ -4,13 +4,15 @@ use dom::bindings::codegen::Bindings::HTMLAreaElementBinding; use dom::bindings::codegen::InheritTypes::HTMLAreaElementDerived; +use dom::bindings::codegen::InheritTypes::{HTMLElementCast, NodeCast}; use dom::bindings::js::{JSRef, Temporary}; use dom::bindings::utils::{Reflectable, Reflector}; use dom::document::Document; use dom::element::HTMLAreaElementTypeId; use dom::eventtarget::{EventTarget, NodeTargetTypeId}; use dom::htmlelement::HTMLElement; -use dom::node::{Node, ElementNodeTypeId}; +use dom::node::{Node, NodeHelpers, ElementNodeTypeId}; +use dom::virtualmethods::VirtualMethods; use servo_util::str::DOMString; #[deriving(Encodable)] @@ -37,6 +39,39 @@ impl HTMLAreaElement { } } +impl<'a> VirtualMethods for JSRef<'a, HTMLAreaElement> { + fn super_type<'a>(&'a self) -> Option<&'a VirtualMethods> { + let htmlelement: &JSRef = HTMLElementCast::from_ref(self); + Some(htmlelement as &VirtualMethods) + } + + fn after_set_attr(&self, name: DOMString, value: DOMString) { + match self.super_type() { + Some(ref s) => s.after_set_attr(name.clone(), value.clone()), + _ => (), + } + + let node: &JSRef = NodeCast::from_ref(self); + match name.as_slice() { + "href" => node.set_enabled_state(true), + _ => () + } + } + + fn before_remove_attr(&self, name: DOMString, value: DOMString) { + match self.super_type() { + Some(ref s) => s.before_remove_attr(name.clone(), value.clone()), + _ => (), + } + + let node: &JSRef = NodeCast::from_ref(self); + match name.as_slice() { + "href" => node.set_enabled_state(false), + _ => () + } + } +} + impl Reflectable for HTMLAreaElement { fn reflector<'a>(&'a self) -> &'a Reflector { self.htmlelement.reflector() diff --git a/src/components/script/dom/htmlbuttonelement.rs b/src/components/script/dom/htmlbuttonelement.rs index 4e89545737b..d0fb4bb16a6 100644 --- a/src/components/script/dom/htmlbuttonelement.rs +++ b/src/components/script/dom/htmlbuttonelement.rs @@ -4,15 +4,17 @@ use dom::bindings::codegen::Bindings::HTMLButtonElementBinding; use dom::bindings::codegen::Bindings::HTMLButtonElementBinding::HTMLButtonElementMethods; -use dom::bindings::codegen::InheritTypes::HTMLButtonElementDerived; +use dom::bindings::codegen::InheritTypes::{ElementCast, HTMLElementCast, NodeCast}; +use dom::bindings::codegen::InheritTypes::{HTMLButtonElementDerived, HTMLFieldSetElementDerived}; use dom::bindings::js::{JSRef, Temporary}; use dom::bindings::utils::{Reflectable, Reflector}; use dom::document::Document; -use dom::element::HTMLButtonElementTypeId; +use dom::element::{AttributeHandlers, Element, HTMLButtonElementTypeId}; use dom::eventtarget::{EventTarget, NodeTargetTypeId}; use dom::htmlelement::HTMLElement; -use dom::node::{Node, ElementNodeTypeId, window_from_node}; +use dom::node::{DisabledStateHelpers, Node, NodeHelpers, ElementNodeTypeId, window_from_node}; use dom::validitystate::ValidityState; +use dom::virtualmethods::VirtualMethods; use servo_util::str::DOMString; #[deriving(Encodable)] @@ -44,6 +46,82 @@ impl<'a> HTMLButtonElementMethods for JSRef<'a, HTMLButtonElement> { let window = window_from_node(self).root(); ValidityState::new(&*window) } + + // http://www.whatwg.org/html/#dom-fe-disabled + fn Disabled(&self) -> bool { + let elem: &JSRef = ElementCast::from_ref(self); + elem.has_attribute("disabled") + } + + // http://www.whatwg.org/html/#dom-fe-disabled + fn SetDisabled(&self, disabled: bool) { + let elem: &JSRef = ElementCast::from_ref(self); + elem.set_bool_attribute("disabled", disabled) + } +} + +impl<'a> VirtualMethods for JSRef<'a, HTMLButtonElement> { + fn super_type<'a>(&'a self) -> Option<&'a VirtualMethods> { + let htmlelement: &JSRef = HTMLElementCast::from_ref(self); + Some(htmlelement as &VirtualMethods) + } + + fn after_set_attr(&self, name: DOMString, value: DOMString) { + match self.super_type() { + Some(ref s) => s.after_set_attr(name.clone(), value.clone()), + _ => (), + } + + let node: &JSRef = NodeCast::from_ref(self); + match name.as_slice() { + "disabled" => { + node.set_disabled_state(true); + node.set_enabled_state(false); + }, + _ => () + } + } + + fn before_remove_attr(&self, name: DOMString, value: DOMString) { + match self.super_type() { + Some(ref s) => s.before_remove_attr(name.clone(), value), + _ => (), + } + + let node: &JSRef = NodeCast::from_ref(self); + match name.as_slice() { + "disabled" => { + node.set_disabled_state(false); + node.set_enabled_state(true); + node.check_ancestors_disabled_state_for_form_control(); + }, + _ => () + } + } + + fn bind_to_tree(&self, tree_in_doc: bool) { + match self.super_type() { + Some(ref s) => s.bind_to_tree(tree_in_doc), + _ => (), + } + + let node: &JSRef = NodeCast::from_ref(self); + node.check_ancestors_disabled_state_for_form_control(); + } + + fn unbind_from_tree(&self, tree_in_doc: bool) { + match self.super_type() { + Some(ref s) => s.unbind_from_tree(tree_in_doc), + _ => (), + } + + let node: &JSRef = NodeCast::from_ref(self); + if node.ancestors().any(|ancestor| ancestor.is_htmlfieldsetelement()) { + node.check_ancestors_disabled_state_for_form_control(); + } else { + node.check_disabled_attribute(); + } + } } impl Reflectable for HTMLButtonElement { diff --git a/src/components/script/dom/htmlfieldsetelement.rs b/src/components/script/dom/htmlfieldsetelement.rs index eed51e9a86a..73001d56ddd 100644 --- a/src/components/script/dom/htmlfieldsetelement.rs +++ b/src/components/script/dom/htmlfieldsetelement.rs @@ -5,15 +5,18 @@ use dom::bindings::codegen::Bindings::HTMLFieldSetElementBinding; use dom::bindings::codegen::Bindings::HTMLFieldSetElementBinding::HTMLFieldSetElementMethods; use dom::bindings::codegen::InheritTypes::{ElementCast, HTMLFieldSetElementDerived, NodeCast}; +use dom::bindings::codegen::InheritTypes::{HTMLElementCast, HTMLLegendElementDerived}; use dom::bindings::js::{JSRef, Temporary}; use dom::bindings::utils::{Reflectable, Reflector}; use dom::document::Document; -use dom::element::{Element, HTMLFieldSetElementTypeId}; +use dom::element::{AttributeHandlers, Element, HTMLFieldSetElementTypeId, HTMLButtonElementTypeId}; +use dom::element::{HTMLInputElementTypeId, HTMLSelectElementTypeId, HTMLTextAreaElementTypeId}; use dom::eventtarget::{EventTarget, NodeTargetTypeId}; use dom::htmlcollection::{HTMLCollection, CollectionFilter}; use dom::htmlelement::HTMLElement; -use dom::node::{Node, ElementNodeTypeId, window_from_node}; +use dom::node::{DisabledStateHelpers, Node, NodeHelpers, ElementNodeTypeId, window_from_node}; use dom::validitystate::ValidityState; +use dom::virtualmethods::VirtualMethods; use servo_util::str::{DOMString, StaticStringVec}; #[deriving(Encodable)] @@ -62,6 +65,89 @@ impl<'a> HTMLFieldSetElementMethods for JSRef<'a, HTMLFieldSetElement> { let window = window_from_node(self).root(); ValidityState::new(&*window) } + + // http://www.whatwg.org/html/#dom-fieldset-disabled + fn Disabled(&self) -> bool { + let elem: &JSRef = ElementCast::from_ref(self); + elem.has_attribute("disabled") + } + + // http://www.whatwg.org/html/#dom-fieldset-disabled + fn SetDisabled(&self, disabled: bool) { + let elem: &JSRef = ElementCast::from_ref(self); + elem.set_bool_attribute("disabled", disabled) + } +} + +impl<'a> VirtualMethods for JSRef<'a, HTMLFieldSetElement> { + fn super_type<'a>(&'a self) -> Option<&'a VirtualMethods> { + let htmlelement: &JSRef = HTMLElementCast::from_ref(self); + Some(htmlelement as &VirtualMethods) + } + + fn after_set_attr(&self, name: DOMString, value: DOMString) { + match self.super_type() { + Some(ref s) => s.after_set_attr(name.clone(), value.clone()), + _ => (), + } + + let node: &JSRef = NodeCast::from_ref(self); + match name.as_slice() { + "disabled" => { + node.set_disabled_state(true); + node.set_enabled_state(false); + let maybe_legend = node.children().find(|node| node.is_htmllegendelement()); + let filtered: Vec> = node.children().filter(|child| { + maybe_legend.map_or(true, |legend| legend != *child) + }).collect(); + for descendant in filtered.iter().flat_map(|child| child.traverse_preorder()) { + match descendant.type_id() { + ElementNodeTypeId(HTMLButtonElementTypeId) | + ElementNodeTypeId(HTMLInputElementTypeId) | + ElementNodeTypeId(HTMLSelectElementTypeId) | + ElementNodeTypeId(HTMLTextAreaElementTypeId) => { + descendant.set_disabled_state(true); + descendant.set_enabled_state(false); + }, + _ => () + } + } + }, + _ => () + } + } + + fn before_remove_attr(&self, name: DOMString, value: DOMString) { + match self.super_type() { + Some(ref s) => s.before_remove_attr(name.clone(), value), + _ => (), + } + + let node: &JSRef = NodeCast::from_ref(self); + match name.as_slice() { + "disabled" => { + node.set_disabled_state(false); + node.set_enabled_state(true); + let maybe_legend = node.children().find(|node| node.is_htmllegendelement()); + let filtered: Vec> = node.children().filter(|child| { + maybe_legend.map_or(true, |legend| legend != *child) + }).collect(); + for descendant in filtered.iter().flat_map(|child| child.traverse_preorder()) { + match descendant.type_id() { + ElementNodeTypeId(HTMLButtonElementTypeId) | + ElementNodeTypeId(HTMLInputElementTypeId) | + ElementNodeTypeId(HTMLSelectElementTypeId) | + ElementNodeTypeId(HTMLTextAreaElementTypeId) => { + descendant.check_disabled_attribute(); + descendant.check_ancestors_disabled_state_for_form_control(); + }, + _ => () + } + } + }, + _ => () + } + } } impl Reflectable for HTMLFieldSetElement { diff --git a/src/components/script/dom/htmlinputelement.rs b/src/components/script/dom/htmlinputelement.rs index 1209fa11619..9701ef384be 100644 --- a/src/components/script/dom/htmlinputelement.rs +++ b/src/components/script/dom/htmlinputelement.rs @@ -3,14 +3,17 @@ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ use dom::bindings::codegen::Bindings::HTMLInputElementBinding; -use dom::bindings::codegen::InheritTypes::HTMLInputElementDerived; +use dom::bindings::codegen::Bindings::HTMLInputElementBinding::HTMLInputElementMethods; +use dom::bindings::codegen::InheritTypes::{ElementCast, HTMLElementCast, NodeCast}; +use dom::bindings::codegen::InheritTypes::{HTMLInputElementDerived, HTMLFieldSetElementDerived}; use dom::bindings::js::{JSRef, Temporary}; use dom::bindings::utils::{Reflectable, Reflector}; use dom::document::Document; -use dom::element::HTMLInputElementTypeId; +use dom::element::{AttributeHandlers, Element, HTMLInputElementTypeId}; use dom::eventtarget::{EventTarget, NodeTargetTypeId}; use dom::htmlelement::HTMLElement; -use dom::node::{Node, ElementNodeTypeId}; +use dom::node::{DisabledStateHelpers, Node, NodeHelpers, ElementNodeTypeId}; +use dom::virtualmethods::VirtualMethods; use servo_util::str::DOMString; #[deriving(Encodable)] @@ -37,6 +40,84 @@ impl HTMLInputElement { } } +impl<'a> HTMLInputElementMethods for JSRef<'a, HTMLInputElement> { + // http://www.whatwg.org/html/#dom-fe-disabled + fn Disabled(&self) -> bool { + let elem: &JSRef = ElementCast::from_ref(self); + elem.has_attribute("disabled") + } + + // http://www.whatwg.org/html/#dom-fe-disabled + fn SetDisabled(&self, disabled: bool) { + let elem: &JSRef = ElementCast::from_ref(self); + elem.set_bool_attribute("disabled", disabled) + } +} + +impl<'a> VirtualMethods for JSRef<'a, HTMLInputElement> { + fn super_type<'a>(&'a self) -> Option<&'a VirtualMethods> { + let htmlelement: &JSRef = HTMLElementCast::from_ref(self); + Some(htmlelement as &VirtualMethods) + } + + fn after_set_attr(&self, name: DOMString, value: DOMString) { + match self.super_type() { + Some(ref s) => s.after_set_attr(name.clone(), value.clone()), + _ => (), + } + + let node: &JSRef = NodeCast::from_ref(self); + match name.as_slice() { + "disabled" => { + node.set_disabled_state(true); + node.set_enabled_state(false); + }, + _ => () + } + } + + fn before_remove_attr(&self, name: DOMString, value: DOMString) { + match self.super_type() { + Some(ref s) => s.before_remove_attr(name.clone(), value), + _ => (), + } + + let node: &JSRef = NodeCast::from_ref(self); + match name.as_slice() { + "disabled" => { + node.set_disabled_state(false); + node.set_enabled_state(true); + node.check_ancestors_disabled_state_for_form_control(); + }, + _ => () + } + } + + fn bind_to_tree(&self, tree_in_doc: bool) { + match self.super_type() { + Some(ref s) => s.bind_to_tree(tree_in_doc), + _ => (), + } + + let node: &JSRef = NodeCast::from_ref(self); + node.check_ancestors_disabled_state_for_form_control(); + } + + fn unbind_from_tree(&self, tree_in_doc: bool) { + match self.super_type() { + Some(ref s) => s.unbind_from_tree(tree_in_doc), + _ => (), + } + + let node: &JSRef = NodeCast::from_ref(self); + if node.ancestors().any(|ancestor| ancestor.is_htmlfieldsetelement()) { + node.check_ancestors_disabled_state_for_form_control(); + } else { + node.check_disabled_attribute(); + } + } +} + impl Reflectable for HTMLInputElement { fn reflector<'a>(&'a self) -> &'a Reflector { self.htmlelement.reflector() diff --git a/src/components/script/dom/htmllinkelement.rs b/src/components/script/dom/htmllinkelement.rs index c10c74adc89..3d3a46d28c4 100644 --- a/src/components/script/dom/htmllinkelement.rs +++ b/src/components/script/dom/htmllinkelement.rs @@ -4,13 +4,15 @@ use dom::bindings::codegen::Bindings::HTMLLinkElementBinding; use dom::bindings::codegen::InheritTypes::HTMLLinkElementDerived; +use dom::bindings::codegen::InheritTypes::{HTMLElementCast, NodeCast}; use dom::bindings::js::{JSRef, Temporary}; use dom::bindings::utils::{Reflectable, Reflector}; use dom::document::Document; use dom::element::HTMLLinkElementTypeId; use dom::eventtarget::{EventTarget, NodeTargetTypeId}; use dom::htmlelement::HTMLElement; -use dom::node::{Node, ElementNodeTypeId}; +use dom::node::{Node, NodeHelpers, ElementNodeTypeId}; +use dom::virtualmethods::VirtualMethods; use servo_util::str::DOMString; #[deriving(Encodable)] @@ -37,6 +39,39 @@ impl HTMLLinkElement { } } +impl<'a> VirtualMethods for JSRef<'a, HTMLLinkElement> { + fn super_type<'a>(&'a self) -> Option<&'a VirtualMethods> { + let htmlelement: &JSRef = HTMLElementCast::from_ref(self); + Some(htmlelement as &VirtualMethods) + } + + fn after_set_attr(&self, name: DOMString, value: DOMString) { + match self.super_type() { + Some(ref s) => s.after_set_attr(name.clone(), value.clone()), + _ => (), + } + + let node: &JSRef = NodeCast::from_ref(self); + match name.as_slice() { + "href" => node.set_enabled_state(true), + _ => () + } + } + + fn before_remove_attr(&self, name: DOMString, value: DOMString) { + match self.super_type() { + Some(ref s) => s.before_remove_attr(name.clone(), value.clone()), + _ => (), + } + + let node: &JSRef = NodeCast::from_ref(self); + match name.as_slice() { + "href" => node.set_enabled_state(false), + _ => () + } + } +} + impl Reflectable for HTMLLinkElement { fn reflector<'a>(&'a self) -> &'a Reflector { self.htmlelement.reflector() diff --git a/src/components/script/dom/htmloptgroupelement.rs b/src/components/script/dom/htmloptgroupelement.rs index 8f3be20a432..4eda6938610 100644 --- a/src/components/script/dom/htmloptgroupelement.rs +++ b/src/components/script/dom/htmloptgroupelement.rs @@ -3,14 +3,17 @@ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ use dom::bindings::codegen::Bindings::HTMLOptGroupElementBinding; -use dom::bindings::codegen::InheritTypes::HTMLOptGroupElementDerived; +use dom::bindings::codegen::Bindings::HTMLOptGroupElementBinding::HTMLOptGroupElementMethods; +use dom::bindings::codegen::InheritTypes::{ElementCast, HTMLElementCast, NodeCast}; +use dom::bindings::codegen::InheritTypes::{HTMLOptGroupElementDerived, HTMLOptionElementDerived}; use dom::bindings::js::{JSRef, Temporary}; use dom::bindings::utils::{Reflectable, Reflector}; use dom::document::Document; -use dom::element::HTMLOptGroupElementTypeId; +use dom::element::{AttributeHandlers, Element, HTMLOptGroupElementTypeId}; use dom::eventtarget::{EventTarget, NodeTargetTypeId}; use dom::htmlelement::HTMLElement; -use dom::node::{Node, ElementNodeTypeId}; +use dom::node::{DisabledStateHelpers, Node, NodeHelpers, ElementNodeTypeId}; +use dom::virtualmethods::VirtualMethods; use servo_util::str::DOMString; #[deriving(Encodable)] @@ -37,6 +40,66 @@ impl HTMLOptGroupElement { } } +impl<'a> HTMLOptGroupElementMethods for JSRef<'a, HTMLOptGroupElement> { + // http://www.whatwg.org/html#dom-optgroup-disabled + fn Disabled(&self) -> bool { + let elem: &JSRef = ElementCast::from_ref(self); + elem.has_attribute("disabled") + } + + // http://www.whatwg.org/html#dom-optgroup-disabled + fn SetDisabled(&self, disabled: bool) { + let elem: &JSRef = ElementCast::from_ref(self); + elem.set_bool_attribute("disabled", disabled) + } +} + +impl<'a> VirtualMethods for JSRef<'a, HTMLOptGroupElement> { + fn super_type<'a>(&'a self) -> Option<&'a VirtualMethods> { + let htmlelement: &JSRef = HTMLElementCast::from_ref(self); + Some(htmlelement as &VirtualMethods) + } + + fn after_set_attr(&self, name: DOMString, value: DOMString) { + match self.super_type() { + Some(ref s) => s.after_set_attr(name.clone(), value.clone()), + _ => (), + } + + let node: &JSRef = NodeCast::from_ref(self); + match name.as_slice() { + "disabled" => { + node.set_disabled_state(true); + node.set_enabled_state(false); + for child in node.children().filter(|child| child.is_htmloptionelement()) { + child.set_disabled_state(true); + child.set_enabled_state(false); + } + }, + _ => () + } + } + + fn before_remove_attr(&self, name: DOMString, value: DOMString) { + match self.super_type() { + Some(ref s) => s.before_remove_attr(name.clone(), value), + _ => (), + } + + let node: &JSRef = NodeCast::from_ref(self); + match name.as_slice() { + "disabled" => { + node.set_disabled_state(false); + node.set_enabled_state(true); + for child in node.children().filter(|child| child.is_htmloptionelement()) { + child.check_disabled_attribute(); + } + }, + _ => () + } + } +} + impl Reflectable for HTMLOptGroupElement { fn reflector<'a>(&'a self) -> &'a Reflector { self.htmlelement.reflector() diff --git a/src/components/script/dom/htmloptionelement.rs b/src/components/script/dom/htmloptionelement.rs index 68b1422412a..a0c4f9e4067 100644 --- a/src/components/script/dom/htmloptionelement.rs +++ b/src/components/script/dom/htmloptionelement.rs @@ -3,14 +3,17 @@ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ use dom::bindings::codegen::Bindings::HTMLOptionElementBinding; +use dom::bindings::codegen::Bindings::HTMLOptionElementBinding::HTMLOptionElementMethods; +use dom::bindings::codegen::InheritTypes::{ElementCast, HTMLElementCast, NodeCast}; use dom::bindings::codegen::InheritTypes::HTMLOptionElementDerived; use dom::bindings::js::{JSRef, Temporary}; use dom::bindings::utils::{Reflectable, Reflector}; use dom::document::Document; -use dom::element::HTMLOptionElementTypeId; +use dom::element::{AttributeHandlers, Element, HTMLOptionElementTypeId}; use dom::eventtarget::{EventTarget, NodeTargetTypeId}; use dom::htmlelement::HTMLElement; -use dom::node::{Node, ElementNodeTypeId}; +use dom::node::{DisabledStateHelpers, Node, NodeHelpers, ElementNodeTypeId}; +use dom::virtualmethods::VirtualMethods; use servo_util::str::DOMString; #[deriving(Encodable)] @@ -37,6 +40,84 @@ impl HTMLOptionElement { } } +impl<'a> HTMLOptionElementMethods for JSRef<'a, HTMLOptionElement> { + // http://www.whatwg.org/html/#dom-option-disabled + fn Disabled(&self) -> bool { + let elem: &JSRef = ElementCast::from_ref(self); + elem.has_attribute("disabled") + } + + // http://www.whatwg.org/html/#dom-option-disabled + fn SetDisabled(&self, disabled: bool) { + let elem: &JSRef = ElementCast::from_ref(self); + elem.set_bool_attribute("disabled", disabled) + } +} + +impl<'a> VirtualMethods for JSRef<'a, HTMLOptionElement> { + fn super_type<'a>(&'a self) -> Option<&'a VirtualMethods> { + let htmlelement: &JSRef = HTMLElementCast::from_ref(self); + Some(htmlelement as &VirtualMethods) + } + + fn after_set_attr(&self, name: DOMString, value: DOMString) { + match self.super_type() { + Some(ref s) => s.after_set_attr(name.clone(), value.clone()), + _ => (), + } + + let node: &JSRef = NodeCast::from_ref(self); + match name.as_slice() { + "disabled" => { + node.set_disabled_state(true); + node.set_enabled_state(false); + }, + _ => () + } + } + + fn before_remove_attr(&self, name: DOMString, value: DOMString) { + match self.super_type() { + Some(ref s) => s.before_remove_attr(name.clone(), value), + _ => (), + } + + let node: &JSRef = NodeCast::from_ref(self); + match name.as_slice() { + "disabled" => { + node.set_disabled_state(false); + node.set_enabled_state(true); + node.check_parent_disabled_state_for_option(); + }, + _ => () + } + } + + fn bind_to_tree(&self, tree_in_doc: bool) { + match self.super_type() { + Some(ref s) => s.bind_to_tree(tree_in_doc), + _ => (), + } + + let node: &JSRef = NodeCast::from_ref(self); + node.check_parent_disabled_state_for_option(); + } + + fn unbind_from_tree(&self, tree_in_doc: bool) { + match self.super_type() { + Some(ref s) => s.unbind_from_tree(tree_in_doc), + _ => (), + } + + let node: &JSRef = NodeCast::from_ref(self); + if node.parent_node().is_some() { + node.check_parent_disabled_state_for_option(); + } else { + node.check_disabled_attribute(); + } + } +} + impl Reflectable for HTMLOptionElement { fn reflector<'a>(&'a self) -> &'a Reflector { self.htmlelement.reflector() diff --git a/src/components/script/dom/htmlselectelement.rs b/src/components/script/dom/htmlselectelement.rs index 3170006bbbd..518bcbeb03c 100644 --- a/src/components/script/dom/htmlselectelement.rs +++ b/src/components/script/dom/htmlselectelement.rs @@ -4,17 +4,19 @@ use dom::bindings::codegen::Bindings::HTMLSelectElementBinding; use dom::bindings::codegen::Bindings::HTMLSelectElementBinding::HTMLSelectElementMethods; -use dom::bindings::codegen::InheritTypes::HTMLSelectElementDerived; +use dom::bindings::codegen::InheritTypes::{ElementCast, HTMLElementCast, NodeCast}; +use dom::bindings::codegen::InheritTypes::{HTMLSelectElementDerived, HTMLFieldSetElementDerived}; use dom::bindings::codegen::UnionTypes::HTMLElementOrLong::HTMLElementOrLong; use dom::bindings::codegen::UnionTypes::HTMLOptionElementOrHTMLOptGroupElement::HTMLOptionElementOrHTMLOptGroupElement; use dom::bindings::js::{JSRef, Temporary}; use dom::bindings::utils::{Reflectable, Reflector}; use dom::document::Document; -use dom::element::HTMLSelectElementTypeId; +use dom::element::{AttributeHandlers, Element, HTMLSelectElementTypeId}; use dom::eventtarget::{EventTarget, NodeTargetTypeId}; use dom::htmlelement::HTMLElement; -use dom::node::{Node, ElementNodeTypeId, window_from_node}; +use dom::node::{DisabledStateHelpers, Node, NodeHelpers, ElementNodeTypeId, window_from_node}; use dom::validitystate::ValidityState; +use dom::virtualmethods::VirtualMethods; use servo_util::str::DOMString; #[deriving(Encodable)] @@ -50,6 +52,82 @@ impl<'a> HTMLSelectElementMethods for JSRef<'a, HTMLSelectElement> { // Note: this function currently only exists for test_union.html. fn Add(&self, _element: HTMLOptionElementOrHTMLOptGroupElement, _before: Option) { } + + // http://www.whatwg.org/html/#dom-fe-disabled + fn Disabled(&self) -> bool { + let elem: &JSRef = ElementCast::from_ref(self); + elem.has_attribute("disabled") + } + + // http://www.whatwg.org/html/#dom-fe-disabled + fn SetDisabled(&self, disabled: bool) { + let elem: &JSRef = ElementCast::from_ref(self); + elem.set_bool_attribute("disabled", disabled) + } +} + +impl<'a> VirtualMethods for JSRef<'a, HTMLSelectElement> { + fn super_type<'a>(&'a self) -> Option<&'a VirtualMethods> { + let htmlelement: &JSRef = HTMLElementCast::from_ref(self); + Some(htmlelement as &VirtualMethods) + } + + fn after_set_attr(&self, name: DOMString, value: DOMString) { + match self.super_type() { + Some(ref s) => s.after_set_attr(name.clone(), value.clone()), + _ => (), + } + + let node: &JSRef = NodeCast::from_ref(self); + match name.as_slice() { + "disabled" => { + node.set_disabled_state(true); + node.set_enabled_state(false); + }, + _ => () + } + } + + fn before_remove_attr(&self, name: DOMString, value: DOMString) { + match self.super_type() { + Some(ref s) => s.before_remove_attr(name.clone(), value), + _ => (), + } + + let node: &JSRef = NodeCast::from_ref(self); + match name.as_slice() { + "disabled" => { + node.set_disabled_state(false); + node.set_enabled_state(true); + node.check_ancestors_disabled_state_for_form_control(); + }, + _ => () + } + } + + fn bind_to_tree(&self, tree_in_doc: bool) { + match self.super_type() { + Some(ref s) => s.bind_to_tree(tree_in_doc), + _ => (), + } + + let node: &JSRef = NodeCast::from_ref(self); + node.check_ancestors_disabled_state_for_form_control(); + } + + fn unbind_from_tree(&self, tree_in_doc: bool) { + match self.super_type() { + Some(ref s) => s.unbind_from_tree(tree_in_doc), + _ => (), + } + + let node: &JSRef = NodeCast::from_ref(self); + if node.ancestors().any(|ancestor| ancestor.is_htmlfieldsetelement()) { + node.check_ancestors_disabled_state_for_form_control(); + } else { + node.check_disabled_attribute(); + } + } } impl Reflectable for HTMLSelectElement { diff --git a/src/components/script/dom/htmltextareaelement.rs b/src/components/script/dom/htmltextareaelement.rs index f4170a20f1f..ac96c0789eb 100644 --- a/src/components/script/dom/htmltextareaelement.rs +++ b/src/components/script/dom/htmltextareaelement.rs @@ -3,14 +3,17 @@ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ use dom::bindings::codegen::Bindings::HTMLTextAreaElementBinding; -use dom::bindings::codegen::InheritTypes::HTMLTextAreaElementDerived; +use dom::bindings::codegen::Bindings::HTMLTextAreaElementBinding::HTMLTextAreaElementMethods; +use dom::bindings::codegen::InheritTypes::{ElementCast, HTMLElementCast, NodeCast}; +use dom::bindings::codegen::InheritTypes::{HTMLTextAreaElementDerived, HTMLFieldSetElementDerived}; use dom::bindings::js::{JSRef, Temporary}; use dom::bindings::utils::{Reflectable, Reflector}; use dom::document::Document; -use dom::element::HTMLTextAreaElementTypeId; +use dom::element::{AttributeHandlers, Element, HTMLTextAreaElementTypeId}; use dom::eventtarget::{EventTarget, NodeTargetTypeId}; use dom::htmlelement::HTMLElement; -use dom::node::{Node, ElementNodeTypeId}; +use dom::node::{DisabledStateHelpers, Node, NodeHelpers, ElementNodeTypeId}; +use dom::virtualmethods::VirtualMethods; use servo_util::str::DOMString; #[deriving(Encodable)] @@ -37,6 +40,84 @@ impl HTMLTextAreaElement { } } +impl<'a> HTMLTextAreaElementMethods for JSRef<'a, HTMLTextAreaElement> { + // http://www.whatwg.org/html/#dom-fe-disabled + fn Disabled(&self) -> bool { + let elem: &JSRef = ElementCast::from_ref(self); + elem.has_attribute("disabled") + } + + // http://www.whatwg.org/html/#dom-fe-disabled + fn SetDisabled(&self, disabled: bool) { + let elem: &JSRef = ElementCast::from_ref(self); + elem.set_bool_attribute("disabled", disabled) + } +} + +impl<'a> VirtualMethods for JSRef<'a, HTMLTextAreaElement> { + fn super_type<'a>(&'a self) -> Option<&'a VirtualMethods> { + let htmlelement: &JSRef = HTMLElementCast::from_ref(self); + Some(htmlelement as &VirtualMethods) + } + + fn after_set_attr(&self, name: DOMString, value: DOMString) { + match self.super_type() { + Some(ref s) => s.after_set_attr(name.clone(), value.clone()), + _ => (), + } + + let node: &JSRef = NodeCast::from_ref(self); + match name.as_slice() { + "disabled" => { + node.set_disabled_state(true); + node.set_enabled_state(false); + }, + _ => () + } + } + + fn before_remove_attr(&self, name: DOMString, value: DOMString) { + match self.super_type() { + Some(ref s) => s.before_remove_attr(name.clone(), value), + _ => (), + } + + let node: &JSRef = NodeCast::from_ref(self); + match name.as_slice() { + "disabled" => { + node.set_disabled_state(false); + node.set_enabled_state(true); + node.check_ancestors_disabled_state_for_form_control(); + }, + _ => () + } + } + + fn bind_to_tree(&self, tree_in_doc: bool) { + match self.super_type() { + Some(ref s) => s.bind_to_tree(tree_in_doc), + _ => (), + } + + let node: &JSRef = NodeCast::from_ref(self); + node.check_ancestors_disabled_state_for_form_control(); + } + + fn unbind_from_tree(&self, tree_in_doc: bool) { + match self.super_type() { + Some(ref s) => s.unbind_from_tree(tree_in_doc), + _ => (), + } + + let node: &JSRef = NodeCast::from_ref(self); + if node.ancestors().any(|ancestor| ancestor.is_htmlfieldsetelement()) { + node.check_ancestors_disabled_state_for_form_control(); + } else { + node.check_disabled_attribute(); + } + } +} + impl Reflectable for HTMLTextAreaElement { fn reflector<'a>(&'a self) -> &'a Reflector { self.htmlelement.reflector() diff --git a/src/components/script/dom/node.rs b/src/components/script/dom/node.rs index a73c4d7e879..8b9f6ce3061 100644 --- a/src/components/script/dom/node.rs +++ b/src/components/script/dom/node.rs @@ -16,6 +16,8 @@ use dom::bindings::codegen::InheritTypes::{CommentCast, DocumentCast, DocumentTy use dom::bindings::codegen::InheritTypes::{ElementCast, TextCast, NodeCast, ElementDerived}; use dom::bindings::codegen::InheritTypes::{CharacterDataCast, NodeBase, NodeDerived}; use dom::bindings::codegen::InheritTypes::{ProcessingInstructionCast, EventTargetCast}; +use dom::bindings::codegen::InheritTypes::{HTMLLegendElementDerived, HTMLFieldSetElementDerived}; +use dom::bindings::codegen::InheritTypes::HTMLOptGroupElementDerived; use dom::bindings::error::{ErrorResult, Fallible, NotFound, HierarchyRequest, Syntax}; use dom::bindings::global::{GlobalRef, Window}; use dom::bindings::js::{JS, JSRef, RootedReference, Temporary, Root, OptionalUnrootable}; @@ -30,7 +32,10 @@ use dom::document::{Document, DocumentHelpers, HTMLDocument, NonHTMLDocument}; use dom::documentfragment::DocumentFragment; use dom::documenttype::DocumentType; use dom::element::{AttributeHandlers, Element, ElementTypeId}; -use dom::element::{HTMLAnchorElementTypeId, ElementHelpers}; +use dom::element::{HTMLAnchorElementTypeId, HTMLButtonElementTypeId, ElementHelpers}; +use dom::element::{HTMLInputElementTypeId, HTMLSelectElementTypeId}; +use dom::element::{HTMLTextAreaElementTypeId, HTMLOptGroupElementTypeId}; +use dom::element::{HTMLOptionElementTypeId, HTMLFieldSetElementTypeId}; use dom::eventtarget::{EventTarget, NodeTargetTypeId}; use dom::nodelist::{NodeList}; use dom::processinginstruction::ProcessingInstruction; @@ -123,8 +128,12 @@ bitflags! { flags NodeFlags: u8 { #[doc = "Specifies whether this node is in a document."] static IsInDoc = 0x01, - #[doc = "Specifies whether this node is hover state for this node"] - static InHoverState = 0x02 + #[doc = "Specifies whether this node is in hover state."] + static InHoverState = 0x02, + #[doc = "Specifies whether this node is in disabled state."] + static InDisabledState = 0x04, + #[doc = "Specifies whether this node is in enabled state."] + static InEnabledState = 0x08 } } @@ -383,6 +392,12 @@ pub trait NodeHelpers { fn get_hover_state(&self) -> bool; fn set_hover_state(&self, state: bool); + fn get_disabled_state(&self) -> bool; + fn set_disabled_state(&self, state: bool); + + fn get_enabled_state(&self) -> bool; + fn set_enabled_state(&self, state: bool); + fn dump(&self); fn dump_indent(&self, indent: uint); fn debug_str(&self) -> String; @@ -500,6 +515,30 @@ impl<'a> NodeHelpers for JSRef<'a, Node> { } } + fn get_disabled_state(&self) -> bool { + self.flags.deref().borrow().contains(InDisabledState) + } + + fn set_disabled_state(&self, state: bool) { + if state { + self.flags.deref().borrow_mut().insert(InDisabledState); + } else { + self.flags.deref().borrow_mut().remove(InDisabledState); + } + } + + fn get_enabled_state(&self) -> bool { + self.flags.deref().borrow().contains(InEnabledState) + } + + fn set_enabled_state(&self, state: bool) { + if state { + self.flags.deref().borrow_mut().insert(InEnabledState); + } else { + self.flags.deref().borrow_mut().remove(InEnabledState); + } + } + /// Iterates over this node and all its descendants, in preorder. fn traverse_preorder<'a>(&'a self) -> TreeIterator<'a> { let mut nodes = vec!(); @@ -728,12 +767,20 @@ impl LayoutNodeHelpers for JS { pub trait RawLayoutNodeHelpers { unsafe fn get_hover_state_for_layout(&self) -> bool; + unsafe fn get_disabled_state_for_layout(&self) -> bool; + unsafe fn get_enabled_state_for_layout(&self) -> bool; } impl RawLayoutNodeHelpers for Node { unsafe fn get_hover_state_for_layout(&self) -> bool { self.flags.deref().borrow().contains(InHoverState) } + unsafe fn get_disabled_state_for_layout(&self) -> bool { + self.flags.deref().borrow().contains(InDisabledState) + } + unsafe fn get_enabled_state_for_layout(&self) -> bool { + self.flags.deref().borrow().contains(InEnabledState) + } } @@ -926,7 +973,7 @@ impl Node { } fn new_(type_id: NodeTypeId, doc: Option>) -> Node { - Node { + let node = Node { eventtarget: EventTarget::new_inherited(NodeTargetTypeId(type_id)), type_id: type_id, @@ -941,7 +988,22 @@ impl Node { flags: Traceable::new(RefCell::new(NodeFlags::new(type_id))), layout_data: LayoutDataRef::new(), + }; + match type_id { + // The following elements are enabled by default. + ElementNodeTypeId(HTMLButtonElementTypeId) | + ElementNodeTypeId(HTMLInputElementTypeId) | + ElementNodeTypeId(HTMLSelectElementTypeId) | + ElementNodeTypeId(HTMLTextAreaElementTypeId) | + ElementNodeTypeId(HTMLOptGroupElementTypeId) | + ElementNodeTypeId(HTMLOptionElementTypeId) | + //ElementNodeTypeId(HTMLMenuItemElementTypeId) | + ElementNodeTypeId(HTMLFieldSetElementTypeId) => { + node.flags.deref().borrow_mut().insert(InEnabledState); + }, + _ => () } + node } // http://dom.spec.whatwg.org/#concept-node-adopt @@ -1966,3 +2028,51 @@ impl<'a> style::TNode> for JSRef<'a, Node> { } } } + +pub trait DisabledStateHelpers { + fn check_ancestors_disabled_state_for_form_control(&self); + fn check_parent_disabled_state_for_option(&self); + fn check_disabled_attribute(&self); +} + +impl<'a> DisabledStateHelpers for JSRef<'a, Node> { + fn check_ancestors_disabled_state_for_form_control(&self) { + if self.get_disabled_state() { return; } + for ancestor in self.ancestors().filter(|ancestor| ancestor.is_htmlfieldsetelement()) { + if !ancestor.get_disabled_state() { continue; } + if ancestor.is_parent_of(self) { + self.set_disabled_state(true); + self.set_enabled_state(false); + return; + } + match ancestor.children().find(|child| child.is_htmllegendelement()) { + Some(ref legend) => { + // XXXabinader: should we save previous ancestor to avoid this iteration? + if self.ancestors().any(|ancestor| ancestor == *legend) { continue; } + }, + None => () + } + self.set_disabled_state(true); + self.set_enabled_state(false); + return; + } + } + + fn check_parent_disabled_state_for_option(&self) { + if self.get_disabled_state() { return; } + match self.parent_node().root() { + Some(ref parent) if parent.is_htmloptgroupelement() && parent.get_disabled_state() => { + self.set_disabled_state(true); + self.set_enabled_state(false); + }, + _ => () + } + } + + fn check_disabled_attribute(&self) { + let elem: &JSRef<'a, Element> = ElementCast::to_ref(self).unwrap(); + let has_disabled_attrib = elem.has_attribute("disabled"); + self.set_disabled_state(has_disabled_attrib); + self.set_enabled_state(!has_disabled_attrib); + } +} diff --git a/src/components/script/dom/virtualmethods.rs b/src/components/script/dom/virtualmethods.rs index 042420d7479..c050d2e5449 100644 --- a/src/components/script/dom/virtualmethods.rs +++ b/src/components/script/dom/virtualmethods.rs @@ -5,28 +5,59 @@ use dom::attr::{AttrValue, StringAttrValue}; use dom::bindings::codegen::InheritTypes::ElementCast; use dom::bindings::codegen::InheritTypes::HTMLAnchorElementCast; +use dom::bindings::codegen::InheritTypes::HTMLAreaElementCast; use dom::bindings::codegen::InheritTypes::HTMLBodyElementCast; +use dom::bindings::codegen::InheritTypes::HTMLButtonElementCast; use dom::bindings::codegen::InheritTypes::HTMLCanvasElementCast; use dom::bindings::codegen::InheritTypes::HTMLElementCast; +use dom::bindings::codegen::InheritTypes::HTMLFieldSetElementCast; use dom::bindings::codegen::InheritTypes::HTMLIFrameElementCast; use dom::bindings::codegen::InheritTypes::HTMLImageElementCast; +use dom::bindings::codegen::InheritTypes::HTMLInputElementCast; +use dom::bindings::codegen::InheritTypes::HTMLLinkElementCast; use dom::bindings::codegen::InheritTypes::HTMLObjectElementCast; +use dom::bindings::codegen::InheritTypes::HTMLOptGroupElementCast; +use dom::bindings::codegen::InheritTypes::HTMLOptionElementCast; +use dom::bindings::codegen::InheritTypes::HTMLSelectElementCast; use dom::bindings::codegen::InheritTypes::HTMLStyleElementCast; +use dom::bindings::codegen::InheritTypes::HTMLTextAreaElementCast; use dom::bindings::js::JSRef; use dom::element::Element; -use dom::element::{ElementTypeId, HTMLAnchorElementTypeId}; -use dom::element::{HTMLBodyElementTypeId, HTMLCanvasElementTypeId}; -use dom::element::{HTMLIFrameElementTypeId, HTMLImageElementTypeId}; -use dom::element::{HTMLObjectElementTypeId, HTMLStyleElementTypeId}; +use dom::element::ElementTypeId; +use dom::element::HTMLAnchorElementTypeId; +use dom::element::HTMLAreaElementTypeId; +use dom::element::HTMLBodyElementTypeId; +use dom::element::HTMLButtonElementTypeId; +use dom::element::HTMLCanvasElementTypeId; +use dom::element::HTMLFieldSetElementTypeId; +use dom::element::HTMLIFrameElementTypeId; +use dom::element::HTMLImageElementTypeId; +use dom::element::HTMLInputElementTypeId; +use dom::element::HTMLLinkElementTypeId; +use dom::element::HTMLObjectElementTypeId; +use dom::element::HTMLOptGroupElementTypeId; +use dom::element::HTMLOptionElementTypeId; +use dom::element::HTMLSelectElementTypeId; +use dom::element::HTMLStyleElementTypeId; +use dom::element::HTMLTextAreaElementTypeId; use dom::event::Event; use dom::htmlanchorelement::HTMLAnchorElement; +use dom::htmlareaelement::HTMLAreaElement; use dom::htmlbodyelement::HTMLBodyElement; +use dom::htmlbuttonelement::HTMLButtonElement; use dom::htmlcanvaselement::HTMLCanvasElement; use dom::htmlelement::HTMLElement; +use dom::htmlfieldsetelement::HTMLFieldSetElement; use dom::htmliframeelement::HTMLIFrameElement; use dom::htmlimageelement::HTMLImageElement; +use dom::htmlinputelement::HTMLInputElement; +use dom::htmllinkelement::HTMLLinkElement; use dom::htmlobjectelement::HTMLObjectElement; +use dom::htmloptgroupelement::HTMLOptGroupElement; +use dom::htmloptionelement::HTMLOptionElement; +use dom::htmlselectelement::HTMLSelectElement; use dom::htmlstyleelement::HTMLStyleElement; +use dom::htmltextareaelement::HTMLTextAreaElement; use dom::node::{Node, NodeHelpers, ElementNodeTypeId}; use servo_util::str::DOMString; @@ -111,14 +142,26 @@ pub fn vtable_for<'a>(node: &'a JSRef) -> &'a VirtualMethods { let element: &JSRef = HTMLAnchorElementCast::to_ref(node).unwrap(); element as &VirtualMethods } + ElementNodeTypeId(HTMLAreaElementTypeId) => { + let element: &JSRef = HTMLAreaElementCast::to_ref(node).unwrap(); + element as &VirtualMethods + } ElementNodeTypeId(HTMLBodyElementTypeId) => { let element: &JSRef = HTMLBodyElementCast::to_ref(node).unwrap(); element as &VirtualMethods } + ElementNodeTypeId(HTMLButtonElementTypeId) => { + let element: &JSRef = HTMLButtonElementCast::to_ref(node).unwrap(); + element as &VirtualMethods + } ElementNodeTypeId(HTMLCanvasElementTypeId) => { let element: &JSRef = HTMLCanvasElementCast::to_ref(node).unwrap(); element as &VirtualMethods } + ElementNodeTypeId(HTMLFieldSetElementTypeId) => { + let element: &JSRef = HTMLFieldSetElementCast::to_ref(node).unwrap(); + element as &VirtualMethods + } ElementNodeTypeId(HTMLImageElementTypeId) => { let element: &JSRef = HTMLImageElementCast::to_ref(node).unwrap(); element as &VirtualMethods @@ -127,14 +170,38 @@ pub fn vtable_for<'a>(node: &'a JSRef) -> &'a VirtualMethods { let element: &JSRef = HTMLIFrameElementCast::to_ref(node).unwrap(); element as &VirtualMethods } + ElementNodeTypeId(HTMLInputElementTypeId) => { + let element: &JSRef = HTMLInputElementCast::to_ref(node).unwrap(); + element as &VirtualMethods + } + ElementNodeTypeId(HTMLLinkElementTypeId) => { + let element: &JSRef = HTMLLinkElementCast::to_ref(node).unwrap(); + element as &VirtualMethods + } ElementNodeTypeId(HTMLObjectElementTypeId) => { let element: &JSRef = HTMLObjectElementCast::to_ref(node).unwrap(); element as &VirtualMethods } + ElementNodeTypeId(HTMLOptGroupElementTypeId) => { + let element: &JSRef = HTMLOptGroupElementCast::to_ref(node).unwrap(); + element as &VirtualMethods + } + ElementNodeTypeId(HTMLOptionElementTypeId) => { + let element: &JSRef = HTMLOptionElementCast::to_ref(node).unwrap(); + element as &VirtualMethods + } + ElementNodeTypeId(HTMLSelectElementTypeId) => { + let element: &JSRef = HTMLSelectElementCast::to_ref(node).unwrap(); + element as &VirtualMethods + } ElementNodeTypeId(HTMLStyleElementTypeId) => { let element: &JSRef = HTMLStyleElementCast::to_ref(node).unwrap(); element as &VirtualMethods } + ElementNodeTypeId(HTMLTextAreaElementTypeId) => { + let element: &JSRef = HTMLTextAreaElementCast::to_ref(node).unwrap(); + element as &VirtualMethods + } ElementNodeTypeId(ElementTypeId) => { let element: &JSRef = ElementCast::to_ref(node).unwrap(); element as &VirtualMethods diff --git a/src/components/script/dom/webidls/HTMLButtonElement.webidl b/src/components/script/dom/webidls/HTMLButtonElement.webidl index 6425ed3e119..ad21e11370f 100644 --- a/src/components/script/dom/webidls/HTMLButtonElement.webidl +++ b/src/components/script/dom/webidls/HTMLButtonElement.webidl @@ -6,7 +6,7 @@ // http://www.whatwg.org/html/#htmlbuttonelement interface HTMLButtonElement : HTMLElement { // attribute boolean autofocus; - // attribute boolean disabled; + attribute boolean disabled; //readonly attribute HTMLFormElement? form; // attribute DOMString formAction; // attribute DOMString formEnctype; diff --git a/src/components/script/dom/webidls/HTMLFieldSetElement.webidl b/src/components/script/dom/webidls/HTMLFieldSetElement.webidl index f854cd23bab..6b64c60bd21 100644 --- a/src/components/script/dom/webidls/HTMLFieldSetElement.webidl +++ b/src/components/script/dom/webidls/HTMLFieldSetElement.webidl @@ -5,7 +5,7 @@ // http://www.whatwg.org/html/#htmlfieldsetelement interface HTMLFieldSetElement : HTMLElement { - // attribute boolean disabled; + attribute boolean disabled; //readonly attribute HTMLFormElement? form; // attribute DOMString name; diff --git a/src/components/script/dom/webidls/HTMLInputElement.webidl b/src/components/script/dom/webidls/HTMLInputElement.webidl index 256cc814cff..1caa9137e0b 100644 --- a/src/components/script/dom/webidls/HTMLInputElement.webidl +++ b/src/components/script/dom/webidls/HTMLInputElement.webidl @@ -12,7 +12,7 @@ interface HTMLInputElement : HTMLElement { // attribute boolean defaultChecked; // attribute boolean checked; // attribute DOMString dirName; - // attribute boolean disabled; + attribute boolean disabled; //readonly attribute HTMLFormElement? form; //readonly attribute FileList? files; // attribute DOMString formAction; diff --git a/src/components/script/dom/webidls/HTMLOptGroupElement.webidl b/src/components/script/dom/webidls/HTMLOptGroupElement.webidl index e5831d1df78..13646f00ab1 100644 --- a/src/components/script/dom/webidls/HTMLOptGroupElement.webidl +++ b/src/components/script/dom/webidls/HTMLOptGroupElement.webidl @@ -5,6 +5,6 @@ // http://www.whatwg.org/html/#htmloptgroupelement interface HTMLOptGroupElement : HTMLElement { - // attribute boolean disabled; + attribute boolean disabled; // attribute DOMString label; }; diff --git a/src/components/script/dom/webidls/HTMLOptionElement.webidl b/src/components/script/dom/webidls/HTMLOptionElement.webidl index 2cb5ab76d64..7855449c6f4 100644 --- a/src/components/script/dom/webidls/HTMLOptionElement.webidl +++ b/src/components/script/dom/webidls/HTMLOptionElement.webidl @@ -6,7 +6,7 @@ // http://www.whatwg.org/html/#htmloptionelement //[NamedConstructor=Option(optional DOMString text = "", optional DOMString value, optional boolean defaultSelected = false, optional boolean selected = false)] interface HTMLOptionElement : HTMLElement { - // attribute boolean disabled; + attribute boolean disabled; //readonly attribute HTMLFormElement? form; // attribute DOMString label; // attribute boolean defaultSelected; diff --git a/src/components/script/dom/webidls/HTMLSelectElement.webidl b/src/components/script/dom/webidls/HTMLSelectElement.webidl index 0dec2171609..91d4c3b0917 100644 --- a/src/components/script/dom/webidls/HTMLSelectElement.webidl +++ b/src/components/script/dom/webidls/HTMLSelectElement.webidl @@ -6,7 +6,7 @@ // http://www.whatwg.org/html/#htmlselectelement interface HTMLSelectElement : HTMLElement { // attribute boolean autofocus; - // attribute boolean disabled; + attribute boolean disabled; //readonly attribute HTMLFormElement? form; // attribute boolean multiple; // attribute DOMString name; diff --git a/src/components/script/dom/webidls/HTMLTextAreaElement.webidl b/src/components/script/dom/webidls/HTMLTextAreaElement.webidl index 5425bb321fb..534bb87a0e5 100644 --- a/src/components/script/dom/webidls/HTMLTextAreaElement.webidl +++ b/src/components/script/dom/webidls/HTMLTextAreaElement.webidl @@ -9,7 +9,7 @@ interface HTMLTextAreaElement : HTMLElement { // attribute boolean autofocus; // attribute unsigned long cols; // attribute DOMString dirName; - // attribute boolean disabled; + attribute boolean disabled; //readonly attribute HTMLFormElement? form; // attribute DOMString inputMode; // attribute long maxLength; diff --git a/src/components/script/script_task.rs b/src/components/script/script_task.rs index 9b31c8b0dfc..5c6ade2b939 100644 --- a/src/components/script/script_task.rs +++ b/src/components/script/script_task.rs @@ -12,13 +12,14 @@ use dom::bindings::js::OptionalRootable; use dom::bindings::utils::Reflectable; use dom::bindings::utils::{wrap_for_same_compartment, pre_wrap}; use dom::document::{Document, HTMLDocument, DocumentHelpers}; -use dom::element::{Element}; +use dom::element::{Element, HTMLButtonElementTypeId, HTMLInputElementTypeId}; +use dom::element::{HTMLSelectElementTypeId, HTMLTextAreaElementTypeId, HTMLOptionElementTypeId}; use dom::event::{Event_, ResizeEvent, ReflowEvent, ClickEvent, MouseDownEvent, MouseMoveEvent, MouseUpEvent}; use dom::event::Event; use dom::uievent::UIEvent; use dom::eventtarget::{EventTarget, EventTargetHelpers}; use dom::node; -use dom::node::{Node, NodeHelpers}; +use dom::node::{ElementNodeTypeId, Node, NodeHelpers}; use dom::window::{TimerId, Window, WindowHelpers}; use dom::xmlhttprequest::{TrustedXHRAddress, XMLHttpRequest, XHRProgress}; use html::hubbub_html_parser::HtmlParserResult; @@ -193,6 +194,24 @@ impl<'a> Drop for ScriptMemoryFailsafe<'a> { } } +trait PrivateScriptTaskHelpers { + fn click_event_filter_by_disabled_state(&self) -> bool; +} + +impl<'a> PrivateScriptTaskHelpers for JSRef<'a, Node> { + fn click_event_filter_by_disabled_state(&self) -> bool { + match self.type_id { + ElementNodeTypeId(HTMLButtonElementTypeId) | + ElementNodeTypeId(HTMLInputElementTypeId) | + // ElementNodeTypeId(HTMLKeygenElementTypeId) | + ElementNodeTypeId(HTMLOptionElementTypeId) | + ElementNodeTypeId(HTMLSelectElementTypeId) | + ElementNodeTypeId(HTMLTextAreaElementTypeId) if self.get_disabled_state() => true, + _ => false + } + } +} + impl ScriptTask { /// Creates a new script task. pub fn new(id: PipelineId, @@ -691,6 +710,8 @@ impl ScriptTask { match maybe_node { Some(node) => { debug!("clicked on {:s}", node.debug_str()); + // Prevent click event if form control element is disabled. + if node.click_event_filter_by_disabled_state() { return; } match *page.frame() { Some(ref frame) => { let window = frame.window.root(); diff --git a/src/components/style/node.rs b/src/components/style/node.rs index 62c588dffd4..9e8da53100a 100644 --- a/src/components/style/node.rs +++ b/src/components/style/node.rs @@ -27,5 +27,7 @@ pub trait TElement { fn get_namespace<'a>(&'a self) -> &'a Namespace; fn get_hover_state(&self) -> bool; fn get_id(&self) -> Option; + fn get_disabled_state(&self) -> bool; + fn get_enabled_state(&self) -> bool; } diff --git a/src/components/style/selector_matching.rs b/src/components/style/selector_matching.rs index 133c7dd65be..2ca288f2f6c 100644 --- a/src/components/style/selector_matching.rs +++ b/src/components/style/selector_matching.rs @@ -778,6 +778,18 @@ fn matches_simple_selector { + *shareable = false; + let elem = element.as_element(); + elem.get_disabled_state() + }, + // http://www.whatwg.org/html/#selector-enabled + Enabled => { + *shareable = false; + let elem = element.as_element(); + elem.get_enabled_state() + }, FirstChild => { *shareable = false; matches_first_child(element) diff --git a/src/components/style/selectors.rs b/src/components/style/selectors.rs index 8eadd371086..e18ad0556a9 100644 --- a/src/components/style/selectors.rs +++ b/src/components/style/selectors.rs @@ -77,6 +77,8 @@ pub enum SimpleSelector { Link, Visited, Hover, + Disabled, + Enabled, FirstChild, LastChild, OnlyChild, // Empty, Root, @@ -218,7 +220,7 @@ fn compute_specificity(mut selector: &CompoundSelector, &ClassSelector(..) | &AttrExists(..) | &AttrEqual(..) | &AttrIncludes(..) | &AttrDashMatch(..) | &AttrPrefixMatch(..) | &AttrSubstringMatch(..) | &AttrSuffixMatch(..) - | &AnyLink | &Link | &Visited | &Hover + | &AnyLink | &Link | &Visited | &Hover | &Disabled | &Enabled | &FirstChild | &LastChild | &OnlyChild | &Root // | &Empty | &Lang(*) | &NthChild(..) | &NthLastChild(..) @@ -479,6 +481,8 @@ fn parse_simple_pseudo_class(name: &str) -> Option { "link" => Some(Link), "visited" => Some(Visited), "hover" => Some(Hover), + "disabled" => Some(Disabled), + "enabled" => Some(Enabled), "first-child" => Some(FirstChild), "last-child" => Some(LastChild), "only-child" => Some(OnlyChild), diff --git a/src/test/content/harness.js b/src/test/content/harness.js index 48c41d214b1..7bd118a9dff 100644 --- a/src/test/content/harness.js +++ b/src/test/content/harness.js @@ -55,6 +55,15 @@ function should_not_throw(f) { } } +function check_selector(elem, selector, matches) { + is(elem.matches(selector), matches); +} + +function check_disabled_selector(elem, disabled) { + check_selector(elem, ":disabled", disabled); + check_selector(elem, ":enabled", !disabled); +} + var _test_complete = false; var _test_timeout = 10000; //10 seconds function finish() { diff --git a/src/test/content/test_enabled_disabled_selectors.html b/src/test/content/test_enabled_disabled_selectors.html new file mode 100644 index 00000000000..b6c255b945e --- /dev/null +++ b/src/test/content/test_enabled_disabled_selectors.html @@ -0,0 +1,181 @@ + + + + Tests for :enabled and :disabled selectors + + + + + + + + + + + + + + + + + + + + + + + +
+
+ + + + +
+
+ +
+ + + + + + +
+ +
+ + + + + + + +
+ +