Implement support for :disabled CSS selector

This commit is contained in:
Bruno de Oliveira Abinader 2014-06-27 15:25:09 -04:00
parent 40048b791c
commit 7771350898
7 changed files with 134 additions and 6 deletions

View file

@ -398,6 +398,12 @@ impl<'le> TElement for LayoutElement<'le> {
fn get_id(&self) -> Option<Atom> {
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 {

View file

@ -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<Node> = NodeCast::from_ref(self);
node.get_disabled_state()
}
}

View file

@ -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<Node> {
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<JSRef<'a, Element>> 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);
}
}

View file

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

View file

@ -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<Atom>;
fn get_disabled_state(&self) -> bool;
}

View file

@ -778,6 +778,12 @@ fn matches_simple_selector<E:TElement,
let elem = element.as_element();
elem.get_hover_state()
},
// http://www.whatwg.org/html/#selector-disabled
Disabled => {
*shareable = false;
let elem = element.as_element();
elem.get_disabled_state()
},
FirstChild => {
*shareable = false;
matches_first_child(element)

View file

@ -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<SimpleSelector> {
"link" => Some(Link),
"visited" => Some(Visited),
"hover" => Some(Hover),
"disabled" => Some(Disabled),
"first-child" => Some(FirstChild),
"last-child" => Some(LastChild),
"only-child" => Some(OnlyChild),