Basic Implementation of document.getElementById(), #740

This commit is contained in:
Tetsuharu OHZEKI 2013-09-16 23:00:18 +09:00
parent f9be872e61
commit db3b5c3c4c
4 changed files with 126 additions and 4 deletions

View file

@ -25,6 +25,8 @@ use js::jsapi::{JSTRACE_OBJECT, JSTracer, JS_CallTracer};
use js::glue::RUST_OBJECT_TO_JSVAL; use js::glue::RUST_OBJECT_TO_JSVAL;
use servo_util::tree::TreeNodeRef; use servo_util::tree::TreeNodeRef;
use std::hashmap::HashMap;
use std::cast; use std::cast;
use std::ptr; use std::ptr;
use std::str::eq_slice; use std::str::eq_slice;
@ -92,6 +94,9 @@ impl AbstractDocument {
})); }));
self.with_mut_base(|document| { self.with_mut_base(|document| {
document.root = Some(root); document.root = Some(root);
// Register elements having "id" attribute to the owner doc.
document.register_nodes_with_id(&root);
document.content_changed(); document.content_changed();
}); });
} }
@ -108,7 +113,8 @@ pub struct Document {
reflector_: Reflector, reflector_: Reflector,
window: Option<@mut Window>, window: Option<@mut Window>,
doctype: DocumentType, doctype: DocumentType,
title: ~str title: ~str,
idmap: HashMap<~str, AbstractNode<ScriptView>>
} }
impl Document { impl Document {
@ -119,7 +125,8 @@ impl Document {
reflector_: Reflector::new(), reflector_: Reflector::new(),
window: window, window: window,
doctype: doctype, doctype: doctype,
title: ~"" title: ~"",
idmap: HashMap::new()
} }
} }
@ -265,8 +272,11 @@ impl Document {
HTMLCollection::new(~[], cx, scope) HTMLCollection::new(~[], cx, scope)
} }
pub fn GetElementById(&self, _id: &DOMString) -> Option<AbstractNode<ScriptView>> { pub fn GetElementById(&self, id: &DOMString) -> Option<AbstractNode<ScriptView>> {
None let key: &~str = &null_str_as_empty(id);
// TODO: "in tree order, within the context object's tree"
// http://dom.spec.whatwg.org/#dom-document-getelementbyid.
self.idmap.find_equiv(key).map(|node| **node)
} }
pub fn CreateElement(&self, abstract_self: AbstractDocument, local_name: &DOMString) -> Fallible<AbstractNode<ScriptView>> { pub fn CreateElement(&self, abstract_self: AbstractDocument, local_name: &DOMString) -> Fallible<AbstractNode<ScriptView>> {
@ -513,6 +523,41 @@ impl Document {
window.wait_until_safe_to_modify_dom(); window.wait_until_safe_to_modify_dom();
} }
} }
pub fn register_nodes_with_id(&mut self, root: &AbstractNode<ScriptView>) {
foreach_ided_elements(root, |id: &~str, abstract_node: &AbstractNode<ScriptView>| {
// TODO: "in tree order, within the context object's tree"
// http://dom.spec.whatwg.org/#dom-document-getelementbyid.
self.idmap.find_or_insert(id.clone(), *abstract_node);
});
}
pub fn unregister_nodes_with_id(&mut self, root: &AbstractNode<ScriptView>) {
foreach_ided_elements(root, |id: &~str, _| {
// TODO: "in tree order, within the context object's tree"
// http://dom.spec.whatwg.org/#dom-document-getelementbyid.
self.idmap.pop(id);
});
}
}
#[inline(always)]
fn foreach_ided_elements(root: &AbstractNode<ScriptView>,
callback: &fn(&~str, &AbstractNode<ScriptView>)) {
for node in root.traverse_preorder() {
if !node.is_element() {
loop;
}
do node.with_imm_element |element| {
match element.get_attr("id") {
Some(id) => {
callback(&id.to_str(), &node);
}
None => ()
}
}
}
} }
impl Traceable for Document { impl Traceable for Document {

View file

@ -170,6 +170,9 @@ impl<'self> Element {
null_str_as_empty_ref(raw_value))); null_str_as_empty_ref(raw_value)));
} }
// TODO: update owner document's id hashmap for `document.getElementById()`
// if `name` == "id".
//XXXjdm We really need something like a vtable so we can call AfterSetAttr. //XXXjdm We really need something like a vtable so we can call AfterSetAttr.
// This hardcoding is awful. // This hardcoding is awful.
match abstract_self.type_id() { match abstract_self.type_id() {

View file

@ -473,6 +473,16 @@ impl Node<ScriptView> {
cur_node = cur_node.unwrap().next_sibling(); cur_node = cur_node.unwrap().next_sibling();
} }
// Unregister elements having "id' from the old doc.
do old_doc.with_mut_base |old_doc| {
old_doc.unregister_nodes_with_id(&abstract_self);
}
// Register elements having "id" attribute to the owner doc.
do doc.with_mut_base |doc| {
doc.register_nodes_with_id(&abstract_self);
}
// Signal the old document that it needs to update its display // Signal the old document that it needs to update its display
if old_doc != doc { if old_doc != doc {
do old_doc.with_base |old_doc| { do old_doc.with_base |old_doc| {
@ -779,6 +789,12 @@ impl Node<ScriptView> {
self.wait_until_safe_to_modify_dom(); self.wait_until_safe_to_modify_dom();
// Unregister elements having "id' from the owner doc.
// This need be called before target nodes are removed from tree.
do self.owner_doc.with_mut_base |doc| {
doc.unregister_nodes_with_id(&abstract_self);
}
abstract_self.remove_child(node); abstract_self.remove_child(node);
// Signal the document that it needs to update its display. // Signal the document that it needs to update its display.
do self.owner_doc.with_base |document| { do self.owner_doc.with_base |document| {

View file

@ -0,0 +1,58 @@
<html>
<head id="foo">
<title></title>
<script src="harness.js"></script>
</head>
<body>
<div id="bar"></div>
<script>
let gBody = document.getElementsByTagName("body")[0];
// test1: on static page
{
let foo = document.getElementById("foo");
isnot(foo, null, "test-1-0, on static page");
is(foo && foo.tagName, "HEAD", "test1-1, on static page");
is(foo instanceof HTMLHeadElement, true, "test1-2, on static page");
let bar = document.getElementById("bar");
isnot(bar, null, "test1-3, on static page");
is(bar && bar.tagName, "DIV", "test1-4, on static page");
is(bar instanceof HTMLDivElement, true, "test1-5, on static page");
}
// test2: scripted element
{
let TEST_ID = "test";
let test = document.createElement("div");
test.setAttribute("id", TEST_ID);
gBody.appendChild(test);
// test: appended element
let appended = document.getElementById(TEST_ID);
isnot(appended, null, "test2-0, appended element");
is(appended && appended.tagName, "DIV", "test2-1, appended element");
is(appended instanceof HTMLDivElement, true, "test2-2, appended element");
// test: removed element
gBody.removeChild(test);
let removed = document.getElementById(TEST_ID);
// `document.getElementById()` returns `null` if there is none.
// http://www.w3.org/TR/DOM-Level-3-Core/core.html#ID-getElBId
// http://dom.spec.whatwg.org/#dom-document-getelementbyid (2013-09-20)
is(removed, null, "test2-3, removed element");
}
// TODO:
// test3: update `id` attribute
// TODO:
// test4: "in tree order, within the context object's tree"
// http://dom.spec.whatwg.org/#dom-document-getelementbyid.
// TODO:
// test5: innerHTML
finish();
</script>
</body>
</html>