Skip to content

Commit bf66add

Browse files
committed
Fix Node drop recursion
`Node::drop` no longer clobbers nodes that have active external handles
1 parent abf850e commit bf66add

1 file changed

Lines changed: 100 additions & 11 deletions

File tree

rcdom/lib.rs

Lines changed: 100 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -259,17 +259,25 @@ impl Node {
259259

260260
impl Drop for Node {
261261
fn drop(&mut self) {
262-
let mut nodes = mem::take(&mut *self.children.borrow_mut());
263-
while let Some(node) = nodes.pop() {
264-
let children = mem::take(&mut *node.children.borrow_mut());
265-
nodes.extend(children.into_iter());
266-
if let NodeData::Element {
267-
ref template_contents,
268-
..
269-
} = node.data
270-
{
271-
if let Some(template_contents) = template_contents.borrow_mut().take() {
272-
nodes.push(template_contents);
262+
// Iteratively drop nodes, to prevent stack overflows, but take care
263+
// to only clear children for doomed nodes.
264+
265+
let mut droplist = mem::take(self.children.get_mut());
266+
267+
while let Some(node) = droplist.pop() {
268+
// Take all of the nodes out of `node` that we can get _ownership_
269+
// of and add them to the droplist. Ownership ensures that there are
270+
// no external live handles to that node.
271+
if let Some(mut node) = Rc::into_inner(node) {
272+
droplist.extend(mem::take(node.children.get_mut()));
273+
if let NodeData::Element {
274+
ref mut template_contents,
275+
..
276+
} = node.data
277+
{
278+
if let Some(node) = template_contents.get_mut().take() {
279+
droplist.push(node);
280+
}
273281
}
274282
}
275283
}
@@ -673,3 +681,84 @@ impl Serialize for SerializableHandle {
673681
Ok(())
674682
}
675683
}
684+
685+
#[cfg(test)]
686+
mod tests {
687+
use super::*;
688+
use html5ever::{parse_document, ParseOpts};
689+
use tendril::TendrilSink;
690+
691+
/// Assert that a node is an element and that it has a certain local name
692+
fn assert_node_name(node: &Node, expected: &str) {
693+
match node.data {
694+
NodeData::Element { ref name, .. } => assert_eq!(&name.local, expected),
695+
_ => panic!("node wasn't an element"),
696+
}
697+
}
698+
699+
/// Assert that a node is a text node and that it has certain contents
700+
fn assert_node_text(node: &Node, expected: &str) {
701+
match node.data {
702+
NodeData::Text { ref contents } => assert_eq!(contents.borrow().as_ref(), expected),
703+
_ => panic!("node isn't a text node"),
704+
}
705+
}
706+
707+
// Ensuse that, when a node is dropped, its children that still have handles
708+
// in circulation are not unlinked from each other
709+
#[test]
710+
fn drop_node_preserves_children() {
711+
let doc = "
712+
<html>\
713+
<head>\
714+
</head>\
715+
<body>\
716+
<header>\
717+
<nav>Nav</nav>\
718+
</header>\
719+
<article>\
720+
<h1>Title</h1>\
721+
<section>\
722+
<p>text 1</p>\
723+
<p>text 2</p>\
724+
</section>\
725+
</article>\
726+
<footer>Footer</footer>\
727+
</body>\
728+
</html>";
729+
730+
// Get just the `article` element and drop everything else. Ensure
731+
// that the article element still has all its children.
732+
let (article, body_weak) = {
733+
let dom =
734+
parse_document(RcDom::default(), ParseOpts::default()).one(StrTendril::from(doc));
735+
736+
let document = dom.document;
737+
let html = document.children.borrow()[0].clone();
738+
let body = html.children.borrow()[1].clone();
739+
let article = body.children.borrow()[1].clone();
740+
let body_weak = Rc::downgrade(&body);
741+
742+
assert_node_name(&article, "article");
743+
744+
(article, body_weak)
745+
};
746+
747+
// Ensure that the body was dropped, as expected
748+
assert!(body_weak.upgrade().is_none());
749+
750+
let children = article.children.borrow();
751+
assert_eq!(children.len(), 2);
752+
753+
let h1 = &children[0];
754+
assert_node_name(h1, "h1");
755+
assert_node_text(&h1.children.borrow()[0], "Title");
756+
757+
let section = &children[1];
758+
assert_node_name(section, "section");
759+
assert_node_name(&section.children.borrow()[0], "p");
760+
assert_node_text(&section.children.borrow()[0].children.borrow()[0], "text 1");
761+
assert_node_name(&section.children.borrow()[1], "p");
762+
assert_node_text(&section.children.borrow()[1].children.borrow()[0], "text 2");
763+
}
764+
}

0 commit comments

Comments
 (0)