auto merge of #1952 : brunoabinader/servo/htmlcollection-live, r=Ms2ger

Closes #1662.

PS: Cached collections will be next step.
This commit is contained in:
bors-servo 2014-03-26 11:46:47 -04:00
commit 0204745595
8 changed files with 197 additions and 81 deletions

View file

@ -20,7 +20,7 @@ use dom::element::{HTMLHtmlElementTypeId, HTMLHeadElementTypeId, HTMLTitleElemen
use dom::element::{HTMLBodyElementTypeId, HTMLFrameSetElementTypeId}; use dom::element::{HTMLBodyElementTypeId, HTMLFrameSetElementTypeId};
use dom::event::Event; use dom::event::Event;
use dom::eventtarget::{EventTarget, NodeTargetTypeId}; use dom::eventtarget::{EventTarget, NodeTargetTypeId};
use dom::htmlcollection::HTMLCollection; use dom::htmlcollection::{HTMLCollection, CollectionFilter};
use dom::nodelist::NodeList; use dom::nodelist::NodeList;
use dom::htmlelement::HTMLElement; use dom::htmlelement::HTMLElement;
use dom::htmlheadelement::HTMLHeadElement; use dom::htmlheadelement::HTMLHeadElement;
@ -475,12 +475,26 @@ impl Document {
pub fn Images(&self, abstract_self: &JS<Document>) -> JS<HTMLCollection> { pub fn Images(&self, abstract_self: &JS<Document>) -> JS<HTMLCollection> {
// FIXME: https://github.com/mozilla/servo/issues/1847 // FIXME: https://github.com/mozilla/servo/issues/1847
HTMLCollection::by_tag_name(&self.window, &NodeCast::from(abstract_self), ~"img") struct ImagesFilter;
impl CollectionFilter for ImagesFilter {
fn filter(&self, elem: &JS<Element>, _root: &JS<Node>) -> bool {
elem.get().tag_name == ~"img"
}
}
let filter = ~ImagesFilter;
HTMLCollection::create(&self.window, &NodeCast::from(abstract_self), filter)
} }
pub fn Embeds(&self, abstract_self: &JS<Document>) -> JS<HTMLCollection> { pub fn Embeds(&self, abstract_self: &JS<Document>) -> JS<HTMLCollection> {
// FIXME: https://github.com/mozilla/servo/issues/1847 // FIXME: https://github.com/mozilla/servo/issues/1847
HTMLCollection::by_tag_name(&self.window, &NodeCast::from(abstract_self), ~"embed") struct EmbedsFilter;
impl CollectionFilter for EmbedsFilter {
fn filter(&self, elem: &JS<Element>, _root: &JS<Node>) -> bool {
elem.get().tag_name == ~"embed"
}
}
let filter = ~EmbedsFilter;
HTMLCollection::create(&self.window, &NodeCast::from(abstract_self), filter)
} }
pub fn Plugins(&self, abstract_self: &JS<Document>) -> JS<HTMLCollection> { pub fn Plugins(&self, abstract_self: &JS<Document>) -> JS<HTMLCollection> {
@ -490,32 +504,63 @@ impl Document {
pub fn Links(&self, abstract_self: &JS<Document>) -> JS<HTMLCollection> { pub fn Links(&self, abstract_self: &JS<Document>) -> JS<HTMLCollection> {
// FIXME: https://github.com/mozilla/servo/issues/1847 // FIXME: https://github.com/mozilla/servo/issues/1847
HTMLCollection::create(&self.window, &NodeCast::from(abstract_self), |elem| { struct LinksFilter;
("a" == elem.get().tag_name || "area" == elem.get().tag_name) && impl CollectionFilter for LinksFilter {
fn filter(&self, elem: &JS<Element>, _root: &JS<Node>) -> bool {
(elem.get().tag_name == ~"a" || elem.get().tag_name == ~"area") &&
elem.get_attribute(Null, "href").is_some() elem.get_attribute(Null, "href").is_some()
}) }
}
let filter = ~LinksFilter;
HTMLCollection::create(&self.window, &NodeCast::from(abstract_self), filter)
} }
pub fn Forms(&self, abstract_self: &JS<Document>) -> JS<HTMLCollection> { pub fn Forms(&self, abstract_self: &JS<Document>) -> JS<HTMLCollection> {
// FIXME: https://github.com/mozilla/servo/issues/1847 // FIXME: https://github.com/mozilla/servo/issues/1847
HTMLCollection::by_tag_name(&self.window, &NodeCast::from(abstract_self), ~"form") struct FormsFilter;
impl CollectionFilter for FormsFilter {
fn filter(&self, elem: &JS<Element>, _root: &JS<Node>) -> bool {
elem.get().tag_name == ~"form"
}
}
let filter = ~FormsFilter;
HTMLCollection::create(&self.window, &NodeCast::from(abstract_self), filter)
} }
pub fn Scripts(&self, abstract_self: &JS<Document>) -> JS<HTMLCollection> { pub fn Scripts(&self, abstract_self: &JS<Document>) -> JS<HTMLCollection> {
// FIXME: https://github.com/mozilla/servo/issues/1847 // FIXME: https://github.com/mozilla/servo/issues/1847
HTMLCollection::by_tag_name(&self.window, &NodeCast::from(abstract_self), ~"script") struct ScriptsFilter;
impl CollectionFilter for ScriptsFilter {
fn filter(&self, elem: &JS<Element>, _root: &JS<Node>) -> bool {
elem.get().tag_name == ~"script"
}
}
let filter = ~ScriptsFilter;
HTMLCollection::create(&self.window, &NodeCast::from(abstract_self), filter)
} }
pub fn Anchors(&self, abstract_self: &JS<Document>) -> JS<HTMLCollection> { pub fn Anchors(&self, abstract_self: &JS<Document>) -> JS<HTMLCollection> {
// FIXME: https://github.com/mozilla/servo/issues/1847 // FIXME: https://github.com/mozilla/servo/issues/1847
HTMLCollection::create(&self.window, &NodeCast::from(abstract_self), |elem| { struct AnchorsFilter;
"a" == elem.get().tag_name && elem.get_attribute(Null, "name").is_some() impl CollectionFilter for AnchorsFilter {
}) fn filter(&self, elem: &JS<Element>, _root: &JS<Node>) -> bool {
elem.get().tag_name == ~"a" && elem.get_attribute(Null, "name").is_some()
}
}
let filter = ~AnchorsFilter;
HTMLCollection::create(&self.window, &NodeCast::from(abstract_self), filter)
} }
pub fn Applets(&self, abstract_self: &JS<Document>) -> JS<HTMLCollection> { pub fn Applets(&self, abstract_self: &JS<Document>) -> JS<HTMLCollection> {
// FIXME: This should be return OBJECT elements containing applets. // FIXME: This should be return OBJECT elements containing applets.
HTMLCollection::by_tag_name(&self.window, &NodeCast::from(abstract_self), ~"applet") struct AppletsFilter;
impl CollectionFilter for AppletsFilter {
fn filter(&self, elem: &JS<Element>, _root: &JS<Node>) -> bool {
elem.get().tag_name == ~"applet"
}
}
let filter = ~AppletsFilter;
HTMLCollection::create(&self.window, &NodeCast::from(abstract_self), filter)
} }
pub fn create_collection<T>(&self, callback: |elem: &JS<Node>| -> Option<JS<T>>) -> ~[JS<T>] { pub fn create_collection<T>(&self, callback: |elem: &JS<Node>| -> Option<JS<T>>) -> ~[JS<T>] {

View file

@ -12,69 +12,123 @@ use dom::window::Window;
use servo_util::namespace::Namespace; use servo_util::namespace::Namespace;
use servo_util::str::DOMString; use servo_util::str::DOMString;
use serialize::{Encoder, Encodable};
pub trait CollectionFilter {
fn filter(&self, elem: &JS<Element>, root: &JS<Node>) -> bool;
}
impl<S: Encoder> Encodable<S> for ~CollectionFilter {
fn encode(&self, _s: &mut S) {}
}
#[deriving(Encodable)]
pub enum CollectionTypeId {
Static(~[JS<Element>]),
Live(JS<Node>, ~CollectionFilter)
}
#[deriving(Encodable)] #[deriving(Encodable)]
pub struct HTMLCollection { pub struct HTMLCollection {
elements: ~[JS<Element>], collection: CollectionTypeId,
reflector_: Reflector, reflector_: Reflector,
window: JS<Window>, window: JS<Window>,
} }
impl HTMLCollection { impl HTMLCollection {
pub fn new_inherited(window: JS<Window>, elements: ~[JS<Element>]) -> HTMLCollection { pub fn new_inherited(window: JS<Window>, collection: CollectionTypeId) -> HTMLCollection {
HTMLCollection { HTMLCollection {
elements: elements, collection: collection,
reflector_: Reflector::new(), reflector_: Reflector::new(),
window: window, window: window,
} }
} }
pub fn new(window: &JS<Window>, elements: ~[JS<Element>]) -> JS<HTMLCollection> { pub fn new(window: &JS<Window>, collection: CollectionTypeId) -> JS<HTMLCollection> {
reflect_dom_object(~HTMLCollection::new_inherited(window.clone(), elements), reflect_dom_object(~HTMLCollection::new_inherited(window.clone(), collection),
window, HTMLCollectionBinding::Wrap) window, HTMLCollectionBinding::Wrap)
} }
} }
impl HTMLCollection { impl HTMLCollection {
pub fn create(window: &JS<Window>, root: &JS<Node>, predicate: |elem: &JS<Element>| -> bool) -> JS<HTMLCollection> { pub fn create(window: &JS<Window>, root: &JS<Node>, filter: ~CollectionFilter) -> JS<HTMLCollection> {
let mut elements = ~[]; HTMLCollection::new(window, Live(root.clone(), filter))
for child in root.traverse_preorder() {
if child.is_element() {
let elem: JS<Element> = ElementCast::to(&child).unwrap();
if predicate(&elem) {
elements.push(elem);
}
}
}
HTMLCollection::new(window, elements)
} }
pub fn by_tag_name(window: &JS<Window>, root: &JS<Node>, tag_name: DOMString) -> JS<HTMLCollection> { pub fn by_tag_name(window: &JS<Window>, root: &JS<Node>, tag: DOMString)
HTMLCollection::create(window, root, |elem| elem.get().tag_name == tag_name) -> JS<HTMLCollection> {
struct TagNameFilter {
tag: DOMString
}
impl CollectionFilter for TagNameFilter {
fn filter(&self, elem: &JS<Element>, _root: &JS<Node>) -> bool {
elem.get().tag_name == self.tag
}
}
let filter = TagNameFilter {
tag: tag
};
HTMLCollection::create(window, root, ~filter)
} }
pub fn by_tag_name_ns(window: &JS<Window>, root: &JS<Node>, tag_name: DOMString, namespace: Namespace) -> JS<HTMLCollection> { pub fn by_tag_name_ns(window: &JS<Window>, root: &JS<Node>, tag: DOMString,
HTMLCollection::create(window, root, |elem| elem.get().namespace == namespace && elem.get().tag_name == tag_name) namespace: Namespace) -> JS<HTMLCollection> {
struct TagNameNSFilter {
tag: DOMString,
namespace: Namespace
}
impl CollectionFilter for TagNameNSFilter {
fn filter(&self, elem: &JS<Element>, _root: &JS<Node>) -> bool {
elem.get().namespace == self.namespace && elem.get().tag_name == self.tag
}
}
let filter = TagNameNSFilter {
tag: tag,
namespace: namespace
};
HTMLCollection::create(window, root, ~filter)
} }
pub fn by_class_name(window: &JS<Window>, root: &JS<Node>, classes: DOMString) -> JS<HTMLCollection> { pub fn by_class_name(window: &JS<Window>, root: &JS<Node>, classes: DOMString)
// FIXME: https://github.com/mozilla/servo/issues/1840 -> JS<HTMLCollection> {
let classes: ~[&str] = classes.split(' ').collect(); struct ClassNameFilter {
HTMLCollection::create(window, root, |elem| classes.iter().all(|class| elem.has_class(*class))) classes: ~[DOMString]
}
impl CollectionFilter for ClassNameFilter {
fn filter(&self, elem: &JS<Element>, _root: &JS<Node>) -> bool {
self.classes.iter().all(|class| elem.has_class(*class))
}
}
let filter = ClassNameFilter {
classes: classes.split(' ').map(|class| class.into_owned()).to_owned_vec()
};
HTMLCollection::create(window, root, ~filter)
} }
} }
impl HTMLCollection { impl HTMLCollection {
// http://dom.spec.whatwg.org/#dom-htmlcollection-length // http://dom.spec.whatwg.org/#dom-htmlcollection-length
pub fn Length(&self) -> u32 { pub fn Length(&self) -> u32 {
self.elements.len() as u32 match self.collection {
Static(ref elems) => elems.len() as u32,
Live(ref root, ref filter) => root.traverse_preorder()
.count(|child| {
let elem: Option<JS<Element>> = ElementCast::to(&child);
elem.map_or(false, |elem| filter.filter(&elem, root))
}) as u32
}
} }
// http://dom.spec.whatwg.org/#dom-htmlcollection-item // http://dom.spec.whatwg.org/#dom-htmlcollection-item
pub fn Item(&self, index: u32) -> Option<JS<Element>> { pub fn Item(&self, index: u32) -> Option<JS<Element>> {
if index < self.Length() { match self.collection {
Some(self.elements[index].clone()) Static(ref elems) => elems
} else { .get(index as uint)
None .map(|elem| elem.clone()),
Live(ref root, ref filter) => root.traverse_preorder()
.filter_map(|node| ElementCast::to(&node))
.filter(|elem| filter.filter(elem, root))
.nth(index as uint).clone()
} }
} }
@ -86,9 +140,20 @@ impl HTMLCollection {
} }
// Step 2. // Step 2.
self.elements.iter().find(|elem| { match self.collection {
elem.get_string_attribute("name") == key || elem.get_string_attribute("id") == key Static(ref elems) => elems.iter()
}).map(|maybe_elem| maybe_elem.clone()) .find(|elem| {
elem.get_string_attribute("name") == key ||
elem.get_string_attribute("id") == key })
.map(|maybe_elem| maybe_elem.clone()),
Live(ref root, ref filter) => root.traverse_preorder()
.filter_map(|node| ElementCast::to(&node))
.filter(|elem| filter.filter(elem, root))
.find(|elem| {
elem.get_string_attribute("name") == key ||
elem.get_string_attribute("id") == key })
.map(|maybe_elem| maybe_elem.clone())
}
} }
} }

View file

@ -6,11 +6,11 @@ use dom::bindings::codegen::HTMLDataListElementBinding;
use dom::bindings::codegen::InheritTypes::{HTMLDataListElementDerived, NodeCast}; use dom::bindings::codegen::InheritTypes::{HTMLDataListElementDerived, NodeCast};
use dom::bindings::js::JS; use dom::bindings::js::JS;
use dom::document::Document; use dom::document::Document;
use dom::element::HTMLDataListElementTypeId; use dom::element::{Element, HTMLDataListElementTypeId};
use dom::eventtarget::{EventTarget, NodeTargetTypeId}; use dom::eventtarget::{EventTarget, NodeTargetTypeId};
use dom::htmlcollection::HTMLCollection; use dom::htmlcollection::{HTMLCollection, CollectionFilter};
use dom::htmlelement::HTMLElement; use dom::htmlelement::HTMLElement;
use dom::node::{Node, ElementNodeTypeId}; use dom::node::{Node, ElementNodeTypeId, window_from_node};
use servo_util::str::DOMString; use servo_util::str::DOMString;
#[deriving(Encodable)] #[deriving(Encodable)]
@ -42,9 +42,14 @@ impl HTMLDataListElement {
impl HTMLDataListElement { impl HTMLDataListElement {
pub fn Options(&self, abstract_self: &JS<HTMLDataListElement>) -> JS<HTMLCollection> { pub fn Options(&self, abstract_self: &JS<HTMLDataListElement>) -> JS<HTMLCollection> {
struct HTMLDataListOptionsFilter;
impl CollectionFilter for HTMLDataListOptionsFilter {
fn filter(&self, elem: &JS<Element>, _root: &JS<Node>) -> bool {
elem.get().tag_name == ~"option"
}
}
let node: JS<Node> = NodeCast::from(abstract_self); let node: JS<Node> = NodeCast::from(abstract_self);
let doc = &self.htmlelement.element.node.owner_doc(); let filter = ~HTMLDataListOptionsFilter;
let window = &doc.get().window; HTMLCollection::create(&window_from_node(&node), &node, filter)
HTMLCollection::by_tag_name(window, &node, ~"option")
} }
} }

View file

@ -10,11 +10,11 @@ use dom::document::Document;
use dom::element::{Element, HTMLFieldSetElementTypeId}; use dom::element::{Element, HTMLFieldSetElementTypeId};
use dom::eventtarget::{EventTarget, NodeTargetTypeId}; use dom::eventtarget::{EventTarget, NodeTargetTypeId};
use dom::htmlformelement::HTMLFormElement; use dom::htmlformelement::HTMLFormElement;
use dom::htmlcollection::HTMLCollection; use dom::htmlcollection::{HTMLCollection, CollectionFilter};
use dom::htmlelement::HTMLElement; use dom::htmlelement::HTMLElement;
use dom::node::{Node, ElementNodeTypeId, window_from_node}; use dom::node::{Node, ElementNodeTypeId, window_from_node};
use dom::validitystate::ValidityState; use dom::validitystate::ValidityState;
use servo_util::str::DOMString; use servo_util::str::{DOMString, StaticStringVec};
#[deriving(Encodable)] #[deriving(Encodable)]
pub struct HTMLFieldSetElement { pub struct HTMLFieldSetElement {
@ -68,12 +68,20 @@ impl HTMLFieldSetElement {
~"" ~""
} }
// http://www.whatwg.org/html/#dom-fieldset-elements
pub fn Elements(&self, abstract_self: &JS<HTMLFieldSetElement>) -> JS<HTMLCollection> { pub fn Elements(&self, abstract_self: &JS<HTMLFieldSetElement>) -> JS<HTMLCollection> {
struct ElementsFilter;
impl CollectionFilter for ElementsFilter {
fn filter(&self, elem: &JS<Element>, root: &JS<Node>) -> bool {
static tag_names: StaticStringVec = &["button", "fieldset", "input",
"keygen", "object", "output", "select", "textarea"];
let root: &JS<Element> = &ElementCast::to(root).unwrap();
elem != root && tag_names.iter().any(|&tag_name| tag_name == elem.get().tag_name)
}
}
let node: JS<Node> = NodeCast::from(abstract_self); let node: JS<Node> = NodeCast::from(abstract_self);
let element: JS<Element> = ElementCast::from(abstract_self); let filter = ~ElementsFilter;
let window = &window_from_node(&node); HTMLCollection::create(&window_from_node(&node), &node, filter)
let listed_elements = ["button", "fieldset", "input", "keygen", "object", "output", "select", "textarea"];
HTMLCollection::create(window, &node, |elem| *elem != element && listed_elements.iter().any(|&tag_name| tag_name == elem.get().tag_name))
} }
pub fn WillValidate(&self) -> bool { pub fn WillValidate(&self) -> bool {

View file

@ -9,7 +9,7 @@ use dom::bindings::error::ErrorResult;
use dom::document::Document; use dom::document::Document;
use dom::element::{Element, HTMLFormElementTypeId}; use dom::element::{Element, HTMLFormElementTypeId};
use dom::eventtarget::{EventTarget, NodeTargetTypeId}; use dom::eventtarget::{EventTarget, NodeTargetTypeId};
use dom::htmlcollection::HTMLCollection; use dom::htmlcollection::{HTMLCollection, Static};
use dom::htmlelement::HTMLElement; use dom::htmlelement::HTMLElement;
use dom::node::{Node, ElementNodeTypeId}; use dom::node::{Node, ElementNodeTypeId};
use servo_util::str::DOMString; use servo_util::str::DOMString;
@ -118,7 +118,7 @@ impl HTMLFormElement {
// FIXME: https://github.com/mozilla/servo/issues/1844 // FIXME: https://github.com/mozilla/servo/issues/1844
let doc = self.htmlelement.element.node.owner_doc(); let doc = self.htmlelement.element.node.owner_doc();
let doc = doc.get(); let doc = doc.get();
HTMLCollection::new(&doc.window, ~[]) HTMLCollection::new(&doc.window, Static(~[]))
} }
pub fn Length(&self) -> i32 { pub fn Length(&self) -> i32 {

View file

@ -9,7 +9,7 @@ use dom::bindings::error::ErrorResult;
use dom::document::Document; use dom::document::Document;
use dom::element::HTMLMapElementTypeId; use dom::element::HTMLMapElementTypeId;
use dom::eventtarget::{EventTarget, NodeTargetTypeId}; use dom::eventtarget::{EventTarget, NodeTargetTypeId};
use dom::htmlcollection::HTMLCollection; use dom::htmlcollection::{HTMLCollection, Static};
use dom::htmlelement::HTMLElement; use dom::htmlelement::HTMLElement;
use dom::node::{Node, ElementNodeTypeId}; use dom::node::{Node, ElementNodeTypeId};
use servo_util::str::DOMString; use servo_util::str::DOMString;
@ -54,6 +54,6 @@ impl HTMLMapElement {
// FIXME: https://github.com/mozilla/servo/issues/1845 // FIXME: https://github.com/mozilla/servo/issues/1845
let doc = self.htmlelement.element.node.owner_doc(); let doc = self.htmlelement.element.node.owner_doc();
let doc = doc.get(); let doc = doc.get();
HTMLCollection::new(&doc.window, ~[]) HTMLCollection::new(&doc.window, Static(~[]))
} }
} }

View file

@ -3,6 +3,7 @@
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
pub type DOMString = ~str; pub type DOMString = ~str;
pub type StaticStringVec = &'static [&'static str];
pub fn null_str_as_empty(s: &Option<DOMString>) -> DOMString { pub fn null_str_as_empty(s: &Option<DOMString>) -> DOMString {
// We don't use map_default because it would allocate ~"" even for Some. // We don't use map_default because it would allocate ~"" even for Some.

View file

@ -40,33 +40,25 @@
is(htmlcollection.length, 1); is(htmlcollection.length, 1);
is(htmlcollection.item(0), live); is(htmlcollection.item(0), live);
is(live.childNodes.length, 0) let new_live = document.createElement("div");
is(htmlcollection.item(0).childNodes.length, 0); new_live.className = "live";
is(document.getElementById("live").childNodes.length, 0); document.body.appendChild(new_live);
is(htmlcollection.length, 2);
is(htmlcollection.item(1), new_live);
live.appendChild(child); document.body.removeChild(new_live);
is(htmlcollection.length, 1);
is(live.childNodes.length, 1);
is(htmlcollection.item(0).childNodes.length, 1);
is(document.getElementById("live").childNodes.length, 1);
} }
// test3: getElementsByTagName // test3: getElementsByTagName
{ {
htmlcollection = document.getElementsByTagName("div"); is(document.getElementsByTagName("DIV").length, 0);
is(htmlcollection.length, 5);
let from_element = document.documentElement.getElementsByTagName("div"); is(document.getElementsByTagName("div").length,
is(htmlcollection.length, from_element.length); document.documentElement.getElementsByTagName("div").length);
htmlcollection = document.getElementsByTagName("DIV"); is(document.getElementsByTagName("p").length,
is(htmlcollection.length, 0); document.getElementById("class-example").getElementsByTagName("p").length);
htmlcollection = document.getElementsByTagName("p");
is(htmlcollection.length, 4);
from_element = document.getElementById("class-example").getElementsByTagName("p");
is(from_element.length, 3);
} }
// test4: getElementsByTagNameNS // test4: getElementsByTagNameNS