diff options
author | Patrick Walton <pcwalton@mimiga.net> | 2013-05-02 19:12:09 -0700 |
---|---|---|
committer | Patrick Walton <pcwalton@mimiga.net> | 2013-05-02 19:12:09 -0700 |
commit | fddf4ca30e7bc68c93a6f61e5a405aa9701cfc0f (patch) | |
tree | b7e64a12ad440fa422e6fa97800a508f05c956d6 | |
parent | c74249aee5f1f3d13949ed8d88eb540276638006 (diff) | |
download | servo-fddf4ca30e7bc68c93a6f61e5a405aa9701cfc0f.tar.gz servo-fddf4ca30e7bc68c93a6f61e5a405aa9701cfc0f.zip |
Update style of flow and remove its reliance on `tree`
`tree` is an old API that predates the use of traits. It should be redone.
This commit breaks the dependency on it.
-rw-r--r-- | src/servo/layout/block.rs | 13 | ||||
-rw-r--r-- | src/servo/layout/box_builder.rs | 44 | ||||
-rw-r--r-- | src/servo/layout/flow.rs | 297 | ||||
-rw-r--r-- | src/servo/layout/root.rs | 4 | ||||
-rw-r--r-- | src/servo/layout/traverse.rs | 12 |
5 files changed, 223 insertions, 147 deletions
diff --git a/src/servo/layout/block.rs b/src/servo/layout/block.rs index 130cc4da6ee..7b1fb67faa2 100644 --- a/src/servo/layout/block.rs +++ b/src/servo/layout/block.rs @@ -4,15 +4,14 @@ // Block layout. -use core::cell::Cell; - use layout::box::{RenderBox}; use layout::context::LayoutContext; use layout::display_list_builder::{DisplayListBuilder, FlowDisplayListBuilderMethods}; -use layout::flow::{FlowContext, FlowTree, InlineBlockFlow, BlockFlow, RootFlow}; +use layout::flow::{BlockFlow, FlowContext, InlineBlockFlow, RootFlow}; use layout::inline::InlineLayout; use au = gfx::geometry; +use core::cell::Cell; use geom::point::Point2D; use geom::rect::Rect; use gfx::display_list::DisplayList; @@ -82,7 +81,7 @@ impl BlockLayout for FlowContext { let mut pref_width = Au(0); /* find max width from child block contexts */ - for FlowTree.each_child(self) |child_ctx| { + for self.each_child |child_ctx| { assert!(child_ctx.starts_block_flow() || child_ctx.starts_inline_flow()); min_width = au::max(min_width, child_ctx.d().min_width); @@ -122,7 +121,7 @@ impl BlockLayout for FlowContext { remaining_width -= left_used.add(&right_used); } - for FlowTree.each_child(self) |child_ctx| { + for self.each_child |child_ctx| { assert!(child_ctx.starts_block_flow() || child_ctx.starts_inline_flow()); child_ctx.d().position.origin.x = left_used; child_ctx.d().position.size.width = remaining_width; @@ -134,7 +133,7 @@ impl BlockLayout for FlowContext { let mut cur_y = Au(0); - for FlowTree.each_child(self) |child_ctx| { + for self.each_child |child_ctx| { child_ctx.d().position.origin.y = cur_y; cur_y += child_ctx.d().position.size.height; } @@ -164,7 +163,7 @@ impl BlockLayout for FlowContext { // TODO: handle any out-of-flow elements // go deeper into the flow tree - for FlowTree.each_child(self) |child| { + for self.each_child |child| { self.build_display_list_for_child(builder, child, dirty, offset, list) } } diff --git a/src/servo/layout/box_builder.rs b/src/servo/layout/box_builder.rs index edaeaf5c00e..f127efa03e7 100644 --- a/src/servo/layout/box_builder.rs +++ b/src/servo/layout/box_builder.rs @@ -14,7 +14,6 @@ use layout::debug::{BoxedMutDebugMethods, DebugMethods}; use layout::flow::*; use layout::inline::{InlineFlowData, InlineLayout}; use layout::root::RootFlowData; -use util::tree; use gfx::image::holder::ImageHolder; use servo_util::range::Range; @@ -223,9 +222,8 @@ impl BuilderContext { priv fn attach_child_flow(&self, child: @mut FlowContext) { let d = self.default_collector.flow.d(); // FIXME: borrow checker workaround let cd = child.d(); // FIXME: borrow checker workaround - debug!("BuilderContext: Adding child flow f%? of f%?", - d.id, cd.id); - tree::add_child(&FlowTree, self.default_collector.flow, child); + debug!("BuilderContext: Adding child flow f%? of f%?", d.id, cd.id); + self.default_collector.flow.add_child(child); } priv fn create_child_flow_of_type(&self, @@ -332,10 +330,10 @@ pub impl LayoutTreeBuilder { // eventually be elided or split, but the mapping between // nodes and FlowContexts should not change during layout. let flow = &mut this_ctx.default_collector.flow; - for tree::each_child(&FlowTree, flow) |child_flow: &@mut FlowContext| { + for flow.each_child |child_flow: @mut FlowContext| { let node = child_flow.d().node; assert!(node.has_layout_data()); - node.layout_data().flow = Some(*child_flow); + node.layout_data().flow = Some(child_flow); } } @@ -355,8 +353,8 @@ pub impl LayoutTreeBuilder { let mut found_child_block = false; let flow = &mut parent_ctx.default_collector.flow; - for tree::each_child(&FlowTree, flow) |child_ctx: &@mut FlowContext| { - match **child_ctx { + for flow.each_child |child_ctx: @mut FlowContext| { + match *child_ctx { InlineFlow(*) | InlineBlockFlow(*) => found_child_inline = true, BlockFlow(*) => found_child_block = true, _ => {} @@ -371,24 +369,36 @@ pub impl LayoutTreeBuilder { // FIXME: this will create refcounted cycles between the removed flow and any // of its RenderBox or FlowContext children, and possibly keep alive other junk let parent_flow = parent_ctx.default_collector.flow; + + // FIXME: Workaround for the borrow check. + let (first_child, last_child) = { + let parent_flow: &mut FlowContext = parent_flow; + let parent_flow_data = parent_flow.d(); + (parent_flow_data.first_child, parent_flow_data.last_child) + }; + // check first/last child for whitespace-ness - for tree::first_child(&FlowTree, &parent_flow).each |first_flow: &@mut FlowContext| { + for first_child.each |first_flow: &@mut FlowContext| { if first_flow.starts_inline_flow() { let boxes = &mut first_flow.inline().boxes; if boxes.len() == 1 && boxes[0].is_whitespace_only() { - debug!("LayoutTreeBuilder: pruning whitespace-only first child flow f%d from parent f%d", - first_flow.d().id, parent_flow.d().id); - tree::remove_child(&FlowTree, parent_flow, *first_flow); + debug!("LayoutTreeBuilder: pruning whitespace-only first child flow \ + f%d from parent f%d", + first_flow.d().id, + parent_flow.d().id); + parent_flow.remove_child(*first_flow); } } } - for tree::last_child(&FlowTree, &parent_flow).each |last_flow: &@mut FlowContext| { + for last_child.each |last_flow: &@mut FlowContext| { if last_flow.starts_inline_flow() { let boxes = &mut last_flow.inline().boxes; if boxes.len() == 1 && boxes.last().is_whitespace_only() { - debug!("LayoutTreeBuilder: pruning whitespace-only last child flow f%d from parent f%d", - last_flow.d().id, parent_flow.d().id); - tree::remove_child(&FlowTree, parent_flow, *last_flow); + debug!("LayoutTreeBuilder: pruning whitespace-only last child flow \ + f%d from parent f%d", + last_flow.d().id, + parent_flow.d().id); + parent_flow.remove_child(*last_flow); } } } @@ -416,7 +426,7 @@ pub impl LayoutTreeBuilder { } fn make_flow(&mut self, ty: FlowContextType, node: AbstractNode) -> @mut FlowContext { - let data = FlowData(self.next_flow_id(), node); + let data = FlowData::new(self.next_flow_id(), node); let ret = match ty { Flow_Absolute => @mut AbsoluteFlow(data), Flow_Block => @mut BlockFlow(data, BlockFlowData()), diff --git a/src/servo/layout/flow.rs b/src/servo/layout/flow.rs index a4ea3965a7b..701136c2db2 100644 --- a/src/servo/layout/flow.rs +++ b/src/servo/layout/flow.rs @@ -2,8 +2,28 @@ * 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 core; -use core::cell::Cell; +//! Servo's experimental layout system builds a tree of `FlowContext` and `RenderBox` objects and +/// solves layout constraints to obtain positions and display attributes of tree nodes. Positions +/// are computed in several tree traversals driven by the fundamental data dependencies required by +/// inline and block layout. +/// +/// Flows are interior nodes in the layout tree and correspond closely to *flow contexts* in the +/// CSS specification. Flows are responsible for positioning their child flow contexts and render +/// boxes. Flows have purpose-specific fields, such as auxiliary line box structs, out-of-flow +/// child lists, and so on. +/// +/// Currently, the important types of flows are: +/// +/// * `BlockFlow`: a flow that establishes a block context. It has several child flows, each of +/// which are positioned according to block formatting context rules (CSS block boxes). Block +/// flows also contain a single `GenericBox` to represent their rendered borders, padding, etc. +/// (In the future, this render box may be folded into `BlockFlow` to save space.) +/// +/// * `InlineFlow`: a flow that establishes an inline context. It has a flat list of child +/// boxes/flows that are subject to inline layout and line breaking and structs to represent +/// line breaks and mapping to CSS boxes, for the purpose of handling `getClientRects()` and +/// similar methods. + use dom::node::AbstractNode; use layout::block::{BlockFlowData, BlockLayout}; use layout::box::RenderBox; @@ -12,41 +32,16 @@ use layout::debug::BoxedMutDebugMethods; use layout::display_list_builder::DisplayListBuilder; use layout::inline::{InlineFlowData, InlineLayout}; use layout::root::{RootFlowData, RootLayout}; -use util::tree; -use geom::rect::Rect; + +use core::cell::Cell; +use core::ptr; use geom::point::Point2D; +use geom::rect::Rect; use gfx::display_list::DisplayList; use gfx::geometry::Au; -/** Servo's experimental layout system builds a tree of FlowContexts -and RenderBoxes, and figures out positions and display attributes of -tree nodes. Positions are computed in several tree traversals driven -by fundamental data dependencies of inline and block layout. - -Flows are interior nodes in the layout tree, and correspond closely to -flow contexts in the CSS specification. Flows are responsible for -positioning their child flow contexts and render boxes. Flows have -purpose-specific fields, such as auxilliary line box structs, -out-of-flow child lists, and so on. - -Currently, the important types of flows are: - - * BlockFlow: a flow that establishes a block context. It has several - child flows, each of which are positioned according to block - formatting context rules (as if child flows CSS block boxes). Block - flows also contain a single GenericBox to represent their rendered - borders, padding, etc. (In the future, this render box may be - folded into BlockFlow to save space.) - - * InlineFlow: a flow that establishes an inline context. It has a - flat list of child boxes/flows that are subject to inline layout - and line breaking, and structs to represent line breaks and mapping - to CSS boxes, for the purpose of handling `getClientRects()`. - -*/ - -/* The type of the formatting context, and data specific to each -context, such as linebox structures or float lists */ +/// The type of the formatting context and data specific to each context, such as line box +/// structures or float lists. pub enum FlowContext { AbsoluteFlow(FlowData), BlockFlow(FlowData, BlockFlowData), @@ -71,8 +66,13 @@ pub enum FlowContextType { render boxes within the context. */ pub struct FlowData { node: AbstractNode, - /* reference to parent, children flow contexts */ - tree: tree::Tree<@mut FlowContext>, + + parent: Option<@mut FlowContext>, + first_child: Option<@mut FlowContext>, + last_child: Option<@mut FlowContext>, + prev_sibling: Option<@mut FlowContext>, + next_sibling: Option<@mut FlowContext>, + /* TODO (Issue #87): debug only */ id: int, @@ -84,55 +84,131 @@ pub struct FlowData { position: Rect<Au>, } -pub fn FlowData(id: int, node: AbstractNode) -> FlowData { - FlowData { - node: node, - tree: tree::empty(), - id: id, +impl FlowData { + pub fn new(id: int, node: AbstractNode) -> FlowData { + FlowData { + node: node, + + parent: None, + first_child: None, + last_child: None, + prev_sibling: None, + next_sibling: None, + + id: id, - min_width: Au(0), - pref_width: Au(0), - position: Au::zero_rect() + min_width: Au(0), + pref_width: Au(0), + position: Au::zero_rect(), + } } } -pub impl<'self> FlowContext { - fn d(&'self mut self) -> &'self mut FlowData { - unsafe { - match *self { - AbsoluteFlow(ref d) => cast::transmute(d), - BlockFlow(ref d, _) => cast::transmute(d), - FloatFlow(ref d) => cast::transmute(d), - InlineBlockFlow(ref d) => cast::transmute(d), - InlineFlow(ref d, _) => cast::transmute(d), - RootFlow(ref d, _) => cast::transmute(d), - TableFlow(ref d) => cast::transmute(d) +impl<'self> FlowContext { + pub fn d(&'self mut self) -> &'self mut FlowData { + unsafe { + match *self { + AbsoluteFlow(ref d) => cast::transmute(d), + BlockFlow(ref d, _) => cast::transmute(d), + FloatFlow(ref d) => cast::transmute(d), + InlineBlockFlow(ref d) => cast::transmute(d), + InlineFlow(ref d, _) => cast::transmute(d), + RootFlow(ref d, _) => cast::transmute(d), + TableFlow(ref d) => cast::transmute(d) + } + } + } + + /// Iterates over the immediate children of this flow. + /// + /// TODO: Fold me into `util::tree`. + pub fn each_child(@mut self, f: &fn(@mut FlowContext) -> bool) { + let mut current_opt = self.d().first_child; + while !current_opt.is_none() { + let current = current_opt.get(); + if !f(current) { + break; + } + current_opt = current.d().next_sibling; } - } } - fn inline(&'self mut self) -> &'self mut InlineFlowData { + /// Adds the given flow to the end of the list of this flow's children. The new child must be + /// detached from the tree before calling this method. + /// + /// TODO: Fold me into `util::tree`. + pub fn add_child(@mut self, child: @mut FlowContext) { + let self_data = self.d(), child_data = child.d(); + + assert!(child_data.parent.is_none()); + assert!(child_data.prev_sibling.is_none()); + assert!(child_data.next_sibling.is_none()); + + match self_data.last_child { + None => { + self_data.first_child = Some(child); + } + Some(last_child) => { + assert!(last_child.d().next_sibling.is_none()); + last_child.d().next_sibling = Some(child); + child_data.prev_sibling = Some(last_child); + } + } + + self_data.last_child = Some(child); + child_data.parent = Some(self); + } + + /// Removes the given flow from the tree. + /// + /// TODO: Fold me into `util::tree`. + pub fn remove_child(@mut self, child: @mut FlowContext) { + let self_data = self.d(), child_data = child.d(); + + assert!(child_data.parent.is_some()); + assert!(ptr::ref_eq(&*child_data.parent.get(), self)); + + match child_data.prev_sibling { + None => self_data.first_child = child_data.next_sibling, + Some(prev_sibling) => { + prev_sibling.d().next_sibling = child_data.next_sibling; + child_data.prev_sibling = None; + } + } + + match child_data.next_sibling { + None => self_data.last_child = child.d().prev_sibling, + Some(next_sibling) => { + next_sibling.d().prev_sibling = Some(next_sibling); + child_data.next_sibling = None; + } + } + + child_data.parent = None; + } + + pub fn inline(&'self mut self) -> &'self mut InlineFlowData { match self { &InlineFlow(_, ref i) => unsafe { cast::transmute(i) }, _ => fail!(fmt!("Tried to access inline data of non-inline: f%d", self.d().id)) } } - fn block(&'self mut self) -> &'self mut BlockFlowData { + pub fn block(&'self mut self) -> &'self mut BlockFlowData { match self { &BlockFlow(_, ref mut b) => unsafe { cast::transmute(b) }, _ => fail!(fmt!("Tried to access block data of non-block: f%d", self.d().id)) } } - fn root(&'self mut self) -> &'self mut RootFlowData { + pub fn root(&'self mut self) -> &'self mut RootFlowData { match self { &RootFlow(_, ref r) => unsafe { cast::transmute(r) }, _ => fail!(fmt!("Tried to access root data of non-root: f%d", self.d().id)) } } - fn bubble_widths(@mut self, ctx: &mut LayoutContext) { + pub fn bubble_widths(@mut self, ctx: &mut LayoutContext) { match self { @BlockFlow(*) => self.bubble_widths_block(ctx), @InlineFlow(*) => self.bubble_widths_inline(ctx), @@ -141,7 +217,7 @@ pub impl<'self> FlowContext { } } - fn assign_widths(@mut self, ctx: &mut LayoutContext) { + pub fn assign_widths(@mut self, ctx: &mut LayoutContext) { match self { @BlockFlow(*) => self.assign_widths_block(ctx), @InlineFlow(*) => self.assign_widths_inline(ctx), @@ -150,7 +226,7 @@ pub impl<'self> FlowContext { } } - fn assign_height(@mut self, ctx: &mut LayoutContext) { + pub fn assign_height(@mut self, ctx: &mut LayoutContext) { match self { @BlockFlow(*) => self.assign_height_block(ctx), @InlineFlow(*) => self.assign_height_inline(ctx), @@ -159,8 +235,11 @@ pub impl<'self> FlowContext { } } - fn build_display_list_recurse(@mut self, builder: &DisplayListBuilder, dirty: &Rect<Au>, - offset: &Point2D<Au>, list: &Cell<DisplayList>) { + pub fn build_display_list_recurse(@mut self, + builder: &DisplayListBuilder, + dirty: &Rect<Au>, + offset: &Point2D<Au>, + list: &Cell<DisplayList>) { let d = self.d(); // FIXME: borrow checker workaround debug!("FlowContext::build_display_list at %?: %s", d.position, self.debug_str()); @@ -173,9 +252,10 @@ pub impl<'self> FlowContext { } // Actual methods that do not require much flow-specific logic - fn foldl_all_boxes<B: Copy>(&mut self, - seed: B, - cb: &fn(a: B, b: @mut RenderBox) -> B) -> B { + pub fn foldl_all_boxes<B:Copy>(&mut self, + seed: B, + cb: &fn(a: B, b: @mut RenderBox) -> B) + -> B { match self { &RootFlow(*) => { let root = self.root(); // FIXME: borrow checker workaround @@ -193,85 +273,67 @@ pub impl<'self> FlowContext { } } - fn foldl_boxes_for_node<B: Copy>(&mut self, - node: AbstractNode, - seed: B, - cb: &fn(a: B,@mut RenderBox) -> B) - -> B { + pub fn foldl_boxes_for_node<B:Copy>(&mut self, + node: AbstractNode, + seed: B, + cb: &fn(a: B, @mut RenderBox) -> B) + -> B { do self.foldl_all_boxes(seed) |acc, box| { if box.d().node == node { cb(acc, box) } else { acc } } } - fn iter_all_boxes<T>(&mut self, cb: &fn(@mut RenderBox) -> T) { + pub fn iter_all_boxes(&mut self, cb: &fn(@mut RenderBox) -> bool) { match self { &RootFlow(*) => { let root = self.root(); // FIXME: borrow checker workaround - for root.box.each |box| { cb(*box); } + for root.box.each |box| { + if !cb(*box) { + break; + } + } } &BlockFlow(*) => { let block = self.block(); // FIXME: borrow checker workaround - for block.box.each |box| { cb(*box); } + for block.box.each |box| { + if !cb(*box) { + break; + } + } } &InlineFlow(*) => { let inline = self.inline(); // FIXME: borrow checker workaround - for inline.boxes.each |box| { cb(*box); } + for inline.boxes.each |box| { + if !cb(*box) { + break; + } + } } _ => fail!(fmt!("Don't know how to iterate node's RenderBoxes for %?", self)) } } - fn iter_boxes_for_node<T>(&mut self, - node: AbstractNode, - cb: &fn(@mut RenderBox) -> T) { - do self.iter_all_boxes |box| { - if box.d().node == node { cb(box); } + pub fn iter_boxes_for_node(&mut self, node: AbstractNode, cb: &fn(@mut RenderBox) -> bool) { + for self.iter_all_boxes |box| { + if box.d().node == node { + if !cb(box) { + break; + } + } } } } -/* The tree holding FlowContexts */ -pub enum FlowTree { FlowTree } - -impl FlowTree { - fn each_child(&self, ctx: @mut FlowContext, f: &fn(box: @mut FlowContext) -> bool) { - tree::each_child(self, &ctx, |box| f(*box) ) - } -} - -impl tree::ReadMethods<@mut FlowContext> for FlowTree { - fn with_tree_fields<R>(&self, box: &@mut FlowContext, f: &fn(&mut tree::Tree<@mut FlowContext>) -> R) -> R { - let tree = &mut box.d().tree; - f(tree) - } -} - -impl FlowTree { - fn add_child(self, parent: @mut FlowContext, child: @mut FlowContext) { - tree::add_child(&self, parent, child) - } -} - -impl tree::WriteMethods<@mut FlowContext> for FlowTree { - fn tree_eq(&self, a: &@mut FlowContext, b: &@mut FlowContext) -> bool { core::managed::mut_ptr_eq(*a, *b) } - - fn with_tree_fields<R>(&self, box: &@mut FlowContext, f: &fn(&mut tree::Tree<@mut FlowContext>) -> R) -> R { - let tree = &mut box.d().tree; - f(tree) - } -} - - impl BoxedMutDebugMethods for FlowContext { fn dump(@mut self) { - self.dump_indent(0u); + self.dump_indent(0); } - /** Dumps the flow tree, for debugging, with indentation. */ + /// Dumps the flow tree, for debugging, with indentation. fn dump_indent(@mut self, indent: uint) { let mut s = ~"|"; - for uint::range(0u, indent) |_i| { + for uint::range(0, indent) |_i| { s += ~"---- "; } @@ -279,8 +341,8 @@ impl BoxedMutDebugMethods for FlowContext { debug!("%s", s); // FIXME: this should have a pure/const version? - for FlowTree.each_child(self) |child| { - child.dump_indent(indent + 1u) + for self.each_child |child| { + child.dump_indent(indent + 1) } } @@ -313,3 +375,4 @@ impl BoxedMutDebugMethods for FlowContext { fmt!("f%? %?", d.id, repr) } } + diff --git a/src/servo/layout/root.rs b/src/servo/layout/root.rs index 98e1e49565b..af92b037d78 100644 --- a/src/servo/layout/root.rs +++ b/src/servo/layout/root.rs @@ -10,7 +10,7 @@ use gfx::geometry::Au; use layout::block::BlockLayout; use layout::box::RenderBox; use layout::context::LayoutContext; -use layout::flow::{FlowContext, FlowTree, RootFlow}; +use layout::flow::{FlowContext, RootFlow}; use layout::display_list_builder::DisplayListBuilder; pub struct RootFlowData { @@ -63,7 +63,7 @@ impl RootLayout for FlowContext { // the root adjusts self height to at least cover the viewport. let mut cur_y = Au(0); - for FlowTree.each_child(self) |child_ctx| { + for self.each_child |child_ctx| { child_ctx.d().position.origin.y = cur_y; cur_y += child_ctx.d().position.size.height; } diff --git a/src/servo/layout/traverse.rs b/src/servo/layout/traverse.rs index 5d8e323ae17..b4710b974bc 100644 --- a/src/servo/layout/traverse.rs +++ b/src/servo/layout/traverse.rs @@ -2,9 +2,9 @@ * 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 layout::flow::{FlowContext, FlowTree}; +use layout::flow::FlowContext; -/** Trait for running tree-based traversals over layout contexts */ +/// A trait for running tree-based traversals over flows contexts. pub trait FlowContextTraversals { fn traverse_preorder(@mut self, preorder_cb: &fn(@mut FlowContext)); fn traverse_postorder(@mut self, postorder_cb: &fn(@mut FlowContext)); @@ -13,11 +13,15 @@ pub trait FlowContextTraversals { impl FlowContextTraversals for FlowContext { fn traverse_preorder(@mut self, preorder_cb: &fn(@mut FlowContext)) { preorder_cb(self); - do FlowTree.each_child(self) |child| { child.traverse_preorder(preorder_cb); true } + for self.each_child |child| { + child.traverse_preorder(preorder_cb); + } } fn traverse_postorder(@mut self, postorder_cb: &fn(@mut FlowContext)) { - do FlowTree.each_child(self) |child| { child.traverse_postorder(postorder_cb); true } + for self.each_child |child| { + child.traverse_postorder(postorder_cb); + } postorder_cb(self); } } |