@@ -259,17 +259,25 @@ impl Node {
259259
260260impl 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