diff options
Diffstat (limited to 'components/script/dom/element.rs')
-rw-r--r-- | components/script/dom/element.rs | 958 |
1 files changed, 958 insertions, 0 deletions
diff --git a/components/script/dom/element.rs b/components/script/dom/element.rs new file mode 100644 index 00000000000..1fe2c9f80bd --- /dev/null +++ b/components/script/dom/element.rs @@ -0,0 +1,958 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * 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/. */ + +//! Element nodes. + +use dom::attr::{Attr, ReplacedAttr, FirstSetAttr, AttrHelpersForLayout}; +use dom::attr::{AttrValue, StringAttrValue, UIntAttrValue, AtomAttrValue}; +use dom::namednodemap::NamedNodeMap; +use dom::bindings::codegen::Bindings::AttrBinding::AttrMethods; +use dom::bindings::codegen::Bindings::ElementBinding; +use dom::bindings::codegen::Bindings::ElementBinding::ElementMethods; +use dom::bindings::codegen::InheritTypes::{ElementDerived, NodeCast}; +use dom::bindings::js::{JS, JSRef, Temporary, TemporaryPushable}; +use dom::bindings::js::{OptionalSettable, OptionalRootable, Root}; +use dom::bindings::trace::Traceable; +use dom::bindings::utils::{Reflectable, Reflector}; +use dom::bindings::error::{ErrorResult, Fallible, NamespaceError, InvalidCharacter, Syntax}; +use dom::bindings::utils::{QName, Name, InvalidXMLName, xml_name_type}; +use dom::domrect::DOMRect; +use dom::domrectlist::DOMRectList; +use dom::document::{Document, DocumentHelpers}; +use dom::domtokenlist::DOMTokenList; +use dom::eventtarget::{EventTarget, NodeTargetTypeId}; +use dom::htmlcollection::HTMLCollection; +use dom::htmlserializer::serialize; +use dom::node::{ElementNodeTypeId, Node, NodeHelpers, NodeIterator, document_from_node}; +use dom::node::{window_from_node, LayoutNodeHelpers}; +use dom::nodelist::NodeList; +use dom::virtualmethods::{VirtualMethods, vtable_for}; +use layout_interface::ContentChangedDocumentDamage; +use layout_interface::MatchSelectorsDocumentDamage; +use style::{matches, parse_selector_list_from_str}; +use style; +use servo_util::atom::Atom; +use servo_util::namespace; +use servo_util::namespace::{Namespace, Null}; +use servo_util::str::{DOMString, null_str_as_empty_ref}; + +use std::ascii::StrAsciiExt; +use std::cell::{Cell, RefCell}; +use std::mem; + +#[deriving(Encodable)] +pub struct Element { + pub node: Node, + pub local_name: Atom, + pub namespace: Namespace, + pub prefix: Option<DOMString>, + pub attrs: RefCell<Vec<JS<Attr>>>, + pub style_attribute: Traceable<RefCell<Option<style::PropertyDeclarationBlock>>>, + pub attr_list: Cell<Option<JS<NamedNodeMap>>>, + class_list: Cell<Option<JS<DOMTokenList>>>, +} + +impl ElementDerived for EventTarget { + fn is_element(&self) -> bool { + match self.type_id { + NodeTargetTypeId(ElementNodeTypeId(_)) => true, + _ => false + } + } +} + +impl Reflectable for Element { + fn reflector<'a>(&'a self) -> &'a Reflector { + self.node.reflector() + } +} + +#[deriving(PartialEq,Encodable)] +pub enum ElementTypeId { + HTMLElementTypeId, + HTMLAnchorElementTypeId, + HTMLAppletElementTypeId, + HTMLAreaElementTypeId, + HTMLAudioElementTypeId, + HTMLBaseElementTypeId, + HTMLBRElementTypeId, + HTMLBodyElementTypeId, + HTMLButtonElementTypeId, + HTMLCanvasElementTypeId, + HTMLDataElementTypeId, + HTMLDataListElementTypeId, + HTMLDirectoryElementTypeId, + HTMLDListElementTypeId, + HTMLDivElementTypeId, + HTMLEmbedElementTypeId, + HTMLFieldSetElementTypeId, + HTMLFontElementTypeId, + HTMLFormElementTypeId, + HTMLFrameElementTypeId, + HTMLFrameSetElementTypeId, + HTMLHRElementTypeId, + HTMLHeadElementTypeId, + HTMLHeadingElementTypeId, + HTMLHtmlElementTypeId, + HTMLIFrameElementTypeId, + HTMLImageElementTypeId, + HTMLInputElementTypeId, + HTMLLabelElementTypeId, + HTMLLegendElementTypeId, + HTMLLinkElementTypeId, + HTMLLIElementTypeId, + HTMLMapElementTypeId, + HTMLMediaElementTypeId, + HTMLMetaElementTypeId, + HTMLMeterElementTypeId, + HTMLModElementTypeId, + HTMLObjectElementTypeId, + HTMLOListElementTypeId, + HTMLOptGroupElementTypeId, + HTMLOptionElementTypeId, + HTMLOutputElementTypeId, + HTMLParagraphElementTypeId, + HTMLParamElementTypeId, + HTMLPreElementTypeId, + HTMLProgressElementTypeId, + HTMLQuoteElementTypeId, + HTMLScriptElementTypeId, + HTMLSelectElementTypeId, + HTMLSourceElementTypeId, + HTMLSpanElementTypeId, + HTMLStyleElementTypeId, + HTMLTableElementTypeId, + HTMLTableCaptionElementTypeId, + HTMLTableDataCellElementTypeId, + HTMLTableHeaderCellElementTypeId, + HTMLTableColElementTypeId, + HTMLTableRowElementTypeId, + HTMLTableSectionElementTypeId, + HTMLTemplateElementTypeId, + HTMLTextAreaElementTypeId, + HTMLTimeElementTypeId, + HTMLTitleElementTypeId, + HTMLTrackElementTypeId, + HTMLUListElementTypeId, + HTMLVideoElementTypeId, + HTMLUnknownElementTypeId, + + ElementTypeId, +} + +// +// Element methods +// + +impl Element { + pub fn new_inherited(type_id: ElementTypeId, local_name: DOMString, namespace: Namespace, prefix: Option<DOMString>, document: &JSRef<Document>) -> Element { + Element { + node: Node::new_inherited(ElementNodeTypeId(type_id), document), + local_name: Atom::from_slice(local_name.as_slice()), + namespace: namespace, + prefix: prefix, + attrs: RefCell::new(vec!()), + attr_list: Cell::new(None), + class_list: Cell::new(None), + style_attribute: Traceable::new(RefCell::new(None)), + } + } + + pub fn new(local_name: DOMString, namespace: Namespace, prefix: Option<DOMString>, document: &JSRef<Document>) -> Temporary<Element> { + let element = Element::new_inherited(ElementTypeId, local_name, namespace, prefix, document); + Node::reflect_node(box element, document, ElementBinding::Wrap) + } +} + +pub trait RawLayoutElementHelpers { + unsafe fn get_attr_val_for_layout(&self, namespace: &Namespace, name: &str) -> Option<&'static str>; + unsafe fn get_attr_atom_for_layout(&self, namespace: &Namespace, name: &str) -> Option<Atom>; +} + +impl RawLayoutElementHelpers for Element { + #[inline] + unsafe fn get_attr_val_for_layout(&self, namespace: &Namespace, name: &str) + -> Option<&'static str> { + // cast to point to T in RefCell<T> directly + let attrs: *const Vec<JS<Attr>> = mem::transmute(&self.attrs); + (*attrs).iter().find(|attr: & &JS<Attr>| { + let attr = attr.unsafe_get(); + name == (*attr).local_name().as_slice() && + (*attr).namespace == *namespace + }).map(|attr| { + let attr = attr.unsafe_get(); + (*attr).value_ref_forever() + }) + } + + #[inline] + unsafe fn get_attr_atom_for_layout(&self, namespace: &Namespace, name: &str) + -> Option<Atom> { + // cast to point to T in RefCell<T> directly + let attrs: *const Vec<JS<Attr>> = mem::transmute(&self.attrs); + (*attrs).iter().find(|attr: & &JS<Attr>| { + let attr = attr.unsafe_get(); + name == (*attr).local_name().as_slice() && + (*attr).namespace == *namespace + }).and_then(|attr| { + let attr = attr.unsafe_get(); + (*attr).value_atom_forever() + }) + } +} + +pub trait LayoutElementHelpers { + unsafe fn html_element_in_html_document_for_layout(&self) -> bool; +} + +impl LayoutElementHelpers for JS<Element> { + unsafe fn html_element_in_html_document_for_layout(&self) -> bool { + if (*self.unsafe_get()).namespace != namespace::HTML { + return false + } + let node: JS<Node> = self.transmute_copy(); + let owner_doc = node.owner_doc_for_layout().unsafe_get(); + (*owner_doc).is_html_document + } +} + +pub trait ElementHelpers { + fn html_element_in_html_document(&self) -> bool; + fn get_local_name<'a>(&'a self) -> &'a Atom; + fn get_namespace<'a>(&'a self) -> &'a Namespace; +} + +impl<'a> ElementHelpers for JSRef<'a, Element> { + fn html_element_in_html_document(&self) -> bool { + let node: &JSRef<Node> = NodeCast::from_ref(self); + self.namespace == namespace::HTML && node.is_in_html_doc() + } + + fn get_local_name<'a>(&'a self) -> &'a Atom { + &self.deref().local_name + } + + fn get_namespace<'a>(&'a self) -> &'a Namespace { + &self.deref().namespace + } +} + +pub trait AttributeHandlers { + fn get_attribute(&self, namespace: Namespace, name: &str) -> Option<Temporary<Attr>>; + fn set_attribute_from_parser(&self, local_name: Atom, + value: DOMString, namespace: Namespace, + prefix: Option<DOMString>); + fn set_attribute(&self, name: &str, value: AttrValue); + fn do_set_attribute(&self, local_name: Atom, value: AttrValue, + name: Atom, namespace: Namespace, + prefix: Option<DOMString>, cb: |&JSRef<Attr>| -> bool); + fn parse_attribute(&self, namespace: &Namespace, local_name: &Atom, + value: DOMString) -> AttrValue; + + fn remove_attribute(&self, namespace: Namespace, name: &str); + fn notify_attribute_changed(&self, local_name: &Atom); + fn has_class(&self, name: &str) -> bool; + + 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; + fn set_string_attribute(&self, name: &str, value: DOMString); + fn set_tokenlist_attribute(&self, name: &str, value: DOMString); + fn get_uint_attribute(&self, name: &str) -> u32; + fn set_uint_attribute(&self, name: &str, value: u32); +} + +impl<'a> AttributeHandlers for JSRef<'a, Element> { + fn get_attribute(&self, namespace: Namespace, name: &str) -> Option<Temporary<Attr>> { + let element: &Element = self.deref(); + let local_name = match self.html_element_in_html_document() { + true => Atom::from_slice(name.to_ascii_lower().as_slice()), + false => Atom::from_slice(name) + }; + element.attrs.borrow().iter().map(|attr| attr.root()).find(|attr| { + *attr.local_name() == local_name && attr.namespace == namespace + }).map(|x| Temporary::from_rooted(&*x)) + } + + fn set_attribute_from_parser(&self, local_name: Atom, + value: DOMString, namespace: Namespace, + prefix: Option<DOMString>) { + let name = match prefix { + None => local_name.clone(), + Some(ref prefix) => { + let name = format!("{:s}:{:s}", *prefix, local_name.as_slice()); + Atom::from_slice(name.as_slice()) + }, + }; + let value = self.parse_attribute(&namespace, &local_name, value); + self.do_set_attribute(local_name, value, name, namespace, prefix, |_| false) + } + + fn set_attribute(&self, name: &str, value: AttrValue) { + assert!(name == name.to_ascii_lower().as_slice()); + assert!(!name.contains(":")); + + let node: &JSRef<Node> = NodeCast::from_ref(self); + node.wait_until_safe_to_modify_dom(); + + let name = Atom::from_slice(name); + self.do_set_attribute(name.clone(), value, name.clone(), + namespace::Null, None, |attr| *attr.local_name() == name); + } + + fn do_set_attribute(&self, local_name: Atom, value: AttrValue, + name: Atom, namespace: Namespace, + prefix: Option<DOMString>, cb: |&JSRef<Attr>| -> bool) { + let idx = self.deref().attrs.borrow().iter() + .map(|attr| attr.root()) + .position(|attr| cb(&*attr)); + let (idx, set_type) = match idx { + Some(idx) => (idx, ReplacedAttr), + None => { + let window = window_from_node(self).root(); + let attr = Attr::new(&*window, local_name, value.clone(), + name, namespace.clone(), prefix, self); + self.deref().attrs.borrow_mut().push_unrooted(&attr); + (self.deref().attrs.borrow().len() - 1, FirstSetAttr) + } + }; + + (*self.deref().attrs.borrow())[idx].root().set_value(set_type, value); + } + + fn parse_attribute(&self, namespace: &Namespace, local_name: &Atom, + value: DOMString) -> AttrValue { + if *namespace == namespace::Null { + vtable_for(NodeCast::from_ref(self)) + .parse_plain_attribute(local_name.as_slice(), value) + } else { + StringAttrValue(value) + } + } + + fn remove_attribute(&self, namespace: Namespace, name: &str) { + let (_, local_name) = get_attribute_parts(name); + let local_name = Atom::from_slice(local_name); + + let idx = self.deref().attrs.borrow().iter().map(|attr| attr.root()).position(|attr| { + *attr.local_name() == local_name + }); + + match idx { + None => (), + Some(idx) => { + { + let node: &JSRef<Node> = NodeCast::from_ref(self); + node.wait_until_safe_to_modify_dom(); + } + + if namespace == namespace::Null { + let removed_raw_value = (*self.deref().attrs.borrow())[idx].root().Value(); + vtable_for(NodeCast::from_ref(self)) + .before_remove_attr(&local_name, + removed_raw_value); + } + + self.deref().attrs.borrow_mut().remove(idx); + } + }; + } + + fn notify_attribute_changed(&self, local_name: &Atom) { + let node: &JSRef<Node> = NodeCast::from_ref(self); + if node.is_in_doc() { + let damage = match local_name.as_slice() { + "style" | "id" | "class" => MatchSelectorsDocumentDamage, + _ => ContentChangedDocumentDamage + }; + let document = node.owner_doc().root(); + document.deref().damage_and_reflow(damage); + } + } + + fn has_class(&self, name: &str) -> bool { + self.get_attribute(Null, "class").root().map(|attr| { + attr.deref().value().tokens().map(|mut tokens| { + tokens.any(|atom| atom.as_slice() == name) + }).unwrap_or(false) + }).unwrap_or(false) + } + + fn set_atomic_attribute(&self, name: &str, value: DOMString) { + assert!(name == name.to_ascii_lower().as_slice()); + let value = AttrValue::from_atomic(value); + self.set_attribute(name, value); + } + + fn has_attribute(&self, name: &str) -> bool { + let name = match self.html_element_in_html_document() { + true => Atom::from_slice(name.to_ascii_lower().as_slice()), + false => Atom::from_slice(name) + }; + self.deref().attrs.borrow().iter().map(|attr| attr.root()).any(|attr| { + *attr.local_name() == 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) + } + fn set_url_attribute(&self, name: &str, value: DOMString) { + self.set_string_attribute(name, value); + } + + fn get_string_attribute(&self, name: &str) -> DOMString { + match self.get_attribute(Null, name) { + Some(x) => { + let x = x.root(); + x.deref().Value() + } + None => "".to_string() + } + } + fn set_string_attribute(&self, name: &str, value: DOMString) { + assert!(name == name.to_ascii_lower().as_slice()); + self.set_attribute(name, StringAttrValue(value)); + } + + fn set_tokenlist_attribute(&self, name: &str, value: DOMString) { + assert!(name == name.to_ascii_lower().as_slice()); + self.set_attribute(name, AttrValue::from_tokenlist(value)); + } + + fn get_uint_attribute(&self, name: &str) -> u32 { + assert!(name == name.to_ascii_lower().as_slice()); + let attribute = self.get_attribute(Null, name).root(); + match attribute { + Some(attribute) => { + match *attribute.deref().value() { + UIntAttrValue(_, value) => value, + _ => fail!("Expected a UIntAttrValue"), + } + } + None => 0, + } + } + fn set_uint_attribute(&self, name: &str, value: u32) { + assert!(name == name.to_ascii_lower().as_slice()); + self.set_attribute(name, UIntAttrValue(value.to_string(), value)); + } +} + +impl Element { + pub fn is_void(&self) -> bool { + if self.namespace != namespace::HTML { + return false + } + match self.local_name.as_slice() { + /* List of void elements from + http://www.whatwg.org/specs/web-apps/current-work/multipage/the-end.html#html-fragment-serialization-algorithm */ + "area" | "base" | "basefont" | "bgsound" | "br" | "col" | "embed" | + "frame" | "hr" | "img" | "input" | "keygen" | "link" | "menuitem" | + "meta" | "param" | "source" | "track" | "wbr" => true, + _ => false + } + } +} + +impl<'a> ElementMethods for JSRef<'a, Element> { + // http://dom.spec.whatwg.org/#dom-element-namespaceuri + fn GetNamespaceURI(&self) -> Option<DOMString> { + match self.namespace { + Null => None, + ref ns => Some(ns.to_str().to_string()) + } + } + + fn LocalName(&self) -> DOMString { + self.local_name.as_slice().to_string() + } + + // http://dom.spec.whatwg.org/#dom-element-prefix + fn GetPrefix(&self) -> Option<DOMString> { + self.prefix.clone() + } + + // http://dom.spec.whatwg.org/#dom-element-tagname + fn TagName(&self) -> DOMString { + let qualified_name = match self.prefix { + Some(ref prefix) => format!("{}:{}", prefix, self.local_name).into_maybe_owned(), + None => self.local_name.as_slice().into_maybe_owned() + }; + if self.html_element_in_html_document() { + qualified_name.as_slice().to_ascii_upper() + } else { + qualified_name.into_string() + } + } + + // http://dom.spec.whatwg.org/#dom-element-id + fn Id(&self) -> DOMString { + self.get_string_attribute("id") + } + + // http://dom.spec.whatwg.org/#dom-element-id + fn SetId(&self, id: DOMString) { + self.set_atomic_attribute("id", id); + } + + // http://dom.spec.whatwg.org/#dom-element-classname + fn ClassName(&self) -> DOMString { + self.get_string_attribute("class") + } + + // http://dom.spec.whatwg.org/#dom-element-classname + fn SetClassName(&self, class: DOMString) { + self.set_tokenlist_attribute("class", class); + } + + // http://dom.spec.whatwg.org/#dom-element-classlist + fn ClassList(&self) -> Temporary<DOMTokenList> { + match self.class_list.get() { + Some(class_list) => Temporary::new(class_list), + None => { + let class_list = DOMTokenList::new(self, "class").root(); + self.class_list.assign(Some(class_list.deref().clone())); + Temporary::from_rooted(&*class_list) + } + } + } + + // http://dom.spec.whatwg.org/#dom-element-attributes + fn Attributes(&self) -> Temporary<NamedNodeMap> { + match self.attr_list.get() { + None => (), + Some(ref list) => return Temporary::new(list.clone()), + } + + let doc = { + let node: &JSRef<Node> = NodeCast::from_ref(self); + node.owner_doc().root() + }; + let window = doc.deref().window.root(); + let list = NamedNodeMap::new(&*window, self); + self.attr_list.assign(Some(list)); + Temporary::new(self.attr_list.get().get_ref().clone()) + } + + // http://dom.spec.whatwg.org/#dom-element-getattribute + fn GetAttribute(&self, name: DOMString) -> Option<DOMString> { + let name = if self.html_element_in_html_document() { + name.as_slice().to_ascii_lower() + } else { + name + }; + self.get_attribute(Null, name.as_slice()).root() + .map(|s| s.deref().Value()) + } + + // http://dom.spec.whatwg.org/#dom-element-getattributens + fn GetAttributeNS(&self, + namespace: Option<DOMString>, + local_name: DOMString) -> Option<DOMString> { + let namespace = Namespace::from_str(null_str_as_empty_ref(&namespace)); + self.get_attribute(namespace, local_name.as_slice()).root() + .map(|attr| attr.deref().Value()) + } + + // http://dom.spec.whatwg.org/#dom-element-setattribute + fn SetAttribute(&self, + name: DOMString, + value: DOMString) -> ErrorResult { + { + let node: &JSRef<Node> = NodeCast::from_ref(self); + node.wait_until_safe_to_modify_dom(); + } + + // Step 1. + match xml_name_type(name.as_slice()) { + InvalidXMLName => return Err(InvalidCharacter), + _ => {} + } + + // Step 2. + let name = if self.html_element_in_html_document() { + name.as_slice().to_ascii_lower() + } else { + name + }; + + // Step 3-5. + let name = Atom::from_slice(name.as_slice()); + let value = self.parse_attribute(&namespace::Null, &name, value); + self.do_set_attribute(name.clone(), value, name.clone(), namespace::Null, None, |attr| { + attr.deref().name.as_slice() == name.as_slice() + }); + Ok(()) + } + + // http://dom.spec.whatwg.org/#dom-element-setattributens + fn SetAttributeNS(&self, + namespace_url: Option<DOMString>, + name: DOMString, + value: DOMString) -> ErrorResult { + { + let node: &JSRef<Node> = NodeCast::from_ref(self); + node.wait_until_safe_to_modify_dom(); + } + + // Step 1. + let namespace = Namespace::from_str(null_str_as_empty_ref(&namespace_url)); + + let name_type = xml_name_type(name.as_slice()); + match name_type { + // Step 2. + InvalidXMLName => return Err(InvalidCharacter), + // Step 3. + Name => return Err(NamespaceError), + QName => {} + } + + // Step 4. + let (prefix, local_name) = get_attribute_parts(name.as_slice()); + match prefix { + Some(ref prefix_str) => { + // Step 5. + if namespace == namespace::Null { + return Err(NamespaceError); + } + + // Step 6. + if "xml" == prefix_str.as_slice() && namespace != namespace::XML { + return Err(NamespaceError); + } + + // Step 7b. + if "xmlns" == prefix_str.as_slice() && namespace != namespace::XMLNS { + return Err(NamespaceError); + } + }, + None => {} + } + + let name = Atom::from_slice(name.as_slice()); + let local_name = Atom::from_slice(local_name); + let xmlns = Atom::from_slice("xmlns"); // TODO: Make this a static atom type + + // Step 7a. + if xmlns == name && namespace != namespace::XMLNS { + return Err(NamespaceError); + } + + // Step 8. + if namespace == namespace::XMLNS && xmlns != name && Some("xmlns") != prefix { + return Err(NamespaceError); + } + + // Step 9. + let value = self.parse_attribute(&namespace, &local_name, value); + self.do_set_attribute(local_name.clone(), value, name, + namespace.clone(), prefix.map(|s| s.to_string()), + |attr| { + *attr.local_name() == local_name && + attr.namespace == namespace + }); + Ok(()) + } + + // http://dom.spec.whatwg.org/#dom-element-removeattribute + fn RemoveAttribute(&self, name: DOMString) { + let name = if self.html_element_in_html_document() { + name.as_slice().to_ascii_lower() + } else { + name + }; + self.remove_attribute(namespace::Null, name.as_slice()) + } + + // http://dom.spec.whatwg.org/#dom-element-removeattributens + fn RemoveAttributeNS(&self, + namespace: Option<DOMString>, + localname: DOMString) { + let namespace = Namespace::from_str(null_str_as_empty_ref(&namespace)); + self.remove_attribute(namespace, localname.as_slice()) + } + + // http://dom.spec.whatwg.org/#dom-element-hasattribute + fn HasAttribute(&self, + name: DOMString) -> bool { + self.has_attribute(name.as_slice()) + } + + // http://dom.spec.whatwg.org/#dom-element-hasattributens + fn HasAttributeNS(&self, + namespace: Option<DOMString>, + local_name: DOMString) -> bool { + self.GetAttributeNS(namespace, local_name).is_some() + } + + fn GetElementsByTagName(&self, localname: DOMString) -> Temporary<HTMLCollection> { + let window = window_from_node(self).root(); + HTMLCollection::by_tag_name(&*window, NodeCast::from_ref(self), localname) + } + + fn GetElementsByTagNameNS(&self, maybe_ns: Option<DOMString>, + localname: DOMString) -> Temporary<HTMLCollection> { + let window = window_from_node(self).root(); + HTMLCollection::by_tag_name_ns(&*window, NodeCast::from_ref(self), localname, maybe_ns) + } + + fn GetElementsByClassName(&self, classes: DOMString) -> Temporary<HTMLCollection> { + let window = window_from_node(self).root(); + HTMLCollection::by_class_name(&*window, NodeCast::from_ref(self), classes) + } + + // http://dev.w3.org/csswg/cssom-view/#dom-element-getclientrects + fn GetClientRects(&self) -> Temporary<DOMRectList> { + let win = window_from_node(self).root(); + let node: &JSRef<Node> = NodeCast::from_ref(self); + let rects = node.get_content_boxes(); + let rects: Vec<Root<DOMRect>> = rects.iter().map(|r| { + DOMRect::new( + &*win, + r.origin.y, + r.origin.y + r.size.height, + r.origin.x, + r.origin.x + r.size.width).root() + }).collect(); + + DOMRectList::new(&*win, rects.iter().map(|rect| rect.deref().clone()).collect()) + } + + // http://dev.w3.org/csswg/cssom-view/#dom-element-getboundingclientrect + fn GetBoundingClientRect(&self) -> Temporary<DOMRect> { + let win = window_from_node(self).root(); + let node: &JSRef<Node> = NodeCast::from_ref(self); + let rect = node.get_bounding_content_box(); + DOMRect::new( + &*win, + rect.origin.y, + rect.origin.y + rect.size.height, + rect.origin.x, + rect.origin.x + rect.size.width) + } + + fn GetInnerHTML(&self) -> Fallible<DOMString> { + //XXX TODO: XML case + Ok(serialize(&mut NodeIterator::new(NodeCast::from_ref(self), false, false))) + } + + fn GetOuterHTML(&self) -> Fallible<DOMString> { + Ok(serialize(&mut NodeIterator::new(NodeCast::from_ref(self), true, false))) + } + + // http://dom.spec.whatwg.org/#dom-parentnode-children + fn Children(&self) -> Temporary<HTMLCollection> { + let window = window_from_node(self).root(); + HTMLCollection::children(&*window, NodeCast::from_ref(self)) + } + + // http://dom.spec.whatwg.org/#dom-parentnode-queryselector + fn QuerySelector(&self, selectors: DOMString) -> Fallible<Option<Temporary<Element>>> { + let root: &JSRef<Node> = NodeCast::from_ref(self); + root.query_selector(selectors) + } + + // http://dom.spec.whatwg.org/#dom-parentnode-queryselectorall + fn QuerySelectorAll(&self, selectors: DOMString) -> Fallible<Temporary<NodeList>> { + let root: &JSRef<Node> = NodeCast::from_ref(self); + root.query_selector_all(selectors) + } + + // http://dom.spec.whatwg.org/#dom-childnode-remove + fn Remove(&self) { + let node: &JSRef<Node> = NodeCast::from_ref(self); + node.remove_self(); + } + + // http://dom.spec.whatwg.org/#dom-element-matches + fn Matches(&self, selectors: DOMString) -> Fallible<bool> { + match parse_selector_list_from_str(selectors.as_slice()) { + Err(()) => Err(Syntax), + Ok(ref selectors) => { + let root: &JSRef<Node> = NodeCast::from_ref(self); + Ok(matches(selectors, root)) + } + } + } +} + +pub fn get_attribute_parts<'a>(name: &'a str) -> (Option<&'a str>, &'a str) { + //FIXME: Throw for XML-invalid names + //FIXME: Throw for XMLNS-invalid names + let (prefix, local_name) = if name.contains(":") { + let mut parts = name.splitn(':', 1); + (Some(parts.next().unwrap()), parts.next().unwrap()) + } else { + (None, name) + }; + + (prefix, local_name) +} + +impl<'a> VirtualMethods for JSRef<'a, Element> { + fn super_type<'a>(&'a self) -> Option<&'a VirtualMethods> { + let node: &JSRef<Node> = NodeCast::from_ref(self); + Some(node as &VirtualMethods) + } + + fn after_set_attr(&self, name: &Atom, value: DOMString) { + match self.super_type() { + Some(ref s) => s.after_set_attr(name, value.clone()), + _ => (), + } + + match name.as_slice() { + "style" => { + let doc = document_from_node(self).root(); + let base_url = doc.deref().url().clone(); + let style = Some(style::parse_style_attribute(value.as_slice(), &base_url)); + *self.deref().style_attribute.deref().borrow_mut() = style; + } + "id" => { + let node: &JSRef<Node> = NodeCast::from_ref(self); + if node.is_in_doc() && !value.is_empty() { + let doc = document_from_node(self).root(); + doc.register_named_element(self, value.clone()); + } + } + _ => () + } + + self.notify_attribute_changed(name); + } + + fn before_remove_attr(&self, name: &Atom, value: DOMString) { + match self.super_type() { + Some(ref s) => s.before_remove_attr(name, value.clone()), + _ => (), + } + + match name.as_slice() { + "style" => { + *self.deref().style_attribute.deref().borrow_mut() = None; + } + "id" => { + let node: &JSRef<Node> = NodeCast::from_ref(self); + if node.is_in_doc() && !value.is_empty() { + let doc = document_from_node(self).root(); + doc.unregister_named_element(self, value); + } + } + _ => () + } + + self.notify_attribute_changed(name); + } + + fn parse_plain_attribute(&self, name: &str, value: DOMString) -> AttrValue { + match name { + "id" => AttrValue::from_atomic(value), + "class" => AttrValue::from_tokenlist(value), + _ => self.super_type().unwrap().parse_plain_attribute(name, value), + } + } + + fn bind_to_tree(&self, tree_in_doc: bool) { + match self.super_type() { + Some(ref s) => s.bind_to_tree(tree_in_doc), + _ => (), + } + + if !tree_in_doc { return; } + + match self.get_attribute(Null, "id").root() { + Some(attr) => { + let doc = document_from_node(self).root(); + let value = attr.deref().Value(); + if !value.is_empty() { + doc.deref().register_named_element(self, value); + } + } + _ => () + } + } + + fn unbind_from_tree(&self, tree_in_doc: bool) { + match self.super_type() { + Some(ref s) => s.unbind_from_tree(tree_in_doc), + _ => (), + } + + if !tree_in_doc { return; } + + match self.get_attribute(Null, "id").root() { + Some(attr) => { + let doc = document_from_node(self).root(); + let value = attr.deref().Value(); + if !value.is_empty() { + doc.deref().unregister_named_element(self, value); + } + } + _ => () + } + } +} + +impl<'a> style::TElement for JSRef<'a, Element> { + fn get_attr(&self, namespace: &Namespace, attr: &str) -> Option<&'static str> { + self.get_attribute(namespace.clone(), attr).root().map(|attr| { + unsafe { mem::transmute(attr.deref().value().as_slice()) } + }) + } + fn get_link(&self) -> Option<&'static str> { + // FIXME: This is HTML only. + let node: &JSRef<Node> = NodeCast::from_ref(self); + match node.type_id() { + // http://www.whatwg.org/specs/web-apps/current-work/multipage/selectors.html# + // selector-link + ElementNodeTypeId(HTMLAnchorElementTypeId) | + ElementNodeTypeId(HTMLAreaElementTypeId) | + ElementNodeTypeId(HTMLLinkElementTypeId) => self.get_attr(&namespace::Null, "href"), + _ => None, + } + } + fn get_local_name<'a>(&'a self) -> &'a Atom { + (self as &ElementHelpers).get_local_name() + } + fn get_namespace<'a>(&'a self) -> &'a Namespace { + (self as &ElementHelpers).get_namespace() + } + fn get_hover_state(&self) -> bool { + let node: &JSRef<Node> = NodeCast::from_ref(self); + node.get_hover_state() + } + fn get_id<'a>(&self) -> Option<Atom> { + self.get_attribute(namespace::Null, "id").map(|attr| { + let attr = attr.root(); + match *attr.value() { + AtomAttrValue(ref val) => val.clone(), + _ => fail!("`id` attribute should be AtomAttrValue"), + } + }) + } + fn get_disabled_state(&self) -> bool { + let node: &JSRef<Node> = NodeCast::from_ref(self); + node.get_disabled_state() + } + fn get_enabled_state(&self) -> bool { + let node: &JSRef<Node> = NodeCast::from_ref(self); + node.get_enabled_state() + } +} |