diff --git a/src/components/layout/wrapper.rs b/src/components/layout/wrapper.rs index b0ecbbcd2fd..026857d9ad7 100644 --- a/src/components/layout/wrapper.rs +++ b/src/components/layout/wrapper.rs @@ -398,6 +398,12 @@ 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_content(content_list: &content::T) -> String { diff --git a/src/components/script/dom/element.rs b/src/components/script/dom/element.rs index 04d9603bdb6..a95d1ff8cc1 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,8 @@ 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() + } } diff --git a/src/components/script/dom/node.rs b/src/components/script/dom/node.rs index a73c4d7e879..425d3b95ef5 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}; @@ -123,8 +125,10 @@ 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 } } @@ -383,6 +387,9 @@ 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 dump(&self); fn dump_indent(&self, indent: uint); fn debug_str(&self) -> String; @@ -500,6 +507,18 @@ 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); + } + } + /// Iterates over this node and all its descendants, in preorder. fn traverse_preorder<'a>(&'a self) -> TreeIterator<'a> { let mut nodes = vec!(); @@ -728,12 +747,16 @@ 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; } 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) + } } @@ -1966,3 +1989,47 @@ 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); + 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); + 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); + }, + _ => () + } + } + + 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); + } +} 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..b98d9651fca 100644 --- a/src/components/style/node.rs +++ b/src/components/style/node.rs @@ -27,5 +27,6 @@ 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; } diff --git a/src/components/style/selector_matching.rs b/src/components/style/selector_matching.rs index 133c7dd65be..5a2b3798ff9 100644 --- a/src/components/style/selector_matching.rs +++ b/src/components/style/selector_matching.rs @@ -778,6 +778,12 @@ fn matches_simple_selector { + *shareable = false; + let elem = element.as_element(); + elem.get_disabled_state() + }, FirstChild => { *shareable = false; matches_first_child(element) diff --git a/src/components/style/selectors.rs b/src/components/style/selectors.rs index 8eadd371086..c1d7bf0b892 100644 --- a/src/components/style/selectors.rs +++ b/src/components/style/selectors.rs @@ -77,6 +77,7 @@ pub enum SimpleSelector { Link, Visited, Hover, + Disabled, FirstChild, LastChild, OnlyChild, // Empty, Root, @@ -218,7 +219,7 @@ fn compute_specificity(mut selector: &CompoundSelector, &ClassSelector(..) | &AttrExists(..) | &AttrEqual(..) | &AttrIncludes(..) | &AttrDashMatch(..) | &AttrPrefixMatch(..) | &AttrSubstringMatch(..) | &AttrSuffixMatch(..) - | &AnyLink | &Link | &Visited | &Hover + | &AnyLink | &Link | &Visited | &Hover | &Disabled | &FirstChild | &LastChild | &OnlyChild | &Root // | &Empty | &Lang(*) | &NthChild(..) | &NthLastChild(..) @@ -479,6 +480,7 @@ fn parse_simple_pseudo_class(name: &str) -> Option { "link" => Some(Link), "visited" => Some(Visited), "hover" => Some(Hover), + "disabled" => Some(Disabled), "first-child" => Some(FirstChild), "last-child" => Some(LastChild), "only-child" => Some(OnlyChild),