/* 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/. */ use dom::bindings::codegen::Bindings::HTMLCollectionBinding; use dom::bindings::codegen::Bindings::HTMLCollectionBinding::HTMLCollectionMethods; use dom::bindings::codegen::InheritTypes::{ElementCast, NodeCast}; use dom::bindings::global::Window; use dom::bindings::js::{JS, JSRef, Temporary}; use dom::bindings::utils::{Reflectable, Reflector, reflect_dom_object}; use dom::element::{Element, AttributeHandlers, ElementHelpers}; use dom::node::{Node, NodeHelpers}; use dom::window::Window; use servo_util::atom::Atom; use servo_util::namespace; use servo_util::namespace::Namespace; use servo_util::str::{DOMString, split_html_space_chars}; use serialize::{Encoder, Encodable}; use std::ascii::StrAsciiExt; pub trait CollectionFilter { fn filter(&self, elem: &JSRef, root: &JSRef) -> bool; } impl, E> Encodable for Box { fn encode(&self, _s: &mut S) -> Result<(), E> { Ok(()) } } #[deriving(Encodable)] #[must_root] pub enum CollectionTypeId { Static(Vec>), Live(JS, Box) } #[deriving(Encodable)] #[must_root] pub struct HTMLCollection { collection: CollectionTypeId, reflector_: Reflector, } impl HTMLCollection { pub fn new_inherited(collection: CollectionTypeId) -> HTMLCollection { HTMLCollection { collection: collection, reflector_: Reflector::new(), } } pub fn new(window: &JSRef, collection: CollectionTypeId) -> Temporary { reflect_dom_object(box HTMLCollection::new_inherited(collection), &Window(*window), HTMLCollectionBinding::Wrap) } } impl HTMLCollection { pub fn create(window: &JSRef, root: &JSRef, filter: Box) -> Temporary { HTMLCollection::new(window, Live(JS::from_rooted(root), filter)) } fn all_elements(window: &JSRef, root: &JSRef, namespace_filter: Option) -> Temporary { struct AllElementFilter { namespace_filter: Option } impl CollectionFilter for AllElementFilter { fn filter(&self, elem: &JSRef, _root: &JSRef) -> bool { match self.namespace_filter { None => true, Some(ref namespace) => elem.namespace == *namespace } } } let filter = AllElementFilter {namespace_filter: namespace_filter}; HTMLCollection::create(window, root, box filter) } pub fn by_tag_name(window: &JSRef, root: &JSRef, tag: DOMString) -> Temporary { if tag.as_slice() == "*" { return HTMLCollection::all_elements(window, root, None); } struct TagNameFilter { tag: Atom, ascii_lower_tag: Atom, } impl CollectionFilter for TagNameFilter { fn filter(&self, elem: &JSRef, _root: &JSRef) -> bool { if elem.html_element_in_html_document() { elem.local_name == self.ascii_lower_tag } else { elem.local_name == self.tag } } } let filter = TagNameFilter { tag: Atom::from_slice(tag.as_slice()), ascii_lower_tag: Atom::from_slice(tag.as_slice().to_ascii_lower().as_slice()), }; HTMLCollection::create(window, root, box filter) } pub fn by_tag_name_ns(window: &JSRef, root: &JSRef, tag: DOMString, maybe_ns: Option) -> Temporary { let namespace_filter = match maybe_ns { Some(namespace) => { match namespace.as_slice() { "*" => None, ns => Some(Namespace::from_str(ns)), } }, None => Some(namespace::Null), }; if tag.as_slice() == "*" { return HTMLCollection::all_elements(window, root, namespace_filter); } struct TagNameNSFilter { tag: Atom, namespace_filter: Option } impl CollectionFilter for TagNameNSFilter { fn filter(&self, elem: &JSRef, _root: &JSRef) -> bool { let ns_match = match self.namespace_filter { Some(ref namespace) => { elem.deref().namespace == *namespace }, None => true }; ns_match && elem.deref().local_name == self.tag } } let filter = TagNameNSFilter { tag: Atom::from_slice(tag.as_slice()), namespace_filter: namespace_filter }; HTMLCollection::create(window, root, box filter) } pub fn by_class_name(window: &JSRef, root: &JSRef, classes: DOMString) -> Temporary { struct ClassNameFilter { classes: Vec } impl CollectionFilter for ClassNameFilter { fn filter(&self, elem: &JSRef, _root: &JSRef) -> bool { self.classes.iter().all(|class| elem.has_class(class)) } } let filter = ClassNameFilter { classes: split_html_space_chars(classes.as_slice()).map(|class| { Atom::from_slice(class) }).collect() }; HTMLCollection::create(window, root, box filter) } pub fn children(window: &JSRef, root: &JSRef) -> Temporary { struct ElementChildFilter; impl CollectionFilter for ElementChildFilter { fn filter(&self, elem: &JSRef, root: &JSRef) -> bool { root.is_parent_of(NodeCast::from_ref(elem)) } } HTMLCollection::create(window, root, box ElementChildFilter) } } impl<'a> HTMLCollectionMethods for JSRef<'a, HTMLCollection> { // http://dom.spec.whatwg.org/#dom-htmlcollection-length fn Length(&self) -> u32 { match self.collection { Static(ref elems) => elems.len() as u32, Live(ref root, ref filter) => { let root = root.root(); root.deref().traverse_preorder() .filter(|&child| { let elem: Option<&JSRef> = ElementCast::to_ref(&child); elem.map_or(false, |elem| filter.filter(elem, &*root)) }).count() as u32 } } } // http://dom.spec.whatwg.org/#dom-htmlcollection-item fn Item(&self, index: u32) -> Option> { match self.collection { Static(ref elems) => elems .as_slice() .get(index as uint) .map(|elem| Temporary::new(elem.clone())), Live(ref root, ref filter) => { let root = root.root(); root.deref().traverse_preorder() .filter_map(|node| { let elem: Option<&JSRef> = ElementCast::to_ref(&node); elem.filtered(|&elem| filter.filter(elem, &*root)) .map(|elem| elem.clone()) }) .nth(index as uint) .clone() .map(|elem| Temporary::from_rooted(&elem)) } } } // http://dom.spec.whatwg.org/#dom-htmlcollection-nameditem fn NamedItem(&self, key: DOMString) -> Option> { // Step 1. if key.is_empty() { return None; } // Step 2. match self.collection { Static(ref elems) => elems.iter() .map(|elem| elem.root()) .find(|elem| { elem.get_string_attribute(&satom!("name")) == key || elem.get_string_attribute(&satom!("id")) == key }) .map(|maybe_elem| Temporary::from_rooted(&*maybe_elem)), Live(ref root, ref filter) => { let root = root.root(); root.deref().traverse_preorder() .filter_map(|node| { let elem: Option<&JSRef> = ElementCast::to_ref(&node); elem.filtered(|&elem| filter.filter(elem, &*root)) .map(|elem| elem.clone()) }) .find(|elem| { elem.get_string_attribute(&satom!("name")) == key || elem.get_string_attribute(&satom!("id")) == key }) .map(|maybe_elem| Temporary::from_rooted(&maybe_elem)) } } } fn IndexedGetter(&self, index: u32, found: &mut bool) -> Option> { let maybe_elem = self.Item(index); *found = maybe_elem.is_some(); maybe_elem } fn NamedGetter(&self, name: DOMString, found: &mut bool) -> Option> { let maybe_elem = self.NamedItem(name); *found = maybe_elem.is_some(); maybe_elem } } impl Reflectable for HTMLCollection { fn reflector<'a>(&'a self) -> &'a Reflector { &self.reflector_ } }